improve ui in messages history screen

This commit is contained in:
2024-07-16 02:50:58 +03:00
parent b252c03be7
commit eb34c0c1cb
6 changed files with 129 additions and 86 deletions
@@ -1,5 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp"> <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="@android:color/white" android:pathData="M16.5,6.75v10.58c0,2.09 -1.53,3.95 -3.61,4.15 -2.39,0.23 -4.39,-1.64 -4.39,-3.98V5.14c0,-1.31 0.94,-2.5 2.24,-2.63 1.5,-0.15 2.76,1.02 2.76,2.49v10.5c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1V6.75c0,-0.41 -0.34,-0.75 -0.75,-0.75s-0.75,0.34 -0.75,0.75v8.61c0,1.31 0.94,2.5 2.24,2.63 1.5,0.15 2.76,-1.02 2.76,-2.49V5.17c0,-2.09 -1.53,-3.95 -3.61,-4.15C9.01,0.79 7,2.66 7,5v12.27c0,2.87 2.1,5.44 4.96,5.71 3.29,0.3 6.04,-2.26 6.04,-5.48V6.75c0,-0.41 -0.34,-0.75 -0.75,-0.75s-0.75,0.34 -0.75,0.75z"/> <path
android:fillColor="@android:color/white"
android:pathData="M16.5,6.75v10.58c0,2.09 -1.53,3.95 -3.61,4.15 -2.39,0.23 -4.39,-1.64 -4.39,-3.98V5.14c0,-1.31 0.94,-2.5 2.24,-2.63 1.5,-0.15 2.76,1.02 2.76,2.49v10.5c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1V6.75c0,-0.41 -0.34,-0.75 -0.75,-0.75s-0.75,0.34 -0.75,0.75v8.61c0,1.31 0.94,2.5 2.24,2.63 1.5,0.15 2.76,-1.02 2.76,-2.49V5.17c0,-2.09 -1.53,-3.95 -3.61,-4.15C9.01,0.79 7,2.66 7,5v12.27c0,2.87 2.1,5.44 4.96,5.71 3.29,0.3 6.04,-2.26 6.04,-5.48V6.75c0,-0.41 -0.34,-0.75 -0.75,-0.75s-0.75,0.34 -0.75,0.75z" />
</vector> </vector>
@@ -223,7 +223,7 @@ class LoginViewModelImpl(
val accessToken = response.accessToken val accessToken = response.accessToken
if (userId == null || accessToken == null) { if (userId == null || accessToken == null) {
// TODO: 11/04/2024, Danil Nikolaev: send unknown error event loginError.update { LoginError.Unknown }
return@processState return@processState
} }
@@ -161,15 +161,13 @@ fun LoginScreen(
val focusManager = LocalFocusManager.current val focusManager = LocalFocusManager.current
val (loginFocusable, passwordFocusable) = FocusRequester.createRefs() val (loginFocusable, passwordFocusable) = FocusRequester.createRefs()
// TODO: 13/07/2024, Danil Nikolaev: remove
var loginText by remember { mutableStateOf(TextFieldValue(screenState.login)) } var loginText by remember { mutableStateOf(TextFieldValue(screenState.login)) }
val showLoginError = screenState.loginError val showLoginError = screenState.loginError
val autoFillEmailHandler = autoFillRequestHandler( val autoFillEmailHandler = autoFillRequestHandler(
autofillTypes = listOf(AutofillType.EmailAddress), autofillTypes = listOf(AutofillType.EmailAddress),
onFill = { value -> onFill = { value ->
loginText = loginText = TextFieldValue(text = value, selection = TextRange(value.length))
TextFieldValue(text = value, selection = TextRange(value.length))
onLoginAutoFilled(value) onLoginAutoFilled(value)
} }
) )
@@ -180,8 +178,7 @@ fun LoginScreen(
val autoFillPasswordHandler = autoFillRequestHandler( val autoFillPasswordHandler = autoFillRequestHandler(
autofillTypes = listOf(AutofillType.Password), autofillTypes = listOf(AutofillType.Password),
onFill = { value -> onFill = { value ->
passwordText = passwordText = TextFieldValue(text = value, selection = TextRange(value.length))
TextFieldValue(text = value, selection = TextRange(value.length))
onPasswordAutoFilled(value) onPasswordAutoFilled(value)
} }
) )
@@ -204,7 +204,7 @@ fun ValidationScreen(
if (newText.text.length > 6) return@TextField if (newText.text.length > 6) return@TextField
code = newText code = newText
onCodeInputChanged((newText.text)) onCodeInputChanged(newText.text)
}, },
label = { Text(text = "Code") }, label = { Text(text = "Code") },
placeholder = { Text(text = "Code") }, placeholder = { Text(text = "Code") },
@@ -1,10 +1,12 @@
package com.meloda.app.fast.messageshistory.presentation package com.meloda.app.fast.messageshistory.presentation
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.ExperimentalLayoutApi
@@ -16,6 +18,7 @@ import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imeNestedScroll import androidx.compose.foundation.layout.imeNestedScroll
import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.navigationBarsPadding
@@ -57,9 +60,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.LayoutDirection
@@ -67,6 +73,7 @@ import androidx.compose.ui.unit.dp
import androidx.core.view.HapticFeedbackConstantsCompat import androidx.core.view.HapticFeedbackConstantsCompat
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.meloda.app.fast.datastore.SettingsKeys import com.meloda.app.fast.datastore.SettingsKeys
import com.meloda.app.fast.datastore.UserSettings
import com.meloda.app.fast.messageshistory.MessagesHistoryViewModel import com.meloda.app.fast.messageshistory.MessagesHistoryViewModel
import com.meloda.app.fast.messageshistory.MessagesHistoryViewModelImpl import com.meloda.app.fast.messageshistory.MessagesHistoryViewModelImpl
import com.meloda.app.fast.messageshistory.model.ActionMode import com.meloda.app.fast.messageshistory.model.ActionMode
@@ -94,10 +101,14 @@ fun MessagesHistoryRoute(
val baseError by viewModel.baseError.collectAsStateWithLifecycle() val baseError by viewModel.baseError.collectAsStateWithLifecycle()
val canPaginate by viewModel.canPaginate.collectAsStateWithLifecycle() val canPaginate by viewModel.canPaginate.collectAsStateWithLifecycle()
val userSettings: UserSettings = koinInject()
val showEmojiButton by userSettings.showEmojiButton.collectAsStateWithLifecycle()
MessagesHistoryScreen( MessagesHistoryScreen(
screenState = screenState, screenState = screenState,
baseError = baseError, baseError = baseError,
canPaginate = canPaginate, canPaginate = canPaginate,
showEmojiButton = showEmojiButton,
onBack = onBack, onBack = onBack,
onChatMaterialsDropdownItemClicked = onChatMaterialsDropdownItemClicked, onChatMaterialsDropdownItemClicked = onChatMaterialsDropdownItemClicked,
onRefreshDropdownItemClicked = viewModel::onRefresh, onRefreshDropdownItemClicked = viewModel::onRefresh,
@@ -119,6 +130,7 @@ fun MessagesHistoryScreen(
screenState: MessagesHistoryScreenState = MessagesHistoryScreenState.EMPTY, screenState: MessagesHistoryScreenState = MessagesHistoryScreenState.EMPTY,
baseError: BaseError? = null, baseError: BaseError? = null,
canPaginate: Boolean = false, canPaginate: Boolean = false,
showEmojiButton: Boolean = false,
onBack: () -> Unit = {}, onBack: () -> Unit = {},
onChatMaterialsDropdownItemClicked: (peerId: Int, conversationMessageId: Int) -> Unit = { _, _ -> }, onChatMaterialsDropdownItemClicked: (peerId: Int, conversationMessageId: Int) -> Unit = { _, _ -> },
onRefreshDropdownItemClicked: () -> Unit = {}, onRefreshDropdownItemClicked: () -> Unit = {},
@@ -170,6 +182,12 @@ fun MessagesHistoryScreen(
animationSpec = tween(durationMillis = 50) animationSpec = tween(durationMillis = 50)
) )
var messageBarHeight by remember {
mutableStateOf(0.dp)
}
val density = LocalDensity.current
Scaffold( Scaffold(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentWindowInsets = WindowInsets.statusBars, contentWindowInsets = WindowInsets.statusBars,
@@ -294,7 +312,8 @@ fun MessagesHistoryScreen(
listState = listState, listState = listState,
immutableMessages = ImmutableList.copyOf(screenState.messages), immutableMessages = ImmutableList.copyOf(screenState.messages),
isPaginating = screenState.isPaginating, isPaginating = screenState.isPaginating,
enableAnimations = animationsEnabled enableAnimations = animationsEnabled,
messageBarHeight = messageBarHeight
) )
Column( Column(
@@ -302,6 +321,7 @@ fun MessagesHistoryScreen(
.fillMaxWidth() .fillMaxWidth()
.align(Alignment.BottomStart) .align(Alignment.BottomStart)
.background(Color.Transparent) .background(Color.Transparent)
.padding(bottom = 8.dp)
.navigationBarsPadding() .navigationBarsPadding()
.imePadding() .imePadding()
) { ) {
@@ -316,55 +336,65 @@ fun MessagesHistoryScreen(
Row( Row(
modifier = Modifier modifier = Modifier
.animateContentSize()
.weight(1f) .weight(1f)
.clip(RoundedCornerShape(percent = 50)) .clip(RoundedCornerShape(36.dp))
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(5.dp)), .background(MaterialTheme.colorScheme.surfaceColorAtElevation(5.dp))
verticalAlignment = Alignment.CenterVertically .onGloballyPositioned {
messageBarHeight = with(density) {
it.size.height.toDp()
}
},
verticalAlignment = Alignment.Bottom
) { ) {
Spacer(modifier = Modifier.width(6.dp)) Spacer(modifier = Modifier.width(6.dp))
if ( if (showEmojiButton) {
preferences.getBoolean(
SettingsKeys.KEY_SHOW_EMOJI_BUTTON,
SettingsKeys.DEFAULT_VALUE_KEY_SHOW_EMOJI_BUTTON
)
) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val rotation = remember { Animatable(0f) } val rotation = remember { Animatable(0f) }
IconButton( Column(verticalArrangement = Arrangement.Bottom) {
onClick = { IconButton(
view.performHapticFeedback(HapticFeedbackConstantsCompat.REJECT) onClick = {
view.performHapticFeedback(HapticFeedbackConstantsCompat.REJECT)
scope.launch { scope.launch {
for (i in 20 downTo 0 step 4) { for (i in 20 downTo 0 step 4) {
rotation.animateTo(
targetValue = i.toFloat(),
animationSpec = tween(50)
)
if (i > 0) {
rotation.animateTo( rotation.animateTo(
targetValue = -i.toFloat(), targetValue = i.toFloat(),
animationSpec = tween(50) animationSpec = tween(50)
) )
if (i > 0) {
rotation.animateTo(
targetValue = -i.toFloat(),
animationSpec = tween(50)
)
}
} }
} }
} },
}, modifier = Modifier.rotate(rotation.value)
modifier = Modifier.rotate(rotation.value) ) {
) { Icon(
Icon( painter = painterResource(id = UiR.drawable.ic_outline_emoji_emotions_24),
painter = painterResource(id = UiR.drawable.ic_outline_emoji_emotions_24), contentDescription = "Emoji button",
contentDescription = "Emoji button", tint = MaterialTheme.colorScheme.primary
tint = MaterialTheme.colorScheme.primary )
) }
Spacer(modifier = Modifier.height(4.dp))
} }
} }
var message by remember { mutableStateOf(TextFieldValue(screenState.message)) }
TextField( TextField(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
value = screenState.message, value = message,
onValueChange = onMessageInputChanged, onValueChange = { newText ->
message = newText
onMessageInputChanged(newText.text)
},
colors = TextFieldDefaults.colors( colors = TextFieldDefaults.colors(
unfocusedContainerColor = Color.Transparent, unfocusedContainerColor = Color.Transparent,
focusedContainerColor = Color.Transparent, focusedContainerColor = Color.Transparent,
@@ -380,60 +410,68 @@ fun MessagesHistoryScreen(
} }
) )
IconButton(onClick = onAttachmentButtonClicked) { Column(verticalArrangement = Arrangement.Bottom) {
Icon( IconButton(onClick = onAttachmentButtonClicked) {
painter = painterResource(id = UiR.drawable.round_attach_file_24), Icon(
contentDescription = "Add attachment button", painter = painterResource(id = UiR.drawable.round_attach_file_24),
tint = MaterialTheme.colorScheme.primary, contentDescription = "Add attachment button",
modifier = Modifier.rotate(30f) tint = MaterialTheme.colorScheme.primary,
) modifier = Modifier.rotate(30f)
)
}
Spacer(modifier = Modifier.height(4.dp))
} }
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val rotation = remember { Animatable(0f) } val rotation = remember { Animatable(0f) }
IconButton( Column(verticalArrangement = Arrangement.Bottom) {
onClick = { IconButton(
if (screenState.actionMode == ActionMode.Record) { onClick = {
view.performHapticFeedback(HapticFeedbackConstantsCompat.REJECT) if (screenState.actionMode == ActionMode.Record) {
view.performHapticFeedback(HapticFeedbackConstantsCompat.REJECT)
scope.launch { scope.launch {
for (i in 20 downTo 0 step 4) { for (i in 20 downTo 0 step 4) {
rotation.animateTo(
targetValue = i.toFloat(),
animationSpec = tween(50)
)
if (i > 0) {
rotation.animateTo( rotation.animateTo(
targetValue = -i.toFloat(), targetValue = i.toFloat(),
animationSpec = tween(50) animationSpec = tween(50)
) )
if (i > 0) {
rotation.animateTo(
targetValue = -i.toFloat(),
animationSpec = tween(50)
)
}
} }
} }
} else {
onActionButtonClicked()
} }
} else {
onActionButtonClicked()
}
},
modifier = Modifier.rotate(rotation.value)
) {
Icon(
painter = painterResource(
id = when (screenState.actionMode) {
ActionMode.Delete -> UiR.drawable.round_delete_outline_24
ActionMode.Edit -> UiR.drawable.ic_round_done_24
ActionMode.Record -> UiR.drawable.ic_round_mic_none_24
ActionMode.Send -> UiR.drawable.round_send_24
}
),
contentDescription = when (screenState.actionMode) {
ActionMode.Delete -> "Delete message button"
ActionMode.Edit -> "Edit message button"
ActionMode.Record -> "Record audio message button"
ActionMode.Send -> "Send message button"
}, },
tint = MaterialTheme.colorScheme.primary modifier = Modifier.rotate(rotation.value)
) ) {
Icon(
painter = painterResource(
id = when (screenState.actionMode) {
ActionMode.Delete -> UiR.drawable.round_delete_outline_24
ActionMode.Edit -> UiR.drawable.ic_round_done_24
ActionMode.Record -> UiR.drawable.ic_round_mic_none_24
ActionMode.Send -> UiR.drawable.round_send_24
}
),
contentDescription = when (screenState.actionMode) {
ActionMode.Delete -> "Delete message button"
ActionMode.Edit -> "Edit message button"
ActionMode.Record -> "Record audio message button"
ActionMode.Send -> "Send message button"
},
tint = MaterialTheme.colorScheme.primary
)
}
Spacer(modifier = Modifier.height(4.dp))
} }
Spacer(modifier = Modifier.width(6.dp)) Spacer(modifier = Modifier.width(6.dp))
@@ -15,6 +15,7 @@ import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.meloda.app.fast.messageshistory.model.UiMessage import com.meloda.app.fast.messageshistory.model.UiMessage
import com.meloda.app.fast.ui.theme.LocalThemeConfig import com.meloda.app.fast.ui.theme.LocalThemeConfig
@@ -32,7 +33,8 @@ fun MessagesList(
listState: LazyListState, listState: LazyListState,
immutableMessages: ImmutableList<UiMessage>, immutableMessages: ImmutableList<UiMessage>,
isPaginating: Boolean, isPaginating: Boolean,
enableAnimations: Boolean enableAnimations: Boolean,
messageBarHeight: Dp
) { ) {
val messages = immutableMessages.toList() val messages = immutableMessages.toList()
val currentTheme = LocalThemeConfig.current val currentTheme = LocalThemeConfig.current
@@ -52,7 +54,7 @@ fun MessagesList(
reverseLayout = true reverseLayout = true
) { ) {
item { item {
Spacer(modifier = Modifier.height(68.dp)) Spacer(modifier = Modifier.height(messageBarHeight.plus(18.dp)))
Spacer( Spacer(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()