From 49718f87235fb692f40406b936cb5c57d772ba3b Mon Sep 17 00:00:00 2001 From: Rahul Patel Date: Wed, 6 May 2026 02:28:12 +0530 Subject: [PATCH 1/9] fix: allow importing favourite app even if list is empty --- .../com/aurora/store/compose/ui/favourite/FavouriteScreen.kt | 4 ++-- .../aurora/store/compose/ui/favourite/menu/FavouriteMenu.kt | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) 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 82e909c77..cd6e9b867 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 @@ -117,7 +117,7 @@ private fun ScreenContent( @Composable fun SetupMenu() { - FavouriteMenu { menuItem -> + FavouriteMenu(items = favourites.itemCount) { menuItem -> when (menuItem) { MenuItem.IMPORT -> onImportFavourites() MenuItem.EXPORT -> onExportFavourites() @@ -130,7 +130,7 @@ private fun ScreenContent( TopAppBar( title = stringResource(R.string.title_favourites_manager), onNavigateUp = onNavigateUp, - actions = { if (favourites.itemCount != 0) SetupMenu() } + actions = { SetupMenu() } ) } ) { paddingValues -> diff --git a/app/src/main/java/com/aurora/store/compose/ui/favourite/menu/FavouriteMenu.kt b/app/src/main/java/com/aurora/store/compose/ui/favourite/menu/FavouriteMenu.kt index 93604236d..11eedd5fd 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/favourite/menu/FavouriteMenu.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/favourite/menu/FavouriteMenu.kt @@ -34,6 +34,7 @@ import com.aurora.store.compose.preview.ThemePreviewProvider fun FavouriteMenu( modifier: Modifier = Modifier, isExpanded: Boolean = false, + items: Int = 0, onMenuItemClicked: (menuItem: MenuItem) -> Unit = {} ) { var expanded by remember { mutableStateOf(isExpanded) } @@ -56,7 +57,8 @@ fun FavouriteMenu( ) DropdownMenuItem( text = { Text(text = stringResource(R.string.action_export)) }, - onClick = { onClick(MenuItem.EXPORT) } + onClick = { onClick(MenuItem.EXPORT) }, + enabled = items > 0 ) } } From 1484e0f24bb77aed77d17af8b7ba6574f0c9cc86 Mon Sep 17 00:00:00 2001 From: Rahul Patel Date: Wed, 6 May 2026 03:15:10 +0530 Subject: [PATCH 2/9] pager: do not skip first batch of packages + fix indexoutofbound exception --- .../com/aurora/store/data/paging/GenericPagingSource.kt | 6 +++--- .../com/aurora/store/viewmodel/all/InstalledViewModel.kt | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/aurora/store/data/paging/GenericPagingSource.kt b/app/src/main/java/com/aurora/store/data/paging/GenericPagingSource.kt index 9b170d274..42cf27838 100644 --- a/app/src/main/java/com/aurora/store/data/paging/GenericPagingSource.kt +++ b/app/src/main/java/com/aurora/store/data/paging/GenericPagingSource.kt @@ -8,6 +8,7 @@ package com.aurora.store.data.paging import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingSource +import androidx.paging.PagingSource.LoadResult.Page.Companion.COUNT_UNDEFINED import androidx.paging.PagingState import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager import com.aurora.store.data.paging.GenericPagingSource.Companion.pager @@ -65,12 +66,11 @@ class GenericPagingSource( return try { withContext(Dispatchers.IO) { val data = block(page) - val totalPages = if (data.isNotEmpty()) page + 1 else page LoadResult.Page( data = data, prevKey = if (page == 1) null else page - 1, - nextKey = if (page == totalPages) null else totalPages, - itemsAfter = if (page == totalPages) 0 else DEFAULT_PAGE_SIZE + nextKey = if (data.isEmpty()) null else page + 1, + itemsAfter = if (data.isEmpty()) 0 else COUNT_UNDEFINED ) } } catch (exception: Exception) { diff --git a/app/src/main/java/com/aurora/store/viewmodel/all/InstalledViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/all/InstalledViewModel.kt index da3ab3f3d..0a0f7493f 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/all/InstalledViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/all/InstalledViewModel.kt @@ -64,9 +64,9 @@ class InstalledViewModel @Inject constructor( manualPager { page -> try { - webAppDetailsHelper.getAppDetails( - pagedPackages[page].map { it.packageName } - ) + // page is 1-indexed, but list is 0-indexed + val chunk = pagedPackages.getOrNull(page - 1) ?: return@manualPager emptyList() + webAppDetailsHelper.getAppDetails(chunk.map { it.packageName }) } catch (exception: Exception) { Log.e(TAG, "Failed to fetch apps", exception) emptyList() From b2d10c17b49d90eb7b088317d9c6b2ae6a7d9616 Mon Sep 17 00:00:00 2001 From: Rahul Patel Date: Wed, 6 May 2026 04:33:29 +0530 Subject: [PATCH 3/9] pager: do not skip next batch if current batch yields no valid apps --- .../java/com/aurora/store/data/PageResult.kt | 11 +++++++++++ .../store/data/paging/GenericPagingSource.kt | 13 +++++++------ .../store/viewmodel/all/InstalledViewModel.kt | 16 ++++++---------- .../viewmodel/browse/StreamBrowseViewModel.kt | 4 +++- .../store/viewmodel/details/ReviewViewModel.kt | 4 +++- .../store/viewmodel/search/SearchViewModel.kt | 4 +++- 6 files changed, 33 insertions(+), 19 deletions(-) create mode 100644 app/src/main/java/com/aurora/store/data/PageResult.kt diff --git a/app/src/main/java/com/aurora/store/data/PageResult.kt b/app/src/main/java/com/aurora/store/data/PageResult.kt new file mode 100644 index 000000000..280735b5b --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/PageResult.kt @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2025 Aurora OSS + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.aurora.store.data + +data class PageResult( + val items: List, + val hasMore: Boolean = items.isNotEmpty() +) diff --git a/app/src/main/java/com/aurora/store/data/paging/GenericPagingSource.kt b/app/src/main/java/com/aurora/store/data/paging/GenericPagingSource.kt index 42cf27838..768592bb3 100644 --- a/app/src/main/java/com/aurora/store/data/paging/GenericPagingSource.kt +++ b/app/src/main/java/com/aurora/store/data/paging/GenericPagingSource.kt @@ -10,6 +10,7 @@ import androidx.paging.PagingConfig import androidx.paging.PagingSource import androidx.paging.PagingSource.LoadResult.Page.Companion.COUNT_UNDEFINED import androidx.paging.PagingState +import com.aurora.store.data.PageResult import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager import com.aurora.store.data.paging.GenericPagingSource.Companion.pager import kotlinx.coroutines.Dispatchers @@ -23,7 +24,7 @@ import kotlinx.coroutines.withContext * @param block Data to load into the pager */ class GenericPagingSource( - private val block: suspend (Int) -> List + private val block: suspend (Int) -> PageResult ) : PagingSource() { companion object { @@ -54,7 +55,7 @@ class GenericPagingSource( fun manualPager( pageSize: Int = DEFAULT_PAGE_SIZE, enablePlaceholders: Boolean = true, - data: suspend (Int) -> List + data: suspend (Int) -> PageResult ): Pager = Pager( config = PagingConfig(enablePlaceholders = enablePlaceholders, pageSize = pageSize), pagingSourceFactory = { GenericPagingSource(data) } @@ -65,12 +66,12 @@ class GenericPagingSource( val page = params.key ?: 1 return try { withContext(Dispatchers.IO) { - val data = block(page) + val result = block(page) LoadResult.Page( - data = data, + data = result.items, prevKey = if (page == 1) null else page - 1, - nextKey = if (data.isEmpty()) null else page + 1, - itemsAfter = if (data.isEmpty()) 0 else COUNT_UNDEFINED + nextKey = if (result.hasMore) page + 1 else null, + itemsAfter = if (result.hasMore) COUNT_UNDEFINED else 0 ) } } catch (exception: Exception) { diff --git a/app/src/main/java/com/aurora/store/viewmodel/all/InstalledViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/all/InstalledViewModel.kt index 0a0f7493f..0867e30be 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/all/InstalledViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/all/InstalledViewModel.kt @@ -20,14 +20,13 @@ package com.aurora.store.viewmodel.all import android.content.Context -import android.util.Log 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.App import com.aurora.gplayapi.helpers.web.WebAppDetailsHelper +import com.aurora.store.data.PageResult import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager import com.aurora.store.data.providers.BlacklistProvider import com.aurora.store.util.PackageUtil @@ -63,14 +62,11 @@ class InstalledViewModel @Inject constructor( .chunked(20) manualPager { page -> - try { - // page is 1-indexed, but list is 0-indexed - val chunk = pagedPackages.getOrNull(page - 1) ?: return@manualPager emptyList() - webAppDetailsHelper.getAppDetails(chunk.map { it.packageName }) - } catch (exception: Exception) { - Log.e(TAG, "Failed to fetch apps", exception) - emptyList() - } + // page is 1-indexed, but list is 0-indexed + val chunk = pagedPackages.getOrNull(page - 1) + ?: return@manualPager PageResult(emptyList(), hasMore = false) + val items = webAppDetailsHelper.getAppDetails(chunk.map { it.packageName }) + PageResult(items, hasMore = page < pagedPackages.size) }.flow.distinctUntilChanged() .cachedIn(viewModelScope) .onEach { _apps.value = it } diff --git a/app/src/main/java/com/aurora/store/viewmodel/browse/StreamBrowseViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/browse/StreamBrowseViewModel.kt index c38526b54..347c1dbb6 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/browse/StreamBrowseViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/browse/StreamBrowseViewModel.kt @@ -28,6 +28,7 @@ import com.aurora.extensions.TAG import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.StreamCluster import com.aurora.gplayapi.helpers.web.WebStreamHelper +import com.aurora.store.data.PageResult import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -61,7 +62,7 @@ class StreamBrowseViewModel @AssistedInject constructor( var nextPageUrl: String = streamCluster.clusterNextPageUrl manualPager { page -> - try { + val items = try { when (page) { 1 -> streamCluster.clusterAppList @@ -79,6 +80,7 @@ class StreamBrowseViewModel @AssistedInject constructor( Log.e(TAG, "Failed to fetch apps for $page: $nextPageUrl", exception) emptyList() } + PageResult(items) }.flow.distinctUntilChanged() .cachedIn(viewModelScope) .onEach { _apps.value = it } diff --git a/app/src/main/java/com/aurora/store/viewmodel/details/ReviewViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/details/ReviewViewModel.kt index 8f588bdba..100bb8448 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/details/ReviewViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/details/ReviewViewModel.kt @@ -13,6 +13,7 @@ import androidx.paging.cachedIn import com.aurora.extensions.TAG import com.aurora.gplayapi.data.models.Review import com.aurora.gplayapi.helpers.ReviewsHelper +import com.aurora.store.data.PageResult import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -46,7 +47,7 @@ class ReviewViewModel @AssistedInject constructor( var reviewsNextPageUrl: String? = null manualPager { page -> - try { + val items = try { when (page) { 1 -> reviewsHelper.getReviews(packageName, filter).also { reviewsNextPageUrl = it.nextPageUrl @@ -66,6 +67,7 @@ class ReviewViewModel @AssistedInject constructor( Log.e(TAG, "Failed to fetch reviews for $page: $reviewsNextPageUrl", exception) emptyList() } + PageResult(items) }.flow.distinctUntilChanged() .cachedIn(viewModelScope) .onEach { _reviews.value = it } diff --git a/app/src/main/java/com/aurora/store/viewmodel/search/SearchViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/search/SearchViewModel.kt index 96c8dfd1e..d39b92ee8 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/search/SearchViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/search/SearchViewModel.kt @@ -20,6 +20,7 @@ import com.aurora.gplayapi.data.models.StreamCluster import com.aurora.gplayapi.helpers.SearchHelper import com.aurora.gplayapi.helpers.contracts.SearchContract import com.aurora.gplayapi.helpers.web.WebSearchHelper +import com.aurora.store.data.PageResult import com.aurora.store.data.model.SearchFilter import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager import com.aurora.store.data.providers.AuthProvider @@ -80,7 +81,7 @@ class SearchViewModel @Inject constructor( }.distinctBy { app -> app.packageName } manualPager { page -> - try { + val items = try { when (page) { 1 -> contract.searchResults(query) .also { nextBundleUrl = it.streamNextPageUrl } @@ -110,6 +111,7 @@ class SearchViewModel @Inject constructor( Log.e(TAG, "Failed to search results for $query", exception) emptyList() } + PageResult(items) }.flow.distinctUntilChanged() .cachedIn(viewModelScope) .onEach { _apps.value = it } From fba6c4a8dff3d8ddbfcd76e6c4de0609dce796a0 Mon Sep 17 00:00:00 2001 From: Rahul Patel Date: Wed, 6 May 2026 05:24:10 +0530 Subject: [PATCH 4/9] compose: add scroll hint to all lazy columns + improve padding --- .../aurora/store/compose/composable/Header.kt | 13 +- .../aurora/store/compose/composable/Info.kt | 4 +- .../compose/ui/blacklist/BlacklistScreen.kt | 77 ++++--- .../compose/ui/details/AppDetailsScreen.kt | 194 +++++++++++------- .../store/compose/ui/details/ExodusScreen.kt | 66 +++--- .../store/compose/ui/details/MoreScreen.kt | 74 ++++--- .../compose/ui/details/PermissionScreen.kt | 64 +++--- .../store/compose/ui/details/ReviewScreen.kt | 48 +++-- .../compose/ui/details/composable/Actions.kt | 6 +- .../ui/details/composable/Changelog.kt | 29 ++- .../compose/ui/details/composable/Details.kt | 6 +- .../ui/details/composable/RatingAndReviews.kt | 9 +- .../ui/details/composable/Screenshots.kt | 6 +- .../compose/ui/details/composable/Tags.kt | 2 + .../compose/ui/details/composable/Testing.kt | 5 +- .../compose/ui/downloads/DownloadsScreen.kt | 49 +++-- .../compose/ui/favourite/FavouriteScreen.kt | 43 ++-- .../compose/ui/installed/InstalledScreen.kt | 37 +++- .../store/compose/ui/search/SearchScreen.kt | 40 +++- 19 files changed, 507 insertions(+), 265 deletions(-) diff --git a/app/src/main/java/com/aurora/store/compose/composable/Header.kt b/app/src/main/java/com/aurora/store/compose/composable/Header.kt index ad72517f4..9bda7ea90 100644 --- a/app/src/main/java/com/aurora/store/compose/composable/Header.kt +++ b/app/src/main/java/com/aurora/store/compose/composable/Header.kt @@ -11,14 +11,12 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -46,18 +44,16 @@ fun Header( Row( modifier = modifier .fillMaxWidth() - .clip(RoundedCornerShape(dimensionResource(R.dimen.radius_small))) .clickable(onClick = { if (onClick != null) onClick() }, enabled = onClick != null) .padding( - horizontal = dimensionResource(R.dimen.padding_small), - vertical = dimensionResource(R.dimen.padding_xxsmall) + horizontal = dimensionResource(R.dimen.padding_medium), + vertical = dimensionResource(R.dimen.padding_xsmall) ), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Column( - modifier = Modifier.weight(1F), - verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.padding_xsmall)) + modifier = Modifier.weight(1F) ) { Text( text = title, @@ -68,7 +64,8 @@ fun Header( if (!subtitle.isNullOrBlank()) { Text( text = subtitle, - style = MaterialTheme.typography.bodyMedium + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant ) } } diff --git a/app/src/main/java/com/aurora/store/compose/composable/Info.kt b/app/src/main/java/com/aurora/store/compose/composable/Info.kt index 80e684f20..0800f2df3 100644 --- a/app/src/main/java/com/aurora/store/compose/composable/Info.kt +++ b/app/src/main/java/com/aurora/store/compose/composable/Info.kt @@ -56,7 +56,7 @@ fun Info( .fillMaxWidth() .clickable(onClick = { if (onClick != null) onClick() }, enabled = onClick != null) .padding( - horizontal = dimensionResource(R.dimen.padding_small), + horizontal = dimensionResource(R.dimen.padding_medium), vertical = dimensionResource(R.dimen.padding_xxsmall) ), horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_normal)), @@ -75,7 +75,7 @@ fun Info( Text( text = description, style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.secondary + color = MaterialTheme.colorScheme.onSurfaceVariant ) } } 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 148e2ddb6..d83ac2820 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 @@ -10,10 +10,14 @@ import android.net.Uri import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.text.input.clearText import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd @@ -30,6 +34,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester @@ -40,6 +45,7 @@ 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.core.content.pm.PackageInfoCompat import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -47,6 +53,7 @@ import com.aurora.Constants import com.aurora.extensions.toast import com.aurora.store.R import com.aurora.store.compose.composable.BlackListItem +import com.aurora.store.compose.composable.ScrollHint import com.aurora.store.compose.preview.ThemePreviewProvider import com.aurora.store.compose.ui.blacklist.menu.BlacklistMenu import com.aurora.store.compose.ui.blacklist.menu.MenuItem @@ -209,35 +216,51 @@ private fun ScreenContent( ) } - Scaffold(topBar = { SearchBar() }) { paddingValues -> - LazyColumn( - modifier = Modifier - .fillMaxWidth() - .padding(paddingValues), - verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_xxsmall)) - ) { - items(items = packages ?: emptyList(), key = { p -> p.packageName.hashCode() }) { pkg -> - val isBlacklisted = isPackageBlacklisted(pkg.packageName) - val isFiltered = isPackageFiltered(pkg) - BlackListItem( - icon = PackageUtil.getIconForPackage(context, pkg.packageName)!!, - displayName = pkg.applicationInfo!!.loadLabel( - context.packageManager - ).toString(), - packageName = pkg.packageName, - versionName = pkg.versionName!!, - versionCode = PackageInfoCompat.getLongVersionCode(pkg), - isChecked = isBlacklisted || isFiltered, - isEnabled = !isFiltered, - onClick = { - if (isBlacklisted) { - onWhitelist(pkg.packageName) - } else { - onBlacklist(pkg.packageName) - } - } + Scaffold( + modifier = Modifier.navigationBarsPadding(), + topBar = { SearchBar() } + ) { paddingValues -> + val listState = rememberLazyListState() + Box(modifier = Modifier.fillMaxSize()) { + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .padding(paddingValues), + state = listState, + verticalArrangement = Arrangement.spacedBy( + dimensionResource(R.dimen.margin_xxsmall) ) + ) { + items(items = packages ?: emptyList(), key = { p -> + p.packageName.hashCode() + }) { pkg -> + val isBlacklisted = isPackageBlacklisted(pkg.packageName) + val isFiltered = isPackageFiltered(pkg) + BlackListItem( + icon = PackageUtil.getIconForPackage(context, pkg.packageName)!!, + displayName = pkg.applicationInfo!!.loadLabel( + context.packageManager + ).toString(), + packageName = pkg.packageName, + versionName = pkg.versionName!!, + versionCode = PackageInfoCompat.getLongVersionCode(pkg), + isChecked = isBlacklisted || isFiltered, + isEnabled = !isFiltered, + onClick = { + if (isBlacklisted) { + onWhitelist(pkg.packageName) + } else { + onBlacklist(pkg.packageName) + } + } + ) + } } + ScrollHint( + listState = listState, + bottomPadding = 5.dp, + modifier = Modifier.align(Alignment.BottomCenter) + ) } } } 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 568c680f4..267d69eef 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 @@ -12,14 +12,15 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold import androidx.compose.material3.adaptive.WindowAdaptiveInfo @@ -49,6 +50,7 @@ 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.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation3.runtime.NavKey @@ -65,6 +67,7 @@ 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.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.Screen @@ -163,7 +166,7 @@ fun AppDetailsScreen( PackageUtil.getLaunchIntent(context, packageName) ) } catch (_: ActivityNotFoundException) { - context.toast(context.getString(R.string.unable_to_open)) + context.toast(R.string.unable_to_open) } }, onTestingSubscriptionChange = { subscribe -> @@ -390,6 +393,7 @@ private fun ScreenContentApp( @Composable fun MainPane() { Scaffold( + modifier = Modifier.navigationBarsPadding(), topBar = { TopAppBar( onNavigateUp = onNavigateUp, @@ -397,81 +401,123 @@ private fun ScreenContentApp( ) } ) { paddingValues -> - Column( - modifier = Modifier - .padding(paddingValues) - .fillMaxSize() - .verticalScroll(rememberScrollState()) - .padding(dimensionResource(R.dimen.padding_medium)), - verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_medium)) - ) { - Details( - app = app, - state = state, - onNavigateToDetailsDevProfile = { showExtraPane(Screen.DevProfile(it)) } - ) - - SetupActions() - - Tags(app = app) - Changelog(changelog = app.changes) - Header( - title = stringResource(R.string.details_more_about_app), - subtitle = app.shortDescription, - onClick = { showExtraPane(ExtraScreen.More) } - ) - - Screenshots( - screenshots = app.screenshots, - onNavigateToScreenshot = { showExtraPane(ExtraScreen.Screenshot(it)) } - ) - - RatingAndReviews( - rating = app.rating, - featuredReviews = featuredReviews, - onNavigateToDetailsReview = { showExtraPane(ExtraScreen.Review) } - ) - - if (!isAnonymous && app.testingProgram?.isAvailable == true) { - Testing( - isSubscribed = app.testingProgram!!.isSubscribed, - onTestingSubscriptionChange = onTestingSubscriptionChange - ) - } - - Compatibility(needsGms = app.requiresGMS(), plexusScores = plexusScores) - - Header( - title = stringResource(R.string.details_permission), - subtitle = if (app.permissions.isNotEmpty()) { - stringResource(R.string.permissions_requested, app.permissions.size) - } else { - stringResource(R.string.details_no_permission) - }, - onClick = if (app.permissions.isNotEmpty()) { - { showExtraPane(ExtraScreen.Permission) } - } else { - null + val listState = rememberLazyListState() + Box(modifier = Modifier.fillMaxSize()) { + LazyColumn( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize(), + verticalArrangement = Arrangement.spacedBy( + dimensionResource(R.dimen.margin_medium) + ), + state = listState + ) { + item { + Details( + app = app, + state = state, + onNavigateToDetailsDevProfile = { showExtraPane(Screen.DevProfile(it)) } + ) } - ) - if (dataSafetyReport != null) { - DataSafety(report = dataSafetyReport, privacyPolicyUrl = app.privacyPolicyUrl) - } - - Privacy( - report = exodusReport, - onNavigateToDetailsExodus = if (exodusReport != null && exodusReport.id != -1) { - { showExtraPane(ExtraScreen.Exodus) } - } else { - null + item { + SetupActions() } - ) - DeveloperDetails( - address = app.developerAddress, - website = app.developerWebsite, - email = app.developerEmail + item { + Tags(app = app) + } + + item { + Changelog(changelog = app.changes) + } + + item { + Header( + title = stringResource(R.string.details_more_about_app), + subtitle = app.shortDescription, + onClick = { showExtraPane(ExtraScreen.More) } + ) + } + + item { + Screenshots( + screenshots = app.screenshots, + onNavigateToScreenshot = { showExtraPane(ExtraScreen.Screenshot(it)) } + ) + } + + item { + RatingAndReviews( + rating = app.rating, + featuredReviews = featuredReviews, + onNavigateToDetailsReview = { showExtraPane(ExtraScreen.Review) } + ) + } + + item { + if (!isAnonymous && app.testingProgram?.isAvailable == true) { + Testing( + isSubscribed = app.testingProgram!!.isSubscribed, + onTestingSubscriptionChange = onTestingSubscriptionChange + ) + } + } + + item { + Compatibility(needsGms = app.requiresGMS(), plexusScores = plexusScores) + } + + item { + Header( + title = stringResource(R.string.details_permission), + subtitle = if (app.permissions.isNotEmpty()) { + stringResource(R.string.permissions_requested, app.permissions.size) + } else { + stringResource(R.string.details_no_permission) + }, + onClick = if (app.permissions.isNotEmpty()) { + { showExtraPane(ExtraScreen.Permission) } + } else { + null + } + ) + } + + item { + if (dataSafetyReport != null) { + DataSafety( + report = dataSafetyReport, + privacyPolicyUrl = app.privacyPolicyUrl + ) + } + } + + item { + Privacy( + report = exodusReport, + onNavigateToDetailsExodus = if (exodusReport != null && + exodusReport.id != -1 + ) { + { showExtraPane(ExtraScreen.Exodus) } + } else { + null + } + ) + } + + item { + DeveloperDetails( + address = app.developerAddress, + website = app.developerWebsite, + email = app.developerEmail + ) + } + } + ScrollHint( + listState = listState, + bottomPadding = 5.dp, + modifier = Modifier.align(Alignment.BottomCenter) ) } } 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 895193be6..81eef1603 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 @@ -5,11 +5,14 @@ package com.aurora.store.compose.ui.details +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold @@ -19,6 +22,7 @@ import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource @@ -27,6 +31,7 @@ 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 @@ -38,6 +43,7 @@ import com.aurora.gplayapi.data.models.App import com.aurora.store.R import com.aurora.store.compose.composable.Error 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.details.ExodusListItem import com.aurora.store.compose.preview.AppPreviewProvider @@ -103,6 +109,7 @@ private fun ScreenContentReport( val context = LocalContext.current Scaffold( + modifier = Modifier.navigationBarsPadding(), topBar = { TopAppBar( title = topAppBarTitle, @@ -119,33 +126,42 @@ private fun ScreenContentReport( } } ) { paddingValues -> - LazyColumn( - modifier = Modifier - .padding(paddingValues) - .fillMaxSize() - .padding(horizontal = dimensionResource(R.dimen.padding_medium)) - ) { - stickyHeader { - Surface(modifier = Modifier.fillMaxWidth()) { - Header( - title = if (report?.trackers.isNullOrEmpty()) { - stringResource(R.string.exodus_no_tracker) - } else { - stringResource( - R.string.exodus_report_trackers, - report.trackers.size, - report.version - ) - }, - subtitle = stringResource(R.string.exodus_view_report), - onClick = { context.browse(EXODUS_REPORT_URL + report!!.id) } - ) + val listState = rememberLazyListState() + Box(modifier = Modifier.fillMaxSize()) { + LazyColumn( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + .padding(horizontal = dimensionResource(R.dimen.padding_medium)), + state = listState + ) { + stickyHeader { + Surface(modifier = Modifier.fillMaxWidth()) { + Header( + title = if (report?.trackers.isNullOrEmpty()) { + stringResource(R.string.exodus_no_tracker) + } else { + stringResource( + R.string.exodus_report_trackers, + report.trackers.size, + report.version + ) + }, + subtitle = stringResource(R.string.exodus_view_report), + onClick = { context.browse(EXODUS_REPORT_URL + report!!.id) } + ) + } + } + + items(items = trackers, key = { item -> item.id }) { tracker -> + ExodusListItem(tracker = tracker) } } - - items(items = trackers, key = { item -> item.id }) { tracker -> - ExodusListItem(tracker = tracker) - } + 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 0d2653f53..c63f14b70 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 @@ -6,14 +6,15 @@ package com.aurora.store.compose.ui.details import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text @@ -21,6 +22,7 @@ import androidx.compose.material3.adaptive.WindowAdaptiveInfo import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLocale import androidx.compose.ui.res.dimensionResource @@ -30,6 +32,7 @@ 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 @@ -38,6 +41,7 @@ import com.aurora.gplayapi.data.models.App import com.aurora.store.R import com.aurora.store.compose.composable.Header 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.preview.AppPreviewProvider @@ -83,6 +87,7 @@ private fun ScreenContent( } Scaffold( + modifier = Modifier.navigationBarsPadding(), topBar = { TopAppBar( title = topAppBarTitle, @@ -91,30 +96,49 @@ private fun ScreenContent( ) } ) { paddingValues -> - Column( - modifier = Modifier - .padding(paddingValues) - .fillMaxSize() - .verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_medium)) - ) { - Header(title = stringResource(R.string.details_description)) - Text( - modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.padding_medium)), - text = AnnotatedString.fromHtml( - htmlString = app.description - ), - style = MaterialTheme.typography.bodyMedium - ) + val listState = rememberLazyListState() + Box(modifier = Modifier.fillMaxSize()) { + LazyColumn( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize(), + state = listState, + verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_medium)) + ) { + item { + Header(title = stringResource(R.string.details_description)) + } - if (dependencies != null) { - AppDependencies( - dependencies = dependencies, - onNavigateToAppDetails = onNavigateToAppDetails - ) + item { + Text( + modifier = Modifier.padding( + horizontal = dimensionResource(R.dimen.padding_medium) + ), + text = AnnotatedString.fromHtml( + htmlString = app.description + ), + style = MaterialTheme.typography.bodyMedium + ) + } + + item { + if (dependencies != null) { + AppDependencies( + dependencies = dependencies, + onNavigateToAppDetails = onNavigateToAppDetails + ) + } + } + + item { + AppInfoMore(app = app) + } } - - AppInfoMore(app = app) + ScrollHint( + listState = listState, + bottomPadding = 5.dp, + modifier = Modifier.align(Alignment.BottomCenter) + ) } } } 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 39ea6ec20..fd55dce7b 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 @@ -7,15 +7,19 @@ package com.aurora.store.compose.ui.details import android.content.pm.PermissionInfo import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.Scaffold import androidx.compose.material3.adaptive.WindowAdaptiveInfo import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLocale @@ -25,6 +29,7 @@ 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 @@ -32,6 +37,7 @@ import com.aurora.extensions.isWindowCompact import com.aurora.gplayapi.data.models.App import com.aurora.store.R 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.preview.AppPreviewProvider import com.aurora.store.compose.preview.ThemePreviewProvider @@ -76,6 +82,7 @@ private fun ScreenContent( val packageManager = LocalContext.current.packageManager Scaffold( + modifier = Modifier.navigationBarsPadding(), topBar = { TopAppBar( title = topAppBarTitle, @@ -84,34 +91,43 @@ private fun ScreenContent( ) } ) { paddingValues -> - LazyColumn( - modifier = Modifier - .padding(paddingValues) - .fillMaxSize() - .padding(horizontal = dimensionResource(R.dimen.padding_medium)), - verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_medium)) - ) { - items(items = permissionsInfo.keys.toList(), key = { it }) { permission -> - val permissionInfo = permissionsInfo.getValue(permission) + val listState = rememberLazyListState() + Box(modifier = Modifier.fillMaxSize()) { + LazyColumn( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + .padding(horizontal = dimensionResource(R.dimen.padding_medium)), + state = listState, + verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_medium)) + ) { + items(items = permissionsInfo.keys.toList(), key = { it }) { permission -> + val permissionInfo = permissionsInfo.getValue(permission) - Info( - title = AnnotatedString( - text = permissionInfo.loadLabel(packageManager) - .toString() - .replaceFirstChar { - if (it.isLowerCase()) { - it.titlecase(LocalLocale.current.platformLocale) - } else { - it.toString() + Info( + title = AnnotatedString( + text = permissionInfo.loadLabel(packageManager) + .toString() + .replaceFirstChar { + if (it.isLowerCase()) { + it.titlecase(LocalLocale.current.platformLocale) + } else { + it.toString() + } } - } - ), - description = AnnotatedString( - text = permissionInfo.loadDescription(packageManager)?.toString() - ?: stringResource(R.string.no_description) + ), + description = AnnotatedString( + text = permissionInfo.loadDescription(packageManager)?.toString() + ?: stringResource(R.string.no_description) + ) ) - ) + } } + 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 0cf2c4f01..6c300045a 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 @@ -6,13 +6,17 @@ package com.aurora.store.compose.ui.details import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.FilterChip import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold @@ -24,6 +28,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf 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 @@ -31,6 +36,7 @@ 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 @@ -45,6 +51,7 @@ import com.aurora.gplayapi.data.models.Review 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.ScrollHint import com.aurora.store.compose.composable.TopAppBar import com.aurora.store.compose.composable.details.ReviewListItem import com.aurora.store.compose.preview.ReviewPreviewProvider @@ -92,22 +99,23 @@ private fun ScreenContent( windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo() ) { Scaffold( + modifier = Modifier.navigationBarsPadding(), topBar = { - TopAppBar( - title = topAppBarTitle, - navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon, - onNavigateUp = onNavigateUp - ) + Column { + TopAppBar( + title = topAppBarTitle, + navigationIcon = windowAdaptiveInfo.adaptiveNavigationIcon, + onNavigateUp = onNavigateUp + ) + FilterHeader { filter -> onFilter(filter) } + } } ) { paddingValues -> Column( modifier = Modifier .padding(paddingValues) .fillMaxSize() - .padding(horizontal = dimensionResource(R.dimen.padding_medium)) ) { - FilterHeader { filter -> onFilter(filter) } - when (reviews.loadState.refresh) { is LoadState.Loading -> ContainedLoadingIndicator() @@ -120,13 +128,24 @@ private fun ScreenContent( } else -> { - LazyColumn(modifier = Modifier.fillMaxSize()) { - items( - count = reviews.itemCount, - key = reviews.itemKey { it.commentId } - ) { index -> - reviews[index]?.let { review -> ReviewListItem(review = review) } + val listState = rememberLazyListState() + Box(modifier = Modifier.fillMaxSize()) { + LazyColumn( + modifier = Modifier.fillMaxSize(), + state = listState + ) { + items( + count = reviews.itemCount, + key = reviews.itemKey { it.commentId } + ) { index -> + reviews[index]?.let { review -> ReviewListItem(review = review) } + } } + ScrollHint( + listState = listState, + bottomPadding = 5.dp, + modifier = Modifier.align(Alignment.BottomCenter) + ) } } } @@ -155,6 +174,7 @@ private fun FilterHeader(onClick: (filter: Review.Filter) -> Unit) { LazyRow( modifier = Modifier.fillMaxWidth(), + contentPadding = PaddingValues(horizontal = dimensionResource(R.dimen.margin_normal)), horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_normal)) ) { items(items = filters.keys.toList(), key = { item -> item }) { filter -> diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/composable/Actions.kt b/app/src/main/java/com/aurora/store/compose/ui/details/composable/Actions.kt index a352b27ba..f374409a4 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/details/composable/Actions.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/details/composable/Actions.kt @@ -7,8 +7,10 @@ package com.aurora.store.compose.ui.details.composable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.widthIn import androidx.compose.material3.Button import androidx.compose.material3.FilledTonalButton @@ -48,7 +50,9 @@ fun Actions( windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo() ) { Row( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .padding(PaddingValues(horizontal = dimensionResource(R.dimen.padding_medium))), horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.padding_medium)) ) { val buttonWidthModifier = when { diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/composable/Changelog.kt b/app/src/main/java/com/aurora/store/compose/ui/details/composable/Changelog.kt index 910db28d6..017112d85 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/details/composable/Changelog.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/details/composable/Changelog.kt @@ -41,18 +41,27 @@ fun Changelog(changelog: String) { Box( modifier = Modifier .fillMaxWidth() - .clip(RoundedCornerShape(dimensionResource(R.dimen.radius_small))) - .background(color = MaterialTheme.colorScheme.secondaryContainer) .padding(dimensionResource(R.dimen.padding_medium)) ) { - Text( - text = if (changelog.isBlank()) { - AnnotatedString(text = stringResource(R.string.details_changelog_unavailable)) - } else { - AnnotatedString.fromHtml(htmlString = changelog) - }, - style = MaterialTheme.typography.bodyMedium - ) + Box( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(dimensionResource(R.dimen.radius_small))) + .background(color = MaterialTheme.colorScheme.secondaryContainer) + .padding( + horizontal = dimensionResource(R.dimen.padding_medium), + vertical = dimensionResource(R.dimen.padding_small) + ) + ) { + Text( + text = if (changelog.isBlank()) { + AnnotatedString(text = stringResource(R.string.details_changelog_unavailable)) + } else { + AnnotatedString.fromHtml(htmlString = changelog) + }, + style = MaterialTheme.typography.bodyMedium + ) + } } } diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/composable/Details.kt b/app/src/main/java/com/aurora/store/compose/ui/details/composable/Details.kt index bfb4bf8a7..7449d3b99 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/details/composable/Details.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/details/composable/Details.kt @@ -105,7 +105,11 @@ fun Details( ) } - Row(modifier = Modifier.fillMaxWidth()) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(dimensionResource(R.dimen.margin_medium)) + ) { AnimatedAppIcon( modifier = Modifier.requiredSize(dimensionResource(R.dimen.icon_size_large)), iconUrl = app.iconArtwork.url, diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/composable/RatingAndReviews.kt b/app/src/main/java/com/aurora/store/compose/ui/details/composable/RatingAndReviews.kt index 5a5494b08..6690a9398 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/details/composable/RatingAndReviews.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/details/composable/RatingAndReviews.kt @@ -87,9 +87,11 @@ fun RatingAndReviews( ) Row( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .padding(dimensionResource(R.dimen.padding_medium)), verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_small)) + horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_large)) ) { Column( modifier = Modifier.padding(dimensionResource(R.dimen.padding_medium)), @@ -111,7 +113,6 @@ fun RatingAndReviews( } Column( - modifier = Modifier.padding(dimensionResource(R.dimen.padding_medium)), verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_small)) ) { stars.reversed().fastForEach { star -> @@ -127,7 +128,7 @@ fun RatingAndReviews( val pagerState = rememberPagerState { featuredReviews.size } HorizontalPager( state = pagerState, - contentPadding = PaddingValues(dimensionResource(R.dimen.padding_large)), + contentPadding = PaddingValues(horizontal = dimensionResource(R.dimen.padding_medium)), pageSpacing = dimensionResource(R.dimen.margin_medium) ) { page -> Box( diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/composable/Screenshots.kt b/app/src/main/java/com/aurora/store/compose/ui/details/composable/Screenshots.kt index 6f76d2db1..641d56f9d 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/details/composable/Screenshots.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/details/composable/Screenshots.kt @@ -8,6 +8,7 @@ package com.aurora.store.compose.ui.details.composable import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.height import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items @@ -34,7 +35,10 @@ import com.aurora.store.compose.preview.ThemePreviewProvider */ @Composable fun Screenshots(screenshots: List, onNavigateToScreenshot: (index: Int) -> Unit = {}) { - LazyRow(horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_small))) { + LazyRow( + contentPadding = PaddingValues(horizontal = dimensionResource(R.dimen.padding_medium)), + horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_small)) + ) { items(items = screenshots, key = { artwork -> artwork.url }) { artwork -> ScreenshotListItem( modifier = Modifier diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/composable/Tags.kt b/app/src/main/java/com/aurora/store/compose/ui/details/composable/Tags.kt index 86690d6d4..d48101a3e 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/details/composable/Tags.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/details/composable/Tags.kt @@ -8,6 +8,7 @@ package com.aurora.store.compose.ui.details.composable import android.text.format.Formatter import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable @@ -61,6 +62,7 @@ fun Tags(app: App) { ).filterKeys { it != null } LazyRow( + contentPadding = PaddingValues(horizontal = dimensionResource(R.dimen.padding_medium)), horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.padding_medium)) ) { items(items = tags.keys.toList()) { label -> diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/composable/Testing.kt b/app/src/main/java/com/aurora/store/compose/ui/details/composable/Testing.kt index 36b3c85a7..d3dee3293 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/details/composable/Testing.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/details/composable/Testing.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -40,7 +41,9 @@ import com.aurora.store.compose.preview.ThemePreviewProvider fun Testing(isSubscribed: Boolean, onTestingSubscriptionChange: (subscribe: Boolean) -> Unit = {}) { Header(title = stringResource(R.string.details_beta)) Row( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .padding(dimensionResource(R.dimen.padding_medium)), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_small)) ) { 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 db745be2b..a7d8a68a6 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 @@ -7,16 +7,20 @@ package com.aurora.store.compose.ui.downloads import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf 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.platform.LocalContext import androidx.compose.ui.res.dimensionResource @@ -25,6 +29,7 @@ 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 @@ -38,6 +43,7 @@ import com.aurora.store.R import com.aurora.store.compose.composable.ContainedLoadingIndicator 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.preview.AppPreviewProvider import com.aurora.store.compose.preview.ThemePreviewProvider @@ -124,6 +130,7 @@ private fun ScreenContent( } Scaffold( + modifier = Modifier.navigationBarsPadding(), topBar = { TopAppBar( title = stringResource(R.string.title_download_manager), @@ -153,23 +160,35 @@ private fun ScreenContent( message = stringResource(R.string.download_none) ) } else { - LazyColumn { - items( - count = downloads.itemCount, - key = downloads.itemKey { it.packageName } - ) { index -> - downloads[index]?.let { download -> - DownloadListItem( - modifier = Modifier.animateItem(), - download = download, - onClick = { onNavigateToAppDetails(download.packageName) }, - onClear = { onClear(download) }, - onCancel = { onCancel(download.packageName) }, - onExport = { onExport(download) }, - onInstall = { onInstall(download) } - ) + val listState = rememberLazyListState() + Box(modifier = Modifier.fillMaxSize()) { + LazyColumn( + state = listState + ) { + items( + count = downloads.itemCount, + key = downloads.itemKey { it.packageName } + ) { index -> + downloads[index]?.let { download -> + DownloadListItem( + modifier = Modifier.animateItem(), + download = download, + onClick = { + onNavigateToAppDetails(download.packageName) + }, + onClear = { onClear(download) }, + onCancel = { onCancel(download.packageName) }, + onExport = { onExport(download) }, + onInstall = { onInstall(download) } + ) + } } } + 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 cd6e9b867..c378dfb72 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 @@ -7,16 +7,20 @@ package com.aurora.store.compose.ui.favourite import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf 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.platform.LocalContext import androidx.compose.ui.res.dimensionResource @@ -25,6 +29,7 @@ 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 @@ -38,6 +43,7 @@ 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.FavouriteListItem +import com.aurora.store.compose.composable.ScrollHint import com.aurora.store.compose.composable.TopAppBar import com.aurora.store.compose.preview.FavouritePreviewProvider import com.aurora.store.compose.preview.ThemePreviewProvider @@ -126,6 +132,7 @@ private fun ScreenContent( } Scaffold( + modifier = Modifier.navigationBarsPadding(), topBar = { TopAppBar( title = stringResource(R.string.title_favourites_manager), @@ -155,20 +162,32 @@ private fun ScreenContent( message = stringResource(R.string.details_no_favourites) ) } else { - LazyColumn { - items( - count = favourites.itemCount, - key = favourites.itemKey { it.packageName } - ) { index -> - favourites[index]?.let { favourite -> - FavouriteListItem( - modifier = Modifier.animateItem(), - favourite = favourite, - onClick = { onNavigateToAppDetails(favourite.packageName) }, - onClear = { onRemoveFavourite(favourite.packageName) } - ) + val listState = rememberLazyListState() + Box(modifier = Modifier.fillMaxSize()) { + LazyColumn( + state = listState + ) { + items( + count = favourites.itemCount, + key = favourites.itemKey { it.packageName } + ) { index -> + favourites[index]?.let { favourite -> + FavouriteListItem( + modifier = Modifier.animateItem(), + favourite = favourite, + onClick = { + onNavigateToAppDetails(favourite.packageName) + }, + onClear = { onRemoveFavourite(favourite.packageName) } + ) + } } } + 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 93cbdca6d..960601e6c 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 @@ -5,22 +5,27 @@ package com.aurora.store.compose.ui.installed +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf 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.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 @@ -32,6 +37,7 @@ import com.aurora.gplayapi.data.models.App 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.ScrollHint import com.aurora.store.compose.composable.TopAppBar import com.aurora.store.compose.composable.app.LargeAppListItem import com.aurora.store.compose.preview.AppPreviewProvider @@ -70,6 +76,7 @@ private fun ScreenContent( var initialLoad by rememberSaveable { mutableStateOf(true) } Scaffold( + modifier = Modifier.navigationBarsPadding(), topBar = { TopAppBar( title = stringResource(R.string.title_apps_games), @@ -97,18 +104,28 @@ private fun ScreenContent( message = stringResource(R.string.no_apps_available) ) } else { - LazyColumn { - items( - count = apps.itemCount, - key = apps.itemKey { it.packageName } - ) { index -> - apps[index]?.let { app -> - LargeAppListItem( - app = app, - onClick = { onNavigateToAppDetails(app.packageName) } - ) + val listState = rememberLazyListState() + Box(modifier = Modifier.fillMaxSize()) { + LazyColumn( + state = listState + ) { + items( + count = apps.itemCount, + key = apps.itemKey { it.packageName } + ) { index -> + apps[index]?.let { app -> + LargeAppListItem( + app = app, + onClick = { onNavigateToAppDetails(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/search/SearchScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/search/SearchScreen.kt index fdf117577..a04ced42e 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 @@ -14,10 +14,12 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.text.input.clearText import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd @@ -45,6 +47,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester @@ -58,6 +61,7 @@ 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 @@ -70,6 +74,7 @@ import com.aurora.gplayapi.data.models.App 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.ScrollHint import com.aurora.store.compose.composable.SearchSuggestionListItem import com.aurora.store.compose.composable.app.LargeAppListItem import com.aurora.store.compose.preview.AppPreviewProvider @@ -221,7 +226,9 @@ private fun ScreenContent( fun ListPane() { // TODO: https://issuetracker.google.com/issues/445720462 Scaffold( - modifier = Modifier.focusable(), + modifier = Modifier + .focusable() + .navigationBarsPadding(), topBar = { SearchBar() } ) { paddingValues -> Column( @@ -255,18 +262,29 @@ private fun ScreenContent( message = stringResource(R.string.no_apps_available) ) } else { - LazyColumn { - items( - count = results.itemCount, - key = { Uuid.random().toString() } - ) { index -> - results[index]?.let { app -> - LargeAppListItem( - app = app, - onClick = { showDetailPane(app.packageName) } - ) + val listState = rememberLazyListState() + Box(modifier = Modifier.fillMaxSize()) { + LazyColumn( + state = listState, + modifier = Modifier.fillMaxSize() + ) { + items( + count = results.itemCount, + key = { Uuid.random().toString() } + ) { index -> + results[index]?.let { app -> + LargeAppListItem( + app = app, + onClick = { showDetailPane(app.packageName) } + ) + } } } + ScrollHint( + listState = listState, + bottomPadding = 5.dp, + modifier = Modifier.align(Alignment.BottomCenter) + ) } } } From ec3427c785b85adc25dce389e58e08304cc6e8cb Mon Sep 17 00:00:00 2001 From: Rahul Patel Date: Wed, 6 May 2026 14:08:36 +0530 Subject: [PATCH 5/9] appdetails: add back view on playstore action --- .../compose/ui/details/AppDetailsScreen.kt | 17 +++++++++++++++++ .../compose/ui/details/menu/AppDetailsMenu.kt | 9 +++++++++ .../store/compose/ui/details/menu/MenuItem.kt | 3 ++- app/src/main/res/values/strings.xml | 1 + 4 files changed, 29 insertions(+), 1 deletion(-) 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 267d69eef..9b19680ea 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 @@ -51,9 +51,11 @@ 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 import androidx.navigation3.runtime.NavKey +import com.aurora.Constants import com.aurora.extensions.appInfo import com.aurora.extensions.requiresGMS import com.aurora.extensions.requiresObbDir @@ -316,6 +318,21 @@ private fun ScreenContentApp( MenuItem.ADD_TO_HOME -> { ShortcutManagerUtil.requestPinShortcut(context, app.packageName) } + + MenuItem.PLAY_STORE -> { + val uri = "${Constants.SHARE_URL}${app.packageName}".toUri() + val intent = Intent(Intent.ACTION_VIEW).apply { data = uri } + + if (intent.resolveActivity(context.packageManager) != null) { + context.startActivity( + intent.apply { + setPackage(Constants.PACKAGE_NAME_PLAY_STORE) + } + ) + } else { + context.startActivity(intent) + } + } } } } diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/menu/AppDetailsMenu.kt b/app/src/main/java/com/aurora/store/compose/ui/details/menu/AppDetailsMenu.kt index 067705246..fbc801f4d 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/details/menu/AppDetailsMenu.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/details/menu/AppDetailsMenu.kt @@ -17,14 +17,17 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext 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.PreviewWrapper +import com.aurora.Constants import com.aurora.store.R import com.aurora.store.compose.composable.TopAppBar import com.aurora.store.compose.preview.ThemePreviewProvider import com.aurora.store.data.model.AppState +import com.aurora.store.util.PackageUtil /** * Menu for the app details screen @@ -40,6 +43,7 @@ fun AppDetailsMenu( isExpanded: Boolean = false, onMenuItemClicked: (menuItem: MenuItem) -> Unit = {} ) { + val context = LocalContext.current var expanded by remember { mutableStateOf(isExpanded) } fun onClick(menuItem: MenuItem) { onMenuItemClicked(menuItem) @@ -87,6 +91,11 @@ fun AppDetailsMenu( onClick = { onClick(MenuItem.ADD_TO_HOME) }, enabled = state is AppState.Installed || state is AppState.Updatable ) + DropdownMenuItem( + text = { Text(text = stringResource(R.string.action_view_on_play)) }, + onClick = { onClick(MenuItem.PLAY_STORE) }, + enabled = PackageUtil.isInstalled(context, Constants.PACKAGE_NAME_PLAY_STORE) + ) } } } diff --git a/app/src/main/java/com/aurora/store/compose/ui/details/menu/MenuItem.kt b/app/src/main/java/com/aurora/store/compose/ui/details/menu/MenuItem.kt index a9e65355b..d0f064acc 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/details/menu/MenuItem.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/details/menu/MenuItem.kt @@ -13,5 +13,6 @@ enum class MenuItem { SHARE, MANUAL_DOWNLOAD, APP_INFO, - ADD_TO_HOME + ADD_TO_HOME, + PLAY_STORE } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 417f4208b..0513cc8c0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -102,6 +102,7 @@ "App info" "Update" "Update all" + "View on Play Store" "Whitelist" "Beta program" "You're a beta tester" From 0d480f5d0d578ae1008207650cacf15b86502194 Mon Sep 17 00:00:00 2001 From: Rahul Patel Date: Thu, 7 May 2026 16:05:17 +0530 Subject: [PATCH 6/9] theme: fix OEM skin edgecases --- .../com/aurora/store/compose/theme/Theme.kt | 42 +++++++++++++++++++ .../compose/ui/blacklist/BlacklistScreen.kt | 6 ++- .../compose/ui/details/AppDetailsScreen.kt | 2 +- .../compose/ui/onboarding/OnboardingScreen.kt | 8 +++- .../compose/ui/onboarding/PermissionsPage.kt | 34 +++++++-------- .../compose/ui/onboarding/WelcomePage.kt | 34 +++++++-------- .../store/compose/ui/search/SearchScreen.kt | 9 +++- 7 files changed, 93 insertions(+), 42 deletions(-) diff --git a/app/src/main/java/com/aurora/store/compose/theme/Theme.kt b/app/src/main/java/com/aurora/store/compose/theme/Theme.kt index e797d772f..0f9efeebb 100644 --- a/app/src/main/java/com/aurora/store/compose/theme/Theme.kt +++ b/app/src/main/java/com/aurora/store/compose/theme/Theme.kt @@ -5,6 +5,9 @@ package com.aurora.store.compose.theme +import android.app.Activity +import android.content.Context +import android.content.ContextWrapper import android.os.Build import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.MaterialExpressiveTheme @@ -14,8 +17,11 @@ import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.colorResource +import androidx.core.view.WindowCompat import com.aurora.store.R import com.aurora.store.util.Preferences @@ -46,5 +52,41 @@ fun AuroraTheme(content: @Composable () -> Unit) { else -> if (isSystemInDarkTheme()) darkScheme else lightScheme } + val darkTheme = when (themeStyle) { + 1 -> false + 2 -> true + else -> isSystemInDarkTheme() + } + + /** + * Side effect to update the system bars to be transparent and match the theme's light/dark mode. + * This is necessary on OEM devices that don't properly support dynamic theming and may have issues with light/dark status bar icons. + */ + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + val activity = view.context.findActivity() ?: return@SideEffect + val window = activity.window + + // Transparent system bars + window.statusBarColor = android.graphics.Color.TRANSPARENT + window.navigationBarColor = android.graphics.Color.TRANSPARENT + + // Control icon colors explicitly + WindowCompat + .getInsetsController(window, view) + .apply { + isAppearanceLightStatusBars = !darkTheme + isAppearanceLightNavigationBars = !darkTheme + } + } + } + MaterialExpressiveTheme(colorScheme = colorScheme, content = content) } + +tailrec fun Context.findActivity(): Activity? = when (this) { + is Activity -> this + is ContextWrapper -> baseContext.findActivity() + else -> null +} 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 d83ac2820..c55489080 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 @@ -38,6 +38,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource @@ -212,7 +213,10 @@ private fun ScreenContent( ) } }, - actions = { if (!packages.isNullOrEmpty()) SetupMenu() } + actions = { if (!packages.isNullOrEmpty()) SetupMenu() }, + colors = SearchBarDefaults.appBarWithSearchColors( + appBarContainerColor = Color.Transparent + ) ) } 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 9b19680ea..6773c6a42 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 @@ -533,7 +533,7 @@ private fun ScreenContentApp( } ScrollHint( listState = listState, - bottomPadding = 5.dp, + bottomPadding = 72.dp, modifier = Modifier.align(Alignment.BottomCenter) ) } diff --git a/app/src/main/java/com/aurora/store/compose/ui/onboarding/OnboardingScreen.kt b/app/src/main/java/com/aurora/store/compose/ui/onboarding/OnboardingScreen.kt index 89c10872d..2d4d80891 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/onboarding/OnboardingScreen.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/onboarding/OnboardingScreen.kt @@ -22,12 +22,14 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.adaptive.WindowAdaptiveInfo import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow @@ -82,7 +84,11 @@ private fun ScreenContent( modifier = Modifier.height(64.dp), title = { PageIndicator(totalPages = pages.size, currentPage = pagerState.currentPage) - } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = Color.Transparent, + scrolledContainerColor = Color.Transparent + ) ) }, bottomBar = { diff --git a/app/src/main/java/com/aurora/store/compose/ui/onboarding/PermissionsPage.kt b/app/src/main/java/com/aurora/store/compose/ui/onboarding/PermissionsPage.kt index 63fb49788..7018c6724 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/onboarding/PermissionsPage.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/onboarding/PermissionsPage.kt @@ -6,10 +6,8 @@ package com.aurora.store.compose.ui.onboarding import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -50,23 +48,21 @@ private fun PageContent( permissions = permissions, onPermissionCallback = onPermissionCallback, header = { - Surface(modifier = Modifier.fillMaxWidth()) { - Column( - modifier = Modifier.padding(dimensionResource(R.dimen.padding_medium)) - ) { - Text( - text = stringResource(R.string.onboarding_title_permissions), - style = MaterialTheme.typography.headlineLargeEmphasized, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Text( - text = stringResource(R.string.onboarding_permission_select), - style = MaterialTheme.typography.titleMedium, - maxLines = 2, - overflow = TextOverflow.Ellipsis - ) - } + Column( + modifier = Modifier.padding(dimensionResource(R.dimen.padding_medium)) + ) { + Text( + text = stringResource(R.string.onboarding_title_permissions), + style = MaterialTheme.typography.headlineLargeEmphasized, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Text( + text = stringResource(R.string.onboarding_permission_select), + style = MaterialTheme.typography.titleMedium, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) } } ) diff --git a/app/src/main/java/com/aurora/store/compose/ui/onboarding/WelcomePage.kt b/app/src/main/java/com/aurora/store/compose/ui/onboarding/WelcomePage.kt index 0b1ea6550..192307972 100644 --- a/app/src/main/java/com/aurora/store/compose/ui/onboarding/WelcomePage.kt +++ b/app/src/main/java/com/aurora/store/compose/ui/onboarding/WelcomePage.kt @@ -9,12 +9,10 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -101,23 +99,21 @@ private fun PageContent(onAboutAurora: () -> Unit = {}) { verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_xxsmall)) ) { stickyHeader { - Surface(modifier = Modifier.fillMaxWidth()) { - Column( - modifier = Modifier.padding(dimensionResource(R.dimen.padding_medium)) - ) { - Text( - text = stringResource(R.string.onboarding_title_welcome), - style = MaterialTheme.typography.headlineLargeEmphasized, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Text( - text = stringResource(R.string.onboarding_welcome_select), - style = MaterialTheme.typography.titleMedium, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } + Column( + modifier = Modifier.padding(dimensionResource(R.dimen.padding_medium)) + ) { + Text( + text = stringResource(R.string.onboarding_title_welcome), + style = MaterialTheme.typography.headlineLargeEmphasized, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Text( + text = stringResource(R.string.onboarding_welcome_select), + style = MaterialTheme.typography.titleMedium, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) } } 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 a04ced42e..021731e56 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 @@ -51,6 +51,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.dimensionResource @@ -210,7 +211,13 @@ private fun ScreenContent( ) } - AppBarWithSearch(state = searchBarState, inputField = inputField) + AppBarWithSearch( + state = searchBarState, + inputField = inputField, + colors = SearchBarDefaults.appBarWithSearchColors( + appBarContainerColor = Color.Transparent + ) + ) ExpandedDockedSearchBar(state = searchBarState, inputField = inputField) { suggestions.forEach { suggestion -> SearchSuggestionListItem( From 3060133485d329de1101bd170f852ffca21f7963 Mon Sep 17 00:00:00 2001 From: Rahul Patel Date: Thu, 7 May 2026 16:52:02 +0530 Subject: [PATCH 7/9] fix: padding issues I introduced --- .../store/compose/ui/blacklist/BlacklistScreen.kt | 12 ++++++------ .../store/compose/ui/details/AppDetailsScreen.kt | 11 ++++++----- .../aurora/store/compose/ui/details/ExodusScreen.kt | 9 +++++---- .../aurora/store/compose/ui/details/MicroGScreen.kt | 7 +++++-- .../aurora/store/compose/ui/details/MoreScreen.kt | 12 ++++++------ .../store/compose/ui/details/PermissionScreen.kt | 9 +++++---- .../aurora/store/compose/ui/details/ReviewScreen.kt | 2 -- .../store/compose/ui/downloads/DownloadsScreen.kt | 2 -- .../store/compose/ui/favourite/FavouriteScreen.kt | 2 -- .../store/compose/ui/installed/InstalledScreen.kt | 2 -- .../aurora/store/compose/ui/search/SearchScreen.kt | 4 +--- 11 files changed, 34 insertions(+), 38 deletions(-) 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 c55489080..df6928908 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 @@ -13,7 +13,6 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -221,15 +220,16 @@ private fun ScreenContent( } Scaffold( - modifier = Modifier.navigationBarsPadding(), topBar = { SearchBar() } ) { paddingValues -> val listState = rememberLazyListState() - Box(modifier = Modifier.fillMaxSize()) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { LazyColumn( - modifier = Modifier - .fillMaxWidth() - .padding(paddingValues), + modifier = Modifier.fillMaxWidth(), state = listState, verticalArrangement = Arrangement.spacedBy( dimensionResource(R.dimen.margin_xxsmall) 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 6773c6a42..eded62a93 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 @@ -16,7 +16,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -410,7 +409,6 @@ private fun ScreenContentApp( @Composable fun MainPane() { Scaffold( - modifier = Modifier.navigationBarsPadding(), topBar = { TopAppBar( onNavigateUp = onNavigateUp, @@ -419,10 +417,13 @@ private fun ScreenContentApp( } ) { paddingValues -> val listState = rememberLazyListState() - Box(modifier = Modifier.fillMaxSize()) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { LazyColumn( modifier = Modifier - .padding(paddingValues) .fillMaxSize(), verticalArrangement = Arrangement.spacedBy( dimensionResource(R.dimen.margin_medium) @@ -533,7 +534,7 @@ private fun ScreenContentApp( } ScrollHint( listState = listState, - bottomPadding = 72.dp, + bottomPadding = 5.dp, modifier = Modifier.align(Alignment.BottomCenter) ) } 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 81eef1603..0ed8a1093 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 @@ -8,7 +8,6 @@ package com.aurora.store.compose.ui.details import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -109,7 +108,6 @@ private fun ScreenContentReport( val context = LocalContext.current Scaffold( - modifier = Modifier.navigationBarsPadding(), topBar = { TopAppBar( title = topAppBarTitle, @@ -127,10 +125,13 @@ private fun ScreenContentReport( } ) { paddingValues -> val listState = rememberLazyListState() - Box(modifier = Modifier.fillMaxSize()) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { LazyColumn( modifier = Modifier - .padding(paddingValues) .fillMaxSize() .padding(horizontal = dimensionResource(R.dimen.padding_medium)), state = listState 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 a15abfdd9..e043478cd 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 @@ -139,10 +139,13 @@ private fun ScreenContent( } } ) { paddingValues -> - Box(modifier = Modifier.fillMaxSize()) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { LazyColumn( modifier = Modifier - .padding(paddingValues) .fillMaxSize() .padding(horizontal = dimensionResource(R.dimen.padding_medium)), state = listState 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 c63f14b70..93202195c 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 @@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow @@ -87,7 +86,6 @@ private fun ScreenContent( } Scaffold( - modifier = Modifier.navigationBarsPadding(), topBar = { TopAppBar( title = topAppBarTitle, @@ -97,11 +95,13 @@ private fun ScreenContent( } ) { paddingValues -> val listState = rememberLazyListState() - Box(modifier = Modifier.fillMaxSize()) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { LazyColumn( - modifier = Modifier - .padding(paddingValues) - .fillMaxSize(), + modifier = Modifier.fillMaxSize(), state = listState, verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.margin_medium)) ) { 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 fd55dce7b..4cb83a20e 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 @@ -9,7 +9,6 @@ import android.content.pm.PermissionInfo import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -82,7 +81,6 @@ private fun ScreenContent( val packageManager = LocalContext.current.packageManager Scaffold( - modifier = Modifier.navigationBarsPadding(), topBar = { TopAppBar( title = topAppBarTitle, @@ -92,10 +90,13 @@ private fun ScreenContent( } ) { paddingValues -> val listState = rememberLazyListState() - Box(modifier = Modifier.fillMaxSize()) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { LazyColumn( modifier = Modifier - .padding(paddingValues) .fillMaxSize() .padding(horizontal = dimensionResource(R.dimen.padding_medium)), state = listState, 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 6c300045a..b7f5eb395 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 @@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow @@ -99,7 +98,6 @@ private fun ScreenContent( windowAdaptiveInfo: WindowAdaptiveInfo = currentWindowAdaptiveInfo() ) { Scaffold( - modifier = Modifier.navigationBarsPadding(), topBar = { Column { TopAppBar( 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 a7d8a68a6..0fd65ac16 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 @@ -10,7 +10,6 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState @@ -130,7 +129,6 @@ private fun ScreenContent( } Scaffold( - modifier = Modifier.navigationBarsPadding(), topBar = { TopAppBar( title = stringResource(R.string.title_download_manager), 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 c378dfb72..7bd470b84 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 @@ -10,7 +10,6 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState @@ -132,7 +131,6 @@ private fun ScreenContent( } Scaffold( - modifier = Modifier.navigationBarsPadding(), topBar = { TopAppBar( title = stringResource(R.string.title_favourites_manager), 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 960601e6c..f9d78cf61 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 @@ -8,7 +8,6 @@ package com.aurora.store.compose.ui.installed import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState @@ -76,7 +75,6 @@ private fun ScreenContent( var initialLoad by rememberSaveable { mutableStateOf(true) } Scaffold( - modifier = Modifier.navigationBarsPadding(), topBar = { TopAppBar( title = stringResource(R.string.title_apps_games), 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 021731e56..e0e571fc2 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 @@ -14,7 +14,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow @@ -234,8 +233,7 @@ private fun ScreenContent( // TODO: https://issuetracker.google.com/issues/445720462 Scaffold( modifier = Modifier - .focusable() - .navigationBarsPadding(), + .focusable(), topBar = { SearchBar() } ) { paddingValues -> Column( From 20736089706c78d5a838e230a0c60379ffbe76e2 Mon Sep 17 00:00:00 2001 From: Rahul Patel Date: Fri, 8 May 2026 01:27:09 +0530 Subject: [PATCH 8/9] blacklist: fix search & group selected apps under sticky headers --- .../compose/ui/blacklist/BlacklistScreen.kt | 133 ++++++++++++++---- .../store/data/model/BlacklistAppItem.kt | 17 +++ .../viewmodel/blacklist/BlacklistViewModel.kt | 48 +++++-- app/src/main/res/values/strings.xml | 4 + 4 files changed, 157 insertions(+), 45 deletions(-) create mode 100644 app/src/main/java/com/aurora/store/data/model/BlacklistAppItem.kt 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 df6928908..47e4c0430 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 @@ -5,10 +5,12 @@ package com.aurora.store.compose.ui.blacklist -import android.content.pm.PackageInfo import android.net.Uri import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize @@ -21,10 +23,13 @@ import androidx.compose.foundation.text.input.clearText import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd import androidx.compose.material3.AppBarWithSearch +import androidx.compose.material3.ExpandedFullScreenSearchBar import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.SearchBarDefaults +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.rememberSearchBarState import androidx.compose.runtime.Composable @@ -46,20 +51,22 @@ 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.core.content.pm.PackageInfoCompat import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.aurora.Constants import com.aurora.extensions.toast import com.aurora.store.R import com.aurora.store.compose.composable.BlackListItem +import com.aurora.store.compose.composable.ContainedLoadingIndicator import com.aurora.store.compose.composable.ScrollHint +import com.aurora.store.compose.composable.TextDividerComposable import com.aurora.store.compose.preview.ThemePreviewProvider import com.aurora.store.compose.ui.blacklist.menu.BlacklistMenu import com.aurora.store.compose.ui.blacklist.menu.MenuItem -import com.aurora.store.util.PackageUtil +import com.aurora.store.data.model.BlacklistAppItem import com.aurora.store.viewmodel.blacklist.BlacklistViewModel import java.util.Calendar +import kotlinx.coroutines.android.awaitFrame import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch @@ -72,7 +79,6 @@ fun BlacklistScreen(onNavigateUp: () -> Unit, viewModel: BlacklistViewModel = hi packages = packages, onNavigateUp = onNavigateUp, isPackageBlacklisted = { pkgName -> pkgName in viewModel.blacklist }, - isPackageFiltered = { pkgInfo -> viewModel.isFiltered(pkgInfo) }, onBlacklistImport = { uri -> viewModel.importBlacklist(uri) context.toast(R.string.toast_black_import_success) @@ -91,10 +97,9 @@ fun BlacklistScreen(onNavigateUp: () -> Unit, viewModel: BlacklistViewModel = hi @Composable private fun ScreenContent( - packages: List? = null, + packages: List? = null, onNavigateUp: () -> Unit = {}, isPackageBlacklisted: (packageName: String) -> Boolean = { false }, - isPackageFiltered: (packageInfo: PackageInfo) -> Boolean = { false }, onBlacklistImport: (uri: Uri) -> Unit = {}, onBlacklistExport: (uri: Uri) -> Unit = {}, onBlacklist: (packageName: String) -> Unit = {}, @@ -119,6 +124,7 @@ private fun ScreenContent( } } ) + val docExportLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.CreateDocument(Constants.JSON_MIME_TYPE), onResult = { @@ -164,11 +170,23 @@ private fun ScreenContent( @Composable fun SearchBar() { + val interactionSource = remember { MutableInteractionSource() } + + LaunchedEffect(interactionSource) { + interactionSource.interactions.collectLatest { interaction -> + if (interaction is PressInteraction.Press) { + awaitFrame() + focusRequester.requestFocus() + } + } + } + val inputField = @Composable { SearchBarDefaults.InputField( modifier = Modifier.focusRequester(focusRequester), searchBarState = searchBarState, textFieldState = textFieldState, + interactionSource = interactionSource, onSearch = { query -> onRequestSearch(query) }, placeholder = { Text( @@ -212,11 +230,30 @@ private fun ScreenContent( ) } }, - actions = { if (!packages.isNullOrEmpty()) SetupMenu() }, + actions = { SetupMenu() }, colors = SearchBarDefaults.appBarWithSearchColors( appBarContainerColor = Color.Transparent ) ) + ExpandedFullScreenSearchBar(state = searchBarState, inputField = inputField) { + if (textFieldState.text.length >= 3) { + packages?.take(10)?.forEach { pkg -> + Text( + text = pkg.displayName, + style = MaterialTheme.typography.bodyMedium, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier + .fillMaxWidth() + .clickable { onRequestSearch(pkg.displayName) } + .padding( + horizontal = dimensionResource(R.dimen.padding_medium), + vertical = dimensionResource(R.dimen.padding_small) + ) + ) + } + } + } } Scaffold( @@ -228,28 +265,24 @@ private fun ScreenContent( .fillMaxSize() .padding(paddingValues) ) { - LazyColumn( - modifier = Modifier.fillMaxWidth(), - state = listState, - verticalArrangement = Arrangement.spacedBy( - dimensionResource(R.dimen.margin_xxsmall) - ) - ) { - items(items = packages ?: emptyList(), key = { p -> - p.packageName.hashCode() - }) { pkg -> + if (packages == null) { + ContainedLoadingIndicator() + } else { + val (selectedPackages, otherPackages) = packages.partition { pkg -> + isPackageBlacklisted(pkg.packageName) || pkg.isFiltered + } + + @Composable + fun BlacklistRow(pkg: BlacklistAppItem) { val isBlacklisted = isPackageBlacklisted(pkg.packageName) - val isFiltered = isPackageFiltered(pkg) BlackListItem( - icon = PackageUtil.getIconForPackage(context, pkg.packageName)!!, - displayName = pkg.applicationInfo!!.loadLabel( - context.packageManager - ).toString(), + icon = pkg.icon, + displayName = pkg.displayName, packageName = pkg.packageName, - versionName = pkg.versionName!!, - versionCode = PackageInfoCompat.getLongVersionCode(pkg), - isChecked = isBlacklisted || isFiltered, - isEnabled = !isFiltered, + versionName = pkg.versionName, + versionCode = pkg.versionCode, + isChecked = isBlacklisted || pkg.isFiltered, + isEnabled = !pkg.isFiltered, onClick = { if (isBlacklisted) { onWhitelist(pkg.packageName) @@ -259,12 +292,50 @@ private fun ScreenContent( } ) } + + LazyColumn( + modifier = Modifier.fillMaxWidth(), + state = listState, + verticalArrangement = Arrangement.spacedBy( + dimensionResource(R.dimen.margin_xxsmall) + ) + ) { + if (selectedPackages.isNotEmpty()) { + stickyHeader(key = "header_selected") { + Surface(modifier = Modifier.fillMaxWidth()) { + TextDividerComposable( + title = stringResource( + R.string.header_blacklist_selected + ) + ) + } + } + items(items = selectedPackages, key = { p -> + p.packageName.hashCode() + }) { pkg -> BlacklistRow(pkg) } + } + + if (otherPackages.isNotEmpty()) { + stickyHeader(key = "header_others") { + Surface(modifier = Modifier.fillMaxWidth()) { + TextDividerComposable( + title = stringResource( + R.string.header_blacklist_others + ) + ) + } + } + items(items = otherPackages, key = { p -> + p.packageName.hashCode() + }) { pkg -> BlacklistRow(pkg) } + } + } + ScrollHint( + listState = listState, + bottomPadding = 5.dp, + modifier = Modifier.align(Alignment.BottomCenter) + ) } - ScrollHint( - listState = listState, - bottomPadding = 5.dp, - modifier = Modifier.align(Alignment.BottomCenter) - ) } } } diff --git a/app/src/main/java/com/aurora/store/data/model/BlacklistAppItem.kt b/app/src/main/java/com/aurora/store/data/model/BlacklistAppItem.kt new file mode 100644 index 000000000..3df97745c --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/model/BlacklistAppItem.kt @@ -0,0 +1,17 @@ +/* + * SPDX-FileCopyrightText: 2025 The Calyx Institute + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.aurora.store.data.model + +import android.graphics.Bitmap + +data class BlacklistAppItem( + val packageName: String, + val displayName: String, + val versionName: String, + val versionCode: Long, + val icon: Bitmap, + val isFiltered: Boolean +) diff --git a/app/src/main/java/com/aurora/store/viewmodel/blacklist/BlacklistViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/blacklist/BlacklistViewModel.kt index bf597fe54..fcb880afb 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/blacklist/BlacklistViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/blacklist/BlacklistViewModel.kt @@ -10,12 +10,14 @@ import android.content.pm.PackageInfo import android.net.Uri import android.util.Log import androidx.compose.runtime.mutableStateListOf +import androidx.core.content.pm.PackageInfoCompat import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.aurora.extensions.TAG import com.aurora.store.AuroraApp import com.aurora.store.data.event.BusEvent import com.aurora.store.data.helper.UpdateHelper +import com.aurora.store.data.model.BlacklistAppItem import com.aurora.store.data.providers.BlacklistProvider import com.aurora.store.util.CertUtil import com.aurora.store.util.PackageUtil @@ -44,8 +46,8 @@ class BlacklistViewModel @Inject constructor( private val isExtendedUpdateEnabled = Preferences.getBoolean(context, Preferences.PREFERENCE_UPDATES_EXTENDED) - private val packages = MutableStateFlow?>(null) - private val _filteredPackages = MutableStateFlow?>(null) + private val packages = MutableStateFlow?>(null) + private val _filteredPackages = MutableStateFlow?>(null) val filteredPackages = _filteredPackages.asStateFlow() val blacklist = mutableStateListOf() @@ -58,28 +60,42 @@ class BlacklistViewModel @Inject constructor( private fun fetchApps() { viewModelScope.launch(Dispatchers.IO) { try { - packages.value = PackageUtil.getAllValidPackages(context).also { pkgList -> - _filteredPackages.value = pkgList - } + packages.value = PackageUtil.getAllValidPackages(context) + .mapNotNull { it.toBlacklistAppItem() } + .also { _filteredPackages.value = it } } catch (exception: Exception) { Log.e(TAG, "Failed to fetch apps", exception) } } } + private fun PackageInfo.toBlacklistAppItem(): BlacklistAppItem? { + val icon = PackageUtil.getIconForPackage(context, packageName) ?: return null + return BlacklistAppItem( + packageName = packageName, + displayName = applicationInfo!!.loadLabel(context.packageManager).toString(), + versionName = versionName ?: "", + versionCode = PackageInfoCompat.getLongVersionCode(this), + icon = icon, + isFiltered = isFiltered(this) + ) + } + fun search(query: String) { - if (query.isNotBlank()) { - _filteredPackages.value = packages.value!! - .filter { - it.applicationInfo!!.loadLabel(context.packageManager).contains(query, true) || + viewModelScope.launch(Dispatchers.Default) { + val allPackages = packages.value ?: return@launch + if (query.isNotBlank()) { + _filteredPackages.value = allPackages.filter { + it.displayName.contains(query, true) || it.packageName.contains(query, true) } - } else { - _filteredPackages.value = packages.value + } else { + _filteredPackages.value = allPackages + } } } - fun isFiltered(packageInfo: PackageInfo): Boolean = when { + private fun isFiltered(packageInfo: PackageInfo): Boolean = when { !isExtendedUpdateEnabled && !packageInfo.applicationInfo!!.enabled -> true isAuroraOnlyFilterEnabled -> !CertUtil.isAuroraStoreApp( @@ -99,7 +115,8 @@ class BlacklistViewModel @Inject constructor( } fun blacklistAll() { - blacklistProvider.blacklist = packages.value!!.map { it.packageName }.toMutableSet() + val allPackages = packages.value ?: return + blacklistProvider.blacklist = allPackages.map { it.packageName }.toMutableSet() blacklist.apply { clear() addAll(blacklistProvider.blacklist) @@ -126,7 +143,10 @@ class BlacklistViewModel @Inject constructor( ) val validImportedSet = importedSet - .filter { pkgName -> packages.value!!.any { it.packageName == pkgName } } + .filter { pkgName -> + packages.value?.any { it.packageName == pkgName } == + true + } blacklistProvider.blacklist.addAll(validImportedSet) blacklist.apply { clear() diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0513cc8c0..4b71e8316 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -493,6 +493,10 @@ Default Available + + Blacklisted + Others + No description available %1$d permissions From 5580bad1e36e43bc0b067706e76e42288e4b05af Mon Sep 17 00:00:00 2001 From: Rahul Patel Date: Fri, 8 May 2026 01:49:33 +0530 Subject: [PATCH 9/9] installed: improve sluggish app load The VM was calling PackageUtil.getAllValidPackages(context) eagerly in its constructor, which Hilt creates on the main thread. The PackageManager IPC plus a per-app loadLabel() for sorting froze the screen long enough that the existing ContainedLoadingIndicator never got a chance to render. --- .../store/viewmodel/all/InstalledViewModel.kt | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/aurora/store/viewmodel/all/InstalledViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/all/InstalledViewModel.kt index 0867e30be..08dbc678f 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/all/InstalledViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/all/InstalledViewModel.kt @@ -33,6 +33,9 @@ import com.aurora.store.util.PackageUtil import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.distinctUntilChanged @@ -46,30 +49,44 @@ class InstalledViewModel @Inject constructor( private val webAppDetailsHelper: WebAppDetailsHelper ) : ViewModel() { - private val packages = PackageUtil.getAllValidPackages(context) private val blacklist = blacklistProvider.blacklist private val _apps = MutableStateFlow>(PagingData.empty()) val apps = _apps.asStateFlow() + /** + * Enumerating installed packages calls into PackageManager and resolves a label + * per app for sorting. Done lazily on [Dispatchers.IO] so the VM constructor and + * the main thread stay snappy; paging awaits this before fetching the first page. + */ + private val pagedPackages = viewModelScope.async( + context = Dispatchers.IO, + start = CoroutineStart.LAZY + ) { + PackageUtil.getAllValidPackages(context) + .filterNot { it.packageName in blacklist } + .chunked(PAGE_SIZE) + } + init { fetchApps() } - fun fetchApps() { - val pagedPackages = packages - .filterNot { it.packageName in blacklist } - .chunked(20) - - manualPager { page -> + private fun fetchApps() { + manualPager(pageSize = PAGE_SIZE) { page -> // page is 1-indexed, but list is 0-indexed - val chunk = pagedPackages.getOrNull(page - 1) + val chunks = pagedPackages.await() + val chunk = chunks.getOrNull(page - 1) ?: return@manualPager PageResult(emptyList(), hasMore = false) val items = webAppDetailsHelper.getAppDetails(chunk.map { it.packageName }) - PageResult(items, hasMore = page < pagedPackages.size) + PageResult(items, hasMore = page < chunks.size) }.flow.distinctUntilChanged() .cachedIn(viewModelScope) .onEach { _apps.value = it } .launchIn(viewModelScope) } + + companion object { + private const val PAGE_SIZE = 20 + } }