From 3ffe38d988f36799f0eff7ebd7529548c9395626 Mon Sep 17 00:00:00 2001 From: austinried <4966622+austinried@users.noreply.github.com> Date: Wed, 21 Oct 2026 07:45:26 +0000 Subject: [PATCH] ui: float the offline indicator opposite the library FAB Drop the strip above the tab bar, which kept the bottom of the library busy, and go back to a floating tonal cloud-off button at the bottom start, level with the FAB on the other side. Tapping it still leaves offline mode, and the tab bar goes back to just tabs and the gear. --- .../com/subtracks/ui/library/LibraryScreen.kt | 60 +++++-------------- app/src/main/res/values/strings.xml | 3 +- .../com/subtracks/ui/LibraryScreenTest.kt | 9 ++- 3 files changed, 20 insertions(+), 52 deletions(-) diff --git a/app/src/main/kotlin/com/subtracks/ui/library/LibraryScreen.kt b/app/src/main/kotlin/com/subtracks/ui/library/LibraryScreen.kt index f1cd8648..ba86d3db 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/LibraryScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/LibraryScreen.kt @@ -17,7 +17,6 @@ import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -46,6 +45,7 @@ import androidx.compose.material.icons.rounded.Search import androidx.compose.material.icons.rounded.Settings import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FilledTonalIconButton import androidx.compose.material3.FilterChip import androidx.compose.material3.FilterChipDefaults import androidx.compose.material3.FloatingActionButton @@ -90,7 +90,6 @@ import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.drawscope.clipPath import androidx.compose.ui.graphics.drawscope.clipRect import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.graphics.lerp import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.boundsInParent import androidx.compose.ui.layout.layout @@ -100,7 +99,6 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics @@ -593,9 +591,6 @@ fun LibraryScreen( .onSizeChanged { tabBarHeightPx = it.height.toFloat() } .padding(bottom = bottomInset), ) { - if (offline) { - OfflineStrip(onGoOnline = onExitOffline, headerColor = headerColor) - } LibraryTabs( pagerState = pagerState, onTabSelected = { tab -> @@ -623,6 +618,20 @@ fun LibraryScreen( } if (!searchActive) { + if (offline) { + FilledTonalIconButton( + onClick = onExitOffline, + modifier = + Modifier + .align(Alignment.BottomStart) + .padding(start = 16.dp, bottom = tabBarHeight + 16.dp), + ) { + Icon( + imageVector = Icons.Rounded.CloudOff, + contentDescription = stringResource(R.string.offline_mode_status), + ) + } + } val searchFab = selectedTab == LibraryTab.Home FloatingActionButton( onClick = { if (searchFab) onOpenSearch() else showOptions = true }, @@ -837,7 +846,6 @@ private val TAB_ICON_SIZE = 24.dp private val TAB_VERTICAL_PADDING = 6.dp private val TOUCH_TARGET = 48.dp private const val LIST_OPTIONS_WEIGHT = 0.11f -private const val OFFLINE_STRIP_TINT = 0.10f private val LIST_OPTIONS_BAR_WIDTHS = listOf(1f, 0.62f, 0.34f) @Composable @@ -1015,44 +1023,6 @@ internal fun LibraryTabs( } } -@Composable -private fun OfflineStrip( - onGoOnline: () -> Unit, - headerColor: Color, - modifier: Modifier = Modifier, -) { - Row( - modifier = - modifier - .fillMaxWidth() - .background(lerp(headerColor, MaterialTheme.colorScheme.primary, OFFLINE_STRIP_TINT)) - .heightIn(min = 48.dp) - .clickable(role = Role.Button, onClick = onGoOnline) - .padding(horizontal = 16.dp, vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp), - ) { - Icon( - imageVector = Icons.Rounded.CloudOff, - contentDescription = null, - tint = MaterialTheme.colorScheme.primary, - modifier = Modifier.size(18.dp), - ) - Text( - text = stringResource(R.string.offline_mode_banner), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onBackground, - maxLines = 2, - modifier = Modifier.weight(1f), - ) - Text( - text = stringResource(R.string.offline_go_online), - style = MaterialTheme.typography.labelLarge, - color = MaterialTheme.colorScheme.primary, - ) - } -} - @OptIn(ExperimentalMaterial3Api::class) @Composable private fun TabButton( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ed5b5a0d..af58820e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -175,8 +175,7 @@ Filters Downloaded Close search - Offline mode - Go online + Offline mode; tap to go online Shuffle artist Playing diff --git a/app/src/test/kotlin/com/subtracks/ui/LibraryScreenTest.kt b/app/src/test/kotlin/com/subtracks/ui/LibraryScreenTest.kt index 646d748f..a31bef1a 100644 --- a/app/src/test/kotlin/com/subtracks/ui/LibraryScreenTest.kt +++ b/app/src/test/kotlin/com/subtracks/ui/LibraryScreenTest.kt @@ -510,7 +510,7 @@ class LibraryScreenTest { @Test @Config(qualifiers = "de-rDE-w320dp-h640dp-xhdpi") - fun theOfflineStripExplainsTheModeAndGoesOnline() { + fun theOfflineIndicatorGoesOnline() { var exited = false composeRule.setContent { SubtracksTheme { @@ -533,11 +533,10 @@ class LibraryScreenTest { } composeRule.waitForIdle() - composeRule.onNodeWithText(composeRule.activity.getString(R.string.offline_mode_banner)).performClick() - assertTrue("the offline strip should leave offline mode", exited) composeRule - .onNodeWithContentDescription(composeRule.activity.getString(R.string.navigation_tabs_settings)) - .assertWidthIsEqualTo(40.dp) + .onNodeWithContentDescription(composeRule.activity.getString(R.string.offline_mode_status)) + .performClick() + assertTrue("the offline indicator should leave offline mode", exited) } private fun tabBounds(tab: LibraryTab) =