compose: migrate remaining screens and fix category browse

CategoryBrowseScreen and ExpandedStreamBrowseScreen are added; the
details / dev / dispenser / downloads / favourite / installed /
search / spoof / blacklist / accounts / about screens, StreamBrowse,
the onboarding MicroG page, the AppDetailsScreen plus the affected
viewmodels (AuthViewModel, ExpandedStreamBrowse, CategoryStream,
TopChart) are reworked to feed the new Compose entry points.

Also fixes CategoryBrowse pagination and the cluster-header
navigation regressions surfaced during migration.
This commit is contained in:
Rahul Patel
2026-05-19 12:13:20 +05:30
parent f2474cfb91
commit 0270dc6f39
29 changed files with 478 additions and 372 deletions

View File

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

View File

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

View File

@@ -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<BlacklistAppItem>? = 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)
)
}

View File

@@ -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() }
)
}
}
}

View File

@@ -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))
}
)
}
}
}
}
}
}
}
}

View File

@@ -35,14 +35,12 @@ import kotlin.random.Random
@Composable
fun PermissionRationaleScreen(
requiredPermissions: Set<PermissionType> = 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<Permission> = 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 ->

View File

@@ -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<App> = 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))
}
)
}
}

View File

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

View File

@@ -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<ExodusTracker> = 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 ->

View File

@@ -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),

View File

@@ -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)
)
}

View File

@@ -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<App>? = 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<App>,
onNavigateToAppDetails: (packageName: String) -> Unit
) {
private fun AppDependencies(dependencies: List<App>, 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)) }
)
}
}

View File

@@ -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<String, PermissionInfo> = 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)
)
}

View File

@@ -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<Review> = 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)
)
}

View File

@@ -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<Artwork> = 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 ->

View File

@@ -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<App>,
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)) }
)
}
}

View File

@@ -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<String> = 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 = {

View File

@@ -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<Download> = 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)
)
}

View File

@@ -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<Favourite> = 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)
)
}

View File

@@ -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<App> = 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)
)
}

View File

@@ -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)
)
}

View File

@@ -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<SearchSuggestEntry> = emptyList(),
results: LazyPagingItems<App> = 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
)

View File

@@ -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<SpoofPage> = 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() }
)
}

View File

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

View File

@@ -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<Update>): 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? {

View File

@@ -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)) {

View File

@@ -1,85 +1,90 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*
* SPDX-FileCopyrightText: 2024 Aurora OSS
* SPDX-FileCopyrightText: 2021 Rahul Kumar Patel <whyorean@gmail.com>
* 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<StreamCluster> = MutableLiveData()
var streamCluster: StreamCluster = StreamCluster()
@AssistedFactory
interface Factory {
fun create(browseUrl: String): ExpandedStreamBrowseViewModel
}
fun getInitialCluster(expandedStreamUrl: String) {
viewModelScope.launch(Dispatchers.IO) {
supervisorScope {
try {
val browseResponse = streamHelper.getBrowseStreamResponse(expandedStreamUrl)
private val _title = MutableStateFlow("")
val title = _title.asStateFlow()
private val _apps = MutableStateFlow<PagingData<App>>(PagingData.empty())
val apps = _apps.asStateFlow()
init {
fetchApps()
}
private fun fetchApps() {
var listUrl = ""
var nextPageUrl = ""
manualPager { page ->
val items = try {
when (page) {
1 -> {
val browseResponse = streamHelper.getBrowseStreamResponse(browseUrl)
if (browseResponse.hasBrowseTab()) {
streamCluster =
streamHelper.getExpandedBrowseClusters(browseResponse.browseTab.listUrl)
liveData.postValue(streamCluster)
listUrl = browseResponse.browseTab.listUrl
val cluster = streamHelper.getExpandedBrowseClusters(listUrl)
_title.value = cluster.clusterTitle
nextPageUrl = cluster.clusterNextPageUrl
cluster.clusterAppList
} else {
}
} catch (_: Exception) {
}
}
emptyList()
}
}
fun next() {
Log.e(TAG, "NEXT CALED")
viewModelScope.launch(Dispatchers.IO) {
supervisorScope {
try {
val newCluster = streamHelper.getExpandedBrowseClusters(
streamCluster.clusterNextPageUrl
)
streamCluster = streamCluster.copy(
clusterAppList = streamCluster.clusterAppList + newCluster.clusterAppList,
clusterNextPageUrl = newCluster.clusterNextPageUrl
)
liveData.postValue(streamCluster)
if (!streamCluster.hasNext()) {
Log.i(TAG, "End of Bundle")
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)
}
}
}
Log.e(TAG, "Failed to fetch apps for page $page", exception)
emptyList()
}
PageResult(items)
}.flow.distinctUntilChanged()
.cachedIn(viewModelScope)
.onEach { _apps.value = it }
.launchIn(viewModelScope)
}
}

View File

@@ -1,26 +1,12 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*
* SPDX-FileCopyrightText: 2024 Aurora OSS
* SPDX-FileCopyrightText: 2021 Rahul Kumar Patel <whyorean@gmail.com>
* 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<ViewState> = MutableLiveData()
@AssistedFactory
interface Factory {
fun create(browseUrl: String): CategoryStreamViewModel
}
private var stash: MutableMap<String, StreamBundle> = 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<ViewState>(
replay = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
val viewState: SharedFlow<ViewState> = _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
}
}

View File

@@ -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<ViewState> = 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<ViewState>(
replay = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
val state: SharedFlow<ViewState> = _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) {
}