[wip] chat materials; some experiments with local composition and blur

This commit is contained in:
2024-07-12 00:51:24 +03:00
parent c43278e4cf
commit fb76b46b22
46 changed files with 1210 additions and 717 deletions
@@ -231,13 +231,13 @@ class LongPollUpdatesParser(
Log.d("LongPollUpdatesParser", "$eventType: $event")
}
private suspend fun <T : LongPollEvent> loadNormalMessage(
private suspend inline fun <reified T : LongPollEvent> loadNormalMessage(
eventType: ApiEvent,
messageId: Int
): T? = suspendCoroutine {
): T? = suspendCoroutine { continuation ->
coroutineScope.launch(Dispatchers.IO) {
messagesUseCase.getById(
messageId = messageId,
messageIds = listOf(messageId),
extended = true,
fields = VkConstants.ALL_FIELDS
).listenValue(this) { state ->
@@ -245,20 +245,26 @@ class LongPollUpdatesParser(
error = { error ->
Log.e("LongPollUpdatesParser", "loadNormalMessage: error: $error")
},
success = { response ->
response?.let { message ->
VkMemoryCache[message.id] = message
messagesUseCase.storeMessage(message)
success = { messages ->
val message = messages.singleOrNull() ?: run {
continuation.resume(null)
return@listenValue
}
val resumeValue: LongPollEvent? = when (eventType) {
ApiEvent.MESSAGE_NEW -> LongPollEvent.VkMessageNewEvent(message)
ApiEvent.MESSAGE_EDIT -> LongPollEvent.VkMessageEditEvent(message)
VkMemoryCache[message.id] = message
messagesUseCase.storeMessage(message)
else -> null
val resumeValue: LongPollEvent? = when (eventType) {
ApiEvent.MESSAGE_NEW -> LongPollEvent.VkMessageNewEvent(message)
ApiEvent.MESSAGE_EDIT -> LongPollEvent.VkMessageEditEvent(message)
else -> {
continuation.resume(null)
null
}
}
resumeValue?.let { value -> it.resume(value as T) }
} ?: it.resume(null)
resumeValue?.let { value -> continuation.resume(value as T) }
}
)
}
@@ -0,0 +1,9 @@
package com.meloda.app.fast.data.api.messages
import com.meloda.app.fast.model.api.domain.VkConversation
import com.meloda.app.fast.model.api.domain.VkMessage
data class MessagesHistoryInfo(
val messages: List<VkMessage>,
val conversations: List<VkConversation>
)
@@ -1,16 +0,0 @@
package com.meloda.app.fast.data.api.messages
import com.meloda.app.fast.model.database.VkMessageEntity
interface MessagesLocalDataSource {
suspend fun getMessages(
conversationId: Int,
offset: Int?,
count: Int?
): List<VkMessageEntity>
suspend fun getMessage(messageId: Int): VkMessageEntity?
suspend fun storeMessages(messages: List<VkMessageEntity>)
}
@@ -1,24 +0,0 @@
package com.meloda.app.fast.data.api.messages
import com.meloda.app.fast.database.dao.MessageDao
import com.meloda.app.fast.model.database.VkMessageEntity
// TODO: 05/05/2024, Danil Nikolaev: use paging for room
class MessagesLocalDataSourceImpl(
private val messageDao: MessageDao
) : MessagesLocalDataSource {
override suspend fun getMessages(
conversationId: Int,
offset: Int?,
count: Int?
): List<VkMessageEntity> = messageDao.getAll(conversationId)
override suspend fun getMessage(
messageId: Int
): VkMessageEntity? = messageDao.getById(messageId)
override suspend fun storeMessages(messages: List<VkMessageEntity>) {
messageDao.insertAll(messages)
}
}
@@ -1,36 +0,0 @@
package com.meloda.app.fast.data.api.messages
import com.meloda.app.fast.model.api.domain.VkAttachment
import com.meloda.app.fast.model.api.domain.VkMessage
import com.meloda.app.fast.network.RestApiErrorDomain
import com.slack.eithernet.ApiResult
interface MessagesNetworkDataSource {
suspend fun getMessagesHistory(
conversationId: Int,
offset: Int?,
count: Int?,
): ApiResult<MessagesHistoryDomain, RestApiErrorDomain>
suspend fun getMessageById(
messagesIds: List<Int>,
extended: Boolean?,
fields: String?
): ApiResult<VkMessage, RestApiErrorDomain>
suspend fun send(
peerId: Int,
randomId: Int,
message: String?,
replyTo: Int?,
attachments: List<VkAttachment>?
): ApiResult<Int, RestApiErrorDomain>
suspend fun markAsRead(
peerId: Int,
startMessageId: Int?
): ApiResult<Int, RestApiErrorDomain>
suspend fun getMessage(messageId: Int): VkMessage?
}
@@ -1,164 +0,0 @@
package com.meloda.app.fast.data.api.messages
import com.meloda.app.fast.common.VkConstants
import com.meloda.app.fast.data.VkGroupsMap
import com.meloda.app.fast.data.VkMemoryCache
import com.meloda.app.fast.data.VkUsersMap
import com.meloda.app.fast.model.api.data.VkContactData
import com.meloda.app.fast.model.api.data.VkGroupData
import com.meloda.app.fast.model.api.data.VkUserData
import com.meloda.app.fast.model.api.data.asDomain
import com.meloda.app.fast.model.api.domain.VkAttachment
import com.meloda.app.fast.model.api.domain.VkConversation
import com.meloda.app.fast.model.api.domain.VkMessage
import com.meloda.app.fast.model.api.requests.MessagesGetByIdRequest
import com.meloda.app.fast.model.api.requests.MessagesGetHistoryRequest
import com.meloda.app.fast.model.api.requests.MessagesMarkAsReadRequest
import com.meloda.app.fast.model.api.requests.MessagesSendRequest
import com.meloda.app.fast.network.RestApiErrorDomain
import com.meloda.app.fast.network.mapApiDefault
import com.meloda.app.fast.network.mapApiResult
import com.meloda.app.fast.network.service.messages.MessagesService
import com.slack.eithernet.ApiResult
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class MessagesNetworkDataSourceImpl(
private val messagesService: MessagesService
) : MessagesNetworkDataSource {
override suspend fun getMessagesHistory(
conversationId: Int,
offset: Int?,
count: Int?
): ApiResult<MessagesHistoryDomain, RestApiErrorDomain> = withContext(Dispatchers.IO) {
val requestModel = MessagesGetHistoryRequest(
count = count,
offset = offset,
peerId = conversationId,
extended = true,
startMessageId = null,
rev = null,
fields = VkConstants.ALL_FIELDS
)
messagesService.getHistory(requestModel.map).mapApiResult(
successMapper = { apiResponse ->
val response = apiResponse.requireResponse()
val profilesList = response.profiles.orEmpty().map(VkUserData::mapToDomain)
val groupsList = response.groups.orEmpty().map(VkGroupData::mapToDomain)
val contactsList = response.contacts.orEmpty().map(VkContactData::mapToDomain)
val usersMap = VkUsersMap.forUsers(profilesList)
val groupsMap = VkGroupsMap.forGroups(groupsList)
VkMemoryCache.appendUsers(profilesList)
VkMemoryCache.appendGroups(groupsList)
VkMemoryCache.appendContacts(contactsList)
val messages = response.items.map { item ->
item.asDomain().let { message ->
message.copy(
user = usersMap.messageUser(message),
group = groupsMap.messageGroup(message),
actionUser = usersMap.messageActionUser(message),
actionGroup = groupsMap.messageActionGroup(message)
).also { VkMemoryCache[message.id] = it }
}
}
val conversations = response.conversations.orEmpty().map { item ->
val message = messages.firstOrNull { it.id == item.lastMessageId }
item.asDomain(message)
.let { conversation ->
conversation.copy(
user = usersMap.conversationUser(conversation),
group = groupsMap.conversationGroup(conversation)
).also { VkMemoryCache[conversation.id] = it }
}
}
MessagesHistoryDomain(
messages = messages,
conversations = conversations
)
},
errorMapper = { error ->
error?.toDomain()
}
)
}
override suspend fun getMessageById(
messagesIds: List<Int>,
extended: Boolean?,
fields: String?
): ApiResult<VkMessage, RestApiErrorDomain> = withContext(Dispatchers.IO) {
val requestModel = MessagesGetByIdRequest(
messagesIds = messagesIds,
extended = extended,
fields = fields
)
messagesService.getById(requestModel.map).mapApiResult(
successMapper = { apiResponse ->
val response = apiResponse.requireResponse()
val message = response.items.single()
val usersMap =
VkUsersMap.forUsers(response.profiles.orEmpty().map(VkUserData::mapToDomain))
val groupsMap =
VkGroupsMap.forGroups(response.groups.orEmpty().map(VkGroupData::mapToDomain))
message.asDomain().copy(
user = usersMap.messageUser(message),
group = groupsMap.messageGroup(message),
actionUser = usersMap.messageActionUser(message),
actionGroup = groupsMap.messageActionGroup(message)
)
},
errorMapper = { error -> error?.toDomain() }
)
}
override suspend fun send(
peerId: Int,
randomId: Int,
message: String?,
replyTo: Int?,
attachments: List<VkAttachment>?
): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
val requestModel = MessagesSendRequest(
peerId = peerId,
randomId = randomId,
message = message,
replyTo = replyTo,
attachments = attachments
)
messagesService.send(requestModel.map).mapApiDefault()
}
override suspend fun markAsRead(
peerId: Int,
startMessageId: Int?
): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
val requestModel = MessagesMarkAsReadRequest(
peerId = peerId,
startMessageId = startMessageId
)
messagesService.markAsRead(requestModel.map).mapApiDefault()
}
override suspend fun getMessage(messageId: Int): VkMessage? = withContext(Dispatchers.IO) {
// TODO: 05/05/2024, Danil Nikolaev: get message
null
}
}
data class MessagesHistoryDomain(
val messages: List<VkMessage>,
val conversations: List<VkConversation>
)
@@ -1,24 +1,24 @@
package com.meloda.app.fast.data.api.messages
import com.meloda.app.fast.model.api.domain.VkAttachment
import com.meloda.app.fast.model.api.domain.VkAttachmentHistoryMessage
import com.meloda.app.fast.model.api.domain.VkMessage
import com.meloda.app.fast.network.RestApiErrorDomain
import com.slack.eithernet.ApiResult
import kotlinx.coroutines.flow.Flow
interface MessagesRepository {
suspend fun getMessagesHistory(
suspend fun getHistory(
conversationId: Int,
offset: Int?,
count: Int?
): ApiResult<MessagesHistoryDomain, RestApiErrorDomain>
): ApiResult<MessagesHistoryInfo, RestApiErrorDomain>
suspend fun getMessageById(
suspend fun getById(
messagesIds: List<Int>,
extended: Boolean?,
fields: String?
): ApiResult<VkMessage, RestApiErrorDomain>
): ApiResult<List<VkMessage>, RestApiErrorDomain>
suspend fun send(
peerId: Int,
@@ -33,14 +33,16 @@ interface MessagesRepository {
startMessageId: Int?
): ApiResult<Int, RestApiErrorDomain>
suspend fun getMessage(messageId: Int): Flow<VkMessage?>
suspend fun getHistoryAttachments(
peerId: Int,
count: Int?,
offset: Int?,
attachmentTypes: List<String>,
conversationMessageId: Int
): ApiResult<List<VkAttachmentHistoryMessage>, RestApiErrorDomain>
suspend fun storeMessages(messages: List<VkMessage>)
// suspend fun getHistory(
// params: MessagesGetHistoryRequest
// ): ApiResult<MessagesGetHistoryResponse, RestApiErrorDomain>
// suspend fun markAsImportant(
// params: MessagesMarkAsImportantRequest
// ): ApiResult<List<Int>, RestApiErrorDomain>
@@ -1,56 +1,132 @@
package com.meloda.app.fast.data.api.messages
import com.meloda.app.fast.common.VkConstants
import com.meloda.app.fast.data.VkGroupsMap
import com.meloda.app.fast.data.VkMemoryCache
import com.meloda.app.fast.data.VkUsersMap
import com.meloda.app.fast.database.dao.MessageDao
import com.meloda.app.fast.model.api.data.VkAttachmentHistoryMessageData
import com.meloda.app.fast.model.api.data.VkContactData
import com.meloda.app.fast.model.api.data.VkGroupData
import com.meloda.app.fast.model.api.data.VkUserData
import com.meloda.app.fast.model.api.data.asDomain
import com.meloda.app.fast.model.api.domain.VkAttachment
import com.meloda.app.fast.model.api.domain.VkAttachmentHistoryMessage
import com.meloda.app.fast.model.api.domain.VkMessage
import com.meloda.app.fast.model.api.domain.asEntity
import com.meloda.app.fast.model.database.asExternalModel
import com.meloda.app.fast.model.api.requests.MessagesGetByIdRequest
import com.meloda.app.fast.model.api.requests.MessagesGetHistoryAttachmentsRequest
import com.meloda.app.fast.model.api.requests.MessagesGetHistoryRequest
import com.meloda.app.fast.model.api.requests.MessagesMarkAsReadRequest
import com.meloda.app.fast.model.api.requests.MessagesSendRequest
import com.meloda.app.fast.network.RestApiErrorDomain
import com.meloda.app.fast.network.mapApiDefault
import com.meloda.app.fast.network.mapApiResult
import com.meloda.app.fast.network.service.messages.MessagesService
import com.slack.eithernet.ApiResult
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withContext
// TODO: 05/05/2024, Danil Nikolaev: implement syncing
class MessagesRepositoryImpl(
private val networkDataSource: MessagesNetworkDataSource,
private val localDataSource: MessagesLocalDataSource
private val messagesService: MessagesService,
private val messageDao: MessageDao,
) : MessagesRepository {
override suspend fun getMessagesHistory(
override suspend fun getHistory(
conversationId: Int,
offset: Int?,
count: Int?
): ApiResult<MessagesHistoryDomain, RestApiErrorDomain> = withContext(Dispatchers.IO) {
// val localMessages = localDataSource.getMessages(
// conversationId = conversationId,
// offset = offset,
// count = count
// ).map(VkMessageEntity::asExternalModel)
//
// emit(localMessages)
//
// val networkMessages = networkDataSource.getMessagesHistory(
// conversationId = conversationId,
// offset = offset,
// count = count
// )
//
// emit(networkMessages)
): ApiResult<MessagesHistoryInfo, RestApiErrorDomain> = withContext(Dispatchers.IO) {
val requestModel = MessagesGetHistoryRequest(
count = count,
offset = offset,
peerId = conversationId,
extended = true,
startMessageId = null,
rev = null,
fields = VkConstants.ALL_FIELDS
)
networkDataSource.getMessagesHistory(conversationId, offset, count)
messagesService.getHistory(requestModel.map).mapApiResult(
successMapper = { apiResponse ->
val response = apiResponse.requireResponse()
val profilesList = response.profiles.orEmpty().map(VkUserData::mapToDomain)
val groupsList = response.groups.orEmpty().map(VkGroupData::mapToDomain)
val contactsList = response.contacts.orEmpty().map(VkContactData::mapToDomain)
val usersMap = VkUsersMap.forUsers(profilesList)
val groupsMap = VkGroupsMap.forGroups(groupsList)
VkMemoryCache.appendUsers(profilesList)
VkMemoryCache.appendGroups(groupsList)
VkMemoryCache.appendContacts(contactsList)
val messages = response.items.map { item ->
item.asDomain().let { message ->
message.copy(
user = usersMap.messageUser(message),
group = groupsMap.messageGroup(message),
actionUser = usersMap.messageActionUser(message),
actionGroup = groupsMap.messageActionGroup(message)
).also { VkMemoryCache[message.id] = it }
}
}
val conversations = response.conversations.orEmpty().map { item ->
val message = messages.firstOrNull { it.id == item.lastMessageId }
item.asDomain(message)
.let { conversation ->
conversation.copy(
user = usersMap.conversationUser(conversation),
group = groupsMap.conversationGroup(conversation)
).also { VkMemoryCache[conversation.id] = it }
}
}
MessagesHistoryInfo(
messages = messages,
conversations = conversations
)
},
errorMapper = { error ->
error?.toDomain()
}
)
}
override suspend fun getMessageById(
override suspend fun getById(
messagesIds: List<Int>,
extended: Boolean?,
fields: String?
): ApiResult<VkMessage, RestApiErrorDomain> = withContext(Dispatchers.IO) {
networkDataSource.getMessageById(
): ApiResult<List<VkMessage>, RestApiErrorDomain> = withContext(Dispatchers.IO) {
val requestModel = MessagesGetByIdRequest(
messagesIds = messagesIds,
extended = extended,
fields = fields
)
messagesService.getById(requestModel.map).mapApiResult(
successMapper = { apiResponse ->
val response = apiResponse.requireResponse()
val messages = response.items
val usersMap =
VkUsersMap.forUsers(response.profiles.orEmpty().map(VkUserData::mapToDomain))
val groupsMap =
VkGroupsMap.forGroups(response.groups.orEmpty().map(VkGroupData::mapToDomain))
messages.map { message ->
message.asDomain().copy(
user = usersMap.messageUser(message),
group = groupsMap.messageGroup(message),
actionUser = usersMap.messageActionUser(message),
actionGroup = groupsMap.messageActionGroup(message)
)
}
},
errorMapper = { error -> error?.toDomain() }
)
}
override suspend fun send(
@@ -60,54 +136,72 @@ class MessagesRepositoryImpl(
replyTo: Int?,
attachments: List<VkAttachment>?
): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
networkDataSource.send(
peerId,
randomId,
message,
replyTo,
attachments
val requestModel = MessagesSendRequest(
peerId = peerId,
randomId = randomId,
message = message,
replyTo = replyTo,
attachments = attachments
)
messagesService.send(requestModel.map).mapApiDefault()
}
override suspend fun markAsRead(
peerId: Int,
startMessageId: Int?
): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
networkDataSource.markAsRead(peerId, startMessageId)
val requestModel = MessagesMarkAsReadRequest(
peerId = peerId,
startMessageId = startMessageId
)
messagesService.markAsRead(requestModel.map).mapApiDefault()
}
override suspend fun getMessage(messageId: Int): Flow<VkMessage?> = flow {
val localMessage = localDataSource.getMessage(messageId)?.asExternalModel()
override suspend fun getHistoryAttachments(
peerId: Int,
count: Int?,
offset: Int?,
attachmentTypes: List<String>,
conversationMessageId: Int
): ApiResult<List<VkAttachmentHistoryMessage>, RestApiErrorDomain> =
withContext(Dispatchers.IO) {
val requestModel = MessagesGetHistoryAttachmentsRequest(
peerId = peerId,
extended = true,
count = count,
offset = offset,
preserveOrder = true,
attachmentTypes = attachmentTypes,
conversationMessageId = conversationMessageId,
fields = VkConstants.ALL_FIELDS
)
emit(localMessage)
messagesService.getHistoryAttachments(requestModel.map).mapApiResult(
successMapper = { apiResponse ->
val response = apiResponse.requireResponse()
val networkMessage = networkDataSource.getMessage(messageId)
val profilesList = response.profiles.orEmpty().map(VkUserData::mapToDomain)
val groupsList = response.groups.orEmpty().map(VkGroupData::mapToDomain)
val contactsList = response.contacts.orEmpty().map(VkContactData::mapToDomain)
emit(networkMessage)
}
VkMemoryCache.appendUsers(profilesList)
VkMemoryCache.appendGroups(groupsList)
VkMemoryCache.appendContacts(contactsList)
response.items.map(VkAttachmentHistoryMessageData::toDomain)
},
errorMapper = { error ->
error?.toDomain()
}
)
}
override suspend fun storeMessages(messages: List<VkMessage>) {
localDataSource.storeMessages(messages.map(VkMessage::asEntity))
messageDao.insertAll(messages.map(VkMessage::asEntity))
}
// override suspend fun getHistory(
// params: MessagesGetHistoryRequest
// ): ApiResult<MessagesGetHistoryResponse, RestApiErrorDomain> = withContext(Dispatchers.IO) {
// messagesService.getHistory(params.map).mapResult(
// successMapper = { response -> response.requireResponse() },
// errorMapper = { error -> error?.toDomain() }
// )
// }
//
// override suspend fun send(
// params: MessagesSendRequest
// ): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
// messagesService.send(params.map).mapResult(
// successMapper = { response -> response.requireResponse() },
// errorMapper = { error -> error?.toDomain() }
// )
// }
//
// override suspend fun markAsImportant(
// params: MessagesMarkAsImportantRequest
// ): ApiResult<List<Int>, RestApiErrorDomain> = withContext(Dispatchers.IO) {
@@ -153,24 +247,6 @@ class MessagesRepositoryImpl(
// )
// }
//
// override suspend fun getById(
// params: MessagesGetByIdRequest
// ): ApiResult<MessagesGetByIdResponse, RestApiErrorDomain> = withContext(Dispatchers.IO) {
// messagesService.getById(params.map).mapResult(
// successMapper = { response -> response.requireResponse() },
// errorMapper = { error -> error?.toDomain() }
// )
// }
//
// override suspend fun markAsRead(
// params: MessagesMarkAsReadRequest
// ): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
// messagesService.markAsRead(params.map).mapResult(
// successMapper = { response -> response.requireResponse() },
// errorMapper = { error -> error?.toDomain() }
// )
// }
//
// override suspend fun getChat(
// params: MessagesGetChatRequest
// ): ApiResult<VkChatData, RestApiErrorDomain> = withContext(Dispatchers.IO) {
@@ -199,3 +275,4 @@ class MessagesRepositoryImpl(
// )
// }
}
@@ -2,6 +2,7 @@ package com.meloda.app.fast.data.api.messages
import com.meloda.app.fast.data.State
import com.meloda.app.fast.model.api.domain.VkAttachment
import com.meloda.app.fast.model.api.domain.VkAttachmentHistoryMessage
import com.meloda.app.fast.model.api.domain.VkMessage
import kotlinx.coroutines.flow.Flow
@@ -11,15 +12,9 @@ interface MessagesUseCase {
conversationId: Int,
count: Int?,
offset: Int?
): Flow<State<MessagesHistoryDomain>>
): Flow<State<MessagesHistoryInfo>>
fun getById(
messageId: Int,
extended: Boolean?,
fields: String?
): Flow<State<VkMessage?>>
fun getByIds(
messageIds: List<Int>,
extended: Boolean?,
fields: String?
@@ -38,6 +33,14 @@ interface MessagesUseCase {
startMessageId: Int
): Flow<State<Int>>
fun getHistoryAttachments(
peerId: Int,
count: Int?,
offset: Int?,
attachmentTypes: List<String>,
conversationMessageId: Int
): Flow<State<List<VkAttachmentHistoryMessage>>>
suspend fun storeMessage(message: VkMessage)
suspend fun storeMessages(messages: List<VkMessage>)
}
@@ -15,10 +15,6 @@ import com.meloda.app.fast.data.api.friends.FriendsRepository
import com.meloda.app.fast.data.api.friends.FriendsRepositoryImpl
import com.meloda.app.fast.data.api.longpoll.LongPollRepository
import com.meloda.app.fast.data.api.longpoll.LongPollRepositoryImpl
import com.meloda.app.fast.data.api.messages.MessagesLocalDataSource
import com.meloda.app.fast.data.api.messages.MessagesLocalDataSourceImpl
import com.meloda.app.fast.data.api.messages.MessagesNetworkDataSource
import com.meloda.app.fast.data.api.messages.MessagesNetworkDataSourceImpl
import com.meloda.app.fast.data.api.messages.MessagesRepository
import com.meloda.app.fast.data.api.messages.MessagesRepositoryImpl
import com.meloda.app.fast.data.api.oauth.OAuthRepository
@@ -59,8 +55,6 @@ val dataModule = module {
singleOf(::LongPollRepositoryImpl) bind LongPollRepository::class
singleOf(::MessagesLocalDataSourceImpl) bind MessagesLocalDataSource::class
singleOf(::MessagesNetworkDataSourceImpl) bind MessagesNetworkDataSource::class
singleOf(::MessagesRepositoryImpl) bind MessagesRepository::class
singleOf(::OAuthRepositoryImpl) bind OAuthRepository::class
@@ -21,7 +21,7 @@ import com.meloda.app.fast.model.database.VkUserEntity
VkConversationEntity::class
],
version = 5
version = 6
)
@TypeConverters(Converters::class)
abstract class CacheDatabase : RoomDatabase() {
@@ -18,12 +18,14 @@ import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.core.view.WindowCompat
import com.meloda.app.fast.datastore.isUsingAmoledBackground
import com.meloda.app.fast.datastore.isUsingDynamicColors
import com.meloda.app.fast.datastore.model.ThemeConfig
import com.meloda.app.fast.datastore.selectedColorScheme
import com.meloda.app.fast.designsystem.colorschemes.ClassicColorScheme
import dev.chrisbanes.haze.HazeState
private val googleSansFonts = FontFamily(
Font(resId = R.font.google_sans_regular),
@@ -115,6 +117,14 @@ val LocalTheme = compositionLocalOf {
)
}
val LocalHazeState = compositionLocalOf {
HazeState()
}
val LocalBottomPadding = compositionLocalOf {
0.dp
}
@Composable
fun AppTheme(
predefinedColorScheme: ColorScheme? = null,
@@ -0,0 +1,25 @@
package com.meloda.app.fast.model.api.data
import com.meloda.app.fast.model.api.domain.VkAttachmentHistoryMessage
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class VkAttachmentHistoryMessageData(
@Json(name = "message_id") val messageId: Int,
@Json(name = "date") val date: Int,
@Json(name = "cmid") val conversationMessageId: Int,
@Json(name = "from_id") val fromId: Int,
@Json(name = "position") val position: Int,
@Json(name = "attachment") val attachment: VkAttachmentItemData
) {
fun toDomain(): VkAttachmentHistoryMessage = VkAttachmentHistoryMessage(
messageId = messageId,
conversationMessageId = conversationMessageId,
date = date,
fromId = fromId,
position = position,
attachment = attachment.toDomain()
)
}
@@ -1,5 +1,7 @@
package com.meloda.app.fast.model.api.data
import com.meloda.app.fast.model.api.domain.VkAttachment
import com.meloda.app.fast.model.api.domain.VkUnknownAttachment
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@@ -12,7 +14,7 @@ data class VkAttachmentItemData(
@Json(name = "doc") val file: VkFileData?,
@Json(name = "link") val link: VkLinkData?,
@Json(name = "mini_app") val miniApp: VkMiniAppData?,
@Json(name = "audio_message") val voiceMessage: VkAudioMessageData?,
@Json(name = "audio_message") val audioMessage: VkAudioMessageData?,
@Json(name = "sticker") val sticker: VkStickerData?,
@Json(name = "gift") val gift: VkGiftData?,
@Json(name = "wall") val wall: VkWallData?,
@@ -20,7 +22,7 @@ data class VkAttachmentItemData(
@Json(name = "poll") val poll: VkPollData?,
@Json(name = "wall_reply") val wallReply: VkWallReplyData?,
@Json(name = "call") val call: VkCallData?,
@Json(name = "group_call_in_progress") val groupCall: VkGroupCallData?,
@Json(name = "group_call_in_progress") val groupCallInProgress: VkGroupCallData?,
@Json(name = "curator") val curator: VkCuratorData?,
@Json(name = "event") val event: VkEventData?,
@Json(name = "story") val story: VkStoryData?,
@@ -30,6 +32,29 @@ data class VkAttachmentItemData(
@Json(name = "audio_playlist") val audioPlaylist: VkAudioPlaylistData?,
@Json(name = "podcast") val podcast: VkPodcastData?
) {
fun getPreparedType(): AttachmentType = AttachmentType.parse(type)
fun toDomain(): VkAttachment = when (AttachmentType.parse(type)) {
AttachmentType.UNKNOWN -> VkUnknownAttachment
AttachmentType.PHOTO -> photo?.toDomain()
AttachmentType.VIDEO -> video?.toDomain()
AttachmentType.AUDIO -> audio?.toDomain()
AttachmentType.FILE -> file?.toDomain()
AttachmentType.LINK -> link?.toDomain()
AttachmentType.MINI_APP -> miniApp?.toDomain()
AttachmentType.AUDIO_MESSAGE -> audioMessage?.toDomain()
AttachmentType.STICKER -> sticker?.toDomain()
AttachmentType.GIFT -> gift?.toDomain()
AttachmentType.WALL -> wall?.toDomain()
AttachmentType.GRAFFITI -> graffiti?.toDomain()
AttachmentType.POLL -> poll?.toDomain()
AttachmentType.WALL_REPLY -> wallReply?.toDomain()
AttachmentType.CALL -> call?.toDomain()
AttachmentType.GROUP_CALL_IN_PROGRESS -> groupCallInProgress?.toDomain()
AttachmentType.CURATOR -> curator?.toDomain()
AttachmentType.EVENT -> event?.toDomain()
AttachmentType.STORY -> story?.toDomain()
AttachmentType.WIDGET -> widget?.toDomain()
AttachmentType.ARTIST -> artist?.toDomain()
AttachmentType.AUDIO_PLAYLIST -> audioPlaylist?.toDomain()
AttachmentType.PODCAST -> podcast?.toDomain()
} ?: VkUnknownAttachment
}
@@ -1,6 +1,5 @@
package com.meloda.app.fast.model.api.data
import com.meloda.app.fast.model.api.domain.VkAttachment
import com.meloda.app.fast.model.api.domain.VkMessage
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@@ -60,6 +59,7 @@ data class VkMessageData(
fun VkMessageData.asDomain(): VkMessage = VkMessage(
id = id ?: -1,
conversationMessageId = conversationMessageId,
text = text.ifBlank { null },
isOut = out == 1,
peerId = peerId ?: -1,
@@ -75,134 +75,10 @@ fun VkMessageData.asDomain(): VkMessage = VkMessage(
important = important,
updateTime = updateTime,
forwards = fwdMessages.orEmpty().map(VkMessageData::asDomain),
attachments = parseAttachments(),
attachments = attachments.map(VkAttachmentItemData::toDomain),
replyMessage = replyMessage?.asDomain(),
user = null,
group = null,
actionUser = null,
actionGroup = null,
)
private fun VkMessageData.parseAttachments(): List<VkAttachment> {
if (attachments.isEmpty()) return emptyList()
val attachments = mutableListOf<VkAttachment>()
for (baseAttachment in this.attachments) {
when (baseAttachment.getPreparedType()) {
AttachmentType.UNKNOWN -> continue
AttachmentType.PHOTO -> {
val photo = baseAttachment.photo ?: continue
attachments += photo.toDomain()
}
AttachmentType.VIDEO -> {
val video = baseAttachment.video ?: continue
attachments += video.toDomain()
}
AttachmentType.AUDIO -> {
val audio = baseAttachment.audio ?: continue
attachments += audio.toDomain()
}
AttachmentType.FILE -> {
val file = baseAttachment.file ?: continue
attachments += file.toDomain()
}
AttachmentType.LINK -> {
val link = baseAttachment.link ?: continue
attachments += link.toDomain()
}
AttachmentType.MINI_APP -> {
val miniApp = baseAttachment.miniApp ?: continue
attachments += miniApp.toDomain()
}
AttachmentType.AUDIO_MESSAGE -> {
val voiceMessage = baseAttachment.voiceMessage ?: continue
attachments += voiceMessage.toDomain()
}
AttachmentType.STICKER -> {
val sticker = baseAttachment.sticker ?: continue
attachments += sticker.toDomain()
}
AttachmentType.GIFT -> {
val gift = baseAttachment.gift ?: continue
attachments += gift.toDomain()
}
AttachmentType.WALL -> {
val wall = baseAttachment.wall ?: continue
attachments += wall.toDomain()
}
AttachmentType.GRAFFITI -> {
val graffiti = baseAttachment.graffiti ?: continue
attachments += graffiti.toDomain()
}
AttachmentType.POLL -> {
val poll = baseAttachment.poll ?: continue
attachments += poll.toDomain()
}
AttachmentType.WALL_REPLY -> {
val wallReply = baseAttachment.wallReply ?: continue
attachments += wallReply.toDomain()
}
AttachmentType.CALL -> {
val call = baseAttachment.call ?: continue
attachments += call.toDomain()
}
AttachmentType.GROUP_CALL_IN_PROGRESS -> {
val groupCall = baseAttachment.groupCall ?: continue
attachments += groupCall.toDomain()
}
AttachmentType.CURATOR -> {
val curator = baseAttachment.curator ?: continue
attachments += curator.toDomain()
}
AttachmentType.EVENT -> {
val event = baseAttachment.event ?: continue
attachments += event.toDomain()
}
AttachmentType.STORY -> {
val story = baseAttachment.story ?: continue
attachments += story.toDomain()
}
AttachmentType.WIDGET -> {
val widget = baseAttachment.widget ?: continue
attachments += widget.toDomain()
}
AttachmentType.ARTIST -> {
val artist = baseAttachment.artist ?: continue
attachments += artist.toDomain()
val audios = baseAttachment.audios ?: continue
audios.map(VkAudioData::toDomain).let(attachments::addAll)
}
AttachmentType.AUDIO_PLAYLIST -> {
val audioPlaylist = baseAttachment.audioPlaylist ?: continue
attachments += audioPlaylist.toDomain()
}
AttachmentType.PODCAST -> {
val podcast = baseAttachment.podcast ?: continue
attachments += podcast.toDomain()
}
}
}
return attachments
}
@@ -28,6 +28,7 @@ data class VkPinnedMessageData(
fun mapToDomain(): VkMessage = VkMessage(
id = id ?: -1,
conversationMessageId = conversationMessageId,
text = text.ifBlank { null },
isOut = out == true,
peerId = peerId ?: -1,
@@ -0,0 +1,10 @@
package com.meloda.app.fast.model.api.domain
data class VkAttachmentHistoryMessage(
val messageId: Int,
val conversationMessageId: Int,
val date: Int,
val fromId: Int,
val position: Int,
val attachment: VkAttachment
)
@@ -4,6 +4,7 @@ import com.meloda.app.fast.model.database.VkMessageEntity
data class VkMessage(
val id: Int,
val conversationMessageId: Int,
val text: String?,
val isOut: Boolean,
val peerId: Int,
@@ -78,6 +79,7 @@ data class VkMessage(
fun VkMessage.asEntity(): VkMessageEntity = VkMessageEntity(
id = id,
conversationMessageId = conversationMessageId,
text = text,
isOut = isOut,
peerId = peerId,
@@ -0,0 +1,7 @@
package com.meloda.app.fast.model.api.domain
import com.meloda.app.fast.model.api.data.AttachmentType
data object VkUnknownAttachment : VkAttachment {
override val type: AttachmentType = AttachmentType.UNKNOWN
}
@@ -243,3 +243,27 @@ data class MessagesRemoveChatUserRequest(
"member_id" to memberId.toString()
)
}
data class MessagesGetHistoryAttachmentsRequest(
val peerId: Int,
val extended: Boolean?,
val count: Int?,
val offset: Int?,
val preserveOrder: Boolean?,
val attachmentTypes: List<String>,
val conversationMessageId: Int,
val fields: String?
) {
val map = mutableMapOf(
"peer_id" to peerId.toString(),
"attachment_types" to attachmentTypes.joinToString(","),
"cmid" to conversationMessageId.toString()
).apply {
extended?.let { this["extended"] = it.toString() }
count?.let { this["count"] = it.toString() }
offset?.let { this["offset"] = it.toString() }
preserveOrder?.let { this["preserve_order"] = it.toString() }
fields?.let { this["fields"] = it }
}
}
@@ -1,11 +1,13 @@
package com.meloda.app.fast.model.api.responses
import com.meloda.app.fast.model.api.data.VkAttachmentHistoryMessageData
import com.meloda.app.fast.model.api.data.VkChatMemberData
import com.meloda.app.fast.model.api.data.VkContactData
import com.meloda.app.fast.model.api.data.VkConversationData
import com.meloda.app.fast.model.api.data.VkGroupData
import com.meloda.app.fast.model.api.data.VkMessageData
import com.meloda.app.fast.model.api.data.VkUserData
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
@@ -33,3 +35,12 @@ data class MessagesGetConversationMembersResponse(
val profiles: List<VkUserData>?,
val groups: List<VkGroupData>?
)
@JsonClass(generateAdapter = true)
data class MessagesGetHistoryAttachmentsResponse(
@Json(name = "items") val items: List<VkAttachmentHistoryMessageData>,
@Json(name = "next_from") val nextFrom: String?,
@Json(name = "profiles") val profiles: List<VkUserData>?,
@Json(name = "groups") val groups: List<VkGroupData>?,
@Json(name = "contacts") val contacts: List<VkContactData>?
)
@@ -8,6 +8,7 @@ import com.meloda.app.fast.model.api.domain.VkMessage
@Entity(tableName = "messages")
data class VkMessageEntity(
@PrimaryKey val id: Int,
val conversationMessageId: Int,
val text: String?,
val isOut: Boolean,
val peerId: Int,
@@ -29,6 +30,7 @@ data class VkMessageEntity(
fun VkMessageEntity.asExternalModel(): VkMessage = VkMessage(
id = id,
conversationMessageId = conversationMessageId,
text = text,
isOut = isOut,
peerId = peerId,
@@ -2,6 +2,7 @@ package com.meloda.app.fast.network.service.messages
import com.meloda.app.fast.model.api.data.VkLongPollData
import com.meloda.app.fast.model.api.responses.MessagesGetByIdResponse
import com.meloda.app.fast.model.api.responses.MessagesGetHistoryAttachmentsResponse
import com.meloda.app.fast.model.api.responses.MessagesGetHistoryResponse
import com.meloda.app.fast.network.ApiResponse
import com.meloda.app.fast.network.RestApiError
@@ -42,6 +43,12 @@ interface MessagesService {
@FieldMap params: Map<String, String>
): ApiResult<ApiResponse<Int>, RestApiError>
@FormUrlEncoded
@POST(MessagesUrls.GET_HISTORY_ATTACHMENTS)
suspend fun getHistoryAttachments(
@FieldMap params: Map<String, String>
): ApiResult<ApiResponse<MessagesGetHistoryAttachmentsResponse>, RestApiError>
// @FormUrlEncoded
// @POST(MessagesUrls.MarkAsImportant)
// suspend fun markAsImportant(
@@ -18,4 +18,5 @@ object MessagesUrls {
const val GET_CHAT = "${AppConstants.URL_API}/messages.getChat"
const val GET_CONVERSATIONS_MEMBERS = "${AppConstants.URL_API}/messages.getConversationMembers"
const val REMOVE_CHAT_USER = "${AppConstants.URL_API}/messages.removeChatUser"
const val GET_HISTORY_ATTACHMENTS = "${AppConstants.URL_API}/messages.getHistoryAttachments"
}