From 8cb3ed87846ba98bb75e6298331a3199fec58294 Mon Sep 17 00:00:00 2001 From: Danil Nikolaev Date: Wed, 27 Aug 2025 05:17:40 +0300 Subject: [PATCH] some improvements, new feature --- .../fast/common/extensions/Extensions.kt | 21 ++++++- .../dev/meloda/fast/datastore/AppSettings.kt | 7 +++ .../dev/meloda/fast/datastore/SettingsKeys.kt | 4 +- .../presentation/ConversationsScreen.kt | 53 ++++++++++------- .../friends/presentation/RootFriendsScreen.kt | 22 +++---- .../MessagesHistoryViewModel.kt | 5 +- .../presentation/MessagesHistoryTopBar.kt | 57 +++++++++++-------- .../meloda/fast/settings/SettingsViewModel.kt | 9 ++- 8 files changed, 114 insertions(+), 64 deletions(-) diff --git a/core/common/src/main/kotlin/dev/meloda/fast/common/extensions/Extensions.kt b/core/common/src/main/kotlin/dev/meloda/fast/common/extensions/Extensions.kt index 339822c9..d7cb5cdc 100644 --- a/core/common/src/main/kotlin/dev/meloda/fast/common/extensions/Extensions.kt +++ b/core/common/src/main/kotlin/dev/meloda/fast/common/extensions/Extensions.kt @@ -1,5 +1,7 @@ package dev.meloda.fast.common.extensions +import android.os.Build +import android.os.Bundle import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.delay @@ -11,6 +13,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.update +import kotlin.reflect.KClass import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds @@ -103,7 +106,7 @@ fun Any.asInt(): Int { } fun Any.asLong(): Long { - return when(this) { + return when (this) { is Number -> this.toLong() else -> throw IllegalArgumentException("Object is not numeric") @@ -117,3 +120,19 @@ fun Any.toList(mapper: (old: Any) -> T): List { else -> emptyList() } } + +fun Bundle.getParcelableCompat(key: String, clazz: Class): T? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + getParcelable(key, clazz) + } else { + getParcelable(key) + } +} + +fun Bundle.getParcelableCompat(key: String, clazz: KClass): T? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + getParcelable(key, clazz.java) + } else { + getParcelable(key) + } +} diff --git a/core/datastore/src/main/kotlin/dev/meloda/fast/datastore/AppSettings.kt b/core/datastore/src/main/kotlin/dev/meloda/fast/datastore/AppSettings.kt index b48335a3..ab7f7a1d 100644 --- a/core/datastore/src/main/kotlin/dev/meloda/fast/datastore/AppSettings.kt +++ b/core/datastore/src/main/kotlin/dev/meloda/fast/datastore/AppSettings.kt @@ -103,6 +103,13 @@ object AppSettings { ) set(value) = put(SettingsKeys.KEY_SHOW_ATTACHMENT_BUTTON, value) + var showManualRefreshOptions: Boolean + get() = get( + SettingsKeys.KEY_SHOW_MANUAL_REFRESH_OPTIONS, + SettingsKeys.DEFAULT_VALUE_SHOW_MANUAL_REFRESH_OPTIONS + ) + set(value) = put(SettingsKeys.KEY_SHOW_MANUAL_REFRESH_OPTIONS, value) + var enableHaptic: Boolean get() = get( SettingsKeys.KEY_ENABLE_HAPTIC, diff --git a/core/datastore/src/main/kotlin/dev/meloda/fast/datastore/SettingsKeys.kt b/core/datastore/src/main/kotlin/dev/meloda/fast/datastore/SettingsKeys.kt index a111e5fe..259e6f76 100644 --- a/core/datastore/src/main/kotlin/dev/meloda/fast/datastore/SettingsKeys.kt +++ b/core/datastore/src/main/kotlin/dev/meloda/fast/datastore/SettingsKeys.kt @@ -13,8 +13,8 @@ object SettingsKeys { const val DEFAULT_VALUE_KEY_SHOW_EMOJI_BUTTON = false const val KEY_SHOW_ATTACHMENT_BUTTON = "show_attachment_button" const val DEFAULT_VALUE_SHOW_ATTACHMENT_BUTTON = false - const val KEY_SHOW_RECORD_VOICE_BUTTON = "show_record_voice_button" - const val DEFAULT_VALUE_SHOW_RECORD_VOICE_BUTTON = false + const val KEY_SHOW_MANUAL_REFRESH_OPTIONS = "show_manual_refresh_options" + const val DEFAULT_VALUE_SHOW_MANUAL_REFRESH_OPTIONS = false const val KEY_APPEARANCE = "appearance" const val KEY_APPEARANCE_MULTILINE = "appearance_multiline" diff --git a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/presentation/ConversationsScreen.kt b/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/presentation/ConversationsScreen.kt index 7d531dd6..3859ae73 100644 --- a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/presentation/ConversationsScreen.kt +++ b/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/presentation/ConversationsScreen.kt @@ -63,7 +63,9 @@ import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import dev.chrisbanes.haze.materials.HazeMaterials import dev.meloda.fast.conversations.model.ConversationsScreenState import dev.meloda.fast.conversations.navigation.ConversationsGraph +import dev.meloda.fast.datastore.AppSettings import dev.meloda.fast.model.BaseError +import dev.meloda.fast.ui.R import dev.meloda.fast.ui.components.FullScreenContainedLoader import dev.meloda.fast.ui.components.NoItemsView import dev.meloda.fast.ui.components.VkErrorView @@ -78,7 +80,6 @@ import dev.meloda.fast.ui.util.emptyImmutableList import dev.meloda.fast.ui.util.isScrollingUp import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.debounce -import dev.meloda.fast.ui.R @OptIn( ExperimentalMaterial3Api::class, @@ -215,11 +216,35 @@ fun ConversationsScreen( } } - IconButton(onClick = { dropDownMenuExpanded = true }) { - Icon( - imageVector = Icons.Rounded.MoreVert, - contentDescription = null - ) + val dropDownItems = mutableListOf<@Composable () -> Unit>() + + if (AppSettings.General.showManualRefreshOptions) { + dropDownItems += { + DropdownMenuItem( + onClick = { + onRefreshDropdownItemClicked() + dropDownMenuExpanded = false + }, + text = { + Text(text = stringResource(id = R.string.action_refresh)) + }, + leadingIcon = { + Icon( + imageVector = Icons.Rounded.Refresh, + contentDescription = null + ) + } + ) + } + } + + if (dropDownItems.isNotEmpty()) { + IconButton(onClick = { dropDownMenuExpanded = true }) { + Icon( + imageVector = Icons.Rounded.MoreVert, + contentDescription = null + ) + } } DropdownMenu( @@ -228,21 +253,7 @@ fun ConversationsScreen( onDismissRequest = { dropDownMenuExpanded = false }, offset = DpOffset(x = (-4).dp, y = (-60).dp) ) { - DropdownMenuItem( - onClick = { - onRefreshDropdownItemClicked() - dropDownMenuExpanded = false - }, - text = { - Text(text = stringResource(id = R.string.action_refresh)) - }, - leadingIcon = { - Icon( - imageVector = Icons.Rounded.Refresh, - contentDescription = null - ) - } - ) + dropDownItems.forEach { it.invoke() } } }, colors = TopAppBarDefaults.topAppBarColors( diff --git a/feature/friends/src/main/kotlin/dev/meloda/fast/friends/presentation/RootFriendsScreen.kt b/feature/friends/src/main/kotlin/dev/meloda/fast/friends/presentation/RootFriendsScreen.kt index 1090cfe7..0e4ae36e 100644 --- a/feature/friends/src/main/kotlin/dev/meloda/fast/friends/presentation/RootFriendsScreen.kt +++ b/feature/friends/src/main/kotlin/dev/meloda/fast/friends/presentation/RootFriendsScreen.kt @@ -31,6 +31,7 @@ import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -69,7 +70,7 @@ fun FriendsRoute( val currentTheme = LocalThemeConfig.current val hazeState = LocalHazeState.current - var canScrollBackward by remember { + var canScrollBackward by rememberSaveable { mutableStateOf(false) } @@ -115,35 +116,30 @@ fun FriendsRoute( derivedStateOf { pagerState.currentPage } } - var orderType: String by remember { mutableStateOf("hints") } - - var showOrderDialog by remember { mutableStateOf(false) } + var orderType: String by rememberSaveable { mutableStateOf("hints") } + var showOrderDialog by rememberSaveable { mutableStateOf(false) } val orderPriority = stringResource(R.string.friends_order_priority) val orderName = stringResource(R.string.friends_order_name) val orderRandom = stringResource(R.string.friends_order_random) - val orderMobile = stringResource(R.string.friends_order_mobile) - val orderSmart = stringResource(R.string.friends_order_smart) val orderTitleItems = remember { ImmutableList.of( orderPriority, orderName, orderRandom, - orderMobile, - orderSmart ) } val orderItems = remember { - listOf("hints", "name", "random", "mobile", "smart") - } - - var selectedIndex by remember { - mutableIntStateOf(0) + listOf("hints", "name", "random") } if (showOrderDialog) { + var selectedIndex by remember { + mutableIntStateOf(orderItems.indexOf(orderType)) + } + MaterialDialog( onDismissRequest = { showOrderDialog = false }, confirmText = stringResource(R.string.ok), diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModel.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModel.kt index 78dd6f1a..9c75c08c 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModel.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModel.kt @@ -25,6 +25,7 @@ import coil.imageLoader import coil.request.ImageRequest import com.conena.nanokt.collections.indexOfFirstOrNull import dev.meloda.fast.common.VkConstants +import dev.meloda.fast.common.extensions.getParcelableCompat import dev.meloda.fast.common.extensions.listenValue import dev.meloda.fast.common.extensions.orDots import dev.meloda.fast.common.extensions.removeIfCompat @@ -57,6 +58,7 @@ import dev.meloda.fast.model.api.domain.VkAttachment import dev.meloda.fast.model.api.domain.VkMessage import dev.meloda.fast.model.api.domain.VkPhotoDomain import dev.meloda.fast.network.VkErrorCode +import dev.meloda.fast.ui.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -67,7 +69,6 @@ import java.io.File import java.io.FileOutputStream import kotlin.math.abs import kotlin.random.Random -import dev.meloda.fast.ui.R interface MessagesHistoryViewModel { @@ -267,7 +268,7 @@ class MessagesHistoryViewModelImpl( override fun onDialogItemPicked(dialog: MessageDialog, bundle: Bundle) { when (dialog) { is MessageDialog.MessageOptions -> { - when (val option = bundle.getParcelable("option")) { + when (val option = bundle.getParcelableCompat("option", MessageOption::class)) { null -> Unit MessageOption.Retry -> { diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesHistoryTopBar.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesHistoryTopBar.kt index d2e3baa6..2b31f471 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesHistoryTopBar.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesHistoryTopBar.kt @@ -51,11 +51,10 @@ import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import dev.chrisbanes.haze.materials.HazeMaterials import dev.meloda.fast.common.model.UiImage import dev.meloda.fast.datastore.AppSettings +import dev.meloda.fast.ui.R import dev.meloda.fast.ui.theme.LocalThemeConfig import dev.meloda.fast.ui.util.getImage -import dev.meloda.fast.ui.R - @OptIn(ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class) @Composable fun MessagesHistoryTopBar( @@ -221,13 +220,37 @@ fun MessagesHistoryTopBar( ) } } else { - IconButton( - onClick = { dropDownMenuExpanded = true } - ) { - Icon( - imageVector = Icons.Outlined.MoreVert, - contentDescription = "Options" - ) + val dropDownItems = mutableListOf<@Composable () -> Unit>() + + if (AppSettings.General.showManualRefreshOptions) { + dropDownItems += { + DropdownMenuItem( + onClick = { + onRefresh() + dropDownMenuExpanded = false + }, + text = { + Text(text = stringResource(R.string.action_refresh)) + }, + leadingIcon = { + Icon( + imageVector = Icons.Rounded.Refresh, + contentDescription = null + ) + } + ) + } + } + + if (dropDownItems.isNotEmpty()) { + IconButton( + onClick = { dropDownMenuExpanded = true } + ) { + Icon( + imageVector = Icons.Outlined.MoreVert, + contentDescription = "Options" + ) + } } DropdownMenu( @@ -238,21 +261,7 @@ fun MessagesHistoryTopBar( }, offset = DpOffset(x = (-4).dp, y = (-60).dp) ) { - DropdownMenuItem( - onClick = { - onRefresh() - dropDownMenuExpanded = false - }, - text = { - Text(text = stringResource(R.string.action_refresh)) - }, - leadingIcon = { - Icon( - imageVector = Icons.Rounded.Refresh, - contentDescription = null - ) - } - ) + dropDownItems.forEach { it.invoke() } } } } diff --git a/feature/settings/src/main/kotlin/dev/meloda/fast/settings/SettingsViewModel.kt b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/SettingsViewModel.kt index 11cdeef1..fffff35b 100644 --- a/feature/settings/src/main/kotlin/dev/meloda/fast/settings/SettingsViewModel.kt +++ b/feature/settings/src/main/kotlin/dev/meloda/fast/settings/SettingsViewModel.kt @@ -30,6 +30,7 @@ import dev.meloda.fast.settings.model.SettingsDialog import dev.meloda.fast.settings.model.SettingsItem import dev.meloda.fast.settings.model.SettingsScreenState import dev.meloda.fast.settings.model.TextProvider +import dev.meloda.fast.ui.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -37,7 +38,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import dev.meloda.fast.ui.R class SettingsViewModel( private val loadUserByIdUseCase: LoadUserByIdUseCase, @@ -351,6 +351,12 @@ class SettingsViewModel( text = UiText.Resource(R.string.settings_general_show_attachment_button_summary), defaultValue = SettingsKeys.DEFAULT_VALUE_SHOW_ATTACHMENT_BUTTON ) + val generalShowManualRefreshOptions = SettingsItem.Switch( + key = SettingsKeys.KEY_SHOW_MANUAL_REFRESH_OPTIONS, + defaultValue = SettingsKeys.DEFAULT_VALUE_SHOW_MANUAL_REFRESH_OPTIONS, + title = UiText.Simple("Refresh options"), + text = UiText.Simple("Show manual refresh options in some screens") + ) val generalEnableHaptic = SettingsItem.Switch( key = SettingsKeys.KEY_ENABLE_HAPTIC, defaultValue = SettingsKeys.DEFAULT_ENABLE_HAPTIC, @@ -538,6 +544,7 @@ class SettingsViewModel( generalUseContactNames, generalShowEmojiButton, generalShowAttachmentButton, + generalShowManualRefreshOptions, generalEnableHaptic ) val appearanceList = listOf(