Implement a navigation system
This commit is contained in:
@@ -4,6 +4,7 @@ plugins {
|
|||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.android)
|
||||||
alias(libs.plugins.kotlin.compose)
|
alias(libs.plugins.kotlin.compose)
|
||||||
|
alias(libs.plugins.kotlin.serialization)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -44,21 +45,27 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
implementation(libs.androidx.core.ktx)
|
implementation(libs.androidx.core.ktx)
|
||||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||||
implementation(libs.androidx.lifecycle.viewmodel)
|
implementation(libs.androidx.lifecycle.viewmodel)
|
||||||
|
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
||||||
implementation(libs.androidx.activity.compose)
|
implementation(libs.androidx.activity.compose)
|
||||||
implementation(platform(libs.androidx.compose.bom))
|
implementation(platform(libs.androidx.compose.bom))
|
||||||
implementation(libs.androidx.ui)
|
implementation(libs.androidx.ui)
|
||||||
implementation(libs.androidx.ui.graphics)
|
implementation(libs.androidx.ui.graphics)
|
||||||
implementation(libs.androidx.ui.tooling.preview)
|
implementation(libs.androidx.ui.tooling.preview)
|
||||||
implementation(libs.androidx.material3)
|
implementation(libs.androidx.material3)
|
||||||
|
|
||||||
|
implementation(libs.androidx.navigation3.runtime)
|
||||||
|
implementation(libs.androidx.navigation3.ui)
|
||||||
|
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
|
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
androidTestImplementation(libs.androidx.espresso.core)
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
androidTestImplementation(platform(libs.androidx.compose.bom))
|
androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||||
androidTestImplementation(libs.androidx.ui.test.junit4)
|
androidTestImplementation(libs.androidx.ui.test.junit4)
|
||||||
|
|
||||||
debugImplementation(libs.androidx.ui.tooling)
|
debugImplementation(libs.androidx.ui.tooling)
|
||||||
debugImplementation(libs.androidx.ui.test.manifest)
|
debugImplementation(libs.androidx.ui.test.manifest)
|
||||||
}
|
}
|
||||||
@@ -4,21 +4,36 @@ import android.os.Bundle
|
|||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.viewModels
|
|
||||||
import org.nsh07.pomodoro.ui.AppScreen
|
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.theme.TomatoTheme
|
||||||
import org.nsh07.pomodoro.ui.viewModel.UiViewModel
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
val viewModel: UiViewModel by viewModels<UiViewModel>()
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
setContent {
|
setContent {
|
||||||
TomatoTheme {
|
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"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.nsh07.pomodoro.ui
|
package org.nsh07.pomodoro.ui
|
||||||
|
|
||||||
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.foundation.layout.calculateEndPadding
|
import androidx.compose.foundation.layout.calculateEndPadding
|
||||||
import androidx.compose.foundation.layout.calculateStartPadding
|
import androidx.compose.foundation.layout.calculateStartPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
@@ -9,7 +10,6 @@ import androidx.compose.material3.Icon
|
|||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.ShortNavigationBar
|
import androidx.compose.material3.ShortNavigationBar
|
||||||
import androidx.compose.material3.ShortNavigationBarItem
|
import androidx.compose.material3.ShortNavigationBarItem
|
||||||
import androidx.compose.material3.ShortNavigationBarItemDefaults
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@@ -22,18 +22,23 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
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.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.withContext
|
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.timerScreen.TimerScreen
|
||||||
import org.nsh07.pomodoro.ui.viewModel.UiViewModel
|
import org.nsh07.pomodoro.ui.viewModel.UiViewModel
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun AppScreen(
|
fun AppScreen(
|
||||||
viewModel: UiViewModel,
|
modifier: Modifier = Modifier,
|
||||||
modifier: Modifier = Modifier
|
viewModel: UiViewModel = viewModel()
|
||||||
) {
|
) {
|
||||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
val remainingTime by viewModel.time.collectAsStateWithLifecycle()
|
val remainingTime by viewModel.time.collectAsStateWithLifecycle()
|
||||||
@@ -50,31 +55,63 @@ fun AppScreen(
|
|||||||
|
|
||||||
val layoutDirection = LocalLayoutDirection.current
|
val layoutDirection = LocalLayoutDirection.current
|
||||||
|
|
||||||
|
val backStack = rememberNavBackStack<Screen>(Screen.Timer)
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
ShortNavigationBar {
|
ShortNavigationBar {
|
||||||
ShortNavigationBarItem(
|
screens.forEach {
|
||||||
selected = true,
|
val selected = backStack.last() == it.route
|
||||||
onClick = { },
|
ShortNavigationBarItem(
|
||||||
icon = { Icon(painterResource(R.drawable.hourglass_filled), null) },
|
selected = selected,
|
||||||
label = { Text("Timer") },
|
onClick = if (it.route != Screen.Timer) {
|
||||||
colors = ShortNavigationBarItemDefaults.colors()
|
{
|
||||||
)
|
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 ->
|
) { contentPadding ->
|
||||||
TimerScreen(
|
NavDisplay(
|
||||||
uiState = uiState,
|
backStack = backStack,
|
||||||
showBrandTitle = showBrandTitle,
|
onBack = { backStack.removeLastOrNull() },
|
||||||
progress = { progress },
|
entryProvider = entryProvider {
|
||||||
resetTimer = viewModel::resetTimer,
|
entry<Screen.Timer> {
|
||||||
skipTimer = viewModel::skipTimer,
|
TimerScreen(
|
||||||
toggleTimer = viewModel::toggleTimer,
|
uiState = uiState,
|
||||||
modifier = modifier.padding(
|
showBrandTitle = showBrandTitle,
|
||||||
start = contentPadding.calculateStartPadding(layoutDirection),
|
progress = { progress },
|
||||||
end = contentPadding.calculateEndPadding(layoutDirection),
|
resetTimer = viewModel::resetTimer,
|
||||||
bottom = contentPadding.calculateBottomPadding()
|
skipTimer = viewModel::skipTimer,
|
||||||
)
|
toggleTimer = viewModel::toggleTimer,
|
||||||
|
modifier = modifier.padding(
|
||||||
|
start = contentPadding.calculateStartPadding(layoutDirection),
|
||||||
|
end = contentPadding.calculateEndPadding(layoutDirection),
|
||||||
|
bottom = contentPadding.calculateBottomPadding()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
entry<Screen.Settings> {
|
||||||
|
Text("Settings")
|
||||||
|
}
|
||||||
|
|
||||||
|
entry<Screen.Stats> {
|
||||||
|
Text("Stats")
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
23
app/src/main/java/org/nsh07/pomodoro/ui/Screen.kt
Normal file
23
app/src/main/java/org/nsh07/pomodoro/ui/Screen.kt
Normal file
@@ -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
|
||||||
|
)
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:autoMirrored="true"
|
|
||||||
android:tint="#000000"
|
android:tint="#000000"
|
||||||
android:viewportWidth="960"
|
android:viewportWidth="960"
|
||||||
android:viewportHeight="960">
|
android:viewportHeight="960">
|
||||||
|
|
||||||
<path
|
<path
|
||||||
android:fillColor="@android:color/white"
|
android:fillColor="@android:color/white"
|
||||||
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-94ZM482,620q58,0 99,-41t41,-99q0,-58 -41,-99t-99,-41q-59,0 -99.5,41T342,480q0,58 40.5,99t99.5,41Z" />
|
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" />
|
||||||
|
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
10
app/src/main/res/drawable/settings_filled.xml
Normal file
10
app/src/main/res/drawable/settings_filled.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="#000000"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
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-94ZM482,620q58,0 99,-41t41,-99q0,-58 -41,-99t-99,-41q-59,0 -99.5,41T342,480q0,58 40.5,99t99.5,41Z" />
|
||||||
|
</vector>
|
||||||
@@ -3,4 +3,5 @@ plugins {
|
|||||||
alias(libs.plugins.android.application) apply false
|
alias(libs.plugins.android.application) apply false
|
||||||
alias(libs.plugins.kotlin.android) apply false
|
alias(libs.plugins.kotlin.android) apply false
|
||||||
alias(libs.plugins.kotlin.compose) apply false
|
alias(libs.plugins.kotlin.compose) apply false
|
||||||
|
alias(libs.plugins.kotlin.serialization) apply false
|
||||||
}
|
}
|
||||||
@@ -7,11 +7,15 @@ junitVersion = "1.2.1"
|
|||||||
espressoCore = "3.6.1"
|
espressoCore = "3.6.1"
|
||||||
lifecycleRuntimeKtx = "2.9.1"
|
lifecycleRuntimeKtx = "2.9.1"
|
||||||
activityCompose = "1.10.1"
|
activityCompose = "1.10.1"
|
||||||
composeBom = "2025.06.01"
|
composeBom = "2025.06.02"
|
||||||
|
navigation3Runtime = "1.0.0-alpha05"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
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 = { 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" }
|
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||||
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
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" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||||
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", 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"}
|
||||||
|
|||||||
Reference in New Issue
Block a user