Redesign SettingsScreen (#172)
This commit is contained in:
parent
563c6175f7
commit
6a3b41ca28
19 changed files with 847 additions and 1557 deletions
|
@ -84,6 +84,7 @@ dependencies {
|
|||
implementation(libs.compose.ripple)
|
||||
implementation(libs.compose.shimmer)
|
||||
implementation(libs.compose.coil)
|
||||
implementation(libs.compose.viewmodel)
|
||||
|
||||
implementation(libs.palette)
|
||||
|
||||
|
@ -97,6 +98,4 @@ dependencies {
|
|||
implementation(projects.kugou)
|
||||
|
||||
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
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.text.BasicText
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
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.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.*
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import it.vfsfitvnm.route.*
|
||||
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
||||
import it.vfsfitvnm.vimusic.R
|
||||
import it.vfsfitvnm.vimusic.ui.components.TopAppBar
|
||||
import it.vfsfitvnm.vimusic.ui.components.badge
|
||||
import it.vfsfitvnm.vimusic.ui.components.themed.Scaffold
|
||||
import it.vfsfitvnm.vimusic.ui.components.themed.Switch
|
||||
import it.vfsfitvnm.vimusic.ui.components.themed.ValueSelectorDialog
|
||||
import it.vfsfitvnm.vimusic.ui.screens.settings.*
|
||||
|
@ -29,192 +26,38 @@ import it.vfsfitvnm.vimusic.utils.*
|
|||
@ExperimentalAnimationApi
|
||||
@Composable
|
||||
fun SettingsScreen() {
|
||||
val scrollState = rememberScrollState()
|
||||
val saveableStateHolder = rememberSaveableStateHolder()
|
||||
|
||||
RouteHandler(
|
||||
listenToGlobalEmitter = true,
|
||||
transitionSpec = {
|
||||
when (targetState.route) {
|
||||
albumRoute, artistRoute -> fastFade
|
||||
else -> when (initialState.route) {
|
||||
albumRoute, artistRoute -> fastFade
|
||||
null -> leftSlide
|
||||
else -> rightSlide
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
val (tabIndex, onTabChanged) = rememberSaveable {
|
||||
mutableStateOf(0)
|
||||
}
|
||||
|
||||
RouteHandler(listenToGlobalEmitter = true) {
|
||||
globalRoutes()
|
||||
|
||||
appearanceSettingsRoute {
|
||||
AppearanceSettingsScreen()
|
||||
}
|
||||
|
||||
playerSettingsRoute {
|
||||
PlayerSettingsScreen()
|
||||
}
|
||||
|
||||
backupAndRestoreRoute {
|
||||
BackupAndRestoreScreen()
|
||||
}
|
||||
|
||||
cacheSettingsRoute {
|
||||
CacheSettingsScreen()
|
||||
}
|
||||
|
||||
otherSettingsRoute {
|
||||
OtherSettingsScreen()
|
||||
}
|
||||
|
||||
aboutRoute {
|
||||
AboutScreen()
|
||||
}
|
||||
|
||||
host {
|
||||
val (colorPalette, typography) = LocalAppearance.current
|
||||
|
||||
var isFirstLaunch by rememberPreference(isFirstLaunchKey, true)
|
||||
|
||||
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)
|
||||
)
|
||||
Scaffold(
|
||||
topIconButtonId = R.drawable.chevron_back,
|
||||
onTopIconButtonClick = pop,
|
||||
tabIndex = tabIndex,
|
||||
onTabChanged = onTabChanged,
|
||||
tabColumnContent = { Item ->
|
||||
Item(0, "Appearance", R.drawable.color_palette)
|
||||
Item(1, "Player", R.drawable.play)
|
||||
Item(2, "Cache", R.drawable.server)
|
||||
Item(3, "Other", R.drawable.shapes)
|
||||
Item(4, "About", R.drawable.information)
|
||||
}
|
||||
|
||||
BasicText(
|
||||
text = "Settings",
|
||||
style = typography.l.semiBold,
|
||||
modifier = Modifier
|
||||
.padding(start = 48.dp)
|
||||
.padding(all = 16.dp)
|
||||
)
|
||||
|
||||
@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
|
||||
)
|
||||
}
|
||||
) { currentTabIndex ->
|
||||
saveableStateHolder.SaveableStateProvider(currentTabIndex) {
|
||||
when (currentTabIndex) {
|
||||
0 -> AppearanceSettingsTab()
|
||||
1 -> PlayerSettingsTab()
|
||||
2 -> CacheSettingsTab()
|
||||
3 -> OtherSettingsTab()
|
||||
4 -> AboutTab()
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
.alpha(if (isEnabled) 1f else 0.5f)
|
||||
.padding(start = 24.dp)
|
||||
.padding(horizontal = 32.dp, vertical = 16.dp)
|
||||
.padding(start = 16.dp)
|
||||
.padding(all = 16.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
|
||||
|
@ -332,8 +175,7 @@ fun SettingsEntry(
|
|||
onClick: () -> Unit,
|
||||
isEnabled: Boolean = true
|
||||
) {
|
||||
val (_, typography) = LocalAppearance.current
|
||||
val (colorPalette) = LocalAppearance.current
|
||||
val (colorPalette, typography) = LocalAppearance.current
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
|
@ -344,8 +186,8 @@ fun SettingsEntry(
|
|||
enabled = isEnabled
|
||||
)
|
||||
.alpha(if (isEnabled) 1f else 0.5f)
|
||||
.padding(start = 24.dp)
|
||||
.padding(horizontal = 32.dp, vertical = 16.dp)
|
||||
.padding(start = 16.dp)
|
||||
.padding(all = 16.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
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
|
||||
fun SettingsDescription(
|
||||
text: String,
|
||||
|
@ -387,24 +213,8 @@ fun SettingsDescription(
|
|||
text = text,
|
||||
style = typography.xxs.secondary,
|
||||
modifier = modifier
|
||||
.padding(start = 56.dp, end = 24.dp)
|
||||
.padding(bottom = 16.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)
|
||||
.padding(start = 16.dp)
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -419,7 +229,17 @@ fun SettingsEntryGroupText(
|
|||
text = title.uppercase(),
|
||||
style = typography.xxs.semiBold.copy(colorPalette.accent),
|
||||
modifier = modifier
|
||||
.padding(start = 24.dp, top = 24.dp)
|
||||
.padding(horizontal = 32.dp)
|
||||
.padding(start = 16.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.interaction.MutableInteractionSource
|
||||
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.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
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.PlaylistPreview
|
||||
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.styling.Dimensions
|
||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||
|
@ -164,88 +161,69 @@ fun PlaylistsTab(
|
|||
contentType = 0,
|
||||
span = { GridItemSpan(maxLineSpan) }
|
||||
) {
|
||||
Column(
|
||||
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)
|
||||
Header(title = "Playlists") {
|
||||
@Composable
|
||||
fun Item(
|
||||
@DrawableRes iconId: Int,
|
||||
sortBy: PlaylistSortBy
|
||||
) {
|
||||
@Composable
|
||||
fun Item(
|
||||
@DrawableRes iconId: Int,
|
||||
sortBy: PlaylistSortBy
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(iconId),
|
||||
contentDescription = null,
|
||||
colorFilter = ColorFilter.tint(if (viewModel.sortBy == sortBy) colorPalette.text else colorPalette.textDisabled),
|
||||
modifier = Modifier
|
||||
.clickable { viewModel.sortBy = sortBy }
|
||||
.padding(all = 4.dp)
|
||||
.size(18.dp)
|
||||
)
|
||||
}
|
||||
|
||||
BasicText(
|
||||
text = "New playlist",
|
||||
style = typography.xxs.medium,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.clickable { isCreatingANewPlaylist = true }
|
||||
.background(colorPalette.background2)
|
||||
.padding(all = 8.dp)
|
||||
.padding(horizontal = 8.dp)
|
||||
)
|
||||
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
)
|
||||
|
||||
Item(
|
||||
iconId = R.drawable.medical,
|
||||
sortBy = PlaylistSortBy.SongCount
|
||||
)
|
||||
|
||||
Item(
|
||||
iconId = R.drawable.text,
|
||||
sortBy = PlaylistSortBy.Name
|
||||
)
|
||||
|
||||
Item(
|
||||
iconId = R.drawable.calendar,
|
||||
sortBy = PlaylistSortBy.DateAdded
|
||||
)
|
||||
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.width(2.dp)
|
||||
)
|
||||
|
||||
Image(
|
||||
painter = painterResource(R.drawable.arrow_up),
|
||||
painter = painterResource(iconId),
|
||||
contentDescription = null,
|
||||
colorFilter = ColorFilter.tint(colorPalette.text),
|
||||
colorFilter = ColorFilter.tint(if (viewModel.sortBy == sortBy) colorPalette.text else colorPalette.textDisabled),
|
||||
modifier = Modifier
|
||||
.clickable { viewModel.sortOrder = !viewModel.sortOrder }
|
||||
.clickable { viewModel.sortBy = sortBy }
|
||||
.padding(all = 4.dp)
|
||||
.size(18.dp)
|
||||
.graphicsLayer { rotationZ = sortOrderIconRotation }
|
||||
)
|
||||
}
|
||||
|
||||
BasicText(
|
||||
text = "New playlist",
|
||||
style = typography.xxs.medium,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.clickable { isCreatingANewPlaylist = true }
|
||||
.background(colorPalette.background2)
|
||||
.padding(all = 8.dp)
|
||||
.padding(horizontal = 8.dp)
|
||||
)
|
||||
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
)
|
||||
|
||||
Item(
|
||||
iconId = R.drawable.medical,
|
||||
sortBy = PlaylistSortBy.SongCount
|
||||
)
|
||||
|
||||
Item(
|
||||
iconId = R.drawable.text,
|
||||
sortBy = PlaylistSortBy.Name
|
||||
)
|
||||
|
||||
Item(
|
||||
iconId = R.drawable.calendar,
|
||||
sortBy = PlaylistSortBy.DateAdded
|
||||
)
|
||||
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.width(2.dp)
|
||||
)
|
||||
|
||||
Image(
|
||||
painter = painterResource(R.drawable.arrow_up),
|
||||
contentDescription = null,
|
||||
colorFilter = ColorFilter.tint(colorPalette.text),
|
||||
modifier = Modifier
|
||||
.clickable { viewModel.sortOrder = !viewModel.sortOrder }
|
||||
.padding(all = 4.dp)
|
||||
.size(18.dp)
|
||||
.graphicsLayer { rotationZ = sortOrderIconRotation }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,19 +14,14 @@ import androidx.compose.foundation.ExperimentalFoundationApi
|
|||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
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.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicText
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
|
@ -34,7 +29,6 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
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.ThumbnailRoundness
|
||||
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.styling.Dimensions
|
||||
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.forcePlayAtIndex
|
||||
import it.vfsfitvnm.vimusic.utils.getEnum
|
||||
import it.vfsfitvnm.vimusic.utils.medium
|
||||
import it.vfsfitvnm.vimusic.utils.mutableStatePreferenceOf
|
||||
import it.vfsfitvnm.vimusic.utils.preferences
|
||||
import it.vfsfitvnm.vimusic.utils.putEnum
|
||||
|
@ -134,72 +128,53 @@ fun SongsTab(
|
|||
key = "header",
|
||||
contentType = 0
|
||||
) {
|
||||
Column(
|
||||
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)
|
||||
Header(title = "Songs") {
|
||||
@Composable
|
||||
fun Item(
|
||||
@DrawableRes iconId: Int,
|
||||
sortBy: SongSortBy
|
||||
) {
|
||||
@Composable
|
||||
fun Item(
|
||||
@DrawableRes iconId: Int,
|
||||
sortBy: SongSortBy
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(iconId),
|
||||
contentDescription = null,
|
||||
colorFilter = ColorFilter.tint(if (viewModel.sortBy == sortBy) colorPalette.text else colorPalette.textDisabled),
|
||||
modifier = Modifier
|
||||
.clickable { viewModel.sortBy = sortBy }
|
||||
.padding(all = 4.dp)
|
||||
.size(18.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Item(
|
||||
iconId = R.drawable.trending,
|
||||
sortBy = SongSortBy.PlayTime
|
||||
)
|
||||
|
||||
Item(
|
||||
iconId = R.drawable.text,
|
||||
sortBy = SongSortBy.Title
|
||||
)
|
||||
|
||||
Item(
|
||||
iconId = R.drawable.calendar,
|
||||
sortBy = SongSortBy.DateAdded
|
||||
)
|
||||
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.width(2.dp)
|
||||
)
|
||||
|
||||
Image(
|
||||
painter = painterResource(R.drawable.arrow_up),
|
||||
painter = painterResource(iconId),
|
||||
contentDescription = null,
|
||||
colorFilter = ColorFilter.tint(colorPalette.text),
|
||||
colorFilter = ColorFilter.tint(if (viewModel.sortBy == sortBy) colorPalette.text else colorPalette.textDisabled),
|
||||
modifier = Modifier
|
||||
.clickable { viewModel.sortOrder = !viewModel.sortOrder }
|
||||
.clickable { viewModel.sortBy = sortBy }
|
||||
.padding(all = 4.dp)
|
||||
.size(18.dp)
|
||||
.graphicsLayer { rotationZ = sortOrderIconRotation }
|
||||
)
|
||||
}
|
||||
|
||||
Item(
|
||||
iconId = R.drawable.trending,
|
||||
sortBy = SongSortBy.PlayTime
|
||||
)
|
||||
|
||||
Item(
|
||||
iconId = R.drawable.text,
|
||||
sortBy = SongSortBy.Title
|
||||
)
|
||||
|
||||
Item(
|
||||
iconId = R.drawable.calendar,
|
||||
sortBy = SongSortBy.DateAdded
|
||||
)
|
||||
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.width(2.dp)
|
||||
)
|
||||
|
||||
Image(
|
||||
painter = painterResource(R.drawable.arrow_up),
|
||||
contentDescription = null,
|
||||
colorFilter = ColorFilter.tint(colorPalette.text),
|
||||
modifier = Modifier
|
||||
.clickable { viewModel.sortOrder = !viewModel.sortOrder }
|
||||
.padding(all = 4.dp)
|
||||
.size(18.dp)
|
||||
.graphicsLayer { rotationZ = sortOrderIconRotation }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ dependencyResolutionManagement {
|
|||
|
||||
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-coil", "io.coil-kt", "coil-compose").version("2.2.1")
|
||||
|
|
Loading…
Reference in a new issue