Store voice player playback index in a datastore.
This commit is contained in:
@@ -26,10 +26,12 @@ dependencies {
|
||||
implementation(projects.libraries.di)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.mediaplayer.api)
|
||||
implementation(projects.libraries.preferences.api)
|
||||
implementation(projects.libraries.uiUtils)
|
||||
implementation(projects.services.analytics.api)
|
||||
|
||||
implementation(libs.androidx.annotationjvm)
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
implementation(libs.coroutines.core)
|
||||
|
||||
testCommonDependencies(libs)
|
||||
|
||||
@@ -26,6 +26,7 @@ class DefaultVoiceMessagePresenterFactory(
|
||||
@SessionCoroutineScope
|
||||
private val sessionCoroutineScope: CoroutineScope,
|
||||
private val voiceMessagePlayerFactory: VoiceMessagePlayer.Factory,
|
||||
private val voicePlayerStore: VoicePlayerStore,
|
||||
) : VoiceMessagePresenterFactory {
|
||||
override fun createVoiceMessagePresenter(
|
||||
eventId: EventId?,
|
||||
@@ -44,6 +45,7 @@ class DefaultVoiceMessagePresenterFactory(
|
||||
return VoiceMessagePresenter(
|
||||
analyticsService = analyticsService,
|
||||
sessionCoroutineScope = sessionCoroutineScope,
|
||||
voicePlayerStore = voicePlayerStore,
|
||||
player = player,
|
||||
eventId = eventId,
|
||||
duration = duration,
|
||||
|
||||
@@ -9,12 +9,13 @@
|
||||
package io.element.android.libraries.voiceplayer.impl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runUpdatingState
|
||||
@@ -34,15 +35,16 @@ import kotlin.time.Duration.Companion.milliseconds
|
||||
class VoiceMessagePresenter(
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val sessionCoroutineScope: CoroutineScope,
|
||||
private val voicePlayerStore: VoicePlayerStore,
|
||||
private val player: VoiceMessagePlayer,
|
||||
private val eventId: EventId?,
|
||||
private val duration: Duration,
|
||||
) : Presenter<VoiceMessageState> {
|
||||
private val play = mutableStateOf<AsyncData<Unit>>(AsyncData.Uninitialized)
|
||||
private val playbackSpeedIndex = mutableIntStateOf(0)
|
||||
|
||||
@Composable
|
||||
override fun present(): VoiceMessageState {
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
val playerState by player.state.collectAsState(
|
||||
VoiceMessagePlayer.State(
|
||||
isReady = false,
|
||||
@@ -53,6 +55,12 @@ class VoiceMessagePresenter(
|
||||
)
|
||||
)
|
||||
|
||||
val playbackSpeedIndex by voicePlayerStore.playBackSpeedIndex().collectAsState(0)
|
||||
|
||||
LaunchedEffect(playbackSpeedIndex) {
|
||||
player.setPlaybackSpeed(VoicePlayerConfig.availablePlaybackSpeeds[playbackSpeedIndex])
|
||||
}
|
||||
|
||||
val buttonType by remember {
|
||||
derivedStateOf {
|
||||
when {
|
||||
@@ -114,9 +122,10 @@ class VoiceMessagePresenter(
|
||||
is VoiceMessageEvent.Seek -> {
|
||||
player.seekTo((event.percentage * duration).toLong())
|
||||
}
|
||||
is VoiceMessageEvent.ChangePlaybackSpeed -> {
|
||||
playbackSpeedIndex.intValue = (playbackSpeedIndex.intValue + 1) % VoicePlayerConfig.availablePlaybackSpeeds.size
|
||||
player.setPlaybackSpeed(VoicePlayerConfig.availablePlaybackSpeeds[playbackSpeedIndex.intValue])
|
||||
is VoiceMessageEvent.ChangePlaybackSpeed -> localCoroutineScope.launch {
|
||||
voicePlayerStore.setPlayBackSpeedIndex(
|
||||
(playbackSpeedIndex + 1) % VoicePlayerConfig.availablePlaybackSpeeds.size
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,7 +135,7 @@ class VoiceMessagePresenter(
|
||||
progress = progress,
|
||||
time = time,
|
||||
showCursor = showCursor,
|
||||
playbackSpeed = VoicePlayerConfig.availablePlaybackSpeeds[playbackSpeedIndex.intValue],
|
||||
playbackSpeed = VoicePlayerConfig.availablePlaybackSpeeds[playbackSpeedIndex],
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.voiceplayer.impl
|
||||
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.intPreferencesKey
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
interface VoicePlayerStore {
|
||||
suspend fun setPlayBackSpeedIndex(index: Int)
|
||||
fun playBackSpeedIndex(): Flow<Int>
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class PreferencesVoicePlayerStore(
|
||||
preferenceDataStoreFactory: PreferenceDataStoreFactory,
|
||||
) : VoicePlayerStore {
|
||||
private val store = preferenceDataStoreFactory.create("elementx_voice_player")
|
||||
private val playbackSpeedIndex = intPreferencesKey("playback_speed_index")
|
||||
|
||||
override fun playBackSpeedIndex(): Flow<Int> {
|
||||
return store.data.map { prefs ->
|
||||
prefs[playbackSpeedIndex] ?: 0
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setPlayBackSpeedIndex(index: Int) {
|
||||
store.edit { prefs ->
|
||||
prefs[playbackSpeedIndex] = index
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.voiceplayer.impl
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
internal class InMemoryVoicePlayerStore(
|
||||
defaultPlaybackSpeedIndex: Int = 0,
|
||||
) : VoicePlayerStore {
|
||||
private val playBackSpeedIndex = MutableStateFlow(defaultPlaybackSpeedIndex)
|
||||
|
||||
override fun playBackSpeedIndex(): Flow<Int> {
|
||||
return playBackSpeedIndex.asStateFlow()
|
||||
}
|
||||
|
||||
override suspend fun setPlayBackSpeedIndex(index: Int) {
|
||||
playBackSpeedIndex.emit(index)
|
||||
}
|
||||
}
|
||||
@@ -240,6 +240,7 @@ fun TestScope.createVoiceMessagePresenter(
|
||||
mediaPlayer: FakeMediaPlayer = FakeMediaPlayer(),
|
||||
voiceMessageMediaRepo: VoiceMessageMediaRepo = FakeVoiceMessageMediaRepo(),
|
||||
analyticsService: AnalyticsService = FakeAnalyticsService(),
|
||||
voicePlayerStore: VoicePlayerStore = InMemoryVoicePlayerStore(),
|
||||
eventId: EventId? = EventId("\$anEventId"),
|
||||
filename: String = "filename doesn't really matter for a voice message",
|
||||
duration: Duration = 61_000.milliseconds,
|
||||
@@ -257,6 +258,7 @@ fun TestScope.createVoiceMessagePresenter(
|
||||
mimeType = mimeType,
|
||||
filename = filename
|
||||
),
|
||||
voicePlayerStore = voicePlayerStore,
|
||||
eventId = eventId,
|
||||
duration = duration,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user