diff --git a/android/core/src/main/java/com/stadiamaps/ferrostar/core/NavigationViewModel.kt b/android/core/src/main/java/com/stadiamaps/ferrostar/core/NavigationViewModel.kt index 0c2a3ed8..f381482a 100644 --- a/android/core/src/main/java/com/stadiamaps/ferrostar/core/NavigationViewModel.kt +++ b/android/core/src/main/java/com/stadiamaps/ferrostar/core/NavigationViewModel.kt @@ -8,8 +8,10 @@ import com.stadiamaps.ferrostar.core.extensions.deviation import com.stadiamaps.ferrostar.core.extensions.progress import com.stadiamaps.ferrostar.core.extensions.remainingSteps import com.stadiamaps.ferrostar.core.extensions.visualInstruction +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import uniffi.ferrostar.GeographicCoordinate @@ -99,10 +101,12 @@ class DefaultNavigationViewModel( ) : ViewModel(), NavigationViewModel { private var userLocation: UserLocation? = locationProvider.lastLocation + private val muteState: StateFlow = + spokenInstructionObserver?.muteState ?: MutableStateFlow(null) override val uiState = - ferrostarCore.state - .map { coreState -> + combine(ferrostarCore.state, muteState) { a, b -> a to b } + .map { (coreState, muteState) -> val location = locationProvider.lastLocation userLocation = when (coreState.tripState) { @@ -110,7 +114,7 @@ class DefaultNavigationViewModel( is TripState.Complete, TripState.Idle -> locationProvider.lastLocation } - uiState(coreState, spokenInstructionObserver?.isMuted, location, userLocation) + uiState(coreState, muteState, location, userLocation) // This awkward dance is required because Kotlin doesn't have a way to map over // StateFlows // without converting to a generic Flow in the process. @@ -134,7 +138,7 @@ class DefaultNavigationViewModel( Log.d("NavigationViewModel", "Spoken instruction observer is null, mute operation ignored.") return } - spokenInstructionObserver.isMuted = !spokenInstructionObserver.isMuted + spokenInstructionObserver.setMuted(!spokenInstructionObserver.isMuted) } // TODO: We can add a hook here to override the current road name. diff --git a/android/core/src/main/java/com/stadiamaps/ferrostar/core/Speech.kt b/android/core/src/main/java/com/stadiamaps/ferrostar/core/Speech.kt index 2a101247..d1d6713b 100644 --- a/android/core/src/main/java/com/stadiamaps/ferrostar/core/Speech.kt +++ b/android/core/src/main/java/com/stadiamaps/ferrostar/core/Speech.kt @@ -3,6 +3,10 @@ package com.stadiamaps.ferrostar.core import android.content.Context import android.speech.tts.TextToSpeech import android.speech.tts.TextToSpeech.OnInitListener +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update import uniffi.ferrostar.SpokenInstruction interface SpokenInstructionObserver { @@ -17,7 +21,12 @@ interface SpokenInstructionObserver { /** Stops speech and clears the queue of spoken utterances. */ fun stopAndClearQueue() - var isMuted: Boolean + fun setMuted(isMuted: Boolean) + + val muteState: StateFlow + + val isMuted: Boolean + get() = muteState.value } /** Observes the status of an [AndroidTtsObserver]. */ @@ -62,14 +71,18 @@ class AndroidTtsObserver( private const val TAG = "AndroidTtsObserver" } - override var isMuted: Boolean = false - set(value) { - field = value + private var _muteState: MutableStateFlow = MutableStateFlow(false) - if (value && tts?.isSpeaking == true) { + override fun setMuted(isMuted: Boolean) { + _muteState.update { _ -> + if (isMuted && tts?.isSpeaking == true) { tts?.stop() } + isMuted } + } + + override val muteState: StateFlow = _muteState.asStateFlow() var tts: TextToSpeech? private set