Эх сурвалжийг харах

differ between incoming and outcoming messages for location

logic was copied from:
- MagicIncomingTextMessageViewHolder.kt
- MagicOutcomingTextMessageViewHolder.kt

xml design was copied from:
- item_custom_incoming_text_message.xml
- item_custom_outcoming_text_message.xml

... and extended for location related things.

because of copying there is now quite a lot of redundant code! But ViewHolders should generally be refactored in the future.. (better inheritance(?) and analyze which of Marios changes to the Chatkit lib were really necessary or if this can be done in an other way..)

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
Marcel Hibbe 3 жил өмнө
parent
commit
1aa15ebd95

+ 322 - 0
app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt

@@ -0,0 +1,322 @@
+package com.nextcloud.talk.adapters.messages
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.Intent
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.LayerDrawable
+import android.net.Uri
+import android.text.Spannable
+import android.text.SpannableString
+import android.text.TextUtils
+import android.util.Log
+import android.util.TypedValue
+import android.view.MotionEvent
+import android.view.View
+import android.webkit.WebView
+import android.webkit.WebViewClient
+import android.widget.ImageView
+import android.widget.RelativeLayout
+import android.widget.TextView
+import android.widget.Toast
+import androidx.core.view.ViewCompat
+import androidx.emoji.widget.EmojiTextView
+import autodagger.AutoInjector
+import butterknife.BindView
+import butterknife.ButterKnife
+import coil.load
+import com.amulyakhare.textdrawable.TextDrawable
+import com.facebook.drawee.view.SimpleDraweeView
+import com.nextcloud.talk.R
+import com.nextcloud.talk.application.NextcloudTalkApplication
+import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
+import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.utils.ApiUtils
+import com.nextcloud.talk.utils.DisplayUtils
+import com.nextcloud.talk.utils.TextMatchers
+import com.nextcloud.talk.utils.preferences.AppPreferences
+import com.stfalcon.chatkit.messages.MessageHolders
+import java.net.URLEncoder
+import javax.inject.Inject
+
+@AutoInjector(NextcloudTalkApplication::class)
+class IncomingLocationMessageViewHolder(incomingView: View) : MessageHolders
+.IncomingTextMessageViewHolder<ChatMessage>(incomingView) {
+
+    private val TAG = "LocationMessageViewHolder"
+
+    var mapProviderUrl: String = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
+    var mapProviderAttribution: String = "OpenStreetMap contributors"
+
+    var locationLon: String? = ""
+    var locationLat: String? = ""
+    var locationName: String? = ""
+    var locationGeoLink: String? = ""
+
+    @JvmField
+    @BindView(R.id.messageAuthor)
+    var messageAuthor: EmojiTextView? = null
+
+    @JvmField
+    @BindView(R.id.messageText)
+    var messageText: EmojiTextView? = null
+
+    @JvmField
+    @BindView(R.id.messageUserAvatar)
+    var messageUserAvatarView: SimpleDraweeView? = null
+
+    @JvmField
+    @BindView(R.id.messageTime)
+    var messageTimeView: TextView? = null
+
+    @JvmField
+    @BindView(R.id.quotedChatMessageView)
+    var quotedChatMessageView: RelativeLayout? = null
+
+    @JvmField
+    @BindView(R.id.quotedMessageAuthor)
+    var quotedUserName: EmojiTextView? = null
+
+    @JvmField
+    @BindView(R.id.quotedMessageImage)
+    var quotedMessagePreview: ImageView? = null
+
+    @JvmField
+    @BindView(R.id.quotedMessage)
+    var quotedMessage: EmojiTextView? = null
+
+    @JvmField
+    @BindView(R.id.quoteColoredView)
+    var quoteColoredView: View? = null
+
+    @JvmField
+    @Inject
+    var context: Context? = null
+
+    @JvmField
+    @Inject
+    var appPreferences: AppPreferences? = null
+
+    @JvmField
+    @BindView(R.id.webview)
+    var webview: WebView? = null
+
+    init {
+        ButterKnife.bind(
+            this,
+            itemView
+        )
+    }
+
+    @SuppressLint("SetTextI18n", "SetJavaScriptEnabled", "ClickableViewAccessibility")
+    override fun onBind(message: ChatMessage) {
+        super.onBind(message)
+        sharedApplication!!.componentApplication.inject(this)
+        val author: String = message.actorDisplayName
+        if (!TextUtils.isEmpty(author)) {
+            messageAuthor!!.text = author
+        } else {
+            messageAuthor!!.setText(R.string.nc_nick_guest)
+        }
+
+        if (!message.isGrouped && !message.isOneToOneConversation) {
+            messageUserAvatarView!!.visibility = View.VISIBLE
+            if (message.actorType == "guests") {
+                // do nothing, avatar is set
+            } else if (message.actorType == "bots" && message.actorId == "changelog") {
+                val layers = arrayOfNulls<Drawable>(2)
+                layers[0] = context?.getDrawable(R.drawable.ic_launcher_background)
+                layers[1] = context?.getDrawable(R.drawable.ic_launcher_foreground)
+                val layerDrawable = LayerDrawable(layers)
+                messageUserAvatarView?.setImageDrawable(DisplayUtils.getRoundedDrawable(layerDrawable))
+            } else if (message.actorType == "bots") {
+                val drawable = TextDrawable.builder()
+                    .beginConfig()
+                    .bold()
+                    .endConfig()
+                    .buildRound(
+                        ">",
+                        context!!.resources.getColor(R.color.black)
+                    )
+                messageUserAvatarView!!.visibility = View.VISIBLE
+                messageUserAvatarView?.setImageDrawable(drawable)
+            }
+        } else {
+            if (message.isOneToOneConversation) {
+                messageUserAvatarView!!.visibility = View.GONE
+            } else {
+                messageUserAvatarView!!.visibility = View.INVISIBLE
+            }
+            messageAuthor!!.visibility = View.GONE
+        }
+
+        val resources = itemView.resources
+
+        val bgBubbleColor = if (message.isDeleted) {
+            resources.getColor(R.color.bg_message_list_incoming_bubble_deleted)
+        } else {
+            resources.getColor(R.color.bg_message_list_incoming_bubble)
+        }
+
+        var bubbleResource = R.drawable.shape_incoming_message
+
+        if (message.isGrouped) {
+            bubbleResource = R.drawable.shape_grouped_incoming_message
+        }
+
+        val bubbleDrawable = DisplayUtils.getMessageSelector(
+            bgBubbleColor,
+            resources.getColor(R.color.transparent),
+            bgBubbleColor, bubbleResource
+        )
+        ViewCompat.setBackground(bubble, bubbleDrawable)
+
+        val messageParameters = message.messageParameters
+
+        itemView.isSelected = false
+        messageTimeView!!.setTextColor(context?.resources!!.getColor(R.color.warm_grey_four))
+
+        var messageString: Spannable = SpannableString(message.text)
+
+        var textSize = context?.resources!!.getDimension(R.dimen.chat_text_size)
+
+        if (messageParameters != null && messageParameters.size > 0) {
+            for (key in messageParameters.keys) {
+                val individualHashMap = message.messageParameters[key]
+                if (individualHashMap != null) {
+                    if (individualHashMap["type"] == "user" || individualHashMap["type"] == "guest" || individualHashMap["type"] == "call") {
+                        if (individualHashMap["id"] == message.activeUser!!.userId) {
+                            messageString = DisplayUtils.searchAndReplaceWithMentionSpan(
+                                messageText!!.context,
+                                messageString,
+                                individualHashMap["id"]!!,
+                                individualHashMap["name"]!!,
+                                individualHashMap["type"]!!,
+                                message.activeUser!!,
+                                R.xml.chip_you
+                            )
+                        } else {
+                            messageString = DisplayUtils.searchAndReplaceWithMentionSpan(
+                                messageText!!.context,
+                                messageString,
+                                individualHashMap["id"]!!,
+                                individualHashMap["name"]!!,
+                                individualHashMap["type"]!!,
+                                message.activeUser!!,
+                                R.xml.chip_others
+                            )
+                        }
+                    } else if (individualHashMap["type"] == "file") {
+                        itemView.setOnClickListener { v ->
+                            val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(individualHashMap["link"]))
+                            context!!.startActivity(browserIntent)
+                        }
+                    }
+                }
+            }
+        } else if (TextMatchers.isMessageWithSingleEmoticonOnly(message.text)) {
+            textSize = (textSize * 2.5).toFloat()
+            itemView.isSelected = true
+            messageAuthor!!.visibility = View.GONE
+        }
+
+        messageText!!.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
+        messageText!!.text = messageString
+
+        // parent message handling
+
+        if (!message.isDeleted && message.parentMessage != null) {
+            var parentChatMessage = message.parentMessage
+            parentChatMessage.activeUser = message.activeUser
+            parentChatMessage.imageUrl?.let {
+                quotedMessagePreview?.visibility = View.VISIBLE
+                quotedMessagePreview?.load(it) {
+                    addHeader(
+                        "Authorization",
+                        ApiUtils.getCredentials(message.activeUser.username, message.activeUser.token)
+                    )
+                }
+            } ?: run {
+                quotedMessagePreview?.visibility = View.GONE
+            }
+            quotedUserName?.text = parentChatMessage.actorDisplayName
+                ?: context!!.getText(R.string.nc_nick_guest)
+            quotedMessage?.text = parentChatMessage.text
+
+            quotedUserName?.setTextColor(context!!.resources.getColor(R.color.textColorMaxContrast))
+
+            if (parentChatMessage.actorId?.equals(message.activeUser.userId) == true) {
+                quoteColoredView?.setBackgroundResource(R.color.colorPrimary)
+            } else {
+                quoteColoredView?.setBackgroundResource(R.color.textColorMaxContrast)
+            }
+
+            quotedChatMessageView?.visibility = View.VISIBLE
+        } else {
+            quotedChatMessageView?.visibility = View.GONE
+        }
+
+        // geo-location
+
+        if (message.messageParameters != null && message.messageParameters.size > 0) {
+            for (key in message.messageParameters.keys) {
+                val individualHashMap: Map<String, String> = message.messageParameters[key]!!
+                if (individualHashMap["type"] == "geo-location") {
+                    locationLon = individualHashMap["longitude"]
+                    locationLat = individualHashMap["latitude"]
+                    locationName = individualHashMap["name"]
+                    locationGeoLink = individualHashMap["id"]
+                }
+            }
+        }
+
+        webview?.settings?.javaScriptEnabled = true
+
+        webview?.webViewClient = object : WebViewClient() {
+            override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
+                return if (url != null && (url.startsWith("http://") || url.startsWith("https://"))
+                ) {
+                    view?.context?.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
+                    true
+                } else {
+                    false
+                }
+            }
+        }
+
+        val urlStringBuffer = StringBuffer("file:///android_asset/leafletMapMessagePreview.html")
+        urlStringBuffer.append("?mapProviderUrl=" + URLEncoder.encode(mapProviderUrl))
+        urlStringBuffer.append("&mapProviderAttribution=" + URLEncoder.encode(mapProviderAttribution))
+        urlStringBuffer.append("&locationLat=" + URLEncoder.encode(locationLat))
+        urlStringBuffer.append("&locationLon=" + URLEncoder.encode(locationLon))
+        urlStringBuffer.append("&locationName=" + URLEncoder.encode(locationName))
+        urlStringBuffer.append("&locationGeoLink=" + URLEncoder.encode(locationGeoLink))
+
+        webview?.loadUrl(urlStringBuffer.toString())
+
+        webview?.setOnTouchListener(object : View.OnTouchListener {
+            override fun onTouch(v: View?, event: MotionEvent?): Boolean {
+                when (event?.action) {
+                    MotionEvent.ACTION_UP -> openGeoLink()
+                }
+
+                return v?.onTouchEvent(event) ?: true
+            }
+        })
+    }
+
+    private fun openGeoLink() {
+        if (!locationGeoLink.isNullOrEmpty()) {
+            val geoLinkWithMarker = addMarkerToGeoLink(locationGeoLink!!)
+            val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(geoLinkWithMarker))
+            context!!.startActivity(browserIntent)
+        } else {
+            Toast.makeText(context, R.string.nc_common_error_sorry, Toast.LENGTH_LONG).show()
+            Log.e(TAG, "locationGeoLink was null or empty")
+        }
+    }
+
+    private fun addMarkerToGeoLink(locationGeoLink: String): String {
+        return locationGeoLink.replace("geo:", "geo:0,0?q=")
+    }
+}

