clean up settings and some other things

This commit is contained in:
2024-07-16 02:12:19 +03:00
parent 46d3fe63fa
commit b252c03be7
60 changed files with 698 additions and 613 deletions
@@ -9,14 +9,15 @@ import androidx.lifecycle.viewModelScope
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionStatus
import com.meloda.app.fast.auth.AuthGraph
import com.meloda.app.fast.common.LongPollController
import com.meloda.app.fast.common.UserConfig
import com.meloda.app.fast.common.extensions.ifEmpty
import com.meloda.app.fast.common.extensions.listenValue
import com.meloda.app.fast.common.extensions.setValue
import com.meloda.app.fast.common.model.LongPollState
import com.meloda.app.fast.data.db.GetCurrentAccountUseCase
import com.meloda.app.fast.datastore.SettingsController
import com.meloda.app.fast.datastore.AppSettings
import com.meloda.app.fast.datastore.UserSettings
import com.meloda.app.fast.datastore.model.LongPollState
import com.meloda.app.fast.model.BaseError
import com.meloda.app.fast.navigation.Main
import kotlinx.coroutines.Dispatchers
@@ -54,7 +55,8 @@ interface MainViewModel {
class MainViewModelImpl(
private val getCurrentAccountUseCase: GetCurrentAccountUseCase,
private val userSettings: UserSettings
private val userSettings: UserSettings,
private val longPollController: LongPollController
) : MainViewModel, ViewModel() {
init {
@@ -83,22 +85,21 @@ class MainViewModelImpl(
override fun onAppResumed() {
if (isNeedToShowNotificationsRationaleDialog.value) {
isNeedToShowNotificationsRationaleDialog.update { false }
isNeedToCheckNotificationsPermission.update { true }
}
userSettings.onLanguageChanged(
AppCompatDelegate.getApplicationLocales()
val newLanguage = AppCompatDelegate.getApplicationLocales()
.toLanguageTags()
.ifEmpty { null }
?: LocaleListCompat.getDefault()
.toLanguageTags()
.ifEmpty { null }
?: LocaleListCompat.getDefault()
.toLanguageTags()
.split(",")
.firstOrNull()
.orEmpty()
.take(5)
)
.split(",")
.firstOrNull()
.orEmpty()
.take(5)
userSettings.updateUsingDarkTheme()
userSettings.onAppLanguageChanged(newLanguage)
}
@ExperimentalPermissionsApi
@@ -151,7 +152,7 @@ class MainViewModelImpl(
}
private fun listenLongPollState() {
userSettings.longPollStateToApply.listenValue { newState ->
longPollController.stateToApply.listenValue { newState ->
if (newState == LongPollState.Background) {
isNeedToCheckNotificationsPermission.update { true }
}
@@ -174,8 +175,8 @@ class MainViewModelImpl(
this.trustedHash = currentAccount.trustedHash
}
userSettings.setLongPollStateToApply(
if (SettingsController.isLongPollInBackgroundEnabled) {
longPollController.setStateToApply(
if (AppSettings.Debug.longPollInBackground) {
LongPollState.Background
} else {
LongPollState.InApp
@@ -191,7 +192,7 @@ class MainViewModelImpl(
}
private fun disableBackgroundLongPoll() {
SettingsController.isLongPollInBackgroundEnabled = false
userSettings.setLongPollStateToApply(LongPollState.InApp)
AppSettings.Debug.longPollInBackground = false
longPollController.setStateToApply(LongPollState.InApp)
}
}
@@ -5,7 +5,7 @@ import androidx.preference.PreferenceManager
import coil.ImageLoader
import coil.ImageLoaderFactory
import com.meloda.app.fast.common.di.applicationModule
import com.meloda.app.fast.datastore.SettingsController
import com.meloda.app.fast.datastore.AppSettings
import org.koin.android.ext.android.get
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
@@ -17,7 +17,7 @@ class AppGlobal : Application(), ImageLoaderFactory {
super.onCreate()
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
SettingsController.init(preferences)
AppSettings.init(preferences)
UserConfig.init(preferences)
initKoin()
@@ -33,15 +33,17 @@ import com.google.accompanist.permissions.rememberPermissionState
import com.meloda.app.fast.MainViewModel
import com.meloda.app.fast.MainViewModelImpl
import com.meloda.app.fast.common.AppConstants
import com.meloda.app.fast.common.LongPollController
import com.meloda.app.fast.common.extensions.isSdkAtLeast
import com.meloda.app.fast.datastore.SettingsController
import com.meloda.app.fast.common.model.LongPollState
import com.meloda.app.fast.datastore.AppSettings
import com.meloda.app.fast.datastore.UserSettings
import com.meloda.app.fast.datastore.model.LongPollState
import com.meloda.app.fast.service.OnlineService
import com.meloda.app.fast.service.longpolling.LongPollingService
import com.meloda.app.fast.ui.model.ThemeConfig
import com.meloda.app.fast.ui.theme.AppTheme
import com.meloda.app.fast.ui.theme.LocalTheme
import com.meloda.app.fast.ui.theme.LocalThemeConfig
import com.meloda.app.fast.ui.util.isNeedToEnableDarkMode
import org.koin.androidx.compose.koinViewModel
import org.koin.compose.KoinContext
import org.koin.compose.koinInject
@@ -53,7 +55,7 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
SettingsController.deviceId = Settings.Secure.getString(
AppSettings.deviceId = Settings.Secure.getString(
contentResolver,
Settings.Secure.ANDROID_ID
)
@@ -78,10 +80,12 @@ class MainActivity : AppCompatActivity() {
setContent {
KoinContext {
val context = LocalContext.current
val userSettings: UserSettings = koinInject()
val longPollCurrentState by userSettings.longPollCurrentState.collectAsStateWithLifecycle()
val longPollStateToApply by userSettings.longPollStateToApply.collectAsStateWithLifecycle()
val userSettings: UserSettings = koinInject()
val longPollController: LongPollController = koinInject()
val longPollCurrentState by longPollController.currentState.collectAsStateWithLifecycle()
val longPollStateToApply by longPollController.stateToApply.collectAsStateWithLifecycle()
val viewModel: MainViewModel = koinViewModel<MainViewModelImpl>()
@@ -136,9 +140,9 @@ class MainActivity : AppCompatActivity() {
}
}
val isOnline by userSettings.online.collectAsStateWithLifecycle()
LifecycleResumeEffect(isOnline) {
toggleOnlineService(isOnline)
val sendOnline by userSettings.sendOnlineStatus.collectAsStateWithLifecycle()
LifecycleResumeEffect(sendOnline) {
toggleOnlineService(sendOnline)
onPauseOrDispose {
toggleOnlineService(false)
@@ -151,25 +155,22 @@ class MainActivity : AppCompatActivity() {
}
}
val theme by userSettings.theme.collectAsStateWithLifecycle()
CompositionLocalProvider(
LocalTheme provides ThemeConfig(
usingDarkStyle = theme.usingDarkStyle,
usingDynamicColors = theme.usingDynamicColors,
selectedColorScheme = theme.selectedColorScheme,
usingAmoledBackground = theme.usingAmoledBackground,
usingBlur = theme.usingBlur,
isMultiline = theme.isMultiline,
isDeviceCompact = isDeviceCompact
)
) {
val currentTheme = LocalTheme.current
val themeConfig = ThemeConfig(
darkMode = isNeedToEnableDarkMode(userSettings.darkMode.value),
dynamicColors = userSettings.enableDynamicColors.value,
selectedColorScheme = 0,
amoledDark = userSettings.enableAmoledDark.value,
enableBlur = userSettings.useBlur.value,
enableMultiline = userSettings.enableMultiline.value,
isDeviceCompact = isDeviceCompact
)
CompositionLocalProvider(LocalThemeConfig provides themeConfig) {
AppTheme(
useDarkTheme = currentTheme.usingDarkStyle,
useDynamicColors = currentTheme.usingDynamicColors,
selectedColorScheme = currentTheme.selectedColorScheme,
useAmoledBackground = currentTheme.usingAmoledBackground,
useDarkTheme = themeConfig.darkMode,
useDynamicColors = themeConfig.dynamicColors,
selectedColorScheme = themeConfig.selectedColorScheme,
useAmoledBackground = themeConfig.amoledDark,
) {
RootScreen(viewModel = viewModel)
}
@@ -220,7 +221,7 @@ class MainActivity : AppCompatActivity() {
private fun toggleLongPollService(
enable: Boolean,
inBackground: Boolean = SettingsController.isLongPollInBackgroundEnabled
inBackground: Boolean = AppSettings.Debug.longPollInBackground
) {
if (enable) {
val longPollIntent = Intent(this, LongPollingService::class.java)
@@ -246,7 +247,7 @@ class MainActivity : AppCompatActivity() {
private fun stopServices() {
toggleOnlineService(enable = false)
val asForeground = SettingsController.isLongPollInBackgroundEnabled
val asForeground = AppSettings.Debug.longPollInBackground
if (!asForeground) {
toggleLongPollService(enable = false)
@@ -28,7 +28,7 @@ import androidx.navigation.compose.rememberNavController
import com.meloda.app.fast.conversations.navigation.conversationsScreen
import com.meloda.app.fast.ui.theme.LocalBottomPadding
import com.meloda.app.fast.ui.theme.LocalHazeState
import com.meloda.app.fast.ui.theme.LocalTheme
import com.meloda.app.fast.ui.theme.LocalThemeConfig
import com.meloda.app.fast.friends.navigation.friendsScreen
import com.meloda.app.fast.model.BaseError
import com.meloda.app.fast.model.BottomNavigationItem
@@ -47,7 +47,7 @@ fun MainScreen(
onSettingsButtonClicked: () -> Unit = {},
onConversationItemClicked: (conversationId: Int) -> Unit = {}
) {
val currentTheme = LocalTheme.current
val currentTheme = LocalThemeConfig.current
val hazeState = remember { HazeState() }
val navController = rememberNavController()
@@ -60,7 +60,7 @@ fun MainScreen(
NavigationBar(
modifier = Modifier
.then(
if (currentTheme.usingBlur) {
if (currentTheme.enableBlur) {
Modifier.hazeChild(
state = hazeState,
style = HazeMaterials.thick()
@@ -69,7 +69,7 @@ fun MainScreen(
)
.fillMaxWidth(),
containerColor = NavigationBarDefaults.containerColor.copy(
alpha = if (currentTheme.usingBlur) 0f else 1f
alpha = if (currentTheme.enableBlur) 0f else 1f
)
) {
navigationItems.forEachIndexed { index, item ->
@@ -105,11 +105,11 @@ fun MainScreen(
Box(
modifier = Modifier
.fillMaxSize()
.padding(bottom = if (currentTheme.usingBlur) 0.dp else padding.calculateBottomPadding())
.padding(bottom = if (currentTheme.enableBlur) 0.dp else padding.calculateBottomPadding())
) {
CompositionLocalProvider(
LocalHazeState provides hazeState,
LocalBottomPadding provides if (currentTheme.usingBlur) padding.calculateBottomPadding() else 0.dp
LocalBottomPadding provides if (currentTheme.enableBlur) padding.calculateBottomPadding() else 0.dp
) {
NavHost(
navController = navController,
@@ -1,13 +1,13 @@
package com.meloda.app.fast.provider
import com.meloda.app.fast.common.ApiLanguage
import com.meloda.app.fast.common.model.ApiLanguage
import com.meloda.app.fast.common.provider.Provider
import com.meloda.app.fast.datastore.UserSettings
class ApiLanguageProvider(private val userSettings: UserSettings) : Provider<ApiLanguage> {
override fun provide(): ApiLanguage? {
val language = userSettings.language.value
val language = userSettings.appLanguage.value
return when {
language == "ru-RU" -> "ru"
@@ -3,7 +3,6 @@ package com.meloda.app.fast.service.longpolling
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import android.os.Build
import android.os.IBinder
@@ -13,19 +12,18 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat
import com.conena.nanokt.android.app.stopForegroundCompat
import com.meloda.app.fast.common.AppConstants
import com.meloda.app.fast.common.LongPollController
import com.meloda.app.fast.common.UserConfig
import com.meloda.app.fast.common.VkConstants
import com.meloda.app.fast.common.extensions.listenValue
import com.meloda.app.fast.common.model.LongPollState
import com.meloda.app.fast.data.LongPollUpdatesParser
import com.meloda.app.fast.data.LongPollUseCase
import com.meloda.app.fast.data.processState
import com.meloda.app.fast.datastore.SettingsController
import com.meloda.app.fast.datastore.SettingsKeys
import com.meloda.app.fast.datastore.UserSettings
import com.meloda.app.fast.datastore.model.LongPollState
import com.meloda.app.fast.ui.R
import com.meloda.app.fast.datastore.AppSettings
import com.meloda.app.fast.model.api.data.LongPollUpdates
import com.meloda.app.fast.model.api.data.VkLongPollData
import com.meloda.app.fast.ui.R
import com.meloda.app.fast.util.NotificationsUtils
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
@@ -40,7 +38,7 @@ import kotlin.coroutines.suspendCoroutine
class LongPollingService : Service() {
private val userSettings: UserSettings by inject()
private val longPollController: LongPollController by inject()
private val job = SupervisorJob()
@@ -51,8 +49,8 @@ class LongPollingService : Service() {
throwable.printStackTrace()
}
userSettings.updateLongPollCurrentState(LongPollState.Exception)
userSettings.setLongPollStateToApply(LongPollState.Exception)
longPollController.updateCurrentState(LongPollState.Exception)
longPollController.setStateToApply(LongPollState.Exception)
}
private val coroutineContext: CoroutineContext
@@ -62,7 +60,6 @@ class LongPollingService : Service() {
private val longPollUseCase: LongPollUseCase by inject()
private val updatesParser: LongPollUpdatesParser by inject()
private val preferences: SharedPreferences by inject()
private var currentJob: Job? = null
@@ -79,10 +76,7 @@ class LongPollingService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (startId > 1) return START_STICKY
val inBackground = preferences.getBoolean(
SettingsKeys.KEY_FEATURES_LONG_POLL_IN_BACKGROUND,
SettingsKeys.DEFAULT_VALUE_FEATURES_LONG_POLL_IN_BACKGROUND
)
val inBackground = AppSettings.Debug.longPollInBackground
Log.d(
STATE_TAG,
@@ -114,7 +108,7 @@ class LongPollingService : Service() {
PendingIntent.FLAG_IMMUTABLE
)
userSettings.updateLongPollCurrentState(
longPollController.updateCurrentState(
if (inBackground) LongPollState.Background
else LongPollState.InApp
)
@@ -254,11 +248,9 @@ class LongPollingService : Service() {
override fun onDestroy() {
Log.d(STATE_TAG, "onDestroy")
userSettings.updateLongPollCurrentState(LongPollState.Stopped)
longPollController.updateCurrentState(LongPollState.Stopped)
try {
SettingsController.edit {
putBoolean(KEY_LONG_POLL_WAS_DESTROYED, true)
}
AppSettings.edit { putBoolean(KEY_LONG_POLL_WAS_DESTROYED, true) }
job.cancel()
} catch (e: Exception) {
e.printStackTrace()
@@ -268,6 +260,7 @@ class LongPollingService : Service() {
override fun onLowMemory() {
Log.d(STATE_TAG, "onLowMemory")
longPollController.updateCurrentState(LongPollState.Stopped)
super.onLowMemory()
}
@@ -0,0 +1,27 @@
package com.meloda.app.fast.common
import com.meloda.app.fast.common.model.LongPollState
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
interface LongPollController {
val currentState: StateFlow<LongPollState>
val stateToApply: StateFlow<LongPollState>
fun updateCurrentState(newState: LongPollState)
fun setStateToApply(newState: LongPollState)
}
class LongPollControllerImpl : LongPollController {
override val currentState = MutableStateFlow<LongPollState>(LongPollState.Stopped)
override val stateToApply = MutableStateFlow<LongPollState>(LongPollState.Stopped)
override fun updateCurrentState(newState: LongPollState) {
currentState.value = newState
}
override fun setStateToApply(newState: LongPollState) {
currentState.value = newState
}
}
@@ -1,6 +1,12 @@
package com.meloda.app.fast.common.di
import coil.ImageLoader
import com.meloda.app.fast.common.LongPollController
import com.meloda.app.fast.common.LongPollControllerImpl
import com.meloda.app.fast.common.provider.ResourceProvider
import com.meloda.app.fast.common.provider.ResourceProviderImpl
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.bind
import org.koin.dsl.module
val commonModule = module {
@@ -9,4 +15,7 @@ val commonModule = module {
.crossfade(true)
.build()
}
singleOf(::LongPollControllerImpl) bind LongPollController::class
singleOf(::ResourceProviderImpl) bind ResourceProvider::class
}
@@ -1,4 +1,4 @@
package com.meloda.app.fast.common
package com.meloda.app.fast.common.extensions
import android.os.Bundle
import android.os.Parcelable
@@ -1,3 +1,3 @@
package com.meloda.app.fast.common
package com.meloda.app.fast.common.model
data class ApiLanguage(val value: String)
@@ -0,0 +1,19 @@
package com.meloda.app.fast.common.model
private const val MODE_NIGHT_NO = 1
private const val MODE_NIGHT_YES = 2
private const val MODE_NIGHT_FOLLOW_SYSTEM = -1
private const val MODE_NIGHT_AUTO_BATTERY = 3
enum class DarkMode(val value: Int) {
DISABLED(MODE_NIGHT_NO),
FOLLOW_SYSTEM(MODE_NIGHT_FOLLOW_SYSTEM),
AUTO_BATTERY(MODE_NIGHT_AUTO_BATTERY),
ENABLED(MODE_NIGHT_YES);
companion object {
fun parse(value: Int): DarkMode = entries.firstOrNull { it.value == value }
?: throw IllegalArgumentException("Unknown dark mode with value: $value")
}
}
@@ -1,5 +1,4 @@
package com.meloda.app.fast.datastore.model
package com.meloda.app.fast.common.model
sealed class LongPollState {
data object Stopped : LongPollState()
@@ -1,4 +1,4 @@
package com.meloda.app.fast.common
package com.meloda.app.fast.common.model
import android.graphics.drawable.Drawable
import androidx.annotation.ColorInt
@@ -1,4 +1,4 @@
package com.meloda.app.fast.common
package com.meloda.app.fast.common.model
import android.content.res.Resources
import androidx.annotation.PluralsRes
@@ -18,6 +18,8 @@ sealed class UiText {
data class Simple(val text: String) : UiText()
data class QuantityResource(@PluralsRes val resId: Int, val quantity: Int) : UiText()
}
fun UiText?.parseString(resources: Resources): String? {
@@ -0,0 +1,15 @@
package com.meloda.app.fast.common.provider
import android.content.res.Resources
interface ResourceProvider {
fun getString(resId: Int): String
}
class ResourceProviderImpl(private val resources: Resources) : ResourceProvider {
override fun getString(resId: Int): String {
return resources.getString(resId)
}
}
@@ -0,0 +1,190 @@
package com.meloda.app.fast.datastore
import android.content.SharedPreferences
import androidx.core.content.edit
import com.meloda.app.fast.common.model.DarkMode
import kotlin.properties.Delegates
import kotlin.reflect.KClass
object AppSettings {
private var preferences: SharedPreferences by Delegates.notNull()
fun init(preferences: SharedPreferences) {
this.preferences = preferences
}
fun edit(
commit: Boolean = false,
action: SharedPreferences.Editor.() -> Unit
) {
preferences.edit(commit, action)
}
fun getString(key: String, defaultValue: String?): String? {
return preferences.getString(key, defaultValue)
}
fun getBoolean(key: String, defaultValue: Boolean): Boolean {
return preferences.getBoolean(key, defaultValue)
}
fun getInt(key: String, defaultValue: Int): Int {
return preferences.getInt(key, defaultValue)
}
fun getLong(key: String, defaultValue: Long): Long {
return preferences.getLong(key, defaultValue)
}
fun getFloat(key: String, defaultValue: Float): Float {
return preferences.getFloat(key, defaultValue)
}
@Suppress("UNCHECKED_CAST")
fun <T : Any> get(clazz: KClass<T>, key: String, defaultValue: T): T {
return when (clazz) {
String::class -> getString(key, defaultValue as String)
Boolean::class -> getBoolean(key, defaultValue as Boolean)
Int::class -> getInt(key, defaultValue as Int)
Long::class -> getLong(key, defaultValue as Long)
Float::class -> getFloat(key, defaultValue as Float)
else -> throw IllegalStateException("Unsupported class: $clazz")
} as T
}
inline fun <reified T> get(key: String, defaultValue: T): T {
return when (T::class) {
String::class -> getString(key, defaultValue as String)
Boolean::class -> getBoolean(key, defaultValue as Boolean)
Int::class -> getInt(key, defaultValue as Int)
Long::class -> getLong(key, defaultValue as Long)
Float::class -> getFloat(key, defaultValue as Float)
else -> throw IllegalStateException("Unsupported class: ${T::class}")
} as T
}
fun <T> put(key: String, newValue: T?) {
preferences.edit {
when (newValue) {
is String -> putString(key, newValue)
is Boolean -> putBoolean(key, newValue)
is Int -> putInt(key, newValue)
is Long -> putLong(key, newValue)
is Float -> putFloat(key, newValue)
}
}
}
var deviceId: String
get() = get("device_id", "")
set(value) = put("device_id", value)
object General {
var useContactNames: Boolean
get() = get(
SettingsKeys.KEY_USE_CONTACT_NAMES,
SettingsKeys.DEFAULT_VALUE_USE_CONTACT_NAMES
)
set(value) = put(SettingsKeys.KEY_USE_CONTACT_NAMES, value)
var enablePullToRefresh: Boolean
get() = get(
SettingsKeys.KEY_ENABLE_PULL_TO_REFRESH,
SettingsKeys.DEFAULT_VALUE_ENABLE_PULL_TO_REFRESH
)
set(value) = put(SettingsKeys.KEY_ENABLE_PULL_TO_REFRESH, value)
}
object Appearance {
var enableMultiline: Boolean
get() = get(
SettingsKeys.KEY_APPEARANCE_MULTILINE,
SettingsKeys.DEFAULT_VALUE_MULTILINE
)
set(value) = put(SettingsKeys.KEY_APPEARANCE_MULTILINE, value)
var darkMode: DarkMode
get() = get(
SettingsKeys.KEY_APPEARANCE_DARK_MODE,
SettingsKeys.DEFAULT_VALUE_APPEARANCE_DARK_MODE
).let(DarkMode.Companion::parse)
set(mode) = put(SettingsKeys.KEY_APPEARANCE_DARK_MODE, mode.value)
var enableAmoledDark: Boolean
get() = get(
SettingsKeys.KEY_APPEARANCE_AMOLED_THEME,
SettingsKeys.DEFAULT_VALUE_APPEARANCE_AMOLED_THEME
)
set(value) = put(SettingsKeys.KEY_APPEARANCE_AMOLED_THEME, value)
var enableDynamicColors: Boolean
get() = get(
SettingsKeys.KEY_USE_DYNAMIC_COLORS,
SettingsKeys.DEFAULT_VALUE_USE_DYNAMIC_COLORS
)
set(value) = put(SettingsKeys.KEY_USE_DYNAMIC_COLORS, value)
var appLanguage: String
get() = get(
SettingsKeys.KEY_APPEARANCE_LANGUAGE,
SettingsKeys.DEFAULT_APPEARANCE_LANGUAGE
)
set(value) = put(SettingsKeys.KEY_APPEARANCE_LANGUAGE, value)
}
object Features {
var fastText: String
get() = get(
SettingsKeys.KEY_FEATURES_FAST_TEXT,
SettingsKeys.DEFAULT_VALUE_FEATURES_FAST_TEXT
)
set(value) = put(SettingsKeys.KEY_FEATURES_FAST_TEXT, value)
}
object Activity {
var sendOnlineStatus: Boolean
get() = get(
SettingsKeys.KEY_ACTIVITY_SEND_ONLINE_STATUS,
SettingsKeys.DEFAULT_VALUE_KEY_ACTIVITY_SEND_ONLINE_STATUS
)
set(value) = put(SettingsKeys.KEY_ACTIVITY_SEND_ONLINE_STATUS, value)
}
object Debug {
var showAlertAfterCrash: Boolean
get() = get(
SettingsKeys.KEY_DEBUG_SHOW_CRASH_ALERT,
true
)
set(value) = put(SettingsKeys.KEY_DEBUG_SHOW_CRASH_ALERT, value)
var longPollInBackground: Boolean
get() = get(
SettingsKeys.KEY_FEATURES_LONG_POLL_IN_BACKGROUND,
SettingsKeys.DEFAULT_VALUE_FEATURES_LONG_POLL_IN_BACKGROUND
)
set(value) = put(SettingsKeys.KEY_FEATURES_LONG_POLL_IN_BACKGROUND, value)
var useBlur: Boolean
get() = get(
SettingsKeys.KEY_APPEARANCE_USE_BLUR,
SettingsKeys.DEFAULT_VALUE_KEY_APPEARANCE_USE_BLUR
)
set(value) = put(SettingsKeys.KEY_APPEARANCE_USE_BLUR, value)
var showEmojiButton: Boolean
get() = get(
SettingsKeys.KEY_SHOW_EMOJI_BUTTON,
SettingsKeys.DEFAULT_VALUE_KEY_SHOW_EMOJI_BUTTON
)
set(value) = put(SettingsKeys.KEY_SHOW_EMOJI_BUTTON, value)
var showDebugCategory: Boolean
get() = get(
SettingsKeys.KEY_SHOW_DEBUG_CATEGORY,
false
)
set(value) = put(SettingsKeys.KEY_SHOW_DEBUG_CATEGORY, value)
}
}
@@ -1,57 +0,0 @@
package com.meloda.app.fast.datastore
import android.content.res.Configuration
import android.content.res.Resources
import android.os.PowerManager
import androidx.appcompat.app.AppCompatDelegate
fun isUsingDarkMode(
resources: Resources,
powerManager: PowerManager,
): Boolean {
val nightThemeMode: Int = SettingsController.getInt(
SettingsKeys.KEY_APPEARANCE_DARK_THEME,
SettingsKeys.DEFAULT_VALUE_APPEARANCE_DARK_THEME
)
val appForceDarkMode = nightThemeMode == AppCompatDelegate.MODE_NIGHT_YES
val appBatterySaver = nightThemeMode == AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY
val systemUiNightMode = resources.configuration.uiMode
val isSystemBatterySaver = powerManager.isPowerSaveMode
val isSystemUsingDarkTheme =
systemUiNightMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
return appForceDarkMode || (appBatterySaver && isSystemBatterySaver) || (!appBatterySaver && isSystemUsingDarkTheme && nightThemeMode == AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
}
fun isUsingDynamicColors(): Boolean = SettingsController.getBoolean(
SettingsKeys.KEY_USE_DYNAMIC_COLORS,
SettingsKeys.DEFAULT_VALUE_USE_DYNAMIC_COLORS
)
fun isUsingAmoledBackground(): Boolean = SettingsController.getBoolean(
SettingsKeys.KEY_APPEARANCE_AMOLED_THEME,
SettingsKeys.DEFAULT_VALUE_APPEARANCE_AMOLED_THEME
)
fun selectedColorScheme(): Int = SettingsController.getInt(
SettingsKeys.KEY_APPEARANCE_COLOR_SCHEME,
SettingsKeys.DEFAULT_VALUE_APPEARANCE_COLOR_SCHEME
)
fun isUsingBlur(): Boolean = SettingsController.getBoolean(
SettingsKeys.KEY_APPEARANCE_BLUR,
SettingsKeys.DEFAULT_VALUE_KEY_APPEARANCE_BLUR
)
fun isDebugSettingsShown(): Boolean = SettingsController.getBoolean(
SettingsKeys.KEY_SHOW_DEBUG_CATEGORY,
false
)
fun isMultiline(): Boolean = SettingsController.getBoolean(
SettingsKeys.KEY_APPEARANCE_MULTILINE,
SettingsKeys.DEFAULT_VALUE_MULTILINE
)
@@ -1,95 +0,0 @@
package com.meloda.app.fast.datastore
import android.content.SharedPreferences
import androidx.core.content.edit
import kotlin.properties.Delegates
import kotlin.reflect.KClass
object SettingsController {
private var preferences: SharedPreferences by Delegates.notNull()
fun init(preferences: SharedPreferences) {
this.preferences = preferences
}
fun edit(
commit: Boolean = false,
action: SharedPreferences.Editor.() -> Unit
) {
preferences.edit(commit, action)
}
fun getString(key: String, defaultValue: String?): String? {
return preferences.getString(key, defaultValue)
}
fun getBoolean(key: String, defaultValue: Boolean): Boolean {
return preferences.getBoolean(key, defaultValue)
}
fun getInt(key: String, defaultValue: Int): Int {
return preferences.getInt(key, defaultValue)
}
fun getLong(key: String, defaultValue: Long): Long {
return preferences.getLong(key, defaultValue)
}
fun getFloat(key: String, defaultValue: Float): Float {
return preferences.getFloat(key, defaultValue)
}
@Suppress("UNCHECKED_CAST")
fun <T : Any> get(clazz: KClass<T>, key: String, defaultValue: T): T {
return when (clazz) {
String::class -> getString(key, defaultValue as String)
Boolean::class -> getBoolean(key, defaultValue as Boolean)
Int::class -> getInt(key, defaultValue as Int)
Long::class -> getLong(key, defaultValue as Long)
Float::class -> getFloat(key, defaultValue as Float)
else -> throw IllegalStateException("Unsupported class: $clazz")
} as T
}
inline fun <reified T> get(key: String, defaultValue: T): T {
return when (T::class) {
String::class -> getString(key, defaultValue as String)
Boolean::class -> getBoolean(key, defaultValue as Boolean)
Int::class -> getInt(key, defaultValue as Int)
Long::class -> getLong(key, defaultValue as Long)
Float::class -> getFloat(key, defaultValue as Float)
else -> throw IllegalStateException("Unsupported class: ${T::class}")
} as T
}
fun <T> put(key: String, newValue: T?) {
preferences.edit {
when (newValue) {
is String -> putString(key, newValue)
is Boolean -> putBoolean(key, newValue)
is Int -> putInt(key, newValue)
is Long -> putLong(key, newValue)
is Float -> putFloat(key, newValue)
}
}
}
var isLongPollInBackgroundEnabled: Boolean
get() = get(
SettingsKeys.KEY_FEATURES_LONG_POLL_IN_BACKGROUND,
SettingsKeys.DEFAULT_VALUE_FEATURES_LONG_POLL_IN_BACKGROUND
)
set(value) = put(SettingsKeys.KEY_FEATURES_LONG_POLL_IN_BACKGROUND, value)
var deviceId: String
get() = get("device_id", "")
set(value) = put("device_id", value)
var enablePullToRefresh: Boolean
get() = get(
SettingsKeys.KEY_ENABLE_PULL_TO_REFRESH,
SettingsKeys.DEFAULT_VALUE_ENABLE_PULL_TO_REFRESH
)
set(value) = put(SettingsKeys.KEY_ENABLE_PULL_TO_REFRESH, value)
}
@@ -17,8 +17,8 @@ object SettingsKeys {
const val KEY_APPEARANCE = "appearance"
const val KEY_APPEARANCE_MULTILINE = "appearance_multiline"
const val DEFAULT_VALUE_MULTILINE = true
const val KEY_APPEARANCE_DARK_THEME = "appearance_appearance_dark_theme"
const val DEFAULT_VALUE_APPEARANCE_DARK_THEME = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
const val KEY_APPEARANCE_DARK_MODE = "appearance_appearance_dark_mode"
const val DEFAULT_VALUE_APPEARANCE_DARK_MODE = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
const val KEY_APPEARANCE_AMOLED_THEME = "appearance_amoled_theme"
const val DEFAULT_VALUE_APPEARANCE_AMOLED_THEME = false
const val KEY_USE_DYNAMIC_COLORS = "appearance_use_dynamic_colors"
@@ -26,8 +26,9 @@ object SettingsKeys {
const val KEY_APPEARANCE_COLOR_SCHEME = "appearance_color_scheme"
const val DEFAULT_VALUE_APPEARANCE_COLOR_SCHEME = 0
const val KEY_APPEARANCE_LANGUAGE = "appearance_language"
const val KEY_APPEARANCE_BLUR = "appearance_blur"
const val DEFAULT_VALUE_KEY_APPEARANCE_BLUR = false
const val DEFAULT_APPEARANCE_LANGUAGE = ""
const val KEY_APPEARANCE_USE_BLUR = "appearance_use_blur"
const val DEFAULT_VALUE_KEY_APPEARANCE_USE_BLUR = false
const val KEY_FEATURES_FAST_TEXT = "features_fast_text"
const val DEFAULT_VALUE_FEATURES_FAST_TEXT = "¯\\_(ツ)_/¯"
@@ -1,144 +1,124 @@
package com.meloda.app.fast.datastore
import android.content.res.Resources
import android.os.PowerManager
import android.util.Log
import com.meloda.app.fast.datastore.model.LongPollState
import com.meloda.app.fast.ui.model.ThemeConfig
import com.meloda.app.fast.common.model.DarkMode
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
interface UserSettings {
val theme: StateFlow<ThemeConfig>
val longPollStateToApply: StateFlow<LongPollState>
val longPollCurrentState: StateFlow<LongPollState>
val online: StateFlow<Boolean>
val debugSettingsEnabled: StateFlow<Boolean>
val useContactNames: StateFlow<Boolean>
val language: StateFlow<String>
val enablePullToRefresh: StateFlow<Boolean>
fun updateUsingDarkTheme()
fun useDarkThemeChanged(use: Boolean)
fun useAmoledThemeChanged(use: Boolean)
fun useDynamicColorsChanged(use: Boolean)
fun useBlurChanged(use: Boolean)
fun useMultiline(use: Boolean)
fun setLongPollStateToApply(newState: LongPollState)
fun updateLongPollCurrentState(currentState: LongPollState)
fun setOnline(use: Boolean)
fun enableDebugSettings(enable: Boolean)
val enableMultiline: StateFlow<Boolean>
val darkMode: StateFlow<DarkMode>
val enableAmoledDark: StateFlow<Boolean>
val enableDynamicColors: StateFlow<Boolean>
val appLanguage: StateFlow<String>
val fastText: StateFlow<String>
val sendOnlineStatus: StateFlow<Boolean>
val showAlertAfterCrash: StateFlow<Boolean>
val longPollInBackground: StateFlow<Boolean>
val useBlur: StateFlow<Boolean>
val showEmojiButton: StateFlow<Boolean>
val showDebugCategory: StateFlow<Boolean>
fun onUseContactNamesChanged(use: Boolean)
fun onLanguageChanged(newLanguage: String)
fun onEnablePullToRefreshChanged(enable: Boolean)
fun onEnableMultilineChanged(enable: Boolean)
fun onDarkModeChanged(mode: DarkMode)
fun onEnableAmoledDarkChanged(enable: Boolean)
fun onEnableDynamicColorsChanged(enable: Boolean)
fun onAppLanguageChanged(language: String)
fun onFastTextChanged(text: String)
fun onSendOnlineStatusChanged(send: Boolean)
fun onShowAlertAfterCrashChanged(show: Boolean)
fun onLongPollInBackgroundChanged(inBackground: Boolean)
fun onUseBlurChanged(use: Boolean)
fun onShowEmojiButtonChanged(show: Boolean)
fun onShowDebugCategoryChanged(show: Boolean)
}
class UserSettingsImpl(
private val resources: Resources,
private val powerManager: PowerManager
) : UserSettings {
class UserSettingsImpl : UserSettings {
override val theme = MutableStateFlow(
ThemeConfig(
usingDarkStyle = isUsingDarkMode(resources, powerManager),
usingDynamicColors = isUsingDynamicColors(),
selectedColorScheme = selectedColorScheme(),
usingAmoledBackground = isUsingAmoledBackground(),
usingBlur = isUsingBlur(),
isMultiline = isMultiline(),
isDeviceCompact = false
)
)
override val useContactNames = MutableStateFlow(AppSettings.General.useContactNames)
override val enablePullToRefresh = MutableStateFlow(AppSettings.General.enablePullToRefresh)
override val longPollStateToApply = MutableStateFlow<LongPollState>(LongPollState.Stopped)
override val longPollCurrentState = MutableStateFlow<LongPollState>(LongPollState.Stopped)
override val enableMultiline = MutableStateFlow(AppSettings.Appearance.enableMultiline)
override val darkMode = MutableStateFlow(AppSettings.Appearance.darkMode)
override val enableAmoledDark = MutableStateFlow(AppSettings.Appearance.enableAmoledDark)
override val enableDynamicColors = MutableStateFlow(AppSettings.Appearance.enableDynamicColors)
override val appLanguage = MutableStateFlow(AppSettings.Appearance.appLanguage)
override val online = MutableStateFlow(
SettingsController.getBoolean(
SettingsKeys.KEY_ACTIVITY_SEND_ONLINE_STATUS,
SettingsKeys.DEFAULT_VALUE_KEY_ACTIVITY_SEND_ONLINE_STATUS
)
)
override val fastText = MutableStateFlow(AppSettings.Features.fastText)
override val debugSettingsEnabled = MutableStateFlow(
SettingsController.getBoolean(
SettingsKeys.KEY_SHOW_DEBUG_CATEGORY,
false
)
)
override val sendOnlineStatus = MutableStateFlow(AppSettings.Activity.sendOnlineStatus)
override val useContactNames = MutableStateFlow(
SettingsController.getBoolean(
SettingsKeys.KEY_USE_CONTACT_NAMES,
SettingsKeys.DEFAULT_VALUE_USE_CONTACT_NAMES
)
)
override val language = MutableStateFlow("")
override val enablePullToRefresh = MutableStateFlow(SettingsController.enablePullToRefresh)
override fun updateUsingDarkTheme() {
useDarkThemeChanged(
isUsingDarkMode(
resources = resources,
powerManager = powerManager,
)
)
}
override fun useDarkThemeChanged(use: Boolean) {
theme.value = theme.value.copy(
usingDarkStyle = use
)
}
override fun useAmoledThemeChanged(use: Boolean) {
theme.value = theme.value.copy(
usingAmoledBackground = use
)
}
override fun useDynamicColorsChanged(use: Boolean) {
theme.value = theme.value.copy(usingDynamicColors = use)
}
override fun useBlurChanged(use: Boolean) {
theme.value = theme.value.copy(usingBlur = use)
}
override fun useMultiline(use: Boolean) {
theme.value = theme.value.copy(isMultiline = use)
}
override fun setLongPollStateToApply(newState: LongPollState) {
longPollStateToApply.update { newState }
Log.d("UserSettings", "setLongPollState: $newState")
}
override fun updateLongPollCurrentState(currentState: LongPollState) {
longPollCurrentState.update { currentState }
Log.d("UserSettings", "updateLongPollCurrentState: $currentState")
}
override fun setOnline(use: Boolean) {
online.value = use
}
override fun enableDebugSettings(enable: Boolean) {
debugSettingsEnabled.update { enable }
}
override val showAlertAfterCrash = MutableStateFlow(AppSettings.Debug.showAlertAfterCrash)
override val longPollInBackground = MutableStateFlow(AppSettings.Debug.longPollInBackground)
override val useBlur = MutableStateFlow(AppSettings.Debug.useBlur)
override val showEmojiButton = MutableStateFlow(AppSettings.Debug.showEmojiButton)
override val showDebugCategory = MutableStateFlow(AppSettings.Debug.showDebugCategory)
override fun onUseContactNamesChanged(use: Boolean) {
useContactNames.update { use }
}
override fun onLanguageChanged(newLanguage: String) {
language.update { newLanguage }
useContactNames.value = use
}
override fun onEnablePullToRefreshChanged(enable: Boolean) {
enablePullToRefresh.update { enable }
enablePullToRefresh.value = enable
}
override fun onEnableMultilineChanged(enable: Boolean) {
enableMultiline.value = enable
}
override fun onDarkModeChanged(mode: DarkMode) {
darkMode.value = mode
}
override fun onEnableAmoledDarkChanged(enable: Boolean) {
enableAmoledDark.value = enable
}
override fun onEnableDynamicColorsChanged(enable: Boolean) {
enableDynamicColors.value = enable
}
override fun onAppLanguageChanged(language: String) {
appLanguage.value = language
}
override fun onFastTextChanged(text: String) {
fastText.value = text
}
override fun onSendOnlineStatusChanged(send: Boolean) {
sendOnlineStatus.value = send
}
override fun onShowAlertAfterCrashChanged(show: Boolean) {
showAlertAfterCrash.value = show
}
override fun onLongPollInBackgroundChanged(inBackground: Boolean) {
longPollInBackground.value = inBackground
}
override fun onUseBlurChanged(use: Boolean) {
useBlur.value = use
}
override fun onShowEmojiButtonChanged(show: Boolean) {
showEmojiButton.value = show
}
override fun onShowDebugCategoryChanged(show: Boolean) {
showDebugCategory.value = show
}
}
@@ -1,7 +1,7 @@
package com.meloda.app.fast.network.interceptor
import androidx.core.net.toUri
import com.meloda.app.fast.common.ApiLanguage
import com.meloda.app.fast.common.model.ApiLanguage
import com.meloda.app.fast.common.provider.Provider
import okhttp3.Interceptor
import okhttp3.Response
@@ -22,7 +22,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.graphics.luminance
import com.meloda.app.fast.ui.theme.LocalIsDarkTheme
import com.meloda.app.fast.ui.theme.LocalThemeConfig
/**
* Default alpha levels used by Material components.
@@ -79,7 +79,7 @@ object ContentAlpha {
lowContrastAlpha: Float
): Float {
val contentColor = LocalContentColor.current
return if (!LocalIsDarkTheme.current) {
return if (!LocalThemeConfig.current.darkMode) {
if (contentColor.luminance() > 0.5) highContrastAlpha else lowContrastAlpha
} else {
if (contentColor.luminance() < 0.5) highContrastAlpha else lowContrastAlpha
@@ -33,8 +33,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
import com.meloda.app.fast.common.UiText
import com.meloda.app.fast.common.parseString
import com.meloda.app.fast.common.model.UiText
import com.meloda.app.fast.common.model.parseString
import com.meloda.app.fast.ui.util.ImmutableList
import com.meloda.app.fast.ui.util.ImmutableList.Companion.toImmutableList
import com.meloda.app.fast.ui.util.getString
@@ -1,11 +1,11 @@
package com.meloda.app.fast.ui.model
data class ThemeConfig(
val usingDarkStyle: Boolean,
val usingDynamicColors: Boolean,
val darkMode: Boolean,
val dynamicColors: Boolean,
val selectedColorScheme: Int,
val usingAmoledBackground: Boolean,
val usingBlur: Boolean,
val isMultiline: Boolean,
val amoledDark: Boolean,
val enableBlur: Boolean,
val enableMultiline: Boolean,
val isDeviceCompact: Boolean
)
@@ -9,7 +9,6 @@ import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.graphics.Color
@@ -104,20 +103,18 @@ private val robotoFonts = FontFamily(
)
)
val LocalTheme = compositionLocalOf {
val LocalThemeConfig = compositionLocalOf {
ThemeConfig(
usingDarkStyle = false,
usingDynamicColors = false,
darkMode = false,
dynamicColors = false,
selectedColorScheme = 0,
usingAmoledBackground = false,
usingBlur = false,
isMultiline = false,
amoledDark = false,
enableBlur = false,
enableMultiline = false,
isDeviceCompact = false
)
}
val LocalIsDarkTheme = compositionLocalOf { false }
val LocalHazeState = compositionLocalOf {
HazeState()
}
@@ -181,11 +178,9 @@ fun AppTheme(
}
}
CompositionLocalProvider(LocalIsDarkTheme provides useDarkTheme) {
MaterialTheme(
colorScheme = predefinedColorScheme ?: colorScheme,
typography = typography,
content = content
)
}
MaterialTheme(
colorScheme = predefinedColorScheme ?: colorScheme,
typography = typography,
content = content
)
}
@@ -1,5 +1,7 @@
package com.meloda.app.fast.ui.util
import android.content.res.Configuration
import android.os.PowerManager
import android.view.KeyEvent
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Composable
@@ -10,9 +12,12 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.key.onKeyEvent
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import com.meloda.app.fast.common.UiText
import androidx.core.content.getSystemService
import com.meloda.app.fast.common.model.DarkMode
import com.meloda.app.fast.common.model.UiText
@Composable
fun UiText?.getString(): String? {
@@ -75,3 +80,19 @@ fun LazyListState.isScrollingUp(): Boolean {
}
}.value
}
@Composable
fun isNeedToEnableDarkMode(darkMode: DarkMode): Boolean {
val context = LocalContext.current
val appForceDarkMode = darkMode == DarkMode.ENABLED
val appBatterySaver = darkMode == DarkMode.AUTO_BATTERY
val systemUiNightMode = context.resources.configuration.uiMode
val isSystemBatterySaver = context.getSystemService<PowerManager>()?.isPowerSaveMode == true
val isSystemUsingDarkTheme =
systemUiNightMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
return appForceDarkMode || (appBatterySaver && isSystemBatterySaver) || (!appBatterySaver && isSystemUsingDarkTheme && darkMode == DarkMode.FOLLOW_SYSTEM)
}
@@ -4,18 +4,19 @@ import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.meloda.app.fast.auth.login.BuildConfig
import com.meloda.app.fast.common.LongPollController
import com.meloda.app.fast.common.UserConfig
import com.meloda.app.fast.common.VkConstants
import com.meloda.app.fast.common.extensions.listenValue
import com.meloda.app.fast.common.extensions.setValue
import com.meloda.app.fast.common.extensions.updateValue
import com.meloda.app.fast.common.model.LongPollState
import com.meloda.app.fast.data.State
import com.meloda.app.fast.data.api.users.UsersUseCase
import com.meloda.app.fast.data.db.AccountsRepository
import com.meloda.app.fast.data.processState
import com.meloda.app.fast.datastore.SettingsController
import com.meloda.app.fast.datastore.AppSettings
import com.meloda.app.fast.datastore.UserSettings
import com.meloda.app.fast.datastore.model.LongPollState
import com.meloda.app.fast.model.database.AccountEntity
import com.meloda.app.fast.network.OAuthErrorDomain
import com.meloda.fast.auth.login.model.CaptchaArguments
@@ -71,7 +72,8 @@ class LoginViewModelImpl(
private val usersUseCase: UsersUseCase,
private val accountsRepository: AccountsRepository,
private val loginValidator: LoginValidator,
private val userSettings: UserSettings
private val userSettings: UserSettings,
private val longPollController: LongPollController
) : ViewModel(), LoginViewModel {
override val screenState = MutableStateFlow(LoginScreenState.EMPTY)
@@ -347,8 +349,8 @@ class LoginViewModelImpl(
}
private fun startLongPoll() {
userSettings.setLongPollStateToApply(
if (SettingsController.isLongPollInBackgroundEnabled) {
longPollController.setStateToApply(
if (AppSettings.Debug.longPollInBackground) {
LongPollState.Background
} else {
LongPollState.InApp
@@ -50,13 +50,13 @@ import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.meloda.app.fast.common.UiText
import com.meloda.app.fast.common.model.UiText
import com.meloda.app.fast.ui.basic.autoFillRequestHandler
import com.meloda.app.fast.ui.basic.connectNode
import com.meloda.app.fast.ui.basic.defaultFocusChangeAutoFill
import com.meloda.app.fast.ui.components.MaterialDialog
import com.meloda.app.fast.ui.components.TextFieldErrorText
import com.meloda.app.fast.ui.theme.LocalTheme
import com.meloda.app.fast.ui.theme.LocalThemeConfig
import com.meloda.app.fast.ui.util.handleEnterKey
import com.meloda.app.fast.ui.util.handleTabKey
import com.meloda.fast.auth.login.LoginViewModel
@@ -157,7 +157,7 @@ fun LoginScreen(
onPasswordFieldGoAction: () -> Unit = {},
onSignInButtonClicked: () -> Unit = {}
) {
val currentTheme = LocalTheme.current
val currentTheme = LocalThemeConfig.current
val focusManager = LocalFocusManager.current
val (loginFocusable, passwordFocusable) = FocusRequester.createRefs()
@@ -31,7 +31,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.meloda.app.fast.ui.theme.LocalTheme
import com.meloda.app.fast.ui.theme.LocalThemeConfig
import com.meloda.fast.auth.login.LoginViewModel
import com.meloda.fast.auth.login.LoginViewModelImpl
import org.koin.androidx.compose.koinViewModel
@@ -64,7 +64,7 @@ fun LogoScreen(
onLogoLongClicked: () -> Unit = {},
onGoNextButtonClicked: () -> Unit = {}
) {
val currentTheme = LocalTheme.current
val currentTheme = LocalThemeConfig.current
Scaffold { padding ->
val topPadding by animateDpAsState(
@@ -7,7 +7,7 @@ import androidx.navigation.compose.composable
import androidx.navigation.toRoute
import com.meloda.app.fast.auth.validation.model.ValidationArguments
import com.meloda.app.fast.auth.validation.presentation.ValidationRoute
import com.meloda.app.fast.common.customNavType
import com.meloda.app.fast.common.extensions.customNavType
import kotlinx.serialization.Serializable
import kotlin.reflect.typeOf
@@ -66,7 +66,7 @@ import com.meloda.app.fast.chatmaterials.ChatMaterialsViewModelImpl
import com.meloda.app.fast.chatmaterials.model.ChatMaterialsScreenState
import com.meloda.app.fast.datastore.UserSettings
import com.meloda.app.fast.ui.R
import com.meloda.app.fast.ui.theme.LocalTheme
import com.meloda.app.fast.ui.theme.LocalThemeConfig
import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.haze
import dev.chrisbanes.haze.hazeChild
@@ -110,7 +110,7 @@ fun ChatMaterialsScreen(
onRefreshDropdownItemClicked: () -> Unit = {},
onRefresh: () -> Unit = {}
) {
val currentTheme = LocalTheme.current
val currentTheme = LocalThemeConfig.current
val attachments = screenState.materials
@@ -154,7 +154,7 @@ fun ChatMaterialsScreen(
Log.d("ChatMaterialsScreen", "ChatMaterialsScreen: canScrollBackward: $canScrollBackward")
val topBarContainerColorAlpha by animateFloatAsState(
targetValue = if (!currentTheme.usingBlur || !canScrollBackward) 1f else 0f,
targetValue = if (!currentTheme.enableBlur || !canScrollBackward) 1f else 0f,
label = "toolbarColorAlpha",
animationSpec = tween(
durationMillis = 200,
@@ -164,7 +164,7 @@ fun ChatMaterialsScreen(
val topBarContainerColor by animateColorAsState(
targetValue =
if (currentTheme.usingBlur || !canScrollBackward)
if (currentTheme.enableBlur || !canScrollBackward)
MaterialTheme.colorScheme.surface
else
MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp),
@@ -182,7 +182,7 @@ fun ChatMaterialsScreen(
Column(
modifier = Modifier
.then(
if (currentTheme.usingBlur) {
if (currentTheme.enableBlur) {
Modifier.hazeChild(
state = hazeState,
style = hazeStyle
@@ -244,7 +244,7 @@ fun ChatMaterialsScreen(
}
)
if (currentTheme.usingBlur) {
if (currentTheme.enableBlur) {
DropdownMenuItem(
text = {
Text(text = if (moreClearBlur) "Default blur" else "Clearer blur")
@@ -300,7 +300,7 @@ fun ChatMaterialsScreen(
horizontalArrangement = Arrangement.spacedBy(2.dp),
modifier = Modifier
.then(
if (currentTheme.usingBlur) {
if (currentTheme.enableBlur) {
Modifier.haze(
state = hazeState,
style = hazeStyle
@@ -331,7 +331,7 @@ fun ChatMaterialsScreen(
state = listState,
modifier = Modifier
.then(
if (currentTheme.usingBlur) {
if (currentTheme.enableBlur) {
Modifier.haze(
state = hazeState,
style = hazeStyle
@@ -1,7 +1,7 @@
package com.meloda.app.fast.conversations.model
import com.meloda.app.fast.common.UiImage
import com.meloda.app.fast.common.UiText
import com.meloda.app.fast.common.model.UiImage
import com.meloda.app.fast.common.model.UiText
import com.meloda.app.fast.ui.R as UiR
sealed class ConversationOption(
@@ -2,7 +2,7 @@ package com.meloda.app.fast.conversations.model
import androidx.compose.runtime.Immutable
import androidx.compose.ui.text.AnnotatedString
import com.meloda.app.fast.common.UiImage
import com.meloda.app.fast.common.model.UiImage
import com.meloda.app.fast.model.api.PeerType
import com.meloda.app.fast.model.api.domain.VkMessage
import com.meloda.app.fast.ui.util.ImmutableList
@@ -52,7 +52,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
import com.meloda.app.fast.common.UiImage
import com.meloda.app.fast.common.model.UiImage
import com.meloda.app.fast.conversations.model.ConversationOption
import com.meloda.app.fast.conversations.model.UiConversation
import com.meloda.app.fast.ui.basic.ContentAlpha
@@ -78,7 +78,7 @@ import com.meloda.app.fast.ui.components.FullScreenLoader
import com.meloda.app.fast.ui.components.MaterialDialog
import com.meloda.app.fast.ui.theme.LocalBottomPadding
import com.meloda.app.fast.ui.theme.LocalHazeState
import com.meloda.app.fast.ui.theme.LocalTheme
import com.meloda.app.fast.ui.theme.LocalThemeConfig
import com.meloda.app.fast.ui.util.isScrollingUp
import dev.chrisbanes.haze.haze
import dev.chrisbanes.haze.hazeChild
@@ -159,10 +159,10 @@ fun ConversationsScreen(
onRefresh: () -> Unit = {}
) {
val view = LocalView.current
val currentTheme = LocalTheme.current
val currentTheme = LocalThemeConfig.current
val maxLines by remember(currentTheme) {
mutableIntStateOf(if (currentTheme.isMultiline) 2 else 1)
mutableIntStateOf(if (currentTheme.enableMultiline) 2 else 1)
}
val listState = rememberLazyListState()
@@ -195,7 +195,7 @@ fun ConversationsScreen(
val toolbarContainerColor by animateColorAsState(
targetValue =
if (currentTheme.usingBlur || !listState.canScrollBackward)
if (currentTheme.enableBlur || !listState.canScrollBackward)
MaterialTheme.colorScheme.surface
else
MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp),
@@ -258,12 +258,12 @@ fun ConversationsScreen(
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = toolbarContainerColor.copy(
alpha = if (currentTheme.usingBlur) toolbarColorAlpha else 1f
alpha = if (currentTheme.enableBlur) toolbarColorAlpha else 1f
)
),
modifier = Modifier
.then(
if (currentTheme.usingBlur) {
if (currentTheme.enableBlur) {
Modifier.hazeChild(
state = hazeState,
style = HazeMaterials.thick()
@@ -355,7 +355,7 @@ fun ConversationsScreen(
screenState = screenState,
state = listState,
maxLines = maxLines,
modifier = if (currentTheme.usingBlur) {
modifier = if (currentTheme.enableBlur) {
Modifier.haze(
state = hazeState,
style = HazeMaterials.thick()
@@ -9,11 +9,11 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import com.conena.nanokt.jvm.util.dayOfMonth
import com.conena.nanokt.jvm.util.month
import com.meloda.app.fast.common.UiImage
import com.meloda.app.fast.common.UiText
import com.meloda.app.fast.common.model.UiImage
import com.meloda.app.fast.common.model.UiText
import com.meloda.app.fast.common.UserConfig
import com.meloda.app.fast.common.extensions.orDots
import com.meloda.app.fast.common.parseString
import com.meloda.app.fast.common.model.parseString
import com.meloda.app.fast.common.util.TimeUtils
import com.meloda.app.fast.conversations.model.ActionState
import com.meloda.app.fast.conversations.model.UiConversation
@@ -1,6 +1,6 @@
package com.meloda.app.fast.friends.model
import com.meloda.app.fast.common.UiImage
import com.meloda.app.fast.common.model.UiImage
import com.meloda.app.fast.model.api.domain.OnlineStatus
data class UiFriend(
@@ -59,7 +59,7 @@ import com.meloda.app.fast.ui.components.FullScreenLoader
import com.meloda.app.fast.ui.components.NoItemsView
import com.meloda.app.fast.ui.model.TabItem
import com.meloda.app.fast.ui.theme.LocalHazeState
import com.meloda.app.fast.ui.theme.LocalTheme
import com.meloda.app.fast.ui.theme.LocalThemeConfig
import com.meloda.app.fast.ui.util.ImmutableList
import dev.chrisbanes.haze.haze
import dev.chrisbanes.haze.hazeChild
@@ -122,11 +122,11 @@ fun FriendsScreen(
onPaginationConditionsMet: () -> Unit = {},
onRefresh: () -> Unit = {}
) {
val currentTheme = LocalTheme.current
val currentTheme = LocalThemeConfig.current
val maxLines by remember {
derivedStateOf {
if (currentTheme.isMultiline) 2 else 1
if (currentTheme.enableMultiline) 2 else 1
}
}
@@ -149,7 +149,7 @@ fun FriendsScreen(
val hazeState = LocalHazeState.current
val topBarContainerColorAlpha by animateFloatAsState(
targetValue = if (!currentTheme.usingBlur || !listState.canScrollBackward) 1f else 0f,
targetValue = if (!currentTheme.enableBlur || !listState.canScrollBackward) 1f else 0f,
label = "toolbarColorAlpha",
animationSpec = tween(
durationMillis = 200,
@@ -159,7 +159,7 @@ fun FriendsScreen(
val topBarContainerColor by animateColorAsState(
targetValue =
if (currentTheme.usingBlur || !listState.canScrollBackward)
if (currentTheme.enableBlur || !listState.canScrollBackward)
MaterialTheme.colorScheme.surface
else
MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp),
@@ -196,7 +196,7 @@ fun FriendsScreen(
Column(
modifier = Modifier
.then(
if (currentTheme.usingBlur) {
if (currentTheme.enableBlur) {
Modifier.hazeChild(
state = hazeState,
style = HazeMaterials.thick()
@@ -295,7 +295,7 @@ fun FriendsScreen(
val friendsToDisplay = screenState.friends
FriendsList(
modifier = if (currentTheme.usingBlur) {
modifier = if (currentTheme.enableBlur) {
Modifier.haze(
state = hazeState,
style = HazeMaterials.thick()
@@ -1,6 +1,6 @@
package com.meloda.app.fast.friends.util
import com.meloda.app.fast.common.UiImage
import com.meloda.app.fast.common.model.UiImage
import com.meloda.app.fast.data.VkMemoryCache
import com.meloda.app.fast.friends.model.UiFriend
import com.meloda.app.fast.model.api.domain.VkUser
@@ -4,9 +4,9 @@ import android.content.res.Resources
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.os.LocaleListCompat
import androidx.lifecycle.ViewModel
import com.meloda.app.fast.common.UiText
import com.meloda.app.fast.common.model.UiText
import com.meloda.app.fast.common.extensions.setValue
import com.meloda.app.fast.common.parseString
import com.meloda.app.fast.common.model.parseString
import com.meloda.app.fast.languagepicker.model.LanguagePickerScreenState
import com.meloda.app.fast.languagepicker.model.SelectableLanguage
import kotlinx.coroutines.flow.MutableStateFlow
@@ -1,7 +1,7 @@
package com.meloda.app.fast.messageshistory.model
import androidx.compose.runtime.Immutable
import com.meloda.app.fast.common.UiImage
import com.meloda.app.fast.common.model.UiImage
import com.meloda.app.fast.model.api.domain.VkAttachment
@Immutable
@@ -1,6 +1,6 @@
package com.meloda.app.fast.messageshistory.model
import com.meloda.app.fast.common.UiImage
import com.meloda.app.fast.common.model.UiImage
data class UiMessage(
val id: Int,
@@ -5,7 +5,7 @@ import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import androidx.navigation.toRoute
import com.meloda.app.fast.common.customNavType
import com.meloda.app.fast.common.extensions.customNavType
import com.meloda.app.fast.messageshistory.model.MessagesHistoryArguments
import com.meloda.app.fast.messageshistory.presentation.MessagesHistoryRoute
import com.meloda.app.fast.model.BaseError
@@ -72,7 +72,7 @@ import com.meloda.app.fast.messageshistory.MessagesHistoryViewModelImpl
import com.meloda.app.fast.messageshistory.model.ActionMode
import com.meloda.app.fast.messageshistory.model.MessagesHistoryScreenState
import com.meloda.app.fast.model.BaseError
import com.meloda.app.fast.ui.theme.LocalTheme
import com.meloda.app.fast.ui.theme.LocalThemeConfig
import com.meloda.app.fast.ui.util.ImmutableList
import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.hazeChild
@@ -131,7 +131,7 @@ fun MessagesHistoryScreen(
val view = LocalView.current
val preferences: SharedPreferences = koinInject()
val currentTheme = LocalTheme.current
val currentTheme = LocalThemeConfig.current
val listState = rememberLazyListState()
@@ -178,7 +178,7 @@ fun MessagesHistoryScreen(
TopAppBar(
modifier = Modifier
.then(
if (currentTheme.usingBlur) {
if (currentTheme.enableBlur) {
Modifier.hazeChild(
state = hazeSate,
style = HazeMaterials.thick()
@@ -203,7 +203,7 @@ fun MessagesHistoryScreen(
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.surface.copy(
alpha = if (currentTheme.usingBlur) toolbarColorAlpha else 1f
alpha = if (currentTheme.enableBlur) toolbarColorAlpha else 1f
)
),
actions = {
@@ -17,7 +17,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.meloda.app.fast.messageshistory.model.UiMessage
import com.meloda.app.fast.ui.theme.LocalTheme
import com.meloda.app.fast.ui.theme.LocalThemeConfig
import com.meloda.app.fast.ui.util.ImmutableList
import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.haze
@@ -35,13 +35,13 @@ fun MessagesList(
enableAnimations: Boolean
) {
val messages = immutableMessages.toList()
val currentTheme = LocalTheme.current
val currentTheme = LocalThemeConfig.current
LazyColumn(
modifier = modifier
.fillMaxWidth()
.then(
if (currentTheme.usingBlur) {
if (currentTheme.enableBlur) {
Modifier.haze(
state = hazeState,
style = HazeMaterials.regular()
@@ -1,11 +1,11 @@
package com.meloda.app.fast.messageshistory.util
import android.content.res.Resources
import com.meloda.app.fast.common.UiImage
import com.meloda.app.fast.common.UiText
import com.meloda.app.fast.common.model.UiImage
import com.meloda.app.fast.common.model.UiText
import com.meloda.app.fast.common.UserConfig
import com.meloda.app.fast.common.extensions.orDots
import com.meloda.app.fast.common.parseString
import com.meloda.app.fast.common.model.parseString
import com.meloda.app.fast.data.VkMemoryCache
import com.meloda.app.fast.ui.R
import com.meloda.app.fast.messageshistory.model.UiMessage
@@ -1,7 +1,7 @@
package com.meloda.app.fast.photoviewer.model
import androidx.compose.runtime.Immutable
import com.meloda.app.fast.common.UiImage
import com.meloda.app.fast.common.model.UiImage
@Immutable
data class PhotoViewArguments(
@@ -1,7 +1,7 @@
package com.meloda.app.fast.photoviewer.model
import androidx.compose.runtime.Immutable
import com.meloda.app.fast.common.UiImage
import com.meloda.app.fast.common.model.UiImage
@Immutable
data class PhotoViewState(
@@ -44,7 +44,7 @@ import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil.compose.AsyncImage
import com.meloda.app.fast.common.UiImage
import com.meloda.app.fast.common.model.UiImage
import com.meloda.app.fast.photoviewer.PhotoViewViewModel
import com.meloda.app.fast.photoviewer.model.PhotoViewState
@@ -6,17 +6,18 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.core.view.HapticFeedbackConstantsCompat
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.meloda.app.fast.common.UiText
import com.meloda.app.fast.common.LongPollController
import com.meloda.app.fast.common.UserConfig
import com.meloda.app.fast.common.extensions.findWithIndex
import com.meloda.app.fast.common.extensions.isSdkAtLeast
import com.meloda.app.fast.common.extensions.setValue
import com.meloda.app.fast.common.model.DarkMode
import com.meloda.app.fast.common.model.LongPollState
import com.meloda.app.fast.common.model.UiText
import com.meloda.app.fast.data.db.AccountsRepository
import com.meloda.app.fast.datastore.SettingsController
import com.meloda.app.fast.datastore.AppSettings
import com.meloda.app.fast.datastore.SettingsKeys
import com.meloda.app.fast.datastore.UserSettings
import com.meloda.app.fast.datastore.isDebugSettingsShown
import com.meloda.app.fast.datastore.model.LongPollState
import com.meloda.app.fast.model.database.AccountEntity
import com.meloda.app.fast.settings.model.SettingsItem
import com.meloda.app.fast.settings.model.SettingsScreenState
@@ -32,8 +33,7 @@ import com.meloda.app.fast.ui.R as UiR
interface SettingsViewModel {
val screenState: StateFlow<SettingsScreenState>
val isLongPollBackgroundEnabled: StateFlow<Boolean?>
val hapticType: StateFlow<HapticType?>
fun onLogOutAlertDismissed()
fun onLogOutAlertPositiveClick()
@@ -46,22 +46,20 @@ interface SettingsViewModel {
fun onSettingsItemChanged(key: String, newValue: Any?)
fun onHapticPerformed()
fun onNotificationsPermissionRequested()
}
class SettingsViewModelImpl(
private val accountsRepository: AccountsRepository,
private val userSettings: UserSettings,
private val resources: Resources
private val resources: Resources,
private val longPollController: LongPollController
) : SettingsViewModel, ViewModel() {
override val screenState = MutableStateFlow(SettingsScreenState.EMPTY)
override val hapticType = MutableStateFlow<HapticType?>(null)
private val settings = MutableStateFlow<List<SettingsItem<*>>>(emptyList())
override val isLongPollBackgroundEnabled = MutableStateFlow<Boolean?>(null)
init {
createSettings()
}
@@ -106,22 +104,15 @@ class SettingsViewModelImpl(
}
SettingsKeys.KEY_DEBUG_HIDE_DEBUG_LIST -> {
val showDebugCategory = isDebugSettingsShown()
val showDebugCategory = AppSettings.Debug.showDebugCategory
if (!showDebugCategory) return
SettingsController.put(
SettingsKeys.KEY_SHOW_DEBUG_CATEGORY,
false
)
onSettingsItemChanged(key, false)
createSettings()
screenState.setValue { old ->
old.copy(
useHaptics = HapticType.REJECT,
showDebugOptions = false
)
}
hapticType.update { HapticType.REJECT }
screenState.setValue { old -> old.copy(showDebugOptions = false) }
}
}
}
@@ -129,18 +120,14 @@ class SettingsViewModelImpl(
override fun onSettingsItemLongClicked(key: String) {
when (key) {
SettingsKeys.KEY_ACTIVITY_SEND_ONLINE_STATUS -> {
if (isDebugSettingsShown()) return
if (AppSettings.Debug.showDebugCategory) return
SettingsController.put(SettingsKeys.KEY_SHOW_DEBUG_CATEGORY, true)
onSettingsItemChanged(key, true)
createSettings()
screenState.setValue { old ->
old.copy(
useHaptics = HapticType.LONG_PRESS,
showDebugOptions = true
)
}
hapticType.update { HapticType.LONG_PRESS }
screenState.setValue { old -> old.copy(showDebugOptions = true) }
}
}
}
@@ -160,70 +147,98 @@ class SettingsViewModelImpl(
}
when (key) {
SettingsKeys.KEY_FEATURES_LONG_POLL_IN_BACKGROUND -> {
val isEnabled = (newValue as? Boolean) == true
userSettings.setLongPollStateToApply(
userSettings.longPollStateToApply.value.let { state ->
if (state.isLaunched()) {
if (isEnabled) LongPollState.Background
else LongPollState.InApp
} else state
}
)
if (isEnabled) {
// TODO: 26/11/2023, Danil Nikolaev: implement
val isNotificationsPermissionGranted = false
if (!isNotificationsPermissionGranted) {
// TODO: 26/11/2023, Danil Nikolaev: implement restart
}
}
}
SettingsKeys.KEY_APPEARANCE_MULTILINE -> {
val isUsing = newValue as? Boolean ?: false
userSettings.useMultiline(isUsing)
}
SettingsKeys.KEY_APPEARANCE_AMOLED_THEME -> {
val isUsing = newValue as? Boolean ?: false
userSettings.useAmoledThemeChanged(isUsing)
}
SettingsKeys.KEY_USE_DYNAMIC_COLORS -> {
val isUsing = newValue as? Boolean ?: false
userSettings.useDynamicColorsChanged(isUsing)
}
SettingsKeys.KEY_APPEARANCE_BLUR -> {
val isUsing = newValue as? Boolean ?: false
userSettings.useBlurChanged(isUsing)
}
SettingsKeys.KEY_ACTIVITY_SEND_ONLINE_STATUS -> {
val isUsing = newValue as? Boolean ?: false
userSettings.setOnline(isUsing)
}
SettingsKeys.KEY_USE_CONTACT_NAMES -> {
val isUsing = newValue as? Boolean ?: false
val isUsing = newValue as? Boolean ?: SettingsKeys.DEFAULT_VALUE_USE_CONTACT_NAMES
userSettings.onUseContactNamesChanged(isUsing)
}
SettingsKeys.KEY_ENABLE_PULL_TO_REFRESH -> {
val enable = newValue as? Boolean ?: false
val enable =
newValue as? Boolean ?: SettingsKeys.DEFAULT_VALUE_ENABLE_PULL_TO_REFRESH
userSettings.onEnablePullToRefreshChanged(enable)
}
SettingsKeys.KEY_APPEARANCE_MULTILINE -> {
val isUsing = newValue as? Boolean ?: SettingsKeys.DEFAULT_VALUE_MULTILINE
userSettings.onEnableMultilineChanged(isUsing)
}
SettingsKeys.KEY_APPEARANCE_DARK_MODE -> {
val newMode = newValue as? Int ?: SettingsKeys.DEFAULT_VALUE_APPEARANCE_DARK_MODE
AppCompatDelegate.setDefaultNightMode(newMode)
userSettings.onDarkModeChanged(DarkMode.parse(newMode))
}
SettingsKeys.KEY_APPEARANCE_AMOLED_THEME -> {
val isUsing =
newValue as? Boolean ?: SettingsKeys.DEFAULT_VALUE_APPEARANCE_AMOLED_THEME
userSettings.onEnableAmoledDarkChanged(isUsing)
}
SettingsKeys.KEY_USE_DYNAMIC_COLORS -> {
val isUsing = newValue as? Boolean ?: SettingsKeys.DEFAULT_VALUE_USE_DYNAMIC_COLORS
userSettings.onEnableDynamicColorsChanged(isUsing)
}
SettingsKeys.KEY_APPEARANCE_LANGUAGE -> {
val newLanguage = newValue as? String ?: SettingsKeys.DEFAULT_APPEARANCE_LANGUAGE
userSettings.onAppLanguageChanged(newLanguage)
}
SettingsKeys.DEFAULT_VALUE_FEATURES_FAST_TEXT -> {
val newText = newValue as? String ?: SettingsKeys.DEFAULT_VALUE_FEATURES_FAST_TEXT
userSettings.onFastTextChanged(newText)
}
SettingsKeys.KEY_ACTIVITY_SEND_ONLINE_STATUS -> {
val isUsing = newValue as? Boolean
?: SettingsKeys.DEFAULT_VALUE_KEY_ACTIVITY_SEND_ONLINE_STATUS
userSettings.onSendOnlineStatusChanged(isUsing)
}
SettingsKeys.KEY_DEBUG_SHOW_CRASH_ALERT -> {
val show = newValue as? Boolean ?: SettingsKeys.DEFAULT_VALUE_KEY_SHOW_EMOJI_BUTTON
userSettings.onShowAlertAfterCrashChanged(show)
}
SettingsKeys.KEY_FEATURES_LONG_POLL_IN_BACKGROUND -> {
val inBackground = newValue as? Boolean
?: SettingsKeys.DEFAULT_VALUE_FEATURES_LONG_POLL_IN_BACKGROUND
userSettings.onLongPollInBackgroundChanged(inBackground)
longPollController.setStateToApply(
longPollController.stateToApply.value.let { state ->
if (state.isLaunched()) {
if (inBackground) LongPollState.Background
else LongPollState.InApp
} else state
}
)
}
SettingsKeys.KEY_APPEARANCE_USE_BLUR -> {
val isUsing =
newValue as? Boolean ?: SettingsKeys.DEFAULT_VALUE_KEY_APPEARANCE_USE_BLUR
userSettings.onUseBlurChanged(isUsing)
}
SettingsKeys.KEY_SHOW_EMOJI_BUTTON -> {
val show = newValue as? Boolean ?: SettingsKeys.DEFAULT_VALUE_KEY_SHOW_EMOJI_BUTTON
userSettings.onShowEmojiButtonChanged(show)
}
SettingsKeys.KEY_SHOW_DEBUG_CATEGORY -> {
val show = newValue as? Boolean ?: false
userSettings.onShowDebugCategoryChanged(show)
}
}
}
override fun onHapticPerformed() {
screenState.setValue { old -> old.copy(useHaptics = null) }
}
override fun onNotificationsPermissionRequested() {
screenState.setValue { old -> old.copy(isNeedToRequestNotificationPermission = false) }
hapticType.update { null }
}
private fun emitShowOptions(function: (SettingsShowOptions) -> SettingsShowOptions) {
@@ -273,22 +288,22 @@ class SettingsViewModelImpl(
)
val darkThemeValues = listOf(
AppCompatDelegate.MODE_NIGHT_YES to UiText.Resource(UiR.string.settings_dark_theme_value_enabled),
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM to UiText.Resource(UiR.string.settings_dark_theme_value_follow_system),
AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY to UiText.Resource(UiR.string.settings_dark_theme_value_battery_saver),
AppCompatDelegate.MODE_NIGHT_NO to UiText.Resource(UiR.string.settings_dark_theme_value_disabled)
DarkMode.ENABLED to UiText.Resource(UiR.string.settings_dark_theme_value_enabled),
DarkMode.FOLLOW_SYSTEM to UiText.Resource(UiR.string.settings_dark_theme_value_follow_system),
DarkMode.AUTO_BATTERY to UiText.Resource(UiR.string.settings_dark_theme_value_battery_saver),
DarkMode.DISABLED to UiText.Resource(UiR.string.settings_dark_theme_value_disabled)
).toMap()
val appearanceDarkTheme = SettingsItem.ListItem(
key = SettingsKeys.KEY_APPEARANCE_DARK_THEME,
key = SettingsKeys.KEY_APPEARANCE_DARK_MODE,
title = UiText.Resource(UiR.string.settings_dark_theme),
valueClass = Int::class,
defaultValue = SettingsKeys.DEFAULT_VALUE_APPEARANCE_DARK_THEME,
defaultValue = SettingsKeys.DEFAULT_VALUE_APPEARANCE_DARK_MODE,
titles = darkThemeValues.values.toList(),
values = darkThemeValues.keys.toList()
values = darkThemeValues.keys.toList().map(DarkMode::value)
).apply {
textProvider = TextProvider { item ->
val darkThemeValue = darkThemeValues[item.value]
val darkThemeValue = darkThemeValues[DarkMode.parse(item.value)]
UiText.ResourceParams(
value = UiR.string.settings_dark_theme_current_value,
@@ -368,8 +383,8 @@ class SettingsViewModelImpl(
text = UiText.Simple("Shows alert dialog with stacktrace after app crashed\n(it will be not shown if you perform crash manually)")
)
val debugUseBlur = SettingsItem.Switch(
key = SettingsKeys.KEY_APPEARANCE_BLUR,
defaultValue = SettingsKeys.DEFAULT_VALUE_KEY_APPEARANCE_BLUR,
key = SettingsKeys.KEY_APPEARANCE_USE_BLUR,
defaultValue = SettingsKeys.DEFAULT_VALUE_KEY_APPEARANCE_USE_BLUR,
title = UiText.Simple("[WIP] Use blur"),
text = UiText.Simple("Adds blur wherever possible\nOn android 11 and older will have transparency instead of blurring"),
)
@@ -432,7 +447,7 @@ class SettingsViewModelImpl(
debugList,
).forEach(settingsList::addAll)
if (!isDebugSettingsShown()) {
if (!AppSettings.Debug.showDebugCategory) {
settingsList.removeAll(debugList)
}
@@ -2,9 +2,9 @@ package com.meloda.app.fast.settings.model
import android.content.res.Resources
import androidx.compose.runtime.Immutable
import com.meloda.app.fast.common.UiText
import com.meloda.app.fast.common.parseString
import com.meloda.app.fast.datastore.SettingsController
import com.meloda.app.fast.common.model.UiText
import com.meloda.app.fast.common.model.parseString
import com.meloda.app.fast.datastore.AppSettings
import kotlin.reflect.KClass
@Immutable
@@ -37,7 +37,7 @@ sealed class SettingsItem<T>(
protected set(newValue) {
field = newValue
SettingsController.put(key, newValue)
AppSettings.put(key, newValue)
}
var title: UiText? = null
@@ -165,7 +165,7 @@ sealed class SettingsItem<T>(
val values: List<T>
) : SettingsItem<T>(
key = key,
value = selectedValue ?: SettingsController.get(valueClass, key, defaultValue),
value = selectedValue ?: AppSettings.get(valueClass, key, defaultValue),
defaultValue = defaultValue
) {
@@ -245,6 +245,6 @@ private inline fun <reified T> getCurrentValue(key: String, defaultValue: T): T
if (T::class == Nothing::class) {
throw IllegalStateException("Items with Nothing does not have a value")
} else {
return SettingsController.get(key, defaultValue)
return AppSettings.get(key, defaultValue)
}
}
@@ -1,15 +1,12 @@
package com.meloda.app.fast.settings.model
import androidx.compose.runtime.Immutable
import com.meloda.app.fast.datastore.isDebugSettingsShown
import com.meloda.app.fast.settings.HapticType
import com.meloda.app.fast.datastore.AppSettings
@Immutable
data class SettingsScreenState(
val showOptions: SettingsShowOptions,
val settings: List<UiItem>,
val useHaptics: HapticType?,
val isNeedToRequestNotificationPermission: Boolean,
val showDebugOptions: Boolean
) {
@@ -17,9 +14,7 @@ data class SettingsScreenState(
val EMPTY: SettingsScreenState = SettingsScreenState(
showOptions = SettingsShowOptions.EMPTY,
settings = emptyList(),
useHaptics = null,
isNeedToRequestNotificationPermission = false,
showDebugOptions = isDebugSettingsShown()
showDebugOptions = AppSettings.Debug.showDebugCategory
)
}
}
@@ -1,6 +1,6 @@
package com.meloda.app.fast.settings.model
import com.meloda.app.fast.common.UiText
import com.meloda.app.fast.common.model.UiText
fun interface TextProvider<T, S : SettingsItem<T>> {
fun provideText(item: S): UiText?
@@ -1,6 +1,6 @@
package com.meloda.app.fast.settings.model
import com.meloda.app.fast.common.UiText
import com.meloda.app.fast.common.model.UiText
fun interface TitleProvider<T, S : SettingsItem<T>> {
fun provideTitle(item: S): UiText?
@@ -1,7 +1,5 @@
package com.meloda.app.fast.settings.presentation
import android.os.PowerManager
import androidx.appcompat.app.AppCompatDelegate
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.calculateEndPadding
@@ -27,17 +25,14 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.LayoutDirection
import androidx.core.content.getSystemService
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.meloda.app.fast.common.UserConfig
import com.meloda.app.fast.datastore.SettingsKeys
import com.meloda.app.fast.datastore.UserSettings
import com.meloda.app.fast.datastore.isUsingDarkMode
import com.meloda.app.fast.settings.HapticType
import com.meloda.app.fast.settings.SettingsViewModel
import com.meloda.app.fast.settings.SettingsViewModelImpl
import com.meloda.app.fast.settings.model.SettingsScreenState
@@ -49,14 +44,13 @@ import com.meloda.app.fast.settings.presentation.item.TitleItem
import com.meloda.app.fast.settings.presentation.item.TitleTextItem
import com.meloda.app.fast.ui.components.ActionInvokeDismiss
import com.meloda.app.fast.ui.components.MaterialDialog
import com.meloda.app.fast.ui.theme.LocalTheme
import com.meloda.app.fast.ui.theme.LocalThemeConfig
import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.haze
import dev.chrisbanes.haze.hazeChild
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import dev.chrisbanes.haze.materials.HazeMaterials
import org.koin.androidx.compose.koinViewModel
import org.koin.compose.koinInject
import com.meloda.app.fast.ui.R as UiR
@Composable
@@ -66,16 +60,12 @@ fun SettingsRoute(
onLanguageItemClicked: () -> Unit,
viewModel: SettingsViewModel = koinViewModel<SettingsViewModelImpl>()
) {
val context = LocalContext.current
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
val hapticType by viewModel.hapticType.collectAsStateWithLifecycle()
val userSettings: UserSettings = koinInject()
LaunchedEffect(true) {
userSettings.enableDebugSettings(screenState.showDebugOptions)
}
SettingsScreen(screenState = screenState,
SettingsScreen(
screenState = screenState,
hapticType = hapticType,
onBack = onBack,
onHapticPerformed = viewModel::onHapticPerformed,
onSettingsItemClicked = { key ->
@@ -88,25 +78,7 @@ fun SettingsRoute(
}
},
onSettingsItemLongClicked = viewModel::onSettingsItemLongClicked,
onSettingsItemValueChanged = { key, newValue ->
viewModel.onSettingsItemChanged(key, newValue)
when (key) {
SettingsKeys.KEY_APPEARANCE_DARK_THEME -> {
val newMode = newValue as? Int ?: 0
AppCompatDelegate.setDefaultNightMode(newMode)
val isUsing = context.getSystemService<PowerManager>()?.let { manager ->
isUsingDarkMode(
context.resources,
manager
)
} ?: false
userSettings.useDarkThemeChanged(isUsing)
}
}
}
onSettingsItemValueChanged = viewModel::onSettingsItemChanged
)
HandlePopups(
@@ -128,6 +100,7 @@ fun SettingsRoute(
@Composable
fun SettingsScreen(
screenState: SettingsScreenState = SettingsScreenState.EMPTY,
hapticType: HapticType? = null,
onBack: () -> Unit = {},
onHapticPerformed: () -> Unit = {},
onSettingsItemClicked: (key: String) -> Unit = {},
@@ -135,7 +108,6 @@ fun SettingsScreen(
onSettingsItemValueChanged: (key: String, newValue: Any?) -> Unit = { _, _ -> }
) {
val view = LocalView.current
val hapticType = screenState.useHaptics
LaunchedEffect(hapticType) {
if (hapticType != null) {
@@ -144,7 +116,7 @@ fun SettingsScreen(
}
}
val currentTheme = LocalTheme.current
val themeConfig = LocalThemeConfig.current
val hazeState = remember { HazeState() }
@@ -164,12 +136,12 @@ fun SettingsScreen(
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.surface.copy(
alpha = if (currentTheme.usingBlur) 0f else 1f
alpha = if (themeConfig.enableBlur) 0f else 1f
)
),
modifier = Modifier
.then(
if (currentTheme.usingBlur) {
if (themeConfig.enableBlur) {
Modifier.hazeChild(
state = hazeState,
style = HazeMaterials.thick()
@@ -185,7 +157,7 @@ fun SettingsScreen(
LazyColumn(
modifier = Modifier
.then(
if (currentTheme.usingBlur) {
if (themeConfig.enableBlur) {
Modifier.haze(
state = hazeState,
style = HazeMaterials.thick()
@@ -32,7 +32,7 @@ import com.meloda.app.fast.ui.basic.LocalContentAlpha
import com.meloda.app.fast.ui.components.ActionInvokeDismiss
import com.meloda.app.fast.ui.components.MaterialDialog
import com.meloda.app.fast.ui.components.SelectionType
import com.meloda.app.fast.ui.theme.LocalTheme
import com.meloda.app.fast.ui.theme.LocalThemeConfig
import com.meloda.app.fast.ui.util.ImmutableList
import com.meloda.app.fast.ui.util.ImmutableList.Companion.toImmutableList
@@ -51,7 +51,7 @@ fun ListItem(
mutableStateOf(false)
}
val currentTheme = LocalTheme.current
val currentTheme = LocalThemeConfig.current
Row(
modifier = modifier
@@ -81,7 +81,7 @@ fun ListItem(
Text(
text = title,
style = MaterialTheme.typography.headlineSmall,
maxLines = if (currentTheme.isMultiline) Int.MAX_VALUE else 1,
maxLines = if (currentTheme.enableMultiline) Int.MAX_VALUE else 1,
overflow = TextOverflow.Ellipsis,
)
}
@@ -94,7 +94,7 @@ fun ListItem(
Text(
text = text,
style = MaterialTheme.typography.bodyMedium,
maxLines = if (currentTheme.isMultiline) Int.MAX_VALUE else 1,
maxLines = if (currentTheme.enableMultiline) Int.MAX_VALUE else 1,
overflow = TextOverflow.Ellipsis,
)
}
@@ -22,7 +22,7 @@ import androidx.compose.ui.unit.dp
import com.meloda.app.fast.settings.model.UiItem
import com.meloda.app.fast.ui.basic.ContentAlpha
import com.meloda.app.fast.ui.basic.LocalContentAlpha
import com.meloda.app.fast.ui.theme.LocalTheme
import com.meloda.app.fast.ui.theme.LocalThemeConfig
@OptIn(ExperimentalFoundationApi::class)
@Composable
@@ -35,7 +35,7 @@ fun SwitchItem(
) {
if (!item.isVisible) return
val currentTheme = LocalTheme.current
val currentTheme = LocalThemeConfig.current
Row(
modifier = modifier
@@ -65,7 +65,7 @@ fun SwitchItem(
Text(
text = title,
style = MaterialTheme.typography.headlineSmall,
maxLines = if (currentTheme.isMultiline) Int.MAX_VALUE else 1,
maxLines = if (currentTheme.enableMultiline) Int.MAX_VALUE else 1,
overflow = TextOverflow.Ellipsis,
)
}
@@ -78,7 +78,7 @@ fun SwitchItem(
Text(
text = text,
style = MaterialTheme.typography.bodyMedium,
maxLines = if (currentTheme.isMultiline) Int.MAX_VALUE else 1,
maxLines = if (currentTheme.enableMultiline) Int.MAX_VALUE else 1,
overflow = TextOverflow.Ellipsis,
)
}
@@ -38,7 +38,7 @@ import com.meloda.app.fast.ui.basic.ContentAlpha
import com.meloda.app.fast.ui.basic.LocalContentAlpha
import com.meloda.app.fast.ui.components.ActionInvokeDismiss
import com.meloda.app.fast.ui.components.MaterialDialog
import com.meloda.app.fast.ui.theme.LocalTheme
import com.meloda.app.fast.ui.theme.LocalThemeConfig
@OptIn(ExperimentalFoundationApi::class)
@Composable
@@ -51,7 +51,7 @@ fun TextFieldItem(
) {
if (!item.isVisible) return
val currentTheme = LocalTheme.current
val currentTheme = LocalThemeConfig.current
var showDialog by rememberSaveable {
mutableStateOf(false)
@@ -93,7 +93,7 @@ fun TextFieldItem(
Text(
text = title,
style = MaterialTheme.typography.headlineSmall,
maxLines = if (currentTheme.isMultiline) Int.MAX_VALUE else 1,
maxLines = if (currentTheme.enableMultiline) Int.MAX_VALUE else 1,
overflow = TextOverflow.Ellipsis,
)
}
@@ -106,7 +106,7 @@ fun TextFieldItem(
Text(
text = text,
style = MaterialTheme.typography.bodyMedium,
maxLines = if (currentTheme.isMultiline) Int.MAX_VALUE else 1,
maxLines = if (currentTheme.enableMultiline) Int.MAX_VALUE else 1,
overflow = TextOverflow.Ellipsis,
)
}
@@ -9,7 +9,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.meloda.app.fast.settings.model.UiItem
import com.meloda.app.fast.ui.theme.LocalTheme
import com.meloda.app.fast.ui.theme.LocalThemeConfig
@Composable
fun TitleItem(
@@ -18,7 +18,7 @@ fun TitleItem(
) {
if (!item.isVisible) return
val currentTheme = LocalTheme.current
val currentTheme = LocalThemeConfig.current
Text(
text = item.title,
@@ -32,7 +32,7 @@ fun TitleItem(
bottom = 4.dp
)
.animateContentSize(),
maxLines = if (currentTheme.isMultiline) Int.MAX_VALUE else 1,
maxLines = if (currentTheme.enableMultiline) Int.MAX_VALUE else 1,
overflow = TextOverflow.Ellipsis,
)
}
@@ -21,7 +21,7 @@ import androidx.compose.ui.unit.dp
import com.meloda.app.fast.settings.model.UiItem
import com.meloda.app.fast.ui.basic.ContentAlpha
import com.meloda.app.fast.ui.basic.LocalContentAlpha
import com.meloda.app.fast.ui.theme.LocalTheme
import com.meloda.app.fast.ui.theme.LocalThemeConfig
@OptIn(ExperimentalFoundationApi::class)
@Composable
@@ -33,7 +33,7 @@ fun TitleTextItem(
) {
if (!item.isVisible) return
val currentTheme = LocalTheme.current
val currentTheme = LocalThemeConfig.current
Row(
modifier = modifier
@@ -62,7 +62,7 @@ fun TitleTextItem(
Text(
text = title,
style = MaterialTheme.typography.headlineSmall,
maxLines = if (currentTheme.isMultiline) Int.MAX_VALUE else 1,
maxLines = if (currentTheme.enableMultiline) Int.MAX_VALUE else 1,
overflow = TextOverflow.Ellipsis,
)
}
@@ -75,7 +75,7 @@ fun TitleTextItem(
Text(
text = text,
style = MaterialTheme.typography.bodyMedium,
maxLines = if (currentTheme.isMultiline) Int.MAX_VALUE else 1,
maxLines = if (currentTheme.enableMultiline) Int.MAX_VALUE else 1,
overflow = TextOverflow.Ellipsis,
)
}