From 8ad960cba8d8d135ac70f28c1ff3e1208b451ec2 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 22 Oct 2025 15:18:26 -0300 Subject: [PATCH] Fix issues with onboarding hints Also use opportunity to re-format Main.kt --- gradle/libs.versions.toml | 2 +- next/src/main/kotlin/org/fdroid/ui/Main.kt | 422 +++++++++++---------- 2 files changed, 215 insertions(+), 209 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a830b2841..28d7108f5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,7 +21,7 @@ glide = "5.0.5" glideCompose = "1.0.0-beta08" coilCompose = "3.3.0" molecule = "2.2.0" -hints = "1.1.1" +hints = "2.0.1" androidxCoreKtx = "1.17.0" androidxAppcompat = "1.7.1" diff --git a/next/src/main/kotlin/org/fdroid/ui/Main.kt b/next/src/main/kotlin/org/fdroid/ui/Main.kt index 938f2e593..982405b44 100644 --- a/next/src/main/kotlin/org/fdroid/ui/Main.kt +++ b/next/src/main/kotlin/org/fdroid/ui/Main.kt @@ -18,12 +18,14 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator +import androidx.navigation3.runtime.NavEntry import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.entryProvider import androidx.navigation3.runtime.rememberNavBackStack import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator import androidx.navigation3.ui.NavDisplay import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_MEDIUM_LOWER_BOUND +import com.viktormykhailiv.compose.hints.HintHost import org.fdroid.R import org.fdroid.database.AppListSortOrder import org.fdroid.fdroid.ui.theme.FDroidContent @@ -75,215 +77,219 @@ fun Main(onListeningForIntent: () -> Unit = {}) { windowAdaptiveInfo.windowSizeClass.isWidthAtLeastBreakpoint(WIDTH_DP_MEDIUM_LOWER_BOUND) } val listDetailStrategy = rememberListDetailSceneStrategy(directive = directive) - FDroidContent { - NavDisplay( - backStack = backStack, - sceneStrategy = listDetailStrategy, - entryDecorators = listOf( - rememberSaveableStateHolderNavEntryDecorator(), - rememberViewModelStoreNavEntryDecorator(), - ), - entryProvider = entryProvider { - entry( - metadata = ListDetailSceneStrategy.listPane("appdetails") { - Text(stringResource(R.string.no_app_selected)) - }, - ) { - val viewModel = hiltViewModel() - val numUpdates = viewModel.numUpdates.collectAsStateWithLifecycle(0).value - Discover( - discoverModel = viewModel.discoverModel.collectAsStateWithLifecycle().value, - onListTap = { - backStack.add(NavigationKey.AppList(it)) - }, - onAppTap = { - backStack.add(NavigationKey.AppDetails(it.packageName)) - }, - onNav = { backStack.add(it) }, - numUpdates = numUpdates, - isBigScreen = isBigScreen, - onSearch = viewModel::search, - onSearchCleared = viewModel::onSearchCleared, - modifier = Modifier, - ) - } - entry( - metadata = ListDetailSceneStrategy.listPane("appdetails") { - Text(stringResource(R.string.no_app_selected)) - }, - ) { - val myAppsViewModel = hiltViewModel() - val myAppsInfo = object : MyAppsInfo { - override val model = - myAppsViewModel.myAppsModel.collectAsStateWithLifecycle().value - override fun refresh() = myAppsViewModel.refresh() - override fun updateAll() = myAppsViewModel.updateAll() - override fun changeSortOrder(sort: AppListSortOrder) = - myAppsViewModel.changeSortOrder(sort) - - override fun search(query: String) = myAppsViewModel.search(query) - override fun confirmAppInstall( - packageName: String, - state: InstallState.UserConfirmationNeeded, - ) = myAppsViewModel.confirmAppInstall(packageName, state) - } - MyApps( - myAppsInfo = myAppsInfo, - currentPackageName = if (isBigScreen) { - (backStack.last() as? NavigationKey.AppDetails)?.packageName - } else null, - onAppItemClick = { - backStack.add(NavigationKey.AppDetails(it)) - }, - onNav = { backStack.add(it) }, - isBigScreen = isBigScreen, - ) - } - entry( - metadata = ListDetailSceneStrategy.detailPane("appdetails") - ) { - val appDetailsViewModel = hiltViewModel() - LaunchedEffect(it.packageName) { - appDetailsViewModel.setAppDetails(it.packageName) - } - AppDetails( - item = appDetailsViewModel.appDetails.collectAsStateWithLifecycle().value, - onNav = { navKey -> backStack.add(navKey) }, - onBackNav = if (isBigScreen) null else { - { backStack.removeLastOrNull() } - }, - modifier = Modifier, - ) - } - entry( - metadata = ListDetailSceneStrategy.listPane("appdetails") { - Text(stringResource(R.string.no_app_selected)) - }, - ) { - val appListViewModel = hiltViewModel() - LaunchedEffect(it.type) { - appListViewModel.load(it.type) - } - val appListInfo = object : AppListInfo { - override val model = - appListViewModel.appListModel.collectAsStateWithLifecycle().value - override val actions: AppListActions = appListViewModel - override val list: AppListType = - appListViewModel.currentList.collectAsStateWithLifecycle().value - override val showFilters: Boolean = - appListViewModel.showFilters.collectAsStateWithLifecycle().value - override val showOnboarding: Boolean = - appListViewModel.showOnboarding.collectAsStateWithLifecycle().value - } - AppList( - appListInfo = appListInfo, - currentPackageName = if (isBigScreen) { - (backStack.last() as? NavigationKey.AppDetails)?.packageName - } else null, - onBackClicked = { backStack.removeLastOrNull() }, - modifier = Modifier, - ) { packageName -> - backStack.add(NavigationKey.AppDetails(packageName)) - } - } - entry( - metadata = ListDetailSceneStrategy.listPane("repos") { - Text(text = stringResource(R.string.no_repository_selected)) - }, - ) { - val viewModel = hiltViewModel() - val info = object : RepositoryInfo { - override val model: RepositoryModel = - viewModel.model.collectAsStateWithLifecycle().value - - override val currentRepositoryId: Long? = if (isBigScreen) { - (backStack.last() as? NavigationKey.RepoDetails)?.repoId - } else null - - override fun onOnboardingSeen() = viewModel.onOnboardingSeen() - - override fun onRepositorySelected(repositoryItem: RepositoryItem) { - backStack.add(NavigationKey.RepoDetails(repositoryItem.repoId)) - } - - override fun onRepositoryEnabled(repoId: Long, enabled: Boolean) = - viewModel.onRepositoryEnabled(repoId, enabled) - - override fun onAddRepo() { - backStack.add(NavigationKey.AddRepo()) - } - - override fun onRepositoryMoved(fromRepoId: Long, toRepoId: Long) = - viewModel.onRepositoriesMoved(fromRepoId, toRepoId) - - override fun onRepositoriesFinishedMoving( - fromRepoId: Long, - toRepoId: Long, - ) = viewModel.onRepositoriesFinishedMoving(fromRepoId, toRepoId) - } - Repositories(info) { - backStack.removeLastOrNull() - } - } - entry( - metadata = ListDetailSceneStrategy.detailPane("repos") - ) { navKey -> - val viewModel = hiltViewModel() - LaunchedEffect(navKey) { - viewModel.setRepoId(navKey.repoId) - } - RepoDetails( - info = object : RepoDetailsInfo { - override val model = viewModel.model.collectAsStateWithLifecycle().value - override val actions = viewModel - }, - onShowAppsClicked = { title, repoId -> - val type = AppListType.Repository(title, repoId) - backStack.add(NavigationKey.AppList(type)) - }, - ) { - backStack.removeLastOrNull() - } - } - entry { navKey -> - val viewModel = hiltViewModel() - LaunchedEffect(navKey) { - if (navKey.uri != null) { - viewModel.onFetchRepo(navKey.uri) - } - } - AddRepo( - state = viewModel.state.collectAsStateWithLifecycle().value, - onFetchRepo = viewModel::onFetchRepo, - onAddRepo = viewModel::addFetchedRepository, - onExistingRepo = { repoId -> - backStack.removeLastOrNull() - backStack.add(NavigationKey.RepoDetails(repoId)) - }, - onRepoAdded = { title, repoId -> - backStack.removeLastOrNull() - backStack.add(NavigationKey.RepoDetails(repoId)) - val type = AppListType.Repository(title, repoId) - backStack.add(NavigationKey.AppList(type)) - }, - onBackClicked = { backStack.removeLastOrNull() }, - ) - } - entry(NavigationKey.Settings) { - val viewModel = hiltViewModel() - Settings( - onSaveLogcat = { - viewModel.onSaveLogcat(it) - backStack.removeLastOrNull() - }, - onBackClicked = { backStack.removeLastOrNull() }, - ) - } - entry(NavigationKey.About) { - About { backStack.removeLastOrNull() } - } + val entryProvider: (NavKey) -> NavEntry = entryProvider { + entry( + metadata = ListDetailSceneStrategy.listPane("appdetails") { + Text(stringResource(R.string.no_app_selected)) }, - ) + ) { + val viewModel = hiltViewModel() + val numUpdates = viewModel.numUpdates.collectAsStateWithLifecycle(0).value + Discover( + discoverModel = viewModel.discoverModel.collectAsStateWithLifecycle().value, + onListTap = { + backStack.add(NavigationKey.AppList(it)) + }, + onAppTap = { + backStack.add(NavigationKey.AppDetails(it.packageName)) + }, + onNav = { backStack.add(it) }, + numUpdates = numUpdates, + isBigScreen = isBigScreen, + onSearch = viewModel::search, + onSearchCleared = viewModel::onSearchCleared, + modifier = Modifier, + ) + } + entry( + metadata = ListDetailSceneStrategy.listPane("appdetails") { + Text(stringResource(R.string.no_app_selected)) + }, + ) { + val myAppsViewModel = hiltViewModel() + val myAppsInfo = object : MyAppsInfo { + override val model = myAppsViewModel.myAppsModel.collectAsStateWithLifecycle().value + + override fun refresh() = myAppsViewModel.refresh() + override fun updateAll() = myAppsViewModel.updateAll() + override fun changeSortOrder(sort: AppListSortOrder) = + myAppsViewModel.changeSortOrder(sort) + + override fun search(query: String) = myAppsViewModel.search(query) + override fun confirmAppInstall( + packageName: String, + state: InstallState.UserConfirmationNeeded, + ) = myAppsViewModel.confirmAppInstall(packageName, state) + } + MyApps( + myAppsInfo = myAppsInfo, + currentPackageName = if (isBigScreen) { + (backStack.last() as? NavigationKey.AppDetails)?.packageName + } else null, + onAppItemClick = { + backStack.add(NavigationKey.AppDetails(it)) + }, + onNav = { backStack.add(it) }, + isBigScreen = isBigScreen, + ) + } + entry( + metadata = ListDetailSceneStrategy.detailPane("appdetails") + ) { + val appDetailsViewModel = hiltViewModel() + LaunchedEffect(it.packageName) { + appDetailsViewModel.setAppDetails(it.packageName) + } + AppDetails( + item = appDetailsViewModel.appDetails.collectAsStateWithLifecycle().value, + onNav = { navKey -> backStack.add(navKey) }, + onBackNav = if (isBigScreen) null else { + { backStack.removeLastOrNull() } + }, + modifier = Modifier, + ) + } + entry( + metadata = ListDetailSceneStrategy.listPane("appdetails") { + Text(stringResource(R.string.no_app_selected)) + }, + ) { + val appListViewModel = hiltViewModel() + LaunchedEffect(it.type) { + appListViewModel.load(it.type) + } + val appListInfo = object : AppListInfo { + override val model = + appListViewModel.appListModel.collectAsStateWithLifecycle().value + override val actions: AppListActions = appListViewModel + override val list: AppListType = + appListViewModel.currentList.collectAsStateWithLifecycle().value + override val showFilters: Boolean = + appListViewModel.showFilters.collectAsStateWithLifecycle().value + override val showOnboarding: Boolean = + appListViewModel.showOnboarding.collectAsStateWithLifecycle().value + } + AppList( + appListInfo = appListInfo, + currentPackageName = if (isBigScreen) { + (backStack.last() as? NavigationKey.AppDetails)?.packageName + } else null, + onBackClicked = { backStack.removeLastOrNull() }, + modifier = Modifier, + ) { packageName -> + backStack.add(NavigationKey.AppDetails(packageName)) + } + } + entry( + metadata = ListDetailSceneStrategy.listPane("repos") { + Text(text = stringResource(R.string.no_repository_selected)) + }, + ) { + val viewModel = hiltViewModel() + val info = object : RepositoryInfo { + override val model: RepositoryModel = + viewModel.model.collectAsStateWithLifecycle().value + + override val currentRepositoryId: Long? = if (isBigScreen) { + (backStack.last() as? NavigationKey.RepoDetails)?.repoId + } else null + + override fun onOnboardingSeen() = viewModel.onOnboardingSeen() + + override fun onRepositorySelected(repositoryItem: RepositoryItem) { + backStack.add(NavigationKey.RepoDetails(repositoryItem.repoId)) + } + + override fun onRepositoryEnabled(repoId: Long, enabled: Boolean) = + viewModel.onRepositoryEnabled(repoId, enabled) + + override fun onAddRepo() { + backStack.add(NavigationKey.AddRepo()) + } + + override fun onRepositoryMoved(fromRepoId: Long, toRepoId: Long) = + viewModel.onRepositoriesMoved(fromRepoId, toRepoId) + + override fun onRepositoriesFinishedMoving( + fromRepoId: Long, + toRepoId: Long, + ) = viewModel.onRepositoriesFinishedMoving(fromRepoId, toRepoId) + } + Repositories(info) { + backStack.removeLastOrNull() + } + } + entry( + metadata = ListDetailSceneStrategy.detailPane("repos") + ) { navKey -> + val viewModel = hiltViewModel() + LaunchedEffect(navKey) { + viewModel.setRepoId(navKey.repoId) + } + RepoDetails( + info = object : RepoDetailsInfo { + override val model = + viewModel.model.collectAsStateWithLifecycle().value + override val actions = viewModel + }, + onShowAppsClicked = { title, repoId -> + val type = AppListType.Repository(title, repoId) + backStack.add(NavigationKey.AppList(type)) + }, + ) { + backStack.removeLastOrNull() + } + } + entry { navKey -> + val viewModel = hiltViewModel() + LaunchedEffect(navKey) { + if (navKey.uri != null) { + viewModel.onFetchRepo(navKey.uri) + } + } + AddRepo( + state = viewModel.state.collectAsStateWithLifecycle().value, + onFetchRepo = viewModel::onFetchRepo, + onAddRepo = viewModel::addFetchedRepository, + onExistingRepo = { repoId -> + backStack.removeLastOrNull() + backStack.add(NavigationKey.RepoDetails(repoId)) + }, + onRepoAdded = { title, repoId -> + backStack.removeLastOrNull() + backStack.add(NavigationKey.RepoDetails(repoId)) + val type = AppListType.Repository(title, repoId) + backStack.add(NavigationKey.AppList(type)) + }, + onBackClicked = { backStack.removeLastOrNull() }, + ) + } + entry(NavigationKey.Settings) { + val viewModel = hiltViewModel() + Settings( + onSaveLogcat = { + viewModel.onSaveLogcat(it) + backStack.removeLastOrNull() + }, + onBackClicked = { backStack.removeLastOrNull() }, + ) + } + entry(NavigationKey.About) { + About { backStack.removeLastOrNull() } + } + } + FDroidContent { + HintHost { + NavDisplay( + backStack = backStack, + sceneStrategy = listDetailStrategy, + entryDecorators = listOf( + rememberSaveableStateHolderNavEntryDecorator(), + rememberViewModelStoreNavEntryDecorator(), + ), + entryProvider = entryProvider, + ) + } } }