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
2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,8 @@ dependencies {

// ML Kit
implementation("com.google.mlkit:entity-extraction:$entityExtractionVersion")
implementation("com.google.mlkit:translate:17.0.1")
implementation("com.google.mlkit:language-id:17.0.4")

testImplementation("com.google.protobuf:protobuf-javalite") {
version {
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/one/mixin/android/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ object Constants {
const val PREF_CLEANUP_THUMB = "pref_cleanup_thumb"
const val PREF_CLEANUP_QUOTE_CONTENT = "pref_cleanup_quote_content"
const val PREF_TRANSFER_SCENE = "pref_transfer_scene"
const val PREF_SHOW_TRANSLATE_BUTTON = "pref_show_translate_button"
const val PREF_TRANSLATE_TARGET_LANG = "pref_translate_target_lang"
const val PREF_LOGIN_VERIFY = "pref_login_verify"
const val PREF_LOGIN_OR_SIGN_UP = "pref_login_or_sign_up"
const val PREF_NOTIFY_ENABLE_BIOMETRIC = "pref_notify_enable_biometric"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import android.Manifest
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.app.Activity
import android.app.ProgressDialog
import android.content.ActivityNotFoundException
import android.content.ClipData
import android.content.ContentResolver
Expand Down Expand Up @@ -120,6 +121,7 @@ import one.mixin.android.extension.getParcelableExtraCompat
import one.mixin.android.extension.getUriForFile
import one.mixin.android.extension.hideKeyboard
import one.mixin.android.extension.inTransaction
import one.mixin.android.extension.indeterminateProgressDialog
import one.mixin.android.extension.isAuto
import one.mixin.android.extension.isBluetoothHeadsetOrWiredHeadset
import one.mixin.android.extension.isGif
Expand Down Expand Up @@ -216,6 +218,7 @@ import one.mixin.android.util.ErrorHandler.Companion.FORBIDDEN
import one.mixin.android.util.GsonHelper
import one.mixin.android.util.MusicPlayer
import one.mixin.android.util.SINGLE_DB_THREAD
import one.mixin.android.util.TranslateManager
import one.mixin.android.util.analytics.AnalyticsTracker
import one.mixin.android.util.debug.debugLongClick
import one.mixin.android.util.markdown.MarkwonUtil.Companion.getMiniMarkwon
Expand Down Expand Up @@ -471,26 +474,30 @@ class ConversationFragment() :
when {
messageAdapter.selectSet.isEmpty() -> binding.toolView.fadeOut()
messageAdapter.selectSet.size == 1 -> {
val firstItem = messageAdapter.selectSet.valueAt(0)
try {
if (messageAdapter.selectSet.valueAt(0).isText()) {
if (firstItem.isText()) {
binding.toolView.copyIv.visibility = VISIBLE
binding.toolView.translateIv.isVisible = shouldShowTranslate(firstItem.messageId)
} else {
binding.toolView.copyIv.visibility = GONE
binding.toolView.translateIv.visibility = GONE
}
} catch (e: ArrayIndexOutOfBoundsException) {
binding.toolView.copyIv.visibility = GONE
binding.toolView.translateIv.visibility = GONE
}
if (messageAdapter.selectSet.valueAt(0).isData()) {
if (firstItem.isData()) {
binding.toolView.shareIv.visibility = VISIBLE
} else {
binding.toolView.shareIv.visibility = GONE
}
if (messageAdapter.selectSet.valueAt(0).supportSticker()) {
if (firstItem.supportSticker()) {
binding.toolView.addStickerIv.visibility = VISIBLE
} else {
binding.toolView.addStickerIv.visibility = GONE
}
if (messageAdapter.selectSet.valueAt(0).canNotReply()) {
if (firstItem.canNotReply()) {
binding.toolView.replyIv.visibility = GONE
} else {
binding.toolView.replyIv.visibility = VISIBLE
Expand All @@ -501,6 +508,7 @@ class ConversationFragment() :
binding.toolView.forwardIv.visibility = VISIBLE
binding.toolView.replyIv.visibility = GONE
binding.toolView.copyIv.visibility = GONE
binding.toolView.translateIv.visibility = GONE
binding.toolView.addStickerIv.visibility = GONE
binding.toolView.shareIv.visibility = GONE
binding.toolView.pinIv.visibility = GONE
Expand All @@ -524,8 +532,10 @@ class ConversationFragment() :
if (b) {
if (messageItem.isText()) {
binding.toolView.copyIv.visibility = VISIBLE
binding.toolView.translateIv.isVisible = shouldShowTranslate(messageItem.messageId)
} else {
binding.toolView.copyIv.visibility = GONE
binding.toolView.translateIv.visibility = GONE
}
if (messageItem.isData()) {
binding.toolView.shareIv.visibility = VISIBLE
Expand Down Expand Up @@ -1353,6 +1363,7 @@ class ConversationFragment() :
AudioPlayer.pause()
AudioPlayer.setStatusListener(null)
AudioPlayer.release()
translateManager?.release()
context?.let {
if (!anyCallServiceRunning(it)) {
audioSwitch.safeStop()
Expand Down Expand Up @@ -1633,6 +1644,33 @@ class ConversationFragment() :
}
closeTool()
}
binding.toolView.translateIv.setOnClickListener {
if (messageAdapter.selectSet.isEmpty()) return@setOnClickListener
val messageItem = messageAdapter.selectSet.valueAt(0)
val content = messageItem.content ?: return@setOnClickListener
if (translateManager == null) {
translateManager = TranslateManager()
}
lifecycleScope.launch {
val translated =
withContext(Dispatchers.IO) {
translateManager?.translate(requireContext(), content) { complete ->
lifecycleScope.launch {
if (complete) {
translateDownloadModelDialog?.dismiss()
} else {
translateDownloadModelDialog =
indeterminateProgressDialog(message = R.string.Please_wait_a_bit)
}
}
}
}
if (translated != null) {
messageAdapter.updateTranslated(messageItem.messageId, translated)
}
}
closeTool()
}
binding.toolView.forwardIv.setOnClickListener {
showForwardDialog()
}
Expand Down Expand Up @@ -1859,6 +1897,13 @@ class ConversationFragment() :
}
}

private var translateManager: TranslateManager? = null
private var translateDownloadModelDialog: ProgressDialog? = null

private fun shouldShowTranslate(messageId: String): Boolean =
defaultSharedPreferences.getBoolean(Constants.Account.PREF_SHOW_TRANSLATE_BUTTON, false) &&
messageAdapter.notTranslated(messageId)

private var deleteDialog: AlertDialog? = null

private fun deleteMessage(messages: List<MessageItem>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,18 @@ class MessageAdapter(
private val isSecret: Boolean = true,
var keyword: String? = null,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), MixinStickyRecyclerHeadersAdapter<TimeHolder> {
private val translatedMap = mutableMapOf<String, String>()

fun updateTranslated(
messageId: String,
translated: String,
) {
translatedMap[messageId] = translated
notifyDataSetChanged()
}

fun notTranslated(messageId: String): Boolean = translatedMap[messageId] == null

override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int,
Expand Down Expand Up @@ -282,6 +294,7 @@ class MessageAdapter(
(holder as TextHolder).bind(
it,
keyword,
translatedMap[it.messageId],
isLast(position),
isFirst(position),
selectSet.size > 0,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package one.mixin.android.ui.conversation.holder

import android.content.ClipData
import android.graphics.Color
import android.util.TypedValue
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import one.mixin.android.Constants
import one.mixin.android.Constants.Colors.LINK_COLOR
import one.mixin.android.Constants.Colors.SELECT_COLOR
Expand All @@ -15,6 +17,7 @@ import one.mixin.android.databinding.ItemChatTextBinding
import one.mixin.android.event.MentionReadEvent
import one.mixin.android.extension.defaultSharedPreferences
import one.mixin.android.extension.doubleClickVibrate
import one.mixin.android.extension.getClipboardManager
import one.mixin.android.extension.initChatMode
import one.mixin.android.extension.maxItemWidth
import one.mixin.android.extension.renderMessage
Expand All @@ -34,11 +37,13 @@ class TextHolder constructor(val binding: ItemChatTextBinding) : BaseMentionHold
binding.chatTime.changeSize(textSize - 4f)
binding.chatName.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize)
binding.chatTv.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize)
binding.translateTv.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize)
}
}
binding.chatTv.initChatMode(LINK_COLOR)
binding.chatTv.setSelectedStateColor(SELECT_COLOR)
binding.chatLayout.setMaxWidth(itemView.context.maxItemWidth())
binding.translateRl.setMaxWidth(itemView.context.maxItemWidth())
}

override fun chatLayout(
Expand Down Expand Up @@ -81,11 +86,31 @@ class TextHolder constructor(val binding: ItemChatTextBinding) : BaseMentionHold
}
}

private fun translatedLayout(isMe: Boolean) {
val lp = binding.translateRl.layoutParams as ConstraintLayout.LayoutParams
if (isMe) {
lp.horizontalBias = 1f
setItemBackgroundResource(
binding.translateRl,
R.drawable.chat_bubble_me,
R.drawable.chat_bubble_me_night,
)
} else {
lp.horizontalBias = 0f
setItemBackgroundResource(
binding.translateRl,
R.drawable.chat_bubble_other,
R.drawable.chat_bubble_other_night,
)
}
}

private var onItemListener: MessageAdapter.OnItemListener? = null

fun bind(
messageItem: MessageItem,
keyword: String?,
translated: String? = null,
isLast: Boolean,
isFirst: Boolean = false,
hasSelect: Boolean,
Expand Down Expand Up @@ -141,6 +166,13 @@ class TextHolder constructor(val binding: ItemChatTextBinding) : BaseMentionHold
}
}

binding.translateTv.setOnLongClickListener {
binding.root.context.getClipboardManager().setPrimaryClip(
ClipData.newPlainText(null, binding.translateTv.text),
)
true
}

itemView.setOnClickListener {
if (hasSelect) {
onItemListener.onSelect(!isSelect, messageItem, absoluteAdapterPosition)
Expand Down Expand Up @@ -179,6 +211,14 @@ class TextHolder constructor(val binding: ItemChatTextBinding) : BaseMentionHold
binding.chatTv.renderMessage(messageItem.content, keyword)
}

if (translated.isNullOrBlank()) {
binding.translateTv.text = null
binding.translateRl.isVisible = false
} else {
binding.translateTv.text = translated
binding.translateRl.isVisible = true
}

val isMe = meId == messageItem.userId
if (isFirst && !isMe) {
binding.chatName.visibility = View.VISIBLE
Expand All @@ -201,6 +241,7 @@ class TextHolder constructor(val binding: ItemChatTextBinding) : BaseMentionHold
chatJumpLayout(binding.chatJump, isMe, messageItem.expireIn, messageItem.expireAt, R.id.chat_layout)

chatLayout(isMe, isLast)
translatedLayout(isMe)

attachAction =
if (messageItem.mentionRead == false) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,25 @@ import android.os.Build
import android.os.Bundle
import android.view.Gravity
import android.view.View
import android.view.ViewGroup.MarginLayoutParams
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.widget.ListPopupWindow
import androidx.core.content.ContextCompat
import androidx.core.os.LocaleListCompat
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.viewModels
import dagger.hilt.android.AndroidEntryPoint
import one.mixin.android.Constants
import one.mixin.android.Constants.Account.PREF_SHOW_TRANSLATE_BUTTON
import one.mixin.android.Constants.Account.PREF_TRANSLATE_TARGET_LANG
import one.mixin.android.R
import one.mixin.android.RxBus
import one.mixin.android.databinding.FragmentAppearanceBinding
import one.mixin.android.event.QuoteColorEvent
import one.mixin.android.extension.alertDialogBuilder
import one.mixin.android.extension.defaultSharedPreferences
import one.mixin.android.extension.dp
import one.mixin.android.extension.dpToPx
import one.mixin.android.extension.navTo
import one.mixin.android.extension.putBoolean
Expand All @@ -27,7 +33,9 @@ import one.mixin.android.extension.singleChoice
import one.mixin.android.session.Session
import one.mixin.android.ui.common.BaseFragment
import one.mixin.android.util.TimeCache
import one.mixin.android.util.TranslateManager
import one.mixin.android.util.getLanguage
import one.mixin.android.util.getLanguageOrDefault
import one.mixin.android.util.getLocaleString
import one.mixin.android.util.isFollowSystem
import one.mixin.android.util.viewBinding
Expand Down Expand Up @@ -130,6 +138,34 @@ class AppearanceFragment : BaseFragment(R.layout.fragment_appearance) {
textSizeRl.setOnClickListener {
navTo(SettingSizeFragment.newInstance(), SettingSizeFragment.TAG)
}
translateSv.setContent(R.string.Show_Translate_Button)
val showTranslateButton = defaultSharedPreferences.getBoolean(PREF_SHOW_TRANSLATE_BUTTON, false)
translateSv.isChecked = showTranslateButton
targetRl.isVisible = showTranslateButton
translateSv.updateLayoutParams<MarginLayoutParams> {
topMargin = if (showTranslateButton) 0 else 20.dp
}
translateSv.setOnCheckedChangeListener { _, isChecked ->
defaultSharedPreferences.putBoolean(PREF_SHOW_TRANSLATE_BUTTON, isChecked)
targetRl.isVisible = isChecked
translateSv.updateLayoutParams<MarginLayoutParams> {
topMargin = if (isChecked) 0 else 20.dp
}
}
targetLangTv.text =
defaultSharedPreferences.getString(PREF_TRANSLATE_TARGET_LANG, getLanguageOrDefault())?.let {
TranslateManager.Language(it).nameInCurrentLanguage
}
targetRl.setOnClickListener {
val languageBottom = TranslateLanguageBottomSheetDialogFragment.newInstance()
languageBottom.callback =
object : TranslateLanguageBottomSheetDialogFragment.Callback {
override fun onLanguageClick(language: TranslateManager.Language) {
targetLangTv.text = language.nameInCurrentLanguage
}
}
languageBottom.showNow(parentFragmentManager, TranslateLanguageBottomSheetDialogFragment.TAG)
}
val quoteColor = requireContext().defaultSharedPreferences.getBoolean(Constants.Account.PREF_QUOTE_COLOR, false)
quoteColorDescTv.setText(
if (quoteColor) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import one.mixin.android.R
import one.mixin.android.RxBus
import one.mixin.android.api.handleMixinResponse
import one.mixin.android.api.request.AccountUpdateRequest
import one.mixin.android.databinding.FragmentCurrencyBottomSheetBinding
import one.mixin.android.databinding.FragmentSearchListBottomSheetBinding
import one.mixin.android.event.WalletOperationType
import one.mixin.android.event.WalletRefreshedEvent
import one.mixin.android.extension.appCompatActionBarHeight
Expand Down Expand Up @@ -38,7 +38,7 @@ class CurrencyBottomSheetDialogFragment : MixinBottomSheetDialogFragment() {
private val currencyAdapter = CurrencyAdapter()
private val currencies = arrayListOf<Currency>()

private val binding by viewBinding(FragmentCurrencyBottomSheetBinding::inflate)
private val binding by viewBinding(FragmentSearchListBottomSheetBinding::inflate)

@SuppressLint("RestrictedApi")
override fun setupDialog(
Expand Down Expand Up @@ -70,7 +70,7 @@ class CurrencyBottomSheetDialogFragment : MixinBottomSheetDialogFragment() {
savePreference(currency)
}
}
currencyRv.adapter = currencyAdapter
rv.adapter = currencyAdapter
}
currencies.clear()
currencies.addAll(getCurrencyData(requireContext().resources))
Expand Down
Loading