+ 0 - 128
app/src/main/java/com/nextcloud/talk/adapters/messages/LocationMessageViewHolder.kt

@@ -1,128 +0,0 @@
-package com.nextcloud.talk.adapters.messages
-
-import android.annotation.SuppressLint
-import android.content.Context
-import android.content.Intent
-import android.net.Uri
-import android.util.Log
-import android.view.MotionEvent
-import android.view.View
-import android.webkit.WebView
-import android.webkit.WebViewClient
-import android.widget.TextView
-import android.widget.Toast
-import autodagger.AutoInjector
-import butterknife.BindView
-import butterknife.ButterKnife
-import com.nextcloud.talk.R
-import com.nextcloud.talk.application.NextcloudTalkApplication
-import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
-import com.nextcloud.talk.models.json.chat.ChatMessage
-import com.stfalcon.chatkit.messages.MessageHolders
-import java.net.URLEncoder
-import javax.inject.Inject
-
-@AutoInjector(NextcloudTalkApplication::class)
-class LocationMessageViewHolder(incomingView: View) : MessageHolders
-.IncomingTextMessageViewHolder<ChatMessage>(incomingView) {
-
-    private val TAG = "LocationMessageViewHolder"
-
-    var mapProviderUrl: String = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
-    var mapProviderAttribution: String = "OpenStreetMap contributors"
-
-    var locationLon: String? = ""
-    var locationLat: String? = ""
-    var locationName: String? = ""
-    var locationGeoLink: String? = ""
-
-    @JvmField
-    @BindView(R.id.locationText)
-    var messageText: TextView? = null
-
-    @JvmField
-    @BindView(R.id.webview)
-    var webview: WebView? = null
-
-    @JvmField
-    @Inject
-    var context: Context? = null
-
-    init {
-        ButterKnife.bind(
-            this,
-            itemView
-        )
-    }
-
-    @SuppressLint("SetTextI18n", "SetJavaScriptEnabled", "ClickableViewAccessibility")
-    override fun onBind(message: ChatMessage) {
-        super.onBind(message)
-        sharedApplication!!.componentApplication.inject(this)
-        // if (message.messageType == ChatMessage.MessageType.SINGLE_NC_GEOLOCATION_MESSAGE) {
-        //     Log.d(TAG, "handle geolocation here")
-        //     messageText!!.text = "geolocation..."
-        // }
-        if (message.messageParameters != null && message.messageParameters.size > 0) {
-            for (key in message.messageParameters.keys) {
-                val individualHashMap: Map<String, String> = message.messageParameters[key]!!
-                if (individualHashMap["type"] == "geo-location") {
-                    locationLon = individualHashMap["longitude"]
-                    locationLat = individualHashMap["latitude"]
-                    locationName = individualHashMap["name"]
-                    locationGeoLink = individualHashMap["id"]
-                }
-            }
-        }
-
-
-        webview?.settings?.javaScriptEnabled = true
-
-        webview?.webViewClient = object : WebViewClient() {
-            override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
-                return if (url != null && (url.startsWith("http://") || url.startsWith("https://"))
-                ) {
-                    view?.context?.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
-                    true
-                } else {
-                    false
-                }
-            }
-        }
-
-        val urlStringBuffer = StringBuffer("file:///android_asset/leafletMapMessagePreview.html")
-        urlStringBuffer.append("?mapProviderUrl=" + URLEncoder.encode(mapProviderUrl))
-        urlStringBuffer.append("&mapProviderAttribution=" + URLEncoder.encode(mapProviderAttribution))
-        urlStringBuffer.append("&locationLat=" + URLEncoder.encode(locationLat))
-        urlStringBuffer.append("&locationLon=" + URLEncoder.encode(locationLon))
-        urlStringBuffer.append("&locationName=" + URLEncoder.encode(locationName))
-        urlStringBuffer.append("&locationGeoLink=" + URLEncoder.encode(locationGeoLink))
-
-        webview?.loadUrl(urlStringBuffer.toString())
-
-        webview?.setOnTouchListener(object : View.OnTouchListener {
-            override fun onTouch(v: View?, event: MotionEvent?): Boolean {
-                when (event?.action) {
-                    MotionEvent.ACTION_UP -> openGeoLink()
-                }
-
-                return v?.onTouchEvent(event) ?: true
-            }
-        })
-    }
-
-    private fun openGeoLink() {
-        if (!locationGeoLink.isNullOrEmpty()) {
-            val geoLinkWithMarker = addMarkerToGeoLink(locationGeoLink!!)
-            val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(geoLinkWithMarker))
-            context!!.startActivity(browserIntent)
-        } else {
-            Toast.makeText(context, R.string.nc_common_error_sorry, Toast.LENGTH_LONG).show()
-            Log.e(TAG, "locationGeoLink was null or empty")
-        }
-    }
-
-    private fun addMarkerToGeoLink(locationGeoLink: String): String {
-        return locationGeoLink.replace("geo:", "geo:0,0?q=")
-    }
-}

