diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 7e74ae51..b62b5c30 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -1,5 +1,4 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat -import org.jetbrains.compose.ExperimentalComposeLibrary plugins { alias(libs.plugins.kotlinMultiplatform) @@ -15,9 +14,9 @@ kotlin { } } } - + jvm("desktop") - + listOf( iosX64(), iosArm64(), @@ -28,29 +27,18 @@ kotlin { isStatic = true } } - + sourceSets { val desktopMain by getting - - androidMain.dependencies { - implementation(libs.compose.ui) - implementation(libs.compose.ui.tooling.preview) - implementation(libs.androidx.activity.compose) - } + desktopMain.dependencies { implementation(compose.desktop.currentOs) } + commonMain.dependencies { implementation(project(":core:core")) implementation(project(":core:ui")) implementation(project(":feature:home")) - - implementation(projects.shared) - implementation(compose.runtime) - implementation(compose.foundation) - implementation(compose.material) - @OptIn(ExperimentalComposeLibrary::class) - implementation(compose.components.resources) } } } diff --git a/composeApp/src/commonMain/kotlin/App.kt b/composeApp/src/commonMain/kotlin/App.kt index aa8a6b2b..f7e3dac5 100644 --- a/composeApp/src/commonMain/kotlin/App.kt +++ b/composeApp/src/commonMain/kotlin/App.kt @@ -1,9 +1,36 @@ import androidx.compose.runtime.Composable +import com.stslex.core.core.coreModule import com.stslex.core.ui.theme.AppTheme +import com.stslex.feature.home.di.homeModule +import di.appModule +import org.koin.compose.KoinApplication +import org.koin.dsl.KoinAppDeclaration @Composable fun App() { - AppTheme { - InitialApp() + SetupKoin { + AppTheme { + InitialApp() + } } } + +@Composable +fun SetupKoin( + content: @Composable () -> Unit +) { + KoinApplication( + application = setupModules(), + content = content + ) +} + +private fun setupModules(): KoinAppDeclaration = { + modules( + listOf( + appModule, + coreModule, + homeModule, + ) + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/AppNavigatorImpl.kt b/composeApp/src/commonMain/kotlin/AppNavigatorImpl.kt new file mode 100644 index 00000000..874caf38 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/AppNavigatorImpl.kt @@ -0,0 +1,19 @@ +import cafe.adriel.voyager.navigator.Navigator +import com.stslex.core.ui.navigation.AppNavigator +import com.stslex.core.ui.navigation.AppScreen +import com.stslex.feature.home.ui.HomeScreen +import com.stslex.feature.home.ui.SecondScreen + +class AppNavigatorImpl( + private val navigator: Lazy +) : AppNavigator { + + override fun navigate( + screen: AppScreen + ) { + when (screen) { + AppScreen.Home -> navigator.value.push(HomeScreen) + is AppScreen.SecondScreen -> navigator.value.push(SecondScreen(screen.text)) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/InitialApp.kt b/composeApp/src/commonMain/kotlin/InitialApp.kt index 272a6384..a0095c69 100644 --- a/composeApp/src/commonMain/kotlin/InitialApp.kt +++ b/composeApp/src/commonMain/kotlin/InitialApp.kt @@ -1,7 +1,28 @@ +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable -import com.stslex.feature.home.HomeScreen +import androidx.compose.ui.Modifier +import cafe.adriel.voyager.navigator.Navigator +import com.stslex.feature.home.ui.HomeScreen @Composable -fun InitialApp() { - HomeScreen() -} \ No newline at end of file +fun InitialApp( + modifier: Modifier = Modifier +) { + Scaffold( + modifier = modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + ) { paddingValues -> + Box( + modifier = Modifier.fillMaxSize() + .padding(paddingValues) + ) { + Navigator(HomeScreen) + } + } +} diff --git a/composeApp/src/commonMain/kotlin/di/appModule.kt b/composeApp/src/commonMain/kotlin/di/appModule.kt new file mode 100644 index 00000000..0971d6b6 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/di/appModule.kt @@ -0,0 +1,13 @@ +package di + +import AppNavigatorImpl +import com.stslex.core.ui.navigation.AppNavigator +import org.koin.dsl.module + +val appModule = module { + single { + AppNavigatorImpl( + lazyOf(get()) + ) + } +} \ No newline at end of file diff --git a/core/core/build.gradle.kts b/core/core/build.gradle.kts index aef9daf8..e8279ccf 100644 --- a/core/core/build.gradle.kts +++ b/core/core/build.gradle.kts @@ -14,7 +14,7 @@ kotlin { } jvm("desktop") - + listOf( iosX64(), iosArm64(), @@ -27,20 +27,16 @@ kotlin { } sourceSets { - val desktopMain by getting - commonMain.dependencies { implementation(libs.kermit) - implementation(projects.shared) implementation(compose.runtime) implementation(compose.foundation) + api(libs.koin.core) + api(libs.koin.compose) } commonTest.dependencies { implementation(libs.kotlin.test) } - desktopMain.dependencies { - implementation(compose.desktop.currentOs) - } } } diff --git a/core/core/src/commonMain/kotlin/com/stslex/core/core/CoreModule.kt b/core/core/src/commonMain/kotlin/com/stslex/core/core/CoreModule.kt new file mode 100644 index 00000000..c1bc8736 --- /dev/null +++ b/core/core/src/commonMain/kotlin/com/stslex/core/core/CoreModule.kt @@ -0,0 +1,8 @@ +package com.stslex.core.core + +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module + +val coreModule = module { + singleOf(::AppDispatcherImpl) +} \ No newline at end of file diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index 184ee588..694a0e31 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -31,21 +31,30 @@ kotlin { sourceSets { val desktopMain by getting + desktopMain.dependencies { + implementation(compose.desktop.currentOs) + } + commonMain.dependencies { implementation(project(":core:core")) - implementation(projects.shared) - implementation(compose.runtime) - implementation(compose.foundation) + api(compose.runtime) + api(compose.foundation) implementation(compose.material) + api(compose.material3) @OptIn(ExperimentalComposeLibrary::class) - implementation(compose.components.resources) + api(compose.components.resources) + api(libs.bundles.voyager) } commonTest.dependencies { implementation(libs.kotlin.test) } - desktopMain.dependencies { - implementation(compose.desktop.currentOs) + androidMain.dependencies { + api(libs.compose.ui) + api(libs.compose.ui.tooling.preview) + api(libs.androidx.activity.compose) + api(libs.koin.android) + api(libs.koin.androidx.compose) } } } diff --git a/core/ui/src/androidMain/kotlin/com/stslex/core/ui/base/ViewModel.android.kt b/core/ui/src/androidMain/kotlin/com/stslex/core/ui/base/ViewModel.android.kt new file mode 100644 index 00000000..b53e9443 --- /dev/null +++ b/core/ui/src/androidMain/kotlin/com/stslex/core/ui/base/ViewModel.android.kt @@ -0,0 +1,27 @@ +package com.stslex.core.ui.base + +import androidx.compose.runtime.Composable +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.CoroutineScope +import org.koin.androidx.compose.koinViewModel +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.core.definition.Definition +import org.koin.core.definition.KoinDefinition +import org.koin.core.module.Module +import org.koin.core.qualifier.Qualifier +import com.stslex.core.ui.base.ViewModel as VM + +actual open class ViewModel : ViewModel() { + + actual val scope: CoroutineScope + get() = viewModelScope +} + +actual inline fun Module.viewModelDefinition( + qualifier: Qualifier?, + noinline definition: Definition, +): KoinDefinition = viewModel(qualifier = qualifier, definition = definition) + +@Composable +actual inline fun getViewModel(): T = koinViewModel() \ No newline at end of file diff --git a/core/ui/src/commonMain/kotlin/com/stslex/core/ui/base/ViewModel.kt b/core/ui/src/commonMain/kotlin/com/stslex/core/ui/base/ViewModel.kt new file mode 100644 index 00000000..0961cbe0 --- /dev/null +++ b/core/ui/src/commonMain/kotlin/com/stslex/core/ui/base/ViewModel.kt @@ -0,0 +1,36 @@ +package com.stslex.core.ui.base + +import androidx.compose.runtime.Composable +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import kotlinx.coroutines.CoroutineScope +import org.koin.compose.getKoin +import org.koin.core.definition.Definition +import org.koin.core.definition.KoinDefinition +import org.koin.core.module.Module +import org.koin.core.qualifier.Qualifier +import org.koin.dsl.module + +expect open class ViewModel() { + + val scope: CoroutineScope +} + +expect inline fun Module.viewModelDefinition( + qualifier: Qualifier? = null, + noinline definition: Definition +): KoinDefinition + +@Composable +expect inline fun getViewModel(): T + +@Composable +inline fun rememberStore(): VM { + val navigator = LocalNavigator.currentOrThrow + val module = module { single { navigator } } + getKoin().loadModules( + modules = listOf(module), + allowOverride = true, + ) + return getViewModel() +} diff --git a/core/ui/src/commonMain/kotlin/com/stslex/core/ui/mvi/BaseStore.kt b/core/ui/src/commonMain/kotlin/com/stslex/core/ui/mvi/BaseStore.kt index 41e525e8..26be0ff6 100644 --- a/core/ui/src/commonMain/kotlin/com/stslex/core/ui/mvi/BaseStore.kt +++ b/core/ui/src/commonMain/kotlin/com/stslex/core/ui/mvi/BaseStore.kt @@ -3,11 +3,12 @@ package com.stslex.core.ui.mvi import com.stslex.core.core.AppDispatcher import com.stslex.core.core.Logger import com.stslex.core.core.coroutineExceptionHandler -import com.stslex.core.ui.navigation.Router +import com.stslex.core.ui.base.ViewModel import com.stslex.core.ui.mvi.Store.Action import com.stslex.core.ui.mvi.Store.Event import com.stslex.core.ui.mvi.Store.Navigation import com.stslex.core.ui.mvi.Store.State +import com.stslex.core.ui.navigation.Router import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -26,15 +27,13 @@ abstract class BaseStore( private val router: Router, private val appDispatcher: AppDispatcher, initialState: S -) { +) : Store, ViewModel() { 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 -> diff --git a/core/ui/src/commonMain/kotlin/com/stslex/core/ui/navigation/AppNavigator.kt b/core/ui/src/commonMain/kotlin/com/stslex/core/ui/navigation/AppNavigator.kt new file mode 100644 index 00000000..b08e5746 --- /dev/null +++ b/core/ui/src/commonMain/kotlin/com/stslex/core/ui/navigation/AppNavigator.kt @@ -0,0 +1,6 @@ +package com.stslex.core.ui.navigation + +interface AppNavigator { + + fun navigate(screen: AppScreen) +} \ No newline at end of file diff --git a/core/ui/src/commonMain/kotlin/com/stslex/core/ui/navigation/AppScreen.kt b/core/ui/src/commonMain/kotlin/com/stslex/core/ui/navigation/AppScreen.kt new file mode 100644 index 00000000..6ca7fbed --- /dev/null +++ b/core/ui/src/commonMain/kotlin/com/stslex/core/ui/navigation/AppScreen.kt @@ -0,0 +1,8 @@ +package com.stslex.core.ui.navigation + +sealed interface AppScreen { + + data object Home : AppScreen + + data class SecondScreen(val text: String) : AppScreen +} \ No newline at end of file diff --git a/core/ui/src/commonMain/kotlin/com/stslex/core/ui/navigation/Router.kt b/core/ui/src/commonMain/kotlin/com/stslex/core/ui/navigation/Router.kt index bd22011c..a6c1bdbc 100644 --- a/core/ui/src/commonMain/kotlin/com/stslex/core/ui/navigation/Router.kt +++ b/core/ui/src/commonMain/kotlin/com/stslex/core/ui/navigation/Router.kt @@ -4,4 +4,5 @@ import com.stslex.core.ui.mvi.Store fun interface Router { operator fun invoke(event: E) -} \ No newline at end of file +} + diff --git a/core/ui/src/commonMain/kotlin/com/stslex/core/ui/theme/AppTheme.kt b/core/ui/src/commonMain/kotlin/com/stslex/core/ui/theme/AppTheme.kt index b316513b..cf78e314 100644 --- a/core/ui/src/commonMain/kotlin/com/stslex/core/ui/theme/AppTheme.kt +++ b/core/ui/src/commonMain/kotlin/com/stslex/core/ui/theme/AppTheme.kt @@ -1,9 +1,9 @@ package com.stslex.core.ui.theme import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material.MaterialTheme -import androidx.compose.material.darkColors -import androidx.compose.material.lightColors +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable @Composable @@ -12,12 +12,12 @@ fun AppTheme( content: @Composable () -> Unit, ) { val colors = if (isDarkTheme) { - darkColors() + darkColorScheme() } else { - lightColors() + lightColorScheme() } MaterialTheme( - colors = colors, + colorScheme = colors, content = content ) } \ No newline at end of file diff --git a/core/ui/src/desktopMain/kotlin/com/stslex/core/ui/base/ViewModel.desktop.kt b/core/ui/src/desktopMain/kotlin/com/stslex/core/ui/base/ViewModel.desktop.kt new file mode 100644 index 00000000..fd4947b9 --- /dev/null +++ b/core/ui/src/desktopMain/kotlin/com/stslex/core/ui/base/ViewModel.desktop.kt @@ -0,0 +1,24 @@ +package com.stslex.core.ui.base + +import androidx.compose.runtime.Composable +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import org.koin.compose.koinInject +import org.koin.core.definition.Definition +import org.koin.core.definition.KoinDefinition +import org.koin.core.module.Module +import org.koin.core.qualifier.Qualifier + +actual open class ViewModel { + + actual val scope: CoroutineScope + get() = CoroutineScope(Dispatchers.Default) +} + +actual inline fun Module.viewModelDefinition( + qualifier: Qualifier?, + noinline definition: Definition, +): KoinDefinition = factory(qualifier = qualifier, definition = definition) + +@Composable +actual inline fun getViewModel(): T = koinInject() \ No newline at end of file diff --git a/core/ui/src/iosMain/kotlin/com/stslex/core/ui/base/ViewModel.ios.kt b/core/ui/src/iosMain/kotlin/com/stslex/core/ui/base/ViewModel.ios.kt new file mode 100644 index 00000000..fd4947b9 --- /dev/null +++ b/core/ui/src/iosMain/kotlin/com/stslex/core/ui/base/ViewModel.ios.kt @@ -0,0 +1,24 @@ +package com.stslex.core.ui.base + +import androidx.compose.runtime.Composable +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import org.koin.compose.koinInject +import org.koin.core.definition.Definition +import org.koin.core.definition.KoinDefinition +import org.koin.core.module.Module +import org.koin.core.qualifier.Qualifier + +actual open class ViewModel { + + actual val scope: CoroutineScope + get() = CoroutineScope(Dispatchers.Default) +} + +actual inline fun Module.viewModelDefinition( + qualifier: Qualifier?, + noinline definition: Definition, +): KoinDefinition = factory(qualifier = qualifier, definition = definition) + +@Composable +actual inline fun getViewModel(): T = koinInject() \ No newline at end of file diff --git a/feature/home/build.gradle.kts b/feature/home/build.gradle.kts index f2be90b6..5536ea80 100644 --- a/feature/home/build.gradle.kts +++ b/feature/home/build.gradle.kts @@ -1,5 +1,3 @@ -import org.jetbrains.compose.ExperimentalComposeLibrary - plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.androidLibrary) @@ -29,25 +27,13 @@ kotlin { } sourceSets { - val desktopMain by getting - commonMain.dependencies { implementation(project(":core:core")) implementation(project(":core:ui")) - - implementation(projects.shared) - implementation(compose.runtime) - implementation(compose.foundation) - implementation(compose.material) - @OptIn(ExperimentalComposeLibrary::class) - implementation(compose.components.resources) } commonTest.dependencies { implementation(libs.kotlin.test) } - desktopMain.dependencies { - implementation(compose.desktop.currentOs) - } } } diff --git a/feature/home/src/commonMain/kotlin/com/stslex/feature/home/HomeScreen.kt b/feature/home/src/commonMain/kotlin/com/stslex/feature/home/HomeScreen.kt deleted file mode 100644 index c38fc7b6..00000000 --- a/feature/home/src/commonMain/kotlin/com/stslex/feature/home/HomeScreen.kt +++ /dev/null @@ -1,66 +0,0 @@ -package com.stslex.feature.home - -import Greeting -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 HomeScreen( - modifier: Modifier = Modifier -) { - 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/feature/home/src/commonMain/kotlin/com/stslex/feature/home/di/HomeModule.kt b/feature/home/src/commonMain/kotlin/com/stslex/feature/home/di/HomeModule.kt new file mode 100644 index 00000000..58bd1044 --- /dev/null +++ b/feature/home/src/commonMain/kotlin/com/stslex/feature/home/di/HomeModule.kt @@ -0,0 +1,14 @@ +package com.stslex.feature.home.di + +import com.stslex.core.ui.base.viewModelDefinition +import com.stslex.feature.home.navigation.HomeScreenRouter +import com.stslex.feature.home.navigation.HomeScreenRouterImpl +import com.stslex.feature.home.ui.store.HomeScreenStore +import org.koin.dsl.module + +val homeModule = module { + viewModelDefinition { HomeScreenStore(get(), get()) } + factory { + HomeScreenRouterImpl(get()) + } +} diff --git a/feature/home/src/commonMain/kotlin/com/stslex/feature/home/navigation/HomeScreenRouter.kt b/feature/home/src/commonMain/kotlin/com/stslex/feature/home/navigation/HomeScreenRouter.kt new file mode 100644 index 00000000..fbd425e8 --- /dev/null +++ b/feature/home/src/commonMain/kotlin/com/stslex/feature/home/navigation/HomeScreenRouter.kt @@ -0,0 +1,19 @@ +package com.stslex.feature.home.navigation + +import com.stslex.core.ui.navigation.AppNavigator +import com.stslex.core.ui.navigation.AppScreen +import com.stslex.core.ui.navigation.Router +import com.stslex.feature.home.ui.store.HomeScreenStoreComponent.Navigation + +interface HomeScreenRouter : Router + +class HomeScreenRouterImpl( + private val navigator: AppNavigator +) : HomeScreenRouter { + + override fun invoke(event: Navigation) { + when (event) { + is Navigation.SecondScreen -> navigator.navigate(AppScreen.SecondScreen(event.text)) + } + } +} \ No newline at end of file diff --git a/feature/home/src/commonMain/kotlin/com/stslex/feature/home/ui/HomeScreen.kt b/feature/home/src/commonMain/kotlin/com/stslex/feature/home/ui/HomeScreen.kt new file mode 100644 index 00000000..76719806 --- /dev/null +++ b/feature/home/src/commonMain/kotlin/com/stslex/feature/home/ui/HomeScreen.kt @@ -0,0 +1,93 @@ +package com.stslex.feature.home.ui + +import androidx.compose.animation.AnimatedVisibility +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.icons.Icons +import androidx.compose.material.icons.filled.Home +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +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 cafe.adriel.voyager.core.screen.Screen +import com.stslex.core.ui.base.rememberStore +import com.stslex.feature.home.ui.store.HomeScreenStore +import com.stslex.feature.home.ui.store.HomeScreenStoreComponent +import com.stslex.feature.home.ui.store.HomeScreenStoreComponent.Action + +object HomeScreen : Screen { + + @Composable + override fun Content() { + val store = rememberStore() + val state by remember { store.state }.collectAsState() + + HomeScreenContent( + state = state, + sendAction = store::sendAction + ) + } +} + +@Composable +private fun HomeScreenContent( + state: HomeScreenStoreComponent.State, + sendAction: (Action) -> Unit, +) { + Box( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + ) { + val defaultGreetState = "Hello World!" + val clickedGreetState = "Compose: ${state.text}" + 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) { + Icon( + imageVector = Icons.Default.Home, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + } + Button( + onClick = { + sendAction(Action.OnClick) + } + ) { + Text("second screen") + } + } + } +} \ No newline at end of file diff --git a/feature/home/src/commonMain/kotlin/com/stslex/feature/home/ui/SecondScreen.kt b/feature/home/src/commonMain/kotlin/com/stslex/feature/home/ui/SecondScreen.kt new file mode 100644 index 00000000..aff48511 --- /dev/null +++ b/feature/home/src/commonMain/kotlin/com/stslex/feature/home/ui/SecondScreen.kt @@ -0,0 +1,15 @@ +package com.stslex.feature.home.ui + +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import cafe.adriel.voyager.core.screen.Screen + +data class SecondScreen( + private val text: String +) : Screen { + + @Composable + override fun Content() { + Text(text = "Second Screen: $text") + } +} \ No newline at end of file diff --git a/feature/home/src/commonMain/kotlin/com/stslex/feature/home/ui/store/HomeScreenStore.kt b/feature/home/src/commonMain/kotlin/com/stslex/feature/home/ui/store/HomeScreenStore.kt new file mode 100644 index 00000000..5a1f4771 --- /dev/null +++ b/feature/home/src/commonMain/kotlin/com/stslex/feature/home/ui/store/HomeScreenStore.kt @@ -0,0 +1,24 @@ +package com.stslex.feature.home.ui.store + +import com.stslex.core.core.AppDispatcher +import com.stslex.core.ui.mvi.BaseStore +import com.stslex.feature.home.navigation.HomeScreenRouter +import com.stslex.feature.home.ui.store.HomeScreenStoreComponent.Action +import com.stslex.feature.home.ui.store.HomeScreenStoreComponent.Event +import com.stslex.feature.home.ui.store.HomeScreenStoreComponent.Navigation +import com.stslex.feature.home.ui.store.HomeScreenStoreComponent.State + +class HomeScreenStore( + appDispatcher: AppDispatcher, + router: HomeScreenRouter +) : HomeScreenStoreComponent, BaseStore( + router = router, + initialState = State.INITIAL, + appDispatcher = appDispatcher +) { + override fun sendAction(action: Action) { + when (action) { + Action.OnClick -> navigate(Navigation.SecondScreen(state.value.text)) + } + } +} \ No newline at end of file diff --git a/feature/home/src/commonMain/kotlin/com/stslex/feature/home/ui/store/HomeScreenStoreComponent.kt b/feature/home/src/commonMain/kotlin/com/stslex/feature/home/ui/store/HomeScreenStoreComponent.kt new file mode 100644 index 00000000..f24da794 --- /dev/null +++ b/feature/home/src/commonMain/kotlin/com/stslex/feature/home/ui/store/HomeScreenStoreComponent.kt @@ -0,0 +1,27 @@ +package com.stslex.feature.home.ui.store + +import com.stslex.core.ui.mvi.Store + +interface HomeScreenStoreComponent : Store { + + data class State( + val text: String + ) : Store.State { + + companion object { + val INITIAL = State(text = "Hello, World!") + } + } + + interface Event : Store.Event + + interface Action : Store.Action { + + object OnClick : Action + } + + interface Navigation : Store.Navigation { + + data class SecondScreen(val text: String) : Navigation + } +} \ No newline at end of file diff --git a/feature/home/src/commonMain/resources/compose-multiplatform.xml b/feature/home/src/commonMain/resources/compose-multiplatform.xml deleted file mode 100644 index d7bf7955..00000000 --- a/feature/home/src/commonMain/resources/compose-multiplatform.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 33eb1987..d27a5f26 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,5 @@ [versions] kermit = "2.0.2" -ktor = "2.3.6" logback = "1.4.11" compose = "1.5.4" compose-plugin = "1.5.10" @@ -19,15 +18,15 @@ androidx-espresso-core = "3.5.1" kotlin = "1.9.20" junit = "4.13.2" +koin = "3.4.3" +koin-compose = "1.0.4" +voyagerVersion = "1.0.0-rc10" + [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" } -logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } -ktor-server-core = { module = "io.ktor:ktor-server-core-jvm", version.ref = "ktor" } -ktor-server-netty = { module = "io.ktor:ktor-server-netty-jvm", version.ref = "ktor" } -ktor-server-tests = { module = "io.ktor:ktor-server-tests-jvm", version.ref = "ktor" } 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" } @@ -41,10 +40,23 @@ androidx-material = { group = "com.google.android.material", name = "material", androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "androidx-constraintlayout" } androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" } +#TODO add for tests +#koin-test = { module = "io.insert-koin:koin-test", version.ref = "koin" } +koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" } +koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" } +koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin-compose"} +koin-androidx-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koin" } + +voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyagerVersion" } +voyager-koin = { module = "cafe.adriel.voyager:voyager-koin", version.ref = "voyagerVersion" } +#todo check if this is needed +logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } [plugins] kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } -ktor = { id = "io.ktor.plugin", version.ref = "ktor" } jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" } androidApplication = { id = "com.android.application", version.ref = "agp" } androidLibrary = { id = "com.android.library", version.ref = "agp" } -kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } \ No newline at end of file +kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } + +[bundles] +voyager = ["voyager-navigator", "voyager-koin"] \ No newline at end of file diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index 66c59c01..a1914a22 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -17,7 +17,7 @@ 058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; }; - 7555FF7B242A565900829871 /* .app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = .app; sourceTree = BUILT_PRODUCTS_DIR; }; + 7555FF7B242A565900829871 /* Wizard.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Wizard.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; @@ -52,7 +52,7 @@ 7555FF7C242A565900829871 /* Products */ = { isa = PBXGroup; children = ( - 7555FF7B242A565900829871 /* .app */, + 7555FF7B242A565900829871 /* Wizard.app */, ); name = Products; sourceTree = ""; @@ -94,7 +94,7 @@ ); name = iosApp; productName = iosApp; - productReference = 7555FF7B242A565900829871 /* .app */; + productReference = 7555FF7B242A565900829871 /* Wizard.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -301,7 +301,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; - DEVELOPMENT_TEAM = "${TEAM_ID}"; + DEVELOPMENT_TEAM = U26KC3ZYKX; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = ( "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", @@ -332,7 +332,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; - DEVELOPMENT_TEAM = "${TEAM_ID}"; + DEVELOPMENT_TEAM = U26KC3ZYKX; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = ( "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", diff --git a/server/build.gradle.kts b/server/build.gradle.kts deleted file mode 100644 index 1453aaed..00000000 --- a/server/build.gradle.kts +++ /dev/null @@ -1,21 +0,0 @@ -plugins { - alias(libs.plugins.kotlinJvm) - alias(libs.plugins.ktor) - application -} - -group = "com.stslex.wizard" -version = "1.0.0" -application { - mainClass.set("com.stslex.wizard.ApplicationKt") - applicationDefaultJvmArgs = listOf("-Dio.ktor.development=${extra["development"] ?: "false"}") -} - -dependencies { - implementation(projects.shared) - implementation(libs.logback) - implementation(libs.ktor.server.core) - implementation(libs.ktor.server.netty) - testImplementation(libs.ktor.server.tests) - testImplementation(libs.kotlin.test.junit) -} \ No newline at end of file diff --git a/server/src/main/kotlin/com/stslex/wizard/Application.kt b/server/src/main/kotlin/com/stslex/wizard/Application.kt deleted file mode 100644 index 21ce3989..00000000 --- a/server/src/main/kotlin/com/stslex/wizard/Application.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.stslex.wizard - -import Greeting -import SERVER_PORT -import io.ktor.server.application.* -import io.ktor.server.engine.* -import io.ktor.server.netty.* -import io.ktor.server.response.* -import io.ktor.server.routing.* - -fun main() { - embeddedServer(Netty, port = SERVER_PORT, host = "0.0.0.0", module = Application::module) - .start(wait = true) -} - -fun Application.module() { - routing { - get("/") { - call.respondText("Ktor: ${Greeting().greet()}") - } - } -} diff --git a/server/src/main/resources/logback.xml b/server/src/main/resources/logback.xml deleted file mode 100644 index bdbb64ec..00000000 --- a/server/src/main/resources/logback.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - - diff --git a/settings.gradle.kts b/settings.gradle.kts index 239c3fa5..d52ab9c3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -18,8 +18,6 @@ dependencyResolutionManagement { } } -include(":server") -include(":shared") include(":composeApp") include(":core:core") include(":core:ui") diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts deleted file mode 100644 index 75231443..00000000 --- a/shared/build.gradle.kts +++ /dev/null @@ -1,34 +0,0 @@ -plugins { - alias(libs.plugins.kotlinMultiplatform) - alias(libs.plugins.androidLibrary) -} - -kotlin { - iosX64() - iosArm64() - iosSimulatorArm64() - - androidTarget { - compilations.all { - kotlinOptions { - jvmTarget = "1.8" - } - } - } - - jvm() - - sourceSets { - commonMain.dependencies { - // put your Multiplatform dependencies here - } - } -} - -android { - namespace = "com.stslex.wizard.shared" - compileSdk = libs.versions.android.compileSdk.get().toInt() - defaultConfig { - minSdk = libs.versions.android.minSdk.get().toInt() - } -} diff --git a/shared/src/androidMain/kotlin/Platform.android.kt b/shared/src/androidMain/kotlin/Platform.android.kt deleted file mode 100644 index 4f3ea051..00000000 --- a/shared/src/androidMain/kotlin/Platform.android.kt +++ /dev/null @@ -1,7 +0,0 @@ -import android.os.Build - -class AndroidPlatform : Platform { - override val name: String = "Android ${Build.VERSION.SDK_INT}" -} - -actual fun getPlatform(): Platform = AndroidPlatform() \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/Constants.kt b/shared/src/commonMain/kotlin/Constants.kt deleted file mode 100644 index e88a3e6c..00000000 --- a/shared/src/commonMain/kotlin/Constants.kt +++ /dev/null @@ -1 +0,0 @@ -const val SERVER_PORT = 8080 diff --git a/shared/src/commonMain/kotlin/Greeting.kt b/shared/src/commonMain/kotlin/Greeting.kt deleted file mode 100644 index 887d8350..00000000 --- a/shared/src/commonMain/kotlin/Greeting.kt +++ /dev/null @@ -1,7 +0,0 @@ -class Greeting { - private val platform = getPlatform() - - fun greet(): String { - return "Hello, ${platform.name}!" - } -} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/Platform.kt b/shared/src/commonMain/kotlin/Platform.kt deleted file mode 100644 index 87ca3fff..00000000 --- a/shared/src/commonMain/kotlin/Platform.kt +++ /dev/null @@ -1,5 +0,0 @@ -interface Platform { - val name: String -} - -expect fun getPlatform(): Platform \ No newline at end of file diff --git a/shared/src/iosMain/kotlin/Platform.ios.kt b/shared/src/iosMain/kotlin/Platform.ios.kt deleted file mode 100644 index 5cef987c..00000000 --- a/shared/src/iosMain/kotlin/Platform.ios.kt +++ /dev/null @@ -1,7 +0,0 @@ -import platform.UIKit.UIDevice - -class IOSPlatform: Platform { - override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion -} - -actual fun getPlatform(): Platform = IOSPlatform() \ No newline at end of file diff --git a/shared/src/jvmMain/kotlin/Platform.jvm.kt b/shared/src/jvmMain/kotlin/Platform.jvm.kt deleted file mode 100644 index f5e7e494..00000000 --- a/shared/src/jvmMain/kotlin/Platform.jvm.kt +++ /dev/null @@ -1,5 +0,0 @@ -class JVMPlatform: Platform { - override val name: String = "Java ${System.getProperty("java.version")}" -} - -actual fun getPlatform(): Platform = JVMPlatform() \ No newline at end of file