diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/WelcomeScreen/atoms/SaveFolderSelection.kt b/app/src/main/java/app/myzel394/alibi/ui/components/WelcomeScreen/atoms/SaveFolderSelection.kt new file mode 100644 index 0000000..c84fe60 --- /dev/null +++ b/app/src/main/java/app/myzel394/alibi/ui/components/WelcomeScreen/atoms/SaveFolderSelection.kt @@ -0,0 +1,110 @@ +package app.myzel394.alibi.ui.components.WelcomeScreen.atoms + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Folder +import androidx.compose.material.icons.filled.Lock +import androidx.compose.material.icons.filled.PermMedia +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.RadioButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.unit.dp +import app.myzel394.alibi.R +import app.myzel394.alibi.db.AppSettings +import app.myzel394.alibi.ui.RECORDER_MEDIA_SELECTED_VALUE +import app.myzel394.alibi.ui.components.atoms.MessageBox +import app.myzel394.alibi.ui.components.atoms.MessageType + +const val CUSTOM_FOLDER = "custom" + +@Composable +fun SaveFolderSelection( + modifier: Modifier = Modifier, + appSettings: AppSettings, + saveFolder: String?, + isLowOnStorage: Boolean, + onSaveFolderChange: (String?) -> Unit, +) { + val OPTIONS = mapOf>( + null to (stringResource(R.string.ui_welcome_saveFolder_values_internal) to Icons.Default.Lock), + RECORDER_MEDIA_SELECTED_VALUE to (stringResource(R.string.ui_welcome_saveFolder_values_media) to Icons.Default.PermMedia), + CUSTOM_FOLDER to (stringResource(R.string.ui_welcome_saveFolder_values_custom) to Icons.Default.Folder), + ) + + Column( + verticalArrangement = Arrangement.spacedBy(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .clip(MaterialTheme.shapes.medium) + .background(MaterialTheme.colorScheme.surfaceContainer) + .then(modifier), + verticalArrangement = Arrangement.Center, + ) { + for ((folder, pair) in OPTIONS) { + val (label, icon) = pair + val a11yLabel = stringResource( + R.string.a11y_selectValue, + label + ) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier + .fillMaxWidth() + .clip(MaterialTheme.shapes.medium) + .semantics { + contentDescription = a11yLabel + } + .clickable { + onSaveFolderChange(folder) + } + .padding(16.dp) + .padding(end = 8.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + RadioButton( + selected = saveFolder == folder, + onClick = { onSaveFolderChange(folder) }, + ) + Text(label) + } + Icon( + icon, + contentDescription = null, + modifier = Modifier + .size(ButtonDefaults.IconSize) + ) + } + } + } + if (isLowOnStorage) + MessageBox( + type = MessageType.ERROR, + message = stringResource(R.string.ui_welcome_saveFolder_externalRequired) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/WelcomeScreen/atoms/TimeSelector.kt b/app/src/main/java/app/myzel394/alibi/ui/components/WelcomeScreen/atoms/TimeSelector.kt index ca3f1fb..182115d 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/WelcomeScreen/atoms/TimeSelector.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/WelcomeScreen/atoms/TimeSelector.kt @@ -69,7 +69,7 @@ fun TimeSelector( ) { for ((duration, label) in OPTIONS) { val a11yLabel = stringResource( - R.string.ui_welcome_timeSettings_selectTime, + R.string.a11y_selectValue, label ) diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/WelcomeScreen/pages/SaveFolderPage.kt b/app/src/main/java/app/myzel394/alibi/ui/components/WelcomeScreen/pages/SaveFolderPage.kt index 73ebbf5..d45ea15 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/WelcomeScreen/pages/SaveFolderPage.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/WelcomeScreen/pages/SaveFolderPage.kt @@ -1,6 +1,7 @@ package app.myzel394.alibi.ui.components.WelcomeScreen.pages import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -10,6 +11,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.InsertDriveFile import androidx.compose.material.icons.filled.ChevronLeft @@ -21,11 +23,19 @@ import androidx.compose.material3.IconButton 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.saveable.rememberSaveable +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.unit.dp import app.myzel394.alibi.R +import app.myzel394.alibi.db.AppSettings +import app.myzel394.alibi.helpers.BatchesFolder +import app.myzel394.alibi.helpers.VideoBatchesFolder import app.myzel394.alibi.ui.BIG_PRIMARY_BUTTON_SIZE import app.myzel394.alibi.ui.components.WelcomeScreen.atoms.SaveFolderSelection @@ -33,7 +43,29 @@ import app.myzel394.alibi.ui.components.WelcomeScreen.atoms.SaveFolderSelection fun SaveFolderPage( onBack: () -> Unit, onContinue: () -> Unit, + appSettings: AppSettings, ) { + var saveFolder by rememberSaveable { mutableStateOf(null) } + + val context = LocalContext.current + + val isLowOnStorage = if (saveFolder != null) + false + else { + val availableBytes = VideoBatchesFolder.viaInternalFolder(context).getAvailableBytes() + + if (availableBytes == null) { + return + } + + val bytesPerMinute = BatchesFolder.requiredBytesForOneMinuteOfRecording(appSettings) + val requiredBytes = appSettings.maxDuration / 1000 / 60 * bytesPerMinute + + // Allow for a 10% margin of error + availableBytes < requiredBytes * 1.1 + } + + Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.SpaceBetween, @@ -63,7 +95,16 @@ fun SaveFolderPage( ) } Spacer(modifier = Modifier.weight(1f)) - SaveFolderSelection() + Box( + modifier = Modifier.widthIn(max = 400.dp) + ) { + SaveFolderSelection( + appSettings = appSettings, + saveFolder = saveFolder, + isLowOnStorage = isLowOnStorage, + onSaveFolderChange = { saveFolder = it }, + ) + } Spacer(modifier = Modifier.weight(1f)) Row( verticalAlignment = Alignment.CenterVertically, @@ -84,6 +125,7 @@ fun SaveFolderPage( } Button( onClick = onContinue, + enabled = !isLowOnStorage, modifier = Modifier .fillMaxWidth() .height(BIG_PRIMARY_BUTTON_SIZE), diff --git a/app/src/main/java/app/myzel394/alibi/ui/screens/WelcomeScreen.kt b/app/src/main/java/app/myzel394/alibi/ui/screens/WelcomeScreen.kt index eebd65c..3ab59bf 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/screens/WelcomeScreen.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/screens/WelcomeScreen.kt @@ -8,7 +8,6 @@ import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -18,6 +17,7 @@ import app.myzel394.alibi.ui.components.WelcomeScreen.pages.ExplanationPage import app.myzel394.alibi.ui.components.WelcomeScreen.pages.ResponsibilityPage import app.myzel394.alibi.ui.components.WelcomeScreen.pages.SaveFolderPage import app.myzel394.alibi.ui.components.WelcomeScreen.pages.TimeSettingsPage +import app.myzel394.alibi.ui.effects.rememberSettings import kotlinx.coroutines.launch @OptIn(ExperimentalFoundationApi::class) @@ -27,10 +27,7 @@ fun WelcomeScreen( ) { val context = LocalContext.current val dataStore = context.dataStore - val settings = dataStore - .data - .collectAsState(initial = null) - .value ?: return + val settings = rememberSettings() val scope = rememberCoroutineScope() val pagerState = rememberPagerState( initialPage = 0, @@ -83,8 +80,11 @@ fun WelcomeScreen( } }, onContinue = { - finishTutorial() - } + scope.launch { + pagerState.animateScrollToPage(3) + } + }, + appSettings = settings ) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 68add45..cc23b40 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,6 +11,8 @@ Please enter a number greater than %s Selected: %s + Select %s + Recorder Shows the current recording status @@ -201,8 +203,11 @@ 15 minutes 30 minutes 1 hour - Select %s You can change this anytime Where should Alibi store the batches? By default, Alibi stores the batches into its own private, encrypted storage. You can change this and specify an external, unencrypted folder. If you want to let Alibi remember more than 15 minutes, you should choose an external folder, as the internal folder is very small. + Internal Storage + Custom Folder + Media Folder + Please select either the Media Folder or a Custom Folder. Alibi has not enough space to store the batches in the internal storage. Alternatively, go back one step and select a shorter duration. \ No newline at end of file