diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts index 4bb0be9..283dd03 100644 --- a/androidApp/build.gradle.kts +++ b/androidApp/build.gradle.kts @@ -1,3 +1,4 @@ + plugins { alias(libs.plugins.androidApplication) alias(libs.plugins.kotlinAndroid) @@ -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" @@ -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 } } diff --git a/androidApp/src/main/AndroidManifest.xml b/androidApp/src/main/AndroidManifest.xml index c15e0a0..bb75a4c 100644 --- a/androidApp/src/main/AndroidManifest.xml +++ b/androidApp/src/main/AndroidManifest.xml @@ -2,6 +2,7 @@ diff --git a/androidApp/src/main/java/ru/otus/otuskmp/android/MainActivity.kt b/androidApp/src/main/java/ru/otus/otuskmp/android/MainActivity.kt index 5193a6e..3357e6c 100644 --- a/androidApp/src/main/java/ru/otus/otuskmp/android/MainActivity.kt +++ b/androidApp/src/main/java/ru/otus/otuskmp/android/MainActivity.kt @@ -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 { @@ -32,7 +32,7 @@ class MainActivity : ComponentActivity() { modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { - StopWatchContent() + StopWatchContent(viewModel = viewModel) } } } @@ -40,9 +40,8 @@ class MainActivity : ComponentActivity() { } @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) { @@ -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}") } } diff --git a/androidApp/src/main/java/ru/otus/otuskmp/android/MyApp.kt b/androidApp/src/main/java/ru/otus/otuskmp/android/MyApp.kt new file mode 100644 index 0000000..24e227c --- /dev/null +++ b/androidApp/src/main/java/ru/otus/otuskmp/android/MyApp.kt @@ -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() + } + } +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index d24ef08..749c033 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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() + } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bcfe86d..7084380 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" } @@ -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" } diff --git a/iosApp/iosApp/ContentView.swift b/iosApp/iosApp/ContentView.swift index 59576cc..8e0eaa0 100644 --- a/iosApp/iosApp/ContentView.swift +++ b/iosApp/iosApp/ContentView.swift @@ -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() @@ -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() } } diff --git a/iosApp/iosApp/iOSApp.swift b/iosApp/iosApp/iOSApp.swift index 0648e86..cbc8c80 100644 --- a/iosApp/iosApp/iOSApp.swift +++ b/iosApp/iosApp/iOSApp.swift @@ -1,10 +1,15 @@ import SwiftUI +import shared @main struct iOSApp: App { + init(){ + KoinInitIOSKt.doInitKoinIos() + } + var body: some Scene { WindowGroup { ContentView() } } -} \ No newline at end of file +} diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index 9db4634..6f1d0d9 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -1,8 +1,12 @@ 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 { @@ -10,12 +14,17 @@ kotlin { compilations.all { compileTaskProvider.configure { compilerOptions { - jvmTarget.set(JvmTarget.JVM_1_8) + jvmTarget.set(JvmTarget.JVM_21) } } } } + js(IR) { + browser() + binaries.executable() + } + listOf( iosX64(), iosArm64(), @@ -28,24 +37,43 @@ kotlin { } } + tasks.withType().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 } } diff --git a/shared/src/androidMain/kotlin/com/example/otuskmp/KMPClipboardManager.android.kt b/shared/src/androidMain/kotlin/com/example/otuskmp/KMPClipboardManager.android.kt new file mode 100644 index 0000000..5e4d8a2 --- /dev/null +++ b/shared/src/androidMain/kotlin/com/example/otuskmp/KMPClipboardManager.android.kt @@ -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 + } + } +} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/com/example/otuskmp/di/PlatformModule.android.kt b/shared/src/androidMain/kotlin/com/example/otuskmp/di/PlatformModule.android.kt new file mode 100644 index 0000000..f00da4d --- /dev/null +++ b/shared/src/androidMain/kotlin/com/example/otuskmp/di/PlatformModule.android.kt @@ -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 { AndroidClipboardManager(get()) } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/example/otuskmp/ClipboardManager.kt b/shared/src/commonMain/kotlin/com/example/otuskmp/ClipboardManager.kt new file mode 100644 index 0000000..aea28b6 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/example/otuskmp/ClipboardManager.kt @@ -0,0 +1,7 @@ +package com.example.otuskmp + +interface KMPClipboardManager{ + fun copyToClipBoard(text: String) + + suspend fun getFromClipBoard(): String? +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/example/otuskmp/StopwatchViewModel.kt b/shared/src/commonMain/kotlin/com/example/otuskmp/StopwatchViewModel.kt index 9c3f112..e8dd39e 100644 --- a/shared/src/commonMain/kotlin/com/example/otuskmp/StopwatchViewModel.kt +++ b/shared/src/commonMain/kotlin/com/example/otuskmp/StopwatchViewModel.kt @@ -12,8 +12,11 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch -class StopwatchViewModel { +private const val BUFFER_IS_EMPTY = "buffer is empty" + +class StopwatchViewModel(private val clipboardManager: KMPClipboardManager) { private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) private var job: Job? = null @@ -25,6 +28,15 @@ class StopwatchViewModel { ) val uiState: AnyStateFlow = _uiState.wrapToAny() + fun onCopyToClipBoard(text: String) { + coroutineScope.launch { + clipboardManager.copyToClipBoard(text) + _uiState.update { + it.copy(clipboardText = clipboardManager.getFromClipBoard() ?: BUFFER_IS_EMPTY) + } + } + } + fun onStartClicked() { startTimer() } @@ -68,7 +80,8 @@ class StopwatchViewModel { data class StopwatchUiState( val currentTimeMillis: Long, - val initialTimeMillis: Long + val initialTimeMillis: Long, + val clipboardText: String = BUFFER_IS_EMPTY ) { private val formatter = DecimalFormat("0.00") val formattedTime: String = diff --git a/shared/src/commonMain/kotlin/com/example/otuskmp/di/KoinModules.kt b/shared/src/commonMain/kotlin/com/example/otuskmp/di/KoinModules.kt new file mode 100644 index 0000000..0c09b55 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/example/otuskmp/di/KoinModules.kt @@ -0,0 +1,20 @@ +package com.example.otuskmp.di + +import com.example.otuskmp.StopwatchViewModel +import org.koin.core.KoinApplication +import org.koin.core.context.startKoin +import org.koin.core.module.Module +import org.koin.dsl.KoinAppDeclaration +import org.koin.dsl.includes +import org.koin.dsl.module + +val sharedModule = module { + factory {StopwatchViewModel(get())} +} + +expect val platformModule: Module + +fun initKoin(config: KoinAppDeclaration? = null): KoinApplication = startKoin { + includes(config) + modules(platformModule, sharedModule) +} \ No newline at end of file diff --git a/shared/src/iosMain/kotlin/com/example/otuskmp/KMPClipboardManager.ios.kt b/shared/src/iosMain/kotlin/com/example/otuskmp/KMPClipboardManager.ios.kt new file mode 100644 index 0000000..29c4604 --- /dev/null +++ b/shared/src/iosMain/kotlin/com/example/otuskmp/KMPClipboardManager.ios.kt @@ -0,0 +1,12 @@ +package com.example.otuskmp + +import platform.UIKit.UIPasteboard + +class IOSClipboardManager: KMPClipboardManager{ + override fun copyToClipBoard(text: String) { + UIPasteboard.generalPasteboard.string = text + } + + override suspend fun getFromClipBoard(): String? = + UIPasteboard.generalPasteboard.string +} \ No newline at end of file diff --git a/shared/src/iosMain/kotlin/com/example/otuskmp/di/KoinInitIOS.kt b/shared/src/iosMain/kotlin/com/example/otuskmp/di/KoinInitIOS.kt new file mode 100644 index 0000000..b62a725 --- /dev/null +++ b/shared/src/iosMain/kotlin/com/example/otuskmp/di/KoinInitIOS.kt @@ -0,0 +1,5 @@ +package com.example.otuskmp.di + +fun initKoinIos(){ + initKoin() +} \ No newline at end of file diff --git a/shared/src/iosMain/kotlin/com/example/otuskmp/di/KoinIosDependencies.kt b/shared/src/iosMain/kotlin/com/example/otuskmp/di/KoinIosDependencies.kt new file mode 100644 index 0000000..f506f04 --- /dev/null +++ b/shared/src/iosMain/kotlin/com/example/otuskmp/di/KoinIosDependencies.kt @@ -0,0 +1,10 @@ +package com.example.otuskmp.di + +import com.example.otuskmp.StopwatchViewModel +import org.koin.core.component.KoinComponent +import org.koin.core.component.get + +class KoinIosDependencies: KoinComponent { + + fun getViewModel(): StopwatchViewModel = get() +} diff --git a/shared/src/iosMain/kotlin/com/example/otuskmp/di/PlatformModule.ios.kt b/shared/src/iosMain/kotlin/com/example/otuskmp/di/PlatformModule.ios.kt new file mode 100644 index 0000000..bc83e9d --- /dev/null +++ b/shared/src/iosMain/kotlin/com/example/otuskmp/di/PlatformModule.ios.kt @@ -0,0 +1,9 @@ +package com.example.otuskmp.di + +import com.example.otuskmp.IOSClipboardManager +import com.example.otuskmp.KMPClipboardManager +import org.koin.dsl.module + +actual val platformModule = module { + single { IOSClipboardManager() } +} \ No newline at end of file diff --git a/shared/src/jsMain/kotlin/com/example/otuskmp/CurrentTimeMillis.js.kt b/shared/src/jsMain/kotlin/com/example/otuskmp/CurrentTimeMillis.js.kt new file mode 100644 index 0000000..88af30e --- /dev/null +++ b/shared/src/jsMain/kotlin/com/example/otuskmp/CurrentTimeMillis.js.kt @@ -0,0 +1,7 @@ +package com.example.otuskmp + +import kotlin.js.Date + +actual fun currentTimeMillis(): Long { + return Date.now().toLong() +} \ No newline at end of file diff --git a/shared/src/jsMain/kotlin/com/example/otuskmp/DecimalFormat.js.kt b/shared/src/jsMain/kotlin/com/example/otuskmp/DecimalFormat.js.kt new file mode 100644 index 0000000..56631db --- /dev/null +++ b/shared/src/jsMain/kotlin/com/example/otuskmp/DecimalFormat.js.kt @@ -0,0 +1,7 @@ +package com.example.otuskmp + +actual class DecimalFormat actual constructor(pattern: String) { + actual fun format(value: Double): String { + return value.asDynamic().toFixed(2) + } +} \ No newline at end of file diff --git a/shared/src/jsMain/kotlin/com/example/otuskmp/KMPClipboardManager.js.kt b/shared/src/jsMain/kotlin/com/example/otuskmp/KMPClipboardManager.js.kt new file mode 100644 index 0000000..b013f18 --- /dev/null +++ b/shared/src/jsMain/kotlin/com/example/otuskmp/KMPClipboardManager.js.kt @@ -0,0 +1,19 @@ +package com.example.otuskmp + +import kotlinx.browser.window +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.await + +class JSClipboardManager : KMPClipboardManager { + override fun copyToClipBoard(text: String) { + window.navigator.clipboard.writeText(text) + } + + override suspend fun getFromClipBoard(): String? { + return try { + window.navigator.clipboard.readText().await() + } catch (e: CancellationException) { + null + } + } +} \ No newline at end of file diff --git a/shared/src/jsMain/kotlin/com/example/otuskmp/Main.kt b/shared/src/jsMain/kotlin/com/example/otuskmp/Main.kt new file mode 100644 index 0000000..289a70c --- /dev/null +++ b/shared/src/jsMain/kotlin/com/example/otuskmp/Main.kt @@ -0,0 +1,86 @@ +package com.example.otuskmp + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import com.example.otuskmp.di.initKoin +import org.jetbrains.compose.web.css.AlignItems +import org.jetbrains.compose.web.css.CSSNumeric +import org.jetbrains.compose.web.css.DisplayStyle +import org.jetbrains.compose.web.css.FlexDirection +import org.jetbrains.compose.web.css.alignItems +import org.jetbrains.compose.web.css.display +import org.jetbrains.compose.web.css.dppx +import org.jetbrains.compose.web.css.flexDirection +import org.jetbrains.compose.web.css.padding +import org.jetbrains.compose.web.dom.Button +import org.jetbrains.compose.web.dom.Div +import org.jetbrains.compose.web.dom.Text +import org.jetbrains.compose.web.renderComposable +import org.koin.compose.koinInject + +fun main() { + initKoin() + + renderComposable(rootElementId = "root") { + val viewModel: StopwatchViewModel = koinInject() + + val state = viewModel.uiState.collectAsState() + + Column(gap = 20.dppx, ) { + Text(value = state.value.formattedTime) + Row(gap = 5.dppx) { + Button(attrs = { + onClick { + viewModel.onStartClicked() + } + }) { + Text(value = "start") + } + Button(attrs = { + onClick { + viewModel.onStopClicked() + } + }) { + Text(value = "stop") + } + } + Button(attrs = { + onClick { + viewModel.onCopyToClipBoard(state.value.formattedTime) + } + }) { + Text(value = "Copy to clipboard") + } + Text(value = state.value.clipboardText) + } + } +} + +@Composable +fun Column(gap: CSSNumeric = 0.dppx, content: @Composable () -> Unit) { + Div(attrs = { + style { + padding(gap) + display(DisplayStyle.Flex) + alignItems(AlignItems.Center) + flexDirection(FlexDirection.Column) + } + }) { + content() + } +} + +@Composable +fun Row(gap: CSSNumeric = 0.dppx, content: @Composable () -> Unit) { + Div(attrs = { + style { + padding(gap) + display(DisplayStyle.Flex) + flexDirection(FlexDirection.Row) + alignItems(AlignItems.Center) + } + }) { + content() + } +} + diff --git a/shared/src/jsMain/kotlin/com/example/otuskmp/di/PlatformModule.js.kt b/shared/src/jsMain/kotlin/com/example/otuskmp/di/PlatformModule.js.kt new file mode 100644 index 0000000..e4386bb --- /dev/null +++ b/shared/src/jsMain/kotlin/com/example/otuskmp/di/PlatformModule.js.kt @@ -0,0 +1,9 @@ +package com.example.otuskmp.di + +import com.example.otuskmp.JSClipboardManager +import com.example.otuskmp.KMPClipboardManager +import org.koin.dsl.module + +actual val platformModule = module { + single { JSClipboardManager() } +} \ No newline at end of file diff --git a/shared/src/jsMain/resources/index.html b/shared/src/jsMain/resources/index.html new file mode 100644 index 0000000..a1da81d --- /dev/null +++ b/shared/src/jsMain/resources/index.html @@ -0,0 +1,11 @@ + + + + + Sample + + +
+ + + \ No newline at end of file