Add theme option to disable animations and fix account avatar loading in bottom bar

This commit is contained in:
2025-03-28 19:59:38 +03:00
parent 9aa85d40c6
commit da9644cde1
14 changed files with 104 additions and 84 deletions
@@ -24,6 +24,7 @@ import dev.meloda.fast.datastore.UserSettings
import dev.meloda.fast.domain.GetCurrentAccountUseCase import dev.meloda.fast.domain.GetCurrentAccountUseCase
import dev.meloda.fast.domain.LoadUserByIdUseCase import dev.meloda.fast.domain.LoadUserByIdUseCase
import dev.meloda.fast.model.BaseError import dev.meloda.fast.model.BaseError
import dev.meloda.fast.model.api.domain.VkUser
import dev.meloda.fast.navigation.Main import dev.meloda.fast.navigation.Main
import dev.meloda.fast.settings.navigation.Settings import dev.meloda.fast.settings.navigation.Settings
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -36,14 +37,13 @@ interface MainViewModel {
val startDestination: StateFlow<Any?> val startDestination: StateFlow<Any?>
val isNeedToReplaceWithAuth: StateFlow<Boolean> val isNeedToReplaceWithAuth: StateFlow<Boolean>
val currentUser: StateFlow<VkUser?>
val isNeedToShowNotificationsDeniedDialog: StateFlow<Boolean> val isNeedToShowNotificationsDeniedDialog: StateFlow<Boolean>
val isNeedToShowNotificationsRationaleDialog: StateFlow<Boolean> val isNeedToShowNotificationsRationaleDialog: StateFlow<Boolean>
val isNeedToCheckNotificationsPermission: StateFlow<Boolean> val isNeedToCheckNotificationsPermission: StateFlow<Boolean>
val isNeedToRequestNotifications: StateFlow<Boolean> val isNeedToRequestNotifications: StateFlow<Boolean>
val profileImageUrl: StateFlow<String?>
fun onError(error: BaseError) fun onError(error: BaseError)
fun onNavigatedToAuth() fun onNavigatedToAuth()
@@ -59,6 +59,8 @@ interface MainViewModel {
fun onNotificationsDeniedDialogDismissed() fun onNotificationsDeniedDialogDismissed()
fun onNotificationsRationaleDialogDismissed() fun onNotificationsRationaleDialogDismissed()
fun onNotificationsRationaleDialogCancelClicked() fun onNotificationsRationaleDialogCancelClicked()
fun onUserAuthenticated()
} }
class MainViewModelImpl( class MainViewModelImpl(
@@ -70,14 +72,13 @@ class MainViewModelImpl(
override val startDestination = MutableStateFlow<Any?>(null) override val startDestination = MutableStateFlow<Any?>(null)
override val isNeedToReplaceWithAuth = MutableStateFlow(false) override val isNeedToReplaceWithAuth = MutableStateFlow(false)
override val currentUser = MutableStateFlow<VkUser?>(null)
override val isNeedToShowNotificationsDeniedDialog = MutableStateFlow(false) override val isNeedToShowNotificationsDeniedDialog = MutableStateFlow(false)
override val isNeedToShowNotificationsRationaleDialog = MutableStateFlow(false) override val isNeedToShowNotificationsRationaleDialog = MutableStateFlow(false)
override val isNeedToCheckNotificationsPermission = MutableStateFlow(false) override val isNeedToCheckNotificationsPermission = MutableStateFlow(false)
override val isNeedToRequestNotifications = MutableStateFlow(false) override val isNeedToRequestNotifications = MutableStateFlow(false)
override val profileImageUrl = MutableStateFlow<String?>(null)
private var openNotificationsSettings = false private var openNotificationsSettings = false
private var openAppSettings = false private var openAppSettings = false
@@ -170,17 +171,20 @@ class MainViewModelImpl(
disableBackgroundLongPoll() disableBackgroundLongPoll()
} }
override fun onUserAuthenticated() {
loadProfile()
}
private fun loadProfile() { private fun loadProfile() {
loadUserByIdUseCase(userId = null) loadUserByIdUseCase(userId = null)
.listenValue(viewModelScope) { state -> .listenValue(viewModelScope) { state ->
state.processState( state.processState(
error = { error -> error = { error ->
profileImageUrl.emit(null) currentUser.emit(null)
}, },
success = { response -> success = { response ->
val user = response ?: return@listenValue val user = response ?: return@listenValue
currentUser.emit(user)
profileImageUrl.emit(user.photo100)
} }
) )
} }
@@ -2,7 +2,6 @@ package dev.meloda.fast.navigation
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import dev.meloda.fast.MainViewModel
import dev.meloda.fast.conversations.navigation.Conversations import dev.meloda.fast.conversations.navigation.Conversations
import dev.meloda.fast.friends.navigation.Friends import dev.meloda.fast.friends.navigation.Friends
import dev.meloda.fast.model.BaseError import dev.meloda.fast.model.BaseError
@@ -25,8 +24,7 @@ fun NavGraphBuilder.mainScreen(
onConversationClicked: (conversationId: Int) -> Unit, onConversationClicked: (conversationId: Int) -> Unit,
onPhotoClicked: (url: String) -> Unit, onPhotoClicked: (url: String) -> Unit,
onMessageClicked: (userId: Int) -> Unit, onMessageClicked: (userId: Int) -> Unit,
onCreateChatClicked: () -> Unit, onCreateChatClicked: () -> Unit
viewModel: MainViewModel
) { ) {
val navigationItems = ImmutableList.of( val navigationItems = ImmutableList.of(
BottomNavigationItem( BottomNavigationItem(
@@ -57,8 +55,7 @@ fun NavGraphBuilder.mainScreen(
onConversationItemClicked = onConversationClicked, onConversationItemClicked = onConversationClicked,
onPhotoClicked = onPhotoClicked, onPhotoClicked = onPhotoClicked,
onMessageClicked = onMessageClicked, onMessageClicked = onMessageClicked,
onCreateChatClicked = onCreateChatClicked, onCreateChatClicked = onCreateChatClicked
viewModel = viewModel
) )
} }
} }
@@ -38,6 +38,7 @@ import dev.meloda.fast.common.LongPollController
import dev.meloda.fast.common.model.LongPollState import dev.meloda.fast.common.model.LongPollState
import dev.meloda.fast.datastore.AppSettings import dev.meloda.fast.datastore.AppSettings
import dev.meloda.fast.datastore.UserSettings import dev.meloda.fast.datastore.UserSettings
import dev.meloda.fast.model.api.domain.VkUser
import dev.meloda.fast.service.OnlineService import dev.meloda.fast.service.OnlineService
import dev.meloda.fast.service.longpolling.LongPollingService import dev.meloda.fast.service.longpolling.LongPollingService
import dev.meloda.fast.ui.model.DeviceSize import dev.meloda.fast.ui.model.DeviceSize
@@ -46,6 +47,7 @@ import dev.meloda.fast.ui.model.ThemeConfig
import dev.meloda.fast.ui.theme.AppTheme import dev.meloda.fast.ui.theme.AppTheme
import dev.meloda.fast.ui.theme.LocalSizeConfig import dev.meloda.fast.ui.theme.LocalSizeConfig
import dev.meloda.fast.ui.theme.LocalThemeConfig import dev.meloda.fast.ui.theme.LocalThemeConfig
import dev.meloda.fast.ui.theme.LocalUser
import dev.meloda.fast.ui.util.isNeedToEnableDarkMode import dev.meloda.fast.ui.util.isNeedToEnableDarkMode
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
import org.koin.compose.KoinContext import org.koin.compose.KoinContext
@@ -98,6 +100,8 @@ class MainActivity : AppCompatActivity() {
val viewModel: MainViewModel = koinViewModel<MainViewModelImpl>() val viewModel: MainViewModel = koinViewModel<MainViewModelImpl>()
val currentUser: VkUser? by viewModel.currentUser.collectAsStateWithLifecycle()
LifecycleResumeEffect(true) { LifecycleResumeEffect(true) {
viewModel.onAppResumed(intent) viewModel.onAppResumed(intent)
onPauseOrDispose {} onPauseOrDispose {}
@@ -202,6 +206,7 @@ class MainActivity : AppCompatActivity() {
val enableBlur by userSettings.useBlur.collectAsStateWithLifecycle() val enableBlur by userSettings.useBlur.collectAsStateWithLifecycle()
val enableMultiline by userSettings.enableMultiline.collectAsStateWithLifecycle() val enableMultiline by userSettings.enableMultiline.collectAsStateWithLifecycle()
val useSystemFont by userSettings.useSystemFont.collectAsStateWithLifecycle() val useSystemFont by userSettings.useSystemFont.collectAsStateWithLifecycle()
val enableAnimations by userSettings.enableAnimations.collectAsStateWithLifecycle()
val setDarkMode = isNeedToEnableDarkMode(darkMode = darkMode) val setDarkMode = isNeedToEnableDarkMode(darkMode = darkMode)
@@ -214,7 +219,7 @@ class MainActivity : AppCompatActivity() {
setDarkMode, setDarkMode,
useSystemFont useSystemFont
) { ) {
mutableStateOf( derivedStateOf {
ThemeConfig( ThemeConfig(
darkMode = setDarkMode, darkMode = setDarkMode,
dynamicColors = dynamicColors, dynamicColors = dynamicColors,
@@ -222,14 +227,16 @@ class MainActivity : AppCompatActivity() {
amoledDark = amoledDark, amoledDark = amoledDark,
enableBlur = enableBlur, enableBlur = enableBlur,
enableMultiline = enableMultiline, enableMultiline = enableMultiline,
useSystemFont = useSystemFont useSystemFont = useSystemFont,
) enableAnimations = enableAnimations
) )
} }
}
CompositionLocalProvider( CompositionLocalProvider(
LocalThemeConfig provides themeConfig, LocalThemeConfig provides themeConfig,
LocalSizeConfig provides sizeConfig LocalSizeConfig provides sizeConfig,
LocalUser provides currentUser
) { ) {
AppTheme( AppTheme(
useDarkTheme = themeConfig.darkMode, useDarkTheme = themeConfig.darkMode,
@@ -16,6 +16,7 @@ import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -27,7 +28,6 @@ import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.navigation import androidx.navigation.compose.navigation
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
@@ -36,7 +36,6 @@ import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.hazeEffect import dev.chrisbanes.haze.hazeEffect
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import dev.chrisbanes.haze.materials.HazeMaterials import dev.chrisbanes.haze.materials.HazeMaterials
import dev.meloda.fast.MainViewModel
import dev.meloda.fast.conversations.navigation.conversationsScreen import dev.meloda.fast.conversations.navigation.conversationsScreen
import dev.meloda.fast.friends.navigation.friendsScreen import dev.meloda.fast.friends.navigation.friendsScreen
import dev.meloda.fast.model.BaseError import dev.meloda.fast.model.BaseError
@@ -45,6 +44,7 @@ import dev.meloda.fast.navigation.MainGraph
import dev.meloda.fast.profile.navigation.profileScreen import dev.meloda.fast.profile.navigation.profileScreen
import dev.meloda.fast.ui.theme.LocalHazeState import dev.meloda.fast.ui.theme.LocalHazeState
import dev.meloda.fast.ui.theme.LocalThemeConfig import dev.meloda.fast.ui.theme.LocalThemeConfig
import dev.meloda.fast.ui.theme.LocalUser
import dev.meloda.fast.ui.util.ImmutableList import dev.meloda.fast.ui.util.ImmutableList
@OptIn(ExperimentalHazeMaterialsApi::class) @OptIn(ExperimentalHazeMaterialsApi::class)
@@ -56,19 +56,21 @@ fun MainScreen(
onConversationItemClicked: (conversationId: Int) -> Unit = {}, onConversationItemClicked: (conversationId: Int) -> Unit = {},
onPhotoClicked: (url: String) -> Unit = {}, onPhotoClicked: (url: String) -> Unit = {},
onMessageClicked: (userId: Int) -> Unit = {}, onMessageClicked: (userId: Int) -> Unit = {},
onCreateChatClicked: () -> Unit = {}, onCreateChatClicked: () -> Unit = {}
viewModel: MainViewModel
) { ) {
val currentTheme = LocalThemeConfig.current val currentTheme = LocalThemeConfig.current
val hazeState = remember { HazeState() } val hazeState = remember { HazeState() }
val navController = rememberNavController() val navController = rememberNavController()
val profileImageUrl by viewModel.profileImageUrl.collectAsStateWithLifecycle()
var selectedItemIndex by rememberSaveable { var selectedItemIndex by rememberSaveable {
mutableIntStateOf(1) mutableIntStateOf(1)
} }
val user = LocalUser.current
val profileImageUrl by remember(user) {
derivedStateOf { user?.photo100 }
}
Scaffold( Scaffold(
bottomBar = { bottomBar = {
NavigationBar( NavigationBar(
@@ -122,9 +124,7 @@ fun MainScreen(
.size(24.dp) .size(24.dp)
.clip(CircleShape) .clip(CircleShape)
.alpha(if (isLoading) 0f else 1f), .alpha(if (isLoading) 0f else 1f),
onSuccess = { onSuccess = { isLoading = false }
isLoading = false
}
) )
} else { } else {
Icon( Icon(
@@ -118,7 +118,10 @@ fun RootScreen(
exitTransition = { fadeOut(animationSpec = tween(200)) } exitTransition = { fadeOut(animationSpec = tween(200)) }
) { ) {
authNavGraph( authNavGraph(
onNavigateToMain = navController::navigateToMain, onNavigateToMain = {
viewModel.onUserAuthenticated()
navController.navigateToMain()
},
navController = navController navController = navController
) )
mainScreen( mainScreen(
@@ -127,8 +130,7 @@ fun RootScreen(
onConversationClicked = navController::navigateToMessagesHistory, onConversationClicked = navController::navigateToMessagesHistory,
onPhotoClicked = { url -> navController.navigateToPhotoView(listOf(url)) }, onPhotoClicked = { url -> navController.navigateToPhotoView(listOf(url)) },
onMessageClicked = navController::navigateToMessagesHistory, onMessageClicked = navController::navigateToMessagesHistory,
onCreateChatClicked = navController::navigateToCreateChat, onCreateChatClicked = navController::navigateToCreateChat
viewModel = viewModel
) )
messagesHistoryScreen( messagesHistoryScreen(
@@ -24,6 +24,7 @@ interface UserSettings {
val showEmojiButton: StateFlow<Boolean> val showEmojiButton: StateFlow<Boolean>
val showTimeInActionMessages: StateFlow<Boolean> val showTimeInActionMessages: StateFlow<Boolean>
val useSystemFont: StateFlow<Boolean> val useSystemFont: StateFlow<Boolean>
val enableAnimations: StateFlow<Boolean>
val showDebugCategory: StateFlow<Boolean> val showDebugCategory: StateFlow<Boolean>
fun onUseContactNamesChanged(use: Boolean) fun onUseContactNamesChanged(use: Boolean)
@@ -68,6 +69,7 @@ class UserSettingsImpl : UserSettings {
override val showTimeInActionMessages = override val showTimeInActionMessages =
MutableStateFlow(AppSettings.Experimental.showTimeInActionMessages) MutableStateFlow(AppSettings.Experimental.showTimeInActionMessages)
override val useSystemFont = MutableStateFlow(AppSettings.Appearance.useSystemFont) override val useSystemFont = MutableStateFlow(AppSettings.Appearance.useSystemFont)
override val enableAnimations = MutableStateFlow(AppSettings.Experimental.moreAnimations)
override val showDebugCategory = MutableStateFlow(AppSettings.Debug.showDebugCategory) override val showDebugCategory = MutableStateFlow(AppSettings.Debug.showDebugCategory)
override fun onUseContactNamesChanged(use: Boolean) { override fun onUseContactNamesChanged(use: Boolean) {
@@ -7,5 +7,6 @@ data class ThemeConfig(
val amoledDark: Boolean, val amoledDark: Boolean,
val enableBlur: Boolean, val enableBlur: Boolean,
val enableMultiline: Boolean, val enableMultiline: Boolean,
val useSystemFont: Boolean val useSystemFont: Boolean,
val enableAnimations: Boolean
) )
@@ -21,6 +21,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.HazeState
import dev.meloda.fast.model.api.domain.VkUser
import dev.meloda.fast.ui.R import dev.meloda.fast.ui.R
import dev.meloda.fast.ui.model.DeviceSize import dev.meloda.fast.ui.model.DeviceSize
import dev.meloda.fast.ui.model.SizeConfig import dev.meloda.fast.ui.model.SizeConfig
@@ -113,7 +114,8 @@ val LocalThemeConfig = compositionLocalOf {
amoledDark = false, amoledDark = false,
enableBlur = false, enableBlur = false,
enableMultiline = false, enableMultiline = false,
useSystemFont = false useSystemFont = false,
enableAnimations = false
) )
} }
@@ -124,13 +126,9 @@ val LocalSizeConfig = compositionLocalOf {
) )
} }
val LocalHazeState = compositionLocalOf { val LocalHazeState = compositionLocalOf { HazeState() }
HazeState() val LocalBottomPadding = compositionLocalOf { 0.dp }
} val LocalUser = compositionLocalOf<VkUser?> { null }
val LocalBottomPadding = compositionLocalOf {
0.dp
}
@Composable @Composable
fun AppTheme( fun AppTheme(
@@ -26,7 +26,7 @@ import dev.meloda.fast.conversations.model.ConversationsScreenState
import dev.meloda.fast.data.UserConfig import dev.meloda.fast.data.UserConfig
import dev.meloda.fast.ui.model.api.ConversationOption import dev.meloda.fast.ui.model.api.ConversationOption
import dev.meloda.fast.ui.model.api.UiConversation import dev.meloda.fast.ui.model.api.UiConversation
import dev.meloda.fast.ui.theme.LocalBottomPadding import dev.meloda.fast.ui.theme.LocalThemeConfig
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -41,6 +41,7 @@ fun ConversationsList(
onOptionClicked: (UiConversation, ConversationOption) -> Unit, onOptionClicked: (UiConversation, ConversationOption) -> Unit,
padding: PaddingValues padding: PaddingValues
) { ) {
val theme = LocalThemeConfig.current
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
LazyColumn( LazyColumn(
@@ -68,7 +69,12 @@ fun ConversationsList(
maxLines = maxLines, maxLines = maxLines,
isUserAccount = isUserAccount, isUserAccount = isUserAccount,
conversation = conversation, conversation = conversation,
modifier = Modifier.animateItem(fadeInSpec = null, fadeOutSpec = null) modifier =
if (theme.enableAnimations) Modifier.animateItem(
fadeInSpec = null,
fadeOutSpec = null
)
else Modifier
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
@@ -78,7 +84,14 @@ fun ConversationsList(
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.animateItem(fadeInSpec = null, fadeOutSpec = null), .then(
if (theme.enableAnimations)
Modifier.animateItem(
fadeInSpec = null,
fadeOutSpec = null
)
else Modifier
),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
if (screenState.isPaginating) { if (screenState.isPaginating) {
@@ -5,10 +5,6 @@ import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.animateIntAsState import androidx.compose.animation.core.animateIntAsState
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideIn
import androidx.compose.animation.slideOut
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
@@ -283,11 +279,6 @@ fun ConversationsScreen(
) )
Column { Column {
// AnimatedVisibility(
// visible = listState.isScrollingUp(),
// enter = slideIn { IntOffset(0, 600) } + fadeIn(tween(200)),
// exit = slideOut { IntOffset(0, 600) } + fadeOut(tween(200))
// ) {
FloatingActionButton( FloatingActionButton(
onClick = onCreateChatButtonClicked, onClick = onCreateChatButtonClicked,
modifier = Modifier.offset { modifier = Modifier.offset {
@@ -299,7 +290,6 @@ fun ConversationsScreen(
contentDescription = "Add chat button" contentDescription = "Add chat button"
) )
} }
// }
Spacer(modifier = Modifier.height(LocalBottomPadding.current)) Spacer(modifier = Modifier.height(LocalBottomPadding.current))
} }
@@ -27,14 +27,21 @@ import androidx.compose.ui.unit.dp
import coil.compose.rememberAsyncImagePainter import coil.compose.rememberAsyncImagePainter
import coil.imageLoader import coil.imageLoader
import dev.meloda.fast.messageshistory.model.UiItem import dev.meloda.fast.messageshistory.model.UiItem
import dev.meloda.fast.ui.theme.LocalThemeConfig
@Composable @Composable
fun IncomingMessageBubble( fun IncomingMessageBubble(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
message: UiItem.Message, message: UiItem.Message,
animate: Boolean,
) { ) {
Row(modifier = modifier.fillMaxWidth().then(if (animate) Modifier.animateContentSize() else Modifier),) { Row(
modifier = modifier
.fillMaxWidth()
.then(
if (LocalThemeConfig.current.enableAnimations) Modifier.animateContentSize()
else Modifier
),
) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth(0.85f) .fillMaxWidth(0.85f)
@@ -81,7 +88,6 @@ fun IncomingMessageBubble(
isOut = false, isOut = false,
date = message.date, date = message.date,
edited = message.isEdited, edited = message.isEdited,
animate = animate,
isRead = message.isRead, isRead = message.isRead,
sendingStatus = message.sendingStatus, sendingStatus = message.sendingStatus,
pinned = message.isPinned pinned = message.isPinned
@@ -30,6 +30,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import dev.meloda.fast.messageshistory.model.SendingStatus import dev.meloda.fast.messageshistory.model.SendingStatus
import dev.meloda.fast.ui.theme.LocalThemeConfig
import dev.meloda.fast.ui.R as UiR import dev.meloda.fast.ui.R as UiR
@Composable @Composable
@@ -39,11 +40,11 @@ fun MessageBubble(
isOut: Boolean, isOut: Boolean,
date: String?, date: String?,
edited: Boolean, edited: Boolean,
animate: Boolean,
isRead: Boolean, isRead: Boolean,
sendingStatus: SendingStatus, sendingStatus: SendingStatus,
pinned: Boolean pinned: Boolean
) { ) {
val theme = LocalThemeConfig.current
val backgroundColor = if (!isOut) { val backgroundColor = if (!isOut) {
MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp) MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)
} else { } else {
@@ -65,7 +66,7 @@ fun MessageBubble(
horizontal = 8.dp, horizontal = 8.dp,
vertical = 6.dp vertical = 6.dp
) )
.then(if (animate) Modifier.animateContentSize() else Modifier), .then(if (theme.enableAnimations) Modifier.animateContentSize() else Modifier),
) { ) {
val minDateContainerWidth = remember(edited, isOut) { val minDateContainerWidth = remember(edited, isOut) {
val mainPart = if (edited) 50.dp else 30.dp val mainPart = if (edited) 50.dp else 30.dp
@@ -89,7 +90,7 @@ fun MessageBubble(
.padding(end = 4.dp) .padding(end = 4.dp)
.padding(end = dateContainerWidth) .padding(end = dateContainerWidth)
.padding(end = 4.dp) .padding(end = 4.dp)
.then(if (animate) Modifier.animateContentSize() else Modifier), .then(if (theme.enableAnimations) Modifier.animateContentSize() else Modifier),
color = textColor color = textColor
) )
} }
@@ -98,7 +99,7 @@ fun MessageBubble(
modifier = Modifier modifier = Modifier
.align(Alignment.BottomEnd) .align(Alignment.BottomEnd)
.defaultMinSize(minWidth = dateContainerWidth) .defaultMinSize(minWidth = dateContainerWidth)
.then(if (animate) Modifier.animateContentSize() else Modifier), .then(if (theme.enableAnimations) Modifier.animateContentSize() else Modifier),
) { ) {
if (pinned) { if (pinned) {
Icon( Icon(
@@ -49,20 +49,17 @@ fun MessagesList(
onMessageClicked: (Int) -> Unit = {}, onMessageClicked: (Int) -> Unit = {},
onMessageLongClicked: (Int) -> Unit = {} onMessageLongClicked: (Int) -> Unit = {}
) { ) {
val enableAnimations = remember {
AppSettings.Experimental.moreAnimations
}
val messages = remember(immutableMessages) { val messages = remember(immutableMessages) {
immutableMessages.toList() immutableMessages.toList()
} }
val currentTheme = LocalThemeConfig.current val theme = LocalThemeConfig.current
val view = LocalView.current val view = LocalView.current
LazyColumn( LazyColumn(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.then( .then(
if (currentTheme.enableBlur) { if (theme.enableBlur) {
Modifier.hazeSource(state = hazeState) Modifier.hazeSource(state = hazeState)
} else Modifier } else Modifier
), ),
@@ -93,7 +90,7 @@ fun MessagesList(
is UiItem.ActionMessage -> { is UiItem.ActionMessage -> {
ActionMessageItem( ActionMessageItem(
modifier = Modifier.then( modifier = Modifier.then(
if (enableAnimations) Modifier.animateItem( if (theme.enableAnimations) Modifier.animateItem(
fadeInSpec = null, fadeInSpec = null,
fadeOutSpec = null fadeOutSpec = null
) else Modifier ) else Modifier
@@ -119,7 +116,7 @@ fun MessagesList(
Surface( Surface(
modifier = Modifier modifier = Modifier
.then( .then(
if (enableAnimations) Modifier.animateItem( if (theme.enableAnimations) Modifier.animateItem(
fadeInSpec = null, fadeInSpec = null,
fadeOutSpec = null fadeOutSpec = null
) else Modifier ) else Modifier
@@ -141,14 +138,13 @@ fun MessagesList(
Modifier Modifier
.padding(vertical = 4.dp) .padding(vertical = 4.dp)
.then( .then(
if (enableAnimations) Modifier.animateItem( if (theme.enableAnimations) Modifier.animateItem(
fadeInSpec = null, fadeInSpec = null,
fadeOutSpec = null fadeOutSpec = null
) )
else Modifier else Modifier
), ),
message = item, message = item
animate = enableAnimations
) )
} else { } else {
IncomingMessageBubble( IncomingMessageBubble(
@@ -156,14 +152,13 @@ fun MessagesList(
Modifier Modifier
.padding(vertical = 4.dp) .padding(vertical = 4.dp)
.then( .then(
if (enableAnimations) Modifier.animateItem( if (theme.enableAnimations) Modifier.animateItem(
fadeInSpec = null, fadeInSpec = null,
fadeOutSpec = null fadeOutSpec = null
) )
else Modifier else Modifier
), ),
message = item, message = item
animate = enableAnimations
) )
} }
} }
@@ -12,15 +12,20 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import dev.meloda.fast.common.extensions.orDots import dev.meloda.fast.common.extensions.orDots
import dev.meloda.fast.messageshistory.model.UiItem import dev.meloda.fast.messageshistory.model.UiItem
import dev.meloda.fast.ui.theme.LocalThemeConfig
@Composable @Composable
fun OutgoingMessageBubble( fun OutgoingMessageBubble(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
message: UiItem.Message, message: UiItem.Message,
animate: Boolean
) { ) {
Row( Row(
modifier = modifier.fillMaxWidth().then(if (animate) Modifier.animateContentSize() else Modifier), modifier = modifier
.fillMaxWidth()
.then(
if (LocalThemeConfig.current.enableAnimations) Modifier.animateContentSize()
else Modifier
),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.End horizontalArrangement = Arrangement.End
) { ) {
@@ -37,7 +42,6 @@ fun OutgoingMessageBubble(
isOut = true, isOut = true,
date = message.date, date = message.date,
edited = message.isEdited, edited = message.isEdited,
animate = animate,
isRead = message.isRead, isRead = message.isRead,
sendingStatus = message.sendingStatus, sendingStatus = message.sendingStatus,
pinned = message.isPinned pinned = message.isPinned