diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e9daefec..f97e537e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -82,7 +82,7 @@ dependencies { implementation(projects.feature.auth) implementation(projects.feature.chatmaterials) - implementation(projects.feature.conversations) + implementation(projects.feature.convos) implementation(projects.feature.languagepicker) implementation(projects.feature.messageshistory) implementation(projects.feature.photoviewer) diff --git a/app/src/main/kotlin/dev/meloda/fast/common/di/ApplicationModule.kt b/app/src/main/kotlin/dev/meloda/fast/common/di/ApplicationModule.kt index eae975f4..e102184e 100644 --- a/app/src/main/kotlin/dev/meloda/fast/common/di/ApplicationModule.kt +++ b/app/src/main/kotlin/dev/meloda/fast/common/di/ApplicationModule.kt @@ -16,8 +16,8 @@ import dev.meloda.fast.common.LongPollControllerImpl import dev.meloda.fast.common.provider.Provider import dev.meloda.fast.common.provider.ResourceProvider import dev.meloda.fast.common.provider.ResourceProviderImpl -import dev.meloda.fast.conversations.di.conversationsModule -import dev.meloda.fast.conversations.di.createChatModule +import dev.meloda.fast.convos.di.convosModule +import dev.meloda.fast.convos.di.createChatModule import dev.meloda.fast.domain.di.domainModule import dev.meloda.fast.friends.di.friendsModule import dev.meloda.fast.languagepicker.di.languagePickerModule @@ -41,7 +41,7 @@ val applicationModule = module { loginModule, validationModule, captchaModule, - conversationsModule, + convosModule, settingsModule, messagesHistoryModule, photoViewModule, diff --git a/app/src/main/kotlin/dev/meloda/fast/navigation/MainGraph.kt b/app/src/main/kotlin/dev/meloda/fast/navigation/MainGraph.kt index 9cfb34e0..10ccfb2c 100644 --- a/app/src/main/kotlin/dev/meloda/fast/navigation/MainGraph.kt +++ b/app/src/main/kotlin/dev/meloda/fast/navigation/MainGraph.kt @@ -2,7 +2,7 @@ package dev.meloda.fast.navigation import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable -import dev.meloda.fast.conversations.navigation.ConversationsGraph +import dev.meloda.fast.convos.navigation.ConvoGraph import dev.meloda.fast.friends.navigation.Friends import dev.meloda.fast.model.BaseError import dev.meloda.fast.model.BottomNavigationItem @@ -21,7 +21,7 @@ object Main fun NavGraphBuilder.mainScreen( onError: (BaseError) -> Unit, onSettingsButtonClicked: () -> Unit, - onNavigateToMessagesHistory: (conversationId: Long) -> Unit, + onNavigateToMessagesHistory: (convoId: Long) -> Unit, onPhotoClicked: (url: String) -> Unit, onMessageClicked: (userid: Long) -> Unit, onNavigateToCreateChat: () -> Unit @@ -34,10 +34,10 @@ fun NavGraphBuilder.mainScreen( route = Friends, ), BottomNavigationItem( - titleResId = R.string.title_conversations, + titleResId = R.string.title_convos, selectedIconResId = R.drawable.baseline_chat_24, unselectedIconResId = R.drawable.outline_chat_24, - route = ConversationsGraph + route = ConvoGraph ), BottomNavigationItem( titleResId = R.string.title_profile, diff --git a/app/src/main/kotlin/dev/meloda/fast/presentation/MainScreen.kt b/app/src/main/kotlin/dev/meloda/fast/presentation/MainScreen.kt index b67b1a23..678e0d24 100644 --- a/app/src/main/kotlin/dev/meloda/fast/presentation/MainScreen.kt +++ b/app/src/main/kotlin/dev/meloda/fast/presentation/MainScreen.kt @@ -38,8 +38,8 @@ import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.hazeEffect import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import dev.chrisbanes.haze.materials.HazeMaterials -import dev.meloda.fast.conversations.navigation.ConversationsGraph -import dev.meloda.fast.conversations.navigation.conversationsGraph +import dev.meloda.fast.convos.navigation.ConvoGraph +import dev.meloda.fast.convos.navigation.convosGraph import dev.meloda.fast.friends.navigation.Friends import dev.meloda.fast.friends.navigation.friendsScreen import dev.meloda.fast.model.BaseError @@ -60,7 +60,7 @@ fun MainScreen( navigationItems: ImmutableList, onError: (BaseError) -> Unit = {}, onSettingsButtonClicked: () -> Unit = {}, - onNavigateToMessagesHistory: (conversationId: Long) -> Unit = {}, + onNavigateToMessagesHistory: (convoId: Long) -> Unit = {}, onPhotoClicked: (url: String) -> Unit = {}, onMessageClicked: (userid: Long) -> Unit = {}, onNavigateToCreateChat: () -> Unit = {} @@ -197,14 +197,14 @@ fun MainScreen( } }, ) - conversationsGraph( + convosGraph( activity = activity, onError = onError, onNavigateToMessagesHistory = onNavigateToMessagesHistory, onNavigateToCreateChat = onNavigateToCreateChat, onScrolledToTop = { tabReselected = tabReselected.toMutableMap().also { - it[ConversationsGraph] = false + it[ConvoGraph] = false } } ) diff --git a/app/src/main/kotlin/dev/meloda/fast/presentation/RootScreen.kt b/app/src/main/kotlin/dev/meloda/fast/presentation/RootScreen.kt index ec82afc4..686a9496 100644 --- a/app/src/main/kotlin/dev/meloda/fast/presentation/RootScreen.kt +++ b/app/src/main/kotlin/dev/meloda/fast/presentation/RootScreen.kt @@ -46,8 +46,8 @@ import dev.meloda.fast.chatmaterials.navigation.chatMaterialsScreen import dev.meloda.fast.chatmaterials.navigation.navigateToChatMaterials import dev.meloda.fast.common.LongPollController import dev.meloda.fast.common.model.LongPollState -import dev.meloda.fast.conversations.navigation.createChatScreen -import dev.meloda.fast.conversations.navigation.navigateToCreateChat +import dev.meloda.fast.convos.navigation.createChatScreen +import dev.meloda.fast.convos.navigation.navigateToCreateChat import dev.meloda.fast.datastore.UserSettings import dev.meloda.fast.languagepicker.navigation.languagePickerScreen import dev.meloda.fast.languagepicker.navigation.navigateToLanguagePicker @@ -354,9 +354,9 @@ fun RootScreen( } ) createChatScreen( - onChatCreated = { conversationId -> + onChatCreated = { convoId -> navController.popBackStack() - navController.navigateToMessagesHistory(conversationId) + navController.navigateToMessagesHistory(convoId) }, navController = navController ) diff --git a/build-logic/convention/src/main/kotlin/dev/meloda/fast/KotlinAndroid.kt b/build-logic/convention/src/main/kotlin/dev/meloda/fast/KotlinAndroid.kt index 35be6c0d..e1463648 100644 --- a/build-logic/convention/src/main/kotlin/dev/meloda/fast/KotlinAndroid.kt +++ b/build-logic/convention/src/main/kotlin/dev/meloda/fast/KotlinAndroid.kt @@ -55,7 +55,8 @@ private inline fun Project.configureKotlin() = "-opt-in=kotlin.RequiresOptIn", // Enable experimental coroutines APIs, including Flow "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", - "-opt-in=kotlinx.coroutines.FlowPreview" + "-opt-in=kotlinx.coroutines.FlowPreview", + "-Xannotation-default-target=param-property", ) } } diff --git a/core/common/src/main/kotlin/dev/meloda/fast/common/extensions/Extensions.kt b/core/common/src/main/kotlin/dev/meloda/fast/common/extensions/Extensions.kt index bb8353e3..2d3c4460 100644 --- a/core/common/src/main/kotlin/dev/meloda/fast/common/extensions/Extensions.kt +++ b/core/common/src/main/kotlin/dev/meloda/fast/common/extensions/Extensions.kt @@ -138,3 +138,17 @@ fun Bundle.getParcelableCompat(key: String, clazz: KClass): T? { getParcelable(key) } } + +infix fun ClosedRange.collidesWith(other: ClosedRange): Boolean { + return this.start < other.endInclusive && other.start < this.endInclusive +} + +fun dickPizda(a: Int): String = "" + +operator fun ClosedRange.minus(other: ClosedRange): ClosedRange { + return (this.start - other.start)..(this.endInclusive - other.endInclusive) +} + +operator fun ClosedRange.minus(other: Int): ClosedRange { + return (this.start - other)..(this.endInclusive - other) +} diff --git a/core/data/src/main/kotlin/dev/meloda/fast/data/VkGroupsMap.kt b/core/data/src/main/kotlin/dev/meloda/fast/data/VkGroupsMap.kt index 6857719e..67b4cb19 100644 --- a/core/data/src/main/kotlin/dev/meloda/fast/data/VkGroupsMap.kt +++ b/core/data/src/main/kotlin/dev/meloda/fast/data/VkGroupsMap.kt @@ -1,7 +1,7 @@ package dev.meloda.fast.data import dev.meloda.fast.model.api.data.VkMessageData -import dev.meloda.fast.model.api.domain.VkConversation +import dev.meloda.fast.model.api.domain.VkConvo import dev.meloda.fast.model.api.domain.VkGroupDomain import dev.meloda.fast.model.api.domain.VkMessage import kotlin.math.abs @@ -16,9 +16,9 @@ class VkGroupsMap( fun groups(): List = map.values.toList() - fun conversationGroup(conversation: VkConversation): VkGroupDomain? = - if (!conversation.peerType.isGroup()) null - else map[abs(conversation.id)] + fun convoGroup(convo: VkConvo): VkGroupDomain? = + if (!convo.peerType.isGroup()) null + else map[abs(convo.id)] fun messageActionGroup(message: VkMessage): VkGroupDomain? = if (message.actionMemberId == null || message.actionMemberId!! >= 0) null diff --git a/core/data/src/main/kotlin/dev/meloda/fast/data/VkMemoryCache.kt b/core/data/src/main/kotlin/dev/meloda/fast/data/VkMemoryCache.kt index 8c5e8ebb..dfb2f748 100644 --- a/core/data/src/main/kotlin/dev/meloda/fast/data/VkMemoryCache.kt +++ b/core/data/src/main/kotlin/dev/meloda/fast/data/VkMemoryCache.kt @@ -2,7 +2,7 @@ package dev.meloda.fast.data import dev.meloda.fast.data.UserConfig.userId import dev.meloda.fast.model.api.domain.VkContactDomain -import dev.meloda.fast.model.api.domain.VkConversation +import dev.meloda.fast.model.api.domain.VkConvo import dev.meloda.fast.model.api.domain.VkGroupDomain import dev.meloda.fast.model.api.domain.VkMessage import dev.meloda.fast.model.api.domain.VkUser @@ -13,7 +13,7 @@ object VkMemoryCache { private val users: HashMap = hashMapOf() private val groups: HashMap = hashMapOf() private val messages: HashMap = hashMapOf() - private val conversations: HashMap = hashMapOf() + private val convos: HashMap = hashMapOf() private val contacts: HashMap = hashMapOf() fun appendUsers(users: List) { @@ -28,9 +28,9 @@ object VkMemoryCache { messages.forEach { message -> VkMemoryCache.messages[message.id] = message } } - fun appendConversations(conversations: List) { - conversations.forEach { conversation -> - VkMemoryCache.conversations[conversation.id] = conversation + fun appendConvos(convos: List) { + convos.forEach { convo -> + VkMemoryCache.convos[convo.id] = convo } } @@ -50,8 +50,8 @@ object VkMemoryCache { messages[messageId] = message } - operator fun set(conversationId: Long, conversation: VkConversation) { - conversations[conversationId] = conversation + operator fun set(convoId: Long, convo: VkConvo) { + convos[convoId] = convo } operator fun set(contactId: Long, contact: VkContactDomain) { @@ -94,16 +94,16 @@ object VkMemoryCache { return ids.mapNotNull { id -> messages[id] } } - fun getConversation(id: Long): VkConversation? { - return getConversations(id).firstOrNull() + fun getConvo(id: Long): VkConvo? { + return getConvos(id).firstOrNull() } - fun getConversations(vararg ids: Long): List { - return getConversations(ids.toList()) + fun getConvos(vararg ids: Long): List { + return getConvos(ids.toList()) } - fun getConversations(ids: List): List { - return ids.mapNotNull { id -> conversations[id] } + fun getConvos(ids: List): List { + return ids.mapNotNull { id -> convos[id] } } fun getContact(id: Long): VkContactDomain? { diff --git a/core/data/src/main/kotlin/dev/meloda/fast/data/VkUsersMap.kt b/core/data/src/main/kotlin/dev/meloda/fast/data/VkUsersMap.kt index 73b1af67..de012887 100644 --- a/core/data/src/main/kotlin/dev/meloda/fast/data/VkUsersMap.kt +++ b/core/data/src/main/kotlin/dev/meloda/fast/data/VkUsersMap.kt @@ -1,8 +1,7 @@ package dev.meloda.fast.data -import dev.meloda.fast.data.UserConfig.userId import dev.meloda.fast.model.api.data.VkMessageData -import dev.meloda.fast.model.api.domain.VkConversation +import dev.meloda.fast.model.api.domain.VkConvo import dev.meloda.fast.model.api.domain.VkMessage import dev.meloda.fast.model.api.domain.VkUser @@ -16,9 +15,9 @@ class VkUsersMap( fun users(): List = map.values.toList() - fun conversationUser(conversation: VkConversation): VkUser? = - if (!conversation.peerType.isUser()) null - else map[conversation.id] + fun convoUser(convo: VkConvo): VkUser? = + if (!convo.peerType.isUser()) null + else map[convo.id] fun messageActionUser(message: VkMessage): VkUser? = if (message.actionMemberId == null || message.actionMemberId!! <= 0) null diff --git a/core/data/src/main/kotlin/dev/meloda/fast/data/api/conversations/ConversationsRepository.kt b/core/data/src/main/kotlin/dev/meloda/fast/data/api/convos/ConvosRepository.kt similarity index 58% rename from core/data/src/main/kotlin/dev/meloda/fast/data/api/conversations/ConversationsRepository.kt rename to core/data/src/main/kotlin/dev/meloda/fast/data/api/convos/ConvosRepository.kt index 22743633..53d73767 100644 --- a/core/data/src/main/kotlin/dev/meloda/fast/data/api/conversations/ConversationsRepository.kt +++ b/core/data/src/main/kotlin/dev/meloda/fast/data/api/convos/ConvosRepository.kt @@ -1,25 +1,25 @@ -package dev.meloda.fast.data.api.conversations +package dev.meloda.fast.data.api.convos import com.slack.eithernet.ApiResult -import dev.meloda.fast.model.ConversationsFilter -import dev.meloda.fast.model.api.domain.VkConversation +import dev.meloda.fast.model.ConvosFilter +import dev.meloda.fast.model.api.domain.VkConvo import dev.meloda.fast.network.RestApiErrorDomain -interface ConversationsRepository { +interface ConvosRepository { - suspend fun storeConversations(conversations: List) + suspend fun storeConvos(convos: List) - suspend fun getConversations( + suspend fun getConvos( count: Int?, offset: Int?, - filter: ConversationsFilter - ): ApiResult, RestApiErrorDomain> + filter: ConvosFilter + ): ApiResult, RestApiErrorDomain> - suspend fun getConversationsById( + suspend fun getConvosById( peerIds: List, extended: Boolean? = null, fields: String? = null - ): ApiResult, RestApiErrorDomain> + ): ApiResult, RestApiErrorDomain> suspend fun delete(peerId: Long): ApiResult suspend fun pin(peerId: Long): ApiResult diff --git a/core/data/src/main/kotlin/dev/meloda/fast/data/api/conversations/ConversationsRepositoryImpl.kt b/core/data/src/main/kotlin/dev/meloda/fast/data/api/convos/ConvosRepositoryImpl.kt similarity index 67% rename from core/data/src/main/kotlin/dev/meloda/fast/data/api/conversations/ConversationsRepositoryImpl.kt rename to core/data/src/main/kotlin/dev/meloda/fast/data/api/convos/ConvosRepositoryImpl.kt index 0872cefa..970cbfd6 100644 --- a/core/data/src/main/kotlin/dev/meloda/fast/data/api/conversations/ConversationsRepositoryImpl.kt +++ b/core/data/src/main/kotlin/dev/meloda/fast/data/api/convos/ConvosRepositoryImpl.kt @@ -1,51 +1,51 @@ -package dev.meloda.fast.data.api.conversations +package dev.meloda.fast.data.api.convos import com.slack.eithernet.ApiResult import dev.meloda.fast.common.VkConstants import dev.meloda.fast.data.VkGroupsMap import dev.meloda.fast.data.VkMemoryCache import dev.meloda.fast.data.VkUsersMap -import dev.meloda.fast.database.dao.ConversationDao +import dev.meloda.fast.database.dao.ConvoDao import dev.meloda.fast.database.dao.GroupDao import dev.meloda.fast.database.dao.MessageDao import dev.meloda.fast.database.dao.UserDao -import dev.meloda.fast.model.ConversationsFilter +import dev.meloda.fast.model.ConvosFilter import dev.meloda.fast.model.api.data.VkContactData import dev.meloda.fast.model.api.data.VkGroupData import dev.meloda.fast.model.api.data.VkUserData import dev.meloda.fast.model.api.data.asDomain -import dev.meloda.fast.model.api.domain.VkConversation +import dev.meloda.fast.model.api.domain.VkConvo import dev.meloda.fast.model.api.domain.VkGroupDomain import dev.meloda.fast.model.api.domain.VkMessage import dev.meloda.fast.model.api.domain.VkUser import dev.meloda.fast.model.api.domain.asEntity -import dev.meloda.fast.model.api.requests.ConversationsGetRequest +import dev.meloda.fast.model.api.requests.ConvosGetRequest import dev.meloda.fast.network.RestApiErrorDomain import dev.meloda.fast.network.mapApiDefault import dev.meloda.fast.network.mapApiResult -import dev.meloda.fast.network.service.conversations.ConversationsService +import dev.meloda.fast.network.service.convos.ConvosService import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -class ConversationsRepositoryImpl( - private val conversationsService: ConversationsService, +class ConvosRepositoryImpl( + private val convosService: ConvosService, private val messageDao: MessageDao, private val userDao: UserDao, private val groupDao: GroupDao, - private val conversationDao: ConversationDao -) : ConversationsRepository { + private val convoDao: ConvoDao +) : ConvosRepository { - override suspend fun storeConversations(conversations: List) { - conversationDao.insertAll(conversations.map(VkConversation::asEntity)) + override suspend fun storeConvos(convos: List) { + convoDao.insertAll(convos.map(VkConvo::asEntity)) } - override suspend fun getConversations( + override suspend fun getConvos( count: Int?, offset: Int?, - filter: ConversationsFilter - ): ApiResult, RestApiErrorDomain> = withContext(Dispatchers.IO) { - val requestModel = ConversationsGetRequest( + filter: ConvosFilter + ): ApiResult, RestApiErrorDomain> = withContext(Dispatchers.IO) { + val requestModel = ConvosGetRequest( count = count, offset = offset, fields = VkConstants.ALL_FIELDS, @@ -54,7 +54,7 @@ class ConversationsRepositoryImpl( startMessageId = null ) - conversationsService.getConversations(requestModel.map).mapApiResult( + convosService.getConvos(requestModel.map).mapApiResult( successMapper = { apiResponse -> val response = apiResponse.requireResponse() @@ -69,7 +69,7 @@ class ConversationsRepositoryImpl( VkMemoryCache.appendGroups(groupsList) VkMemoryCache.appendContacts(contactsList) - val conversations = response.items.map { item -> + val convos = response.items.map { item -> val lastMessage = item.lastMessage?.asDomain()?.let { message -> message.copy( user = usersMap.messageUser(message), @@ -84,24 +84,24 @@ class ConversationsRepositoryImpl( ) ).also { VkMemoryCache[message.id] = it } } - item.conversation.asDomain(lastMessage).let { conversation -> - conversation.copy( - user = usersMap.conversationUser(conversation), - group = groupsMap.conversationGroup(conversation) - ).also { VkMemoryCache[conversation.id] = it } + item.convo.asDomain(lastMessage).let { convo -> + convo.copy( + user = usersMap.convoUser(convo), + group = groupsMap.convoGroup(convo) + ).also { VkMemoryCache[convo.id] = it } } } - val messages = conversations.mapNotNull(VkConversation::lastMessage) + val messages = convos.mapNotNull(VkConvo::lastMessage) launch(Dispatchers.IO) { - conversationDao.insertAll(conversations.map(VkConversation::asEntity)) + convoDao.insertAll(convos.map(VkConvo::asEntity)) messageDao.insertAll(messages.map(VkMessage::asEntity)) userDao.insertAll(profilesList.map(VkUser::asEntity)) groupDao.insertAll(groupsList.map(VkGroupDomain::asEntity)) } - conversations + convos }, errorMapper = { error -> error?.toDomain() @@ -109,11 +109,11 @@ class ConversationsRepositoryImpl( ) } - override suspend fun getConversationsById( + override suspend fun getConvosById( peerIds: List, extended: Boolean?, fields: String? - ): ApiResult, RestApiErrorDomain> = withContext(Dispatchers.IO) { + ): ApiResult, RestApiErrorDomain> = withContext(Dispatchers.IO) { val requestParams = mutableMapOf( "peer_ids" to peerIds.joinToString(separator = ",") ).apply { @@ -121,7 +121,7 @@ class ConversationsRepositoryImpl( fields?.let { this["fields"] = it } } - conversationsService.getConversationsById(requestParams).mapApiResult( + convosService.getConvosById(requestParams).mapApiResult( successMapper = { apiResponse -> val response = apiResponse.requireResponse() @@ -132,17 +132,17 @@ class ConversationsRepositoryImpl( val usersMap = VkUsersMap.forUsers(profilesList) val groupsMap = VkGroupsMap.forGroups(groupsList) - val conversations = response.items.map { item -> - item.asDomain().let { conversation -> - conversation.copy( - user = usersMap.conversationUser(conversation), - group = groupsMap.conversationGroup(conversation) - ).also { VkMemoryCache[conversation.id] = it } + val convos = response.items.map { item -> + item.asDomain().let { convo -> + convo.copy( + user = usersMap.convoUser(convo), + group = groupsMap.convoGroup(convo) + ).also { VkMemoryCache[convo.id] = it } } } launch(Dispatchers.IO) { - conversationDao.insertAll(conversations.map(VkConversation::asEntity)) + convoDao.insertAll(convos.map(VkConvo::asEntity)) userDao.insertAll(profilesList.map(VkUser::asEntity)) groupDao.insertAll(groupsList.map(VkGroupDomain::asEntity)) } @@ -151,7 +151,7 @@ class ConversationsRepositoryImpl( VkMemoryCache.appendGroups(groupsList) VkMemoryCache.appendContacts(contactsList) - conversations + convos }, errorMapper = { error -> error?.toDomain() @@ -161,7 +161,7 @@ class ConversationsRepositoryImpl( override suspend fun delete(peerId: Long): ApiResult = withContext(Dispatchers.IO) { - conversationsService.delete(mapOf("peer_id" to peerId.toString())).mapApiResult( + convosService.delete(mapOf("peer_id" to peerId.toString())).mapApiResult( successMapper = { response -> response.requireResponse().lastDeletedId }, errorMapper = { error -> error?.toDomain() } ) @@ -170,19 +170,19 @@ class ConversationsRepositoryImpl( override suspend fun pin( peerId: Long ): ApiResult = withContext(Dispatchers.IO) { - conversationsService.pin(mapOf("peer_id" to peerId.toString())).mapApiDefault() + convosService.pin(mapOf("peer_id" to peerId.toString())).mapApiDefault() } override suspend fun unpin( peerId: Long ): ApiResult = withContext(Dispatchers.IO) { - conversationsService.unpin(mapOf("peer_id" to peerId.toString())).mapApiDefault() + convosService.unpin(mapOf("peer_id" to peerId.toString())).mapApiDefault() } override suspend fun reorderPinned( peerIds: List ): ApiResult = withContext(Dispatchers.IO) { - conversationsService + convosService .reorderPinned(mapOf("peer_ids" to peerIds.joinToString(","))) .mapApiDefault() } @@ -190,12 +190,12 @@ class ConversationsRepositoryImpl( override suspend fun archive( peerId: Long ): ApiResult = withContext(Dispatchers.IO) { - conversationsService.archive(mapOf("peer_id" to peerId.toString())).mapApiDefault() + convosService.archive(mapOf("peer_id" to peerId.toString())).mapApiDefault() } override suspend fun unarchive( peerId: Long ): ApiResult = withContext(Dispatchers.IO) { - conversationsService.unarchive(mapOf("peer_id" to peerId.toString())).mapApiDefault() + convosService.unarchive(mapOf("peer_id" to peerId.toString())).mapApiDefault() } } diff --git a/core/data/src/main/kotlin/dev/meloda/fast/data/api/messages/MessagesHistoryInfo.kt b/core/data/src/main/kotlin/dev/meloda/fast/data/api/messages/MessagesHistoryInfo.kt index 484f5e92..1dda3d38 100644 --- a/core/data/src/main/kotlin/dev/meloda/fast/data/api/messages/MessagesHistoryInfo.kt +++ b/core/data/src/main/kotlin/dev/meloda/fast/data/api/messages/MessagesHistoryInfo.kt @@ -1,9 +1,9 @@ package dev.meloda.fast.data.api.messages -import dev.meloda.fast.model.api.domain.VkConversation +import dev.meloda.fast.model.api.domain.VkConvo import dev.meloda.fast.model.api.domain.VkMessage data class MessagesHistoryInfo( val messages: List, - val conversations: List + val convos: List ) diff --git a/core/data/src/main/kotlin/dev/meloda/fast/data/api/messages/MessagesRepository.kt b/core/data/src/main/kotlin/dev/meloda/fast/data/api/messages/MessagesRepository.kt index db58c458..a204c3e0 100644 --- a/core/data/src/main/kotlin/dev/meloda/fast/data/api/messages/MessagesRepository.kt +++ b/core/data/src/main/kotlin/dev/meloda/fast/data/api/messages/MessagesRepository.kt @@ -5,7 +5,7 @@ import dev.meloda.fast.model.api.data.VkChatData import dev.meloda.fast.model.api.domain.VkAttachment import dev.meloda.fast.model.api.domain.VkAttachmentHistoryMessage import dev.meloda.fast.model.api.domain.VkMessage -import dev.meloda.fast.model.api.responses.MessagesGetConversationMembersResponse +import dev.meloda.fast.model.api.responses.MessagesGetConvoMembersResponse import dev.meloda.fast.model.api.responses.MessagesGetReadPeersResponse import dev.meloda.fast.model.api.responses.MessagesSendResponse import dev.meloda.fast.network.RestApiErrorDomain @@ -15,7 +15,7 @@ interface MessagesRepository { suspend fun storeMessages(messages: List) suspend fun getHistory( - conversationId: Long, + convoId: Long, offset: Int?, count: Int? ): ApiResult @@ -99,13 +99,13 @@ interface MessagesRepository { fields: String? = null ): ApiResult - suspend fun getConversationMembers( + suspend fun getConvoMembers( peerId: Long, offset: Int? = null, count: Int? = null, extended: Boolean? = null, fields: String? = null - ): ApiResult + ): ApiResult suspend fun removeChatUser( chatId: Long, diff --git a/core/data/src/main/kotlin/dev/meloda/fast/data/api/messages/MessagesRepositoryImpl.kt b/core/data/src/main/kotlin/dev/meloda/fast/data/api/messages/MessagesRepositoryImpl.kt index 140c338c..3634deb8 100644 --- a/core/data/src/main/kotlin/dev/meloda/fast/data/api/messages/MessagesRepositoryImpl.kt +++ b/core/data/src/main/kotlin/dev/meloda/fast/data/api/messages/MessagesRepositoryImpl.kt @@ -5,7 +5,7 @@ import dev.meloda.fast.common.VkConstants import dev.meloda.fast.data.VkGroupsMap import dev.meloda.fast.data.VkMemoryCache import dev.meloda.fast.data.VkUsersMap -import dev.meloda.fast.database.dao.ConversationDao +import dev.meloda.fast.database.dao.ConvoDao import dev.meloda.fast.database.dao.GroupDao import dev.meloda.fast.database.dao.MessageDao import dev.meloda.fast.database.dao.UserDao @@ -17,7 +17,7 @@ import dev.meloda.fast.model.api.data.VkUserData import dev.meloda.fast.model.api.data.asDomain import dev.meloda.fast.model.api.domain.VkAttachment import dev.meloda.fast.model.api.domain.VkAttachmentHistoryMessage -import dev.meloda.fast.model.api.domain.VkConversation +import dev.meloda.fast.model.api.domain.VkConvo import dev.meloda.fast.model.api.domain.VkGroupDomain import dev.meloda.fast.model.api.domain.VkMessage import dev.meloda.fast.model.api.domain.VkUser @@ -27,7 +27,7 @@ import dev.meloda.fast.model.api.requests.MessagesDeleteRequest import dev.meloda.fast.model.api.requests.MessagesEditRequest import dev.meloda.fast.model.api.requests.MessagesGetByIdRequest import dev.meloda.fast.model.api.requests.MessagesGetChatRequest -import dev.meloda.fast.model.api.requests.MessagesGetConversationMembersRequest +import dev.meloda.fast.model.api.requests.MessagesGetConvoMembersRequest import dev.meloda.fast.model.api.requests.MessagesGetHistoryAttachmentsRequest import dev.meloda.fast.model.api.requests.MessagesGetHistoryRequest import dev.meloda.fast.model.api.requests.MessagesMarkAsImportantRequest @@ -36,7 +36,7 @@ import dev.meloda.fast.model.api.requests.MessagesPinMessageRequest import dev.meloda.fast.model.api.requests.MessagesRemoveChatUserRequest import dev.meloda.fast.model.api.requests.MessagesSendRequest import dev.meloda.fast.model.api.requests.MessagesUnpinMessageRequest -import dev.meloda.fast.model.api.responses.MessagesGetConversationMembersResponse +import dev.meloda.fast.model.api.responses.MessagesGetConvoMembersResponse import dev.meloda.fast.model.api.responses.MessagesGetReadPeersResponse import dev.meloda.fast.model.api.responses.MessagesSendResponse import dev.meloda.fast.network.RestApiErrorDomain @@ -52,18 +52,18 @@ class MessagesRepositoryImpl( private val messageDao: MessageDao, private val userDao: UserDao, private val groupDao: GroupDao, - private val conversationDao: ConversationDao + private val convoDao: ConvoDao ) : MessagesRepository { override suspend fun getHistory( - conversationId: Long, + convoId: Long, offset: Int?, count: Int? ): ApiResult = withContext(Dispatchers.IO) { val requestModel = MessagesGetHistoryRequest( count = count, offset = offset, - peerId = conversationId, + peerId = convoId, extended = true, startMessageId = null, rev = null, @@ -104,19 +104,19 @@ class MessagesRepositoryImpl( } } - val conversations = response.conversations.orEmpty().map { item -> + val convos = response.convos.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 } + .let { convo -> + convo.copy( + user = usersMap.convoUser(convo), + group = groupsMap.convoGroup(convo) + ).also { VkMemoryCache[convo.id] = it } } } launch(Dispatchers.IO) { - conversationDao.insertAll(conversations.map(VkConversation::asEntity)) + convoDao.insertAll(convos.map(VkConvo::asEntity)) messageDao.insertAll(messages.map(VkMessage::asEntity)) userDao.insertAll(profilesList.map(VkUser::asEntity)) groupDao.insertAll(groupsList.map(VkGroupDomain::asEntity)) @@ -124,7 +124,7 @@ class MessagesRepositoryImpl( MessagesHistoryInfo( messages = messages, - conversations = conversations + convos = convos ) }, errorMapper = { error -> @@ -243,7 +243,7 @@ class MessagesRepositoryImpl( offset = offset, preserveOrder = true, attachmentTypes = attachmentTypes, - conversationMessageId = cmId, + cmId = cmId, fields = VkConstants.ALL_FIELDS ) @@ -297,7 +297,7 @@ class MessagesRepositoryImpl( val requestModel = MessagesPinMessageRequest( peerId = peerId, messageId = messageId, - conversationMessageId = cmId + cmId = cmId ) messagesService.pin(requestModel.map).mapApiResult( @@ -343,7 +343,7 @@ class MessagesRepositoryImpl( val requestModel = MessagesDeleteRequest( peerId = peerId, messagesIds = messageIds, - conversationsMessagesIds = cmIds, + cmIds = cmIds, isSpam = spam, deleteForAll = deleteForAll ) @@ -394,15 +394,15 @@ class MessagesRepositoryImpl( messagesService.getChat(requestModel.map).mapApiDefault() } - override suspend fun getConversationMembers( + override suspend fun getConvoMembers( peerId: Long, offset: Int?, count: Int?, extended: Boolean?, fields: String? - ): ApiResult = + ): ApiResult = withContext(Dispatchers.IO) { - val requestModel = MessagesGetConversationMembersRequest( + val requestModel = MessagesGetConvoMembersRequest( peerId = peerId, offset = offset, count = count, @@ -410,7 +410,7 @@ class MessagesRepositoryImpl( fields = fields ) - messagesService.getConversationMembers(requestModel.map).mapApiDefault() + messagesService.getConvoMembers(requestModel.map).mapApiDefault() } override suspend fun removeChatUser( diff --git a/core/data/src/main/kotlin/dev/meloda/fast/data/di/DataModule.kt b/core/data/src/main/kotlin/dev/meloda/fast/data/di/DataModule.kt index d091e805..c85a3781 100644 --- a/core/data/src/main/kotlin/dev/meloda/fast/data/di/DataModule.kt +++ b/core/data/src/main/kotlin/dev/meloda/fast/data/di/DataModule.kt @@ -6,8 +6,8 @@ import dev.meloda.fast.data.api.account.AccountRepositoryImpl import dev.meloda.fast.data.api.audios.AudiosRepository import dev.meloda.fast.data.api.auth.AuthRepository import dev.meloda.fast.data.api.auth.AuthRepositoryImpl -import dev.meloda.fast.data.api.conversations.ConversationsRepository -import dev.meloda.fast.data.api.conversations.ConversationsRepositoryImpl +import dev.meloda.fast.data.api.convos.ConvosRepository +import dev.meloda.fast.data.api.convos.ConvosRepositoryImpl import dev.meloda.fast.data.api.files.FilesRepository import dev.meloda.fast.data.api.friends.FriendsRepository import dev.meloda.fast.data.api.friends.FriendsRepositoryImpl @@ -45,7 +45,7 @@ val dataModule = module { singleOf(::AuthRepositoryImpl) bind AuthRepository::class - singleOf(::ConversationsRepositoryImpl) bind ConversationsRepository::class + singleOf(::ConvosRepositoryImpl) bind ConvosRepository::class singleOf(::FilesRepository) diff --git a/core/database/schemas/dev.meloda.fast.database.CacheDatabase/10.json b/core/database/schemas/dev.meloda.fast.database.CacheDatabase/10.json index bddff7b1..55708714 100644 --- a/core/database/schemas/dev.meloda.fast.database.CacheDatabase/10.json +++ b/core/database/schemas/dev.meloda.fast.database.CacheDatabase/10.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 10, - "identityHash": "fa307a5eb2e1f7d601bd1374174635cd", + "identityHash": "6c315b7f800694f635318d86032746ec", "entities": [ { "tableName": "users", @@ -41,50 +41,42 @@ { "fieldPath": "onlineAppId", "columnName": "onlineAppId", - "affinity": "INTEGER", - "notNull": false + "affinity": "INTEGER" }, { "fieldPath": "lastSeen", "columnName": "lastSeen", - "affinity": "INTEGER", - "notNull": false + "affinity": "INTEGER" }, { "fieldPath": "lastSeenStatus", "columnName": "lastSeenStatus", - "affinity": "TEXT", - "notNull": false + "affinity": "TEXT" }, { "fieldPath": "birthday", "columnName": "birthday", - "affinity": "TEXT", - "notNull": false + "affinity": "TEXT" }, { "fieldPath": "photo50", "columnName": "photo50", - "affinity": "TEXT", - "notNull": false + "affinity": "TEXT" }, { "fieldPath": "photo100", "columnName": "photo100", - "affinity": "TEXT", - "notNull": false + "affinity": "TEXT" }, { "fieldPath": "photo200", "columnName": "photo200", - "affinity": "TEXT", - "notNull": false + "affinity": "TEXT" }, { "fieldPath": "photo400Orig", "columnName": "photo400Orig", - "affinity": "TEXT", - "notNull": false + "affinity": "TEXT" } ], "primaryKey": { @@ -92,9 +84,7 @@ "columnNames": [ "id" ] - }, - "indices": [], - "foreignKeys": [] + } }, { "tableName": "groups", @@ -121,26 +111,22 @@ { "fieldPath": "photo50", "columnName": "photo50", - "affinity": "TEXT", - "notNull": false + "affinity": "TEXT" }, { "fieldPath": "photo100", "columnName": "photo100", - "affinity": "TEXT", - "notNull": false + "affinity": "TEXT" }, { "fieldPath": "photo200", "columnName": "photo200", - "affinity": "TEXT", - "notNull": false + "affinity": "TEXT" }, { "fieldPath": "membersCount", "columnName": "membersCount", - "affinity": "INTEGER", - "notNull": false + "affinity": "INTEGER" } ], "primaryKey": { @@ -148,13 +134,11 @@ "columnNames": [ "id" ] - }, - "indices": [], - "foreignKeys": [] + } }, { "tableName": "messages", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `conversationMessageId` INTEGER NOT NULL, `text` TEXT, `isOut` INTEGER NOT NULL, `peerId` INTEGER NOT NULL, `fromId` INTEGER NOT NULL, `date` INTEGER NOT NULL, `randomId` INTEGER NOT NULL, `action` TEXT, `actionMemberId` INTEGER, `actionText` TEXT, `actionConversationMessageId` INTEGER, `actionMessage` TEXT, `updateTime` INTEGER, `important` INTEGER NOT NULL, `forwardIds` TEXT, `attachments` TEXT, `replyMessageId` INTEGER, `geoType` TEXT, `pinnedAt` INTEGER, `isPinned` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `cmId` INTEGER NOT NULL, `text` TEXT, `isOut` INTEGER NOT NULL, `peerId` INTEGER NOT NULL, `fromId` INTEGER NOT NULL, `date` INTEGER NOT NULL, `randomId` INTEGER NOT NULL, `action` TEXT, `actionMemberId` INTEGER, `actionText` TEXT, `actionCmId` INTEGER, `actionMessage` TEXT, `updateTime` INTEGER, `important` INTEGER NOT NULL, `forwardIds` TEXT, `attachments` TEXT, `replyMessageId` INTEGER, `geoType` TEXT, `pinnedAt` INTEGER, `isPinned` INTEGER NOT NULL, PRIMARY KEY(`id`))", "fields": [ { "fieldPath": "id", @@ -163,16 +147,15 @@ "notNull": true }, { - "fieldPath": "conversationMessageId", - "columnName": "conversationMessageId", + "fieldPath": "cmId", + "columnName": "cmId", "affinity": "INTEGER", "notNull": true }, { "fieldPath": "text", "columnName": "text", - "affinity": "TEXT", - "notNull": false + "affinity": "TEXT" }, { "fieldPath": "isOut", @@ -207,38 +190,32 @@ { "fieldPath": "action", "columnName": "action", - "affinity": "TEXT", - "notNull": false + "affinity": "TEXT" }, { "fieldPath": "actionMemberId", "columnName": "actionMemberId", - "affinity": "INTEGER", - "notNull": false + "affinity": "INTEGER" }, { "fieldPath": "actionText", "columnName": "actionText", - "affinity": "TEXT", - "notNull": false + "affinity": "TEXT" }, { - "fieldPath": "actionConversationMessageId", - "columnName": "actionConversationMessageId", - "affinity": "INTEGER", - "notNull": false + "fieldPath": "actionCmId", + "columnName": "actionCmId", + "affinity": "INTEGER" }, { "fieldPath": "actionMessage", "columnName": "actionMessage", - "affinity": "TEXT", - "notNull": false + "affinity": "TEXT" }, { "fieldPath": "updateTime", "columnName": "updateTime", - "affinity": "INTEGER", - "notNull": false + "affinity": "INTEGER" }, { "fieldPath": "important", @@ -249,32 +226,27 @@ { "fieldPath": "forwardIds", "columnName": "forwardIds", - "affinity": "TEXT", - "notNull": false + "affinity": "TEXT" }, { "fieldPath": "attachments", "columnName": "attachments", - "affinity": "TEXT", - "notNull": false + "affinity": "TEXT" }, { "fieldPath": "replyMessageId", "columnName": "replyMessageId", - "affinity": "INTEGER", - "notNull": false + "affinity": "INTEGER" }, { "fieldPath": "geoType", "columnName": "geoType", - "affinity": "TEXT", - "notNull": false + "affinity": "TEXT" }, { "fieldPath": "pinnedAt", "columnName": "pinnedAt", - "affinity": "INTEGER", - "notNull": false + "affinity": "INTEGER" }, { "fieldPath": "isPinned", @@ -288,13 +260,11 @@ "columnNames": [ "id" ] - }, - "indices": [], - "foreignKeys": [] + } }, { "tableName": "conversations", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `localId` INTEGER NOT NULL, `ownerId` INTEGER, `title` TEXT, `photo50` TEXT, `photo100` TEXT, `photo200` TEXT, `isPhantom` INTEGER NOT NULL, `lastConversationMessageId` INTEGER NOT NULL, `inReadCmId` INTEGER NOT NULL, `outReadCmId` INTEGER NOT NULL, `inRead` INTEGER NOT NULL, `outRead` INTEGER NOT NULL, `lastMessageId` INTEGER, `unreadCount` INTEGER NOT NULL, `membersCount` INTEGER, `canChangePin` INTEGER NOT NULL, `canChangeInfo` INTEGER NOT NULL, `majorId` INTEGER NOT NULL, `minorId` INTEGER NOT NULL, `pinnedMessageId` INTEGER, `peerType` TEXT NOT NULL, `isArchived` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `localId` INTEGER NOT NULL, `ownerId` INTEGER, `title` TEXT, `photo50` TEXT, `photo100` TEXT, `photo200` TEXT, `isPhantom` INTEGER NOT NULL, `lastCmId` INTEGER NOT NULL, `inReadCmId` INTEGER NOT NULL, `outReadCmId` INTEGER NOT NULL, `inRead` INTEGER NOT NULL, `outRead` INTEGER NOT NULL, `lastMessageId` INTEGER, `unreadCount` INTEGER NOT NULL, `membersCount` INTEGER, `canChangePin` INTEGER NOT NULL, `canChangeInfo` INTEGER NOT NULL, `majorId` INTEGER NOT NULL, `minorId` INTEGER NOT NULL, `pinnedMessageId` INTEGER, `peerType` TEXT NOT NULL, `isArchived` INTEGER NOT NULL, PRIMARY KEY(`id`))", "fields": [ { "fieldPath": "id", @@ -311,32 +281,27 @@ { "fieldPath": "ownerId", "columnName": "ownerId", - "affinity": "INTEGER", - "notNull": false + "affinity": "INTEGER" }, { "fieldPath": "title", "columnName": "title", - "affinity": "TEXT", - "notNull": false + "affinity": "TEXT" }, { "fieldPath": "photo50", "columnName": "photo50", - "affinity": "TEXT", - "notNull": false + "affinity": "TEXT" }, { "fieldPath": "photo100", "columnName": "photo100", - "affinity": "TEXT", - "notNull": false + "affinity": "TEXT" }, { "fieldPath": "photo200", "columnName": "photo200", - "affinity": "TEXT", - "notNull": false + "affinity": "TEXT" }, { "fieldPath": "isPhantom", @@ -345,8 +310,8 @@ "notNull": true }, { - "fieldPath": "lastConversationMessageId", - "columnName": "lastConversationMessageId", + "fieldPath": "lastCmId", + "columnName": "lastCmId", "affinity": "INTEGER", "notNull": true }, @@ -377,8 +342,7 @@ { "fieldPath": "lastMessageId", "columnName": "lastMessageId", - "affinity": "INTEGER", - "notNull": false + "affinity": "INTEGER" }, { "fieldPath": "unreadCount", @@ -389,8 +353,7 @@ { "fieldPath": "membersCount", "columnName": "membersCount", - "affinity": "INTEGER", - "notNull": false + "affinity": "INTEGER" }, { "fieldPath": "canChangePin", @@ -419,8 +382,7 @@ { "fieldPath": "pinnedMessageId", "columnName": "pinnedMessageId", - "affinity": "INTEGER", - "notNull": false + "affinity": "INTEGER" }, { "fieldPath": "peerType", @@ -440,15 +402,12 @@ "columnNames": [ "id" ] - }, - "indices": [], - "foreignKeys": [] + } } ], - "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'fa307a5eb2e1f7d601bd1374174635cd')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6c315b7f800694f635318d86032746ec')" ] } } \ No newline at end of file diff --git a/core/database/schemas/dev.meloda.fast.database.CacheDatabase/11.json b/core/database/schemas/dev.meloda.fast.database.CacheDatabase/11.json new file mode 100644 index 00000000..c7129b41 --- /dev/null +++ b/core/database/schemas/dev.meloda.fast.database.CacheDatabase/11.json @@ -0,0 +1,413 @@ +{ + "formatVersion": 1, + "database": { + "version": 11, + "identityHash": "a746865995959331f8a1b512c049dacb", + "entities": [ + { + "tableName": "users", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `firstName` TEXT NOT NULL, `lastName` TEXT NOT NULL, `isOnline` INTEGER NOT NULL, `isOnlineMobile` INTEGER NOT NULL, `onlineAppId` INTEGER, `lastSeen` INTEGER, `lastSeenStatus` TEXT, `birthday` TEXT, `photo50` TEXT, `photo100` TEXT, `photo200` TEXT, `photo400Orig` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "firstName", + "columnName": "firstName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lastName", + "columnName": "lastName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isOnline", + "columnName": "isOnline", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isOnlineMobile", + "columnName": "isOnlineMobile", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "onlineAppId", + "columnName": "onlineAppId", + "affinity": "INTEGER" + }, + { + "fieldPath": "lastSeen", + "columnName": "lastSeen", + "affinity": "INTEGER" + }, + { + "fieldPath": "lastSeenStatus", + "columnName": "lastSeenStatus", + "affinity": "TEXT" + }, + { + "fieldPath": "birthday", + "columnName": "birthday", + "affinity": "TEXT" + }, + { + "fieldPath": "photo50", + "columnName": "photo50", + "affinity": "TEXT" + }, + { + "fieldPath": "photo100", + "columnName": "photo100", + "affinity": "TEXT" + }, + { + "fieldPath": "photo200", + "columnName": "photo200", + "affinity": "TEXT" + }, + { + "fieldPath": "photo400Orig", + "columnName": "photo400Orig", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "groups", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `screenName` TEXT NOT NULL, `photo50` TEXT, `photo100` TEXT, `photo200` TEXT, `membersCount` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "screenName", + "columnName": "screenName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "photo50", + "columnName": "photo50", + "affinity": "TEXT" + }, + { + "fieldPath": "photo100", + "columnName": "photo100", + "affinity": "TEXT" + }, + { + "fieldPath": "photo200", + "columnName": "photo200", + "affinity": "TEXT" + }, + { + "fieldPath": "membersCount", + "columnName": "membersCount", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "messages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `cmId` INTEGER NOT NULL, `text` TEXT, `isOut` INTEGER NOT NULL, `peerId` INTEGER NOT NULL, `fromId` INTEGER NOT NULL, `date` INTEGER NOT NULL, `randomId` INTEGER NOT NULL, `action` TEXT, `actionMemberId` INTEGER, `actionText` TEXT, `actionCmId` INTEGER, `actionMessage` TEXT, `updateTime` INTEGER, `important` INTEGER NOT NULL, `forwardIds` TEXT, `attachments` TEXT, `replyMessageId` INTEGER, `geoType` TEXT, `pinnedAt` INTEGER, `isPinned` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "cmId", + "columnName": "cmId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT" + }, + { + "fieldPath": "isOut", + "columnName": "isOut", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "peerId", + "columnName": "peerId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "fromId", + "columnName": "fromId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "randomId", + "columnName": "randomId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "action", + "columnName": "action", + "affinity": "TEXT" + }, + { + "fieldPath": "actionMemberId", + "columnName": "actionMemberId", + "affinity": "INTEGER" + }, + { + "fieldPath": "actionText", + "columnName": "actionText", + "affinity": "TEXT" + }, + { + "fieldPath": "actionCmId", + "columnName": "actionCmId", + "affinity": "INTEGER" + }, + { + "fieldPath": "actionMessage", + "columnName": "actionMessage", + "affinity": "TEXT" + }, + { + "fieldPath": "updateTime", + "columnName": "updateTime", + "affinity": "INTEGER" + }, + { + "fieldPath": "important", + "columnName": "important", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "forwardIds", + "columnName": "forwardIds", + "affinity": "TEXT" + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT" + }, + { + "fieldPath": "replyMessageId", + "columnName": "replyMessageId", + "affinity": "INTEGER" + }, + { + "fieldPath": "geoType", + "columnName": "geoType", + "affinity": "TEXT" + }, + { + "fieldPath": "pinnedAt", + "columnName": "pinnedAt", + "affinity": "INTEGER" + }, + { + "fieldPath": "isPinned", + "columnName": "isPinned", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "convos", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `localId` INTEGER NOT NULL, `ownerId` INTEGER, `title` TEXT, `photo50` TEXT, `photo100` TEXT, `photo200` TEXT, `isPhantom` INTEGER NOT NULL, `lastCmId` INTEGER NOT NULL, `inReadCmId` INTEGER NOT NULL, `outReadCmId` INTEGER NOT NULL, `inRead` INTEGER NOT NULL, `outRead` INTEGER NOT NULL, `lastMessageId` INTEGER, `unreadCount` INTEGER NOT NULL, `membersCount` INTEGER, `canChangePin` INTEGER NOT NULL, `canChangeInfo` INTEGER NOT NULL, `majorId` INTEGER NOT NULL, `minorId` INTEGER NOT NULL, `pinnedMessageId` INTEGER, `peerType` TEXT NOT NULL, `isArchived` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "localId", + "columnName": "localId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ownerId", + "columnName": "ownerId", + "affinity": "INTEGER" + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT" + }, + { + "fieldPath": "photo50", + "columnName": "photo50", + "affinity": "TEXT" + }, + { + "fieldPath": "photo100", + "columnName": "photo100", + "affinity": "TEXT" + }, + { + "fieldPath": "photo200", + "columnName": "photo200", + "affinity": "TEXT" + }, + { + "fieldPath": "isPhantom", + "columnName": "isPhantom", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastCmId", + "columnName": "lastCmId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "inReadCmId", + "columnName": "inReadCmId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "outReadCmId", + "columnName": "outReadCmId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "inRead", + "columnName": "inRead", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "outRead", + "columnName": "outRead", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastMessageId", + "columnName": "lastMessageId", + "affinity": "INTEGER" + }, + { + "fieldPath": "unreadCount", + "columnName": "unreadCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "membersCount", + "columnName": "membersCount", + "affinity": "INTEGER" + }, + { + "fieldPath": "canChangePin", + "columnName": "canChangePin", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canChangeInfo", + "columnName": "canChangeInfo", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "majorId", + "columnName": "majorId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "minorId", + "columnName": "minorId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "pinnedMessageId", + "columnName": "pinnedMessageId", + "affinity": "INTEGER" + }, + { + "fieldPath": "peerType", + "columnName": "peerType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isArchived", + "columnName": "isArchived", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a746865995959331f8a1b512c049dacb')" + ] + } +} \ No newline at end of file diff --git a/core/database/src/main/kotlin/dev/meloda/fast/database/CacheDatabase.kt b/core/database/src/main/kotlin/dev/meloda/fast/database/CacheDatabase.kt index c4abb7f2..5dbec1e6 100644 --- a/core/database/src/main/kotlin/dev/meloda/fast/database/CacheDatabase.kt +++ b/core/database/src/main/kotlin/dev/meloda/fast/database/CacheDatabase.kt @@ -3,12 +3,12 @@ package dev.meloda.fast.database import androidx.room.Database import androidx.room.RoomDatabase import androidx.room.TypeConverters -import dev.meloda.fast.database.dao.ConversationDao +import dev.meloda.fast.database.dao.ConvoDao import dev.meloda.fast.database.dao.GroupDao import dev.meloda.fast.database.dao.MessageDao import dev.meloda.fast.database.dao.UserDao import dev.meloda.fast.database.typeconverters.Converters -import dev.meloda.fast.model.database.VkConversationEntity +import dev.meloda.fast.model.database.VkConvoEntity import dev.meloda.fast.model.database.VkGroupEntity import dev.meloda.fast.model.database.VkMessageEntity import dev.meloda.fast.model.database.VkUserEntity @@ -18,15 +18,15 @@ import dev.meloda.fast.model.database.VkUserEntity VkUserEntity::class, VkGroupEntity::class, VkMessageEntity::class, - VkConversationEntity::class + VkConvoEntity::class ], - version = 10 + version = 11 ) @TypeConverters(Converters::class) abstract class CacheDatabase : RoomDatabase() { abstract fun userDao(): UserDao abstract fun groupDao(): GroupDao abstract fun messageDao(): MessageDao - abstract fun conversationDao(): ConversationDao + abstract fun convoDao(): ConvoDao } diff --git a/core/database/src/main/kotlin/dev/meloda/fast/database/dao/ConversationDao.kt b/core/database/src/main/kotlin/dev/meloda/fast/database/dao/ConversationDao.kt deleted file mode 100644 index 8f40b279..00000000 --- a/core/database/src/main/kotlin/dev/meloda/fast/database/dao/ConversationDao.kt +++ /dev/null @@ -1,30 +0,0 @@ -package dev.meloda.fast.database.dao - -import androidx.room.Dao -import androidx.room.Query -import androidx.room.Transaction -import dev.meloda.fast.model.database.ConversationWithMessage -import dev.meloda.fast.model.database.VkConversationEntity - -@Dao -abstract class ConversationDao : EntityDao { - - @Query("SELECT * FROM conversations") - abstract suspend fun getAll(): List - - @Query("SELECT * FROM conversations WHERE id IN (:ids)") - abstract suspend fun getAllByIds(ids: List): List - - @Query("SELECT * FROM conversations WHERE id IS (:id)") - abstract suspend fun getById(id: Long): VkConversationEntity? - - @Transaction - @Query("SELECT * FROM conversations WHERE id IS (:id)") - abstract suspend fun getByIdWithMessage(id: Long): ConversationWithMessage? - - @Query("DELETE FROM conversations WHERE rowid IN (:ids)") - abstract suspend fun deleteByIds(ids: List): Int -} - - - diff --git a/core/database/src/main/kotlin/dev/meloda/fast/database/dao/ConvoDao.kt b/core/database/src/main/kotlin/dev/meloda/fast/database/dao/ConvoDao.kt new file mode 100644 index 00000000..40494ba1 --- /dev/null +++ b/core/database/src/main/kotlin/dev/meloda/fast/database/dao/ConvoDao.kt @@ -0,0 +1,30 @@ +package dev.meloda.fast.database.dao + +import androidx.room.Dao +import androidx.room.Query +import androidx.room.Transaction +import dev.meloda.fast.model.database.ConvoWithMessage +import dev.meloda.fast.model.database.VkConvoEntity + +@Dao +abstract class ConvoDao : EntityDao { + + @Query("SELECT * FROM convos") + abstract suspend fun getAll(): List + + @Query("SELECT * FROM convos WHERE id IN (:ids)") + abstract suspend fun getAllByIds(ids: List): List + + @Query("SELECT * FROM convos WHERE id IS (:id)") + abstract suspend fun getById(id: Long): VkConvoEntity? + + @Transaction + @Query("SELECT * FROM convos WHERE id IS (:id)") + abstract suspend fun getByIdWithMessage(id: Long): ConvoWithMessage? + + @Query("DELETE FROM convos WHERE rowid IN (:ids)") + abstract suspend fun deleteByIds(ids: List): Int +} + + + diff --git a/core/database/src/main/kotlin/dev/meloda/fast/database/dao/MessageDao.kt b/core/database/src/main/kotlin/dev/meloda/fast/database/dao/MessageDao.kt index 68e9b634..4daecc9b 100644 --- a/core/database/src/main/kotlin/dev/meloda/fast/database/dao/MessageDao.kt +++ b/core/database/src/main/kotlin/dev/meloda/fast/database/dao/MessageDao.kt @@ -10,8 +10,8 @@ abstract class MessageDao : EntityDao { @Query("SELECT * FROM messages") abstract suspend fun getAll(): List - @Query("SELECT * FROM messages WHERE peerId IS (:conversationId)") - abstract suspend fun getAll(conversationId: Long): List + @Query("SELECT * FROM messages WHERE peerId IS (:convoId)") + abstract suspend fun getAll(convoId: Long): List @Query("SELECT * FROM messages WHERE id IN (:ids)") abstract suspend fun getAllByIds(ids: List): List diff --git a/core/database/src/main/kotlin/dev/meloda/fast/database/di/DatabaseModule.kt b/core/database/src/main/kotlin/dev/meloda/fast/database/di/DatabaseModule.kt index 35f5b279..00e38a30 100644 --- a/core/database/src/main/kotlin/dev/meloda/fast/database/di/DatabaseModule.kt +++ b/core/database/src/main/kotlin/dev/meloda/fast/database/di/DatabaseModule.kt @@ -23,7 +23,7 @@ val databaseModule = module { single { cacheDB().userDao() } single { cacheDB().groupDao() } single { cacheDB().messageDao() } - single { cacheDB().conversationDao() } + single { cacheDB().convoDao() } } private fun Scope.cacheDB(): CacheDatabase = get() diff --git a/core/domain/build.gradle.kts b/core/domain/build.gradle.kts index 4352644b..5b382d07 100644 --- a/core/domain/build.gradle.kts +++ b/core/domain/build.gradle.kts @@ -8,6 +8,7 @@ android { } dependencies { + api(projects.core.common) api(projects.core.data) api(projects.core.model) @@ -15,4 +16,8 @@ dependencies { implementation(libs.kotlinx.coroutines.core) implementation(libs.koin.core) implementation(libs.eithernet) + + implementation(libs.bundles.nanokt) + + implementation(libs.compose.ui) } diff --git a/core/domain/src/main/kotlin/dev/meloda/fast/domain/ConversationsUseCase.kt b/core/domain/src/main/kotlin/dev/meloda/fast/domain/ConvoUseCase.kt similarity index 56% rename from core/domain/src/main/kotlin/dev/meloda/fast/domain/ConversationsUseCase.kt rename to core/domain/src/main/kotlin/dev/meloda/fast/domain/ConvoUseCase.kt index 895866aa..d5217e8f 100644 --- a/core/domain/src/main/kotlin/dev/meloda/fast/domain/ConversationsUseCase.kt +++ b/core/domain/src/main/kotlin/dev/meloda/fast/domain/ConvoUseCase.kt @@ -1,25 +1,25 @@ package dev.meloda.fast.domain import dev.meloda.fast.data.State -import dev.meloda.fast.model.ConversationsFilter -import dev.meloda.fast.model.api.domain.VkConversation +import dev.meloda.fast.model.ConvosFilter +import dev.meloda.fast.model.api.domain.VkConvo import kotlinx.coroutines.flow.Flow -interface ConversationsUseCase : BaseUseCase { +interface ConvoUseCase : BaseUseCase { - suspend fun storeConversations(conversations: List) + suspend fun storeConvos(convos: List) - fun getConversations( + fun getConvos( count: Int? = null, offset: Int? = null, - filter: ConversationsFilter - ): Flow>> + filter: ConvosFilter + ): Flow>> fun getById( peerIds: List, extended: Boolean? = null, fields: String? = null - ): Flow>> + ): Flow>> fun delete(peerId: Long): Flow> diff --git a/core/domain/src/main/kotlin/dev/meloda/fast/domain/ConversationsUseCaseImpl.kt b/core/domain/src/main/kotlin/dev/meloda/fast/domain/ConvoUseCaseImpl.kt similarity index 65% rename from core/domain/src/main/kotlin/dev/meloda/fast/domain/ConversationsUseCaseImpl.kt rename to core/domain/src/main/kotlin/dev/meloda/fast/domain/ConvoUseCaseImpl.kt index 6574153d..094fc675 100644 --- a/core/domain/src/main/kotlin/dev/meloda/fast/domain/ConversationsUseCaseImpl.kt +++ b/core/domain/src/main/kotlin/dev/meloda/fast/domain/ConvoUseCaseImpl.kt @@ -1,30 +1,30 @@ package dev.meloda.fast.domain import dev.meloda.fast.data.State -import dev.meloda.fast.data.api.conversations.ConversationsRepository +import dev.meloda.fast.data.api.convos.ConvosRepository import dev.meloda.fast.data.mapToState -import dev.meloda.fast.model.ConversationsFilter -import dev.meloda.fast.model.api.domain.VkConversation +import dev.meloda.fast.model.ConvosFilter +import dev.meloda.fast.model.api.domain.VkConvo import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.withContext -class ConversationsUseCaseImpl( - private val repository: ConversationsRepository, -) : ConversationsUseCase { +class ConvoUseCaseImpl( + private val repository: ConvosRepository, +) : ConvoUseCase { - override suspend fun storeConversations( - conversations: List + override suspend fun storeConvos( + convos: List ) = withContext(Dispatchers.IO) { - repository.storeConversations(conversations) + repository.storeConvos(convos) } - override fun getConversations( + override fun getConvos( count: Int?, offset: Int?, - filter: ConversationsFilter - ): Flow>> = flowNewState { - repository.getConversations( + filter: ConvosFilter + ): Flow>> = flowNewState { + repository.getConvos( count = count, offset = offset, filter = filter @@ -35,8 +35,8 @@ class ConversationsUseCaseImpl( peerIds: List, extended: Boolean?, fields: String? - ): Flow>> = flowNewState { - repository.getConversationsById( + ): Flow>> = flowNewState { + repository.getConvosById( peerIds = peerIds, extended = extended, fields = fields diff --git a/core/domain/src/main/kotlin/dev/meloda/fast/domain/LoadConversationsByIdUseCase.kt b/core/domain/src/main/kotlin/dev/meloda/fast/domain/LoadConvosByIdUseCase.kt similarity index 54% rename from core/domain/src/main/kotlin/dev/meloda/fast/domain/LoadConversationsByIdUseCase.kt rename to core/domain/src/main/kotlin/dev/meloda/fast/domain/LoadConvosByIdUseCase.kt index be9f2830..ed4df799 100644 --- a/core/domain/src/main/kotlin/dev/meloda/fast/domain/LoadConversationsByIdUseCase.kt +++ b/core/domain/src/main/kotlin/dev/meloda/fast/domain/LoadConvosByIdUseCase.kt @@ -1,22 +1,22 @@ package dev.meloda.fast.domain import dev.meloda.fast.data.State -import dev.meloda.fast.data.api.conversations.ConversationsRepository +import dev.meloda.fast.data.api.convos.ConvosRepository import dev.meloda.fast.data.mapToState -import dev.meloda.fast.model.api.domain.VkConversation +import dev.meloda.fast.model.api.domain.VkConvo import kotlinx.coroutines.flow.Flow -class LoadConversationsByIdUseCase( - private val conversationsRepository: ConversationsRepository +class LoadConvosByIdUseCase( + private val convosRepository: ConvosRepository ) : BaseUseCase { operator fun invoke( peerIds: List, extended: Boolean? = null, fields: String? = null - ): Flow>> = flowNewState { - conversationsRepository - .getConversationsById( + ): Flow>> = flowNewState { + convosRepository + .getConvosById( peerIds = peerIds, extended = extended, fields = fields, diff --git a/core/domain/src/main/kotlin/dev/meloda/fast/domain/LongPollUpdatesParser.kt b/core/domain/src/main/kotlin/dev/meloda/fast/domain/LongPollUpdatesParser.kt index 5223a06e..43497f23 100644 --- a/core/domain/src/main/kotlin/dev/meloda/fast/domain/LongPollUpdatesParser.kt +++ b/core/domain/src/main/kotlin/dev/meloda/fast/domain/LongPollUpdatesParser.kt @@ -9,12 +9,12 @@ import dev.meloda.fast.common.extensions.toList import dev.meloda.fast.data.UserConfig import dev.meloda.fast.data.processState import dev.meloda.fast.model.ApiEvent -import dev.meloda.fast.model.ConversationFlags +import dev.meloda.fast.model.ConvoFlags import dev.meloda.fast.model.InteractionType import dev.meloda.fast.model.LongPollEvent import dev.meloda.fast.model.LongPollParsedEvent import dev.meloda.fast.model.MessageFlags -import dev.meloda.fast.model.api.domain.VkConversation +import dev.meloda.fast.model.api.domain.VkConvo import dev.meloda.fast.model.api.domain.VkMessage import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope @@ -28,7 +28,7 @@ import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine class LongPollUpdatesParser( - private val conversationsUseCase: ConversationsUseCase, + private val convoUseCase: ConvoUseCase, private val messagesUseCase: MessagesUseCase ) { private val job = SupervisorJob() @@ -271,9 +271,9 @@ class LongPollUpdatesParser( val message = async { loadMessage(peerId = peerId, cmId = cmId) }.await() - val conversation = + val convo = async { - loadConversation( + loadConvo( peerId = peerId, extended = true, fields = VkConstants.ALL_FIELDS @@ -287,7 +287,7 @@ class LongPollUpdatesParser( .onEvent( LongPollParsedEvent.NewMessage( message = message, - inArchive = conversation?.isArchived == true + inArchive = convo?.isArchived == true // TODO: 03-Apr-25, Danil Nikolaev: // load user settings about restoring chats with // enabled notifications from archive @@ -368,13 +368,13 @@ class LongPollUpdatesParser( val eventsToSend = mutableListOf() - val parsedFlags = ConversationFlags.parse(flags) + val parsedFlags = ConvoFlags.parse(flags) coroutineScope.launch(Dispatchers.IO) { parsedFlags.forEach { flag -> when (flag) { - ConversationFlags.ARCHIVED -> { - val conversation = loadConversation( + ConvoFlags.ARCHIVED -> { + val convo = loadConvo( peerId = peerId, extended = true, fields = VkConstants.ALL_FIELDS @@ -382,11 +382,11 @@ class LongPollUpdatesParser( val message = loadMessage( peerId = peerId, - cmId = conversation.lastCmId + cmId = convo.lastCmId ) val eventToSend = LongPollParsedEvent.ChatArchived( - conversation = conversation.copy(lastMessage = message), + convo = convo.copy(lastMessage = message), archived = false ) eventsToSend += eventToSend @@ -423,13 +423,13 @@ class LongPollUpdatesParser( val eventsToSend = mutableListOf() - val parsedFlags = ConversationFlags.parse(flags) + val parsedFlags = ConvoFlags.parse(flags) coroutineScope.launch(Dispatchers.IO) { parsedFlags.forEach { flag -> when (flag) { - ConversationFlags.ARCHIVED -> { - val conversation = loadConversation( + ConvoFlags.ARCHIVED -> { + val convo = loadConvo( peerId = peerId, extended = true, fields = VkConstants.ALL_FIELDS @@ -437,11 +437,11 @@ class LongPollUpdatesParser( val message = loadMessage( peerId = peerId, - cmId = conversation.lastCmId + cmId = convo.lastCmId ) val eventToSend = LongPollParsedEvent.ChatArchived( - conversation = conversation.copy(lastMessage = message), + convo = convo.copy(lastMessage = message), archived = true ) eventsToSend += eventToSend @@ -673,29 +673,29 @@ class LongPollUpdatesParser( } } - private suspend fun loadConversation( + private suspend fun loadConvo( peerId: Long, extended: Boolean = false, fields: String? = null - ): VkConversation? = suspendCoroutine { continuation -> + ): VkConvo? = suspendCoroutine { continuation -> coroutineScope.launch(Dispatchers.IO) { - conversationsUseCase.getById( + convoUseCase.getById( peerIds = listOf(peerId), extended = extended, fields = fields ).listenValue(coroutineScope) { state -> state.processState( error = { error -> - Log.e("LongPollUpdatesParser", "loadConversation: error: $error") + Log.e("LongPollUpdatesParser", "loadConvo: error: $error") continuation.resume(null) }, success = { response -> - val conversation = response.singleOrNull() ?: run { + val convo = response.singleOrNull() ?: run { continuation.resume(null) return@listenValue } - continuation.resume(conversation) + continuation.resume(convo) } ) } diff --git a/core/domain/src/main/kotlin/dev/meloda/fast/domain/MessagesUseCase.kt b/core/domain/src/main/kotlin/dev/meloda/fast/domain/MessagesUseCase.kt index 89a8b0ac..e5d880ef 100644 --- a/core/domain/src/main/kotlin/dev/meloda/fast/domain/MessagesUseCase.kt +++ b/core/domain/src/main/kotlin/dev/meloda/fast/domain/MessagesUseCase.kt @@ -14,7 +14,7 @@ interface MessagesUseCase : BaseUseCase { suspend fun storeMessages(messages: List) fun getMessagesHistory( - conversationId: Long, + convoId: Long, count: Int?, offset: Int? ): Flow> diff --git a/core/domain/src/main/kotlin/dev/meloda/fast/domain/MessagesUseCaseImpl.kt b/core/domain/src/main/kotlin/dev/meloda/fast/domain/MessagesUseCaseImpl.kt index 804f9380..3bbd40d7 100644 --- a/core/domain/src/main/kotlin/dev/meloda/fast/domain/MessagesUseCaseImpl.kt +++ b/core/domain/src/main/kotlin/dev/meloda/fast/domain/MessagesUseCaseImpl.kt @@ -23,12 +23,12 @@ class MessagesUseCaseImpl( } override fun getMessagesHistory( - conversationId: Long, + convoId: Long, count: Int?, offset: Int? ): Flow> = flowNewState { repository.getHistory( - conversationId = conversationId, + convoId = convoId, offset = offset, count = count ).mapToState() diff --git a/core/domain/src/main/kotlin/dev/meloda/fast/domain/di/DomainModule.kt b/core/domain/src/main/kotlin/dev/meloda/fast/domain/di/DomainModule.kt index de371265..eefcc8df 100644 --- a/core/domain/src/main/kotlin/dev/meloda/fast/domain/di/DomainModule.kt +++ b/core/domain/src/main/kotlin/dev/meloda/fast/domain/di/DomainModule.kt @@ -7,7 +7,7 @@ import dev.meloda.fast.domain.GetCurrentAccountUseCase import dev.meloda.fast.domain.GetLocalUserByIdUseCase import dev.meloda.fast.domain.GetLocalUsersByIdsUseCase import dev.meloda.fast.domain.GetMessageReadPeersUseCase -import dev.meloda.fast.domain.LoadConversationsByIdUseCase +import dev.meloda.fast.domain.LoadConvosByIdUseCase import dev.meloda.fast.domain.LoadUserByIdUseCase import dev.meloda.fast.domain.LoadUsersByIdsUseCase import dev.meloda.fast.domain.StoreUsersUseCase @@ -27,7 +27,7 @@ val domainModule = module { singleOf(::AccountUseCaseImpl) bind AccountUseCase::class singleOf(::GetCurrentAccountUseCase) - singleOf(::LoadConversationsByIdUseCase) + singleOf(::LoadConvosByIdUseCase) singleOf(::GetMessageReadPeersUseCase) } diff --git a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/util/ConversationDomainMapper.kt b/core/domain/src/main/kotlin/dev/meloda/fast/domain/util/ConvoMapper.kt similarity index 82% rename from feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/util/ConversationDomainMapper.kt rename to core/domain/src/main/kotlin/dev/meloda/fast/domain/util/ConvoMapper.kt index 95a57f41..9e231dc1 100644 --- a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/util/ConversationDomainMapper.kt +++ b/core/domain/src/main/kotlin/dev/meloda/fast/domain/util/ConvoMapper.kt @@ -1,7 +1,6 @@ -package dev.meloda.fast.conversations.util +package dev.meloda.fast.domain.util import android.content.res.Resources -import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString @@ -13,64 +12,22 @@ import dev.meloda.fast.common.extensions.orDots import dev.meloda.fast.common.model.UiImage import dev.meloda.fast.common.model.UiText import dev.meloda.fast.common.model.parseString -import dev.meloda.fast.common.util.TimeUtils import dev.meloda.fast.data.UserConfig import dev.meloda.fast.data.VkMemoryCache import dev.meloda.fast.model.InteractionType import dev.meloda.fast.model.api.PeerType import dev.meloda.fast.model.api.data.AttachmentType import dev.meloda.fast.model.api.domain.VkAttachment -import dev.meloda.fast.model.api.domain.VkConversation +import dev.meloda.fast.model.api.domain.VkConvo import dev.meloda.fast.model.api.domain.VkMessage import dev.meloda.fast.model.api.domain.VkVideoDomain import dev.meloda.fast.ui.R -import dev.meloda.fast.ui.model.api.ActionState -import dev.meloda.fast.ui.model.api.ConversationOption -import dev.meloda.fast.ui.model.api.UiConversation -import dev.meloda.fast.ui.util.ImmutableList -import dev.meloda.fast.ui.util.emptyImmutableList import java.util.Calendar import java.util.Locale import kotlin.math.ln import kotlin.math.pow -fun VkConversation.asPresentation( - resources: Resources, - useContactName: Boolean, - isExpanded: Boolean = false, - options: ImmutableList = emptyImmutableList() -): UiConversation = UiConversation( - id = id, - lastMessageId = lastMessageId, - avatar = extractAvatar(), - title = extractTitle(this, useContactName, resources), - unreadCount = extractUnreadCount(lastMessage, this), - date = TimeUtils.getLocalizedTime( - date = (lastMessage?.date ?: -1) * 1000L, - yearShort = { resources.getString(R.string.year_short) }, - monthShort = { resources.getString(R.string.month_short) }, - weekShort = { resources.getString(R.string.week_short) }, - dayShort = { resources.getString(R.string.day_short) }, - now = { resources.getString(R.string.time_now) }, - ), - message = extractMessage(resources, lastMessage, id, peerType), - attachmentImage = if (lastMessage?.text == null) null - else getAttachmentConversationIcon(lastMessage), - isPinned = majorId > 0, - actionImageId = ActionState.parse(isPhantom, isCallInProgress).getResourceId(), - isBirthday = extractBirthday(this), - isUnread = !isRead(), - isAccount = isAccount(id), - isOnline = !isAccount(id) && user?.onlineStatus?.isOnline() == true, - lastMessage = lastMessage, - peerType = peerType, - interactionText = extractInteractionText(resources, this), - isExpanded = isExpanded, - isArchived = isArchived, - options = options -) - -fun VkConversation.extractAvatar() = when (peerType) { +fun VkConvo.extractAvatar(): UiImage = when (peerType) { PeerType.USER -> { if (isAccount(id)) null else user?.photo200 @@ -85,18 +42,17 @@ fun VkConversation.extractAvatar() = when (peerType) { } }?.let(UiImage::Url) ?: UiImage.Resource(R.drawable.ic_account_circle_cut) -private fun extractTitle( - conversation: VkConversation, +fun VkConvo.extractTitle( useContactName: Boolean, resources: Resources -) = when (conversation.peerType) { +) = when (peerType) { PeerType.USER -> { - if (isAccount(conversation.id)) { + if (isAccount(id)) { UiText.Resource(R.string.favorites) } else { - val userName = conversation.user?.let { user -> + val userName = user?.let { user -> if (useContactName) { - VkMemoryCache.getContact(user.id)?.name ?: user.fullName + VkMemoryCache.getContact(user.id)?.name } else { user.fullName } @@ -106,22 +62,22 @@ private fun extractTitle( } } - PeerType.GROUP -> UiText.Simple(conversation.group?.name.orDots()) - PeerType.CHAT -> UiText.Simple(conversation.title.orDots()) + PeerType.GROUP -> UiText.Simple(group?.name.orDots()) + PeerType.CHAT -> UiText.Simple(title.orDots()) }.parseString(resources).orDots() -private fun extractUnreadCount( +fun extractUnreadCount( lastMessage: VkMessage?, - conversation: VkConversation + convo: VkConvo ): String? = when { - lastMessage?.isOut == false && conversation.isInRead() -> null - conversation.unreadCount == 0 -> null - conversation.unreadCount < 1000 -> conversation.unreadCount.toString() + lastMessage?.isOut == false && convo.isInRead() -> null + convo.unreadCount == 0 -> null + convo.unreadCount < 1000 -> convo.unreadCount.toString() else -> { - val exp = (ln(conversation.unreadCount.toDouble()) / ln(1000.0)).toInt() + val exp = (ln(convo.unreadCount.toDouble()) / ln(1000.0)).toInt() val suffix = "KMBT"[exp - 1] - val result = conversation.unreadCount / 1000.0.pow(exp.toDouble()) + val result = convo.unreadCount / 1000.0.pow(exp.toDouble()) if (result.toLong().toDouble() == result) { String.format(Locale.getDefault(), "%.0f%s", result, suffix) @@ -131,11 +87,12 @@ private fun extractUnreadCount( } } -private fun extractMessage( +fun extractMessage( resources: Resources, lastMessage: VkMessage?, peerId: Long, - peerType: PeerType + peerType: PeerType, + showPeer: Boolean = true ): AnnotatedString { val youPrefix = UiText.Resource(R.string.you_message_prefix) .parseString(resources) @@ -160,6 +117,8 @@ private fun extractMessage( val messageText = lastMessage?.text.orEmpty() val prefixText: AnnotatedString? = when { + !showPeer -> null + actionMessage != null -> null lastMessage == null -> null @@ -226,16 +185,17 @@ private fun extractMessage( .let { text -> extractTextWithVisualizedMentions( isOut = lastMessage?.isOut == true, - originalText = text + originalText = text, + formatData = null ) } - .let { text -> prefix + text } + .let { text -> prefix + text.orEmpty() } } return finalText } -private fun extractActionText( +fun extractActionText( lastMessage: VkMessage?, resources: Resources, youPrefix: String @@ -539,7 +499,7 @@ private fun extractAttachmentIcon( } } -private fun extractAttachmentText( +fun extractAttachmentText( resources: Resources, lastMessage: VkMessage? ): AnnotatedString? = when { @@ -649,7 +609,7 @@ private fun isAttachmentsHaveOneType(attachments: List): Boolean { return true } -private fun extractForwardsText( +fun extractForwardsText( resources: Resources, lastMessage: VkMessage? ): AnnotatedString? = when { @@ -670,69 +630,7 @@ private fun extractForwardsText( else -> null } -fun extractTextWithVisualizedMentions( - isOut: Boolean, - originalText: String -): AnnotatedString = buildAnnotatedString { - val regex = """\[(id|club)(\d+)\|([^]]+)]""".toRegex() - - val mentions = mutableListOf() - - var currentIndex = 0 - val replacements = mutableListOf>() - - val result = regex.replace(originalText) { matchResult -> - val idPrefix = matchResult.groups[1]?.value.orEmpty() - val startIndex = matchResult.range.first - val endIndex = matchResult.range.last - - val id = matchResult.groups[2]?.value ?: "" - - val replaced = matchResult.groups[3]?.value.orEmpty() - - val indexRange = - (startIndex + currentIndex)..startIndex + currentIndex + replaced.length - - replacements.add(indexRange to replaced) - - mentions += MentionIndex( - id = id.toLongOrNull() ?: -1, - idPrefix = idPrefix, - indexRange = indexRange - ) - - currentIndex += replaced.length - (endIndex - startIndex + 1) - - replaced - } - - append(result) - - mentions.forEach { mention -> - val startIndex = mention.indexRange.first - val endIndex = mention.indexRange.last - - addStyle( - style = SpanStyle(color = Color.Red), - start = startIndex, - end = endIndex - ) - addStringAnnotation( - tag = mention.idPrefix, - annotation = mention.id.toString(), - start = startIndex, - end = endIndex - ) - } -} - -data class MentionIndex( - val id: Long, - val idPrefix: String, - val indexRange: IntRange -) - -private fun getAttachmentUiText( +fun getAttachmentUiText( attachment: VkAttachment, size: Int = 1, ): UiText { @@ -787,7 +685,7 @@ private fun getAttachmentUiText( }.let(UiText::Resource) } -private fun getAttachmentConversationIcon(message: VkMessage?): UiImage? { +fun getAttachmentConvoIcon(message: VkMessage?): UiImage? { return message?.attachments?.let { attachments -> if (attachments.isEmpty()) return null if (attachments.size == 1 || isAttachmentsHaveOneType(attachments)) { @@ -801,8 +699,8 @@ private fun getAttachmentConversationIcon(message: VkMessage?): UiImage? { } } -private fun extractBirthday(conversation: VkConversation): Boolean { - val birthday = conversation.user?.birthday ?: return false +fun extractBirthday(convo: VkConvo): Boolean { + val birthday = convo.user?.birthday ?: return false val splitBirthday = birthday.split(".").mapNotNull(String::toIntOrNull) if (splitBirthday.isEmpty()) return false @@ -822,25 +720,23 @@ private fun extractBirthday(conversation: VkConversation): Boolean { } else false } -private fun extractReadCondition( - conversation: VkConversation, +fun extractReadCondition( + convo: VkConvo, lastMessage: VkMessage? -): Boolean = !conversation.isRead(lastMessage) +): Boolean = !convo.isRead(lastMessage) -private fun isAccount(peerId: Long) = peerId == UserConfig.userId - -private fun extractInteractionText( +fun extractInteractionText( resources: Resources, - conversation: VkConversation + convo: VkConvo ): String? { - val interactionType = InteractionType.parse(conversation.interactionType) - val interactiveUsers = extractInteractionUsers(conversation) + val interactionType = InteractionType.parse(convo.interactionType) + val interactiveUsers = extractInteractionUsers(convo) val typingText = if (interactionType == null) { null } else { - if (!conversation.peerType.isChat() && interactiveUsers.size == 1) { + if (!convo.peerType.isChat() && interactiveUsers.size == 1) { when (interactionType) { InteractionType.File -> R.string.chat_interaction_uploading_file InteractionType.Photo -> R.string.chat_interaction_uploading_photo @@ -865,8 +761,8 @@ private fun extractInteractionText( return typingText } -private fun extractInteractionUsers(conversation: VkConversation): List { - return conversation.interactionIds.mapNotNull { id -> +fun extractInteractionUsers(convo: VkConvo): List { + return convo.interactionIds.mapNotNull { id -> when { id > 0 -> VkMemoryCache.getUser(id)?.fullName id < 0 -> VkMemoryCache.getGroup(id)?.name diff --git a/core/domain/src/main/kotlin/dev/meloda/fast/domain/util/ConvoUiMapper.kt b/core/domain/src/main/kotlin/dev/meloda/fast/domain/util/ConvoUiMapper.kt new file mode 100644 index 00000000..bfa03b61 --- /dev/null +++ b/core/domain/src/main/kotlin/dev/meloda/fast/domain/util/ConvoUiMapper.kt @@ -0,0 +1,47 @@ +package dev.meloda.fast.domain.util + +import android.content.res.Resources +import dev.meloda.fast.common.util.TimeUtils +import dev.meloda.fast.model.api.domain.VkConvo +import dev.meloda.fast.ui.R +import dev.meloda.fast.ui.model.vk.ActionState +import dev.meloda.fast.ui.model.vk.ConvoOption +import dev.meloda.fast.ui.model.vk.UiConvo +import dev.meloda.fast.ui.util.ImmutableList +import dev.meloda.fast.ui.util.emptyImmutableList + +fun VkConvo.asPresentation( + resources: Resources, + useContactName: Boolean, + isExpanded: Boolean = false, + options: ImmutableList = emptyImmutableList() +): UiConvo = UiConvo( + id = id, + lastMessageId = lastMessageId, + avatar = extractAvatar(), + title = extractTitle(useContactName, resources), + unreadCount = extractUnreadCount(lastMessage, this), + date = TimeUtils.getLocalizedTime( + date = (lastMessage?.date ?: -1) * 1000L, + yearShort = { resources.getString(R.string.year_short) }, + monthShort = { resources.getString(R.string.month_short) }, + weekShort = { resources.getString(R.string.week_short) }, + dayShort = { resources.getString(R.string.day_short) }, + now = { resources.getString(R.string.time_now) }, + ), + message = extractMessage(resources, lastMessage, id, peerType), + attachmentImage = if (lastMessage?.text == null) null + else getAttachmentConvoIcon(lastMessage), + isPinned = majorId > 0, + actionImageId = ActionState.parse(isPhantom, isCallInProgress).getResourceId(), + isBirthday = extractBirthday(this), + isUnread = !isRead(), + isAccount = isAccount(id), + isOnline = !isAccount(id) && user?.onlineStatus?.isOnline() == true, + lastMessage = lastMessage, + peerType = peerType, + interactionText = extractInteractionText(resources, this), + isExpanded = isExpanded, + isArchived = isArchived, + options = options +) diff --git a/core/domain/src/main/kotlin/dev/meloda/fast/domain/util/FriendMapper.kt b/core/domain/src/main/kotlin/dev/meloda/fast/domain/util/FriendUiMapper.kt similarity index 92% rename from core/domain/src/main/kotlin/dev/meloda/fast/domain/util/FriendMapper.kt rename to core/domain/src/main/kotlin/dev/meloda/fast/domain/util/FriendUiMapper.kt index 0c37194d..ded32796 100644 --- a/core/domain/src/main/kotlin/dev/meloda/fast/domain/util/FriendMapper.kt +++ b/core/domain/src/main/kotlin/dev/meloda/fast/domain/util/FriendUiMapper.kt @@ -3,7 +3,7 @@ package dev.meloda.fast.domain.util import dev.meloda.fast.common.model.UiImage import dev.meloda.fast.data.VkMemoryCache import dev.meloda.fast.model.api.domain.VkUser -import dev.meloda.fast.ui.model.api.UiFriend +import dev.meloda.fast.ui.model.vk.UiFriend fun VkUser.asPresentation( useContactNames: Boolean = false diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/util/MessageMapper.kt b/core/domain/src/main/kotlin/dev/meloda/fast/domain/util/MessageMapper.kt similarity index 66% rename from feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/util/MessageMapper.kt rename to core/domain/src/main/kotlin/dev/meloda/fast/domain/util/MessageMapper.kt index 12ae68ca..6b774fa4 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/util/MessageMapper.kt +++ b/core/domain/src/main/kotlin/dev/meloda/fast/domain/util/MessageMapper.kt @@ -1,35 +1,21 @@ -package dev.meloda.fast.messageshistory.util +package dev.meloda.fast.domain.util import android.content.res.Resources -import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.AnnotatedString.Annotation import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.StringAnnotation import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextDecoration import dev.meloda.fast.common.extensions.orDots import dev.meloda.fast.common.model.UiImage import dev.meloda.fast.common.model.UiText import dev.meloda.fast.common.model.parseString -import dev.meloda.fast.common.provider.ResourceProvider import dev.meloda.fast.data.UserConfig -import dev.meloda.fast.data.VkMemoryCache -import dev.meloda.fast.messageshistory.model.SendingStatus -import dev.meloda.fast.messageshistory.model.UiItem -import dev.meloda.fast.model.api.PeerType -import dev.meloda.fast.model.api.domain.FormatDataType -import dev.meloda.fast.model.api.domain.VkConversation +import dev.meloda.fast.model.api.PeerType.Companion.getPeerType import dev.meloda.fast.model.api.domain.VkMessage import dev.meloda.fast.ui.R -import dev.meloda.fast.ui.util.ImmutableList.Companion.toImmutableList import java.text.SimpleDateFormat import java.util.Locale -private fun isAccount(fromId: Long) = fromId == UserConfig.userId - fun VkMessage.extractAvatar() = when { isUser() -> { if (isAccount(id)) null @@ -59,111 +45,15 @@ fun VkMessage.extractTitle(): String = when { fun VkMessage.extractReplyTitle(): String? = replyMessage?.extractTitle() -// TODO: 24-Jun-25, Danil Nikolaev: improve -fun VkMessage.extractReplySummary(): String? = when (val message = replyMessage) { - null -> null - else -> { - when { - message.text != null -> message.text - else -> null - } - } -} - -fun VkConversation.extractAvatar(): UiImage = when (peerType) { - PeerType.USER -> { - if (isAccount(id)) null - else user?.photo200 - } - - PeerType.GROUP -> { - group?.photo200 - } - - PeerType.CHAT -> { - photo200 - } -}?.let(UiImage::Url) ?: UiImage.Resource(R.drawable.ic_account_circle_cut) - -fun VkConversation.extractTitle( - useContactName: Boolean, - resources: Resources -) = when (peerType) { - PeerType.USER -> { - if (isAccount(id)) { - UiText.Resource(R.string.favorites) - } else { - val userName = user?.let { user -> - if (useContactName) { - VkMemoryCache.getContact(user.id)?.name - } else { - user.fullName - } - } - - UiText.Simple(userName.orDots()) - } - } - - PeerType.GROUP -> UiText.Simple(group?.name.orDots()) - PeerType.CHAT -> UiText.Simple(title.orDots()) -}.parseString(resources).orDots() - -fun VkMessage.asPresentation( - conversation: VkConversation, - resourceProvider: ResourceProvider, - showName: Boolean, - prevMessage: VkMessage?, - nextMessage: VkMessage?, - showTimeInActionMessages: Boolean, - isSelected: Boolean -): UiItem = when { - action != null -> UiItem.ActionMessage( - id = id, - cmId = cmId, - text = extractActionText( - resources = resourceProvider.resources, - youPrefix = resourceProvider.getString(R.string.you_message_prefix), - showTime = showTimeInActionMessages - ) ?: buildAnnotatedString { }, - actionCmId = actionConversationMessageId +fun VkMessage.extractReplySummary(resources: Resources): AnnotatedString? = + extractMessage( + resources = resources, + lastMessage = this, + peerId = peerId, + peerType = getPeerType(), + showPeer = false ) - else -> UiItem.Message( - id = id, - cmId = cmId, - text = extractTextWithVisualizedMentions( - isOut = isOut, - originalText = text, - formatData = formatData - ), - isOut = isOut, - fromId = fromId, - date = extractDate(), - randomId = randomId, - isInChat = isPeerChat(), - name = extractTitle(), - showDate = true, - showAvatar = extractShowAvatar(nextMessage), - showName = showName && extractShowName(prevMessage), - avatar = extractAvatar(), - isEdited = updateTime != null, - isRead = isRead(conversation), - sendingStatus = when { - isFailed() -> SendingStatus.FAILED - id <= 0 -> SendingStatus.SENDING - else -> SendingStatus.SENT - }, - isSelected = isSelected, - isPinned = isPinned, - isImportant = isImportant, - attachments = attachments?.ifEmpty { null }?.toImmutableList(), - replyCmId = replyMessage?.cmId, - replyTitle = extractReplyTitle(), - replySummary = extractReplySummary() - ) -} - fun VkMessage.extractShowAvatar(nextMessage: VkMessage?): Boolean { if (isOut) return false return nextMessage == null || nextMessage.fromId != fromId @@ -569,153 +459,3 @@ fun VkMessage.extractActionText( } } } - -// TODO: 04-Apr-25, Danil Nikolaev: get rid of method duplication -fun extractTextWithVisualizedMentions( - isOut: Boolean, - originalText: String?, - formatData: VkMessage.FormatData? -): AnnotatedString? { - if (originalText == null) return null - - val annotations = - mutableListOf>() - - val regex = """\[(id|club)(\d+)\|([^]]+)]""".toRegex() - - val mentions = mutableListOf() - - var currentIndex = 0 - val replacements = mutableListOf>() - - val newText = regex.replace(originalText) { matchResult -> - val idPrefix = matchResult.groups[1]?.value.orEmpty() - val startIndex = matchResult.range.first - val endIndex = matchResult.range.last - - val id = matchResult.groups[2]?.value ?: "" - - val replaced = matchResult.groups[3]?.value.orEmpty() - - val indexRange = - (startIndex + currentIndex)..startIndex + currentIndex + replaced.length - - replacements.add(indexRange to replaced) - - mentions += MentionIndex( - id = id.toLongOrNull() ?: -1, - idPrefix = idPrefix, - indexRange = indexRange - ) - - currentIndex += replaced.length - (endIndex - startIndex + 1) - - replaced - } - - mentions.forEach { mention -> - val startIndex = mention.indexRange.first - val endIndex = mention.indexRange.last - - annotations += if (isOut) { - AnnotatedString.Range( - item = SpanStyle(textDecoration = TextDecoration.Underline), - start = startIndex, - end = endIndex - ) - } else { - AnnotatedString.Range( - item = SpanStyle(color = Color.Red), - start = startIndex, - end = endIndex - ) - } - - annotations += AnnotatedString.Range( - item = StringAnnotation(mention.id.toString()), - tag = mention.idPrefix, - start = startIndex, - end = endIndex - ) - } - - if (formatData == null) return AnnotatedString(text = newText, annotations = annotations) - - var current = 0 - - val newOffsets = formatData.items.map { (offset, length) -> - val r = replacements.filter { (range, _) -> - (range - current) collidesWith (offset.. range.first - } - - current = r.sumOf { (range, _) -> range.last - range.first - 1 } - - offset + current - } - - formatData.items.forEachIndexed { index, item -> - val offset = newOffsets[index] - - val spanStyle = when (item.type) { - FormatDataType.BOLD -> { - SpanStyle(fontWeight = FontWeight.SemiBold) - } - - FormatDataType.ITALIC -> { - SpanStyle(fontStyle = FontStyle.Italic) - } - - FormatDataType.UNDERLINE -> { - SpanStyle(textDecoration = TextDecoration.Underline) - } - - FormatDataType.URL -> { - annotations += AnnotatedString.Range( - item = StringAnnotation(item.url.orEmpty()), - start = offset, - end = offset + item.length, - tag = newText.substring(offset, offset + item.length) - ) - - if (isOut) { - SpanStyle( - fontWeight = FontWeight.SemiBold, - textDecoration = TextDecoration.Underline - ) - - } else { - SpanStyle( - fontWeight = FontWeight.SemiBold, - color = Color.Red - ) - } - } - } - - annotations += AnnotatedString.Range( - item = spanStyle, - start = offset, - end = offset + item.length - ) - } - - return AnnotatedString(text = newText, annotations = annotations) -} - -data class MentionIndex( - val id: Long, - val idPrefix: String, - val indexRange: IntRange -) - -infix fun ClosedRange.collidesWith(other: ClosedRange): Boolean { - return this.start < other.endInclusive && other.start < this.endInclusive -} - -operator fun ClosedRange.minus(other: ClosedRange): ClosedRange { - return (this.start - other.start)..(this.endInclusive - other.endInclusive) -} - -operator fun ClosedRange.minus(other: Int): ClosedRange { - return (this.start - other)..(this.endInclusive - other) -} diff --git a/core/domain/src/main/kotlin/dev/meloda/fast/domain/util/MessageUiMapper.kt b/core/domain/src/main/kotlin/dev/meloda/fast/domain/util/MessageUiMapper.kt new file mode 100644 index 00000000..9cdb0f24 --- /dev/null +++ b/core/domain/src/main/kotlin/dev/meloda/fast/domain/util/MessageUiMapper.kt @@ -0,0 +1,65 @@ +package dev.meloda.fast.domain.util + +import androidx.compose.ui.text.buildAnnotatedString +import dev.meloda.fast.common.provider.ResourceProvider +import dev.meloda.fast.model.api.domain.VkConvo +import dev.meloda.fast.model.api.domain.VkMessage +import dev.meloda.fast.ui.R +import dev.meloda.fast.ui.model.vk.MessageUiItem +import dev.meloda.fast.ui.model.vk.SendingStatus +import dev.meloda.fast.ui.util.ImmutableList.Companion.toImmutableList + +fun VkMessage.asPresentation( + convo: VkConvo, + resourceProvider: ResourceProvider, + showName: Boolean, + prevMessage: VkMessage?, + nextMessage: VkMessage?, + showTimeInActionMessages: Boolean, + isSelected: Boolean +): MessageUiItem = when { + action != null -> MessageUiItem.ActionMessage( + id = id, + cmId = cmId, + text = extractActionText( + resources = resourceProvider.resources, + youPrefix = resourceProvider.getString(R.string.you_message_prefix), + showTime = showTimeInActionMessages + ) ?: buildAnnotatedString { }, + actionCmId = actionCmId + ) + + else -> MessageUiItem.Message( + id = id, + cmId = cmId, + text = extractTextWithVisualizedMentions( + isOut = isOut, + originalText = text, + formatData = formatData + ), + isOut = isOut, + fromId = fromId, + date = extractDate(), + randomId = randomId, + isInChat = isPeerChat(), + name = extractTitle(), + showDate = true, + showAvatar = extractShowAvatar(nextMessage), + showName = showName && extractShowName(prevMessage), + avatar = extractAvatar(), + isEdited = updateTime != null, + isRead = isRead(convo), + sendingStatus = when { + isFailed() -> SendingStatus.FAILED + id <= 0 -> SendingStatus.SENDING + else -> SendingStatus.SENT + }, + isSelected = isSelected, + isPinned = isPinned, + isImportant = isImportant, + attachments = attachments?.ifEmpty { null }?.toImmutableList(), + replyCmId = replyMessage?.cmId, + replyTitle = extractReplyTitle(), + replySummary = replyMessage?.extractReplySummary(resourceProvider.resources) + ) +} diff --git a/core/domain/src/main/kotlin/dev/meloda/fast/domain/util/Utils.kt b/core/domain/src/main/kotlin/dev/meloda/fast/domain/util/Utils.kt new file mode 100644 index 00000000..39d0579e --- /dev/null +++ b/core/domain/src/main/kotlin/dev/meloda/fast/domain/util/Utils.kt @@ -0,0 +1,177 @@ +package dev.meloda.fast.domain.util + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.StringAnnotation +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextDecoration +import com.conena.nanokt.collections.indexOfFirstOrNull +import dev.meloda.fast.common.extensions.collidesWith +import dev.meloda.fast.common.extensions.minus +import dev.meloda.fast.data.UserConfig +import dev.meloda.fast.model.api.domain.FormatDataType +import dev.meloda.fast.model.api.domain.VkMessage +import dev.meloda.fast.ui.model.vk.MentionIndex +import dev.meloda.fast.ui.model.vk.MessageUiItem + +fun emptyAnnotatedString(): AnnotatedString = AnnotatedString(text = "") + +fun AnnotatedString?.orEmpty(): AnnotatedString = this ?: emptyAnnotatedString() + +fun String.annotated(): AnnotatedString = AnnotatedString(text = this) + +fun isAccount(id: Long) = id == UserConfig.userId + +fun extractTextWithVisualizedMentions( + isOut: Boolean, + originalText: String?, + formatData: VkMessage.FormatData? +): AnnotatedString? { + if (originalText == null) return null + + val annotations = + mutableListOf>() + + val regex = """\[(id|club)(\d+)\|([^]]+)]""".toRegex() + + val mentions = mutableListOf() + + var currentIndex = 0 + val replacements = mutableListOf>() + + val newText = regex.replace(originalText) { matchResult -> + val idPrefix = matchResult.groups[1]?.value.orEmpty() + val startIndex = matchResult.range.first + val endIndex = matchResult.range.last + + val id = matchResult.groups[2]?.value ?: "" + + val replaced = matchResult.groups[3]?.value.orEmpty() + + val indexRange = + (startIndex + currentIndex)..startIndex + currentIndex + replaced.length + + replacements.add(indexRange to replaced) + + mentions += MentionIndex( + id = id.toLongOrNull() ?: -1, + idPrefix = idPrefix, + indexRange = indexRange + ) + + currentIndex += replaced.length - (endIndex - startIndex + 1) + + replaced + } + + mentions.forEach { mention -> + val startIndex = mention.indexRange.first + val endIndex = mention.indexRange.last + + annotations += if (isOut) { + AnnotatedString.Range( + item = SpanStyle(textDecoration = TextDecoration.Underline), + start = startIndex, + end = endIndex + ) + } else { + AnnotatedString.Range( + item = SpanStyle(color = Color.Red), + start = startIndex, + end = endIndex + ) + } + + annotations += AnnotatedString.Range( + item = StringAnnotation(mention.id.toString()), + tag = mention.idPrefix, + start = startIndex, + end = endIndex + ) + } + + if (formatData == null) { + return AnnotatedString(text = newText, annotations = annotations) + } + + var current = 0 + + val newOffsets = formatData.items.map { (offset, length) -> + val r = replacements.filter { (range, _) -> + (range - current) collidesWith (offset.. range.first + } + + current = r.sumOf { (range, _) -> range.last - range.first - 1 } + + offset + current + } + + formatData.items.forEachIndexed { index, item -> + val offset = newOffsets[index] + + val spanStyle = when (item.type) { + FormatDataType.BOLD -> { + SpanStyle(fontWeight = FontWeight.SemiBold) + } + + FormatDataType.ITALIC -> { + SpanStyle(fontStyle = FontStyle.Italic) + } + + FormatDataType.UNDERLINE -> { + SpanStyle(textDecoration = TextDecoration.Underline) + } + + FormatDataType.URL -> { + annotations += AnnotatedString.Range( + item = StringAnnotation(item.url.orEmpty()), + start = offset, + end = offset + item.length, + tag = newText.substring(offset, offset + item.length) + ) + + if (isOut) { + SpanStyle( + fontWeight = FontWeight.SemiBold, + textDecoration = TextDecoration.Underline + ) + + } else { + SpanStyle( + fontWeight = FontWeight.SemiBold, + color = Color.Red + ) + } + } + } + + annotations += AnnotatedString.Range( + item = spanStyle, + start = offset, + end = offset + item.length + ) + } + + return AnnotatedString(text = newText, annotations = annotations) +} + + +fun List.firstMessage(): MessageUiItem.Message = + filterIsInstance().first() + +fun List.firstMessageOrNull(): MessageUiItem.Message? = + filterIsInstance().firstOrNull() + +fun List.indexOfMessageById(messageId: Long): Int = + indexOfFirst { it.id == messageId } + +fun List.findMessageById(messageId: Long): MessageUiItem.Message? = + firstOrNull { it.id == messageId } as MessageUiItem.Message? + +fun List.indexOfMessageByCmId(cmId: Long): Int? = + indexOfFirstOrNull { it.cmId == cmId } + +fun List.findMessageByCmId(cmId: Long): MessageUiItem.Message = + first { it.cmId == cmId } as MessageUiItem.Message diff --git a/core/model/src/main/kotlin/dev/meloda/fast/model/ConversationFlags.kt b/core/model/src/main/kotlin/dev/meloda/fast/model/ConvoFlags.kt similarity index 74% rename from core/model/src/main/kotlin/dev/meloda/fast/model/ConversationFlags.kt rename to core/model/src/main/kotlin/dev/meloda/fast/model/ConvoFlags.kt index fcae5e05..2b092505 100644 --- a/core/model/src/main/kotlin/dev/meloda/fast/model/ConversationFlags.kt +++ b/core/model/src/main/kotlin/dev/meloda/fast/model/ConvoFlags.kt @@ -1,6 +1,6 @@ package dev.meloda.fast.model -enum class ConversationFlags(val value: Int) { +enum class ConvoFlags(val value: Int) { DISABLE_PUSH(16), DISABLE_SOUND(32), INCOMING_CHAT_REQUEST(256), @@ -17,10 +17,10 @@ enum class ConversationFlags(val value: Int) { companion object { - fun parse(mask: Int): List { - val flags = mutableListOf() + fun parse(mask: Int): List { + val flags = mutableListOf() - ConversationFlags.entries.forEach { flag -> + ConvoFlags.entries.forEach { flag -> if (mask and flag.value > 0) { flags.add(flag) } diff --git a/core/model/src/main/kotlin/dev/meloda/fast/model/ConversationsFilter.kt b/core/model/src/main/kotlin/dev/meloda/fast/model/ConvosFilter.kt similarity index 69% rename from core/model/src/main/kotlin/dev/meloda/fast/model/ConversationsFilter.kt rename to core/model/src/main/kotlin/dev/meloda/fast/model/ConvosFilter.kt index 9e0ebfd2..6ca0cb82 100644 --- a/core/model/src/main/kotlin/dev/meloda/fast/model/ConversationsFilter.kt +++ b/core/model/src/main/kotlin/dev/meloda/fast/model/ConvosFilter.kt @@ -1,5 +1,5 @@ package dev.meloda.fast.model -enum class ConversationsFilter { +enum class ConvosFilter { ALL, UNREAD, ARCHIVE, BUSINESS_NOTIFY } diff --git a/core/model/src/main/kotlin/dev/meloda/fast/model/LongPollParsedEvent.kt b/core/model/src/main/kotlin/dev/meloda/fast/model/LongPollParsedEvent.kt index 4095ee8f..c1729d2e 100644 --- a/core/model/src/main/kotlin/dev/meloda/fast/model/LongPollParsedEvent.kt +++ b/core/model/src/main/kotlin/dev/meloda/fast/model/LongPollParsedEvent.kt @@ -1,6 +1,6 @@ package dev.meloda.fast.model -import dev.meloda.fast.model.api.domain.VkConversation +import dev.meloda.fast.model.api.domain.VkConvo import dev.meloda.fast.model.api.domain.VkMessage sealed interface LongPollParsedEvent { @@ -92,7 +92,7 @@ sealed interface LongPollParsedEvent { ) : LongPollParsedEvent data class ChatArchived( - val conversation: VkConversation, + val convo: VkConvo, val archived: Boolean ) : LongPollParsedEvent } diff --git a/core/model/src/main/kotlin/dev/meloda/fast/model/api/PeerType.kt b/core/model/src/main/kotlin/dev/meloda/fast/model/api/PeerType.kt index 07a7487d..5b5c134d 100644 --- a/core/model/src/main/kotlin/dev/meloda/fast/model/api/PeerType.kt +++ b/core/model/src/main/kotlin/dev/meloda/fast/model/api/PeerType.kt @@ -1,5 +1,7 @@ package dev.meloda.fast.model.api +import dev.meloda.fast.model.api.domain.VkMessage + enum class PeerType(val value: String) { USER("user"), GROUP("group"), @@ -13,5 +15,14 @@ enum class PeerType(val value: String) { fun parse(type: String): PeerType { return entries.first { it.value == type } } + + fun VkMessage.getPeerType(): PeerType { + return when { + peerId > 2_000_000_000 -> CHAT + peerId > 0 -> USER + peerId < 0 -> GROUP + else -> throw IllegalArgumentException("Unknown peer type for peerId: 0") + } + } } } diff --git a/core/model/src/main/kotlin/dev/meloda/fast/model/api/data/VkAttachmentHistoryMessageData.kt b/core/model/src/main/kotlin/dev/meloda/fast/model/api/data/VkAttachmentHistoryMessageData.kt index 27c11a35..2eff091d 100644 --- a/core/model/src/main/kotlin/dev/meloda/fast/model/api/data/VkAttachmentHistoryMessageData.kt +++ b/core/model/src/main/kotlin/dev/meloda/fast/model/api/data/VkAttachmentHistoryMessageData.kt @@ -8,7 +8,7 @@ import dev.meloda.fast.model.api.domain.VkAttachmentHistoryMessage data class VkAttachmentHistoryMessageData( @Json(name = "message_id") val messageId: Long, @Json(name = "date") val date: Int, - @Json(name = "cmid") val conversationMessageId: Long, + @Json(name = "cmid") val cmId: Long, @Json(name = "from_id") val fromId: Long, @Json(name = "position") val position: Int, @Json(name = "attachment") val attachment: VkAttachmentItemData @@ -16,7 +16,7 @@ data class VkAttachmentHistoryMessageData( fun toDomain(): VkAttachmentHistoryMessage = VkAttachmentHistoryMessage( messageId = messageId, - conversationMessageId = conversationMessageId, + cmId = cmId, date = date, fromId = fromId, position = position, diff --git a/core/model/src/main/kotlin/dev/meloda/fast/model/api/data/VkConversationData.kt b/core/model/src/main/kotlin/dev/meloda/fast/model/api/data/VkConvoData.kt similarity index 91% rename from core/model/src/main/kotlin/dev/meloda/fast/model/api/data/VkConversationData.kt rename to core/model/src/main/kotlin/dev/meloda/fast/model/api/data/VkConvoData.kt index 07eb6b23..ef1e2b5d 100644 --- a/core/model/src/main/kotlin/dev/meloda/fast/model/api/data/VkConversationData.kt +++ b/core/model/src/main/kotlin/dev/meloda/fast/model/api/data/VkConvoData.kt @@ -3,19 +3,19 @@ package dev.meloda.fast.model.api.data import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import dev.meloda.fast.model.api.PeerType -import dev.meloda.fast.model.api.domain.VkConversation +import dev.meloda.fast.model.api.domain.VkConvo import dev.meloda.fast.model.api.domain.VkMessage @JsonClass(generateAdapter = true) -data class VkConversationData( +data class VkConvoData( @Json(name = "peer") val peer: Peer, @Json(name = "last_message_id") val lastMessageId: Long?, @Json(name = "in_read") val inRead: Long, @Json(name = "out_read") val outRead: Long, - @Json(name = "in_read_cmid") val inReadConversationMessageId: Long, - @Json(name = "out_read_cmid") val outReadConversationMessageId: Long, + @Json(name = "in_read_cmid") val inReadCmId: Long, + @Json(name = "out_read_cmid") val outReadCmId: Long, @Json(name = "sort_id") val sortId: SortId, - @Json(name = "last_conversation_message_id") val lastConversationMessageId: Long, + @Json(name = "last_conversation_message_id") val lastCmId: Long, @Json(name = "is_marked_unread") val isMarkedUnread: Boolean, @Json(name = "important") val important: Boolean, @Json(name = "push_settings") val pushSettings: PushSettings?, @@ -111,7 +111,7 @@ data class VkConversationData( fun asDomain( lastMessage: VkMessage? = null, - ): VkConversation = VkConversation( + ): VkConvo = VkConvo( id = peer.id, localId = peer.localId, title = chatSettings?.title, @@ -120,7 +120,7 @@ data class VkConversationData( photo200 = chatSettings?.photo?.photo200, isCallInProgress = callInProgress != null, isPhantom = chatSettings?.isDisappearing == true, - lastCmId = lastConversationMessageId, + lastCmId = lastCmId, inRead = inRead, outRead = outRead, lastMessageId = lastMessageId, @@ -132,8 +132,8 @@ data class VkConversationData( canChangePin = chatSettings?.acl?.canChangePin == true, canChangeInfo = chatSettings?.acl?.canChangeInfo == true, pinnedMessageId = chatSettings?.pinnedMessage?.id, - inReadCmId = inReadConversationMessageId, - outReadCmId = outReadConversationMessageId, + inReadCmId = inReadCmId, + outReadCmId = outReadCmId, interactionType = -1, interactionIds = emptyList(), peerType = PeerType.parse(peer.type), diff --git a/core/model/src/main/kotlin/dev/meloda/fast/model/api/data/VkMessageData.kt b/core/model/src/main/kotlin/dev/meloda/fast/model/api/data/VkMessageData.kt index d6ef7650..1aa0fbb5 100644 --- a/core/model/src/main/kotlin/dev/meloda/fast/model/api/data/VkMessageData.kt +++ b/core/model/src/main/kotlin/dev/meloda/fast/model/api/data/VkMessageData.kt @@ -56,7 +56,7 @@ data class VkMessageData( @Json(name = "type") val type: String, @Json(name = "member_id") val memberId: Long?, @Json(name = "text") val text: String?, - @Json(name = "conversation_message_id") val conversationMessageId: Long?, + @Json(name = "conversation_message_id") val cmId: Long?, @Json(name = "message") val message: String? ) @@ -102,7 +102,7 @@ fun VkMessageData.asDomain(): VkMessage = VkMessage( action = VkMessage.Action.parse(action?.type), actionMemberId = action?.memberId, actionText = action?.text, - actionConversationMessageId = action?.conversationMessageId, + actionCmId = action?.cmId, actionMessage = action?.message, geoType = geo?.type, isImportant = important == true, diff --git a/core/model/src/main/kotlin/dev/meloda/fast/model/api/data/VkPinnedMessageData.kt b/core/model/src/main/kotlin/dev/meloda/fast/model/api/data/VkPinnedMessageData.kt index 82886752..2c44f0fd 100644 --- a/core/model/src/main/kotlin/dev/meloda/fast/model/api/data/VkPinnedMessageData.kt +++ b/core/model/src/main/kotlin/dev/meloda/fast/model/api/data/VkPinnedMessageData.kt @@ -12,7 +12,7 @@ data class VkPinnedMessageData( @Json(name = "from_id") val fromId: Long, @Json(name = "out") val out: Boolean?, @Json(name = "text") val text: String, - @Json(name = "conversation_message_id") val conversationMessageId: Long, + @Json(name = "conversation_message_id") val cmId: Long, @Json(name = "fwd_messages") val forwards: List?, @Json(name = "important") val important: Boolean = false, @Json(name = "random_id") val randomId: Long = 0, @@ -28,7 +28,7 @@ data class VkPinnedMessageData( fun mapToDomain(): VkMessage = VkMessage( id = id ?: -1, - cmId = conversationMessageId, + cmId = cmId, text = text.ifBlank { null }, isOut = out == true, peerId = peerId ?: -1, @@ -38,7 +38,7 @@ data class VkPinnedMessageData( action = VkMessage.Action.parse(action?.type), actionMemberId = action?.memberId, actionText = action?.text, - actionConversationMessageId = action?.conversationMessageId, + actionCmId = action?.cmId, actionMessage = action?.message, geoType = geo?.type, isImportant = important, diff --git a/core/model/src/main/kotlin/dev/meloda/fast/model/api/domain/VkAttachmentHistoryMessage.kt b/core/model/src/main/kotlin/dev/meloda/fast/model/api/domain/VkAttachmentHistoryMessage.kt index a4617240..026275fe 100644 --- a/core/model/src/main/kotlin/dev/meloda/fast/model/api/domain/VkAttachmentHistoryMessage.kt +++ b/core/model/src/main/kotlin/dev/meloda/fast/model/api/domain/VkAttachmentHistoryMessage.kt @@ -2,7 +2,7 @@ package dev.meloda.fast.model.api.domain data class VkAttachmentHistoryMessage( val messageId: Long, - val conversationMessageId: Long, + val cmId: Long, val date: Int, val fromId: Long, val position: Int, diff --git a/core/model/src/main/kotlin/dev/meloda/fast/model/api/domain/VkConversation.kt b/core/model/src/main/kotlin/dev/meloda/fast/model/api/domain/VkConvo.kt similarity index 91% rename from core/model/src/main/kotlin/dev/meloda/fast/model/api/domain/VkConversation.kt rename to core/model/src/main/kotlin/dev/meloda/fast/model/api/domain/VkConvo.kt index 9b7fa495..6331730f 100644 --- a/core/model/src/main/kotlin/dev/meloda/fast/model/api/domain/VkConversation.kt +++ b/core/model/src/main/kotlin/dev/meloda/fast/model/api/domain/VkConvo.kt @@ -1,9 +1,9 @@ package dev.meloda.fast.model.api.domain import dev.meloda.fast.model.api.PeerType -import dev.meloda.fast.model.database.VkConversationEntity +import dev.meloda.fast.model.database.VkConvoEntity -data class VkConversation( +data class VkConvo( val id: Long, val localId: Long, val ownerId: Long?, @@ -54,7 +54,7 @@ data class VkConversation( } companion object { - val EMPTY: VkConversation = VkConversation( + val EMPTY: VkConvo = VkConvo( id = -1, localId = -1, ownerId = null, @@ -90,7 +90,7 @@ data class VkConversation( } } -fun VkConversation.asEntity(): VkConversationEntity = VkConversationEntity( +fun VkConvo.asEntity(): VkConvoEntity = VkConvoEntity( id = id, localId = localId, ownerId = ownerId, @@ -99,7 +99,7 @@ fun VkConversation.asEntity(): VkConversationEntity = VkConversationEntity( photo100 = photo100, photo200 = photo200, isPhantom = isPhantom, - lastConversationMessageId = lastCmId, + lastCmId = lastCmId, inReadCmId = inReadCmId, outReadCmId = outReadCmId, inRead = inRead, diff --git a/core/model/src/main/kotlin/dev/meloda/fast/model/api/domain/VkMessage.kt b/core/model/src/main/kotlin/dev/meloda/fast/model/api/domain/VkMessage.kt index cedf9a8e..10c036bd 100644 --- a/core/model/src/main/kotlin/dev/meloda/fast/model/api/domain/VkMessage.kt +++ b/core/model/src/main/kotlin/dev/meloda/fast/model/api/domain/VkMessage.kt @@ -16,7 +16,7 @@ data class VkMessage( val action: Action?, val actionMemberId: Long?, val actionText: String?, - val actionConversationMessageId: Long?, + val actionCmId: Long?, val actionMessage: String?, val updateTime: Int?, @@ -44,9 +44,9 @@ data class VkMessage( fun isGroup() = fromId < 0 - fun isRead(conversation: VkConversation): Boolean = when { + fun isRead(convo: VkConvo): Boolean = when { id <= 0 -> false - else -> conversation.isRead(this) + else -> convo.isRead(this) } fun hasAttachments(): Boolean = attachments.orEmpty().isNotEmpty() @@ -98,7 +98,7 @@ data class VkMessage( fun VkMessage.asEntity(): VkMessageEntity = VkMessageEntity( id = id, - conversationMessageId = cmId, + cmId = cmId, text = text, isOut = isOut, peerId = peerId, @@ -108,7 +108,7 @@ fun VkMessage.asEntity(): VkMessageEntity = VkMessageEntity( action = action?.value, actionMemberId = actionMemberId, actionText = actionText, - actionConversationMessageId = actionConversationMessageId, + actionCmId = actionCmId, actionMessage = actionMessage, updateTime = updateTime, important = isImportant, diff --git a/core/model/src/main/kotlin/dev/meloda/fast/model/api/requests/ConversationsRequest.kt b/core/model/src/main/kotlin/dev/meloda/fast/model/api/requests/ConvosRequest.kt similarity index 80% rename from core/model/src/main/kotlin/dev/meloda/fast/model/api/requests/ConversationsRequest.kt rename to core/model/src/main/kotlin/dev/meloda/fast/model/api/requests/ConvosRequest.kt index adeee04c..c210928d 100644 --- a/core/model/src/main/kotlin/dev/meloda/fast/model/api/requests/ConversationsRequest.kt +++ b/core/model/src/main/kotlin/dev/meloda/fast/model/api/requests/ConvosRequest.kt @@ -1,12 +1,12 @@ package dev.meloda.fast.model.api.requests -import dev.meloda.fast.model.ConversationsFilter +import dev.meloda.fast.model.ConvosFilter -data class ConversationsGetRequest( +data class ConvosGetRequest( val count: Int? = null, val offset: Int? = null, val fields: String = "", - val filter: ConversationsFilter = ConversationsFilter.ALL, + val filter: ConvosFilter = ConvosFilter.ALL, val extended: Boolean? = true, val startMessageId: Long? = null ) { diff --git a/core/model/src/main/kotlin/dev/meloda/fast/model/api/requests/MessagesRequest.kt b/core/model/src/main/kotlin/dev/meloda/fast/model/api/requests/MessagesRequest.kt index 572a3cd3..ecbc50e6 100644 --- a/core/model/src/main/kotlin/dev/meloda/fast/model/api/requests/MessagesRequest.kt +++ b/core/model/src/main/kotlin/dev/meloda/fast/model/api/requests/MessagesRequest.kt @@ -115,7 +115,7 @@ data class MessagesGetLongPollServerRequest( data class MessagesPinMessageRequest( val peerId: Long, val messageId: Long? = null, - val conversationMessageId: Long? = null + val cmId: Long? = null ) { val map: Map @@ -123,7 +123,7 @@ data class MessagesPinMessageRequest( "peer_id" to peerId.toString() ).apply { messageId?.let { this["message_id"] = it.toString() } - conversationMessageId?.let { this["conversation_message_id"] = it.toString() } + cmId?.let { this["conversation_message_id"] = it.toString() } } } @@ -136,7 +136,7 @@ data class MessagesUnpinMessageRequest(val peerId: Long) { data class MessagesDeleteRequest( val peerId: Long, val messagesIds: List? = null, - val conversationsMessagesIds: List? = null, + val cmIds: List? = null, val isSpam: Boolean? = null, val deleteForAll: Boolean? = null ) { @@ -149,7 +149,7 @@ data class MessagesDeleteRequest( deleteForAll?.let { this["delete_for_all"] = it.asInt().toString() } messagesIds?.let { this["message_ids"] = it.joinToString() } - conversationsMessagesIds?.let { + cmIds?.let { this["conversation_message_ids"] = it.joinToString() } } @@ -228,7 +228,7 @@ data class MessagesGetChatRequest( } -data class MessagesGetConversationMembersRequest( +data class MessagesGetConvoMembersRequest( val peerId: Long, val offset: Int? = null, val count: Int? = null, @@ -267,14 +267,14 @@ data class MessagesGetHistoryAttachmentsRequest( val offset: Int?, val preserveOrder: Boolean?, val attachmentTypes: List, - val conversationMessageId: Long, + val cmId: Long, val fields: String? ) { val map = mutableMapOf( "peer_id" to peerId.toString(), "attachment_types" to attachmentTypes.joinToString(","), - "cmid" to conversationMessageId.toString() + "cmid" to cmId.toString() ).apply { extended?.let { this["extended"] = it.toString() } count?.let { this["count"] = it.toString() } diff --git a/core/model/src/main/kotlin/dev/meloda/fast/model/api/responses/ConversationsResponse.kt b/core/model/src/main/kotlin/dev/meloda/fast/model/api/responses/ConvosResponse.kt similarity index 72% rename from core/model/src/main/kotlin/dev/meloda/fast/model/api/responses/ConversationsResponse.kt rename to core/model/src/main/kotlin/dev/meloda/fast/model/api/responses/ConvosResponse.kt index 70c0fb93..9a56685e 100644 --- a/core/model/src/main/kotlin/dev/meloda/fast/model/api/responses/ConversationsResponse.kt +++ b/core/model/src/main/kotlin/dev/meloda/fast/model/api/responses/ConvosResponse.kt @@ -3,15 +3,15 @@ package dev.meloda.fast.model.api.responses import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import dev.meloda.fast.model.api.data.VkContactData -import dev.meloda.fast.model.api.data.VkConversationData +import dev.meloda.fast.model.api.data.VkConvoData import dev.meloda.fast.model.api.data.VkGroupData import dev.meloda.fast.model.api.data.VkMessageData import dev.meloda.fast.model.api.data.VkUserData @JsonClass(generateAdapter = true) -data class ConversationsGetResponse( +data class ConvosGetResponse( @Json(name = "count") val count: Int, - @Json(name = "items") val items: List, + @Json(name = "items") val items: List, @Json(name = "unread_count") val unreadCount: Int?, @Json(name = "profiles") val profiles: List?, @Json(name = "groups") val groups: List?, @@ -19,21 +19,21 @@ data class ConversationsGetResponse( ) @JsonClass(generateAdapter = true) -data class ConversationsGetByIdResponse( +data class ConvosGetByIdResponse( @Json(name = "count") val count: Int, - @Json(name = "items") val items: List, + @Json(name = "items") val items: List, @Json(name = "profiles") val profiles: List?, @Json(name = "groups") val groups: List?, @Json(name = "contacts") val contacts: List? ) @JsonClass(generateAdapter = true) -data class ConversationsResponseItem( - @Json(name = "conversation") val conversation: VkConversationData, +data class ConvosResponseItem( + @Json(name = "conversation") val convo: VkConvoData, @Json(name = "last_message") val lastMessage: VkMessageData? ) @JsonClass(generateAdapter = true) -data class ConversationsDeleteResponse( +data class ConvosDeleteResponse( @Json(name = "last_deleted_id") val lastDeletedId: Long ) diff --git a/core/model/src/main/kotlin/dev/meloda/fast/model/api/responses/MessagesResponse.kt b/core/model/src/main/kotlin/dev/meloda/fast/model/api/responses/MessagesResponse.kt index f60adcec..55c2b418 100644 --- a/core/model/src/main/kotlin/dev/meloda/fast/model/api/responses/MessagesResponse.kt +++ b/core/model/src/main/kotlin/dev/meloda/fast/model/api/responses/MessagesResponse.kt @@ -5,7 +5,7 @@ import com.squareup.moshi.JsonClass import dev.meloda.fast.model.api.data.VkAttachmentHistoryMessageData import dev.meloda.fast.model.api.data.VkChatMemberData import dev.meloda.fast.model.api.data.VkContactData -import dev.meloda.fast.model.api.data.VkConversationData +import dev.meloda.fast.model.api.data.VkConvoData import dev.meloda.fast.model.api.data.VkGroupData import dev.meloda.fast.model.api.data.VkMessageData import dev.meloda.fast.model.api.data.VkUserData @@ -14,7 +14,7 @@ import dev.meloda.fast.model.api.data.VkUserData data class MessagesGetHistoryResponse( val count: Int, val items: List, - val conversations: List?, + val convos: List?, val profiles: List?, val groups: List?, val contacts: List? @@ -30,7 +30,7 @@ data class MessagesGetByIdResponse( ) @JsonClass(generateAdapter = true) -data class MessagesGetConversationMembersResponse( +data class MessagesGetConvoMembersResponse( val count: Int, val items: List?, val profiles: List?, diff --git a/core/model/src/main/kotlin/dev/meloda/fast/model/database/ConversationWithMessage.kt b/core/model/src/main/kotlin/dev/meloda/fast/model/database/ConvoWithMessage.kt similarity index 71% rename from core/model/src/main/kotlin/dev/meloda/fast/model/database/ConversationWithMessage.kt rename to core/model/src/main/kotlin/dev/meloda/fast/model/database/ConvoWithMessage.kt index 32c5f5dd..37cb3b62 100644 --- a/core/model/src/main/kotlin/dev/meloda/fast/model/database/ConversationWithMessage.kt +++ b/core/model/src/main/kotlin/dev/meloda/fast/model/database/ConvoWithMessage.kt @@ -3,8 +3,8 @@ package dev.meloda.fast.model.database import androidx.room.Embedded import androidx.room.Relation -data class ConversationWithMessage( - @Embedded val conversation: VkConversationEntity, +data class ConvoWithMessage( + @Embedded val convo: VkConvoEntity, @Relation( parentColumn = "lastMessageId", entityColumn = "id" diff --git a/core/model/src/main/kotlin/dev/meloda/fast/model/database/VkConversationEntity.kt b/core/model/src/main/kotlin/dev/meloda/fast/model/database/VkConvoEntity.kt similarity index 84% rename from core/model/src/main/kotlin/dev/meloda/fast/model/database/VkConversationEntity.kt rename to core/model/src/main/kotlin/dev/meloda/fast/model/database/VkConvoEntity.kt index 094e8e9e..9a7ea077 100644 --- a/core/model/src/main/kotlin/dev/meloda/fast/model/database/VkConversationEntity.kt +++ b/core/model/src/main/kotlin/dev/meloda/fast/model/database/VkConvoEntity.kt @@ -3,10 +3,10 @@ package dev.meloda.fast.model.database import androidx.room.Entity import androidx.room.PrimaryKey import dev.meloda.fast.model.api.PeerType -import dev.meloda.fast.model.api.domain.VkConversation +import dev.meloda.fast.model.api.domain.VkConvo -@Entity(tableName = "conversations") -data class VkConversationEntity( +@Entity(tableName = "convos") +data class VkConvoEntity( @PrimaryKey val id: Long, val localId: Long, val ownerId: Long?, @@ -15,7 +15,7 @@ data class VkConversationEntity( val photo100: String?, val photo200: String?, val isPhantom: Boolean, - val lastConversationMessageId: Long, + val lastCmId: Long, val inReadCmId: Long, val outReadCmId: Long, val inRead: Long, @@ -32,7 +32,7 @@ data class VkConversationEntity( val isArchived: Boolean ) -fun VkConversationEntity.asExternalModel(): VkConversation = VkConversation( +fun VkConvoEntity.asExternalModel(): VkConvo = VkConvo( id = id, localId = localId, ownerId = ownerId, @@ -42,7 +42,7 @@ fun VkConversationEntity.asExternalModel(): VkConversation = VkConversation( photo200 = photo200, isCallInProgress = false, isPhantom = isPhantom, - lastCmId = lastConversationMessageId, + lastCmId = lastCmId, inReadCmId = inReadCmId, outReadCmId = outReadCmId, inRead = inRead, diff --git a/core/model/src/main/kotlin/dev/meloda/fast/model/database/VkMessageEntity.kt b/core/model/src/main/kotlin/dev/meloda/fast/model/database/VkMessageEntity.kt index 6ff5d676..8595fbb4 100644 --- a/core/model/src/main/kotlin/dev/meloda/fast/model/database/VkMessageEntity.kt +++ b/core/model/src/main/kotlin/dev/meloda/fast/model/database/VkMessageEntity.kt @@ -8,7 +8,7 @@ import dev.meloda.fast.model.api.domain.VkUnknownAttachment @Entity(tableName = "messages") data class VkMessageEntity( @PrimaryKey val id: Long, - val conversationMessageId: Long, + val cmId: Long, val text: String?, val isOut: Boolean, val peerId: Long, @@ -18,7 +18,7 @@ data class VkMessageEntity( val action: String?, val actionMemberId: Long?, val actionText: String?, - val actionConversationMessageId: Long?, + val actionCmId: Long?, val actionMessage: String?, val updateTime: Int?, val important: Boolean, @@ -32,7 +32,7 @@ data class VkMessageEntity( fun VkMessageEntity.asExternalModel(): VkMessage = VkMessage( id = id, - cmId = conversationMessageId, + cmId = cmId, text = text, isOut = isOut, peerId = peerId, @@ -42,7 +42,7 @@ fun VkMessageEntity.asExternalModel(): VkMessage = VkMessage( action = VkMessage.Action.parse(action), actionMemberId = actionMemberId, actionText = actionText, - actionConversationMessageId = actionConversationMessageId, + actionCmId = actionCmId, actionMessage = actionMessage, updateTime = updateTime, isImportant = important, diff --git a/core/network/src/main/kotlin/dev/meloda/fast/network/di/NetworkModule.kt b/core/network/src/main/kotlin/dev/meloda/fast/network/di/NetworkModule.kt index ec429e19..746c2d2b 100644 --- a/core/network/src/main/kotlin/dev/meloda/fast/network/di/NetworkModule.kt +++ b/core/network/src/main/kotlin/dev/meloda/fast/network/di/NetworkModule.kt @@ -16,7 +16,7 @@ import dev.meloda.fast.network.interceptor.VersionInterceptor import dev.meloda.fast.network.service.account.AccountService import dev.meloda.fast.network.service.audios.AudiosService import dev.meloda.fast.network.service.auth.AuthService -import dev.meloda.fast.network.service.conversations.ConversationsService +import dev.meloda.fast.network.service.convos.ConvosService import dev.meloda.fast.network.service.files.FilesService import dev.meloda.fast.network.service.friends.FriendsService import dev.meloda.fast.network.service.longpoll.LongPollService @@ -80,7 +80,7 @@ val networkModule = module { single { service(AccountService::class.java) } single { service(AudiosService::class.java) } - single { service(ConversationsService::class.java) } + single { service(ConvosService::class.java) } single { service(FilesService::class.java) } single { service(LongPollService::class.java) } single { service(MessagesService::class.java) } diff --git a/core/network/src/main/kotlin/dev/meloda/fast/network/service/conversations/ConversationsService.kt b/core/network/src/main/kotlin/dev/meloda/fast/network/service/convos/ConvosService.kt similarity index 56% rename from core/network/src/main/kotlin/dev/meloda/fast/network/service/conversations/ConversationsService.kt rename to core/network/src/main/kotlin/dev/meloda/fast/network/service/convos/ConvosService.kt index 8fa267f5..f93b4082 100644 --- a/core/network/src/main/kotlin/dev/meloda/fast/network/service/conversations/ConversationsService.kt +++ b/core/network/src/main/kotlin/dev/meloda/fast/network/service/convos/ConvosService.kt @@ -1,61 +1,61 @@ -package dev.meloda.fast.network.service.conversations +package dev.meloda.fast.network.service.convos import com.slack.eithernet.ApiResult -import dev.meloda.fast.model.api.responses.ConversationsDeleteResponse -import dev.meloda.fast.model.api.responses.ConversationsGetByIdResponse -import dev.meloda.fast.model.api.responses.ConversationsGetResponse +import dev.meloda.fast.model.api.responses.ConvosDeleteResponse +import dev.meloda.fast.model.api.responses.ConvosGetByIdResponse +import dev.meloda.fast.model.api.responses.ConvosGetResponse import dev.meloda.fast.network.ApiResponse import dev.meloda.fast.network.RestApiError import retrofit2.http.FieldMap import retrofit2.http.FormUrlEncoded import retrofit2.http.POST -interface ConversationsService { +interface ConvosService { @FormUrlEncoded - @POST(ConversationsUrls.GET) - suspend fun getConversations( + @POST(ConvosUrls.GET) + suspend fun getConvos( @FieldMap params: Map - ): ApiResult, RestApiError> + ): ApiResult, RestApiError> @FormUrlEncoded - @POST(ConversationsUrls.GET_BY_ID) - suspend fun getConversationsById( + @POST(ConvosUrls.GET_BY_ID) + suspend fun getConvosById( @FieldMap params: Map - ): ApiResult, RestApiError> + ): ApiResult, RestApiError> @FormUrlEncoded - @POST(ConversationsUrls.DELETE) + @POST(ConvosUrls.DELETE) suspend fun delete( @FieldMap params: Map - ): ApiResult, RestApiError> + ): ApiResult, RestApiError> @FormUrlEncoded - @POST(ConversationsUrls.PIN) + @POST(ConvosUrls.PIN) suspend fun pin( @FieldMap params: Map ): ApiResult, RestApiError> @FormUrlEncoded - @POST(ConversationsUrls.UNPIN) + @POST(ConvosUrls.UNPIN) suspend fun unpin( @FieldMap params: Map ): ApiResult, RestApiError> @FormUrlEncoded - @POST(ConversationsUrls.REORDER_PINNED) + @POST(ConvosUrls.REORDER_PINNED) suspend fun reorderPinned( @FieldMap params: Map ): ApiResult, RestApiError> @FormUrlEncoded - @POST(ConversationsUrls.ARCHIVE) + @POST(ConvosUrls.ARCHIVE) suspend fun archive( @FieldMap params: Map ): ApiResult, RestApiError> @FormUrlEncoded - @POST(ConversationsUrls.UNARCHIVE) + @POST(ConvosUrls.UNARCHIVE) suspend fun unarchive( @FieldMap params: Map ): ApiResult, RestApiError> diff --git a/core/network/src/main/kotlin/dev/meloda/fast/network/service/conversations/ConversationsUrls.kt b/core/network/src/main/kotlin/dev/meloda/fast/network/service/convos/ConvosUrls.kt similarity index 87% rename from core/network/src/main/kotlin/dev/meloda/fast/network/service/conversations/ConversationsUrls.kt rename to core/network/src/main/kotlin/dev/meloda/fast/network/service/convos/ConvosUrls.kt index c1230abf..cab61c04 100644 --- a/core/network/src/main/kotlin/dev/meloda/fast/network/service/conversations/ConversationsUrls.kt +++ b/core/network/src/main/kotlin/dev/meloda/fast/network/service/convos/ConvosUrls.kt @@ -1,8 +1,8 @@ -package dev.meloda.fast.network.service.conversations +package dev.meloda.fast.network.service.convos import dev.meloda.fast.common.AppConstants -object ConversationsUrls { +object ConvosUrls { private const val URL = AppConstants.URL_API diff --git a/core/network/src/main/kotlin/dev/meloda/fast/network/service/messages/MessagesService.kt b/core/network/src/main/kotlin/dev/meloda/fast/network/service/messages/MessagesService.kt index 84d9bba5..e14cbe2b 100644 --- a/core/network/src/main/kotlin/dev/meloda/fast/network/service/messages/MessagesService.kt +++ b/core/network/src/main/kotlin/dev/meloda/fast/network/service/messages/MessagesService.kt @@ -6,7 +6,7 @@ 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.MessagesGetByIdResponse -import dev.meloda.fast.model.api.responses.MessagesGetConversationMembersResponse +import dev.meloda.fast.model.api.responses.MessagesGetConvoMembersResponse import dev.meloda.fast.model.api.responses.MessagesGetHistoryAttachmentsResponse import dev.meloda.fast.model.api.responses.MessagesGetHistoryResponse import dev.meloda.fast.model.api.responses.MessagesGetReadPeersResponse @@ -99,10 +99,10 @@ interface MessagesService { ): ApiResult, RestApiError> @FormUrlEncoded - @POST(MessagesUrls.GET_CONVERSATIONS_MEMBERS) - suspend fun getConversationMembers( + @POST(MessagesUrls.GET_CONVOS_MEMBERS) + suspend fun getConvoMembers( @FieldMap params: Map - ): ApiResult, RestApiError> + ): ApiResult, RestApiError> @FormUrlEncoded @POST(MessagesUrls.REMOVE_CHAT_USER) diff --git a/core/network/src/main/kotlin/dev/meloda/fast/network/service/messages/MessagesUrls.kt b/core/network/src/main/kotlin/dev/meloda/fast/network/service/messages/MessagesUrls.kt index 1cee7a88..1bec5260 100644 --- a/core/network/src/main/kotlin/dev/meloda/fast/network/service/messages/MessagesUrls.kt +++ b/core/network/src/main/kotlin/dev/meloda/fast/network/service/messages/MessagesUrls.kt @@ -18,7 +18,7 @@ object MessagesUrls { const val GET_BY_ID = "$URL/messages.getById" const val MARK_AS_READ = "$URL/messages.markAsRead" const val GET_CHAT = "$URL/messages.getChat" - const val GET_CONVERSATIONS_MEMBERS = "$URL/messages.getConversationMembers" + const val GET_CONVOS_MEMBERS = "$URL/messages.getConversationMembers" const val REMOVE_CHAT_USER = "$URL/messages.removeChatUser" const val GET_HISTORY_ATTACHMENTS = "$URL/messages.getHistoryAttachments" const val CREATE_CHAT = "$URL/messages.createChat" diff --git a/core/ui/src/main/kotlin/dev/meloda/fast/ui/model/api/ActionState.kt b/core/ui/src/main/kotlin/dev/meloda/fast/ui/model/vk/ActionState.kt similarity index 92% rename from core/ui/src/main/kotlin/dev/meloda/fast/ui/model/api/ActionState.kt rename to core/ui/src/main/kotlin/dev/meloda/fast/ui/model/vk/ActionState.kt index 73e07a1d..4793958e 100644 --- a/core/ui/src/main/kotlin/dev/meloda/fast/ui/model/api/ActionState.kt +++ b/core/ui/src/main/kotlin/dev/meloda/fast/ui/model/vk/ActionState.kt @@ -1,4 +1,4 @@ -package dev.meloda.fast.ui.model.api +package dev.meloda.fast.ui.model.vk enum class ActionState { PHANTOM, CALL_IN_PROGRESS, NONE; diff --git a/core/ui/src/main/kotlin/dev/meloda/fast/ui/model/api/ConversationOption.kt b/core/ui/src/main/kotlin/dev/meloda/fast/ui/model/vk/ConvoOption.kt similarity index 61% rename from core/ui/src/main/kotlin/dev/meloda/fast/ui/model/api/ConversationOption.kt rename to core/ui/src/main/kotlin/dev/meloda/fast/ui/model/vk/ConvoOption.kt index 32689d70..fcedd14c 100644 --- a/core/ui/src/main/kotlin/dev/meloda/fast/ui/model/api/ConversationOption.kt +++ b/core/ui/src/main/kotlin/dev/meloda/fast/ui/model/vk/ConvoOption.kt @@ -1,41 +1,41 @@ -package dev.meloda.fast.ui.model.api +package dev.meloda.fast.ui.model.vk import dev.meloda.fast.common.model.UiImage import dev.meloda.fast.common.model.UiText import dev.meloda.fast.ui.R -sealed class ConversationOption( +sealed class ConvoOption( val title: UiText, val icon: UiImage ) { - data object MarkAsRead : ConversationOption( + data object MarkAsRead : ConvoOption( title = UiText.Resource(R.string.action_mark_as_read), icon = UiImage.Resource(R.drawable.round_done_all_24) ) - data object Pin : ConversationOption( + data object Pin : ConvoOption( title = UiText.Resource(R.string.action_pin), icon = UiImage.Resource(R.drawable.pin_outline_24) ) - data object Unpin : ConversationOption( + data object Unpin : ConvoOption( title = UiText.Resource(R.string.action_unpin), icon = UiImage.Resource(R.drawable.pin_off_outline_24) ) - data object Delete : ConversationOption( + data object Delete : ConvoOption( title = UiText.Resource(R.string.action_delete), icon = UiImage.Resource(R.drawable.round_delete_outline_24) ) - data object Archive : ConversationOption( - title = UiText.Resource(R.string.conversation_context_action_archive), + data object Archive : ConvoOption( + title = UiText.Resource(R.string.convo_context_action_archive), icon = UiImage.Resource(R.drawable.outline_archive_24) ) - data object Unarchive : ConversationOption( - title = UiText.Resource(R.string.conversation_context_action_unarchive), + data object Unarchive : ConvoOption( + title = UiText.Resource(R.string.convo_context_action_unarchive), icon = UiImage.Resource(R.drawable.outline_unarchive_24) ) } diff --git a/core/ui/src/main/kotlin/dev/meloda/fast/ui/model/vk/MentionIndex.kt b/core/ui/src/main/kotlin/dev/meloda/fast/ui/model/vk/MentionIndex.kt new file mode 100644 index 00000000..d2b12593 --- /dev/null +++ b/core/ui/src/main/kotlin/dev/meloda/fast/ui/model/vk/MentionIndex.kt @@ -0,0 +1,7 @@ +package dev.meloda.fast.ui.model.vk + +data class MentionIndex( + val id: Long, + val idPrefix: String, + val indexRange: IntRange +) diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/model/UiItem.kt b/core/ui/src/main/kotlin/dev/meloda/fast/ui/model/vk/MessageUiItem.kt similarity index 87% rename from feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/model/UiItem.kt rename to core/ui/src/main/kotlin/dev/meloda/fast/ui/model/vk/MessageUiItem.kt index 6ba346c7..f3db24ec 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/model/UiItem.kt +++ b/core/ui/src/main/kotlin/dev/meloda/fast/ui/model/vk/MessageUiItem.kt @@ -1,4 +1,4 @@ -package dev.meloda.fast.messageshistory.model +package dev.meloda.fast.ui.model.vk import androidx.compose.runtime.Stable import androidx.compose.ui.text.AnnotatedString @@ -6,7 +6,8 @@ import dev.meloda.fast.common.model.UiImage import dev.meloda.fast.model.api.domain.VkAttachment import dev.meloda.fast.ui.util.ImmutableList -sealed class UiItem( +@Stable +sealed class MessageUiItem( open val id: Long, open val cmId: Long ) { @@ -35,8 +36,8 @@ sealed class UiItem( val attachments: ImmutableList?, val replyCmId: Long?, val replyTitle: String?, - val replySummary: String? - ) : UiItem(id, cmId) + val replySummary: AnnotatedString? + ) : MessageUiItem(id, cmId) @Stable data class ActionMessage( @@ -44,5 +45,5 @@ sealed class UiItem( override val cmId: Long, val text: AnnotatedString, val actionCmId: Long? - ) : UiItem(id, cmId) + ) : MessageUiItem(id, cmId) } diff --git a/core/ui/src/main/kotlin/dev/meloda/fast/ui/model/vk/SendingStatus.kt b/core/ui/src/main/kotlin/dev/meloda/fast/ui/model/vk/SendingStatus.kt new file mode 100644 index 00000000..0f0b3868 --- /dev/null +++ b/core/ui/src/main/kotlin/dev/meloda/fast/ui/model/vk/SendingStatus.kt @@ -0,0 +1,5 @@ +package dev.meloda.fast.ui.model.vk + +enum class SendingStatus { + SENDING, SENT, FAILED +} diff --git a/core/ui/src/main/kotlin/dev/meloda/fast/ui/model/api/UiConversation.kt b/core/ui/src/main/kotlin/dev/meloda/fast/ui/model/vk/UiConvo.kt similarity index 87% rename from core/ui/src/main/kotlin/dev/meloda/fast/ui/model/api/UiConversation.kt rename to core/ui/src/main/kotlin/dev/meloda/fast/ui/model/vk/UiConvo.kt index 3a725d6b..ecd883b4 100644 --- a/core/ui/src/main/kotlin/dev/meloda/fast/ui/model/api/UiConversation.kt +++ b/core/ui/src/main/kotlin/dev/meloda/fast/ui/model/vk/UiConvo.kt @@ -1,4 +1,4 @@ -package dev.meloda.fast.ui.model.api +package dev.meloda.fast.ui.model.vk import androidx.compose.runtime.Immutable import androidx.compose.ui.text.AnnotatedString @@ -8,7 +8,7 @@ import dev.meloda.fast.model.api.domain.VkMessage import dev.meloda.fast.ui.util.ImmutableList @Immutable -data class UiConversation( +data class UiConvo( val id: Long, val lastMessageId: Long?, val avatar: UiImage?, @@ -28,5 +28,5 @@ data class UiConversation( val interactionText: String?, val isExpanded: Boolean, val isArchived: Boolean, - val options: ImmutableList, + val options: ImmutableList, ) diff --git a/core/ui/src/main/kotlin/dev/meloda/fast/ui/model/api/UiFriend.kt b/core/ui/src/main/kotlin/dev/meloda/fast/ui/model/vk/UiFriend.kt similarity index 90% rename from core/ui/src/main/kotlin/dev/meloda/fast/ui/model/api/UiFriend.kt rename to core/ui/src/main/kotlin/dev/meloda/fast/ui/model/vk/UiFriend.kt index ed4aa94c..f8054254 100644 --- a/core/ui/src/main/kotlin/dev/meloda/fast/ui/model/api/UiFriend.kt +++ b/core/ui/src/main/kotlin/dev/meloda/fast/ui/model/vk/UiFriend.kt @@ -1,4 +1,4 @@ -package dev.meloda.fast.ui.model.api +package dev.meloda.fast.ui.model.vk import androidx.compose.runtime.Immutable import dev.meloda.fast.common.model.UiImage diff --git a/core/ui/src/main/kotlin/dev/meloda/fast/ui/util/Extensions.kt b/core/ui/src/main/kotlin/dev/meloda/fast/ui/util/Extensions.kt index 3cfc47ad..4825cf3d 100644 --- a/core/ui/src/main/kotlin/dev/meloda/fast/ui/util/Extensions.kt +++ b/core/ui/src/main/kotlin/dev/meloda/fast/ui/util/Extensions.kt @@ -11,6 +11,10 @@ import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.compositeOver +import androidx.compose.ui.graphics.lerp +import androidx.compose.ui.graphics.luminance import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.input.key.onKeyEvent @@ -121,3 +125,14 @@ fun isNeedToEnableDarkMode(darkMode: DarkMode): Boolean { return appForceDarkMode || (appBatterySaver && isSystemBatterySaver) || (!appBatterySaver && isSystemUsingDarkTheme && darkMode == DarkMode.FOLLOW_SYSTEM) } + +fun Color.lighten(amount: Float) = lerp(this, Color.White, amount.coerceIn(0f, 1f)) +fun Color.darken(amount: Float) = lerp(this, Color.Black, amount.coerceIn(0f, 1f)) + +fun Color.isDark( + background: Color = Color.White, + threshold: Float = 0.5f +): Boolean { + val opaque = if (alpha < 1f) this.compositeOver(background) else this + return opaque.luminance() < threshold +} diff --git a/core/ui/src/main/res/values-ru/strings.xml b/core/ui/src/main/res/values-ru/strings.xml index 37cf84d2..60068d5c 100644 --- a/core/ui/src/main/res/values-ru/strings.xml +++ b/core/ui/src/main/res/values-ru/strings.xml @@ -24,16 +24,16 @@ Пометить как спам Прочитать Удалить - Из архива - Удалить - Удалить чат? + Из архива + Удалить + Удалить чат? Выйти Выйти? - Открепить - Закрепить - Открепить чат? - Закрепить чат? - Разархивировать чат? + Открепить + Закрепить + Открепить чат? + Закрепить чат? + Разархивировать чат? Закрепить Открепить Пометить @@ -181,7 +181,7 @@ Использовать AMOLED тему с чистым чёрным фоновым цветом, когда используется тёмная тема Участники: %1$d Загрузка… - Чаты + Чаты Архив Друзья Профиль @@ -262,8 +262,8 @@ Вы уверены, что хотите убрать пометку спама у этого сообщения? Закрепить сообщение Скопировано в буфер обмена - В архив - Архивировать чат? + В архив + Архивировать чат? В архив Автозаполнение Жирный diff --git a/core/ui/src/main/res/values/strings.xml b/core/ui/src/main/res/values/strings.xml index c25e95db..46fccbf1 100644 --- a/core/ui/src/main/res/values/strings.xml +++ b/core/ui/src/main/res/values/strings.xml @@ -153,18 +153,18 @@ Read Delete - Archive - Unarchive - Delete - Delete the conversation? + Archive + Unarchive + Delete + Delete the conversation? Sign out Sign out? - Unpin - Pin - Unpin the conversation? - Pin the conversation? - Archive the conversation? - Unarchive the conversation? + Unpin + Pin + Unpin the conversation? + Pin the conversation? + Archive the conversation? + Unarchive the conversation? Pin Unpin Mark @@ -245,7 +245,7 @@ Refresh Members: %1$d Loading… - Conversations + Conversations Archive Friends Profile diff --git a/feature/chatmaterials/src/main/kotlin/dev/meloda/fast/chatmaterials/ChatMaterialsViewModel.kt b/feature/chatmaterials/src/main/kotlin/dev/meloda/fast/chatmaterials/ChatMaterialsViewModel.kt index 4da7a2e2..2853ac75 100644 --- a/feature/chatmaterials/src/main/kotlin/dev/meloda/fast/chatmaterials/ChatMaterialsViewModel.kt +++ b/feature/chatmaterials/src/main/kotlin/dev/meloda/fast/chatmaterials/ChatMaterialsViewModel.kt @@ -53,7 +53,7 @@ class ChatMaterialsViewModelImpl( screenState.setValue { old -> old.copy( peerId = arguments.peerId, - cmId = arguments.conversationMessageId + cmId = arguments.cmId ) } @@ -101,7 +101,7 @@ class ChatMaterialsViewModelImpl( isPaginationExhausted = paginationExhausted, cmId = if (loadedMaterials.size + offset > 200) { currentOffset.setValue { 0 } - loadedMaterials.lastOrNull()?.conversationMessageId ?: -1 + loadedMaterials.lastOrNull()?.cmId ?: -1 } else { screenState.value.cmId } diff --git a/feature/chatmaterials/src/main/kotlin/dev/meloda/fast/chatmaterials/model/UiChatMaterial.kt b/feature/chatmaterials/src/main/kotlin/dev/meloda/fast/chatmaterials/model/UiChatMaterial.kt index f63b8b6d..31aac390 100644 --- a/feature/chatmaterials/src/main/kotlin/dev/meloda/fast/chatmaterials/model/UiChatMaterial.kt +++ b/feature/chatmaterials/src/main/kotlin/dev/meloda/fast/chatmaterials/model/UiChatMaterial.kt @@ -1,43 +1,43 @@ package dev.meloda.fast.chatmaterials.model sealed class UiChatMaterial( - open val conversationMessageId: Long + open val cmId: Long ) { data class Photo( - override val conversationMessageId: Long, + override val cmId: Long, val previewUrl: String - ) : UiChatMaterial(conversationMessageId) + ) : UiChatMaterial(cmId) data class Video( - override val conversationMessageId: Long, + override val cmId: Long, val previewUrl: String?, val title: String, val views: Int, val duration: String - ) : UiChatMaterial(conversationMessageId) + ) : UiChatMaterial(cmId) data class Audio( - override val conversationMessageId: Long, + override val cmId: Long, val previewUrl: String?, val title: String, val artist: String, val duration: String - ) : UiChatMaterial(conversationMessageId) + ) : UiChatMaterial(cmId) data class File( - override val conversationMessageId: Long, + override val cmId: Long, val previewUrl: String?, val title: String, val size: String, val extension: String - ) : UiChatMaterial(conversationMessageId) + ) : UiChatMaterial(cmId) data class Link( - override val conversationMessageId: Long, + override val cmId: Long, val previewUrl: String?, val title: String?, val url: String, val urlFirstChar: String - ) : UiChatMaterial(conversationMessageId) + ) : UiChatMaterial(cmId) } diff --git a/feature/chatmaterials/src/main/kotlin/dev/meloda/fast/chatmaterials/navigation/ChatMaterialsNavigation.kt b/feature/chatmaterials/src/main/kotlin/dev/meloda/fast/chatmaterials/navigation/ChatMaterialsNavigation.kt index 1d6c5033..0d4c3da3 100644 --- a/feature/chatmaterials/src/main/kotlin/dev/meloda/fast/chatmaterials/navigation/ChatMaterialsNavigation.kt +++ b/feature/chatmaterials/src/main/kotlin/dev/meloda/fast/chatmaterials/navigation/ChatMaterialsNavigation.kt @@ -11,7 +11,7 @@ import kotlinx.serialization.Serializable @Serializable data class ChatMaterials( val peerId: Long, - val conversationMessageId: Long + val cmId: Long ) { companion object { fun from(savedStateHandle: SavedStateHandle) = @@ -31,11 +31,11 @@ fun NavGraphBuilder.chatMaterialsScreen( } } -fun NavController.navigateToChatMaterials(peerId: Long, conversationMessageId: Long) { +fun NavController.navigateToChatMaterials(peerId: Long, cmId: Long) { this.navigate( ChatMaterials( peerId = peerId, - conversationMessageId = conversationMessageId + cmId = cmId ) ) } diff --git a/feature/chatmaterials/src/main/kotlin/dev/meloda/fast/chatmaterials/util/ChatMaterialMapper.kt b/feature/chatmaterials/src/main/kotlin/dev/meloda/fast/chatmaterials/util/ChatMaterialMapper.kt index 7a78b73d..84186ac2 100644 --- a/feature/chatmaterials/src/main/kotlin/dev/meloda/fast/chatmaterials/util/ChatMaterialMapper.kt +++ b/feature/chatmaterials/src/main/kotlin/dev/meloda/fast/chatmaterials/util/ChatMaterialMapper.kt @@ -17,7 +17,7 @@ fun VkAttachmentHistoryMessage.asPresentation(): UiChatMaterial? = AttachmentType.PHOTO -> { val attachment = this.attachment as VkPhotoDomain UiChatMaterial.Photo( - conversationMessageId = this.conversationMessageId, + cmId = this.cmId, previewUrl = attachment.getSizeOrSmaller(VkPhotoDomain.SIZE_TYPE_1080_1024)?.url.orEmpty() ) } @@ -47,7 +47,7 @@ fun VkAttachmentHistoryMessage.asPresentation(): UiChatMaterial? = builder.toString().format(Locale.getDefault(), *args.toTypedArray()) UiChatMaterial.Video( - conversationMessageId = this.conversationMessageId, + cmId = this.cmId, previewUrl = attachment.images.maxByOrNull(VkVideoDomain.VideoImage::width)?.url.orEmpty(), title = attachment.title, views = attachment.views, @@ -80,7 +80,7 @@ fun VkAttachmentHistoryMessage.asPresentation(): UiChatMaterial? = builder.toString().format(Locale.getDefault(), *args.toTypedArray()) UiChatMaterial.Audio( - conversationMessageId = this.conversationMessageId, + cmId = this.cmId, previewUrl = null, title = attachment.title, artist = attachment.artist, @@ -112,7 +112,7 @@ fun VkAttachmentHistoryMessage.asPresentation(): UiChatMaterial? = } UiChatMaterial.File( - conversationMessageId = this.conversationMessageId, + cmId = this.cmId, title = attachment.title, previewUrl = previewUrl, size = AndroidUtils.bytesToHumanReadableSize(attachment.size.toDouble()), @@ -124,7 +124,7 @@ fun VkAttachmentHistoryMessage.asPresentation(): UiChatMaterial? = val attachment = this.attachment as VkLinkDomain UiChatMaterial.Link( - conversationMessageId = this.conversationMessageId, + cmId = this.cmId, title = attachment.title, previewUrl = attachment.photo?.getMaxSize()?.url, url = attachment.url, diff --git a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/ConversationsViewModel.kt b/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/ConversationsViewModel.kt deleted file mode 100644 index bdfa52e0..00000000 --- a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/ConversationsViewModel.kt +++ /dev/null @@ -1,746 +0,0 @@ -package dev.meloda.fast.conversations - -import android.content.Context -import android.content.res.Resources -import android.os.Bundle -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import coil.ImageLoader -import coil.request.ImageRequest -import com.conena.nanokt.collections.indexOfFirstOrNull -import dev.meloda.fast.common.VkConstants -import dev.meloda.fast.common.extensions.createTimerFlow -import dev.meloda.fast.common.extensions.findWithIndex -import dev.meloda.fast.common.extensions.listenValue -import dev.meloda.fast.common.extensions.setValue -import dev.meloda.fast.common.extensions.updateValue -import dev.meloda.fast.conversations.model.ConversationDialog -import dev.meloda.fast.conversations.model.ConversationNavigation -import dev.meloda.fast.conversations.model.ConversationsScreenState -import dev.meloda.fast.conversations.model.InteractionJob -import dev.meloda.fast.conversations.model.NewInteractionException -import dev.meloda.fast.conversations.util.asPresentation -import dev.meloda.fast.conversations.util.extractAvatar -import dev.meloda.fast.data.VkUtils -import dev.meloda.fast.data.processState -import dev.meloda.fast.datastore.UserSettings -import dev.meloda.fast.domain.ConversationsUseCase -import dev.meloda.fast.domain.LoadConversationsByIdUseCase -import dev.meloda.fast.domain.LongPollUpdatesParser -import dev.meloda.fast.domain.MessagesUseCase -import dev.meloda.fast.model.BaseError -import dev.meloda.fast.model.ConversationsFilter -import dev.meloda.fast.model.InteractionType -import dev.meloda.fast.model.LongPollParsedEvent -import dev.meloda.fast.model.api.domain.VkConversation -import dev.meloda.fast.ui.model.api.ConversationOption -import dev.meloda.fast.ui.model.api.UiConversation -import dev.meloda.fast.ui.util.ImmutableList.Companion.toImmutableList -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.flow.update - -class ConversationsViewModel( - updatesParser: LongPollUpdatesParser, - private val filter: ConversationsFilter, - private val conversationsUseCase: ConversationsUseCase, - private val messagesUseCase: MessagesUseCase, - private val resources: Resources, - private val userSettings: UserSettings, - private val imageLoader: ImageLoader, - private val applicationContext: Context, - private val loadConversationsByIdUseCase: LoadConversationsByIdUseCase -) : ViewModel() { - private val _screenState = MutableStateFlow(ConversationsScreenState.EMPTY) - val screenState = _screenState.asStateFlow() - - private val _navigation = MutableStateFlow(null) - val navigation = _navigation.asStateFlow() - - private val _dialog = MutableStateFlow(null) - val dialog = _dialog.asStateFlow() - - private val _conversations = MutableStateFlow>(emptyList()) - val conversations = _conversations.asStateFlow() - - private val _uiConversations = MutableStateFlow>(emptyList()) - val uiConversations = _uiConversations.asStateFlow() - - private val pinnedConversationsCount = conversations.map { conversations -> - conversations.count(VkConversation::isPinned) - }.stateIn(viewModelScope, SharingStarted.Eagerly, 0) - - private val _baseError = MutableStateFlow(null) - val baseError = _baseError.asStateFlow() - - private val _currentOffset = MutableStateFlow(0) - val currentOffset = _currentOffset.asStateFlow() - - private val _canPaginate = MutableStateFlow(false) - val canPaginate = _canPaginate.asStateFlow() - - private val expandedConversationId = MutableStateFlow(0L) - - private val useContactNames: Boolean get() = userSettings.useContactNames.value - - private val interactionsTimers = hashMapOf() - - init { - _screenState.updateValue { copy(isArchive = filter == ConversationsFilter.ARCHIVE) } - - loadConversations() - - updatesParser.onNewMessage(::handleNewMessage) - updatesParser.onMessageEdited(::handleEditedMessage) - updatesParser.onMessageIncomingRead(::handleReadIncomingMessage) - updatesParser.onMessageOutgoingRead(::handleReadOutgoingMessage) - updatesParser.onInteractions(::handleInteraction) - updatesParser.onChatMajorChanged(::handleChatMajorChanged) - updatesParser.onChatMinorChanged(::handleChatMinorChanged) - updatesParser.onChatCleared(::handleChatClearing) - updatesParser.onChatArchived(::handleChatArchived) - - userSettings.useContactNames.listenValue(viewModelScope) { - syncUiConversation() - } - } - - fun onNavigationConsumed() { - _navigation.setValue { null } - } - - fun onDialogConfirmed(dialog: ConversationDialog, bundle: Bundle) { - onDialogDismissed(dialog) - - when (dialog) { - is ConversationDialog.ConversationDelete -> { - deleteConversation(dialog.conversationId) - } - - is ConversationDialog.ConversationPin -> { - pinConversation(dialog.conversationId, true) - } - - is ConversationDialog.ConversationUnpin -> { - pinConversation(dialog.conversationId, false) - } - - is ConversationDialog.ConversationArchive -> { - archiveConversation(dialog.conversationId, true) - } - - is ConversationDialog.ConversationUnarchive -> { - archiveConversation(dialog.conversationId, false) - } - } - - expandedConversationId.setValue { 0 } - syncUiConversation() - } - - fun onDialogDismissed(dialog: ConversationDialog) { - _dialog.setValue { null } - } - - fun onDialogItemPicked(dialog: ConversationDialog, bundle: Bundle) { - when (dialog) { - is ConversationDialog.ConversationDelete -> Unit - is ConversationDialog.ConversationPin -> Unit - is ConversationDialog.ConversationUnpin -> Unit - is ConversationDialog.ConversationArchive -> Unit - is ConversationDialog.ConversationUnarchive -> Unit - } - } - - fun onErrorButtonClicked() { - when (baseError.value) { - null -> Unit - - is BaseError.ConnectionError, - is BaseError.InternalError, - is BaseError.SimpleError, - is BaseError.UnknownError -> onRefresh() - - else -> Unit - } - } - - fun onPaginationConditionsMet() { - _currentOffset.update { conversations.value.size } - loadConversations() - } - - fun onRefresh() { - onErrorConsumed() - loadConversations(offset = 0) - } - - fun onConversationItemClick(conversation: UiConversation) { - collapseConversations() - _navigation.setValue { ConversationNavigation.MessagesHistory(peerId = conversation.id) } - } - - fun onConversationItemLongClick(conversation: UiConversation) { - expandedConversationId.setValue { - if (conversation.isExpanded) 0 - else conversation.id - } - syncUiConversation() - } - - fun onOptionClicked( - conversation: UiConversation, - option: ConversationOption - ) { - when (option) { - ConversationOption.Delete -> { - _dialog.setValue { ConversationDialog.ConversationDelete(conversation.id) } - } - - ConversationOption.MarkAsRead -> { - conversation.lastMessageId?.let { lastMessageId -> - readConversation( - peerId = conversation.id, - startMessageId = lastMessageId - ) - collapseConversations() - } - } - - ConversationOption.Pin -> { - _dialog.setValue { ConversationDialog.ConversationPin(conversation.id) } - } - - ConversationOption.Unpin -> { - _dialog.setValue { ConversationDialog.ConversationUnpin(conversation.id) } - } - - ConversationOption.Archive -> { - _dialog.setValue { ConversationDialog.ConversationArchive(conversation.id) } - } - - ConversationOption.Unarchive -> { - _dialog.setValue { ConversationDialog.ConversationUnarchive(conversation.id) } - } - } - } - - fun onErrorConsumed() { - _baseError.setValue { null } - } - - fun setScrollIndex(index: Int) { - _screenState.setValue { old -> old.copy(scrollIndex = index) } - } - - fun setScrollOffset(offset: Int) { - _screenState.setValue { old -> old.copy(scrollOffset = offset) } - } - - fun onCreateChatButtonClicked() { - _navigation.setValue { ConversationNavigation.CreateChat } - } - - private fun collapseConversations() { - expandedConversationId.setValue { 0 } - syncUiConversation() - } - - private fun loadConversations( - offset: Int = currentOffset.value - ) { - conversationsUseCase.getConversations( - count = LOAD_COUNT, - offset = offset, - filter = filter - ).listenValue(viewModelScope) { state -> - state.processState( - error = { error -> - val newBaseError = VkUtils.parseError(error) - _baseError.update { newBaseError } - }, - success = { response -> - val conversations = response - val fullConversations = if (offset == 0) { - conversations - } else { - this.conversations.value.plus(conversations) - } - - val itemsCountSufficient = response.size == LOAD_COUNT - - val paginationExhausted = !itemsCountSufficient && - this.conversations.value.isNotEmpty() - - _screenState.updateValue { - copy(isPaginationExhausted = paginationExhausted) - } - - val imagesToPreload = - response.mapNotNull { it.extractAvatar().extractUrl() } - - imagesToPreload.forEach { url -> - imageLoader.enqueue( - ImageRequest.Builder(applicationContext) - .data(url) - .build() - ) - } - - conversationsUseCase.storeConversations(response) - - _conversations.emit(fullConversations) - syncUiConversation() - _canPaginate.setValue { itemsCountSufficient } - } - ) - - _screenState.setValue { old -> - old.copy( - isLoading = offset == 0 && state.isLoading(), - isPaginating = offset > 0 && state.isLoading() - ) - } - } - } - - private fun deleteConversation(peerId: Long) { - conversationsUseCase.delete(peerId).listenValue(viewModelScope) { state -> - state.processState( - error = {}, - success = { - val newConversations = conversations.value.toMutableList() - val conversationIndex = - newConversations.indexOfFirstOrNull { it.id == peerId } - ?: return@processState - - newConversations.removeAt(conversationIndex) - _conversations.update { newConversations.sorted() } - syncUiConversation() - } - ) - _screenState.emit(screenState.value.copy(isLoading = state.isLoading())) - } - } - - private fun pinConversation(peerId: Long, pin: Boolean) { - conversationsUseCase.changePinState(peerId, pin) - .listenValue(viewModelScope) { state -> - state.processState( - error = {}, - success = { - handleChatMajorChanged( - LongPollParsedEvent.ChatMajorChanged( - peerId = peerId, - majorId = if (pin) { - pinnedConversationsCount.value.plus(1) * 16 - } else { - 0 - } - ) - ) - } - ) - - _screenState.setValue { old -> old.copy(isLoading = state.isLoading()) } - } - } - - private fun archiveConversation(peerId: Long, archive: Boolean) { - conversationsUseCase.changeArchivedState(peerId, archive) - .listenValue(viewModelScope) { state -> - state.processState( - error = {}, - success = { - conversations.value.find { it.id == peerId }?.let { conversation -> - handleChatArchived( - LongPollParsedEvent.ChatArchived( - conversation = conversation, - archived = archive - ) - ) - } - } - ) - } - } - - // TODO: 03-Apr-25, Danil Nikolaev: handle business messages - private fun handleNewMessage(event: LongPollParsedEvent.NewMessage) { - val message = event.message - - val newConversations = conversations.value.toMutableList() - val conversationIndex = - newConversations.indexOfFirstOrNull { it.id == message.peerId } - - if (conversationIndex == null) { - if (event.inArchive != (filter == ConversationsFilter.ARCHIVE)) return - - loadConversationsByIdUseCase( - peerIds = listOf(message.peerId), - extended = true, - fields = VkConstants.ALL_FIELDS - ).listenValue(viewModelScope) { state -> - state.processState( - error = {}, - success = { response -> - val conversation = (response.firstOrNull() ?: return@listenValue) - .copy(lastMessage = message) - - newConversations.add(pinnedConversationsCount.value, conversation) - _conversations.update { newConversations.sorted() } - syncUiConversation() - } - ) - } - } else { - val conversation = newConversations[conversationIndex] - var newConversation = conversation.copy( - lastMessage = message, - lastMessageId = message.id, - lastCmId = message.cmId, - unreadCount = if (message.isOut) conversation.unreadCount - else conversation.unreadCount + 1 - ) - - interactionsTimers[conversation.id]?.let { job -> - if (job.interactionType == InteractionType.Typing - && message.fromId in conversation.interactionIds - ) { - val newInteractionIds = newConversation.interactionIds.filter { id -> - id != message.fromId - } - - newConversation = newConversation.copy( - interactionType = if (newInteractionIds.isEmpty()) -1 else { - newConversation.interactionType - }, - interactionIds = newInteractionIds - ) - } - } - - if (conversation.isPinned()) { - newConversations[conversationIndex] = newConversation - } else { - newConversations.removeAt(conversationIndex) - - val toPosition = pinnedConversationsCount.value - newConversations.add(toPosition, newConversation) - } - - _conversations.update { newConversations.sorted() } - syncUiConversation() - } - } - - private fun handleEditedMessage(event: LongPollParsedEvent.MessageEdited) { - val message = event.message - val newConversations = conversations.value.toMutableList() - - val conversationIndex = newConversations.indexOfFirstOrNull { it.id == message.peerId } - if (conversationIndex == null) { // диалога нет в списке - // pizdets - } else { - val conversation = newConversations[conversationIndex] - newConversations[conversationIndex] = conversation.copy( - lastMessage = message, - lastMessageId = message.id, - lastCmId = message.cmId - ) - _conversations.update { newConversations } - syncUiConversation() - } - } - - private fun handleReadIncomingMessage(event: LongPollParsedEvent.IncomingMessageRead) { - val newConversations = conversations.value.toMutableList() - - val conversationIndex = - newConversations.indexOfFirstOrNull { it.id == event.peerId } - - if (conversationIndex == null) { // диалога нет в списке - // pizdets - } else { - newConversations[conversationIndex] = - newConversations[conversationIndex].copy( - inReadCmId = event.cmId, - unreadCount = event.unreadCount - ) - - _conversations.update { newConversations } - syncUiConversation() - } - } - - private fun handleReadOutgoingMessage(event: LongPollParsedEvent.OutgoingMessageRead) { - val newConversations = conversations.value.toMutableList() - - val conversationIndex = - newConversations.indexOfFirstOrNull { it.id == event.peerId } - - if (conversationIndex == null) { // диалога нет в списке - // pizdets - } else { - newConversations[conversationIndex] = - newConversations[conversationIndex].copy( - outReadCmId = event.cmId, - unreadCount = event.unreadCount - ) - - _conversations.update { newConversations } - syncUiConversation() - } - } - - private fun handleInteraction(event: LongPollParsedEvent.Interaction) { - val interactionType = event.interactionType - val peerId = event.peerId - val userIds = event.userIds - - val newConversations = conversations.value.toMutableList() - val conversationAndIndex = - newConversations.findWithIndex { it.id == peerId } - - if (conversationAndIndex != null) { - newConversations[conversationAndIndex.first] = - conversationAndIndex.second.copy( - interactionType = interactionType.value, - interactionIds = userIds - ) - - _conversations.update { newConversations } - syncUiConversation() - - interactionsTimers[peerId]?.let { interactionJob -> - if (interactionJob.interactionType == interactionType) { - interactionJob.timerJob.cancel(NewInteractionException()) - } - } - - var timeoutAction: (() -> Unit)? = null - - val timerJob = createTimerFlow( - time = 6, - onTimeoutAction = { timeoutAction?.invoke() } - ).launchIn(viewModelScope) - - val newInteractionJob = InteractionJob( - interactionType = interactionType, - timerJob = timerJob - ) - - interactionsTimers[peerId] = newInteractionJob - - timeoutAction = { - stopInteraction(peerId, newInteractionJob) - } - } - } - - private fun stopInteraction(peerId: Long, interactionJob: InteractionJob) { - interactionsTimers[peerId] ?: return - - val newConversations = conversations.value.toMutableList() - val conversationAndIndex = - newConversations.findWithIndex { it.id == peerId } ?: return - - newConversations[conversationAndIndex.first] = - conversationAndIndex.second.copy( - interactionType = -1, - interactionIds = emptyList() - ) - - _conversations.update { newConversations } - syncUiConversation() - - interactionJob.timerJob.cancel() - interactionsTimers[peerId] = null - } - - private fun handleChatMajorChanged(event: LongPollParsedEvent.ChatMajorChanged) { - val newConversations = conversations.value.toMutableList() - val conversationIndex = - newConversations.indexOfFirstOrNull { it.id == event.peerId } - - if (conversationIndex == null) { // диалога нет в списке - // pizdets - } else { - newConversations[conversationIndex] = - newConversations[conversationIndex].copy(majorId = event.majorId) - - _conversations.setValue { newConversations.sorted() } - syncUiConversation() - } - } - - private fun handleChatMinorChanged(event: LongPollParsedEvent.ChatMinorChanged) { - val newConversations = conversations.value.toMutableList() - val conversationIndex = - newConversations.indexOfFirstOrNull { it.id == event.peerId } - - if (conversationIndex == null) { // диалога нет в списке - // pizdets - } else { - newConversations[conversationIndex] = - newConversations[conversationIndex].copy(minorId = event.minorId) - - _conversations.setValue { newConversations.sorted() } - syncUiConversation() - } - } - - private fun handleChatClearing(event: LongPollParsedEvent.ChatCleared) { - val newConversations = conversations.value.toMutableList() - - val conversationIndex = newConversations.indexOfFirstOrNull { it.id == event.peerId } - - if (conversationIndex == null) { // диалога нет в списке - // pizdets - } else { - newConversations.removeAt(conversationIndex) - - _conversations.setValue { newConversations.sorted() } - syncUiConversation() - } - } - - private fun handleChatArchived(event: LongPollParsedEvent.ChatArchived) { - val conversation = event.conversation - - val newConversations = conversations.value.toMutableList() - - when (filter) { - ConversationsFilter.BUSINESS_NOTIFY -> Unit - - ConversationsFilter.ARCHIVE -> { - if (event.archived) { - newConversations.add(0, conversation) - } else { - val index = newConversations.indexOfFirstOrNull { it.id == conversation.id } - if (index == null) return - - newConversations.removeAt(index) - } - - _conversations.update { newConversations } - syncUiConversation() - } - - else -> { - if (event.archived) { - val index = newConversations.indexOfFirstOrNull { it.id == conversation.id } - if (index == null) return - - newConversations.removeAt(index) - } else { - newConversations.add(pinnedConversationsCount.value, conversation) - } - - _conversations.update { newConversations.sorted() } - syncUiConversation() - } - } - } - - private fun readConversation(peerId: Long, startMessageId: Long) { - messagesUseCase.markAsRead( - peerId = peerId, - startMessageId = startMessageId - ).listenValue(viewModelScope) { state -> - state.processState( - error = {}, - success = { - val newConversations = conversations.value.toMutableList() - val conversationIndex = - newConversations.indexOfFirstOrNull { it.id == peerId } - ?: return@listenValue - - newConversations[conversationIndex] = - newConversations[conversationIndex].copy(inRead = startMessageId) - - _conversations.update { newConversations } - syncUiConversation() - } - ) - } - } - - private fun List.sorted(): List { - val newConversations = toMutableList() - - val pinnedConversations = newConversations - .filter(VkConversation::isPinned) - .sortedWith { c1, c2 -> - val diff = c2.majorId - c1.majorId - - if (diff == 0) { - c2.minorId - c1.minorId - } else { - diff - } - } - - newConversations.removeAll(pinnedConversations) - newConversations.sortWith { c1, c2 -> - (c2.lastMessage?.date ?: 0) - (c1.lastMessage?.date ?: 0) - } - - newConversations.addAll(0, pinnedConversations) - return newConversations - } - - private fun syncUiConversation(): List { - val conversations = conversations.value - - val newUiConversations = conversations.map { conversation -> - val options = mutableListOf() - conversation.lastMessage?.run { - if (!conversation.isRead() && !this.isOut) { - options += ConversationOption.MarkAsRead - } - } - - val conversationsSize = this.conversations.value.size - val pinnedCount = pinnedConversationsCount.value - - val canPinOneMoreDialog = - conversationsSize > 4 && pinnedCount < 5 && !conversation.isPinned() - - if (conversation.isPinned()) { - options += ConversationOption.Unpin - } else if (canPinOneMoreDialog) { - options += ConversationOption.Pin - } - - when (filter) { - ConversationsFilter.ARCHIVE -> ConversationOption.Unarchive - - ConversationsFilter.UNREAD, - ConversationsFilter.ALL -> ConversationOption.Archive - - ConversationsFilter.BUSINESS_NOTIFY -> null - }?.let(options::add) - - options += ConversationOption.Delete - - conversation.asPresentation( - resources = resources, - useContactName = useContactNames, - isExpanded = expandedConversationId.value == conversation.id, - options = options.toImmutableList() - ) - } - _uiConversations.setValue { newUiConversations } - - return newUiConversations - } - - companion object { - const val LOAD_COUNT = 30 - } -} diff --git a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/di/ConversationsModule.kt b/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/di/ConversationsModule.kt deleted file mode 100644 index 9fde6c17..00000000 --- a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/di/ConversationsModule.kt +++ /dev/null @@ -1,37 +0,0 @@ -package dev.meloda.fast.conversations.di - -import dev.meloda.fast.conversations.ConversationsViewModel -import dev.meloda.fast.domain.ConversationsUseCase -import dev.meloda.fast.domain.ConversationsUseCaseImpl -import dev.meloda.fast.model.ConversationsFilter -import org.koin.core.module.dsl.singleOf -import org.koin.core.module.dsl.viewModel -import org.koin.core.qualifier.named -import org.koin.core.scope.Scope -import org.koin.dsl.bind -import org.koin.dsl.module - -val conversationsModule = module { - viewModel(named(ConversationsFilter.ALL)) { - createConversationsViewModel(ConversationsFilter.ALL) - } - viewModel(named(ConversationsFilter.ARCHIVE)) { - createConversationsViewModel(ConversationsFilter.ARCHIVE) - } - - singleOf(::ConversationsUseCaseImpl) bind ConversationsUseCase::class -} - -private fun Scope.createConversationsViewModel(filter: ConversationsFilter): ConversationsViewModel { - return ConversationsViewModel( - filter = filter, - updatesParser = get(), - conversationsUseCase = get(), - messagesUseCase = get(), - resources = get(), - userSettings = get(), - imageLoader = get(), - applicationContext = get(), - loadConversationsByIdUseCase = get() - ) -} diff --git a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/model/ConversationDialog.kt b/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/model/ConversationDialog.kt deleted file mode 100644 index 779a71cc..00000000 --- a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/model/ConversationDialog.kt +++ /dev/null @@ -1,12 +0,0 @@ -package dev.meloda.fast.conversations.model - -import androidx.compose.runtime.Immutable - -@Immutable -sealed class ConversationDialog { - data class ConversationPin(val conversationId: Long) : ConversationDialog() - data class ConversationUnpin(val conversationId: Long) : ConversationDialog() - data class ConversationDelete(val conversationId: Long) : ConversationDialog() - data class ConversationArchive(val conversationId: Long) : ConversationDialog() - data class ConversationUnarchive(val conversationId: Long) : ConversationDialog() -} diff --git a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/model/ConversationNavigation.kt b/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/model/ConversationNavigation.kt deleted file mode 100644 index d409284e..00000000 --- a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/model/ConversationNavigation.kt +++ /dev/null @@ -1,11 +0,0 @@ -package dev.meloda.fast.conversations.model - -import androidx.compose.runtime.Immutable - -@Immutable -sealed class ConversationNavigation { - - data class MessagesHistory(val peerId: Long) : ConversationNavigation() - - data object CreateChat : ConversationNavigation() -} diff --git a/feature/conversations/.gitignore b/feature/convos/.gitignore similarity index 100% rename from feature/conversations/.gitignore rename to feature/convos/.gitignore diff --git a/feature/conversations/build.gradle.kts b/feature/convos/build.gradle.kts similarity index 94% rename from feature/conversations/build.gradle.kts rename to feature/convos/build.gradle.kts index ef4cf1a8..71aca7bd 100644 --- a/feature/conversations/build.gradle.kts +++ b/feature/convos/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } android { - namespace = "dev.meloda.fast.conversations" + namespace = "dev.meloda.fast.convos" } dependencies { diff --git a/feature/convos/src/main/kotlin/dev/meloda/fast/convos/ConvosViewModel.kt b/feature/convos/src/main/kotlin/dev/meloda/fast/convos/ConvosViewModel.kt new file mode 100644 index 00000000..57b00db4 --- /dev/null +++ b/feature/convos/src/main/kotlin/dev/meloda/fast/convos/ConvosViewModel.kt @@ -0,0 +1,746 @@ +package dev.meloda.fast.convos + +import android.content.Context +import android.content.res.Resources +import android.os.Bundle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import coil.ImageLoader +import coil.request.ImageRequest +import com.conena.nanokt.collections.indexOfFirstOrNull +import dev.meloda.fast.common.VkConstants +import dev.meloda.fast.common.extensions.createTimerFlow +import dev.meloda.fast.common.extensions.findWithIndex +import dev.meloda.fast.common.extensions.listenValue +import dev.meloda.fast.common.extensions.setValue +import dev.meloda.fast.common.extensions.updateValue +import dev.meloda.fast.convos.model.ConvoDialog +import dev.meloda.fast.convos.model.ConvoNavigation +import dev.meloda.fast.convos.model.ConvosScreenState +import dev.meloda.fast.convos.model.InteractionJob +import dev.meloda.fast.convos.model.NewInteractionException +import dev.meloda.fast.data.VkUtils +import dev.meloda.fast.data.processState +import dev.meloda.fast.datastore.UserSettings +import dev.meloda.fast.domain.ConvoUseCase +import dev.meloda.fast.domain.LoadConvosByIdUseCase +import dev.meloda.fast.domain.LongPollUpdatesParser +import dev.meloda.fast.domain.MessagesUseCase +import dev.meloda.fast.domain.util.asPresentation +import dev.meloda.fast.domain.util.extractAvatar +import dev.meloda.fast.model.BaseError +import dev.meloda.fast.model.ConvosFilter +import dev.meloda.fast.model.InteractionType +import dev.meloda.fast.model.LongPollParsedEvent +import dev.meloda.fast.model.api.domain.VkConvo +import dev.meloda.fast.ui.model.vk.ConvoOption +import dev.meloda.fast.ui.model.vk.UiConvo +import dev.meloda.fast.ui.util.ImmutableList.Companion.toImmutableList +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update + +class ConvosViewModel( + updatesParser: LongPollUpdatesParser, + private val filter: ConvosFilter, + private val convoUseCase: ConvoUseCase, + private val messagesUseCase: MessagesUseCase, + private val resources: Resources, + private val userSettings: UserSettings, + private val imageLoader: ImageLoader, + private val applicationContext: Context, + private val loadConvosByIdUseCase: LoadConvosByIdUseCase +) : ViewModel() { + private val _screenState = MutableStateFlow(ConvosScreenState.EMPTY) + val screenState = _screenState.asStateFlow() + + private val _navigation = MutableStateFlow(null) + val navigation = _navigation.asStateFlow() + + private val _dialog = MutableStateFlow(null) + val dialog = _dialog.asStateFlow() + + private val _convos = MutableStateFlow>(emptyList()) + val convos = _convos.asStateFlow() + + private val _uiConvos = MutableStateFlow>(emptyList()) + val uiConvos = _uiConvos.asStateFlow() + + private val pinnedConvosCount = convos.map { convos -> + convos.count(VkConvo::isPinned) + }.stateIn(viewModelScope, SharingStarted.Eagerly, 0) + + private val _baseError = MutableStateFlow(null) + val baseError = _baseError.asStateFlow() + + private val _currentOffset = MutableStateFlow(0) + val currentOffset = _currentOffset.asStateFlow() + + private val _canPaginate = MutableStateFlow(false) + val canPaginate = _canPaginate.asStateFlow() + + private val expandedConvoId = MutableStateFlow(0L) + + private val useContactNames: Boolean get() = userSettings.useContactNames.value + + private val interactionsTimers = hashMapOf() + + init { + _screenState.updateValue { copy(isArchive = filter == ConvosFilter.ARCHIVE) } + + loadConvos() + + updatesParser.onNewMessage(::handleNewMessage) + updatesParser.onMessageEdited(::handleEditedMessage) + updatesParser.onMessageIncomingRead(::handleReadIncomingMessage) + updatesParser.onMessageOutgoingRead(::handleReadOutgoingMessage) + updatesParser.onInteractions(::handleInteraction) + updatesParser.onChatMajorChanged(::handleChatMajorChanged) + updatesParser.onChatMinorChanged(::handleChatMinorChanged) + updatesParser.onChatCleared(::handleChatClearing) + updatesParser.onChatArchived(::handleChatArchived) + + userSettings.useContactNames.listenValue(viewModelScope) { + syncUiConvos() + } + } + + fun onNavigationConsumed() { + _navigation.setValue { null } + } + + fun onDialogConfirmed(dialog: ConvoDialog, bundle: Bundle) { + onDialogDismissed(dialog) + + when (dialog) { + is ConvoDialog.ConvoDelete -> { + deleteConvo(dialog.convoId) + } + + is ConvoDialog.ConvoPin -> { + pinConvo(dialog.convoId, true) + } + + is ConvoDialog.ConvoUnpin -> { + pinConvo(dialog.convoId, false) + } + + is ConvoDialog.ConvoArchive -> { + archiveConvo(dialog.convoId, true) + } + + is ConvoDialog.ConvoUnarchive -> { + archiveConvo(dialog.convoId, false) + } + } + + expandedConvoId.setValue { 0 } + syncUiConvos() + } + + fun onDialogDismissed(dialog: ConvoDialog) { + _dialog.setValue { null } + } + + fun onDialogItemPicked(dialog: ConvoDialog, bundle: Bundle) { + when (dialog) { + is ConvoDialog.ConvoDelete -> Unit + is ConvoDialog.ConvoPin -> Unit + is ConvoDialog.ConvoUnpin -> Unit + is ConvoDialog.ConvoArchive -> Unit + is ConvoDialog.ConvoUnarchive -> Unit + } + } + + fun onErrorButtonClicked() { + when (baseError.value) { + null -> Unit + + is BaseError.ConnectionError, + is BaseError.InternalError, + is BaseError.SimpleError, + is BaseError.UnknownError -> onRefresh() + + else -> Unit + } + } + + fun onPaginationConditionsMet() { + _currentOffset.update { convos.value.size } + loadConvos() + } + + fun onRefresh() { + onErrorConsumed() + loadConvos(offset = 0) + } + + fun onConvoItemClick(convo: UiConvo) { + collapseConvos() + _navigation.setValue { ConvoNavigation.MessagesHistory(peerId = convo.id) } + } + + fun onConvoItemLongClick(convo: UiConvo) { + expandedConvoId.setValue { + if (convo.isExpanded) 0 + else convo.id + } + syncUiConvos() + } + + fun onOptionClicked( + convo: UiConvo, + option: ConvoOption + ) { + when (option) { + ConvoOption.Delete -> { + _dialog.setValue { ConvoDialog.ConvoDelete(convo.id) } + } + + ConvoOption.MarkAsRead -> { + convo.lastMessageId?.let { lastMessageId -> + readConvo( + peerId = convo.id, + startMessageId = lastMessageId + ) + collapseConvos() + } + } + + ConvoOption.Pin -> { + _dialog.setValue { ConvoDialog.ConvoPin(convo.id) } + } + + ConvoOption.Unpin -> { + _dialog.setValue { ConvoDialog.ConvoUnpin(convo.id) } + } + + ConvoOption.Archive -> { + _dialog.setValue { ConvoDialog.ConvoArchive(convo.id) } + } + + ConvoOption.Unarchive -> { + _dialog.setValue { ConvoDialog.ConvoUnarchive(convo.id) } + } + } + } + + fun onErrorConsumed() { + _baseError.setValue { null } + } + + fun setScrollIndex(index: Int) { + _screenState.setValue { old -> old.copy(scrollIndex = index) } + } + + fun setScrollOffset(offset: Int) { + _screenState.setValue { old -> old.copy(scrollOffset = offset) } + } + + fun onCreateChatButtonClicked() { + _navigation.setValue { ConvoNavigation.CreateChat } + } + + private fun collapseConvos() { + expandedConvoId.setValue { 0 } + syncUiConvos() + } + + private fun loadConvos( + offset: Int = currentOffset.value + ) { + convoUseCase.getConvos( + count = LOAD_COUNT, + offset = offset, + filter = filter + ).listenValue(viewModelScope) { state -> + state.processState( + error = { error -> + val newBaseError = VkUtils.parseError(error) + _baseError.update { newBaseError } + }, + success = { response -> + val convos = response + val fullConvos = if (offset == 0) { + convos + } else { + this.convos.value.plus(convos) + } + + val itemsCountSufficient = response.size == LOAD_COUNT + + val paginationExhausted = !itemsCountSufficient && + this.convos.value.isNotEmpty() + + _screenState.updateValue { + copy(isPaginationExhausted = paginationExhausted) + } + + val imagesToPreload = + response.mapNotNull { it.extractAvatar().extractUrl() } + + imagesToPreload.forEach { url -> + imageLoader.enqueue( + ImageRequest.Builder(applicationContext) + .data(url) + .build() + ) + } + + convoUseCase.storeConvos(response) + + _convos.emit(fullConvos) + syncUiConvos() + _canPaginate.setValue { itemsCountSufficient } + } + ) + + _screenState.setValue { old -> + old.copy( + isLoading = offset == 0 && state.isLoading(), + isPaginating = offset > 0 && state.isLoading() + ) + } + } + } + + private fun deleteConvo(peerId: Long) { + convoUseCase.delete(peerId).listenValue(viewModelScope) { state -> + state.processState( + error = {}, + success = { + val newConvos = convos.value.toMutableList() + val convoIndex = + newConvos.indexOfFirstOrNull { it.id == peerId } + ?: return@processState + + newConvos.removeAt(convoIndex) + _convos.update { newConvos.sorted() } + syncUiConvos() + } + ) + _screenState.emit(screenState.value.copy(isLoading = state.isLoading())) + } + } + + private fun pinConvo(peerId: Long, pin: Boolean) { + convoUseCase.changePinState(peerId, pin) + .listenValue(viewModelScope) { state -> + state.processState( + error = {}, + success = { + handleChatMajorChanged( + LongPollParsedEvent.ChatMajorChanged( + peerId = peerId, + majorId = if (pin) { + pinnedConvosCount.value.plus(1) * 16 + } else { + 0 + } + ) + ) + } + ) + + _screenState.setValue { old -> old.copy(isLoading = state.isLoading()) } + } + } + + private fun archiveConvo(peerId: Long, archive: Boolean) { + convoUseCase.changeArchivedState(peerId, archive) + .listenValue(viewModelScope) { state -> + state.processState( + error = {}, + success = { + convos.value.find { it.id == peerId }?.let { convo -> + handleChatArchived( + LongPollParsedEvent.ChatArchived( + convo = convo, + archived = archive + ) + ) + } + } + ) + } + } + + // TODO: 03-Apr-25, Danil Nikolaev: handle business messages + private fun handleNewMessage(event: LongPollParsedEvent.NewMessage) { + val message = event.message + + val newConvos = convos.value.toMutableList() + val convoIndex = + newConvos.indexOfFirstOrNull { it.id == message.peerId } + + if (convoIndex == null) { + if (event.inArchive != (filter == ConvosFilter.ARCHIVE)) return + + loadConvosByIdUseCase( + peerIds = listOf(message.peerId), + extended = true, + fields = VkConstants.ALL_FIELDS + ).listenValue(viewModelScope) { state -> + state.processState( + error = {}, + success = { response -> + val convo = (response.firstOrNull() ?: return@listenValue) + .copy(lastMessage = message) + + newConvos.add(pinnedConvosCount.value, convo) + _convos.update { newConvos.sorted() } + syncUiConvos() + } + ) + } + } else { + val convo = newConvos[convoIndex] + var newConvo = convo.copy( + lastMessage = message, + lastMessageId = message.id, + lastCmId = message.cmId, + unreadCount = if (message.isOut) convo.unreadCount + else convo.unreadCount + 1 + ) + + interactionsTimers[convo.id]?.let { job -> + if (job.interactionType == InteractionType.Typing + && message.fromId in convo.interactionIds + ) { + val newInteractionIds = newConvo.interactionIds.filter { id -> + id != message.fromId + } + + newConvo = newConvo.copy( + interactionType = if (newInteractionIds.isEmpty()) -1 else { + newConvo.interactionType + }, + interactionIds = newInteractionIds + ) + } + } + + if (convo.isPinned()) { + newConvos[convoIndex] = newConvo + } else { + newConvos.removeAt(convoIndex) + + val toPosition = pinnedConvosCount.value + newConvos.add(toPosition, newConvo) + } + + _convos.update { newConvos.sorted() } + syncUiConvos() + } + } + + private fun handleEditedMessage(event: LongPollParsedEvent.MessageEdited) { + val message = event.message + val newConvos = convos.value.toMutableList() + + val convoIndex = newConvos.indexOfFirstOrNull { it.id == message.peerId } + if (convoIndex == null) { // диалога нет в списке + // pizdets + } else { + val convo = newConvos[convoIndex] + newConvos[convoIndex] = convo.copy( + lastMessage = message, + lastMessageId = message.id, + lastCmId = message.cmId + ) + _convos.update { newConvos } + syncUiConvos() + } + } + + private fun handleReadIncomingMessage(event: LongPollParsedEvent.IncomingMessageRead) { + val newConvos = convos.value.toMutableList() + + val convoIndex = + newConvos.indexOfFirstOrNull { it.id == event.peerId } + + if (convoIndex == null) { // диалога нет в списке + // pizdets + } else { + newConvos[convoIndex] = + newConvos[convoIndex].copy( + inReadCmId = event.cmId, + unreadCount = event.unreadCount + ) + + _convos.update { newConvos } + syncUiConvos() + } + } + + private fun handleReadOutgoingMessage(event: LongPollParsedEvent.OutgoingMessageRead) { + val newConvos = convos.value.toMutableList() + + val convoIndex = + newConvos.indexOfFirstOrNull { it.id == event.peerId } + + if (convoIndex == null) { // диалога нет в списке + // pizdets + } else { + newConvos[convoIndex] = + newConvos[convoIndex].copy( + outReadCmId = event.cmId, + unreadCount = event.unreadCount + ) + + _convos.update { newConvos } + syncUiConvos() + } + } + + private fun handleInteraction(event: LongPollParsedEvent.Interaction) { + val interactionType = event.interactionType + val peerId = event.peerId + val userIds = event.userIds + + val newConvos = convos.value.toMutableList() + val convoAndIndex = + newConvos.findWithIndex { it.id == peerId } + + if (convoAndIndex != null) { + newConvos[convoAndIndex.first] = + convoAndIndex.second.copy( + interactionType = interactionType.value, + interactionIds = userIds + ) + + _convos.update { newConvos } + syncUiConvos() + + interactionsTimers[peerId]?.let { interactionJob -> + if (interactionJob.interactionType == interactionType) { + interactionJob.timerJob.cancel(NewInteractionException()) + } + } + + var timeoutAction: (() -> Unit)? = null + + val timerJob = createTimerFlow( + time = 6, + onTimeoutAction = { timeoutAction?.invoke() } + ).launchIn(viewModelScope) + + val newInteractionJob = InteractionJob( + interactionType = interactionType, + timerJob = timerJob + ) + + interactionsTimers[peerId] = newInteractionJob + + timeoutAction = { + stopInteraction(peerId, newInteractionJob) + } + } + } + + private fun stopInteraction(peerId: Long, interactionJob: InteractionJob) { + interactionsTimers[peerId] ?: return + + val newConvos = convos.value.toMutableList() + val convoAndIndex = + newConvos.findWithIndex { it.id == peerId } ?: return + + newConvos[convoAndIndex.first] = + convoAndIndex.second.copy( + interactionType = -1, + interactionIds = emptyList() + ) + + _convos.update { newConvos } + syncUiConvos() + + interactionJob.timerJob.cancel() + interactionsTimers[peerId] = null + } + + private fun handleChatMajorChanged(event: LongPollParsedEvent.ChatMajorChanged) { + val newConvos = convos.value.toMutableList() + val convoIndex = + newConvos.indexOfFirstOrNull { it.id == event.peerId } + + if (convoIndex == null) { // диалога нет в списке + // pizdets + } else { + newConvos[convoIndex] = + newConvos[convoIndex].copy(majorId = event.majorId) + + _convos.setValue { newConvos.sorted() } + syncUiConvos() + } + } + + private fun handleChatMinorChanged(event: LongPollParsedEvent.ChatMinorChanged) { + val newConvos = convos.value.toMutableList() + val convoIndex = + newConvos.indexOfFirstOrNull { it.id == event.peerId } + + if (convoIndex == null) { // диалога нет в списке + // pizdets + } else { + newConvos[convoIndex] = + newConvos[convoIndex].copy(minorId = event.minorId) + + _convos.setValue { newConvos.sorted() } + syncUiConvos() + } + } + + private fun handleChatClearing(event: LongPollParsedEvent.ChatCleared) { + val newConvos = convos.value.toMutableList() + + val convoIndex = newConvos.indexOfFirstOrNull { it.id == event.peerId } + + if (convoIndex == null) { // диалога нет в списке + // pizdets + } else { + newConvos.removeAt(convoIndex) + + _convos.setValue { newConvos.sorted() } + syncUiConvos() + } + } + + private fun handleChatArchived(event: LongPollParsedEvent.ChatArchived) { + val convo = event.convo + + val newConvos = convos.value.toMutableList() + + when (filter) { + ConvosFilter.BUSINESS_NOTIFY -> Unit + + ConvosFilter.ARCHIVE -> { + if (event.archived) { + newConvos.add(0, convo) + } else { + val index = newConvos.indexOfFirstOrNull { it.id == convo.id } + if (index == null) return + + newConvos.removeAt(index) + } + + _convos.update { newConvos } + syncUiConvos() + } + + else -> { + if (event.archived) { + val index = newConvos.indexOfFirstOrNull { it.id == convo.id } + if (index == null) return + + newConvos.removeAt(index) + } else { + newConvos.add(pinnedConvosCount.value, convo) + } + + _convos.update { newConvos.sorted() } + syncUiConvos() + } + } + } + + private fun readConvo(peerId: Long, startMessageId: Long) { + messagesUseCase.markAsRead( + peerId = peerId, + startMessageId = startMessageId + ).listenValue(viewModelScope) { state -> + state.processState( + error = {}, + success = { + val newConvos = convos.value.toMutableList() + val convoIndex = + newConvos.indexOfFirstOrNull { it.id == peerId } + ?: return@listenValue + + newConvos[convoIndex] = + newConvos[convoIndex].copy(inRead = startMessageId) + + _convos.update { newConvos } + syncUiConvos() + } + ) + } + } + + private fun List.sorted(): List { + val newConvos = toMutableList() + + val pinnedConvos = newConvos + .filter(VkConvo::isPinned) + .sortedWith { c1, c2 -> + val diff = c2.majorId - c1.majorId + + if (diff == 0) { + c2.minorId - c1.minorId + } else { + diff + } + } + + newConvos.removeAll(pinnedConvos) + newConvos.sortWith { c1, c2 -> + (c2.lastMessage?.date ?: 0) - (c1.lastMessage?.date ?: 0) + } + + newConvos.addAll(0, pinnedConvos) + return newConvos + } + + private fun syncUiConvos(): List { + val convos = convos.value + + val newUiConvos = convos.map { convo -> + val options = mutableListOf() + convo.lastMessage?.run { + if (!convo.isRead() && !this.isOut) { + options += ConvoOption.MarkAsRead + } + } + + val convosSize = this.convos.value.size + val pinnedCount = pinnedConvosCount.value + + val canPinOneMoreDialog = + convosSize > 4 && pinnedCount < 5 && !convo.isPinned() + + if (convo.isPinned()) { + options += ConvoOption.Unpin + } else if (canPinOneMoreDialog) { + options += ConvoOption.Pin + } + + when (filter) { + ConvosFilter.ARCHIVE -> ConvoOption.Unarchive + + ConvosFilter.UNREAD, + ConvosFilter.ALL -> ConvoOption.Archive + + ConvosFilter.BUSINESS_NOTIFY -> null + }?.let(options::add) + + options += ConvoOption.Delete + + convo.asPresentation( + resources = resources, + useContactName = useContactNames, + isExpanded = expandedConvoId.value == convo.id, + options = options.toImmutableList() + ) + } + _uiConvos.setValue { newUiConvos } + + return newUiConvos + } + + companion object { + const val LOAD_COUNT = 30 + } +} diff --git a/feature/convos/src/main/kotlin/dev/meloda/fast/convos/di/ConvosModule.kt b/feature/convos/src/main/kotlin/dev/meloda/fast/convos/di/ConvosModule.kt new file mode 100644 index 00000000..b54191ef --- /dev/null +++ b/feature/convos/src/main/kotlin/dev/meloda/fast/convos/di/ConvosModule.kt @@ -0,0 +1,37 @@ +package dev.meloda.fast.convos.di + +import dev.meloda.fast.convos.ConvosViewModel +import dev.meloda.fast.domain.ConvoUseCase +import dev.meloda.fast.domain.ConvoUseCaseImpl +import dev.meloda.fast.model.ConvosFilter +import org.koin.core.module.dsl.singleOf +import org.koin.core.module.dsl.viewModel +import org.koin.core.qualifier.named +import org.koin.core.scope.Scope +import org.koin.dsl.bind +import org.koin.dsl.module + +val convosModule = module { + viewModel(named(ConvosFilter.ALL)) { + createConvosViewModel(ConvosFilter.ALL) + } + viewModel(named(ConvosFilter.ARCHIVE)) { + createConvosViewModel(ConvosFilter.ARCHIVE) + } + + singleOf(::ConvoUseCaseImpl) bind ConvoUseCase::class +} + +private fun Scope.createConvosViewModel(filter: ConvosFilter): ConvosViewModel { + return ConvosViewModel( + filter = filter, + updatesParser = get(), + convoUseCase = get(), + messagesUseCase = get(), + resources = get(), + userSettings = get(), + imageLoader = get(), + applicationContext = get(), + loadConvosByIdUseCase = get() + ) +} diff --git a/feature/convos/src/main/kotlin/dev/meloda/fast/convos/model/ConvoDialog.kt b/feature/convos/src/main/kotlin/dev/meloda/fast/convos/model/ConvoDialog.kt new file mode 100644 index 00000000..7066ed0b --- /dev/null +++ b/feature/convos/src/main/kotlin/dev/meloda/fast/convos/model/ConvoDialog.kt @@ -0,0 +1,12 @@ +package dev.meloda.fast.convos.model + +import androidx.compose.runtime.Immutable + +@Immutable +sealed class ConvoDialog { + data class ConvoPin(val convoId: Long) : ConvoDialog() + data class ConvoUnpin(val convoId: Long) : ConvoDialog() + data class ConvoDelete(val convoId: Long) : ConvoDialog() + data class ConvoArchive(val convoId: Long) : ConvoDialog() + data class ConvoUnarchive(val convoId: Long) : ConvoDialog() +} diff --git a/feature/convos/src/main/kotlin/dev/meloda/fast/convos/model/ConvoNavigation.kt b/feature/convos/src/main/kotlin/dev/meloda/fast/convos/model/ConvoNavigation.kt new file mode 100644 index 00000000..b1bf8309 --- /dev/null +++ b/feature/convos/src/main/kotlin/dev/meloda/fast/convos/model/ConvoNavigation.kt @@ -0,0 +1,11 @@ +package dev.meloda.fast.convos.model + +import androidx.compose.runtime.Immutable + +@Immutable +sealed class ConvoNavigation { + + data class MessagesHistory(val peerId: Long) : ConvoNavigation() + + data object CreateChat : ConvoNavigation() +} diff --git a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/model/ConversationsScreenState.kt b/feature/convos/src/main/kotlin/dev/meloda/fast/convos/model/ConvosScreenState.kt similarity index 72% rename from feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/model/ConversationsScreenState.kt rename to feature/convos/src/main/kotlin/dev/meloda/fast/convos/model/ConvosScreenState.kt index a0ab9319..f04ca53d 100644 --- a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/model/ConversationsScreenState.kt +++ b/feature/convos/src/main/kotlin/dev/meloda/fast/convos/model/ConvosScreenState.kt @@ -1,10 +1,9 @@ -package dev.meloda.fast.conversations.model +package dev.meloda.fast.convos.model import androidx.compose.runtime.Immutable -import dev.meloda.fast.ui.model.api.UiConversation @Immutable -data class ConversationsScreenState( +data class ConvosScreenState( val isLoading: Boolean, val isPaginating: Boolean, val isPaginationExhausted: Boolean, @@ -15,7 +14,7 @@ data class ConversationsScreenState( ) { companion object { - val EMPTY: ConversationsScreenState = ConversationsScreenState( + val EMPTY: ConvosScreenState = ConvosScreenState( isLoading = true, isPaginating = false, isPaginationExhausted = false, diff --git a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/model/InteractionJob.kt b/feature/convos/src/main/kotlin/dev/meloda/fast/convos/model/InteractionJob.kt similarity index 79% rename from feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/model/InteractionJob.kt rename to feature/convos/src/main/kotlin/dev/meloda/fast/convos/model/InteractionJob.kt index b0b6f1ac..1409a403 100644 --- a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/model/InteractionJob.kt +++ b/feature/convos/src/main/kotlin/dev/meloda/fast/convos/model/InteractionJob.kt @@ -1,4 +1,4 @@ -package dev.meloda.fast.conversations.model +package dev.meloda.fast.convos.model import dev.meloda.fast.model.InteractionType import kotlinx.coroutines.Job diff --git a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/model/NewInteractionException.kt b/feature/convos/src/main/kotlin/dev/meloda/fast/convos/model/NewInteractionException.kt similarity index 70% rename from feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/model/NewInteractionException.kt rename to feature/convos/src/main/kotlin/dev/meloda/fast/convos/model/NewInteractionException.kt index ca01ea53..9cfe1efa 100644 --- a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/model/NewInteractionException.kt +++ b/feature/convos/src/main/kotlin/dev/meloda/fast/convos/model/NewInteractionException.kt @@ -1,4 +1,4 @@ -package dev.meloda.fast.conversations.model +package dev.meloda.fast.convos.model import kotlinx.coroutines.CancellationException diff --git a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/navigation/ConversationsNavigation.kt b/feature/convos/src/main/kotlin/dev/meloda/fast/convos/navigation/ConvosNavigation.kt similarity index 65% rename from feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/navigation/ConversationsNavigation.kt rename to feature/convos/src/main/kotlin/dev/meloda/fast/convos/navigation/ConvosNavigation.kt index ab87452c..ccad5b46 100644 --- a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/navigation/ConversationsNavigation.kt +++ b/feature/convos/src/main/kotlin/dev/meloda/fast/convos/navigation/ConvosNavigation.kt @@ -1,13 +1,13 @@ -package dev.meloda.fast.conversations.navigation +package dev.meloda.fast.convos.navigation import androidx.appcompat.app.AppCompatActivity import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import androidx.navigation.navigation -import dev.meloda.fast.conversations.ConversationsViewModel -import dev.meloda.fast.conversations.presentation.ConversationsRoute +import dev.meloda.fast.convos.ConvosViewModel +import dev.meloda.fast.convos.presentation.ConvosRoute import dev.meloda.fast.model.BaseError -import dev.meloda.fast.model.ConversationsFilter +import dev.meloda.fast.model.ConvosFilter import dev.meloda.fast.ui.extensions.getOrThrow import dev.meloda.fast.ui.theme.LocalNavController import kotlinx.serialization.Serializable @@ -15,32 +15,32 @@ import org.koin.androidx.viewmodel.ext.android.getViewModel import org.koin.core.qualifier.named @Serializable -object ConversationsGraph +object ConvoGraph @Serializable -object Conversations +object Convos @Serializable object Archive -fun NavGraphBuilder.conversationsGraph( +fun NavGraphBuilder.convosGraph( activity: AppCompatActivity, onError: (BaseError) -> Unit, onNavigateToMessagesHistory: (id: Long) -> Unit, onNavigateToCreateChat: () -> Unit, onScrolledToTop: () -> Unit ) { - navigation( - startDestination = Conversations + navigation( + startDestination = Convos ) { - val conversationsViewModel: ConversationsViewModel = with(activity) { - getViewModel(qualifier = named(ConversationsFilter.ALL)) + val convosViewModel: ConvosViewModel = with(activity) { + getViewModel(qualifier = named(ConvosFilter.ALL)) } - composable { + composable { val navController = LocalNavController.getOrThrow() - ConversationsRoute( - viewModel = conversationsViewModel, + ConvosRoute( + viewModel = convosViewModel, onError = onError, onNavigateToMessagesHistory = onNavigateToMessagesHistory, onNavigateToCreateChat = onNavigateToCreateChat, @@ -51,10 +51,10 @@ fun NavGraphBuilder.conversationsGraph( composable { val navController = LocalNavController.getOrThrow() - ConversationsRoute( + ConvosRoute( viewModel = with(activity) { - getViewModel( - qualifier = named(ConversationsFilter.ARCHIVE) + getViewModel( + qualifier = named(ConvosFilter.ARCHIVE) ) }, onBack = navController::navigateUp, diff --git a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/presentation/ConversationDialogs.kt b/feature/convos/src/main/kotlin/dev/meloda/fast/convos/presentation/ConvoDialogs.kt similarity index 73% rename from feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/presentation/ConversationDialogs.kt rename to feature/convos/src/main/kotlin/dev/meloda/fast/convos/presentation/ConvoDialogs.kt index 0066e46c..d8846928 100644 --- a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/presentation/ConversationDialogs.kt +++ b/feature/convos/src/main/kotlin/dev/meloda/fast/convos/presentation/ConvoDialogs.kt @@ -1,70 +1,70 @@ -package dev.meloda.fast.conversations.presentation +package dev.meloda.fast.convos.presentation import android.os.Bundle import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import androidx.core.os.bundleOf -import dev.meloda.fast.conversations.model.ConversationDialog -import dev.meloda.fast.conversations.model.ConversationsScreenState +import dev.meloda.fast.convos.model.ConvoDialog +import dev.meloda.fast.convos.model.ConvosScreenState import dev.meloda.fast.ui.components.MaterialDialog import dev.meloda.fast.ui.R @Composable fun HandleDialogs( - screenState: ConversationsScreenState, - dialog: ConversationDialog?, - onConfirmed: (ConversationDialog, Bundle) -> Unit = { _, _ -> }, - onDismissed: (ConversationDialog) -> Unit = {}, - onItemPicked: (ConversationDialog, Bundle) -> Unit = { _, _ -> } + screenState: ConvosScreenState, + dialog: ConvoDialog?, + onConfirmed: (ConvoDialog, Bundle) -> Unit = { _, _ -> }, + onDismissed: (ConvoDialog) -> Unit = {}, + onItemPicked: (ConvoDialog, Bundle) -> Unit = { _, _ -> } ) { when (dialog) { null -> Unit - is ConversationDialog.ConversationArchive -> { + is ConvoDialog.ConvoArchive -> { MaterialDialog( onDismissRequest = { onDismissed(dialog) }, - title = stringResource(id = R.string.confirm_archive_conversation), + title = stringResource(id = R.string.confirm_archive_convo), confirmAction = { onConfirmed(dialog, bundleOf()) }, confirmText = stringResource(id = R.string.action_archive), cancelText = stringResource(id = R.string.cancel) ) } - is ConversationDialog.ConversationUnarchive -> { + is ConvoDialog.ConvoUnarchive -> { MaterialDialog( onDismissRequest = { onDismissed(dialog) }, - title = stringResource(id = R.string.confirm_unarchive_conversation), + title = stringResource(id = R.string.confirm_unarchive_convo), confirmAction = { onConfirmed(dialog, bundleOf()) }, confirmText = stringResource(id = R.string.action_unarchive), cancelText = stringResource(id = R.string.cancel) ) } - is ConversationDialog.ConversationDelete -> { + is ConvoDialog.ConvoDelete -> { MaterialDialog( onDismissRequest = { onDismissed(dialog) }, - title = stringResource(id = R.string.confirm_delete_conversation), + title = stringResource(id = R.string.confirm_delete_convo), confirmAction = { onConfirmed(dialog, bundleOf()) }, confirmText = stringResource(id = R.string.action_delete), cancelText = stringResource(id = R.string.cancel) ) } - is ConversationDialog.ConversationPin -> { + is ConvoDialog.ConvoPin -> { MaterialDialog( onDismissRequest = { onDismissed(dialog) }, - title = stringResource(id = R.string.confirm_pin_conversation), + title = stringResource(id = R.string.confirm_pin_convo), confirmAction = { onConfirmed(dialog, bundleOf()) }, confirmText = stringResource(id = R.string.action_pin), cancelText = stringResource(id = R.string.cancel) ) } - is ConversationDialog.ConversationUnpin -> { + is ConvoDialog.ConvoUnpin -> { MaterialDialog( onDismissRequest = { onDismissed(dialog) }, - title = stringResource(id = R.string.confirm_unpin_conversation), + title = stringResource(id = R.string.confirm_unpin_convo), confirmAction = { onConfirmed(dialog, bundleOf()) }, confirmText = stringResource(id = R.string.action_unpin), cancelText = stringResource(id = R.string.cancel) diff --git a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/presentation/ConversationItem.kt b/feature/convos/src/main/kotlin/dev/meloda/fast/convos/presentation/ConvoItem.kt similarity index 89% rename from feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/presentation/ConversationItem.kt rename to feature/convos/src/main/kotlin/dev/meloda/fast/convos/presentation/ConvoItem.kt index 05bf74fd..b1c3d529 100644 --- a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/presentation/ConversationItem.kt +++ b/feature/convos/src/main/kotlin/dev/meloda/fast/convos/presentation/ConvoItem.kt @@ -1,4 +1,4 @@ -package dev.meloda.fast.conversations.presentation +package dev.meloda.fast.convos.presentation import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateDpAsState @@ -48,8 +48,8 @@ import coil.compose.AsyncImage import dev.meloda.fast.ui.basic.ContentAlpha import dev.meloda.fast.ui.basic.LocalContentAlpha import dev.meloda.fast.ui.components.DotsFlashing -import dev.meloda.fast.ui.model.api.ConversationOption -import dev.meloda.fast.ui.model.api.UiConversation +import dev.meloda.fast.ui.model.vk.ConvoOption +import dev.meloda.fast.ui.model.vk.UiConvo import dev.meloda.fast.ui.util.getImage import dev.meloda.fast.ui.util.getResourcePainter import dev.meloda.fast.ui.util.getString @@ -59,19 +59,19 @@ val BirthdayColor = Color(0xffb00b69) @OptIn(ExperimentalFoundationApi::class) @Composable -fun ConversationItem( - onItemClick: (UiConversation) -> Unit, - onItemLongClick: (conversation: UiConversation) -> Unit, - onOptionClicked: (UiConversation, ConversationOption) -> Unit, +fun ConvoItem( + onItemClick: (UiConvo) -> Unit, + onItemLongClick: (convo: UiConvo) -> Unit, + onOptionClicked: (UiConvo, ConvoOption) -> Unit, maxLines: Int, isUserAccount: Boolean, - conversation: UiConversation, + convo: UiConvo, modifier: Modifier = Modifier ) { val hapticFeedback = LocalHapticFeedback.current val bottomStartCornerRadius by animateDpAsState( - targetValue = if (conversation.isExpanded) 10.dp else 34.dp, + targetValue = if (convo.isExpanded) 10.dp else 34.dp, label = "bottomStartCornerRadius" ) @@ -79,15 +79,15 @@ fun ConversationItem( modifier = modifier .fillMaxWidth() .combinedClickable( - onClick = { onItemClick(conversation) }, + onClick = { onItemClick(convo) }, onLongClick = { - onItemLongClick(conversation) + onItemLongClick(convo) hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) } ) ) { - val showBackground by remember(conversation) { - derivedStateOf { conversation.isUnread || conversation.isExpanded } + val showBackground by remember(convo) { + derivedStateOf { convo.isUnread || convo.isExpanded } } AnimatedVisibility( @@ -133,7 +133,7 @@ fun ConversationItem( ) } } else { - val avatarImage = conversation.avatar?.getImage() + val avatarImage = convo.avatar?.getImage() if (avatarImage is Painter) { Icon( modifier = Modifier @@ -155,7 +155,7 @@ fun ConversationItem( } } - if (conversation.isPinned) { + if (convo.isPinned) { Box( modifier = Modifier .clip(CircleShape) @@ -173,13 +173,13 @@ fun ConversationItem( } } - if (conversation.isOnline) { + if (convo.isOnline) { Box( modifier = Modifier .clip(CircleShape) .size(18.dp) .background( - if (conversation.isUnread) { + if (convo.isUnread) { MaterialTheme.colorScheme.surfaceColorAtElevation(5.dp) } else { MaterialTheme.colorScheme.background @@ -197,13 +197,13 @@ fun ConversationItem( } } - if (conversation.isBirthday) { + if (convo.isBirthday) { Box( modifier = Modifier .clip(CircleShape) .size(16.dp) .background( - if (conversation.isUnread) { + if (convo.isUnread) { MaterialTheme.colorScheme.surfaceColorAtElevation(5.dp) } else { MaterialTheme.colorScheme.background @@ -237,16 +237,16 @@ fun ConversationItem( modifier = Modifier.weight(1f) ) { Text( - text = conversation.title, + text = convo.title, minLines = 1, maxLines = maxLines, style = MaterialTheme.typography.headlineSmall.copy(fontSize = 20.sp), ) Row { - if (conversation.interactionText != null) { + if (convo.interactionText != null) { Text( - text = conversation.interactionText.orEmpty(), + text = convo.interactionText.orEmpty(), color = MaterialTheme.colorScheme.primary ) Spacer(modifier = Modifier.width(4.dp)) @@ -258,7 +258,7 @@ fun ConversationItem( dotColor = MaterialTheme.colorScheme.primary ) } else { - conversation.attachmentImage?.getResourcePainter()?.let { painter -> + convo.attachmentImage?.getResourcePainter()?.let { painter -> Column { Spacer(modifier = Modifier.height(4.dp)) Icon( @@ -277,9 +277,9 @@ fun ConversationItem( modifier = Modifier.weight(1f), text = kotlin.run { val builder = - AnnotatedString.Builder(conversation.message.text) + AnnotatedString.Builder(convo.message.text) - conversation.message.spanStyles.map { spanStyleRange -> + convo.message.spanStyles.map { spanStyleRange -> val updatedSpanStyle = if (spanStyleRange.item.color == Color.Red) { spanStyleRange.item.copy(color = MaterialTheme.colorScheme.primary) @@ -294,7 +294,7 @@ fun ConversationItem( ) } - conversation.message.paragraphStyles.forEach { style -> + convo.message.paragraphStyles.forEach { style -> builder.addStyle( style = style.item, start = style.start, @@ -318,12 +318,12 @@ fun ConversationItem( Column { LocalContentAlpha(alpha = ContentAlpha.medium) { Text( - text = conversation.date, + text = convo.date, style = MaterialTheme.typography.bodySmall ) } - conversation.unreadCount?.let { count -> + convo.unreadCount?.let { count -> Spacer(modifier = Modifier.height(6.dp)) Box( modifier = Modifier @@ -351,7 +351,7 @@ fun ConversationItem( Spacer(modifier = Modifier.width(24.dp)) } - AnimatedVisibility(conversation.isExpanded) { + AnimatedVisibility(convo.isExpanded) { Column( modifier = Modifier .fillMaxWidth() @@ -367,9 +367,9 @@ fun ConversationItem( .fillMaxWidth() .padding(horizontal = 10.dp) ) { - items(conversation.options.toList()) { option -> + items(convo.options.toList()) { option -> ElevatedAssistChip( - onClick = { onOptionClicked(conversation, option) }, + onClick = { onOptionClicked(convo, option) }, leadingIcon = { option.icon.getResourcePainter()?.let { painter -> Icon( @@ -390,7 +390,7 @@ fun ConversationItem( } val bottomSpacerHeight by animateDpAsState( - targetValue = if (conversation.isExpanded) 4.dp else 8.dp, + targetValue = if (convo.isExpanded) 4.dp else 8.dp, label = "bottomSpacerHeight" ) diff --git a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/presentation/ConversationsList.kt b/feature/convos/src/main/kotlin/dev/meloda/fast/convos/presentation/ConvosList.kt similarity index 80% rename from feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/presentation/ConversationsList.kt rename to feature/convos/src/main/kotlin/dev/meloda/fast/convos/presentation/ConvosList.kt index ac221607..8a5b79b1 100644 --- a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/presentation/ConversationsList.kt +++ b/feature/convos/src/main/kotlin/dev/meloda/fast/convos/presentation/ConvosList.kt @@ -1,4 +1,4 @@ -package dev.meloda.fast.conversations.presentation +package dev.meloda.fast.convos.presentation import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -21,11 +21,11 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp -import dev.meloda.fast.conversations.model.ConversationsScreenState +import dev.meloda.fast.convos.model.ConvosScreenState import dev.meloda.fast.data.UserConfig import dev.meloda.fast.ui.R -import dev.meloda.fast.ui.model.api.ConversationOption -import dev.meloda.fast.ui.model.api.UiConversation +import dev.meloda.fast.ui.model.vk.ConvoOption +import dev.meloda.fast.ui.model.vk.UiConvo import dev.meloda.fast.ui.theme.LocalBottomPadding import dev.meloda.fast.ui.theme.LocalThemeConfig import dev.meloda.fast.ui.util.ImmutableList @@ -33,15 +33,15 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @Composable -fun ConversationsList( +fun ConvosList( modifier: Modifier = Modifier, - conversations: ImmutableList, - onConversationsClick: (UiConversation) -> Unit, - onConversationsLongClick: (UiConversation) -> Unit, - screenState: ConversationsScreenState, + convos: ImmutableList, + onConvosClick: (UiConvo) -> Unit, + onConvosLongClick: (UiConvo) -> Unit, + screenState: ConvosScreenState, state: LazyListState, maxLines: Int, - onOptionClicked: (UiConversation, ConversationOption) -> Unit, + onOptionClicked: (UiConvo, ConvoOption) -> Unit, padding: PaddingValues ) { val theme = LocalThemeConfig.current @@ -56,22 +56,22 @@ fun ConversationsList( Spacer(modifier = Modifier.height(8.dp)) } items( - items = conversations.values, - key = UiConversation::id, - ) { conversation -> - val isUserAccount by remember(conversation) { + items = convos.values, + key = UiConvo::id, + ) { convo -> + val isUserAccount by remember(convo) { derivedStateOf { - conversation.id == UserConfig.userId + convo.id == UserConfig.userId } } - ConversationItem( - onItemClick = onConversationsClick, - onItemLongClick = onConversationsLongClick, + ConvoItem( + onItemClick = onConvosClick, + onItemLongClick = onConvosLongClick, onOptionClicked = onOptionClicked, maxLines = maxLines, isUserAccount = isUserAccount, - conversation = conversation, + convo = convo, modifier = if (theme.enableAnimations) Modifier.animateItem( fadeInSpec = null, diff --git a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/presentation/ConversationsRoute.kt b/feature/convos/src/main/kotlin/dev/meloda/fast/convos/presentation/ConvosRoute.kt similarity index 77% rename from feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/presentation/ConversationsRoute.kt rename to feature/convos/src/main/kotlin/dev/meloda/fast/convos/presentation/ConvosRoute.kt index 076a0e9b..6ccb8fba 100644 --- a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/presentation/ConversationsRoute.kt +++ b/feature/convos/src/main/kotlin/dev/meloda/fast/convos/presentation/ConvosRoute.kt @@ -1,27 +1,27 @@ -package dev.meloda.fast.conversations.presentation +package dev.meloda.fast.convos.presentation import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.lifecycle.compose.collectAsStateWithLifecycle -import dev.meloda.fast.conversations.ConversationsViewModel -import dev.meloda.fast.conversations.model.ConversationNavigation +import dev.meloda.fast.convos.ConvosViewModel +import dev.meloda.fast.convos.model.ConvoNavigation import dev.meloda.fast.model.BaseError import dev.meloda.fast.ui.util.ImmutableList.Companion.toImmutableList @Composable -fun ConversationsRoute( - viewModel: ConversationsViewModel, +fun ConvosRoute( + viewModel: ConvosViewModel, onBack: (() -> Unit)? = null, onError: (BaseError) -> Unit, - onNavigateToMessagesHistory: (conversationId: Long) -> Unit, + onNavigateToMessagesHistory: (convoId: Long) -> Unit, onNavigateToCreateChat: (() -> Unit)? = null, onNavigateToArchive: (() -> Unit)? = null, onScrolledToTop: () -> Unit, ) { val screenState by viewModel.screenState.collectAsStateWithLifecycle() val navigationEvent by viewModel.navigation.collectAsStateWithLifecycle() - val conversations by viewModel.uiConversations.collectAsStateWithLifecycle() + val convos by viewModel.uiConvos.collectAsStateWithLifecycle() val dialog by viewModel.dialog.collectAsStateWithLifecycle() val baseError by viewModel.baseError.collectAsStateWithLifecycle() val canPaginate by viewModel.canPaginate.collectAsStateWithLifecycle() @@ -30,12 +30,12 @@ fun ConversationsRoute( val shouldBeConsumed: Boolean = when (val navigation = navigationEvent) { null -> false - is ConversationNavigation.CreateChat -> { + is ConvoNavigation.CreateChat -> { onNavigateToCreateChat?.invoke() true } - is ConversationNavigation.MessagesHistory -> { + is ConvoNavigation.MessagesHistory -> { onNavigateToMessagesHistory(navigation.peerId) true } @@ -44,14 +44,14 @@ fun ConversationsRoute( if (shouldBeConsumed) viewModel.onNavigationConsumed() } - ConversationsScreen( + ConvosScreen( onBack = { onBack?.invoke() }, screenState = screenState, - conversations = conversations.toImmutableList(), + convos = convos.toImmutableList(), baseError = baseError, canPaginate = canPaginate, - onConversationItemClicked = viewModel::onConversationItemClick, - onConversationItemLongClicked = viewModel::onConversationItemLongClick, + onConvoItemClicked = viewModel::onConvoItemClick, + onConvoItemLongClicked = viewModel::onConvoItemLongClick, onOptionClicked = viewModel::onOptionClicked, onPaginationConditionsMet = viewModel::onPaginationConditionsMet, onRefreshDropdownItemClicked = viewModel::onRefresh, diff --git a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/presentation/ConversationsScreen.kt b/feature/convos/src/main/kotlin/dev/meloda/fast/convos/presentation/ConvosScreen.kt similarity index 91% rename from feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/presentation/ConversationsScreen.kt rename to feature/convos/src/main/kotlin/dev/meloda/fast/convos/presentation/ConvosScreen.kt index c95c25c1..647f0d34 100644 --- a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/presentation/ConversationsScreen.kt +++ b/feature/convos/src/main/kotlin/dev/meloda/fast/convos/presentation/ConvosScreen.kt @@ -1,4 +1,4 @@ -package dev.meloda.fast.conversations.presentation +package dev.meloda.fast.convos.presentation import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateColorAsState @@ -57,16 +57,16 @@ import dev.chrisbanes.haze.hazeEffect import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import dev.chrisbanes.haze.materials.HazeMaterials -import dev.meloda.fast.conversations.model.ConversationsScreenState -import dev.meloda.fast.conversations.navigation.ConversationsGraph +import dev.meloda.fast.convos.model.ConvosScreenState +import dev.meloda.fast.convos.navigation.ConvoGraph import dev.meloda.fast.datastore.AppSettings import dev.meloda.fast.model.BaseError import dev.meloda.fast.ui.R import dev.meloda.fast.ui.components.FullScreenContainedLoader import dev.meloda.fast.ui.components.NoItemsView import dev.meloda.fast.ui.components.VkErrorView -import dev.meloda.fast.ui.model.api.ConversationOption -import dev.meloda.fast.ui.model.api.UiConversation +import dev.meloda.fast.ui.model.vk.ConvoOption +import dev.meloda.fast.ui.model.vk.UiConvo import dev.meloda.fast.ui.theme.LocalBottomPadding import dev.meloda.fast.ui.theme.LocalHazeState import dev.meloda.fast.ui.theme.LocalReselectedTab @@ -82,15 +82,15 @@ import kotlinx.coroutines.flow.debounce ExperimentalHazeMaterialsApi::class, ExperimentalMaterial3ExpressiveApi::class, ) @Composable -fun ConversationsScreen( - screenState: ConversationsScreenState = ConversationsScreenState.EMPTY, - conversations: ImmutableList = emptyImmutableList(), +fun ConvosScreen( + screenState: ConvosScreenState = ConvosScreenState.EMPTY, + convos: ImmutableList = emptyImmutableList(), baseError: BaseError? = null, canPaginate: Boolean = false, onBack: () -> Unit = {}, - onConversationItemClicked: (conversation: UiConversation) -> Unit = {}, - onConversationItemLongClicked: (conversation: UiConversation) -> Unit = {}, - onOptionClicked: (UiConversation, ConversationOption) -> Unit = { _, _ -> }, + onConvoItemClicked: (convo: UiConvo) -> Unit = {}, + onConvoItemLongClicked: (convo: UiConvo) -> Unit = {}, + onOptionClicked: (UiConvo, ConvoOption) -> Unit = { _, _ -> }, onPaginationConditionsMet: () -> Unit = {}, onRefreshDropdownItemClicked: () -> Unit = {}, onRefresh: () -> Unit = {}, @@ -109,7 +109,7 @@ fun ConversationsScreen( initialFirstVisibleItemScrollOffset = screenState.scrollOffset ) - val currentTabReselected = LocalReselectedTab.current[ConversationsGraph] == true + val currentTabReselected = LocalReselectedTab.current[ConvoGraph] == true LaunchedEffect(currentTabReselected) { if (currentTabReselected) { if (screenState.isArchive) { @@ -182,7 +182,7 @@ fun ConversationsScreen( id = when { screenState.isLoading -> R.string.title_loading screenState.isArchive -> R.string.title_archive - else -> R.string.title_conversations + else -> R.string.title_convos } ), maxLines = 1, @@ -268,7 +268,7 @@ fun ConversationsScreen( ) val showHorizontalProgressBar by remember(screenState) { - derivedStateOf { screenState.isLoading && conversations.isNotEmpty() } + derivedStateOf { screenState.isLoading && convos.isNotEmpty() } } AnimatedVisibility(showHorizontalProgressBar) { LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) @@ -310,7 +310,7 @@ fun ConversationsScreen( ) } - screenState.isLoading && conversations.isEmpty() -> FullScreenContainedLoader() + screenState.isLoading && convos.isEmpty() -> FullScreenContainedLoader() else -> { val pullToRefreshState = rememberPullToRefreshState() @@ -334,10 +334,10 @@ fun ConversationsScreen( ) } ) { - ConversationsList( - conversations = conversations, - onConversationsClick = onConversationItemClicked, - onConversationsLongClick = onConversationItemLongClicked, + ConvosList( + convos = convos, + onConvosClick = onConvoItemClicked, + onConvosLongClick = onConvoItemLongClicked, screenState = screenState, state = listState, maxLines = maxLines, @@ -350,7 +350,7 @@ fun ConversationsScreen( padding = padding ) - if (conversations.isEmpty()) { + if (convos.isEmpty()) { NoItemsView( buttonText = stringResource(R.string.action_refresh), onButtonClick = onRefresh diff --git a/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/CreateChatViewModel.kt b/feature/createchat/src/main/kotlin/dev/meloda/fast/convos/CreateChatViewModel.kt similarity index 98% rename from feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/CreateChatViewModel.kt rename to feature/createchat/src/main/kotlin/dev/meloda/fast/convos/CreateChatViewModel.kt index 6692cc60..45eafea4 100644 --- a/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/CreateChatViewModel.kt +++ b/feature/createchat/src/main/kotlin/dev/meloda/fast/convos/CreateChatViewModel.kt @@ -1,4 +1,4 @@ -package dev.meloda.fast.conversations +package dev.meloda.fast.convos import android.content.Context import androidx.lifecycle.ViewModel @@ -7,7 +7,7 @@ import coil.ImageLoader import coil.request.ImageRequest import dev.meloda.fast.common.extensions.listenValue import dev.meloda.fast.common.extensions.setValue -import dev.meloda.fast.conversations.model.CreateChatScreenState +import dev.meloda.fast.convos.model.CreateChatScreenState import dev.meloda.fast.data.State import dev.meloda.fast.data.UserConfig import dev.meloda.fast.data.processState @@ -19,7 +19,7 @@ import dev.meloda.fast.domain.util.asPresentation import dev.meloda.fast.model.BaseError import dev.meloda.fast.model.api.domain.VkUser import dev.meloda.fast.network.VkErrorCode -import dev.meloda.fast.ui.model.api.UiFriend +import dev.meloda.fast.ui.model.vk.UiFriend import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow diff --git a/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/di/CreateChatModule.kt b/feature/createchat/src/main/kotlin/dev/meloda/fast/convos/di/CreateChatModule.kt similarity index 59% rename from feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/di/CreateChatModule.kt rename to feature/createchat/src/main/kotlin/dev/meloda/fast/convos/di/CreateChatModule.kt index e57b7382..ef7caf9f 100644 --- a/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/di/CreateChatModule.kt +++ b/feature/createchat/src/main/kotlin/dev/meloda/fast/convos/di/CreateChatModule.kt @@ -1,6 +1,6 @@ -package dev.meloda.fast.conversations.di +package dev.meloda.fast.convos.di -import dev.meloda.fast.conversations.CreateChatViewModel +import dev.meloda.fast.convos.CreateChatViewModel import org.koin.core.module.dsl.viewModelOf import org.koin.dsl.module diff --git a/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/model/CreateChatScreenState.kt b/feature/createchat/src/main/kotlin/dev/meloda/fast/convos/model/CreateChatScreenState.kt similarity index 88% rename from feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/model/CreateChatScreenState.kt rename to feature/createchat/src/main/kotlin/dev/meloda/fast/convos/model/CreateChatScreenState.kt index 878ef734..9283c824 100644 --- a/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/model/CreateChatScreenState.kt +++ b/feature/createchat/src/main/kotlin/dev/meloda/fast/convos/model/CreateChatScreenState.kt @@ -1,7 +1,7 @@ -package dev.meloda.fast.conversations.model +package dev.meloda.fast.convos.model import androidx.compose.runtime.Immutable -import dev.meloda.fast.ui.model.api.UiFriend +import dev.meloda.fast.ui.model.vk.UiFriend @Immutable data class CreateChatScreenState( diff --git a/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/navigation/CreateChatNavigation.kt b/feature/createchat/src/main/kotlin/dev/meloda/fast/convos/navigation/CreateChatNavigation.kt similarity index 84% rename from feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/navigation/CreateChatNavigation.kt rename to feature/createchat/src/main/kotlin/dev/meloda/fast/convos/navigation/CreateChatNavigation.kt index 2ea96c69..803fb79e 100644 --- a/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/navigation/CreateChatNavigation.kt +++ b/feature/createchat/src/main/kotlin/dev/meloda/fast/convos/navigation/CreateChatNavigation.kt @@ -1,12 +1,12 @@ -package dev.meloda.fast.conversations.navigation +package dev.meloda.fast.convos.navigation import androidx.appcompat.app.AppCompatActivity import androidx.compose.ui.platform.LocalContext import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable -import dev.meloda.fast.conversations.CreateChatViewModel -import dev.meloda.fast.conversations.presentation.CreateChatRoute +import dev.meloda.fast.convos.CreateChatViewModel +import dev.meloda.fast.convos.presentation.CreateChatRoute import kotlinx.serialization.Serializable import org.koin.compose.viewmodel.koinViewModel diff --git a/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/presentation/CreateChatItem.kt b/feature/createchat/src/main/kotlin/dev/meloda/fast/convos/presentation/CreateChatItem.kt similarity index 97% rename from feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/presentation/CreateChatItem.kt rename to feature/createchat/src/main/kotlin/dev/meloda/fast/convos/presentation/CreateChatItem.kt index 226ffc87..27fb1d28 100644 --- a/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/presentation/CreateChatItem.kt +++ b/feature/createchat/src/main/kotlin/dev/meloda/fast/convos/presentation/CreateChatItem.kt @@ -1,4 +1,4 @@ -package dev.meloda.fast.conversations.presentation +package dev.meloda.fast.convos.presentation import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -25,7 +25,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import coil.compose.AsyncImage import dev.meloda.fast.ui.R -import dev.meloda.fast.ui.model.api.UiFriend +import dev.meloda.fast.ui.model.vk.UiFriend @Composable diff --git a/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/presentation/CreateChatList.kt b/feature/createchat/src/main/kotlin/dev/meloda/fast/convos/presentation/CreateChatList.kt similarity index 95% rename from feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/presentation/CreateChatList.kt rename to feature/createchat/src/main/kotlin/dev/meloda/fast/convos/presentation/CreateChatList.kt index 2eef18dd..932bb8f4 100644 --- a/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/presentation/CreateChatList.kt +++ b/feature/createchat/src/main/kotlin/dev/meloda/fast/convos/presentation/CreateChatList.kt @@ -1,4 +1,4 @@ -package dev.meloda.fast.conversations.presentation +package dev.meloda.fast.convos.presentation import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -19,9 +19,9 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp -import dev.meloda.fast.conversations.model.CreateChatScreenState +import dev.meloda.fast.convos.model.CreateChatScreenState import dev.meloda.fast.ui.R -import dev.meloda.fast.ui.model.api.UiFriend +import dev.meloda.fast.ui.model.vk.UiFriend import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch diff --git a/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/presentation/CreateChatScreen.kt b/feature/createchat/src/main/kotlin/dev/meloda/fast/convos/presentation/CreateChatScreen.kt similarity index 98% rename from feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/presentation/CreateChatScreen.kt rename to feature/createchat/src/main/kotlin/dev/meloda/fast/convos/presentation/CreateChatScreen.kt index 7b6ae2c6..13018ed1 100644 --- a/feature/createchat/src/main/kotlin/dev/meloda/fast/conversations/presentation/CreateChatScreen.kt +++ b/feature/createchat/src/main/kotlin/dev/meloda/fast/convos/presentation/CreateChatScreen.kt @@ -1,4 +1,4 @@ -package dev.meloda.fast.conversations.presentation +package dev.meloda.fast.convos.presentation import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.FastOutLinearInEasing @@ -58,8 +58,8 @@ import dev.chrisbanes.haze.hazeEffect import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import dev.chrisbanes.haze.materials.HazeMaterials -import dev.meloda.fast.conversations.CreateChatViewModel -import dev.meloda.fast.conversations.model.CreateChatScreenState +import dev.meloda.fast.convos.CreateChatViewModel +import dev.meloda.fast.convos.model.CreateChatScreenState import dev.meloda.fast.model.BaseError import dev.meloda.fast.ui.R import dev.meloda.fast.ui.components.FullScreenContainedLoader diff --git a/feature/friends/src/main/kotlin/dev/meloda/fast/friends/FriendsViewModel.kt b/feature/friends/src/main/kotlin/dev/meloda/fast/friends/FriendsViewModel.kt index 044c9935..03b587cb 100644 --- a/feature/friends/src/main/kotlin/dev/meloda/fast/friends/FriendsViewModel.kt +++ b/feature/friends/src/main/kotlin/dev/meloda/fast/friends/FriendsViewModel.kt @@ -121,8 +121,8 @@ abstract class BaseFriendsViewModelImpl : ViewModel(), FriendsViewModel { val friends = friends.value if (friends.isEmpty()) return - val uiFriends = friends.map { conversation -> - conversation.asPresentation(useContactNames) + val uiFriends = friends.map { convo -> + convo.asPresentation(useContactNames) } screenState.setValue { old -> diff --git a/feature/friends/src/main/kotlin/dev/meloda/fast/friends/model/FriendsScreenState.kt b/feature/friends/src/main/kotlin/dev/meloda/fast/friends/model/FriendsScreenState.kt index 967466e4..3cdaa549 100644 --- a/feature/friends/src/main/kotlin/dev/meloda/fast/friends/model/FriendsScreenState.kt +++ b/feature/friends/src/main/kotlin/dev/meloda/fast/friends/model/FriendsScreenState.kt @@ -1,7 +1,7 @@ package dev.meloda.fast.friends.model import androidx.compose.runtime.Immutable -import dev.meloda.fast.ui.model.api.UiFriend +import dev.meloda.fast.ui.model.vk.UiFriend @Immutable data class FriendsScreenState( diff --git a/feature/friends/src/main/kotlin/dev/meloda/fast/friends/presentation/FriendItem.kt b/feature/friends/src/main/kotlin/dev/meloda/fast/friends/presentation/FriendItem.kt index 4723f8b4..d5c7ba68 100644 --- a/feature/friends/src/main/kotlin/dev/meloda/fast/friends/presentation/FriendItem.kt +++ b/feature/friends/src/main/kotlin/dev/meloda/fast/friends/presentation/FriendItem.kt @@ -26,7 +26,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import coil.compose.AsyncImage import dev.meloda.fast.ui.R -import dev.meloda.fast.ui.model.api.UiFriend +import dev.meloda.fast.ui.model.vk.UiFriend @Composable fun FriendItem( diff --git a/feature/friends/src/main/kotlin/dev/meloda/fast/friends/presentation/FriendsList.kt b/feature/friends/src/main/kotlin/dev/meloda/fast/friends/presentation/FriendsList.kt index 4e67e9cd..b0c5ee80 100644 --- a/feature/friends/src/main/kotlin/dev/meloda/fast/friends/presentation/FriendsList.kt +++ b/feature/friends/src/main/kotlin/dev/meloda/fast/friends/presentation/FriendsList.kt @@ -22,7 +22,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import dev.meloda.fast.friends.model.FriendsScreenState import dev.meloda.fast.ui.R -import dev.meloda.fast.ui.model.api.UiFriend +import dev.meloda.fast.ui.model.vk.UiFriend import dev.meloda.fast.ui.theme.LocalBottomPadding import dev.meloda.fast.ui.util.ImmutableList import kotlinx.coroutines.Dispatchers diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModel.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModel.kt index 3cc103ff..c87d25d8 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModel.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModel.kt @@ -5,9 +5,9 @@ import androidx.compose.ui.text.input.TextFieldValue import dev.meloda.fast.messageshistory.model.MessageDialog import dev.meloda.fast.messageshistory.model.MessageNavigation import dev.meloda.fast.messageshistory.model.MessagesHistoryScreenState -import dev.meloda.fast.messageshistory.model.UiItem import dev.meloda.fast.model.BaseError import dev.meloda.fast.model.api.domain.VkMessage +import dev.meloda.fast.ui.model.vk.MessageUiItem import kotlinx.coroutines.flow.StateFlow interface MessagesHistoryViewModel { @@ -15,7 +15,7 @@ interface MessagesHistoryViewModel { val screenState: StateFlow val navigation: StateFlow val messages: StateFlow> - val uiMessages: StateFlow> + val uiMessages: StateFlow> val dialog: StateFlow val selectedMessages: StateFlow> diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModelImpl.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModelImpl.kt index 6a9f3714..8a4d72f9 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModelImpl.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModelImpl.kt @@ -37,21 +37,21 @@ import dev.meloda.fast.data.VkMemoryCache import dev.meloda.fast.data.processState import dev.meloda.fast.datastore.AppSettings import dev.meloda.fast.datastore.UserSettings -import dev.meloda.fast.domain.ConversationsUseCase +import dev.meloda.fast.domain.ConvoUseCase import dev.meloda.fast.domain.GetMessageReadPeersUseCase -import dev.meloda.fast.domain.LoadConversationsByIdUseCase +import dev.meloda.fast.domain.LoadConvosByIdUseCase import dev.meloda.fast.domain.LongPollUpdatesParser import dev.meloda.fast.domain.MessagesUseCase +import dev.meloda.fast.domain.util.asPresentation +import dev.meloda.fast.domain.util.extractAvatar +import dev.meloda.fast.domain.util.extractReplySummary +import dev.meloda.fast.domain.util.extractTitle import dev.meloda.fast.messageshistory.model.ActionMode import dev.meloda.fast.messageshistory.model.MessageDialog import dev.meloda.fast.messageshistory.model.MessageNavigation 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.navigation.MessagesHistory -import dev.meloda.fast.messageshistory.util.asPresentation -import dev.meloda.fast.messageshistory.util.extractAvatar -import dev.meloda.fast.messageshistory.util.extractTitle import dev.meloda.fast.model.BaseError import dev.meloda.fast.model.LongPollParsedEvent import dev.meloda.fast.model.api.domain.FormatDataType @@ -60,6 +60,7 @@ import dev.meloda.fast.model.api.domain.VkMessage import dev.meloda.fast.model.api.domain.VkPhotoDomain import dev.meloda.fast.network.VkErrorCode import dev.meloda.fast.ui.R +import dev.meloda.fast.ui.model.vk.MessageUiItem import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update @@ -79,10 +80,10 @@ import kotlin.random.Random class MessagesHistoryViewModelImpl( private val applicationContext: Context, private val messagesUseCase: MessagesUseCase, - private val conversationsUseCase: ConversationsUseCase, + private val convoUseCase: ConvoUseCase, private val resourceProvider: ResourceProvider, private val userSettings: UserSettings, - private val loadConversationsByIdUseCase: LoadConversationsByIdUseCase, + private val loadConvosByIdUseCase: LoadConvosByIdUseCase, private val getMessageReadPeersUseCase: GetMessageReadPeersUseCase, updatesParser: LongPollUpdatesParser, savedStateHandle: SavedStateHandle @@ -105,7 +106,7 @@ class MessagesHistoryViewModelImpl( override val canPaginate = MutableStateFlow(false) override val messages = MutableStateFlow>(emptyList()) - override val uiMessages = MutableStateFlow>(emptyList()) + override val uiMessages = MutableStateFlow>(emptyList()) private var lastMessageText: String? = null @@ -117,9 +118,9 @@ class MessagesHistoryViewModelImpl( init { val arguments = MessagesHistory.from(savedStateHandle).arguments - screenState.setValue { old -> old.copy(conversationId = arguments.conversationId) } + screenState.setValue { old -> old.copy(convoId = arguments.convoId) } - loadConversation() + loadConvo() loadMessagesHistory() updatesParser.onNewMessage(::handleNewMessage) @@ -142,7 +143,7 @@ class MessagesHistoryViewModelImpl( navigation.setValue { MessageNavigation.ChatMaterials( - peerId = screenState.value.conversationId, + peerId = screenState.value.convoId, cmId = cmId ) } @@ -411,7 +412,7 @@ class MessagesHistoryViewModelImpl( override fun onPinnedMessageClicked(messageId: Long) { val uiMessages = uiMessages.value val messageIndex = uiMessages.indexOfFirstOrNull { - it is UiItem.Message && it.id == messageId + it is MessageUiItem.Message && it.id == messageId } if (messageIndex == null) { // сообщения нет в списке @@ -442,7 +443,7 @@ class MessagesHistoryViewModelImpl( screenState.setValue { old -> old.copy( replyTitle = messageToReply.extractTitle(), - replyText = messageToReply.text + replyText = messageToReply.extractReplySummary(resourceProvider.resources) ) } } @@ -601,7 +602,7 @@ class MessagesHistoryViewModelImpl( Log.d("MessagesHistoryViewModel", "handleNewMessage: $message") - if (message.peerId != screenState.value.conversationId) return + if (message.peerId != screenState.value.convoId) return if (messages.value.indexOfFirstOrNull { it.id == message.id } != null) return val randomIds = messages.value.map(VkMessage::randomId) @@ -617,7 +618,7 @@ class MessagesHistoryViewModelImpl( private fun handleEditedMessage(event: LongPollParsedEvent.MessageEdited) { val message = event.message - if (message.peerId != screenState.value.conversationId) return + if (message.peerId != screenState.value.convoId) return val newMessages = messages.value.toMutableList() val index = newMessages.indexOfFirstOrNull { it.id == message.id } @@ -631,7 +632,7 @@ class MessagesHistoryViewModelImpl( } private fun handleReadIncomingEvent(event: LongPollParsedEvent.IncomingMessageRead) { - if (event.peerId != screenState.value.conversationId) return + if (event.peerId != screenState.value.convoId) return val messages = messages.value val index = messages.indexOfFirstOrNull { it.cmId == event.cmId } @@ -639,12 +640,12 @@ class MessagesHistoryViewModelImpl( if (index == null) { // диалога нет в списке // pizdets } else { - val newConversation = screenState.value.conversation.copy( + val newConvo = screenState.value.convo.copy( inReadCmId = event.cmId ) screenState.setValue { old -> - old.copy(conversation = newConversation) + old.copy(convo = newConvo) } syncUiMessages() @@ -652,7 +653,7 @@ class MessagesHistoryViewModelImpl( } private fun handleReadOutgoingEvent(event: LongPollParsedEvent.OutgoingMessageRead) { - if (event.peerId != screenState.value.conversationId) return + if (event.peerId != screenState.value.convoId) return val messages = messages.value val index = messages.indexOfFirstOrNull { it.cmId == event.cmId } @@ -660,12 +661,12 @@ class MessagesHistoryViewModelImpl( if (index == null) { // сообщения нет в списке // pizdets } else { - val newConversation = screenState.value.conversation.copy( + val newConvo = screenState.value.convo.copy( outReadCmId = event.cmId ) screenState.setValue { old -> - old.copy(conversation = newConversation) + old.copy(convo = newConvo) } syncUiMessages() @@ -673,7 +674,7 @@ class MessagesHistoryViewModelImpl( } private fun handleMessageDeleted(event: LongPollParsedEvent.MessageDeleted) { - if (event.peerId != screenState.value.conversationId) return + if (event.peerId != screenState.value.convoId) return val newMessages = messages.value.toMutableList() val index = newMessages.indexOfFirstOrNull { it.cmId == event.cmId } @@ -688,7 +689,7 @@ class MessagesHistoryViewModelImpl( } private fun handleMessageRestored(event: LongPollParsedEvent.MessageRestored) { - if (event.message.peerId != screenState.value.conversationId) return + if (event.message.peerId != screenState.value.convoId) return val newMessages = messages.value.toMutableList() val minDate = newMessages.minOf(VkMessage::date) @@ -704,7 +705,7 @@ class MessagesHistoryViewModelImpl( } private fun handleMessageMarkedAsImportant(event: LongPollParsedEvent.MessageMarkedAsImportant) { - if (event.peerId != screenState.value.conversationId) return + if (event.peerId != screenState.value.convoId) return val newMessages = messages.value.toMutableList() val index = newMessages.indexOfFirstOrNull { it.cmId == event.cmId } @@ -720,7 +721,7 @@ class MessagesHistoryViewModelImpl( } private fun handleMessageMarkedAsSpam(event: LongPollParsedEvent.MessageMarkedAsSpam) { - if (event.peerId != screenState.value.conversationId) return + if (event.peerId != screenState.value.convoId) return val newMessages = messages.value.toMutableList() val index = newMessages.indexOfFirstOrNull { it.cmId == event.cmId } @@ -735,7 +736,7 @@ class MessagesHistoryViewModelImpl( } private fun handleMessageMarkedAsNotSpam(event: LongPollParsedEvent.MessageMarkedAsNotSpam) { - if (event.message.peerId != screenState.value.conversationId) return + if (event.message.peerId != screenState.value.convoId) return val newMessages = messages.value.toMutableList() val maxDate = newMessages.maxOf(VkMessage::date) @@ -748,33 +749,33 @@ class MessagesHistoryViewModelImpl( syncUiMessages() } - private fun loadConversation() { - Log.d("MessagesHistoryViewModelImpl", "loadConversation()") + private fun loadConvo() { + Log.d("MessagesHistoryViewModelImpl", "loadConvo()") - loadConversationsByIdUseCase( - peerIds = listOf(screenState.value.conversationId), + loadConvosByIdUseCase( + peerIds = listOf(screenState.value.convoId), extended = true, fields = VkConstants.ALL_FIELDS ).listenValue(viewModelScope) { state -> state.processState( error = ::handleError, success = { response -> - val conversation = response.firstOrNull() ?: return@listenValue - val title = conversation.extractTitle( + val convo = response.firstOrNull() ?: return@listenValue + val title = convo.extractTitle( useContactName = AppSettings.General.useContactNames, resources = resourceProvider.resources ) - val avatar = conversation.extractAvatar() + val avatar = convo.extractAvatar() screenState.setValue { old -> old.copy( - conversation = conversation, + convo = convo, title = title, avatar = avatar ) } - conversation.pinnedMessage?.let(::handlePinnedMessage) + convo.pinnedMessage?.let(::handlePinnedMessage) } ) } @@ -785,7 +786,7 @@ class MessagesHistoryViewModelImpl( screenState.setValue { old -> old.copy( pinnedMessage = null, - conversation = old.conversation.copy( + convo = old.convo.copy( pinnedMessage = null, pinnedMessageId = null ), @@ -807,7 +808,7 @@ class MessagesHistoryViewModelImpl( screenState.setValue { old -> old.copy( pinnedMessage = pinnedMessage, - conversation = old.conversation.copy( + convo = old.convo.copy( pinnedMessage = pinnedMessage, pinnedMessageId = pinnedMessage.id ), @@ -821,7 +822,7 @@ class MessagesHistoryViewModelImpl( Log.d("MessagesHistoryViewModel", "loadMessagesHistory: $offset") messagesUseCase.getMessagesHistory( - conversationId = screenState.value.conversationId, + convoId = screenState.value.convoId, count = MESSAGES_LOAD_COUNT, offset = offset, ).listenValue(viewModelScope) { state -> @@ -835,14 +836,14 @@ class MessagesHistoryViewModelImpl( this.messages.value.plus(messages) }.sorted() - val conversations = response.conversations + val convos = response.convos imagesToPreload.setValue { messages.mapNotNull { it.extractAvatar().extractUrl() } } messagesUseCase.storeMessages(messages) - conversationsUseCase.storeConversations(conversations) + convoUseCase.storeConvos(convos) val itemsCountSufficient = messages.size == MESSAGES_LOAD_COUNT @@ -925,14 +926,14 @@ class MessagesHistoryViewModelImpl( cmId = -1L - sendingMessages.size, text = lastMessageText, isOut = true, - peerId = screenState.value.conversationId, + peerId = screenState.value.convoId, fromId = UserConfig.userId, date = (System.currentTimeMillis() / 1000).toInt(), randomId = Random.nextInt().toLong(), action = null, actionMemberId = null, actionText = null, - actionConversationMessageId = null, + actionCmId = null, actionMessage = null, updateTime = null, isImportant = false, @@ -972,7 +973,7 @@ class MessagesHistoryViewModelImpl( val forward = when { replyCmId != null -> { buildJsonObject { - put("peer_id", screenState.value.conversationId) + put("peer_id", screenState.value.convoId) put("conversation_message_ids", buildJsonArray { add(replyCmId) }) put("is_reply", true) }.toString() @@ -982,7 +983,7 @@ class MessagesHistoryViewModelImpl( } messagesUseCase.sendMessage( - peerId = screenState.value.conversationId, + peerId = screenState.value.convoId, randomId = newMessage.randomId, message = newMessage.text, forward = forward, @@ -1019,7 +1020,7 @@ class MessagesHistoryViewModelImpl( important: Boolean, ) { messagesUseCase.markAsImportant( - peerId = screenState.value.conversationId, + peerId = screenState.value.convoId, messageIds = messageIds, important = important ).listenValue(viewModelScope) { state -> @@ -1049,7 +1050,7 @@ class MessagesHistoryViewModelImpl( onSuccess: () -> Unit = {} ) { messagesUseCase.delete( - peerId = screenState.value.conversationId, + peerId = screenState.value.convoId, messageIds = messageIds, spam = spam, deleteForAll = deleteForAll @@ -1070,7 +1071,7 @@ class MessagesHistoryViewModelImpl( private fun pinMessage(messageId: Long) { messagesUseCase.pin( - peerId = screenState.value.conversationId, + peerId = screenState.value.convoId, messageId = messageId, cmId = null ).listenValue(viewModelScope) { state -> @@ -1095,7 +1096,7 @@ class MessagesHistoryViewModelImpl( } private fun unpinMessage(messageId: Long) { - messagesUseCase.unpin(screenState.value.conversationId) + messagesUseCase.unpin(screenState.value.convoId) .listenValue(viewModelScope) { state -> state.processState( error = ::handleError, @@ -1142,24 +1143,24 @@ class MessagesHistoryViewModelImpl( private fun readMessage(message: VkMessage) { messagesUseCase.markAsRead( - peerId = screenState.value.conversationId, + peerId = screenState.value.convoId, startMessageId = message.id ).listenValue(viewModelScope) { state -> state.processState( error = ::handleError, success = { - val oldConversation = screenState.value.conversation - val newConversation = oldConversation.copy( + val oldConvo = screenState.value.convo + val newConvo = oldConvo.copy( inRead = if (!message.isOut) message.id - else oldConversation.inRead, + else oldConvo.inRead, outRead = if (message.isOut) message.id - else oldConversation.outRead + else oldConvo.outRead ) screenState.setValue { old -> - old.copy(conversation = newConversation) + old.copy(convo = newConvo) } syncUiMessages() @@ -1224,7 +1225,7 @@ class MessagesHistoryViewModelImpl( } } - private fun syncUiMessages(): List { + private fun syncUiMessages(): List { val messages = messages.value val selectedMessages = selectedMessages.value @@ -1235,7 +1236,7 @@ class MessagesHistoryViewModelImpl( prevMessage = messages.getOrNull(index + 1), nextMessage = messages.getOrNull(index - 1), showTimeInActionMessages = AppSettings.Experimental.showTimeInActionMessages, - conversation = screenState.value.conversation, + convo = screenState.value.convo, isSelected = selectedMessages.indexOfFirstOrNull { it.id == message.id } != null ) } diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/model/MessagesHistoryArguments.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/model/MessagesHistoryArguments.kt index 8e68b618..a3539ba6 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/model/MessagesHistoryArguments.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/model/MessagesHistoryArguments.kt @@ -6,4 +6,4 @@ import kotlinx.serialization.Serializable @Parcelize @Serializable -data class MessagesHistoryArguments(val conversationId: Long) : Parcelable +data class MessagesHistoryArguments(val convoId: Long) : Parcelable diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/model/MessagesHistoryScreenState.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/model/MessagesHistoryScreenState.kt index 845d429c..55f7ab03 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/model/MessagesHistoryScreenState.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/model/MessagesHistoryScreenState.kt @@ -5,12 +5,12 @@ import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.input.TextFieldValue import dev.meloda.fast.common.model.UiImage import dev.meloda.fast.model.api.domain.VkAttachment -import dev.meloda.fast.model.api.domain.VkConversation +import dev.meloda.fast.model.api.domain.VkConvo import dev.meloda.fast.model.api.domain.VkMessage @Immutable data class MessagesHistoryScreenState( - val conversationId: Long, + val convoId: Long, val title: String, val status: String?, val avatar: UiImage, @@ -21,17 +21,17 @@ data class MessagesHistoryScreenState( val isPaginationExhausted: Boolean, val actionMode: ActionMode, val chatImageUrl: String?, - val conversation: VkConversation, + val convo: VkConvo, val pinnedMessage: VkMessage?, val pinnedTitle: String?, val pinnedSummary: AnnotatedString?, val replyTitle: String?, - val replyText: String? + val replyText: AnnotatedString? ) { companion object { val EMPTY: MessagesHistoryScreenState = MessagesHistoryScreenState( - conversationId = -1, + convoId = -1, title = "", status = null, avatar = UiImage.Color(0), @@ -42,7 +42,7 @@ data class MessagesHistoryScreenState( isPaginationExhausted = false, actionMode = ActionMode.RECORD_AUDIO, chatImageUrl = null, - conversation = VkConversation.EMPTY, + convo = VkConvo.EMPTY, pinnedMessage = null, pinnedTitle = null, pinnedSummary = null, diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/model/SendingStatus.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/model/SendingStatus.kt index 48bd9d92..4a00e24b 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/model/SendingStatus.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/model/SendingStatus.kt @@ -1,5 +1,3 @@ package dev.meloda.fast.messageshistory.model -enum class SendingStatus { - SENDING, SENT, FAILED -} + diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/navigation/MessagesHistoryNavigation.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/navigation/MessagesHistoryNavigation.kt index e5192f55..75e33887 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/navigation/MessagesHistoryNavigation.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/navigation/MessagesHistoryNavigation.kt @@ -5,7 +5,6 @@ import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import androidx.navigation.toRoute -import dev.meloda.fast.common.model.UiImage import dev.meloda.fast.messageshistory.model.MessagesHistoryArguments import dev.meloda.fast.messageshistory.presentation.MessagesHistoryRoute import dev.meloda.fast.model.BaseError @@ -41,6 +40,6 @@ fun NavGraphBuilder.messagesHistoryScreen( } } -fun NavController.navigateToMessagesHistory(conversationId: Long) { - this.navigate(MessagesHistory(MessagesHistoryArguments(conversationId))) +fun NavController.navigateToMessagesHistory(convoId: Long) { + this.navigate(MessagesHistory(MessagesHistoryArguments(convoId))) } diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/ActionMessageItem.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/ActionMessageItem.kt index 1d3e95b9..8b283a7b 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/ActionMessageItem.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/ActionMessageItem.kt @@ -16,11 +16,11 @@ import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import dev.meloda.fast.messageshistory.model.UiItem +import dev.meloda.fast.ui.model.vk.MessageUiItem @Composable fun ActionMessageItem( - item: UiItem.ActionMessage, + item: MessageUiItem.ActionMessage, modifier: Modifier = Modifier, onClick: () -> Unit = {} ) { @@ -56,7 +56,7 @@ fun ActionMessageItemPreview() { .padding(10.dp) ) { ActionMessageItem( - item = UiItem.ActionMessage( + item = MessageUiItem.ActionMessage( id = 0, text = buildAnnotatedString { append("You pinned message \"wow hello there\"") diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/DateStatus.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/DateStatus.kt index 38889f9a..3a88a680 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/DateStatus.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/DateStatus.kt @@ -17,8 +17,8 @@ import androidx.compose.ui.draw.rotate import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import dev.meloda.fast.messageshistory.model.SendingStatus import dev.meloda.fast.ui.R +import dev.meloda.fast.ui.model.vk.SendingStatus import dev.meloda.fast.ui.theme.LocalThemeConfig @Composable diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/InputBar.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/InputBar.kt index d778c708..2d467760 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/InputBar.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/InputBar.kt @@ -51,6 +51,7 @@ 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.AnnotatedString import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -62,6 +63,7 @@ import dev.chrisbanes.haze.hazeEffect import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import dev.chrisbanes.haze.materials.HazeMaterials import dev.meloda.fast.datastore.AppSettings +import dev.meloda.fast.domain.util.annotated import dev.meloda.fast.messageshistory.model.ActionMode import dev.meloda.fast.ui.R import dev.meloda.fast.ui.components.FastTextField @@ -78,7 +80,7 @@ fun InputBar( showAttachmentButton: Boolean, actionMode: ActionMode, replyTitle: String?, - replyText: String?, + replyText: AnnotatedString?, inputFieldFocusRequester: Boolean, onMessageInputChanged: (TextFieldValue) -> Unit = {}, onBoldRequested: () -> Unit = {}, @@ -136,7 +138,7 @@ fun InputBar( ReplyContainer( modifier = Modifier.padding(horizontal = 8.dp), title = replyTitle.orEmpty(), - text = replyText.orEmpty(), + text = replyText, onCloseClicked = onReplyCloseClicked, ) } @@ -357,7 +359,7 @@ private fun InputBarPreview() { showAttachmentButton = true, actionMode = ActionMode.SEND, replyTitle = "Иннокентий Панфилович", - replyText = "Ого, ром!", + replyText = "Ого, ром!".annotated(), inputFieldFocusRequester = false ) } diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessageBubble.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessageBubble.kt index 848f0902..7ec2254e 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessageBubble.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessageBubble.kt @@ -13,7 +13,6 @@ import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.Immutable @@ -33,15 +32,20 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import dev.meloda.fast.messageshistory.model.SendingStatus +import dev.meloda.fast.domain.util.annotated import dev.meloda.fast.messageshistory.presentation.attachments.Attachments import dev.meloda.fast.messageshistory.presentation.attachments.Reply import dev.meloda.fast.model.api.domain.VkAttachment import dev.meloda.fast.model.api.domain.VkStickerDomain import dev.meloda.fast.model.api.domain.VkVideoMessageDomain +import dev.meloda.fast.ui.model.vk.SendingStatus +import dev.meloda.fast.ui.theme.AppTheme import dev.meloda.fast.ui.theme.LocalThemeConfig import dev.meloda.fast.ui.util.ImmutableList +import dev.meloda.fast.ui.util.darken import dev.meloda.fast.ui.util.emptyImmutableList +import dev.meloda.fast.ui.util.isDark +import dev.meloda.fast.ui.util.lighten @Composable fun MessageBubble( @@ -57,7 +61,7 @@ fun MessageBubble( isSelected: Boolean, attachments: ImmutableList?, replyTitle: String?, - replySummary: String? = null, + replySummary: AnnotatedString? = null, onClick: (VkAttachment) -> Unit = {}, onLongClick: (VkAttachment) -> Unit = {}, onReplyClick: () -> Unit = {}, @@ -260,10 +264,18 @@ private data class MessageBubbleColors( @Composable private fun messageBubbleColors(isOut: Boolean): MessageBubbleColors { return if (isOut) { + val containerColor = MaterialTheme.colorScheme.primaryContainer + + val replyContainerColor = if (containerColor.isDark()) { + containerColor.lighten(0.15f) + } else { + containerColor.darken(0.075f) + } + MessageBubbleColors( - container = MaterialTheme.colorScheme.primaryContainer, + container = containerColor, content = MaterialTheme.colorScheme.onPrimaryContainer, - replyContainer = MaterialTheme.colorScheme.inversePrimary + replyContainer = replyContainerColor ) } else { MessageBubbleColors( @@ -277,41 +289,46 @@ private fun messageBubbleColors(isOut: Boolean): MessageBubbleColors { @Preview @Composable private fun Bubble() { - Column { - MessageBubble( - modifier = Modifier, - text = AnnotatedString("Some cool text"), - isOut = true, - date = "19:01", - isEdited = true, - isRead = true, - sendingStatus = SendingStatus.SENT, - isPinned = true, - isImportant = true, - isSelected = false, - attachments = emptyImmutableList(), - replyTitle = "Danil Nikolaev", - replySummary = "2 photos", - onClick = {}, - onLongClick = {}, - ) + AppTheme( + useDarkTheme = true, + useDynamicColors = true + ) { + Column { + MessageBubble( + modifier = Modifier, + text = AnnotatedString("Some cool text"), + isOut = true, + date = "19:01", + isEdited = true, + isRead = true, + sendingStatus = SendingStatus.SENT, + isPinned = true, + isImportant = true, + isSelected = false, + attachments = emptyImmutableList(), + replyTitle = "Danil Nikolaev", + replySummary = "2 photos".annotated(), + onClick = {}, + onLongClick = {}, + ) - MessageBubble( - modifier = Modifier, - text = AnnotatedString("Some cool text"), - isOut = false, - date = "19:01", - isEdited = true, - isRead = true, - sendingStatus = SendingStatus.SENT, - isPinned = true, - isImportant = true, - isSelected = false, - attachments = emptyImmutableList(), - replyTitle = "Danil Nikolaev", - replySummary = "2 photos", - onClick = {}, - onLongClick = {}, - ) + MessageBubble( + modifier = Modifier, + text = AnnotatedString("Some cool text"), + isOut = false, + date = "19:01", + isEdited = true, + isRead = true, + sendingStatus = SendingStatus.SENT, + isPinned = true, + isImportant = true, + isSelected = false, + attachments = emptyImmutableList(), + replyTitle = "Danil Nikolaev", + replySummary = "2 photos".annotated(), + onClick = {}, + onLongClick = {}, + ) + } } } diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessageBubbleIncoming.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessageBubbleIncoming.kt index 663d370f..39008cf8 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessageBubbleIncoming.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessageBubbleIncoming.kt @@ -37,16 +37,16 @@ import androidx.compose.ui.unit.dp import coil.compose.rememberAsyncImagePainter import coil.imageLoader import com.conena.nanokt.android.content.dpInPx -import dev.meloda.fast.messageshistory.model.UiItem import dev.meloda.fast.model.api.domain.VkAttachment import dev.meloda.fast.ui.R +import dev.meloda.fast.ui.model.vk.MessageUiItem import kotlin.math.roundToInt @Composable fun IncomingMessageBubble( enableAnimations: Boolean, modifier: Modifier = Modifier, - message: UiItem.Message, + message: MessageUiItem.Message, offsetX: Float = 0f, onClick: (VkAttachment) -> Unit = {}, onLongClick: (VkAttachment) -> Unit = {}, diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessageBubbleOutgoing.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessageBubbleOutgoing.kt index 9e38a9f1..99758989 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessageBubbleOutgoing.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessageBubbleOutgoing.kt @@ -18,17 +18,16 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import com.conena.nanokt.android.content.dpInPx -import dev.meloda.fast.messageshistory.model.UiItem import dev.meloda.fast.model.api.domain.VkAttachment import dev.meloda.fast.ui.R -import dev.meloda.fast.ui.util.ImmutableList.Companion.toImmutableList +import dev.meloda.fast.ui.model.vk.MessageUiItem import kotlin.math.roundToInt @Composable fun OutgoingMessageBubble( modifier: Modifier = Modifier, enableAnimations: Boolean, - message: UiItem.Message, + message: MessageUiItem.Message, offsetX: Float = 0f, onClick: (VkAttachment) -> Unit = {}, onLongClick: (VkAttachment) -> Unit = {}, diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesHistoryDialogs.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesHistoryDialogs.kt index c9da9897..01f49666 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesHistoryDialogs.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesHistoryDialogs.kt @@ -123,11 +123,11 @@ fun MessageOptionsDialog( options += MessageOption.ForwardHere options += MessageOption.Forward - if (message.isPeerChat() && screenState.conversation.canChangePin) { + if (message.isPeerChat() && screenState.convo.canChangePin) { options += if (message.isPinned) MessageOption.Unpin else MessageOption.Pin } - if (!message.isOut && !message.isRead(screenState.conversation)) { + if (!message.isOut && !message.isRead(screenState.convo)) { options += MessageOption.Read } diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesHistoryRoute.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesHistoryRoute.kt index 48fa1acc..6c7b9312 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesHistoryRoute.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesHistoryRoute.kt @@ -16,7 +16,7 @@ import org.koin.androidx.compose.koinViewModel fun MessagesHistoryRoute( onError: (BaseError) -> Unit, onBack: () -> Unit, - onNavigateToChatMaterials: (peerId: Long, conversationMessageId: Long) -> Unit, + onNavigateToChatMaterials: (peerId: Long, cmId: Long) -> Unit, onNavigateToPhotoViewer: (images: List, index: Int) -> Unit, viewModel: MessagesHistoryViewModel = koinViewModel() ) { diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesHistoryScreen.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesHistoryScreen.kt index 042e34aa..782ce70c 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesHistoryScreen.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesHistoryScreen.kt @@ -39,14 +39,14 @@ import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import dev.meloda.fast.data.UserConfig import dev.meloda.fast.datastore.AppSettings +import dev.meloda.fast.domain.util.indexOfMessageByCmId import dev.meloda.fast.messageshistory.model.MessagesHistoryScreenState -import dev.meloda.fast.messageshistory.model.UiItem -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.R import dev.meloda.fast.ui.components.Loader import dev.meloda.fast.ui.components.VkErrorView +import dev.meloda.fast.ui.model.vk.MessageUiItem import dev.meloda.fast.ui.theme.LocalThemeConfig import dev.meloda.fast.ui.util.ImmutableList import dev.meloda.fast.ui.util.emptyImmutableList @@ -61,7 +61,7 @@ import kotlinx.coroutines.launch fun MessagesHistoryScreen( screenState: MessagesHistoryScreenState = MessagesHistoryScreenState.EMPTY, messages: ImmutableList = emptyImmutableList(), - uiMessages: ImmutableList = emptyImmutableList(), + uiMessages: ImmutableList = emptyImmutableList(), isSelectedAtLeastOne: Boolean = false, scrollIndex: Int? = null, selectedMessages: ImmutableList = emptyImmutableList(), @@ -183,7 +183,7 @@ fun MessagesHistoryScreen( topBarContainerColorAlpha = topBarContainerColorAlpha, isClickable = !(screenState.isLoading && messages.isEmpty()), isMessagesSelecting = selectedMessages.isNotEmpty(), - isPeerAccount = screenState.conversationId == UserConfig.userId, + isPeerAccount = screenState.convoId == UserConfig.userId, avatar = screenState.avatar, title = topBarTitle, showHorizontalProgressBar = screenState.isLoading && messages.isNotEmpty(), @@ -191,7 +191,7 @@ fun MessagesHistoryScreen( pinnedMessage = pinnedMessage, pinnedTitle = screenState.pinnedTitle, pinnedSummary = screenState.pinnedSummary, - showUnpinButton = screenState.conversation.canChangePin, + showUnpinButton = screenState.convo.canChangePin, onTopBarClicked = onTopBarClicked, onBack = onBack, onClose = onClose, diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesList.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesList.kt index 03b545b8..9a6123ae 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesList.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/MessagesList.kt @@ -44,11 +44,11 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import dev.chrisbanes.haze.HazeState import dev.chrisbanes.haze.hazeSource import dev.meloda.fast.datastore.AppSettings -import dev.meloda.fast.messageshistory.model.UiItem import dev.meloda.fast.model.api.domain.VkAttachment import dev.meloda.fast.model.api.domain.VkFileDomain import dev.meloda.fast.model.api.domain.VkLinkDomain import dev.meloda.fast.model.api.domain.VkPhotoDomain +import dev.meloda.fast.ui.model.vk.MessageUiItem import dev.meloda.fast.ui.theme.LocalThemeConfig import dev.meloda.fast.ui.util.ImmutableList import kotlinx.coroutines.flow.distinctUntilChanged @@ -61,7 +61,7 @@ fun MessagesList( hasPinnedMessage: Boolean, hazeState: HazeState, listState: LazyListState, - uiMessages: ImmutableList, + uiMessages: ImmutableList, isSelectedAtLeastOne: Boolean, isPaginating: Boolean, isReplying: Boolean, @@ -78,7 +78,7 @@ fun MessagesList( val scope = rememberCoroutineScope() - val onAttachmentClick by rememberUpdatedState { message: UiItem.Message, attachment: VkAttachment -> + val onAttachmentClick by rememberUpdatedState { message: MessageUiItem.Message, attachment: VkAttachment -> if (isSelectedAtLeastOne) { onMessageClicked(message.id) } else { @@ -117,7 +117,7 @@ fun MessagesList( } } - val onAttachmentLongClick by rememberUpdatedState { message: UiItem.Message, attachment: VkAttachment -> + val onAttachmentLongClick by rememberUpdatedState { message: MessageUiItem.Message, attachment: VkAttachment -> if (isSelectedAtLeastOne) { onMessageLongClicked(message.id) uiMessages @@ -158,16 +158,16 @@ fun MessagesList( items( items = uiMessages.values, - key = UiItem::id, + key = MessageUiItem::id, contentType = { item -> when (item) { - is UiItem.ActionMessage -> "action_message" - is UiItem.Message -> "message" + is MessageUiItem.ActionMessage -> "action_message" + is MessageUiItem.Message -> "message" } } ) { item -> when (item) { - is UiItem.ActionMessage -> { + is MessageUiItem.ActionMessage -> { ActionMessageItem( modifier = Modifier.then( if (theme.enableAnimations) Modifier.animateItem( @@ -178,13 +178,13 @@ fun MessagesList( item = item, onClick = { if (item.actionCmId != null) { - onRequestScrollToCmId(item.actionCmId) + onRequestScrollToCmId(item.actionCmId!!) } } ) } - is UiItem.Message -> { + is MessageUiItem.Message -> { val backgroundColor by animateColorAsState( targetValue = if (item.isSelected) { MaterialTheme.colorScheme.primary.copy(alpha = 0.35f) @@ -275,7 +275,7 @@ fun MessagesList( }, onReplyClick = { if (item.replyCmId != null) { - onRequestScrollToCmId(item.replyCmId) + onRequestScrollToCmId(item.replyCmId!!) } }, offsetX = offsetX.value @@ -302,7 +302,7 @@ fun MessagesList( }, onReplyClick = { if (item.replyCmId != null) { - onRequestScrollToCmId(item.replyCmId) + onRequestScrollToCmId(item.replyCmId!!) } }, offsetX = offsetX.value diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/ReplyContainer.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/ReplyContainer.kt index e5aded22..f69202ff 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/ReplyContainer.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/ReplyContainer.kt @@ -22,16 +22,19 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import dev.meloda.fast.domain.util.annotated +import dev.meloda.fast.domain.util.orEmpty import dev.meloda.fast.ui.R import dev.meloda.fast.ui.components.RippledClickContainer @Composable fun ReplyContainer( title: String, - text: String?, + text: AnnotatedString?, modifier: Modifier = Modifier, onCloseClicked: () -> Unit = {}, backgroundColor: Color = MaterialTheme.colorScheme.surfaceColorAtElevation(5.dp) @@ -103,7 +106,7 @@ private fun ReplyContainerPreview() { ReplyContainer( onCloseClicked = {}, title = "В ответ Ишак", - text = "Приветствую тебя, Ишак!", + text = "Приветствую тебя, Ишак!".annotated(), ) } } diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/attachments/Reply.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/attachments/Reply.kt index f51d8076..b6e8be9d 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/attachments/Reply.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/presentation/attachments/Reply.kt @@ -22,10 +22,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import dev.meloda.fast.domain.util.annotated +import dev.meloda.fast.domain.util.orEmpty @Composable fun Reply( @@ -35,7 +38,7 @@ fun Reply( backgroundColor: Color, innerBackgroundColor: Color, title: String, - summary: String?, + summary: AnnotatedString?, modifier: Modifier = Modifier ) { Box( @@ -105,7 +108,7 @@ private fun ReplyBasePreview( ), onClick = {}, title = "Danil Nikolaev", - summary = "2 photos", + summary = "2 photos".annotated(), backgroundColor = backgroundColor, innerBackgroundColor = innerBackgroundColor, bottomPadding = 0.dp diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/util/Ext.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/util/Ext.kt deleted file mode 100644 index 4ac0a280..00000000 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/util/Ext.kt +++ /dev/null @@ -1,20 +0,0 @@ -package dev.meloda.fast.messageshistory.util - -import com.conena.nanokt.collections.indexOfFirstOrNull -import dev.meloda.fast.messageshistory.model.UiItem - -fun List.firstMessage(): UiItem.Message = filterIsInstance().first() - -fun List.firstMessageOrNull(): UiItem.Message? = filterIsInstance().firstOrNull() - -fun List.indexOfMessageById(messageId: Long): Int = - indexOfFirst { it.id == messageId } - -fun List.findMessageById(messageId: Long): UiItem.Message? = - firstOrNull { it.id == messageId } as UiItem.Message? - -fun List.indexOfMessageByCmId(cmId: Long): Int? = - indexOfFirstOrNull { it.cmId == cmId } - -fun List.findMessageByCmId(cmId: Long): UiItem.Message = - first { it.cmId == cmId } as UiItem.Message diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 14f1e4ab..94c68a36 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,9 +1,9 @@ [versions] -agp = "8.13.1" +agp = "8.13.2" retrofit = "3.0.0" eithernet = "2.0.0" haze = "1.7.1" -kotlin = "2.2.21" +kotlin = "2.3.0" ksp = "2.3.3" moduleGraph = "2.9.0" diff --git a/settings.gradle.kts b/settings.gradle.kts index affd20d8..462ebbfe 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -30,7 +30,7 @@ include(":core:domain") include(":core:model") include(":feature:messageshistory") -include(":feature:conversations") +include(":feature:convos") include(":feature:auth") include(":feature:chatmaterials") include(":feature:languagepicker")