Redesign SettingsScreen (#172)
This commit is contained in:
@@ -84,6 +84,7 @@ dependencies {
|
|||||||
implementation(libs.compose.ripple)
|
implementation(libs.compose.ripple)
|
||||||
implementation(libs.compose.shimmer)
|
implementation(libs.compose.shimmer)
|
||||||
implementation(libs.compose.coil)
|
implementation(libs.compose.coil)
|
||||||
|
implementation(libs.compose.viewmodel)
|
||||||
|
|
||||||
implementation(libs.palette)
|
implementation(libs.palette)
|
||||||
|
|
||||||
@@ -97,6 +98,4 @@ dependencies {
|
|||||||
implementation(projects.kugou)
|
implementation(projects.kugou)
|
||||||
|
|
||||||
coreLibraryDesugaring(libs.desugaring)
|
coreLibraryDesugaring(libs.desugaring)
|
||||||
|
|
||||||
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1")
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,106 +0,0 @@
|
|||||||
package it.vfsfitvnm.vimusic.ui.components.themed
|
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
|
||||||
import androidx.compose.foundation.layout.IntrinsicSize
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.layout.widthIn
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.foundation.text.BasicText
|
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.draw.shadow
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
|
||||||
import it.vfsfitvnm.vimusic.utils.medium
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun DropDownSection(content: @Composable ColumnScope.() -> Unit) {
|
|
||||||
val (colorPalette) = LocalAppearance.current
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.shadow(
|
|
||||||
elevation = 2.dp,
|
|
||||||
shape = RoundedCornerShape(16.dp)
|
|
||||||
)
|
|
||||||
.background(colorPalette.background1)
|
|
||||||
.width(IntrinsicSize.Max),
|
|
||||||
content = content
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun DropDownSectionSpacer() {
|
|
||||||
Spacer(
|
|
||||||
modifier = Modifier
|
|
||||||
.height(4.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun DropDownTextItem(
|
|
||||||
text: String,
|
|
||||||
isSelected: Boolean,
|
|
||||||
onClick: () -> Unit
|
|
||||||
) {
|
|
||||||
val (colorPalette) = LocalAppearance.current
|
|
||||||
|
|
||||||
DropDownTextItem(
|
|
||||||
text = text,
|
|
||||||
textColor = if (isSelected) {
|
|
||||||
colorPalette.onAccent
|
|
||||||
} else {
|
|
||||||
colorPalette.textSecondary
|
|
||||||
},
|
|
||||||
backgroundColor = if (isSelected) {
|
|
||||||
colorPalette.accent
|
|
||||||
} else {
|
|
||||||
colorPalette.background1
|
|
||||||
},
|
|
||||||
onClick = onClick
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun DropDownTextItem(
|
|
||||||
text: String,
|
|
||||||
backgroundColor: Color? = null,
|
|
||||||
textColor: Color? = null,
|
|
||||||
onClick: () -> Unit
|
|
||||||
) {
|
|
||||||
val (colorPalette, typography) = LocalAppearance.current
|
|
||||||
|
|
||||||
BasicText(
|
|
||||||
text = text,
|
|
||||||
style = typography.xxs.medium.copy(
|
|
||||||
color = textColor ?: colorPalette.text,
|
|
||||||
letterSpacing = 1.sp
|
|
||||||
),
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(RoundedCornerShape(16.dp))
|
|
||||||
.clickable(
|
|
||||||
indication = rememberRipple(bounded = true),
|
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
|
||||||
onClick = onClick
|
|
||||||
)
|
|
||||||
.background(backgroundColor ?: colorPalette.background1)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.widthIn(min = 124.dp, max = 248.dp)
|
|
||||||
.padding(
|
|
||||||
horizontal = 16.dp,
|
|
||||||
vertical = 8.dp
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,205 +0,0 @@
|
|||||||
package it.vfsfitvnm.vimusic.ui.components.themed
|
|
||||||
|
|
||||||
import androidx.compose.animation.core.LinearOutSlowInEasing
|
|
||||||
import androidx.compose.animation.core.MutableTransitionState
|
|
||||||
import androidx.compose.animation.core.animateFloat
|
|
||||||
import androidx.compose.animation.core.tween
|
|
||||||
import androidx.compose.animation.core.updateTransition
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.Immutable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.TransformOrigin
|
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.unit.Density
|
|
||||||
import androidx.compose.ui.unit.DpOffset
|
|
||||||
import androidx.compose.ui.unit.IntOffset
|
|
||||||
import androidx.compose.ui.unit.IntRect
|
|
||||||
import androidx.compose.ui.unit.IntSize
|
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.window.Popup
|
|
||||||
import androidx.compose.ui.window.PopupPositionProvider
|
|
||||||
import androidx.compose.ui.window.PopupProperties
|
|
||||||
import kotlin.math.max
|
|
||||||
import kotlin.math.min
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun DropdownMenu(
|
|
||||||
isDisplayed: Boolean,
|
|
||||||
onDismissRequest: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
offset: DpOffset = DpOffset(0.dp, 0.dp),
|
|
||||||
properties: PopupProperties = PopupProperties(focusable = true),
|
|
||||||
content: @Composable ColumnScope.() -> Unit
|
|
||||||
) {
|
|
||||||
val expandedStates = remember {
|
|
||||||
MutableTransitionState(false)
|
|
||||||
}.apply { targetState = isDisplayed }
|
|
||||||
|
|
||||||
if (expandedStates.currentState || expandedStates.targetState) {
|
|
||||||
val density = LocalDensity.current
|
|
||||||
|
|
||||||
var transformOrigin by remember {
|
|
||||||
mutableStateOf(TransformOrigin.Center)
|
|
||||||
}
|
|
||||||
|
|
||||||
val popupPositionProvider =
|
|
||||||
DropdownMenuPositionProvider(offset, density) { parentBounds, menuBounds ->
|
|
||||||
transformOrigin = calculateTransformOrigin(parentBounds, menuBounds)
|
|
||||||
}
|
|
||||||
|
|
||||||
Popup(
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
popupPositionProvider = popupPositionProvider,
|
|
||||||
properties = properties
|
|
||||||
) {
|
|
||||||
DropdownMenuContent(
|
|
||||||
expandedStates = expandedStates,
|
|
||||||
transformOrigin = transformOrigin,
|
|
||||||
modifier = modifier,
|
|
||||||
content = content
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
internal fun DropdownMenuContent(
|
|
||||||
expandedStates: MutableTransitionState<Boolean>,
|
|
||||||
transformOrigin: TransformOrigin,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
content: @Composable ColumnScope.() -> Unit
|
|
||||||
) {
|
|
||||||
val transition = updateTransition(expandedStates, "DropDownMenu")
|
|
||||||
|
|
||||||
val scale by transition.animateFloat(
|
|
||||||
transitionSpec = {
|
|
||||||
if (false isTransitioningTo true) {
|
|
||||||
// Dismissed to expanded
|
|
||||||
tween(
|
|
||||||
durationMillis = 128,
|
|
||||||
easing = LinearOutSlowInEasing
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// Expanded to dismissed.
|
|
||||||
tween(
|
|
||||||
durationMillis = 64,
|
|
||||||
delayMillis = 64
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}, label = ""
|
|
||||||
) { isDisplayed ->
|
|
||||||
if (isDisplayed) 1f else 0.9f
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = modifier
|
|
||||||
.graphicsLayer {
|
|
||||||
scaleX = scale
|
|
||||||
scaleY = scale
|
|
||||||
this.transformOrigin = transformOrigin
|
|
||||||
},
|
|
||||||
content = content,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
private data class DropdownMenuPositionProvider(
|
|
||||||
val contentOffset: DpOffset,
|
|
||||||
val density: Density,
|
|
||||||
val onPositionCalculated: (IntRect, IntRect) -> Unit = { _, _ -> }
|
|
||||||
) : PopupPositionProvider {
|
|
||||||
override fun calculatePosition(
|
|
||||||
anchorBounds: IntRect,
|
|
||||||
windowSize: IntSize,
|
|
||||||
layoutDirection: LayoutDirection,
|
|
||||||
popupContentSize: IntSize
|
|
||||||
): IntOffset {
|
|
||||||
// The min margin above and below the menu, relative to the screen.
|
|
||||||
val verticalMargin = with(density) { 48.dp.roundToPx() }
|
|
||||||
// The content offset specified using the dropdown offset parameter.
|
|
||||||
val contentOffsetX = with(density) { contentOffset.x.roundToPx() }
|
|
||||||
val contentOffsetY = with(density) { contentOffset.y.roundToPx() }
|
|
||||||
|
|
||||||
// Compute horizontal position.
|
|
||||||
val toRight = anchorBounds.left + contentOffsetX
|
|
||||||
val toLeft = anchorBounds.right - contentOffsetX - popupContentSize.width
|
|
||||||
val toDisplayRight = windowSize.width - popupContentSize.width
|
|
||||||
val toDisplayLeft = 0
|
|
||||||
val x = if (layoutDirection == LayoutDirection.Ltr) {
|
|
||||||
sequenceOf(
|
|
||||||
toRight,
|
|
||||||
toLeft,
|
|
||||||
// If the anchor gets outside of the window on the left, we want to position
|
|
||||||
// toDisplayLeft for proximity to the anchor. Otherwise, toDisplayRight.
|
|
||||||
if (anchorBounds.left >= 0) toDisplayRight else toDisplayLeft
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
sequenceOf(
|
|
||||||
toLeft,
|
|
||||||
toRight,
|
|
||||||
// If the anchor gets outside of the window on the right, we want to position
|
|
||||||
// toDisplayRight for proximity to the anchor. Otherwise, toDisplayLeft.
|
|
||||||
if (anchorBounds.right <= windowSize.width) toDisplayLeft else toDisplayRight
|
|
||||||
)
|
|
||||||
}.firstOrNull {
|
|
||||||
it >= 0 && it + popupContentSize.width <= windowSize.width
|
|
||||||
} ?: toLeft
|
|
||||||
|
|
||||||
// Compute vertical position.
|
|
||||||
val toBottom = maxOf(anchorBounds.bottom + contentOffsetY, verticalMargin)
|
|
||||||
val toTop = anchorBounds.top - contentOffsetY - popupContentSize.height
|
|
||||||
val toCenter = anchorBounds.top - popupContentSize.height / 2
|
|
||||||
val toDisplayBottom = windowSize.height - popupContentSize.height - verticalMargin
|
|
||||||
val y = sequenceOf(toBottom, toTop, toCenter, toDisplayBottom).firstOrNull {
|
|
||||||
it >= verticalMargin &&
|
|
||||||
it + popupContentSize.height <= windowSize.height - verticalMargin
|
|
||||||
} ?: toTop
|
|
||||||
|
|
||||||
onPositionCalculated(
|
|
||||||
anchorBounds,
|
|
||||||
IntRect(x, y, x + popupContentSize.width, y + popupContentSize.height)
|
|
||||||
)
|
|
||||||
return IntOffset(x, y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun calculateTransformOrigin(
|
|
||||||
parentBounds: IntRect,
|
|
||||||
menuBounds: IntRect
|
|
||||||
): TransformOrigin {
|
|
||||||
val pivotX = when {
|
|
||||||
menuBounds.left >= parentBounds.right -> 0f
|
|
||||||
menuBounds.right <= parentBounds.left -> 1f
|
|
||||||
menuBounds.width == 0 -> 0f
|
|
||||||
else -> {
|
|
||||||
val intersectionCenter =
|
|
||||||
(
|
|
||||||
max(parentBounds.left, menuBounds.left) +
|
|
||||||
min(parentBounds.right, menuBounds.right)
|
|
||||||
) / 2
|
|
||||||
(intersectionCenter - menuBounds.left).toFloat() / menuBounds.width
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val pivotY = when {
|
|
||||||
menuBounds.top >= parentBounds.bottom -> 0f
|
|
||||||
menuBounds.bottom <= parentBounds.top -> 1f
|
|
||||||
menuBounds.height == 0 -> 0f
|
|
||||||
else -> {
|
|
||||||
val intersectionCenter =
|
|
||||||
(
|
|
||||||
max(parentBounds.top, menuBounds.top) +
|
|
||||||
min(parentBounds.bottom, menuBounds.bottom)
|
|
||||||
) / 2
|
|
||||||
(intersectionCenter - menuBounds.top).toFloat() / menuBounds.height
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return TransformOrigin(pivotX, pivotY)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package it.vfsfitvnm.vimusic.ui.components.themed
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
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.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.text.BasicText
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
|
import it.vfsfitvnm.vimusic.utils.medium
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Header(
|
||||||
|
title: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
actionsContent: @Composable RowScope.() -> Unit = {},
|
||||||
|
) {
|
||||||
|
val typography = LocalAppearance.current.typography
|
||||||
|
|
||||||
|
Header(
|
||||||
|
modifier = modifier,
|
||||||
|
titleContent = {
|
||||||
|
BasicText(
|
||||||
|
text = title,
|
||||||
|
style = typography.xxl.medium
|
||||||
|
)
|
||||||
|
},
|
||||||
|
actionsContent = actionsContent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Header(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
titleContent: @Composable ColumnScope.() -> Unit,
|
||||||
|
actionsContent: @Composable RowScope.() -> Unit,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.End,
|
||||||
|
modifier = modifier
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
.height(128.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.height(48.dp),
|
||||||
|
)
|
||||||
|
|
||||||
|
titleContent()
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.height(48.dp),
|
||||||
|
content = actionsContent,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +1,22 @@
|
|||||||
package it.vfsfitvnm.vimusic.ui.screens
|
package it.vfsfitvnm.vimusic.ui.screens
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes
|
|
||||||
import androidx.compose.animation.*
|
import androidx.compose.animation.*
|
||||||
import androidx.compose.foundation.*
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.foundation.text.BasicText
|
import androidx.compose.foundation.text.BasicText
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.graphics.*
|
import androidx.compose.ui.graphics.*
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import it.vfsfitvnm.route.*
|
import it.vfsfitvnm.route.*
|
||||||
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
|
||||||
import it.vfsfitvnm.vimusic.R
|
import it.vfsfitvnm.vimusic.R
|
||||||
import it.vfsfitvnm.vimusic.ui.components.TopAppBar
|
import it.vfsfitvnm.vimusic.ui.components.themed.Scaffold
|
||||||
import it.vfsfitvnm.vimusic.ui.components.badge
|
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.Switch
|
import it.vfsfitvnm.vimusic.ui.components.themed.Switch
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.ValueSelectorDialog
|
import it.vfsfitvnm.vimusic.ui.components.themed.ValueSelectorDialog
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.settings.*
|
import it.vfsfitvnm.vimusic.ui.screens.settings.*
|
||||||
@@ -29,193 +26,39 @@ import it.vfsfitvnm.vimusic.utils.*
|
|||||||
@ExperimentalAnimationApi
|
@ExperimentalAnimationApi
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsScreen() {
|
fun SettingsScreen() {
|
||||||
val scrollState = rememberScrollState()
|
val saveableStateHolder = rememberSaveableStateHolder()
|
||||||
|
|
||||||
RouteHandler(
|
val (tabIndex, onTabChanged) = rememberSaveable {
|
||||||
listenToGlobalEmitter = true,
|
mutableStateOf(0)
|
||||||
transitionSpec = {
|
|
||||||
when (targetState.route) {
|
|
||||||
albumRoute, artistRoute -> fastFade
|
|
||||||
else -> when (initialState.route) {
|
|
||||||
albumRoute, artistRoute -> fastFade
|
|
||||||
null -> leftSlide
|
|
||||||
else -> rightSlide
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
RouteHandler(listenToGlobalEmitter = true) {
|
||||||
) {
|
|
||||||
globalRoutes()
|
globalRoutes()
|
||||||
|
|
||||||
appearanceSettingsRoute {
|
|
||||||
AppearanceSettingsScreen()
|
|
||||||
}
|
|
||||||
|
|
||||||
playerSettingsRoute {
|
|
||||||
PlayerSettingsScreen()
|
|
||||||
}
|
|
||||||
|
|
||||||
backupAndRestoreRoute {
|
|
||||||
BackupAndRestoreScreen()
|
|
||||||
}
|
|
||||||
|
|
||||||
cacheSettingsRoute {
|
|
||||||
CacheSettingsScreen()
|
|
||||||
}
|
|
||||||
|
|
||||||
otherSettingsRoute {
|
|
||||||
OtherSettingsScreen()
|
|
||||||
}
|
|
||||||
|
|
||||||
aboutRoute {
|
|
||||||
AboutScreen()
|
|
||||||
}
|
|
||||||
|
|
||||||
host {
|
host {
|
||||||
val (colorPalette, typography) = LocalAppearance.current
|
Scaffold(
|
||||||
|
topIconButtonId = R.drawable.chevron_back,
|
||||||
var isFirstLaunch by rememberPreference(isFirstLaunchKey, true)
|
onTopIconButtonClick = pop,
|
||||||
|
tabIndex = tabIndex,
|
||||||
Column(
|
onTabChanged = onTabChanged,
|
||||||
modifier = Modifier
|
tabColumnContent = { Item ->
|
||||||
.background(colorPalette.background0)
|
Item(0, "Appearance", R.drawable.color_palette)
|
||||||
.fillMaxSize()
|
Item(1, "Player", R.drawable.play)
|
||||||
.verticalScroll(scrollState)
|
Item(2, "Cache", R.drawable.server)
|
||||||
.padding(LocalPlayerAwarePaddingValues.current)
|
Item(3, "Other", R.drawable.shapes)
|
||||||
) {
|
Item(4, "About", R.drawable.information)
|
||||||
TopAppBar(
|
|
||||||
modifier = Modifier
|
|
||||||
.height(52.dp)
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(R.drawable.chevron_back),
|
|
||||||
contentDescription = null,
|
|
||||||
colorFilter = ColorFilter.tint(colorPalette.text),
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable(onClick = pop)
|
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
|
||||||
.size(24.dp)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
) { currentTabIndex ->
|
||||||
BasicText(
|
saveableStateHolder.SaveableStateProvider(currentTabIndex) {
|
||||||
text = "Settings",
|
when (currentTabIndex) {
|
||||||
style = typography.l.semiBold,
|
0 -> AppearanceSettingsTab()
|
||||||
modifier = Modifier
|
1 -> PlayerSettingsTab()
|
||||||
.padding(start = 48.dp)
|
2 -> CacheSettingsTab()
|
||||||
.padding(all = 16.dp)
|
3 -> OtherSettingsTab()
|
||||||
)
|
4 -> AboutTab()
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun Entry(
|
|
||||||
@DrawableRes icon: Int,
|
|
||||||
color: Color,
|
|
||||||
title: String,
|
|
||||||
description: String,
|
|
||||||
route: Route0,
|
|
||||||
withAlert: Boolean = false,
|
|
||||||
onClick: (() -> Unit)? = null
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable(
|
|
||||||
indication = rememberRipple(bounded = true),
|
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
|
||||||
onClick = {
|
|
||||||
route()
|
|
||||||
onClick?.invoke()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.padding(horizontal = 16.dp, vertical = 12.dp)
|
|
||||||
.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.background(color = color, shape = CircleShape)
|
|
||||||
.size(36.dp)
|
|
||||||
.badge(color = colorPalette.red, isDisplayed = withAlert)
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(icon),
|
|
||||||
contentDescription = null,
|
|
||||||
colorFilter = ColorFilter.tint(colorPalette.text),
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.Center)
|
|
||||||
.size(16.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
) {
|
|
||||||
BasicText(
|
|
||||||
text = title,
|
|
||||||
style = typography.s.semiBold,
|
|
||||||
)
|
|
||||||
|
|
||||||
BasicText(
|
|
||||||
text = description,
|
|
||||||
style = typography.xs.secondary.medium,
|
|
||||||
maxLines = 2
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Entry(
|
|
||||||
color = colorPalette.background2,
|
|
||||||
icon = R.drawable.color_palette,
|
|
||||||
title = "Appearance",
|
|
||||||
description = "Change the colors and shapes",
|
|
||||||
route = appearanceSettingsRoute,
|
|
||||||
)
|
|
||||||
|
|
||||||
Entry(
|
|
||||||
color = colorPalette.background2,
|
|
||||||
icon = R.drawable.play,
|
|
||||||
title = "Player & Audio",
|
|
||||||
description = "Player and audio settings",
|
|
||||||
route = playerSettingsRoute,
|
|
||||||
)
|
|
||||||
|
|
||||||
Entry(
|
|
||||||
color = colorPalette.background2,
|
|
||||||
icon = R.drawable.server,
|
|
||||||
title = "Cache",
|
|
||||||
description = "Manage the used space",
|
|
||||||
route = cacheSettingsRoute
|
|
||||||
)
|
|
||||||
|
|
||||||
Entry(
|
|
||||||
color = colorPalette.background2,
|
|
||||||
icon = R.drawable.save,
|
|
||||||
title = "Backup & Restore",
|
|
||||||
description = "Backup and restore the database",
|
|
||||||
route = backupAndRestoreRoute
|
|
||||||
)
|
|
||||||
|
|
||||||
Entry(
|
|
||||||
color = colorPalette.background2,
|
|
||||||
icon = R.drawable.shapes,
|
|
||||||
title = "Other",
|
|
||||||
description = "Advanced settings",
|
|
||||||
route = otherSettingsRoute,
|
|
||||||
withAlert = isFirstLaunch,
|
|
||||||
onClick = {
|
|
||||||
isFirstLaunch = false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
Entry(
|
|
||||||
color = colorPalette.background2,
|
|
||||||
icon = R.drawable.information,
|
|
||||||
title = "About",
|
|
||||||
description = "App version and social links",
|
|
||||||
route = aboutRoute
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -300,8 +143,8 @@ fun SwitchSettingEntry(
|
|||||||
enabled = isEnabled
|
enabled = isEnabled
|
||||||
)
|
)
|
||||||
.alpha(if (isEnabled) 1f else 0.5f)
|
.alpha(if (isEnabled) 1f else 0.5f)
|
||||||
.padding(start = 24.dp)
|
.padding(start = 16.dp)
|
||||||
.padding(horizontal = 32.dp, vertical = 16.dp)
|
.padding(all = 16.dp)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@@ -332,8 +175,7 @@ fun SettingsEntry(
|
|||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
isEnabled: Boolean = true
|
isEnabled: Boolean = true
|
||||||
) {
|
) {
|
||||||
val (_, typography) = LocalAppearance.current
|
val (colorPalette, typography) = LocalAppearance.current
|
||||||
val (colorPalette) = LocalAppearance.current
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
@@ -344,8 +186,8 @@ fun SettingsEntry(
|
|||||||
enabled = isEnabled
|
enabled = isEnabled
|
||||||
)
|
)
|
||||||
.alpha(if (isEnabled) 1f else 0.5f)
|
.alpha(if (isEnabled) 1f else 0.5f)
|
||||||
.padding(start = 24.dp)
|
.padding(start = 16.dp)
|
||||||
.padding(horizontal = 32.dp, vertical = 16.dp)
|
.padding(all = 16.dp)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
BasicText(
|
BasicText(
|
||||||
@@ -360,22 +202,6 @@ fun SettingsEntry(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun SettingsTitle(
|
|
||||||
text: String,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
val (_, typography) = LocalAppearance.current
|
|
||||||
|
|
||||||
BasicText(
|
|
||||||
text = text,
|
|
||||||
style = typography.m.semiBold,
|
|
||||||
modifier = modifier
|
|
||||||
.padding(start = 40.dp)
|
|
||||||
.padding(all = 16.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsDescription(
|
fun SettingsDescription(
|
||||||
text: String,
|
text: String,
|
||||||
@@ -387,24 +213,8 @@ fun SettingsDescription(
|
|||||||
text = text,
|
text = text,
|
||||||
style = typography.xxs.secondary,
|
style = typography.xxs.secondary,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.padding(start = 56.dp, end = 24.dp)
|
.padding(start = 16.dp)
|
||||||
.padding(bottom = 16.dp)
|
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun SettingsGroupDescription(
|
|
||||||
text: String,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
val (_, typography) = LocalAppearance.current
|
|
||||||
|
|
||||||
BasicText(
|
|
||||||
text = text,
|
|
||||||
style = typography.xxs.secondary,
|
|
||||||
modifier = modifier
|
|
||||||
.padding(start = 56.dp, end = 24.dp)
|
|
||||||
.padding(vertical = 8.dp)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,7 +229,17 @@ fun SettingsEntryGroupText(
|
|||||||
text = title.uppercase(),
|
text = title.uppercase(),
|
||||||
style = typography.xxs.semiBold.copy(colorPalette.accent),
|
style = typography.xxs.semiBold.copy(colorPalette.accent),
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.padding(start = 24.dp, top = 24.dp)
|
.padding(start = 16.dp)
|
||||||
.padding(horizontal = 32.dp)
|
.padding(horizontal = 16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SettingsGroupSpacer(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Spacer(
|
||||||
|
modifier = modifier
|
||||||
|
.height(24.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
package it.vfsfitvnm.vimusic.ui.screens.settings
|
|
||||||
|
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import it.vfsfitvnm.route.RouteHandler
|
|
||||||
import it.vfsfitvnm.vimusic.BuildConfig
|
|
||||||
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
|
||||||
import it.vfsfitvnm.vimusic.R
|
|
||||||
import it.vfsfitvnm.vimusic.ui.components.TopAppBar
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.SettingsDescription
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.SettingsEntry
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.SettingsEntryGroupText
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.SettingsTitle
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.globalRoutes
|
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
|
||||||
|
|
||||||
@ExperimentalAnimationApi
|
|
||||||
@Composable
|
|
||||||
fun AboutScreen() {
|
|
||||||
val scrollState = rememberScrollState()
|
|
||||||
|
|
||||||
RouteHandler(listenToGlobalEmitter = true) {
|
|
||||||
globalRoutes()
|
|
||||||
|
|
||||||
host {
|
|
||||||
val (colorPalette) = LocalAppearance.current
|
|
||||||
val uriHandler = LocalUriHandler.current
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.background(colorPalette.background0)
|
|
||||||
.fillMaxSize()
|
|
||||||
.verticalScroll(scrollState)
|
|
||||||
.padding(LocalPlayerAwarePaddingValues.current)
|
|
||||||
) {
|
|
||||||
TopAppBar(
|
|
||||||
modifier = Modifier
|
|
||||||
.height(52.dp)
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(R.drawable.chevron_back),
|
|
||||||
contentDescription = null,
|
|
||||||
colorFilter = ColorFilter.tint(colorPalette.text),
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable(onClick = pop)
|
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
|
||||||
.size(24.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsTitle(text = "About")
|
|
||||||
|
|
||||||
SettingsDescription(text = "v${BuildConfig.VERSION_NAME}\nby vfsfitvnm")
|
|
||||||
|
|
||||||
SettingsEntryGroupText(title = "SOCIAL")
|
|
||||||
|
|
||||||
SettingsEntry(
|
|
||||||
title = "GitHub",
|
|
||||||
text = "View the source code",
|
|
||||||
onClick = {
|
|
||||||
uriHandler.openUri("https://github.com/vfsfitvnm/ViMusic")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
SettingsEntryGroupText(title = "TROUBLESHOOTING")
|
|
||||||
|
|
||||||
SettingsEntry(
|
|
||||||
title = "Report an issue",
|
|
||||||
text = "You will be redirected to GitHub",
|
|
||||||
onClick = {
|
|
||||||
uriHandler.openUri("https://github.com/vfsfitvnm/ViMusic/issues/new?assignees=&labels=bug&template=bug_report.yaml")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
SettingsEntry(
|
|
||||||
title = "Request a feature or suggest an idea",
|
|
||||||
text = "You will be redirected to GitHub",
|
|
||||||
onClick = {
|
|
||||||
uriHandler.openUri("https://github.com/vfsfitvnm/ViMusic/issues/new?assignees=&labels=enhancement&template=feature_request.yaml")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package it.vfsfitvnm.vimusic.ui.screens.settings
|
||||||
|
|
||||||
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.text.BasicText
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
|
import it.vfsfitvnm.vimusic.BuildConfig
|
||||||
|
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
|
import it.vfsfitvnm.vimusic.ui.screens.SettingsEntry
|
||||||
|
import it.vfsfitvnm.vimusic.ui.screens.SettingsEntryGroupText
|
||||||
|
import it.vfsfitvnm.vimusic.ui.screens.SettingsGroupSpacer
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
|
import it.vfsfitvnm.vimusic.utils.secondary
|
||||||
|
|
||||||
|
@ExperimentalAnimationApi
|
||||||
|
@Composable
|
||||||
|
fun AboutTab() {
|
||||||
|
val (colorPalette, typography) = LocalAppearance.current
|
||||||
|
val uriHandler = LocalUriHandler.current
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(colorPalette.background0)
|
||||||
|
.fillMaxSize()
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(LocalPlayerAwarePaddingValues.current)
|
||||||
|
) {
|
||||||
|
Header(title = "About") {
|
||||||
|
BasicText(
|
||||||
|
text = "v${BuildConfig.VERSION_NAME} by vfsfitvnm",
|
||||||
|
style = typography.s.secondary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsEntryGroupText(title = "SOCIAL")
|
||||||
|
|
||||||
|
SettingsEntry(
|
||||||
|
title = "GitHub",
|
||||||
|
text = "View the source code",
|
||||||
|
onClick = {
|
||||||
|
uriHandler.openUri("https://github.com/vfsfitvnm/ViMusic")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
SettingsGroupSpacer()
|
||||||
|
|
||||||
|
SettingsEntryGroupText(title = "TROUBLESHOOTING")
|
||||||
|
|
||||||
|
SettingsEntry(
|
||||||
|
title = "Report an issue",
|
||||||
|
text = "You will be redirected to GitHub",
|
||||||
|
onClick = {
|
||||||
|
uriHandler.openUri("https://github.com/vfsfitvnm/ViMusic/issues/new?assignees=&labels=bug&template=bug_report.yaml")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
SettingsEntry(
|
||||||
|
title = "Request a feature or suggest an idea",
|
||||||
|
text = "You will be redirected to GitHub",
|
||||||
|
onClick = {
|
||||||
|
uriHandler.openUri("https://github.com/vfsfitvnm/ViMusic/issues/new?assignees=&labels=enhancement&template=feature_request.yaml")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
package it.vfsfitvnm.vimusic.ui.screens.settings
|
|
||||||
|
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import it.vfsfitvnm.route.RouteHandler
|
|
||||||
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
|
||||||
import it.vfsfitvnm.vimusic.R
|
|
||||||
import it.vfsfitvnm.vimusic.enums.ColorPaletteMode
|
|
||||||
import it.vfsfitvnm.vimusic.enums.ColorPaletteName
|
|
||||||
import it.vfsfitvnm.vimusic.enums.ThumbnailRoundness
|
|
||||||
import it.vfsfitvnm.vimusic.ui.components.TopAppBar
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.EnumValueSelectorSettingsEntry
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.SettingsEntryGroupText
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.SettingsTitle
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.SwitchSettingEntry
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.globalRoutes
|
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
|
||||||
import it.vfsfitvnm.vimusic.utils.colorPaletteModeKey
|
|
||||||
import it.vfsfitvnm.vimusic.utils.colorPaletteNameKey
|
|
||||||
import it.vfsfitvnm.vimusic.utils.isShowingThumbnailInLockscreenKey
|
|
||||||
import it.vfsfitvnm.vimusic.utils.rememberPreference
|
|
||||||
import it.vfsfitvnm.vimusic.utils.thumbnailRoundnessKey
|
|
||||||
|
|
||||||
@ExperimentalAnimationApi
|
|
||||||
@Composable
|
|
||||||
fun AppearanceSettingsScreen() {
|
|
||||||
val scrollState = rememberScrollState()
|
|
||||||
|
|
||||||
RouteHandler(listenToGlobalEmitter = true) {
|
|
||||||
globalRoutes()
|
|
||||||
|
|
||||||
host {
|
|
||||||
val (colorPalette) = LocalAppearance.current
|
|
||||||
|
|
||||||
var colorPaletteName by rememberPreference(colorPaletteNameKey, ColorPaletteName.Dynamic)
|
|
||||||
var colorPaletteMode by rememberPreference(colorPaletteModeKey, ColorPaletteMode.System)
|
|
||||||
var thumbnailRoundness by rememberPreference(
|
|
||||||
thumbnailRoundnessKey,
|
|
||||||
ThumbnailRoundness.Light
|
|
||||||
)
|
|
||||||
var isShowingThumbnailInLockscreen by rememberPreference(
|
|
||||||
isShowingThumbnailInLockscreenKey,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.background(colorPalette.background0)
|
|
||||||
.fillMaxSize()
|
|
||||||
.verticalScroll(scrollState)
|
|
||||||
.padding(LocalPlayerAwarePaddingValues.current)
|
|
||||||
) {
|
|
||||||
TopAppBar(
|
|
||||||
modifier = Modifier
|
|
||||||
.height(52.dp)
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(R.drawable.chevron_back),
|
|
||||||
contentDescription = null,
|
|
||||||
colorFilter = ColorFilter.tint(colorPalette.text),
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable(onClick = pop)
|
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
|
||||||
.size(24.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsTitle(text = "Appearance")
|
|
||||||
|
|
||||||
SettingsEntryGroupText(title = "COLORS")
|
|
||||||
|
|
||||||
EnumValueSelectorSettingsEntry(
|
|
||||||
title = "Theme",
|
|
||||||
selectedValue = colorPaletteName,
|
|
||||||
onValueSelected = {
|
|
||||||
colorPaletteName = it
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
EnumValueSelectorSettingsEntry(
|
|
||||||
title = "Theme mode",
|
|
||||||
selectedValue = colorPaletteMode,
|
|
||||||
isEnabled = colorPaletteName != ColorPaletteName.PureBlack,
|
|
||||||
onValueSelected = {
|
|
||||||
colorPaletteMode = it
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
SettingsEntryGroupText(title = "SHAPES")
|
|
||||||
|
|
||||||
EnumValueSelectorSettingsEntry(
|
|
||||||
title = "Thumbnail roundness",
|
|
||||||
selectedValue = thumbnailRoundness,
|
|
||||||
onValueSelected = {
|
|
||||||
thumbnailRoundness = it
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
SettingsEntryGroupText(title = "LOCKSCREEN")
|
|
||||||
|
|
||||||
SwitchSettingEntry(
|
|
||||||
title = "Show song cover",
|
|
||||||
text = "Use the playing song cover as the lockscreen wallpaper",
|
|
||||||
isChecked = isShowingThumbnailInLockscreen,
|
|
||||||
onCheckedChange = { isShowingThumbnailInLockscreen = it }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package it.vfsfitvnm.vimusic.ui.screens.settings
|
||||||
|
|
||||||
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
||||||
|
import it.vfsfitvnm.vimusic.enums.ColorPaletteMode
|
||||||
|
import it.vfsfitvnm.vimusic.enums.ColorPaletteName
|
||||||
|
import it.vfsfitvnm.vimusic.enums.ThumbnailRoundness
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
|
import it.vfsfitvnm.vimusic.ui.screens.EnumValueSelectorSettingsEntry
|
||||||
|
import it.vfsfitvnm.vimusic.ui.screens.SettingsEntryGroupText
|
||||||
|
import it.vfsfitvnm.vimusic.ui.screens.SettingsGroupSpacer
|
||||||
|
import it.vfsfitvnm.vimusic.ui.screens.SwitchSettingEntry
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
|
import it.vfsfitvnm.vimusic.utils.colorPaletteModeKey
|
||||||
|
import it.vfsfitvnm.vimusic.utils.colorPaletteNameKey
|
||||||
|
import it.vfsfitvnm.vimusic.utils.isShowingThumbnailInLockscreenKey
|
||||||
|
import it.vfsfitvnm.vimusic.utils.rememberPreference
|
||||||
|
import it.vfsfitvnm.vimusic.utils.thumbnailRoundnessKey
|
||||||
|
|
||||||
|
@ExperimentalAnimationApi
|
||||||
|
@Composable
|
||||||
|
fun AppearanceSettingsTab() {
|
||||||
|
val (colorPalette) = LocalAppearance.current
|
||||||
|
|
||||||
|
var colorPaletteName by rememberPreference(colorPaletteNameKey, ColorPaletteName.Dynamic)
|
||||||
|
var colorPaletteMode by rememberPreference(colorPaletteModeKey, ColorPaletteMode.System)
|
||||||
|
var thumbnailRoundness by rememberPreference(
|
||||||
|
thumbnailRoundnessKey,
|
||||||
|
ThumbnailRoundness.Light
|
||||||
|
)
|
||||||
|
var isShowingThumbnailInLockscreen by rememberPreference(
|
||||||
|
isShowingThumbnailInLockscreenKey,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(colorPalette.background0)
|
||||||
|
.fillMaxSize()
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(LocalPlayerAwarePaddingValues.current)
|
||||||
|
) {
|
||||||
|
Header(title = "Appearance")
|
||||||
|
|
||||||
|
SettingsEntryGroupText(title = "COLORS")
|
||||||
|
|
||||||
|
EnumValueSelectorSettingsEntry(
|
||||||
|
title = "Theme",
|
||||||
|
selectedValue = colorPaletteName,
|
||||||
|
onValueSelected = { colorPaletteName = it }
|
||||||
|
)
|
||||||
|
|
||||||
|
EnumValueSelectorSettingsEntry(
|
||||||
|
title = "Theme mode",
|
||||||
|
selectedValue = colorPaletteMode,
|
||||||
|
isEnabled = colorPaletteName != ColorPaletteName.PureBlack,
|
||||||
|
onValueSelected = { colorPaletteMode = it }
|
||||||
|
)
|
||||||
|
|
||||||
|
SettingsGroupSpacer()
|
||||||
|
|
||||||
|
SettingsEntryGroupText(title = "SHAPES")
|
||||||
|
|
||||||
|
EnumValueSelectorSettingsEntry(
|
||||||
|
title = "Thumbnail roundness",
|
||||||
|
selectedValue = thumbnailRoundness,
|
||||||
|
onValueSelected = { thumbnailRoundness = it }
|
||||||
|
)
|
||||||
|
|
||||||
|
SettingsGroupSpacer()
|
||||||
|
|
||||||
|
SettingsEntryGroupText(title = "LOCKSCREEN")
|
||||||
|
|
||||||
|
SwitchSettingEntry(
|
||||||
|
title = "Show song cover",
|
||||||
|
text = "Use the playing song cover as the lockscreen wallpaper",
|
||||||
|
isChecked = isShowingThumbnailInLockscreen,
|
||||||
|
onCheckedChange = { isShowingThumbnailInLockscreen = it }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
package it.vfsfitvnm.vimusic.ui.screens.settings
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import it.vfsfitvnm.route.RouteHandler
|
|
||||||
import it.vfsfitvnm.vimusic.Database
|
|
||||||
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
|
||||||
import it.vfsfitvnm.vimusic.R
|
|
||||||
import it.vfsfitvnm.vimusic.checkpoint
|
|
||||||
import it.vfsfitvnm.vimusic.internal
|
|
||||||
import it.vfsfitvnm.vimusic.path
|
|
||||||
import it.vfsfitvnm.vimusic.query
|
|
||||||
import it.vfsfitvnm.vimusic.service.PlayerService
|
|
||||||
import it.vfsfitvnm.vimusic.ui.components.TopAppBar
|
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.ConfirmationDialog
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.SettingsEntry
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.SettingsEntryGroupText
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.SettingsGroupDescription
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.SettingsTitle
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.globalRoutes
|
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
|
||||||
import it.vfsfitvnm.vimusic.utils.intent
|
|
||||||
import java.io.FileInputStream
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Date
|
|
||||||
import kotlin.system.exitProcess
|
|
||||||
|
|
||||||
@ExperimentalAnimationApi
|
|
||||||
@Composable
|
|
||||||
fun BackupAndRestoreScreen() {
|
|
||||||
val scrollState = rememberScrollState()
|
|
||||||
|
|
||||||
RouteHandler(listenToGlobalEmitter = true) {
|
|
||||||
globalRoutes()
|
|
||||||
|
|
||||||
host {
|
|
||||||
val (colorPalette) = LocalAppearance.current
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
val backupLauncher =
|
|
||||||
rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("application/vnd.sqlite3")) { uri ->
|
|
||||||
if (uri == null) return@rememberLauncherForActivityResult
|
|
||||||
|
|
||||||
query {
|
|
||||||
Database.internal.checkpoint()
|
|
||||||
context.applicationContext.contentResolver.openOutputStream(uri)
|
|
||||||
?.use { outputStream ->
|
|
||||||
FileInputStream(Database.internal.path).use { inputStream ->
|
|
||||||
inputStream.copyTo(outputStream)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val restoreLauncher =
|
|
||||||
rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
|
|
||||||
if (uri == null) return@rememberLauncherForActivityResult
|
|
||||||
|
|
||||||
query {
|
|
||||||
Database.internal.checkpoint()
|
|
||||||
Database.internal.close()
|
|
||||||
|
|
||||||
FileOutputStream(Database.internal.path).use { outputStream ->
|
|
||||||
context.applicationContext.contentResolver.openInputStream(uri)
|
|
||||||
?.use { inputStream ->
|
|
||||||
inputStream.copyTo(outputStream)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context.stopService(context.intent<PlayerService>())
|
|
||||||
exitProcess(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var isShowingRestoreDialog by rememberSaveable {
|
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isShowingRestoreDialog) {
|
|
||||||
ConfirmationDialog(
|
|
||||||
text = "The application will automatically close itself to avoid problems after restoring the database.",
|
|
||||||
onDismiss = {
|
|
||||||
isShowingRestoreDialog = false
|
|
||||||
},
|
|
||||||
onConfirm = {
|
|
||||||
restoreLauncher.launch(
|
|
||||||
arrayOf(
|
|
||||||
"application/x-sqlite3",
|
|
||||||
"application/vnd.sqlite3",
|
|
||||||
"application/octet-stream"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
confirmText = "Ok"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.background(colorPalette.background0)
|
|
||||||
.fillMaxSize()
|
|
||||||
.verticalScroll(scrollState)
|
|
||||||
.padding(LocalPlayerAwarePaddingValues.current)
|
|
||||||
) {
|
|
||||||
TopAppBar(
|
|
||||||
modifier = Modifier
|
|
||||||
.height(52.dp)
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(R.drawable.chevron_back),
|
|
||||||
contentDescription = null,
|
|
||||||
colorFilter = ColorFilter.tint(colorPalette.text),
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable(onClick = pop)
|
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
|
||||||
.size(24.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsTitle(text = "Backup & Restore")
|
|
||||||
|
|
||||||
SettingsEntryGroupText(title = "BACKUP")
|
|
||||||
|
|
||||||
SettingsGroupDescription(text = "Personal preferences (i.e. the theme mode) and the cache are excluded.")
|
|
||||||
|
|
||||||
SettingsEntry(
|
|
||||||
title = "Backup",
|
|
||||||
text = "Export the database to the external storage",
|
|
||||||
onClick = {
|
|
||||||
@SuppressLint("SimpleDateFormat")
|
|
||||||
val dateFormat = SimpleDateFormat("yyyyMMddHHmmss")
|
|
||||||
backupLauncher.launch("vimusic_${dateFormat.format(Date())}.db")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
SettingsEntryGroupText(title = "RESTORE")
|
|
||||||
|
|
||||||
SettingsGroupDescription(text = "Existing data will be overwritten.")
|
|
||||||
|
|
||||||
SettingsEntry(
|
|
||||||
title = "Restore",
|
|
||||||
text = "Import the database from the external storage",
|
|
||||||
onClick = {
|
|
||||||
isShowingRestoreDialog = true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
package it.vfsfitvnm.vimusic.ui.screens.settings
|
|
||||||
|
|
||||||
import android.text.format.Formatter
|
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import coil.Coil
|
|
||||||
import coil.annotation.ExperimentalCoilApi
|
|
||||||
import it.vfsfitvnm.route.RouteHandler
|
|
||||||
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
|
||||||
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
|
|
||||||
import it.vfsfitvnm.vimusic.R
|
|
||||||
import it.vfsfitvnm.vimusic.enums.CoilDiskCacheMaxSize
|
|
||||||
import it.vfsfitvnm.vimusic.enums.ExoPlayerDiskCacheMaxSize
|
|
||||||
import it.vfsfitvnm.vimusic.ui.components.TopAppBar
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.EnumValueSelectorSettingsEntry
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.SettingsDescription
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.SettingsEntryGroupText
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.SettingsGroupDescription
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.SettingsTitle
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.globalRoutes
|
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
|
||||||
import it.vfsfitvnm.vimusic.utils.coilDiskCacheMaxSizeKey
|
|
||||||
import it.vfsfitvnm.vimusic.utils.exoPlayerDiskCacheMaxSizeKey
|
|
||||||
import it.vfsfitvnm.vimusic.utils.rememberPreference
|
|
||||||
|
|
||||||
@OptIn(ExperimentalCoilApi::class)
|
|
||||||
@ExperimentalAnimationApi
|
|
||||||
@Composable
|
|
||||||
fun CacheSettingsScreen() {
|
|
||||||
|
|
||||||
val scrollState = rememberScrollState()
|
|
||||||
|
|
||||||
RouteHandler(listenToGlobalEmitter = true) {
|
|
||||||
globalRoutes()
|
|
||||||
|
|
||||||
host {
|
|
||||||
val context = LocalContext.current
|
|
||||||
val (colorPalette, _) = LocalAppearance.current
|
|
||||||
val binder = LocalPlayerServiceBinder.current
|
|
||||||
|
|
||||||
var coilDiskCacheMaxSize by rememberPreference(
|
|
||||||
coilDiskCacheMaxSizeKey,
|
|
||||||
CoilDiskCacheMaxSize.`128MB`
|
|
||||||
)
|
|
||||||
var exoPlayerDiskCacheMaxSize by rememberPreference(
|
|
||||||
exoPlayerDiskCacheMaxSizeKey,
|
|
||||||
ExoPlayerDiskCacheMaxSize.`2GB`
|
|
||||||
)
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.background(colorPalette.background0)
|
|
||||||
.fillMaxSize()
|
|
||||||
.verticalScroll(scrollState)
|
|
||||||
.padding(LocalPlayerAwarePaddingValues.current)
|
|
||||||
) {
|
|
||||||
TopAppBar(
|
|
||||||
modifier = Modifier
|
|
||||||
.height(52.dp)
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(R.drawable.chevron_back),
|
|
||||||
contentDescription = null,
|
|
||||||
colorFilter = ColorFilter.tint(colorPalette.text),
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable(onClick = pop)
|
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
|
||||||
.size(24.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsTitle(text = "Cache")
|
|
||||||
|
|
||||||
SettingsDescription(text = "When the cache runs out of space, the resources that haven't been accessed for the longest time are cleared.")
|
|
||||||
|
|
||||||
Coil.imageLoader(context).diskCache?.let { diskCache ->
|
|
||||||
val diskCacheSize = remember(diskCache) {
|
|
||||||
diskCache.size
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsEntryGroupText(title = "IMAGE CACHE")
|
|
||||||
|
|
||||||
SettingsGroupDescription(text = "${Formatter.formatShortFileSize(context, diskCacheSize)} used (${diskCacheSize * 100 / coilDiskCacheMaxSize.bytes.coerceAtLeast(1)}%)")
|
|
||||||
|
|
||||||
EnumValueSelectorSettingsEntry(
|
|
||||||
title = "Max size",
|
|
||||||
selectedValue = coilDiskCacheMaxSize,
|
|
||||||
onValueSelected = {
|
|
||||||
coilDiskCacheMaxSize = it
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
binder?.cache?.let { cache ->
|
|
||||||
val diskCacheSize by remember {
|
|
||||||
derivedStateOf {
|
|
||||||
cache.cacheSpace
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsEntryGroupText(title = "SONG CACHE")
|
|
||||||
|
|
||||||
SettingsGroupDescription(
|
|
||||||
text = buildString {
|
|
||||||
append(Formatter.formatShortFileSize(context, diskCacheSize))
|
|
||||||
append(" used")
|
|
||||||
when (val size = exoPlayerDiskCacheMaxSize) {
|
|
||||||
ExoPlayerDiskCacheMaxSize.Unlimited -> {}
|
|
||||||
else -> append(" (${diskCacheSize * 100 / size.bytes}%)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
EnumValueSelectorSettingsEntry(
|
|
||||||
title = "Max size",
|
|
||||||
selectedValue = exoPlayerDiskCacheMaxSize,
|
|
||||||
onValueSelected = {
|
|
||||||
exoPlayerDiskCacheMaxSize = it
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package it.vfsfitvnm.vimusic.ui.screens.settings
|
||||||
|
|
||||||
|
import android.text.format.Formatter
|
||||||
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import coil.Coil
|
||||||
|
import coil.annotation.ExperimentalCoilApi
|
||||||
|
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
||||||
|
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
|
||||||
|
import it.vfsfitvnm.vimusic.enums.CoilDiskCacheMaxSize
|
||||||
|
import it.vfsfitvnm.vimusic.enums.ExoPlayerDiskCacheMaxSize
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
|
import it.vfsfitvnm.vimusic.ui.screens.EnumValueSelectorSettingsEntry
|
||||||
|
import it.vfsfitvnm.vimusic.ui.screens.SettingsDescription
|
||||||
|
import it.vfsfitvnm.vimusic.ui.screens.SettingsEntryGroupText
|
||||||
|
import it.vfsfitvnm.vimusic.ui.screens.SettingsGroupSpacer
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
|
import it.vfsfitvnm.vimusic.utils.coilDiskCacheMaxSizeKey
|
||||||
|
import it.vfsfitvnm.vimusic.utils.exoPlayerDiskCacheMaxSizeKey
|
||||||
|
import it.vfsfitvnm.vimusic.utils.rememberPreference
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoilApi::class)
|
||||||
|
@ExperimentalAnimationApi
|
||||||
|
@Composable
|
||||||
|
fun CacheSettingsTab() {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val (colorPalette) = LocalAppearance.current
|
||||||
|
val binder = LocalPlayerServiceBinder.current
|
||||||
|
|
||||||
|
var coilDiskCacheMaxSize by rememberPreference(
|
||||||
|
coilDiskCacheMaxSizeKey,
|
||||||
|
CoilDiskCacheMaxSize.`128MB`
|
||||||
|
)
|
||||||
|
var exoPlayerDiskCacheMaxSize by rememberPreference(
|
||||||
|
exoPlayerDiskCacheMaxSizeKey,
|
||||||
|
ExoPlayerDiskCacheMaxSize.`2GB`
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(colorPalette.background0)
|
||||||
|
.fillMaxSize()
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(LocalPlayerAwarePaddingValues.current)
|
||||||
|
) {
|
||||||
|
Header(title = "Cache")
|
||||||
|
|
||||||
|
SettingsDescription(text = "When the cache runs out of space, the resources that haven't been accessed for the longest time are cleared")
|
||||||
|
|
||||||
|
Coil.imageLoader(context).diskCache?.let { diskCache ->
|
||||||
|
val diskCacheSize = remember(diskCache) {
|
||||||
|
diskCache.size
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsGroupSpacer()
|
||||||
|
|
||||||
|
SettingsEntryGroupText(title = "IMAGE CACHE")
|
||||||
|
|
||||||
|
SettingsDescription(text = "${Formatter.formatShortFileSize(context, diskCacheSize)} used (${diskCacheSize * 100 / coilDiskCacheMaxSize.bytes.coerceAtLeast(1)}%)")
|
||||||
|
|
||||||
|
EnumValueSelectorSettingsEntry(
|
||||||
|
title = "Max size",
|
||||||
|
selectedValue = coilDiskCacheMaxSize,
|
||||||
|
onValueSelected = {
|
||||||
|
coilDiskCacheMaxSize = it
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
binder?.cache?.let { cache ->
|
||||||
|
val diskCacheSize by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
cache.cacheSpace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsGroupSpacer()
|
||||||
|
|
||||||
|
SettingsEntryGroupText(title = "SONG CACHE")
|
||||||
|
|
||||||
|
SettingsDescription(
|
||||||
|
text = buildString {
|
||||||
|
append(Formatter.formatShortFileSize(context, diskCacheSize))
|
||||||
|
append(" used")
|
||||||
|
when (val size = exoPlayerDiskCacheMaxSize) {
|
||||||
|
ExoPlayerDiskCacheMaxSize.Unlimited -> {}
|
||||||
|
else -> append(" (${diskCacheSize * 100 / size.bytes}%)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
EnumValueSelectorSettingsEntry(
|
||||||
|
title = "Max size",
|
||||||
|
selectedValue = exoPlayerDiskCacheMaxSize,
|
||||||
|
onValueSelected = {
|
||||||
|
exoPlayerDiskCacheMaxSize = it
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
package it.vfsfitvnm.vimusic.ui.screens.settings
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Build
|
|
||||||
import android.provider.Settings
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.text.BasicText
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import it.vfsfitvnm.route.RouteHandler
|
|
||||||
import it.vfsfitvnm.vimusic.Database
|
|
||||||
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
|
||||||
import it.vfsfitvnm.vimusic.R
|
|
||||||
import it.vfsfitvnm.vimusic.query
|
|
||||||
import it.vfsfitvnm.vimusic.ui.components.TopAppBar
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.SettingsDescription
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.SettingsEntry
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.SettingsEntryGroupText
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.SettingsGroupDescription
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.SwitchSettingEntry
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.globalRoutes
|
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
|
||||||
import it.vfsfitvnm.vimusic.utils.isIgnoringBatteryOptimizations
|
|
||||||
import it.vfsfitvnm.vimusic.utils.isInvincibilityEnabledKey
|
|
||||||
import it.vfsfitvnm.vimusic.utils.rememberPreference
|
|
||||||
import it.vfsfitvnm.vimusic.utils.semiBold
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
|
|
||||||
@ExperimentalAnimationApi
|
|
||||||
@Composable
|
|
||||||
fun OtherSettingsScreen() {
|
|
||||||
|
|
||||||
val scrollState = rememberScrollState()
|
|
||||||
|
|
||||||
RouteHandler(listenToGlobalEmitter = true) {
|
|
||||||
globalRoutes()
|
|
||||||
|
|
||||||
host {
|
|
||||||
val context = LocalContext.current
|
|
||||||
val (colorPalette, typography) = LocalAppearance.current
|
|
||||||
|
|
||||||
val queriesCount by remember {
|
|
||||||
Database.queriesCount()
|
|
||||||
}.collectAsState(initial = 0, context = Dispatchers.IO)
|
|
||||||
|
|
||||||
var isInvincibilityEnabled by rememberPreference(isInvincibilityEnabledKey, false)
|
|
||||||
|
|
||||||
var isIgnoringBatteryOptimizations by remember {
|
|
||||||
mutableStateOf(context.isIgnoringBatteryOptimizations)
|
|
||||||
}
|
|
||||||
|
|
||||||
val activityResultLauncher =
|
|
||||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
|
||||||
isIgnoringBatteryOptimizations = context.isIgnoringBatteryOptimizations
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.background(colorPalette.background0)
|
|
||||||
.fillMaxSize()
|
|
||||||
.verticalScroll(scrollState)
|
|
||||||
.padding(LocalPlayerAwarePaddingValues.current)
|
|
||||||
) {
|
|
||||||
TopAppBar(
|
|
||||||
modifier = Modifier
|
|
||||||
.height(52.dp)
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(R.drawable.chevron_back),
|
|
||||||
contentDescription = null,
|
|
||||||
colorFilter = ColorFilter.tint(colorPalette.text),
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable(onClick = pop)
|
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
|
||||||
.size(24.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
BasicText(
|
|
||||||
text = "Other",
|
|
||||||
style = typography.m.semiBold,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(start = 40.dp)
|
|
||||||
.padding(all = 16.dp)
|
|
||||||
)
|
|
||||||
|
|
||||||
SettingsEntryGroupText(title = "SEARCH HISTORY")
|
|
||||||
|
|
||||||
SettingsEntry(
|
|
||||||
title = "Clear search history",
|
|
||||||
text = if (queriesCount > 0) {
|
|
||||||
"Delete $queriesCount search queries"
|
|
||||||
} else {
|
|
||||||
"History is empty"
|
|
||||||
},
|
|
||||||
isEnabled = queriesCount > 0,
|
|
||||||
onClick = {
|
|
||||||
query {
|
|
||||||
Database.clearQueries()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
SettingsEntryGroupText(title = "SERVICE LIFETIME")
|
|
||||||
|
|
||||||
SettingsGroupDescription(text = "If battery optimizations are applied, the playback notification can suddenly disappear when paused.")
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
||||||
SettingsDescription(text = "Since Android 12, disabling battery optimizations is required for the \"Invincible service\" option to take effect.")
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsEntry(
|
|
||||||
title = "Ignore battery optimizations",
|
|
||||||
isEnabled = !isIgnoringBatteryOptimizations,
|
|
||||||
text = if (isIgnoringBatteryOptimizations) {
|
|
||||||
"Already unrestricted"
|
|
||||||
} else {
|
|
||||||
"Disable background restrictions"
|
|
||||||
},
|
|
||||||
onClick = {
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return@SettingsEntry
|
|
||||||
|
|
||||||
@SuppressLint("BatteryLife")
|
|
||||||
val intent =
|
|
||||||
Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply {
|
|
||||||
data = Uri.parse("package:${context.packageName}")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (intent.resolveActivity(context.packageManager) != null) {
|
|
||||||
activityResultLauncher.launch(intent)
|
|
||||||
} else {
|
|
||||||
val fallbackIntent =
|
|
||||||
Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS)
|
|
||||||
|
|
||||||
if (fallbackIntent.resolveActivity(context.packageManager) != null) {
|
|
||||||
activityResultLauncher.launch(fallbackIntent)
|
|
||||||
} else {
|
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
"Couldn't find battery optimization settings, please whitelist ViMusic manually",
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
SwitchSettingEntry(
|
|
||||||
title = "Invincible service",
|
|
||||||
text = "When turning off battery optimizations is not enough",
|
|
||||||
isChecked = isInvincibilityEnabled,
|
|
||||||
onCheckedChange = {
|
|
||||||
isInvincibilityEnabled = it
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
package it.vfsfitvnm.vimusic.ui.screens.settings
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
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.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import it.vfsfitvnm.vimusic.Database
|
||||||
|
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
||||||
|
import it.vfsfitvnm.vimusic.checkpoint
|
||||||
|
import it.vfsfitvnm.vimusic.internal
|
||||||
|
import it.vfsfitvnm.vimusic.path
|
||||||
|
import it.vfsfitvnm.vimusic.query
|
||||||
|
import it.vfsfitvnm.vimusic.service.PlayerService
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.ConfirmationDialog
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
|
import it.vfsfitvnm.vimusic.ui.screens.SettingsDescription
|
||||||
|
import it.vfsfitvnm.vimusic.ui.screens.SettingsEntry
|
||||||
|
import it.vfsfitvnm.vimusic.ui.screens.SettingsEntryGroupText
|
||||||
|
import it.vfsfitvnm.vimusic.ui.screens.SettingsGroupSpacer
|
||||||
|
import it.vfsfitvnm.vimusic.ui.screens.SwitchSettingEntry
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
|
import it.vfsfitvnm.vimusic.utils.intent
|
||||||
|
import it.vfsfitvnm.vimusic.utils.isIgnoringBatteryOptimizations
|
||||||
|
import it.vfsfitvnm.vimusic.utils.isInvincibilityEnabledKey
|
||||||
|
import it.vfsfitvnm.vimusic.utils.rememberPreference
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
||||||
|
@ExperimentalAnimationApi
|
||||||
|
@Composable
|
||||||
|
fun OtherSettingsTab() {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val (colorPalette) = LocalAppearance.current
|
||||||
|
|
||||||
|
val queriesCount by remember {
|
||||||
|
Database.queriesCount()
|
||||||
|
}.collectAsState(initial = 0, context = Dispatchers.IO)
|
||||||
|
|
||||||
|
var isInvincibilityEnabled by rememberPreference(isInvincibilityEnabledKey, false)
|
||||||
|
|
||||||
|
var isIgnoringBatteryOptimizations by remember {
|
||||||
|
mutableStateOf(context.isIgnoringBatteryOptimizations)
|
||||||
|
}
|
||||||
|
|
||||||
|
var isShowingRestoreDialog by rememberSaveable {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
val activityResultLauncher =
|
||||||
|
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
|
isIgnoringBatteryOptimizations = context.isIgnoringBatteryOptimizations
|
||||||
|
}
|
||||||
|
|
||||||
|
val backupLauncher =
|
||||||
|
rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("application/vnd.sqlite3")) { uri ->
|
||||||
|
if (uri == null) return@rememberLauncherForActivityResult
|
||||||
|
|
||||||
|
query {
|
||||||
|
Database.internal.checkpoint()
|
||||||
|
context.applicationContext.contentResolver.openOutputStream(uri)
|
||||||
|
?.use { outputStream ->
|
||||||
|
FileInputStream(Database.internal.path).use { inputStream ->
|
||||||
|
inputStream.copyTo(outputStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val restoreLauncher =
|
||||||
|
rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
|
||||||
|
if (uri == null) return@rememberLauncherForActivityResult
|
||||||
|
|
||||||
|
query {
|
||||||
|
Database.internal.checkpoint()
|
||||||
|
Database.internal.close()
|
||||||
|
|
||||||
|
FileOutputStream(Database.internal.path).use { outputStream ->
|
||||||
|
context.applicationContext.contentResolver.openInputStream(uri)
|
||||||
|
?.use { inputStream ->
|
||||||
|
inputStream.copyTo(outputStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.stopService(context.intent<PlayerService>())
|
||||||
|
exitProcess(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isShowingRestoreDialog) {
|
||||||
|
ConfirmationDialog(
|
||||||
|
text = "The application will automatically close itself to avoid problems after restoring the database.",
|
||||||
|
onDismiss = {
|
||||||
|
isShowingRestoreDialog = false
|
||||||
|
},
|
||||||
|
onConfirm = {
|
||||||
|
restoreLauncher.launch(
|
||||||
|
arrayOf(
|
||||||
|
"application/x-sqlite3",
|
||||||
|
"application/vnd.sqlite3",
|
||||||
|
"application/octet-stream"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
confirmText = "Ok"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(colorPalette.background0)
|
||||||
|
.fillMaxSize()
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(LocalPlayerAwarePaddingValues.current)
|
||||||
|
) {
|
||||||
|
Header(title = "Other")
|
||||||
|
|
||||||
|
SettingsEntryGroupText(title = "SEARCH HISTORY")
|
||||||
|
|
||||||
|
SettingsEntry(
|
||||||
|
title = "Clear search history",
|
||||||
|
text = if (queriesCount > 0) {
|
||||||
|
"Delete $queriesCount search queries"
|
||||||
|
} else {
|
||||||
|
"History is empty"
|
||||||
|
},
|
||||||
|
isEnabled = queriesCount > 0,
|
||||||
|
onClick = {
|
||||||
|
query {
|
||||||
|
Database.clearQueries()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
SettingsGroupSpacer()
|
||||||
|
|
||||||
|
SettingsEntryGroupText(title = "SERVICE LIFETIME")
|
||||||
|
|
||||||
|
SettingsDescription(text = "If battery optimizations are applied, the playback notification can suddenly disappear when paused.")
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
SettingsDescription(text = "Since Android 12, disabling battery optimizations is required for the \"Invincible service\" option to take effect.")
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsEntry(
|
||||||
|
title = "Ignore battery optimizations",
|
||||||
|
isEnabled = !isIgnoringBatteryOptimizations,
|
||||||
|
text = if (isIgnoringBatteryOptimizations) {
|
||||||
|
"Already unrestricted"
|
||||||
|
} else {
|
||||||
|
"Disable background restrictions"
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return@SettingsEntry
|
||||||
|
|
||||||
|
@SuppressLint("BatteryLife")
|
||||||
|
val intent =
|
||||||
|
Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply {
|
||||||
|
data = Uri.parse("package:${context.packageName}")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intent.resolveActivity(context.packageManager) != null) {
|
||||||
|
activityResultLauncher.launch(intent)
|
||||||
|
} else {
|
||||||
|
val fallbackIntent =
|
||||||
|
Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS)
|
||||||
|
|
||||||
|
if (fallbackIntent.resolveActivity(context.packageManager) != null) {
|
||||||
|
activityResultLauncher.launch(fallbackIntent)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
"Couldn't find battery optimization settings, please whitelist ViMusic manually",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
SwitchSettingEntry(
|
||||||
|
title = "Invincible service",
|
||||||
|
text = "When turning off battery optimizations is not enough",
|
||||||
|
isChecked = isInvincibilityEnabled,
|
||||||
|
onCheckedChange = { isInvincibilityEnabled = it }
|
||||||
|
)
|
||||||
|
|
||||||
|
SettingsGroupSpacer()
|
||||||
|
|
||||||
|
SettingsEntryGroupText(title = "BACKUP")
|
||||||
|
|
||||||
|
SettingsDescription(text = "Personal preferences (i.e. the theme mode) and the cache are excluded.")
|
||||||
|
|
||||||
|
SettingsEntry(
|
||||||
|
title = "Backup",
|
||||||
|
text = "Export the database to the external storage",
|
||||||
|
onClick = {
|
||||||
|
@SuppressLint("SimpleDateFormat")
|
||||||
|
val dateFormat = SimpleDateFormat("yyyyMMddHHmmss")
|
||||||
|
backupLauncher.launch("vimusic_${dateFormat.format(Date())}.db")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
SettingsGroupSpacer()
|
||||||
|
|
||||||
|
SettingsEntryGroupText(title = "RESTORE")
|
||||||
|
|
||||||
|
SettingsDescription(text = "Existing data will be overwritten.")
|
||||||
|
|
||||||
|
SettingsEntry(
|
||||||
|
title = "Restore",
|
||||||
|
text = "Import the database from the external storage",
|
||||||
|
onClick = {
|
||||||
|
isShowingRestoreDialog = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
package it.vfsfitvnm.vimusic.ui.screens.settings
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.media.audiofx.AudioEffect
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import it.vfsfitvnm.route.RouteHandler
|
|
||||||
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
|
||||||
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
|
|
||||||
import it.vfsfitvnm.vimusic.R
|
|
||||||
import it.vfsfitvnm.vimusic.ui.components.TopAppBar
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.SettingsEntry
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.SettingsEntryGroupText
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.SettingsTitle
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.SwitchSettingEntry
|
|
||||||
import it.vfsfitvnm.vimusic.ui.screens.globalRoutes
|
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
|
||||||
import it.vfsfitvnm.vimusic.utils.persistentQueueKey
|
|
||||||
import it.vfsfitvnm.vimusic.utils.rememberPreference
|
|
||||||
import it.vfsfitvnm.vimusic.utils.skipSilenceKey
|
|
||||||
import it.vfsfitvnm.vimusic.utils.volumeNormalizationKey
|
|
||||||
|
|
||||||
@ExperimentalAnimationApi
|
|
||||||
@Composable
|
|
||||||
fun PlayerSettingsScreen() {
|
|
||||||
|
|
||||||
val scrollState = rememberScrollState()
|
|
||||||
|
|
||||||
RouteHandler(listenToGlobalEmitter = true) {
|
|
||||||
globalRoutes()
|
|
||||||
|
|
||||||
host {
|
|
||||||
val context = LocalContext.current
|
|
||||||
val (colorPalette) = LocalAppearance.current
|
|
||||||
val binder = LocalPlayerServiceBinder.current
|
|
||||||
|
|
||||||
var persistentQueue by rememberPreference(persistentQueueKey, false)
|
|
||||||
var skipSilence by rememberPreference(skipSilenceKey, false)
|
|
||||||
var volumeNormalization by rememberPreference(volumeNormalizationKey, false)
|
|
||||||
|
|
||||||
val activityResultLauncher =
|
|
||||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.background(colorPalette.background0)
|
|
||||||
.fillMaxSize()
|
|
||||||
.verticalScroll(scrollState)
|
|
||||||
.padding(LocalPlayerAwarePaddingValues.current)
|
|
||||||
) {
|
|
||||||
TopAppBar(
|
|
||||||
modifier = Modifier
|
|
||||||
.height(52.dp)
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(R.drawable.chevron_back),
|
|
||||||
contentDescription = null,
|
|
||||||
colorFilter = ColorFilter.tint(colorPalette.text),
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable(onClick = pop)
|
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
|
||||||
.size(24.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsTitle(text = "Player & Audio")
|
|
||||||
|
|
||||||
SettingsEntryGroupText(title = "PLAYER")
|
|
||||||
|
|
||||||
SwitchSettingEntry(
|
|
||||||
title = "Persistent queue",
|
|
||||||
text = "Save and restore playing songs",
|
|
||||||
isChecked = persistentQueue,
|
|
||||||
onCheckedChange = {
|
|
||||||
persistentQueue = it
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
SettingsEntryGroupText(title = "AUDIO")
|
|
||||||
|
|
||||||
SwitchSettingEntry(
|
|
||||||
title = "Skip silence",
|
|
||||||
text = "Skip silent parts during playback",
|
|
||||||
isChecked = skipSilence,
|
|
||||||
onCheckedChange = {
|
|
||||||
skipSilence = it
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
SwitchSettingEntry(
|
|
||||||
title = "Loudness normalization",
|
|
||||||
text = "Lower the volume to a standard level",
|
|
||||||
isChecked = volumeNormalization,
|
|
||||||
onCheckedChange = {
|
|
||||||
volumeNormalization = it
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
SettingsEntry(
|
|
||||||
title = "Equalizer",
|
|
||||||
text = "Interact with the system equalizer",
|
|
||||||
onClick = {
|
|
||||||
val intent =
|
|
||||||
Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL).apply {
|
|
||||||
putExtra(
|
|
||||||
AudioEffect.EXTRA_AUDIO_SESSION,
|
|
||||||
binder?.player?.audioSessionId
|
|
||||||
)
|
|
||||||
putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.packageName)
|
|
||||||
putExtra(
|
|
||||||
AudioEffect.EXTRA_CONTENT_TYPE,
|
|
||||||
AudioEffect.CONTENT_TYPE_MUSIC
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (intent.resolveActivity(context.packageManager) != null) {
|
|
||||||
activityResultLauncher.launch(intent)
|
|
||||||
} else {
|
|
||||||
Toast.makeText(context, "No equalizer app found!", Toast.LENGTH_SHORT)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
package it.vfsfitvnm.vimusic.ui.screens.settings
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.media.audiofx.AudioEffect
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
||||||
|
import it.vfsfitvnm.vimusic.LocalPlayerServiceBinder
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
|
import it.vfsfitvnm.vimusic.ui.screens.SettingsEntry
|
||||||
|
import it.vfsfitvnm.vimusic.ui.screens.SettingsEntryGroupText
|
||||||
|
import it.vfsfitvnm.vimusic.ui.screens.SettingsGroupSpacer
|
||||||
|
import it.vfsfitvnm.vimusic.ui.screens.SwitchSettingEntry
|
||||||
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
|
import it.vfsfitvnm.vimusic.utils.persistentQueueKey
|
||||||
|
import it.vfsfitvnm.vimusic.utils.rememberPreference
|
||||||
|
import it.vfsfitvnm.vimusic.utils.skipSilenceKey
|
||||||
|
import it.vfsfitvnm.vimusic.utils.volumeNormalizationKey
|
||||||
|
|
||||||
|
@ExperimentalAnimationApi
|
||||||
|
@Composable
|
||||||
|
fun PlayerSettingsTab() {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val (colorPalette) = LocalAppearance.current
|
||||||
|
val binder = LocalPlayerServiceBinder.current
|
||||||
|
|
||||||
|
var persistentQueue by rememberPreference(persistentQueueKey, false)
|
||||||
|
var skipSilence by rememberPreference(skipSilenceKey, false)
|
||||||
|
var volumeNormalization by rememberPreference(volumeNormalizationKey, false)
|
||||||
|
|
||||||
|
val activityResultLauncher =
|
||||||
|
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(colorPalette.background0)
|
||||||
|
.fillMaxSize()
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(LocalPlayerAwarePaddingValues.current)
|
||||||
|
) {
|
||||||
|
Header(title = "Player & Audio")
|
||||||
|
|
||||||
|
SettingsEntryGroupText(title = "PLAYER")
|
||||||
|
|
||||||
|
SwitchSettingEntry(
|
||||||
|
title = "Persistent queue",
|
||||||
|
text = "Save and restore playing songs",
|
||||||
|
isChecked = persistentQueue,
|
||||||
|
onCheckedChange = {
|
||||||
|
persistentQueue = it
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
SettingsGroupSpacer()
|
||||||
|
|
||||||
|
SettingsEntryGroupText(title = "AUDIO")
|
||||||
|
|
||||||
|
SwitchSettingEntry(
|
||||||
|
title = "Skip silence",
|
||||||
|
text = "Skip silent parts during playback",
|
||||||
|
isChecked = skipSilence,
|
||||||
|
onCheckedChange = {
|
||||||
|
skipSilence = it
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
SwitchSettingEntry(
|
||||||
|
title = "Loudness normalization",
|
||||||
|
text = "Lower the volume to a standard level",
|
||||||
|
isChecked = volumeNormalization,
|
||||||
|
onCheckedChange = {
|
||||||
|
volumeNormalization = it
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
SettingsEntry(
|
||||||
|
title = "Equalizer",
|
||||||
|
text = "Interact with the system equalizer",
|
||||||
|
onClick = {
|
||||||
|
val intent =
|
||||||
|
Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL).apply {
|
||||||
|
putExtra(
|
||||||
|
AudioEffect.EXTRA_AUDIO_SESSION,
|
||||||
|
binder?.player?.audioSessionId
|
||||||
|
)
|
||||||
|
putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.packageName)
|
||||||
|
putExtra(
|
||||||
|
AudioEffect.EXTRA_CONTENT_TYPE,
|
||||||
|
AudioEffect.CONTENT_TYPE_MUSIC
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intent.resolveActivity(context.packageManager) != null) {
|
||||||
|
activityResultLauncher.launch(intent)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(context, "No equalizer app found!", Toast.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,12 +12,8 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
@@ -54,6 +50,7 @@ import it.vfsfitvnm.vimusic.enums.SortOrder
|
|||||||
import it.vfsfitvnm.vimusic.models.Playlist
|
import it.vfsfitvnm.vimusic.models.Playlist
|
||||||
import it.vfsfitvnm.vimusic.models.PlaylistPreview
|
import it.vfsfitvnm.vimusic.models.PlaylistPreview
|
||||||
import it.vfsfitvnm.vimusic.query
|
import it.vfsfitvnm.vimusic.query
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.TextFieldDialog
|
import it.vfsfitvnm.vimusic.ui.components.themed.TextFieldDialog
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
@@ -164,25 +161,7 @@ fun PlaylistsTab(
|
|||||||
contentType = 0,
|
contentType = 0,
|
||||||
span = { GridItemSpan(maxLineSpan) }
|
span = { GridItemSpan(maxLineSpan) }
|
||||||
) {
|
) {
|
||||||
Column(
|
Header(title = "Playlists") {
|
||||||
horizontalAlignment = Alignment.End,
|
|
||||||
verticalArrangement = Arrangement.Bottom,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = 16.dp)
|
|
||||||
.height(128.dp)
|
|
||||||
.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
BasicText(
|
|
||||||
text = "Playlists",
|
|
||||||
style = typography.xxl.medium
|
|
||||||
)
|
|
||||||
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(vertical = 8.dp)
|
|
||||||
) {
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Item(
|
fun Item(
|
||||||
@DrawableRes iconId: Int,
|
@DrawableRes iconId: Int,
|
||||||
@@ -247,7 +226,6 @@ fun PlaylistsTab(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
item(key = "favorites") {
|
item(key = "favorites") {
|
||||||
BuiltInPlaylistItem(
|
BuiltInPlaylistItem(
|
||||||
|
|||||||
@@ -14,19 +14,14 @@ import androidx.compose.foundation.ExperimentalFoundationApi
|
|||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.foundation.text.BasicText
|
import androidx.compose.foundation.text.BasicText
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -34,7 +29,6 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
@@ -54,6 +48,7 @@ import it.vfsfitvnm.vimusic.enums.SongSortBy
|
|||||||
import it.vfsfitvnm.vimusic.enums.SortOrder
|
import it.vfsfitvnm.vimusic.enums.SortOrder
|
||||||
import it.vfsfitvnm.vimusic.enums.ThumbnailRoundness
|
import it.vfsfitvnm.vimusic.enums.ThumbnailRoundness
|
||||||
import it.vfsfitvnm.vimusic.models.DetailedSong
|
import it.vfsfitvnm.vimusic.models.DetailedSong
|
||||||
|
import it.vfsfitvnm.vimusic.ui.components.themed.Header
|
||||||
import it.vfsfitvnm.vimusic.ui.components.themed.InHistoryMediaItemMenu
|
import it.vfsfitvnm.vimusic.ui.components.themed.InHistoryMediaItemMenu
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
import it.vfsfitvnm.vimusic.ui.styling.Dimensions
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
@@ -63,7 +58,6 @@ import it.vfsfitvnm.vimusic.utils.center
|
|||||||
import it.vfsfitvnm.vimusic.utils.color
|
import it.vfsfitvnm.vimusic.utils.color
|
||||||
import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex
|
import it.vfsfitvnm.vimusic.utils.forcePlayAtIndex
|
||||||
import it.vfsfitvnm.vimusic.utils.getEnum
|
import it.vfsfitvnm.vimusic.utils.getEnum
|
||||||
import it.vfsfitvnm.vimusic.utils.medium
|
|
||||||
import it.vfsfitvnm.vimusic.utils.mutableStatePreferenceOf
|
import it.vfsfitvnm.vimusic.utils.mutableStatePreferenceOf
|
||||||
import it.vfsfitvnm.vimusic.utils.preferences
|
import it.vfsfitvnm.vimusic.utils.preferences
|
||||||
import it.vfsfitvnm.vimusic.utils.putEnum
|
import it.vfsfitvnm.vimusic.utils.putEnum
|
||||||
@@ -134,25 +128,7 @@ fun SongsTab(
|
|||||||
key = "header",
|
key = "header",
|
||||||
contentType = 0
|
contentType = 0
|
||||||
) {
|
) {
|
||||||
Column(
|
Header(title = "Songs") {
|
||||||
horizontalAlignment = Alignment.End,
|
|
||||||
verticalArrangement = Arrangement.Bottom,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = 16.dp)
|
|
||||||
.height(128.dp)
|
|
||||||
.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
BasicText(
|
|
||||||
text = "Songs",
|
|
||||||
style = typography.xxl.medium
|
|
||||||
)
|
|
||||||
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(vertical = 8.dp)
|
|
||||||
) {
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Item(
|
fun Item(
|
||||||
@DrawableRes iconId: Int,
|
@DrawableRes iconId: Int,
|
||||||
@@ -201,7 +177,6 @@ fun SongsTab(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
itemsIndexed(
|
itemsIndexed(
|
||||||
items = viewModel.items,
|
items = viewModel.items,
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ dependencyResolutionManagement {
|
|||||||
|
|
||||||
library("compose-shimmer", "com.valentinilk.shimmer", "compose-shimmer").version("1.0.3")
|
library("compose-shimmer", "com.valentinilk.shimmer", "compose-shimmer").version("1.0.3")
|
||||||
|
|
||||||
|
library("compose-viewmodel", "androidx.lifecycle", "lifecycle-viewmodel-compose").version("2.6.0-alpha02")
|
||||||
library("compose-activity", "androidx.activity", "activity-compose").version("1.5.1")
|
library("compose-activity", "androidx.activity", "activity-compose").version("1.5.1")
|
||||||
|
|
||||||
library("compose-coil", "io.coil-kt", "coil-compose").version("2.2.1")
|
library("compose-coil", "io.coil-kt", "coil-compose").version("2.2.1")
|
||||||
|
|||||||
Reference in New Issue
Block a user