feat: Add AudioRecordingStart and VideoRecordingStart

This commit is contained in:
Myzel394 2023-12-02 18:45:25 +01:00
parent e7989e2eba
commit 261753ad75
No known key found for this signature in database
GPG Key ID: 79CC92F37B3E1A2B
6 changed files with 207 additions and 72 deletions

View File

@ -23,9 +23,8 @@ import app.myzel394.alibi.ui.enums.Screen
import app.myzel394.alibi.ui.models.AudioRecorderModel import app.myzel394.alibi.ui.models.AudioRecorderModel
import app.myzel394.alibi.ui.models.VideoRecorderModel import app.myzel394.alibi.ui.models.VideoRecorderModel
import app.myzel394.alibi.ui.screens.AboutScreen import app.myzel394.alibi.ui.screens.AboutScreen
import app.myzel394.alibi.ui.screens.AudioRecorderScreen import app.myzel394.alibi.ui.screens.RecorderScreen
import app.myzel394.alibi.ui.screens.CustomRecordingNotificationsScreen import app.myzel394.alibi.ui.screens.CustomRecordingNotificationsScreen
import app.myzel394.alibi.ui.screens.POCVideo
import app.myzel394.alibi.ui.screens.SettingsScreen import app.myzel394.alibi.ui.screens.SettingsScreen
import app.myzel394.alibi.ui.screens.WelcomeScreen import app.myzel394.alibi.ui.screens.WelcomeScreen
@ -73,9 +72,10 @@ fun Navigation(
scaleOut(targetScale = SCALE_IN) + fadeOut(tween(durationMillis = 150)) scaleOut(targetScale = SCALE_IN) + fadeOut(tween(durationMillis = 150))
} }
) { ) {
AudioRecorderScreen( RecorderScreen(
navController = navController, navController = navController,
audioRecorder = audioRecorder, audioRecorder = audioRecorder,
videoRecorder = videoRecorder,
) )
} }
composable( composable(

View File

@ -0,0 +1,90 @@
package app.myzel394.alibi.ui.components.AudioRecorder.atoms
import android.Manifest
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Mic
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.LaunchedEffect
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.draw.clip
import androidx.compose.ui.platform.LocalContext
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.components.atoms.PermissionRequester
import app.myzel394.alibi.ui.models.AudioRecorderModel
@Composable
fun AudioRecordingStart(
audioRecorder: AudioRecorderModel,
appSettings: AppSettings,
) {
val context = LocalContext.current
// We can't get the current `notificationDetails` inside the
// `onPermissionAvailable` function. We'll instead use this hack
// with `LaunchedEffect` to get the current value.
var startRecording by rememberSaveable { mutableStateOf(false) }
LaunchedEffect(startRecording) {
if (startRecording) {
startRecording = false
audioRecorder.startRecording(context, appSettings)
}
}
PermissionRequester(
permission = Manifest.permission.RECORD_AUDIO,
icon = Icons.Default.Mic,
onPermissionAvailable = {
startRecording = true
}
) { trigger ->
val label = stringResource(R.string.ui_audioRecorder_action_start_label)
Button(
onClick = trigger,
modifier = Modifier
.semantics {
contentDescription = label
}
.size(200.dp)
.clip(shape = CircleShape),
colors = ButtonDefaults.outlinedButtonColors(),
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(
Icons.Default.Mic,
contentDescription = null,
modifier = Modifier
.size(80.dp),
)
Spacer(modifier = Modifier.height(ButtonDefaults.IconSpacing))
Text(
label,
style = MaterialTheme.typography.titleSmall,
)
}
}
}
}

View File

@ -0,0 +1,92 @@
package app.myzel394.alibi.ui.components.AudioRecorder.atoms
import android.Manifest
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CameraAlt
import androidx.compose.material.icons.filled.Mic
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.LaunchedEffect
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.draw.clip
import androidx.compose.ui.platform.LocalContext
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.components.atoms.PermissionRequester
import app.myzel394.alibi.ui.models.AudioRecorderModel
import app.myzel394.alibi.ui.models.VideoRecorderModel
@Composable
fun VideoRecordingStart(
videoRecorder: VideoRecorderModel,
appSettings: AppSettings,
) {
val context = LocalContext.current
// We can't get the current `notificationDetails` inside the
// `onPermissionAvailable` function. We'll instead use this hack
// with `LaunchedEffect` to get the current value.
var startRecording by rememberSaveable { mutableStateOf(false) }
LaunchedEffect(startRecording) {
if (startRecording) {
startRecording = false
videoRecorder.startRecording(context, appSettings)
}
}
PermissionRequester(
permission = Manifest.permission.RECORD_AUDIO,
icon = Icons.Default.Mic,
onPermissionAvailable = {
startRecording = true
}
) { trigger ->
val label = stringResource(R.string.ui_videoRecorder_action_start_label)
Button(
onClick = trigger,
modifier = Modifier
.semantics {
contentDescription = label
}
.size(200.dp)
.clip(shape = CircleShape),
colors = ButtonDefaults.outlinedButtonColors(),
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(
Icons.Default.CameraAlt,
contentDescription = null,
modifier = Modifier
.size(80.dp),
)
Spacer(modifier = Modifier.height(ButtonDefaults.IconSpacing))
Text(
label,
style = MaterialTheme.typography.titleSmall,
)
}
}
}
}