+ 4 - 4
app/src/main/java/com/nextcloud/talk/adapters/messages/MagicIncomingTextMessageViewHolder.kt

@@ -101,10 +101,6 @@ class MagicIncomingTextMessageViewHolder(itemView: View) : MessageHolders
     @Inject
     var appPreferences: AppPreferences? = null
 
-    init {
-        ButterKnife.bind(this, itemView)
-    }
-
     override fun onBind(message: ChatMessage) {
         super.onBind(message)
         sharedApplication!!.componentApplication.inject(this)
@@ -258,4 +254,8 @@ class MagicIncomingTextMessageViewHolder(itemView: View) : MessageHolders
 
         itemView.setTag(MessageSwipeCallback.REPLYABLE_VIEW_TAG, message.isReplyable)
     }
+
+    init {
+        ButterKnife.bindthis, itemView)
+    }
 }

+ 0 - 2
app/src/main/java/com/nextcloud/talk/adapters/messages/MagicPreviewMessageViewHolder.java

@@ -177,8 +177,6 @@ public class MagicPreviewMessageViewHolder extends MessageHolders.IncomingImageM
             } catch (ExecutionException | InterruptedException e) {
                 Log.e(TAG, "Error when checking if worker already exists", e);
             }
-        } else if (message.getMessageType() == ChatMessage.MessageType.SINGLE_NC_GEOLOCATION_MESSAGE) {
-            Log.d(TAG, "handle geolocation here");
         } else if (message.getMessageType() == ChatMessage.MessageType.SINGLE_LINK_GIPHY_MESSAGE) {
             messageText.setText("GIPHY");
             DisplayUtils.setClickableString("GIPHY", "https://giphy.com", messageText);

+ 285 - 0
app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLocationMessageViewHolder.kt

@@ -0,0 +1,285 @@
+package com.nextcloud.talk.adapters.messages
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.Intent
+import android.graphics.PorterDuff
+import android.net.Uri
+import android.text.Spannable
+import android.text.SpannableString
+import android.util.Log
+import android.util.TypedValue
+import android.view.MotionEvent
+import android.view.View
+import android.webkit.WebView
+import android.webkit.WebViewClient
+import android.widget.ImageView
+import android.widget.RelativeLayout
+import android.widget.TextView
+import android.widget.Toast
+import androidx.core.view.ViewCompat
+import androidx.emoji.widget.EmojiTextView
+import autodagger.AutoInjector
+import butterknife.BindView
+import butterknife.ButterKnife
+import coil.load
+import com.google.android.flexbox.FlexboxLayout
+import com.nextcloud.talk.R
+import com.nextcloud.talk.application.NextcloudTalkApplication
+import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
+import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.models.json.chat.ReadStatus
+import com.nextcloud.talk.utils.ApiUtils
+import com.nextcloud.talk.utils.DisplayUtils
+import com.nextcloud.talk.utils.TextMatchers
+import com.stfalcon.chatkit.messages.MessageHolders
+import java.net.URLEncoder
+import java.util.HashMap
+import javax.inject.Inject
+
+@AutoInjector(NextcloudTalkApplication::class)
+class OutcomingLocationMessageViewHolder(incomingView: View) : MessageHolders
+.OutcomingTextMessageViewHolder<ChatMessage>(incomingView) {
+
+    private val TAG = "LocationMessageViewHolder"
+
+    var mapProviderUrl: String = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
+    var mapProviderAttribution: String = "OpenStreetMap contributors"
+
+    var locationLon: String? = ""
+    var locationLat: String? = ""
+    var locationName: String? = ""
+    var locationGeoLink: String? = ""
+
+    @JvmField
+    @BindView(R.id.messageText)
+    var messageText: EmojiTextView? = null
+
+    @JvmField
+    @BindView(R.id.messageTime)
+    var messageTimeView: TextView? = null
+
+    @JvmField
+    @BindView(R.id.quotedChatMessageView)
+    var quotedChatMessageView: RelativeLayout? = null
+
+    @JvmField
+    @BindView(R.id.quotedMessageAuthor)
+    var quotedUserName: EmojiTextView? = null
+
+    @JvmField
+    @BindView(R.id.quotedMessageImage)
+    var quotedMessagePreview: ImageView? = null
+
+    @JvmField
+    @BindView(R.id.quotedMessage)
+    var quotedMessage: EmojiTextView? = null
+
+    @JvmField
+    @BindView(R.id.quoteColoredView)
+    var quoteColoredView: View? = null
+
+    @JvmField
+    @BindView(R.id.checkMark)
+    var checkMark: ImageView? = null
+
+    @JvmField
+    @Inject
+    var context: Context? = null
+
+    @JvmField
+    @BindView(R.id.webview)
+    var webview: WebView? = null
+
+    private val realView: View
+
+    @SuppressLint("SetTextI18n", "SetJavaScriptEnabled", "ClickableViewAccessibility")
+    override fun onBind(message: ChatMessage) {
+        super.onBind(message)
+        sharedApplication!!.componentApplication.inject(this)
+        val messageParameters: HashMap<String, HashMap<String, String>>? = message.messageParameters
+        var messageString: Spannable = SpannableString(message.text)
+        realView.isSelected = false
+        messageTimeView!!.setTextColor(context!!.resources.getColor(R.color.white60))
+        val layoutParams = messageTimeView!!.layoutParams as FlexboxLayout.LayoutParams
+        layoutParams.isWrapBefore = false
+        var textSize = context!!.resources.getDimension(R.dimen.chat_text_size)
+        if (messageParameters != null && messageParameters.size > 0) {
+            for (key in messageParameters.keys) {
+                val individualHashMap: HashMap<String, String>? = message.messageParameters[key]
+                if (individualHashMap != null) {
+                    if (individualHashMap["type"] == "user" || (
+                            individualHashMap["type"] == "guest"
+                            ) || individualHashMap["type"] == "call"
+                    ) {
+                        messageString = DisplayUtils.searchAndReplaceWithMentionSpan(
+                            messageText!!.context,
+                            messageString,
+                            individualHashMap["id"]!!,
+                            individualHashMap["name"]!!,
+                            individualHashMap["type"]!!,
+                            message.activeUser,
+                            R.xml.chip_others
+                        )
+                    } else if (individualHashMap["type"] == "file") {
+                        realView.setOnClickListener(
+                            View.OnClickListener { v: View? ->
+                                val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(individualHashMap["link"]))
+                                context!!.startActivity(browserIntent)
+                            }
+                        )
+                    }
+                }
+            }
+        } else if (TextMatchers.isMessageWithSingleEmoticonOnly(message.text)) {
+            textSize = (textSize * 2.5).toFloat()
+            layoutParams.isWrapBefore = true
+            messageTimeView!!.setTextColor(context!!.resources.getColor(R.color.warm_grey_four))
+            realView.isSelected = true
+        }
+        val resources = sharedApplication!!.resources
+        val bgBubbleColor = if (message.isDeleted) {
+            resources.getColor(R.color.bg_message_list_outcoming_bubble_deleted)
+        } else {
+            resources.getColor(R.color.bg_message_list_outcoming_bubble)
+        }
+        if (message.isGrouped) {
+            val bubbleDrawable = DisplayUtils.getMessageSelector(
+                bgBubbleColor,
+                resources.getColor(R.color.transparent),
+                bgBubbleColor,
+                R.drawable.shape_grouped_outcoming_message
+            )
+            ViewCompat.setBackground(bubble, bubbleDrawable)
+        } else {
+            val bubbleDrawable = DisplayUtils.getMessageSelector(
+                bgBubbleColor,
+                resources.getColor(R.color.transparent),
+                bgBubbleColor,
+                R.drawable.shape_outcoming_message
+            )
+            ViewCompat.setBackground(bubble, bubbleDrawable)
+        }
+        messageText!!.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
+        messageTimeView!!.layoutParams = layoutParams
+        messageText!!.text = messageString
+
+        // parent message handling
+
+        if (!message.isDeleted && message.parentMessage != null) {
+            var parentChatMessage = message.parentMessage
+            parentChatMessage.activeUser = message.activeUser
+            parentChatMessage.imageUrl?.let {
+                quotedMessagePreview?.visibility = View.VISIBLE
+                quotedMessagePreview?.load(it) {
+                    addHeader(
+                        "Authorization",
+                        ApiUtils.getCredentials(message.activeUser.username, message.activeUser.token)
+                    )
+                }
+            } ?: run {
+                quotedMessagePreview?.visibility = View.GONE
+            }
+            quotedUserName?.text = parentChatMessage.actorDisplayName
+                ?: context!!.getText(R.string.nc_nick_guest)
+            quotedMessage?.text = parentChatMessage.text
+            quotedMessage?.setTextColor(context!!.resources.getColor(R.color.nc_outcoming_text_default))
+            quotedUserName?.setTextColor(context!!.resources.getColor(R.color.nc_grey))
+
+            quoteColoredView?.setBackgroundResource(R.color.white)
+
+            quotedChatMessageView?.visibility = View.VISIBLE
+        } else {
+            quotedChatMessageView?.visibility = View.GONE
+        }
+
+        val readStatusDrawableInt = when (message.readStatus) {
+            ReadStatus.READ -> R.drawable.ic_check_all
+            ReadStatus.SENT -> R.drawable.ic_check
+            else -> null
+        }
+
+        val readStatusContentDescriptionString = when (message.readStatus) {
+            ReadStatus.READ -> context?.resources?.getString(R.string.nc_message_read)
+            ReadStatus.SENT -> context?.resources?.getString(R.string.nc_message_sent)
+            else -> null
+        }
+
+        readStatusDrawableInt?.let { drawableInt ->
+            context?.resources?.getDrawable(drawableInt, null)?.let {
+                it.setColorFilter(context?.resources!!.getColor(R.color.white60), PorterDuff.Mode.SRC_ATOP)
+                checkMark?.setImageDrawable(it)
+            }
+        }
+
+        checkMark?.setContentDescription(readStatusContentDescriptionString)
+
+        // geo-location
+
+        if (message.messageParameters != null && message.messageParameters.size > 0) {
+            for (key in message.messageParameters.keys) {
+                val individualHashMap: Map<String, String> = message.messageParameters[key]!!
+                if (individualHashMap["type"] == "geo-location") {
+                    locationLon = individualHashMap["longitude"]
+                    locationLat = individualHashMap["latitude"]
+                    locationName = individualHashMap["name"]
+                    locationGeoLink = individualHashMap["id"]
+                }
+            }
+        }
+
+        webview?.settings?.javaScriptEnabled = true
+
+        webview?.webViewClient = object : WebViewClient() {
+            override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
+                return if (url != null && (url.startsWith("http://") || url.startsWith("https://"))
+                ) {
+                    view?.context?.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
+                    true
+                } else {
+                    false
+                }
+            }
+        }
+
+        val urlStringBuffer = StringBuffer("file:///android_asset/leafletMapMessagePreview.html")
+        urlStringBuffer.append("?mapProviderUrl=" + URLEncoder.encode(mapProviderUrl))
+        urlStringBuffer.append("&mapProviderAttribution=" + URLEncoder.encode(mapProviderAttribution))
+        urlStringBuffer.append("&locationLat=" + URLEncoder.encode(locationLat))
+        urlStringBuffer.append("&locationLon=" + URLEncoder.encode(locationLon))
+        urlStringBuffer.append("&locationName=" + URLEncoder.encode(locationName))
+        urlStringBuffer.append("&locationGeoLink=" + URLEncoder.encode(locationGeoLink))
+
+        webview?.loadUrl(urlStringBuffer.toString())
+
+        webview?.setOnTouchListener(object : View.OnTouchListener {
+            override fun onTouch(v: View?, event: MotionEvent?): Boolean {
+                when (event?.action) {
+                    MotionEvent.ACTION_UP -> openGeoLink()
+                }
+
+                return v?.onTouchEvent(event) ?: true
+            }
+        })
+    }
+
+    private fun openGeoLink() {
+        if (!locationGeoLink.isNullOrEmpty()) {
+            val geoLinkWithMarker = addMarkerToGeoLink(locationGeoLink!!)
+            val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(geoLinkWithMarker))
+            context!!.startActivity(browserIntent)
+        } else {
+            Toast.makeText(context, R.string.nc_common_error_sorry, Toast.LENGTH_LONG).show()
+            Log.e(TAG, "locationGeoLink was null or empty")
+        }
+    }
+
+    private fun addMarkerToGeoLink(locationGeoLink: String): String {
+        return locationGeoLink.replace("geo:", "geo:0,0?q=")
+    }
+
+    init {
+        ButterKnife.bind(this, itemView)
+        this.realView = itemView
+    }
+}

