diff --git a/core/common/src/main/kotlin/dev/meloda/fast/common/util/Utils.kt b/core/common/src/main/kotlin/dev/meloda/fast/common/util/Utils.kt new file mode 100644 index 00000000..93d7caac --- /dev/null +++ b/core/common/src/main/kotlin/dev/meloda/fast/common/util/Utils.kt @@ -0,0 +1,7 @@ +package dev.meloda.fast.common.util + +import java.net.URLEncoder + +fun String.urlEncode(encoding: String = "utf-8"): String { + return URLEncoder.encode(this, encoding) +} diff --git a/core/ui/src/main/res/drawable/round_reply_24px.xml b/core/ui/src/main/res/drawable/round_reply_24px.xml new file mode 100644 index 00000000..380df664 --- /dev/null +++ b/core/ui/src/main/res/drawable/round_reply_24px.xml @@ -0,0 +1,11 @@ + + + + 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 new file mode 100644 index 00000000..4f08f01f --- /dev/null +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModel.kt @@ -0,0 +1,66 @@ +package dev.meloda.fast.messageshistory + +import android.os.Bundle +import androidx.compose.ui.text.input.TextFieldValue +import dev.meloda.fast.messageshistory.model.MessageDialog +import dev.meloda.fast.messageshistory.model.MessageNavigation +import dev.meloda.fast.messageshistory.model.MessagesHistoryScreenState +import dev.meloda.fast.messageshistory.model.UiItem +import dev.meloda.fast.model.BaseError +import dev.meloda.fast.model.api.domain.VkMessage +import kotlinx.coroutines.flow.StateFlow + +interface MessagesHistoryViewModel { + + val screenState: StateFlow + val navigation: StateFlow + val messages: StateFlow> + val uiMessages: StateFlow> + val dialog: StateFlow + val selectedMessages: StateFlow> + + val inputFieldFocusRequester: StateFlow + + val isNeedToScrollToIndex: StateFlow + + val baseError: StateFlow + val imagesToPreload: StateFlow> + + val currentOffset: StateFlow + val canPaginate: StateFlow + + fun onNavigationConsumed() + + fun onTopBarClicked() + + fun onDialogConfirmed(dialog: MessageDialog, bundle: Bundle) + fun onDialogDismissed(dialog: MessageDialog) + fun onDialogItemPicked(dialog: MessageDialog, bundle: Bundle) + + fun onScrolledToIndex() + + fun onCloseButtonClicked() + fun onRefresh() + fun onAttachmentButtonClicked() + fun onMessageInputChanged(newText: TextFieldValue) + fun onEmojiButtonLongClicked() + fun onActionButtonClicked() + + fun onPaginationConditionsMet() + + fun onMessageClicked(messageId: Long) + fun onMessageLongClicked(messageId: Long) + + fun onPinnedMessageClicked(messageId: Long) + fun onUnpinMessageClicked() + + fun onDeleteSelectedMessagesClicked() + + fun onBoldClicked() + fun onItalicClicked() + fun onUnderlineClicked() + fun onLinkClicked() + fun onRegularClicked() + + fun onReplyCloseClicked() +} diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/ReplyContainer.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/ReplyContainer.kt new file mode 100644 index 00000000..159a9a14 --- /dev/null +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/ReplyContainer.kt @@ -0,0 +1,132 @@ +package dev.meloda.fast.messageshistory.presentation + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.ripple +import androidx.compose.material3.surfaceColorAtElevation +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import dev.meloda.fast.ui.R + +@Composable +fun ReplyContainer( + onCloseClicked: () -> Unit = {}, + title: String, + text: String?, + modifier: Modifier = Modifier, + backgroundColor: Color = MaterialTheme.colorScheme.surfaceColorAtElevation(5.dp) +) { + Row( + modifier = modifier + .padding(horizontal = 8.dp) + .fillMaxWidth() + .heightIn(min = 48.dp) + .clip( + RoundedCornerShape( + topStart = 24.dp, + topEnd = 24.dp, + bottomStart = 0.dp, + bottomEnd = 0.dp + ) + ) + .background(backgroundColor) + .padding(horizontal = 8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painter = painterResource(R.drawable.round_reply_24px), + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(0.dp) + ) { + Text( + text = title, + style = MaterialTheme.typography.labelLarge, + color = MaterialTheme.colorScheme.primary + ) + + AnimatedVisibility(text != null) { + Text( + text = text.orEmpty(), + style = MaterialTheme.typography.bodySmall, + ) + } + } + + RippledClickContainer( + modifier = Modifier.size(36.dp), + shape = CircleShape, + onClick = onCloseClicked + ) { + Icon( + painter = painterResource(R.drawable.round_close_24px), + contentDescription = null, + ) + } + } +} + +@Composable +fun RippledClickContainer( + modifier: Modifier = Modifier, + shape: Shape = RoundedCornerShape(4.dp), + onClick: () -> Unit, + content: @Composable () -> Unit +) { + Box( + modifier = modifier + .clip(shape) + .clickable( + interactionSource = null, + indication = ripple(), + onClick = onClick + ), + contentAlignment = Alignment.Center + ) { + content() + } +} + +@Preview +@Composable +private fun ReplyContainerPreview() { + Box( + modifier = Modifier + .fillMaxSize() + .background(Color.White), + contentAlignment = Alignment.Center + ) { + ReplyContainer( + onCloseClicked = {}, + title = "В ответ Ишак", + text = "Приветствую тебя, Ишак!", + ) + } +}