From 0f1790081f25eff27cbde52eb0c0e00f291d7a19 Mon Sep 17 00:00:00 2001 From: stslex Date: Thu, 23 Nov 2023 21:36:38 +0300 Subject: [PATCH] initial setup --- composeApp/build.gradle.kts | 2 + composeApp/src/commonMain/kotlin/App.kt | 75 +--------------- composeApp/src/commonMain/kotlin/AppTheme.kt | 21 +++++ .../src/commonMain/kotlin/InitialApp.kt | 61 +++++++++++++ .../commonMain/kotlin/core/AppDispatcher.kt | 18 ++++ .../src/commonMain/kotlin/core/CommonUtils.kt | 7 ++ .../src/commonMain/kotlin/core/Logger.kt | 34 +++++++ .../kotlin/main_screen/MainScreen.kt | 24 +++++ .../src/commonMain/kotlin/store/BaseStore.kt | 89 +++++++++++++++++++ .../src/commonMain/kotlin/store/Router.kt | 5 ++ .../src/commonMain/kotlin/store/Store.kt | 12 +++ gradle/libs.versions.toml | 2 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 ++ settings.gradle.kts | 4 +- 14 files changed, 287 insertions(+), 75 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/AppTheme.kt create mode 100644 composeApp/src/commonMain/kotlin/InitialApp.kt create mode 100644 composeApp/src/commonMain/kotlin/core/AppDispatcher.kt create mode 100644 composeApp/src/commonMain/kotlin/core/CommonUtils.kt create mode 100644 composeApp/src/commonMain/kotlin/core/Logger.kt create mode 100644 composeApp/src/commonMain/kotlin/main_screen/MainScreen.kt create mode 100644 composeApp/src/commonMain/kotlin/store/BaseStore.kt create mode 100644 composeApp/src/commonMain/kotlin/store/Router.kt create mode 100644 composeApp/src/commonMain/kotlin/store/Store.kt create mode 100644 iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 8c9528e0..3d6e766d 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -41,6 +41,8 @@ kotlin { implementation(compose.desktop.currentOs) } commonMain.dependencies { + implementation(libs.kermit) + implementation(projects.shared) implementation(compose.runtime) implementation(compose.foundation) diff --git a/composeApp/src/commonMain/kotlin/App.kt b/composeApp/src/commonMain/kotlin/App.kt index 7bca07a8..73babd79 100644 --- a/composeApp/src/commonMain/kotlin/App.kt +++ b/composeApp/src/commonMain/kotlin/App.kt @@ -1,82 +1,9 @@ -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material.Button -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.material.darkColors -import androidx.compose.material.lightColors import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier import org.jetbrains.compose.resources.ExperimentalResourceApi -import org.jetbrains.compose.resources.painterResource -@OptIn(ExperimentalResourceApi::class) @Composable fun App() { AppTheme { - Box( - modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.colors.background) - ) { - val defaultGreetState = "Hello World!" - val clickedGreetState = "Compose: ${Greeting().greet()}" - var isClicked by remember { mutableStateOf(false) } - val greetingText by remember { - derivedStateOf { - if (isClicked) { - clickedGreetState - } else { - defaultGreetState - } - } - } - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Button( - onClick = { - isClicked = !isClicked - } - ) { - Text(greetingText) - } - AnimatedVisibility(isClicked) { - Image( - painterResource("compose-multiplatform.xml"), - null - ) - } - } - } + InitialApp() } } - -@Composable -fun AppTheme( - isDarkTheme: Boolean = isSystemInDarkTheme(), - content: @Composable () -> Unit, -) { - val colors = if (isDarkTheme) { - darkColors() - } else { - lightColors() - } - MaterialTheme( - colors = colors, - content = content - ) -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/AppTheme.kt b/composeApp/src/commonMain/kotlin/AppTheme.kt new file mode 100644 index 00000000..89e9d566 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/AppTheme.kt @@ -0,0 +1,21 @@ +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material.MaterialTheme +import androidx.compose.material.darkColors +import androidx.compose.material.lightColors +import androidx.compose.runtime.Composable + +@Composable +fun AppTheme( + isDarkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit, +) { + val colors = if (isDarkTheme) { + darkColors() + } else { + lightColors() + } + MaterialTheme( + colors = colors, + content = content + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/InitialApp.kt b/composeApp/src/commonMain/kotlin/InitialApp.kt new file mode 100644 index 00000000..6a076584 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/InitialApp.kt @@ -0,0 +1,61 @@ +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.Button +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.painterResource + +@OptIn(ExperimentalResourceApi::class) +@Composable +fun InitialApp() { + Box( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colors.background) + ) { + val defaultGreetState = "Hello World!" + val clickedGreetState = "Compose: ${Greeting().greet()}" + var isClicked by remember { mutableStateOf(false) } + val greetingText by remember { + derivedStateOf { + if (isClicked) { + clickedGreetState + } else { + defaultGreetState + } + } + } + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Button( + onClick = { + isClicked = !isClicked + } + ) { + Text(greetingText) + } + AnimatedVisibility(isClicked) { + Image( + painterResource("compose-multiplatform.xml"), + null + ) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/core/AppDispatcher.kt b/composeApp/src/commonMain/kotlin/core/AppDispatcher.kt new file mode 100644 index 00000000..973b9ddd --- /dev/null +++ b/composeApp/src/commonMain/kotlin/core/AppDispatcher.kt @@ -0,0 +1,18 @@ +package core + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO +import kotlinx.coroutines.MainCoroutineDispatcher + +interface AppDispatcher { + val io: CoroutineDispatcher + val main: MainCoroutineDispatcher + val default: CoroutineDispatcher +} + +class AppDispatcherImpl : AppDispatcher { + override val io: CoroutineDispatcher = Dispatchers.IO + override val main: MainCoroutineDispatcher = Dispatchers.Main + override val default: CoroutineDispatcher = Dispatchers.Default +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/core/CommonUtils.kt b/composeApp/src/commonMain/kotlin/core/CommonUtils.kt new file mode 100644 index 00000000..056973a2 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/core/CommonUtils.kt @@ -0,0 +1,7 @@ +package core + +import kotlinx.coroutines.CoroutineExceptionHandler + +val coroutineExceptionHandler = CoroutineExceptionHandler { _, throwable -> + Logger.exception(throwable) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/core/Logger.kt b/composeApp/src/commonMain/kotlin/core/Logger.kt new file mode 100644 index 00000000..5e04aca9 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/core/Logger.kt @@ -0,0 +1,34 @@ +package core + +import co.touchlab.kermit.Logger as Log + +object Logger { + + private const val DEFAULT_TAG = "WIZARD" + + fun exception( + throwable: Throwable, + tag: String? = null, + message: String? = null + ) { + // TODO check build config if (BuildConfig.DEBUG.not()) return + val currentTag = "$DEFAULT_TAG:${tag.orEmpty()}" + Log.e( + tag = currentTag, + throwable = throwable, + messageString = message ?: throwable.message.orEmpty(), + ) + } + + fun debug( + message: String, + tag: String? = null, + ) { + // TODO check build config if (BuildConfig.DEBUG.not()) return + val currentTag = "$DEFAULT_TAG:${tag.orEmpty()}" + Log.d( + tag = currentTag, + messageString = message + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/main_screen/MainScreen.kt b/composeApp/src/commonMain/kotlin/main_screen/MainScreen.kt new file mode 100644 index 00000000..e6443bfd --- /dev/null +++ b/composeApp/src/commonMain/kotlin/main_screen/MainScreen.kt @@ -0,0 +1,24 @@ +package main_screen + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier + +@Composable +fun MainScreen( + modifier: Modifier = Modifier +) { + Box( + modifier = modifier + .fillMaxSize() + .background(MaterialTheme.colors.background), + contentAlignment = Alignment.Center + ) { + Text(text = "Main Screen") + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/store/BaseStore.kt b/composeApp/src/commonMain/kotlin/store/BaseStore.kt new file mode 100644 index 00000000..c1892728 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/store/BaseStore.kt @@ -0,0 +1,89 @@ +package store + +import core.AppDispatcher +import core.Logger +import core.coroutineExceptionHandler +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import st.slex.csplashscreen.core.ui.mvi.Router +import st.slex.csplashscreen.core.ui.mvi.Store.Action +import st.slex.csplashscreen.core.ui.mvi.Store.Event +import st.slex.csplashscreen.core.ui.mvi.Store.Navigation +import st.slex.csplashscreen.core.ui.mvi.Store.State + +abstract class BaseStore( + private val router: Router, + private val appDispatcher: AppDispatcher, + initialState: S +) { + + abstract fun sendAction(action: A) + + private val _state: MutableStateFlow = MutableStateFlow(initialState) + val state: StateFlow = _state.asStateFlow() + + private val scope: CoroutineScope = CoroutineScope(appDispatcher.default) + + private fun exceptionHandler( + onError: suspend (cause: Throwable) -> Unit = {}, + ) = CoroutineExceptionHandler { coroutineContext, throwable -> + Logger.exception(throwable) + CoroutineScope(coroutineContext).launch(coroutineExceptionHandler) { + onError(throwable) + } + } + + val event: MutableSharedFlow = MutableSharedFlow() + + fun updateState(update: (S) -> S) { + _state.update(update) + } + + fun sendEvent(event: E) { + scope.launch(appDispatcher.default) { + this@BaseStore.event.emit(event) + } + } + + fun navigate(event: N) { + router(event) + } + + fun launch( + onError: suspend (Throwable) -> Unit = {}, + block: suspend CoroutineScope.() -> Unit, + ): Job = scope.launch( + context = exceptionHandler(onError), + block = block + ) + + fun launch( + action: suspend CoroutineScope.() -> T, + onError: suspend (Throwable) -> Unit = {}, + onSuccess: (T) -> Unit, + ): Job = scope.launch( + context = exceptionHandler(onError), + block = { + onSuccess(action()) + } + ) + + fun Flow.launch( + onError: suspend (cause: Throwable) -> Unit = {}, + each: suspend (T) -> Unit + ): Job = this + .onEach(each) + .flowOn(exceptionHandler(onError)) + .launchIn(scope) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/store/Router.kt b/composeApp/src/commonMain/kotlin/store/Router.kt new file mode 100644 index 00000000..4763faa2 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/store/Router.kt @@ -0,0 +1,5 @@ +package st.slex.csplashscreen.core.ui.mvi + +fun interface Router { + operator fun invoke(event: E) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/store/Store.kt b/composeApp/src/commonMain/kotlin/store/Store.kt new file mode 100644 index 00000000..a3117d6b --- /dev/null +++ b/composeApp/src/commonMain/kotlin/store/Store.kt @@ -0,0 +1,12 @@ +package st.slex.csplashscreen.core.ui.mvi + +interface Store { + + interface State + + interface Event + + interface Navigation + + interface Action +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dc6fd32b..33eb1987 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,4 +1,5 @@ [versions] +kermit = "2.0.2" ktor = "2.3.6" logback = "1.4.11" compose = "1.5.4" @@ -19,6 +20,7 @@ kotlin = "1.9.20" junit = "4.13.2" [libraries] +kermit = { module = "co.touchlab:kermit", version.ref = "kermit" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" } junit = { group = "junit", name = "junit", version.ref = "junit" } diff --git a/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/settings.gradle.kts b/settings.gradle.kts index 45dac6a9..fb96de02 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,4 +20,6 @@ dependencyResolutionManagement { include(":server") include(":shared") -include(":composeApp") \ No newline at end of file +include(":composeApp") +include(":core") +include(":home")