+ 6 - 5
app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt

@@ -78,12 +78,13 @@ 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.LocationMessageViewHolder
+import com.nextcloud.talk.adapters.messages.IncomingLocationMessageViewHolder
 import com.nextcloud.talk.adapters.messages.MagicIncomingTextMessageViewHolder
 import com.nextcloud.talk.adapters.messages.MagicOutcomingTextMessageViewHolder
 import com.nextcloud.talk.adapters.messages.MagicPreviewMessageViewHolder
 import com.nextcloud.talk.adapters.messages.MagicSystemMessageViewHolder
 import com.nextcloud.talk.adapters.messages.MagicUnreadNoticeMessageViewHolder
+import com.nextcloud.talk.adapters.messages.OutcomingLocationMessageViewHolder
 import com.nextcloud.talk.adapters.messages.TalkMessagesListAdapter
 import com.nextcloud.talk.api.NcApi
 import com.nextcloud.talk.application.NextcloudTalkApplication
@@ -437,10 +438,10 @@ class ChatController(args: Bundle) :
 
             messageHolders.registerContentType(
                 CONTENT_TYPE_LOCATION,
-                LocationMessageViewHolder::class.java,
-                R.layout.item_custom_location_message,
-                LocationMessageViewHolder::class.java,
-                R.layout.item_custom_location_message,
+                IncomingLocationMessageViewHolder::class.java,
+                R.layout.item_custom_incoming_location_message,
+                OutcomingLocationMessageViewHolder::class.java,
+                R.layout.item_custom_outcoming_location_message,
                 this
             )
 

+ 87 - 0
app/src/main/res/layout/item_custom_incoming_location_message.xml

@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Nextcloud Talk application
+  ~
+  ~ @author Mario Danic
+  ~ @author Marcel Hibbe
+  ~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
+  ~ Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
+  ~
+  ~ This program is free software: you can redistribute it and/or modify
+  ~ it under the terms of the GNU General Public License as published by
+  ~ the Free Software Foundation, either version 3 of the License, or
+  ~ at your option) any later version.
+  ~
+  ~ This program is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  ~ GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+  -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginLeft="16dp"
+    android:layout_marginTop="2dp"
+    android:layout_marginRight="16dp"
+    android:layout_marginBottom="2dp">
+
+        <com.facebook.drawee.view.SimpleDraweeView
+            android:id="@id/messageUserAvatar"
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:layout_alignParentTop="true"
+            android:layout_marginEnd="8dp"
+            app:roundAsCircle="true" />
+
+        <com.google.android.flexbox.FlexboxLayout
+            android:id="@id/bubble"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_marginEnd="@dimen/message_incoming_bubble_margin_right"
+            android:layout_toEndOf="@id/messageUserAvatar"
+            android:orientation="vertical"
+            app:alignContent="stretch"
+            app:alignItems="stretch"
+            app:flexWrap="wrap"
+            app:justifyContent="flex_end">
+
+                <include layout="@layout/item_message_quote" android:visibility="gone"/>
+
+                <WebView
+                    android:id="@+id/webview"
+                    android:layout_width="400dp"
+                    android:layout_height="200dp"
+                    />
+
+                <androidx.emoji.widget.EmojiTextView
+                    android:id="@+id/messageAuthor"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginBottom="4dp"
+                    android:textColor="@color/textColorMaxContrast"
+                    android:textSize="12sp" />
+
+                <androidx.emoji.widget.EmojiTextView
+                    android:id="@id/messageText"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:lineSpacingMultiplier="1.2"
+                    android:textIsSelectable="true"
+                    app:layout_alignSelf="flex_start"
+                    app:layout_flexGrow="1"
+                    app:layout_wrapBefore="true" />
+
+                <TextView
+                    android:id="@id/messageTime"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_below="@id/messageText"
+                    android:layout_marginStart="8dp"
+                    app:layout_alignSelf="center" />
+
+        </com.google.android.flexbox.FlexboxLayout>
+</RelativeLayout>

