diff --git a/app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/13.json b/app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/13.json index 2e23450016a..fc41c7d0912 100644 --- a/app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/13.json +++ b/app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/13.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 13, - "identityHash": "a521f027909f69f4c7d1855f84a2e67f", + "identityHash": "b3d3d5405b220baf1819c1397f935f95", "entities": [ { "tableName": "User", @@ -738,12 +738,64 @@ ] } ] + }, + { + "tableName": "user_circles", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `displayName` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "displayName", + "columnName": "displayName", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "user_groups", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `groups` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "groups", + "columnName": "groups", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "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, 'a521f027909f69f4c7d1855f84a2e67f')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b3d3d5405b220baf1819c1397f935f95')" ] } } \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt index 0828b475f85..35997f9cbc5 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/activities/MainActivity.kt @@ -138,6 +138,10 @@ class MainActivity : BaseActivity(), ActionBarProvider { super.onStop() } + override fun onDestroy() { + super.onDestroy() + } + private fun openConversationList() { val intent = Intent(this, ConversationsListActivity::class.java) intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLinkPreviewMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLinkPreviewMessageViewHolder.kt index b1993172676..8c6f6c7301b 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLinkPreviewMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLinkPreviewMessageViewHolder.kt @@ -83,7 +83,9 @@ class IncomingLinkPreviewMessageViewHolder(incomingView: View, payload: Any) : viewThemeUtils, processedMessageText!!, message, - itemView + itemView, + null, + null ) binding.messageText.text = processedMessageText diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPreviewMessageViewHolder.java b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPreviewMessageViewHolder.java index 572bd9e9ac5..21c1a738538 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPreviewMessageViewHolder.java +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPreviewMessageViewHolder.java @@ -60,7 +60,9 @@ public void onBind(@NonNull ChatMessage message) { viewThemeUtils, processedMessageText, message, - binding.incomingPreviewMessageBubble); + binding.incomingPreviewMessageBubble, + null, + null); } binding.incomingPreviewMessageBubble.setOnClickListener(null); diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt index 18b298756d7..26f19775e51 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt @@ -27,6 +27,7 @@ import com.nextcloud.talk.chat.ChatActivity import com.nextcloud.talk.chat.data.ChatMessageRepository import com.nextcloud.talk.chat.data.model.ChatMessage import com.nextcloud.talk.data.user.model.User +import com.nextcloud.talk.data.database.model.UserGroupsCirclesRepository import com.nextcloud.talk.databinding.ItemCustomIncomingTextMessageBinding import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.ApiUtils @@ -43,6 +44,8 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.util.Date @@ -74,6 +77,11 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) : lateinit var commonMessageInterface: CommonMessageInterface + @Inject + lateinit var userGroupsCirclesRepository: UserGroupsCirclesRepository + + private val coroutineScope = CoroutineScope(Dispatchers.Main) + @Inject lateinit var chatRepository: ChatMessageRepository @@ -86,15 +94,32 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) : colorizeMessageBubble(message) itemView.isSelected = false val user = currentUserProvider.currentUser.blockingGet() - val hasCheckboxes = processCheckboxes( - message, - user - ) - processMessage(message, hasCheckboxes) + lateinit var userGroups: List + lateinit var userCircles: List + coroutineScope.launch { + userGroups = userGroupsCirclesRepository.getUserGroups() + .map { list -> list.mapNotNull { it.groups } }.firstOrNull() ?: emptyList() + + userCircles = userGroupsCirclesRepository.getUserCircles() + .map { list -> list.mapNotNull { it.displayName } }.firstOrNull() ?: emptyList() + + val hasCheckboxes = processCheckboxes( + message, + user + ) + processMessage(message, hasCheckboxes, userGroups, userCircles) + } } - private fun processMessage(message: ChatMessage, hasCheckboxes: Boolean) { + @Suppress("Detekt.LongMethod") + private fun processMessage( + message: ChatMessage, + hasCheckboxes: Boolean, + userGroups: List, + userCircles: List + ) { var textSize = context.resources!!.getDimension(R.dimen.chat_text_size) + if (!hasCheckboxes) { binding.messageText.visibility = View.VISIBLE binding.checkboxContainer.visibility = View.GONE @@ -132,7 +157,9 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) : viewThemeUtils, processedMessageText, message, - itemView + itemView, + userGroups, + userCircles ) val messageParameters = message.messageParameters if ( diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLinkPreviewMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLinkPreviewMessageViewHolder.kt index 8d553dae323..bd02a97a63a 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLinkPreviewMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLinkPreviewMessageViewHolder.kt @@ -83,7 +83,9 @@ class OutcomingLinkPreviewMessageViewHolder(outcomingView: View, payload: Any) : viewThemeUtils, processedMessageText!!, message, - itemView + itemView, + null, + null ) binding.messageText.text = processedMessageText diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPreviewMessageViewHolder.java b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPreviewMessageViewHolder.java index eb7d54452c9..c5ca7effa2a 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPreviewMessageViewHolder.java +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPreviewMessageViewHolder.java @@ -60,7 +60,9 @@ public void onBind(@NonNull ChatMessage message) { viewThemeUtils, processedMessageText, message, - binding.outgoingPreviewMessageBubble); + binding.outgoingPreviewMessageBubble, + null, + null); } binding.outgoingPreviewMessageBubble.setOnClickListener(null); diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt index dc3aea52d32..8778e0ad279 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt @@ -29,6 +29,7 @@ import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedA import com.nextcloud.talk.chat.ChatActivity import com.nextcloud.talk.chat.data.ChatMessageRepository import com.nextcloud.talk.chat.data.model.ChatMessage +import com.nextcloud.talk.data.database.model.UserGroupsCirclesRepository import com.nextcloud.talk.data.network.NetworkMonitor import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.ItemCustomOutcomingTextMessageBinding @@ -47,6 +48,8 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.util.Date @@ -85,20 +88,39 @@ class OutcomingTextMessageViewHolder(itemView: View) : private var job: Job? = null - @Suppress("Detekt.LongMethod") + @Inject + lateinit var userGroupsCirclesRepository: UserGroupsCirclesRepository + + private val coroutineScope = CoroutineScope(Dispatchers.Main) + override fun onBind(message: ChatMessage) { super.onBind(message) sharedApplication!!.componentApplication.inject(this) val user = currentUserProvider.currentUser.blockingGet() - val hasCheckboxes = processCheckboxes( - message, - user - ) - processMessage(message, hasCheckboxes) + lateinit var userGroups: List + lateinit var userCircles: List + coroutineScope.launch { + userGroups = userGroupsCirclesRepository.getUserGroups() + .map { list -> list.mapNotNull { it.groups } }.firstOrNull() ?: emptyList() + + userCircles = userGroupsCirclesRepository.getUserCircles() + .map { list -> list.mapNotNull { it.displayName } }.firstOrNull() ?: emptyList() + + val hasCheckboxes = processCheckboxes( + message, + user + ) + processMessage(message, hasCheckboxes, userCircles, userGroups) + } } @Suppress("Detekt.LongMethod") - private fun processMessage(message: ChatMessage, hasCheckboxes: Boolean) { + private fun processMessage( + message: ChatMessage, + hasCheckboxes: Boolean, + userGroups: List, + userCircles: List + ) { var isBubbled = true val layoutParams = binding.messageTime.layoutParams as FlexboxLayout.LayoutParams var textSize = context.resources.getDimension(R.dimen.chat_text_size) @@ -106,6 +128,7 @@ class OutcomingTextMessageViewHolder(itemView: View) : realView.isSelected = false layoutParams.isWrapBefore = false viewThemeUtils.platform.colorTextView(binding.messageTime, ColorRole.ON_SURFACE_VARIANT) + viewThemeUtils.platform.colorTextView(binding.messageTime, ColorRole.ON_SURFACE_VARIANT) binding.messageText.visibility = View.VISIBLE binding.checkboxContainer.visibility = View.GONE @@ -115,6 +138,7 @@ class OutcomingTextMessageViewHolder(itemView: View) : message, false, viewThemeUtils + ) val spansFromString: Array = processedMessageText!!.getSpans( @@ -144,8 +168,11 @@ class OutcomingTextMessageViewHolder(itemView: View) : viewThemeUtils, processedMessageText, message, - itemView + itemView, + userGroups, + userCircles ) + binding.messageText.text = processedMessageText if ( (message.messageParameters == null || message.messageParameters!!.size <= 0) && @@ -156,7 +183,6 @@ class OutcomingTextMessageViewHolder(itemView: View) : realView.isSelected = true isBubbled = false } - binding.messageTime.layoutParams = layoutParams viewThemeUtils.platform.colorTextView(binding.messageText, ColorRole.ON_SURFACE_VARIANT) binding.messageText.text = processedMessageText @@ -186,7 +212,10 @@ class OutcomingTextMessageViewHolder(itemView: View) : binding.sendingProgress.visibility = View.GONE if (message.sendingFailed) { - updateStatus(R.drawable.baseline_error_outline_24, context.resources?.getString(R.string.nc_message_failed)) + updateStatus( + R.drawable.baseline_error_outline_24, + context.resources?.getString(R.string.nc_message_failed) + ) } else if (message.isTemporary) { updateStatus(R.drawable.baseline_schedule_24, context.resources?.getString(R.string.nc_message_sending)) } else if (message.readStatus == ReadStatus.READ) { diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApi.java b/app/src/main/java/com/nextcloud/talk/api/NcApi.java index ca5d7afe592..2c4b626de09 100644 --- a/app/src/main/java/com/nextcloud/talk/api/NcApi.java +++ b/app/src/main/java/com/nextcloud/talk/api/NcApi.java @@ -34,6 +34,8 @@ import com.nextcloud.talk.models.json.signaling.settings.SignalingSettingsOverall; import com.nextcloud.talk.models.json.status.StatusOverall; import com.nextcloud.talk.models.json.unifiedsearch.UnifiedSearchOverall; +import com.nextcloud.talk.models.json.usercircles.UserCirclesOverall; +import com.nextcloud.talk.models.json.usergroups.UserGroupsOverall; import com.nextcloud.talk.models.json.userprofile.UserProfileFieldsOverall; import com.nextcloud.talk.models.json.userprofile.UserProfileOverall; import com.nextcloud.talk.polls.repositories.model.PollOverall; @@ -646,4 +648,5 @@ Observable acceptInvitation(@Header("Authorization") String auth @DELETE Observable rejectInvitation(@Header("Authorization") String authorization, @Url String url); + } \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt b/app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt index a2e58dcd166..ff63a08529c 100644 --- a/app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt +++ b/app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt @@ -20,6 +20,8 @@ import com.nextcloud.talk.models.json.participants.TalkBanOverall import com.nextcloud.talk.models.json.profile.ProfileOverall import com.nextcloud.talk.models.json.testNotification.TestNotificationOverall import com.nextcloud.talk.models.json.userAbsence.UserAbsenceOverall +import com.nextcloud.talk.models.json.usercircles.UserCirclesOverall +import com.nextcloud.talk.models.json.usergroups.UserGroupsOverall import okhttp3.MultipartBody import okhttp3.RequestBody import retrofit2.http.Body @@ -285,4 +287,10 @@ interface NcApiCoroutines { @DELETE suspend fun unbindRoom(@Header("Authorization") authorization: String, @Url url: String): GenericOverall + + @GET + suspend fun getUserGroups(@Header("Authorization") authorization: String?, @Url url: String?): UserGroupsOverall + + @GET + suspend fun getUserCircles(@Header("Authorization") authorization: String?, @Url url: String?): UserCirclesOverall } diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index c92ca2e4af8..4feda24ecb8 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -196,6 +196,7 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_RECORDING_STATE import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_START_CALL_AFTER_ROOM_SWITCH import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SWITCH_TO_ROOM +import com.nextcloud.talk.utils.message.MessageUtils import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil import com.nextcloud.talk.utils.rx.DisposableSet import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder @@ -271,6 +272,9 @@ class ChatActivity : @Inject lateinit var networkMonitor: NetworkMonitor + @Inject + lateinit var messageUtils: MessageUtils + lateinit var chatViewModel: ChatViewModel lateinit var conversationInfoViewModel: ConversationInfoViewModel @@ -669,6 +673,10 @@ class ChatActivity : } } + conversationUser?.let { user -> + chatViewModel.fetchUserData(user) + } + if (currentConversation?.objectType == ConversationEnums.ObjectType.EVENT && hasSpreedFeatureCapability( conversationUser?.capabilities!!.spreedCapability!!, diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/ChatNetworkDataSource.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/ChatNetworkDataSource.kt index 7276f448942..8945a6987db 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/ChatNetworkDataSource.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/ChatNetworkDataSource.kt @@ -77,4 +77,6 @@ interface ChatNetworkDataSource { ): List suspend fun getOpenGraph(credentials: String, baseUrl: String, extractedLinkToPreview: String): Reference? suspend fun unbindRoom(credentials: String, baseUrl: String, roomToken: String): GenericOverall + suspend fun getUserGroups(user: User): Set + suspend fun getUserCircles(user: User): Set } diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt index bf36274f634..85aa4763725 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt @@ -222,4 +222,25 @@ class RetrofitChatNetwork( val url = ApiUtils.getUrlForUnbindingRoom(baseUrl, roomToken) return ncApiCoroutines.unbindRoom(credentials, url) } + + override suspend fun getUserGroups(user: User): Set { + val credentials: String = ApiUtils.getCredentials(user.username, user.token)!! + val response = ncApiCoroutines.getUserGroups( + credentials, + ApiUtils.getUrlForUserGroups( + user.baseUrl!!, + user.userId!! + ) + ) + return response.ocs?.data?.groups?.toSet() ?: emptySet() + } + + override suspend fun getUserCircles(user: User): Set { + val credentials: String = ApiUtils.getCredentials(user.username, user.token)!! + val response = ncApiCoroutines.getUserCircles( + credentials, + ApiUtils.getUrlForUserCircles(user.baseUrl!!) + ) + return response.ocs?.data?.map { it.displayName!! }?.toSet() ?: emptySet() + } } diff --git a/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt b/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt index 1086d9d8e50..d5dceebb5e3 100644 --- a/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt @@ -89,6 +89,7 @@ import com.nextcloud.talk.contacts.ContactsUiState import com.nextcloud.talk.contacts.ContactsViewModel import com.nextcloud.talk.contacts.RoomUiState import com.nextcloud.talk.conversationlist.viewmodels.ConversationsListViewModel +import com.nextcloud.talk.data.database.model.UserGroupsCirclesRepository import com.nextcloud.talk.data.network.NetworkMonitor import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.ActivityConversationsBinding @@ -152,6 +153,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers import io.reactivex.subjects.BehaviorSubject +import kotlinx.coroutines.Job import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -202,6 +204,11 @@ class ConversationsListActivity : lateinit var conversationsListViewModel: ConversationsListViewModel + private var job: Job? = null + + @Inject + lateinit var userGroupsOrCirclesRepository: UserGroupsCirclesRepository + override val appBarLayoutType: AppBarLayoutType get() = AppBarLayoutType.SEARCH_BAR @@ -271,6 +278,11 @@ class ConversationsListActivity : forwardMessage = intent.getBooleanExtra(KEY_FORWARD_MSG_FLAG, false) onBackPressedDispatcher.addCallback(this, onBackPressedCallback) + job = lifecycleScope.launch { + val initialized = userGroupsOrCirclesRepository.initialize() + Log.d(TAG, "user groups and circles - $initialized ") + } + initObservers() } @@ -1355,6 +1367,7 @@ class ConversationsListActivity : if (searchViewDisposable != null && !searchViewDisposable!!.isDisposed) { searchViewDisposable!!.dispose() } + job?.cancel() } private fun onQueryTextChange(newText: String?) { diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/DaosModule.kt b/app/src/main/java/com/nextcloud/talk/dagger/modules/DaosModule.kt index 27ec540a07b..3403bcfed3f 100644 --- a/app/src/main/java/com/nextcloud/talk/dagger/modules/DaosModule.kt +++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/DaosModule.kt @@ -10,6 +10,7 @@ package com.nextcloud.talk.dagger.modules import com.nextcloud.talk.data.database.dao.ChatBlocksDao import com.nextcloud.talk.data.database.dao.ChatMessagesDao import com.nextcloud.talk.data.database.dao.ConversationsDao +import com.nextcloud.talk.data.database.dao.UserCirclesOrGroupsDao import com.nextcloud.talk.data.source.local.TalkDatabase import dagger.Module import dagger.Provides @@ -24,4 +25,7 @@ internal object DaosModule { @Provides fun providesChatBlocksDao(database: TalkDatabase): ChatBlocksDao = database.chatBlocksDao() + + @Provides + fun providesUserCirclesOrGroups(database: TalkDatabase): UserCirclesOrGroupsDao = database.userCirclesOrGroupsDao() } diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt b/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt index 6eb4d0aa43b..2f052817768 100644 --- a/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt +++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt @@ -28,6 +28,8 @@ import com.nextcloud.talk.conversationlist.data.network.RetrofitConversationsNet import com.nextcloud.talk.data.database.dao.ChatBlocksDao import com.nextcloud.talk.data.database.dao.ChatMessagesDao import com.nextcloud.talk.data.database.dao.ConversationsDao +import com.nextcloud.talk.data.database.dao.UserCirclesOrGroupsDao +import com.nextcloud.talk.data.database.model.UserGroupsCirclesRepository import com.nextcloud.talk.data.network.NetworkMonitor import com.nextcloud.talk.data.source.local.TalkDatabase import com.nextcloud.talk.data.storage.ArbitraryStoragesRepository @@ -186,4 +188,13 @@ class RepositoryModule { ncApiCoroutines: NcApiCoroutines, currentUserProviderNew: CurrentUserProviderNew ): ConversationCreationRepository = ConversationCreationRepositoryImpl(ncApiCoroutines, currentUserProviderNew) + + @Provides + fun provideUserGroupsCirclesRepository( + userCirclesOrGroupsDao: UserCirclesOrGroupsDao, + ncApiCoroutines: NcApiCoroutines, + currentUserProvider: CurrentUserProviderNew + ): UserGroupsCirclesRepository { + return UserGroupsCirclesRepository(userCirclesOrGroupsDao, ncApiCoroutines, currentUserProvider) + } } diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/UtilsModule.kt b/app/src/main/java/com/nextcloud/talk/dagger/modules/UtilsModule.kt index 05eb3b29fe2..4bd15fc8381 100644 --- a/app/src/main/java/com/nextcloud/talk/dagger/modules/UtilsModule.kt +++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/UtilsModule.kt @@ -9,7 +9,6 @@ package com.nextcloud.talk.dagger.modules import android.content.Context import com.nextcloud.talk.utils.DateUtils -import com.nextcloud.talk.utils.message.MessageUtils import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil import com.nextcloud.talk.utils.permissions.PlatformPermissionUtilImpl import dagger.Module @@ -29,10 +28,4 @@ class UtilsModule { fun provideDateUtils(context: Context): DateUtils { return DateUtils(context) } - - @Provides - @Reusable - fun provideMessageUtils(context: Context): MessageUtils { - return MessageUtils(context) - } } diff --git a/app/src/main/java/com/nextcloud/talk/data/database/dao/UserCirclesOrGroupsDao.kt b/app/src/main/java/com/nextcloud/talk/data/database/dao/UserCirclesOrGroupsDao.kt new file mode 100644 index 00000000000..0bcdc80e6ad --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/data/database/dao/UserCirclesOrGroupsDao.kt @@ -0,0 +1,38 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2025 Sowjanya Kota + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.nextcloud.talk.data.database.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import com.nextcloud.talk.data.database.model.UserCirclesEntity +import com.nextcloud.talk.data.database.model.UserGroupsEntity +import kotlinx.coroutines.flow.Flow + +@Dao +interface UserCirclesOrGroupsDao { + + @Query("SELECT * FROM user_groups") + fun getUserGroups(): Flow> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertUserGroups(groups: List) + + @Query("SELECT * FROM user_circles") + fun getUserCircles(): Flow> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertUserCircles(circles: List) + + @Query("DELETE FROM user_groups") + suspend fun deleteAllUserGroups() + + @Query("DELETE FROM user_circles") + suspend fun deleteAllUserCircles() +} diff --git a/app/src/main/java/com/nextcloud/talk/data/database/model/UserCirclesEntity.kt b/app/src/main/java/com/nextcloud/talk/data/database/model/UserCirclesEntity.kt new file mode 100644 index 00000000000..7d2ae4d8951 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/data/database/model/UserCirclesEntity.kt @@ -0,0 +1,22 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2025 Sowjanya Kota + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.nextcloud.talk.data.database.model + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import javax.annotation.Nonnull + +@Entity(tableName = "user_circles") +data class UserCirclesEntity( + @Nonnull + @PrimaryKey(autoGenerate = true) + @ColumnInfo(name = "id") var id: Int = 0, + @ColumnInfo(name = "displayName") + var displayName: String? +) diff --git a/app/src/main/java/com/nextcloud/talk/data/database/model/UserGroupsCirclesRepository.kt b/app/src/main/java/com/nextcloud/talk/data/database/model/UserGroupsCirclesRepository.kt new file mode 100644 index 00000000000..95fa31c2980 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/data/database/model/UserGroupsCirclesRepository.kt @@ -0,0 +1,77 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2025 Sowjanya Kota + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.nextcloud.talk.data.database.model + +import android.util.Log +import com.nextcloud.talk.api.NcApiCoroutines +import com.nextcloud.talk.data.database.dao.UserCirclesOrGroupsDao +import com.nextcloud.talk.utils.ApiUtils +import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import javax.inject.Inject + +class UserGroupsCirclesRepository @Inject constructor( + private val userCirclesOrGroupsDao: UserCirclesOrGroupsDao, + private val ncApiCoroutines: NcApiCoroutines, + private val currentUserProvider: CurrentUserProviderNew +) { + + @Suppress("Detekt.TooGenericExceptionCaught") + suspend fun initialize(): Boolean = + withContext(Dispatchers.IO) { + try { + val user = currentUserProvider.currentUser.blockingGet() + val credentials: String = ApiUtils.getCredentials(user.username, user.token)!! + + coroutineScope { + launch { + userCirclesOrGroupsDao.deleteAllUserGroups() + val response = ncApiCoroutines.getUserGroups( + credentials, + ApiUtils.getUrlForUserGroups( + user.baseUrl!!, + user.userId!! + ) + ) + val groups = response.ocs?.data?.groups ?: emptyList() + Log.d("UserDataRepo", "$groups") + userCirclesOrGroupsDao.insertUserGroups( + groups.map { + UserGroupsEntity(id = 0, it) + } + ) + } + + launch { + userCirclesOrGroupsDao.deleteAllUserCircles() + val response = ncApiCoroutines.getUserCircles( + credentials, + ApiUtils.getUrlForUserCircles(user.baseUrl!!) + ) + val circles = response.ocs?.data?.map { it.displayName!! } ?: emptyList() + Log.d("UserDataRepo", "$circles") + userCirclesOrGroupsDao.insertUserCircles( + circles.map { + UserCirclesEntity(id = 0, it) + } + ) + } + } + return@withContext true + } catch (e: Exception) { + Log.e("UserDataRepo", "Error initializing user data", e) + return@withContext false + } + } + fun getUserGroups(): Flow> = userCirclesOrGroupsDao.getUserGroups() + fun getUserCircles(): Flow> = userCirclesOrGroupsDao.getUserCircles() +} diff --git a/app/src/main/java/com/nextcloud/talk/data/database/model/UserGroupsEntity.kt b/app/src/main/java/com/nextcloud/talk/data/database/model/UserGroupsEntity.kt new file mode 100644 index 00000000000..c781fab2ea1 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/data/database/model/UserGroupsEntity.kt @@ -0,0 +1,21 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2025 Sowjanya Kota + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.nextcloud.talk.data.database.model + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import javax.annotation.Nonnull + +@Entity(tableName = "user_groups") +data class UserGroupsEntity( + @Nonnull + @PrimaryKey(autoGenerate = true) + @ColumnInfo(name = "id") var id: Int = 0, + @ColumnInfo(name = "groups") var groups: String? +) diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt index 1108fa394bb..05f8c467a14 100644 --- a/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt +++ b/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt @@ -20,9 +20,12 @@ import com.nextcloud.talk.R import com.nextcloud.talk.data.database.dao.ChatBlocksDao import com.nextcloud.talk.data.database.dao.ChatMessagesDao import com.nextcloud.talk.data.database.dao.ConversationsDao +import com.nextcloud.talk.data.database.dao.UserCirclesOrGroupsDao import com.nextcloud.talk.data.database.model.ChatBlockEntity import com.nextcloud.talk.data.database.model.ChatMessageEntity import com.nextcloud.talk.data.database.model.ConversationEntity +import com.nextcloud.talk.data.database.model.UserCirclesEntity +import com.nextcloud.talk.data.database.model.UserGroupsEntity import com.nextcloud.talk.data.source.local.converters.ArrayListConverter import com.nextcloud.talk.data.source.local.converters.CapabilitiesConverter import com.nextcloud.talk.data.source.local.converters.ExternalSignalingServerConverter @@ -47,7 +50,9 @@ import java.util.Locale ArbitraryStorageEntity::class, ConversationEntity::class, ChatMessageEntity::class, - ChatBlockEntity::class + ChatBlockEntity::class, + UserCirclesEntity::class, + UserGroupsEntity::class ], version = 16, autoMigrations = [ @@ -72,6 +77,7 @@ abstract class TalkDatabase : RoomDatabase() { abstract fun chatMessagesDao(): ChatMessagesDao abstract fun chatBlocksDao(): ChatBlocksDao abstract fun arbitraryStoragesDao(): ArbitraryStoragesDao + abstract fun userCirclesOrGroupsDao(): UserCirclesOrGroupsDao companion object { const val TAG = "TalkDatabase" diff --git a/app/src/main/java/com/nextcloud/talk/models/json/usercircles/UserCircles.kt b/app/src/main/java/com/nextcloud/talk/models/json/usercircles/UserCircles.kt new file mode 100644 index 00000000000..260426a240a --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/usercircles/UserCircles.kt @@ -0,0 +1,44 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2025 Sowjanya Kota + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.nextcloud.talk.models.json.usercircles + +import android.os.Parcelable +import com.bluelinelabs.logansquare.annotation.JsonField +import com.bluelinelabs.logansquare.annotation.JsonObject +import kotlinx.parcelize.Parcelize + +@Parcelize +@JsonObject +data class UserCircles( + @JsonField(name = ["id"]) + var id: String? = null, + @JsonField(name = ["name"]) + var name: String? = null, + @JsonField(name = ["displayName"]) + var displayName: String? = null, + @JsonField(name = ["sanitizedName"]) + var sanitizedName: String? = null, + @JsonField(name = ["source"]) + var source: Int = 0, + @JsonField(name = ["population"]) + var population: Int = 0, + @JsonField(name = ["config"]) + var config: Int = 0, + @JsonField(name = ["description"]) + var description: String? = null, + @JsonField(name = ["url"]) + var url: String? = null, + @JsonField(name = ["creation"]) + var creation: Int = 0, + @JsonField(name = ["initiator"]) + var initiator: String? = null + +) : Parcelable { + // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' + constructor() : this(null, null, null, null, 0, 0, 0, null, null, 0, null) +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/usercircles/UserCirclesOCS.kt b/app/src/main/java/com/nextcloud/talk/models/json/usercircles/UserCirclesOCS.kt new file mode 100644 index 00000000000..8e5fb9137d2 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/usercircles/UserCirclesOCS.kt @@ -0,0 +1,26 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2025 Sowjanya Kota + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.nextcloud.talk.models.json.usercircles + +import android.os.Parcelable +import com.bluelinelabs.logansquare.annotation.JsonField +import com.bluelinelabs.logansquare.annotation.JsonObject +import com.nextcloud.talk.models.json.generic.GenericMeta +import kotlinx.parcelize.Parcelize + +@Parcelize +@JsonObject +data class UserCirclesOCS( + @JsonField(name = ["meta"]) + var meta: GenericMeta?, + @JsonField(name = ["data"]) + var data: List? +) : Parcelable { + // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' + constructor() : this(null, null) +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/usercircles/UserCirclesOverall.kt b/app/src/main/java/com/nextcloud/talk/models/json/usercircles/UserCirclesOverall.kt new file mode 100644 index 00000000000..e2db21c5bb3 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/usercircles/UserCirclesOverall.kt @@ -0,0 +1,23 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2025 Sowjanya Kota + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.nextcloud.talk.models.json.usercircles + +import android.os.Parcelable +import com.bluelinelabs.logansquare.annotation.JsonField +import com.bluelinelabs.logansquare.annotation.JsonObject +import kotlinx.parcelize.Parcelize + +@Parcelize +@JsonObject +data class UserCirclesOverall( + @JsonField(name = ["ocs"]) + var ocs: UserCirclesOCS? +) : Parcelable { + // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' + constructor() : this(null) +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/usergroups/UserGroupsData.kt b/app/src/main/java/com/nextcloud/talk/models/json/usergroups/UserGroupsData.kt new file mode 100644 index 00000000000..f075d09bf69 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/usergroups/UserGroupsData.kt @@ -0,0 +1,23 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2025 Sowjanya Kota + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.nextcloud.talk.models.json.usergroups + +import android.os.Parcelable +import com.bluelinelabs.logansquare.annotation.JsonField +import com.bluelinelabs.logansquare.annotation.JsonObject +import kotlinx.parcelize.Parcelize + +@Parcelize +@JsonObject +data class UserGroupsData( + @JsonField(name = ["groups"]) + var groups: List? +) : Parcelable { + // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' + constructor() : this(null) +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/usergroups/UserGroupsOCS.kt b/app/src/main/java/com/nextcloud/talk/models/json/usergroups/UserGroupsOCS.kt new file mode 100644 index 00000000000..28f1999e678 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/usergroups/UserGroupsOCS.kt @@ -0,0 +1,26 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2025 Sowjanya Kota + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.nextcloud.talk.models.json.usergroups + +import android.os.Parcelable +import com.bluelinelabs.logansquare.annotation.JsonField +import com.bluelinelabs.logansquare.annotation.JsonObject +import com.nextcloud.talk.models.json.generic.GenericMeta +import kotlinx.parcelize.Parcelize + +@Parcelize +@JsonObject +data class UserGroupsOCS( + @JsonField(name = ["meta"]) + var meta: GenericMeta?, + @JsonField(name = ["data"]) + var data: UserGroupsData? +) : Parcelable { + // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' + constructor() : this(null, null) +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/usergroups/UserGroupsOverall.kt b/app/src/main/java/com/nextcloud/talk/models/json/usergroups/UserGroupsOverall.kt new file mode 100644 index 00000000000..23b7ec6b7b1 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/usergroups/UserGroupsOverall.kt @@ -0,0 +1,23 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2025 Sowjanya Kota + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package com.nextcloud.talk.models.json.usergroups + +import android.os.Parcelable +import com.bluelinelabs.logansquare.annotation.JsonField +import com.bluelinelabs.logansquare.annotation.JsonObject +import kotlinx.parcelize.Parcelize + +@Parcelize +@JsonObject +data class UserGroupsOverall( + @JsonField(name = ["ocs"]) + var ocs: UserGroupsOCS? +) : Parcelable { + // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' + constructor() : this(null) +} diff --git a/app/src/main/java/com/nextcloud/talk/ui/ComposeChatAdapter.kt b/app/src/main/java/com/nextcloud/talk/ui/ComposeChatAdapter.kt index e473f0684c8..cb626e0386d 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/ComposeChatAdapter.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/ComposeChatAdapter.kt @@ -621,7 +621,7 @@ class ComposeChatAdapter( ) processedMessageText = viewModel.messageUtils.processMessageParameters( - ctx, viewModel.viewThemeUtils, processedMessageText!!, message, null + ctx, viewModel.viewThemeUtils, processedMessageText!!, message, null, null, null ) EmojiTextView(ctx).apply { diff --git a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.kt index fe258be3c8e..e8e34c4f3c2 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.kt @@ -363,6 +363,14 @@ object ApiUtils { return "$baseUrl$OCS_API_VERSION/cloud/users/$userId" } + fun getUrlForUserGroups(baseUrl: String, userId: String): String { + return "$baseUrl$OCS_API_VERSION/cloud/users/$userId/groups" + } + + fun getUrlForUserCircles(baseUrl: String): String { + return "$baseUrl$OCS_API_VERSION/apps/circles/probecircles" + } + fun getUrlForUserSettings(baseUrl: String): String { // FIXME Introduce API version return "$baseUrl$OCS_API_VERSION$SPREED_API_VERSION/settings/user" diff --git a/app/src/main/java/com/nextcloud/talk/utils/message/MessageUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/message/MessageUtils.kt index 985a0aa1ea9..1d08bbb9f99 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/message/MessageUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/message/MessageUtils.kt @@ -28,8 +28,10 @@ import io.noties.markwon.ext.strikethrough.StrikethroughPlugin import io.noties.markwon.ext.tables.TablePlugin import io.noties.markwon.ext.tasklist.TaskListDrawable import io.noties.markwon.ext.tasklist.TaskListPlugin +import javax.inject.Inject + +class MessageUtils @Inject constructor(val context: Context) { -class MessageUtils(val context: Context) { fun enrichChatReplyMessageText( context: Context, message: ChatMessage, @@ -75,12 +77,15 @@ class MessageUtils(val context: Context) { return viewThemeUtils.talk.themeMarkdown(context, message, incoming) } + @Suppress("LongParameterList") fun processMessageParameters( themingContext: Context, viewThemeUtils: ViewThemeUtils, spannedText: Spanned, message: ChatMessage, - itemView: View? + itemView: View?, + userGroups: List?, + userCircles: List? ): Spanned { var processedMessageText = spannedText val messageParameters = message.messageParameters @@ -91,20 +96,25 @@ class MessageUtils(val context: Context) { messageParameters, message, processedMessageText, - itemView + itemView, + userGroups ?: emptyList(), + userCircles ?: emptyList() + ) } return processedMessageText } - @Suppress("NestedBlockDepth", "LongParameterList") + @Suppress("NestedBlockDepth", "LongParameterList", "Detekt.LongMethod") private fun processMessageParameters( themingContext: Context, viewThemeUtils: ViewThemeUtils, messageParameters: HashMap>, message: ChatMessage, messageString: Spanned, - itemView: View? + itemView: View?, + userGroups: List, + userCircles: List ): Spanned { var messageStringInternal = messageString for (key in messageParameters.keys) { @@ -112,7 +122,11 @@ class MessageUtils(val context: Context) { if (individualHashMap != null) { when (individualHashMap["type"]) { "user", "guest", "call", "user-group", "email", "circle" -> { - val chip = if (individualHashMap["id"]?.equals(message.activeUser?.userId) == true) { + val chip = if (individualHashMap["id"] == message.activeUser!!.userId || + userGroups.any { it == individualHashMap["name"] } || + userCircles.any { it == individualHashMap["name"] } || + individualHashMap["type"] == "call" + ) { R.xml.chip_you } else { R.xml.chip_others