|
@@ -20,13 +20,15 @@
|
|
|
|
|
|
package com.nextcloud.talk.controllers
|
|
|
|
|
|
-
|
|
|
+import android.content.ClipData
|
|
|
import android.content.Context
|
|
|
import android.content.Intent
|
|
|
import android.content.res.Resources
|
|
|
import android.graphics.Bitmap
|
|
|
import android.graphics.PorterDuff
|
|
|
import android.graphics.drawable.ColorDrawable
|
|
|
+import android.graphics.drawable.Drawable
|
|
|
+import android.os.Build
|
|
|
import android.os.Bundle
|
|
|
import android.os.Handler
|
|
|
import android.os.Parcelable
|
|
@@ -35,16 +37,20 @@ import android.text.InputFilter
|
|
|
import android.text.TextUtils
|
|
|
import android.text.TextWatcher
|
|
|
import android.util.Log
|
|
|
+import android.util.TypedValue
|
|
|
import android.view.*
|
|
|
import android.widget.*
|
|
|
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
|
|
|
import androidx.emoji.text.EmojiCompat
|
|
|
import androidx.emoji.widget.EmojiEditText
|
|
|
+import androidx.emoji.widget.EmojiTextView
|
|
|
import androidx.recyclerview.widget.LinearLayoutManager
|
|
|
import androidx.recyclerview.widget.RecyclerView
|
|
|
import autodagger.AutoInjector
|
|
|
import butterknife.BindView
|
|
|
import butterknife.OnClick
|
|
|
+import coil.api.load
|
|
|
+import coil.transform.CircleCropTransformation
|
|
|
import com.bluelinelabs.conductor.RouterTransaction
|
|
|
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
|
|
|
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler
|
|
@@ -54,6 +60,7 @@ import com.facebook.datasource.DataSource
|
|
|
import com.facebook.drawee.backends.pipeline.Fresco
|
|
|
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber
|
|
|
import com.facebook.imagepipeline.image.CloseableImage
|
|
|
+import com.google.android.flexbox.FlexboxLayout
|
|
|
import com.nextcloud.talk.R
|
|
|
import com.nextcloud.talk.activities.MagicCallActivity
|
|
|
import com.nextcloud.talk.adapters.messages.*
|
|
@@ -108,7 +115,7 @@ import javax.inject.Inject
|
|
|
@AutoInjector(NextcloudTalkApplication::class)
|
|
|
class ChatController(args: Bundle) : BaseController(args), MessagesListAdapter
|
|
|
.OnLoadMoreListener, MessagesListAdapter.Formatter<Date>, MessagesListAdapter
|
|
|
-.OnMessageLongClickListener<IMessage>, MessageHolders.ContentChecker<IMessage> {
|
|
|
+.OnMessageViewLongClickListener<IMessage>, MessageHolders.ContentChecker<IMessage> {
|
|
|
|
|
|
@Inject
|
|
|
@JvmField
|
|
@@ -150,6 +157,9 @@ class ChatController(args: Bundle) : BaseController(args), MessagesListAdapter
|
|
|
@JvmField
|
|
|
var conversationLobbyText: TextView? = null
|
|
|
val disposableList = ArrayList<Disposable>()
|
|
|
+ @JvmField
|
|
|
+ @BindView(R.id.quotedChatMessageView)
|
|
|
+ var quotedChatMessageView: RelativeLayout? = null
|
|
|
var roomToken: String? = null
|
|
|
val conversationUser: UserEntity?
|
|
|
val roomPassword: String
|
|
@@ -202,7 +212,7 @@ class ChatController(args: Bundle) : BaseController(args), MessagesListAdapter
|
|
|
if (conversationUser?.userId == "?") {
|
|
|
credentials = null
|
|
|
} else {
|
|
|
- credentials = ApiUtils.getCredentials(conversationUser.username, conversationUser.token)
|
|
|
+ credentials = ApiUtils.getCredentials(conversationUser!!.username, conversationUser!!.token)
|
|
|
}
|
|
|
|
|
|
if (args.containsKey(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL)) {
|
|
@@ -300,7 +310,7 @@ class ChatController(args: Bundle) : BaseController(args), MessagesListAdapter
|
|
|
.intrinsicWidth.toFloat(), activity).toInt()
|
|
|
|
|
|
val imageRequest = DisplayUtils.getImageRequestForUrl(ApiUtils.getUrlForAvatarWithNameAndPixels(conversationUser?.baseUrl,
|
|
|
- currentConversation?.name, avatarSize / 2), null)
|
|
|
+ currentConversation?.name, avatarSize / 2), conversationUser!!)
|
|
|
|
|
|
val imagePipeline = Fresco.getImagePipeline()
|
|
|
val dataSource = imagePipeline.fetchDecodedImage(imageRequest, null)
|
|
@@ -362,7 +372,7 @@ class ChatController(args: Bundle) : BaseController(args), MessagesListAdapter
|
|
|
messagesListView?.setAdapter(adapter)
|
|
|
adapter?.setLoadMoreListener(this)
|
|
|
adapter?.setDateHeadersFormatter { format(it) }
|
|
|
- adapter?.setOnMessageLongClickListener { onMessageLongClick(it) }
|
|
|
+ adapter?.setOnMessageViewLongClickListener { view, message -> onMessageViewLongClick(view, message)}
|
|
|
|
|
|
layoutManager = messagesListView?.layoutManager as LinearLayoutManager?
|
|
|
|
|
@@ -403,7 +413,6 @@ class ChatController(args: Bundle) : BaseController(args), MessagesListAdapter
|
|
|
val filters = arrayOfNulls<InputFilter>(1)
|
|
|
val lengthFilter = conversationUser?.messageMaxLength ?: 1000
|
|
|
|
|
|
-
|
|
|
filters[0] = InputFilter.LengthFilter(lengthFilter)
|
|
|
messageInput?.filters = filters
|
|
|
|
|
@@ -831,16 +840,22 @@ class ChatController(args: Bundle) : BaseController(args), MessagesListAdapter
|
|
|
}
|
|
|
|
|
|
messageInput?.setText("")
|
|
|
- sendMessage(editable)
|
|
|
+ val replyMessageId: Long? = view?.findViewById<RelativeLayout>(R.id.quotedChatMessageView)?.tag as Long?
|
|
|
+ sendMessage(editable, if (view?.findViewById<RelativeLayout>(R.id.quotedChatMessageView)?.visibility == View.VISIBLE) replyMessageId?.toInt() else null )
|
|
|
+ cancelReply()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private fun sendMessage(message: CharSequence) {
|
|
|
+ private fun sendMessage(message: CharSequence, replyTo: Int?) {
|
|
|
|
|
|
if (conversationUser != null) {
|
|
|
- ncApi?.sendChatMessage(credentials, ApiUtils.getUrlForChat(conversationUser?.baseUrl,
|
|
|
- roomToken),
|
|
|
- message, conversationUser.displayName)
|
|
|
+ ncApi!!.sendChatMessage(
|
|
|
+ credentials, ApiUtils.getUrlForChat(
|
|
|
+ conversationUser.baseUrl,
|
|
|
+ roomToken
|
|
|
+ ),
|
|
|
+ message, conversationUser.displayName, replyTo
|
|
|
+ )
|
|
|
?.subscribeOn(Schedulers.io())
|
|
|
?.observeOn(AndroidSchedulers.mainThread())
|
|
|
?.subscribe(object : Observer<GenericOverall> {
|
|
@@ -1240,12 +1255,71 @@ class ChatController(args: Bundle) : BaseController(args), MessagesListAdapter
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- override fun onMessageLongClick(message: IMessage) {
|
|
|
- if (activity != null) {
|
|
|
- val clipboardManager = activity?.getSystemService(Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager
|
|
|
- val clipData = android.content.ClipData.newPlainText(
|
|
|
- resources?.getString(R.string.nc_app_name), message.text)
|
|
|
- clipboardManager.primaryClip = clipData
|
|
|
+ @OnClick(R.id.cancelReplyButton)
|
|
|
+ fun cancelReply() {
|
|
|
+ quotedChatMessageView?.visibility = View.GONE
|
|
|
+ messageInputView?.findViewById<ImageButton>(R.id.attachmentButton)?.visibility = View.VISIBLE
|
|
|
+ messageInputView?.findViewById<Space>(R.id.attachmentButtonSpace)?.visibility = View.VISIBLE
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onMessageViewLongClick(view: View?, message: IMessage?) {
|
|
|
+ PopupMenu(this.context, view, if (message?.user?.id == conversationUser?.userId) Gravity.END else Gravity.START).apply {
|
|
|
+ setOnMenuItemClickListener { item ->
|
|
|
+ when (item?.itemId) {
|
|
|
+
|
|
|
+ R.id.action_copy_message -> {
|
|
|
+ val clipboardManager =
|
|
|
+ activity?.getSystemService(Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager
|
|
|
+ val clipData = ClipData.newPlainText(resources?.getString(R.string.nc_app_name), message?.text)
|
|
|
+ clipboardManager.setPrimaryClip(clipData)
|
|
|
+ true
|
|
|
+ }
|
|
|
+ R.id.action_reply_to_message -> {
|
|
|
+ val chatMessage = message as ChatMessage?
|
|
|
+ chatMessage?.let {
|
|
|
+ messageInputView?.findViewById<ImageButton>(R.id.attachmentButton)?.visibility = View.GONE
|
|
|
+ messageInputView?.findViewById<Space>(R.id.attachmentButtonSpace)?.visibility = View.GONE
|
|
|
+ messageInputView?.findViewById<ImageButton>(R.id.cancelReplyButton)?.visibility = View.VISIBLE
|
|
|
+ messageInputView?.findViewById<EmojiTextView>(R.id.quotedMessage)?.maxLines = 2
|
|
|
+ messageInputView?.findViewById<EmojiTextView>(R.id.quotedMessage)?.ellipsize = TextUtils.TruncateAt.END
|
|
|
+ messageInputView?.findViewById<EmojiTextView>(R.id.quotedMessage)?.text = it.text
|
|
|
+ messageInputView?.findViewById<TextView>(R.id.quotedMessageTime)?.text = DateFormatter.format(it.createdAt, DateFormatter.Template.TIME)
|
|
|
+ messageInputView?.findViewById<EmojiTextView>(R.id.quotedMessageAuthor)?.text = it.actorDisplayName ?: context!!.getText(R.string.nc_nick_guest)
|
|
|
+
|
|
|
+ conversationUser?.let { currentUser ->
|
|
|
+
|
|
|
+ messageInputView?.findViewById<ImageView>(R.id.quotedUserAvatar)?.load(it.user.avatar) {
|
|
|
+ addHeader("Authorization", credentials!!)
|
|
|
+ transformations(CircleCropTransformation())
|
|
|
+ }
|
|
|
+
|
|
|
+ chatMessage.imageUrl?.let{ previewImageUrl ->
|
|
|
+ messageInputView?.findViewById<ImageView>(R.id.quotedMessageImage)?.visibility = View.VISIBLE
|
|
|
+
|
|
|
+ val px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 96f, resources?.displayMetrics)
|
|
|
+ messageInputView?.findViewById<ImageView>(R.id.quotedMessageImage)?.maxHeight = px.toInt()
|
|
|
+ val layoutParams = messageInputView?.findViewById<ImageView>(R.id.quotedMessageImage)?.layoutParams as FlexboxLayout.LayoutParams
|
|
|
+ layoutParams.flexGrow = 0f
|
|
|
+ messageInputView?.findViewById<ImageView>(R.id.quotedMessageImage)?.layoutParams = layoutParams
|
|
|
+ messageInputView?.findViewById<ImageView>(R.id.quotedMessageImage)?.load(previewImageUrl) {
|
|
|
+ addHeader("Authorization", credentials!!)
|
|
|
+ }
|
|
|
+ } ?: run {
|
|
|
+ messageInputView?.findViewById<ImageView>(R.id.quotedMessageImage)?.visibility = View.GONE
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ quotedChatMessageView?.tag = message?.jsonMessageId
|
|
|
+ quotedChatMessageView?.visibility = View.VISIBLE
|
|
|
+ }
|
|
|
+ true
|
|
|
+ }
|
|
|
+ else -> false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ inflate(R.menu.chat_message_menu)
|
|
|
+ menu.findItem(R.id.action_reply_to_message).isVisible = (message as ChatMessage).replyable
|
|
|
+ show()
|
|
|
}
|
|
|
}
|
|
|
|