Coverage Summary for Class: UserPreferencesRepository (com.vsevolodganin.clicktrack.storage)

Class Method, % Branch, % Line, % Instruction, %
UserPreferencesRepository 0% (0/9) 0% (0/6) 0% (0/57) 0% (0/292)
UserPreferencesRepository$Const
UserPreferencesRepository$UserPropertyAccess
UserPreferencesRepository$UserPropertyAccessWithMapping 0% (0/4) 0% (0/14) 0% (0/68)
UserPreferencesRepository$UserPropertyAccessWithMapping$1 0% (0/1) 0% (0/1) 0% (0/23)
UserPreferencesRepository$UserPropertyAccessWithMapping$1$1 0% (0/1) 0% (0/3) 0% (0/30)
UserPreferencesRepository$UserPropertyAccessWithMapping$stateFlow$1 0% (0/1) 0% (0/4) 0% (0/1) 0% (0/44)
UserPreferencesRepository$UserPropertyAccessWithNoMapping 0% (0/3) 0% (0/6) 0% (0/12)
Total 0% (0/19) 0% (0/10) 0% (0/82) 0% (0/469)


 package com.vsevolodganin.clicktrack.storage
 
 import com.russhwolf.settings.ExperimentalSettingsApi
 import com.russhwolf.settings.coroutines.FlowSettings
 import com.vsevolodganin.clicktrack.di.component.ApplicationScope
 import com.vsevolodganin.clicktrack.model.BeatsPerMinute
 import com.vsevolodganin.clicktrack.model.BuiltinClickSounds
 import com.vsevolodganin.clicktrack.model.ClickSoundsId
 import com.vsevolodganin.clicktrack.model.CueDuration
 import com.vsevolodganin.clicktrack.model.NotePattern
 import com.vsevolodganin.clicktrack.model.TwoLayerPolyrhythm
 import com.vsevolodganin.clicktrack.model.bpm
 import com.vsevolodganin.clicktrack.storage.UserPreferencesRepository.Const.NO_APP_VERSION_CODE
 import com.vsevolodganin.clicktrack.storage.UserPreferencesRepository.Const.REVIEW_NOT_REQUESTED
 import com.vsevolodganin.clicktrack.theme.Theme
 import com.vsevolodganin.clicktrack.training.TrainingEditState.TrainingMode
 import com.vsevolodganin.clicktrack.training.TrainingValidState
 import com.vsevolodganin.clicktrack.utils.settings.PreferenceKey
 import kotlinx.coroutines.CoroutineStart
 import kotlinx.coroutines.DelicateCoroutinesApi
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.drop
 import kotlinx.coroutines.flow.update
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
 import kotlinx.serialization.json.Json
 import me.tatarka.inject.annotations.Inject
 
 @OptIn(ExperimentalSettingsApi::class)
 @ApplicationScope
 @Inject
 class UserPreferencesRepository(
     private val settings: FlowSettings,
     private val json: Json,
 ) {
     object Const {
         const val NO_APP_VERSION_CODE = -1
         const val REVIEW_NOT_REQUESTED = -1L
     }
 
     interface UserPropertyAccess<T> {
         val flow: Flow<T>
         var value: T
 
         fun edit(transform: (T) -> T)
     }
 
     val appVersionCode: UserPropertyAccess<Int> = UserPropertyAccessWithNoMapping(
         key = PreferenceKey.Int("app_version_code"),
         defaultValue = NO_APP_VERSION_CODE,
     )
 
     val reviewRequestTimestamp: UserPropertyAccess<Long> = UserPropertyAccessWithNoMapping(
         key = PreferenceKey.Long("review_request_timestamp"),
         defaultValue = REVIEW_NOT_REQUESTED,
     )
 
     val metronomeBpm: UserPropertyAccess<BeatsPerMinute> = UserPropertyAccessWithMapping(
         key = PreferenceKey.Int("metronome_bpm"),
         defaultValue = 120.bpm,
         toExternal = { it.bpm },
         toInternal = { it.value },
     )
 
     val metronomePattern: UserPropertyAccess<NotePattern> = UserPropertyAccessWithMapping(
         key = PreferenceKey.String("metronome_pattern"),
         defaultValue = NotePattern.STRAIGHT_X1,
         toExternal = { NotePattern.valueOf(it) },
         toInternal = { it.toString() },
     )
 
     val theme: UserPropertyAccess<Theme> = UserPropertyAccessWithMapping(
         key = PreferenceKey.String("theme"),
         defaultValue = Theme.SYSTEM,
         toExternal = { Theme.valueOf(it) },
         toInternal = { it.toString() },
     )
 
     val selectedSoundsId: UserPropertyAccess<ClickSoundsId> = UserPropertyAccessWithMapping(
         key = PreferenceKey.String("selected_sounds_id"),
         defaultValue = ClickSoundsId.Builtin(BuiltinClickSounds.BEEP),
         toExternal = { stringValue ->
             stringValue.toLongOrNull()?.let(ClickSoundsId::Database)
                 ?: BuiltinClickSounds.entries.firstOrNull { it.storageKey == stringValue }?.let(ClickSoundsId::Builtin)
                 ?: ClickSoundsId.Builtin(BuiltinClickSounds.BEEP)
         },
         toInternal = {
             when (it) {
                 is ClickSoundsId.Database -> it.value.toString()
                 is ClickSoundsId.Builtin -> it.value.storageKey
             }
         },
     )
 
     val trainingState: UserPropertyAccess<TrainingValidState> = UserPropertyAccessWithMapping(
         key = PreferenceKey.String("training_state"),
         defaultValue = TrainingValidState(
             startingTempo = 120.bpm,
             mode = TrainingMode.INCREASE_TEMPO,
             segmentLength = CueDuration.Measures(4),
             tempoChange = 5.bpm,
             ending = TrainingValidState.Ending.ByTempo(160.bpm),
         ),
         toExternal = json::decodeFromString,
         toInternal = json::encodeToString,
     )
 
     val polyrhythm: UserPropertyAccess<TwoLayerPolyrhythm> = UserPropertyAccessWithMapping(
         key = PreferenceKey.String("polyrhythm"),
         defaultValue = TwoLayerPolyrhythm(
             bpm = 120.bpm,
             layer1 = 3,
             layer2 = 2,
         ),
         toExternal = json::decodeFromString,
         toInternal = json::encodeToString,
     )
 
     val ignoreAudioFocus: UserPropertyAccess<Boolean> = UserPropertyAccessWithNoMapping(
         key = PreferenceKey.Boolean("ignore_audio_focus"),
         defaultValue = false,
     )
 
     val playTrackingMode: UserPropertyAccess<Boolean> = UserPropertyAccessWithNoMapping(
         key = PreferenceKey.Boolean("play_tracking_mode"),
         defaultValue = true,
     )
 
     @OptIn(ExperimentalSettingsApi::class, DelicateCoroutinesApi::class)
     private open inner class UserPropertyAccessWithMapping<TInternal, TExternal>(
         private val key: PreferenceKey<TInternal>,
         private val defaultValue: TExternal,
         private val toExternal: (TInternal) -> TExternal,
         private val toInternal: (TExternal) -> TInternal,
     ) : UserPropertyAccess<TExternal> {
         private val stateFlow = MutableStateFlow(
             runBlocking {
                 with(key) { settings.get() }?.let(toExternal) ?: defaultValue
             },
         )
 
         init {
             GlobalScope.launch(Dispatchers.Default, CoroutineStart.UNDISPATCHED) {
                 stateFlow.drop(1).collect { value ->
                     with(key) {
                         settings.put(toInternal(value))
                     }
                 }
             }
         }
 
         override val flow: Flow<TExternal> = stateFlow
 
         override var value: TExternal
             get() = stateFlow.value
             set(value) {
                 stateFlow.value = value
             }
 
         override fun edit(transform: (TExternal) -> TExternal) = stateFlow.update(transform)
     }
 
     private inner class UserPropertyAccessWithNoMapping<T>(
         key: PreferenceKey<T>,
         defaultValue: T,
     ) : UserPropertyAccessWithMapping<T, T>(
         key = key,
         defaultValue = defaultValue,
         toExternal = { it },
         toInternal = { it },
     )
 }