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.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.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
|
||||
@@ -51,79 +35,17 @@ fun OtherSettings() {
|
||||
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)
|
||||
@@ -133,25 +55,6 @@ fun OtherSettings() {
|
||||
) {
|
||||
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.")
|
||||
@@ -202,35 +105,5 @@ fun OtherSettings() {
|
||||
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
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,8 +46,9 @@ fun SettingsScreen() {
|
||||
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)
|
||||
Item(3, "Database", R.drawable.server)
|
||||
Item(4, "Other", R.drawable.shapes)
|
||||
Item(5, "About", R.drawable.information)
|
||||
}
|
||||
) { currentTabIndex ->
|
||||
saveableStateHolder.SaveableStateProvider(currentTabIndex) {
|
||||
@@ -55,8 +56,9 @@ fun SettingsScreen() {
|
||||
0 -> AppearanceSettings()
|
||||
1 -> PlayerSettings()
|
||||
2 -> CacheSettings()
|
||||
3 -> OtherSettings()
|
||||
4 -> About()
|
||||
3 -> DatabaseSettings()
|
||||
4 -> OtherSettings()
|
||||
5 -> About()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user