diff --git a/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt b/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt index ae04f09026..d73efeb224 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt @@ -128,7 +128,7 @@ class MessageInputFragment : Fragment() { private var mentionAutocomplete: Autocomplete<*>? = null private var xcounter = 0f private var ycounter = 0f - private var isCollapsed = false + private var collapsed = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -219,11 +219,7 @@ class MessageInputFragment : Fragment() { binding.fragmentCallStarted.callAuthorChipSecondary.text = message.actorDisplayName val user = userManager.currentUser.blockingGet() val url: String = if (message.actorType == "guests" || message.actorType == "guest") { - ApiUtils.getUrlForGuestAvatar( - user!!.baseUrl!!, - message.actorDisplayName, - true - ) + ApiUtils.getUrlForGuestAvatar(user!!.baseUrl!!, message.actorDisplayName, true) } else { ApiUtils.getUrlForAvatar(user!!.baseUrl!!, message.actorId, false) } @@ -457,20 +453,12 @@ class MessageInputFragment : Fragment() { } binding.fragmentCallStarted.callStartedCloseBtn.setOnClickListener { - isCollapsed = !isCollapsed - if (isCollapsed) { - binding.fragmentCallStarted.callAuthorLayout.visibility = View.GONE - binding.fragmentCallStarted.callBtnLayout.visibility = View.GONE - binding.fragmentCallStarted.callAuthorChipSecondary.visibility = View.VISIBLE - binding.fragmentCallStarted.callStartedSecondaryText.visibility = View.VISIBLE - } else { - binding.fragmentCallStarted.callAuthorLayout.visibility = View.VISIBLE - binding.fragmentCallStarted.callBtnLayout.visibility = View.VISIBLE - binding.fragmentCallStarted.callAuthorChipSecondary.visibility = View.GONE - binding.fragmentCallStarted.callStartedSecondaryText.visibility = View.GONE - } - - setDropDown(isCollapsed) + collapsed = !collapsed + binding.fragmentCallStarted.callAuthorLayout.visibility = if (collapsed) View.GONE else View.VISIBLE + binding.fragmentCallStarted.callBtnLayout.visibility = if (collapsed) View.GONE else View.VISIBLE + binding.fragmentCallStarted.callAuthorChipSecondary.visibility = if (collapsed) View.VISIBLE else View.GONE + binding.fragmentCallStarted.callStartedSecondaryText.visibility = if (collapsed) View.VISIBLE else View.GONE + setDropDown(collapsed) } } diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt index bf3a9f7fab..05008a7138 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt @@ -506,55 +506,67 @@ class OfflineFirstChatRepository @Inject constructor( } if (result.second.isNotEmpty()) { - val chatMessagesJson = result.second + chatMessagesFromSync = updateMessagesData( + result.second, + blockContainingQueriedMessage, + lookIntoFuture, + hasHistory) + } else { + Log.d(TAG, "no data is updated...") + } - handleUpdateMessages(chatMessagesJson) + return chatMessagesFromSync + } - chatMessagesFromSync = chatMessagesJson.map { - it.asEntity(currentUser.id!!) - } + private suspend fun OfflineFirstChatRepository.updateMessagesData( + chatMessagesJson: List, + blockContainingQueriedMessage: ChatBlockEntity?, + lookIntoFuture: Boolean, + hasHistory: Boolean + ): List { + handleUpdateMessages(chatMessagesJson) - chatDao.upsertChatMessages(chatMessagesFromSync) + val chatMessagesFromSyncToProcess = chatMessagesJson.map { + it.asEntity(currentUser.id!!) + } - val oldestIdFromSync = chatMessagesFromSync.minByOrNull { it.id }!!.id - val newestIdFromSync = chatMessagesFromSync.maxByOrNull { it.id }!!.id - Log.d(TAG, "oldestIdFromSync: $oldestIdFromSync") - Log.d(TAG, "newestIdFromSync: $newestIdFromSync") + chatDao.upsertChatMessages(chatMessagesFromSyncToProcess) - var oldestMessageIdForNewChatBlock = oldestIdFromSync - var newestMessageIdForNewChatBlock = newestIdFromSync + val oldestIdFromSync = chatMessagesFromSyncToProcess.minByOrNull { it.id }!!.id + val newestIdFromSync = chatMessagesFromSyncToProcess.maxByOrNull { it.id }!!.id + Log.d(TAG, "oldestIdFromSync: $oldestIdFromSync") + Log.d(TAG, "newestIdFromSync: $newestIdFromSync") - if (blockContainingQueriedMessage != null) { - if (lookIntoFuture) { - val oldestMessageIdFromBlockOfQueriedMessage = blockContainingQueriedMessage.oldestMessageId - Log.d(TAG, "oldestMessageIdFromBlockOfQueriedMessage: $oldestMessageIdFromBlockOfQueriedMessage") - oldestMessageIdForNewChatBlock = oldestMessageIdFromBlockOfQueriedMessage - } else { - val newestMessageIdFromBlockOfQueriedMessage = blockContainingQueriedMessage.newestMessageId - Log.d(TAG, "newestMessageIdFromBlockOfQueriedMessage: $newestMessageIdFromBlockOfQueriedMessage") - newestMessageIdForNewChatBlock = newestMessageIdFromBlockOfQueriedMessage - } - } + var oldestMessageIdForNewChatBlock = oldestIdFromSync + var newestMessageIdForNewChatBlock = newestIdFromSync - Log.d(TAG, "oldestMessageIdForNewChatBlock: $oldestMessageIdForNewChatBlock") - Log.d(TAG, "newestMessageIdForNewChatBlock: $newestMessageIdForNewChatBlock") + if (blockContainingQueriedMessage != null) { + if (lookIntoFuture) { + val oldestMessageIdFromBlockOfQueriedMessage = blockContainingQueriedMessage.oldestMessageId + Log.d(TAG, "oldestMessageIdFromBlockOfQueriedMessage: $oldestMessageIdFromBlockOfQueriedMessage") + oldestMessageIdForNewChatBlock = oldestMessageIdFromBlockOfQueriedMessage + } else { + val newestMessageIdFromBlockOfQueriedMessage = blockContainingQueriedMessage.newestMessageId + Log.d(TAG, "newestMessageIdFromBlockOfQueriedMessage: $newestMessageIdFromBlockOfQueriedMessage") + newestMessageIdForNewChatBlock = newestMessageIdFromBlockOfQueriedMessage + } + } - val newChatBlock = ChatBlockEntity( - internalConversationId = internalConversationId, - accountId = conversationModel.accountId, - token = conversationModel.token, - oldestMessageId = oldestMessageIdForNewChatBlock, - newestMessageId = newestMessageIdForNewChatBlock, - hasHistory = hasHistory - ) - chatBlocksDao.upsertChatBlock(newChatBlock) + Log.d(TAG, "oldestMessageIdForNewChatBlock: $oldestMessageIdForNewChatBlock") + Log.d(TAG, "newestMessageIdForNewChatBlock: $newestMessageIdForNewChatBlock") - updateBlocks(newChatBlock) - } else { - Log.d(TAG, "no data is updated...") - } + val newChatBlock = ChatBlockEntity( + internalConversationId = internalConversationId, + accountId = conversationModel.accountId, + token = conversationModel.token, + oldestMessageId = oldestMessageIdForNewChatBlock, + newestMessageId = newestMessageIdForNewChatBlock, + hasHistory = hasHistory + ) + chatBlocksDao.upsertChatBlock(newChatBlock) - return chatMessagesFromSync + updateBlocks(newChatBlock) + return chatMessagesFromSyncToProcess } private suspend fun handleUpdateMessages(messagesJson: List) { diff --git a/app/src/main/java/com/nextcloud/talk/conversationinfo/ConversationInfoActivity.kt b/app/src/main/java/com/nextcloud/talk/conversationinfo/ConversationInfoActivity.kt index d6f72f3cb1..1bac369566 100644 --- a/app/src/main/java/com/nextcloud/talk/conversationinfo/ConversationInfoActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/conversationinfo/ConversationInfoActivity.kt @@ -21,6 +21,7 @@ import android.view.MenuItem import android.view.View import android.view.View.GONE import android.view.View.VISIBLE +import androidx.annotation.DrawableRes import androidx.appcompat.app.AlertDialog import androidx.core.content.res.ResourcesCompat import androidx.fragment.app.FragmentTransaction @@ -204,33 +205,7 @@ class ConversationInfoActivity : } private fun initObservers() { - viewModel.viewState.observe(this) { state -> - when (state) { - is ConversationInfoViewModel.GetRoomSuccessState -> { - conversation = state.conversationModel - viewModel.getCapabilities(conversationUser, conversationToken, conversation!!) - if (ConversationUtils.isNoteToSelfConversation(conversation)) { - binding.shareConversationButton.visibility = GONE - } - val canGeneratePrettyURL = CapabilitiesUtil.canGeneratePrettyURL(conversationUser) - binding.shareConversationButton.setOnClickListener { - ShareUtils.shareConversationLink( - this, - conversationUser.baseUrl, - conversation?.token, - conversation?.name, - canGeneratePrettyURL - ) - } - } - - is ConversationInfoViewModel.GetRoomErrorState -> { - Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show() - } - - else -> {} - } - } + initViewStateObserver() viewModel.getCapabilitiesViewState.observe(this) { state -> when (state) { @@ -289,6 +264,36 @@ class ConversationInfoActivity : } } + private fun initViewStateObserver() { + viewModel.viewState.observe(this) { state -> + when (state) { + is ConversationInfoViewModel.GetRoomSuccessState -> { + conversation = state.conversationModel + viewModel.getCapabilities(conversationUser, conversationToken, conversation!!) + if (ConversationUtils.isNoteToSelfConversation(conversation)) { + binding.shareConversationButton.visibility = GONE + } + val canGeneratePrettyURL = CapabilitiesUtil.canGeneratePrettyURL(conversationUser) + binding.shareConversationButton.setOnClickListener { + ShareUtils.shareConversationLink( + this, + conversationUser.baseUrl, + conversation?.token, + conversation?.name, + canGeneratePrettyURL + ) + } + } + + is ConversationInfoViewModel.GetRoomErrorState -> { + Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show() + } + + else -> {} + } + } + } + private fun setupActionBar() { setSupportActionBar(binding.conversationInfoToolbar) binding.conversationInfoToolbar.setNavigationOnClickListener { @@ -1330,117 +1335,57 @@ class ConversationInfoActivity : val userItem = adapter?.getItem(position) as ParticipantItem val participant = userItem.model - val apiVersion = ApiUtils.getConversationApiVersion(conversationUser, intArrayOf(ApiUtils.API_V4, 1)) if (participant.calculatedActorType == USERS && participant.calculatedActorId == conversationUser.userId) { if (participant.attendeePin?.isNotEmpty() == true) { - val items = mutableListOf( - BasicListItemWithImage( - R.drawable.ic_lock_grey600_24px, - context.getString(R.string.nc_attendee_pin, participant.attendeePin) - ) + launchRemoveAttendeeFromConversationDialog( + participant, + apiVersion, + context.getString(R.string.nc_attendee_pin, participant.attendeePin), + R.drawable.ic_lock_grey600_24px ) - MaterialDialog(this, BottomSheet(WRAP_CONTENT)).show { - cornerRadius(res = R.dimen.corner_radius) - - title(text = participant.displayName) - viewThemeUtils.material.colorBottomSheetBackground(this.view) - listItemsWithImage(items = items) { _, index, _ -> - if (index == 0) { - removeAttendeeFromConversation(apiVersion, participant) - } - } - } } - return true - } - - if (participant.type == Participant.ParticipantType.OWNER) { + } else if (participant.type == Participant.ParticipantType.OWNER) { // Can not moderate owner - return true - } - - if (participant.calculatedActorType == GROUPS) { - val items = mutableListOf( - BasicListItemWithImage( - R.drawable.ic_delete_grey600_24dp, - context.getString(R.string.nc_remove_group_and_members) - ) + } else if (participant.calculatedActorType == GROUPS) { + launchRemoveAttendeeFromConversationDialog( + participant, + apiVersion, + context.getString(R.string.nc_remove_group_and_members) ) - MaterialDialog(this, BottomSheet(WRAP_CONTENT)).show { - cornerRadius(res = R.dimen.corner_radius) - - title(text = participant.displayName) - listItemsWithImage(items = items) { _, index, _ -> - if (index == 0) { - removeAttendeeFromConversation(apiVersion, participant) - } - } - } - return true - } - - if (participant.calculatedActorType == CIRCLES) { - val items = mutableListOf( - BasicListItemWithImage( - R.drawable.ic_delete_grey600_24dp, - context.getString(R.string.nc_remove_team_and_members) - ) + } else if (participant.calculatedActorType == CIRCLES) { + launchRemoveAttendeeFromConversationDialog( + participant, + apiVersion, + context.getString(R.string.nc_remove_team_and_members) ) - MaterialDialog(this, BottomSheet(WRAP_CONTENT)).show { - cornerRadius(res = R.dimen.corner_radius) - - title(text = participant.displayName) - listItemsWithImage(items = items) { _, index, _ -> - if (index == 0) { - removeAttendeeFromConversation(apiVersion, participant) - } - } - } - return true + } else { + launchDefaultActions(participant, apiVersion) } + return true + } - val items = mutableListOf( - BasicListItemWithImage( - R.drawable.ic_lock_grey600_24px, - context.getString(R.string.nc_attendee_pin, participant.attendeePin) - ), - BasicListItemWithImage( - R.drawable.ic_pencil_grey600_24dp, - context.getString(R.string.nc_promote) - ), - BasicListItemWithImage( - R.drawable.ic_pencil_grey600_24dp, - context.getString(R.string.nc_demote) - ), - BasicListItemWithImage( - R.drawable.ic_delete_grey600_24dp, - context.getString(R.string.nc_remove_participant) - ) - ) + @SuppressLint("CheckResult", "StringFormatInvalid") + private fun launchDefaultActions(participant: Participant, apiVersion: Int) { + val items = getDefaultActionItems(participant) if (CapabilitiesUtil.isBanningAvailable(conversationUser.capabilities?.spreedCapability!!)) { - items.add( - BasicListItemWithImage( - R.drawable.baseline_block_24, - context.getString(R.string.ban_participant) - ) - ) + items.add(BasicListItemWithImage(R.drawable.baseline_block_24, context.getString(R.string.ban_participant))) } - if (participant.type == Participant.ParticipantType.MODERATOR || - participant.type == Participant.ParticipantType.GUEST_MODERATOR - ) { - items.removeAt(1) - } else if (participant.type == Participant.ParticipantType.USER || - participant.type == Participant.ParticipantType.GUEST - ) { - items.removeAt(2) - } else { - // Self joined users can not be promoted nor demoted - items.removeAt(2) - items.removeAt(1) + when (participant.type) { + Participant.ParticipantType.MODERATOR, Participant.ParticipantType.GUEST_MODERATOR -> { + items.removeAt(1) + } + Participant.ParticipantType.USER, Participant.ParticipantType.GUEST -> { + items.removeAt(2) + } + else -> { + // Self joined users can not be promoted nor demoted + items.removeAt(2) + items.removeAt(1) + } } if (participant.attendeePin == null || participant.attendeePin!!.isEmpty()) { @@ -1483,7 +1428,53 @@ class ConversationInfoActivity : } } } - return true + } + + private fun getDefaultActionItems(participant: Participant): MutableList { + val items = mutableListOf( + BasicListItemWithImage( + R.drawable.ic_lock_grey600_24px, + context.getString(R.string.nc_attendee_pin, participant.attendeePin) + ), + BasicListItemWithImage( + R.drawable.ic_pencil_grey600_24dp, + context.getString(R.string.nc_promote) + ), + BasicListItemWithImage( + R.drawable.ic_pencil_grey600_24dp, + context.getString(R.string.nc_demote) + ), + BasicListItemWithImage( + R.drawable.ic_delete_grey600_24dp, + context.getString(R.string.nc_remove_participant) + ) + ) + return items + } + + @SuppressLint("CheckResult") + private fun launchRemoveAttendeeFromConversationDialog( + participant: Participant, + apiVersion: Int, + itemText: String, + @DrawableRes itemIcon: Int = R.drawable.ic_delete_grey600_24dp, + ) { + val items = mutableListOf( + BasicListItemWithImage( + itemIcon, + itemText + ) + ) + MaterialDialog(this, BottomSheet(WRAP_CONTENT)).show { + cornerRadius(res = R.dimen.corner_radius) + + title(text = participant.displayName) + listItemsWithImage(items = items) { _, index, _ -> + if (index == 0) { + removeAttendeeFromConversation(apiVersion, participant) + } + } + } } private fun MaterialDialog.handleBan(participant: Participant) { diff --git a/app/src/main/java/com/nextcloud/talk/conversationinfoedit/ConversationInfoEditActivity.kt b/app/src/main/java/com/nextcloud/talk/conversationinfoedit/ConversationInfoEditActivity.kt index 43d7cb2b2b..ab72e226c9 100644 --- a/app/src/main/java/com/nextcloud/talk/conversationinfoedit/ConversationInfoEditActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/conversationinfoedit/ConversationInfoEditActivity.kt @@ -122,6 +122,45 @@ class ConversationInfoEditActivity : BaseActivity() { } private fun initObservers() { + initViewStateObserver() + conversationInfoEditViewModel.renameRoomUiState.observe(this) { uiState -> + when (uiState) { + is ConversationInfoEditViewModel.RenameRoomUiState.None -> { + } + is ConversationInfoEditViewModel.RenameRoomUiState.Success -> { + if (CapabilitiesUtil.isConversationDescriptionEndpointAvailable(spreedCapabilities)) { + saveConversationDescription() + } else { + finish() + } + } + is ConversationInfoEditViewModel.RenameRoomUiState.Error -> { + Snackbar + .make(binding.root, context.getString(R.string.default_error_msg), Snackbar.LENGTH_LONG) + .show() + Log.e(TAG, "Error while saving conversation name", uiState.exception) + } + } + } + + conversationInfoEditViewModel.setConversationDescriptionUiState.observe(this) { uiState -> + when (uiState) { + is ConversationInfoEditViewModel.SetConversationDescriptionUiState.None -> { + } + is ConversationInfoEditViewModel.SetConversationDescriptionUiState.Success -> { + finish() + } + is ConversationInfoEditViewModel.SetConversationDescriptionUiState.Error -> { + Snackbar + .make(binding.root, context.getString(R.string.default_error_msg), Snackbar.LENGTH_LONG) + .show() + Log.e(TAG, "Error while saving conversation description", uiState.exception) + } + } + } + } + + private fun initViewStateObserver() { conversationInfoEditViewModel.viewState.observe(this) { state -> when (state) { is ConversationInfoEditViewModel.GetRoomSuccessState -> { @@ -131,7 +170,7 @@ class ConversationInfoEditActivity : BaseActivity() { binding.conversationName.setText(conversation!!.displayName) - if (conversation!!.description != null && conversation!!.description!!.isNotEmpty()) { + if (conversation!!.description.isNotEmpty()) { binding.conversationDescription.setText(conversation!!.description) } @@ -167,45 +206,6 @@ class ConversationInfoEditActivity : BaseActivity() { else -> {} } } - conversationInfoEditViewModel.renameRoomUiState.observe(this) { uiState -> - when (uiState) { - is ConversationInfoEditViewModel.RenameRoomUiState.None -> { - } - is ConversationInfoEditViewModel.RenameRoomUiState.Success -> { - if (CapabilitiesUtil.isConversationDescriptionEndpointAvailable(spreedCapabilities)) { - saveConversationDescription() - } else { - finish() - } - } - is ConversationInfoEditViewModel.RenameRoomUiState.Error -> { - Snackbar.make( - binding.root, - context.getString(R.string.default_error_msg), - Snackbar.LENGTH_LONG - ).show() - Log.e(TAG, "Error while saving conversation name", uiState.exception) - } - } - } - - conversationInfoEditViewModel.setConversationDescriptionUiState.observe(this) { uiState -> - when (uiState) { - is ConversationInfoEditViewModel.SetConversationDescriptionUiState.None -> { - } - is ConversationInfoEditViewModel.SetConversationDescriptionUiState.Success -> { - finish() - } - is ConversationInfoEditViewModel.SetConversationDescriptionUiState.Error -> { - Snackbar.make( - binding.root, - context.getString(R.string.default_error_msg), - Snackbar.LENGTH_LONG - ).show() - Log.e(TAG, "Error while saving conversation description", uiState.exception) - } - } - } } private fun setupAvatarOptions() { 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 82a1481b8e..6712bbb548 100644 --- a/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt @@ -325,18 +325,12 @@ class ConversationsListActivity : conversationsListViewModel.getFederationInvitationsViewState.observe(this) { state -> when (state) { is ConversationsListViewModel.GetFederationInvitationsStartState -> { - binding.conversationListHintInclude.conversationListHintLayout.visibility = - View.GONE + binding.conversationListHintInclude.conversationListHintLayout.visibility = View.GONE } is ConversationsListViewModel.GetFederationInvitationsSuccessState -> { - if (state.showInvitationsHint) { - binding.conversationListHintInclude.conversationListHintLayout.visibility = - View.VISIBLE - } else { - binding.conversationListHintInclude.conversationListHintLayout.visibility = - View.GONE - } + binding.conversationListHintInclude.conversationListHintLayout.visibility = + if (state.showInvitationsHint) View.VISIBLE else View.GONE } is ConversationsListViewModel.GetFederationInvitationsErrorState -> { @@ -387,31 +381,35 @@ class ConversationsListActivity : lifecycleScope.launch { conversationsListViewModel.getRoomsFlow .onEach { list -> - // Update Conversations - conversationItems.clear() - conversationItemsWithHeader.clear() - for (conversation in list) { - addToConversationItems(conversation) - } - sortConversations(conversationItems) - sortConversations(conversationItemsWithHeader) - - // Filter Conversations - if (!hasFilterEnabled()) filterableConversationItems = conversationItems - filterConversation() - adapter!!.updateDataSet(filterableConversationItems, false) - Handler().postDelayed({ checkToShowUnreadBubble() }, UNREAD_BUBBLE_DELAY.toLong()) - - // Fetch Open Conversations - val apiVersion = ApiUtils.getConversationApiVersion( - currentUser!!, - intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1) - ) - fetchOpenConversations(apiVersion) + setConversationList(list) }.collect() } } + private fun setConversationList(list: List) { + // Update Conversations + conversationItems.clear() + conversationItemsWithHeader.clear() + for (conversation in list) { + addToConversationItems(conversation) + } + sortConversations(conversationItems) + sortConversations(conversationItemsWithHeader) + + // Filter Conversations + if (!hasFilterEnabled()) filterableConversationItems = conversationItems + filterConversation() + adapter!!.updateDataSet(filterableConversationItems, false) + Handler().postDelayed({ checkToShowUnreadBubble() }, UNREAD_BUBBLE_DELAY.toLong()) + + // Fetch Open Conversations + val apiVersion = ApiUtils.getConversationApiVersion( + currentUser!!, + intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1) + ) + fetchOpenConversations(apiVersion) + } + private fun hasFilterEnabled(): Boolean { for ((k, v) in filterState) { if (k != FilterConversationFragment.DEFAULT && v) return true diff --git a/app/src/main/java/com/nextcloud/talk/jobs/ContactAddressBookWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/ContactAddressBookWorker.kt index db3bba39cf..a4f1178a52 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/ContactAddressBookWorker.kt +++ b/app/src/main/java/com/nextcloud/talk/jobs/ContactAddressBookWorker.kt @@ -292,16 +292,74 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar return hasLinkedAccount } + fun createOps( + cloudId: String, + numbers: MutableList, + displayName: String? + ): ArrayList { + val ops = ArrayList() + val rawContactsUri = ContactsContract.RawContacts.CONTENT_URI.buildUpon().build() + val dataUri = ContactsContract.Data.CONTENT_URI.buildUpon().build() + + ops.add( + ContentProviderOperation + .newInsert(rawContactsUri) + .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, accountName) + .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, accountType) + .withValue( + ContactsContract.RawContacts.AGGREGATION_MODE, + ContactsContract.RawContacts.AGGREGATION_MODE_DEFAULT + ) + .withValue(ContactsContract.RawContacts.SYNC2, cloudId) + .build() + ) + ops.add( + ContentProviderOperation + .newInsert(dataUri) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue( + ContactsContract.Data.MIMETYPE, + ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE + ) + .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, numbers[0]) + .build() + ) + ops.add( + ContentProviderOperation + .newInsert(dataUri) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue( + ContactsContract.Data.MIMETYPE, + ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE + ) + .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, displayName) + .build() + ) + ops.add( + ContentProviderOperation + .newInsert(dataUri) + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + .withValue( + ContactsContract.Data.MIMETYPE, + "vnd.android.cursor.item/vnd.com.nextcloud.talk2.chat" + ) + .withValue(ContactsContract.Data.DATA1, cloudId) + .withValue( + ContactsContract.Data.DATA2, + String.format( + context.resources.getString(R.string.nc_phone_book_integration_chat_via), + accountName + ) + ) + .build() + ) + return ops + } + fun createLinkedAccount(lookupKey: String, cloudId: String) { val lookupUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, lookupKey) val lookupContactUri = ContactsContract.Contacts.lookupContact(context.contentResolver, lookupUri) - val contactCursor = context.contentResolver.query( - lookupContactUri, - null, - null, - null, - null - ) + val contactCursor = context.contentResolver.query(lookupContactUri, null, null, null, null) if (contactCursor != null) { if (contactCursor.count > 0) { @@ -319,71 +377,14 @@ class ContactAddressBookWorker(val context: Context, workerParameters: WorkerPar return } - val ops = ArrayList() - val rawContactsUri = ContactsContract.RawContacts.CONTENT_URI.buildUpon().build() - val dataUri = ContactsContract.Data.CONTENT_URI.buildUpon().build() - - ops.add( - ContentProviderOperation - .newInsert(rawContactsUri) - .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, accountName) - .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, accountType) - .withValue( - ContactsContract.RawContacts.AGGREGATION_MODE, - ContactsContract.RawContacts.AGGREGATION_MODE_DEFAULT - ) - .withValue(ContactsContract.RawContacts.SYNC2, cloudId) - .build() - ) - ops.add( - ContentProviderOperation - .newInsert(dataUri) - .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) - .withValue( - ContactsContract.Data.MIMETYPE, - ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE - ) - .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, numbers[0]) - .build() - ) - ops.add( - ContentProviderOperation - .newInsert(dataUri) - .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) - .withValue( - ContactsContract.Data.MIMETYPE, - ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE - ) - .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, displayName) - .build() - ) - ops.add( - ContentProviderOperation - .newInsert(dataUri) - .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) - .withValue( - ContactsContract.Data.MIMETYPE, - "vnd.android.cursor.item/vnd.com.nextcloud.talk2.chat" - ) - .withValue(ContactsContract.Data.DATA1, cloudId) - .withValue( - ContactsContract.Data.DATA2, - String.format( - context.resources.getString( - R.string.nc_phone_book_integration_chat_via - ), - accountName - ) - ) - .build() - ) + val ops = createOps(cloudId, numbers, displayName) try { context.contentResolver.applyBatch(ContactsContract.AUTHORITY, ops) } catch (e: OperationApplicationException) { - Log.e(javaClass.simpleName, "", e) + Log.e(TAG, "", e) } catch (e: RemoteException) { - Log.e(javaClass.simpleName, "", e) + Log.e(TAG, "", e) } Log.d( diff --git a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt index 7829d49f11..02780dce2a 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt +++ b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt @@ -217,8 +217,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor private fun handleCallPushMessage() { val userBeingCalled = userManager.getUserWithId(signatureVerification.user!!.id!!).blockingGet() - fun prepareCallNotificationScreen(conversation: ConversationModel) { - val fullScreenIntent = Intent(context, CallNotificationActivity::class.java) + fun createBundle(conversation: ConversationModel): Bundle { val bundle = Bundle() bundle.putString(KEY_ROOM_TOKEN, pushMessage.id) bundle.putInt(KEY_NOTIFICATION_TIMESTAMP, pushMessage.timestamp.toInt()) @@ -248,6 +247,12 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor BundleKeys.KEY_IS_MODERATOR, ConversationUtils.isParticipantOwnerOrModerator(conversation) ) + return bundle + } + + fun prepareCallNotificationScreen(conversation: ConversationModel) { + val fullScreenIntent = Intent(context, CallNotificationActivity::class.java) + val bundle = createBundle(conversation) fullScreenIntent.putExtras(bundle) fullScreenIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK @@ -509,6 +514,42 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor val autoCancelOnClick = TYPE_RECORDING != pushMessage.type + val notificationBuilder = + createNotificationBuilder(category, contentTitle, contentText, baseUrl, pendingIntent, autoCancelOnClick) + val activeStatusBarNotification = findNotificationForRoom( + context, + signatureVerification.user!!, + pushMessage.id!! + ) + + // NOTE - systemNotificationId is an internal ID used on the device only. + // It is NOT the same as the notification ID used in communication with the server. + val systemNotificationId: Int = + activeStatusBarNotification?.id ?: calculateCRC32(System.currentTimeMillis().toString()).toInt() + + if ((TYPE_CHAT == pushMessage.type || TYPE_REMINDER == pushMessage.type) && + pushMessage.notificationUser != null + ) { + prepareChatNotification(notificationBuilder, activeStatusBarNotification, systemNotificationId) + addReplyAction(notificationBuilder, systemNotificationId) + addMarkAsReadAction(notificationBuilder, systemNotificationId) + } + + if (TYPE_RECORDING == pushMessage.type && ncNotification != null) { + addDismissRecordingAvailableAction(notificationBuilder, systemNotificationId, ncNotification) + addShareRecordingToChatAction(notificationBuilder, systemNotificationId, ncNotification) + } + sendNotification(systemNotificationId, notificationBuilder.build()) + } + + private fun createNotificationBuilder( + category: String, + contentTitle: CharSequence?, + contentText: CharSequence?, + baseUrl: String?, + pendingIntent: PendingIntent?, + autoCancelOnClick: Boolean + ): NotificationCompat.Builder { val notificationBuilder = NotificationCompat.Builder(context!!, "1") .setPriority(NotificationCompat.PRIORITY_HIGH) .setCategory(category) @@ -551,30 +592,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor notificationBuilder.setContentIntent(pendingIntent) val groupName = signatureVerification.user!!.id.toString() + "@" + pushMessage.id notificationBuilder.setGroup(calculateCRC32(groupName).toString()) - val activeStatusBarNotification = findNotificationForRoom( - context, - signatureVerification.user!!, - pushMessage.id!! - ) - - // NOTE - systemNotificationId is an internal ID used on the device only. - // It is NOT the same as the notification ID used in communication with the server. - val systemNotificationId: Int = - activeStatusBarNotification?.id ?: calculateCRC32(System.currentTimeMillis().toString()).toInt() - - if ((TYPE_CHAT == pushMessage.type || TYPE_REMINDER == pushMessage.type) && - pushMessage.notificationUser != null - ) { - prepareChatNotification(notificationBuilder, activeStatusBarNotification, systemNotificationId) - addReplyAction(notificationBuilder, systemNotificationId) - addMarkAsReadAction(notificationBuilder, systemNotificationId) - } - - if (TYPE_RECORDING == pushMessage.type && ncNotification != null) { - addDismissRecordingAvailableAction(notificationBuilder, systemNotificationId, ncNotification) - addShareRecordingToChatAction(notificationBuilder, systemNotificationId, ncNotification) - } - sendNotification(systemNotificationId, notificationBuilder.build()) + return notificationBuilder } private fun getLargeIcon(): Bitmap { diff --git a/app/src/main/java/com/nextcloud/talk/jobs/SaveFileToStorageWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/SaveFileToStorageWorker.kt index cacbdc84a5..3e8c7b22bc 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/SaveFileToStorageWorker.kt +++ b/app/src/main/java/com/nextcloud/talk/jobs/SaveFileToStorageWorker.kt @@ -74,33 +74,21 @@ class SaveFileToStorageWorker(val context: Context, workerParameters: WorkerPara MediaScannerConnection.scanFile(context, arrayOf(cacheFile.absolutePath), null, null) Handler(Looper.getMainLooper()).post { - Toast.makeText( - context, - context.resources.getString(R.string.nc_save_success), - Toast.LENGTH_SHORT - ).show() + Toast.makeText(context, R.string.nc_save_success, Toast.LENGTH_SHORT).show() } return Result.success() } catch (e: IOException) { Log.e(TAG, "Something went wrong when trying to save file to internal storage", e) Handler(Looper.getMainLooper()).post { - Toast.makeText( - context, - context.resources.getString(R.string.nc_common_error_sorry), - Toast.LENGTH_SHORT - ).show() + Toast.makeText(context, R.string.nc_common_error_sorry, Toast.LENGTH_SHORT).show() } return Result.failure() } catch (e: NullPointerException) { Log.e(TAG, "Something went wrong when trying to save file to internal storage", e) Handler(Looper.getMainLooper()).post { - Toast.makeText( - context, - context.resources.getString(R.string.nc_common_error_sorry), - Toast.LENGTH_SHORT - ).show() + Toast.makeText(context, R.string.nc_common_error_sorry, Toast.LENGTH_SHORT).show() } return Result.failure() diff --git a/app/src/main/java/com/nextcloud/talk/jobs/UploadAndShareFilesWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/UploadAndShareFilesWorker.kt index 93d0b92126..b12902e7f2 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/UploadAndShareFilesWorker.kt +++ b/app/src/main/java/com/nextcloud/talk/jobs/UploadAndShareFilesWorker.kt @@ -110,46 +110,10 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa fileName = FileUtils.getFileName(sourceFileUri, context) file = FileUtils.getFileFromUri(context, sourceFileUri) val remotePath = getRemotePath(currentUser) - val uploadSuccess: Boolean initNotificationSetup() file?.let { isChunkedUploading = it.length() > CHUNK_UPLOAD_THRESHOLD_SIZE } - if (file == null) { - uploadSuccess = false - } else if (isChunkedUploading) { - Log.d(TAG, "starting chunked upload because size is " + file!!.length()) - - initNotificationWithPercentage() - val mimeType = context.contentResolver.getType(sourceFileUri)?.toMediaTypeOrNull() - - chunkedFileUploader = ChunkedFileUploader( - okHttpClient, - currentUser, - roomToken, - metaData, - this - ) - - uploadSuccess = chunkedFileUploader!!.upload( - file!!, - mimeType, - remotePath - ) - } else { - Log.d(TAG, "starting normal upload (not chunked) of $fileName") - - uploadSuccess = FileUploader( - context, - currentUser, - roomToken, - ncApi - ).upload( - sourceFileUri, - fileName, - remotePath, - metaData - ).blockingFirst() - } + val uploadSuccess: Boolean = uploadFile(sourceFileUri, metaData, remotePath) if (uploadSuccess) { cancelNotification() @@ -169,16 +133,31 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa } } + private fun uploadFile(sourceFileUri: Uri, metaData: String?, remotePath: String): Boolean { + return if (file == null) { + false + } else if (isChunkedUploading) { + Log.d(TAG, "starting chunked upload because size is " + file!!.length()) + + initNotificationWithPercentage() + val mimeType = context.contentResolver.getType(sourceFileUri)?.toMediaTypeOrNull() + + chunkedFileUploader = ChunkedFileUploader(okHttpClient, currentUser, roomToken, metaData, this) + chunkedFileUploader!!.upload(file!!, mimeType, remotePath) + } else { + Log.d(TAG, "starting normal upload (not chunked) of $fileName") + + FileUploader(context, currentUser, roomToken, ncApi) + .upload(sourceFileUri, fileName, remotePath, metaData) + .blockingFirst() + } + } + private fun getRemotePath(currentUser: User): String { - var remotePath = CapabilitiesUtil.getAttachmentFolder( + val remotePath = CapabilitiesUtil.getAttachmentFolder( currentUser.capabilities!!.spreedCapability!! ) + "/" + fileName - remotePath = RemoteFileUtils.getNewPathIfFileExists( - ncApi, - currentUser, - remotePath - ) - return remotePath + return RemoteFileUtils.getNewPathIfFileExists(ncApi, currentUser, remotePath) } override fun onTransferProgress(percentage: Int) { diff --git a/detekt.yml b/detekt.yml index 225413717a..5d6ef08bcb 100644 --- a/detekt.yml +++ b/detekt.yml @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors # SPDX-License-Identifier: GPL-3.0-or-later build: - maxIssues: 99 + maxIssues: 87 weights: # complexity: 2 # LongParameterList: 1