Fix picker access and finish Wolfskin on root Back
This commit is contained in:
parent
febcded034
commit
9cfe9362ee
11 changed files with 481 additions and 31 deletions
|
|
@ -2,6 +2,8 @@ package com.github.damontecres.wholphin.services
|
|||
|
||||
import androidx.navigation3.runtime.NavBackStack
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import org.acra.ACRA
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
|
@ -15,6 +17,11 @@ class NavigationManager
|
|||
@Inject
|
||||
constructor() {
|
||||
var backStack: MutableList<Destination> = NavBackStack(Destination.Home())
|
||||
private val exits = Channel<Unit>(Channel.CONFLATED)
|
||||
val exitRequests = exits.receiveAsFlow()
|
||||
|
||||
/** Finish the containing task; never expose an older setup/user entry. */
|
||||
fun requestExit() { exits.trySend(Unit) }
|
||||
|
||||
/**
|
||||
* Go to the specified [com.github.damontecres.wholphin.ui.nav.Destination]
|
||||
|
|
@ -41,12 +48,14 @@ class NavigationManager
|
|||
}
|
||||
|
||||
/**
|
||||
* Go to the previous page
|
||||
* Go to the previous page, or exit when there is no parent.
|
||||
*/
|
||||
fun goBack() {
|
||||
synchronized(this) {
|
||||
if (backStack.size > 1) {
|
||||
backStack.removeLastOrNull()
|
||||
} else {
|
||||
requestExit()
|
||||
}
|
||||
}
|
||||
log()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,147 @@
|
|||
package org.justinvisser.wolfskin.tv
|
||||
|
||||
import android.app.Application
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.snapshots.Snapshot
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.key.Key
|
||||
import androidx.compose.ui.semantics.SemanticsActions
|
||||
import androidx.compose.ui.test.*
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.ImageUrlService
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.services.SeerrRatingsService
|
||||
import com.github.damontecres.wholphin.ui.LocalImageUrlService
|
||||
import com.github.damontecres.wholphin.ui.components.LocalSeerrRatingsService
|
||||
import com.github.damontecres.wholphin.ui.main.settings.HomePageSettingsState
|
||||
import com.github.damontecres.wholphin.ui.main.settings.HomeSettingsPage
|
||||
import com.github.damontecres.wholphin.ui.main.settings.HomeSettingsViewModel
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.setup.InstallUpdatePage
|
||||
import com.github.damontecres.wholphin.ui.setup.InstallUpdateState
|
||||
import com.github.damontecres.wholphin.ui.setup.UpdateViewModel
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.justinvisser.wolfskin.design.*
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.Shadows
|
||||
import org.robolectric.annotation.Config
|
||||
import org.robolectric.annotation.GraphicsMode
|
||||
|
||||
/** Real full-screen pages keep their local Back ladder before task-level exit. */
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [34], application = Application::class, qualifiers = "w960dp-h540dp-xhdpi")
|
||||
@GraphicsMode(GraphicsMode.Mode.NATIVE)
|
||||
class WolfskinBackStackTest {
|
||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
||||
private lateinit var activity: ComponentActivity
|
||||
private val navigation = NavigationManager()
|
||||
private val preferences = UserPreferences(AppPreferences.getDefaultInstance(), null)
|
||||
private val shell = ShellState(listOf(ShellPerson("justin", "Justin", "J", true)), "justin") {
|
||||
when (it) {
|
||||
ShellAction.Pop -> navigation.goBack()
|
||||
ShellAction.Exit -> navigation.requestExit()
|
||||
else -> Unit
|
||||
}
|
||||
}.apply { pickerVisible = false; pickerLaunch = false; navFocused = true }
|
||||
|
||||
@Before fun initialize() { activity = rule.activity }
|
||||
|
||||
@Test fun singletonUpdatesBackFinishesInsteadOfBecomingADeadEnd() {
|
||||
navigation.replace(Destination.UpdateApp)
|
||||
val model = updatesModel()
|
||||
rule.setContent {
|
||||
rememberWolfskinBackHandler(shell, navigation)
|
||||
WolfskinTheme { InstallUpdatePage(preferences, viewModel = model) }
|
||||
}
|
||||
rule.onNodeWithText("No release found! Check the update URL.").assertIsDisplayed()
|
||||
back()
|
||||
assertTrue(activity.isFinishing)
|
||||
assertEquals(listOf(Destination.UpdateApp), navigation.backStack.toList())
|
||||
}
|
||||
|
||||
@Test fun pushedUpdatesPopsSettingsThenPickerBeforeRootExit() {
|
||||
navigation.navigateTo(Destination.Wolfskin("settings"))
|
||||
navigation.navigateTo(Destination.UpdateApp)
|
||||
shell.enterDestination("settings")
|
||||
shell.navFocused = false
|
||||
val model = updatesModel()
|
||||
rule.setContent {
|
||||
rememberWolfskinBackHandler(shell, navigation)
|
||||
WolfskinTheme {
|
||||
if (navigation.backStack.last() == Destination.UpdateApp) InstallUpdatePage(preferences, viewModel = model)
|
||||
else ShellSurface(shell) { SettingsList(shell, it) }
|
||||
}
|
||||
}
|
||||
rule.onNodeWithText("No release found! Check the update URL.").assertIsDisplayed()
|
||||
back()
|
||||
assertFalse(activity.isFinishing)
|
||||
assertEquals(listOf(Destination.Home(), Destination.Wolfskin("settings")), navigation.backStack.toList())
|
||||
back()
|
||||
assertFalse(activity.isFinishing)
|
||||
assertTrue(shell.pickerVisible)
|
||||
assertEquals(listOf(Destination.Home()), navigation.backStack.toList())
|
||||
back()
|
||||
assertFalse(shell.pickerVisible)
|
||||
assertTrue(shell.navFocused)
|
||||
assertFalse(activity.isFinishing)
|
||||
back()
|
||||
assertTrue(activity.isFinishing)
|
||||
}
|
||||
|
||||
@Test fun unsavedHomeSettingsPromptsBeforeDiscardCanExitTheTask() {
|
||||
navigation.replace(Destination.HomeSettings)
|
||||
val model = mockk<HomeSettingsViewModel>(relaxed = true)
|
||||
every { model.state } returns MutableStateFlow(HomePageSettingsState.EMPTY.copy(loading = LoadingState.Success))
|
||||
every { model.serverPluginActive } returns MutableStateFlow(false)
|
||||
every { model.settingsChanged } returns MutableStateFlow(true)
|
||||
every { model.navigationManager } returns navigation
|
||||
every { model.discardChanges() } returns Job().apply { complete() }
|
||||
val images = mockk<ImageUrlService>(relaxed = true)
|
||||
val ratings = SeerrRatingsService(mockk(relaxed = true), mockk(relaxed = true))
|
||||
rule.setContent {
|
||||
rememberWolfskinBackHandler(shell, navigation)
|
||||
WolfskinTheme {
|
||||
CompositionLocalProvider(LocalSeerrRatingsService provides ratings, LocalImageUrlService provides images) {
|
||||
HomeSettingsPage(preferences, Modifier.fillMaxSize(), viewModel = model)
|
||||
}
|
||||
}
|
||||
}
|
||||
back()
|
||||
assertFalse("Back must not discard unsaved settings or finish the task", activity.isFinishing)
|
||||
assertEquals(listOf(Destination.HomeSettings), navigation.backStack.toList())
|
||||
val discard = rule.onNodeWithText("Discard").assertIsDisplayed().assertIsEnabled()
|
||||
discard.performSemanticsAction(SemanticsActions.RequestFocus) { it() }
|
||||
discard.assertIsFocused().performKeyInput { pressKey(Key.DirectionCenter) }
|
||||
rule.waitUntil(10_000) {
|
||||
Snapshot.sendApplyNotifications()
|
||||
Shadows.shadowOf(android.os.Looper.getMainLooper()).idle()
|
||||
activity.isFinishing
|
||||
}
|
||||
assertEquals(listOf(Destination.HomeSettings), navigation.backStack.toList())
|
||||
}
|
||||
|
||||
private fun updatesModel(): UpdateViewModel = mockk<UpdateViewModel>(relaxed = true).also { model ->
|
||||
every { model.state } returns MutableStateFlow(InstallUpdateState(loading = LoadingState.Success))
|
||||
every { model.navigationManager } returns navigation
|
||||
every { model.updater.hasPermissions() } returns true
|
||||
}
|
||||
|
||||
private fun back() {
|
||||
rule.runOnIdle { activity.onBackPressedDispatcher.onBackPressed() }
|
||||
rule.mainClock.advanceTimeBy(400)
|
||||
if (!activity.isFinishing) rule.waitForIdle()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
package org.justinvisser.wolfskin.tv
|
||||
|
||||
import android.app.Application
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.runtime.snapshots.Snapshot
|
||||
import androidx.compose.ui.input.key.Key
|
||||
import androidx.compose.ui.test.*
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.data.model.JellyfinServer
|
||||
import com.github.damontecres.wholphin.data.model.JellyfinServerUsers
|
||||
import com.github.damontecres.wholphin.data.model.JellyfinUser
|
||||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.mockk
|
||||
import java.util.UUID
|
||||
import kotlinx.coroutines.cancel
|
||||
import org.junit.After
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.justinvisser.wolfskin.conductarr.MemoryCacheStore
|
||||
import org.justinvisser.wolfskin.design.*
|
||||
import org.justinvisser.wolfskin.tv.read.LibraryReadFacade
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.Shadows
|
||||
import org.robolectric.annotation.Config
|
||||
import org.robolectric.annotation.GraphicsMode
|
||||
|
||||
/** Actual Wolfskin NavDisplay and Android Back finish the activity instead of exposing upstream setup. */
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [34], application = Application::class, qualifiers = "w960dp-h540dp-xhdpi")
|
||||
@GraphicsMode(GraphicsMode.Mode.NATIVE)
|
||||
class WolfskinRootExitTest {
|
||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
||||
private lateinit var activity: ComponentActivity
|
||||
private lateinit var model: WolfskinShellViewModel
|
||||
private lateinit var conductarr: ConductarrSession
|
||||
private val navigation = NavigationManager()
|
||||
private val session = WolfskinSession()
|
||||
private var oldFixtures = false
|
||||
private var oldScreens = true
|
||||
|
||||
@Before fun initialize() {
|
||||
activity = rule.activity
|
||||
ConductarrPreference.initialize(activity)
|
||||
WolfskinScreenPreference.initialize(activity)
|
||||
oldFixtures = ConductarrPreference.fixtures
|
||||
oldScreens = WolfskinScreenPreference.enabled
|
||||
ConductarrPreference.updateFixtures(true)
|
||||
WolfskinScreenPreference.enabled = true
|
||||
}
|
||||
|
||||
@After fun cleanup() {
|
||||
if (::model.isInitialized) model.viewModelScope.cancel()
|
||||
if (::conductarr.isInitialized) conductarr.close()
|
||||
ConductarrPreference.updateFixtures(oldFixtures)
|
||||
WolfskinScreenPreference.enabled = oldScreens
|
||||
}
|
||||
|
||||
@Test fun homeContentThenNavBackFinishesTheActivity() = verifyRoot("home")
|
||||
@Test fun discoverContentThenNavBackFinishesTheActivity() = verifyRoot("discover")
|
||||
@Test fun moviesContentThenNavBackFinishesTheActivity() = verifyRoot("movies")
|
||||
@Test fun showsContentThenNavBackFinishesTheActivity() = verifyRoot("shows")
|
||||
@Test fun animeContentThenNavBackFinishesTheActivity() = verifyRoot("anime")
|
||||
|
||||
@Test fun hardwareBackUsesTheSameContentNavExitLadder() {
|
||||
mount()
|
||||
val shell = session.state!!
|
||||
rule.runOnIdle { shell.pickerVisible = false; shell.pickerLaunch = false }
|
||||
rule.onNodeWithTag("wolfskin-shell").performKeyInput { pressKey(Key.Back) }
|
||||
assertTrue(shell.navFocused)
|
||||
assertFalse(activity.isFinishing)
|
||||
rule.onNodeWithTag("wolfskin-shell").performKeyInput { pressKey(Key.Back) }
|
||||
rule.mainClock.advanceTimeBy(400)
|
||||
assertTrue(activity.isFinishing)
|
||||
assertEquals(listOf(Destination.Home()), navigation.backStack.toList())
|
||||
}
|
||||
|
||||
@Test fun coldPickerBackFinishesWithoutSelectingAnAvatarOrShowingUpstreamUi() {
|
||||
mount()
|
||||
assertTrue(session.state!!.pickerLaunch)
|
||||
back()
|
||||
assertTrue("Back from the cold picker must finish the activity", activity.isFinishing)
|
||||
assertEquals(listOf(Destination.Home()), navigation.backStack.toList())
|
||||
}
|
||||
|
||||
@Test fun reopenedPickerBackDismissesToNavAndTheFollowingBackExits() {
|
||||
mount()
|
||||
val shell = session.state!!
|
||||
rule.runOnIdle { shell.pickerVisible = false; shell.focusNav(shell.tabs.size); shell.key(ShellKey.Ok) }
|
||||
assertTrue(shell.pickerVisible)
|
||||
back()
|
||||
assertFalse(shell.pickerVisible)
|
||||
assertTrue(shell.navFocused)
|
||||
assertFalse(activity.isFinishing)
|
||||
back()
|
||||
assertTrue(activity.isFinishing)
|
||||
}
|
||||
|
||||
@Test fun comingInSheetDismissesBeforeContentNavAndExit() {
|
||||
mount()
|
||||
val shell = session.state!!
|
||||
rule.runOnIdle {
|
||||
shell.pickerVisible = false
|
||||
shell.pickerLaunch = false
|
||||
shell.navFocused = false
|
||||
shell.openRequests()
|
||||
}
|
||||
assertTrue(shell.requestsVisible)
|
||||
back()
|
||||
assertFalse(shell.requestsVisible)
|
||||
assertFalse(shell.navFocused)
|
||||
assertFalse(activity.isFinishing)
|
||||
back()
|
||||
assertTrue(shell.navFocused)
|
||||
assertFalse(activity.isFinishing)
|
||||
back()
|
||||
assertTrue(activity.isFinishing)
|
||||
}
|
||||
|
||||
private fun verifyRoot(route: String) {
|
||||
mount()
|
||||
val shell = session.state!!
|
||||
val destination = if (route == "home") Destination.Home() else Destination.Wolfskin(route)
|
||||
rule.runOnIdle {
|
||||
shell.pickerVisible = false
|
||||
shell.pickerLaunch = false
|
||||
shell.enterDestination(route)
|
||||
navigation.replace(destination)
|
||||
}
|
||||
rule.mainClock.advanceTimeBy(1200)
|
||||
rule.waitForIdle()
|
||||
rule.waitUntil(10_000) {
|
||||
Snapshot.sendApplyNotifications()
|
||||
Shadows.shadowOf(android.os.Looper.getMainLooper()).idle()
|
||||
shell.contentFocus?.hasContent == true &&
|
||||
(route !in LIBRARY_DESTINATIONS || shell.rememberedView(route).library?.status == LibraryStatus.Ready)
|
||||
}
|
||||
rule.runOnIdle { shell.navFocused = false }
|
||||
back()
|
||||
assertFalse("The first Back must not finish $route content", activity.isFinishing)
|
||||
assertTrue("The first Back must move $route content to nav", shell.navFocused)
|
||||
assertEquals(listOf(destination), navigation.backStack.toList())
|
||||
back()
|
||||
assertTrue("Back from $route nav must finish the activity", activity.isFinishing)
|
||||
assertEquals("Exit cannot detour to old Home", listOf(destination), navigation.backStack.toList())
|
||||
}
|
||||
|
||||
private fun mount() {
|
||||
val server = JellyfinServer(UUID.randomUUID(), "Fixture", "https://example.invalid", null)
|
||||
val user = JellyfinUser(id = UUID.randomUUID(), serverId = server.id, name = "Justin", accessToken = null)
|
||||
val repository = mockk<ServerRepository>(relaxed = true)
|
||||
coEvery { repository.serverDao.getServer(server.id) } returns JellyfinServerUsers(server, listOf(user))
|
||||
val libraries = mockk<LibraryReadFacade>(relaxed = true)
|
||||
coEvery { libraries.readDestinations(user.id) } returns emptyMap()
|
||||
conductarr = ConductarrSession(MemoryCacheStore())
|
||||
model = WolfskinShellViewModel(activity, session, repository, navigation, mockk(relaxed = true),
|
||||
mockk(relaxed = true), libraries, WolfskinSeeAllLists(), conductarr, mockk(relaxed = true))
|
||||
val home = HomeFixture(activity).snapshot("justin")
|
||||
rule.setContent {
|
||||
WolfskinApp(server, user, navigation, UserPreferences(AppPreferences.getDefaultInstance(), null), viewModel = model,
|
||||
homeContent = { shell, _, _, modifier -> HomeScreen(shell, home, modifier) {} })
|
||||
}
|
||||
rule.waitUntil(10_000) {
|
||||
Snapshot.sendApplyNotifications()
|
||||
Shadows.shadowOf(android.os.Looper.getMainLooper()).idle()
|
||||
session.state?.people?.size == 3 && model.signedInPerson == "justin"
|
||||
}
|
||||
rule.waitForIdle()
|
||||
}
|
||||
|
||||
private fun back() {
|
||||
rule.runOnIdle { activity.onBackPressedDispatcher.onBackPressed() }
|
||||
rule.mainClock.advanceTimeBy(400)
|
||||
if (!activity.isFinishing) rule.waitForIdle()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
package org.justinvisser.wolfskin.tv
|
||||
|
||||
import android.content.Context
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -201,6 +200,7 @@ class WolfskinShellViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
ShellAction.Pop -> navigation.goBack()
|
||||
ShellAction.Exit -> navigation.requestExit()
|
||||
is ShellAction.HandOff -> navigation.navigateTo(handOffDestination(action.group))
|
||||
is ShellAction.Person -> selectPerson(action.person, server)
|
||||
ShellAction.RetryPeople -> viewModelScope.launch { retryConductarr(server, user) }
|
||||
|
|
@ -271,7 +271,9 @@ fun WolfskinApp(
|
|||
}
|
||||
}
|
||||
val loading = remember(user.id) {
|
||||
ShellState(listOf(ShellPerson(user.name.orEmpty().lowercase(), user.name.orEmpty(), user.name.orEmpty().take(1), false)), user.name.orEmpty().lowercase()).apply { peopleStatus = PeopleStatus.Loading }
|
||||
ShellState(listOf(ShellPerson(user.name.orEmpty().lowercase(), user.name.orEmpty(), user.name.orEmpty().take(1), false)), user.name.orEmpty().lowercase()) {
|
||||
if (it == ShellAction.Exit) navigationManager.requestExit()
|
||||
}.apply { peopleStatus = PeopleStatus.Loading }
|
||||
}
|
||||
val state = viewModel.session.state ?: loading
|
||||
val discover = rememberDiscoverState(
|
||||
|
|
@ -285,6 +287,7 @@ fun WolfskinApp(
|
|||
},
|
||||
)
|
||||
RequestsRefresh(state, viewModel::refreshRequests)
|
||||
val onBack = rememberWolfskinBackHandler(state, navigationManager)
|
||||
WolfskinTheme {
|
||||
CompositionLocalProvider(LocalShellState provides state, LocalDiscoverState provides discover,
|
||||
org.justinvisser.wolfskin.design.LocalScreenRatings provides if (identity?.signedIn == true && !ConductarrPreference.fixtures) remember(identity?.client) { ConductarrRatings(identity!!.client) } else null) {
|
||||
|
|
@ -300,7 +303,7 @@ fun WolfskinApp(
|
|||
}
|
||||
NavDisplay(
|
||||
backStack = navigationManager.backStack,
|
||||
onBack = { if (navigationManager.backStack.last().fullScreen) navigationManager.goBack() else state.key(org.justinvisser.wolfskin.design.ShellKey.Back) },
|
||||
onBack = onBack,
|
||||
entryDecorators = listOf(rememberSaveableStateHolderNavEntryDecorator(), rememberViewModelStoreNavEntryDecorator()),
|
||||
entryProvider = { destination ->
|
||||
NavEntry(destination, contentKey = "${destination}_${server.id}_${user.id}") {
|
||||
|
|
@ -321,7 +324,6 @@ fun WolfskinApp(
|
|||
if (destination.fullScreen) {
|
||||
DestinationContent(destination, preferences, viewModel::clearBackdrop, Modifier.fillMaxSize())
|
||||
} else {
|
||||
BackHandler(enabled = isCurrent) { state.key(org.justinvisser.wolfskin.design.ShellKey.Back) }
|
||||
val upstream = resolveUpstreamDestination(destination, WolfskinScreenPreference.enabled, viewModel.libraryDestinations)
|
||||
if (upstream == null && (route.startsWith("title/") || route.startsWith("person/"))) {
|
||||
val jellyfinIdentity = destination is Destination.MediaItem || android.net.Uri.decode(route.substringAfter('/')).toUUIDOrNull() != null
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
package org.justinvisser.wolfskin.tv
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.findActivity
|
||||
import org.justinvisser.wolfskin.design.ShellKey
|
||||
import org.justinvisser.wolfskin.design.ShellState
|
||||
|
||||
/** Own root Back even when NavDisplay has no parent; child menus/dialogs retain first refusal. */
|
||||
@Composable
|
||||
internal fun rememberWolfskinBackHandler(shell: ShellState, navigation: NavigationManager): () -> Unit {
|
||||
val activity = checkNotNull(LocalContext.current.findActivity()) { "Wolfskin root requires an Activity" }
|
||||
LaunchedEffect(navigation, activity) {
|
||||
navigation.exitRequests.collect { activity.finishAndRemoveTask() }
|
||||
}
|
||||
val onBack: () -> Unit = remember(shell, navigation) {
|
||||
{
|
||||
if (navigation.backStack.lastOrNull()?.fullScreen == true) navigation.goBack()
|
||||
else shell.key(ShellKey.Back)
|
||||
}
|
||||
}
|
||||
BackHandler(onBack = onBack)
|
||||
return onBack
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue