Skip to content

Commit

Permalink
initial setup
Browse files Browse the repository at this point in the history
  • Loading branch information
stslex committed Nov 23, 2023
1 parent bfc8834 commit 0f17900
Show file tree
Hide file tree
Showing 14 changed files with 287 additions and 75 deletions.
2 changes: 2 additions & 0 deletions composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ kotlin {
implementation(compose.desktop.currentOs)
}
commonMain.dependencies {
implementation(libs.kermit)

implementation(projects.shared)
implementation(compose.runtime)
implementation(compose.foundation)
Expand Down
75 changes: 1 addition & 74 deletions composeApp/src/commonMain/kotlin/App.kt
Original file line number Diff line number Diff line change
@@ -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
)
}
21 changes: 21 additions & 0 deletions composeApp/src/commonMain/kotlin/AppTheme.kt
Original file line number Diff line number Diff line change
@@ -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
)
}
61 changes: 61 additions & 0 deletions composeApp/src/commonMain/kotlin/InitialApp.kt
Original file line number Diff line number Diff line change
@@ -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
)
}
}
}
}
18 changes: 18 additions & 0 deletions composeApp/src/commonMain/kotlin/core/AppDispatcher.kt
Original file line number Diff line number Diff line change
@@ -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
}
7 changes: 7 additions & 0 deletions composeApp/src/commonMain/kotlin/core/CommonUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package core

import kotlinx.coroutines.CoroutineExceptionHandler

val coroutineExceptionHandler = CoroutineExceptionHandler { _, throwable ->
Logger.exception(throwable)
}
34 changes: 34 additions & 0 deletions composeApp/src/commonMain/kotlin/core/Logger.kt
Original file line number Diff line number Diff line change
@@ -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
)
}
}
24 changes: 24 additions & 0 deletions composeApp/src/commonMain/kotlin/main_screen/MainScreen.kt
Original file line number Diff line number Diff line change
@@ -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")
}
}
89 changes: 89 additions & 0 deletions composeApp/src/commonMain/kotlin/store/BaseStore.kt
Original file line number Diff line number Diff line change
@@ -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<S : State, E : Event, A : Action, N : Navigation>(
private val router: Router<N>,
private val appDispatcher: AppDispatcher,
initialState: S
) {

abstract fun sendAction(action: A)

private val _state: MutableStateFlow<S> = MutableStateFlow(initialState)
val state: StateFlow<S> = _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<E> = 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 <T> launch(
action: suspend CoroutineScope.() -> T,
onError: suspend (Throwable) -> Unit = {},
onSuccess: (T) -> Unit,
): Job = scope.launch(
context = exceptionHandler(onError),
block = {
onSuccess(action())
}
)

fun <T> Flow<T>.launch(
onError: suspend (cause: Throwable) -> Unit = {},
each: suspend (T) -> Unit
): Job = this
.onEach(each)
.flowOn(exceptionHandler(onError))
.launchIn(scope)
}
5 changes: 5 additions & 0 deletions composeApp/src/commonMain/kotlin/store/Router.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package st.slex.csplashscreen.core.ui.mvi

fun interface Router<in E : Store.Navigation> {
operator fun invoke(event: E)
}
12 changes: 12 additions & 0 deletions composeApp/src/commonMain/kotlin/store/Store.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package st.slex.csplashscreen.core.ui.mvi

interface Store {

interface State

interface Event

interface Navigation

interface Action
}
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[versions]
kermit = "2.0.2"
ktor = "2.3.6"
logback = "1.4.11"
compose = "1.5.4"
Expand All @@ -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" }
Expand Down
Loading

0 comments on commit 0f17900

Please sign in to comment.