feat: Add SaveFolderTile

This commit is contained in:
Myzel394 2023-10-30 14:02:02 +01:00
parent 6605f44eec
commit aa8fd2a37f
No known key found for this signature in database
GPG Key ID: 79CC92F37B3E1A2B
6 changed files with 222 additions and 8 deletions

View File

@ -173,10 +173,6 @@ data class AudioRecorderSettings(
runCatching {
return File(saveFolder!!).apply {
if (!AudioRecorderExporter.canFolderBeUsed(this)) {
throw SecurityException("Can't write to folder")
}
if (!exists()) {
mkdirs()
}

View File

@ -114,9 +114,5 @@ data class AudioRecorderExporter(
fun hasRecordingsAvailable(context: Context) =
getFolder(context).listFiles()?.isNotEmpty() ?: false
// Write required for saving the audio files
// Read required for concatenating the audio files
fun canFolderBeUsed(file: File) = file.canRead() && file.canWrite()
}
}

View File

@ -0,0 +1,196 @@
package app.myzel394.alibi.ui.components.SettingsScreen.atoms
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AudioFile
import androidx.compose.material.icons.filled.Cancel
import androidx.compose.material.icons.filled.Folder
import androidx.compose.material.icons.filled.Lock
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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 androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.core.net.toFile
import app.myzel394.alibi.R
import app.myzel394.alibi.dataStore
import app.myzel394.alibi.db.AppSettings
import app.myzel394.alibi.helpers.AudioRecorderExporter
import app.myzel394.alibi.ui.components.atoms.SettingsTile
import app.myzel394.alibi.ui.utils.rememberFolderSelectorDialog
import com.maxkeppeker.sheets.core.models.base.rememberUseCaseState
import kotlinx.coroutines.launch
import java.io.File
@Composable
fun SaveFolderTile(
settings: AppSettings,
) {
val scope = rememberCoroutineScope()
val context = LocalContext.current
val dataStore = context.dataStore
fun updateValue(path: String?) {
scope.launch {
dataStore.updateData {
it.setAudioRecorderSettings(
it.audioRecorderSettings.setSaveFolder(path)
)
}
}
}
val selectFolder = rememberFolderSelectorDialog { folder ->
if (folder == null) {
return@rememberFolderSelectorDialog
}
updateValue(folder.path)
}
var showWarning by remember { mutableStateOf(false) }
if (showWarning) {
val title = stringResource(R.string.ui_settings_option_saveFolder_warning_title)
val text = stringResource(R.string.ui_settings_option_saveFolder_warning_text)
AlertDialog(
icon = {
Icon(
Icons.Default.Warning,
contentDescription = null,
)
},
onDismissRequest = {
showWarning = false
},
title = {
Text(text = title)
},
text = {
Text(text = text)
},
confirmButton = {
Button(
onClick = {
showWarning = false
selectFolder()
},
) {
Text(
text = stringResource(R.string.ui_settings_option_saveFolder_warning_action_confirm),
)
}
},
dismissButton = {
Button(
onClick = {
showWarning = false
},
colors = ButtonDefaults.textButtonColors(),
) {
Icon(
Icons.Default.Cancel,
contentDescription = null,
modifier = Modifier.size(ButtonDefaults.IconSize),
)
Spacer(modifier = Modifier.width(ButtonDefaults.IconSpacing))
Text(stringResource(R.string.dialog_close_cancel_label))
}
}
)
}
SettingsTile(
title = stringResource(R.string.ui_settings_option_saveFolder_title),
description = stringResource(R.string.ui_settings_option_saveFolder_explanation),
leading = {
Icon(
Icons.Default.AudioFile,
contentDescription = null,
)
},
trailing = {
Button(
onClick = {
showWarning = true
},
colors = ButtonDefaults.filledTonalButtonColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
),
shape = MaterialTheme.shapes.medium,
) {
Icon(
Icons.Default.Folder,
contentDescription = null,
modifier = Modifier.size(ButtonDefaults.IconSize),
)
Spacer(
modifier = Modifier.size(ButtonDefaults.IconSpacing)
)
Text(
text = stringResource(R.string.ui_settings_option_saveFolder_action_select_label),
)
}
},
extra = {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
if (settings.audioRecorderSettings.saveFolder != null) {
Button(
colors = ButtonDefaults.filledTonalButtonColors(),
onClick = {
updateValue(null)
}
) {
Icon(
Icons.Default.Lock,
contentDescription = null,
modifier = Modifier.size(ButtonDefaults.IconSize),
)
Spacer(
modifier = Modifier.size(ButtonDefaults.IconSpacing)
)
Text(
text = stringResource(R.string.ui_settings_option_saveFolder_action_default_label),
)
}
}
Text(
text = stringResource(
R.string.form_value_selected,
settings.audioRecorderSettings.saveFolder
?: stringResource(R.string.ui_settings_option_saveFolder_defaultValue)
),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth(),
)
}
}
)
}

