mirror of
https://github.com/bbara04/Purefin.git
synced 2026-07-23 19:26:50 +00:00
feat: add exit confirmation dialog on back navigation
This commit is contained in:
@@ -3,6 +3,7 @@ package hu.bbara.purefin.tv
|
|||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
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
|
||||||
@@ -50,6 +51,7 @@ import hu.bbara.purefin.core.jellyfin.JellyfinAuthInterceptor
|
|||||||
import hu.bbara.purefin.core.navigation.NavigationCommand
|
import hu.bbara.purefin.core.navigation.NavigationCommand
|
||||||
import hu.bbara.purefin.core.navigation.NavigationManager
|
import hu.bbara.purefin.core.navigation.NavigationManager
|
||||||
import hu.bbara.purefin.core.navigation.Route
|
import hu.bbara.purefin.core.navigation.Route
|
||||||
|
import hu.bbara.purefin.ui.common.dialog.ExitAppDialog
|
||||||
import hu.bbara.purefin.navigation.LocalNavigationBackStack
|
import hu.bbara.purefin.navigation.LocalNavigationBackStack
|
||||||
import hu.bbara.purefin.navigation.LocalNavigationManager
|
import hu.bbara.purefin.navigation.LocalNavigationManager
|
||||||
import hu.bbara.purefin.ui.screen.login.TvLoginScreen
|
import hu.bbara.purefin.ui.screen.login.TvLoginScreen
|
||||||
@@ -147,6 +149,7 @@ class TvActivity : ComponentActivity() {
|
|||||||
updateViewModel: AppUpdateViewModel = hiltViewModel()
|
updateViewModel: AppUpdateViewModel = hiltViewModel()
|
||||||
) {
|
) {
|
||||||
var sessionLoaded by remember { mutableStateOf(false) }
|
var sessionLoaded by remember { mutableStateOf(false) }
|
||||||
|
var showExitDialog by remember { mutableStateOf(false) }
|
||||||
val isLoggedIn by userSessionRepository.isLoggedIn.collectAsStateWithLifecycle(initialValue = false)
|
val isLoggedIn by userSessionRepository.isLoggedIn.collectAsStateWithLifecycle(initialValue = false)
|
||||||
val availableUpdate by updateViewModel.availableUpdate.collectAsStateWithLifecycle()
|
val availableUpdate by updateViewModel.availableUpdate.collectAsStateWithLifecycle()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@@ -168,6 +171,9 @@ class TvActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!sessionLoaded) {
|
if (!sessionLoaded) {
|
||||||
|
BackHandler {
|
||||||
|
showExitDialog = true
|
||||||
|
}
|
||||||
PurefinWaitingScreen(modifier = Modifier.fillMaxSize())
|
PurefinWaitingScreen(modifier = Modifier.fillMaxSize())
|
||||||
} else if (isLoggedIn) {
|
} else if (isLoggedIn) {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
@@ -179,7 +185,13 @@ class TvActivity : ComponentActivity() {
|
|||||||
LaunchedEffect(navigationManager, backStack) {
|
LaunchedEffect(navigationManager, backStack) {
|
||||||
navigationManager.commands.collect { command ->
|
navigationManager.commands.collect { command ->
|
||||||
when (command) {
|
when (command) {
|
||||||
NavigationCommand.Pop -> if (backStack.size > 1) backStack.removeLastOrNull()
|
NavigationCommand.Pop -> {
|
||||||
|
if (backStack.size > 1) {
|
||||||
|
backStack.removeLastOrNull()
|
||||||
|
} else {
|
||||||
|
showExitDialog = true
|
||||||
|
}
|
||||||
|
}
|
||||||
is NavigationCommand.Navigate -> backStack.add(command.route)
|
is NavigationCommand.Navigate -> backStack.add(command.route)
|
||||||
is NavigationCommand.ReplaceAll -> {
|
is NavigationCommand.ReplaceAll -> {
|
||||||
backStack.clear()
|
backStack.clear()
|
||||||
@@ -189,13 +201,23 @@ class TvActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BackHandler(enabled = backStack.size == 1) {
|
||||||
|
showExitDialog = true
|
||||||
|
}
|
||||||
|
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalNavigationManager provides navigationManager,
|
LocalNavigationManager provides navigationManager,
|
||||||
LocalNavigationBackStack provides backStack.toList()
|
LocalNavigationBackStack provides backStack.toList()
|
||||||
) {
|
) {
|
||||||
NavDisplay(
|
NavDisplay(
|
||||||
backStack = backStack,
|
backStack = backStack,
|
||||||
onBack = { navigationManager.pop() },
|
onBack = {
|
||||||
|
if (backStack.size > 1) {
|
||||||
|
navigationManager.pop()
|
||||||
|
} else {
|
||||||
|
showExitDialog = true
|
||||||
|
}
|
||||||
|
},
|
||||||
modifier = Modifier.fillMaxSize().background(backgroundDark),
|
modifier = Modifier.fillMaxSize().background(backgroundDark),
|
||||||
transitionSpec = {
|
transitionSpec = {
|
||||||
fadeIn(
|
fadeIn(
|
||||||
@@ -229,6 +251,9 @@ class TvActivity : ComponentActivity() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
BackHandler {
|
||||||
|
showExitDialog = true
|
||||||
|
}
|
||||||
TvLoginScreen()
|
TvLoginScreen()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,6 +264,13 @@ class TvActivity : ComponentActivity() {
|
|||||||
onDecline = updateViewModel::declineUpdate
|
onDecline = updateViewModel::declineUpdate
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showExitDialog) {
|
||||||
|
ExitAppDialog(
|
||||||
|
onCloseConfirmed = { finishAndRemoveTask() },
|
||||||
|
onDismiss = { showExitDialog = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package hu.bbara.purefin.ui.screen
|
package hu.bbara.purefin.ui.screen
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Home
|
import androidx.compose.material.icons.outlined.Home
|
||||||
@@ -102,6 +103,10 @@ fun TvAppScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BackHandler(enabled = backStack.size == 1) {
|
||||||
|
navigationManager.pop()
|
||||||
|
}
|
||||||
|
|
||||||
TvNavigationDrawer(
|
TvNavigationDrawer(
|
||||||
destinations = destinations,
|
destinations = destinations,
|
||||||
selectedDestination = selectedDestination,
|
selectedDestination = selectedDestination,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package hu.bbara.purefin
|
|||||||
|
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
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
|
||||||
@@ -51,6 +52,7 @@ import hu.bbara.purefin.navigation.LocalSharedTransitionScope
|
|||||||
import hu.bbara.purefin.core.navigation.NavigationCommand
|
import hu.bbara.purefin.core.navigation.NavigationCommand
|
||||||
import hu.bbara.purefin.core.navigation.NavigationManager
|
import hu.bbara.purefin.core.navigation.NavigationManager
|
||||||
import hu.bbara.purefin.core.navigation.Route
|
import hu.bbara.purefin.core.navigation.Route
|
||||||
|
import hu.bbara.purefin.ui.common.dialog.ExitAppDialog
|
||||||
import hu.bbara.purefin.ui.screen.waiting.PurefinWaitingScreen
|
import hu.bbara.purefin.ui.screen.waiting.PurefinWaitingScreen
|
||||||
import hu.bbara.purefin.ui.screen.login.LoginScreen
|
import hu.bbara.purefin.ui.screen.login.LoginScreen
|
||||||
import hu.bbara.purefin.ui.theme.AppTheme
|
import hu.bbara.purefin.ui.theme.AppTheme
|
||||||
@@ -146,6 +148,7 @@ class PurefinActivity : ComponentActivity() {
|
|||||||
navigationManager: NavigationManager
|
navigationManager: NavigationManager
|
||||||
) {
|
) {
|
||||||
var sessionLoaded by remember { mutableStateOf(false) }
|
var sessionLoaded by remember { mutableStateOf(false) }
|
||||||
|
var showExitDialog by remember { mutableStateOf(false) }
|
||||||
val isLoggedIn by userSessionRepository.isLoggedIn.collectAsStateWithLifecycle(initialValue = false)
|
val isLoggedIn by userSessionRepository.isLoggedIn.collectAsStateWithLifecycle(initialValue = false)
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
@@ -155,11 +158,11 @@ class PurefinActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!sessionLoaded) {
|
if (!sessionLoaded) {
|
||||||
PurefinWaitingScreen(modifier = Modifier.fillMaxSize())
|
BackHandler {
|
||||||
return
|
showExitDialog = true
|
||||||
}
|
}
|
||||||
|
PurefinWaitingScreen(modifier = Modifier.fillMaxSize())
|
||||||
if (isLoggedIn) {
|
} else if (isLoggedIn) {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
val backStack = rememberNavBackStack(Route.Home) as NavBackStack<Route>
|
val backStack = rememberNavBackStack(Route.Home) as NavBackStack<Route>
|
||||||
var homeMediaSharedBoundsKey by remember { mutableStateOf<String?>(null) }
|
var homeMediaSharedBoundsKey by remember { mutableStateOf<String?>(null) }
|
||||||
@@ -171,7 +174,13 @@ class PurefinActivity : ComponentActivity() {
|
|||||||
LaunchedEffect(navigationManager, backStack) {
|
LaunchedEffect(navigationManager, backStack) {
|
||||||
navigationManager.commands.collect { command ->
|
navigationManager.commands.collect { command ->
|
||||||
when (command) {
|
when (command) {
|
||||||
NavigationCommand.Pop -> if (backStack.size > 1) backStack.removeLastOrNull()
|
NavigationCommand.Pop -> {
|
||||||
|
if (backStack.size > 1) {
|
||||||
|
backStack.removeLastOrNull()
|
||||||
|
} else {
|
||||||
|
showExitDialog = true
|
||||||
|
}
|
||||||
|
}
|
||||||
is NavigationCommand.Navigate -> backStack.add(command.route)
|
is NavigationCommand.Navigate -> backStack.add(command.route)
|
||||||
is NavigationCommand.ReplaceAll -> {
|
is NavigationCommand.ReplaceAll -> {
|
||||||
backStack.clear()
|
backStack.clear()
|
||||||
@@ -181,6 +190,10 @@ class PurefinActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BackHandler(enabled = backStack.size == 1) {
|
||||||
|
showExitDialog = true
|
||||||
|
}
|
||||||
|
|
||||||
SharedTransitionLayout {
|
SharedTransitionLayout {
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalNavigationManager provides navigationManager,
|
LocalNavigationManager provides navigationManager,
|
||||||
@@ -193,7 +206,13 @@ class PurefinActivity : ComponentActivity() {
|
|||||||
) {
|
) {
|
||||||
NavDisplay(
|
NavDisplay(
|
||||||
backStack = backStack,
|
backStack = backStack,
|
||||||
onBack = { navigationManager.pop() },
|
onBack = {
|
||||||
|
if (backStack.size > 1) {
|
||||||
|
navigationManager.pop()
|
||||||
|
} else {
|
||||||
|
showExitDialog = true
|
||||||
|
}
|
||||||
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(backgroundDark)
|
.background(backgroundDark)
|
||||||
@@ -232,9 +251,19 @@ class PurefinActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
BackHandler {
|
||||||
|
showExitDialog = true
|
||||||
|
}
|
||||||
LoginScreen(
|
LoginScreen(
|
||||||
modifier = Modifier.semantics { testTagsAsResourceId = true }
|
modifier = Modifier.semantics { testTagsAsResourceId = true }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showExitDialog) {
|
||||||
|
ExitAppDialog(
|
||||||
|
onCloseConfirmed = { finishAndRemoveTask() },
|
||||||
|
onDismiss = { showExitDialog = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package hu.bbara.purefin.ui.screen
|
package hu.bbara.purefin.ui.screen
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
@@ -138,6 +139,10 @@ fun AppScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BackHandler(enabled = backStack.size == 1) {
|
||||||
|
navigationManager.pop()
|
||||||
|
}
|
||||||
|
|
||||||
NavDisplay(
|
NavDisplay(
|
||||||
backStack = backStack,
|
backStack = backStack,
|
||||||
onBack = {
|
onBack = {
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package hu.bbara.purefin.ui.common.dialog
|
||||||
|
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ExitAppDialog(
|
||||||
|
onCloseConfirmed: () -> Unit,
|
||||||
|
onDismiss: () -> Unit
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
title = { Text("Close app?") },
|
||||||
|
text = { Text("Are you sure?") },
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onCloseConfirmed) {
|
||||||
|
Text("Close")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text("Cancel")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user