diff --git a/AGENTS.md b/AGENTS.md index 2b66da6..a933726 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -512,6 +512,9 @@ box switched off — and is not a thin wrapper around something the box does. Known limit: `ß` is not decomposed, so "Strasse" does not find "Straße". - Contains-matching cannot use an index, so the DAO queries carry a `LIMIT` instead of an index. Wildcards in the query are escaped — `100%` is a search, not a match-everything. +- **Tapping the library tab while already in the library focuses the search field** and raises + the keyboard — the "double tap Library to search" gesture. It travels as a `SharedFlow` event + from `CoilApp` rather than as state, so returning to the tab later never replays it. - **What is searchable is what has been fetched**: all albums once the album tab has loaded, plus folders and tracks from levels that were opened. The empty state says so rather than implying the library is empty. @@ -537,7 +540,7 @@ Three levels, and the distinction matters: - **`app/`** — the whole `CoilApp` scaffold with a screen inside it: top bar with the box indicator, bottom navigation, mini player, offline banner. This is what the app looks like. - Captured on **three device profiles** (`_phone`, `_small`, `_tablet`), so the goldens say + Captured on **four device profiles** (`_phone`, `_small`, `_landscape`, `_tablet`), so the goldens say something about layout and not only about colour. - **`player/`, `library/`** — one screen on its own, for states that would be tedious to reach through the whole app: web radio with no duration, a running sleep timer, search with and @@ -626,7 +629,7 @@ Three levels, and the distinction matters: - **Roborazzi is pinned at 1.60.0** because 1.61.0 and later carry Kotlin 2.3 metadata that Kotlin 2.0.21 cannot read. Upgrading it means upgrading Kotlin and the Compose compiler too. -Covered so far: the whole app on three devices (player light and dark, library, favourites in both +Covered so far: the whole app on four devices (player light and dark, library, favourites in both layouts and sorted A–Z, settings top and lower half, box management, one box's page, offline, onboarding), the player screen (playing, paused, idle, web radio, sleep timer, a cover still resolving, light and @@ -925,6 +928,15 @@ still needs doing, in rough order of importance: screen whose entire job is the transport controls. Library, favourites and settings are still single-column and merely stretch, which reads as roomy rather than broken — see `app/library_tablet.png`. + + **A short window is handled separately, by the scaffold.** Below 480 dp of height (Material's + compact height class — a phone on its side, a split-screen half; `isCompactHeight()` in + `CoilApp.kt`) the bottom navigation bar becomes a `NavigationRail` at the side, the top bar + drops to 48 dp, and the mini player sits under the screen rather than under the rail. + Stacked, the three bars took two thirds of a landscape phone and the library showed no rows + at all. Two screens adapt on their own through the same check: the library puts its search + field and Folders/Albums tabs on one line, and the favourites grid uses smaller tiles so a + whole row fits. See `app/*_landscape.png`. 6. **Android 16's local network restriction will eventually break Coil outright.** Access to local-network addresses — which is every socket this app opens, plus `NsdManager` discovery and the HTTP cover fetches — will require the `NEARBY_WIFI_DEVICES` permission, granted by the user diff --git a/CHANGELOG.md b/CHANGELOG.md index 4084fcb..ab31c65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,22 @@ automatically from the `## [x.y.z]` heading matching `versionName` in `app/build ## [Unreleased] +## [1.2.5] - 2026-09-25 + +### Added +- **Tap Library twice to search.** Tapping the library tab while already in the library jumps + to the search field and raises the keyboard, so searching is a double tap from anywhere in + the app. + +### Changed +- **Landscape is usable.** With the phone on its side, the navigation moves to a rail at the + side and the top bar slims down, so the library, favourites and settings get most of the + screen instead of a sliver between three stacked bars. The library puts its search field and + tabs on one line there, and favourites use smaller tiles so a whole row fits. +- **Long titles scroll instead of being cut off.** The player and the mini player show the title + and artist on one line that scrolls when it does not fit, fading out at the edge, so the end of + a long chapter or movement name is no longer lost behind an ellipsis. + ## [1.2.4] - 2026-09-09 ### Added diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 183bead..a2de654 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -20,8 +20,8 @@ android { applicationId = "app.coilforphoniebox" minSdk = 26 targetSdk = 36 - versionCode = 12 - versionName = "1.2.4" + versionCode = 13 + versionName = "1.2.5" } androidResources { diff --git a/app/src/main/kotlin/app/coilforphoniebox/ui/CoilApp.kt b/app/src/main/kotlin/app/coilforphoniebox/ui/CoilApp.kt index 11bf7d8..94b7020 100644 --- a/app/src/main/kotlin/app/coilforphoniebox/ui/CoilApp.kt +++ b/app/src/main/kotlin/app/coilforphoniebox/ui/CoilApp.kt @@ -3,7 +3,12 @@ package app.coilforphoniebox.ui import androidx.annotation.StringRes import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack @@ -25,10 +30,13 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.NavigationRail +import androidx.compose.material3.NavigationRailItem import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -37,6 +45,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.selected @@ -71,6 +80,7 @@ import app.coilforphoniebox.ui.player.PlayerViewModel import app.coilforphoniebox.ui.settings.SettingsScreen import app.coilforphoniebox.ui.settings.SettingsViewModel import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow private enum class Destination( val route: String, @@ -108,6 +118,9 @@ fun CoilApp(appViewModel: AppViewModel) { val navController = rememberNavController() val snackbarHostState = remember { SnackbarHostState() } var switcherOpen by remember { mutableStateOf(false) } + // A tap on the library tab while already in the library is a request to search. An event + // rather than state, so coming back to the tab later does not replay it. + val librarySearchRequests = remember { MutableSharedFlow(extraBufferCapacity = 1) } // Nothing in the app is usable without a box, so a first launch is the add-box screen // and nothing else — no feature tour, no carousel (§11.2). @@ -125,6 +138,27 @@ fun CoilApp(appViewModel: AppViewModel) { val backStackEntry by navController.currentBackStackEntryAsState() val currentRoute = backStackEntry?.destination?.route val selectedDestination = owningDestination(currentRoute) + val compactHeight = isCompactHeight() + + val onNavigate: (Destination) -> Unit = { destination -> + if (destination == selectedDestination && destination == Destination.LIBRARY) { + librarySearchRequests.tryEmit(Unit) + } + navController.navigateSingleTop(destination.route) + } + + val miniPlayer: @Composable () -> Unit = { + if (state.showMiniPlayer && currentRoute != Destination.PLAYER.route) { + MiniPlayer( + status = state.status, + coverUrl = state.coverUrl, + coverName = state.coverName, + coverPending = state.coverPending, + onClick = { navController.navigateSingleTop(Destination.PLAYER.route) }, + onToggle = appViewModel::togglePlayback, + ) + } + } Scaffold( snackbarHost = { SnackbarHost(snackbarHostState) }, @@ -179,100 +213,119 @@ fun CoilApp(appViewModel: AppViewModel) { } } }, + // A phone on its side has height to spare nowhere, and the bar is mostly empty + // space around one pill — so it gives back a quarter of itself there. + expandedHeight = if (compactHeight) { + COMPACT_TOP_BAR_HEIGHT + } else { + TopAppBarDefaults.TopAppBarExpandedHeight + }, ) }, bottomBar = { - Column { - if (state.showMiniPlayer && currentRoute != Destination.PLAYER.route) { - MiniPlayer( - status = state.status, - coverUrl = state.coverUrl, - coverName = state.coverName, - coverPending = state.coverPending, - onClick = { navController.navigateSingleTop(Destination.PLAYER.route) }, - onToggle = appViewModel::togglePlayback, - ) + if (!compactHeight) { + Column { + miniPlayer() + BottomBar(selected = selectedDestination, onNavigate = onNavigate) } - BottomBar(navController = navController, selected = selectedDestination) } }, ) { padding -> - Column( + // In a short window the navigation moves to a rail at the side. Stacked, the top bar, + // the mini player and a bottom bar took two thirds of a landscape phone's height and + // left the library without a single visible row; width is what such a window has, so + // that is where the navigation goes. The mini player stays at the bottom, under the + // screen rather than under the rail, so it lines up with what it belongs to. + Row( Modifier .padding(padding) .fillMaxSize(), ) { - if (state.isOffline) { - OfflineBanner( - text = stringResource(R.string.offline_banner), - modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp), - ) + if (compactHeight) { + SideRail(selected = selectedDestination, onNavigate = onNavigate) } - NavHost( - navController = navController, - startDestination = Destination.PLAYER.route, - modifier = Modifier.fillMaxSize(), + Column( + Modifier + .weight(1f) + .fillMaxHeight(), ) { - composable(Destination.PLAYER.route) { - val viewModel = hiltViewModel() - SnackbarMessages(viewModel.messages, snackbarHostState) - PlayerScreen(viewModel) + if (state.isOffline) { + OfflineBanner( + text = stringResource(R.string.offline_banner), + modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp), + ) } - composable(Destination.LIBRARY.route) { - val viewModel = hiltViewModel() - SnackbarMessages(viewModel.messages, snackbarHostState) - LibraryScreen(viewModel) - } + NavHost( + navController = navController, + startDestination = Destination.PLAYER.route, + modifier = Modifier + .weight(1f) + .fillMaxWidth(), + ) { + composable(Destination.PLAYER.route) { + val viewModel = hiltViewModel() + SnackbarMessages(viewModel.messages, snackbarHostState) + PlayerScreen(viewModel) + } - composable(Destination.FAVOURITES.route) { - val viewModel = hiltViewModel() - SnackbarMessages(viewModel.messages, snackbarHostState) - FavoritesScreen( - viewModel = viewModel, - layout = state.settings.favoritesLayout, - sort = state.settings.favoritesSort, - ) - } + composable(Destination.LIBRARY.route) { + val viewModel = hiltViewModel() + SnackbarMessages(viewModel.messages, snackbarHostState) + LibraryScreen(viewModel, searchRequests = librarySearchRequests) + } - composable(Destination.SETTINGS.route) { - val viewModel = hiltViewModel() - SnackbarMessages(viewModel.messages, snackbarHostState) - SettingsScreen( - viewModel = viewModel, - onAddBox = { navController.navigate(ROUTE_ADD_BOX) }, - onManageBoxes = { navController.navigate(ROUTE_BOXES) }, - ) - } + composable(Destination.FAVOURITES.route) { + val viewModel = hiltViewModel() + SnackbarMessages(viewModel.messages, snackbarHostState) + FavoritesScreen( + viewModel = viewModel, + layout = state.settings.favoritesLayout, + sort = state.settings.favoritesSort, + ) + } - composable(ROUTE_BOXES) { - val viewModel = hiltViewModel() - SnackbarMessages(viewModel.messages, snackbarHostState) - BoxesScreen( - viewModel = viewModel, - onOpenBox = { boxId -> navController.navigate(boxDetailRoute(boxId)) }, - onAddBox = { navController.navigate(ROUTE_ADD_BOX) }, - ) - } + composable(Destination.SETTINGS.route) { + val viewModel = hiltViewModel() + SnackbarMessages(viewModel.messages, snackbarHostState) + SettingsScreen( + viewModel = viewModel, + onAddBox = { navController.navigate(ROUTE_ADD_BOX) }, + onManageBoxes = { navController.navigate(ROUTE_BOXES) }, + ) + } - composable(ROUTE_BOX_DETAIL) { entry -> - val viewModel = hiltViewModel() - SnackbarMessages(viewModel.messages, snackbarHostState) - BoxDetailScreen( - viewModel = viewModel, - boxId = entry.arguments?.getString(ARG_BOX_ID).orEmpty(), - onRemoved = { navController.popBackStack() }, - ) - } + composable(ROUTE_BOXES) { + val viewModel = hiltViewModel() + SnackbarMessages(viewModel.messages, snackbarHostState) + BoxesScreen( + viewModel = viewModel, + onOpenBox = { boxId -> navController.navigate(boxDetailRoute(boxId)) }, + onAddBox = { navController.navigate(ROUTE_ADD_BOX) }, + ) + } - composable(ROUTE_ADD_BOX) { - AddBoxScreen( - viewModel = hiltViewModel(), - onSaved = { navController.popBackStack() }, - onCancel = { navController.popBackStack() }, - ) + composable(ROUTE_BOX_DETAIL) { entry -> + val viewModel = hiltViewModel() + SnackbarMessages(viewModel.messages, snackbarHostState) + BoxDetailScreen( + viewModel = viewModel, + boxId = entry.arguments?.getString(ARG_BOX_ID).orEmpty(), + onRemoved = { navController.popBackStack() }, + ) + } + + composable(ROUTE_ADD_BOX) { + AddBoxScreen( + viewModel = hiltViewModel(), + onSaved = { navController.popBackStack() }, + onCancel = { navController.popBackStack() }, + ) + } } + + if (compactHeight) miniPlayer() } } } @@ -389,19 +442,56 @@ private fun FavoritesSortAction(current: FavoritesSort, onSelect: (FavoritesSort } @Composable -private fun BottomBar(navController: NavHostController, selected: Destination?) { +private fun BottomBar(selected: Destination?, onNavigate: (Destination) -> Unit) { NavigationBar { Destination.entries.forEach { destination -> NavigationBarItem( selected = selected == destination, - onClick = { navController.navigateSingleTop(destination.route) }, + onClick = { onNavigate(destination) }, + icon = { Icon(imageVector = destination.icon, contentDescription = null) }, + label = { Text(stringResource(destination.label)) }, + ) + } + } +} + +/** + * The same four destinations as [BottomBar], down the side of a short window. + * + * Takes no insets of its own: it sits inside the scaffold's content, whose padding already + * keeps it clear of the system bars and a side-mounted cutout. + */ +@Composable +private fun SideRail(selected: Destination?, onNavigate: (Destination) -> Unit) { + NavigationRail( + windowInsets = WindowInsets(0, 0, 0, 0), + modifier = Modifier.fillMaxHeight(), + ) { + Spacer(Modifier.weight(1f)) + Destination.entries.forEach { destination -> + NavigationRailItem( + selected = selected == destination, + onClick = { onNavigate(destination) }, icon = { Icon(imageVector = destination.icon, contentDescription = null) }, label = { Text(stringResource(destination.label)) }, ) } + Spacer(Modifier.weight(1f)) } } +/** + * Whether the window is short enough that the navigation belongs at the side: a phone in + * landscape, or a split-screen half. The window, not the device — `screenHeightDp` follows + * multi-window — and the same 480 dp line Material draws between compact and medium height. + */ +@Composable +internal fun isCompactHeight(): Boolean = + LocalConfiguration.current.screenHeightDp < COMPACT_HEIGHT_DP + +private const val COMPACT_HEIGHT_DP = 480 +private val COMPACT_TOP_BAR_HEIGHT = 48.dp + /** Shows one-off view model messages, which arrive as string resources rather than text. */ @Composable private fun SnackbarMessages(messages: Flow, host: SnackbarHostState) { diff --git a/app/src/main/kotlin/app/coilforphoniebox/ui/components/MiniPlayer.kt b/app/src/main/kotlin/app/coilforphoniebox/ui/components/MiniPlayer.kt index 163f60c..f02aa00 100644 --- a/app/src/main/kotlin/app/coilforphoniebox/ui/components/MiniPlayer.kt +++ b/app/src/main/kotlin/app/coilforphoniebox/ui/components/MiniPlayer.kt @@ -20,7 +20,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import app.coilforphoniebox.R import app.coilforphoniebox.domain.model.PlayerStatus @@ -73,7 +72,9 @@ fun MiniPlayer( text = status.displayTitle ?: stringResource(R.string.player_no_title), style = MaterialTheme.typography.bodyMedium, maxLines = 1, - overflow = TextOverflow.Ellipsis, + // Scrolls rather than truncates, like the player's own title: this + // row is the only place the title is visible from the other tabs. + modifier = Modifier.scrollingText(), ) val subtitle = status.artist ?: status.album if (subtitle != null) { @@ -82,7 +83,7 @@ fun MiniPlayer( style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1, - overflow = TextOverflow.Ellipsis, + modifier = Modifier.scrollingText(), ) } } diff --git a/app/src/main/kotlin/app/coilforphoniebox/ui/components/ScrollingText.kt b/app/src/main/kotlin/app/coilforphoniebox/ui/components/ScrollingText.kt new file mode 100644 index 0000000..a4a39ef --- /dev/null +++ b/app/src/main/kotlin/app/coilforphoniebox/ui/components/ScrollingText.kt @@ -0,0 +1,65 @@ +package app.coilforphoniebox.ui.components + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.basicMarquee +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.CompositingStrategy +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.layout.layout +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp + +/** + * A single line of text that scrolls when it does not fit, for titles whose end matters as + * much as their start — a chapter number, a movement, a part two. + * + * The end edge fades out while the text overflows, because a plain marquee clips mid-letter + * and a clipped letter at rest reads as a layout bug rather than as "there is more". Text that + * fits is left alone: fading it would soften its last glyph for nothing. + */ +@OptIn(ExperimentalFoundationApi::class) +fun Modifier.scrollingText(): Modifier = composed { + // basicMarquee measures its content without a width limit, so the layout step below sees + // the text's full width, and the draw step compares it with the width actually available. + var contentWidth by remember { mutableIntStateOf(0) } + + this + .graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen } + .drawWithContent { + drawContent() + if (contentWidth <= size.width) return@drawWithContent + + val fade = FADE_WIDTH.toPx().coerceAtMost(size.width / 2) + val ltr = layoutDirection == LayoutDirection.Ltr + val edge = if (ltr) size.width else 0f + val inner = if (ltr) size.width - fade else fade + drawRect( + brush = Brush.horizontalGradient( + colors = listOf(Color.Black, Color.Transparent), + startX = inner, + endX = edge, + ), + topLeft = Offset(if (ltr) inner else 0f, 0f), + size = size.copy(width = fade), + blendMode = BlendMode.DstIn, + ) + } + .basicMarquee(iterations = Int.MAX_VALUE) + .layout { measurable, constraints -> + val placeable = measurable.measure(constraints) + contentWidth = placeable.width + layout(placeable.width, placeable.height) { placeable.place(0, 0) } + } +} + +private val FADE_WIDTH = 24.dp diff --git a/app/src/main/kotlin/app/coilforphoniebox/ui/favorites/FavoritesScreen.kt b/app/src/main/kotlin/app/coilforphoniebox/ui/favorites/FavoritesScreen.kt index 8fa04e2..8e832f0 100644 --- a/app/src/main/kotlin/app/coilforphoniebox/ui/favorites/FavoritesScreen.kt +++ b/app/src/main/kotlin/app/coilforphoniebox/ui/favorites/FavoritesScreen.kt @@ -53,6 +53,7 @@ import app.coilforphoniebox.domain.model.ordered import app.coilforphoniebox.ui.components.CoverArt import app.coilforphoniebox.ui.components.EmptyState import app.coilforphoniebox.ui.components.shareLink +import app.coilforphoniebox.ui.isCompactHeight /** * The favourites tab, in one of two layouts (§7.2) and one of three orders. @@ -91,8 +92,13 @@ fun FavoritesScreen( // broken — the tile would not budge. val movable = sort == FavoritesSort.MANUAL + // Tiles are square, so their height follows the width they are given. In a short window + // the usual size filled the whole screen with one row of tiles cut off at the label; + // smaller tiles keep a whole row, name and all, in view. + val tileSize = if (isCompactHeight()) COMPACT_TILE_MIN_SIZE else TILE_MIN_SIZE + LazyVerticalGrid( - columns = if (compact) GridCells.Fixed(1) else GridCells.Adaptive(minSize = 148.dp), + columns = if (compact) GridCells.Fixed(1) else GridCells.Adaptive(minSize = tileSize), contentPadding = if (compact) PaddingValues(vertical = 8.dp) else PaddingValues(12.dp), horizontalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(if (compact) 2.dp else 12.dp), @@ -342,3 +348,6 @@ private val Favorite.placeholderIcon: ImageVector FavoriteType.ALBUM -> Icons.Rounded.Album FavoriteType.TRACK -> Icons.Rounded.MusicNote } + +private val TILE_MIN_SIZE = 148.dp +private val COMPACT_TILE_MIN_SIZE = 112.dp diff --git a/app/src/main/kotlin/app/coilforphoniebox/ui/library/LibraryScreen.kt b/app/src/main/kotlin/app/coilforphoniebox/ui/library/LibraryScreen.kt index 5001978..7732fef 100644 --- a/app/src/main/kotlin/app/coilforphoniebox/ui/library/LibraryScreen.kt +++ b/app/src/main/kotlin/app/coilforphoniebox/ui/library/LibraryScreen.kt @@ -18,6 +18,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.grid.GridCells @@ -60,7 +61,10 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextOverflow @@ -76,6 +80,7 @@ import app.coilforphoniebox.domain.model.LibrarySource import app.coilforphoniebox.domain.model.LibraryTrack import app.coilforphoniebox.domain.model.PlayTarget import app.coilforphoniebox.domain.model.key +import app.coilforphoniebox.ui.isCompactHeight import app.coilforphoniebox.ui.components.ActionMenuItem import app.coilforphoniebox.ui.components.CoverArt import app.coilforphoniebox.ui.components.DetailRow @@ -85,19 +90,37 @@ import app.coilforphoniebox.ui.components.FavoriteMenuItem import app.coilforphoniebox.ui.components.formatDuration import app.coilforphoniebox.ui.components.formatNumber import app.coilforphoniebox.ui.components.rememberFreshnessLabel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow private const val TAB_FOLDERS = 0 private const val TAB_ALBUMS = 1 +/** Room for both tab labels in the longest launch locale, beside the search field. */ +private val COMPACT_TABS_WIDTH = 280.dp + @OptIn(ExperimentalMaterial3Api::class) @Composable fun LibraryScreen( viewModel: LibraryViewModel, modifier: Modifier = Modifier, + /** Fires when the library tab is tapped again from inside the library. */ + searchRequests: Flow = emptyFlow(), ) { var selectedTab by rememberSaveable { mutableIntStateOf(TAB_FOLDERS) } val query by viewModel.searchQuery.collectAsStateWithLifecycle() val searching = query.isNotEmpty() + val searchFocus = remember { FocusRequester() } + val keyboard = LocalSoftwareKeyboardController.current + + // Tapping the tab you are already on has nothing left to navigate to, so it goes to the + // one thing on this screen worth jumping to: the search field, keyboard up. + LaunchedEffect(searchRequests) { + searchRequests.collect { + searchFocus.requestFocus() + keyboard?.show() + } + } // Back leaves search before it does anything else, so a query is never something the user // has to undo by hand. The two handlers are mutually exclusive, so their order is moot. @@ -106,34 +129,57 @@ fun LibraryScreen( // undoes it rather than dropping out of the library entirely. BackHandler(enabled = !searching && selectedTab != TAB_FOLDERS) { selectedTab = TAB_FOLDERS } - Column(modifier.fillMaxSize()) { + // In a short window the search field and the tab row share one line: stacked, the two of + // them and the breadcrumb were most of what a landscape phone could show, and there is + // width to spare for both. + val compactHeight = isCompactHeight() + + val searchField: @Composable (Modifier) -> Unit = { fieldModifier -> SearchField( query = query, onQueryChange = viewModel::onSearchQueryChange, onClear = viewModel::clearSearch, + focusRequester = searchFocus, + modifier = fieldModifier, ) + } + + // The tabs are a way of browsing, and searching is not browsing: with a query up, + // folders, albums and tracks all appear in one list and the tab row would only pose a + // question with no answer. + val tabs: @Composable (Modifier) -> Unit = { tabsModifier -> + if (!searching) { + PrimaryTabRow(selectedTabIndex = selectedTab, modifier = tabsModifier) { + Tab( + selected = selectedTab == TAB_FOLDERS, + onClick = { selectedTab = TAB_FOLDERS }, + text = { Text(stringResource(R.string.library_tab_folders)) }, + ) + Tab( + selected = selectedTab == TAB_ALBUMS, + onClick = { selectedTab = TAB_ALBUMS }, + text = { Text(stringResource(R.string.library_tab_albums)) }, + ) + } + } + } + + Column(modifier.fillMaxSize()) { + if (compactHeight) { + Row(verticalAlignment = Alignment.CenterVertically) { + searchField(Modifier.weight(1f)) + tabs(Modifier.width(COMPACT_TABS_WIDTH)) + } + } else { + searchField(Modifier.fillMaxWidth()) + tabs(Modifier) + } - // The tabs are a way of browsing, and searching is not browsing: with a query up, - // folders, albums and tracks all appear in one list and the tab row would only pose a - // question with no answer. if (searching) { SearchResults(viewModel) return@Column } - PrimaryTabRow(selectedTabIndex = selectedTab) { - Tab( - selected = selectedTab == TAB_FOLDERS, - onClick = { selectedTab = TAB_FOLDERS }, - text = { Text(stringResource(R.string.library_tab_folders)) }, - ) - Tab( - selected = selectedTab == TAB_ALBUMS, - onClick = { selectedTab = TAB_ALBUMS }, - text = { Text(stringResource(R.string.library_tab_albums)) }, - ) - } - when (selectedTab) { TAB_FOLDERS -> FolderTab(viewModel) else -> AlbumTab(viewModel) @@ -146,6 +192,8 @@ private fun SearchField( query: String, onQueryChange: (String) -> Unit, onClear: () -> Unit, + focusRequester: FocusRequester, + modifier: Modifier = Modifier, ) { OutlinedTextField( value = query, @@ -164,9 +212,9 @@ private fun SearchField( }, singleLine = true, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 12.dp, vertical = 4.dp), + modifier = modifier + .padding(horizontal = 12.dp, vertical = 4.dp) + .focusRequester(focusRequester), ) } diff --git a/app/src/main/kotlin/app/coilforphoniebox/ui/player/PlayerScreen.kt b/app/src/main/kotlin/app/coilforphoniebox/ui/player/PlayerScreen.kt index 6d07bae..1ee1b27 100644 --- a/app/src/main/kotlin/app/coilforphoniebox/ui/player/PlayerScreen.kt +++ b/app/src/main/kotlin/app/coilforphoniebox/ui/player/PlayerScreen.kt @@ -66,7 +66,6 @@ import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -77,6 +76,7 @@ import app.coilforphoniebox.ui.components.CoverArt import app.coilforphoniebox.ui.components.FavoriteMenuItem import app.coilforphoniebox.ui.components.formatDuration import app.coilforphoniebox.ui.components.formatNumber +import app.coilforphoniebox.ui.components.scrollingText /** * The start screen: cover, title, progress, transport, volume. @@ -293,7 +293,13 @@ private fun WidePlayer( } } -/** Title, artist and album, with the favourite star beside them. */ +/** + * Title, artist and album, with the favourite star beside them. + * + * One line each that scrolls when it does not fit, rather than two lines and an ellipsis: + * audiobook chapters and classical movements routinely run past two lines, and the part cut + * off is usually the part that tells one track from the next. + */ @Composable private fun TitleBlock( state: PlayerViewModel.State, @@ -310,8 +316,8 @@ private fun TitleBlock( stringResource(R.string.player_nothing_playing) }, style = style, - maxLines = 2, - overflow = TextOverflow.Ellipsis, + maxLines = 1, + modifier = Modifier.scrollingText(), ) val subtitle = listOfNotNull(state.status.artist, state.status.album) .distinct() @@ -323,8 +329,8 @@ private fun TitleBlock( text = subtitle, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, - maxLines = 2, - overflow = TextOverflow.Ellipsis, + maxLines = 1, + modifier = Modifier.scrollingText(), ) } } diff --git a/app/src/testDebug/kotlin/app/coilforphoniebox/screenshot/AppScreenshotTest.kt b/app/src/testDebug/kotlin/app/coilforphoniebox/screenshot/AppScreenshotTest.kt index b36cd3a..c5e3c7f 100644 --- a/app/src/testDebug/kotlin/app/coilforphoniebox/screenshot/AppScreenshotTest.kt +++ b/app/src/testDebug/kotlin/app/coilforphoniebox/screenshot/AppScreenshotTest.kt @@ -259,3 +259,14 @@ class SmallPhoneAppScreenshotTest : AppScreenshotTest() { class TabletAppScreenshotTest : AppScreenshotTest() { override val device = "tablet" } + +/** + * A phone on its side: plenty of width and barely any height. This is where a top bar, a mini + * player and a bottom navigation bar stacked on top of each other left the screen between them + * a sliver, so the scaffold moves the navigation into a rail here — see `CoilApp`. + */ +@HiltAndroidTest +@Config(qualifiers = "w800dp-h360dp-normal-long-notround-land-xhdpi-keyshidden-nonav") +class LandscapePhoneAppScreenshotTest : AppScreenshotTest() { + override val device = "landscape" +} diff --git a/app/src/testDebug/screenshots/app/box_detail_landscape.png b/app/src/testDebug/screenshots/app/box_detail_landscape.png new file mode 100644 index 0000000..2bd92f7 Binary files /dev/null and b/app/src/testDebug/screenshots/app/box_detail_landscape.png differ diff --git a/app/src/testDebug/screenshots/app/box_detail_small.png b/app/src/testDebug/screenshots/app/box_detail_small.png index 285dce2..c30e91e 100644 Binary files a/app/src/testDebug/screenshots/app/box_detail_small.png and b/app/src/testDebug/screenshots/app/box_detail_small.png differ diff --git a/app/src/testDebug/screenshots/app/box_detail_tablet.png b/app/src/testDebug/screenshots/app/box_detail_tablet.png index f4cbdff..740327e 100644 Binary files a/app/src/testDebug/screenshots/app/box_detail_tablet.png and b/app/src/testDebug/screenshots/app/box_detail_tablet.png differ diff --git a/app/src/testDebug/screenshots/app/boxes_landscape.png b/app/src/testDebug/screenshots/app/boxes_landscape.png new file mode 100644 index 0000000..d7b35ba Binary files /dev/null and b/app/src/testDebug/screenshots/app/boxes_landscape.png differ diff --git a/app/src/testDebug/screenshots/app/boxes_small.png b/app/src/testDebug/screenshots/app/boxes_small.png index d475263..95b7f4c 100644 Binary files a/app/src/testDebug/screenshots/app/boxes_small.png and b/app/src/testDebug/screenshots/app/boxes_small.png differ diff --git a/app/src/testDebug/screenshots/app/boxes_tablet.png b/app/src/testDebug/screenshots/app/boxes_tablet.png index 5ab10fe..fdd1261 100644 Binary files a/app/src/testDebug/screenshots/app/boxes_tablet.png and b/app/src/testDebug/screenshots/app/boxes_tablet.png differ diff --git a/app/src/testDebug/screenshots/app/favourites_compact_landscape.png b/app/src/testDebug/screenshots/app/favourites_compact_landscape.png new file mode 100644 index 0000000..0070761 Binary files /dev/null and b/app/src/testDebug/screenshots/app/favourites_compact_landscape.png differ diff --git a/app/src/testDebug/screenshots/app/favourites_compact_small.png b/app/src/testDebug/screenshots/app/favourites_compact_small.png index 7dad7cf..8e1a00f 100644 Binary files a/app/src/testDebug/screenshots/app/favourites_compact_small.png and b/app/src/testDebug/screenshots/app/favourites_compact_small.png differ diff --git a/app/src/testDebug/screenshots/app/favourites_compact_tablet.png b/app/src/testDebug/screenshots/app/favourites_compact_tablet.png index 02d5f6a..0e2cb30 100644 Binary files a/app/src/testDebug/screenshots/app/favourites_compact_tablet.png and b/app/src/testDebug/screenshots/app/favourites_compact_tablet.png differ diff --git a/app/src/testDebug/screenshots/app/favourites_landscape.png b/app/src/testDebug/screenshots/app/favourites_landscape.png new file mode 100644 index 0000000..9ce9e61 Binary files /dev/null and b/app/src/testDebug/screenshots/app/favourites_landscape.png differ diff --git a/app/src/testDebug/screenshots/app/favourites_small.png b/app/src/testDebug/screenshots/app/favourites_small.png index 6c5a03b..2cc8f5c 100644 Binary files a/app/src/testDebug/screenshots/app/favourites_small.png and b/app/src/testDebug/screenshots/app/favourites_small.png differ diff --git a/app/src/testDebug/screenshots/app/favourites_sorted_landscape.png b/app/src/testDebug/screenshots/app/favourites_sorted_landscape.png new file mode 100644 index 0000000..8c4bdce Binary files /dev/null and b/app/src/testDebug/screenshots/app/favourites_sorted_landscape.png differ diff --git a/app/src/testDebug/screenshots/app/favourites_sorted_small.png b/app/src/testDebug/screenshots/app/favourites_sorted_small.png index 12e584e..478180d 100644 Binary files a/app/src/testDebug/screenshots/app/favourites_sorted_small.png and b/app/src/testDebug/screenshots/app/favourites_sorted_small.png differ diff --git a/app/src/testDebug/screenshots/app/favourites_sorted_tablet.png b/app/src/testDebug/screenshots/app/favourites_sorted_tablet.png index c3749f8..1789b7a 100644 Binary files a/app/src/testDebug/screenshots/app/favourites_sorted_tablet.png and b/app/src/testDebug/screenshots/app/favourites_sorted_tablet.png differ diff --git a/app/src/testDebug/screenshots/app/favourites_tablet.png b/app/src/testDebug/screenshots/app/favourites_tablet.png index 9562d7b..3955329 100644 Binary files a/app/src/testDebug/screenshots/app/favourites_tablet.png and b/app/src/testDebug/screenshots/app/favourites_tablet.png differ diff --git a/app/src/testDebug/screenshots/app/library_landscape.png b/app/src/testDebug/screenshots/app/library_landscape.png new file mode 100644 index 0000000..22df7e5 Binary files /dev/null and b/app/src/testDebug/screenshots/app/library_landscape.png differ diff --git a/app/src/testDebug/screenshots/app/library_small.png b/app/src/testDebug/screenshots/app/library_small.png index 7016da4..f59a48e 100644 Binary files a/app/src/testDebug/screenshots/app/library_small.png and b/app/src/testDebug/screenshots/app/library_small.png differ diff --git a/app/src/testDebug/screenshots/app/library_tablet.png b/app/src/testDebug/screenshots/app/library_tablet.png index 646d98d..8276638 100644 Binary files a/app/src/testDebug/screenshots/app/library_tablet.png and b/app/src/testDebug/screenshots/app/library_tablet.png differ diff --git a/app/src/testDebug/screenshots/app/offline_landscape.png b/app/src/testDebug/screenshots/app/offline_landscape.png new file mode 100644 index 0000000..6ed5206 Binary files /dev/null and b/app/src/testDebug/screenshots/app/offline_landscape.png differ diff --git a/app/src/testDebug/screenshots/app/offline_small.png b/app/src/testDebug/screenshots/app/offline_small.png index 723061b..6b2f170 100644 Binary files a/app/src/testDebug/screenshots/app/offline_small.png and b/app/src/testDebug/screenshots/app/offline_small.png differ diff --git a/app/src/testDebug/screenshots/app/offline_tablet.png b/app/src/testDebug/screenshots/app/offline_tablet.png index b71818c..8e70e62 100644 Binary files a/app/src/testDebug/screenshots/app/offline_tablet.png and b/app/src/testDebug/screenshots/app/offline_tablet.png differ diff --git a/app/src/testDebug/screenshots/app/onboarding_landscape.png b/app/src/testDebug/screenshots/app/onboarding_landscape.png new file mode 100644 index 0000000..f7aa501 Binary files /dev/null and b/app/src/testDebug/screenshots/app/onboarding_landscape.png differ diff --git a/app/src/testDebug/screenshots/app/player_dark_landscape.png b/app/src/testDebug/screenshots/app/player_dark_landscape.png new file mode 100644 index 0000000..740c30d Binary files /dev/null and b/app/src/testDebug/screenshots/app/player_dark_landscape.png differ diff --git a/app/src/testDebug/screenshots/app/player_dark_phone.png b/app/src/testDebug/screenshots/app/player_dark_phone.png index ba07b7a..f066189 100644 Binary files a/app/src/testDebug/screenshots/app/player_dark_phone.png and b/app/src/testDebug/screenshots/app/player_dark_phone.png differ diff --git a/app/src/testDebug/screenshots/app/player_dark_small.png b/app/src/testDebug/screenshots/app/player_dark_small.png index c83c4ab..f256af1 100644 Binary files a/app/src/testDebug/screenshots/app/player_dark_small.png and b/app/src/testDebug/screenshots/app/player_dark_small.png differ diff --git a/app/src/testDebug/screenshots/app/player_landscape.png b/app/src/testDebug/screenshots/app/player_landscape.png new file mode 100644 index 0000000..161517a Binary files /dev/null and b/app/src/testDebug/screenshots/app/player_landscape.png differ diff --git a/app/src/testDebug/screenshots/app/player_phone.png b/app/src/testDebug/screenshots/app/player_phone.png index 34187c4..b097d88 100644 Binary files a/app/src/testDebug/screenshots/app/player_phone.png and b/app/src/testDebug/screenshots/app/player_phone.png differ diff --git a/app/src/testDebug/screenshots/app/player_small.png b/app/src/testDebug/screenshots/app/player_small.png index 624af8a..f55c7c2 100644 Binary files a/app/src/testDebug/screenshots/app/player_small.png and b/app/src/testDebug/screenshots/app/player_small.png differ diff --git a/app/src/testDebug/screenshots/app/settings_landscape.png b/app/src/testDebug/screenshots/app/settings_landscape.png new file mode 100644 index 0000000..2f850a5 Binary files /dev/null and b/app/src/testDebug/screenshots/app/settings_landscape.png differ diff --git a/app/src/testDebug/screenshots/app/settings_lower_landscape.png b/app/src/testDebug/screenshots/app/settings_lower_landscape.png new file mode 100644 index 0000000..204e1b0 Binary files /dev/null and b/app/src/testDebug/screenshots/app/settings_lower_landscape.png differ diff --git a/app/src/testDebug/screenshots/app/settings_lower_small.png b/app/src/testDebug/screenshots/app/settings_lower_small.png index 9dca505..de95ad2 100644 Binary files a/app/src/testDebug/screenshots/app/settings_lower_small.png and b/app/src/testDebug/screenshots/app/settings_lower_small.png differ diff --git a/app/src/testDebug/screenshots/app/settings_lower_tablet.png b/app/src/testDebug/screenshots/app/settings_lower_tablet.png index c5d4870..5fadfe8 100644 Binary files a/app/src/testDebug/screenshots/app/settings_lower_tablet.png and b/app/src/testDebug/screenshots/app/settings_lower_tablet.png differ diff --git a/app/src/testDebug/screenshots/app/settings_small.png b/app/src/testDebug/screenshots/app/settings_small.png index 1a98dbd..7dcfca1 100644 Binary files a/app/src/testDebug/screenshots/app/settings_small.png and b/app/src/testDebug/screenshots/app/settings_small.png differ diff --git a/app/src/testDebug/screenshots/app/settings_tablet.png b/app/src/testDebug/screenshots/app/settings_tablet.png index f5f3aaa..814b434 100644 Binary files a/app/src/testDebug/screenshots/app/settings_tablet.png and b/app/src/testDebug/screenshots/app/settings_tablet.png differ diff --git a/app/src/testDebug/screenshots/chrome/mini_player_paused_dark.png b/app/src/testDebug/screenshots/chrome/mini_player_paused_dark.png index 9d81d2f..5b3f7e9 100644 Binary files a/app/src/testDebug/screenshots/chrome/mini_player_paused_dark.png and b/app/src/testDebug/screenshots/chrome/mini_player_paused_dark.png differ diff --git a/app/src/testDebug/screenshots/chrome/mini_player_playing.png b/app/src/testDebug/screenshots/chrome/mini_player_playing.png index fd608b3..6c9bdfd 100644 Binary files a/app/src/testDebug/screenshots/chrome/mini_player_playing.png and b/app/src/testDebug/screenshots/chrome/mini_player_playing.png differ diff --git a/app/src/testDebug/screenshots/player/cover_resolving_light.png b/app/src/testDebug/screenshots/player/cover_resolving_light.png index e8dac91..c0e7966 100644 Binary files a/app/src/testDebug/screenshots/player/cover_resolving_light.png and b/app/src/testDebug/screenshots/player/cover_resolving_light.png differ diff --git a/app/src/testDebug/screenshots/player/paused_light.png b/app/src/testDebug/screenshots/player/paused_light.png index 446867e..87dff1a 100644 Binary files a/app/src/testDebug/screenshots/player/paused_light.png and b/app/src/testDebug/screenshots/player/paused_light.png differ diff --git a/app/src/testDebug/screenshots/player/playing_dark.png b/app/src/testDebug/screenshots/player/playing_dark.png index 4388892..da7c8c1 100644 Binary files a/app/src/testDebug/screenshots/player/playing_dark.png and b/app/src/testDebug/screenshots/player/playing_dark.png differ diff --git a/app/src/testDebug/screenshots/player/playing_landscape.png b/app/src/testDebug/screenshots/player/playing_landscape.png index f24c670..5027853 100644 Binary files a/app/src/testDebug/screenshots/player/playing_landscape.png and b/app/src/testDebug/screenshots/player/playing_landscape.png differ diff --git a/app/src/testDebug/screenshots/player/playing_light.png b/app/src/testDebug/screenshots/player/playing_light.png index 02008b4..b2c271f 100644 Binary files a/app/src/testDebug/screenshots/player/playing_light.png and b/app/src/testDebug/screenshots/player/playing_light.png differ diff --git a/app/src/testDebug/screenshots/player/queue_unavailable_light.png b/app/src/testDebug/screenshots/player/queue_unavailable_light.png index 91ac1d0..0f5a6f6 100644 Binary files a/app/src/testDebug/screenshots/player/queue_unavailable_light.png and b/app/src/testDebug/screenshots/player/queue_unavailable_light.png differ diff --git a/app/src/testDebug/screenshots/player/sleep_timer_light.png b/app/src/testDebug/screenshots/player/sleep_timer_light.png index 446d35d..d2b77d7 100644 Binary files a/app/src/testDebug/screenshots/player/sleep_timer_light.png and b/app/src/testDebug/screenshots/player/sleep_timer_light.png differ diff --git a/docs/pages/assets/screenshots/tablet10/02_library.png b/docs/pages/assets/screenshots/tablet10/02_library.png index 5da8252..c342173 100644 Binary files a/docs/pages/assets/screenshots/tablet10/02_library.png and b/docs/pages/assets/screenshots/tablet10/02_library.png differ diff --git a/docs/pages/assets/screenshots/tablet10/03_favourites.png b/docs/pages/assets/screenshots/tablet10/03_favourites.png index 13dfc39..3cc22f4 100644 Binary files a/docs/pages/assets/screenshots/tablet10/03_favourites.png and b/docs/pages/assets/screenshots/tablet10/03_favourites.png differ diff --git a/docs/pages/assets/screenshots/tablet10/04_search.png b/docs/pages/assets/screenshots/tablet10/04_search.png index d260d24..770f8a8 100644 Binary files a/docs/pages/assets/screenshots/tablet10/04_search.png and b/docs/pages/assets/screenshots/tablet10/04_search.png differ diff --git a/docs/pages/assets/screenshots/tablet7/02_library.png b/docs/pages/assets/screenshots/tablet7/02_library.png index 5c91c11..1049520 100644 Binary files a/docs/pages/assets/screenshots/tablet7/02_library.png and b/docs/pages/assets/screenshots/tablet7/02_library.png differ diff --git a/docs/pages/assets/screenshots/tablet7/03_favourites.png b/docs/pages/assets/screenshots/tablet7/03_favourites.png index 2d74ef8..e841fdd 100644 Binary files a/docs/pages/assets/screenshots/tablet7/03_favourites.png and b/docs/pages/assets/screenshots/tablet7/03_favourites.png differ diff --git a/docs/pages/assets/screenshots/tablet7/04_search.png b/docs/pages/assets/screenshots/tablet7/04_search.png index 997f0dd..dac6115 100644 Binary files a/docs/pages/assets/screenshots/tablet7/04_search.png and b/docs/pages/assets/screenshots/tablet7/04_search.png differ diff --git a/fastlane/metadata/android/de-DE/changelogs/13.txt b/fastlane/metadata/android/de-DE/changelogs/13.txt new file mode 100644 index 0000000..dfc82bb --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/13.txt @@ -0,0 +1 @@ +Querformat funktioniert jetzt richtig: Die Navigation wandert an den Rand, sodass Bibliothek, Favoriten und Einstellungen den Großteil des Bildschirms bekommen. Lange Titel laufen durch, statt abgeschnitten zu werden. Und ein zweites Tippen auf „Bibliothek“ springt direkt zur Suche. \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/13.txt b/fastlane/metadata/android/en-US/changelogs/13.txt new file mode 100644 index 0000000..c6ad9e9 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/13.txt @@ -0,0 +1 @@ +Landscape works properly now: navigation moves to the side, so the library, favourites and settings get most of the screen. Long titles scroll instead of being cut off. And tapping Library a second time jumps straight to search. \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/02_library.png b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/02_library.png index 5c91c11..1049520 100644 Binary files a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/02_library.png and b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/02_library.png differ diff --git a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/03_favourites.png b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/03_favourites.png index 2d74ef8..e841fdd 100644 Binary files a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/03_favourites.png and b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/03_favourites.png differ diff --git a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/04_search.png b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/04_search.png index 997f0dd..dac6115 100644 Binary files a/fastlane/metadata/android/en-US/images/sevenInchScreenshots/04_search.png and b/fastlane/metadata/android/en-US/images/sevenInchScreenshots/04_search.png differ diff --git a/fastlane/metadata/android/en-US/images/tenInchScreenshots/02_library.png b/fastlane/metadata/android/en-US/images/tenInchScreenshots/02_library.png index 5da8252..c342173 100644 Binary files a/fastlane/metadata/android/en-US/images/tenInchScreenshots/02_library.png and b/fastlane/metadata/android/en-US/images/tenInchScreenshots/02_library.png differ diff --git a/fastlane/metadata/android/en-US/images/tenInchScreenshots/03_favourites.png b/fastlane/metadata/android/en-US/images/tenInchScreenshots/03_favourites.png index 13dfc39..3cc22f4 100644 Binary files a/fastlane/metadata/android/en-US/images/tenInchScreenshots/03_favourites.png and b/fastlane/metadata/android/en-US/images/tenInchScreenshots/03_favourites.png differ diff --git a/fastlane/metadata/android/en-US/images/tenInchScreenshots/04_search.png b/fastlane/metadata/android/en-US/images/tenInchScreenshots/04_search.png index d260d24..770f8a8 100644 Binary files a/fastlane/metadata/android/en-US/images/tenInchScreenshots/04_search.png and b/fastlane/metadata/android/en-US/images/tenInchScreenshots/04_search.png differ diff --git a/fastlane/metadata/android/es-ES/changelogs/13.txt b/fastlane/metadata/android/es-ES/changelogs/13.txt new file mode 100644 index 0000000..3ba1f9b --- /dev/null +++ b/fastlane/metadata/android/es-ES/changelogs/13.txt @@ -0,0 +1 @@ +El modo horizontal ya funciona bien: la navegación pasa al lateral, así que la biblioteca, los favoritos y los ajustes aprovechan casi toda la pantalla. Los títulos largos se desplazan en lugar de cortarse. Y al tocar «Biblioteca» por segunda vez se abre directamente la búsqueda. \ No newline at end of file diff --git a/fastlane/metadata/android/fr-FR/changelogs/13.txt b/fastlane/metadata/android/fr-FR/changelogs/13.txt new file mode 100644 index 0000000..329487f --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/13.txt @@ -0,0 +1 @@ +Le mode paysage fonctionne enfin correctement : la navigation passe sur le côté, et la bibliothèque, les favoris et les réglages profitent de presque tout l’écran. Les titres longs défilent au lieu d’être coupés. Et un second appui sur « Bibliothèque » ouvre directement la recherche. \ No newline at end of file diff --git a/fastlane/metadata/android/nl-NL/changelogs/13.txt b/fastlane/metadata/android/nl-NL/changelogs/13.txt new file mode 100644 index 0000000..4710bef --- /dev/null +++ b/fastlane/metadata/android/nl-NL/changelogs/13.txt @@ -0,0 +1 @@ +Liggend werkt nu goed: de navigatie verhuist naar de zijkant, zodat de bibliotheek, favorieten en instellingen het grootste deel van het scherm krijgen. Lange titels scrollen in plaats van afgekapt te worden. En nog een keer op Bibliotheek tikken springt meteen naar zoeken. \ No newline at end of file