View File

@ -51,6 +51,7 @@ import app.myzel394.alibi.ui.components.SettingsScreen.atoms.IntervalDurationTil
import app.myzel394.alibi.ui.components.SettingsScreen.atoms.MaxDurationTile
import app.myzel394.alibi.ui.components.SettingsScreen.atoms.OutputFormatTile
import app.myzel394.alibi.ui.components.SettingsScreen.atoms.SamplingRateTile
import app.myzel394.alibi.ui.components.SettingsScreen.atoms.SaveFolderTile
import app.myzel394.alibi.ui.components.SettingsScreen.atoms.ShowAllMicrophonesTile
import app.myzel394.alibi.ui.components.SettingsScreen.atoms.ThemeSelector
import app.myzel394.alibi.ui.components.atoms.GlobalSwitch
@ -161,6 +162,7 @@ fun SettingsScreen(
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 32.dp)
)
SaveFolderTile(settings = settings)
ShowAllMicrophonesTile(settings = settings)
BitrateTile(settings = settings)
SamplingRateTile(settings = settings)

View File

@ -56,3 +56,18 @@ fun rememberFileSelectorDialog(
launcher.launch(arrayOf(mimeType))
}
}
@Composable
fun rememberFolderSelectorDialog(
callback: (Uri?) -> Unit
): (() -> Unit) {
val launcher =
rememberLauncherForActivityResult(
ActivityResultContracts.OpenDocumentTree(),
callback,
)
return {
launcher.launch(null)
}
}

View File

@ -9,6 +9,7 @@
<string name="form_error_type_notNumber">Please enter a valid number</string>
<string name="form_error_value_notInRange">Please enter a number between <xliff:g name="min">%s</xliff:g> and <xliff:g name="max">%s</xliff:g></string>
<string name="form_error_value_mustBeGreaterThan">Please enter a number greater than <xliff:g name="min">%s</xliff:g></string>
<string name="form_value_selected">Selected: %s</string>
<string name="notificationChannels_recorder_name">Recorder</string>
<string name="notificationChannels_recorder_description">Shows the current recording status</string>
@ -109,4 +110,12 @@
<string name="ui_about_contribute_donation_githubSponsors">Become a GitHub Sponsor</string>
<string name="ui_settings_option_deleteRecordingsImmediately_title">Delete Recordings Immediately</string>
<string name="ui_settings_option_deleteRecordingsImmediately_description">If enabled, Alibi will immediately delete recordings after you have saved the file.</string>
<string name="ui_settings_option_saveFolder_title">Batches folder</string>
<string name="ui_settings_option_saveFolder_explanation">Where Alibi should store the temporary batches of your recordings.</string>
<string name="ui_settings_option_saveFolder_action_select_label">Select</string>
<string name="ui_settings_option_saveFolder_defaultValue">Encrypted Internal Storage</string>
<string name="ui_settings_option_saveFolder_warning_title">Are you sure you want to change the folder?</string>
<string name="ui_settings_option_saveFolder_warning_text">By default, Alibi will save the recording batches into its private, encrypted file storage. You can change this and specify an external, unencrypted folder. This will allow you to access the batches manually. ONLY DO THIS IF YOU KNOW WHAT YOU ARE DOING!</string>
<string name="ui_settings_option_saveFolder_warning_action_confirm">Yes, change folder</string>
<string name="ui_settings_option_saveFolder_action_default_label">Use private, encrypted storage</string>
</resources>