From 8c6b70304d8138d55f7128f26eeaeab70cee41fb Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Thu, 23 Apr 2026 14:42:35 +0700 Subject: [PATCH] android: add basic screens --- composeApp/build.gradle.kts | 3 + .../androidMain/kotlin/su/reya/coop/App.kt | 107 ++++++++++++------ .../kotlin/su/reya/coop/MainActivity.kt | 1 - .../kotlin/su/reya/coop/Screens.kt | 98 ++++++++++++++++ gradle/libs.versions.toml | 4 + 5 files changed, 176 insertions(+), 37 deletions(-) create mode 100644 composeApp/src/androidMain/kotlin/su/reya/coop/Screens.kt diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index a6af89a..1db42c0 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -5,6 +5,7 @@ plugins { alias(libs.plugins.androidApplication) alias(libs.plugins.composeMultiplatform) alias(libs.plugins.composeCompiler) + kotlin("plugin.serialization") version libs.versions.kotlin.get() } kotlin { @@ -18,6 +19,8 @@ kotlin { androidMain.dependencies { implementation(libs.compose.uiToolingPreview) implementation(libs.androidx.activity.compose) + implementation("androidx.navigation:navigation-compose:2.8.8") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0") implementation("androidx.datastore:datastore-preferences:1.2.1") implementation("androidx.datastore:datastore-preferences-core:1.2.1") } diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/App.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/App.kt index 6e03338..42222e4 100644 --- a/composeApp/src/androidMain/kotlin/su/reya/coop/App.kt +++ b/composeApp/src/androidMain/kotlin/su/reya/coop/App.kt @@ -1,48 +1,83 @@ package su.reya.coop -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.safeContentPadding -import androidx.compose.material3.Button +import android.content.Context import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import org.jetbrains.compose.resources.painterResource +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.platform.LocalContext +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.preferencesDataStore +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import androidx.navigation.toRoute +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch -import coop.composeapp.generated.resources.Res -import coop.composeapp.generated.resources.compose_multiplatform +private val Context.dataStore: DataStore by preferencesDataStore(name = "settings") +private val FIRST_TIME_KEY = booleanPreferencesKey("first_time") @Composable -@Preview fun App() { MaterialTheme { - var showContent by remember { mutableStateOf(false) } - Column( - modifier = Modifier - .background(MaterialTheme.colorScheme.primaryContainer) - .safeContentPadding() - .fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Button(onClick = { showContent = !showContent }) { - Text("Click me!") + val context = LocalContext.current + val scope = rememberCoroutineScope() + val navController = rememberNavController() + + val isFirstTimeFlow = remember { + context.dataStore.data.map { preferences -> + preferences[FIRST_TIME_KEY] ?: true } - AnimatedVisibility(showContent) { - val greeting = remember { Greeting().greet() } - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Image(painterResource(Res.drawable.compose_multiplatform), null) - Text("Compose: $greeting") - } + } + val isFirstTime by isFirstTimeFlow.collectAsState(initial = null) + + if (isFirstTime == null) { + // Loading state + return@MaterialTheme + } + + NavHost( + navController = navController, + startDestination = if (isFirstTime == true) Screen.Welcome else Screen.Home + ) { + composable { backStackEntry -> + WelcomeScreen(onContinue = { + scope.launch { + context.dataStore.edit { settings -> + settings[FIRST_TIME_KEY] = false + } + navController.navigate(Screen.Home) { + popUpTo { inclusive = true } + } + } + }) + } + composable { backStackEntry -> + HomeScreen( + onOpenChat = { id -> navController.navigate(Screen.Chat(id)) } + ) + } + composable { backStackEntry -> + val chat: Screen.Chat = backStackEntry.toRoute() + ChatScreen(id = chat.id) + } + composable { backStackEntry -> + OnboardingScreen( + onOpenImport = { navController.navigate(Screen.Import) }, + onOpenNew = { navController.navigate(Screen.New) } + ) + } + composable { backStackEntry -> + ImportScreen() + } + composable { backStackEntry -> + NewScreen() } } } diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/MainActivity.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/MainActivity.kt index c7a3d8f..2ba9751 100644 --- a/composeApp/src/androidMain/kotlin/su/reya/coop/MainActivity.kt +++ b/composeApp/src/androidMain/kotlin/su/reya/coop/MainActivity.kt @@ -32,7 +32,6 @@ class MainActivity : ComponentActivity() { setContent { App() } - } } diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/Screens.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/Screens.kt new file mode 100644 index 0000000..47ff239 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/su/reya/coop/Screens.kt @@ -0,0 +1,98 @@ +package su.reya.coop + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import kotlinx.serialization.Serializable + +sealed interface Screen { + @Serializable + data object Welcome : Screen + + @Serializable + data object Home : Screen + + @Serializable + data class Chat(val id: String) : Screen + + @Serializable + data object Onboarding : Screen + + @Serializable + data object Import : Screen + + @Serializable + data object New : Screen +} + +@Composable +fun WelcomeScreen(onContinue: () -> Unit) { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text("Welcome Screen") + Spacer(modifier = Modifier.height(16.dp)) + Button(onClick = onContinue) { + Text("Get Started") + } + } + } +} + +@Composable +fun HomeScreen(onOpenChat: (String) -> Unit) { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text("Home Screen") + Spacer(modifier = Modifier.height(16.dp)) + Button(onClick = { onOpenChat("123") }) { + Text("Open Chat 123") + } + } + } +} + +@Composable +fun ChatScreen(id: String) { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Text("Chat Screen (ID: $id)") + } +} + +@Composable +fun OnboardingScreen(onOpenImport: () -> Unit, onOpenNew: () -> Unit) { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text("Onboarding Screen") + Spacer(modifier = Modifier.height(16.dp)) + Button(onClick = onOpenImport) { + Text("Import") + } + Spacer(modifier = Modifier.height(8.dp)) + Button(onClick = onOpenNew) { + Text("New") + } + } + } +} + +@Composable +fun ImportScreen() { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Text("Import Screen") + } +} + +@Composable +fun NewScreen() { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Text("New Screen") + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 76c4c43..5cbba71 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,10 +8,12 @@ androidx-appcompat = "1.7.1" androidx-core = "1.18.0" androidx-espresso = "3.7.0" androidx-lifecycle = "2.10.0" +androidx-navigation = "2.8.8" androidx-testExt = "1.3.0" composeMultiplatform = "1.10.3" junit = "4.13.2" kotlin = "2.3.20" +kotlinx-serialization = "1.8.0" material3 = "1.10.0-alpha05" [libraries] @@ -23,6 +25,8 @@ androidx-testExt-junit = { module = "androidx.test.ext:junit", version.ref = "an androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-espresso" } androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" } +androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidx-navigation" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" } compose-uiTooling = { module = "org.jetbrains.compose.ui:ui-tooling", version.ref = "composeMultiplatform" } androidx-lifecycle-viewmodelCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" } androidx-lifecycle-runtimeCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }