diff --git a/app/src/main/java/app/myzel394/alibi/db/AppSettings.kt b/app/src/main/java/app/myzel394/alibi/db/AppSettings.kt index 33c951d..2fd5abc 100644 --- a/app/src/main/java/app/myzel394/alibi/db/AppSettings.kt +++ b/app/src/main/java/app/myzel394/alibi/db/AppSettings.kt @@ -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")) + } } } diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/atoms/ImportExport.kt b/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/atoms/ImportExport.kt index 698ff2b..b42888e 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/atoms/ImportExport.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/SettingsScreen/atoms/ImportExport.kt @@ -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(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( diff --git a/app/src/main/java/app/myzel394/alibi/ui/screens/AudioRecorder.kt b/app/src/main/java/app/myzel394/alibi/ui/screens/AudioRecorder.kt index 0db7196..1e121bf 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/screens/AudioRecorder.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/screens/AudioRecorder.kt @@ -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() diff --git a/app/src/main/java/app/myzel394/alibi/ui/utils/file.kt b/app/src/main/java/app/myzel394/alibi/ui/utils/file.kt index 8838d04..cd50b80 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/utils/file.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/utils/file.kt @@ -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(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)) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 34a9509..e67d141 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -65,4 +65,6 @@ Change Import Settings Export Settings + Are you sure you want to import these settings? Your current settings will be overwritten! + Import settings \ No newline at end of file