diff --git a/app/build.gradle.kts b/app/build.gradle.kts index efec7f6..34d6d08 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -4,6 +4,7 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) + alias(libs.plugins.kotlin.serialization) } android { @@ -44,21 +45,27 @@ android { } dependencies { - implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.lifecycle.viewmodel) + implementation(libs.androidx.lifecycle.viewmodel.compose) implementation(libs.androidx.activity.compose) implementation(platform(libs.androidx.compose.bom)) implementation(libs.androidx.ui) implementation(libs.androidx.ui.graphics) implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.material3) + + implementation(libs.androidx.navigation3.runtime) + implementation(libs.androidx.navigation3.ui) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(platform(libs.androidx.compose.bom)) androidTestImplementation(libs.androidx.ui.test.junit4) + debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.test.manifest) } \ No newline at end of file diff --git a/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt b/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt index 7d638fa..3bcc06d 100644 --- a/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt +++ b/app/src/main/java/org/nsh07/pomodoro/MainActivity.kt @@ -4,21 +4,36 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.activity.viewModels import org.nsh07.pomodoro.ui.AppScreen +import org.nsh07.pomodoro.ui.NavItem +import org.nsh07.pomodoro.ui.Screen import org.nsh07.pomodoro.ui.theme.TomatoTheme -import org.nsh07.pomodoro.ui.viewModel.UiViewModel class MainActivity : ComponentActivity() { - val viewModel: UiViewModel by viewModels() - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { TomatoTheme { - AppScreen(viewModel) + AppScreen() } } } + + companion object { + val screens = listOf( + NavItem( + Screen.Timer, + R.drawable.hourglass, + R.drawable.hourglass_filled, + "Timer" + ), + NavItem( + Screen.Settings, + R.drawable.settings, + R.drawable.settings_filled, + "Settings" + ) + ) + } } \ No newline at end of file diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt index 05018de..0436408 100644 --- a/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt +++ b/app/src/main/java/org/nsh07/pomodoro/ui/AppScreen.kt @@ -1,5 +1,6 @@ package org.nsh07.pomodoro.ui +import androidx.compose.animation.Crossfade import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.padding @@ -9,7 +10,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold import androidx.compose.material3.ShortNavigationBar import androidx.compose.material3.ShortNavigationBarItem -import androidx.compose.material3.ShortNavigationBarItemDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -22,18 +22,23 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.painterResource import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation3.runtime.entry +import androidx.navigation3.runtime.entryProvider +import androidx.navigation3.runtime.rememberNavBackStack +import androidx.navigation3.ui.NavDisplay import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.withContext -import org.nsh07.pomodoro.R +import org.nsh07.pomodoro.MainActivity.Companion.screens import org.nsh07.pomodoro.ui.timerScreen.TimerScreen import org.nsh07.pomodoro.ui.viewModel.UiViewModel @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable fun AppScreen( - viewModel: UiViewModel, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + viewModel: UiViewModel = viewModel() ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() val remainingTime by viewModel.time.collectAsStateWithLifecycle() @@ -50,31 +55,63 @@ fun AppScreen( val layoutDirection = LocalLayoutDirection.current + val backStack = rememberNavBackStack(Screen.Timer) + Scaffold( bottomBar = { ShortNavigationBar { - ShortNavigationBarItem( - selected = true, - onClick = { }, - icon = { Icon(painterResource(R.drawable.hourglass_filled), null) }, - label = { Text("Timer") }, - colors = ShortNavigationBarItemDefaults.colors() - ) + screens.forEach { + val selected = backStack.last() == it.route + ShortNavigationBarItem( + selected = selected, + onClick = if (it.route != Screen.Timer) { + { + if (backStack.size < 2) backStack.add(it.route) + else backStack[1] = it.route + } + } else { + { if (backStack.size > 1) backStack.removeAt(1) } + }, + icon = { + Crossfade (selected) { selected -> + if (selected) Icon(painterResource(it.selectedIcon), null) + else Icon(painterResource(it.unselectedIcon), null) + } + }, + label = { Text(it.label) } + ) + } } } ) { contentPadding -> - TimerScreen( - uiState = uiState, - showBrandTitle = showBrandTitle, - progress = { progress }, - resetTimer = viewModel::resetTimer, - skipTimer = viewModel::skipTimer, - toggleTimer = viewModel::toggleTimer, - modifier = modifier.padding( - start = contentPadding.calculateStartPadding(layoutDirection), - end = contentPadding.calculateEndPadding(layoutDirection), - bottom = contentPadding.calculateBottomPadding() - ) + NavDisplay( + backStack = backStack, + onBack = { backStack.removeLastOrNull() }, + entryProvider = entryProvider { + entry { + TimerScreen( + uiState = uiState, + showBrandTitle = showBrandTitle, + progress = { progress }, + resetTimer = viewModel::resetTimer, + skipTimer = viewModel::skipTimer, + toggleTimer = viewModel::toggleTimer, + modifier = modifier.padding( + start = contentPadding.calculateStartPadding(layoutDirection), + end = contentPadding.calculateEndPadding(layoutDirection), + bottom = contentPadding.calculateBottomPadding() + ) + ) + } + + entry { + Text("Settings") + } + + entry { + Text("Stats") + } + } ) } } \ No newline at end of file diff --git a/app/src/main/java/org/nsh07/pomodoro/ui/Screen.kt b/app/src/main/java/org/nsh07/pomodoro/ui/Screen.kt new file mode 100644 index 0000000..5f359c3 --- /dev/null +++ b/app/src/main/java/org/nsh07/pomodoro/ui/Screen.kt @@ -0,0 +1,23 @@ +package org.nsh07.pomodoro.ui + +import androidx.annotation.DrawableRes +import androidx.navigation3.runtime.NavKey +import kotlinx.serialization.Serializable + +sealed class Screen: NavKey { + @Serializable + object Timer : Screen() + @Serializable + object Settings : Screen() + @Serializable + object Stats : Screen() +} + +data class NavItem( + val route: Screen, + @param:DrawableRes + val unselectedIcon: Int, + @param:DrawableRes + val selectedIcon: Int, + val label: String +) \ No newline at end of file diff --git a/app/src/main/res/drawable/settings.xml b/app/src/main/res/drawable/settings.xml index 9495a98..842177d 100644 --- a/app/src/main/res/drawable/settings.xml +++ b/app/src/main/res/drawable/settings.xml @@ -1,13 +1,10 @@ - - + android:pathData="M433,880q-27,0 -46.5,-18T363,818l-9,-66q-13,-5 -24.5,-12T307,725l-62,26q-25,11 -50,2t-39,-32l-47,-82q-14,-23 -8,-49t27,-43l53,-40q-1,-7 -1,-13.5v-27q0,-6.5 1,-13.5l-53,-40q-21,-17 -27,-43t8,-49l47,-82q14,-23 39,-32t50,2l62,26q11,-8 23,-15t24,-12l9,-66q4,-26 23.5,-44t46.5,-18h94q27,0 46.5,18t23.5,44l9,66q13,5 24.5,12t22.5,15l62,-26q25,-11 50,-2t39,32l47,82q14,23 8,49t-27,43l-53,40q1,7 1,13.5v27q0,6.5 -2,13.5l53,40q21,17 27,43t-8,49l-48,82q-14,23 -39,32t-50,-2l-60,-26q-11,8 -23,15t-24,12l-9,66q-4,26 -23.5,44T527,880h-94ZM440,800h79l14,-106q31,-8 57.5,-23.5T639,633l99,41 39,-68 -86,-65q5,-14 7,-29.5t2,-31.5q0,-16 -2,-31.5t-7,-29.5l86,-65 -39,-68 -99,42q-22,-23 -48.5,-38.5T533,266l-13,-106h-79l-14,106q-31,8 -57.5,23.5T321,327l-99,-41 -39,68 86,64q-5,15 -7,30t-2,32q0,16 2,31t7,30l-86,65 39,68 99,-42q22,23 48.5,38.5T427,694l13,106ZM482,620q58,0 99,-41t41,-99q0,-58 -41,-99t-99,-41q-59,0 -99.5,41T342,480q0,58 40.5,99t99.5,41ZM480,480Z" /> diff --git a/app/src/main/res/drawable/settings_filled.xml b/app/src/main/res/drawable/settings_filled.xml new file mode 100644 index 0000000..c565939 --- /dev/null +++ b/app/src/main/res/drawable/settings_filled.xml @@ -0,0 +1,10 @@ + + + diff --git a/build.gradle.kts b/build.gradle.kts index 952b930..5ba8ae0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,4 +3,5 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kotlin.compose) apply false + alias(libs.plugins.kotlin.serialization) apply false } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7356262..d7e6a71 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,11 +7,15 @@ junitVersion = "1.2.1" espressoCore = "3.6.1" lifecycleRuntimeKtx = "2.9.1" activityCompose = "1.10.1" -composeBom = "2025.06.01" +composeBom = "2025.06.02" +navigation3Runtime = "1.0.0-alpha05" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel", version.ref = "lifecycleRuntimeKtx" } +androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleRuntimeKtx" } +androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "navigation3Runtime" } +androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "navigation3Runtime" } junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } @@ -30,4 +34,4 @@ androidx-material3 = { group = "androidx.compose.material3", name = "material3" android-application = { id = "com.android.application", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } - +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin"}