From 657f4d3e9c297a7192d2964e3f9ba663cd593f67 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@matrix.org>
Date: Fri, 12 Apr 2019 12:38:02 +0200
Subject: [PATCH] Timeline : handle file/audio message

---
 .../core/platform/VectorBaseActivity.kt       |   9 +-
 .../home/room/detail/RoomDetailFragment.kt    |  15 +-
 .../timeline/TimelineEventController.kt       |   4 +
 .../timeline/factory/MessageItemFactory.kt    |  26 ++++
 .../detail/timeline/item/MessageFileItem.kt   |  57 ++++++++
 .../item_timeline_event_file_message.xml      | 135 ++++++++++++++++++
 6 files changed, 239 insertions(+), 7 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageFileItem.kt
 create mode 100644 vector/src/main/res/layout/item_timeline_event_file_message.xml

diff --git a/vector/src/main/java/im/vector/riotredesign/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/riotredesign/core/platform/VectorBaseActivity.kt
index 388d34254a..20a2c091c0 100644
--- a/vector/src/main/java/im/vector/riotredesign/core/platform/VectorBaseActivity.kt
+++ b/vector/src/main/java/im/vector/riotredesign/core/platform/VectorBaseActivity.kt
@@ -32,6 +32,7 @@ import com.bumptech.glide.util.Util
 import com.google.android.material.snackbar.Snackbar
 import im.vector.riotredesign.BuildConfig
 import im.vector.riotredesign.R
+import im.vector.riotredesign.core.utils.toast
 import im.vector.riotredesign.features.rageshake.BugReportActivity
 import im.vector.riotredesign.features.rageshake.BugReporter
 import im.vector.riotredesign.features.rageshake.RageShake
@@ -284,9 +285,9 @@ abstract class VectorBaseActivity : BaseMvRxActivity() {
      * PUBLIC METHODS
      * ========================================================================================== */
 
-    protected fun showSnackbar(message: String) {
+    fun showSnackbar(message: String) {
         coordinatorLayout?.let {
-            Snackbar.make(it, message, Snackbar.LENGTH_SHORT)
+            Snackbar.make(it, message, Snackbar.LENGTH_SHORT).show()
         }
     }
 
@@ -294,8 +295,8 @@ abstract class VectorBaseActivity : BaseMvRxActivity() {
      * Temporary method
      * ========================================================================================== */
 
-    protected fun notImplemented() {
-        showSnackbar(getString(R.string.not_implemented))
+    fun notImplemented() {
+        toast(getString(R.string.not_implemented))
     }
 
 }
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt
index d3319b7476..8a8ef50e4f 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt
@@ -37,6 +37,8 @@ import com.otaliastudios.autocomplete.Autocomplete
 import com.otaliastudios.autocomplete.AutocompleteCallback
 import com.otaliastudios.autocomplete.CharPolicy
 import im.vector.matrix.android.api.session.Session
+import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent
+import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
 import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
 import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
 import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
@@ -371,7 +373,7 @@ class RoomDetailFragment : VectorBaseFragment(), TimelineEventController.Callbac
                 .show()
     }
 
-// TimelineEventController.Callback ************************************************************
+    // TimelineEventController.Callback ************************************************************
 
     override fun onUrlClicked(url: String) {
         homePermalinkHandler.launch(url)
@@ -387,11 +389,18 @@ class RoomDetailFragment : VectorBaseFragment(), TimelineEventController.Callbac
     }
 
     override fun onVideoMessageClicked(messageVideoContent: MessageVideoContent, mediaData: ImageContentRenderer.Data, view: View) {
-        //TODO handle
+        vectorBaseActivity.notImplemented()
     }
 
+    override fun onFileMessageClicked(messageFileContent: MessageFileContent) {
+        vectorBaseActivity.notImplemented()
+    }
 
-// AutocompleteUserPresenter.Callback
+    override fun onAudioMessageClicked(messageAudioContent: MessageAudioContent) {
+        vectorBaseActivity.notImplemented()
+    }
+
+    // AutocompleteUserPresenter.Callback
 
     override fun onQueryUsers(query: CharSequence?) {
         textComposerViewModel.process(TextComposerActions.QueryUsers(query))
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt
index b96ba1ddc8..67654861c8 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt
@@ -25,6 +25,8 @@ import androidx.recyclerview.widget.RecyclerView
 import com.airbnb.epoxy.EpoxyController
 import com.airbnb.epoxy.EpoxyModel
 import com.airbnb.epoxy.VisibilityState
+import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent
+import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
 import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
 import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
 import im.vector.matrix.android.api.session.room.timeline.Timeline
@@ -52,6 +54,8 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter,
         fun onUrlClicked(url: String)
         fun onImageMessageClicked(messageImageContent: MessageImageContent, mediaData: ImageContentRenderer.Data, view: View)
         fun onVideoMessageClicked(messageVideoContent: MessageVideoContent, mediaData: ImageContentRenderer.Data, view: View)
+        fun onFileMessageClicked(messageFileContent: MessageFileContent)
+        fun onAudioMessageClicked(messageAudioContent: MessageAudioContent)
     }
 
     private val modelCache = arrayListOf<List<EpoxyModel<*>>>()
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt
index 884e8c3416..28bf49da1d 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt
@@ -23,8 +23,10 @@ import im.vector.matrix.android.api.permalinks.MatrixLinkify
 import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan
 import im.vector.matrix.android.api.session.events.model.EventType
 import im.vector.matrix.android.api.session.events.model.toModel
+import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent
 import im.vector.matrix.android.api.session.room.model.message.MessageContent
 import im.vector.matrix.android.api.session.room.model.message.MessageEmoteContent
+import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
 import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
 import im.vector.matrix.android.api.session.room.model.message.MessageNoticeContent
 import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
@@ -40,6 +42,8 @@ import im.vector.riotredesign.features.home.room.detail.timeline.helper.Timeline
 import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
 import im.vector.riotredesign.features.home.room.detail.timeline.item.DefaultItem
 import im.vector.riotredesign.features.home.room.detail.timeline.item.DefaultItem_
+import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageFileItem
+import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageFileItem_
 import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageImageVideoItem
 import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageImageVideoItem_
 import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
@@ -95,10 +99,32 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
             is MessageImageContent  -> buildImageMessageItem(messageContent, informationData, callback)
             is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, callback)
             is MessageVideoContent  -> buildVideoMessageItem(messageContent, informationData, callback)
+            is MessageFileContent   -> buildFileMessageItem(messageContent, informationData, callback)
+            is MessageAudioContent  -> buildAudioMessageItem(messageContent, informationData, callback)
             else                    -> buildNotHandledMessageItem(messageContent)
         }
     }
 
