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 android.util.Log
import com.arthenica.ffmpegkit.FFmpegKit import com.arthenica.ffmpegkit.FFmpegKit
import com.arthenica.ffmpegkit.ReturnCode import com.arthenica.ffmpegkit.ReturnCode
import kotlinx.coroutines.delay
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import org.json.JSONObject import org.json.JSONObject
import java.io.File 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 { companion object {
fun getDefaultInstance(): AppSettings = AppSettings() fun getDefaultInstance(): AppSettings = AppSettings()
@ -63,6 +75,11 @@ data class AppSettings(
theme = Theme.valueOf(data.getString("theme")), 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.fillMaxWidth
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons 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.Download
import androidx.compose.material.icons.filled.Upload import androidx.compose.material.icons.filled.Upload
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable 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.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import app.myzel394.alibi.R 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 @Composable
fun ImportExport() { 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( Row(
horizontalArrangement = Arrangement.SpaceEvenly, horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) { ) {
Button( Button(
onClick = { /*TODO*/ }, onClick = {
openFile("application/json")
},
colors = ButtonDefaults.filledTonalButtonColors(), colors = ButtonDefaults.filledTonalButtonColors(),
) { ) {
Icon( Icon(
@ -38,7 +129,14 @@ fun ImportExport() {
Text(stringResource(R.string.ui_settings_option_import_label)) Text(stringResource(R.string.ui_settings_option_import_label))
} }
Button( 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(), colors = ButtonDefaults.filledTonalButtonColors(),
) { ) {
Icon( Icon(

View File

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

View File

@ -1,20 +1,24 @@
package app.myzel394.alibi.ui.utils package app.myzel394.alibi.ui.utils
import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
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.setValue
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import java.io.File import java.io.File
@Composable @Composable
fun rememberFileSaverDialog(mimeType: String): ((File) -> Unit) { fun rememberFileSaverDialog(mimeType: String): ((File, String) -> Unit) {
val context = LocalContext.current val context = LocalContext.current
var file = remember { mutableStateOf<File?>(null) } var file = remember { mutableStateOf<File?>(null) }
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument(mimeType)) { val launcher =
rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument(mimeType)) {
it?.let { it?.let {
context.contentResolver.openOutputStream(it)?.use { outputStream -> context.contentResolver.openOutputStream(it)?.use { outputStream ->
file.value!!.inputStream().use { inputStream -> file.value!!.inputStream().use { inputStream ->
@ -26,8 +30,24 @@ fun rememberFileSaverDialog(mimeType: String): ((File) -> Unit) {
file.value = null file.value = null
} }
return { return { it, name ->
file.value = it file.value = it
launcher.launch(it.name) launcher.launch(name ?: it.name)
}
}
@Composable
fun rememberFileSelectorDialog(
callback: (Uri) -> Unit
): ((String) -> Unit) {
val launcher =
rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) {
if (it != null) {
callback(it)
}
}
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_language_update_label">Change</string>
<string name="ui_settings_option_import_label">Import Settings</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_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> </resources>