Feature/collapse playing queue fab (#358)

This commit is contained in:
Max Grakov
2026-03-01 21:33:26 +03:00
committed by GitHub
parent d1cec349ea
commit acde3e52d3
15 changed files with 163 additions and 145 deletions

View File

@@ -274,8 +274,6 @@ class LissenSharedPreferences
val materialYouFlow = asFlow(KEY_MATERIAL_YOU_ENABLED, ::getMaterialYouColors)
val collapseOnFlingFlow = asFlow(KEY_COLLAPSE_ON_FLING, ::getCollapseOnFling)
val forceCacheFlow = asFlow(CACHE_FORCE_ENABLED, ::isForceCache)
val hideCompletedFlow = asFlow(KEY_HIDE_COMPLETED, ::getHideCompleted)
@@ -441,13 +439,6 @@ class LissenSharedPreferences
}
}
fun getCollapseOnFling(): Boolean = sharedPreferences.getBoolean(KEY_COLLAPSE_ON_FLING, true)
fun saveCollapseOnFling(value: Boolean) =
sharedPreferences.edit {
putBoolean(KEY_COLLAPSE_ON_FLING, value)
}
fun getSoftwareCodecsEnabled(): Boolean = sharedPreferences.getBoolean(KEY_SOFTWARE_CODECS, false)
fun saveSoftwareCodecsEnabled(value: Boolean) =
@@ -489,7 +480,6 @@ class LissenSharedPreferences
private const val KEY_PREFERRED_AUTO_DOWNLOAD_LIBRARY_TYPE = "preferred_auto_download_library_type"
private const val KEY_AUTO_DOWNLOAD_DELAYED = "auto_download_delayed"
private const val KEY_PREFERRED_LIBRARY_ORDERING = "preferred_library_ordering"
private const val KEY_COLLAPSE_ON_FLING = "collapse_on_fling"
private const val KEY_SOFTWARE_CODECS = "software_codecs"
private const val KEY_HIDE_COMPLETED = "hide_completed"

View File

