mirror of
https://github.com/f-droid/fdroidclient.git
synced 2026-06-15 19:29:56 -04:00
Handle apps not found in app details
This commit is contained in:
@@ -4,7 +4,7 @@ import org.fdroid.LocaleChooser.getBestLocale
|
||||
import org.fdroid.download.NetworkState
|
||||
import org.fdroid.install.InstallState
|
||||
import org.fdroid.ui.details.AppDetails
|
||||
import org.fdroid.ui.details.AppDetailsItem
|
||||
import org.fdroid.ui.details.LoadedAppDetailsItem
|
||||
import org.fdroid.ui.details.VersionItem
|
||||
import org.fdroid.ui.utils.getAppDetailsActions
|
||||
import org.fdroid.ui.utils.testVersion1
|
||||
@@ -23,7 +23,7 @@ class DetailsScreenshotTest(localeName: String) : LocalizedScreenshotTest(locale
|
||||
fun appDetails() =
|
||||
screenshotTest("4_Details", showBottomBar = false, dark = true) { localeList ->
|
||||
val item =
|
||||
AppDetailsItem(
|
||||
LoadedAppDetailsItem(
|
||||
app = appMetadata,
|
||||
actions = getAppDetailsActions(),
|
||||
installState = InstallState.Unknown,
|
||||
|
||||
@@ -2,9 +2,11 @@ package org.fdroid.ui.details
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.calculateEndPadding
|
||||
import androidx.compose.foundation.layout.calculateStartPadding
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
@@ -42,6 +44,7 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
@@ -99,11 +102,21 @@ fun AppDetails(
|
||||
var showInstallError by remember { mutableStateOf(false) }
|
||||
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(topAppBarState)
|
||||
if (item == null) BigLoadingIndicator()
|
||||
else
|
||||
else {
|
||||
Scaffold(
|
||||
topBar = { AppDetailsTopAppBar(item, topAppBarState, scrollBehavior, onBackNav) },
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
) { innerPadding ->
|
||||
if (item is NotFoundAppDetailsItem) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier.fillMaxSize().padding(innerPadding),
|
||||
) {
|
||||
Text(stringResource(R.string.no_such_app))
|
||||
}
|
||||
return@Scaffold
|
||||
}
|
||||
item as LoadedAppDetailsItem
|
||||
// react to install state changes
|
||||
LaunchedEffect(item.installState) {
|
||||
val state = item.installState
|
||||
@@ -413,33 +426,34 @@ fun AppDetails(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (showInstallError && item != null && item.installState is InstallState.Error)
|
||||
AlertDialog(
|
||||
onDismissRequest = { showInstallError = false },
|
||||
containerColor = MaterialTheme.colorScheme.errorContainer,
|
||||
title = { Text(stringResource(R.string.install_error_notify_title, item.name)) },
|
||||
text = {
|
||||
if (item.installState.msg == null) {
|
||||
Text(stringResource(R.string.app_details_install_error_text))
|
||||
} else {
|
||||
ExpandableSection(
|
||||
icon = null,
|
||||
title = stringResource(R.string.app_details_install_error_text),
|
||||
) {
|
||||
SelectionContainer {
|
||||
Text(
|
||||
text = item.installState.msg,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
)
|
||||
if (item is LoadedAppDetailsItem && showInstallError && item.installState is InstallState.Error)
|
||||
AlertDialog(
|
||||
onDismissRequest = { showInstallError = false },
|
||||
containerColor = MaterialTheme.colorScheme.errorContainer,
|
||||
title = { Text(stringResource(R.string.install_error_notify_title, item.name)) },
|
||||
text = {
|
||||
if (item.installState.msg == null) {
|
||||
Text(stringResource(R.string.app_details_install_error_text))
|
||||
} else {
|
||||
ExpandableSection(
|
||||
icon = null,
|
||||
title = stringResource(R.string.app_details_install_error_text),
|
||||
) {
|
||||
SelectionContainer {
|
||||
Text(
|
||||
text = item.installState.msg,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = { showInstallError = false }) { Text(stringResource(R.string.ok)) }
|
||||
},
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = { showInstallError = false }) { Text(stringResource(R.string.ok)) }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@@ -448,6 +462,12 @@ fun AppDetailsLoadingPreview() {
|
||||
FDroidContent { AppDetails(null, {}, {}) }
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun AppDetailsNotFoundPreview() {
|
||||
FDroidContent { AppDetails(NotFoundAppDetailsItem, {}, {}) }
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun AppDetailsPreview() {
|
||||
|
||||
@@ -93,7 +93,7 @@ import org.fdroid.ui.utils.testApp
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
/** Timestamp [now] gets passed in for screenshot tests to have a stable download speed. */
|
||||
fun AppDetailsHeader(
|
||||
item: AppDetailsItem,
|
||||
item: LoadedAppDetailsItem,
|
||||
onNav: (NavigationKey) -> Unit,
|
||||
innerPadding: PaddingValues,
|
||||
now: Long = System.currentTimeMillis(),
|
||||
|
||||
@@ -24,7 +24,11 @@ import org.fdroid.install.SessionInstallManager
|
||||
import org.fdroid.search.SearchHelper.removeZeroWhiteSpace
|
||||
import org.fdroid.ui.categories.CategoryItem
|
||||
|
||||
data class AppDetailsItem(
|
||||
sealed class AppDetailsItem
|
||||
|
||||
data object NotFoundAppDetailsItem : AppDetailsItem()
|
||||
|
||||
data class LoadedAppDetailsItem(
|
||||
val app: AppMetadata,
|
||||
val actions: AppDetailsActions,
|
||||
val installState: InstallState,
|
||||
@@ -66,7 +70,7 @@ data class AppDetailsItem(
|
||||
val issue: AppIssue? = null,
|
||||
val authorHasMoreThanOneApp: Boolean = false,
|
||||
val proxy: ProxyConfig? = null,
|
||||
) {
|
||||
) : AppDetailsItem() {
|
||||
constructor(
|
||||
repository: Repository,
|
||||
preferredRepoId: Long,
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.fdroid.ui.utils.testApp
|
||||
|
||||
@Composable
|
||||
fun AppDetailsMenu(
|
||||
item: AppDetailsItem,
|
||||
item: LoadedAppDetailsItem,
|
||||
uninstallLauncher: ActivityResultLauncher<Intent>,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
|
||||
@@ -32,28 +32,30 @@ fun AppDetailsTopAppBar(
|
||||
TopAppBar(
|
||||
colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
|
||||
title = {
|
||||
if (topAppBarState.overlappedFraction == 1f) {
|
||||
if (item is LoadedAppDetailsItem && topAppBarState.overlappedFraction == 1f) {
|
||||
Text(item.name, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
},
|
||||
navigationIcon = { if (onBackNav != null) BackButton(onClick = onBackNav) },
|
||||
actions = {
|
||||
val context = LocalContext.current
|
||||
item.actions.shareIntent?.let { shareIntent ->
|
||||
TopAppBarButton(
|
||||
imageVector = Icons.Filled.Share,
|
||||
contentDescription = stringResource(R.string.menu_share),
|
||||
onClick = { context.startActivitySafe(shareIntent) },
|
||||
)
|
||||
}
|
||||
// the launcher needs to be at least here,
|
||||
// because if the menu is dismissed we don't get the result
|
||||
val uninstallLauncher =
|
||||
rememberLauncherForActivityResult(StartActivityForResult()) {
|
||||
item.actions.onUninstallResult(it)
|
||||
if (item is LoadedAppDetailsItem) {
|
||||
val context = LocalContext.current
|
||||
item.actions.shareIntent?.let { shareIntent ->
|
||||
TopAppBarButton(
|
||||
imageVector = Icons.Filled.Share,
|
||||
contentDescription = stringResource(R.string.menu_share),
|
||||
onClick = { context.startActivitySafe(shareIntent) },
|
||||
)
|
||||
}
|
||||
// the launcher needs to be at least here,
|
||||
// because if the menu is dismissed we don't get the result
|
||||
val uninstallLauncher =
|
||||
rememberLauncherForActivityResult(StartActivityForResult()) {
|
||||
item.actions.onUninstallResult(it)
|
||||
}
|
||||
TopAppBarOverflowButton { onDismissRequest ->
|
||||
AppDetailsMenu(item, uninstallLauncher, onDismissRequest)
|
||||
}
|
||||
TopAppBarOverflowButton { onDismissRequest ->
|
||||
AppDetailsMenu(item, uninstallLauncher, onDismissRequest)
|
||||
}
|
||||
},
|
||||
scrollBehavior = scrollBehavior,
|
||||
|
||||
@@ -165,7 +165,7 @@ constructor(
|
||||
|
||||
@UiThread
|
||||
fun onUninstallResult(activityResult: ActivityResult) {
|
||||
val name = appDetails.value?.name
|
||||
val name = (appDetails.value as? LoadedAppDetailsItem)?.name
|
||||
val result = appInstallManager.onUninstallResult(packageName, name, activityResult)
|
||||
log.info { "Uninstall result was: $result" }
|
||||
}
|
||||
@@ -195,7 +195,7 @@ constructor(
|
||||
val diskCache = SingletonImageLoader.get(application).diskCache
|
||||
if (diskCache != null)
|
||||
scope.launch {
|
||||
appDetails.value?.phoneScreenshots?.forEach { screenshot ->
|
||||
(appDetails.value as? LoadedAppDetailsItem)?.phoneScreenshots?.forEach { screenshot ->
|
||||
if (screenshot is DownloadRequest) {
|
||||
try {
|
||||
diskCache.remove(screenshot.getCacheKey())
|
||||
@@ -209,7 +209,7 @@ constructor(
|
||||
|
||||
@UiThread
|
||||
fun allowBetaUpdates() {
|
||||
val appPrefs = appDetails.value?.appPrefs ?: return
|
||||
val appPrefs = (appDetails.value as? LoadedAppDetailsItem)?.appPrefs ?: return
|
||||
scope.launch {
|
||||
db.getAppPrefsDao().update(appPrefs.toggleReleaseChannel(RELEASE_CHANNEL_BETA))
|
||||
updatesManager.loadUpdates()
|
||||
@@ -218,7 +218,7 @@ constructor(
|
||||
|
||||
@UiThread
|
||||
fun ignoreAllUpdates() {
|
||||
val appPrefs = appDetails.value?.appPrefs ?: return
|
||||
val appPrefs = (appDetails.value as? LoadedAppDetailsItem)?.appPrefs ?: return
|
||||
scope.launch {
|
||||
db.getAppPrefsDao().update(appPrefs.toggleIgnoreAllUpdates())
|
||||
updatesManager.loadUpdates()
|
||||
@@ -227,8 +227,9 @@ constructor(
|
||||
|
||||
@UiThread
|
||||
fun ignoreThisUpdate() {
|
||||
val appPrefs = appDetails.value?.appPrefs ?: return
|
||||
val versionCode = appDetails.value?.possibleUpdate?.versionCode ?: return
|
||||
val appPrefs = (appDetails.value as? LoadedAppDetailsItem)?.appPrefs ?: return
|
||||
val versionCode =
|
||||
(appDetails.value as? LoadedAppDetailsItem)?.possibleUpdate?.versionCode ?: return
|
||||
scope.launch {
|
||||
db.getAppPrefsDao().update(appPrefs.toggleIgnoreVersionCodeUpdate(versionCode))
|
||||
updatesManager.loadUpdates()
|
||||
|
||||
@@ -30,7 +30,7 @@ import org.fdroid.ui.FDroidContent
|
||||
import org.fdroid.ui.utils.testApp
|
||||
|
||||
@Composable
|
||||
fun AppDetailsWarnings(item: AppDetailsItem, modifier: Modifier = Modifier) {
|
||||
fun AppDetailsWarnings(item: LoadedAppDetailsItem, modifier: Modifier = Modifier) {
|
||||
val (color, string) =
|
||||
when {
|
||||
// app issues take priority
|
||||
|
||||
@@ -68,7 +68,7 @@ fun DetailsPresenter(
|
||||
}
|
||||
}
|
||||
}
|
||||
.value ?: return null
|
||||
.value ?: return NotFoundAppDetailsItem
|
||||
val versions =
|
||||
produceState<List<AppVersion>?>(null, currentRepoId) {
|
||||
withContext(dispatcher) {
|
||||
@@ -123,7 +123,7 @@ fun DetailsPresenter(
|
||||
produceState<Repository?>(null, app) {
|
||||
withContext(dispatcher) { value = repoManager.getRepository(app.repoId) }
|
||||
}
|
||||
.value ?: return null
|
||||
.value ?: return NotFoundAppDetailsItem
|
||||
val repositories =
|
||||
produceState(emptyList(), packageName) {
|
||||
withContext(dispatcher) {
|
||||
@@ -196,7 +196,7 @@ fun DetailsPresenter(
|
||||
Log.d(TAG, " versions: ${versions?.size}")
|
||||
Log.d(TAG, " appPrefs: $appPrefs")
|
||||
Log.d(TAG, " installState: $installState")
|
||||
return AppDetailsItem(
|
||||
return LoadedAppDetailsItem(
|
||||
repository = repo,
|
||||
preferredRepoId = preferredRepoId,
|
||||
repositories = repositories,
|
||||
|
||||
@@ -17,7 +17,7 @@ import org.fdroid.ui.FDroidContent
|
||||
import org.fdroid.ui.utils.testApp
|
||||
|
||||
@Composable
|
||||
fun TechnicalInfo(item: AppDetailsItem) {
|
||||
fun TechnicalInfo(item: LoadedAppDetailsItem) {
|
||||
val items = mutableMapOf(stringResource(R.string.package_name) to item.app.packageName)
|
||||
if (item.installedVersionCode != null) {
|
||||
items[stringResource(R.string.installed_version)] =
|
||||
|
||||
@@ -47,7 +47,7 @@ import org.fdroid.ui.utils.testApp
|
||||
|
||||
@Composable
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
fun Versions(item: AppDetailsItem, scrollUp: suspend () -> Unit) {
|
||||
fun Versions(item: LoadedAppDetailsItem, scrollUp: suspend () -> Unit) {
|
||||
ExpandableSection(
|
||||
icon = rememberVectorPainter(Icons.Default.AccessTime),
|
||||
title = stringResource(R.string.versions),
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.fdroid.ui.categories.CategoryItem
|
||||
import org.fdroid.ui.details.AntiFeature
|
||||
import org.fdroid.ui.details.AppDetailsActions
|
||||
import org.fdroid.ui.details.AppDetailsItem
|
||||
import org.fdroid.ui.details.LoadedAppDetailsItem
|
||||
import org.fdroid.ui.details.VersionItem
|
||||
import org.fdroid.ui.lists.AntiFeatureItem
|
||||
import org.fdroid.ui.lists.AppListActions
|
||||
@@ -144,7 +145,7 @@ val categoryItems =
|
||||
)
|
||||
|
||||
val testApp =
|
||||
AppDetailsItem(
|
||||
LoadedAppDetailsItem(
|
||||
app =
|
||||
AppMetadata(
|
||||
repoId = 1,
|
||||
|
||||
@@ -18,13 +18,14 @@ fun DetailsTest() =
|
||||
testApp.copy(
|
||||
versions = emptyList(),
|
||||
categories = testApp.categories?.subList(0, 2),
|
||||
phoneScreenshots = listOf(
|
||||
"https://f-droid.org/repo/org.fdroid.fdroid.2024-05-31-17-00-00.png",
|
||||
"https://f-droid.org/repo/org.fdroid.fdroid.2024-05-31-17-00-00.png",
|
||||
"https://f-droid.org/repo/org.fdroid.fdroid.2024-05-31-17-00-00.png",
|
||||
"https://f-droid.org/repo/org.fdroid.fdroid.2024-05-31-17-00-00.png",
|
||||
"https://f-droid.org/repo/org.fdroid.fdroid.2024-05-31-17-00-00.png",
|
||||
),
|
||||
phoneScreenshots =
|
||||
listOf(
|
||||
"https://f-droid.org/repo/org.fdroid.fdroid.2024-05-31-17-00-00.png",
|
||||
"https://f-droid.org/repo/org.fdroid.fdroid.2024-05-31-17-00-00.png",
|
||||
"https://f-droid.org/repo/org.fdroid.fdroid.2024-05-31-17-00-00.png",
|
||||
"https://f-droid.org/repo/org.fdroid.fdroid.2024-05-31-17-00-00.png",
|
||||
"https://f-droid.org/repo/org.fdroid.fdroid.2024-05-31-17-00-00.png",
|
||||
),
|
||||
networkState = NetworkState(isOnline = true, isMetered = false),
|
||||
),
|
||||
onNav = {},
|
||||
@@ -32,6 +33,12 @@ fun DetailsTest() =
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
@PreviewTest
|
||||
fun AppDetailsNotFoundTest() =
|
||||
ScreenshotTest(showBottomBar = false) { AppDetails(NotFoundAppDetailsItem, {}, {}) }
|
||||
|
||||
@Composable
|
||||
@PreviewTest
|
||||
@Preview(showBackground = true, showSystemUi = true, heightDp = 3000)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9bc256fb0c2f4e7736bda965bff6fcfd2004cca3ac6bd307235ad068aef821af
|
||||
size 21381
|
||||
Reference in New Issue
Block a user