diff --git a/app/src/main/java/com/aurora/store/compose/ui/about/AboutScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/about/AboutScreen.kt index 79936b56b..e5cc278a3 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/about/AboutScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/about/AboutScreen.kt @@ -46,7 +46,7 @@ import com.aurora.store.compose.preview.ThemePreviewProvider import com.aurora.store.data.model.Link @Composable -fun AboutScreen(onNavigateUp: () -> Unit) { +fun AboutScreen() { var shouldShowAboutDialog by rememberSaveable { mutableStateOf(false) } if (shouldShowAboutDialog) { @@ -54,13 +54,12 @@ fun AboutScreen(onNavigateUp: () -> Unit) { } ScreenContent( - onNavigateUp = onNavigateUp, onAboutAurora = { shouldShowAboutDialog = true } ) } @Composable -private fun ScreenContent(onNavigateUp: () -> Unit = {}, onAboutAurora: () -> Unit = {}) { +private fun ScreenContent(onAboutAurora: () -> Unit = {}) { val context = LocalContext.current val linkURLS = stringArrayResource(R.array.link_urls) @@ -94,8 +93,7 @@ private fun ScreenContent(onNavigateUp: () -> Unit = {}, onAboutAurora: () -> Un Scaffold( topBar = { TopAppBar( - title = stringResource(R.string.title_about), - onNavigateUp = onNavigateUp + title = stringResource(R.string.title_about) ) } ) { paddingValues -> diff --git a/app/src/main/java/com/aurora/store/compose/ui/accounts/AccountsScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/accounts/AccountsScreen.kt index b8bb41717..1f0c7de50 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/accounts/AccountsScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/accounts/AccountsScreen.kt @@ -48,19 +48,18 @@ import com.aurora.Constants.URL_TOS import com.aurora.extensions.browse import com.aurora.store.R import com.aurora.store.compose.composable.TopAppBar +import com.aurora.store.compose.navigation.Destination import com.aurora.store.compose.preview.ThemePreviewProvider import com.aurora.store.data.providers.AccountProvider import com.aurora.store.viewmodel.accounts.AccountsViewModel @Composable fun AccountsScreen( - onNavigateUp: () -> Unit, - onNavigateToSplash: () -> Unit, + onNavigateTo: (Destination) -> Unit, viewModel: AccountsViewModel = hiltViewModel() ) { val userProfile = viewModel.authProvider.authData?.userProfile val avatar = when { - viewModel.authProvider.isAnonymous -> R.mipmap.ic_launcher userProfile != null -> userProfile.artwork.url else -> R.mipmap.ic_launcher } @@ -70,7 +69,6 @@ fun AccountsScreen( else -> stringResource(R.string.account_anonymous) } val email = when { - viewModel.authProvider.isAnonymous -> stringResource(R.string.account_anonymous_email) userProfile != null -> userProfile.email else -> stringResource(R.string.account_anonymous_email) } @@ -83,14 +81,13 @@ fun AccountsScreen( onConfirm = { shouldShowLogoutDialog = false AccountProvider.logout(context) - onNavigateToSplash() + onNavigateTo(Destination.Splash) }, onDismiss = { shouldShowLogoutDialog = false } ) } ScreenContent( - onNavigateUp = onNavigateUp, avatar = avatar, name = name, email = email, @@ -100,17 +97,15 @@ fun AccountsScreen( @Composable private fun ScreenContent( - avatar: Any = R.mipmap.ic_launcher, + avatar: Any = R.drawable.ic_account, name: String = stringResource(R.string.account_anonymous), email: String = stringResource(R.string.account_anonymous_email), - onNavigateUp: () -> Unit = {}, onLogout: () -> Unit = {} ) { Scaffold( topBar = { TopAppBar( - title = stringResource(R.string.title_account_manager), - onNavigateUp = onNavigateUp + title = stringResource(R.string.title_account_manager) ) } ) { paddingValues -> @@ -118,7 +113,7 @@ private fun ScreenContent( modifier = Modifier .padding(paddingValues) .fillMaxSize() - .padding(vertical = dimensionResource(R.dimen.padding_medium)), + .padding(vertical = dimensionResource(R.dimen.padding_small)), verticalArrangement = Arrangement.SpaceBetween ) { AssistHeader() @@ -126,7 +121,7 @@ private fun ScreenContent( Column( modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_medium)) + verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_small)) ) { AsyncImage( model = ImageRequest.Builder(LocalContext.current) diff --git a/app/src/main/java/com/aurora/store/compose/ui/blacklist/BlacklistScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/blacklist/BlacklistScreen.kt index f82cb0e0b..fd71e7272 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/blacklist/BlacklistScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/blacklist/BlacklistScreen.kt @@ -7,6 +7,8 @@ package com.aurora.store.compose.ui.blacklist import android.net.Uri +import androidx.activity.ComponentActivity +import androidx.activity.compose.LocalActivity import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.clickable @@ -51,7 +53,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewWrapper -import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.aurora.Constants @@ -72,13 +73,12 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch @Composable -fun BlacklistScreen(onNavigateUp: () -> Unit, viewModel: BlacklistViewModel = hiltViewModel()) { +fun BlacklistScreen(viewModel: BlacklistViewModel = hiltViewModel()) { val context = LocalContext.current val packages by viewModel.filteredPackages.collectAsStateWithLifecycle() ScreenContent( packages = packages, - onNavigateUp = onNavigateUp, isPackageBlacklisted = { pkgName -> pkgName in viewModel.blacklist }, onBlacklistImport = { uri -> viewModel.importBlacklist(uri) @@ -99,7 +99,6 @@ fun BlacklistScreen(onNavigateUp: () -> Unit, viewModel: BlacklistViewModel = hi @Composable private fun ScreenContent( packages: List? = null, - onNavigateUp: () -> Unit = {}, isPackageBlacklisted: (packageName: String) -> Boolean = { false }, onBlacklistImport: (uri: Uri) -> Unit = {}, onBlacklistExport: (uri: Uri) -> Unit = {}, @@ -109,6 +108,7 @@ private fun ScreenContent( onWhitelistAll: () -> Unit = {}, onSearch: (query: String) -> Unit = {} ) { + val activity = LocalActivity.current as? ComponentActivity val context = LocalContext.current val textFieldState = rememberTextFieldState() val searchBarState = rememberSearchBarState() @@ -224,7 +224,7 @@ private fun ScreenContent( state = searchBarState, inputField = inputField, navigationIcon = { - IconButton(onClick = onNavigateUp) { + IconButton(onClick = { activity?.onBackPressedDispatcher?.onBackPressed() }) { Icon( painter = painterResource(R.drawable.ic_arrow_back), contentDescription = stringResource(R.string.action_back) @@ -333,7 +333,6 @@ private fun ScreenContent( } ScrollHint( listState = listState, - bottomPadding = 5.dp, modifier = Modifier.align(Alignment.BottomCenter) ) } diff --git a/app/src/main/java/com/aurora/store/compose/ui/commons/CategoryBrowseScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/commons/CategoryBrowseScreen.kt new file mode 100644 index 000000000..036b19556 --- /dev/null +++ b/app/src/main/java/com/aurora/store/compose/ui/commons/CategoryBrowseScreen.kt @@ -0,0 +1,65 @@ +/* + * SPDX-FileCopyrightText: 2026 Aurora OSS + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.aurora.store.compose.ui.commons + +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import com.aurora.gplayapi.data.models.StreamBundle +import com.aurora.store.R +import com.aurora.store.compose.composable.Error +import com.aurora.store.compose.composable.StreamCarousel +import com.aurora.store.compose.composable.TopAppBar +import com.aurora.store.compose.composition.collectForced +import com.aurora.store.compose.navigation.Destination +import com.aurora.store.data.model.ViewState +import com.aurora.store.viewmodel.subcategory.CategoryStreamViewModel + +@Composable +fun CategoryBrowseScreen( + title: String, + browseUrl: String, + onNavigateTo: (Destination) -> Unit, + viewModel: CategoryStreamViewModel = hiltViewModel( + creationCallback = { factory: CategoryStreamViewModel.Factory -> + factory.create(browseUrl) + } + ) +) { + val uiState by viewModel.viewState.collectForced(ViewState.Loading) + + Scaffold( + topBar = { TopAppBar(title = title) } + ) { paddingValues -> + if (uiState is ViewState.Error) { + Error( + modifier = Modifier.padding(paddingValues), + painter = painterResource(R.drawable.ic_disclaimer), + message = stringResource(R.string.error) + ) + } else { + val bundle = (uiState as? ViewState.Success<*>)?.data as? StreamBundle + StreamCarousel( + modifier = Modifier.padding(paddingValues), + streamBundle = bundle, + filterSingleAppClusters = false, + onHeaderClick = { cluster -> + if (cluster.clusterBrowseUrl.isNotBlank()) { + onNavigateTo(Destination.StreamBrowse(cluster)) + } + }, + onAppClick = { onNavigateTo(Destination.AppDetails(it.packageName)) }, + onClusterScrolled = { viewModel.fetchNextCluster(it) }, + onScrolledToEnd = { viewModel.fetchNextPage() } + ) + } + } +} diff --git a/app/src/main/java/com/aurora/store/compose/ui/commons/ExpandedStreamBrowseScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/commons/ExpandedStreamBrowseScreen.kt new file mode 100644 index 000000000..cd696d810 --- /dev/null +++ b/app/src/main/java/com/aurora/store/compose/ui/commons/ExpandedStreamBrowseScreen.kt @@ -0,0 +1,98 @@ +/* + * SPDX-FileCopyrightText: 2026 Aurora OSS + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.aurora.store.compose.ui.commons + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.paging.LoadState +import androidx.paging.compose.collectAsLazyPagingItems +import com.aurora.store.R +import com.aurora.store.compose.composable.ContainedLoadingIndicator +import com.aurora.store.compose.composable.Error +import com.aurora.store.compose.composable.TopAppBar +import com.aurora.store.compose.composable.app.LargeAppListItem +import com.aurora.store.compose.navigation.Destination +import com.aurora.store.viewmodel.browse.ExpandedStreamBrowseViewModel +import kotlin.uuid.Uuid + +@Composable +fun ExpandedStreamBrowseScreen( + browseUrl: String, + defaultTitle: String, + onNavigateTo: (Destination) -> Unit, + viewModel: ExpandedStreamBrowseViewModel = hiltViewModel( + creationCallback = { factory: ExpandedStreamBrowseViewModel.Factory -> + factory.create(browseUrl) + } + ) +) { + val title by viewModel.title.collectAsStateWithLifecycle() + val apps = viewModel.apps.collectAsLazyPagingItems() + + Scaffold( + topBar = { + TopAppBar( + title = title.ifBlank { defaultTitle } + ) + } + ) { paddingValues -> + when (apps.loadState.refresh) { + is LoadState.Loading -> ContainedLoadingIndicator() + + is LoadState.Error -> { + Error( + modifier = Modifier.padding(paddingValues), + painter = painterResource(R.drawable.ic_disclaimer), + message = stringResource(R.string.error) + ) + } + + else -> { + if (apps.itemCount == 0) { + Error( + modifier = Modifier.padding(paddingValues), + painter = painterResource(R.drawable.ic_disclaimer), + message = stringResource(R.string.no_apps_available) + ) + } else { + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues), + verticalArrangement = Arrangement.spacedBy( + dimensionResource(R.dimen.margin_medium) + ) + ) { + items( + count = apps.itemCount, + key = { Uuid.random().toString() } + ) { index -> + apps[index]?.let { app -> + LargeAppListItem( + app = app, + onClick = { + onNavigateTo(Destination.AppDetails(app.packageName)) + } + ) + } + } + } + } + } + } + } +} diff --git a/app/src/main/java/com/aurora/store/compose/ui/commons/PermissionRationaleScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/commons/PermissionRationaleScreen.kt index 1a27bb5c6..3aad2fc3d 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/commons/PermissionRationaleScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/commons/PermissionRationaleScreen.kt @@ -35,14 +35,12 @@ import kotlin.random.Random @Composable fun PermissionRationaleScreen( requiredPermissions: Set = emptySet(), - onNavigateUp: () -> Unit, onPermissionCallback: (type: PermissionType) -> Unit = {}, viewModel: PermissionRationaleViewModel = hiltViewModel() ) { val permissions by viewModel.permissions.collectAsStateWithLifecycle() ScreenContent( - onNavigateUp = onNavigateUp, permissions = permissions .filter { it.type in requiredPermissions } .map { permission -> permission.copy(optional = false) }, @@ -56,7 +54,6 @@ fun PermissionRationaleScreen( @Composable private fun ScreenContent( permissions: List = emptyList(), - onNavigateUp: () -> Unit = {}, onPermissionCallback: (type: PermissionType) -> Unit = {}, windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfoV2() ) { @@ -64,8 +61,7 @@ private fun ScreenContent( topBar = { TopAppBar( title = pluralStringResource(R.plurals.permissions_required, permissions.size), - navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon, - onNavigateUp = onNavigateUp + navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon ) } ) { paddingValues -> diff --git a/app/src/main/java/com/aurora/store/compose/ui/commons/StreamBrowseScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/commons/StreamBrowseScreen.kt index 34c2e6f8b..a3571e00c 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/commons/StreamBrowseScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/commons/StreamBrowseScreen.kt @@ -5,12 +5,14 @@ package com.aurora.store.compose.ui.commons +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -29,6 +31,7 @@ import com.aurora.store.compose.composable.ContainedLoadingIndicator import com.aurora.store.compose.composable.Error import com.aurora.store.compose.composable.TopAppBar import com.aurora.store.compose.composable.app.LargeAppListItem +import com.aurora.store.compose.navigation.Destination import com.aurora.store.compose.preview.AppPreviewProvider import com.aurora.store.compose.preview.ThemePreviewProvider import com.aurora.store.viewmodel.browse.StreamBrowseViewModel @@ -39,8 +42,7 @@ import kotlinx.coroutines.flow.MutableStateFlow @Composable fun StreamBrowseScreen( streamCluster: StreamCluster, - onNavigateUp: () -> Unit, - onNavigateToAppDetails: (packageName: String) -> Unit, + onNavigateTo: (Destination) -> Unit, viewModel: StreamBrowseViewModel = hiltViewModel( creationCallback = { factory: StreamBrowseViewModel.Factory -> factory.create(streamCluster) @@ -52,8 +54,7 @@ fun StreamBrowseScreen( ScreenContent( title = streamCluster.clusterTitle, apps = apps, - onNavigateUp = onNavigateUp, - onNavigateToAppDetails = onNavigateToAppDetails + onNavigateTo = onNavigateTo ) } @@ -61,12 +62,11 @@ fun StreamBrowseScreen( private fun ScreenContent( title: String = String(), apps: LazyPagingItems = emptyPagingItems(), - onNavigateToAppDetails: (packageName: String) -> Unit = {}, - onNavigateUp: () -> Unit = {} + onNavigateTo: (Destination) -> Unit = {} ) { Scaffold( topBar = { - TopAppBar(title = title, onNavigateUp = onNavigateUp) + TopAppBar(title = title) } ) { paddingValues -> when (apps.loadState.refresh) { @@ -91,7 +91,10 @@ private fun ScreenContent( LazyColumn( modifier = Modifier .fillMaxSize() - .padding(paddingValues) + .padding(paddingValues), + verticalArrangement = Arrangement.spacedBy( + dimensionResource(R.dimen.margin_medium) + ) ) { items( count = apps.itemCount, @@ -100,7 +103,9 @@ private fun ScreenContent( apps[index]?.let { app -> LargeAppListItem( app = app, - onClick = { onNavigateToAppDetails(app.packageName) } + onClick = { + onNavigateTo(Destination.AppDetails(app.packageName)) + } ) } } diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/AppDetailsScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/details/AppDetailsScreen.kt index a4b9342fb..f36834149 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/details/AppDetailsScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/details/AppDetailsScreen.kt @@ -50,7 +50,6 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewScreenSizes import androidx.compose.ui.tooling.preview.PreviewWrapper -import androidx.compose.ui.unit.dp import androidx.core.net.toUri import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -72,6 +71,7 @@ import com.aurora.store.compose.composable.Header import com.aurora.store.compose.composable.ScrollHint import com.aurora.store.compose.composable.TopAppBar import com.aurora.store.compose.composable.app.LargeAppListItem +import com.aurora.store.compose.navigation.Destination import com.aurora.store.compose.navigation.Screen import com.aurora.store.compose.preview.AppPreviewProvider import com.aurora.store.compose.preview.ThemePreviewProvider @@ -110,8 +110,7 @@ import kotlinx.coroutines.launch @Composable fun AppDetailsScreen( packageName: String, - onNavigateUp: () -> Unit, - onNavigateToAppDetails: (packageName: String) -> Unit, + onNavigateTo: (Destination) -> Unit, viewModel: AppDetailsViewModel = hiltViewModel(key = packageName), forceSinglePane: Boolean = false ) { @@ -136,11 +135,10 @@ fun AppDetailsScreen( ) { currentState -> when { currentState is AppState.Loading || app == null -> - ScreenContentLoading(onNavigateUp = onNavigateUp) + ScreenContentLoading() currentState is AppState.Error -> ScreenContentError( - onNavigateUp = onNavigateUp, message = currentState.message ) @@ -156,8 +154,7 @@ fun AppDetailsScreen( plexusScores = plexusScores, dataSafetyReport = dataSafetyReport, exodusReport = exodusReport, - onNavigateUp = onNavigateUp, - onNavigateToAppDetails = onNavigateToAppDetails, + onNavigateTo = onNavigateTo, onDownload = { requestedApp -> viewModel.enqueueDownload(requestedApp) }, onFavorite = { viewModel.toggleFavourite(loadedApp) }, onCancelDownload = { viewModel.cancelDownload(loadedApp) }, @@ -196,9 +193,9 @@ private fun stateKey(state: AppState, app: App?): String = when { * Composable to show progress while fetching app details */ @Composable -private fun ScreenContentLoading(onNavigateUp: () -> Unit = {}) { +private fun ScreenContentLoading() { Scaffold( - topBar = { TopAppBar(onNavigateUp = onNavigateUp) } + topBar = { TopAppBar() } ) { paddingValues -> ContainedLoadingIndicator(modifier = Modifier.padding(paddingValues)) } @@ -208,9 +205,9 @@ private fun ScreenContentLoading(onNavigateUp: () -> Unit = {}) { * Composable to display errors related to fetching app details */ @Composable -private fun ScreenContentError(onNavigateUp: () -> Unit = {}, message: String? = null) { +private fun ScreenContentError(message: String? = null) { Scaffold( - topBar = { TopAppBar(onNavigateUp = onNavigateUp) } + topBar = { TopAppBar() } ) { paddingValues -> Error( modifier = Modifier.padding(paddingValues), @@ -234,8 +231,7 @@ private fun ScreenContentApp( plexusScores: Scores? = null, dataSafetyReport: DataSafetyReport? = null, exodusReport: Report? = null, - onNavigateUp: () -> Unit = {}, - onNavigateToAppDetails: (packageName: String) -> Unit = {}, + onNavigateTo: (Destination) -> Unit = {}, onDownload: (requestedApp: App) -> Unit = {}, onFavorite: () -> Unit = {}, onCancelDownload: () -> Unit = {}, @@ -412,7 +408,6 @@ private fun ScreenContentApp( Scaffold( topBar = { TopAppBar( - onNavigateUp = onNavigateUp, actions = { if (shouldShowMenuOnMainPane) SetupMenu() } ) } @@ -535,7 +530,6 @@ private fun ScreenContentApp( } ScrollHint( listState = listState, - bottomPadding = 5.dp, modifier = Modifier.align(Alignment.BottomCenter) ) } @@ -546,7 +540,10 @@ private fun ScreenContentApp( fun SupportingPane() { Scaffold( topBar = { - TopAppBar(actions = { if (!shouldShowMenuOnMainPane) SetupMenu() }) + TopAppBar( + showNavigationIcon = false, + actions = { if (!shouldShowMenuOnMainPane) SetupMenu() } + ) } ) { paddingValues -> if (suggestions.isNotEmpty()) { @@ -568,12 +565,15 @@ private fun ScreenContentApp( LazyColumn( modifier = Modifier .fillMaxSize() - .padding(vertical = dimensionResource(R.dimen.padding_medium)) + .padding(vertical = dimensionResource(R.dimen.padding_medium)), + verticalArrangement = Arrangement.spacedBy( + dimensionResource(R.dimen.margin_medium) + ) ) { items(items = suggestions, key = { item -> item.id }) { app -> LargeAppListItem( app = app, - onClick = { onNavigateToAppDetails(app.packageName) } + onClick = { onNavigateTo(Destination.AppDetails(app.packageName)) } ) } } @@ -585,52 +585,43 @@ private fun ScreenContentApp( @Composable fun ExtraPane(screen: NavKey) = when (screen) { is ExtraScreen.Review -> ReviewScreen( - packageName = app.packageName, - onNavigateUp = ::onNavigateBack + packageName = app.packageName ) is ExtraScreen.Exodus -> ExodusScreen( - packageName = app.packageName, - onNavigateUp = ::onNavigateBack + packageName = app.packageName ) is ExtraScreen.More -> MoreScreen( packageName = app.packageName, - onNavigateUp = ::onNavigateBack, - onNavigateToAppDetails = onNavigateToAppDetails + onNavigateTo = onNavigateTo ) is ExtraScreen.Permission -> PermissionScreen( - packageName = app.packageName, - onNavigateUp = ::onNavigateBack + packageName = app.packageName ) is ExtraScreen.Screenshot -> ScreenshotScreen( packageName = app.packageName, - index = screen.index, - onNavigateUp = ::onNavigateBack + index = screen.index ) is ExtraScreen.ManualDownload -> ManualDownloadScreen( packageName = app.packageName, - onNavigateUp = ::onNavigateBack, onRequestInstall = { requestedApp -> onInstall(requestedApp) } ) is ExtraScreen.MicroG -> MicroGScreen( packageName = app.packageName, - onNavigateUp = ::onNavigateBack, onProceed = { onInstall(ignoreMicroG = true) } ) is Screen.DevProfile -> DevProfileScreen( publisherId = app.developerName, - onNavigateUp = ::onNavigateBack, - onNavigateToAppDetails = { onNavigateToAppDetails(it) } + onNavigateTo = onNavigateTo ) is Screen.PermissionRationale -> PermissionRationaleScreen( - onNavigateUp = ::onNavigateBack, requiredPermissions = screen.requiredPermissions, onPermissionCallback = { type -> val isStoragePermission = type == PermissionType.EXTERNAL_STORAGE || diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/ExodusScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/details/ExodusScreen.kt index f85046245..d6602fbce 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/details/ExodusScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/details/ExodusScreen.kt @@ -31,7 +31,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewWrapper -import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.aurora.Constants.EXODUS_REPORT_URL @@ -56,7 +55,6 @@ import com.aurora.store.viewmodel.details.ExodusViewModel @Composable fun ExodusScreen( packageName: String, - onNavigateUp: () -> Unit, appDetailsViewModel: AppDetailsViewModel = hiltViewModel(key = packageName), exodusViewModel: ExodusViewModel = hiltViewModel(key = "$packageName/exodus"), windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfoV2() @@ -80,7 +78,6 @@ fun ExodusScreen( null -> { ScreenContentError( topAppBarTitle = topAppBarTitle, - onNavigateUp = onNavigateUp, onRequestAnalysis = { context.browse("${EXODUS_SUBMIT_PAGE}${app!!.packageName}") } ) } @@ -90,7 +87,6 @@ fun ExodusScreen( topAppBarTitle = topAppBarTitle, report = report, trackers = trackers, - onNavigateUp = onNavigateUp, onRequestAnalysis = { context.browse("${EXODUS_SUBMIT_PAGE}${app!!.packageName}") } ) } @@ -102,7 +98,6 @@ private fun ScreenContentReport( topAppBarTitle: String? = null, report: Report? = null, trackers: List = emptyList(), - onNavigateUp: () -> Unit = {}, onRequestAnalysis: () -> Unit = {}, windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfoV2() ) { @@ -112,8 +107,7 @@ private fun ScreenContentReport( topBar = { TopAppBar( title = topAppBarTitle, - navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon, - onNavigateUp = onNavigateUp + navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon ) }, floatingActionButton = { @@ -161,7 +155,6 @@ private fun ScreenContentReport( } ScrollHint( listState = listState, - bottomPadding = 5.dp, modifier = Modifier.align(Alignment.BottomCenter) ) } @@ -174,7 +167,6 @@ private fun ScreenContentReport( @Composable private fun ScreenContentError( topAppBarTitle: String? = null, - onNavigateUp: () -> Unit = {}, onRequestAnalysis: () -> Unit = {}, windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfoV2() ) { @@ -182,8 +174,7 @@ private fun ScreenContentError( topBar = { TopAppBar( title = topAppBarTitle, - navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon, - onNavigateUp = onNavigateUp + navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon ) } ) { paddingValues -> diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/ManualDownloadScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/details/ManualDownloadScreen.kt index 81384ac1e..4ebd59b64 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/details/ManualDownloadScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/details/ManualDownloadScreen.kt @@ -6,6 +6,8 @@ package com.aurora.store.compose.ui.details +import androidx.activity.ComponentActivity +import androidx.activity.compose.LocalActivity import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -69,7 +71,6 @@ import kotlinx.coroutines.launch @Composable fun ManualDownloadScreen( packageName: String, - onNavigateUp: () -> Unit, onRequestInstall: (requestedApp: App) -> Unit, viewModel: AppDetailsViewModel = hiltViewModel(key = packageName), windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfoV2() @@ -85,7 +86,6 @@ fun ManualDownloadScreen( state = state, topAppBarTitle = topAppBarTitle, currentVersionCode = app!!.versionCode, - onNavigateUp = onNavigateUp, onRequestInstall = { versionCode -> val requestedApp = app!!.copy( versionCode = versionCode, @@ -105,10 +105,10 @@ private fun ScreenContent( state: AppState = AppState.Unavailable, topAppBarTitle: String? = null, currentVersionCode: Long = 0L, - onNavigateUp: () -> Unit = {}, onRequestInstall: (versionCode: Long) -> Unit = {}, windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfoV2() ) { + val activity = LocalActivity.current as? ComponentActivity val coroutineScope = rememberCoroutineScope() val snackBarHostState = remember { SnackbarHostState() } val errorMessage = stringResource(R.string.manual_download_version_error) @@ -130,8 +130,7 @@ private fun ScreenContent( topBar = { TopAppBar( title = topAppBarTitle, - navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon, - onNavigateUp = onNavigateUp + navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon ) }, snackbarHost = { SnackbarHost(hostState = snackBarHostState) } @@ -187,7 +186,7 @@ private fun ScreenContent( ) { FilledTonalButton( modifier = Modifier.weight(1F), - onClick = onNavigateUp + onClick = { activity?.onBackPressedDispatcher?.onBackPressed() } ) { Text( text = stringResource(R.string.action_close), diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/MicroGScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/details/MicroGScreen.kt index 145c6cda9..bb5c7010e 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/details/MicroGScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/details/MicroGScreen.kt @@ -6,6 +6,8 @@ package com.aurora.store.compose.ui.details +import androidx.activity.ComponentActivity +import androidx.activity.compose.LocalActivity import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row @@ -55,7 +57,6 @@ import com.aurora.store.viewmodel.onboarding.MicroGViewModel @Composable fun MicroGScreen( packageName: String, - onNavigateUp: () -> Unit, onProceed: () -> Unit, appDetailsViewModel: AppDetailsViewModel = hiltViewModel(key = packageName), viewModel: MicroGViewModel = hiltViewModel(), @@ -70,7 +71,6 @@ fun MicroGScreen( ScreenContent( topAppBarTitle = topAppBarTitle, uiState = viewModel.uiState, - onNavigateUp = onNavigateUp, onInstall = { viewModel.downloadMicroG() }, onRetry = { viewModel.retryDownload() }, onProceed = onProceed @@ -81,12 +81,12 @@ fun MicroGScreen( private fun ScreenContent( topAppBarTitle: String? = null, uiState: MicroGUIState = MicroGUIState(), - onNavigateUp: () -> Unit = {}, onInstall: () -> Unit = {}, onRetry: () -> Unit = {}, onProceed: () -> Unit = {}, windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfoV2() ) { + val activity = LocalActivity.current as? ComponentActivity val context = LocalContext.current val listState = rememberLazyListState() @@ -94,8 +94,7 @@ private fun ScreenContent( topBar = { TopAppBar( title = topAppBarTitle, - navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon, - onNavigateUp = onNavigateUp + navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon ) }, bottomBar = { @@ -112,7 +111,7 @@ private fun ScreenContent( ) { FilledTonalButton( modifier = Modifier.weight(1F), - onClick = onNavigateUp + onClick = { activity?.onBackPressedDispatcher?.onBackPressed() } ) { Text( text = stringResource(R.string.action_cancel), @@ -174,7 +173,6 @@ private fun ScreenContent( ScrollHint( listState = listState, - bottomPadding = 5.dp, modifier = Modifier.align(Alignment.BottomCenter) ) } diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/MoreScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/details/MoreScreen.kt index f9a66ccaa..5212c40db 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/details/MoreScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/details/MoreScreen.kt @@ -32,7 +32,6 @@ import androidx.compose.ui.text.fromHtml import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewWrapper -import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.aurora.extensions.adaptiveNavigationIcon @@ -44,6 +43,7 @@ import com.aurora.store.compose.composable.Info import com.aurora.store.compose.composable.ScrollHint import com.aurora.store.compose.composable.TopAppBar import com.aurora.store.compose.composable.app.AppListItem +import com.aurora.store.compose.navigation.Destination import com.aurora.store.compose.preview.AppPreviewProvider import com.aurora.store.compose.preview.ThemePreviewProvider import com.aurora.store.viewmodel.details.AppDetailsViewModel @@ -52,8 +52,7 @@ import com.aurora.store.viewmodel.details.MoreViewModel @Composable fun MoreScreen( packageName: String, - onNavigateUp: () -> Unit, - onNavigateToAppDetails: (packageName: String) -> Unit, + onNavigateTo: (Destination) -> Unit, appDetailsViewModel: AppDetailsViewModel = hiltViewModel(key = packageName), moreViewModel: MoreViewModel = hiltViewModel( key = "$packageName/more", @@ -68,8 +67,7 @@ fun MoreScreen( ScreenContent( app = app!!, dependencies = dependencies, - onNavigateUp = onNavigateUp, - onNavigateToAppDetails = onNavigateToAppDetails + onNavigateTo = onNavigateTo ) } @@ -77,8 +75,7 @@ fun MoreScreen( private fun ScreenContent( app: App, dependencies: List? = null, - onNavigateUp: () -> Unit = {}, - onNavigateToAppDetails: (packageName: String) -> Unit = {}, + onNavigateTo: (Destination) -> Unit = {}, windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfoV2() ) { val topAppBarTitle = when { @@ -90,8 +87,7 @@ private fun ScreenContent( topBar = { TopAppBar( title = topAppBarTitle, - navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon, - onNavigateUp = onNavigateUp + navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon ) } ) { paddingValues -> @@ -126,7 +122,7 @@ private fun ScreenContent( if (dependencies != null) { AppDependencies( dependencies = dependencies, - onNavigateToAppDetails = onNavigateToAppDetails + onNavigateTo = onNavigateTo ) } } @@ -137,7 +133,6 @@ private fun ScreenContent( } ScrollHint( listState = listState, - bottomPadding = 5.dp, modifier = Modifier.align(Alignment.BottomCenter) ) } @@ -148,10 +143,7 @@ private fun ScreenContent( * Composable to show dependencies of an app */ @Composable -private fun AppDependencies( - dependencies: List, - onNavigateToAppDetails: (packageName: String) -> Unit -) { +private fun AppDependencies(dependencies: List, onNavigateTo: (Destination) -> Unit) { Header(title = stringResource(R.string.details_dependencies)) if (dependencies.isEmpty()) { Info( @@ -162,7 +154,7 @@ private fun AppDependencies( items(items = dependencies, key = { item -> item.id }) { app -> AppListItem( app = app, - onClick = { onNavigateToAppDetails(app.packageName) } + onClick = { onNavigateTo(Destination.AppDetails(app.packageName)) } ) } } diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/PermissionScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/details/PermissionScreen.kt index d3d52194f..1fc1e911d 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/details/PermissionScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/details/PermissionScreen.kt @@ -29,7 +29,6 @@ import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewWrapper -import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.aurora.extensions.adaptiveNavigationIcon @@ -47,7 +46,6 @@ import com.aurora.store.viewmodel.details.PermissionViewModel @Composable fun PermissionScreen( packageName: String, - onNavigateUp: () -> Unit, appDetailsViewModel: AppDetailsViewModel = hiltViewModel(key = packageName), permissionViewModel: PermissionViewModel = hiltViewModel( key = "$packageName/permission", @@ -67,7 +65,6 @@ fun PermissionScreen( ScreenContent( topAppBarTitle = topAppBarTitle, - onNavigateUp = onNavigateUp, permissionsInfo = permissionsInfo ) } @@ -76,7 +73,6 @@ fun PermissionScreen( private fun ScreenContent( topAppBarTitle: String? = null, permissionsInfo: Map = emptyMap(), - onNavigateUp: () -> Unit = {}, windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfoV2() ) { val packageManager = LocalContext.current.packageManager @@ -85,8 +81,7 @@ private fun ScreenContent( topBar = { TopAppBar( title = topAppBarTitle, - navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon, - onNavigateUp = onNavigateUp + navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon ) } ) { paddingValues -> @@ -127,7 +122,6 @@ private fun ScreenContent( } ScrollHint( listState = listState, - bottomPadding = 5.dp, modifier = Modifier.align(Alignment.BottomCenter) ) } diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/ReviewScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/details/ReviewScreen.kt index 79b295803..f0fd4b088 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/details/ReviewScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/details/ReviewScreen.kt @@ -36,7 +36,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewWrapper -import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.paging.LoadState @@ -64,7 +63,6 @@ import kotlinx.coroutines.flow.MutableStateFlow @Composable fun ReviewScreen( packageName: String, - onNavigateUp: () -> Unit, appDetailsViewModel: AppDetailsViewModel = hiltViewModel(key = packageName), reviewViewModel: ReviewViewModel = hiltViewModel( key = "$packageName/review", @@ -85,7 +83,6 @@ fun ReviewScreen( ScreenContent( topAppBarTitle = topAppBarTitle, reviews = reviews, - onNavigateUp = onNavigateUp, onFilter = { filter -> reviewViewModel.fetchReviews(filter) } ) } @@ -93,7 +90,6 @@ fun ReviewScreen( @Composable private fun ScreenContent( topAppBarTitle: String? = null, - onNavigateUp: () -> Unit = {}, reviews: LazyPagingItems = emptyPagingItems(), onFilter: (filter: Review.Filter) -> Unit = {}, windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfoV2() @@ -103,8 +99,7 @@ private fun ScreenContent( Column { TopAppBar( title = topAppBarTitle, - navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon, - onNavigateUp = onNavigateUp + navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon ) FilterHeader { filter -> onFilter(filter) } } @@ -142,7 +137,6 @@ private fun ScreenContent( } ScrollHint( listState = listState, - bottomPadding = 5.dp, modifier = Modifier.align(Alignment.BottomCenter) ) } diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/ScreenshotScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/details/ScreenshotScreen.kt index ab808a78e..444b76e94 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/details/ScreenshotScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/details/ScreenshotScreen.kt @@ -17,7 +17,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalResources import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewWrapper @@ -36,7 +36,6 @@ import com.aurora.store.viewmodel.details.AppDetailsViewModel fun ScreenshotScreen( packageName: String, index: Int, - onNavigateUp: () -> Unit, viewModel: AppDetailsViewModel = hiltViewModel(key = packageName), windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfoV2() ) { @@ -49,7 +48,6 @@ fun ScreenshotScreen( ScreenContent( topAppBarTitle = topAppBarTitle, - onNavigateUp = onNavigateUp, screenshots = app!!.screenshots, index = index ) @@ -58,12 +56,11 @@ fun ScreenshotScreen( @Composable private fun ScreenContent( topAppBarTitle: String? = null, - onNavigateUp: () -> Unit = {}, screenshots: List = emptyList(), index: Int = 0, windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfoV2() ) { - val displayMetrics = LocalContext.current.resources.displayMetrics + val displayMetrics = LocalResources.current.displayMetrics val pagerState = rememberPagerState(initialPage = index) { screenshots.size } LaunchedEffect(key1 = index) { @@ -74,8 +71,7 @@ private fun ScreenContent( topBar = { TopAppBar( title = topAppBarTitle, - navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon, - onNavigateUp = onNavigateUp + navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon ) } ) { paddingValues -> diff --git a/app/src/main/java/com/aurora/store/compose/ui/dev/DevProfileScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/dev/DevProfileScreen.kt index 054f55c8f..aa2b3ac2d 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/dev/DevProfileScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/dev/DevProfileScreen.kt @@ -6,6 +6,7 @@ package com.aurora.store.compose.ui.dev +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn @@ -34,6 +35,7 @@ import com.aurora.store.compose.composable.ContainedLoadingIndicator import com.aurora.store.compose.composable.Error import com.aurora.store.compose.composable.TopAppBar import com.aurora.store.compose.composable.app.LargeAppListItem +import com.aurora.store.compose.navigation.Destination import com.aurora.store.compose.preview.AppPreviewProvider import com.aurora.store.compose.preview.ThemePreviewProvider import com.aurora.store.viewmodel.details.DevProfileViewModel @@ -48,8 +50,7 @@ import kotlinx.coroutines.flow.MutableStateFlow @Composable fun DevProfileScreen( developerId: String, - onNavigateUp: () -> Unit, - onNavigateToAppDetails: (packageName: String) -> Unit, + onNavigateTo: (Destination) -> Unit, viewModel: DevProfileViewModel = hiltViewModel() ) { // TODO: Implement when migrating logic for current DevProfileFragment @@ -62,8 +63,7 @@ fun DevProfileScreen( @Composable fun DevProfileScreen( publisherId: String, - onNavigateUp: () -> Unit, - onNavigateToAppDetails: (packageName: String) -> Unit, + onNavigateTo: (Destination) -> Unit, viewModel: SearchViewModel = hiltViewModel() ) { val apps = viewModel.apps.collectAsLazyPagingItems() @@ -73,8 +73,7 @@ fun DevProfileScreen( ScreenContent( apps = apps, topAppBarTitle = publisherId, - onNavigateUp = onNavigateUp, - onNavigateToAppDetails = onNavigateToAppDetails + onNavigateTo = onNavigateTo ) } @@ -82,16 +81,14 @@ fun DevProfileScreen( private fun ScreenContent( apps: LazyPagingItems, topAppBarTitle: String, - onNavigateUp: () -> Unit = {}, - onNavigateToAppDetails: (packageName: String) -> Unit = {}, + onNavigateTo: (Destination) -> Unit = {}, windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfoV2() ) { Scaffold( topBar = { TopAppBar( title = topAppBarTitle, - navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon, - onNavigateUp = onNavigateUp + navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon ) } ) { paddingValues -> @@ -111,13 +108,16 @@ private fun ScreenContent( modifier = Modifier .padding(paddingValues) .fillMaxSize() - .padding(vertical = dimensionResource(R.dimen.padding_medium)) + .padding(vertical = dimensionResource(R.dimen.padding_medium)), + verticalArrangement = Arrangement.spacedBy( + dimensionResource(R.dimen.margin_medium) + ) ) { items(count = apps.itemCount, key = apps.itemKey { it.id }) { index -> apps[index]?.let { app -> LargeAppListItem( app = app, - onClick = { onNavigateToAppDetails(app.packageName) } + onClick = { onNavigateTo(Destination.AppDetails(app.packageName)) } ) } } diff --git a/app/src/main/java/com/aurora/store/compose/ui/dispenser/DispenserScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/dispenser/DispenserScreen.kt index 25645ad44..e5121bc7c 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/dispenser/DispenserScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/dispenser/DispenserScreen.kt @@ -35,7 +35,7 @@ import com.aurora.store.compose.preview.ThemePreviewProvider import com.aurora.store.viewmodel.dispenser.DispenserViewModel @Composable -fun DispenserScreen(onNavigateUp: () -> Unit, viewModel: DispenserViewModel = hiltViewModel()) { +fun DispenserScreen(viewModel: DispenserViewModel = hiltViewModel()) { val dispensers by viewModel.dispensers.collectAsStateWithLifecycle() var shouldShowInputDialog by rememberSaveable { mutableStateOf(false) } @@ -63,7 +63,6 @@ fun DispenserScreen(onNavigateUp: () -> Unit, viewModel: DispenserViewModel = hi ScreenContent( dispensers = dispensers, - onNavigateUp = onNavigateUp, onAddDispenser = { shouldShowInputDialog = true }, onRemoveDispenser = { url -> shouldRemoveDispenser = url } ) @@ -71,7 +70,6 @@ fun DispenserScreen(onNavigateUp: () -> Unit, viewModel: DispenserViewModel = hi @Composable private fun ScreenContent( - onNavigateUp: () -> Unit = {}, dispensers: Set = emptySet(), onAddDispenser: () -> Unit = {}, onRemoveDispenser: (url: String) -> Unit = {} @@ -81,8 +79,7 @@ private fun ScreenContent( Scaffold( topBar = { TopAppBar( - title = stringResource(R.string.pref_dispenser_title), - onNavigateUp = onNavigateUp + title = stringResource(R.string.pref_dispenser_title) ) }, floatingActionButton = { diff --git a/app/src/main/java/com/aurora/store/compose/ui/downloads/DownloadsScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/downloads/DownloadsScreen.kt index 068c5fe09..500059878 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/downloads/DownloadsScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/downloads/DownloadsScreen.kt @@ -29,7 +29,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewWrapper -import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.paging.LoadState import androidx.paging.PagingData @@ -45,6 +44,7 @@ import com.aurora.store.compose.composable.DownloadListItem import com.aurora.store.compose.composable.Error import com.aurora.store.compose.composable.ScrollHint import com.aurora.store.compose.composable.TopAppBar +import com.aurora.store.compose.navigation.Destination import com.aurora.store.compose.preview.AppPreviewProvider import com.aurora.store.compose.preview.ThemePreviewProvider import com.aurora.store.compose.ui.downloads.menu.DownloadsMenu @@ -57,9 +57,8 @@ import kotlinx.coroutines.flow.MutableStateFlow @Composable fun DownloadsScreen( - onNavigateUp: () -> Unit, - viewModel: DownloadsViewModel = hiltViewModel(), - onNavigateToAppDetails: (packageName: String) -> Unit + onNavigateTo: (Destination) -> Unit, + viewModel: DownloadsViewModel = hiltViewModel() ) { val context = LocalContext.current val downloads = viewModel.downloads.collectAsLazyPagingItems() @@ -79,9 +78,8 @@ fun DownloadsScreen( ) ScreenContent( - onNavigateUp = onNavigateUp, downloads = downloads, - onNavigateToAppDetails = onNavigateToAppDetails, + onNavigateTo = onNavigateTo, onCancelAll = { viewModel.cancelAll() }, onForceClearAll = { viewModel.clearAll() }, onClearFinished = { viewModel.clearFinished() }, @@ -100,8 +98,7 @@ fun DownloadsScreen( @Composable private fun ScreenContent( downloads: LazyPagingItems = emptyPagingItems(), - onNavigateUp: () -> Unit = {}, - onNavigateToAppDetails: (packageName: String) -> Unit = {}, + onNavigateTo: (Destination) -> Unit = {}, onCancel: (packageName: String) -> Unit = {}, onClear: (download: Download) -> Unit = {}, onExport: (download: Download) -> Unit = {}, @@ -133,7 +130,6 @@ private fun ScreenContent( topBar = { TopAppBar( title = stringResource(R.string.title_download_manager), - onNavigateUp = onNavigateUp, actions = { if (downloads.itemCount != 0) SetupMenu() } ) } @@ -173,7 +169,9 @@ private fun ScreenContent( modifier = Modifier.animateItem(), download = download, onClick = { - onNavigateToAppDetails(download.packageName) + onNavigateTo( + Destination.AppDetails(download.packageName) + ) }, onClear = { onClear(download) }, onCancel = { onCancel(download.packageName) }, @@ -185,7 +183,6 @@ private fun ScreenContent( } ScrollHint( listState = listState, - bottomPadding = 5.dp, modifier = Modifier.align(Alignment.BottomCenter) ) } diff --git a/app/src/main/java/com/aurora/store/compose/ui/favourite/FavouriteScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/favourite/FavouriteScreen.kt index 75306c346..546693ed8 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/favourite/FavouriteScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/favourite/FavouriteScreen.kt @@ -29,7 +29,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewWrapper -import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.paging.LoadState import androidx.paging.PagingData @@ -45,6 +44,7 @@ import com.aurora.store.compose.composable.Error import com.aurora.store.compose.composable.FavouriteListItem import com.aurora.store.compose.composable.ScrollHint import com.aurora.store.compose.composable.TopAppBar +import com.aurora.store.compose.navigation.Destination import com.aurora.store.compose.preview.FavouritePreviewProvider import com.aurora.store.compose.preview.ThemePreviewProvider import com.aurora.store.compose.ui.favourite.menu.FavouriteMenu @@ -57,8 +57,7 @@ import kotlinx.coroutines.flow.MutableStateFlow @Composable fun FavouriteScreen( - onNavigateUp: () -> Unit, - onNavigateToAppDetails: (packageName: String) -> Unit, + onNavigateTo: (Destination) -> Unit, viewModel: FavouriteViewModel = hiltViewModel() ) { val context = LocalContext.current @@ -90,8 +89,7 @@ fun FavouriteScreen( ScreenContent( favourites = favourites, - onNavigateUp = onNavigateUp, - onNavigateToAppDetails = onNavigateToAppDetails, + onNavigateTo = onNavigateTo, onRemoveFavourite = { packageName -> viewModel.removeFavourite(packageName) }, onImportFavourites = { documentOpenLauncher.launch(arrayOf(JSON_MIME_TYPE)) @@ -107,8 +105,7 @@ fun FavouriteScreen( @Composable private fun ScreenContent( favourites: LazyPagingItems = emptyPagingItems(), - onNavigateUp: () -> Unit = {}, - onNavigateToAppDetails: (packageName: String) -> Unit = {}, + onNavigateTo: (Destination) -> Unit = {}, onRemoveFavourite: (packageName: String) -> Unit = {}, onImportFavourites: () -> Unit = {}, onExportFavourites: () -> Unit = {} @@ -135,7 +132,6 @@ private fun ScreenContent( topBar = { TopAppBar( title = stringResource(R.string.title_favourites_manager), - onNavigateUp = onNavigateUp, actions = { SetupMenu() } ) } @@ -175,7 +171,9 @@ private fun ScreenContent( modifier = Modifier.animateItem(), favourite = favourite, onClick = { - onNavigateToAppDetails(favourite.packageName) + onNavigateTo( + Destination.AppDetails(favourite.packageName) + ) }, onClear = { onRemoveFavourite(favourite.packageName) } ) @@ -184,7 +182,6 @@ private fun ScreenContent( } ScrollHint( listState = listState, - bottomPadding = 5.dp, modifier = Modifier.align(Alignment.BottomCenter) ) } diff --git a/app/src/main/java/com/aurora/store/compose/ui/installed/InstalledScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/installed/InstalledScreen.kt index 84e8396e3..ed7d9f6f3 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/installed/InstalledScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/installed/InstalledScreen.kt @@ -6,6 +6,7 @@ package com.aurora.store.compose.ui.installed +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -20,12 +21,12 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewWrapper -import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.paging.LoadState import androidx.paging.PagingData @@ -40,6 +41,7 @@ import com.aurora.store.compose.composable.Error import com.aurora.store.compose.composable.ScrollHint import com.aurora.store.compose.composable.TopAppBar import com.aurora.store.compose.composable.app.LargeAppListItem +import com.aurora.store.compose.navigation.Destination import com.aurora.store.compose.preview.AppPreviewProvider import com.aurora.store.compose.preview.ThemePreviewProvider import com.aurora.store.viewmodel.all.InstalledViewModel @@ -48,24 +50,21 @@ import kotlinx.coroutines.flow.MutableStateFlow @Composable fun InstalledScreen( - onNavigateUp: () -> Unit, - onNavigateToAppDetails: (packageName: String) -> Unit, + onNavigateTo: (Destination) -> Unit, viewModel: InstalledViewModel = hiltViewModel() ) { val apps = viewModel.apps.collectAsLazyPagingItems() ScreenContent( apps = apps, - onNavigateUp = onNavigateUp, - onNavigateToAppDetails = onNavigateToAppDetails + onNavigateTo = onNavigateTo ) } @Composable private fun ScreenContent( - onNavigateUp: () -> Unit = {}, apps: LazyPagingItems = emptyPagingItems(), - onNavigateToAppDetails: (packageName: String) -> Unit = {} + onNavigateTo: (Destination) -> Unit = {} ) { /* * For some reason paging3 frequently out-of-nowhere invalidates the list which causes @@ -78,8 +77,7 @@ private fun ScreenContent( Scaffold( topBar = { TopAppBar( - title = stringResource(R.string.title_apps_games), - onNavigateUp = onNavigateUp + title = stringResource(R.string.title_apps_games) ) } ) { paddingValues -> @@ -106,7 +104,10 @@ private fun ScreenContent( val listState = rememberLazyListState() Box(modifier = Modifier.fillMaxSize()) { LazyColumn( - state = listState + state = listState, + verticalArrangement = Arrangement.spacedBy( + dimensionResource(R.dimen.margin_medium) + ) ) { items( count = apps.itemCount, @@ -115,14 +116,17 @@ private fun ScreenContent( apps[index]?.let { app -> LargeAppListItem( app = app, - onClick = { onNavigateToAppDetails(app.packageName) } + onClick = { + onNavigateTo( + Destination.AppDetails(app.packageName) + ) + } ) } } } ScrollHint( listState = listState, - bottomPadding = 5.dp, modifier = Modifier.align(Alignment.BottomCenter) ) } diff --git a/app/src/main/java/com/aurora/store/compose/ui/onboarding/MicroGPage.kt b/app/src/main/java/com/aurora/store/compose/ui/onboarding/MicroGPage.kt index b0ff6d245..9b763682b 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/onboarding/MicroGPage.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/onboarding/MicroGPage.kt @@ -23,7 +23,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewWrapper -import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import com.aurora.extensions.toast import com.aurora.store.R @@ -105,7 +104,6 @@ private fun ScreenContent( ScrollHint( listState = listState, - bottomPadding = 5.dp, modifier = Modifier.align(Alignment.BottomCenter) ) } diff --git a/app/src/main/java/com/aurora/store/compose/ui/search/SearchScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/search/SearchScreen.kt index b20cf151b..1a443f1ad 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/search/SearchScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/search/SearchScreen.kt @@ -6,8 +6,9 @@ package com.aurora.store.compose.ui.search +import androidx.activity.ComponentActivity +import androidx.activity.compose.LocalActivity import androidx.annotation.StringRes -import androidx.compose.foundation.focusable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.foundation.layout.Arrangement @@ -62,7 +63,6 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewScreenSizes import androidx.compose.ui.tooling.preview.PreviewWrapper -import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.paging.LoadState @@ -78,6 +78,7 @@ import com.aurora.store.compose.composable.Error import com.aurora.store.compose.composable.ScrollHint import com.aurora.store.compose.composable.SearchSuggestionListItem import com.aurora.store.compose.composable.app.LargeAppListItem +import com.aurora.store.compose.navigation.Destination import com.aurora.store.compose.preview.AppPreviewProvider import com.aurora.store.compose.preview.ThemePreviewProvider import com.aurora.store.compose.ui.details.AppDetailsScreen @@ -91,7 +92,7 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch @Composable -fun SearchScreen(onNavigateUp: () -> Unit, viewModel: SearchViewModel = hiltViewModel()) { +fun SearchScreen(viewModel: SearchViewModel = hiltViewModel()) { val suggestions by viewModel.suggestions.collectAsStateWithLifecycle() val results = viewModel.apps.collectAsLazyPagingItems() @@ -102,7 +103,6 @@ fun SearchScreen(onNavigateUp: () -> Unit, viewModel: SearchViewModel = hiltView ScreenContent( suggestions = suggestions, results = results, - onNavigateUp = onNavigateUp, onSearch = onSearchCallback, onFetchSuggestions = onFetchSuggestionsCallback, onFilter = { filter -> viewModel.filterResults(filter) }, @@ -114,12 +114,12 @@ fun SearchScreen(onNavigateUp: () -> Unit, viewModel: SearchViewModel = hiltView private fun ScreenContent( suggestions: List = emptyList(), results: LazyPagingItems = emptyPagingItems(), - onNavigateUp: () -> Unit = {}, onFetchSuggestions: (String) -> Unit = {}, onSearch: (String) -> Unit = {}, onFilter: (filter: SearchFilter) -> Unit = {}, isAnonymous: Boolean = true ) { + val activity = LocalActivity.current as? ComponentActivity val textFieldState = rememberTextFieldState() val searchBarState = rememberSearchBarState() var isSearching by rememberSaveable { mutableStateOf(false) } @@ -186,7 +186,7 @@ private fun ScreenContent( ) }, leadingIcon = { - IconButton(onClick = onNavigateUp) { + IconButton(onClick = { activity?.onBackPressedDispatcher?.onBackPressed() }) { Icon( painter = painterResource(R.drawable.ic_arrow_back), contentDescription = stringResource(R.string.action_back) @@ -231,10 +231,7 @@ private fun ScreenContent( @Composable fun ListPane() { - // TODO: https://issuetracker.google.com/issues/445720462 Scaffold( - modifier = Modifier - .focusable(), topBar = { SearchBar() } ) { paddingValues -> Column( @@ -272,7 +269,10 @@ private fun ScreenContent( Box(modifier = Modifier.fillMaxSize()) { LazyColumn( state = listState, - modifier = Modifier.fillMaxSize() + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy( + dimensionResource(R.dimen.margin_medium) + ) ) { items( count = results.itemCount, @@ -288,7 +288,6 @@ private fun ScreenContent( } ScrollHint( listState = listState, - bottomPadding = 5.dp, modifier = Modifier.align(Alignment.BottomCenter) ) } @@ -306,9 +305,10 @@ private fun ScreenContent( this != null -> { AppDetailsScreen( packageName = this, - onNavigateToAppDetails = { packageName -> showDetailPane(packageName) }, - onNavigateUp = { - coroutineScope.launch { scaffoldNavigator.navigateBack() } + onNavigateTo = { destination -> + if (destination is Destination.AppDetails) { + showDetailPane(destination.packageName) + } }, forceSinglePane = true ) diff --git a/app/src/main/java/com/aurora/store/compose/ui/spoof/SpoofScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/spoof/SpoofScreen.kt index 4c2f1e392..f43b42fa2 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/spoof/SpoofScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/spoof/SpoofScreen.kt @@ -36,6 +36,7 @@ import com.aurora.Constants import com.aurora.extensions.toast import com.aurora.store.R import com.aurora.store.compose.composable.TopAppBar +import com.aurora.store.compose.navigation.Destination import com.aurora.store.compose.ui.spoof.menu.MenuItem import com.aurora.store.compose.ui.spoof.menu.SpoofMenu import com.aurora.store.compose.ui.spoof.navigation.SpoofPage @@ -44,14 +45,9 @@ import com.aurora.store.viewmodel.spoof.SpoofViewModel import kotlinx.coroutines.launch @Composable -fun SpoofScreen( - onNavigateUp: () -> Unit, - onNavigateToSplash: () -> Unit, - viewModel: SpoofViewModel = hiltViewModel() -) { +fun SpoofScreen(onNavigateTo: (Destination) -> Unit, viewModel: SpoofViewModel = hiltViewModel()) { ScreenContent( - onNavigateUp = onNavigateUp, - onNavigateToSplash = onNavigateToSplash, + onNavigateTo = onNavigateTo, onDeviceSpoofImport = { uri -> viewModel.importDeviceSpoof(uri) }, onDeviceSpoofExport = { uri -> viewModel.exportDeviceSpoof(uri) } ) @@ -60,8 +56,7 @@ fun SpoofScreen( @Composable private fun ScreenContent( pages: List = listOf(SpoofPage.DEVICE, SpoofPage.LOCALE), - onNavigateUp: () -> Unit = {}, - onNavigateToSplash: () -> Unit = {}, + onNavigateTo: (Destination) -> Unit = {}, onDeviceSpoofImport: (uri: Uri) -> Unit = {}, onDeviceSpoofExport: (uri: Uri) -> Unit = {} ) { @@ -101,7 +96,7 @@ private fun ScreenContent( when (result) { SnackbarResult.ActionPerformed -> { AccountProvider.logout(context) - onNavigateToSplash() + onNavigateTo(Destination.Splash) } else -> Unit @@ -133,7 +128,6 @@ private fun ScreenContent( topBar = { TopAppBar( title = stringResource(R.string.title_spoof_manager), - onNavigateUp = onNavigateUp, actions = { SetupMenu() } ) } diff --git a/app/src/main/java/com/aurora/store/data/providers/PermissionProvider.kt b/app/src/main/java/com/aurora/store/data/providers/PermissionProvider.kt index 9226be283..7c169b34d 100644 --- a/app/src/main/java/com/aurora/store/data/providers/PermissionProvider.kt +++ b/app/src/main/java/com/aurora/store/data/providers/PermissionProvider.kt @@ -1,3 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2026 Aurora OSS + * SPDX-License-Identifier: GPL-3.0-or-later + */ + package com.aurora.store.data.providers import android.Manifest diff --git a/app/src/main/java/com/aurora/store/util/NotificationUtil.kt b/app/src/main/java/com/aurora/store/util/NotificationUtil.kt index da2e95e64..99a6b9634 100644 --- a/app/src/main/java/com/aurora/store/util/NotificationUtil.kt +++ b/app/src/main/java/com/aurora/store/util/NotificationUtil.kt @@ -10,14 +10,13 @@ import android.graphics.Bitmap import android.graphics.Color import android.net.Uri import android.os.Build -import android.os.Bundle import androidx.core.app.NotificationCompat import androidx.core.app.PendingIntentCompat import androidx.core.content.getSystemService -import androidx.navigation.NavDeepLinkBuilder import com.aurora.Constants -import com.aurora.store.MainActivity +import com.aurora.store.ComposeActivity import com.aurora.store.R +import com.aurora.store.compose.navigation.Screen import com.aurora.store.data.activity.InstallActivity import com.aurora.store.data.helper.DownloadHelper import com.aurora.store.data.installer.AppInstaller @@ -213,16 +212,7 @@ object NotificationUtil { .build() fun getUpdateNotification(context: Context, updatesList: List): Notification { - val arguments = Bundle().apply { - putInt("destinationId", R.id.updatesFragment) - } - - val contentIntent = NavDeepLinkBuilder(context) - .setGraph(R.navigation.mobile_navigation) - .setDestination(R.id.splashFragment) - .setComponentName(MainActivity::class.java) - .setArguments(arguments) - .createPendingIntent() + val contentIntent = getContentIntentForMain(context, initialTab = 2) return NotificationCompat.Builder(context, Constants.NOTIFICATION_CHANNEL_UPDATES) .setSmallIcon(R.drawable.ic_updates) @@ -297,7 +287,7 @@ object NotificationUtil { .setContentTitle(context.getString(R.string.authentication_required_title)) .setContentText(context.getString(R.string.authentication_required_unarchive)) .setAutoCancel(true) - .setContentIntent(getContentIntentForSplash(context, packageName)) + .setContentIntent(getContentIntentForDetails(context, packageName)) .setCategory(NotificationCompat.CATEGORY_ERROR) .setPriority(NotificationCompat.PRIORITY_HIGH) .build() @@ -359,28 +349,32 @@ object NotificationUtil { ) } - private fun getContentIntentForSplash(context: Context, packageName: String): PendingIntent { - val arguments = Bundle().apply { - putString("packageName", packageName) + private fun getContentIntentForDetails(context: Context, packageName: String): PendingIntent? { + val intent = Intent(context, ComposeActivity::class.java).apply { + putExtra(Screen.PARCEL_KEY, Screen.AppDetails(packageName) as android.os.Parcelable) + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK } - return NavDeepLinkBuilder(context) - .setGraph(R.navigation.mobile_navigation) - .setDestination(R.id.splashFragment) - .setComponentName(MainActivity::class.java) - .setArguments(arguments) - .createPendingIntent() + return PendingIntentCompat.getActivity( + context, + packageName.hashCode().absoluteValue, + intent, + PendingIntent.FLAG_UPDATE_CURRENT, + false + ) } - private fun getContentIntentForDetails(context: Context, packageName: String): PendingIntent { - val arguments = Bundle().apply { - putString("packageName", packageName) + private fun getContentIntentForMain(context: Context, initialTab: Int): PendingIntent? { + val intent = Intent(context, ComposeActivity::class.java).apply { + putExtra(Screen.PARCEL_KEY, Screen.Main(initialTab) as android.os.Parcelable) + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK } - return NavDeepLinkBuilder(context) - .setGraph(R.navigation.mobile_navigation) - .setDestination(R.id.splashFragment) - .setComponentName(MainActivity::class.java) - .setArguments(arguments) - .createPendingIntent() + return PendingIntentCompat.getActivity( + context, + initialTab, + intent, + PendingIntent.FLAG_UPDATE_CURRENT, + false + ) } private fun getInstallIntent(context: Context, download: Download): PendingIntent? { diff --git a/app/src/main/java/com/aurora/store/viewmodel/auth/AuthViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/auth/AuthViewModel.kt index 20a06c9b3..ed8ec0541 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/auth/AuthViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/auth/AuthViewModel.kt @@ -34,6 +34,7 @@ import com.aurora.store.data.model.AccountType import com.aurora.store.data.model.AuthState import com.aurora.store.data.providers.AccountProvider import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.data.providers.NetworkProvider import com.aurora.store.util.AC2DMTask import com.aurora.store.util.PackageUtil import com.aurora.store.util.Preferences @@ -50,6 +51,7 @@ import kotlinx.coroutines.launch @HiltViewModel class AuthViewModel @Inject constructor( val authProvider: AuthProvider, + val networkProvider: NetworkProvider, @ApplicationContext private val context: Context, private val aC2DMTask: AC2DMTask ) : ViewModel() { @@ -117,6 +119,8 @@ class AuthViewModel @Inject constructor( } } + fun retry() = updateAuthState() + private fun updateAuthState() { if (_authState.value != AuthState.Fetching) { if (AccountProvider.isLoggedIn(context)) { diff --git a/app/src/main/java/com/aurora/store/viewmodel/browse/ExpandedStreamBrowseViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/browse/ExpandedStreamBrowseViewModel.kt index 9679aa0e0..9c503fc0b 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/browse/ExpandedStreamBrowseViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/browse/ExpandedStreamBrowseViewModel.kt @@ -1,85 +1,90 @@ /* - * Aurora Store - * Copyright (C) 2021, Rahul Kumar Patel - * - * Aurora Store is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * Aurora Store is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Aurora Store. If not, see . - * + * SPDX-FileCopyrightText: 2024 Aurora OSS + * SPDX-FileCopyrightText: 2021 Rahul Kumar Patel + * SPDX-License-Identifier: GPL-3.0-or-later */ package com.aurora.store.viewmodel.browse import android.util.Log -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.paging.PagingData +import androidx.paging.cachedIn import com.aurora.extensions.TAG -import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.helpers.ExpandedBrowseHelper +import com.aurora.store.data.PageResult +import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.supervisorScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach -@HiltViewModel -class ExpandedStreamBrowseViewModel @Inject constructor( +@HiltViewModel(assistedFactory = ExpandedStreamBrowseViewModel.Factory::class) +class ExpandedStreamBrowseViewModel @AssistedInject constructor( + @Assisted val browseUrl: String, private val streamHelper: ExpandedBrowseHelper ) : ViewModel() { - val liveData: MutableLiveData = MutableLiveData() - var streamCluster: StreamCluster = StreamCluster() - - fun getInitialCluster(expandedStreamUrl: String) { - viewModelScope.launch(Dispatchers.IO) { - supervisorScope { - try { - val browseResponse = streamHelper.getBrowseStreamResponse(expandedStreamUrl) - if (browseResponse.hasBrowseTab()) { - streamCluster = - streamHelper.getExpandedBrowseClusters(browseResponse.browseTab.listUrl) - liveData.postValue(streamCluster) - } else { - } - } catch (_: Exception) { - } - } - } + @AssistedFactory + interface Factory { + fun create(browseUrl: String): ExpandedStreamBrowseViewModel } - fun next() { - Log.e(TAG, "NEXT CALED") - viewModelScope.launch(Dispatchers.IO) { - supervisorScope { - try { - val newCluster = streamHelper.getExpandedBrowseClusters( - streamCluster.clusterNextPageUrl - ) + private val _title = MutableStateFlow("") + val title = _title.asStateFlow() - streamCluster = streamCluster.copy( - clusterAppList = streamCluster.clusterAppList + newCluster.clusterAppList, - clusterNextPageUrl = newCluster.clusterNextPageUrl - ) + private val _apps = MutableStateFlow>(PagingData.empty()) + val apps = _apps.asStateFlow() - liveData.postValue(streamCluster) + init { + fetchApps() + } - if (!streamCluster.hasNext()) { - Log.i(TAG, "End of Bundle") + private fun fetchApps() { + var listUrl = "" + var nextPageUrl = "" + + manualPager { page -> + val items = try { + when (page) { + 1 -> { + val browseResponse = streamHelper.getBrowseStreamResponse(browseUrl) + if (browseResponse.hasBrowseTab()) { + listUrl = browseResponse.browseTab.listUrl + val cluster = streamHelper.getExpandedBrowseClusters(listUrl) + _title.value = cluster.clusterTitle + nextPageUrl = cluster.clusterNextPageUrl + cluster.clusterAppList + } else { + emptyList() + } + } + + else -> { + if (nextPageUrl.isNotBlank()) { + val cluster = streamHelper.getExpandedBrowseClusters(nextPageUrl) + nextPageUrl = cluster.clusterNextPageUrl + cluster.clusterAppList + } else { + emptyList() + } } - } catch (exception: Exception) { - Log.e(TAG, "Failed to fetch next stream", exception) } + } catch (exception: Exception) { + Log.e(TAG, "Failed to fetch apps for page $page", exception) + emptyList() } - } + PageResult(items) + }.flow.distinctUntilChanged() + .cachedIn(viewModelScope) + .onEach { _apps.value = it } + .launchIn(viewModelScope) } } diff --git a/app/src/main/java/com/aurora/store/viewmodel/subcategory/CategoryStreamViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/subcategory/CategoryStreamViewModel.kt index 5976baf93..05cba8221 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/subcategory/CategoryStreamViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/subcategory/CategoryStreamViewModel.kt @@ -1,26 +1,12 @@ /* - * Aurora Store - * Copyright (C) 2021, Rahul Kumar Patel - * - * Aurora Store is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * Aurora Store is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Aurora Store. If not, see . - * + * SPDX-FileCopyrightText: 2024 Aurora OSS + * SPDX-FileCopyrightText: 2021 Rahul Kumar Patel + * SPDX-License-Identifier: GPL-3.0-or-later */ package com.aurora.store.viewmodel.subcategory import android.util.Log -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.aurora.extensions.TAG @@ -30,70 +16,82 @@ import com.aurora.gplayapi.helpers.contracts.CategoryStreamContract import com.aurora.gplayapi.helpers.contracts.StreamContract import com.aurora.gplayapi.helpers.web.WebCategoryStreamHelper import com.aurora.store.data.model.ViewState +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope -@HiltViewModel -class CategoryStreamViewModel @Inject constructor( +@HiltViewModel(assistedFactory = CategoryStreamViewModel.Factory::class) +class CategoryStreamViewModel @AssistedInject constructor( + @Assisted val browseUrl: String, private val webCategoryStreamHelper: WebCategoryStreamHelper ) : ViewModel() { - val liveData: MutableLiveData = MutableLiveData() + @AssistedFactory + interface Factory { + fun create(browseUrl: String): CategoryStreamViewModel + } - private var stash: MutableMap = mutableMapOf() + // SharedFlow (instead of StateFlow) because StreamBundle/StreamCluster override equals + // to compare only id, which is preserved by copy(). StateFlow would conflate every update + // and break paginated scroll loading. + private val _viewState = MutableSharedFlow( + replay = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + val viewState: SharedFlow = _viewState.asSharedFlow() private val categoryStreamContract: CategoryStreamContract get() = webCategoryStreamHelper - fun getStreamBundle(browseUrl: String) { - liveData.postValue(ViewState.Loading) - observe(browseUrl) + private var streamBundle = StreamBundle() + + init { + fetchNextPage() } - fun observe(browseUrl: String) { - liveData.postValue(ViewState.Loading) + fun fetchNextPage() { viewModelScope.launch(Dispatchers.IO) { supervisorScope { - val bundle = targetBundle(browseUrl) - if (bundle.streamClusters.isNotEmpty()) { - liveData.postValue(ViewState.Success(stash)) + if (streamBundle.streamClusters.isNotEmpty()) { + _viewState.tryEmit(ViewState.Success(streamBundle)) } try { - if (!bundle.hasCluster() || bundle.hasNext()) { - // Fetch new stream bundle - val newBundle = if (bundle.streamClusters.isEmpty()) { + if (!streamBundle.hasCluster() || streamBundle.hasNext()) { + val newBundle = if (streamBundle.streamClusters.isEmpty()) { categoryStreamContract.fetch(browseUrl) } else { categoryStreamContract.nextStreamBundle( StreamContract.Category.NONE, - bundle.streamNextPageUrl + streamBundle.streamNextPageUrl ) } - // Update old bundle - val mergedBundle = bundle.copy( - streamClusters = bundle.streamClusters + newBundle.streamClusters, + streamBundle = streamBundle.copy( + streamClusters = streamBundle.streamClusters + newBundle.streamClusters, streamNextPageUrl = newBundle.streamNextPageUrl ) - stash[browseUrl] = mergedBundle - // Post updated to UI - liveData.postValue(ViewState.Success(stash)) + _viewState.tryEmit(ViewState.Success(streamBundle)) } else { Log.i(TAG, "End of Bundle") } } catch (e: Exception) { - liveData.postValue(ViewState.Error(e.message)) + _viewState.tryEmit(ViewState.Error(e.message)) } } } } - fun observeCluster(browseUrl: String, streamCluster: StreamCluster) { + fun fetchNextCluster(streamCluster: StreamCluster) { viewModelScope.launch(Dispatchers.IO) { supervisorScope { try { @@ -101,36 +99,24 @@ class CategoryStreamViewModel @Inject constructor( val newCluster = categoryStreamContract.nextStreamCluster( streamCluster.clusterNextPageUrl ) - updateCluster(browseUrl, streamCluster.id, newCluster) - liveData.postValue(ViewState.Success(stash)) + val mergedCluster = streamCluster.copy( + clusterNextPageUrl = newCluster.clusterNextPageUrl, + clusterAppList = + streamCluster.clusterAppList + newCluster.clusterAppList + ) + + val newClusters = streamBundle.streamClusters.toMutableMap().apply { + this[streamCluster.id] = mergedCluster + } + streamBundle = streamBundle.copy(streamClusters = newClusters) + _viewState.tryEmit(ViewState.Success(streamBundle)) } else { Log.i(TAG, "End of cluster") } } catch (e: Exception) { - liveData.postValue(ViewState.Error(e.message)) + Log.e(TAG, "Failed to fetch next cluster", e) } } } } - - private fun updateCluster(browseUrl: String, clusterID: Int, newCluster: StreamCluster) { - val bundle = targetBundle(browseUrl) - bundle.streamClusters[clusterID]?.let { oldCluster -> - val mergedCluster = oldCluster.copy( - clusterNextPageUrl = newCluster.clusterNextPageUrl, - clusterAppList = oldCluster.clusterAppList + newCluster.clusterAppList - ) - - val newStreamClusters = bundle.streamClusters.toMutableMap().apply { - this[clusterID] = mergedCluster - } - - stash.put(browseUrl, bundle.copy(streamClusters = newStreamClusters)) - } - } - - private fun targetBundle(browseUrl: String): StreamBundle { - val streamBundle = stash.getOrPut(browseUrl) { StreamBundle() } - return streamBundle - } } diff --git a/app/src/main/java/com/aurora/store/viewmodel/topchart/TopChartViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/topchart/TopChartViewModel.kt index 4fe8f9f07..256b9fc75 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/topchart/TopChartViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/topchart/TopChartViewModel.kt @@ -19,7 +19,6 @@ package com.aurora.store.viewmodel.topchart -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.aurora.gplayapi.data.models.StreamCluster @@ -30,6 +29,10 @@ import com.aurora.store.data.model.ViewState import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope @@ -40,7 +43,14 @@ class TopChartViewModel @Inject constructor( private var stash: TopChartStash = mutableMapOf() - val liveData: MutableLiveData = MutableLiveData() + // SharedFlow (instead of StateFlow) because StreamCluster overrides equals to compare + // only id, which is preserved by copy(). StateFlow would conflate paginated updates and + // break scroll loading. See CategoryStreamViewModel for the same fix. + private val _state = MutableSharedFlow( + replay = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + val state: SharedFlow = _state.asSharedFlow() private val topChartsContract: TopChartsContract get() = webTopChartsHelper @@ -48,14 +58,18 @@ class TopChartViewModel @Inject constructor( fun getStreamCluster(type: TopChartsContract.Type, chart: TopChartsContract.Chart) { viewModelScope.launch(Dispatchers.IO) { if (targetCluster(type, chart).clusterAppList.isNotEmpty()) { - liveData.postValue(ViewState.Success(stash)) + _state.tryEmit(ViewState.Success(targetCluster(type, chart))) + return@launch } + _state.tryEmit(ViewState.Loading) + try { val cluster = topChartsContract.getCluster(type.value, chart.value) updateCluster(type, chart, cluster) - liveData.postValue(ViewState.Success(stash)) - } catch (_: Exception) { + _state.tryEmit(ViewState.Success(targetCluster(type, chart))) + } catch (e: Exception) { + _state.tryEmit(ViewState.Error(e.message)) } } } @@ -72,7 +86,7 @@ class TopChartViewModel @Inject constructor( updateCluster(type, chart, newCluster) - liveData.postValue(ViewState.Success(stash)) + _state.tryEmit(ViewState.Success(targetCluster(type, chart))) } } catch (_: Exception) { }