@@ -1,10 +1,14 @@
package org.grakovne.lissen.ui.screens.player.composable
import android.view.ViewConfiguration
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
@@ -18,7 +22,12 @@ import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.FloatingActionButtonDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.typography
@@ -30,9 +39,11 @@ import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableIntStateOf
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.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
@@ -66,7 +77,6 @@ fun PlayingQueueComposable(
val book by viewModel.book.observeAsState()
val searchToken by viewModel.searchToken.observeAsState("")
val collapseOnFling by viewModel.collapseOnFling.collectAsState(false)
val showingChapters by remember {
derivedStateOf {
@@ -126,6 +136,34 @@ fun PlayingQueueComposable(
label = "playing_queue_font_size",
)
var showCollapseFab by remember { mutableStateOf(false) }
LaunchedEffect(playingQueueExpanded) {
if (!playingQueueExpanded) {
showCollapseFab = false
}
}
val fabScrollConnection =
remember(playingQueueExpanded, collapseFlingThreshold) {
object : NestedScrollConnection {
override suspend fun onPreFling(available: Velocity): Velocity {
if (!playingQueueExpanded) return Velocity.Zero
if (available.y > collapseFlingThreshold) {
showCollapseFab = true
return Velocity.Zero
}
if (available.y < -collapseFlingThreshold) {
showCollapseFab = false
return Velocity.Zero
}
return Velocity.Zero
}
}
}
LaunchedEffect(currentTrackIndex) {
awaitFrame()
scrollPlayingQueue(
@@ -136,122 +174,147 @@ fun PlayingQueueComposable(
playingQueueExpanded = playingQueueExpanded,
)
}
Column(
Box(
modifier =
modifier
.fillMaxSize()
.let {
when (playingQueueExpanded) {
true -> {
it.withScrollbar(
state = listState,
color = colorScheme.onBackground.copy(alpha = scrollbarAlpha),
totalItems = showingChapters.size,
ignoreItems = emptyList(),
)
}
false -> {
it
}
}
}.padding(horizontal = 16.dp),
.nestedScroll(fabScrollConnection),
) {
if (playingQueueExpanded.not()) {
Text(
text = provideNowPlayingTitle(libraryViewModel.fetchPreferredLibraryType(), context),
fontSize = fontSize.sp,
fontWeight = FontWeight.SemiBold,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(horizontal = 6.dp),
)
Spacer(modifier = Modifier.height(12.dp))
}
LazyColumn(
contentPadding =
when (playingQueueExpanded) {
true -> PaddingValues(bottom = 12.dp)
false -> PaddingValues(bottom = with(density) { collapsedPlayingQueueHeight.toDp() })
},
Column(
modifier =
Modifier
.fillMaxHeight()
.scrollable(
state = rememberScrollState(),
orientation = Orientation.Vertical,
enabled = playingQueueExpanded,
).onGloballyPositioned {
if (collapsedPlayingQueueHeight == 0) {
collapsedPlayingQueueHeight = it.size.height
}
}.onSizeChanged { intSize ->
if (intSize.height != collapsedPlayingQueueHeight) {
coroutineScope.launch {
awaitFrame()
scrollPlayingQueue(
currentTrackIndex = currentTrackIndex,
listState = listState,
playbackReady = playbackReady,
animate = false,
playingQueueExpanded = playingQueueExpanded,
.fillMaxSize()
.let {
when (playingQueueExpanded) {
true -> {
it.withScrollbar(
state = listState,
color = colorScheme.onBackground.copy(alpha = scrollbarAlpha),
totalItems = showingChapters.size,
ignoreItems = emptyList(),
)
}
}
}.nestedScroll(
object : NestedScrollConnection {
override fun onPreScroll(
available: Offset,
source: NestedScrollSource,
): Offset = if (playingQueueExpanded) Offset.Zero else available
override suspend fun onPreFling(available: Velocity): Velocity {
if (available.y < -expandFlingThreshold && !playingQueueExpanded) {
viewModel.expandPlayingQueue()
return available
}
if (available.y > collapseFlingThreshold && playingQueueExpanded && collapseOnFling) {
viewModel.collapsePlayingQueue()
return available
}
return Velocity.Zero
false -> {
it
}
},
),
state = listState,
}
}.padding(horizontal = 16.dp),
) {
val maxDuration = showingChapters.maxOfOrNull { it.duration } ?: 0.0
itemsIndexed(showingChapters) { index, chapter ->
val isCached by cachingModelView
.provideCacheState(
bookId = book?.id ?: "",
chapterId = chapter.id,
).observeAsState(false)
PlaylistItemComposable(
track = chapter,
onClick = { viewModel.setChapter(chapter) },
isSelected = chapter.id == currentTrackId?.id,
modifier = Modifier.wrapContentWidth(),
maxDuration = maxDuration,
isCached = isCached,
if (playingQueueExpanded.not()) {
Text(
text = provideNowPlayingTitle(libraryViewModel.fetchPreferredLibraryType(), context),
fontSize = fontSize.sp,
fontWeight = FontWeight.SemiBold,
color = colorScheme.primary,
modifier = Modifier.padding(horizontal = 6.dp),
)
if (index < showingChapters.size - 1) {
HorizontalDivider(
thickness = 1.dp,
modifier =
Modifier
.padding(start = 24.dp)
.padding(vertical = 8.dp),
Spacer(modifier = Modifier.height(12.dp))
}
LazyColumn(
contentPadding =
when (playingQueueExpanded) {
true -> PaddingValues(bottom = 12.dp)
false -> PaddingValues(bottom = with(density) { collapsedPlayingQueueHeight.toDp() })
},
modifier =
Modifier
.fillMaxHeight()
.scrollable(
state = rememberScrollState(),
orientation = Orientation.Vertical,
enabled = playingQueueExpanded,
).onGloballyPositioned {
if (collapsedPlayingQueueHeight == 0) {
collapsedPlayingQueueHeight = it.size.height
}
}.onSizeChanged { intSize ->
if (intSize.height != collapsedPlayingQueueHeight) {
coroutineScope.launch {
awaitFrame()
scrollPlayingQueue(
currentTrackIndex = currentTrackIndex,
listState = listState,
playbackReady = playbackReady,
animate = false,
playingQueueExpanded = playingQueueExpanded,
)
}
}
}.nestedScroll(
object : NestedScrollConnection {
override fun onPreScroll(
available: Offset,
source: NestedScrollSource,
): Offset = if (playingQueueExpanded) Offset.Zero else available
override suspend fun onPreFling(available: Velocity): Velocity {
if (available.y < -expandFlingThreshold && !playingQueueExpanded) {
viewModel.expandPlayingQueue()
return available
}
return Velocity.Zero
}
},
),
state = listState,
) {
val maxDuration = showingChapters.maxOfOrNull { it.duration } ?: 0.0
itemsIndexed(showingChapters) { index, chapter ->
val isCached by cachingModelView
.provideCacheState(
bookId = book?.id ?: "",
chapterId = chapter.id,
).observeAsState(false)
PlaylistItemComposable(
track = chapter,
onClick = { viewModel.setChapter(chapter) },
isSelected = chapter.id == currentTrackId?.id,
modifier = Modifier.wrapContentWidth(),
maxDuration = maxDuration,
isCached = isCached,
)
if (index < showingChapters.size - 1) {
HorizontalDivider(
thickness = 1.dp,
modifier =
Modifier
.padding(start = 24.dp)
.padding(vertical = 8.dp),
)
}
}
}
}
AnimatedVisibility(
visible = playingQueueExpanded && showCollapseFab,
enter = fadeIn(),
exit = fadeOut(),
modifier =
Modifier
.align(Alignment.TopCenter)
.padding(top = 8.dp),
) {
FloatingActionButton(
onClick = { viewModel.collapsePlayingQueue() },
containerColor = colorScheme.surfaceContainer,
contentColor = colorScheme.primary,
elevation = FloatingActionButtonDefaults.loweredElevation(),
) {
Icon(
imageVector = Icons.Filled.ExpandMore,
contentDescription = "Collapse queue",
)
}
}
}
}

View File

@@ -51,7 +51,6 @@ fun AdvancedSettingsComposable(
val cachingModelView: CachingModelView = hiltViewModel()
val viewModel: SettingsViewModel = hiltViewModel()
val collapseOnFling by viewModel.collapseOnFling.observeAsState(false)
val crashReporting by viewModel.crashReporting.observeAsState(true)
val bypassSsl by viewModel.bypassSsl.observeAsState(false)
@@ -109,12 +108,6 @@ fun AdvancedSettingsComposable(
onclick = { navController.showSeekSettings() },
)
SettingsToggleItem(
stringResource(R.string.settings_screen_collapse_on_fling_title),
stringResource(R.string.settings_screen_collapse_on_fling_description),
collapseOnFling,
) { viewModel.preferCollapseOnFling(it) }
AdvancedSettingsNavigationItemComposable(
title = stringResource(R.string.settings_screen_custom_headers_title),
description = stringResource(R.string.settings_screen_custom_header_hint),

View File

@@ -49,7 +49,6 @@ class PlayerViewModel
val searchToken: LiveData<String> = _searchToken
val isPlaying: LiveData<Boolean> = mediaRepository.isPlaying
val collapseOnFling = preferences.collapseOnFlingFlow
val bookmarks = mediaRepository.bookmarks

View File

@@ -90,8 +90,6 @@ class SettingsViewModel
private val _hideCompleted = preferences.hideCompletedFlow
val hideCompleted = _hideCompleted
val collapseOnFling = MutableLiveData(preferences.getCollapseOnFling())
private val _autoDownloadDelayed = MutableLiveData(preferences.getAutoDownloadDelayed())
val autoDownloadDelayed = _autoDownloadDelayed
@@ -218,11 +216,6 @@ class SettingsViewModel
preferences.saveMaterialYouColors(value)
}
fun preferCollapseOnFling(value: Boolean) {
collapseOnFling.postValue(value)
preferences.saveCollapseOnFling(value)
}
fun preferSoftwareCodecsEnabled(value: Boolean) {
_softwareCodecsEnabled.postValue(value)
preferences.saveSoftwareCodecsEnabled(value)

View File

@@ -167,8 +167,6 @@
<string name="codes_not_supported_warning_toast">صيغة الملف الصوتي غير مدعومه</string>
<string name="bookmarks_create_bookmark">إنشاء إشارة مرجعية</string>
<string name="bookmarks_title">إشارات مرجعية</string>
<string name="settings_screen_collapse_on_fling_title">طيّ قائمة التشغيل عند السحب السريع</string>
<string name="settings_screen_collapse_on_fling_description">اسحب لطي قائمة الفصول</string>
<string name="settings_screen_material_you_title">ألوان Material You</string>
<string name="settings_screen_material_you_description">مطابقة ألوان التطبيق لخلفية الشاشة</string>
<string name="restart_the_app_to_start_using_the_new_codecs_cta">إعادة التشغيل</string>

View File

@@ -25,8 +25,6 @@
<string name="settings_screen_connection_type">Typ připojení</string>
<string name="settings_screen_connection_local">Lokální síť</string>
<string name="settings_screen_connection_external">Vnější síť</string>
<string name="settings_screen_collapse_on_fling_title">Sbalit frontu gestem</string>
<string name="settings_screen_collapse_on_fling_description">Sbalit seznam kapitol přejetím prstem</string>
<string name="login_error_host_is_down">Host je mimo provoz</string>
<string name="login_error_host_url_is_missing">Chybí URL adresa hostu</string>
<string name="login_error_username_is_missing">Chybí přihlašovací jméno</string>

View File

@@ -159,8 +159,6 @@
<string name="settings_screen_bypass_ssl_title">SSL Zertifikatprüfung deaktivieren</string>
<string name="settings_screen_bypass_ssl_hint">Verbindung zum Server mit jedem Zertifikat</string>
<string name="volume_boost_max">Maximum</string>
<string name="settings_screen_collapse_on_fling_title">Wiedergabeliste mittels swipe einklappen</string>
<string name="settings_screen_collapse_on_fling_description">Wische um die Kapitelliste einzuklappen</string>
<string name="settings_screen_material_you_title">Material You Farben</string>
<string name="settings_screen_material_you_description">Passe die Farben der App an dein Hintergrundbild an</string>
<string name="settings_screen_software_codecs_enabled_title">Software-Decodierung erzwingen</string>

View File

@@ -161,8 +161,6 @@
<string name="volume_boost_high">Visoko</string>
<string name="download_settings_description">Definiranje preuzimanja i upravljanje sadržajem</string>
<string name="settings_screen_advanced_preferences_description">Kontrola pretraživanja, proxy zaglavlje ili zvuk</string>
<string name="settings_screen_collapse_on_fling_title">Sažmi red za reprodukciju prilikom flistanja</string>
<string name="settings_screen_collapse_on_fling_description">Prijeđi prstom da bi sažeo popis poglavlja</string>
<string name="settings_screen_material_you_description">Uskladi boje aplikacije s pozadinom</string>
<string name="timer_option_disabled">Onemogućeno</string>
<string name="settings_screen_software_codecs_enabled_title">Forsiraj softversko dekodiranje</string>

View File

@@ -159,8 +159,6 @@
<string name="settings_screen_bypass_ssl_title">SSL-ellenőrzés letiltása</string>
<string name="settings_screen_bypass_ssl_hint">Csatlakozás bármilyen tanúsítvánnyal rendelkező kiszolgálókhoz</string>
<string name="volume_boost_max">Maximum</string>
<string name="settings_screen_collapse_on_fling_title">Lendítés közben a lejátszási sor összecsukása</string>
<string name="settings_screen_collapse_on_fling_description">Csúsztatás a fejezetlista összecsukásához</string>
<string name="settings_screen_material_you_title">Material You színek</string>
<string name="settings_screen_material_you_description">Az alkalmazás színeinek illesztése a háttérképhez</string>
<string name="timer_option_disabled">Letiltva</string>

View File

@@ -159,8 +159,6 @@
<string name="settings_screen_delay_autodownload_description">Jeda singkat sebelum memulai unduhan</string>
<string name="settings_screen_bypass_ssl_title">Nonaktifkan verifikasi SSL</string>
<string name="settings_screen_bypass_ssl_hint">Hubungkan ke server dengan sertifikat apa pun</string>
<string name="settings_screen_collapse_on_fling_title">Sembunyikan antrean putar saat usap cepat</string>
<string name="settings_screen_collapse_on_fling_description">Usap untuk menyembunyikan daftar chapter</string>
<string name="settings_screen_material_you_title">Warna Material You</string>
<string name="settings_screen_material_you_description">Sesuaikan warna aplikasi dengan wallpaper</string>
<string name="timer_option_disabled">Nonaktif</string>

View File

@@ -159,10 +159,8 @@
<string name="volume_boost_max">Massimo</string>
<string name="settings_screen_bypass_ssl_title">Disabilita verifica SSL</string>
<string name="settings_screen_bypass_ssl_hint">Connetti ai server senza alcun certificato</string>
<string name="settings_screen_collapse_on_fling_description">Scorri per comprimere l\'elenco dei capitoli</string>
<string name="settings_screen_material_you_title">Colori Material You</string>
<string name="settings_screen_material_you_description">Abbina i colori dell\'app al tuo sfondo</string>
<string name="settings_screen_collapse_on_fling_title">Comprimi coda con scorrimento rapido</string>
<string name="timer_option_disabled">Disabilitato</string>
<string name="settings_screen_software_codecs_enabled_title">Forza decodifica software</string>
<string name="settings_screen_software_codecs_enabled_description">Usa codec software per la riproduzione audio</string>

View File

@@ -162,8 +162,6 @@
<string name="settings_screen_connection_external">Внешняя сеть</string>
<string name="settings_screen_bypass_ssl_title">Игнорировать проверку SSL</string>
<string name="settings_screen_bypass_ssl_hint">Подключаться с невалидным сертификатом</string>
<string name="settings_screen_collapse_on_fling_title">Сворачивать список глав свайпом</string>
<string name="settings_screen_collapse_on_fling_description">Смахните, чтобы свернуть очередь воспроизведения</string>
<string name="settings_screen_material_you_title">Цвета Material You</string>
<string name="settings_screen_material_you_description">Подбирать цвета приложения под обои</string>
<string name="restart_the_app_to_start_using_the_new_codecs_title">Перезапустите приложение, чтобы применить новые кодеки</string>

View File

@@ -161,8 +161,6 @@
<string name="volume_boost_max">最大</string>
<string name="settings_screen_material_you_description">根据当前壁纸自动调整应用的颜色主题</string>
<string name="settings_screen_material_you_title">Material You 动态配色</string>
<string name="settings_screen_collapse_on_fling_title">快速滑动收起播放列表</string>
<string name="settings_screen_collapse_on_fling_description">向下滑动即可收起章节列表</string>
<string name="timer_option_disabled">禁用</string>
<string name="settings_screen_software_codecs_enabled_title">强制使用软件解码</string>
<string name="settings_screen_software_codecs_enabled_description">使用软件解码器播放音频</string>

View File

@@ -25,8 +25,6 @@
<string name="settings_screen_connection_type">Connection type</string>
<string name="settings_screen_connection_local">Local network</string>
<string name="settings_screen_connection_external">External network</string>
<string name="settings_screen_collapse_on_fling_title">Collapse playback queue on fling</string>
<string name="settings_screen_collapse_on_fling_description">Swipe to collapse the chapters list</string>
<string name="login_error_host_is_down">Host is down</string>
<string name="login_error_host_url_is_missing">Host URL is missing</string>
<string name="login_error_username_is_missing">Username is missing</string>