diff --git a/ComposeKit b/ComposeKit index db82c54d5..0d2306680 160000 --- a/ComposeKit +++ b/ComposeKit @@ -1 +1 @@ -Subproject commit db82c54d58a598c789faf7ffc63c911ee0b87044 +Subproject commit 0d2306680c579f5eefa2028bd6dfe4ca60a0d7c8 diff --git a/desktopApp/src/jvmMain/kotlin/main.kt b/desktopApp/src/jvmMain/kotlin/main.kt index 9c09f89ff..9e404116d 100644 --- a/desktopApp/src/jvmMain/kotlin/main.kt +++ b/desktopApp/src/jvmMain/kotlin/main.kt @@ -17,12 +17,7 @@ import androidx.compose.ui.window.rememberWindowState import com.toasterofbread.composekit.platform.composable.onWindowBackPressed import com.toasterofbread.spmp.model.settings.category.DesktopSettings import com.toasterofbread.spmp.platform.AppContext -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.cancel -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext +import kotlinx.coroutines.* import org.jetbrains.skiko.OS import org.jetbrains.skiko.hostOs import java.awt.Toolkit @@ -56,7 +51,10 @@ fun main() { } return@Window false }, - state = rememberWindowState(size = DpSize(1280.dp, 720.dp), position = WindowPosition(Alignment.Center)) + state = rememberWindowState( + size = DpSize(1280.dp, 720.dp), + position = WindowPosition(Alignment.Center) + ) ) { LaunchedEffect(Unit) { val startup_command: String = DesktopSettings.Key.STARTUP_COMMAND.get() diff --git a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/model/settings/category/ThemeSettings.kt b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/model/settings/category/ThemeSettings.kt index c8f20cbbe..714fc81fb 100644 --- a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/model/settings/category/ThemeSettings.kt +++ b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/model/settings/category/ThemeSettings.kt @@ -3,9 +3,9 @@ package com.toasterofbread.spmp.model.settings.category import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Palette import com.toasterofbread.spmp.model.settings.SettingsKey -import com.toasterofbread.spmp.platform.AppContext import com.toasterofbread.spmp.resources.getString import com.toasterofbread.spmp.ui.layout.apppage.settingspage.category.getThemeCategoryItems +import com.toasterofbread.spmp.ui.layout.nowplaying.ThemeMode data object ThemeSettings: SettingsCategory("theme") { override val keys: List = Key.entries.toList() @@ -32,7 +32,7 @@ data object ThemeSettings: SettingsCategory("theme") { ACCENT_COLOUR_SOURCE -> AccentColourSource.THUMBNAIL.ordinal CURRENT_THEME -> 0 THEMES -> "[]" - NOWPLAYING_THEME_MODE -> 0 + NOWPLAYING_THEME_MODE -> ThemeMode.DEFAULT.ordinal NOWPLAYING_DEFAULT_GRADIENT_DEPTH -> 1f } as T } diff --git a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/platform/AppContext.kt b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/platform/AppContext.kt index 692703e85..9027f8167 100644 --- a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/platform/AppContext.kt +++ b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/platform/AppContext.kt @@ -122,7 +122,7 @@ fun PlayerState.getDefaultHorizontalPadding(): Dp = if (Platform.DESKTOP.isCurrent() && form_factor == FormFactor.LANDSCAPE) 30.dp else 10.dp @Composable fun PlayerState.getDefaultVerticalPadding(): Dp = - if (Platform.DESKTOP.isCurrent() && form_factor == FormFactor.LANDSCAPE) 30.dp else 10.dp // TODO + if (Platform.DESKTOP.isCurrent() && form_factor == FormFactor.LANDSCAPE) 10.dp else 10.dp @Composable fun PlayerState.getDefaultPaddingValues(): PaddingValues = PaddingValues(horizontal = getDefaultHorizontalPadding(), vertical = getDefaultVerticalPadding()) diff --git a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/component/LyricsLineDisplay.kt b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/component/LyricsLineDisplay.kt index 22752505a..db08cab2b 100644 --- a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/component/LyricsLineDisplay.kt +++ b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/component/LyricsLineDisplay.kt @@ -1,9 +1,6 @@ package com.toasterofbread.spmp.ui.component -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.fadeOut -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically +import androidx.compose.animation.* import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.fillMaxWidth @@ -20,6 +17,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import com.toasterofbread.composekit.utils.composable.AlignableCrossfade import com.toasterofbread.spmp.model.lyrics.SongLyrics +import com.toasterofbread.spmp.model.settings.category.TopBarSettings import kotlinx.coroutines.delay private const val UPDATE_INTERVAL_MS = 100L @@ -43,16 +41,17 @@ private fun getCurrentLine(lyrics: SongLyrics, time: Long, linger: Boolean): Int fun LyricsLineDisplay( lyrics: SongLyrics, getTime: () -> Long, - lyrics_linger: Boolean = true, - show_furigana: Boolean = true, - max_lines: Int = 1, - preallocate_needed_space: Boolean = false, modifier: Modifier = Modifier, text_colour: Color = LocalContentColor.current, emptyContent: (@Composable () -> Unit)? = null ) { require(lyrics.synced) + val lyrics_linger: Boolean by TopBarSettings.Key.LYRICS_LINGER.rememberMutableState() + val show_furigana: Boolean by TopBarSettings.Key.LYRICS_SHOW_FURIGANA.rememberMutableState() + val max_lines: Int by TopBarSettings.Key.LYRICS_MAX_LINES.rememberMutableState() + val preallocate_max_space: Boolean by TopBarSettings.Key.LYRICS_PREAPPLY_MAX_LINES.rememberMutableState() + var current_line: Int? by remember { mutableStateOf(getCurrentLine(lyrics, getTime(), lyrics_linger)) } var line_a: Int? by remember { mutableStateOf(current_line) } var line_b: Int? by remember { mutableStateOf(null) } @@ -62,7 +61,7 @@ fun LyricsLineDisplay( while (true) { delay(UPDATE_INTERVAL_MS) - val line = getCurrentLine(lyrics, getTime(), lyrics_linger) + val line: Int? = getCurrentLine(lyrics, getTime(), lyrics_linger) if (lyrics_linger && line == null) { continue } @@ -80,17 +79,17 @@ fun LyricsLineDisplay( } } - val enter = slideInVertically { it } - val exit = slideOutVertically { -it } + fadeOut() + val enter: EnterTransition = slideInVertically { it } + val exit: ExitTransition = slideOutVertically { -it } + fadeOut() Box(modifier.height(IntrinsicSize.Min), contentAlignment = Alignment.Center) { - val show_a = line_a != null && show_line_a - val show_b = line_b != null && !show_line_a + val show_a: Boolean = line_a != null && show_line_a + val show_b: Boolean = line_b != null && !show_line_a @Composable fun phase(show: Boolean, index: Int?) { AnimatedVisibility(show, Modifier.height(IntrinsicSize.Min).fillMaxWidth(), enter = enter, exit = exit) { - var line by remember { mutableStateOf(index) } + var line: Int? by remember { mutableStateOf(index) } LaunchedEffect(index) { if (index != null) { line = index @@ -103,7 +102,7 @@ fun LyricsLineDisplay( show_readings = show_furigana, text_colour = text_colour, max_lines = max_lines, - preallocate_needed_space = preallocate_needed_space + preallocate_needed_space = preallocate_max_space ) } } diff --git a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/component/MusicTopBar.kt b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/component/MusicTopBar.kt index 2b7695f32..513f3da4b 100644 --- a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/component/MusicTopBar.kt +++ b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/component/MusicTopBar.kt @@ -299,21 +299,12 @@ class MusicTopBar(val player: PlayerState) { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { when (state) { is SongLyrics -> { - val linger: Boolean by TopBarSettings.Key.LYRICS_LINGER.rememberMutableState() - val show_furigana: Boolean by TopBarSettings.Key.LYRICS_SHOW_FURIGANA.rememberMutableState() - val max_lines: Int by TopBarSettings.Key.LYRICS_MAX_LINES.rememberMutableState() - val preallocate_max_space: Boolean by TopBarSettings.Key.LYRICS_PREAPPLY_MAX_LINES.rememberMutableState() - LyricsLineDisplay( lyrics = state, getTime = { (player.controller?.current_position_ms ?: 0) + (sync_offset_state?.value ?: 0) }, - lyrics_linger = linger, - show_furigana = show_furigana, - max_lines = max_lines, - preallocate_needed_space = preallocate_max_space, modifier = Modifier.fillMaxWidth(), emptyContent = { TopBarEmptyContent() diff --git a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/component/WaveBorder.kt b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/component/WaveBorder.kt index 6553ff896..7fee6e59d 100644 --- a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/component/WaveBorder.kt +++ b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/component/WaveBorder.kt @@ -32,15 +32,16 @@ const val WAVE_BORDER_HEIGHT_DP: Float = 20f data class WaveShape( val waves: Int, val offset: Float, - val invert: Boolean = false + val invert: Boolean = false, + val width_multiplier: Float = 1f ): Shape { override fun createOutline(size: Size, layoutDirection: LayoutDirection, density: Density): Outline { val path: Path = Path() path.addRect(Rect(0f, 0f, size.width, size.height / 2)) - wavePath(path, size, 1, waves) { offset } - wavePath(path, size, -1, waves) { offset } + wavePath(path, size, 1, waves, width_multiplier) { offset } + wavePath(path, size, -1, waves, width_multiplier) { offset } if (invert) { path.transform( @@ -63,6 +64,7 @@ fun WaveBorder( getOffset: ((height: Float) -> Float)? = null, waves: Int = 3, invert: Boolean = false, + width_multiplier: Float = 1f, getWaveOffset: (Density.() -> Float)? = null, border_thickness: Dp = 0.dp, border_colour: Color = LocalContentColor.current @@ -72,8 +74,8 @@ fun WaveBorder( val colour: Color = getColour(player.theme) val offset: Float? = getWaveOffset?.invoke(density) - val shape: WaveShape = remember(waves, offset) { - WaveShape(waves, offset ?: 0f, invert = invert) + val shape: WaveShape = remember(waves, offset, width_multiplier) { + WaveShape(waves, offset ?: 0f, invert = invert, width_multiplier = width_multiplier) } Box(modifier.requiredHeight(0.dp)) { @@ -86,8 +88,8 @@ fun WaveBorder( with(density) { val user_offset: Dp = getOffset?.invoke(height.toPx())?.toDp() ?: ( - if (invert) (-height / 2) + 0.1.dp - else (height / 2) - 0.1.dp + if (invert) (-height / 2) + 0.2.dp + else (height / 2) - 0.2.dp ) user_offset + border_thickness } @@ -103,6 +105,7 @@ inline fun DrawScope.drawWave( waves: Int, wave_size: Size = size, stroke_width: Float = 2f, + width_multiplier: Float = 1f, getWaveOffset: () -> Float, getColour: () -> Color, ) { @@ -111,12 +114,12 @@ inline fun DrawScope.drawWave( val stroke: Stroke = Stroke(stroke_width) // Above equilibrium - wavePath(path, wave_size, -1, waves, getWaveOffset) + wavePath(path, wave_size, -1, waves, width_multiplier, getWaveOffset) drawPath(path, colour, style = stroke) path.reset() // Below equilibrium - wavePath(path, wave_size, 1, waves, getWaveOffset) + wavePath(path, wave_size, 1, waves, width_multiplier, getWaveOffset) drawPath(path, colour, style = stroke) } @@ -125,6 +128,7 @@ inline fun wavePath( size: Size, direction: Int, waves: Int, + width_multiplier: Float, getOffset: () -> Float ): Path { val y_offset: Float = size.height / 2 @@ -135,7 +139,7 @@ inline fun wavePath( path.moveTo(x = offset_px, y = y_offset) - for (i in 0 until ceil(size.width / (half_period + 1)).toInt()) { + for (i in 0 until ceil((size.width * width_multiplier) / (half_period + 1)).toInt()) { if ((i % 2 == 0) != (direction == 1)) { path.relativeMoveTo(half_period, 0f) continue diff --git a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/PinnedItemsRow.kt b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/PinnedItemsRow.kt index 700169474..5484b7d51 100644 --- a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/PinnedItemsRow.kt +++ b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/PinnedItemsRow.kt @@ -24,18 +24,19 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import com.toasterofbread.spmp.model.mediaitem.MediaItem import com.toasterofbread.spmp.model.mediaitem.db.rememberPinnedItems import com.toasterofbread.spmp.model.mediaitem.db.setPinned import com.toasterofbread.spmp.ui.component.mediaitemlayout.MediaItemGrid +import com.toasterofbread.spmp.ui.component.multiselect.MediaItemMultiSelectContext import com.toasterofbread.spmp.ui.layout.apppage.mainpage.PlayerState @Composable fun PinnedItemsRow( - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + multiselect_context: MediaItemMultiSelectContext? = null ) { val player: PlayerState = LocalPlayerState.current val pinned_items: List? = rememberPinnedItems() @@ -89,13 +90,9 @@ fun PinnedItemsRow( } } }, - multiselect_context = player.main_multiselect_context, + multiselect_context = multiselect_context, itemSizeProvider = { - val width: Dp = 100.dp - return@MediaItemGrid DpSize( - width, - width + 50.dp - ) + DpSize(100.dp, 100.dp) } ) } diff --git a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/apppage/mainpage/PlayerStateImpl.kt b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/apppage/mainpage/PlayerStateImpl.kt index fead349fa..e7ffd0ff2 100644 --- a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/apppage/mainpage/PlayerStateImpl.kt +++ b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/apppage/mainpage/PlayerStateImpl.kt @@ -3,6 +3,7 @@ package com.toasterofbread.spmp.ui.layout.apppage.mainpage import LocalPlayerState import SpMp import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.Spring import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.spring import androidx.compose.animation.fadeIn @@ -19,8 +20,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.* -import androidx.compose.ui.window.Dialog -import com.toasterofbread.composekit.platform.Platform import com.toasterofbread.composekit.platform.PlatformPreferences import com.toasterofbread.composekit.platform.PlatformPreferencesListener import com.toasterofbread.composekit.platform.composable.BackHandler @@ -76,7 +75,7 @@ class PlayerStateImpl(override val context: AppContext, private val coroutine_sc np_swipe_state.value.animateTo( page, when (form_factor) { -// NowPlayingMainTabPage.Mode.LARGE -> spring(Spring.DampingRatioNoBouncy, Spring.StiffnessMediumLow) + FormFactor.LANDSCAPE -> spring(Spring.DampingRatioNoBouncy, Spring.StiffnessMediumLow) else -> spring() } ) @@ -101,8 +100,7 @@ class PlayerStateImpl(override val context: AppContext, private val coroutine_sc override val app_page_state = AppPageState(this) override val main_multiselect_context: MediaItemMultiSelectContext = AppPageMultiSelectContext(this) - override var np_theme_mode: ThemeMode by mutableStateOf( - Settings.getEnum(ThemeSettings.Key.NOWPLAYING_THEME_MODE, context.getPrefs())) + override var np_theme_mode: ThemeMode by mutableStateOf(Settings.getEnum(ThemeSettings.Key.NOWPLAYING_THEME_MODE, context.getPrefs())) override val np_overlay_menu: MutableState = mutableStateOf(null) override val top_bar: MusicTopBar = MusicTopBar(this) diff --git a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/apppage/mainpage/RootView.kt b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/apppage/mainpage/RootView.kt index 4cba415c8..cf7ec49db 100644 --- a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/apppage/mainpage/RootView.kt +++ b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/apppage/mainpage/RootView.kt @@ -5,9 +5,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Density @@ -21,8 +19,6 @@ val MINIMISED_NOW_PLAYING_HEIGHT_DP: Float @Composable get() = LocalPlayerState.current.form_factor.getMinimisedPlayerHeight().value val MINIMISED_NOW_PLAYING_V_PADDING_DP: Float @Composable get() = LocalPlayerState.current.form_factor.getMinimisedPlayerVPadding().value -const val MEDIAITEM_PREVIEW_SQUARE_SIZE_SMALL: Float = 100f -const val MEDIAITEM_PREVIEW_SQUARE_SIZE_LARGE: Float = 200f @Composable fun RootView(player: PlayerStateImpl) { @@ -40,24 +36,10 @@ fun RootView(player: PlayerStateImpl) { } ) - val focus_requester: FocusRequester = remember { FocusRequester() } - Column( Modifier .fillMaxSize() .background(player.theme.background_provider) -// .pointerInput(Unit) { -// while (currentCoroutineContext().isActive) { -// awaitPointerEventScope { -// val event = awaitPointerEvent(PointerEventPass.Final) -// if (event.type == PointerEventType.Press && (event.buttons.isPrimaryPressed || event.buttons.isSecondaryPressed || event.buttons.isTertiaryPressed)) { -// println(event.changes.any { it.isConsumed }) -// focus_requester.requestFocus() -// } -// } -// } -// } -// .focusRequester(focus_requester) ) { player.HomePage() player.NowPlaying() diff --git a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/apppage/settingspage/SettingsAppPage.kt b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/apppage/settingspage/SettingsAppPage.kt index 8d9586eb1..20ca7010f 100644 --- a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/apppage/settingspage/SettingsAppPage.kt +++ b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/apppage/settingspage/SettingsAppPage.kt @@ -112,7 +112,8 @@ class SettingsAppPage(override val state: AppPageState, footer_modifier: Modifie pill_menu.PillMenu() Column(Modifier.fillMaxSize().padding(horizontal = PREFS_PAGE_EXTRA_PADDING_DP.dp)) { - val top_padding: Dp = player.top_bar.MusicTopBar( + val top_padding: Dp = 0.dp + player.top_bar.MusicTopBar( TopBarSettings.Key.SHOW_IN_SETTINGS, Modifier.fillMaxWidth().zIndex(10f), getBottomBorderColour = player.theme.background_provider, diff --git a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/apppage/songfeedpage/SFFSongFeedAppPage.kt b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/apppage/songfeedpage/SFFSongFeedAppPage.kt index f45bbf687..6b53e645c 100644 --- a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/apppage/songfeedpage/SFFSongFeedAppPage.kt +++ b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/apppage/songfeedpage/SFFSongFeedAppPage.kt @@ -4,29 +4,13 @@ import LocalPlayerState import androidx.compose.animation.Crossfade import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.tween -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.KeyboardDoubleArrowDown -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.material3.* +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer @@ -48,6 +32,7 @@ import com.toasterofbread.spmp.resources.uilocalisation.LocalisedString import com.toasterofbread.spmp.ui.component.multiselect.MediaItemMultiSelectContext import com.toasterofbread.spmp.ui.layout.PinnedItemsRow import com.toasterofbread.spmp.ui.layout.apppage.mainpage.FeedLoadState +import com.toasterofbread.spmp.ui.layout.apppage.mainpage.PlayerState import com.toasterofbread.spmp.youtubeapi.NotImplementedMessage @Composable @@ -67,7 +52,7 @@ fun SongFeedAppPage.SFFSongFeedAppPage( loadFeed(false) } - val player = LocalPlayerState.current + val player: PlayerState = LocalPlayerState.current val artists_layout: MediaItemLayout = remember { MediaItemLayout( mutableStateListOf(), @@ -121,8 +106,8 @@ fun SongFeedAppPage.SFFSongFeedAppPage( indicator = false, modifier = Modifier.fillMaxSize() ) { - val target_state = if (load_state == FeedLoadState.LOADING || load_state == FeedLoadState.PREINIT) null else layouts ?: false - var current_state by remember { mutableStateOf(target_state) } + val target_state: Any? = if (load_state == FeedLoadState.LOADING || load_state == FeedLoadState.PREINIT) null else layouts ?: false + var current_state: Any? by remember { mutableStateOf(target_state) } val state_alpha = remember { Animatable(1f) } LaunchedEffect(target_state) { @@ -144,7 +129,7 @@ fun SongFeedAppPage.SFFSongFeedAppPage( @Composable fun TopContent(modifier: Modifier = Modifier) { - PinnedItemsRow(modifier.padding(bottom = 10.dp)) + PinnedItemsRow(modifier, multiselect_context = multiselect_context) } var hiding_layout: MediaItemLayout? by remember { mutableStateOf(null) } @@ -182,15 +167,15 @@ fun SongFeedAppPage.SFFSongFeedAppPage( ) } - val state = current_state + val state: Any? = current_state when (state) { // Loaded is List<*> -> { - val onContinuationRequested = if (continuation != null) { - { loadFeed(true) } - } else null - val loading_continuation = load_state != FeedLoadState.NONE + val onContinuationRequested: (() -> Unit)? = + if (continuation != null) {{ loadFeed(true) }} + else null + val loading_continuation: Boolean = load_state != FeedLoadState.NONE val horizontal_padding: PaddingValues = content_padding.horizontal LazyColumn( @@ -205,7 +190,7 @@ fun SongFeedAppPage.SFFSongFeedAppPage( item { if (artists_layout.items.isNotEmpty()) { - artists_layout.Layout(multiselect_context = player.main_multiselect_context, apply_filter = true, content_padding = horizontal_padding) + artists_layout.Layout(multiselect_context = multiselect_context, apply_filter = true, content_padding = horizontal_padding) } } @@ -240,7 +225,7 @@ fun SongFeedAppPage.SFFSongFeedAppPage( } } ), - multiselect_context = player.main_multiselect_context, + multiselect_context = multiselect_context, apply_filter = true, square_item_max_text_rows = square_item_max_text_rows, show_download_indicators = show_download_indicators, diff --git a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/NowPlaying.kt b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/NowPlaying.kt index 80b0ebb51..d748379e3 100644 --- a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/NowPlaying.kt +++ b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/NowPlaying.kt @@ -47,7 +47,17 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch -enum class ThemeMode { BACKGROUND, ELEMENTS, NONE } +enum class ThemeMode { + BACKGROUND, ELEMENTS, NONE; + + companion object { + val DEFAULT: ThemeMode = + when (Platform.current) { + Platform.ANDROID -> BACKGROUND + Platform.DESKTOP -> NONE + } + } +} fun getNowPlayingVerticalPageCount(player: PlayerState): Int = NowPlayingPage.ALL.count { it.shouldShow(player) } @@ -62,13 +72,13 @@ val SwipeableState.actualCurrentValue: Int get() = if (direction < 0) progr val SwipeableState.actualTargetValue: Int get() = if (direction < 0) progress.from else progress.to @OptIn(ExperimentalMaterialApi::class) -private fun PlayerState.getBackgroundColourOverride(theme_mode: ThemeMode): Color { +private fun PlayerState.getBackgroundColourOverride(): Color { val pages: List = NowPlayingPage.ALL.filter { it.shouldShow(this) } var current: Color? = pages.getOrNull(expansion.swipe_state.actualCurrentValue - 1)?.getPlayerBackgroundColourOverride(this) var target: Color? = pages.getOrNull(expansion.swipe_state.actualTargetValue - 1)?.getPlayerBackgroundColourOverride(this) - val default: Color = when (theme_mode) { + val default: Color = when (np_theme_mode) { ThemeMode.BACKGROUND -> theme.accent ThemeMode.ELEMENTS -> theme.card ThemeMode.NONE -> theme.card @@ -90,15 +100,15 @@ private fun PlayerState.getBackgroundColourOverride(theme_mode: ThemeMode): Colo private var derived_np_background: State? = null -internal fun PlayerState.getNPBackground(theme_mode: ThemeMode = np_theme_mode): Color { +internal fun PlayerState.getNPBackground(): Color { if (derived_np_background == null) { - derived_np_background = derivedStateOf { getBackgroundColourOverride(theme_mode) } + derived_np_background = derivedStateOf { getBackgroundColourOverride() } } return derived_np_background!!.value } internal fun PlayerState.getNPOnBackground(): Color { - return getBackgroundColourOverride(np_theme_mode).getContrasted() + return getBackgroundColourOverride().getContrasted() // val override: Color? = getBackgroundColourOverride()?.getPlayerBackgroundColourOverride(this) // if (override != null) { // return override.getContrasted() @@ -118,8 +128,8 @@ internal fun PlayerState.getNPAltBackground(theme_mode: ThemeMode = np_theme_mod } } -internal fun PlayerState.getNPAltOnBackground(theme_mode: ThemeMode = np_theme_mode): Color = - getNPBackground(theme_mode).amplifyPercent(-0.4f, opposite_percent = -0.1f) +internal fun PlayerState.getNPAltOnBackground(): Color = + getNPBackground().amplifyPercent(-0.4f, opposite_percent = -0.1f) @OptIn(ExperimentalMaterialApi::class) @Composable @@ -229,8 +239,9 @@ fun NowPlaying(swipe_state: SwipeableState, swipe_anchors: Map, val song_gradient_depth: Float? = player.status.m_song?.PlayerGradientDepth?.observe(player.database)?.value + val large_form_factor: Boolean = player.form_factor.is_large - val swipe_modifier: Modifier = remember(swipe_anchors) { + val swipe_modifier: Modifier = remember(swipe_anchors, large_form_factor) { Modifier.swipeable( state = swipe_state, anchors = swipe_anchors, @@ -238,7 +249,7 @@ fun NowPlaying(swipe_state: SwipeableState, swipe_anchors: Map, orientation = Orientation.Vertical, reverseDirection = true, interactionSource = swipe_interaction_source, - enabled = !Platform.DESKTOP.isCurrent() + enabled = !large_form_factor ) } diff --git a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/maintab/Controls.kt b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/maintab/Controls.kt index a20facefc..e8c989020 100644 --- a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/maintab/Controls.kt +++ b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/maintab/Controls.kt @@ -4,14 +4,7 @@ import LocalPlayerState import androidx.compose.foundation.Canvas import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.requiredSize +import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Pause import androidx.compose.material.icons.rounded.PlayArrow @@ -131,13 +124,15 @@ internal fun Controls( artist_font_size: TextUnit = ARTIST_FONT_SIZE_SP.sp, text_align: TextAlign = TextAlign.Center, title_text_max_lines: Int = 1, + button_size: Dp = 60.dp, getBackgroundColour: PlayerState.() -> Color = { getNPBackground() }, getOnBackgroundColour: PlayerState.() -> Color = { getNPOnBackground() }, getAccentColour: (PlayerState.() -> Color)? = null, buttonRowStartContent: @Composable RowScope.() -> Unit = {}, buttonRowEndContent: @Composable RowScope.() -> Unit = {}, artistRowStartContent: @Composable RowScope.() -> Unit = {}, - artistRowEndContent: @Composable RowScope.() -> Unit = {} + artistRowEndContent: @Composable RowScope.() -> Unit = {}, + textRowStartContent: @Composable RowScope.() -> Unit = {} ) { val player: PlayerState = LocalPlayerState.current @@ -155,60 +150,66 @@ internal fun Controls( } Column(modifier, verticalArrangement = vertical_arrangement) { - Column(verticalArrangement = Arrangement.spacedBy(5.dp)) { - Marquee(Modifier.fillMaxWidth(), disable = disable_text_marquees) { - Text( - song_title ?: "", - fontSize = title_font_size, - color = getOnBackgroundColour(player), - textAlign = text_align, - maxLines = title_text_max_lines, - // Using ellipsis makes this go weird, no clue why - overflow = TextOverflow.Clip, - modifier = Modifier - .fillMaxWidth() - .platformClickable( - enabled = enabled, - onAltClick = { - show_title_edit_dialog = !show_title_edit_dialog - player.context.vibrateShort() - } - ) - ) - } - Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { - artistRowStartContent() - Spacer(Modifier) - Text( - song_artist_title ?: "", - fontSize = artist_font_size, - color = getOnBackgroundColour(player).copy(alpha = 0.5f), - textAlign = text_align, - maxLines = 1, - softWrap = false, -// overflow = TextOverflow.Ellipsis, - modifier = Modifier - .fillMaxWidth() - .platformClickable( - enabled = enabled, - onClick = { - val artist: Artist? = song?.Artist?.get(player.database) - if (artist?.isForItem() == false) { - player.onMediaItemClicked(artist) - } - }, - onAltClick = { - val artist: Artist? = song?.Artist?.get(player.database) - if (artist?.isForItem() == false) { - player.onMediaItemLongClicked(artist) + Row(verticalAlignment = Alignment.CenterVertically) { + textRowStartContent() + + Column(verticalArrangement = Arrangement.spacedBy(5.dp)) { + Marquee(Modifier.fillMaxWidth(), disable = disable_text_marquees) { + Text( + song_title ?: "", + fontSize = title_font_size, + color = getOnBackgroundColour(player), + textAlign = text_align, + maxLines = title_text_max_lines, + // Using ellipsis makes this go weird, no clue why + overflow = TextOverflow.Clip, + modifier = Modifier + .fillMaxWidth() + .platformClickable( + enabled = enabled, + onAltClick = { + show_title_edit_dialog = !show_title_edit_dialog player.context.vibrateShort() } - } - ) - ) - Spacer(Modifier) - artistRowEndContent() + ) + ) + } + + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + artistRowStartContent() + Spacer(Modifier) + Text( + song_artist_title ?: "", + fontSize = artist_font_size, + color = getOnBackgroundColour(player).copy(alpha = 0.5f), + textAlign = text_align, + maxLines = 1, + softWrap = false, + // Using ellipsis makes this go weird, no clue why + overflow = TextOverflow.Clip, + modifier = Modifier + .fillMaxWidth() + .platformClickable( + enabled = enabled, + onClick = { + val artist: Artist? = song?.Artist?.get(player.database) + if (artist?.isForItem() == false) { + player.onMediaItemClicked(artist) + } + }, + onAltClick = { + val artist: Artist? = song?.Artist?.get(player.database) + if (artist?.isForItem() == false) { + player.onMediaItemLongClicked(artist) + player.context.vibrateShort() + } + } + ) + ) + Spacer(Modifier) + artistRowEndContent() + } } } @@ -231,7 +232,7 @@ internal fun Controls( PlayerButton( Icons.Rounded.SkipPrevious, enabled = enabled && player.status.m_has_previous, - size = 60.dp, + size = button_size, getBackgroundColour = getBackgroundColour, getOnBackgroundColour = getOnBackgroundColour, getAccentColour = getAccentColour @@ -243,7 +244,7 @@ internal fun Controls( PlayerButton( if (player.status.m_playing) Icons.Rounded.Pause else Icons.Rounded.PlayArrow, enabled = enabled && song != null, - size = 75.dp, + size = button_size + 15.dp, getBackgroundColour = getBackgroundColour, getOnBackgroundColour = getOnBackgroundColour, getAccentColour = getAccentColour @@ -255,7 +256,7 @@ internal fun Controls( PlayerButton( Icons.Rounded.SkipNext, enabled = enabled && player.status.m_has_next, - size = 60.dp, + size = button_size, getBackgroundColour = getBackgroundColour, getOnBackgroundColour = getOnBackgroundColour, getAccentColour = getAccentColour @@ -264,7 +265,13 @@ internal fun Controls( } if (seek_bar_next_to_buttons) { - SeekBar(seek, Modifier.fillMaxWidth().weight(1f), getColour = getOnBackgroundColour, getTrackColour = getSeekBarTrackColour, enabled = enabled) + SeekBar( + seek, + Modifier.fillMaxWidth().weight(1f).padding(start = 10.dp), + getColour = getOnBackgroundColour, + getTrackColour = getSeekBarTrackColour, + enabled = enabled + ) } buttonRowEndContent() diff --git a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/maintab/LargeBottomBar.kt b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/maintab/LargeBottomBar.kt index 19cf087f5..da7c6c56e 100644 --- a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/maintab/LargeBottomBar.kt +++ b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/maintab/LargeBottomBar.kt @@ -1,54 +1,94 @@ package com.toasterofbread.spmp.ui.layout.nowplaying.maintab import LocalPlayerState -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.animation.Crossfade +import androidx.compose.foundation.layout.* import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material3.LocalContentColor import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.toasterofbread.composekit.utils.common.getValue +import com.toasterofbread.composekit.utils.common.thenIf import com.toasterofbread.composekit.utils.modifier.bounceOnClick +import com.toasterofbread.spmp.model.mediaitem.loader.SongLyricsLoader import com.toasterofbread.spmp.model.mediaitem.song.Song -import com.toasterofbread.spmp.ui.component.LikeDislikeButton +import com.toasterofbread.spmp.ui.component.LyricsLineDisplay import com.toasterofbread.spmp.ui.layout.apppage.mainpage.PlayerState import com.toasterofbread.spmp.ui.theme.appHover -import com.toasterofbread.spmp.youtubeapi.YoutubeApi @Composable -internal fun LargeBottomBar(modifier: Modifier = Modifier) { +internal fun LargeBottomBar( + modifier: Modifier = Modifier, + inset_start: Dp = Dp.Unspecified, + inset_end: Dp = Dp.Unspecified, + inset_depth: Dp = 0.dp +) { val player: PlayerState = LocalPlayerState.current - val auth_state: YoutubeApi.UserAuthState? = player.context.ytapi.user_auth_state val current_song: Song? by player.status.song_state val button_colour: Color = LocalContentColor.current.copy(alpha = 0.5f) - Row( - modifier, - verticalAlignment = Alignment.CenterVertically - ) { - if (auth_state != null) { - current_song?.also { song -> - LikeDislikeButton(song, auth_state) { button_colour } - } - } - - Spacer(Modifier.fillMaxWidth().weight(1f)) - + CompositionLocalProvider(LocalContentColor provides button_colour) { Row( - Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End, + modifier, verticalAlignment = Alignment.CenterVertically ) { - IconButton({ player.expansion.close() }, Modifier.bounceOnClick().appHover(true)) { - Icon(Icons.Default.KeyboardArrowDown, null, tint = button_colour) + Row( + Modifier + .thenIf( + inset_depth > 0.dp, + elseAction = { + width(IntrinsicSize.Min) + } + ) { + align(Alignment.Top) + .offset(x = inset_start, y = inset_depth) + .width(inset_end - inset_start) + }, + verticalAlignment = Alignment.CenterVertically + ) { + NowPlayingMainTabActionButtons.OpenExternalButton(current_song) + NowPlayingMainTabActionButtons.LikeDislikeButton(current_song) + + Spacer(Modifier.fillMaxWidth().weight(1f)) + + NowPlayingMainTabActionButtons.DownloadButton(current_song) + NowPlayingMainTabActionButtons.RadioButton(current_song) + } + + Row( + Modifier.fillMaxWidth().weight(1f), + verticalAlignment = Alignment.CenterVertically + ) { + val lyrics_state: SongLyricsLoader.ItemState? = remember(current_song?.id) { current_song?.let { SongLyricsLoader.getItemState(it, player.context) } } + val lyrics_sync_offset: Long? by current_song?.getLyricsSyncOffset(player.database, false) + + Crossfade(lyrics_state?.lyrics, Modifier.fillMaxWidth().weight(1f)) { lyrics -> + if (lyrics == null) { + return@Crossfade + } + + LyricsLineDisplay( + lyrics = lyrics, + getTime = { + (player.controller?.current_position_ms ?: 0) + (lyrics_sync_offset ?: 0) + } + ) + } + + IconButton({ player.expansion.close() }, Modifier.bounceOnClick().appHover(true)) { + Icon(Icons.Default.KeyboardArrowDown, null, tint = button_colour) + } } } } diff --git a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/maintab/NowPlayingMainTabActionButtons.kt b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/maintab/NowPlayingMainTabActionButtons.kt new file mode 100644 index 000000000..dc60920ed --- /dev/null +++ b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/maintab/NowPlayingMainTabActionButtons.kt @@ -0,0 +1,117 @@ +package com.toasterofbread.spmp.ui.layout.nowplaying.maintab + +import LocalPlayerState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Download +import androidx.compose.material.icons.rounded.OpenInNew +import androidx.compose.material.icons.rounded.Radio +import androidx.compose.material.icons.rounded.Shuffle +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.minimumInteractiveComponentSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import com.toasterofbread.composekit.utils.modifier.bounceOnClick +import com.toasterofbread.spmp.model.mediaitem.song.Song +import com.toasterofbread.spmp.ui.component.LikeDislikeButton +import com.toasterofbread.spmp.ui.layout.apppage.mainpage.PlayerState +import com.toasterofbread.spmp.ui.theme.appHover +import com.toasterofbread.spmp.youtubeapi.YoutubeApi + +internal object NowPlayingMainTabActionButtons { + @Composable + fun LikeDislikeButton(song: Song?, modifier: Modifier = Modifier, colour: Color = LocalContentColor.current) { + if (song == null) { + return + } + + val player: PlayerState = LocalPlayerState.current + val auth_state: YoutubeApi.UserAuthState? = player.context.ytapi.user_auth_state + if (auth_state != null) { + LikeDislikeButton( + song, + auth_state, + modifier.minimumInteractiveComponentSize(), + getColour = { colour } + ) + } + } + + @Composable + fun RadioButton(song: Song?, modifier: Modifier = Modifier, colour: Color = LocalContentColor.current) { + val player: PlayerState = LocalPlayerState.current + + IconButton( + { + if (song != null) { + player.withPlayer { + undoableAction { + startRadioAtIndex(current_song_index + 1, song, current_song_index, skip_first = true) + } + } + player.expansion.scrollTo(2.coerceIn(player.expansion.getPageRange())) + } + }, + modifier.bounceOnClick().appHover(true) + ) { + Icon(Icons.Rounded.Radio, null, tint = colour) + } + } + + @Composable + fun ShuffleButton(modifier: Modifier = Modifier, colour: Color = LocalContentColor.current) { + val player: PlayerState = LocalPlayerState.current + + IconButton( + { + player.withPlayer { + undoableAction { + shuffleQueue(start = current_song_index + 1) + } + } + }, + modifier.bounceOnClick().appHover(true) + ) { + Icon(Icons.Rounded.Shuffle, null, tint = colour) + } + } + + @Composable + fun OpenExternalButton(song: Song?, modifier: Modifier = Modifier) { + val player: PlayerState = LocalPlayerState.current + if (!player.context.canOpenUrl()) { + return + } + + IconButton( + { + if (song == null) { + return@IconButton + } + player.context.openUrl(song.getURL(player.context)) + }, + modifier.bounceOnClick().appHover(true) + ) { + Icon(Icons.Rounded.OpenInNew, null) + } + } + + @Composable + fun DownloadButton(song: Song?, modifier: Modifier = Modifier) { + val player: PlayerState = LocalPlayerState.current + + IconButton( + { + if (song == null) { + return@IconButton + } + player.onSongDownloadRequested(song, null) + }, + modifier.bounceOnClick().appHover(true) + ) { + Icon(Icons.Rounded.Download, null) + } + } +} diff --git a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/maintab/NowPlayingMainTabLarge.kt b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/maintab/NowPlayingMainTabLarge.kt index dfb3bbd06..7e1eb5085 100644 --- a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/maintab/NowPlayingMainTabLarge.kt +++ b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/maintab/NowPlayingMainTabLarge.kt @@ -2,42 +2,18 @@ package com.toasterofbread.spmp.ui.layout.nowplaying.maintab import LocalNowPlayingExpansion import LocalPlayerState -import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.Canvas import androidx.compose.foundation.border import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxWithConstraints -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.calculateEndPadding -import androidx.compose.foundation.layout.calculateStartPadding -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.requiredHeight -import androidx.compose.foundation.layout.requiredSize -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.LocalContentColor -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clipToBounds -import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.draw.scale import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color @@ -47,29 +23,20 @@ import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.positionInParent -import androidx.compose.ui.layout.positionInWindow +import androidx.compose.ui.layout.positionInRoot import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.unit.coerceAtLeast -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.lerp -import androidx.compose.ui.unit.sp -import androidx.compose.ui.unit.times +import androidx.compose.ui.unit.* import androidx.compose.ui.zIndex import com.github.krottv.compose.sliders.lerp import com.toasterofbread.composekit.platform.composable.composeScope -import com.toasterofbread.composekit.utils.common.amplify -import com.toasterofbread.composekit.utils.common.blendWith -import com.toasterofbread.composekit.utils.common.getContrasted -import com.toasterofbread.composekit.utils.common.thenIf -import com.toasterofbread.composekit.utils.common.toFloat +import com.toasterofbread.composekit.utils.common.* import com.toasterofbread.composekit.utils.composable.getTop +import com.toasterofbread.spmp.model.mediaitem.MediaItemThumbnailProvider +import com.toasterofbread.spmp.model.mediaitem.song.Song import com.toasterofbread.spmp.model.settings.category.NowPlayingQueueWaveBorderMode +import com.toasterofbread.spmp.ui.component.Thumbnail import com.toasterofbread.spmp.ui.layout.apppage.mainpage.MINIMISED_NOW_PLAYING_HEIGHT_DP import com.toasterofbread.spmp.ui.layout.apppage.mainpage.MINIMISED_NOW_PLAYING_V_PADDING_DP import com.toasterofbread.spmp.ui.layout.apppage.mainpage.PlayerState @@ -82,6 +49,7 @@ import com.toasterofbread.spmp.ui.layout.nowplaying.NowPlayingTopBar import com.toasterofbread.spmp.ui.layout.nowplaying.ThemeMode import com.toasterofbread.spmp.ui.layout.nowplaying.getNPAltBackground import com.toasterofbread.spmp.ui.layout.nowplaying.maintab.thumbnailrow.LargeThumbnailRow +import com.toasterofbread.spmp.ui.layout.nowplaying.overlay.DEFAULT_THUMBNAIL_ROUNDING import com.toasterofbread.spmp.ui.layout.nowplaying.queue.QueueTab import kotlin.math.absoluteValue import kotlin.math.roundToInt @@ -90,10 +58,12 @@ import kotlin.math.roundToInt private fun MainTabControls( onSeek: (Float) -> Unit, enabled: Boolean, - modifier: Modifier = Modifier + button_size: Dp, + seek_bar_next_to_buttons: Boolean, + modifier: Modifier = Modifier, + textRowStartContent: @Composable RowScope.() -> Unit = {} ) { val player: PlayerState = LocalPlayerState.current - val expansion: NowPlayingExpansionState = LocalNowPlayingExpansion.current Controls( player.status.m_song, @@ -109,11 +79,11 @@ private fun MainTabControls( title_text_max_lines = 2, title_font_size = 25.sp, artist_font_size = 18.sp, - seek_bar_next_to_buttons = true, + button_size = button_size, + seek_bar_next_to_buttons = seek_bar_next_to_buttons, + button_row_arrangement = Arrangement.Start, text_align = TextAlign.Start, - getBackgroundColour = { theme.background }, - getOnBackgroundColour = { theme.on_background }, - getAccentColour = { theme.accent } + textRowStartContent = textRowStartContent ) } @@ -146,10 +116,9 @@ internal fun NowPlayingMainTabPage.NowPlayingMainTabLarge(page_height: Dp, top_b val top_padding: Dp = top_padding val bottom_padding: Dp = bottom_padding - val bottom_bar_height: Dp = (horizontal_padding * 2) + bottom_padding + val bottom_bar_height: Dp = MINIMISED_NOW_PLAYING_HEIGHT_DP.dp val inner_bottom_padding: Dp = horizontal_padding - var controls_y_position: Float by remember { mutableStateOf(0f) } var thumbnail_y_position: Float by remember { mutableStateOf(0f) } val bar_background_colour: Color = player.theme.card @@ -172,10 +141,34 @@ internal fun NowPlayingMainTabPage.NowPlayingMainTabLarge(page_height: Dp, top_b verticalAlignment = Alignment.Top, horizontalArrangement = Arrangement.spacedBy(5.dp) ) { - val extra_width: Dp = 0.dp//(page_width / 2) - page_height val parent_max_width: Dp = this@BoxWithConstraints.maxWidth val thumb_size: Dp = minOf(height, parent_max_width * 0.5f) - (inner_padding) + var actual_thumb_size: DpSize by remember { mutableStateOf(DpSize.Zero) } + var actual_thumb_position: DpOffset by remember { mutableStateOf(DpOffset.Zero) } + + val controls_target_height: Dp = 200.dp + var controls_height: Dp by remember { mutableStateOf(0.dp) } + + val column_min_width: Dp = 300.dp + val column_width: Dp = + lerp( + parent_max_width, + maxOf( + column_min_width, + minOf( + this@BoxWithConstraints.maxHeight - controls_height, + thumb_size + ) + ), + absolute_expansion + ) + + val current_thumb_size: Dp = this@BoxWithConstraints.maxHeight - controls_height + val compact_mode: Boolean = absolute_expansion > 0.9f && ( + (current_thumb_size / column_width) < 0.75f + ) + Box(Modifier.requiredSize(0.dp).zIndex(1f)) { Box( Modifier @@ -192,7 +185,7 @@ internal fun NowPlayingMainTabPage.NowPlayingMainTabLarge(page_height: Dp, top_b } } ) { - Canvas(Modifier.fillMaxSize()) { + Canvas(Modifier.fillMaxSize().offset(x = (-0.5).dp, y = 3.dp)) { drawLine( stroke_colour, start = Offset.Zero, @@ -204,11 +197,23 @@ internal fun NowPlayingMainTabPage.NowPlayingMainTabLarge(page_height: Dp, top_b } CompositionLocalProvider(LocalContentColor provides bar_background_colour.getContrasted()) { + var bottom_bar_position: DpOffset by remember { mutableStateOf(DpOffset.Zero) } + val inset_depth: Dp = (actual_thumb_position.y + actual_thumb_size.height - bottom_bar_position.y).coerceAtLeast(0.dp) + LargeBottomBar( Modifier .align(Alignment.CenterEnd) .fillMaxWidth() .padding(horizontal = 15.dp) + .onGloballyPositioned { + bottom_bar_position = with (density) { + val position: Offset = it.positionInRoot() + DpOffset(position.x.toDp(), position.y.toDp()) + } + }, + inset_start = actual_thumb_position.x - bottom_bar_position.x, + inset_end = actual_thumb_position.x + actual_thumb_size.width - bottom_bar_position.x, + inset_depth = if (compact_mode) 0.dp else inset_depth ) } } @@ -222,44 +227,67 @@ internal fun NowPlayingMainTabPage.NowPlayingMainTabLarge(page_height: Dp, top_b BoxWithConstraints( Modifier.fillMaxHeight().weight(1f) ) { - val controls_height: Dp = 200.dp - Column( - Modifier.width( - lerp( - parent_max_width, - minOf( - this@BoxWithConstraints.maxHeight - controls_height, - thumb_size - ), - absolute_expansion - ) - ), + Modifier.width(column_width), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { + Spacer(Modifier.requiredHeight(inner_bottom_padding).weight(1f, false)) + Spacer(Modifier.fillMaxHeight().weight(1f)) + MainTabControls( { seek_state = it }, - expanded, - Modifier - .requiredHeight(controls_height * ((absolute_expansion - 0.5f) * 2f)) - .onGloballyPositioned { - controls_y_position = it.positionInParent().y + enabled = expanded, + button_size = 50.dp, + seek_bar_next_to_buttons = !compact_mode, + modifier = Modifier + .thenIf(!compact_mode) { + requiredHeight(controls_target_height * ((absolute_expansion - 0.5f) * 2f)) } .scale(1f, absolute_expansion) .padding(bottom = 20.dp) + .onSizeChanged { + controls_height = with (density) { + it.height.toDp() + } + }, + textRowStartContent = { + if (compact_mode) { + val song: Song? by player.status.song_state + + val thumbnail_rounding: Int? by song?.ThumbnailRounding?.observe(player.context.database) + val thumbnail_shape: RoundedCornerShape = RoundedCornerShape(thumbnail_rounding ?: DEFAULT_THUMBNAIL_ROUNDING) + + song?.Thumbnail( + MediaItemThumbnailProvider.Quality.HIGH, + Modifier + .padding(end = 10.dp) + .size(controls_target_height - 100.dp) + .clip(thumbnail_shape) + ) + } + } ) + val thumbnail_row_height: Dp = + if (compact_mode) (1f - absolute_expansion) * thumb_size + else thumb_size + LargeThumbnailRow( Modifier - .height(thumb_size) - .padding(start = (extra_width * absolute_expansion / 2).coerceAtLeast(0.dp)) + .height(thumbnail_row_height) + .offset { + IntOffset( + (((column_width - minOf(current_thumb_size, thumbnail_row_height)).toPx() / 2) * absolute_expansion).roundToInt(), + 0 + ) + } .onGloballyPositioned { thumbnail_y_position = with (density) {( it.positionInParent().y - + lerp(-controls_height.toPx() / 2f, 0f, 1f - absolute_expansion) + + lerp(-controls_target_height.toPx() / 2f, 0f, 1f - absolute_expansion) - 50.dp.toPx() )} }, @@ -270,8 +298,19 @@ internal fun NowPlayingMainTabPage.NowPlayingMainTabLarge(page_height: Dp, top_b setThemeColour(it, true) }, getSeekState = { seek_state }, - disable_parent_scroll_while_menu_open = false + disable_parent_scroll_while_menu_open = false, + thumbnail_modifier = Modifier.onGloballyPositioned { + with (density) { + val position: Offset = it.positionInRoot() + actual_thumb_position = DpOffset(position.x.toDp(), position.y.toDp()) + actual_thumb_size = DpSize(it.size.width.toDp(), it.size.height.toDp()) + } + } ) + + Spacer(Modifier.requiredHeight(animateDpAsState(inner_bottom_padding * compact_mode.toInt()).value).weight(1f, false)) + + Spacer(Modifier.fillMaxHeight().weight(1f)) } } @@ -279,7 +318,7 @@ internal fun NowPlayingMainTabPage.NowPlayingMainTabLarge(page_height: Dp, top_b } } - val controls_height: Dp = page_height - top_padding - bottom_padding - inner_bottom_padding + val current_controls_height: Dp = page_height - top_padding - bottom_padding - inner_bottom_padding Box( Modifier @@ -299,7 +338,7 @@ internal fun NowPlayingMainTabPage.NowPlayingMainTabLarge(page_height: Dp, top_b .offset { IntOffset( ((1f - player.expansion.getBounded()) * page_height).roundToPx() / 2, - (controls_height * (1f - player.expansion.getBounded())).roundToPx() + (current_controls_height * (1f - player.expansion.getBounded())).roundToPx() ) } .graphicsLayer { @@ -320,12 +359,6 @@ internal fun NowPlayingMainTabPage.NowPlayingMainTabLarge(page_height: Dp, top_b Modifier .fillMaxHeight() .weight(1f) - .offset { - IntOffset( - 0, - controls_y_position.roundToInt() - ) - } .thenIf(player.np_theme_mode != ThemeMode.BACKGROUND) { border( stroke_width, @@ -334,12 +367,12 @@ internal fun NowPlayingMainTabPage.NowPlayingMainTabLarge(page_height: Dp, top_b ) }, inline = true, - border_thickness = stroke_width, + border_thickness = stroke_width + 1.dp, wave_border_mode_override = NowPlayingQueueWaveBorderMode.SCROLL, shape = queue_shape, - button_row_arrangement = Arrangement.spacedBy(5.dp), + button_row_arrangement = Arrangement.spacedBy(5.dp, Alignment.CenterHorizontally), content_padding = PaddingValues( - bottom = (inner_bottom_padding + with (density) { controls_y_position.toDp() }).coerceAtLeast(0.dp) + bottom = inner_bottom_padding.coerceAtLeast(0.dp) ), getBackgroundColour = { getNPAltBackground() @@ -347,14 +380,13 @@ internal fun NowPlayingMainTabPage.NowPlayingMainTabLarge(page_height: Dp, top_b // else theme.background }, getOnBackgroundColour = { - theme.accent.blendWith(theme.background, 0.01f) -// when (player.np_theme_mode) { -// ThemeMode.BACKGROUND -> getNPBackground() -// ThemeMode.ELEMENTS -> theme.accent -// ThemeMode.NONE -> theme.on_background -// } + when (player.np_theme_mode) { + ThemeMode.BACKGROUND -> theme.vibrant_accent + ThemeMode.ELEMENTS -> theme.accent + ThemeMode.NONE -> theme.on_background + } }, - getWaveBorderColour = { stroke_colour } +// getWaveBorderColour = { stroke_colour } ) } } diff --git a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/maintab/NowPlayingMainTabPage.kt b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/maintab/NowPlayingMainTabPage.kt index 2ea2af172..682581a0f 100644 --- a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/maintab/NowPlayingMainTabPage.kt +++ b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/maintab/NowPlayingMainTabPage.kt @@ -14,7 +14,6 @@ import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.toasterofbread.composekit.platform.Platform -import com.toasterofbread.composekit.utils.common.blendWith import com.toasterofbread.composekit.utils.common.getThemeColour import com.toasterofbread.spmp.model.mediaitem.song.Song import com.toasterofbread.spmp.platform.FormFactor @@ -28,14 +27,18 @@ private const val NARROW_PLAYER_MAX_SIZE_DP: Float = 120f fun FormFactor.getMinimisedPlayerHeight(): Dp = when (this) { - FormFactor.LANDSCAPE -> 80.dp - else -> 64.dp + FormFactor.PORTRAIT -> 64.dp + FormFactor.LANDSCAPE -> + if (Platform.DESKTOP.isCurrent()) 80.dp + else 70.dp } fun FormFactor.getMinimisedPlayerVPadding(): Dp = when (this) { - FormFactor.LANDSCAPE -> 10.dp - else -> 7.dp + FormFactor.PORTRAIT -> 7.dp + FormFactor.LANDSCAPE -> + if (Platform.DESKTOP.isCurrent()) 10.dp + else 8.dp } class NowPlayingMainTabPage: NowPlayingPage() { @@ -83,9 +86,9 @@ class NowPlayingMainTabPage: NowPlayingPage() { override fun shouldShow(player: PlayerState): Boolean = true override fun getPlayerBackgroundColourOverride(player: PlayerState): Color? { - if (Platform.DESKTOP.isCurrent()) { - return player.theme.accent.blendWith(player.theme.background, 0.05f) - } +// if (Platform.DESKTOP.isCurrent()) { +// return player.theme.accent.blendWith(player.theme.background, 0.05f) +// } return null } diff --git a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/maintab/NowPlayingMainTabPortrait.kt b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/maintab/NowPlayingMainTabPortrait.kt index 5e9baa428..adce14e41 100644 --- a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/maintab/NowPlayingMainTabPortrait.kt +++ b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/maintab/NowPlayingMainTabPortrait.kt @@ -44,7 +44,6 @@ import com.toasterofbread.composekit.platform.composable.composeScope import com.toasterofbread.composekit.utils.modifier.bounceOnClick import com.toasterofbread.spmp.model.mediaitem.song.Song import com.toasterofbread.spmp.model.settings.category.PlayerSettings -import com.toasterofbread.spmp.ui.component.LikeDislikeButton import com.toasterofbread.spmp.ui.layout.apppage.mainpage.MINIMISED_NOW_PLAYING_HEIGHT_DP import com.toasterofbread.spmp.ui.layout.apppage.mainpage.MINIMISED_NOW_PLAYING_V_PADDING_DP import com.toasterofbread.spmp.ui.layout.apppage.mainpage.PlayerState @@ -57,7 +56,6 @@ import com.toasterofbread.spmp.ui.layout.nowplaying.getNPBackground import com.toasterofbread.spmp.ui.layout.nowplaying.getNPOnBackground import com.toasterofbread.spmp.ui.layout.nowplaying.maintab.thumbnailrow.SmallThumbnailRow import com.toasterofbread.spmp.ui.layout.nowplaying.queue.RepeatButton -import com.toasterofbread.spmp.youtubeapi.YoutubeApi import kotlin.math.absoluteValue internal const val MINIMISED_NOW_PLAYING_HORIZ_PADDING: Float = 10f @@ -141,37 +139,14 @@ internal fun NowPlayingMainTabPage.NowPlayingMainTabPortrait(page_height: Dp, to .padding(end = side_button_padding) .then(button_modifier) ) { - current_song?.let { song -> - val auth_state: YoutubeApi.UserAuthState? = player.context.ytapi.user_auth_state - if (auth_state != null) { - LikeDislikeButton( - song, - auth_state, - getColour = { player.getNPOnBackground() } - ) - } - } + NowPlayingMainTabActionButtons.LikeDislikeButton(current_song, button_modifier, colour = player.getNPOnBackground()) } }, buttonRowEndContent = { Box( contentAlignment = Alignment.CenterEnd ) { - IconButton( - { - current_song?.let { song -> - player.withPlayer { - undoableAction { - startRadioAtIndex(current_song_index + 1, song, current_song_index, skip_first = true) - } - } - player.expansion.scrollTo(2) - } - }, - button_modifier.padding(start = side_button_padding).bounceOnClick() - ) { - Icon(Icons.Rounded.Radio, null, tint = player.getNPOnBackground()) - } + NowPlayingMainTabActionButtons.RadioButton(current_song, button_modifier.padding(start = side_button_padding).bounceOnClick()) } }, artistRowStartContent = { @@ -184,18 +159,7 @@ internal fun NowPlayingMainTabPage.NowPlayingMainTabPortrait(page_height: Dp, to }, artistRowEndContent = { if (show_shuffle_repeat_buttons) { - IconButton( - { - player.withPlayer { - undoableAction { - shuffleQueue(start = current_song_index + 1) - } - } - }, - button_modifier - ) { - Icon(Icons.Rounded.Shuffle, null) - } + NowPlayingMainTabActionButtons.ShuffleButton(button_modifier, colour = player.getNPOnBackground()) } else { Spacer(Modifier.height(40.dp)) diff --git a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/maintab/thumbnailrow/LargeThumbnailRow.kt b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/maintab/thumbnailrow/LargeThumbnailRow.kt index ab62b21c2..a2e7df0b2 100644 --- a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/maintab/thumbnailrow/LargeThumbnailRow.kt +++ b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/maintab/thumbnailrow/LargeThumbnailRow.kt @@ -100,11 +100,11 @@ fun LargeThumbnailRow( val song_title: String? by current_song?.observeActiveTitle() val song_artist_title: String? by current_song?.Artist?.observePropertyActiveTitle() - val thumbnail_rounding: Int? = current_song?.ThumbnailRounding?.observe(player.context.database)?.value + val thumbnail_rounding: Int? by current_song?.ThumbnailRounding?.observe(player.context.database) + val thumbnail_shape: RoundedCornerShape = RoundedCornerShape(thumbnail_rounding ?: DEFAULT_THUMBNAIL_ROUNDING) var overlay_menu: PlayerOverlayMenu? by player.np_overlay_menu var current_thumb_image: ImageBitmap? by remember { mutableStateOf(null) } - val thumbnail_shape: RoundedCornerShape = RoundedCornerShape(thumbnail_rounding ?: DEFAULT_THUMBNAIL_ROUNDING) var image_size: IntSize by remember { mutableStateOf(IntSize(1, 1)) } var colourpick_callback: ColourpickCallback? by remember { mutableStateOf(null) } diff --git a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/overlay/lyrics/CoreLyricsDisplay.kt b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/overlay/lyrics/CoreLyricsDisplay.kt index d3fdceeeb..0dd7c1105 100644 --- a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/overlay/lyrics/CoreLyricsDisplay.kt +++ b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/overlay/lyrics/CoreLyricsDisplay.kt @@ -32,11 +32,7 @@ import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.compose.ui.unit.times +import androidx.compose.ui.unit.* import com.toasterofbread.composekit.platform.composable.platformClickable import com.toasterofbread.composekit.utils.common.thenIf import com.toasterofbread.composekit.utils.composable.SubtleLoadingIndicator @@ -45,7 +41,9 @@ import com.toasterofbread.spmp.model.mediaitem.song.Song import com.toasterofbread.spmp.model.settings.Settings import com.toasterofbread.spmp.model.settings.category.LyricsSettings import com.toasterofbread.spmp.ui.component.AnnotatedReadingTerm +import com.toasterofbread.spmp.ui.component.ReadingTextData import com.toasterofbread.spmp.ui.component.calculateReadingsAnnotatedString +import com.toasterofbread.spmp.ui.layout.apppage.mainpage.PlayerState import com.toasterofbread.spmp.ui.layout.nowplaying.NOW_PLAYING_MAIN_PADDING_DP import kotlinx.coroutines.delay @@ -60,22 +58,22 @@ fun CoreLyricsDisplay( enable_autoscroll: Boolean = true, getOnLongClick: () -> ((line_data: Pair>) -> Unit)? ) { - val player = LocalPlayerState.current - val density = LocalDensity.current + val player: PlayerState = LocalPlayerState.current + val density: Density = LocalDensity.current val lyrics_sync_offset: Long? by song.getLyricsSyncOffset(player.database, false) var area_size: Dp by remember { mutableStateOf(0.dp) } - val size_px = with(LocalDensity.current) { ((area_size - (NOW_PLAYING_MAIN_PADDING_DP.dp * 2) - (15.dp * getExpansion() * 2)).value * 0.9.dp).toPx() } - val line_height = with (LocalDensity.current) { 20.sp.toPx() } - val line_spacing = with (LocalDensity.current) { 25.dp.toPx() } + val size_px: Float = with(LocalDensity.current) { ((area_size - (NOW_PLAYING_MAIN_PADDING_DP.dp * 2) - (15.dp * getExpansion() * 2)).value * 0.9.dp).toPx() } + val line_height: Float = with (LocalDensity.current) { 20.sp.toPx() } + val line_spacing: Float = with (LocalDensity.current) { 25.dp.toPx() } val add_padding: Boolean = Settings.get(LyricsSettings.Key.EXTRA_PADDING) - val static_scroll_offset = with(LocalDensity.current) { 2.dp.toPx().toInt() } - val padding_height = + val static_scroll_offset: Int = with(LocalDensity.current) { 2.dp.toPx().toInt() } + val padding_height: Int = if (add_padding) (size_px + line_height + line_spacing).toInt() + static_scroll_offset else line_height.toInt() + static_scroll_offset - val terms = remember(lyrics) { lyrics.getReadingTerms() } + val terms: MutableList = remember(lyrics) { lyrics.getReadingTerms() } var current_range: IntRange? by remember { mutableStateOf(null) } fun getScrollOffset(follow_offset: Float = LyricsSettings.Key.FOLLOW_OFFSET.get()): Int = diff --git a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/queue/QueueTab.kt b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/queue/QueueTab.kt index 8310362e2..1f5f35fa9 100644 --- a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/queue/QueueTab.kt +++ b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/nowplaying/queue/QueueTab.kt @@ -388,7 +388,8 @@ private fun QueueBorder( } }, border_thickness = border_thickness, - border_colour = getBorderColour(player) + border_colour = getBorderColour(player), + width_multiplier = 2f ) } } diff --git a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/youtubeapi/formats/NewPipeVideoFormatsEndpoint.kt b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/youtubeapi/formats/NewPipeVideoFormatsEndpoint.kt index 946df3f60..c682607e1 100644 --- a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/youtubeapi/formats/NewPipeVideoFormatsEndpoint.kt +++ b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/youtubeapi/formats/NewPipeVideoFormatsEndpoint.kt @@ -11,8 +11,10 @@ import org.schabi.newpipe.extractor.downloader.Request import org.schabi.newpipe.extractor.downloader.Response import org.schabi.newpipe.extractor.exceptions.ParsingException import org.schabi.newpipe.extractor.exceptions.ReCaptchaException +import org.schabi.newpipe.extractor.linkhandler.LinkHandler import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeStreamLinkHandlerFactory import org.schabi.newpipe.extractor.stream.AudioStream +import org.schabi.newpipe.extractor.stream.StreamExtractor import org.schabi.newpipe.extractor.stream.StreamInfo import org.schabi.newpipe.extractor.stream.VideoStream import java.io.IOException @@ -28,27 +30,27 @@ class NewPipeVideoFormatsEndpoint(override val api: YoutubeApi): VideoFormatsEnd return YoutubeVideoFormat(itag, format!!.mimeType, bitrate, url = content) } - override suspend fun getVideoFormats(id: String, filter: ((YoutubeVideoFormat) -> Boolean)?): Result> { + val link_handler: LinkHandler = YoutubeStreamLinkHandlerFactory.getInstance().fromId(id) + val youtube_stream_extractor: StreamExtractor = NewPipe.getService(ServiceList.YouTube.serviceId).getStreamExtractor(link_handler) - val linkHandler = YoutubeStreamLinkHandlerFactory.getInstance().fromId(id) - val youtubeStreamExtractor = NewPipe.getService(ServiceList.YouTube.serviceId).getStreamExtractor(linkHandler) - - val streamInfo = try { - StreamInfo.getInfo(youtubeStreamExtractor) - } catch (e: ParsingException) { + val stream_info: StreamInfo + try { + stream_info = StreamInfo.getInfo(youtube_stream_extractor) + } + catch (e: ParsingException) { return Result.failure(e) } - val filteredAudio = streamInfo.audioStreams + val audio_streams: List = stream_info.audioStreams .map { it.toYoutubeVideoFormat() } .filter { filter?.invoke(it) ?: true } - val filteredVideo = streamInfo.videoStreams + val video_streams: List = stream_info.videoStreams .map { it.toYoutubeVideoFormat() } .filter { filter?.invoke(it) ?: true } - return Result.success(filteredAudio + filteredVideo) + return Result.success(audio_streams + video_streams) } } diff --git a/shared/src/commonMain/resources/assets/values-ja-JP/strings.xml b/shared/src/commonMain/resources/assets/values-ja-JP/strings.xml index 39da2fd23..3f364d381 100644 --- a/shared/src/commonMain/resources/assets/values-ja-JP/strings.xml +++ b/shared/src/commonMain/resources/assets/values-ja-JP/strings.xml @@ -198,7 +198,7 @@ プレイヤーサービスとの接続がありません バグが発生してる可能性があります サーバーコマンドの実行中にエラーが発生しました - $xのサーバーに接続中 + $x のサーバーに接続中 初期状態の準備中 接続設定 サーバーの接続設定 diff --git a/shared/src/desktopMain/kotlin/com/toasterofbread/spmp/platform/playerservice/tryConnectToServer.kt b/shared/src/desktopMain/kotlin/com/toasterofbread/spmp/platform/playerservice/tryConnectToServer.kt index 2dce0ab88..1b8cc7c38 100644 --- a/shared/src/desktopMain/kotlin/com/toasterofbread/spmp/platform/playerservice/tryConnectToServer.kt +++ b/shared/src/desktopMain/kotlin/com/toasterofbread/spmp/platform/playerservice/tryConnectToServer.kt @@ -26,7 +26,7 @@ internal suspend fun ZmqSpMsPlayerService.tryConnectToServer( setLoadState( PlayerServiceLoadState( true, - getString("desktop_splash_connecting_to_server_at_\$x").replace("\$x", server_url) + getString("desktop_splash_connecting_to_server_at_\$x").replace("\$x", server_url.split("://", limit = 2).last()) ) )