Fix issues with onboarding hints

Also use opportunity to re-format Main.kt
This commit is contained in:
Torsten Grote
2025-10-22 15:18:26 -03:00
parent 198bd20a69
commit 8ad960cba8
2 changed files with 215 additions and 209 deletions

View File

@@ -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"

View File

@@ -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<NavKey>(directive = directive)
FDroidContent {
NavDisplay(
backStack = backStack,
sceneStrategy = listDetailStrategy,
entryDecorators = listOf(
rememberSaveableStateHolderNavEntryDecorator(),
rememberViewModelStoreNavEntryDecorator(),
),
entryProvider = entryProvider {
entry<NavigationKey.Discover>(
metadata = ListDetailSceneStrategy.listPane("appdetails") {
Text(stringResource(R.string.no_app_selected))
},
) {
val viewModel = hiltViewModel<DiscoverViewModel>()
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<NavigationKey.MyApps>(
metadata = ListDetailSceneStrategy.listPane("appdetails") {
Text(stringResource(R.string.no_app_selected))
},
) {
val myAppsViewModel = hiltViewModel<MyAppsViewModel>()
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<NavigationKey.AppDetails>(
metadata = ListDetailSceneStrategy.detailPane("appdetails")
) {
val appDetailsViewModel = hiltViewModel<AppDetailsViewModel>()
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<NavigationKey.AppList>(
metadata = ListDetailSceneStrategy.listPane("appdetails") {
Text(stringResource(R.string.no_app_selected))
},
) {
val appListViewModel = hiltViewModel<AppListViewModel>()
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<NavigationKey.Repos>(
metadata = ListDetailSceneStrategy.listPane("repos") {
Text(text = stringResource(R.string.no_repository_selected))
},
) {
val viewModel = hiltViewModel<RepositoriesViewModel>()
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<NavigationKey.RepoDetails>(
metadata = ListDetailSceneStrategy.detailPane("repos")
) { navKey ->
val viewModel = hiltViewModel<RepoDetailsViewModel>()
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<NavigationKey.AddRepo> { navKey ->
val viewModel = hiltViewModel<AddRepoViewModel>()
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<SettingsViewModel>()
Settings(
onSaveLogcat = {
viewModel.onSaveLogcat(it)
backStack.removeLastOrNull()
},
onBackClicked = { backStack.removeLastOrNull() },
)
}
entry(NavigationKey.About) {
About { backStack.removeLastOrNull() }
}
val entryProvider: (NavKey) -> NavEntry<NavKey> = entryProvider {
entry<NavigationKey.Discover>(
metadata = ListDetailSceneStrategy.listPane("appdetails") {
Text(stringResource(R.string.no_app_selected))
},
)
) {
val viewModel = hiltViewModel<DiscoverViewModel>()
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<NavigationKey.MyApps>(
metadata = ListDetailSceneStrategy.listPane("appdetails") {
Text(stringResource(R.string.no_app_selected))
},
) {
val myAppsViewModel = hiltViewModel<MyAppsViewModel>()
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<NavigationKey.AppDetails>(
metadata = ListDetailSceneStrategy.detailPane("appdetails")
) {
val appDetailsViewModel = hiltViewModel<AppDetailsViewModel>()
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<NavigationKey.AppList>(
metadata = ListDetailSceneStrategy.listPane("appdetails") {
Text(stringResource(R.string.no_app_selected))
},
) {
val appListViewModel = hiltViewModel<AppListViewModel>()
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<NavigationKey.Repos>(
metadata = ListDetailSceneStrategy.listPane("repos") {
Text(text = stringResource(R.string.no_repository_selected))
},
) {
val viewModel = hiltViewModel<RepositoriesViewModel>()
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<NavigationKey.RepoDetails>(
metadata = ListDetailSceneStrategy.detailPane("repos")
) { navKey ->
val viewModel = hiltViewModel<RepoDetailsViewModel>()
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<NavigationKey.AddRepo> { navKey ->
val viewModel = hiltViewModel<AddRepoViewModel>()
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<SettingsViewModel>()
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,
)
}
}
}