This commit is contained in:
@@ -55,6 +55,7 @@ import it.hamy.muza.models.SongPlaylistMap
|
|||||||
import it.hamy.muza.models.SortedSongPlaylistMap
|
import it.hamy.muza.models.SortedSongPlaylistMap
|
||||||
import kotlin.jvm.Throws
|
import kotlin.jvm.Throws
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import it.hamy.muza.models.EventWithSong
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
interface Database {
|
interface Database {
|
||||||
@@ -316,6 +317,11 @@ interface Database {
|
|||||||
@RewriteQueriesToDropUnusedColumns
|
@RewriteQueriesToDropUnusedColumns
|
||||||
fun trending(now: Long = System.currentTimeMillis()): Flow<Song?>
|
fun trending(now: Long = System.currentTimeMillis()): Flow<Song?>
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
@Query("SELECT * FROM Event ORDER BY timestamp DESC")
|
||||||
|
fun events(): Flow<List<EventWithSong>>
|
||||||
|
|
||||||
|
|
||||||
@Query("SELECT COUNT (*) FROM Event")
|
@Query("SELECT COUNT (*) FROM Event")
|
||||||
fun eventsCount(): Flow<Int>
|
fun eventsCount(): Flow<Int>
|
||||||
|
|
||||||
|
|||||||
14
app/src/main/kotlin/it/hamy/muza/Dependencies.kt
Normal file
14
app/src/main/kotlin/it/hamy/muza/Dependencies.kt
Normal file
@@ -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")
|
||||||
@@ -14,6 +14,7 @@ class MainApplication : Application(), ImageLoaderFactory {
|
|||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
DatabaseInitializer()
|
DatabaseInitializer()
|
||||||
|
Dependencies.init(this)
|
||||||
MobileAds.initialize(this) {
|
MobileAds.initialize(this) {
|
||||||
/**
|
/**
|
||||||
* Инициализация либы яндекса
|
* Инициализация либы яндекса
|
||||||
|
|||||||
16
app/src/main/kotlin/it/hamy/muza/models/EventWithSong.kt
Normal file
16
app/src/main/kotlin/it/hamy/muza/models/EventWithSong.kt
Normal file
@@ -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
|
||||||
|
)
|
||||||
@@ -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" })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 <T : Any> sharedPreferencesProperty(
|
||||||
|
getValue: SharedPreferences.(key: String) -> T,
|
||||||
|
setValue: SharedPreferences.Editor.(key: String, value: T) -> Unit,
|
||||||
|
defaultValue: T
|
||||||
|
) = object : ReadWriteProperty<PreferencesHolder, T> {
|
||||||
|
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 <reified T : Enum<T>> enum(defaultValue: T) = sharedPreferencesProperty(
|
||||||
|
getValue = {
|
||||||
|
getString(it, null)?.let { runCatching { enumValueOf<T>(it) }.getOrNull() } ?: defaultValue
|
||||||
|
},
|
||||||
|
setValue = { k, v -> putString(k, v.name) },
|
||||||
|
defaultValue
|
||||||
|
)
|
||||||
|
|
||||||
|
fun stringSet(defaultValue: Set<String>) = sharedPreferencesProperty(
|
||||||
|
getValue = { getStringSet(it, null) ?: defaultValue },
|
||||||
|
setValue = { k, v -> putStringSet(k, v) },
|
||||||
|
defaultValue
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -76,6 +76,7 @@ import it.hamy.muza.utils.isLandscape
|
|||||||
import it.hamy.muza.utils.secondary
|
import it.hamy.muza.utils.secondary
|
||||||
import it.hamy.muza.utils.semiBold
|
import it.hamy.muza.utils.semiBold
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import it.hamy.muza.preferences.DataPreferences
|
||||||
|
|
||||||
@ExperimentalFoundationApi
|
@ExperimentalFoundationApi
|
||||||
@ExperimentalAnimationApi
|
@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 songThumbnailSizeDp = Dimensions.thumbnails.song
|
||||||
val songThumbnailSizePx = songThumbnailSizeDp.px
|
val songThumbnailSizePx = songThumbnailSizeDp.px
|
||||||
val albumThumbnailSizeDp = 108.dp
|
val albumThumbnailSizeDp = 108.dp
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import it.hamy.muza.Database
|
import it.hamy.muza.Database
|
||||||
import it.hamy.muza.LocalPlayerAwareWindowInsets
|
import it.hamy.muza.LocalPlayerAwareWindowInsets
|
||||||
|
import it.hamy.muza.preferences.DataPreferences
|
||||||
import it.hamy.muza.query
|
import it.hamy.muza.query
|
||||||
import it.hamy.muza.service.PlayerMediaBrowserService
|
import it.hamy.muza.service.PlayerMediaBrowserService
|
||||||
import it.hamy.muza.ui.components.themed.Header
|
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.rememberPreference
|
||||||
import it.hamy.muza.utils.toast
|
import it.hamy.muza.utils.toast
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import okhttp3.internal.toImmutableList
|
||||||
import java.net.Proxy
|
import java.net.Proxy
|
||||||
|
|
||||||
|
|
||||||
@@ -116,6 +118,17 @@ fun OtherSettings() {
|
|||||||
) {
|
) {
|
||||||
Header(title = "Другое")
|
Header(title = "Другое")
|
||||||
|
|
||||||
|
SettingsEntryGroupText(title = "ОБЗОР")
|
||||||
|
|
||||||
|
ValueSelectorSettingsEntry(
|
||||||
|
title = "Режим отображения",
|
||||||
|
selectedValue = DataPreferences.quickPicksSource,
|
||||||
|
values = enumValues<DataPreferences.QuickPicksSource>().toList().toImmutableList(),
|
||||||
|
onValueSelected = { DataPreferences.quickPicksSource = it }
|
||||||
|
)
|
||||||
|
|
||||||
|
SettingsGroupSpacer()
|
||||||
|
|
||||||
SettingsEntryGroupText(title = "АНДРОИД АВТО")
|
SettingsEntryGroupText(title = "АНДРОИД АВТО")
|
||||||
|
|
||||||
SettingsDescription(text = "Включите опцию \"неизвестные источники\" в настройках разработчика в Андроид Авто.")
|
SettingsDescription(text = "Включите опцию \"неизвестные источники\" в настройках разработчика в Андроид Авто.")
|
||||||
@@ -196,18 +209,18 @@ fun OtherSettings() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
SettingsEntryGroupText(title = "PROXY")
|
SettingsEntryGroupText(title = "ПРОКСИ")
|
||||||
|
|
||||||
SwitchSettingEntry(
|
SwitchSettingEntry(
|
||||||
title = "Proxy",
|
title = "Прокси",
|
||||||
text = "Включить proxy",
|
text = "Включить прокси",
|
||||||
isChecked = isProxyEnabled,
|
isChecked = isProxyEnabled,
|
||||||
onCheckedChange = { isProxyEnabled = it }
|
onCheckedChange = { isProxyEnabled = it }
|
||||||
)
|
)
|
||||||
|
|
||||||
AnimatedVisibility(visible = isProxyEnabled) {
|
AnimatedVisibility(visible = isProxyEnabled) {
|
||||||
Column {
|
Column {
|
||||||
EnumValueSelectorSettingsEntry(title = "Proxy",
|
EnumValueSelectorSettingsEntry(title = "Прокси",
|
||||||
selectedValue = proxyMode, onValueSelected = {proxyMode = it})
|
selectedValue = proxyMode, onValueSelected = {proxyMode = it})
|
||||||
TextDialogSettingEntry(
|
TextDialogSettingEntry(
|
||||||
title = "Хост",
|
title = "Хост",
|
||||||
@@ -221,6 +234,5 @@ fun OtherSettings() {
|
|||||||
onTextSave = { proxyPort = it.toIntOrNull() ?: 1080 })
|
onTextSave = { proxyPort = it.toIntOrNull() ?: 1080 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user