voyager -> androidx.compose.navigation

This commit is contained in:
2025-03-30 04:32:29 +03:00
parent 8e3d822a56
commit 885ed5a018
17 changed files with 442 additions and 384 deletions
+8 -3
View File
@@ -60,8 +60,7 @@ kotlin {
implementation(compose.material3) implementation(compose.material3)
implementation(compose.materialIconsExtended) implementation(compose.materialIconsExtended)
implementation(compose.components.resources) implementation(compose.components.resources)
implementation(libs.voyager.navigator) implementation(libs.androidx.navigation.compose)
implementation(libs.voyager.transitions)
implementation(libs.coil) implementation(libs.coil)
implementation(libs.coil.network.ktor) implementation(libs.coil.network.ktor)
implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.coroutines.core)
@@ -165,7 +164,13 @@ android {
getByName("release") { getByName("release") {
signingConfig = signingConfigs.getByName("release") signingConfig = signingConfigs.getByName("release")
isMinifyEnabled = false isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
} }
} }
compileOptions { compileOptions {
@@ -1,6 +1,6 @@
package dev.meloda.overseerr.model package dev.meloda.overseerr.model
actual class Platform actual constructor() { internal actual class Platform actual constructor() {
actual val name: String actual val name: String
get() = "Android" get() = "Android"
} }
@@ -5,6 +5,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.WindowInsetsControllerCompat
import androidx.navigation.NavController
@Composable @Composable
internal actual fun SystemAppearance(isDark: Boolean) { internal actual fun SystemAppearance(isDark: Boolean) {
@@ -17,3 +18,7 @@ internal actual fun SystemAppearance(isDark: Boolean) {
} }
} }
} }
@Composable
internal actual fun NavigationSettings(navController: NavController) {
}
@@ -7,11 +7,16 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import cafe.adriel.voyager.navigator.Navigator import androidx.navigation.compose.NavHost
import cafe.adriel.voyager.transitions.FadeTransition import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import dev.meloda.overseerr.screens.login.presentation.LoginScreen
import dev.meloda.overseerr.screens.main.MainScreen import dev.meloda.overseerr.screens.main.MainScreen
import dev.meloda.overseerr.screens.requests.presentation.RequestsScreen
import dev.meloda.overseerr.screens.url.presentation.UrlScreen
import dev.meloda.overseerr.settings.SettingsController import dev.meloda.overseerr.settings.SettingsController
import dev.meloda.overseerr.theme.AppTheme import dev.meloda.overseerr.theme.AppTheme
import dev.meloda.overseerr.theme.NavigationSettings
import io.github.aakira.napier.DebugAntilog import io.github.aakira.napier.DebugAntilog
import io.github.aakira.napier.Napier import io.github.aakira.napier.Napier
import org.koin.compose.KoinContext import org.koin.compose.KoinContext
@@ -32,10 +37,31 @@ internal fun App() = KoinContext {
settingsController.loadAppSettings() settingsController.loadAppSettings()
} }
val navController = rememberNavController()
NavigationSettings(navController)
AppTheme(themeMode = settings.themeMode) { AppTheme(themeMode = settings.themeMode) {
Surface(modifier = Modifier.fillMaxSize()) { Surface(modifier = Modifier.fillMaxSize()) {
Navigator(MainScreen()) { navigator -> NavHost(
FadeTransition(navigator) navController = navController,
startDestination = MainScreen
) {
composable<MainScreen> {
MainScreen(navController)
}
composable<LoginScreen> {
LoginScreen(onBack = navController::popBackStack)
}
composable<RequestsScreen> {
RequestsScreen(onBack = navController::popBackStack)
}
composable<UrlScreen> {
UrlScreen(onBack = navController::popBackStack)
}
} }
} }
} }
@@ -1,5 +1,5 @@
package dev.meloda.overseerr.model package dev.meloda.overseerr.model
expect class Platform() { internal expect class Platform() {
val name: String val name: String
} }
@@ -8,8 +8,11 @@ import androidx.compose.material.icons.automirrored.rounded.ArrowForward
import androidx.compose.material.icons.rounded.Visibility import androidx.compose.material.icons.rounded.Visibility
import androidx.compose.material.icons.rounded.VisibilityOff import androidx.compose.material.icons.rounded.VisibilityOff
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
@@ -18,20 +21,18 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import dev.meloda.overseerr.screens.login.LoginViewModel import dev.meloda.overseerr.screens.login.LoginViewModel
import dev.meloda.overseerr.screens.login.LoginViewModelImpl import dev.meloda.overseerr.screens.login.LoginViewModelImpl
import dev.meloda.overseerr.screens.login.model.LoginScreenState import dev.meloda.overseerr.screens.login.model.LoginScreenState
import kotlinx.serialization.Serializable
import org.koin.compose.viewmodel.koinViewModel import org.koin.compose.viewmodel.koinViewModel
class LoginScreen : Screen { @Serializable
data object LoginScreen
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
override fun Content() { fun LoginScreen(onBack: () -> Unit = {}) {
val navigator = LocalNavigator.currentOrThrow
val viewModel: LoginViewModel = koinViewModel<LoginViewModelImpl>() val viewModel: LoginViewModel = koinViewModel<LoginViewModelImpl>()
val screenState: LoginScreenState by viewModel.screenState.collectAsStateWithLifecycle() val screenState: LoginScreenState by viewModel.screenState.collectAsStateWithLifecycle()
@@ -49,9 +50,7 @@ class LoginScreen : Screen {
Text(text = "Log in") Text(text = "Log in")
}, },
navigationIcon = { navigationIcon = {
IconButton( IconButton(onClick = onBack) {
onClick = navigator::pop
) {
Icon( Icon(
imageVector = Icons.AutoMirrored.Rounded.ArrowBack, imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
contentDescription = null contentDescription = null
@@ -130,5 +129,4 @@ class LoginScreen : Screen {
} }
} }
} }
}
} }
@@ -10,26 +10,24 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import cafe.adriel.voyager.core.screen.Screen import androidx.navigation.NavController
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import dev.meloda.overseerr.screens.login.presentation.LoginScreen import dev.meloda.overseerr.screens.login.presentation.LoginScreen
import dev.meloda.overseerr.screens.requests.presentation.RequestsScreen import dev.meloda.overseerr.screens.requests.presentation.RequestsScreen
import dev.meloda.overseerr.screens.url.presentation.UrlScreen import dev.meloda.overseerr.screens.url.presentation.UrlScreen
import dev.meloda.overseerr.settings.SettingsController import dev.meloda.overseerr.settings.SettingsController
import dev.meloda.overseerr.settings.model.ThemeMode import dev.meloda.overseerr.settings.model.ThemeMode
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
import org.koin.compose.koinInject import org.koin.compose.koinInject
class MainScreen : Screen { @Serializable
data object MainScreen
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
override fun Content() { fun MainScreen(navController: NavController) {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val navigator = LocalNavigator.currentOrThrow
val settingsController: SettingsController = koinInject() val settingsController: SettingsController = koinInject()
val settings by settingsController.settings.collectAsStateWithLifecycle() val settings by settingsController.settings.collectAsStateWithLifecycle()
@@ -69,26 +67,25 @@ class MainScreen : Screen {
horizontalArrangement = Arrangement.spacedBy(16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp),
) { ) {
Button( Button(
onClick = { navigator.push(UrlScreen()) }, onClick = { navController.navigate(UrlScreen) },
modifier = Modifier.weight(0.3f) modifier = Modifier.weight(0.3f)
) { ) {
Text(text = "Url") Text(text = "Url")
} }
Button( Button(
onClick = { navigator.push(LoginScreen()) }, onClick = { navController.navigate(LoginScreen) },
modifier = Modifier.weight(0.3f) modifier = Modifier.weight(0.3f)
) { ) {
Text(text = "Login") Text(text = "Login")
} }
Button( Button(
onClick = { navigator.push(RequestsScreen()) }, onClick = { navController.navigate(RequestsScreen) },
modifier = Modifier.weight(0.3f) modifier = Modifier.weight(0.3f)
) { ) {
Text(text = "Requests") Text(text = "Requests")
} }
} }
} }
}
} }
@@ -12,35 +12,40 @@ import androidx.compose.material3.*
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults.Indicator import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults.Indicator
import androidx.compose.material3.pulltorefresh.pullToRefresh import androidx.compose.material3.pulltorefresh.pullToRefresh
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.* import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import cafe.adriel.voyager.core.screen.Screen import dev.chrisbanes.haze.HazeState
import cafe.adriel.voyager.navigator.LocalNavigator import dev.chrisbanes.haze.hazeEffect
import cafe.adriel.voyager.navigator.currentOrThrow import dev.chrisbanes.haze.hazeSource
import dev.chrisbanes.haze.*
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import dev.chrisbanes.haze.materials.HazeMaterials import dev.chrisbanes.haze.materials.HazeMaterials
import dev.meloda.overseerr.screens.requests.RequestsViewModel import dev.meloda.overseerr.screens.requests.RequestsViewModel
import dev.meloda.overseerr.screens.requests.RequestsViewModelImpl import dev.meloda.overseerr.screens.requests.RequestsViewModelImpl
import dev.meloda.overseerr.screens.requests.model.RequestsScreenState import dev.meloda.overseerr.screens.requests.model.RequestsScreenState
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.serialization.Serializable
import org.koin.compose.viewmodel.koinViewModel import org.koin.compose.viewmodel.koinViewModel
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
class RequestsScreen : Screen { @Serializable
data object RequestsScreen
@OptIn( @OptIn(
ExperimentalMaterial3Api::class, ExperimentalMaterial3Api::class,
ExperimentalHazeMaterialsApi::class, ExperimentalMaterial3Api::class ExperimentalHazeMaterialsApi::class
) )
@Composable @Composable
override fun Content() { fun RequestsScreen(
val navigator = LocalNavigator.currentOrThrow onBack: () -> Unit = {}
) {
val viewModel: RequestsViewModel = koinViewModel<RequestsViewModelImpl>() val viewModel: RequestsViewModel = koinViewModel<RequestsViewModelImpl>()
val screenState: RequestsScreenState by viewModel.screenState.collectAsStateWithLifecycle() val screenState: RequestsScreenState by viewModel.screenState.collectAsStateWithLifecycle()
@@ -67,7 +72,7 @@ class RequestsScreen : Screen {
TopAppBar( TopAppBar(
title = { Text(text = "Requests") }, title = { Text(text = "Requests") },
navigationIcon = { navigationIcon = {
IconButton(onClick = navigator::pop) { IconButton(onClick = onBack) {
Icon( Icon(
imageVector = Icons.AutoMirrored.Rounded.ArrowBack, imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
contentDescription = null contentDescription = null
@@ -168,5 +173,4 @@ class RequestsScreen : Screen {
} }
} }
} }
}
} }
@@ -13,19 +13,17 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import dev.meloda.overseerr.screens.url.UrlViewModel import dev.meloda.overseerr.screens.url.UrlViewModel
import dev.meloda.overseerr.screens.url.UrlViewModelImpl import dev.meloda.overseerr.screens.url.UrlViewModelImpl
import kotlinx.serialization.Serializable
import org.koin.compose.viewmodel.koinViewModel import org.koin.compose.viewmodel.koinViewModel
class UrlScreen : Screen { @Serializable
data object UrlScreen
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
override fun Content() { fun UrlScreen(onBack: () -> Unit = {}) {
val navigator = LocalNavigator.currentOrThrow
val viewModel: UrlViewModel = koinViewModel<UrlViewModelImpl>() val viewModel: UrlViewModel = koinViewModel<UrlViewModelImpl>()
val screenState by viewModel.screenState.collectAsStateWithLifecycle() val screenState by viewModel.screenState.collectAsStateWithLifecycle()
@@ -35,7 +33,7 @@ class UrlScreen : Screen {
TopAppBar( TopAppBar(
title = { Text(text = "Url") }, title = { Text(text = "Url") },
navigationIcon = { navigationIcon = {
IconButton(onClick = navigator::pop) { IconButton(onClick = onBack) {
Icon( Icon(
imageVector = Icons.AutoMirrored.Rounded.ArrowBack, imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
contentDescription = null contentDescription = null
@@ -107,5 +105,4 @@ class UrlScreen : Screen {
} }
} }
} }
}
} }
@@ -6,6 +6,7 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.darkColorScheme import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.navigation.NavController
import dev.meloda.overseerr.settings.model.ThemeMode import dev.meloda.overseerr.settings.model.ThemeMode
internal val LocalThemeIsDark = compositionLocalOf { mutableStateOf(true) } internal val LocalThemeIsDark = compositionLocalOf { mutableStateOf(true) }
@@ -36,3 +37,6 @@ internal fun AppTheme(
@Composable @Composable
internal expect fun SystemAppearance(isDark: Boolean) internal expect fun SystemAppearance(isDark: Boolean)
@Composable
internal expect fun NavigationSettings(navController: NavController)
@@ -1,6 +1,6 @@
package dev.meloda.overseerr.model package dev.meloda.overseerr.model
actual class Platform actual constructor() { internal actual class Platform actual constructor() {
actual val name: String actual val name: String
get() = "iOS" get() = "iOS"
} }
@@ -2,6 +2,7 @@ package dev.meloda.overseerr.theme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.navigation.NavController
import platform.UIKit.UIApplication import platform.UIKit.UIApplication
import platform.UIKit.UIStatusBarStyleDarkContent import platform.UIKit.UIStatusBarStyleDarkContent
import platform.UIKit.UIStatusBarStyleLightContent import platform.UIKit.UIStatusBarStyleLightContent
@@ -15,3 +16,7 @@ internal actual fun SystemAppearance(isDark: Boolean) {
) )
} }
} }
@Composable
internal actual fun NavigationSettings(navController: NavController) {
}
@@ -1,6 +1,6 @@
package dev.meloda.overseerr.model package dev.meloda.overseerr.model
actual class Platform actual constructor() { internal actual class Platform actual constructor() {
actual val name: String actual val name: String
get() = "JVM" get() = "JVM"
} }
@@ -1,7 +1,12 @@
package dev.meloda.overseerr.theme package dev.meloda.overseerr.theme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.navigation.NavController
@Composable @Composable
internal actual fun SystemAppearance(isDark: Boolean) { internal actual fun SystemAppearance(isDark: Boolean) {
} }
@Composable
internal actual fun NavigationSettings(navController: NavController) {
}
@@ -1,5 +1,5 @@
package dev.meloda.overseerr.model package dev.meloda.overseerr.model
actual class Platform actual constructor() { internal actual class Platform actual constructor() {
actual val name: String = "JS" actual val name: String = "JS"
} }
@@ -1,7 +1,20 @@
package dev.meloda.overseerr.theme package dev.meloda.overseerr.theme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.navigation.ExperimentalBrowserHistoryApi
import androidx.navigation.NavController
import androidx.navigation.bindToNavigation
import kotlinx.browser.window
@Composable @Composable
internal actual fun SystemAppearance(isDark: Boolean) { internal actual fun SystemAppearance(isDark: Boolean) {
} }
@OptIn(ExperimentalBrowserHistoryApi::class)
@Composable
internal actual fun NavigationSettings(navController: NavController) {
LaunchedEffect(navController) {
window.bindToNavigation(navController)
}
}
+2 -3
View File
@@ -6,7 +6,6 @@ agp = "8.7.3"
androidx-lifecycle = "2.8.4" androidx-lifecycle = "2.8.4"
androidx-activity-compose = "1.10.1" androidx-activity-compose = "1.10.1"
androidx-uitest = "1.7.8" androidx-uitest = "1.7.8"
voyager = "1.1.0-beta03"
coil = "3.0.4" coil = "3.0.4"
kotlinx-coroutines = "1.10.1" kotlinx-coroutines = "1.10.1"
ktor = "3.0.1" ktor = "3.0.1"
@@ -17,6 +16,7 @@ haze = "1.5.2"
kstore = "0.9.1" kstore = "0.9.1"
appdirs = "1.2.2" appdirs = "1.2.2"
napier = "2.7.1" napier = "2.7.1"
androidx-navigation-compose = "2.9.0-alpha15"
[libraries] [libraries]
@@ -24,10 +24,9 @@ androidx-activityCompose = { module = "androidx.activity:activity-compose", vers
androidx-lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "viewmodel-compose" } androidx-lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "viewmodel-compose" }
androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" } androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" }
androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
androidx-navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "androidx-navigation-compose" }
androidx-uitest-testManifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "androidx-uitest" } androidx-uitest-testManifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "androidx-uitest" }
androidx-uitest-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "androidx-uitest" } androidx-uitest-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "androidx-uitest" }
voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" }
voyager-transitions = { module = "cafe.adriel.voyager:voyager-transitions", version.ref = "voyager" }
coil = { module = "io.coil-kt.coil3:coil-compose-core", version.ref = "coil" } coil = { module = "io.coil-kt.coil3:coil-compose-core", version.ref = "coil" }
coil-network-ktor = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil" } coil-network-ktor = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }