forked from melod1n/fast-messenger
feat(messages): add message selection and actions
This commit is contained in:
@@ -170,7 +170,6 @@ fun MainScreen(
|
||||
conversationsScreen(
|
||||
onError = onError,
|
||||
onConversationItemClicked = onConversationItemClicked,
|
||||
onPhotoClicked = onPhotoClicked,
|
||||
onCreateChatClicked = onCreateChatClicked,
|
||||
navController = navController,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M10,9V7.41c0,-0.89 -1.08,-1.34 -1.71,-0.71L3.7,11.29c-0.39,0.39 -0.39,1.02 0,1.41l4.59,4.59c0.63,0.63 1.71,0.19 1.71,-0.7V14.9c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z" />
|
||||
|
||||
</vector>
|
||||
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M7,7.56c0,-0.94 -1.14,-1.42 -1.81,-0.75L0.71,11.29c-0.39,0.39 -0.39,1.02 0,1.41l4.48,4.48c0.67,0.68 1.81,0.2 1.81,-0.74 0,-0.28 -0.11,-0.55 -0.31,-0.75L3,12l3.69,-3.69c0.2,-0.2 0.31,-0.47 0.31,-0.75zM13,9V7.41c0,-0.89 -1.08,-1.34 -1.71,-0.71L6.7,11.29c-0.39,0.39 -0.39,1.02 0,1.41l4.59,4.59c0.63,0.63 1.71,0.18 1.71,-0.71V14.9c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z" />
|
||||
|
||||
</vector>
|
||||
-2
@@ -16,7 +16,6 @@ object Conversations
|
||||
fun NavGraphBuilder.conversationsScreen(
|
||||
onError: (BaseError) -> Unit,
|
||||
onConversationItemClicked: (id: Int) -> Unit,
|
||||
onPhotoClicked: (url: String) -> Unit,
|
||||
onCreateChatClicked: () -> Unit,
|
||||
navController: NavController,
|
||||
) {
|
||||
@@ -27,7 +26,6 @@ fun NavGraphBuilder.conversationsScreen(
|
||||
ConversationsRoute(
|
||||
onError = onError,
|
||||
onConversationItemClicked = onConversationItemClicked,
|
||||
onConversationPhotoClicked = onPhotoClicked,
|
||||
onCreateChatButtonClicked = onCreateChatClicked,
|
||||
viewModel = viewModel
|
||||
)
|
||||
|
||||
+2
-11
@@ -6,7 +6,6 @@ import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -40,7 +39,6 @@ import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
@@ -69,10 +67,8 @@ fun ConversationItem(
|
||||
maxLines: Int,
|
||||
isUserAccount: Boolean,
|
||||
conversation: UiConversation,
|
||||
modifier: Modifier = Modifier,
|
||||
onPhotoClicked: (url: String) -> Unit
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
|
||||
val bottomStartCornerRadius by animateDpAsState(
|
||||
@@ -154,12 +150,7 @@ fun ConversationItem(
|
||||
contentDescription = "Avatar",
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.clip(CircleShape)
|
||||
.clickable {
|
||||
if (avatarImage is String) {
|
||||
onPhotoClicked(avatarImage)
|
||||
}
|
||||
},
|
||||
.clip(CircleShape),
|
||||
placeholder = painterResource(id = UiR.drawable.ic_account_circle_cut)
|
||||
)
|
||||
}
|
||||
|
||||
+2
-6
@@ -39,13 +39,10 @@ fun ConversationsList(
|
||||
maxLines: Int,
|
||||
modifier: Modifier,
|
||||
onOptionClicked: (UiConversation, ConversationOption) -> Unit,
|
||||
padding: PaddingValues,
|
||||
onPhotoClicked: (url: String) -> Unit
|
||||
padding: PaddingValues
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
val bottomPadding = LocalBottomPadding.current
|
||||
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
state = state
|
||||
@@ -71,8 +68,7 @@ fun ConversationsList(
|
||||
maxLines = maxLines,
|
||||
isUserAccount = isUserAccount,
|
||||
conversation = conversation,
|
||||
modifier = Modifier.animateItem(fadeInSpec = null, fadeOutSpec = null),
|
||||
onPhotoClicked = onPhotoClicked
|
||||
modifier = Modifier.animateItem(fadeInSpec = null, fadeOutSpec = null)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
+1
-5
@@ -85,7 +85,6 @@ import dev.meloda.fast.ui.R as UiR
|
||||
fun ConversationsRoute(
|
||||
onError: (BaseError) -> Unit,
|
||||
onConversationItemClicked: (conversationId: Int) -> Unit,
|
||||
onConversationPhotoClicked: (url: String) -> Unit,
|
||||
onCreateChatButtonClicked: () -> Unit,
|
||||
viewModel: ConversationsViewModel
|
||||
) {
|
||||
@@ -107,7 +106,6 @@ fun ConversationsRoute(
|
||||
onPaginationConditionsMet = viewModel::onPaginationConditionsMet,
|
||||
onRefreshDropdownItemClicked = viewModel::onRefresh,
|
||||
onRefresh = viewModel::onRefresh,
|
||||
onConversationPhotoClicked = onConversationPhotoClicked,
|
||||
onCreateChatButtonClicked = onCreateChatButtonClicked,
|
||||
setScrollIndex = viewModel::setScrollIndex,
|
||||
setScrollOffset = viewModel::setScrollOffset
|
||||
@@ -135,7 +133,6 @@ fun ConversationsScreen(
|
||||
onPaginationConditionsMet: () -> Unit = {},
|
||||
onRefreshDropdownItemClicked: () -> Unit = {},
|
||||
onRefresh: () -> Unit = {},
|
||||
onConversationPhotoClicked: (url: String) -> Unit = {},
|
||||
onCreateChatButtonClicked: () -> Unit = {},
|
||||
setScrollIndex: (Int) -> Unit = {},
|
||||
setScrollOffset: (Int) -> Unit = {}
|
||||
@@ -356,8 +353,7 @@ fun ConversationsScreen(
|
||||
Modifier
|
||||
}.fillMaxSize(),
|
||||
onOptionClicked = onOptionClicked,
|
||||
padding = padding,
|
||||
onPhotoClicked = onConversationPhotoClicked
|
||||
padding = padding
|
||||
)
|
||||
|
||||
if (screenState.conversations.isEmpty()) {
|
||||
|
||||
+75
-1
@@ -1,6 +1,8 @@
|
||||
package dev.meloda.fast.messageshistory
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
@@ -45,6 +47,7 @@ import kotlin.random.Random
|
||||
interface MessagesHistoryViewModel {
|
||||
|
||||
val screenState: StateFlow<MessagesHistoryScreenState>
|
||||
val selectedMessages: StateFlow<List<Int>>
|
||||
|
||||
val baseError: StateFlow<BaseError?>
|
||||
val imagesToPreload: StateFlow<List<String>>
|
||||
@@ -53,6 +56,7 @@ interface MessagesHistoryViewModel {
|
||||
|
||||
val canPaginate: StateFlow<Boolean>
|
||||
|
||||
fun onCloseButtonClicked()
|
||||
fun onRefresh()
|
||||
fun onAttachmentButtonClicked()
|
||||
fun onMessageInputChanged(newText: TextFieldValue)
|
||||
@@ -60,19 +64,23 @@ interface MessagesHistoryViewModel {
|
||||
fun onActionButtonClicked()
|
||||
|
||||
fun onPaginationConditionsMet()
|
||||
|
||||
fun onMessageClicked(messageId: Int)
|
||||
fun onMessageLongClicked(messageId: Int)
|
||||
}
|
||||
|
||||
class MessagesHistoryViewModelImpl(
|
||||
private val applicationContext: Context,
|
||||
private val messagesUseCase: MessagesUseCase,
|
||||
private val conversationsUseCase: ConversationsUseCase,
|
||||
private val resourceProvider: ResourceProvider,
|
||||
private val userSettings: UserSettings,
|
||||
private val loadConversationsByIdUseCase: LoadConversationsByIdUseCase,
|
||||
updatesParser: LongPollUpdatesParser,
|
||||
savedStateHandle: SavedStateHandle
|
||||
) : MessagesHistoryViewModel, ViewModel() {
|
||||
|
||||
override val screenState = MutableStateFlow(MessagesHistoryScreenState.EMPTY)
|
||||
override val selectedMessages = MutableStateFlow<List<Int>>(emptyList())
|
||||
|
||||
override val baseError = MutableStateFlow<BaseError?>(null)
|
||||
override val imagesToPreload = MutableStateFlow<List<String>>(emptyList())
|
||||
@@ -104,6 +112,18 @@ class MessagesHistoryViewModelImpl(
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCloseButtonClicked() {
|
||||
screenState.setValue { old ->
|
||||
old.copy(
|
||||
messages = old.messages.map {
|
||||
if (it is UiItem.Message) it.copy(isSelected = false)
|
||||
else it
|
||||
}
|
||||
)
|
||||
}
|
||||
selectedMessages.setValue { emptyList() }
|
||||
}
|
||||
|
||||
override fun onRefresh() {
|
||||
loadMessagesHistory(offset = 0)
|
||||
}
|
||||
@@ -158,6 +178,60 @@ class MessagesHistoryViewModelImpl(
|
||||
loadMessagesHistory()
|
||||
}
|
||||
|
||||
override fun onMessageClicked(messageId: Int) {
|
||||
val messageIndex = screenState.value.messages.indexOfFirstOrNull {
|
||||
it is UiItem.Message && it.id == messageId
|
||||
} ?: return
|
||||
|
||||
val newMessages = screenState.value.messages.toMutableList()
|
||||
val currentMessage: UiItem.Message = newMessages[messageIndex] as UiItem.Message
|
||||
|
||||
if (selectedMessages.value.isNotEmpty()) {
|
||||
val isSelected = selectedMessages.value.contains(currentMessage.id)
|
||||
|
||||
newMessages[messageIndex] = currentMessage.copy(
|
||||
isSelected = !isSelected
|
||||
)
|
||||
screenState.setValue { old -> old.copy(messages = newMessages) }
|
||||
selectedMessages.setValue { old ->
|
||||
old.toMutableList().also {
|
||||
if (isSelected) {
|
||||
it.remove(currentMessage.id)
|
||||
} else {
|
||||
it.add(currentMessage.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(applicationContext, "Click", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMessageLongClicked(messageId: Int) {
|
||||
val messageIndex = screenState.value.messages.indexOfFirstOrNull {
|
||||
it is UiItem.Message && it.id == messageId
|
||||
} ?: return
|
||||
|
||||
val newMessages = screenState.value.messages.toMutableList()
|
||||
val currentMessage: UiItem.Message = newMessages[messageIndex] as UiItem.Message
|
||||
|
||||
val isSelected = selectedMessages.value.contains(currentMessage.id)
|
||||
|
||||
newMessages[messageIndex] = currentMessage.copy(
|
||||
isSelected = !isSelected
|
||||
)
|
||||
screenState.setValue { old -> old.copy(messages = newMessages) }
|
||||
selectedMessages.setValue { old ->
|
||||
old.toMutableList().also {
|
||||
if (isSelected) {
|
||||
it.remove(currentMessage.id)
|
||||
} else {
|
||||
it.add(currentMessage.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNewMessage(event: LongPollParsedEvent.NewMessage) {
|
||||
val message = event.message
|
||||
|
||||
|
||||
+2
-1
@@ -24,7 +24,8 @@ sealed class UiItem(
|
||||
val avatar: UiImage,
|
||||
val isEdited: Boolean,
|
||||
val isRead: Boolean,
|
||||
val sendingStatus: SendingStatus = SendingStatus.SENT
|
||||
val sendingStatus: SendingStatus = SendingStatus.SENT,
|
||||
val isSelected: Boolean = false
|
||||
) : UiItem(id, conversationMessageId)
|
||||
|
||||
data class ActionMessage(
|
||||
|
||||
+6
-5
@@ -31,12 +31,11 @@ import dev.meloda.fast.messageshistory.model.UiItem
|
||||
fun IncomingMessageBubble(
|
||||
modifier: Modifier = Modifier,
|
||||
message: UiItem.Message,
|
||||
animate: Boolean
|
||||
animate: Boolean,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
Row(modifier = modifier.fillMaxWidth()) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(0.75f)
|
||||
.padding(start = 16.dp),
|
||||
verticalAlignment = Alignment.Bottom,
|
||||
@@ -48,7 +47,7 @@ fun IncomingMessageBubble(
|
||||
message.avatar.extractUrl()?.let { url ->
|
||||
rememberAsyncImagePainter(
|
||||
model = url,
|
||||
imageLoader = context.imageLoader
|
||||
imageLoader = LocalContext.current.imageLoader
|
||||
)
|
||||
} ?: painterResource(id = message.avatar.extractResId()),
|
||||
contentDescription = null,
|
||||
@@ -87,4 +86,6 @@ fun IncomingMessageBubble(
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier=Modifier.fillMaxWidth(0.25f))
|
||||
}
|
||||
}
|
||||
|
||||
+90
-8
@@ -1,5 +1,6 @@
|
||||
package dev.meloda.fast.messageshistory.presentation
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.core.Animatable
|
||||
@@ -33,6 +34,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
||||
import androidx.compose.material.icons.outlined.MoreVert
|
||||
import androidx.compose.material.icons.rounded.Close
|
||||
import androidx.compose.material.icons.rounded.Refresh
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
@@ -107,6 +109,7 @@ fun MessagesHistoryRoute(
|
||||
viewModel: MessagesHistoryViewModel = koinViewModel<MessagesHistoryViewModelImpl>()
|
||||
) {
|
||||
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
|
||||
val selectedMessages by viewModel.selectedMessages.collectAsStateWithLifecycle()
|
||||
val baseError by viewModel.baseError.collectAsStateWithLifecycle()
|
||||
val canPaginate by viewModel.canPaginate.collectAsStateWithLifecycle()
|
||||
|
||||
@@ -115,10 +118,12 @@ fun MessagesHistoryRoute(
|
||||
|
||||
MessagesHistoryScreen(
|
||||
screenState = screenState,
|
||||
selectedMessages = ImmutableList.copyOf(selectedMessages),
|
||||
baseError = baseError,
|
||||
canPaginate = canPaginate,
|
||||
showEmojiButton = showEmojiButton,
|
||||
onBack = onBack,
|
||||
onClose = viewModel::onCloseButtonClicked,
|
||||
onSessionExpiredLogOutButtonClicked = { onError(BaseError.SessionExpired) },
|
||||
onChatMaterialsDropdownItemClicked = onChatMaterialsDropdownItemClicked,
|
||||
onRefresh = viewModel::onRefresh,
|
||||
@@ -126,7 +131,9 @@ fun MessagesHistoryRoute(
|
||||
onMessageInputChanged = viewModel::onMessageInputChanged,
|
||||
onAttachmentButtonClicked = viewModel::onAttachmentButtonClicked,
|
||||
onActionButtonClicked = viewModel::onActionButtonClicked,
|
||||
onEmojiButtonLongClicked = viewModel::onEmojiButtonLongClicked
|
||||
onEmojiButtonLongClicked = viewModel::onEmojiButtonLongClicked,
|
||||
onMessageClicked = viewModel::onMessageClicked,
|
||||
onMessageLongClicked = viewModel::onMessageLongClicked
|
||||
)
|
||||
}
|
||||
|
||||
@@ -138,10 +145,12 @@ fun MessagesHistoryRoute(
|
||||
@Composable
|
||||
fun MessagesHistoryScreen(
|
||||
screenState: MessagesHistoryScreenState = MessagesHistoryScreenState.EMPTY,
|
||||
selectedMessages: ImmutableList<Int> = ImmutableList.empty(),
|
||||
baseError: BaseError? = null,
|
||||
canPaginate: Boolean = false,
|
||||
showEmojiButton: Boolean = false,
|
||||
onBack: () -> Unit = {},
|
||||
onClose: () -> Unit = {},
|
||||
onSessionExpiredLogOutButtonClicked: () -> Unit = {},
|
||||
onChatMaterialsDropdownItemClicked: (peerId: Int, conversationMessageId: Int) -> Unit = { _, _ -> },
|
||||
onRefresh: () -> Unit = {},
|
||||
@@ -149,7 +158,9 @@ fun MessagesHistoryScreen(
|
||||
onMessageInputChanged: (TextFieldValue) -> Unit = {},
|
||||
onAttachmentButtonClicked: () -> Unit = {},
|
||||
onActionButtonClicked: () -> Unit = {},
|
||||
onEmojiButtonLongClicked: () -> Unit = {}
|
||||
onEmojiButtonLongClicked: () -> Unit = {},
|
||||
onMessageClicked: (Int) -> Unit = {},
|
||||
onMessageLongClicked: (Int) -> Unit = {}
|
||||
) {
|
||||
val view = LocalView.current
|
||||
|
||||
@@ -157,6 +168,11 @@ fun MessagesHistoryScreen(
|
||||
|
||||
val currentTheme = LocalThemeConfig.current
|
||||
|
||||
BackHandler(
|
||||
enabled = selectedMessages.isNotEmpty(),
|
||||
onBack = onClose
|
||||
)
|
||||
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
val paginationConditionMet by remember(canPaginate, listState) {
|
||||
@@ -191,6 +207,10 @@ fun MessagesHistoryScreen(
|
||||
|
||||
val density = LocalDensity.current
|
||||
|
||||
val showReplyAction by remember(screenState) {
|
||||
mutableStateOf(selectedMessages.size == 1)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentWindowInsets = WindowInsets.statusBars,
|
||||
@@ -213,6 +233,7 @@ fun MessagesHistoryScreen(
|
||||
.weight(1f),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
if (selectedMessages.isEmpty()) {
|
||||
val avatar = screenState.avatar.getImage()
|
||||
if (avatar is Painter) {
|
||||
Image(
|
||||
@@ -234,11 +255,14 @@ fun MessagesHistoryScreen(
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
}
|
||||
|
||||
Text(
|
||||
text =
|
||||
if (screenState.isLoading) stringResource(id = UiR.string.title_loading)
|
||||
else screenState.title,
|
||||
text = when {
|
||||
screenState.isLoading -> stringResource(id = UiR.string.title_loading)
|
||||
selectedMessages.size > 0 -> "(${selectedMessages.size})"
|
||||
else -> screenState.title
|
||||
},
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.headlineSmall
|
||||
@@ -246,9 +270,18 @@ fun MessagesHistoryScreen(
|
||||
}
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
if (selectedMessages.isEmpty()) onBack()
|
||||
else onClose()
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
imageVector = if (selectedMessages.isEmpty()) {
|
||||
Icons.AutoMirrored.Rounded.ArrowBack
|
||||
} else {
|
||||
Icons.Rounded.Close
|
||||
},
|
||||
contentDescription = "Back button"
|
||||
)
|
||||
}
|
||||
@@ -259,6 +292,46 @@ fun MessagesHistoryScreen(
|
||||
)
|
||||
),
|
||||
actions = {
|
||||
if (selectedMessages.isNotEmpty()) {
|
||||
AnimatedVisibility(showReplyAction) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
if (AppSettings.General.enableHaptic) {
|
||||
view.performHapticFeedback(HapticFeedbackConstantsCompat.REJECT)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(UiR.drawable.round_reply_24),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
if (AppSettings.General.enableHaptic) {
|
||||
view.performHapticFeedback(HapticFeedbackConstantsCompat.REJECT)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(UiR.drawable.round_reply_all_24),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
if (AppSettings.General.enableHaptic) {
|
||||
view.performHapticFeedback(HapticFeedbackConstantsCompat.REJECT)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(UiR.drawable.round_delete_outline_24),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
} else {
|
||||
IconButton(
|
||||
onClick = { dropDownMenuExpanded = true }
|
||||
) {
|
||||
@@ -309,6 +382,7 @@ fun MessagesHistoryScreen(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
val showHorizontalProgressBar by remember(screenState) {
|
||||
@@ -323,7 +397,6 @@ fun MessagesHistoryScreen(
|
||||
}
|
||||
}
|
||||
) { padding ->
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
@@ -343,7 +416,16 @@ fun MessagesHistoryScreen(
|
||||
index = screenState.messages.indexOfMessageByCmId(cmId)
|
||||
)
|
||||
}
|
||||
},
|
||||
onMessageClicked = { id ->
|
||||
if (selectedMessages.isNotEmpty()) {
|
||||
if (AppSettings.General.enableHaptic) {
|
||||
view.performHapticFeedback(HapticFeedbackConstantsCompat.CONTEXT_CLICK)
|
||||
}
|
||||
}
|
||||
onMessageClicked(id)
|
||||
},
|
||||
onMessageLongClicked = onMessageLongClicked
|
||||
)
|
||||
|
||||
Column(
|
||||
|
||||
+45
-7
@@ -1,30 +1,41 @@
|
||||
package dev.meloda.fast.messageshistory.presentation
|
||||
|
||||
import android.view.HapticFeedbackConstants
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.chrisbanes.haze.HazeState
|
||||
import dev.chrisbanes.haze.haze
|
||||
import dev.chrisbanes.haze.hazeSource
|
||||
import dev.meloda.fast.datastore.AppSettings
|
||||
import dev.meloda.fast.messageshistory.model.UiItem
|
||||
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||
import dev.meloda.fast.ui.util.ImmutableList
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun MessagesList(
|
||||
modifier: Modifier = Modifier,
|
||||
@@ -33,7 +44,9 @@ fun MessagesList(
|
||||
immutableMessages: ImmutableList<UiItem>,
|
||||
isPaginating: Boolean,
|
||||
messageBarHeight: Dp,
|
||||
onRequestScrollToCmId: (cmId: Int) -> Unit = {}
|
||||
onRequestScrollToCmId: (cmId: Int) -> Unit = {},
|
||||
onMessageClicked: (Int) -> Unit = {},
|
||||
onMessageLongClicked: (Int) -> Unit = {}
|
||||
) {
|
||||
val enableAnimations = remember {
|
||||
AppSettings.Experimental.moreAnimations
|
||||
@@ -42,13 +55,14 @@ fun MessagesList(
|
||||
immutableMessages.toList()
|
||||
}
|
||||
val currentTheme = LocalThemeConfig.current
|
||||
val view = LocalView.current
|
||||
|
||||
LazyColumn(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.then(
|
||||
if (currentTheme.enableBlur) {
|
||||
Modifier.haze(state = hazeState)
|
||||
Modifier.hazeSource(state = hazeState)
|
||||
} else Modifier
|
||||
),
|
||||
state = listState,
|
||||
@@ -87,10 +101,33 @@ fun MessagesList(
|
||||
}
|
||||
|
||||
is UiItem.Message -> {
|
||||
val backgroundColor by animateColorAsState(
|
||||
targetValue = if (item.isSelected) {
|
||||
MaterialTheme.colorScheme.primary.copy(alpha = 0.35f)
|
||||
} else {
|
||||
Color.Transparent
|
||||
}
|
||||
)
|
||||
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.combinedClickable(
|
||||
onLongClick = {
|
||||
if (AppSettings.General.enableHaptic) {
|
||||
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
|
||||
}
|
||||
onMessageLongClicked(item.id)
|
||||
},
|
||||
onClick = { onMessageClicked(item.id) }
|
||||
),
|
||||
color = backgroundColor
|
||||
) {
|
||||
if (item.isOut) {
|
||||
OutgoingMessageBubble(
|
||||
modifier =
|
||||
Modifier.then(
|
||||
Modifier
|
||||
.padding(vertical = 4.dp)
|
||||
.then(
|
||||
if (enableAnimations) Modifier.animateItem(
|
||||
fadeInSpec = null,
|
||||
fadeOutSpec = null
|
||||
@@ -103,7 +140,9 @@ fun MessagesList(
|
||||
} else {
|
||||
IncomingMessageBubble(
|
||||
modifier =
|
||||
Modifier.then(
|
||||
Modifier
|
||||
.padding(vertical = 4.dp)
|
||||
.then(
|
||||
if (enableAnimations) Modifier.animateItem(
|
||||
fadeInSpec = null,
|
||||
fadeOutSpec = null
|
||||
@@ -116,8 +155,7 @@ fun MessagesList(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
|
||||
Reference in New Issue
Block a user