From bfa32318238615f0119b49797f35a548360bbf6e Mon Sep 17 00:00:00 2001 From: hammsterr Date: Sat, 24 Feb 2024 01:02:27 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=20=D0=B8=D1=81=D1=82?= =?UTF-8?q?=D0=BE=D1=87=D0=BD=D0=B8=D0=BA=D0=B0=20=D0=9E=D0=B1=D0=B7=D0=BE?= =?UTF-8?q?=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/kotlin/it/hamy/muza/Database.kt | 6 ++ .../main/kotlin/it/hamy/muza/Dependencies.kt | 14 +++ .../kotlin/it/hamy/muza/MainApplication.kt | 1 + .../it/hamy/muza/models/EventWithSong.kt | 16 +++ .../hamy/muza/preferences/DataPreferences.kt | 27 +++++ .../muza/preferences/PreferencesHolder.kt | 100 ++++++++++++++++++ .../hamy/muza/ui/screens/home/QuickPicks.kt | 27 +++++ .../muza/ui/screens/settings/OtherSettings.kt | 22 +++- 8 files changed, 208 insertions(+), 5 deletions(-) create mode 100644 app/src/main/kotlin/it/hamy/muza/Dependencies.kt create mode 100644 app/src/main/kotlin/it/hamy/muza/models/EventWithSong.kt create mode 100644 app/src/main/kotlin/it/hamy/muza/preferences/DataPreferences.kt create mode 100644 app/src/main/kotlin/it/hamy/muza/preferences/PreferencesHolder.kt diff --git a/app/src/main/kotlin/it/hamy/muza/Database.kt b/app/src/main/kotlin/it/hamy/muza/Database.kt index fa0b755..dc4e982 100644 --- a/app/src/main/kotlin/it/hamy/muza/Database.kt +++ b/app/src/main/kotlin/it/hamy/muza/Database.kt @@ -55,6 +55,7 @@ import it.hamy.muza.models.SongPlaylistMap import it.hamy.muza.models.SortedSongPlaylistMap import kotlin.jvm.Throws import kotlinx.coroutines.flow.Flow +import it.hamy.muza.models.EventWithSong @Dao interface Database { @@ -316,6 +317,11 @@ interface Database { @RewriteQueriesToDropUnusedColumns fun trending(now: Long = System.currentTimeMillis()): Flow + @Transaction + @Query("SELECT * FROM Event ORDER BY timestamp DESC") + fun events(): Flow> + + @Query("SELECT COUNT (*) FROM Event") fun eventsCount(): Flow diff --git a/app/src/main/kotlin/it/hamy/muza/Dependencies.kt b/app/src/main/kotlin/it/hamy/muza/Dependencies.kt new file mode 100644 index 0000000..71cc15d --- /dev/null +++ b/app/src/main/kotlin/it/hamy/muza/Dependencies.kt @@ -0,0 +1,14 @@ +package it.hamy.muza + +import it.hamy.muza.preferences.PreferencesHolder + +object Dependencies { + lateinit var application: MainApplication + private set + + internal fun init(application: MainApplication) { + this.application = application + } +} + +open class GlobalPreferencesHolder : PreferencesHolder(Dependencies.application, "preferences") \ No newline at end of file diff --git a/app/src/main/kotlin/it/hamy/muza/MainApplication.kt b/app/src/main/kotlin/it/hamy/muza/MainApplication.kt index 2e7182e..6125020 100644 --- a/app/src/main/kotlin/it/hamy/muza/MainApplication.kt +++ b/app/src/main/kotlin/it/hamy/muza/MainApplication.kt @@ -14,6 +14,7 @@ class MainApplication : Application(), ImageLoaderFactory { override fun onCreate() { super.onCreate() DatabaseInitializer() + Dependencies.init(this) MobileAds.initialize(this) { /** * Инициализация либы яндекса diff --git a/app/src/main/kotlin/it/hamy/muza/models/EventWithSong.kt b/app/src/main/kotlin/it/hamy/muza/models/EventWithSong.kt new file mode 100644 index 0000000..7dd2e8b --- /dev/null +++ b/app/src/main/kotlin/it/hamy/muza/models/EventWithSong.kt @@ -0,0 +1,16 @@ +package it.hamy.muza.models + +import androidx.compose.runtime.Immutable +import androidx.room.Embedded +import androidx.room.Relation + +@Immutable +data class EventWithSong( + @Embedded val event: Event, + @Relation( + entity = Song::class, + parentColumn = "songId", + entityColumn = "id" + ) + val song: Song +) \ No newline at end of file diff --git a/app/src/main/kotlin/it/hamy/muza/preferences/DataPreferences.kt b/app/src/main/kotlin/it/hamy/muza/preferences/DataPreferences.kt new file mode 100644 index 0000000..99497b5 --- /dev/null +++ b/app/src/main/kotlin/it/hamy/muza/preferences/DataPreferences.kt @@ -0,0 +1,27 @@ +package it.hamy.muza.preferences + +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import it.hamy.muza.GlobalPreferencesHolder +import it.hamy.muza.R +import kotlin.time.Duration +import kotlin.time.Duration.Companion.days + +object DataPreferences : GlobalPreferencesHolder() { + var topListLength by int(10) + var topListPeriod by enum(TopListPeriod.AllTime) + var quickPicksSource by enum(QuickPicksSource.Trending) + + enum class TopListPeriod(val displayName: @Composable () -> String, val duration: Duration? = null) { + PastDay(displayName = { "Day" }, duration = 1.days), + PastWeek(displayName = { "Week" }, duration = 7.days), + PastMonth(displayName = { "Month" }, duration = 30.days), + PastYear(displayName = { "Year" }, 365.days), + AllTime(displayName = { "AllTime" }) + } + + enum class QuickPicksSource(val displayName: @Composable () -> String) { + Trending(displayName = { "Trend" }), + LastInteraction(displayName = { "LastInteraction" }) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/it/hamy/muza/preferences/PreferencesHolder.kt b/app/src/main/kotlin/it/hamy/muza/preferences/PreferencesHolder.kt new file mode 100644 index 0000000..e938e21 --- /dev/null +++ b/app/src/main/kotlin/it/hamy/muza/preferences/PreferencesHolder.kt @@ -0,0 +1,100 @@ +package it.hamy.muza.preferences + +import android.app.Application +import android.content.Context +import android.content.SharedPreferences +import android.content.SharedPreferences.OnSharedPreferenceChangeListener +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.snapshots.Snapshot +import androidx.core.content.edit +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +private val coroutineScope = CoroutineScope(Dispatchers.IO) + +fun sharedPreferencesProperty( + getValue: SharedPreferences.(key: String) -> T, + setValue: SharedPreferences.Editor.(key: String, value: T) -> Unit, + defaultValue: T +) = object : ReadWriteProperty { + private var state = mutableStateOf(defaultValue) + private var listener: OnSharedPreferenceChangeListener? = null + + override fun getValue(thisRef: PreferencesHolder, property: KProperty<*>): T { + if (listener == null && !Snapshot.current.readOnly) { + state.value = thisRef.getValue(property.name) + listener = OnSharedPreferenceChangeListener { preferences, key -> + if (key == property.name) preferences.getValue(property.name) + .let { if (it != state && !Snapshot.current.readOnly) state.value = it } + } + thisRef.registerOnSharedPreferenceChangeListener(listener) + } + return state.value + } + + override fun setValue(thisRef: PreferencesHolder, property: KProperty<*>, value: T) = + coroutineScope.launch { + thisRef.edit(commit = true) { + setValue(property.name, value) + } + }.let { } +} + +/** + * A snapshottable, thread-safe, compose-first, extensible SharedPreferences wrapper that supports + * virtually all types, and if it doesn't, one could simply type `fun myNewType(...) = sharedPreferencesProperty(...)` + * and start implementing. Starts off as given defaultValue until we are allowed to subscribe to SharedPreferences + * @sample AppearancePreferences + */ +open class PreferencesHolder( + application: Application, + name: String, + mode: Int = Context.MODE_PRIVATE +) : SharedPreferences by application.getSharedPreferences(name, mode) { + fun boolean(defaultValue: Boolean) = sharedPreferencesProperty( + getValue = { getBoolean(it, defaultValue) }, + setValue = { k, v -> putBoolean(k, v) }, + defaultValue + ) + + fun string(defaultValue: String) = sharedPreferencesProperty( + getValue = { getString(it, null) ?: defaultValue }, + setValue = { k, v -> putString(k, v) }, + defaultValue + ) + + fun int(defaultValue: Int) = sharedPreferencesProperty( + getValue = { getInt(it, defaultValue) }, + setValue = { k, v -> putInt(k, v) }, + defaultValue + ) + + fun float(defaultValue: Float) = sharedPreferencesProperty( + getValue = { getFloat(it, defaultValue) }, + setValue = { k, v -> putFloat(k, v) }, + defaultValue + ) + + fun long(defaultValue: Long) = sharedPreferencesProperty( + getValue = { getLong(it, defaultValue) }, + setValue = { k, v -> putLong(k, v) }, + defaultValue + ) + + inline fun > enum(defaultValue: T) = sharedPreferencesProperty( + getValue = { + getString(it, null)?.let { runCatching { enumValueOf(it) }.getOrNull() } ?: defaultValue + }, + setValue = { k, v -> putString(k, v.name) }, + defaultValue + ) + + fun stringSet(defaultValue: Set) = sharedPreferencesProperty( + getValue = { getStringSet(it, null) ?: defaultValue }, + setValue = { k, v -> putStringSet(k, v) }, + defaultValue + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/it/hamy/muza/ui/screens/home/QuickPicks.kt b/app/src/main/kotlin/it/hamy/muza/ui/screens/home/QuickPicks.kt index 4d4a2f3..e11104e 100644 --- a/app/src/main/kotlin/it/hamy/muza/ui/screens/home/QuickPicks.kt +++ b/app/src/main/kotlin/it/hamy/muza/ui/screens/home/QuickPicks.kt @@ -76,6 +76,7 @@ import it.hamy.muza.utils.isLandscape import it.hamy.muza.utils.secondary import it.hamy.muza.utils.semiBold import kotlinx.coroutines.flow.distinctUntilChanged +import it.hamy.muza.preferences.DataPreferences @ExperimentalFoundationApi @ExperimentalAnimationApi @@ -105,6 +106,32 @@ fun QuickPicks( } } + + LaunchedEffect(DataPreferences.quickPicksSource) { + suspend fun handleSong(song: Song?) { + if (relatedPageResult == null || trending?.id != song?.id) relatedPageResult = + Innertube.relatedPage( + NextBody( + videoId = (song?.id ?: "J7p4bzqLvCw") + ) + ) + trending = song + } + when (DataPreferences.quickPicksSource) { + DataPreferences.QuickPicksSource.Trending -> + Database + .trending() + .distinctUntilChanged() + .collect { handleSong(it) } + + DataPreferences.QuickPicksSource.LastInteraction -> + Database + .events() + .distinctUntilChanged() + .collect { handleSong(it.firstOrNull()?.song) } + } + } + val songThumbnailSizeDp = Dimensions.thumbnails.song val songThumbnailSizePx = songThumbnailSizeDp.px val albumThumbnailSizeDp = 108.dp diff --git a/app/src/main/kotlin/it/hamy/muza/ui/screens/settings/OtherSettings.kt b/app/src/main/kotlin/it/hamy/muza/ui/screens/settings/OtherSettings.kt index 94c9605..9cc9fd6 100644 --- a/app/src/main/kotlin/it/hamy/muza/ui/screens/settings/OtherSettings.kt +++ b/app/src/main/kotlin/it/hamy/muza/ui/screens/settings/OtherSettings.kt @@ -31,6 +31,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import it.hamy.muza.Database import it.hamy.muza.LocalPlayerAwareWindowInsets +import it.hamy.muza.preferences.DataPreferences import it.hamy.muza.query import it.hamy.muza.service.PlayerMediaBrowserService import it.hamy.muza.ui.components.themed.Header @@ -47,6 +48,7 @@ import it.hamy.muza.utils.proxyPortKey import it.hamy.muza.utils.rememberPreference import it.hamy.muza.utils.toast import kotlinx.coroutines.flow.distinctUntilChanged +import okhttp3.internal.toImmutableList import java.net.Proxy @@ -116,6 +118,17 @@ fun OtherSettings() { ) { Header(title = "Другое") + SettingsEntryGroupText(title = "ОБЗОР") + + ValueSelectorSettingsEntry( + title = "Режим отображения", + selectedValue = DataPreferences.quickPicksSource, + values = enumValues().toList().toImmutableList(), + onValueSelected = { DataPreferences.quickPicksSource = it } + ) + + SettingsGroupSpacer() + SettingsEntryGroupText(title = "АНДРОИД АВТО") SettingsDescription(text = "Включите опцию \"неизвестные источники\" в настройках разработчика в Андроид Авто.") @@ -196,18 +209,18 @@ fun OtherSettings() { ) - SettingsEntryGroupText(title = "PROXY") + SettingsEntryGroupText(title = "ПРОКСИ") SwitchSettingEntry( - title = "Proxy", - text = "Включить proxy", + title = "Прокси", + text = "Включить прокси", isChecked = isProxyEnabled, onCheckedChange = { isProxyEnabled = it } ) AnimatedVisibility(visible = isProxyEnabled) { Column { - EnumValueSelectorSettingsEntry(title = "Proxy", + EnumValueSelectorSettingsEntry(title = "Прокси", selectedValue = proxyMode, onValueSelected = {proxyMode = it}) TextDialogSettingEntry( title = "Хост", @@ -221,6 +234,5 @@ fun OtherSettings() { onTextSave = { proxyPort = it.toIntOrNull() ?: 1080 }) } } - } }