Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions androidApp/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

plugins {
alias(libs.plugins.androidApplication)
alias(libs.plugins.kotlinAndroid)
Expand All @@ -6,10 +7,11 @@ plugins {

android {
namespace = "ru.otus.otuskmp.android"
compileSdk = 35
compileSdk = 36
defaultConfig {
applicationId = "ru.otus.otuskmp.android"
minSdk = 24
//noinspection OldTargetApi
targetSdk = 35
versionCode = 1
versionName = "1.0"
Expand All @@ -28,11 +30,8 @@ android {
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
}

Expand Down
1 change: 1 addition & 0 deletions androidApp/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application
android:name=".MyApp"
android:allowBackup="false"
android:supportsRtl="true"
android:theme="@style/AppTheme">
Expand Down
22 changes: 10 additions & 12 deletions androidApp/src/main/java/ru/otus/otuskmp/android/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.otuskmp.StopwatchViewModel
import org.koin.android.ext.android.inject

class MainActivity : ComponentActivity() {
private val viewModel: StopwatchViewModel by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Expand All @@ -32,17 +32,16 @@ class MainActivity : ComponentActivity() {
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
StopWatchContent()
StopWatchContent(viewModel = viewModel)
}
}
}
}
}

@Composable
fun StopWatchContent(modifier: Modifier = Modifier) {
fun StopWatchContent(modifier: Modifier = Modifier, viewModel: StopwatchViewModel) {
// fixme: Use jetpack ViewModel for handling configuration changes
val viewModel = remember { StopwatchViewModel() }
val uiState by viewModel.uiState.collectAsState()

DisposableEffect(Unit) {
Expand Down Expand Up @@ -72,13 +71,12 @@ fun StopWatchContent(modifier: Modifier = Modifier) {
Text("Stop")
}
}
}
}
TextButton(
modifier = Modifier.padding(top = 16.dp),
onClick = { viewModel.onCopyToClipBoard(uiState.formattedTime) }) {
Text("Copy to clipboard")
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
MyApplicationTheme {
StopWatchContent()
Text(text = "Text from buffer:${uiState.clipboardText}")
}
}
17 changes: 17 additions & 0 deletions androidApp/src/main/java/ru/otus/otuskmp/android/MyApp.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package ru.otus.otuskmp.android

import android.app.Application
import com.example.otuskmp.di.initKoin
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger

class MyApp : Application() {
override fun onCreate() {
super.onCreate()

initKoin {
androidContext(this@MyApp)
androidLogger()
}
}
}
9 changes: 9 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,13 @@ plugins {
alias(libs.plugins.kotlinAndroid).apply(false)
alias(libs.plugins.kotlinMultiplatform).apply(false)
alias(libs.plugins.compose.compiler).apply(false)
alias (libs.plugins.jetbrains.compose).apply(false)
alias(libs.plugins.koin.compiler).apply(false)
}

allprojects {
repositories {
google()
mavenCentral()
}
}
34 changes: 26 additions & 8 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,19 +1,35 @@
[versions]
agp = "8.9.2"
kotlin = "2.1.21"
compose = "1.8.1"
compose-material3 = "1.3.2"
compose-kmp = "1.7.3"
koin-bom = "4.2.0"
koin = "1.0.1"

# Версии для Android (Google Compose)
androidx-activityCompose = "1.10.1"
compose-android = "1.7.8"
compose-material3-android = "1.3.1"

[libraries]
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.10.2"}
kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.10.2" }

#android libs
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose" }
compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" }
compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" }
compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose" }
compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "compose-material3" }
compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose-android" }
compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose-android" }
compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose-android" }
compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose-android" }
compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "compose-material3-android" }
koin-android = { module = "io.insert-koin:koin-android" }

#koin
koin-bom = { module = "io.insert-koin:koin-bom", version.ref = "koin-bom" }
koin-core = { module = "io.insert-koin:koin-core" }
koin-test = { module = "io.insert-koin:koin-test" }
koin-compose = { module = "io.insert-koin:koin-compose" }