+ 0 - 51
app/src/main/res/layout/item_custom_location_message.xml

@@ -1,51 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-  ~ Nextcloud Talk application
-  ~
-  ~ @author Mario Danic
-  ~ @author Marcel Hibbe
-  ~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
-  ~ Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
-  ~
-  ~ This program is free software: you can redistribute it and/or modify
-  ~ it under the terms of the GNU General Public License as published by
-  ~ the Free Software Foundation, either version 3 of the License, or
-  ~ at your option) any later version.
-  ~
-  ~ This program is distributed in the hope that it will be useful,
-  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
-  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-  ~ GNU General Public License for more details.
-  ~
-  ~ You should have received a copy of the GNU General Public License
-  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.
-  -->
-
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="200dp"
-    xmlns:tools="http://schemas.android.com/tools"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:layout_marginStart="16dp"
-    android:layout_marginTop="8dp"
-    android:layout_marginEnd="16dp"
-    android:layout_marginBottom="8dp">
-
-        <WebView
-            android:id="@+id/webview"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            />
-
-        <TextView
-            android:id="@+id/locationText"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:textColor="@color/warm_grey_four"
-            android:textSize="12sp"
-            tools:text="17:30"
-            android:layout_marginStart="8dp"
-            app:layout_alignSelf="center"
-            app:layout_flexGrow="1"
-            app:layout_wrapBefore="false"/>
-
-</RelativeLayout>

