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
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.otuskmp.StopwatchViewModel
import com.example.otuskmp.initClipboard

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initClipboard(this)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лучше это инициализировать либо в Application классе, либо в инициализаторах из androidx-startup

setContent {
MyApplicationTheme {
Surface(
Expand Down Expand Up @@ -71,6 +73,11 @@ fun StopWatchContent(modifier: Modifier = Modifier) {
) {
Text("Stop")
}
TextButton(
onClick = viewModel::onCopyClicked
) {
Text("Copy")
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ plugins {
alias(libs.plugins.kotlinAndroid).apply(false)
alias(libs.plugins.kotlinMultiplatform).apply(false)
alias(libs.plugins.compose.compiler).apply(false)
alias(libs.plugins.composeMultiplatform).apply(false)
}
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
agp = "8.9.2"
kotlin = "2.1.21"
compose = "1.8.1"
compose-plugin = "1.8.1"
compose-material3 = "1.3.2"
androidx-activityCompose = "1.10.1"

Expand All @@ -22,3 +23,4 @@ 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" }
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-plugin" }
7 changes: 7 additions & 0 deletions iosApp/iosApp/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ struct ContentView: View {
Button(action: viewModel.onStopClicked, label: {
Text("Stop")
})
Button(action: viewModel.onCopyClicked, label: {
Text("Copy")
})
}
}
}
Expand All @@ -34,6 +37,10 @@ class StopwatchViewModelWrapper: ObservableObject {
func onStopClicked() {
viewModel.onStopClicked()
}

func onCopyClicked() {
viewModel.onCopyClicked()
}

init() {
observer = viewModel.uiState.collect {
Expand Down
2,880 changes: 2,880 additions & 0 deletions kotlin-js-store/yarn.lock

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidLibrary)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.compose.compiler)
}

kotlin {
Expand All @@ -28,13 +30,25 @@ kotlin {
}
}

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

sourceSets {
commonMain.dependencies {
api(libs.kotlinx.coroutines)
implementation(compose.runtime)
}
commonTest.dependencies {
implementation(libs.kotlin.test)
}
val jsMain by getting {
dependencies {
implementation(compose.html.core)
implementation(compose.runtime)
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.example.otuskmp

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

internal lateinit var appContext: Context

fun initClipboard(context: Context) {
appContext = context.applicationContext
}

actual fun copyToClipboard(text: String) {
val clipboard = appContext.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setPrimaryClip(ClipData.newPlainText("stopwatch", text))
}
3 changes: 3 additions & 0 deletions shared/src/commonMain/kotlin/com/example/otuskmp/Clipboard.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.example.otuskmp

expect fun copyToClipboard(text: String)
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ class StopwatchViewModel {
job?.cancel()
}

fun onCopyClicked() {
copyToClipboard(_uiState.value.formattedTime)
}

fun onDestroy() {
coroutineScope.cancel()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.example.otuskmp

import platform.UIKit.UIPasteboard

actual fun copyToClipboard(text: String) {
UIPasteboard.generalPasteboard.string = text
}
76 changes: 76 additions & 0 deletions shared/src/jsMain/kotlin/Main.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import com.example.otuskmp.StopwatchViewModel
import kotlinx.coroutines.flow.StateFlow
import org.jetbrains.compose.web.css.*
import org.jetbrains.compose.web.dom.*
import org.jetbrains.compose.web.renderComposable

@Composable
fun <T> StateFlow<T>.collectAsState(): State<T> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Эта функция уже есть в androidx.compose.runtime.collectAsState

val state = remember(this) { mutableStateOf(this.value) }
LaunchedEffect(this) {
this@collectAsState.collect { state.value = it }
}
return state
}

fun main() {
renderComposable(rootElementId = "root") {
val viewModel = remember { StopwatchViewModel() }
val uiState by viewModel.uiState.collectAsState()

DisposableEffect(Unit) {
onDispose { viewModel.onDestroy() }
}

Div({
style {
display(DisplayStyle.Flex)
flexDirection(FlexDirection.Column)
alignItems(AlignItems.Center)
justifyContent(JustifyContent.Center)
height(100.vh)
fontFamily("sans-serif")
}
}) {
Div({
style {
fontSize(32.px)
marginBottom(16.px)
}
}) {
Text(uiState.formattedTime)
}

Div({
style {
display(DisplayStyle.Flex)
flexDirection(FlexDirection.Row)
gap(8.px)
}
}) {
Button(attrs = {
onClick { viewModel.onStartClicked() }
}) {
Text("Start")
}
Button(attrs = {
onClick { viewModel.onStopClicked() }
}) {
Text("Stop")
}
Button(attrs = {
onClick { viewModel.onCopyClicked() }
}) {
Text("Copy")
}
}
}
}
}
7 changes: 7 additions & 0 deletions shared/src/jsMain/kotlin/com/example/otuskmp/Clipboard.js.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.example.otuskmp

import kotlinx.browser.window

actual fun copyToClipboard(text: String) {
window.navigator.clipboard.writeText(text)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.example.otuskmp

import kotlin.js.Date

actual fun currentTimeMillis(): Long = Date().getTime().toLong()
Original file line number Diff line number Diff line change
@@ -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) as String
}
}
11 changes: 11 additions & 0 deletions shared/src/jsMain/resources/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Stopwatch</title>
</head>
<body>
<div id="root"></div>
<script src="shared.js"></script>
</body>
</html>