Redesign SettingsScreen (#172)
This commit is contained in:
@@ -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()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user