Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.ContextThemeWrapper
import android.view.Gravity
import android.view.View
import androidx.appcompat.widget.PopupMenu
import androidx.core.view.children
Expand Down Expand Up @@ -47,7 +48,6 @@ import one.mixin.android.extension.openEmail
import one.mixin.android.extension.openMedia
import one.mixin.android.extension.openPermissionSetting
import one.mixin.android.extension.screenHeight
import one.mixin.android.extension.showIcon
import one.mixin.android.extension.toast
import one.mixin.android.job.AttachmentDownloadJob
import one.mixin.android.job.ConvertVideoJob
Expand Down Expand Up @@ -598,7 +598,8 @@ class ChatHistoryActivity : BaseActivity() {
lifecycleScope.launch {
val role = withContext(Dispatchers.IO) { conversationRepository.findParticipantById(conversationId, Session.getAccountId()!!) }?.role
val isAdmin = role == ParticipantRole.OWNER.name || role == ParticipantRole.ADMIN.name
val popMenu = PopupMenu(this@ChatHistoryActivity, view)
val anchor = binding.recyclerView.findContainingItemView(view) ?: view
val popMenu = PopupMenu(this@ChatHistoryActivity, anchor, Gravity.END, 0, R.style.MessageActionPopup)
popMenu.menuInflater.inflate(
R.menu.chathistory,
popMenu.menu,
Expand All @@ -607,7 +608,7 @@ class ChatHistoryActivity : BaseActivity() {
popMenu.menu.findItem(R.id.copy).isVisible = messageItem.isText()
popMenu.menu.findItem(R.id.forward).isVisible = !isTranscript && !messageItem.isAppButtonGroup()

popMenu.showIcon()
popMenu.setForceShowIcon(true)
popMenu.setOnMenuItemClickListener {
when (it.itemId) {
R.id.copy -> {
Expand Down
192 changes: 125 additions & 67 deletions app/src/main/java/one/mixin/android/widget/ToolView.kt
Original file line number Diff line number Diff line change
@@ -1,99 +1,157 @@
@file:Suppress("DEPRECATION")

package one.mixin.android.widget

import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.util.AttributeSet
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.widget.PopupWindow
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.core.widget.PopupWindowCompat
import androidx.appcompat.widget.PopupMenu
import androidx.core.view.isVisible
import one.mixin.android.R
import one.mixin.android.databinding.ViewToolBinding
import one.mixin.android.extension.dp
import one.mixin.android.vo.MessageItem
import one.mixin.android.vo.isData
import one.mixin.android.vo.isText
import one.mixin.android.vo.supportSticker
import one.mixin.android.websocket.PinAction

class ToolView constructor(context: Context, attrs: AttributeSet) : RelativeLayout(context, attrs) {
enum class Action(@StringRes val title: Int, @DrawableRes val icon: Int) {
REPLY(R.string.Reply, R.drawable.ic_reply),
COPY(R.string.Copy, R.drawable.ic_content_copy),
FORWARD(R.string.Forward, R.drawable.ic_msg_forward),
PIN(R.string.pin_title, R.drawable.ic_message_pin),
UNPIN(R.string.Unpin, R.drawable.ic_message_unpin),
SHARE(R.string.Share, R.drawable.ic_share),
ADD_STICKER(R.string.Add_Sticker, R.drawable.ic_sticker_add),
DELETE(R.string.Delete, R.drawable.ic_msg_delete),
SELECT(R.string.Select_Multiple_Messages, R.drawable.ic_check_black_24dp),
}

private val binding = ViewToolBinding.inflate(LayoutInflater.from(context), this, true)
val closeIv = binding.closeIv
val copyIv = binding.copyIv
val countTv = binding.countTv
val deleteIv = binding.deleteIv
val addStickerIv = binding.addStickerIv
val replyIv = binding.replyIv
val forwardIv = binding.forwardIv
val shareIv = binding.shareIv
val pinIv = binding.pinIv
var onAction: ((Action) -> Unit)? = null

private val actionViews = mapOf(
Action.REPLY to binding.replyIv,
Action.COPY to binding.copyIv,
Action.FORWARD to binding.forwardIv,
Action.PIN to binding.pinIv,
Action.SHARE to binding.shareIv,
Action.ADD_STICKER to binding.addStickerIv,
Action.DELETE to binding.deleteIv,
)
private var actions = emptyList<Action>()
private var popupMenu: PopupMenu? = null
private var multipleSelection = false
private var selectionCount = 0

init {
closeIv.setOnLongClickListener {
showTip(it, R.string.Close)
true
}
copyIv.setOnLongClickListener {
showTip(it, R.string.Copy)
true
}
deleteIv.setOnLongClickListener {
showTip(it, R.string.Delete)
true
binding.moreIv.setOnClickListener { showMenu(it) }
binding.closeIv.tooltipText = context.getString(R.string.Close)
}

fun updateSelection(messages: Collection<MessageItem>, pinAction: PinAction? = null) {
selectionCount = messages.size
multipleSelection = messages.isNotEmpty() && (multipleSelection || messages.size > 1)
actions = messageSelectionActions(messages, pinAction)
updateToolbar()
if (actions.isEmpty() || multipleSelection) {
dismissMenu()
} else {
popupMenu?.menu?.let { menu ->
Action.entries.forEach { action ->
menu.findItem(action.ordinal + 1).isVisible = action in actions
}
}
}
addStickerIv.setOnLongClickListener {
showTip(it, R.string.Add_Sticker)
true
}

fun enterMultipleSelection() {
if (actions.isEmpty()) return
multipleSelection = true
dismissMenu()
updateToolbar()
}

private fun updateToolbar() {
binding.countTv.text = if (multipleSelection) {
selectionCount.toString()
} else {
resources.getQuantityString(R.plurals.items_selected, selectionCount, selectionCount)
}
forwardIv.setOnLongClickListener {
showTip(it, R.string.Forward)
true
binding.closeIv.setImageResource(if (multipleSelection) R.drawable.ic_msg_close else R.drawable.ic_arrow_back)
binding.moreIv.isVisible = !multipleSelection && actions.isNotEmpty()
actionViews.forEach { (action, view) ->
val currentAction = if (action == Action.PIN && Action.UNPIN in actions) Action.UNPIN else action
view.isVisible = multipleSelection && currentAction in actions
view.setImageResource(currentAction.icon)
view.contentDescription = context.getString(currentAction.title)
view.tooltipText = view.contentDescription
view.setOnClickListener {
if (currentAction in actions) onAction?.invoke(currentAction)
}
}
replyIv.setOnLongClickListener {
showTip(it, R.string.Reply)
true
}

fun showMenu(anchor: View) {
if (actions.isEmpty() || multipleSelection || !anchor.isAttachedToWindow) return
dismissMenu()
val popup = PopupMenu(context, anchor, Gravity.END, 0, R.style.MessageActionPopup)
popup.setForceShowIcon(true)
Action.entries.forEach { action ->
popup.menu.add(0, action.ordinal + 1, action.ordinal, action.title).apply {
setIcon(action.icon)
isVisible = action in actions
}
}
shareIv.setOnLongClickListener {
showTip(it, R.string.Share)
true
popup.setOnMenuItemClickListener { item ->
val action = Action.entries.getOrNull(item.itemId - 1)
if (action == null || action !in actions) {
false
} else {
dismissMenu()
onAction?.invoke(action)
true
}
}
pinIv.setOnLongClickListener {
showTip(
it,
if (it.tag == PinAction.PIN) {
R.string.pin_title
} else {
R.string.Unpin
},
)
true
popup.setOnDismissListener {
if (popupMenu === it) popupMenu = null
}
popupMenu = popup
popup.show()
}

private val tipView by lazy {
LayoutInflater.from(context).inflate(R.layout.view_tip, null, false)
fun dismissMenu() {
popupMenu?.dismiss()
popupMenu = null
}
private val popupWindow by lazy {
PopupWindow(context).apply {
isOutsideTouchable = true
setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
contentView = tipView
}

override fun onDetachedFromWindow() {
dismissMenu()
super.onDetachedFromWindow()
}
}

private fun showTip(
view: View,
@StringRes str: Int,
) {
tipView.apply {
(this as TextView).setText(str)
setOnClickListener {
popupWindow.dismiss()
}
internal fun messageSelectionActions(
messages: Collection<MessageItem>,
pinAction: PinAction? = null,
): List<ToolView.Action> {
if (messages.isEmpty()) return emptyList()
val message = messages.singleOrNull()
return buildList {
if (message != null && !message.canNotReply()) add(ToolView.Action.REPLY)
if (message?.isText() == true) add(ToolView.Action.COPY)
if (messages.size <= 99 && messages.none { it.canNotForward() }) add(ToolView.Action.FORWARD)
if (message != null && !message.canNotPin() && pinAction != null) {
add(if (pinAction == PinAction.PIN) ToolView.Action.PIN else ToolView.Action.UNPIN)
}
PopupWindowCompat.showAsDropDown(popupWindow, view, 0, 12.dp, Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL)
if (message?.isData() == true) add(ToolView.Action.SHARE)
if (message?.supportSticker() == true) add(ToolView.Action.ADD_STICKER)
add(ToolView.Action.DELETE)
if (message != null) add(ToolView.Action.SELECT)
}
}
4 changes: 4 additions & 0 deletions app/src/main/res/drawable/bg_message_action_popup.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/bg_round_white_10dp"
android:inset="@dimen/margin16" />
27 changes: 19 additions & 8 deletions app/src/main/res/layout/view_tool.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
android:padding="8dp"
android:layout_marginStart="8dp"
android:src="@drawable/ic_msg_close"
tools:ignore="ContentDescription" />
android:contentDescription="@string/Close" />

<TextView
android:id="@+id/count_tv"
Expand All @@ -40,7 +40,7 @@
android:padding="8dp"
android:layout_marginEnd="8dp"
android:src="@drawable/ic_msg_delete"
tools:ignore="ContentDescription" />
android:contentDescription="@string/Delete" />

<ImageView
android:id="@+id/copy_iv"
Expand All @@ -52,7 +52,7 @@
android:padding="8dp"
android:src="@drawable/ic_content_copy"
android:layout_marginEnd="8dp"
tools:ignore="ContentDescription" />
android:contentDescription="@string/Copy" />

<ImageView
android:id="@+id/forward_iv"
Expand All @@ -64,7 +64,7 @@
android:padding="8dp"
android:src="@drawable/ic_msg_forward"
android:layout_marginEnd="8dp"
tools:ignore="ContentDescription" />
android:contentDescription="@string/Forward" />

<ImageView
android:id="@+id/add_sticker_iv"
Expand All @@ -76,7 +76,7 @@
android:padding="8dp"
android:src="@drawable/ic_sticker_add"
android:layout_marginEnd="8dp"
tools:ignore="ContentDescription" />
android:contentDescription="@string/Add_Sticker" />

<ImageView
android:id="@+id/reply_iv"
Expand All @@ -88,7 +88,7 @@
android:padding="8dp"
android:src="@drawable/ic_reply"
android:layout_marginEnd="8dp"
tools:ignore="ContentDescription" />
android:contentDescription="@string/Reply" />

<ImageView
android:id="@+id/pin_iv"
Expand All @@ -100,7 +100,7 @@
android:padding="8dp"
android:src="@drawable/ic_message_pin"
android:layout_marginEnd="8dp"
tools:ignore="ContentDescription" />
android:contentDescription="@string/pin_title" />

<ImageView
android:id="@+id/share_iv"
Expand All @@ -112,5 +112,16 @@
android:padding="8dp"
android:src="@drawable/ic_share"
android:layout_marginEnd="8dp"
tools:ignore="ContentDescription" />
android:contentDescription="@string/Share" />
<ImageView
android:id="@+id/more_iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginEnd="8dp"
android:background="@drawable/mixin_ripple"
android:contentDescription="@string/More"
android:padding="8dp"
android:src="@drawable/ic_more" />
</RelativeLayout>
14 changes: 7 additions & 7 deletions app/src/main/res/menu/chathistory.xml
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/forward"
android:icon="@drawable/ic_menu_forward"
android:title="@string/Forward" />
<item
android:id="@+id/copy"
android:icon="@drawable/ic_menu_copy"
android:icon="@drawable/ic_content_copy"
android:title="@string/Copy" />
<item
android:id="@+id/forward"
android:icon="@drawable/ic_msg_forward"
android:title="@string/Forward" />
<item
android:id="@+id/unpin"
android:icon="@drawable/ic_menu_unpin"
android:icon="@drawable/ic_message_unpin"
android:title="@string/Unpin" />
</menu>
</menu>
1 change: 1 addition & 0 deletions app/src/main/res/values-zh-rCN/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -971,6 +971,7 @@
<string name="Secured_by_TIP">由 TIP 提供安全保障</string>
<string name="Security">安全</string>
<string name="Select">选择</string>
<string name="Select_Multiple_Messages">多选</string>
<string name="Select_Emergency_Contact">选择恢复联系人</string>
<string name="Send">发送</string>
<string name="Sending">正在发送</string>
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-zh-rTW/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,7 @@
<string name="Searching">搜尋中</string>
<string name="Security">安全</string>
<string name="Select">選擇</string>
<string name="Select_Multiple_Messages">多選</string>
<string name="Select_Emergency_Contact">選擇緊急聯絡人</string>
<string name="Send">傳送</string>
<string name="Send_as_file">按檔案傳送</string>
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -999,6 +999,7 @@
<string name="Secured_by_TIP">Secured by TIP</string>
<string name="Security">Security</string>
<string name="Select">Select</string>
<string name="Select_Multiple_Messages">Select Multiple Messages</string>
<string name="Select_Emergency_Contact">Select Recovery Contact</string>
<string name="Send">Send</string>
<string name="Sending">Sending</string>
Expand Down
Loading
Loading