diff --git a/app/src/main/java/hrln/interactive/sidekit/apps/apps/AppRow.kt b/app/src/main/java/hrln/interactive/sidekit/apps/apps/AppRow.kt index d61fe1e0..d53d1b0c 100644 --- a/app/src/main/java/hrln/interactive/sidekit/apps/apps/AppRow.kt +++ b/app/src/main/java/hrln/interactive/sidekit/apps/apps/AppRow.kt @@ -4,6 +4,7 @@ import android.graphics.Bitmap import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -64,8 +65,13 @@ internal fun AppRow( ), secondary = secondary, showLinkArrow = true, + // The slot is always present, so a row doesn't shift sideways while its icon loads. leading = { - icon?.let { Image(it.asImageBitmap(), contentDescription = null, modifier = Modifier.size(SidekitTheme.spacing.iconSize)) } + if (icon != null) { + Image(icon.asImageBitmap(), contentDescription = null, modifier = Modifier.size(SidekitTheme.spacing.iconSize)) + } else { + Box(Modifier.size(SidekitTheme.spacing.iconSize)) + } } ) } diff --git a/app/src/main/java/hrln/interactive/sidekit/apps/apps/AppsGraph.kt b/app/src/main/java/hrln/interactive/sidekit/apps/apps/AppsGraph.kt index d2ae0a35..033ea5a1 100644 --- a/app/src/main/java/hrln/interactive/sidekit/apps/apps/AppsGraph.kt +++ b/app/src/main/java/hrln/interactive/sidekit/apps/apps/AppsGraph.kt @@ -58,14 +58,17 @@ internal fun AppsScreen(vm: AppsViewModel, back: () -> Unit) { SidekitList(padding, state = list) { item { AppSearchField(query) { vm.query.value = it } } item { SidekitCard { SidekitText(selected?.let { "${it.label} selected. Press Options for app info or uninstall." } ?: "Tap to open · hold an app for Options", style = SidekitTheme.typography.label, maxLines = Int.MAX_VALUE) } } - (message ?: state.error)?.let { item { SidekitCard(onClick = { message = null }) { SidekitText(it, style = SidekitTheme.typography.label, maxLines = Int.MAX_VALUE) } } } + val banner = message ?: state.error + banner?.let { item { SidekitCard(onClick = { message = null }) { SidekitText(it, style = SidekitTheme.typography.label, maxLines = Int.MAX_VALUE) } } } if (!state.loaded && state.error == null) item { SidekitItem("Loading apps…") } if (state.loaded && state.apps.isEmpty()) item { SidekitItem("No matching apps") } + // listFade needs the row's real list index: search field and hint, plus the banner if shown. + val rowsStart = 2 + if (banner != null) 1 else 0 itemsIndexed(state.apps, key = { _, it -> it.key }) { index, target -> AppRow( target, vm.repository, - Modifier.listFade(list, index + 2), + Modifier.listFade(list, index + rowsStart), secondary = if (target.user != Process.myUserHandle()) "Work profile" else null, onClick = { selected = target diff --git a/app/src/main/java/hrln/interactive/sidekit/launcher/LauncherScreen.kt b/app/src/main/java/hrln/interactive/sidekit/launcher/LauncherScreen.kt index c033c842..08ddf808 100644 --- a/app/src/main/java/hrln/interactive/sidekit/launcher/LauncherScreen.kt +++ b/app/src/main/java/hrln/interactive/sidekit/launcher/LauncherScreen.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.res.stringResource import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import hrln.interactive.sidekit.R +import hrln.interactive.sidekit.apps.apps.matchesApp import hrln.interactive.sidekit.apps.settings.* import hrln.interactive.sidekit.apps.sideport.registry.PortedApp import hrln.interactive.sidekit.apps.sideport.registry.PortedApps @@ -173,7 +174,8 @@ fun LauncherScreen( LaunchedEffect(searching) { if (searching) runCatching { searchFocus.requestFocus() } } val clear = SideAction.square("search.clear", SidekitIcon.Close, "Clear app search", ::clearSearch) val entries = home.ifEmpty { defaultHome() } - val visible = entries.filter { it.id !in hiddenApps && it.label.contains(text, ignoreCase = true) } + // Same matcher as the Apps list, so a T9 query finds the same apps in both places. + val visible = entries.filter { it.id !in hiddenApps && matchesApp(it.label, text) } BackHandler { clearSearch() } SidekitScaffold( diff --git a/app/src/main/java/hrln/interactive/sidekit/navigation/SubApp.kt b/app/src/main/java/hrln/interactive/sidekit/navigation/SubApp.kt index 003d789d..b4fec380 100644 --- a/app/src/main/java/hrln/interactive/sidekit/navigation/SubApp.kt +++ b/app/src/main/java/hrln/interactive/sidekit/navigation/SubApp.kt @@ -28,7 +28,7 @@ enum class SubApp(val label: String, val icon: SidekitIcon, val designed: Boolea Banking("Banking", SidekitIcon.AccountBalanceWallet), Settings("Settings", SidekitIcon.Settings), WebBrowser("Web browser", SidekitIcon.TravelExplore), - Apps("Apps", SidekitIcon.DeployedCode), + Apps("Apps", SidekitIcon.Apps), Camera("Camera", SidekitIcon.PhotoCamera), Sideweather("Sideweather", SidekitIcon.PartlyCloudyDay), Calendar("Calendar", SidekitIcon.CalendarMonth), diff --git a/sidekit/src/main/java/hrln/interactive/sidekit/design/icons/SidekitIcon.kt b/sidekit/src/main/java/hrln/interactive/sidekit/design/icons/SidekitIcon.kt index 7e5beb63..67f581af 100644 --- a/sidekit/src/main/java/hrln/interactive/sidekit/design/icons/SidekitIcon.kt +++ b/sidekit/src/main/java/hrln/interactive/sidekit/design/icons/SidekitIcon.kt @@ -15,6 +15,7 @@ enum class SidekitIcon(@DrawableRes val drawable: Int) { Album(R.drawable.sk_ic_album), AndroidCell4Bar(R.drawable.sk_ic_android_cell_4_bar), AndroidWifi3Bar(R.drawable.sk_ic_android_wifi_3_bar), + Apps(R.drawable.sk_ic_apps), ArrowLeftAlt(R.drawable.sk_ic_arrow_left_alt), ArrowRightAlt(R.drawable.sk_ic_arrow_right_alt), ArrowUpward(R.drawable.sk_ic_arrow_upward), diff --git a/sidekit/src/main/res/drawable/sk_ic_apps.xml b/sidekit/src/main/res/drawable/sk_ic_apps.xml new file mode 100644 index 00000000..7271520e --- /dev/null +++ b/sidekit/src/main/res/drawable/sk_ic_apps.xml @@ -0,0 +1,9 @@ + + + + + + +