feat: Add permission required dialog to SaveFolder; some refactoring

This commit is contained in:
Myzel394 2023-12-31 22:40:20 +01:00
parent f4334bf26b
commit 5e3e2a2e22
No known key found for this signature in database
GPG Key ID: 79CC92F37B3E1A2B
2 changed files with 239 additions and 130 deletions

View File

@ -1,5 +1,6 @@
package app.myzel394.alibi.ui.components.SettingsScreen.Tiles package app.myzel394.alibi.ui.components.SettingsScreen.Tiles
import android.Manifest
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@ -28,6 +29,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.SheetState
import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -41,7 +43,6 @@ 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.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@ -57,9 +58,10 @@ import app.myzel394.alibi.ui.SHEET_BOTTOM_OFFSET
import app.myzel394.alibi.ui.SUPPORTS_SCOPED_STORAGE import app.myzel394.alibi.ui.SUPPORTS_SCOPED_STORAGE
import app.myzel394.alibi.ui.components.atoms.MessageBox import app.myzel394.alibi.ui.components.atoms.MessageBox
import app.myzel394.alibi.ui.components.atoms.MessageType import app.myzel394.alibi.ui.components.atoms.MessageType
import app.myzel394.alibi.ui.components.atoms.PermissionRequester
import app.myzel394.alibi.ui.components.atoms.SettingsTile import app.myzel394.alibi.ui.components.atoms.SettingsTile
import app.myzel394.alibi.ui.utils.PermissionHelper
import app.myzel394.alibi.ui.utils.rememberFolderSelectorDialog import app.myzel394.alibi.ui.utils.rememberFolderSelectorDialog
import com.maxkeppeker.sheets.core.models.base.SelectionButton
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.net.URLDecoder import java.net.URLDecoder
@ -102,67 +104,6 @@ fun SaveFolderTile(
} }
} }
val selectFolder = rememberFolderSelectorDialog { folder ->
if (folder == null) {
return@rememberFolderSelectorDialog
}
updateValue(folder.toString())
}
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))
}
}
)
}
var selectionVisible by remember { mutableStateOf(false) } var selectionVisible by remember { mutableStateOf(false) }
val selectionSheetState = rememberModalBottomSheetState(true) val selectionSheetState = rememberModalBottomSheetState(true)
@ -174,75 +115,16 @@ fun SaveFolderTile(
} }
if (selectionVisible) { if (selectionVisible) {
ModalBottomSheet( SelectionSheet(
sheetState = selectionSheetState, sheetState = selectionSheetState,
onDismissRequest = ::hideSheet, updateValue = { path ->
) { updateValue(path)
Column(
modifier = Modifier
.padding(horizontal = 16.dp)
.padding(bottom = SHEET_BOTTOM_OFFSET),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(24.dp),
) {
Text(
stringResource(R.string.ui_settings_option_saveFolder_title),
style = MaterialTheme.typography.headlineMedium,
textAlign = TextAlign.Center,
)
SelectionButton(
label = stringResource(R.string.ui_settings_option_saveFolder_action_default_label),
icon = Icons.Default.Lock,
onClick = {
hideSheet() hideSheet()
updateValue(null)
}, },
onDismiss = ::hideSheet,
) )
}
Divider()
SelectionButton(
label = stringResource(R.string.ui_settings_option_saveFolder_action_dcim_label),
icon = Icons.Default.PermMedia,
onClick = {
hideSheet()
updateValue(RECORDER_MEDIA_SELECTED_VALUE)
},
)
Divider()
Column {
SelectionButton(
label = stringResource(R.string.ui_settings_option_saveFolder_action_custom_label),
icon = Icons.Default.Folder,
onClick = {
hideSheet()
showWarning = true
},
)
if (!SUPPORTS_SCOPED_STORAGE) {
Column(
modifier = Modifier.padding(horizontal = 32.dp, vertical = 12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
MessageBox(
type = MessageType.INFO,
message = stringResource(R.string.ui_settings_option_saveFolder_videoUnsupported),
)
Text(
stringResource(R.string.ui_minApiRequired, 8, 26),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface,
)
}
}
}
}
}
}
SettingsTile( SettingsTile(
title = stringResource(R.string.ui_settings_option_saveFolder_title), title = stringResource(R.string.ui_settings_option_saveFolder_title),
description = stringResource(R.string.ui_settings_option_saveFolder_explanation), description = stringResource(R.string.ui_settings_option_saveFolder_explanation),
@ -294,6 +176,126 @@ fun SaveFolderTile(
) )
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SelectionSheet(
sheetState: SheetState,
updateValue: (String?) -> Unit,
onDismiss: () -> Unit,
) {
val context = LocalContext.current
var showCustomFolderWarning by remember { mutableStateOf(false) }
if (showCustomFolderWarning) {
val selectFolder = rememberFolderSelectorDialog { folder ->
if (folder == null) {
return@rememberFolderSelectorDialog
}
updateValue(folder.toString())
}
CustomFolderWarningDialog(
onDismiss = {
showCustomFolderWarning = false
},
onConfirm = {
showCustomFolderWarning = false
selectFolder()
},
)
}
var showExternalPermissionRequired by remember { mutableStateOf(false) }
if (showExternalPermissionRequired) {
ExternalPermissionRequiredDialog(
onDismiss = {
showExternalPermissionRequired = false
},
onGranted = {
showExternalPermissionRequired = false
updateValue(RECORDER_MEDIA_SELECTED_VALUE)
},
)
}
ModalBottomSheet(
sheetState = sheetState,
onDismissRequest = onDismiss,
) {
Column(
modifier = Modifier
.padding(horizontal = 16.dp)
.padding(bottom = SHEET_BOTTOM_OFFSET),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(24.dp),
) {
Text(
stringResource(R.string.ui_settings_option_saveFolder_title),
style = MaterialTheme.typography.headlineMedium,
textAlign = TextAlign.Center,
)
SelectionButton(
label = stringResource(R.string.ui_settings_option_saveFolder_action_default_label),
icon = Icons.Default.Lock,
onClick = {
updateValue(null)
},
)
Divider()
SelectionButton(
label = stringResource(R.string.ui_settings_option_saveFolder_action_dcim_label),
icon = Icons.Default.PermMedia,
onClick = {
if (PermissionHelper.hasGranted(
context,
Manifest.permission.READ_EXTERNAL_STORAGE
)
) {
updateValue(RECORDER_MEDIA_SELECTED_VALUE)
} else {
showExternalPermissionRequired = true
}
},
)
Divider()
Column {
SelectionButton(
label = stringResource(R.string.ui_settings_option_saveFolder_action_custom_label),
icon = Icons.Default.Folder,
onClick = {
showCustomFolderWarning = true
},
)
if (!SUPPORTS_SCOPED_STORAGE) {
Column(
modifier = Modifier.padding(horizontal = 32.dp, vertical = 12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
MessageBox(
type = MessageType.INFO,
message = stringResource(R.string.ui_settings_option_saveFolder_videoUnsupported),
)
Text(
stringResource(R.string.ui_minApiRequired, 8, 26),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface,
)
}
}
}
}
}
}
@Composable @Composable
fun SelectionButton( fun SelectionButton(
label: String, label: String,
@ -325,6 +327,110 @@ fun SelectionButton(
} }
} }
@Composable
fun CustomFolderWarningDialog(
onDismiss: () -> Unit,
onConfirm: () -> Unit,
) {
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 = onDismiss,
title = {
Text(text = title)
},
text = {
Text(text = text)
},
confirmButton = {
Button(
onClick = onConfirm,
) {
Text(
text = stringResource(R.string.ui_settings_option_saveFolder_warning_action_confirm),
)
}
},
dismissButton = {
Button(
onClick = onDismiss,
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))
}
}
)
}
@Composable
fun ExternalPermissionRequiredDialog(
onDismiss: () -> Unit,
onGranted: () -> Unit,
) {
PermissionRequester(
icon = Icons.Default.PermMedia,
permission = Manifest.permission.READ_EXTERNAL_STORAGE,
onPermissionAvailable = onGranted,
) { trigger ->
AlertDialog(
icon = {
Icon(
Icons.Default.Warning,
contentDescription = null,
)
},
onDismissRequest = onDismiss,
title = {
Text(
stringResource(R.string.ui_settings_option_saveFolder_externalPermissionRequired_title),
)
},
text = {
Text(
stringResource(R.string.ui_settings_option_saveFolder_externalPermissionRequired_text),
)
},
confirmButton = {
Button(
onClick = trigger,
) {
Text(
stringResource(R.string.ui_settings_option_saveFolder_externalPermissionRequired_action_confirm),
)
}
},
dismissButton = {
Button(
onClick = onDismiss,
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))
}
}
)
}
}
fun splitPath(path: String): List<String> { fun splitPath(path: String): List<String> {
return try { return try {
URLDecoder URLDecoder

View File

@ -173,4 +173,7 @@
<string name="ui_settings_option_saveFolder_action_dcim_label">Use the DCIM folder</string> <string name="ui_settings_option_saveFolder_action_dcim_label">Use the DCIM folder</string>
<string name="ui_settings_option_saveFolder_dcimValue">DCIM Folder</string> <string name="ui_settings_option_saveFolder_dcimValue">DCIM Folder</string>
<string name="ui_settings_option_saveFolder_success">Batches Folder has been changed successfully</string> <string name="ui_settings_option_saveFolder_success">Batches Folder has been changed successfully</string>
<string name="ui_settings_option_saveFolder_externalPermissionRequired_title">Permission required</string>
<string name="ui_settings_option_saveFolder_externalPermissionRequired_text">To access the DCIM folder, you need to grant Alibi the permission to access external storage. Alibi will only use this permission to write your recordings to the DCIM folder.</string>
<string name="ui_settings_option_saveFolder_externalPermissionRequired_action_confirm">Grant permission</string>
</resources> </resources>