forked from melod1n/fast-messenger
pinned message in messages history draft
This commit is contained in:
@@ -38,7 +38,7 @@ Unofficial messenger for russian social network VKontakte
|
|||||||
- [ ] Link
|
- [ ] Link
|
||||||
- [ ] TODO
|
- [ ] TODO
|
||||||
- [x] Send messages
|
- [x] Send messages
|
||||||
- [ ] Pinned message
|
- [x] Pinned message
|
||||||
- [ ] Pin & unpin messages
|
- [ ] Pin & unpin messages
|
||||||
- [ ] Reply to message
|
- [ ] Reply to message
|
||||||
- [ ] Delete message
|
- [ ] Delete message
|
||||||
|
|||||||
@@ -46,20 +46,22 @@ interface MessagesRepository {
|
|||||||
title: String?
|
title: String?
|
||||||
): ApiResult<Int, RestApiErrorDomain>
|
): ApiResult<Int, RestApiErrorDomain>
|
||||||
|
|
||||||
|
suspend fun pin(
|
||||||
|
peerId: Int,
|
||||||
|
messageId: Int?,
|
||||||
|
conversationMessageId: Int?
|
||||||
|
): ApiResult<VkMessage, RestApiErrorDomain>
|
||||||
|
|
||||||
|
suspend fun unpin(
|
||||||
|
peerId: Int
|
||||||
|
): ApiResult<Int, RestApiErrorDomain>
|
||||||
|
|
||||||
suspend fun storeMessages(messages: List<VkMessage>)
|
suspend fun storeMessages(messages: List<VkMessage>)
|
||||||
|
|
||||||
// suspend fun markAsImportant(
|
// suspend fun markAsImportant(
|
||||||
// params: MessagesMarkAsImportantRequest
|
// params: MessagesMarkAsImportantRequest
|
||||||
// ): ApiResult<List<Int>, RestApiErrorDomain>
|
// ): ApiResult<List<Int>, RestApiErrorDomain>
|
||||||
//
|
//
|
||||||
// suspend fun pin(
|
|
||||||
// params: MessagesPinMessageRequest
|
|
||||||
// ): ApiResult<VkMessageData, RestApiErrorDomain>
|
|
||||||
//
|
|
||||||
// suspend fun unpin(
|
|
||||||
// params: MessagesUnPinMessageRequest
|
|
||||||
// ): ApiResult<Unit, RestApiErrorDomain>
|
|
||||||
//
|
|
||||||
// suspend fun delete(
|
// suspend fun delete(
|
||||||
// params: MessagesDeleteRequest
|
// params: MessagesDeleteRequest
|
||||||
// ): ApiResult<Unit, RestApiErrorDomain>
|
// ): ApiResult<Unit, RestApiErrorDomain>
|
||||||
|
|||||||
+28
-19
@@ -20,7 +20,9 @@ import dev.meloda.fast.model.api.requests.MessagesGetByIdRequest
|
|||||||
import dev.meloda.fast.model.api.requests.MessagesGetHistoryAttachmentsRequest
|
import dev.meloda.fast.model.api.requests.MessagesGetHistoryAttachmentsRequest
|
||||||
import dev.meloda.fast.model.api.requests.MessagesGetHistoryRequest
|
import dev.meloda.fast.model.api.requests.MessagesGetHistoryRequest
|
||||||
import dev.meloda.fast.model.api.requests.MessagesMarkAsReadRequest
|
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.MessagesSendRequest
|
||||||
|
import dev.meloda.fast.model.api.requests.MessagesUnPinMessageRequest
|
||||||
import dev.meloda.fast.network.RestApiErrorDomain
|
import dev.meloda.fast.network.RestApiErrorDomain
|
||||||
import dev.meloda.fast.network.mapApiDefault
|
import dev.meloda.fast.network.mapApiDefault
|
||||||
import dev.meloda.fast.network.mapApiResult
|
import dev.meloda.fast.network.mapApiResult
|
||||||
@@ -216,6 +218,32 @@ class MessagesRepositoryImpl(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun pin(
|
||||||
|
peerId: Int,
|
||||||
|
messageId: Int?,
|
||||||
|
conversationMessageId: Int?
|
||||||
|
): ApiResult<VkMessage, RestApiErrorDomain> = withContext(Dispatchers.IO) {
|
||||||
|
val requestModel = MessagesPinMessageRequest(
|
||||||
|
peerId = peerId,
|
||||||
|
messageId = messageId,
|
||||||
|
conversationMessageId = conversationMessageId
|
||||||
|
)
|
||||||
|
|
||||||
|
messagesService.pin(requestModel.map).mapApiResult(
|
||||||
|
successMapper = { apiResponse ->
|
||||||
|
apiResponse.requireResponse().asDomain()
|
||||||
|
},
|
||||||
|
errorMapper = { error -> error?.toDomain() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun unpin(
|
||||||
|
peerId: Int
|
||||||
|
): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
|
||||||
|
val requestModel = MessagesUnPinMessageRequest(peerId = peerId)
|
||||||
|
messagesService.unpin(requestModel.map).mapApiDefault()
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun storeMessages(messages: List<VkMessage>) {
|
override suspend fun storeMessages(messages: List<VkMessage>) {
|
||||||
messageDao.insertAll(messages.map(VkMessage::asEntity))
|
messageDao.insertAll(messages.map(VkMessage::asEntity))
|
||||||
}
|
}
|
||||||
@@ -229,24 +257,6 @@ class MessagesRepositoryImpl(
|
|||||||
// )
|
// )
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// override suspend fun pin(
|
|
||||||
// params: MessagesPinMessageRequest
|
|
||||||
// ): ApiResult<VkMessageData, RestApiErrorDomain> = withContext(Dispatchers.IO) {
|
|
||||||
// messagesService.pin(params.map).mapResult(
|
|
||||||
// successMapper = { response -> response.requireResponse() },
|
|
||||||
// errorMapper = { error -> error?.toDomain() }
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// override suspend fun unpin(
|
|
||||||
// params: MessagesUnPinMessageRequest
|
|
||||||
// ): ApiResult<Unit, RestApiErrorDomain> = withContext(Dispatchers.IO) {
|
|
||||||
// messagesService.unpin(params.map).mapResult(
|
|
||||||
// successMapper = {},
|
|
||||||
// errorMapper = { error -> error?.toDomain() }
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// override suspend fun delete(
|
// override suspend fun delete(
|
||||||
// params: MessagesDeleteRequest
|
// params: MessagesDeleteRequest
|
||||||
// ): ApiResult<Unit, RestApiErrorDomain> = withContext(Dispatchers.IO) {
|
// ): ApiResult<Unit, RestApiErrorDomain> = withContext(Dispatchers.IO) {
|
||||||
@@ -293,4 +303,3 @@ class MessagesRepositoryImpl(
|
|||||||
// )
|
// )
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import dev.meloda.fast.model.database.VkUserEntity
|
|||||||
VkConversationEntity::class
|
VkConversationEntity::class
|
||||||
],
|
],
|
||||||
|
|
||||||
version = 7
|
version = 8
|
||||||
)
|
)
|
||||||
@TypeConverters(Converters::class)
|
@TypeConverters(Converters::class)
|
||||||
abstract class CacheDatabase : RoomDatabase() {
|
abstract class CacheDatabase : RoomDatabase() {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package dev.meloda.fast.database.di
|
|||||||
|
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
import dev.meloda.fast.database.AccountsDatabase
|
import dev.meloda.fast.database.AccountsDatabase
|
||||||
|
import dev.meloda.fast.database.CacheDatabase
|
||||||
import org.koin.core.scope.Scope
|
import org.koin.core.scope.Scope
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
@@ -12,7 +13,7 @@ val databaseModule = module {
|
|||||||
single { get<AccountsDatabase>().accountDao() }
|
single { get<AccountsDatabase>().accountDao() }
|
||||||
|
|
||||||
single {
|
single {
|
||||||
Room.databaseBuilder(get(), dev.meloda.fast.database.CacheDatabase::class.java, "cache")
|
Room.databaseBuilder(get(), CacheDatabase::class.java, "cache")
|
||||||
.fallbackToDestructiveMigration()
|
.fallbackToDestructiveMigration()
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
@@ -22,4 +23,4 @@ val databaseModule = module {
|
|||||||
single { cacheDB().conversationDao() }
|
single { cacheDB().conversationDao() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Scope.cacheDB(): dev.meloda.fast.database.CacheDatabase = get()
|
private fun Scope.cacheDB(): CacheDatabase = get()
|
||||||
|
|||||||
@@ -47,6 +47,16 @@ interface MessagesUseCase {
|
|||||||
title: String?
|
title: String?
|
||||||
): Flow<State<Int>>
|
): Flow<State<Int>>
|
||||||
|
|
||||||
|
fun pin(
|
||||||
|
peerId: Int,
|
||||||
|
messageId: Int?,
|
||||||
|
conversationMessageId: Int?
|
||||||
|
): Flow<State<VkMessage>>
|
||||||
|
|
||||||
|
fun unpin(
|
||||||
|
peerId: Int
|
||||||
|
): Flow<State<Int>>
|
||||||
|
|
||||||
suspend fun storeMessage(message: VkMessage)
|
suspend fun storeMessage(message: VkMessage)
|
||||||
suspend fun storeMessages(messages: List<VkMessage>)
|
suspend fun storeMessages(messages: List<VkMessage>)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,6 +108,30 @@ class MessagesUseCaseImpl(
|
|||||||
emit(newState)
|
emit(newState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun pin(
|
||||||
|
peerId: Int,
|
||||||
|
messageId: Int?,
|
||||||
|
conversationMessageId: Int?
|
||||||
|
): Flow<State<VkMessage>> = flow {
|
||||||
|
emit(State.Loading)
|
||||||
|
|
||||||
|
val newState = repository.pin(
|
||||||
|
peerId = peerId,
|
||||||
|
messageId = messageId,
|
||||||
|
conversationMessageId = conversationMessageId
|
||||||
|
).mapToState()
|
||||||
|
|
||||||
|
emit(newState)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun unpin(peerId: Int): Flow<State<Int>> = flow {
|
||||||
|
emit(State.Loading)
|
||||||
|
|
||||||
|
val newState = repository.unpin(peerId = peerId).mapToState()
|
||||||
|
|
||||||
|
emit(newState)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun storeMessage(message: VkMessage) {
|
override suspend fun storeMessage(message: VkMessage) {
|
||||||
repository.storeMessages(listOf(message))
|
repository.storeMessages(listOf(message))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package dev.meloda.fast.model.api.data
|
package dev.meloda.fast.model.api.data
|
||||||
|
|
||||||
import dev.meloda.fast.model.api.domain.VkMessage
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import dev.meloda.fast.model.api.domain.VkMessage
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class VkMessageData(
|
data class VkMessageData(
|
||||||
@@ -23,7 +23,9 @@ data class VkMessageData(
|
|||||||
@Json(name = "action") val action: Action?,
|
@Json(name = "action") val action: Action?,
|
||||||
@Json(name = "ttl") val ttl: Int?,
|
@Json(name = "ttl") val ttl: Int?,
|
||||||
@Json(name = "reply_message") val replyMessage: VkMessageData?,
|
@Json(name = "reply_message") val replyMessage: VkMessageData?,
|
||||||
@Json(name = "update_time") val updateTime: Int?
|
@Json(name = "update_time") val updateTime: Int?,
|
||||||
|
@Json(name = "is_pinned") val isPinned: Boolean?,
|
||||||
|
@Json(name = "pinned_at") val pinnedAt: Int?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
@@ -72,7 +74,7 @@ fun VkMessageData.asDomain(): VkMessage = VkMessage(
|
|||||||
actionConversationMessageId = action?.conversationMessageId,
|
actionConversationMessageId = action?.conversationMessageId,
|
||||||
actionMessage = action?.message,
|
actionMessage = action?.message,
|
||||||
geoType = geo?.type,
|
geoType = geo?.type,
|
||||||
important = important,
|
isImportant = important,
|
||||||
updateTime = updateTime,
|
updateTime = updateTime,
|
||||||
forwards = fwdMessages.orEmpty().map(VkMessageData::asDomain),
|
forwards = fwdMessages.orEmpty().map(VkMessageData::asDomain),
|
||||||
attachments = attachments.map(VkAttachmentItemData::toDomain),
|
attachments = attachments.map(VkAttachmentItemData::toDomain),
|
||||||
@@ -81,4 +83,6 @@ fun VkMessageData.asDomain(): VkMessage = VkMessage(
|
|||||||
group = null,
|
group = null,
|
||||||
actionUser = null,
|
actionUser = null,
|
||||||
actionGroup = null,
|
actionGroup = null,
|
||||||
|
pinnedAt = pinnedAt,
|
||||||
|
isPinned = isPinned == true
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ data class VkPinnedMessageData(
|
|||||||
actionConversationMessageId = action?.conversationMessageId,
|
actionConversationMessageId = action?.conversationMessageId,
|
||||||
actionMessage = action?.message,
|
actionMessage = action?.message,
|
||||||
geoType = geo?.type,
|
geoType = geo?.type,
|
||||||
important = important,
|
isImportant = important,
|
||||||
updateTime = updateTime,
|
updateTime = updateTime,
|
||||||
forwards = forwards.orEmpty().map(VkMessageData::asDomain),
|
forwards = forwards.orEmpty().map(VkMessageData::asDomain),
|
||||||
|
|
||||||
@@ -52,6 +52,7 @@ data class VkPinnedMessageData(
|
|||||||
group = null,
|
group = null,
|
||||||
actionUser = null,
|
actionUser = null,
|
||||||
actionGroup = null,
|
actionGroup = null,
|
||||||
|
pinnedAt = null,
|
||||||
|
isPinned = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ data class VkMessage(
|
|||||||
val actionMessage: String?,
|
val actionMessage: String?,
|
||||||
|
|
||||||
val updateTime: Int?,
|
val updateTime: Int?,
|
||||||
|
val pinnedAt: Int?,
|
||||||
val important: Boolean = false,
|
val isPinned: Boolean,
|
||||||
|
val isImportant: Boolean = false,
|
||||||
|
|
||||||
val forwards: List<VkMessage>?,
|
val forwards: List<VkMessage>?,
|
||||||
val attachments: List<VkAttachment>?,
|
val attachments: List<VkAttachment>?,
|
||||||
@@ -91,10 +92,12 @@ fun VkMessage.asEntity(): VkMessageEntity = VkMessageEntity(
|
|||||||
actionConversationMessageId = actionConversationMessageId,
|
actionConversationMessageId = actionConversationMessageId,
|
||||||
actionMessage = actionMessage,
|
actionMessage = actionMessage,
|
||||||
updateTime = updateTime,
|
updateTime = updateTime,
|
||||||
important = important,
|
important = isImportant,
|
||||||
forwardIds = forwards.orEmpty().map(VkMessage::id),
|
forwardIds = forwards.orEmpty().map(VkMessage::id),
|
||||||
// TODO: 05/05/2024, Danil Nikolaev: save attachments
|
// TODO: 05/05/2024, Danil Nikolaev: save attachments
|
||||||
attachments = emptyList(),
|
attachments = emptyList(),
|
||||||
replyMessageId = replyMessage?.id,
|
replyMessageId = replyMessage?.id,
|
||||||
geoType = geoType
|
geoType = geoType,
|
||||||
|
pinnedAt = pinnedAt,
|
||||||
|
isPinned = isPinned,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ data class VkMessageEntity(
|
|||||||
val forwardIds: List<Int>?,
|
val forwardIds: List<Int>?,
|
||||||
val attachments: List<String>?, // TODO: 01/05/2024, Danil Nikolaev: how to store???
|
val attachments: List<String>?, // TODO: 01/05/2024, Danil Nikolaev: how to store???
|
||||||
val replyMessageId: Int?,
|
val replyMessageId: Int?,
|
||||||
val geoType: String?
|
val geoType: String?,
|
||||||
|
val pinnedAt: Int?,
|
||||||
|
val isPinned: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
fun VkMessageEntity.asExternalModel(): VkMessage = VkMessage(
|
fun VkMessageEntity.asExternalModel(): VkMessage = VkMessage(
|
||||||
@@ -43,7 +45,7 @@ fun VkMessageEntity.asExternalModel(): VkMessage = VkMessage(
|
|||||||
actionConversationMessageId = actionConversationMessageId,
|
actionConversationMessageId = actionConversationMessageId,
|
||||||
actionMessage = actionMessage,
|
actionMessage = actionMessage,
|
||||||
updateTime = updateTime,
|
updateTime = updateTime,
|
||||||
important = important,
|
isImportant = important,
|
||||||
forwards = emptyList(),//forwards.orEmpty().map(VkMessageEntity::asExternalModel),
|
forwards = emptyList(),//forwards.orEmpty().map(VkMessageEntity::asExternalModel),
|
||||||
// TODO: 05/05/2024, Danil Nikolaev: restore attachments
|
// TODO: 05/05/2024, Danil Nikolaev: restore attachments
|
||||||
attachments = attachments.orEmpty().map { VkUnknownAttachment },
|
attachments = attachments.orEmpty().map { VkUnknownAttachment },
|
||||||
@@ -53,4 +55,6 @@ fun VkMessageEntity.asExternalModel(): VkMessage = VkMessage(
|
|||||||
group = null,
|
group = null,
|
||||||
actionUser = null,
|
actionUser = null,
|
||||||
actionGroup = null,
|
actionGroup = null,
|
||||||
|
pinnedAt = pinnedAt,
|
||||||
|
isPinned = isPinned
|
||||||
)
|
)
|
||||||
|
|||||||
+13
-12
@@ -2,6 +2,7 @@ package dev.meloda.fast.network.service.messages
|
|||||||
|
|
||||||
import com.slack.eithernet.ApiResult
|
import com.slack.eithernet.ApiResult
|
||||||
import dev.meloda.fast.model.api.data.VkLongPollData
|
import dev.meloda.fast.model.api.data.VkLongPollData
|
||||||
|
import dev.meloda.fast.model.api.data.VkMessageData
|
||||||
import dev.meloda.fast.model.api.responses.MessagesCreateChatResponse
|
import dev.meloda.fast.model.api.responses.MessagesCreateChatResponse
|
||||||
import dev.meloda.fast.model.api.responses.MessagesGetByIdResponse
|
import dev.meloda.fast.model.api.responses.MessagesGetByIdResponse
|
||||||
import dev.meloda.fast.model.api.responses.MessagesGetHistoryAttachmentsResponse
|
import dev.meloda.fast.model.api.responses.MessagesGetHistoryAttachmentsResponse
|
||||||
@@ -56,6 +57,18 @@ interface MessagesService {
|
|||||||
@FieldMap params: Map<String, String>
|
@FieldMap params: Map<String, String>
|
||||||
): ApiResult<ApiResponse<MessagesCreateChatResponse>, RestApiError>
|
): ApiResult<ApiResponse<MessagesCreateChatResponse>, RestApiError>
|
||||||
|
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST(MessagesUrls.PIN)
|
||||||
|
suspend fun pin(
|
||||||
|
@FieldMap params: Map<String, String>
|
||||||
|
): ApiResult<ApiResponse<VkMessageData>, RestApiError>
|
||||||
|
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST(MessagesUrls.UNPIN)
|
||||||
|
suspend fun unpin(
|
||||||
|
@FieldMap params: Map<String, String>
|
||||||
|
): ApiResult<ApiResponse<Int>, RestApiError>
|
||||||
|
|
||||||
// @FormUrlEncoded
|
// @FormUrlEncoded
|
||||||
// @POST(MessagesUrls.MarkAsImportant)
|
// @POST(MessagesUrls.MarkAsImportant)
|
||||||
// suspend fun markAsImportant(
|
// suspend fun markAsImportant(
|
||||||
@@ -63,18 +76,6 @@ interface MessagesService {
|
|||||||
// ): ApiResult<ApiResponse<List<Int>>, RestApiError>
|
// ): ApiResult<ApiResponse<List<Int>>, RestApiError>
|
||||||
//
|
//
|
||||||
// @FormUrlEncoded
|
// @FormUrlEncoded
|
||||||
// @POST(MessagesUrls.Pin)
|
|
||||||
// suspend fun pin(
|
|
||||||
// @FieldMap params: Map<String, String>
|
|
||||||
// ): ApiResult<ApiResponse<VkMessageData>, RestApiError>
|
|
||||||
//
|
|
||||||
// @FormUrlEncoded
|
|
||||||
// @POST(MessagesUrls.Unpin)
|
|
||||||
// suspend fun unpin(
|
|
||||||
// @FieldMap params: Map<String, String>
|
|
||||||
// ): ApiResult<ApiResponse<Unit>, RestApiError>
|
|
||||||
//
|
|
||||||
// @FormUrlEncoded
|
|
||||||
// @POST(MessagesUrls.Delete)
|
// @POST(MessagesUrls.Delete)
|
||||||
// suspend fun delete(
|
// suspend fun delete(
|
||||||
// @FieldMap params: Map<String, String>
|
// @FieldMap params: Map<String, String>
|
||||||
|
|||||||
@@ -6,13 +6,16 @@
|
|||||||
<string name="yes">Да</string>
|
<string name="yes">Да</string>
|
||||||
<string name="no">Нет</string>
|
<string name="no">Нет</string>
|
||||||
<string name="message_context_action_reply">Ответить</string>
|
<string name="message_context_action_reply">Ответить</string>
|
||||||
|
<string name="message_context_action_forward">Переслать</string>
|
||||||
<string name="message_context_action_mark_as_important">Пометить как важное</string>
|
<string name="message_context_action_mark_as_important">Пометить как важное</string>
|
||||||
<string name="message_context_action_unmark_as_important">Помететить как не важное</string>
|
<string name="message_context_action_unmark_as_important">Пометить как не важное</string>
|
||||||
<string name="time_format">Время: %1$s</string>
|
<string name="time_format">Время: %1$s</string>
|
||||||
|
<string name="message_context_action_unmark_as_spam">Помеьиьб как не спам</string>
|
||||||
<string name="message_context_action_pin">Закрепить</string>
|
<string name="message_context_action_pin">Закрепить</string>
|
||||||
<string name="message_context_action_unpin">Открепить</string>
|
<string name="message_context_action_unpin">Открепить</string>
|
||||||
<string name="message_context_action_edit">Изменить</string>
|
<string name="message_context_action_edit">Изменить</string>
|
||||||
<string name="message_context_action_delete">Удалить</string>
|
<string name="message_context_action_delete">Удалить</string>
|
||||||
|
<string name="message_context_action_copy">Скопировать</string>
|
||||||
<string name="confirm_delete_message">Удалить сообщение?</string>
|
<string name="confirm_delete_message">Удалить сообщение?</string>
|
||||||
<string name="message_delete_for_all">Для всех</string>
|
<string name="message_delete_for_all">Для всех</string>
|
||||||
<string name="message_mark_as_spam">Пометить как спам</string>
|
<string name="message_mark_as_spam">Пометить как спам</string>
|
||||||
@@ -229,4 +232,5 @@
|
|||||||
<string name="chat_attachment_music">Музыка</string>
|
<string name="chat_attachment_music">Музыка</string>
|
||||||
<string name="chat_attachment_files">Файлы</string>
|
<string name="chat_attachment_files">Файлы</string>
|
||||||
<string name="chat_attachment_links">Ссылки</string>
|
<string name="chat_attachment_links">Ссылки</string>
|
||||||
|
<string name="message_context_action_mark_as_spam">Пометить как спам</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -124,14 +124,19 @@
|
|||||||
<string name="sign_out_confirm">Signing out will delete all data related to this account from this device. Continue?</string>
|
<string name="sign_out_confirm">Signing out will delete all data related to this account from this device. Continue?</string>
|
||||||
<string name="yes">Yes</string>
|
<string name="yes">Yes</string>
|
||||||
<string name="no">No</string>
|
<string name="no">No</string>
|
||||||
|
<string name="time_format">Time: %1$s</string>
|
||||||
|
|
||||||
<string name="message_context_action_reply">Reply</string>
|
<string name="message_context_action_reply">Reply</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_mark_as_important">Mark as important</string>
|
||||||
<string name="message_context_action_unmark_as_important">Unmark as important</string>
|
<string name="message_context_action_unmark_as_important">Unmark as important</string>
|
||||||
<string name="time_format">Time: %1$s</string>
|
<string name="message_context_action_mark_as_spam">Mark as spam</string>
|
||||||
|
<string name="message_context_action_unmark_as_spam">Unmark as spam</string>
|
||||||
<string name="message_context_action_pin">Pin</string>
|
<string name="message_context_action_pin">Pin</string>
|
||||||
<string name="message_context_action_unpin">Unpin</string>
|
<string name="message_context_action_unpin">Unpin</string>
|
||||||
<string name="message_context_action_edit">Edit</string>
|
<string name="message_context_action_edit">Edit</string>
|
||||||
<string name="message_context_action_delete">Delete</string>
|
<string name="message_context_action_delete">Delete</string>
|
||||||
|
<string name="message_context_action_copy">Copy</string>
|
||||||
|
|
||||||
<string name="confirm_delete_message">Delete the message?</string>
|
<string name="confirm_delete_message">Delete the message?</string>
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -170,6 +170,6 @@ class ChatMaterialsViewModelImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val LOAD_COUNT = 200
|
const val LOAD_COUNT = 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+80
-7
@@ -1,8 +1,6 @@
|
|||||||
package dev.meloda.fast.messageshistory
|
package dev.meloda.fast.messageshistory
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.ui.text.TextRange
|
import androidx.compose.ui.text.TextRange
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import androidx.compose.ui.text.input.TextFieldValue
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
@@ -54,13 +52,18 @@ interface MessagesHistoryViewModel {
|
|||||||
val screenState: StateFlow<MessagesHistoryScreenState>
|
val screenState: StateFlow<MessagesHistoryScreenState>
|
||||||
val selectedMessages: StateFlow<List<Int>>
|
val selectedMessages: StateFlow<List<Int>>
|
||||||
|
|
||||||
|
val isNeedToScrollToIndex: StateFlow<Int?>
|
||||||
|
|
||||||
val baseError: StateFlow<BaseError?>
|
val baseError: StateFlow<BaseError?>
|
||||||
val imagesToPreload: StateFlow<List<String>>
|
val imagesToPreload: StateFlow<List<String>>
|
||||||
|
|
||||||
val currentOffset: StateFlow<Int>
|
val showMessageOptions: StateFlow<VkMessage?>
|
||||||
|
|
||||||
|
val currentOffset: StateFlow<Int>
|
||||||
val canPaginate: StateFlow<Boolean>
|
val canPaginate: StateFlow<Boolean>
|
||||||
|
|
||||||
|
fun onScrolledToIndex()
|
||||||
|
|
||||||
fun onCloseButtonClicked()
|
fun onCloseButtonClicked()
|
||||||
fun onRefresh()
|
fun onRefresh()
|
||||||
fun onAttachmentButtonClicked()
|
fun onAttachmentButtonClicked()
|
||||||
@@ -72,10 +75,12 @@ interface MessagesHistoryViewModel {
|
|||||||
|
|
||||||
fun onMessageClicked(messageId: Int)
|
fun onMessageClicked(messageId: Int)
|
||||||
fun onMessageLongClicked(messageId: Int)
|
fun onMessageLongClicked(messageId: Int)
|
||||||
|
fun onMessageOptionsDialogDismissed()
|
||||||
|
fun onPinnedMessageClicked(messageId: Int)
|
||||||
|
fun onUnpinMessageClicked()
|
||||||
}
|
}
|
||||||
|
|
||||||
class MessagesHistoryViewModelImpl(
|
class MessagesHistoryViewModelImpl(
|
||||||
private val applicationContext: Context,
|
|
||||||
private val messagesUseCase: MessagesUseCase,
|
private val messagesUseCase: MessagesUseCase,
|
||||||
private val conversationsUseCase: ConversationsUseCase,
|
private val conversationsUseCase: ConversationsUseCase,
|
||||||
private val resourceProvider: ResourceProvider,
|
private val resourceProvider: ResourceProvider,
|
||||||
@@ -88,9 +93,13 @@ class MessagesHistoryViewModelImpl(
|
|||||||
override val screenState = MutableStateFlow(MessagesHistoryScreenState.EMPTY)
|
override val screenState = MutableStateFlow(MessagesHistoryScreenState.EMPTY)
|
||||||
override val selectedMessages = MutableStateFlow<List<Int>>(emptyList())
|
override val selectedMessages = MutableStateFlow<List<Int>>(emptyList())
|
||||||
|
|
||||||
|
override val isNeedToScrollToIndex = MutableStateFlow<Int?>(null)
|
||||||
|
|
||||||
override val baseError = MutableStateFlow<BaseError?>(null)
|
override val baseError = MutableStateFlow<BaseError?>(null)
|
||||||
override val imagesToPreload = MutableStateFlow<List<String>>(emptyList())
|
override val imagesToPreload = MutableStateFlow<List<String>>(emptyList())
|
||||||
|
|
||||||
|
override val showMessageOptions = MutableStateFlow<VkMessage?>(null)
|
||||||
|
|
||||||
override val currentOffset = MutableStateFlow(0)
|
override val currentOffset = MutableStateFlow(0)
|
||||||
|
|
||||||
override val canPaginate = MutableStateFlow(false)
|
override val canPaginate = MutableStateFlow(false)
|
||||||
@@ -120,6 +129,10 @@ class MessagesHistoryViewModelImpl(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onScrolledToIndex() {
|
||||||
|
isNeedToScrollToIndex.setValue { null }
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCloseButtonClicked() {
|
override fun onCloseButtonClicked() {
|
||||||
screenState.setValue { old ->
|
screenState.setValue { old ->
|
||||||
old.copy(
|
old.copy(
|
||||||
@@ -211,7 +224,9 @@ class MessagesHistoryViewModelImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(applicationContext, "Click", Toast.LENGTH_SHORT).show()
|
messages.value.firstOrNull { it.id == currentMessage.id }?.let { message ->
|
||||||
|
showMessageOptions.setValue { message }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,6 +255,62 @@ class MessagesHistoryViewModelImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onMessageOptionsDialogDismissed() {
|
||||||
|
showMessageOptions.setValue { null }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPinnedMessageClicked(messageId: Int) {
|
||||||
|
val messageIndex = screenState.value.messages.indexOfFirstOrNull {
|
||||||
|
it is UiItem.Message && it.id == messageId
|
||||||
|
}
|
||||||
|
|
||||||
|
if (messageIndex == null) { // сообщения нет в списке
|
||||||
|
// pizdets
|
||||||
|
} else {
|
||||||
|
isNeedToScrollToIndex.setValue { messageIndex }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUnpinMessageClicked() {
|
||||||
|
// TODO: 27.03.2025, Danil Nikolaev: confirmation alert
|
||||||
|
val pinnedMessageId = screenState.value.pinnedMessage?.id ?: return
|
||||||
|
unpinMessage(pinnedMessageId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unpinMessage(messageId: Int) {
|
||||||
|
val messageIndex = screenState.value.messages.indexOfFirstOrNull {
|
||||||
|
it is UiItem.Message && it.id == messageId
|
||||||
|
}
|
||||||
|
|
||||||
|
messagesUseCase.unpin(screenState.value.conversationId)
|
||||||
|
.listenValue(viewModelScope) { state ->
|
||||||
|
state.processState(
|
||||||
|
error = ::handleError,
|
||||||
|
success = {
|
||||||
|
var newState = screenState.value.copy(
|
||||||
|
pinnedMessage = null,
|
||||||
|
conversation = screenState.value.conversation.copy(
|
||||||
|
pinnedMessage = null,
|
||||||
|
pinnedMessageId = null
|
||||||
|
),
|
||||||
|
pinnedSummary = null,
|
||||||
|
pinnedTitle = null
|
||||||
|
)
|
||||||
|
|
||||||
|
if (messageIndex != null) {
|
||||||
|
val newMessages = screenState.value.messages.toMutableList()
|
||||||
|
val currentMessage: UiItem.Message =
|
||||||
|
newMessages[messageIndex] as UiItem.Message
|
||||||
|
newMessages[messageIndex] = currentMessage.copy(isPinned = false)
|
||||||
|
newState = newState.copy(messages = newMessages)
|
||||||
|
}
|
||||||
|
|
||||||
|
screenState.setValue { old -> newState }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleNewMessage(event: LongPollParsedEvent.NewMessage) {
|
private fun handleNewMessage(event: LongPollParsedEvent.NewMessage) {
|
||||||
val message = event.message
|
val message = event.message
|
||||||
|
|
||||||
@@ -543,7 +614,7 @@ class MessagesHistoryViewModelImpl(
|
|||||||
actionConversationMessageId = null,
|
actionConversationMessageId = null,
|
||||||
actionMessage = null,
|
actionMessage = null,
|
||||||
updateTime = null,
|
updateTime = null,
|
||||||
important = false,
|
isImportant = false,
|
||||||
forwards = null,
|
forwards = null,
|
||||||
attachments = null,
|
attachments = null,
|
||||||
replyMessage = null,
|
replyMessage = null,
|
||||||
@@ -551,7 +622,9 @@ class MessagesHistoryViewModelImpl(
|
|||||||
user = VkMemoryCache.getUser(UserConfig.userId),
|
user = VkMemoryCache.getUser(UserConfig.userId),
|
||||||
group = null,
|
group = null,
|
||||||
actionUser = null,
|
actionUser = null,
|
||||||
actionGroup = null
|
actionGroup = null,
|
||||||
|
isPinned = false,
|
||||||
|
pinnedAt = null
|
||||||
)
|
)
|
||||||
sendingMessages += newMessage
|
sendingMessages += newMessage
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -24,8 +24,9 @@ sealed class UiItem(
|
|||||||
val avatar: UiImage,
|
val avatar: UiImage,
|
||||||
val isEdited: Boolean,
|
val isEdited: Boolean,
|
||||||
val isRead: Boolean,
|
val isRead: Boolean,
|
||||||
val sendingStatus: SendingStatus = SendingStatus.SENT,
|
val sendingStatus: SendingStatus,
|
||||||
val isSelected: Boolean = false
|
val isSelected: Boolean,
|
||||||
|
val isPinned: Boolean
|
||||||
) : UiItem(id, conversationMessageId)
|
) : UiItem(id, conversationMessageId)
|
||||||
|
|
||||||
data class ActionMessage(
|
data class ActionMessage(
|
||||||
|
|||||||
+5
-3
@@ -27,13 +27,15 @@ fun ActionMessageItem(
|
|||||||
Text(
|
Text(
|
||||||
text = item.text,
|
text = item.text,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.padding(horizontal = 32.dp)
|
.padding(
|
||||||
|
horizontal = 32.dp,
|
||||||
|
vertical = 4.dp
|
||||||
|
)
|
||||||
.clip(RoundedCornerShape(12.dp))
|
.clip(RoundedCornerShape(12.dp))
|
||||||
.then(
|
.then(
|
||||||
if (item.actionCmId != null) {
|
if (item.actionCmId != null) {
|
||||||
Modifier.clickable(onClick = onClick)
|
Modifier.clickable(onClick = onClick)
|
||||||
}
|
} else Modifier
|
||||||
else Modifier
|
|
||||||
)
|
)
|
||||||
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(5.dp))
|
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(5.dp))
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
|||||||
+5
-3
@@ -1,6 +1,7 @@
|
|||||||
package dev.meloda.fast.messageshistory.presentation
|
package dev.meloda.fast.messageshistory.presentation
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -33,10 +34,10 @@ fun IncomingMessageBubble(
|
|||||||
message: UiItem.Message,
|
message: UiItem.Message,
|
||||||
animate: Boolean,
|
animate: Boolean,
|
||||||
) {
|
) {
|
||||||
Row(modifier = modifier.fillMaxWidth()) {
|
Row(modifier = modifier.fillMaxWidth().then(if (animate) Modifier.animateContentSize() else Modifier),) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth(0.75f)
|
.fillMaxWidth(0.85f)
|
||||||
.padding(start = 16.dp),
|
.padding(start = 16.dp),
|
||||||
verticalAlignment = Alignment.Bottom,
|
verticalAlignment = Alignment.Bottom,
|
||||||
horizontalArrangement = Arrangement.Start
|
horizontalArrangement = Arrangement.Start
|
||||||
@@ -82,7 +83,8 @@ fun IncomingMessageBubble(
|
|||||||
edited = message.isEdited,
|
edited = message.isEdited,
|
||||||
animate = animate,
|
animate = animate,
|
||||||
isRead = message.isRead,
|
isRead = message.isRead,
|
||||||
sendingStatus = message.sendingStatus
|
sendingStatus = message.sendingStatus,
|
||||||
|
pinned = message.isPinned
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-2
@@ -25,6 +25,7 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -40,7 +41,8 @@ fun MessageBubble(
|
|||||||
edited: Boolean,
|
edited: Boolean,
|
||||||
animate: Boolean,
|
animate: Boolean,
|
||||||
isRead: Boolean,
|
isRead: Boolean,
|
||||||
sendingStatus: SendingStatus
|
sendingStatus: SendingStatus,
|
||||||
|
pinned: Boolean
|
||||||
) {
|
) {
|
||||||
val backgroundColor = if (!isOut) {
|
val backgroundColor = if (!isOut) {
|
||||||
MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)
|
MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp)
|
||||||
@@ -63,12 +65,14 @@ fun MessageBubble(
|
|||||||
horizontal = 8.dp,
|
horizontal = 8.dp,
|
||||||
vertical = 6.dp
|
vertical = 6.dp
|
||||||
)
|
)
|
||||||
|
.then(if (animate) Modifier.animateContentSize() else Modifier),
|
||||||
) {
|
) {
|
||||||
val minDateContainerWidth = remember(edited, isOut) {
|
val minDateContainerWidth = remember(edited, isOut) {
|
||||||
val mainPart = if (edited) 50.dp else 30.dp
|
val mainPart = if (edited) 50.dp else 30.dp
|
||||||
val readIndicatorPart = if (isOut) 14.dp else 0.dp
|
val readIndicatorPart = if (isOut) 14.dp else 0.dp
|
||||||
|
val pinnedIndicatorPart = if (pinned) 14.dp else 0.dp
|
||||||
|
|
||||||
mainPart + readIndicatorPart
|
mainPart + readIndicatorPart + pinnedIndicatorPart
|
||||||
}
|
}
|
||||||
|
|
||||||
val dateContainerWidth by animateDpAsState(
|
val dateContainerWidth by animateDpAsState(
|
||||||
@@ -94,7 +98,18 @@ fun MessageBubble(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.BottomEnd)
|
.align(Alignment.BottomEnd)
|
||||||
.defaultMinSize(minWidth = dateContainerWidth)
|
.defaultMinSize(minWidth = dateContainerWidth)
|
||||||
|
.then(if (animate) Modifier.animateContentSize() else Modifier),
|
||||||
) {
|
) {
|
||||||
|
if (pinned) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(UiR.drawable.ic_round_push_pin_24),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(14.dp)
|
||||||
|
.rotate(45f)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
|
}
|
||||||
if (edited) {
|
if (edited) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Rounded.Create,
|
imageVector = Icons.Rounded.Create,
|
||||||
|
|||||||
+75
-6
@@ -64,6 +64,7 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
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
|
||||||
@@ -100,6 +101,8 @@ import dev.meloda.fast.ui.basic.ContentAlpha
|
|||||||
import dev.meloda.fast.ui.basic.LocalContentAlpha
|
import dev.meloda.fast.ui.basic.LocalContentAlpha
|
||||||
import dev.meloda.fast.ui.components.ErrorView
|
import dev.meloda.fast.ui.components.ErrorView
|
||||||
import dev.meloda.fast.ui.components.IconButton
|
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.theme.LocalThemeConfig
|
||||||
import dev.meloda.fast.ui.util.ImmutableList
|
import dev.meloda.fast.ui.util.ImmutableList
|
||||||
import dev.meloda.fast.ui.util.getImage
|
import dev.meloda.fast.ui.util.getImage
|
||||||
@@ -119,18 +122,22 @@ fun MessagesHistoryRoute(
|
|||||||
val selectedMessages by viewModel.selectedMessages.collectAsStateWithLifecycle()
|
val selectedMessages by viewModel.selectedMessages.collectAsStateWithLifecycle()
|
||||||
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 showMessageOptions by viewModel.showMessageOptions.collectAsStateWithLifecycle()
|
||||||
|
val scrollIndex by viewModel.isNeedToScrollToIndex.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
val userSettings: UserSettings = koinInject()
|
val userSettings: UserSettings = koinInject()
|
||||||
val showEmojiButton by userSettings.showEmojiButton.collectAsStateWithLifecycle()
|
val showEmojiButton by userSettings.showEmojiButton.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
MessagesHistoryScreen(
|
MessagesHistoryScreen(
|
||||||
screenState = screenState,
|
screenState = screenState,
|
||||||
|
scrollIndex = scrollIndex,
|
||||||
selectedMessages = ImmutableList.copyOf(selectedMessages),
|
selectedMessages = ImmutableList.copyOf(selectedMessages),
|
||||||
baseError = baseError,
|
baseError = baseError,
|
||||||
canPaginate = canPaginate,
|
canPaginate = canPaginate,
|
||||||
showEmojiButton = showEmojiButton,
|
showEmojiButton = showEmojiButton,
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onClose = viewModel::onCloseButtonClicked,
|
onClose = viewModel::onCloseButtonClicked,
|
||||||
|
onScrolledToIndex = viewModel::onScrolledToIndex,
|
||||||
onSessionExpiredLogOutButtonClicked = { onError(BaseError.SessionExpired) },
|
onSessionExpiredLogOutButtonClicked = { onError(BaseError.SessionExpired) },
|
||||||
onChatMaterialsDropdownItemClicked = onChatMaterialsDropdownItemClicked,
|
onChatMaterialsDropdownItemClicked = onChatMaterialsDropdownItemClicked,
|
||||||
onRefresh = viewModel::onRefresh,
|
onRefresh = viewModel::onRefresh,
|
||||||
@@ -140,8 +147,45 @@ fun MessagesHistoryRoute(
|
|||||||
onActionButtonClicked = viewModel::onActionButtonClicked,
|
onActionButtonClicked = viewModel::onActionButtonClicked,
|
||||||
onEmojiButtonLongClicked = viewModel::onEmojiButtonLongClicked,
|
onEmojiButtonLongClicked = viewModel::onEmojiButtonLongClicked,
|
||||||
onMessageClicked = viewModel::onMessageClicked,
|
onMessageClicked = viewModel::onMessageClicked,
|
||||||
onMessageLongClicked = viewModel::onMessageLongClicked
|
onMessageLongClicked = viewModel::onMessageLongClicked,
|
||||||
|
onPinnedMessageClicked = viewModel::onPinnedMessageClicked,
|
||||||
|
onUnpinMessageButtonClicked = viewModel::onUnpinMessageClicked
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (showMessageOptions != null) {
|
||||||
|
val message = showMessageOptions!!
|
||||||
|
|
||||||
|
val messageOptions = mutableListOf(
|
||||||
|
stringResource(UiR.string.message_context_action_reply),
|
||||||
|
stringResource(UiR.string.message_context_action_forward)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (message.isPeerChat() && screenState.conversation.canChangePin) {
|
||||||
|
messageOptions += stringResource(
|
||||||
|
if (message.isPinned) UiR.string.message_context_action_unpin
|
||||||
|
else UiR.string.message_context_action_pin
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
// if (!message.isOut) {
|
||||||
|
// messageOptions += "Mark as spam"
|
||||||
|
// }
|
||||||
|
|
||||||
|
messageOptions += stringResource(UiR.string.message_context_action_delete)
|
||||||
|
|
||||||
|
MaterialDialog(
|
||||||
|
onDismissRequest = viewModel::onMessageOptionsDialogDismissed,
|
||||||
|
selectionType = SelectionType.None,
|
||||||
|
items = ImmutableList.copyOf(messageOptions),
|
||||||
|
confirmText = stringResource(UiR.string.ok)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(
|
@OptIn(
|
||||||
@@ -152,12 +196,14 @@ fun MessagesHistoryRoute(
|
|||||||
@Composable
|
@Composable
|
||||||
fun MessagesHistoryScreen(
|
fun MessagesHistoryScreen(
|
||||||
screenState: MessagesHistoryScreenState = MessagesHistoryScreenState.EMPTY,
|
screenState: MessagesHistoryScreenState = MessagesHistoryScreenState.EMPTY,
|
||||||
|
scrollIndex: Int? = null,
|
||||||
selectedMessages: ImmutableList<Int> = ImmutableList.empty(),
|
selectedMessages: ImmutableList<Int> = ImmutableList.empty(),
|
||||||
baseError: BaseError? = null,
|
baseError: BaseError? = null,
|
||||||
canPaginate: Boolean = false,
|
canPaginate: Boolean = false,
|
||||||
showEmojiButton: Boolean = false,
|
showEmojiButton: Boolean = false,
|
||||||
onBack: () -> Unit = {},
|
onBack: () -> Unit = {},
|
||||||
onClose: () -> Unit = {},
|
onClose: () -> Unit = {},
|
||||||
|
onScrolledToIndex: () -> Unit = {},
|
||||||
onSessionExpiredLogOutButtonClicked: () -> Unit = {},
|
onSessionExpiredLogOutButtonClicked: () -> Unit = {},
|
||||||
onChatMaterialsDropdownItemClicked: (peerId: Int, conversationMessageId: Int) -> Unit = { _, _ -> },
|
onChatMaterialsDropdownItemClicked: (peerId: Int, conversationMessageId: Int) -> Unit = { _, _ -> },
|
||||||
onRefresh: () -> Unit = {},
|
onRefresh: () -> Unit = {},
|
||||||
@@ -167,7 +213,9 @@ fun MessagesHistoryScreen(
|
|||||||
onActionButtonClicked: () -> Unit = {},
|
onActionButtonClicked: () -> Unit = {},
|
||||||
onEmojiButtonLongClicked: () -> Unit = {},
|
onEmojiButtonLongClicked: () -> Unit = {},
|
||||||
onMessageClicked: (Int) -> Unit = {},
|
onMessageClicked: (Int) -> Unit = {},
|
||||||
onMessageLongClicked: (Int) -> Unit = {}
|
onMessageLongClicked: (Int) -> Unit = {},
|
||||||
|
onPinnedMessageClicked: (Int) -> Unit = {},
|
||||||
|
onUnpinMessageButtonClicked: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
val view = LocalView.current
|
val view = LocalView.current
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
@@ -175,6 +223,15 @@ fun MessagesHistoryScreen(
|
|||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
val hazeState = remember { HazeState() }
|
val hazeState = remember { HazeState() }
|
||||||
|
|
||||||
|
LaunchedEffect(scrollIndex) {
|
||||||
|
if (scrollIndex != null) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
listState.animateScrollToItem(scrollIndex)
|
||||||
|
onScrolledToIndex()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BackHandler(
|
BackHandler(
|
||||||
enabled = selectedMessages.isNotEmpty(),
|
enabled = selectedMessages.isNotEmpty(),
|
||||||
onBack = onClose
|
onBack = onClose
|
||||||
@@ -440,14 +497,14 @@ fun MessagesHistoryScreen(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(56.dp)
|
.height(56.dp)
|
||||||
.clickable {
|
.clickable { onPinnedMessageClicked(pinnedMessage!!.id) }
|
||||||
|
|
||||||
}
|
|
||||||
.padding(horizontal = 16.dp),
|
.padding(horizontal = 16.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.rotate(45f),
|
modifier = Modifier
|
||||||
|
.rotate(45f)
|
||||||
|
.alpha(0.5f),
|
||||||
painter = painterResource(UiR.drawable.ic_round_push_pin_24),
|
painter = painterResource(UiR.drawable.ic_round_push_pin_24),
|
||||||
contentDescription = null
|
contentDescription = null
|
||||||
)
|
)
|
||||||
@@ -468,6 +525,18 @@ fun MessagesHistoryScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
HorizontalDivider()
|
HorizontalDivider()
|
||||||
}
|
}
|
||||||
|
|||||||
+12
@@ -92,6 +92,12 @@ fun MessagesList(
|
|||||||
when (item) {
|
when (item) {
|
||||||
is UiItem.ActionMessage -> {
|
is UiItem.ActionMessage -> {
|
||||||
ActionMessageItem(
|
ActionMessageItem(
|
||||||
|
modifier = Modifier.then(
|
||||||
|
if (enableAnimations) Modifier.animateItem(
|
||||||
|
fadeInSpec = null,
|
||||||
|
fadeOutSpec = null
|
||||||
|
) else Modifier
|
||||||
|
),
|
||||||
item = item,
|
item = item,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (item.actionCmId != null) {
|
if (item.actionCmId != null) {
|
||||||
@@ -112,6 +118,12 @@ fun MessagesList(
|
|||||||
|
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.then(
|
||||||
|
if (enableAnimations) Modifier.animateItem(
|
||||||
|
fadeInSpec = null,
|
||||||
|
fadeOutSpec = null
|
||||||
|
) else Modifier
|
||||||
|
)
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
if (AppSettings.General.enableHaptic) {
|
if (AppSettings.General.enableHaptic) {
|
||||||
|
|||||||
+5
-3
@@ -1,5 +1,6 @@
|
|||||||
package dev.meloda.fast.messageshistory.presentation
|
package dev.meloda.fast.messageshistory.presentation
|
||||||
|
|
||||||
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -19,14 +20,14 @@ fun OutgoingMessageBubble(
|
|||||||
animate: Boolean
|
animate: Boolean
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth().then(if (animate) Modifier.animateContentSize() else Modifier),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.End
|
horizontalArrangement = Arrangement.End
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(end = 16.dp)
|
.padding(end = 16.dp)
|
||||||
.fillMaxWidth(0.75f),
|
.fillMaxWidth(0.85f),
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
horizontalAlignment = Alignment.End,
|
horizontalAlignment = Alignment.End,
|
||||||
) {
|
) {
|
||||||
@@ -38,7 +39,8 @@ fun OutgoingMessageBubble(
|
|||||||
edited = message.isEdited,
|
edited = message.isEdited,
|
||||||
animate = animate,
|
animate = animate,
|
||||||
isRead = message.isRead,
|
isRead = message.isRead,
|
||||||
sendingStatus = message.sendingStatus
|
sendingStatus = message.sendingStatus,
|
||||||
|
pinned = message.isPinned
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -128,7 +128,9 @@ fun VkMessage.asPresentation(
|
|||||||
sendingStatus = when {
|
sendingStatus = when {
|
||||||
id <= 0 -> SendingStatus.SENDING
|
id <= 0 -> SendingStatus.SENDING
|
||||||
else -> SendingStatus.SENT
|
else -> SendingStatus.SENT
|
||||||
}
|
},
|
||||||
|
isSelected = false,
|
||||||
|
isPinned = isPinned
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user