From d5624b044ab366b9153d9a79460b2573eb37a6c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Rottsch=C3=A4fer?= Date: Mon, 9 Nov 2026 22:40:16 +0100 Subject: [PATCH 1/2] fix(launcher): give the Apps tile its own icon Apps and Sideport both used SidekitIcon.DeployedCode, so the two launcher tiles were visually identical. Apps now uses a new Material Symbols 'apps' grid glyph; Sideport keeps DeployedCode, which it already uses for ported app tiles and the 'In Sideport' row. --- .../java/hrln/interactive/sidekit/navigation/SubApp.kt | 2 +- .../hrln/interactive/sidekit/design/icons/SidekitIcon.kt | 1 + sidekit/src/main/res/drawable/sk_ic_apps.xml | 9 +++++++++ 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 sidekit/src/main/res/drawable/sk_ic_apps.xml 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 @@ + + + + + + + From 407be5882aaa8a829204feecf5cbb6b4b17ea2df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20Rottsch=C3=A4fer?= Date: Tue, 10 Nov 2026 09:00:04 +0100 Subject: [PATCH 2/2] fix(apps): align the app list and launcher search - AppRow reserves the icon slot, so rows no longer shift while icons load. - Apps list fade uses the real row index instead of a hardcoded +2. - Launcher search uses matchesApp, so T9 queries find the same apps as the Apps list. --- .../java/hrln/interactive/sidekit/apps/apps/AppRow.kt | 8 +++++++- .../java/hrln/interactive/sidekit/apps/apps/AppsGraph.kt | 7 +++++-- .../hrln/interactive/sidekit/launcher/LauncherScreen.kt | 4 +++- 3 files changed, 15 insertions(+), 4 deletions(-) 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(