diff --git a/app/src/androidTest/java/org/fdroid/ui/screenshots/DetailsScreenshotTest.kt b/app/src/androidTest/java/org/fdroid/ui/screenshots/DetailsScreenshotTest.kt index 16a249076..4c105dc23 100644 --- a/app/src/androidTest/java/org/fdroid/ui/screenshots/DetailsScreenshotTest.kt +++ b/app/src/androidTest/java/org/fdroid/ui/screenshots/DetailsScreenshotTest.kt @@ -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, diff --git a/app/src/main/kotlin/org/fdroid/ui/details/AppDetails.kt b/app/src/main/kotlin/org/fdroid/ui/details/AppDetails.kt index 0dd0d5fd2..45b2143eb 100644 --- a/app/src/main/kotlin/org/fdroid/ui/details/AppDetails.kt +++ b/app/src/main/kotlin/org/fdroid/ui/details/AppDetails.kt @@ -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() { diff --git a/app/src/main/kotlin/org/fdroid/ui/details/AppDetailsHeader.kt b/app/src/main/kotlin/org/fdroid/ui/details/AppDetailsHeader.kt index f076e9c2c..65c41c5a9 100644 --- a/app/src/main/kotlin/org/fdroid/ui/details/AppDetailsHeader.kt +++ b/app/src/main/kotlin/org/fdroid/ui/details/AppDetailsHeader.kt @@ -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(), diff --git a/app/src/main/kotlin/org/fdroid/ui/details/AppDetailsItem.kt b/app/src/main/kotlin/org/fdroid/ui/details/AppDetailsItem.kt index 98b7e5395..ff88e3147 100644 --- a/app/src/main/kotlin/org/fdroid/ui/details/AppDetailsItem.kt +++ b/app/src/main/kotlin/org/fdroid/ui/details/AppDetailsItem.kt @@ -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, diff --git a/app/src/main/kotlin/org/fdroid/ui/details/AppDetailsMenu.kt b/app/src/main/kotlin/org/fdroid/ui/details/AppDetailsMenu.kt index 9a17b7337..e6c0e9851 100644 --- a/app/src/main/kotlin/org/fdroid/ui/details/AppDetailsMenu.kt +++ b/app/src/main/kotlin/org/fdroid/ui/details/AppDetailsMenu.kt @@ -28,7 +28,7 @@ import org.fdroid.ui.utils.testApp @Composable fun AppDetailsMenu( - item: AppDetailsItem, + item: LoadedAppDetailsItem, uninstallLauncher: ActivityResultLauncher, onDismiss: () -> Unit, ) { diff --git a/app/src/main/kotlin/org/fdroid/ui/details/AppDetailsTopAppBar.kt b/app/src/main/kotlin/org/fdroid/ui/details/AppDetailsTopAppBar.kt index 43d8f2f8b..05441c084 100644 --- a/app/src/main/kotlin/org/fdroid/ui/details/AppDetailsTopAppBar.kt +++ b/app/src/main/kotlin/org/fdroid/ui/details/AppDetailsTopAppBar.kt @@ -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, diff --git a/app/src/main/kotlin/org/fdroid/ui/details/AppDetailsViewModel.kt b/app/src/main/kotlin/org/fdroid/ui/details/AppDetailsViewModel.kt index 11b310122..de11a692a 100644 --- a/app/src/main/kotlin/org/fdroid/ui/details/AppDetailsViewModel.kt +++ b/app/src/main/kotlin/org/fdroid/ui/details/AppDetailsViewModel.kt @@ -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() diff --git a/app/src/main/kotlin/org/fdroid/ui/details/AppDetailsWarnings.kt b/app/src/main/kotlin/org/fdroid/ui/details/AppDetailsWarnings.kt index 561ca2b85..f69616219 100644 --- a/app/src/main/kotlin/org/fdroid/ui/details/AppDetailsWarnings.kt +++ b/app/src/main/kotlin/org/fdroid/ui/details/AppDetailsWarnings.kt @@ -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 diff --git a/app/src/main/kotlin/org/fdroid/ui/details/DetailsPresenter.kt b/app/src/main/kotlin/org/fdroid/ui/details/DetailsPresenter.kt index 083486aa0..86f0fd8fa 100644 --- a/app/src/main/kotlin/org/fdroid/ui/details/DetailsPresenter.kt +++ b/app/src/main/kotlin/org/fdroid/ui/details/DetailsPresenter.kt @@ -68,7 +68,7 @@ fun DetailsPresenter( } } } - .value ?: return null + .value ?: return NotFoundAppDetailsItem val versions = produceState?>(null, currentRepoId) { withContext(dispatcher) { @@ -123,7 +123,7 @@ fun DetailsPresenter( produceState(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, diff --git a/app/src/main/kotlin/org/fdroid/ui/details/TechnicalInfo.kt b/app/src/main/kotlin/org/fdroid/ui/details/TechnicalInfo.kt index a6a470be9..643117b7c 100644 --- a/app/src/main/kotlin/org/fdroid/ui/details/TechnicalInfo.kt +++ b/app/src/main/kotlin/org/fdroid/ui/details/TechnicalInfo.kt @@ -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)] = diff --git a/app/src/main/kotlin/org/fdroid/ui/details/Versions.kt b/app/src/main/kotlin/org/fdroid/ui/details/Versions.kt index 9b86974d8..a48d7a33d 100644 --- a/app/src/main/kotlin/org/fdroid/ui/details/Versions.kt +++ b/app/src/main/kotlin/org/fdroid/ui/details/Versions.kt @@ -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), diff --git a/app/src/main/kotlin/org/fdroid/ui/utils/PreviewUtils.kt b/app/src/main/kotlin/org/fdroid/ui/utils/PreviewUtils.kt index 604933da5..fc7d46b22 100644 --- a/app/src/main/kotlin/org/fdroid/ui/utils/PreviewUtils.kt +++ b/app/src/main/kotlin/org/fdroid/ui/utils/PreviewUtils.kt @@ -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, diff --git a/app/src/screenshotTest/kotlin/org/fdroid/ui/details/DetailsTest.kt b/app/src/screenshotTest/kotlin/org/fdroid/ui/details/DetailsTest.kt index e8d4b4f13..5ed7b89fb 100644 --- a/app/src/screenshotTest/kotlin/org/fdroid/ui/details/DetailsTest.kt +++ b/app/src/screenshotTest/kotlin/org/fdroid/ui/details/DetailsTest.kt @@ -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) diff --git a/app/src/screenshotTestBasicDefaultDebug/reference/org/fdroid/ui/details/DetailsTestKt/AppDetailsNotFoundTest_0.png b/app/src/screenshotTestBasicDefaultDebug/reference/org/fdroid/ui/details/DetailsTestKt/AppDetailsNotFoundTest_0.png new file mode 100644 index 000000000..ea45d134e --- /dev/null +++ b/app/src/screenshotTestBasicDefaultDebug/reference/org/fdroid/ui/details/DetailsTestKt/AppDetailsNotFoundTest_0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9bc256fb0c2f4e7736bda965bff6fcfd2004cca3ac6bd307235ad068aef821af +size 21381