diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/DefaultVoiceMessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/DefaultVoiceMessageComposerPresenter.kt index 56b0e402d2..9b5961c364 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/DefaultVoiceMessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/DefaultVoiceMessageComposerPresenter.kt @@ -17,6 +17,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.core.net.toUri import androidx.lifecycle.Lifecycle @@ -69,6 +70,7 @@ class DefaultVoiceMessageComposerPresenter( } private val permissionsPresenter = permissionsPresenterFactory.create(Manifest.permission.RECORD_AUDIO) + private var pendingEvent: VoiceMessageRecorderEvent.Start? = null private val mediaSender = mediaSenderFactory.create(timelineMode) @Composable @@ -77,8 +79,7 @@ class DefaultVoiceMessageComposerPresenter( val recorderState by voiceRecorder.state.collectAsState(initial = VoiceRecorderState.Idle) val playerState by player.state.collectAsState(initial = VoiceMessageComposerPlayer.State.Initial) val keepScreenOn by remember { derivedStateOf { recorderState is VoiceRecorderState.Recording } } - - val permissionState = permissionsPresenter.present() + val permissionState by rememberUpdatedState(permissionsPresenter.present()) var isSending by remember { mutableStateOf(false) } var showSendFailureDialog by remember { mutableStateOf(false) } @@ -88,6 +89,15 @@ class DefaultVoiceMessageComposerPresenter( player.setMedia(recording.file.path) } + LaunchedEffect(permissionState.permissionGranted) { + if (permissionState.permissionGranted) { + pendingEvent?.let { + localCoroutineScope.startRecording() + pendingEvent = null + } + } + } + fun handleLifecycleEvent(event: Lifecycle.Event) { when (event) { Lifecycle.Event.ON_PAUSE -> { @@ -102,6 +112,7 @@ class DefaultVoiceMessageComposerPresenter( } fun handleVoiceMessageRecorderEvent(event: VoiceMessageRecorderEvent) { + pendingEvent = null when (event) { VoiceMessageRecorderEvent.Start -> { Timber.v("Voice message record button pressed") @@ -111,6 +122,7 @@ class DefaultVoiceMessageComposerPresenter( } else -> { Timber.i("Voice message permission needed") + pendingEvent = VoiceMessageRecorderEvent.Start permissionState.eventSink(PermissionsEvent.RequestPermissions) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/DefaultVoiceMessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/DefaultVoiceMessageComposerPresenterTest.kt index 48348f5e9a..8a7324d9e5 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/DefaultVoiceMessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/DefaultVoiceMessageComposerPresenterTest.kt @@ -522,7 +522,9 @@ class DefaultVoiceMessageComposerPresenterTest { permissionsPresenter.setPermissionGranted() awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) - val finalState = awaitItem() + advanceUntilIdle() + + val finalState = expectMostRecentItem() assertThat(finalState.voiceMessageState).isEqualTo(RECORDING_STATE) voiceRecorder.assertCalls(stopped = 1, started = 1) @@ -547,14 +549,16 @@ class DefaultVoiceMessageComposerPresenterTest { assertThat(it.showPermissionRationaleDialog).isTrue() it.eventSink(VoiceMessageComposerEvent.AcceptPermissionRationale) } + skipItems(1) // Dialog is hidden, user accepts permissions assertThat(awaitItem().showPermissionRationaleDialog).isFalse() + // Permission is granted, recording starts automatically permissionsPresenter.setPermissionGranted() + advanceUntilIdle() - awaitItem().eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) - val finalState = awaitItem() + val finalState = expectMostRecentItem() assertThat(finalState.voiceMessageState).isEqualTo(RECORDING_STATE) voiceRecorder.assertCalls(started = 1) @@ -579,12 +583,14 @@ class DefaultVoiceMessageComposerPresenterTest { assertThat(it.showPermissionRationaleDialog).isTrue() it.eventSink(VoiceMessageComposerEvent.DismissPermissionsRationale) } + skipItems(1) // Dialog is hidden, user tries to record again awaitItem().also { assertThat(it.showPermissionRationaleDialog).isFalse() it.eventSink(VoiceMessageComposerEvent.RecorderEvent(VoiceMessageRecorderEvent.Start)) } + skipItems(1) // Dialog is shown once again val finalState = awaitItem().also { @@ -593,6 +599,7 @@ class DefaultVoiceMessageComposerPresenterTest { } voiceRecorder.assertCalls(started = 0) + cancelAndIgnoreRemainingEvents() testPauseAndDestroy(finalState) } }