diff --git a/jami-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.kt b/jami-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.kt index 78c82c4541ff7d4d4dc662ab985c9901ccc6f1a9..344e7280ce1c457ffdd042c48bfdd5c96cf6aed0 100644 --- a/jami-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.kt +++ b/jami-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.kt @@ -84,7 +84,6 @@ import net.jami.model.Account.ComposingStatus import net.jami.model.Interaction.InteractionStatus import net.jami.utils.StringUtils import java.io.File -import java.lang.ref.WeakReference import java.text.DateFormat import java.util.* import java.util.concurrent.TimeUnit @@ -743,12 +742,24 @@ class ConversationAdapter( player.seekTo(1) } - private fun openItemMenu(cvh: ConversationViewHolder, v: View, interaction: Interaction) { - MenuConversationBinding.inflate(LayoutInflater.from(v.context)).apply { + /** + * Display and manage the popup that allows user to react/reply/share/edit/delete message ... + * + * @param conversationViewHolder the view layout. + * @param view + * @param interaction + */ + private fun openItemMenu( + conversationViewHolder: ConversationViewHolder, view: View, interaction: Interaction + ) { + + // Inflate design from XML. + MenuConversationBinding.inflate(LayoutInflater.from(view.context)).apply { val history = interaction.historyObservable.blockingFirst() val lastElement = history.last() val isDeleted = lastElement is TextMessage && lastElement.body.isNullOrEmpty() - Log.w(TAG, "isDeleted $isDeleted ${history.size}") + + // Configure what should be displayed convActionOpenText.isVisible = interaction is DataTransfer && interaction.isComplete convActionDownloadText.isVisible = interaction is DataTransfer && interaction.isComplete convActionCopyText.isVisible = !isDeleted @@ -756,77 +767,141 @@ class ConversationAdapter( convActionDelete.isVisible = !isDeleted && !interaction.isIncoming convActionHistory.isVisible = !isDeleted && history.size > 1 root.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED) - val popupWindow = WeakReference(PopupWindow(root, LinearLayout.LayoutParams.WRAP_CONTENT, root.measuredHeight, true).apply { - setOnDismissListener { - if (convColor != 0 - && interaction.type == Interaction.InteractionType.TEXT - && !interaction.isIncoming - ) { - v.background?.setTint(convColor) - } else { - v.background?.setTintList(null) + + // The popup that display all the buttons + val popupWindow = PopupWindow( + root, LinearLayout.LayoutParams.WRAP_CONTENT, root.measuredHeight, true + ) + .apply { + elevation = view.context.resources.getDimension(R.dimen.call_preview_elevation) + showAsDropDown(view) + } + + val textViews = listOf( + convActionEmoji1.chip, convActionEmoji2.chip, + convActionEmoji3.chip, convActionEmoji4.chip + ) + + convActionEmoji1.chip.text = view.context.getString(R.string.default_emoji_1) + convActionEmoji2.chip.text = view.context.getString(R.string.default_emoji_2) + convActionEmoji3.chip.text = view.context.getString(R.string.default_emoji_3) + convActionEmoji4.chip.text = view.context.getString(R.string.default_emoji_4) + + // Subscribe on reactions to allows user to see which reaction he already selected. + val disposable = interaction.reactionObservable + .observeOn(DeviceUtils.uiScheduler) + .subscribe { reactions -> + textViews.forEach { textView -> + // Set checked reactions already sent. + textView.isChecked = reactions.any { + (textView.text == it.body) && (it.contact?.isUser == true) + } } + popupWindow.update() } - elevation = v.context.resources.getDimension(R.dimen.call_preview_elevation) - showAsDropDown(v) - }) + conversationViewHolder.compositeDisposable.add(disposable) + + popupWindow.setOnDismissListener { + if (convColor != 0 + && interaction.type == Interaction.InteractionType.TEXT + && !interaction.isIncoming + ) view.background?.setTint(convColor) + else view.background?.setTintList(null) + // Remove disposable. + conversationViewHolder.compositeDisposable.remove(disposable) + } + + // Callback executed when emoji is clicked. + // We want to know if the reaction is already set. + // If set we want to remove, else we want to append. val emojiCallback = View.OnClickListener { view -> - presenter.sendReaction(interaction, (view as TextView).text) - popupWindow.get()?.dismiss() + // Subscribe to know which are the current reactions. + conversationViewHolder.compositeDisposable.add(interaction.reactionObservable + .observeOn(DeviceUtils.uiScheduler) + .firstOrError() + .subscribe { reactions -> + // Try to find a reaction having corresponding to the one clicked. + val reaction = reactions.firstOrNull { + (it.body == (view as TextView).text) && (it.contact?.isUser == true) + } + if (reaction != null) + // Previously, it was not forbidden to send multiple times the same + // reaction. Hence, we only remove the first one. + presenter.removeReaction(reaction) + else // If null, it means we didn't find anything. So let's send it. + presenter.sendReaction(interaction, (view as TextView).text) + popupWindow.dismiss() + } + ) } - convActionEmoji1.setOnClickListener(emojiCallback) - convActionEmoji2.setOnClickListener(emojiCallback) - convActionEmoji3.setOnClickListener(emojiCallback) - convActionEmoji4.setOnClickListener(emojiCallback) + textViews.forEach { it.setOnClickListener(emojiCallback) } // Set callback + + // Configure reply convActionReply.setOnClickListener { presenter.startReplyTo(interaction) - popupWindow.get()?.dismiss() + popupWindow.dismiss() } + convActionMore.setOnClickListener { menuActions.isVisible = !menuActions.isVisible } + + // Open file convActionOpenText.setOnClickListener { presenter.openFile(interaction) } + + // Save file convActionDownloadText.setOnClickListener { presenter.saveFile(interaction) } + + // Manage copy convActionCopyText.setOnClickListener { addToClipboard(lastElement.body) - popupWindow.get()?.dismiss() + popupWindow.dismiss() } + + // Manage Edit and Delete actions if (!interaction.isIncoming) { + // Edit convActionEdit.setOnClickListener { try { val i = Intent(it.context, MessageEditActivity::class.java) .setData(Uri.withAppendedPath(ConversationPath.toUri(interaction.account!!, interaction.conversationId!!), interaction.messageId)) .setAction(Intent.ACTION_EDIT) - .putExtra(Intent.EXTRA_TEXT, cvh.mMsgTxt!!.text.toString()) - val options = ActivityOptionsCompat.makeSceneTransitionAnimation(conversationFragment.requireActivity(), cvh.mMsgTxt!!, "messageEdit") + .putExtra(Intent.EXTRA_TEXT, conversationViewHolder.mMsgTxt!!.text.toString()) + val options = ActivityOptionsCompat.makeSceneTransitionAnimation(conversationFragment.requireActivity(), conversationViewHolder.mMsgTxt!!, "messageEdit") conversationFragment.startActivityForResult(i, ConversationFragment.REQUEST_CODE_EDIT_MESSAGE, options.toBundle()) } catch (e: Exception) { Log.w(TAG, "Can't open picture", e) } - popupWindow.get()?.dismiss() + popupWindow.dismiss() } + + // Delete convActionDelete.setOnClickListener { presenter.deleteConversationItem(interaction) - popupWindow.get()?.dismiss() + popupWindow.dismiss() } } else { convActionEdit.setOnClickListener(null) convActionDelete.setOnClickListener(null) } + + // Share convActionShare.setOnClickListener { if (interaction is DataTransfer) presenter.shareFile(interaction) else if (interaction is TextMessage) presenter.shareText(interaction) - popupWindow.get()?.dismiss() + popupWindow.dismiss() } + + // Message history if (convActionHistory.isVisible) convActionHistory.setOnClickListener { - cvh.compositeDisposable.add( + conversationViewHolder.compositeDisposable.add( interaction.historyObservable.firstOrError().subscribe { c -> Log.w(TAG, "Message history ${c.size}") c.forEach { @@ -839,8 +914,9 @@ class ConversationAdapter( { dialog, which -> dialog.dismiss() } .create() .show() - }) - popupWindow.get()?.dismiss() + } + ) + popupWindow.dismiss() } } } diff --git a/jami-android/app/src/main/res/layout/menu_conversation.xml b/jami-android/app/src/main/res/layout/menu_conversation.xml index 8abfb1cd41a024785a0d75416cd20d198cc0d4bf..7ccd391a56aea2f437e94f84c977e4868aaaa694 100644 --- a/jami-android/app/src/main/res/layout/menu_conversation.xml +++ b/jami-android/app/src/main/res/layout/menu_conversation.xml @@ -15,49 +15,17 @@ android:orientation="horizontal" android:padding="8dp"> - <TextView - android:id="@+id/conv_action_emoji1" - android:layout_width="36dp" - android:layout_height="36dp" - android:background="?selectableItemBackgroundBorderless" - android:clickable="true" - android:focusable="true" - android:gravity="center" - android:text="@string/default_emoji_1" - android:textSize="18dp" /> + <include layout="@layout/item_reaction_chip_18" + android:id="@+id/conv_action_emoji1"/> - <TextView - android:id="@+id/conv_action_emoji2" - android:layout_width="36dp" - android:layout_height="36dp" - android:background="?selectableItemBackgroundBorderless" - android:clickable="true" - android:focusable="true" - android:gravity="center" - android:text="@string/default_emoji_2" - android:textSize="18dp" /> + <include layout="@layout/item_reaction_chip_18" + android:id="@+id/conv_action_emoji2"/> - <TextView - android:id="@+id/conv_action_emoji3" - android:layout_width="36dp" - android:layout_height="36dp" - android:background="?selectableItemBackgroundBorderless" - android:clickable="true" - android:focusable="true" - android:gravity="center" - android:text="@string/default_emoji_3" - android:textSize="18dp" /> + <include layout="@layout/item_reaction_chip_18" + android:id="@+id/conv_action_emoji3"/> - <TextView - android:id="@+id/conv_action_emoji4" - android:layout_width="36dp" - android:layout_height="36dp" - android:background="?selectableItemBackgroundBorderless" - android:clickable="true" - android:focusable="true" - android:gravity="center" - android:text="@string/default_emoji_5" - android:textSize="18dp" /> + <include layout="@layout/item_reaction_chip_18" + android:id="@+id/conv_action_emoji4"/> <ImageView android:visibility="gone"