feat: Add import functionality

This commit is contained in:
Myzel394 2023-10-22 16:47:43 +02:00
parent d9420ddff5
commit 5e9f46d979
No known key found for this signature in database
GPG Key ID: 79CC92F37B3E1A2B
5 changed files with 154 additions and 17 deletions

View File

@ -5,7 +5,6 @@ import android.os.Build
import android.util.Log
import com.arthenica.ffmpegkit.FFmpegKit
import com.arthenica.ffmpegkit.ReturnCode
import kotlinx.coroutines.delay
import kotlinx.serialization.Serializable
import org.json.JSONObject
import java.io.File
@ -52,6 +51,19 @@ data class AppSettings(
)
}
fun exportToString(): String {
return JSONObject(
mapOf(
"_meta" to mapOf(
"version" to 1,
"date" to LocalDateTime.now().format(ISO_DATE_TIME),
"app" to "app.myzel394.alibi",
),
"data" to toJSONObject(),
)
).toString(0)
}
companion object {
fun getDefaultInstance(): AppSettings = AppSettings()
@ -63,6 +75,11 @@ data class AppSettings(
theme = Theme.valueOf(data.getString("theme")),
)
}
fun fromExportedString(data: String): AppSettings {
val json = JSONObject(data)
return fromJSONObject(json.getJSONObject("data"))
}
}
}

View File

@ -6,27 +6,118 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.Download
import androidx.compose.material.icons.filled.Upload
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
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.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import app.myzel394.alibi.R
import app.myzel394.alibi.dataStore
import app.myzel394.alibi.db.AppSettings
import app.myzel394.alibi.ui.utils.rememberFileSaverDialog
import app.myzel394.alibi.ui.utils.rememberFileSelectorDialog
import kotlinx.coroutines.launch
import java.io.File
@Composable
fun ImportExport() {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val dataStore = LocalContext.current.dataStore
val settings = dataStore
.data
.collectAsState(initial = AppSettings.getDefaultInstance())
.value
var settingsToBeImported by remember { mutableStateOf<AppSettings?>(null) }
val saveFile = rememberFileSaverDialog("application/json")
val openFile = rememberFileSelectorDialog { uri ->
val file = File.createTempFile("alibi_settings", ".json")
context.contentResolver.openInputStream(uri)!!.use {
it.copyTo(file.outputStream())
}
val rawContent = file.readText()
settingsToBeImported = AppSettings.fromExportedString(rawContent)
}
if (settingsToBeImported != null) {
AlertDialog(
onDismissRequest = {
settingsToBeImported = null
},
title = {
Text(stringResource(R.string.ui_settings_option_import_label))
},
text = {
Text(stringResource(R.string.ui_settings_option_import_dialog_text))
},
icon = {
Icon(
Icons.Default.Download,
contentDescription = null,
)
},
confirmButton = {
Button(
onClick = {
scope.launch {
dataStore.updateData {
settingsToBeImported!!
}
settingsToBeImported = null
}
},
) {
Icon(
Icons.Default.CheckCircle,
contentDescription = null,
modifier = Modifier.size(ButtonDefaults.IconSize),
)
Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing))
Text(stringResource(R.string.ui_settings_option_import_dialog_confirm))
}
},
dismissButton = {
Button(
onClick = {
settingsToBeImported = null
},
colors = ButtonDefaults.textButtonColors(),
) {
Text(stringResource(R.string.dialog_close_cancel_label))
}
},
)
}
Row(
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth(),
) {
Button(
onClick = { /*TODO*/ },
onClick = {
openFile("application/json")
},
colors = ButtonDefaults.filledTonalButtonColors(),
) {
Icon(
@ -38,7 +129,14 @@ fun ImportExport() {
Text(stringResource(R.string.ui_settings_option_import_label))
}
Button(
onClick = { /*TODO*/ },
onClick = {
val rawContent = settings.exportToString()
val tempFile = File.createTempFile("alibi_settings", ".json")
tempFile.writeText(rawContent)
saveFile(tempFile, "alibi_settings.json")
},
colors = ButtonDefaults.filledTonalButtonColors(),
) {
Icon(

View File

@ -64,7 +64,7 @@ fun AudioRecorder(
try {
val file = audioRecorder.lastRecording!!.concatenateFiles()
saveFile(file)
saveFile(file, file.name)
} catch (error: Exception) {
Log.getStackTraceString(error)
} finally {
@ -165,7 +165,7 @@ fun AudioRecorder(
}
)
},
) {padding ->
) { padding ->
Box(
modifier = Modifier
.fillMaxSize()

View File

@ -1,33 +1,53 @@
package app.myzel394.alibi.ui.utils
import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import java.io.File
@Composable
fun rememberFileSaverDialog(mimeType: String): ((File) -> Unit) {
fun rememberFileSaverDialog(mimeType: String): ((File, String) -> Unit) {
val context = LocalContext.current
var file = remember { mutableStateOf<File?>(null) }
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument(mimeType)) {
it?.let {
context.contentResolver.openOutputStream(it)?.use { outputStream ->
file.value!!.inputStream().use { inputStream ->
inputStream.copyTo(outputStream)
val launcher =
rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument(mimeType)) {
it?.let {
context.contentResolver.openOutputStream(it)?.use { outputStream ->
file.value!!.inputStream().use { inputStream ->
inputStream.copyTo(outputStream)
}
}
}
file.value = null
}
return { it, name ->
file.value = it
launcher.launch(name ?: it.name)
}
}
@Composable
fun rememberFileSelectorDialog(
callback: (Uri) -> Unit
): ((String) -> Unit) {
val launcher =
rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) {
if (it != null) {
callback(it)
}
}
file.value = null
}
return {
file.value = it
launcher.launch(it.name)
return { mimeType ->
launcher.launch(arrayOf(mimeType))
}
}

View File

@ -65,4 +65,6 @@
<string name="ui_settings_language_update_label">Change</string>
<string name="ui_settings_option_import_label">Import Settings</string>
<string name="ui_settings_option_export_label">Export Settings</string>
<string name="ui_settings_option_import_dialog_text">Are you sure you want to import these settings? Your current settings will be overwritten!</string>
<string name="ui_settings_option_import_dialog_confirm">Import settings</string>
</resources>