+ 81 - 0
app/src/main/res/layout/item_custom_outcoming_location_message.xml

@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Nextcloud Talk application
+  ~
+  ~ @author Mario Danic
+  ~ @author Andy Scherzinger
+  ~ Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
+  ~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
+  ~
+  ~ This program is free software: you can redistribute it and/or modify
+  ~ it under the terms of the GNU General Public License as published by
+  ~ the Free Software Foundation, either version 3 of the License, or
+  ~ at your option) any later version.
+  ~
+  ~ This program is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  ~ GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+  -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginLeft="16dp"
+    android:layout_marginTop="2dp"
+    android:layout_marginRight="16dp"
+    android:layout_marginBottom="2dp">
+
+    <com.google.android.flexbox.FlexboxLayout
+        android:id="@id/bubble"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_alignParentEnd="true"
+        android:layout_marginStart="@dimen/message_outcoming_bubble_margin_left"
+        app:alignContent="stretch"
+        app:alignItems="stretch"
+        app:flexWrap="wrap"
+        app:justifyContent="flex_end">
+
+        <include layout="@layout/item_message_quote" android:visibility="gone"/>
+
+        <WebView
+            android:id="@+id/webview"
+            android:layout_width="400dp"
+            android:layout_height="200dp"
+            />
+
+        <androidx.emoji.widget.EmojiTextView
+            android:id="@id/messageText"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignWithParentIfMissing="true"
+            android:lineSpacingMultiplier="1.2"
+            android:textColorHighlight="@color/nc_grey"
+            android:textIsSelectable="true"
+            tools:text="Talk to ayou later!" />
+
+        <TextView
+            android:id="@id/messageTime"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/messageText"
+            android:layout_marginStart="8dp"
+            app:layout_alignSelf="center"
+            tools:text="10:35" />
+
+        <ImageView
+            android:id="@+id/checkMark"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/messageTime"
+            android:layout_marginStart="8dp"
+            app:layout_alignSelf="center"
+            android:contentDescription="@null" />
+
+    </com.google.android.flexbox.FlexboxLayout>
+</RelativeLayout>