Coverage Summary for Class: SoundLibraryViewModelImpl (com.vsevolodganin.clicktrack.soundlibrary)
Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
SoundLibraryViewModelImpl |
0%
(0/11)
|
0%
(0/30)
|
0%
(0/48)
|
0%
(0/313)
|
SoundLibraryViewModelImpl$onAddNewClick$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/9)
|
SoundLibraryViewModelImpl$onBackClick$$inlined$pop$default$1 |
0%
(0/1)
|
|
SoundLibraryViewModelImpl$onBackClick$$inlined$pop$default$2 |
0%
(0/1)
|
|
SoundLibraryViewModelImpl$onItemRemove$1 |
0%
(0/2)
|
0%
(0/2)
|
0%
(0/5)
|
0%
(0/25)
|
SoundLibraryViewModelImpl$onItemSoundSelect$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/16)
|
SoundLibraryViewModelImpl$special$$inlined$doOnPause$default$1 |
0%
(0/2)
|
|
SoundLibraryViewModelImpl$special$$inlined$map$1 |
0%
(0/2)
|
|
SoundLibraryViewModelImpl$special$$inlined$map$1$2 |
0%
(0/1)
|
|
SoundLibraryViewModelImpl$special$$inlined$map$1$2$1 |
|
SoundLibraryViewModelImpl$special$$inlined$registerSaveStateFor$1 |
0%
(0/1)
|
|
SoundLibraryViewModelImpl$state$2 |
0%
(0/1)
|
|
0%
(0/5)
|
0%
(0/54)
|
Total |
0%
(0/24)
|
0%
(0/32)
|
0%
(0/60)
|
0%
(0/417)
|
package com.vsevolodganin.clicktrack.soundlibrary
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.router.stack.pop
import com.arkivanov.essenty.lifecycle.doOnPause
import com.vsevolodganin.clicktrack.ScreenStackNavigation
import com.vsevolodganin.clicktrack.model.BuiltinClickSounds
import com.vsevolodganin.clicktrack.model.ClickSoundSource
import com.vsevolodganin.clicktrack.model.ClickSoundType
import com.vsevolodganin.clicktrack.model.ClickSoundsId
import com.vsevolodganin.clicktrack.model.ClickTrackId
import com.vsevolodganin.clicktrack.model.PlayableId
import com.vsevolodganin.clicktrack.model.UriClickSounds
import com.vsevolodganin.clicktrack.model.UserClickSounds
import com.vsevolodganin.clicktrack.player.PlayerServiceAccess
import com.vsevolodganin.clicktrack.storage.ClickSoundsRepository
import com.vsevolodganin.clicktrack.storage.UserPreferencesRepository
import com.vsevolodganin.clicktrack.utils.decompose.consumeSavedState
import com.vsevolodganin.clicktrack.utils.decompose.coroutineScope
import com.vsevolodganin.clicktrack.utils.decompose.registerSaveStateFor
import com.vsevolodganin.clicktrack.utils.optionalCast
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import me.tatarka.inject.annotations.Assisted
import me.tatarka.inject.annotations.Inject
@Inject
class SoundLibraryViewModelImpl(
@Assisted componentContext: ComponentContext,
private val navigation: ScreenStackNavigation,
private val userPreferences: UserPreferencesRepository,
private val clickSoundsRepository: ClickSoundsRepository,
private val documentMetadataHelper: DocumentMetadataHelper,
private val playerServiceAccess: PlayerServiceAccess,
private val soundChooser: SoundChooser,
) : SoundLibraryViewModel, ComponentContext by componentContext {
private val scope = coroutineScope()
override val state: StateFlow<SoundLibraryState?> = combine(
userPreferences.selectedSoundsId.flow,
clickSoundsRepository.getAll(),
playerServiceAccess.playbackState().map { it?.id }.distinctUntilChanged(),
) { selectedId, userItems, playingId ->
SoundLibraryState(
buildList {
this += BuiltinClickSounds.entries.map { it.toItem(selectedId) }
this += userItems.map { it.toItem(selectedId, playingId) }
},
)
}.stateIn(scope, SharingStarted.Eagerly, consumeSavedState())
init {
registerSaveStateFor(state)
}
private fun BuiltinClickSounds.toItem(selectedId: ClickSoundsId): SelectableClickSoundsItem.Builtin {
return SelectableClickSoundsItem.Builtin(
data = this,
selected = ClickSoundsId.Builtin(this) == selectedId,
)
}
private fun UserClickSounds.toItem(selectedId: ClickSoundsId, playingId: PlayableId?): SelectableClickSoundsItem.UserDefined {
return SelectableClickSoundsItem.UserDefined(
id = id,
strongBeatValue = value.strongBeat.toText(),
weakBeatValue = value.weakBeat.toText(),
hasError = value.strongBeat.hasError() || value.weakBeat.hasError(),
isPlaying = playingId?.optionalCast<ClickTrackId.Builtin.ClickSoundsTest>()?.soundsId == id,
selected = selectedId == id,
)
}
init {
lifecycle.doOnPause {
val state = state.value ?: return@doOnPause
if (state.items.any { it.optionalCast<SelectableClickSoundsItem.UserDefined>()?.isPlaying == true }) {
playerServiceAccess.stop()
}
}
}
override fun onBackClick() = navigation.pop()
override fun onAddNewClick() {
scope.launch {
clickSoundsRepository.insert(UriClickSounds(null, null))
}
}
override fun onItemClick(id: ClickSoundsId) {
userPreferences.selectedSoundsId.value = id
}
override fun onItemRemove(id: ClickSoundsId.Database) {
scope.launch {
userPreferences.selectedSoundsId.edit {
if (it == id) {
ClickSoundsId.Builtin(BuiltinClickSounds.BEEP)
} else {
it
}
}
clickSoundsRepository.remove(id)
}
}
override fun onItemSoundSelect(id: ClickSoundsId.Database, type: ClickSoundType) {
scope.launch {
soundChooser.launchFor(id, type)
}
}
override fun onItemSoundTestToggle(id: ClickSoundsId.Database) {
val testedItem = state.value?.items?.firstOrNull { it.id == id }
?.optionalCast<SelectableClickSoundsItem.UserDefined>()
?: return
if (testedItem.isPlaying) {
playerServiceAccess.stop()
} else {
playerServiceAccess.start(ClickTrackId.Builtin.ClickSoundsTest(id), soundsId = id)
}
}
private fun ClickSoundSource?.toText(): String {
return when (this) {
is ClickSoundSource.Bundled -> ""
is ClickSoundSource.Uri -> documentMetadataHelper.getDisplayName(value) ?: value
null -> ""
}
}
private fun ClickSoundSource?.hasError(): Boolean {
return when (this) {
is ClickSoundSource.Bundled -> false
is ClickSoundSource.Uri -> !documentMetadataHelper.hasReadPermission(value) || !documentMetadataHelper.isAccessible(value)
null -> false
}
}
}