[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
Expand All @@ -22,3 +38,5 @@ kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotlinCocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", version.ref = "kotlin" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
jetbrains-compose = { id = "org.jetbrains.compose", version.ref = "compose-kmp" }
koin-compiler = { id = "io.insert-koin.compiler.plugin", version.ref = "koin" }
16 changes: 11 additions & 5 deletions iosApp/iosApp/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,17 @@ struct ContentView: View {
Text("Stop")
})
}
Button(action: {viewModel.onCopyToClipboardClicked(text: viewModel.uiState.formattedTime)}, label: {Text("Copy to clipboard")} )
Text("Text from buffer: \(viewModel.uiState.clipboardText)")
}
}
}

class StopwatchViewModelWrapper: ObservableObject {
private let viewModel = StopwatchViewModel()
private lazy var viewModel = KoinIosDependencies().getViewModel()
private var observer: Cancellable?

@Published var uiState: StopwatchUiState = StopwatchUiState(currentTimeMillis: 0, initialTimeMillis: 0)
@Published var uiState: StopwatchUiState = StopwatchUiState(currentTimeMillis: 0, initialTimeMillis: 0, clipboardText: "buffer is empty")

func onStartClicked() {
viewModel.onStartClicked()
Expand All @@ -35,15 +37,19 @@ class StopwatchViewModelWrapper: ObservableObject {
viewModel.onStopClicked()
}

func onCopyToClipboardClicked(text: String){
viewModel.onCopyToClipBoard(text: text)
}

init() {
observer = viewModel.uiState.collect {
observer = viewModel.uiState.collect {
[weak self] value in self?.uiState = value
}
}

deinit {
observer?.cancel()
viewModel.onDestroy()
observer?.cancel()
viewModel.onDestroy()
}
}

Expand Down
7 changes: 6 additions & 1 deletion iosApp/iosApp/iOSApp.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import SwiftUI
import shared

@main
struct iOSApp: App {
init(){
KoinInitIOSKt.doInitKoinIos()
}

var body: some Scene {
WindowGroup {
ContentView()
}
}
}
}
36 changes: 32 additions & 4 deletions shared/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinJsCompile

plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidLibrary)
alias(libs.plugins.jetbrains.compose)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.koin.compiler)
}

kotlin {
androidTarget {
compilations.all {
compileTaskProvider.configure {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_1_8)
jvmTarget.set(JvmTarget.JVM_21)
}
}
}
}

js(IR) {
browser()
binaries.executable()
}

listOf(
iosX64(),
iosArm64(),
Expand All @@ -28,24 +37,43 @@ kotlin {
}
}

tasks.withType<KotlinJsCompile>().configureEach {
compilerOptions {
target = "es2015"
}
}

sourceSets {
jsMain.dependencies {
implementation(compose.html.core)
implementation(compose.runtime)
}

commonMain.dependencies {
api(libs.kotlinx.coroutines)
api(project.dependencies.platform(libs.koin.bom))
api(libs.koin.core)
api(libs.koin.compose)
}
commonTest.dependencies {
implementation(libs.kotlin.test)
implementation(libs.koin.test)
}

androidMain.dependencies {
api(libs.koin.android)
}
}
}

android {
namespace = "com.example.otuskmp"
compileSdk = 35
compileSdk = 36
defaultConfig {
minSdk = 24
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.example.otuskmp

import android.content.ClipData
import android.content.ClipDescription
import android.content.ClipboardManager
import android.content.Context

private const val LABEL = "CLIPBOARD_TEXT"

class AndroidClipboardManager(private val context: Context) : KMPClipboardManager {
private val clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager

override fun copyToClipBoard(text: String) {
clipboardManager.setPrimaryClip(ClipData.newPlainText(LABEL, text))
}

override suspend fun getFromClipBoard(): String? {
val description = clipboardManager.primaryClipDescription
return if (description!=null && description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)){
clipboardManager.primaryClip?.getItemAt(0)?.text.toString()
} else {
null
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.otuskmp.di

import com.example.otuskmp.AndroidClipboardManager
import com.example.otuskmp.KMPClipboardManager
import org.koin.dsl.module

actual val platformModule = module {
single<KMPClipboardManager> { AndroidClipboardManager(get()) }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.example.otuskmp

interface KMPClipboardManager{
fun copyToClipBoard(text: String)

suspend fun getFromClipBoard(): String?
}
Loading