diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7a12b47bd..67285df4e 100755 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -123,6 +123,7 @@ dependencies { implementation(libs.material3) implementation(libs.palette) implementation(projects.materialColorUtilities) + implementation(libs.squigglyslider) implementation(libs.accompanist.swiperefresh) diff --git a/app/src/main/java/com/zionhuang/music/constants/PreferenceKeys.kt b/app/src/main/java/com/zionhuang/music/constants/PreferenceKeys.kt index 2fbeece33..84a6849a5 100644 --- a/app/src/main/java/com/zionhuang/music/constants/PreferenceKeys.kt +++ b/app/src/main/java/com/zionhuang/music/constants/PreferenceKeys.kt @@ -9,6 +9,12 @@ val DynamicThemeKey = booleanPreferencesKey("dynamicTheme") val DarkModeKey = stringPreferencesKey("darkMode") val PureBlackKey = booleanPreferencesKey("pureBlack") val DefaultOpenTabKey = stringPreferencesKey("defaultOpenTab") +val PlayerTextAlignmentKey = stringPreferencesKey("playerTextAlignment") +val SliderStyleKey = stringPreferencesKey("sliderStyle") + +enum class SliderStyle { + DEFAULT, SQUIGGLY +} const val SYSTEM_DEFAULT = "SYSTEM_DEFAULT" val ContentLanguageKey = stringPreferencesKey("contentLanguage") @@ -110,7 +116,6 @@ enum class AlbumFilter { } val ShowLyricsKey = booleanPreferencesKey("showLyrics") -val PlayerTextAlignmentKey = stringPreferencesKey("playerTextAlignment") val TranslateLyricsKey = booleanPreferencesKey("translateLyrics") val PlayerVolumeKey = floatPreferencesKey("playerVolume") diff --git a/app/src/main/java/com/zionhuang/music/ui/player/Player.kt b/app/src/main/java/com/zionhuang/music/ui/player/Player.kt index 0ba8fe5ec..b8d79f6da 100644 --- a/app/src/main/java/com/zionhuang/music/ui/player/Player.kt +++ b/app/src/main/java/com/zionhuang/music/ui/player/Player.kt @@ -27,6 +27,7 @@ import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Slider @@ -68,6 +69,8 @@ import com.zionhuang.music.constants.PlayerHorizontalPadding import com.zionhuang.music.constants.PlayerTextAlignmentKey import com.zionhuang.music.constants.PureBlackKey import com.zionhuang.music.constants.QueuePeekHeight +import com.zionhuang.music.constants.SliderStyle +import com.zionhuang.music.constants.SliderStyleKey import com.zionhuang.music.extensions.togglePlayPause import com.zionhuang.music.extensions.toggleRepeatMode import com.zionhuang.music.models.MediaMetadata @@ -82,7 +85,9 @@ import com.zionhuang.music.utils.rememberEnumPreference import com.zionhuang.music.utils.rememberPreference import kotlinx.coroutines.delay import kotlinx.coroutines.isActive +import me.saket.squiggles.SquigglySlider +@OptIn(ExperimentalMaterial3Api::class) @Composable fun BottomSheetPlayer( state: BottomSheetState, @@ -105,6 +110,7 @@ fun BottomSheetPlayer( } val playerTextAlignment by rememberEnumPreference(PlayerTextAlignmentKey, PlayerTextAlignment.CENTER) + val sliderStyle by rememberEnumPreference(SliderStyleKey, SliderStyle.DEFAULT) val playbackState by playerConnection.playbackState.collectAsState() val isPlaying by playerConnection.isPlaying.collectAsState() @@ -219,22 +225,47 @@ fun BottomSheetPlayer( Spacer(Modifier.height(12.dp)) - Slider( - value = (sliderPosition ?: position).toFloat(), - valueRange = 0f..(if (duration == C.TIME_UNSET) 0f else duration.toFloat()), - onValueChange = { - sliderPosition = it.toLong() - }, - onValueChangeFinished = { - sliderPosition?.let { - playerConnection.player.seekTo(it) - position = it - } - sliderPosition = null - }, - modifier = Modifier.padding(horizontal = PlayerHorizontalPadding) - ) + when (sliderStyle) { + SliderStyle.DEFAULT -> { + Slider( + value = (sliderPosition ?: position).toFloat(), + valueRange = 0f..(if (duration == C.TIME_UNSET) 0f else duration.toFloat()), + onValueChange = { + sliderPosition = it.toLong() + }, + onValueChangeFinished = { + sliderPosition?.let { + playerConnection.player.seekTo(it) + position = it + } + sliderPosition = null + }, + modifier = Modifier.padding(horizontal = PlayerHorizontalPadding) + ) + } + SliderStyle.SQUIGGLY -> { + SquigglySlider( + value = (sliderPosition ?: position).toFloat(), + valueRange = 0f..(if (duration == C.TIME_UNSET) 0f else duration.toFloat()), + onValueChange = { + sliderPosition = it.toLong() + }, + onValueChangeFinished = { + sliderPosition?.let { + playerConnection.player.seekTo(it) + position = it + } + sliderPosition = null + }, + squigglesSpec = SquigglySlider.SquigglesSpec( + amplitude = if (isPlaying) 2.dp else 0.dp, + strokeWidth = 4.dp, + ), + modifier = Modifier.padding(horizontal = PlayerHorizontalPadding), + ) + } + } Spacer(Modifier.height(4.dp)) Row( diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/settings/AppearanceSettings.kt b/app/src/main/java/com/zionhuang/music/ui/screens/settings/AppearanceSettings.kt index 549fc51b9..8369b4be5 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/settings/AppearanceSettings.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/settings/AppearanceSettings.kt @@ -1,21 +1,41 @@ package com.zionhuang.music.ui.screens.settings import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Slider import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +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.draw.clip +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import androidx.navigation.NavController import com.zionhuang.music.LocalPlayerAwareWindowInsets import com.zionhuang.music.R @@ -24,12 +44,17 @@ import com.zionhuang.music.constants.DefaultOpenTabKey import com.zionhuang.music.constants.DynamicThemeKey import com.zionhuang.music.constants.PlayerTextAlignmentKey import com.zionhuang.music.constants.PureBlackKey +import com.zionhuang.music.constants.SliderStyle +import com.zionhuang.music.constants.SliderStyleKey +import com.zionhuang.music.ui.component.DefaultDialog import com.zionhuang.music.ui.component.EnumListPreference import com.zionhuang.music.ui.component.IconButton +import com.zionhuang.music.ui.component.PreferenceEntry import com.zionhuang.music.ui.component.SwitchPreference import com.zionhuang.music.ui.utils.backToMain import com.zionhuang.music.utils.rememberEnumPreference import com.zionhuang.music.utils.rememberPreference +import me.saket.squiggles.SquigglySlider @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -42,12 +67,105 @@ fun AppearanceSettings( val (pureBlack, onPureBlackChange) = rememberPreference(PureBlackKey, defaultValue = false) val (defaultOpenTab, onDefaultOpenTabChange) = rememberEnumPreference(DefaultOpenTabKey, defaultValue = NavigationTab.HOME) val (playerTextAlignment, onPlayerTextAlignmentChange) = rememberEnumPreference(PlayerTextAlignmentKey, defaultValue = PlayerTextAlignment.CENTER) + val (sliderStyle, onSliderStyleChange) = rememberEnumPreference(SliderStyleKey, defaultValue = SliderStyle.DEFAULT) val isSystemInDarkTheme = isSystemInDarkTheme() val useDarkTheme = remember(darkMode, isSystemInDarkTheme) { if (darkMode == DarkMode.AUTO) isSystemInDarkTheme else darkMode == DarkMode.ON } + var showSliderOptionDialog by rememberSaveable { + mutableStateOf(false) + } + + if (showSliderOptionDialog) { + DefaultDialog( + buttons = { + TextButton( + onClick = { showSliderOptionDialog = false } + ) { + Text(text = stringResource(android.R.string.cancel)) + } + }, + onDismiss = { + showSliderOptionDialog = false + } + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier + .aspectRatio(1f) + .weight(1f) + .clip(RoundedCornerShape(16.dp)) + .border(1.dp, if (sliderStyle == SliderStyle.DEFAULT) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outlineVariant, RoundedCornerShape(16.dp)) + .clickable { + onSliderStyleChange(SliderStyle.DEFAULT) + showSliderOptionDialog = false + } + .padding(16.dp) + ) { + var sliderValue by remember { + mutableFloatStateOf(0.5f) + } + Slider( + value = sliderValue, + valueRange = 0f..1f, + onValueChange = { + sliderValue = it + }, + modifier = Modifier + .weight(1f) + .pointerInput(Unit) { + detectTapGestures( + onPress = {} + ) + } + ) + + Text( + text = stringResource(R.string.default_), + style = MaterialTheme.typography.labelLarge + ) + } + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier + .aspectRatio(1f) + .weight(1f) + .clip(RoundedCornerShape(16.dp)) + .border(1.dp, if (sliderStyle == SliderStyle.SQUIGGLY) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outlineVariant, RoundedCornerShape(16.dp)) + .clickable { + onSliderStyleChange(SliderStyle.SQUIGGLY) + showSliderOptionDialog = false + } + .padding(16.dp) + ) { + var sliderValue by remember { + mutableFloatStateOf(0.5f) + } + SquigglySlider( + value = sliderValue, + valueRange = 0f..1f, + onValueChange = { + sliderValue = it + }, + modifier = Modifier.weight(1f) + ) + + Text( + text = stringResource(R.string.squiggly), + style = MaterialTheme.typography.labelLarge + ) + } + } + } + } + Column( Modifier .windowInsetsPadding(LocalPlayerAwareWindowInsets.current) @@ -107,6 +225,30 @@ fun AppearanceSettings( } } ) + + PreferenceEntry( + title = { Text(stringResource(R.string.player_slider_style)) }, + description = when (sliderStyle) { + SliderStyle.DEFAULT -> stringResource(R.string.default_) + SliderStyle.SQUIGGLY -> stringResource(R.string.squiggly) + }, + icon = { Icon(painterResource(R.drawable.sliders), null) }, + onClick = { + showSliderOptionDialog = true + } + ) +// EnumListPreference( +// title = { Text(stringResource(R.string.slider_style)) }, +// icon = { Icon(painterResource(R.drawable.sliders), null) }, +// selectedValue = sliderStyle, +// onValueSelected = onSliderStyleChange, +// valueText = { +// when (it) { +// SliderStyle.DEFAULT -> stringResource(R.string.default_) +// SliderStyle.SQUIGGLY -> stringResource(R.string.squiggly) +// } +// } +// ) } TopAppBar( diff --git a/app/src/main/res/drawable/sliders.xml b/app/src/main/res/drawable/sliders.xml new file mode 100644 index 000000000..329999e60 --- /dev/null +++ b/app/src/main/res/drawable/sliders.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 3a5de5fa4..67175fbe0 100755 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -212,6 +212,9 @@ 靠左 置中 靠右 + 播放器滑桿樣式 + 預設 + 波浪 內容 登入 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6540e5b12..ba4eeca81 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -220,6 +220,9 @@ Left Center Right + Player slider style + Default + Squiggly Content Login diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 28987ff27..bcfb46de3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -39,6 +39,7 @@ viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel- material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" } material3-windowsize = { group = "androidx.compose.material3", name = "material3-window-size-class", version.ref = "material3" } +squigglyslider = { group = "me.saket.squigglyslider", name = "squigglyslider", version = "1.0.0" } accompanist-swiperefresh = { group = "com.google.accompanist", name = "accompanist-swiperefresh", version = "0.28.0" }