diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/AudioRecorder/atoms/MicrophoneDisconnectedDialog.kt b/app/src/main/java/app/myzel394/alibi/ui/components/AudioRecorder/atoms/MicrophoneDisconnectedDialog.kt new file mode 100644 index 0000000..338293b --- /dev/null +++ b/app/src/main/java/app/myzel394/alibi/ui/components/AudioRecorder/atoms/MicrophoneDisconnectedDialog.kt @@ -0,0 +1,62 @@ +package app.myzel394.alibi.ui.components.AudioRecorder.atoms + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.MicOff +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.style.TextAlign +import app.myzel394.alibi.R + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MicrophoneDisconnectedDialog( + microphoneName: String, + onClose: () -> Unit, +) { + AlertDialog( + onDismissRequest = onClose, + title = { + Text( + stringResource( + R.string.ui_audioRecorder_error_microphoneDisconnected_title, + ), + textAlign = TextAlign.Center, + ) + }, + text = { + Text( + stringResource( + R.string.ui_audioRecorder_error_microphoneDisconnected_message, + microphoneName, + ) + ) + }, + icon = { + Icon( + Icons.Default.MicOff, + contentDescription = null, + ) + }, + confirmButton = { + val label = stringResource(R.string.dialog_close_neutral_label) + + Button( + modifier = Modifier + .semantics { + contentDescription = label + }, + onClick = onClose, + ) { + Text(label) + } + } + ) +} \ No newline at end of file diff --git a/app/src/main/java/app/myzel394/alibi/ui/components/AudioRecorder/organisms/RecordingStatus.kt b/app/src/main/java/app/myzel394/alibi/ui/components/AudioRecorder/organisms/RecordingStatus.kt index d928f09..c2c1cb2 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/components/AudioRecorder/organisms/RecordingStatus.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/components/AudioRecorder/organisms/RecordingStatus.kt @@ -24,11 +24,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import app.myzel394.alibi.ui.components.AudioRecorder.atoms.DeleteButton +import app.myzel394.alibi.ui.components.AudioRecorder.atoms.MicrophoneDisconnectedDialog import app.myzel394.alibi.ui.components.AudioRecorder.atoms.PauseResumeButton import app.myzel394.alibi.ui.components.AudioRecorder.atoms.RealtimeAudioVisualizer import app.myzel394.alibi.ui.components.AudioRecorder.atoms.RecordingTime import app.myzel394.alibi.ui.components.AudioRecorder.atoms.SaveButton import app.myzel394.alibi.ui.components.AudioRecorder.molecules.MicrophoneSelection +import app.myzel394.alibi.ui.effects.rememberPrevious import app.myzel394.alibi.ui.models.AudioRecorderModel import app.myzel394.alibi.ui.utils.KeepScreenOn import app.myzel394.alibi.ui.utils.MicrophoneInfo @@ -137,19 +139,41 @@ fun RecordingStatus( val microphones = MicrophoneInfo.fetchDeviceMicrophones(context) + val microphoneStatus = audioRecorder.microphoneStatus + val previousStatus = rememberPrevious(microphoneStatus) + + var showMicrophoneStatusDialog by remember { + // null = no dialog + // `MicrophoneConnectivityStatus.CONNECTED` = Reconnected dialog + // `MicrophoneConnectivityStatus.DISCONNECTED` = Disconnected dialog + mutableStateOf(null) + } + + LaunchedEffect(microphoneStatus) { + println(microphoneStatus) + println(previousStatus) + if (microphoneStatus != previousStatus && showMicrophoneStatusDialog == null && previousStatus != null) { + showMicrophoneStatusDialog = microphoneStatus + } + } + + if (showMicrophoneStatusDialog == AudioRecorderModel.MicrophoneConnectivityStatus.DISCONNECTED) { + MicrophoneDisconnectedDialog( + onClose = { + showMicrophoneStatusDialog = null + }, + microphoneName = audioRecorder.selectedMicrophone?.name ?: "", + ) + } if (microphones.isNotEmpty()) { MicrophoneSelection( microphones = microphones, selectedMicrophone = audioRecorder.selectedMicrophone, - onSelect = { - audioRecorder.changeMicrophone(it) - - if (!audioRecorder.isPaused) { - audioRecorder.recorderService!!.startNewCycle() - } - } + onSelect = audioRecorder::changeMicrophone, ) + } else { + Box {} } } } \ No newline at end of file diff --git a/app/src/main/java/app/myzel394/alibi/ui/effects/remember-previous.kt b/app/src/main/java/app/myzel394/alibi/ui/effects/remember-previous.kt new file mode 100644 index 0000000..7dc2adc --- /dev/null +++ b/app/src/main/java/app/myzel394/alibi/ui/effects/remember-previous.kt @@ -0,0 +1,41 @@ +package app.myzel394.alibi.ui.effects + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.remember + +/** + * Returns a dummy MutableState that does not cause render when setting it + */ +@Composable +private fun rememberRef(): MutableState { + // for some reason it always recreated the value with vararg keys, + // leaving out the keys as a parameter for remember for now + return remember() { + object : MutableState { + override var value: T? = null + + override fun component1(): T? = value + + override fun component2(): (T?) -> Unit = { value = it } + } + } +} + +@Composable +fun rememberPrevious( + current: T, + shouldUpdate: (prev: T?, curr: T) -> Boolean = { a: T?, b: T -> a != b }, +): T? { + val ref = rememberRef() + + // launched after render, so the current render will have the old value anyway + SideEffect { + if (shouldUpdate(ref.value, current)) { + ref.value = current + } + } + + return ref.value +} \ No newline at end of file