forked from melod1n/fast-messenger
a shit ton features, improvements and fixes in messages history screen and others
This commit is contained in:
@@ -46,8 +46,8 @@ inline fun <T> State<T>.processState(
|
||||
) {
|
||||
when (this) {
|
||||
is State.Error -> {
|
||||
error(this)
|
||||
any()
|
||||
error(this)
|
||||
}
|
||||
|
||||
State.Idle -> idle()
|
||||
@@ -55,8 +55,8 @@ inline fun <T> State<T>.processState(
|
||||
State.Loading -> loading()
|
||||
|
||||
is State.Success -> {
|
||||
success(data)
|
||||
any()
|
||||
success(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,15 +56,22 @@ interface MessagesRepository {
|
||||
peerId: Int
|
||||
): ApiResult<Int, RestApiErrorDomain>
|
||||
|
||||
suspend fun storeMessages(messages: List<VkMessage>)
|
||||
suspend fun markAsImportant(
|
||||
peerId: Int,
|
||||
messageIds: List<Int>?,
|
||||
conversationMessageIds: List<Int>?,
|
||||
important: Boolean
|
||||
): ApiResult<List<Int>, RestApiErrorDomain>
|
||||
|
||||
// suspend fun markAsImportant(
|
||||
// params: MessagesMarkAsImportantRequest
|
||||
// ): ApiResult<List<Int>, RestApiErrorDomain>
|
||||
//
|
||||
// suspend fun delete(
|
||||
// params: MessagesDeleteRequest
|
||||
// ): ApiResult<Unit, RestApiErrorDomain>
|
||||
suspend fun delete(
|
||||
peerId: Int,
|
||||
messageIds: List<Int>?,
|
||||
conversationMessageIds: List<Int>?,
|
||||
spam: Boolean,
|
||||
deleteForAll: Boolean
|
||||
): ApiResult<List<Any>, RestApiErrorDomain>
|
||||
|
||||
suspend fun storeMessages(messages: List<VkMessage>)
|
||||
//
|
||||
// suspend fun edit(
|
||||
// params: MessagesEditRequest
|
||||
|
||||
+34
-2
@@ -16,13 +16,15 @@ import dev.meloda.fast.model.api.domain.VkAttachmentHistoryMessage
|
||||
import dev.meloda.fast.model.api.domain.VkMessage
|
||||
import dev.meloda.fast.model.api.domain.asEntity
|
||||
import dev.meloda.fast.model.api.requests.MessagesCreateChatRequest
|
||||
import dev.meloda.fast.model.api.requests.MessagesDeleteRequest
|
||||
import dev.meloda.fast.model.api.requests.MessagesGetByIdRequest
|
||||
import dev.meloda.fast.model.api.requests.MessagesGetHistoryAttachmentsRequest
|
||||
import dev.meloda.fast.model.api.requests.MessagesGetHistoryRequest
|
||||
import dev.meloda.fast.model.api.requests.MessagesMarkAsImportantRequest
|
||||
import dev.meloda.fast.model.api.requests.MessagesMarkAsReadRequest
|
||||
import dev.meloda.fast.model.api.requests.MessagesPinMessageRequest
|
||||
import dev.meloda.fast.model.api.requests.MessagesSendRequest
|
||||
import dev.meloda.fast.model.api.requests.MessagesUnPinMessageRequest
|
||||
import dev.meloda.fast.model.api.requests.MessagesUnpinMessageRequest
|
||||
import dev.meloda.fast.network.RestApiErrorDomain
|
||||
import dev.meloda.fast.network.mapApiDefault
|
||||
import dev.meloda.fast.network.mapApiResult
|
||||
@@ -240,10 +242,40 @@ class MessagesRepositoryImpl(
|
||||
override suspend fun unpin(
|
||||
peerId: Int
|
||||
): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
|
||||
val requestModel = MessagesUnPinMessageRequest(peerId = peerId)
|
||||
val requestModel = MessagesUnpinMessageRequest(peerId = peerId)
|
||||
messagesService.unpin(requestModel.map).mapApiDefault()
|
||||
}
|
||||
|
||||
override suspend fun markAsImportant(
|
||||
peerId: Int,
|
||||
messageIds: List<Int>?,
|
||||
conversationMessageIds: List<Int>?,
|
||||
important: Boolean
|
||||
): ApiResult<List<Int>, RestApiErrorDomain> = withContext(Dispatchers.IO) {
|
||||
val requestModel = MessagesMarkAsImportantRequest(
|
||||
messagesIds = messageIds.orEmpty(),
|
||||
important = important
|
||||
)
|
||||
messagesService.markAsImportant(requestModel.map).mapApiDefault()
|
||||
}
|
||||
|
||||
override suspend fun delete(
|
||||
peerId: Int,
|
||||
messageIds: List<Int>?,
|
||||
conversationMessageIds: List<Int>?,
|
||||
spam: Boolean,
|
||||
deleteForAll: Boolean
|
||||
): ApiResult<List<Any>, RestApiErrorDomain> = withContext(Dispatchers.IO) {
|
||||
val requestModel = MessagesDeleteRequest(
|
||||
peerId = peerId,
|
||||
messagesIds = messageIds,
|
||||
conversationsMessagesIds = conversationMessageIds,
|
||||
isSpam = spam,
|
||||
deleteForAll = deleteForAll
|
||||
)
|
||||
messagesService.delete(requestModel.map).mapApiDefault()
|
||||
}
|
||||
|
||||
override suspend fun storeMessages(messages: List<VkMessage>) {
|
||||
messageDao.insertAll(messages.map(VkMessage::asEntity))
|
||||
}
|
||||
|
||||
@@ -57,6 +57,19 @@ interface MessagesUseCase {
|
||||
peerId: Int
|
||||
): Flow<State<Int>>
|
||||
|
||||
fun markAsImportant(
|
||||
peerId: Int,
|
||||
messageIds: List<Int>,
|
||||
important: Boolean
|
||||
): Flow<State<List<Int>>>
|
||||
|
||||
fun delete(
|
||||
peerId: Int,
|
||||
messageIds: List<Int>,
|
||||
spam: Boolean = false,
|
||||
deleteForAll: Boolean = false
|
||||
): Flow<State<List<Any>>>
|
||||
|
||||
suspend fun storeMessage(message: VkMessage)
|
||||
suspend fun storeMessages(messages: List<VkMessage>)
|
||||
}
|
||||
|
||||
@@ -102,9 +102,7 @@ class MessagesUseCaseImpl(
|
||||
|
||||
override fun createChat(userIds: List<Int>?, title: String?): Flow<State<Int>> = flow {
|
||||
emit(State.Loading)
|
||||
|
||||
val newState = repository.createChat(userIds, title).mapToState()
|
||||
|
||||
emit(newState)
|
||||
}
|
||||
|
||||
@@ -126,8 +124,42 @@ class MessagesUseCaseImpl(
|
||||
|
||||
override fun unpin(peerId: Int): Flow<State<Int>> = flow {
|
||||
emit(State.Loading)
|
||||
|
||||
val newState = repository.unpin(peerId = peerId).mapToState()
|
||||
emit(newState)
|
||||
}
|
||||
|
||||
override fun markAsImportant(
|
||||
peerId: Int,
|
||||
messageIds: List<Int>,
|
||||
important: Boolean
|
||||
): Flow<State<List<Int>>> = flow {
|
||||
emit(State.Loading)
|
||||
|
||||
val newState = repository.markAsImportant(
|
||||
peerId = peerId,
|
||||
messageIds = messageIds,
|
||||
conversationMessageIds = null,
|
||||
important = important
|
||||
).mapToState()
|
||||
|
||||
emit(newState)
|
||||
}
|
||||
|
||||
override fun delete(
|
||||
peerId: Int,
|
||||
messageIds: List<Int>,
|
||||
spam: Boolean,
|
||||
deleteForAll: Boolean
|
||||
): Flow<State<List<Any>>> = flow {
|
||||
emit(State.Loading)
|
||||
|
||||
val newState = repository.delete(
|
||||
peerId = peerId,
|
||||
messageIds = messageIds,
|
||||
conversationMessageIds = null,
|
||||
spam = spam,
|
||||
deleteForAll = deleteForAll
|
||||
).mapToState()
|
||||
|
||||
emit(newState)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package dev.meloda.fast.model.api.domain
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import dev.meloda.fast.model.database.VkMessageEntity
|
||||
|
||||
@Immutable
|
||||
data class VkMessage(
|
||||
val id: Int,
|
||||
val conversationMessageId: Int,
|
||||
@@ -21,6 +23,7 @@ data class VkMessage(
|
||||
val pinnedAt: Int?,
|
||||
val isPinned: Boolean,
|
||||
val isImportant: Boolean = false,
|
||||
val isSpam: Boolean = false,
|
||||
|
||||
val forwards: List<VkMessage>?,
|
||||
val attachments: List<VkAttachment>?,
|
||||
@@ -55,6 +58,8 @@ data class VkMessage(
|
||||
|
||||
fun isUpdated(): Boolean = updateTime != null && updateTime > 0
|
||||
|
||||
fun isFailed(): Boolean = id <= -500_000
|
||||
|
||||
enum class Action(val value: String) {
|
||||
CHAT_CREATE("chat_create"),
|
||||
CHAT_PHOTO_UPDATE("chat_photo_update"),
|
||||
|
||||
@@ -119,7 +119,7 @@ data class MessagesPinMessageRequest(
|
||||
|
||||
}
|
||||
|
||||
data class MessagesUnPinMessageRequest(val peerId: Int) {
|
||||
data class MessagesUnpinMessageRequest(val peerId: Int) {
|
||||
val map: Map<String, String>
|
||||
get() = mapOf("peer_id" to peerId.toString())
|
||||
}
|
||||
|
||||
+11
-11
@@ -69,17 +69,17 @@ interface MessagesService {
|
||||
@FieldMap params: Map<String, String>
|
||||
): ApiResult<ApiResponse<Int>, RestApiError>
|
||||
|
||||
// @FormUrlEncoded
|
||||
// @POST(MessagesUrls.MarkAsImportant)
|
||||
// suspend fun markAsImportant(
|
||||
// @FieldMap params: Map<String, String>
|
||||
// ): ApiResult<ApiResponse<List<Int>>, RestApiError>
|
||||
//
|
||||
// @FormUrlEncoded
|
||||
// @POST(MessagesUrls.Delete)
|
||||
// suspend fun delete(
|
||||
// @FieldMap params: Map<String, String>
|
||||
// ): ApiResult<ApiResponse<Unit>, RestApiError>
|
||||
@FormUrlEncoded
|
||||
@POST(MessagesUrls.MARK_AS_IMPORTANT)
|
||||
suspend fun markAsImportant(
|
||||
@FieldMap params: Map<String, String>
|
||||
): ApiResult<ApiResponse<List<Int>>, RestApiError>
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST(MessagesUrls.DELETE)
|
||||
suspend fun delete(
|
||||
@FieldMap params: Map<String, String>
|
||||
): ApiResult<ApiResponse<List<Any>>, RestApiError>
|
||||
//
|
||||
// @FormUrlEncoded
|
||||
// @POST(MessagesUrls.Edit)
|
||||
|
||||
@@ -110,13 +110,12 @@ fun MaterialDialog(
|
||||
.verticalScroll(scrollState)
|
||||
.onPlaced { isPlaced = true }
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
if (text != null && title == null) {
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
}
|
||||
|
||||
if (text != null) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Row {
|
||||
Spacer(modifier = Modifier.width(24.dp))
|
||||
Text(
|
||||
@@ -137,8 +136,6 @@ fun MaterialDialog(
|
||||
selectionType = selectionType,
|
||||
items = alertItems,
|
||||
onItemClick = { index ->
|
||||
onItemClick?.invoke(index)
|
||||
|
||||
if (selectionType == SelectionType.None) {
|
||||
onDismissRequest.invoke()
|
||||
} else {
|
||||
@@ -149,6 +146,8 @@ fun MaterialDialog(
|
||||
|
||||
alertItems = newItems
|
||||
}
|
||||
|
||||
onItemClick?.invoke(index)
|
||||
},
|
||||
onItemCheckedChanged = { index ->
|
||||
val newItems = alertItems.toMutableList()
|
||||
@@ -161,11 +160,7 @@ fun MaterialDialog(
|
||||
)
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
} else {
|
||||
if (customContent != null) {
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
customContent.invoke(this)
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
}
|
||||
customContent?.invoke(this)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -183,7 +183,7 @@ fun AppTheme(
|
||||
bodySmall = MaterialTheme.typography.bodySmall.copy(fontFamily = robotoFonts),
|
||||
labelLarge = MaterialTheme.typography.labelLarge.copy(fontFamily = robotoFonts),
|
||||
labelMedium = MaterialTheme.typography.labelMedium.copy(fontFamily = robotoFonts),
|
||||
labelSmall = MaterialTheme.typography.labelSmall.copy(fontFamily = robotoFonts)
|
||||
labelSmall = MaterialTheme.typography.labelSmall.copy(fontFamily = robotoFonts),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -65,3 +65,5 @@ class ImmutableList<T>(val values: List<T>) : Iterable<T> {
|
||||
|
||||
override fun iterator(): Iterator<T> = values.listIterator()
|
||||
}
|
||||
|
||||
fun <T> emptyImmutableList(): ImmutableList<T> = ImmutableList(emptyList())
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<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="M15,20H5V7c0,-0.55 -0.45,-1 -1,-1h0C3.45,6 3,6.45 3,7v13c0,1.1 0.9,2 2,2h10c0.55,0 1,-0.45 1,-1v0C16,20.45 15.55,20 15,20zM20,16V4c0,-1.1 -0.9,-2 -2,-2H9C7.9,2 7,2.9 7,4v12c0,1.1 0.9,2 2,2h9C19.1,18 20,17.1 20,16zM18,16H9V4h9V16z" />
|
||||
|
||||
</vector>
|
||||
@@ -0,0 +1,11 @@
|
||||
<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="M3,17.46v3.04c0,0.28 0.22,0.5 0.5,0.5h3.04c0.13,0 0.26,-0.05 0.35,-0.15L17.81,9.94l-3.75,-3.75L3.15,17.1c-0.1,0.1 -0.15,0.22 -0.15,0.36zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z" />
|
||||
|
||||
</vector>
|
||||
+4
-1
@@ -1,9 +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="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z" />
|
||||
android:pathData="M12,8V6.41c0,-0.89 1.08,-1.34 1.71,-0.71l5.59,5.59c0.39,0.39 0.39,1.02 0,1.41l-5.59,5.59c-0.63,0.63 -1.71,0.19 -1.71,-0.7V16H5c-0.55,0 -1,-0.45 -1,-1V9c0,-0.55 0.45,-1 1,-1h7z" />
|
||||
|
||||
</vector>
|
||||
@@ -0,0 +1,11 @@
|
||||
<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="M18.05,21.29c-0.39,0.39 -1.02,0.39 -1.41,0l-2.12,-2.12c-0.39,-0.39 -0.39,-1.02 0,-1.41h0c0.39,-0.39 1.02,-0.39 1.41,0l1.41,1.41l3.54,-3.54c0.39,-0.39 1.02,-0.39 1.41,0l0,0c0.39,0.39 0.39,1.02 0,1.41L18.05,21.29zM12.08,20H4c-1.1,0 -2,-0.9 -2,-2V6c0,-1.1 0.9,-2 2,-2h16c1.1,0 2,0.9 2,2v6.68C21.09,12.25 20.08,12 19,12c-3.87,0 -7,3.13 -7,7C12,19.34 12.03,19.67 12.08,20zM11.47,12.67c0.32,0.2 0.74,0.2 1.06,0l7.07,-4.42C19.85,8.09 20,7.82 20,7.53c0,-0.67 -0.73,-1.07 -1.3,-0.72L12,11L5.3,6.81C4.73,6.46 4,6.86 4,7.53c0,0.29 0.15,0.56 0.4,0.72L11.47,12.67z" />
|
||||
|
||||
</vector>
|
||||
@@ -0,0 +1,19 @@
|
||||
<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="M20.71,7.98L16.03,3.3c-0.19,-0.19 -0.45,-0.3 -0.71,-0.3H8.68c-0.26,0 -0.52,0.11 -0.7,0.29L3.29,7.98c-0.18,0.18 -0.29,0.44 -0.29,0.7v6.63c0,0.27 0.11,0.52 0.29,0.71l4.68,4.68c0.19,0.19 0.45,0.3 0.71,0.3h6.63c0.27,0 0.52,-0.11 0.71,-0.29l4.68,-4.68c0.19,-0.19 0.29,-0.44 0.29,-0.71V8.68c0.01,-0.26 -0.1,-0.52 -0.28,-0.7zM19,14.9L14.9,19H9.1L5,14.9V9.1L9.1,5h5.8L19,9.1v5.8z" />
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,16m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0" />
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,7c-0.55,0 -1,0.45 -1,1v5c0,0.55 0.45,1 1,1s1,-0.45 1,-1V8c0,-0.55 -0.45,-1 -1,-1z" />
|
||||
|
||||
</vector>
|
||||
@@ -0,0 +1,11 @@
|
||||
<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="M12,7c0.55,0 1,0.45 1,1v1.33l7.2,7.2 0.51,-0.51c0.19,-0.19 0.29,-0.44 0.29,-0.71V8.68c0,-0.27 -0.11,-0.52 -0.29,-0.71l-4.68,-4.68c-0.19,-0.18 -0.45,-0.29 -0.71,-0.29H8.68c-0.26,0 -0.52,0.11 -0.7,0.29l-0.51,0.51 3.69,3.69c0.17,-0.29 0.48,-0.49 0.84,-0.49zM2.41,1.58L1,2.99l3.64,3.64 -1.35,1.35c-0.18,0.18 -0.29,0.44 -0.29,0.7v6.63c0,0.27 0.11,0.52 0.29,0.71l4.68,4.68c0.19,0.19 0.45,0.3 0.71,0.3h6.63c0.27,0 0.52,-0.11 0.71,-0.29l1.35,-1.35L21.01,23l1.41,-1.41L2.41,1.58zM12,17.3c-0.72,0 -1.3,-0.58 -1.3,-1.3 0,-0.72 0.58,-1.3 1.3,-1.3s1.3,0.58 1.3,1.3c0,0.72 -0.58,1.3 -1.3,1.3z" />
|
||||
|
||||
</vector>
|
||||
@@ -0,0 +1,11 @@
|
||||
<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="M12,17.27l4.15,2.51c0.76,0.46 1.69,-0.22 1.49,-1.08l-1.1,-4.72l3.67,-3.18c0.67,-0.58 0.31,-1.68 -0.57,-1.75l-4.83,-0.41l-1.89,-4.46c-0.34,-0.81 -1.5,-0.81 -1.84,0L9.19,8.63L4.36,9.04c-0.88,0.07 -1.24,1.17 -0.57,1.75l3.67,3.18l-1.1,4.72c-0.2,0.86 0.73,1.54 1.49,1.08L12,17.27z" />
|
||||
|
||||
</vector>
|
||||
@@ -0,0 +1,11 @@
|
||||
<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="M19.65,9.04l-4.84,-0.42 -1.89,-4.45c-0.34,-0.81 -1.5,-0.81 -1.84,0L9.19,8.63l-4.83,0.41c-0.88,0.07 -1.24,1.17 -0.57,1.75l3.67,3.18 -1.1,4.72c-0.2,0.86 0.73,1.54 1.49,1.08l4.15,-2.5 4.15,2.51c0.76,0.46 1.69,-0.22 1.49,-1.08l-1.1,-4.73 3.67,-3.18c0.67,-0.58 0.32,-1.68 -0.56,-1.75zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z" />
|
||||
|
||||
</vector>
|
||||
@@ -5,7 +5,9 @@
|
||||
<string name="sign_out_confirm">При выходе из учётной записи с устройства будут удалены все связанные с ней данные. Продолжить?</string>
|
||||
<string name="yes">Да</string>
|
||||
<string name="no">Нет</string>
|
||||
<string name="message_context_action_retry">Повторить</string>
|
||||
<string name="message_context_action_reply">Ответить</string>
|
||||
<string name="message_context_action_forward_here">Переслать сюда</string>
|
||||
<string name="message_context_action_forward">Переслать</string>
|
||||
<string name="message_context_action_mark_as_important">Пометить как важное</string>
|
||||
<string name="message_context_action_unmark_as_important">Пометить как не важное</string>
|
||||
@@ -15,6 +17,7 @@
|
||||
<string name="message_context_action_unpin">Открепить</string>
|
||||
<string name="message_context_action_edit">Изменить</string>
|
||||
<string name="message_context_action_delete">Удалить</string>
|
||||
<string name="message_context_action_read">Прочитать</string>
|
||||
<string name="message_context_action_copy">Скопировать</string>
|
||||
<string name="confirm_delete_message">Удалить сообщение?</string>
|
||||
<string name="message_delete_for_all">Для всех</string>
|
||||
@@ -31,6 +34,8 @@
|
||||
<string name="confirm_pin_conversation">Закрепить чат?</string>
|
||||
<string name="action_pin">Закрепить</string>
|
||||
<string name="action_unpin">Открепить</string>
|
||||
<string name="action_mark">Пометить</string>
|
||||
<string name="action_unmark">Убрать пометку</string>
|
||||
<string name="message_call_type_outgoing">Исходящий вызов</string>
|
||||
<string name="message_call_type_incoming">Входящий вызов</string>
|
||||
<string name="message_call_state_ended">Закончился</string>
|
||||
@@ -233,4 +238,19 @@
|
||||
<string name="chat_attachment_files">Файлы</string>
|
||||
<string name="chat_attachment_links">Ссылки</string>
|
||||
<string name="message_context_action_mark_as_spam">Пометить как спам</string>
|
||||
<string name="pin_message_text">Вы уверены, что хотите закрепить это сообщение? Это изменение увидят все участники чата.</string>
|
||||
<string name="unpin_message_title">Открепить сообщение</string>
|
||||
<string name="unpin_message_text">Вы уверены, что хотите открепить это сообщение? Все участники чата увидят это изменение.</string>
|
||||
<string name="delete_message_title">Удалить сообщение?</string>
|
||||
<string name="delete_message_for_everyone">Для всех</string>
|
||||
<string name="important_message_title">Пометить как важное</string>
|
||||
<string name="important_message_text">Вы уверены, что хотите пометить это сообщение как важное?</string>
|
||||
<string name="unimportant_message_text">Вы уверены, что хотите убрать пометку избранного у этого сообщения?</string>
|
||||
<string name="spam_message_title">Пометить как спам</string>
|
||||
<string name="spam_message_text">Вы уверены, что хотите пометить это сообщение как спам?</string>
|
||||
<string name="unimportant_message_title">Убрать пометку избранного</string>
|
||||
<string name="unspam_message_title">Убрать пометку спама</string>
|
||||
<string name="unspam_message_text">Вы уверены, что хотите убрать пометку спама у этого сообщения?</string>
|
||||
<string name="pin_message_title">Закрепить сообщение</string>
|
||||
<string name="copied_to_clipboard">Скопировано в буфер обмена</string>
|
||||
</resources>
|
||||
|
||||
@@ -126,7 +126,9 @@
|
||||
<string name="no">No</string>
|
||||
<string name="time_format">Time: %1$s</string>
|
||||
|
||||
<string name="message_context_action_retry">Retry</string>
|
||||
<string name="message_context_action_reply">Reply</string>
|
||||
<string name="message_context_action_forward_here">Forward here</string>
|
||||
<string name="message_context_action_forward">Forward</string>
|
||||
<string name="message_context_action_mark_as_important">Mark as important</string>
|
||||
<string name="message_context_action_unmark_as_important">Unmark as important</string>
|
||||
@@ -136,6 +138,7 @@
|
||||
<string name="message_context_action_unpin">Unpin</string>
|
||||
<string name="message_context_action_edit">Edit</string>
|
||||
<string name="message_context_action_delete">Delete</string>
|
||||
<string name="message_context_action_read">Read</string>
|
||||
<string name="message_context_action_copy">Copy</string>
|
||||
|
||||
<string name="confirm_delete_message">Delete the message?</string>
|
||||
@@ -156,6 +159,8 @@
|
||||
<string name="confirm_pin_conversation">Pin the conversation?</string>
|
||||
<string name="action_pin">Pin</string>
|
||||
<string name="action_unpin">Unpin</string>
|
||||
<string name="action_mark">Mark</string>
|
||||
<string name="action_unmark">Unmark</string>
|
||||
<string name="message_call_type_outgoing">Outgoing call</string>
|
||||
<string name="message_call_type_incoming">Incoming call</string>
|
||||
<string name="message_call_state_ended">Ended</string>
|
||||
@@ -301,4 +306,26 @@
|
||||
<string name="chat_attachment_music">Music</string>
|
||||
<string name="chat_attachment_files">Files</string>
|
||||
<string name="chat_attachment_links">Links</string>
|
||||
|
||||
<string name="pin_message_title">Pin message</string>
|
||||
<string name="pin_message_text">Are you sure you want to pin this message? All chat members will see this change.</string>
|
||||
|
||||
<string name="unpin_message_title">Unpin message</string>
|
||||
<string name="unpin_message_text">Are you sure you want to unpin this message? All chat members will see this change.</string>
|
||||
|
||||
<string name="delete_message_title">Delete the message?</string>
|
||||
<string name="delete_message_for_everyone">For everyone</string>
|
||||
|
||||
<string name="important_message_title">Mark as important</string>
|
||||
<string name="important_message_text">Are you sure you want to mark this message as important?</string>
|
||||
|
||||
<string name="unimportant_message_title">Unmark as important</string>
|
||||
<string name="unimportant_message_text">Are you sure you want to unmark this message as important?</string>
|
||||
|
||||
<string name="spam_message_title">Mark as spam</string>
|
||||
<string name="spam_message_text">Are you sure you want to mark this message as spam?</string>
|
||||
|
||||
<string name="unspam_message_title">Unmark as spam</string>
|
||||
<string name="unspam_message_text">Are you sure you want to unmark this message as spam?</string>
|
||||
<string name="copied_to_clipboard">Copied to clipboard</string>
|
||||
</resources>
|
||||
|
||||
+1
-1
@@ -286,7 +286,7 @@ fun ConversationsScreen(
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = UiR.drawable.ic_baseline_create_24),
|
||||
painter = painterResource(id = UiR.drawable.round_create_24),
|
||||
contentDescription = "Add chat button"
|
||||
)
|
||||
}
|
||||
|
||||
+492
-322
File diff suppressed because it is too large
Load Diff
+21
@@ -0,0 +1,21 @@
|
||||
package dev.meloda.fast.messageshistory.model
|
||||
|
||||
import dev.meloda.fast.model.api.domain.VkMessage
|
||||
|
||||
sealed class MessageDialog {
|
||||
data class MessageOptions(val message: VkMessage) : MessageDialog()
|
||||
data class MessagePin(val messageId: Int) : MessageDialog()
|
||||
data class MessageUnpin(val messageId: Int) : MessageDialog()
|
||||
data class MessageDelete(val message: VkMessage) : MessageDialog()
|
||||
data class MessagesDelete(val messages: List<VkMessage>) : MessageDialog()
|
||||
|
||||
data class MessageSpam(
|
||||
val message: VkMessage,
|
||||
val isSpam: Boolean
|
||||
) : MessageDialog()
|
||||
|
||||
data class MessageMarkImportance(
|
||||
val message: VkMessage,
|
||||
val isImportant: Boolean
|
||||
) : MessageDialog()
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package dev.meloda.fast.messageshistory.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import dev.meloda.fast.ui.R
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
sealed class MessageOption(
|
||||
@StringRes val titleResId: Int,
|
||||
@DrawableRes val iconResId: Int
|
||||
) : Parcelable {
|
||||
|
||||
data object Retry : MessageOption(
|
||||
titleResId = R.string.message_context_action_retry,
|
||||
iconResId = R.drawable.round_restart_alt_24
|
||||
)
|
||||
|
||||
data object Reply : MessageOption(
|
||||
titleResId = R.string.message_context_action_reply,
|
||||
iconResId = R.drawable.round_reply_24
|
||||
)
|
||||
|
||||
data object ForwardHere : MessageOption(
|
||||
titleResId = R.string.message_context_action_forward_here,
|
||||
iconResId = R.drawable.round_reply_all_24
|
||||
)
|
||||
|
||||
data object Forward : MessageOption(
|
||||
titleResId = R.string.message_context_action_forward,
|
||||
iconResId = R.drawable.round_forward_24
|
||||
)
|
||||
|
||||
data object Pin : MessageOption(
|
||||
titleResId = R.string.message_context_action_pin,
|
||||
iconResId = R.drawable.pin_outline_24
|
||||
)
|
||||
|
||||
data object Unpin : MessageOption(
|
||||
titleResId = R.string.message_context_action_unpin,
|
||||
iconResId = R.drawable.pin_off_outline_24
|
||||
)
|
||||
|
||||
data object Read : MessageOption(
|
||||
titleResId = R.string.message_context_action_read,
|
||||
iconResId = R.drawable.round_mark_email_read_24
|
||||
)
|
||||
|
||||
data object Copy : MessageOption(
|
||||
titleResId = R.string.message_context_action_copy,
|
||||
iconResId = R.drawable.round_content_copy_24
|
||||
)
|
||||
|
||||
data object MarkAsImportant : MessageOption(
|
||||
titleResId = R.string.message_context_action_mark_as_important,
|
||||
iconResId = R.drawable.round_star_24
|
||||
)
|
||||
|
||||
data object UnmarkAsImportant : MessageOption(
|
||||
titleResId = R.string.message_context_action_unmark_as_important,
|
||||
iconResId = R.drawable.round_star_outline_24
|
||||
)
|
||||
|
||||
data object MarkAsSpam : MessageOption(
|
||||
titleResId = R.string.message_context_action_mark_as_spam,
|
||||
iconResId = R.drawable.round_report_gmailerrorred_24
|
||||
)
|
||||
|
||||
data object UnmarkAsSpam : MessageOption(
|
||||
titleResId = R.string.message_context_action_unmark_as_spam,
|
||||
iconResId = R.drawable.round_report_off_24
|
||||
)
|
||||
|
||||
data object Edit : MessageOption(
|
||||
titleResId = R.string.message_context_action_edit,
|
||||
iconResId = R.drawable.round_create_24
|
||||
)
|
||||
|
||||
data object Delete : MessageOption(
|
||||
titleResId = R.string.message_context_action_delete,
|
||||
iconResId = R.drawable.round_delete_outline_24
|
||||
)
|
||||
}
|
||||
-2
@@ -14,7 +14,6 @@ data class MessagesHistoryScreenState(
|
||||
val title: String,
|
||||
val status: String?,
|
||||
val avatar: UiImage,
|
||||
val messages: List<UiItem>,
|
||||
val message: TextFieldValue,
|
||||
val attachments: List<VkAttachment>,
|
||||
val isLoading: Boolean,
|
||||
@@ -34,7 +33,6 @@ data class MessagesHistoryScreenState(
|
||||
title = "",
|
||||
status = null,
|
||||
avatar = UiImage.Color(0),
|
||||
messages = emptyList(),
|
||||
message = TextFieldValue(),
|
||||
attachments = emptyList(),
|
||||
isLoading = true,
|
||||
|
||||
+2
-1
@@ -26,7 +26,8 @@ sealed class UiItem(
|
||||
val isRead: Boolean,
|
||||
val sendingStatus: SendingStatus,
|
||||
val isSelected: Boolean,
|
||||
val isPinned: Boolean
|
||||
val isPinned: Boolean,
|
||||
val isImportant: Boolean
|
||||
) : UiItem(id, conversationMessageId)
|
||||
|
||||
data class ActionMessage(
|
||||
|
||||
+3
-1
@@ -90,7 +90,9 @@ fun IncomingMessageBubble(
|
||||
edited = message.isEdited,
|
||||
isRead = message.isRead,
|
||||
sendingStatus = message.sendingStatus,
|
||||
pinned = message.isPinned
|
||||
pinned = message.isPinned,
|
||||
important = message.isImportant,
|
||||
isSelected = message.isSelected
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+45
-17
@@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Create
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -20,6 +21,7 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -42,7 +44,9 @@ fun MessageBubble(
|
||||
edited: Boolean,
|
||||
isRead: Boolean,
|
||||
sendingStatus: SendingStatus,
|
||||
pinned: Boolean
|
||||
pinned: Boolean,
|
||||
important: Boolean,
|
||||
isSelected: Boolean
|
||||
) {
|
||||
val theme = LocalThemeConfig.current
|
||||
val backgroundColor = if (!isOut) {
|
||||
@@ -68,12 +72,15 @@ fun MessageBubble(
|
||||
)
|
||||
.then(if (theme.enableAnimations) Modifier.animateContentSize() else Modifier),
|
||||
) {
|
||||
val minDateContainerWidth = remember(edited, isOut) {
|
||||
val mainPart = if (edited) 50.dp else 30.dp
|
||||
val readIndicatorPart = if (isOut) 14.dp else 0.dp
|
||||
val pinnedIndicatorPart = if (pinned) 14.dp else 0.dp
|
||||
val minDateContainerWidth by remember(edited, isOut, pinned, important) {
|
||||
derivedStateOf {
|
||||
val mainPart = if (edited) 50.dp else 30.dp
|
||||
val readIndicatorPart = if (isOut) 14.dp else 0.dp
|
||||
val pinnedIndicatorPart = if (pinned) 14.dp else 0.dp
|
||||
val importantIndicatorPart = if (important) 14.dp else 0.dp
|
||||
|
||||
mainPart + readIndicatorPart + pinnedIndicatorPart
|
||||
mainPart + readIndicatorPart + pinnedIndicatorPart + importantIndicatorPart
|
||||
}
|
||||
}
|
||||
|
||||
val dateContainerWidth by animateDpAsState(
|
||||
@@ -82,17 +89,29 @@ fun MessageBubble(
|
||||
)
|
||||
|
||||
if (text != null) {
|
||||
Text(
|
||||
text = text,
|
||||
modifier = Modifier
|
||||
.padding(2.dp)
|
||||
.align(Alignment.Center)
|
||||
.padding(end = 4.dp)
|
||||
.padding(end = dateContainerWidth)
|
||||
.padding(end = 4.dp)
|
||||
.then(if (theme.enableAnimations) Modifier.animateContentSize() else Modifier),
|
||||
color = textColor
|
||||
)
|
||||
val textLambda: @Composable () -> Unit = remember {
|
||||
{
|
||||
Text(
|
||||
text = text,
|
||||
modifier = Modifier
|
||||
.padding(2.dp)
|
||||
.align(Alignment.Center)
|
||||
.padding(end = 4.dp)
|
||||
.padding(end = dateContainerWidth)
|
||||
.padding(end = 4.dp)
|
||||
.then(if (theme.enableAnimations) Modifier.animateContentSize() else Modifier),
|
||||
color = textColor
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (isSelected) {
|
||||
SelectionContainer {
|
||||
textLambda.invoke()
|
||||
}
|
||||
} else {
|
||||
textLambda.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
@@ -101,6 +120,14 @@ fun MessageBubble(
|
||||
.defaultMinSize(minWidth = dateContainerWidth)
|
||||
.then(if (theme.enableAnimations) Modifier.animateContentSize() else Modifier),
|
||||
) {
|
||||
if (important) {
|
||||
Icon(
|
||||
painter = painterResource(UiR.drawable.round_star_24),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(14.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
}
|
||||
if (pinned) {
|
||||
Icon(
|
||||
painter = painterResource(UiR.drawable.ic_round_push_pin_24),
|
||||
@@ -119,6 +146,7 @@ fun MessageBubble(
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
}
|
||||
|
||||
Text(
|
||||
text = date.orEmpty(),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
|
||||
+327
-84
@@ -1,5 +1,6 @@
|
||||
package dev.meloda.fast.messageshistory.presentation
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
@@ -39,6 +40,7 @@ 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.Checkbox
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
@@ -53,6 +55,7 @@ import androidx.compose.material3.TextField
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.minimumInteractiveComponentSize
|
||||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -64,7 +67,6 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -74,12 +76,12 @@ import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.HapticFeedbackConstantsCompat
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import coil.compose.AsyncImage
|
||||
@@ -93,22 +95,28 @@ import dev.meloda.fast.datastore.UserSettings
|
||||
import dev.meloda.fast.messageshistory.MessagesHistoryViewModel
|
||||
import dev.meloda.fast.messageshistory.MessagesHistoryViewModelImpl
|
||||
import dev.meloda.fast.messageshistory.model.ActionMode
|
||||
import dev.meloda.fast.messageshistory.model.MessageDialog
|
||||
import dev.meloda.fast.messageshistory.model.MessageOption
|
||||
import dev.meloda.fast.messageshistory.model.MessagesHistoryScreenState
|
||||
import dev.meloda.fast.messageshistory.model.UiItem
|
||||
import dev.meloda.fast.messageshistory.util.firstMessage
|
||||
import dev.meloda.fast.messageshistory.util.indexOfMessageByCmId
|
||||
import dev.meloda.fast.model.BaseError
|
||||
import dev.meloda.fast.model.api.domain.VkMessage
|
||||
import dev.meloda.fast.ui.basic.ContentAlpha
|
||||
import dev.meloda.fast.ui.basic.LocalContentAlpha
|
||||
import dev.meloda.fast.ui.components.ErrorView
|
||||
import dev.meloda.fast.ui.components.IconButton
|
||||
import dev.meloda.fast.ui.components.MaterialDialog
|
||||
import dev.meloda.fast.ui.components.SelectionType
|
||||
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||
import dev.meloda.fast.ui.util.ImmutableList
|
||||
import dev.meloda.fast.ui.util.ImmutableList.Companion.toImmutableList
|
||||
import dev.meloda.fast.ui.util.emptyImmutableList
|
||||
import dev.meloda.fast.ui.util.getImage
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import org.koin.compose.koinInject
|
||||
import java.util.concurrent.TimeUnit
|
||||
import dev.meloda.fast.ui.R as UiR
|
||||
|
||||
@Composable
|
||||
@@ -119,10 +127,12 @@ fun MessagesHistoryRoute(
|
||||
viewModel: MessagesHistoryViewModel = koinViewModel<MessagesHistoryViewModelImpl>()
|
||||
) {
|
||||
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
|
||||
val messages by viewModel.messages.collectAsStateWithLifecycle()
|
||||
val uiMessages by viewModel.uiMessages.collectAsStateWithLifecycle()
|
||||
val messageDialog by viewModel.messageDialog.collectAsStateWithLifecycle()
|
||||
val selectedMessages by viewModel.selectedMessages.collectAsStateWithLifecycle()
|
||||
val baseError by viewModel.baseError.collectAsStateWithLifecycle()
|
||||
val canPaginate by viewModel.canPaginate.collectAsStateWithLifecycle()
|
||||
val showMessageOptions by viewModel.showMessageOptions.collectAsStateWithLifecycle()
|
||||
val scrollIndex by viewModel.isNeedToScrollToIndex.collectAsStateWithLifecycle()
|
||||
|
||||
val userSettings: UserSettings = koinInject()
|
||||
@@ -130,8 +140,10 @@ fun MessagesHistoryRoute(
|
||||
|
||||
MessagesHistoryScreen(
|
||||
screenState = screenState,
|
||||
messages = messages.toImmutableList(),
|
||||
uiMessages = uiMessages.toImmutableList(),
|
||||
scrollIndex = scrollIndex,
|
||||
selectedMessages = ImmutableList.copyOf(selectedMessages),
|
||||
selectedMessages = selectedMessages.toImmutableList(),
|
||||
baseError = baseError,
|
||||
canPaginate = canPaginate,
|
||||
showEmojiButton = showEmojiButton,
|
||||
@@ -149,43 +161,300 @@ fun MessagesHistoryRoute(
|
||||
onMessageClicked = viewModel::onMessageClicked,
|
||||
onMessageLongClicked = viewModel::onMessageLongClicked,
|
||||
onPinnedMessageClicked = viewModel::onPinnedMessageClicked,
|
||||
onUnpinMessageButtonClicked = viewModel::onUnpinMessageClicked
|
||||
onUnpinMessageButtonClicked = viewModel::onUnpinMessageClicked,
|
||||
onDeleteSelectedButtonClicked = viewModel::onDeleteSelectedMessagesClicked
|
||||
)
|
||||
|
||||
if (showMessageOptions != null) {
|
||||
val message = showMessageOptions!!
|
||||
HandleDialogs(
|
||||
screenState = screenState,
|
||||
messageDialog = messageDialog,
|
||||
onConfirmed = viewModel::onDialogConfirmed,
|
||||
onCancelled = viewModel::onDialogCancelled,
|
||||
onDismissed = viewModel::onDialogDismissed,
|
||||
onItemPicked = viewModel::onDialogItemPicked
|
||||
)
|
||||
}
|
||||
|
||||
val messageOptions = mutableListOf(
|
||||
stringResource(UiR.string.message_context_action_reply),
|
||||
stringResource(UiR.string.message_context_action_forward)
|
||||
)
|
||||
@Composable
|
||||
fun HandleDialogs(
|
||||
screenState: MessagesHistoryScreenState,
|
||||
messageDialog: MessageDialog?,
|
||||
onConfirmed: (MessageDialog, Bundle) -> Unit = { _, _ -> },
|
||||
onCancelled: (MessageDialog) -> Unit = {},
|
||||
onDismissed: (MessageDialog) -> Unit = {},
|
||||
onItemPicked: (MessageDialog, Bundle) -> Unit = { _, _ -> }
|
||||
) {
|
||||
when (messageDialog) {
|
||||
null -> Unit
|
||||
|
||||
if (message.isPeerChat() && screenState.conversation.canChangePin) {
|
||||
messageOptions += stringResource(
|
||||
if (message.isPinned) UiR.string.message_context_action_unpin
|
||||
else UiR.string.message_context_action_pin
|
||||
is MessageDialog.MessageOptions -> {
|
||||
MessageOptionsDialog(
|
||||
screenState = screenState,
|
||||
message = messageDialog.message,
|
||||
onDismissed = { onDismissed(messageDialog) },
|
||||
onItemPicked = { bundle -> onItemPicked(messageDialog, bundle) }
|
||||
)
|
||||
}
|
||||
|
||||
messageOptions += stringResource(UiR.string.message_context_action_copy)
|
||||
messageOptions += stringResource(
|
||||
if (message.isImportant) UiR.string.message_context_action_unmark_as_important
|
||||
else UiR.string.message_context_action_mark_as_important
|
||||
)
|
||||
is MessageDialog.MessageDelete -> {
|
||||
MessageDeleteDialog(
|
||||
messages = listOf(messageDialog.message),
|
||||
onConfirmed = { onConfirmed(messageDialog, it) },
|
||||
onDismissed = { onDismissed(messageDialog) }
|
||||
)
|
||||
}
|
||||
|
||||
// if (!message.isOut) {
|
||||
// messageOptions += "Mark as spam"
|
||||
// }
|
||||
is MessageDialog.MessagesDelete -> {
|
||||
MessageDeleteDialog(
|
||||
messages = messageDialog.messages,
|
||||
onConfirmed = { onConfirmed(messageDialog, it) },
|
||||
onDismissed = { onDismissed(messageDialog) }
|
||||
)
|
||||
}
|
||||
|
||||
messageOptions += stringResource(UiR.string.message_context_action_delete)
|
||||
is MessageDialog.MessagePin,
|
||||
is MessageDialog.MessageUnpin -> {
|
||||
MessagePinStateDialog(
|
||||
pin = messageDialog is MessageDialog.MessagePin,
|
||||
onConfirmed = { onConfirmed(messageDialog, bundleOf()) },
|
||||
onDismissed = { onDismissed(messageDialog) }
|
||||
)
|
||||
}
|
||||
|
||||
MaterialDialog(
|
||||
onDismissRequest = viewModel::onMessageOptionsDialogDismissed,
|
||||
selectionType = SelectionType.None,
|
||||
items = ImmutableList.copyOf(messageOptions),
|
||||
confirmText = stringResource(UiR.string.ok)
|
||||
is MessageDialog.MessageMarkImportance -> {
|
||||
MessageImportanceDialog(
|
||||
important = messageDialog.isImportant,
|
||||
onConfirmed = { onConfirmed(messageDialog, bundleOf()) },
|
||||
onDismissed = { onDismissed(messageDialog) }
|
||||
)
|
||||
}
|
||||
|
||||
is MessageDialog.MessageSpam -> {
|
||||
MessageSpamDialog(
|
||||
spam = messageDialog.isSpam,
|
||||
onConfirmed = { onConfirmed(messageDialog, bundleOf()) },
|
||||
onDismissed = { onDismissed(messageDialog) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun MessageOptionsDialog(
|
||||
screenState: MessagesHistoryScreenState,
|
||||
message: VkMessage,
|
||||
onDismissed: () -> Unit = {},
|
||||
onItemPicked: (Bundle) -> Unit
|
||||
) {
|
||||
val options = mutableListOf<MessageOption>()
|
||||
if (message.isFailed()) {
|
||||
options += MessageOption.Retry
|
||||
} else {
|
||||
options += MessageOption.Reply
|
||||
options += MessageOption.ForwardHere
|
||||
options += MessageOption.Forward
|
||||
|
||||
if (message.isPeerChat() && screenState.conversation.canChangePin) {
|
||||
options += if (message.isPinned) MessageOption.Unpin else MessageOption.Pin
|
||||
}
|
||||
|
||||
if (!message.isRead(screenState.conversation)) {
|
||||
options += MessageOption.Read
|
||||
}
|
||||
|
||||
options += MessageOption.Copy
|
||||
|
||||
if (message.isOut) {
|
||||
val diff = System.currentTimeMillis() - message.date * 1000L
|
||||
if (diff - TimeUnit.DAYS.toMillis(1) <= 0) {
|
||||
options += MessageOption.Edit
|
||||
}
|
||||
}
|
||||
|
||||
options += if (message.isImportant) MessageOption.UnmarkAsImportant
|
||||
else MessageOption.MarkAsImportant
|
||||
|
||||
|
||||
if (!message.isOut) {
|
||||
options += if (message.isSpam) MessageOption.UnmarkAsSpam
|
||||
else MessageOption.MarkAsSpam
|
||||
}
|
||||
}
|
||||
|
||||
options += MessageOption.Delete
|
||||
|
||||
val messageOptions = options.map { option ->
|
||||
Triple(
|
||||
stringResource(option.titleResId),
|
||||
painterResource(option.iconResId),
|
||||
when {
|
||||
option in listOf(
|
||||
MessageOption.Delete,
|
||||
MessageOption.MarkAsSpam
|
||||
) -> MaterialTheme.colorScheme.error
|
||||
|
||||
else -> MaterialTheme.colorScheme.primary
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
MaterialDialog(onDismissRequest = onDismissed) {
|
||||
messageOptions
|
||||
.forEachIndexed { index, (title, painter, tintColor) ->
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Row {
|
||||
Text(text = title)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
}
|
||||
},
|
||||
leadingIcon = {
|
||||
Row {
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Icon(
|
||||
painter = painter,
|
||||
contentDescription = null,
|
||||
tint = tintColor
|
||||
)
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
onDismissed()
|
||||
val pickedOption = options[index]
|
||||
onItemPicked(bundleOf("option" to pickedOption))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MessageDeleteDialog(
|
||||
messages: List<VkMessage>,
|
||||
onConfirmed: (Bundle) -> Unit = {},
|
||||
onDismissed: () -> Unit = {},
|
||||
) {
|
||||
var forEveryone by remember {
|
||||
mutableStateOf(messages.all(VkMessage::isOut))
|
||||
}
|
||||
|
||||
val shouldBeDisabled by remember(messages) {
|
||||
mutableStateOf(messages.any(VkMessage::isFailed) || !messages.all(VkMessage::isOut))
|
||||
}
|
||||
|
||||
MaterialDialog(
|
||||
onDismissRequest = onDismissed,
|
||||
title = stringResource(UiR.string.delete_message_title),
|
||||
confirmText = stringResource(UiR.string.action_delete),
|
||||
confirmAction = {
|
||||
onConfirmed(
|
||||
bundleOf("everyone" to if (messages.all(VkMessage::isOut)) forEveryone else false)
|
||||
)
|
||||
},
|
||||
cancelText = stringResource(UiR.string.cancel),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.then(
|
||||
if (!shouldBeDisabled) {
|
||||
Modifier.clickable { forEveryone = !forEveryone }
|
||||
} else Modifier)
|
||||
.fillMaxWidth()
|
||||
.minimumInteractiveComponentSize()
|
||||
.padding(start = 24.dp, end = 16.dp)
|
||||
) {
|
||||
Checkbox(
|
||||
checked = forEveryone,
|
||||
onCheckedChange = null,
|
||||
enabled = !shouldBeDisabled
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
LocalContentAlpha(
|
||||
alpha = if (shouldBeDisabled) ContentAlpha.disabled
|
||||
else ContentAlpha.high
|
||||
) {
|
||||
Text(text = stringResource(UiR.string.delete_message_for_everyone))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MessagePinStateDialog(
|
||||
pin: Boolean,
|
||||
onConfirmed: () -> Unit = {},
|
||||
onDismissed: () -> Unit = {},
|
||||
) {
|
||||
MaterialDialog(
|
||||
onDismissRequest = onDismissed,
|
||||
title = stringResource(
|
||||
if (pin) UiR.string.pin_message_title
|
||||
else UiR.string.unpin_message_title
|
||||
),
|
||||
text = stringResource(
|
||||
if (pin) UiR.string.pin_message_text
|
||||
else UiR.string.unpin_message_text
|
||||
),
|
||||
confirmText = stringResource(
|
||||
if (pin) UiR.string.action_pin
|
||||
else UiR.string.action_unpin
|
||||
),
|
||||
confirmAction = onConfirmed,
|
||||
cancelText = stringResource(UiR.string.cancel)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MessageImportanceDialog(
|
||||
important: Boolean,
|
||||
onConfirmed: () -> Unit = {},
|
||||
onDismissed: () -> Unit = {},
|
||||
) {
|
||||
MaterialDialog(
|
||||
onDismissRequest = onDismissed,
|
||||
title = stringResource(
|
||||
if (important) UiR.string.important_message_title
|
||||
else UiR.string.unimportant_message_title
|
||||
),
|
||||
text = stringResource(
|
||||
if (important) UiR.string.important_message_text
|
||||
else UiR.string.unimportant_message_text
|
||||
),
|
||||
confirmText = stringResource(
|
||||
if (important) UiR.string.action_mark
|
||||
else UiR.string.action_unmark
|
||||
),
|
||||
confirmAction = onConfirmed,
|
||||
cancelText = stringResource(UiR.string.cancel)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MessageSpamDialog(
|
||||
spam: Boolean,
|
||||
onConfirmed: () -> Unit = {},
|
||||
onDismissed: () -> Unit = {},
|
||||
) {
|
||||
MaterialDialog(
|
||||
onDismissRequest = onDismissed,
|
||||
title = stringResource(
|
||||
if (spam) UiR.string.spam_message_title
|
||||
else UiR.string.unspam_message_title
|
||||
),
|
||||
text = stringResource(
|
||||
if (spam) UiR.string.spam_message_text
|
||||
else UiR.string.unspam_message_text
|
||||
),
|
||||
confirmText = stringResource(
|
||||
if (spam) UiR.string.action_mark
|
||||
else UiR.string.action_unmark
|
||||
),
|
||||
confirmAction = onConfirmed,
|
||||
cancelText = stringResource(UiR.string.cancel)
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(
|
||||
@@ -196,8 +465,10 @@ fun MessagesHistoryRoute(
|
||||
@Composable
|
||||
fun MessagesHistoryScreen(
|
||||
screenState: MessagesHistoryScreenState = MessagesHistoryScreenState.EMPTY,
|
||||
messages: ImmutableList<VkMessage> = emptyImmutableList(),
|
||||
uiMessages: ImmutableList<UiItem> = emptyImmutableList(),
|
||||
scrollIndex: Int? = null,
|
||||
selectedMessages: ImmutableList<Int> = ImmutableList.empty(),
|
||||
selectedMessages: ImmutableList<VkMessage> = emptyImmutableList(),
|
||||
baseError: BaseError? = null,
|
||||
canPaginate: Boolean = false,
|
||||
showEmojiButton: Boolean = false,
|
||||
@@ -215,7 +486,8 @@ fun MessagesHistoryScreen(
|
||||
onMessageClicked: (Int) -> Unit = {},
|
||||
onMessageLongClicked: (Int) -> Unit = {},
|
||||
onPinnedMessageClicked: (Int) -> Unit = {},
|
||||
onUnpinMessageButtonClicked: () -> Unit = {}
|
||||
onUnpinMessageButtonClicked: () -> Unit = {},
|
||||
onDeleteSelectedButtonClicked: () -> Unit = {}
|
||||
) {
|
||||
val view = LocalView.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
@@ -288,8 +560,8 @@ fun MessagesHistoryScreen(
|
||||
|
||||
val density = LocalDensity.current
|
||||
|
||||
val showReplyAction by remember(screenState) {
|
||||
mutableStateOf(selectedMessages.size == 1)
|
||||
val showReplyAction by remember(selectedMessages) {
|
||||
derivedStateOf { selectedMessages.size == 1 }
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
@@ -414,6 +686,12 @@ fun MessagesHistoryScreen(
|
||||
}
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(UiR.drawable.round_forward_24),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
IconButton(onClick = onDeleteSelectedButtonClicked) {
|
||||
Icon(
|
||||
painter = painterResource(UiR.drawable.round_delete_outline_24),
|
||||
contentDescription = null
|
||||
@@ -449,7 +727,7 @@ fun MessagesHistoryScreen(
|
||||
// TODO: 23-Mar-25, Danil Nikolaev: crash if no messages (ex. new chat)
|
||||
onChatMaterialsDropdownItemClicked(
|
||||
screenState.conversationId,
|
||||
screenState.messages.firstMessage().conversationMessageId
|
||||
uiMessages.values.firstMessage().conversationMessageId
|
||||
)
|
||||
},
|
||||
text = {
|
||||
@@ -483,7 +761,7 @@ fun MessagesHistoryScreen(
|
||||
)
|
||||
|
||||
val showHorizontalProgressBar by remember(screenState) {
|
||||
derivedStateOf { screenState.isLoading && screenState.messages.isNotEmpty() }
|
||||
derivedStateOf { screenState.isLoading && messages.isNotEmpty() }
|
||||
}
|
||||
if (showHorizontalProgressBar) {
|
||||
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
|
||||
@@ -493,51 +771,15 @@ fun MessagesHistoryScreen(
|
||||
}
|
||||
|
||||
if (!screenState.isLoading && pinnedMessage != null) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(56.dp)
|
||||
.clickable { onPinnedMessageClicked(pinnedMessage!!.id) }
|
||||
.padding(horizontal = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.rotate(45f)
|
||||
.alpha(0.5f),
|
||||
painter = painterResource(UiR.drawable.ic_round_push_pin_24),
|
||||
contentDescription = null
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text(
|
||||
text = screenState.pinnedTitle.orDots(),
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
screenState.pinnedSummary?.let { summary ->
|
||||
LocalContentAlpha(alpha = ContentAlpha.medium) {
|
||||
Text(text = summary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (screenState.conversation.canChangePin) {
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
|
||||
IconButton(onClick = onUnpinMessageButtonClicked) {
|
||||
Icon(
|
||||
modifier = Modifier.alpha(0.5f),
|
||||
imageVector = Icons.Rounded.Close,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
PinnedMessageContainer(
|
||||
modifier = Modifier,
|
||||
pinnedMessage = requireNotNull(pinnedMessage),
|
||||
title = screenState.pinnedTitle.orDots(),
|
||||
summary = screenState.pinnedSummary,
|
||||
canChangePin = screenState.conversation.canChangePin,
|
||||
onPinnedMessageClicked = onPinnedMessageClicked,
|
||||
onUnpinMessageButtonClicked = onUnpinMessageButtonClicked
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
@@ -551,16 +793,17 @@ fun MessagesHistoryScreen(
|
||||
.padding(bottom = padding.calculateBottomPadding()),
|
||||
) {
|
||||
MessagesList(
|
||||
modifier = Modifier.align(Alignment.BottomStart),
|
||||
hazeState = hazeState,
|
||||
listState = listState,
|
||||
hasPinnedMessage = pinnedMessage != null,
|
||||
immutableMessages = ImmutableList.copyOf(screenState.messages),
|
||||
uiMessages = uiMessages,
|
||||
isPaginating = screenState.isPaginating,
|
||||
messageBarHeight = messageBarHeight,
|
||||
onRequestScrollToCmId = { cmId ->
|
||||
coroutineScope.launch {
|
||||
listState.animateScrollToItem(
|
||||
index = screenState.messages.indexOfMessageByCmId(cmId)
|
||||
index = uiMessages.values.indexOfMessageByCmId(cmId)
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -775,7 +1018,7 @@ fun MessagesHistoryScreen(
|
||||
}
|
||||
|
||||
when {
|
||||
screenState.isLoading && screenState.messages.isEmpty() -> {
|
||||
screenState.isLoading && messages.values.isEmpty() -> {
|
||||
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -42,15 +42,15 @@ fun MessagesList(
|
||||
hasPinnedMessage: Boolean,
|
||||
hazeState: HazeState,
|
||||
listState: LazyListState,
|
||||
immutableMessages: ImmutableList<UiItem>,
|
||||
uiMessages: ImmutableList<UiItem>,
|
||||
isPaginating: Boolean,
|
||||
messageBarHeight: Dp,
|
||||
onRequestScrollToCmId: (cmId: Int) -> Unit = {},
|
||||
onMessageClicked: (Int) -> Unit = {},
|
||||
onMessageLongClicked: (Int) -> Unit = {}
|
||||
) {
|
||||
val messages = remember(immutableMessages) {
|
||||
immutableMessages.toList()
|
||||
val messages = remember(uiMessages) {
|
||||
uiMessages.toList()
|
||||
}
|
||||
val theme = LocalThemeConfig.current
|
||||
val view = LocalView.current
|
||||
|
||||
+3
-1
@@ -44,7 +44,9 @@ fun OutgoingMessageBubble(
|
||||
edited = message.isEdited,
|
||||
isRead = message.isRead,
|
||||
sendingStatus = message.sendingStatus,
|
||||
pinned = message.isPinned
|
||||
pinned = message.isPinned,
|
||||
important = message.isImportant,
|
||||
isSelected = message.isSelected
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
package dev.meloda.fast.messageshistory.presentation
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Close
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.meloda.fast.model.api.domain.VkMessage
|
||||
import dev.meloda.fast.ui.R
|
||||
import dev.meloda.fast.ui.basic.ContentAlpha
|
||||
import dev.meloda.fast.ui.basic.LocalContentAlpha
|
||||
import dev.meloda.fast.ui.components.IconButton
|
||||
|
||||
@Composable
|
||||
fun PinnedMessageContainer(
|
||||
modifier: Modifier = Modifier,
|
||||
pinnedMessage: VkMessage,
|
||||
title: String,
|
||||
summary: AnnotatedString?,
|
||||
canChangePin: Boolean,
|
||||
onPinnedMessageClicked: (Int) -> Unit = {},
|
||||
onUnpinMessageButtonClicked: () -> Unit = {}
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(56.dp)
|
||||
.clickable { onPinnedMessageClicked(pinnedMessage.id) }
|
||||
.padding(start = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.rotate(45f)
|
||||
.alpha(0.5f),
|
||||
painter = painterResource(R.drawable.ic_round_push_pin_24),
|
||||
contentDescription = null
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
summary?.let { summary ->
|
||||
LocalContentAlpha(alpha = ContentAlpha.medium) {
|
||||
Text(text = summary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (canChangePin) {
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
|
||||
IconButton(onClick = onUnpinMessageButtonClicked) {
|
||||
Icon(
|
||||
modifier = Modifier.alpha(0.5f),
|
||||
imageVector = Icons.Rounded.Close,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-3
@@ -96,7 +96,8 @@ fun VkMessage.asPresentation(
|
||||
showName: Boolean,
|
||||
prevMessage: VkMessage?,
|
||||
nextMessage: VkMessage?,
|
||||
showTimeInActionMessages: Boolean
|
||||
showTimeInActionMessages: Boolean,
|
||||
isSelected: Boolean
|
||||
): UiItem = when {
|
||||
action != null -> UiItem.ActionMessage(
|
||||
id = id,
|
||||
@@ -126,11 +127,13 @@ fun VkMessage.asPresentation(
|
||||
isEdited = updateTime != null,
|
||||
isRead = isRead(conversation),
|
||||
sendingStatus = when {
|
||||
isFailed() -> SendingStatus.FAILED
|
||||
id <= 0 -> SendingStatus.SENDING
|
||||
else -> SendingStatus.SENT
|
||||
},
|
||||
isSelected = false,
|
||||
isPinned = isPinned
|
||||
isSelected = isSelected,
|
||||
isPinned = isPinned,
|
||||
isImportant = isImportant
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+7
-3
@@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -139,8 +140,11 @@ fun EditTextAlert(
|
||||
cancelText = stringResource(id = R.string.cancel),
|
||||
actionInvokeDismiss = ActionInvokeDismiss.Always
|
||||
) {
|
||||
Row(modifier = Modifier.fillMaxWidth()) {
|
||||
Spacer(modifier = Modifier.width(20.dp))
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 20.dp)
|
||||
) {
|
||||
TextField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -155,8 +159,8 @@ fun EditTextAlert(
|
||||
placeholder = { Text(text = "Value") },
|
||||
shape = RoundedCornerShape(10.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.width(20.dp))
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
|
||||
Reference in New Issue
Block a user