View File

@ -1,6 +1,5 @@
package app.myzel394.alibi.ui.components.AudioRecorder.molecules package app.myzel394.alibi.ui.components.AudioRecorder.molecules
import android.Manifest
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@ -11,9 +10,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Mic
import androidx.compose.material.icons.filled.Save import androidx.compose.material.icons.filled.Save
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
@ -21,15 +18,8 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
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.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.contentDescription
@ -37,18 +27,20 @@ import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
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.db.AppSettings
import app.myzel394.alibi.ui.BIG_PRIMARY_BUTTON_SIZE import app.myzel394.alibi.ui.BIG_PRIMARY_BUTTON_SIZE
import app.myzel394.alibi.ui.components.atoms.PermissionRequester import app.myzel394.alibi.ui.components.AudioRecorder.atoms.AudioRecordingStart
import app.myzel394.alibi.ui.components.AudioRecorder.atoms.VideoRecordingStart
import app.myzel394.alibi.ui.effects.rememberForceUpdateOnLifeCycleChange import app.myzel394.alibi.ui.effects.rememberForceUpdateOnLifeCycleChange
import app.myzel394.alibi.ui.models.AudioRecorderModel import app.myzel394.alibi.ui.models.AudioRecorderModel
import app.myzel394.alibi.ui.models.VideoRecorderModel
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle import java.time.format.FormatStyle
@Composable @Composable
fun StartRecording( fun StartRecording(
audioRecorder: AudioRecorderModel, audioRecorder: AudioRecorderModel,
videoRecorder: VideoRecorderModel,
// Loading this from parent, because if we load it ourselves // Loading this from parent, because if we load it ourselves
// and permissions have already been granted, initial // and permissions have already been granted, initial
// settings will be used, instead of the actual settings. // settings will be used, instead of the actual settings.
@ -57,70 +49,26 @@ fun StartRecording(
) { ) {
val context = LocalContext.current val context = LocalContext.current
// We can't get the current `notificationDetails` inside the
// `onPermissionAvailable` function. We'll instead use this hack
// with `LaunchedEffect` to get the current value.
var startRecording by rememberSaveable { mutableStateOf(false) }
LaunchedEffect(startRecording) {
if (startRecording) {
startRecording = false
audioRecorder.startRecording(context, appSettings)
}
}
Column( Column(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.SpaceBetween, verticalArrangement = Arrangement.SpaceBetween,
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
PermissionRequester(
permission = Manifest.permission.RECORD_AUDIO, AudioRecordingStart(
icon = Icons.Default.Mic, audioRecorder = audioRecorder,
onPermissionAvailable = { appSettings = appSettings,
startRecording = true
},
) { trigger ->
val label = stringResource(R.string.ui_audioRecorder_action_start_label)
Button(
onClick = trigger,
modifier = Modifier
.semantics {
contentDescription = label
}
.size(200.dp)
.clip(shape = CircleShape),
colors = ButtonDefaults.outlinedButtonColors(),
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(
Icons.Default.Mic,
contentDescription = null,
modifier = Modifier
.size(80.dp),
) )
Spacer(modifier = Modifier.height(ButtonDefaults.IconSpacing)) VideoRecordingStart(
Text( videoRecorder = videoRecorder,
label, appSettings = appSettings,
style = MaterialTheme.typography.titleSmall,
) )
}
}
}
val settings = LocalContext
.current
.dataStore
.data
.collectAsState(initial = AppSettings.getDefaultInstance())
.value
Text( Text(
stringResource( stringResource(
R.string.ui_audioRecorder_action_start_description, R.string.ui_audioRecorder_action_start_description,
settings.maxDuration / 1000 / 60 appSettings.maxDuration / 1000 / 60
), ),
style = MaterialTheme.typography.bodySmall.copy( style = MaterialTheme.typography.bodySmall.copy(
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,

View File

@ -48,14 +48,16 @@ import app.myzel394.alibi.helpers.AudioBatchesFolder
import app.myzel394.alibi.helpers.BatchesFolder import app.myzel394.alibi.helpers.BatchesFolder
import app.myzel394.alibi.ui.effects.rememberSettings import app.myzel394.alibi.ui.effects.rememberSettings
import app.myzel394.alibi.ui.models.AudioRecorderModel import app.myzel394.alibi.ui.models.AudioRecorderModel
import app.myzel394.alibi.ui.models.VideoRecorderModel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun AudioRecorderScreen( fun RecorderScreen(
navController: NavController, navController: NavController,
audioRecorder: AudioRecorderModel, audioRecorder: AudioRecorderModel,
videoRecorder: VideoRecorderModel,
) { ) {
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = remember { SnackbarHostState() }
val context = LocalContext.current val context = LocalContext.current
@ -299,7 +301,9 @@ fun AudioRecorderScreen(
RecordingStatus(audioRecorder = audioRecorder) RecordingStatus(audioRecorder = audioRecorder)
else else
StartRecording( StartRecording(
audioRecorder = audioRecorder, appSettings = appSettings, audioRecorder = audioRecorder,
videoRecorder = videoRecorder,
appSettings = appSettings,
onSaveLastRecording = ::saveRecording, onSaveLastRecording = ::saveRecording,
) )
} }

View File

@ -18,7 +18,7 @@
<string name="ui_permissions_request">Please grant the permission to continue</string> <string name="ui_permissions_request">Please grant the permission to continue</string>
<string name="ui_permissions_permanentlyDenied_message">You will be redirected to the app settings to grant the permission there.</string> <string name="ui_permissions_permanentlyDenied_message">You will be redirected to the app settings to grant the permission there.</string>
<string name="ui_audioRecorder_action_start_label">Start Recording</string> <string name="ui_audioRecorder_action_start_label">Start Audio Recording</string>
<string name="ui_audioRecorder_action_saveOldRecording_label">Save Recording from <xliff:g name="date">%s</xliff:g></string> <string name="ui_audioRecorder_action_saveOldRecording_label">Save Recording from <xliff:g name="date">%s</xliff:g></string>
<string name="ui_audioRecorder_action_delete_label">Delete</string> <string name="ui_audioRecorder_action_delete_label">Delete</string>
<string name="ui_audioRecorder_action_delete_confirm_title">Delete Recording?</string> <string name="ui_audioRecorder_action_delete_confirm_title">Delete Recording?</string>
@ -138,4 +138,5 @@
<string name="ui_settings_value_videoQuality_values_hd">HD</string> <string name="ui_settings_value_videoQuality_values_hd">HD</string>
<string name="ui_settings_value_videoQuality_values_sd">Standard</string> <string name="ui_settings_value_videoQuality_values_sd">Standard</string>
<string name="ui_settings_value_videoQuality_values_lowest">Lowest</string> <string name="ui_settings_value_videoQuality_values_lowest">Lowest</string>
<string name="ui_videoRecorder_action_start_label">Start Video Recording</string>
</resources> </resources>