Add DatabaseSettings tab to SettingsScreen
This commit is contained in:
@@ -0,0 +1,188 @@
|
|||||||
|
package it.vfsfitvnm.vimusic.ui.screens.settings
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
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.mutableStateOf
|
||||||
|
import androidx.compose.runtime.saveable.autoSaver
|
||||||
|
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.styling.LocalAppearance
|
||||||
|
import it.vfsfitvnm.vimusic.utils.intent
|
||||||
|
import it.vfsfitvnm.vimusic.utils.produceSaveableState
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
|
||||||
|
@ExperimentalAnimationApi
|
||||||
|
@Composable
|
||||||
|
fun DatabaseSettings() {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val (colorPalette) = LocalAppearance.current
|
||||||
|
|
||||||
|
var isShowingRestoreDialog by rememberSaveable {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
val queriesCount by produceSaveableState(initialValue = 0, stateSaver = autoSaver()) {
|
||||||
|
Database.queriesCount().flowOn(Dispatchers.IO).distinctUntilChanged().collect { value = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
val openDocumentContract = ActivityResultContracts.OpenDocument()
|
||||||
|
val createDocumentContract = ActivityResultContracts.CreateDocument("application/vnd.sqlite3")
|
||||||
|
|
||||||
|
val backupLauncher = rememberLauncherForActivityResult(createDocumentContract) { 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(openDocumentContract) { 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 = {
|
||||||
|
val input = arrayOf(
|
||||||
|
"application/x-sqlite3",
|
||||||
|
"application/vnd.sqlite3",
|
||||||
|
"application/octet-stream"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (openDocumentContract.createIntent(context, input)
|
||||||
|
.resolveActivity(context.packageManager) != null
|
||||||
|
) {
|
||||||
|
restoreLauncher.launch(input)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
"Can't read the database from the external storage",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmText = "Ok"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(colorPalette.background0)
|
||||||
|
.fillMaxSize()
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(LocalPlayerAwarePaddingValues.current)
|
||||||
|
) {
|
||||||
|
Header(title = "Database")
|
||||||
|
|
||||||
|
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 = "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")
|
||||||
|
val input = "vimusic_${dateFormat.format(Date())}.db"
|
||||||
|
|
||||||
|
if (createDocumentContract.createIntent(context, input)
|
||||||
|
.resolveActivity(context.packageManager) != null
|
||||||
|
) {
|
||||||
|
backupLauncher.launch(input)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
"Can't copy the database to the external storage",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
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 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,34 +16,18 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import it.vfsfitvnm.vimusic.Database
|
|
||||||
import it.vfsfitvnm.vimusic.LocalPlayerAwarePaddingValues
|
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.components.themed.Header
|
||||||
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
import it.vfsfitvnm.vimusic.ui.styling.LocalAppearance
|
||||||
import it.vfsfitvnm.vimusic.utils.intent
|
|
||||||
import it.vfsfitvnm.vimusic.utils.isIgnoringBatteryOptimizations
|
import it.vfsfitvnm.vimusic.utils.isIgnoringBatteryOptimizations
|
||||||
import it.vfsfitvnm.vimusic.utils.isInvincibilityEnabledKey
|
import it.vfsfitvnm.vimusic.utils.isInvincibilityEnabledKey
|
||||||
import it.vfsfitvnm.vimusic.utils.rememberPreference
|
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
|
@ExperimentalAnimationApi
|
||||||
@Composable
|
@Composable
|
||||||
@@ -51,79 +35,17 @@ fun OtherSettings() {
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val (colorPalette) = LocalAppearance.current
|
val (colorPalette) = LocalAppearance.current
|
||||||
|
|
||||||
val queriesCount by remember {
|
|
||||||
Database.queriesCount()
|
|
||||||
}.collectAsState(initial = 0, context = Dispatchers.IO)
|
|
||||||
|
|
||||||
var isInvincibilityEnabled by rememberPreference(isInvincibilityEnabledKey, false)
|
var isInvincibilityEnabled by rememberPreference(isInvincibilityEnabledKey, false)
|
||||||
|
|
||||||
var isIgnoringBatteryOptimizations by remember {
|
var isIgnoringBatteryOptimizations by remember {
|
||||||
mutableStateOf(context.isIgnoringBatteryOptimizations)
|
mutableStateOf(context.isIgnoringBatteryOptimizations)
|
||||||
}
|
}
|
||||||
|
|
||||||
var isShowingRestoreDialog by rememberSaveable {
|
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
val activityResultLauncher =
|
val activityResultLauncher =
|
||||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
isIgnoringBatteryOptimizations = context.isIgnoringBatteryOptimizations
|
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(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(colorPalette.background0)
|
.background(colorPalette.background0)
|
||||||
@@ -133,25 +55,6 @@ fun OtherSettings() {
|
|||||||
) {
|
) {
|
||||||
Header(title = "Other")
|
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")
|
SettingsEntryGroupText(title = "SERVICE LIFETIME")
|
||||||
|
|
||||||
SettingsDescription(text = "If battery optimizations are applied, the playback notification can suddenly disappear when paused.")
|
SettingsDescription(text = "If battery optimizations are applied, the playback notification can suddenly disappear when paused.")
|
||||||
@@ -202,35 +105,5 @@ fun OtherSettings() {
|
|||||||
isChecked = isInvincibilityEnabled,
|
isChecked = isInvincibilityEnabled,
|
||||||
onCheckedChange = { isInvincibilityEnabled = it }
|
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
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,8 +46,9 @@ fun SettingsScreen() {
|
|||||||
Item(0, "Appearance", R.drawable.color_palette)
|
Item(0, "Appearance", R.drawable.color_palette)
|
||||||
Item(1, "Player", R.drawable.play)
|
Item(1, "Player", R.drawable.play)
|
||||||
Item(2, "Cache", R.drawable.server)
|
Item(2, "Cache", R.drawable.server)
|
||||||
Item(3, "Other", R.drawable.shapes)
|
Item(3, "Database", R.drawable.server)
|
||||||
Item(4, "About", R.drawable.information)
|
Item(4, "Other", R.drawable.shapes)
|
||||||
|
Item(5, "About", R.drawable.information)
|
||||||
}
|
}
|
||||||
) { currentTabIndex ->
|
) { currentTabIndex ->
|
||||||
saveableStateHolder.SaveableStateProvider(currentTabIndex) {
|
saveableStateHolder.SaveableStateProvider(currentTabIndex) {
|
||||||
@@ -55,8 +56,9 @@ fun SettingsScreen() {
|
|||||||
0 -> AppearanceSettings()
|
0 -> AppearanceSettings()
|
||||||
1 -> PlayerSettings()
|
1 -> PlayerSettings()
|
||||||
2 -> CacheSettings()
|
2 -> CacheSettings()
|
||||||
3 -> OtherSettings()
|
3 -> DatabaseSettings()
|
||||||
4 -> About()
|
4 -> OtherSettings()
|
||||||
|
5 -> About()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user