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" }