+    private fun buildAudioMessageItem(messageContent: MessageAudioContent,
+                                      informationData: MessageInformationData,
+                                      callback: TimelineEventController.Callback?): MessageFileItem? {
+        return MessageFileItem_()
+                .informationData(informationData)
+                .filename(messageContent.body)
+                .iconRes(R.drawable.filetype_audio)
+                .clickListener { _ -> callback?.onAudioMessageClicked(messageContent) }
+    }
+
+    private fun buildFileMessageItem(messageContent: MessageFileContent,
+                                     informationData: MessageInformationData,
+                                     callback: TimelineEventController.Callback?): MessageFileItem? {
+        return MessageFileItem_()
+                .informationData(informationData)
+                .filename(messageContent.body)
+                .iconRes(R.drawable.filetype_attachment)
+                .clickListener { _ -> callback?.onFileMessageClicked(messageContent) }
+    }
+
     private fun buildNotHandledMessageItem(messageContent: MessageContent): DefaultItem? {
         val text = "${messageContent.type} message events are not yet handled"
         return DefaultItem_().text(text)
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageFileItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageFileItem.kt
new file mode 100644
index 0000000000..edd3c779e6
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageFileItem.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2019 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.riotredesign.features.home.room.detail.timeline.item
+
+import android.graphics.Paint
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.annotation.DrawableRes
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import im.vector.riotredesign.R
+
+@EpoxyModelClass(layout = R.layout.item_timeline_event_file_message)
+abstract class MessageFileItem : AbsMessageItem<MessageFileItem.Holder>() {
+
+    @EpoxyAttribute var filename: CharSequence = ""
+    @EpoxyAttribute @DrawableRes var iconRes: Int = 0
+    @EpoxyAttribute override lateinit var informationData: MessageInformationData
+    @EpoxyAttribute var clickListener: View.OnClickListener? = null
+
+    override fun bind(holder: Holder) {
+        super.bind(holder)
+        holder.fileLayout.renderSendState()
+        holder.filenameView.text = filename
+        holder.fileImageView.setImageResource(iconRes)
+        holder.filenameView.setOnClickListener(clickListener)
+        holder.filenameView.paintFlags = (holder.filenameView.paintFlags or Paint.UNDERLINE_TEXT_FLAG)
+    }
+
+
+    class Holder : AbsMessageItem.Holder() {
+        override val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView)
+        override val memberNameView by bind<TextView>(R.id.messageMemberNameView)
+        override val timeView by bind<TextView>(R.id.messageTimeView)
+        val fileLayout by bind<ViewGroup>(R.id.messageFileLayout)
+        val fileImageView by bind<ImageView>(R.id.messageFileImageView)
+        val filenameView by bind<TextView>(R.id.messageFilenameView)
+    }
+
+
+}
\ No newline at end of file
diff --git a/vector/src/main/res/layout/item_timeline_event_file_message.xml b/vector/src/main/res/layout/item_timeline_event_file_message.xml
new file mode 100644
index 0000000000..83bc481500
--- /dev/null
+++ b/vector/src/main/res/layout/item_timeline_event_file_message.xml
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2019 New Vector Ltd
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~ http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<androidx.constraintlayout.widget.ConstraintLayout 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:paddingLeft="16dp"
+    android:paddingRight="16dp">
+
+
+    <ImageView
+        android:id="@+id/messageAvatarImageView"
+        android:layout_width="@dimen/chat_avatar_size"
+        android:layout_height="@dimen/chat_avatar_size"
+        android:layout_marginTop="8dp"
+        android:layout_marginBottom="8dp"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:src="@tools:sample/avatars" />
+
+    <TextView
+        android:id="@+id/messageMemberNameView"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="64dp"
+        android:layout_marginLeft="64dp"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="8dp"
+        android:layout_marginRight="8dp"
+        android:ellipsize="end"
+        android:maxLines="1"
+        android:textSize="15sp"
+        app:layout_constraintEnd_toStartOf="@+id/messageTimeView"
+        app:layout_constraintHorizontal_bias="0.0"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:text="@tools:sample/full_names" />
+
+
+    <TextView
+        android:id="@+id/messageTimeView"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="8dp"
+        android:layout_marginLeft="8dp"
+        android:duplicateParentState="true"
+        android:textColor="@color/brown_grey"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0.0"
+        app:layout_constraintTop_toTopOf="@id/messageMemberNameView"
+        tools:text="@tools:sample/date/hhmm" />
+
+
+    <LinearLayout
+        android:id="@+id/messageFileLayout"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="64dp"
+        android:layout_marginLeft="64dp"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="32dp"
+        android:layout_marginRight="32dp"
+        android:layout_marginBottom="8dp"
+        android:duplicateParentState="true"
+        android:orientation="horizontal"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/messageMemberNameView">
+
+        <ImageView
+            android:id="@+id/messageFilee2eIcon"
+            android:layout_width="14dp"
+            android:layout_height="14dp"
+            android:src="@drawable/e2e_verified"
+            android:visibility="gone" />
+
+        <!-- the media type -->
+        <ImageView
+            android:id="@+id/messageFileImageView"
+            android:layout_width="@dimen/chat_avatar_size"
+            android:layout_height="@dimen/chat_avatar_size"
+            android:layout_marginStart="4dp"
+            android:layout_marginLeft="4dp"
+            android:src="@drawable/filetype_image" />
+
+        <!-- the media -->
+        <TextView
+            android:id="@+id/messageFilenameView"
+            android:layout_width="0dp"
+            android:layout_height="@dimen/chat_avatar_size"
+            android:layout_marginStart="4dp"
+            android:layout_marginLeft="4dp"
+            android:layout_weight="1"
+            android:autoLink="none"
+            android:gravity="center_vertical"
+            tools:text="A filename here" />
+
+    </LinearLayout>
+
+
+    <include
+        android:id="@+id/messageMediaUploadProgressLayout"
+        layout="@layout/media_upload_download_progress_layout"
+        android:layout_width="0dp"
+        android:layout_height="46dp"
+        android:layout_marginStart="64dp"
+        android:layout_marginLeft="64dp"
+        android:layout_marginTop="8dp"
+        android:layout_marginEnd="32dp"
+        android:layout_marginRight="32dp"
+        android:layout_marginBottom="8dp"
+        android:visibility="gone"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/messageFileLayout"
+        tools:visibility="visible" />
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file