Forráskód Böngészése

Add overview for shared items of a conversation

Via the conversation info or the menu entry in the conversation menu a
overview of shared items can be opened.

Signed-off-by: Tim Krüger <t@timkrueger.me>
Tim Krüger 3 éve
szülő
commit
a1b7e1260c

+ 4 - 0
app/build.gradle

@@ -332,6 +332,10 @@ dependencies {
 
     gplayImplementation 'com.google.android.gms:play-services-base:18.0.1'
     gplayImplementation "com.google.firebase:firebase-messaging:23.0.0"
+
+    // TODO: Define variable for version
+    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
+    // implementation 'androidx.activity:activity-ktx:1.4.0'
 }
 
 task installGitHooks(type: Copy, group: "development") {

+ 5 - 0
app/src/main/AndroidManifest.xml

@@ -96,6 +96,11 @@
             android:name="android.max_aspect"
             android:value="10" />
 
+        <activity
+            android:name=".activities.SharedItemsActivity"
+            android:exported="false"
+            android:theme="@style/AppTheme"/>
+
         <activity
             android:name=".activities.MainActivity"
             android:label="@string/nc_app_name"

+ 44 - 0
app/src/main/java/com/nextcloud/talk/activities/SharedItemsActivity.kt

@@ -0,0 +1,44 @@
+package com.nextcloud.talk.activities
+
+import android.os.Bundle
+import android.util.Log
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.ViewModelProvider
+import com.nextcloud.talk.adapters.SharedItemsAdapter
+import com.nextcloud.talk.databinding.ActivitySharedItemsBinding
+import com.nextcloud.talk.models.database.UserEntity
+import com.nextcloud.talk.utils.bundle.BundleKeys
+import com.nextcloud.talk.viewmodels.SharedItemsViewModel
+
+class SharedItemsActivity : AppCompatActivity() {
+    companion object {
+        private val TAG = SharedItemsActivity::class.simpleName
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        val roomToken = intent.getStringExtra(BundleKeys.KEY_ROOM_TOKEN)!!
+        val userEntity = intent.getParcelableExtra<UserEntity>(BundleKeys.KEY_USER_ENTITY)!!
+
+        binding = ActivitySharedItemsBinding.inflate(layoutInflater)
+        setContentView(binding.root)
+
+        viewModel = ViewModelProvider(
+            this,
+            SharedItemsViewModel.Factory(userEntity, roomToken)
+        ).get(SharedItemsViewModel::class.java)
+
+        viewModel.media.observe(this) {
+            Log.d(TAG, "Items received: $it")
+            val adapter = SharedItemsAdapter()
+            adapter.items = it.items
+            adapter.authHeader = it.authHeader
+            binding.imageRecycler.adapter = adapter
+        }
+    }
+
+    private lateinit var binding: ActivitySharedItemsBinding
+
+    private lateinit var viewModel: SharedItemsViewModel
+}

+ 51 - 0
app/src/main/java/com/nextcloud/talk/adapters/SharedItemsAdapter.kt

@@ -0,0 +1,51 @@
+package com.nextcloud.talk.adapters
+
+import android.net.Uri
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import com.facebook.drawee.backends.pipeline.Fresco
+import com.facebook.drawee.interfaces.DraweeController
+import com.facebook.imagepipeline.common.RotationOptions
+import com.facebook.imagepipeline.request.ImageRequestBuilder
+import com.nextcloud.talk.databinding.AttachmentItemBinding
+import com.nextcloud.talk.repositories.SharedItem
+
+class SharedItemsAdapter : RecyclerView.Adapter<SharedItemsAdapter.ViewHolder>() {
+
+    class ViewHolder(val binding: AttachmentItemBinding, itemView: View) : RecyclerView.ViewHolder(itemView)
+
+    var authHeader: Map<String, String> = emptyMap()
+    var items: List<SharedItem> = emptyList()
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+        val binding = AttachmentItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+        return ViewHolder(binding, binding.root)
+    }
+
+    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+
+        val currentItem = items[position]
+
+        if (currentItem.previewAvailable) {
+            val imageRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(currentItem.previewLink))
+                .setProgressiveRenderingEnabled(true)
+                .setRotationOptions(RotationOptions.autoRotate())
+                .disableDiskCache()
+                .setHeaders(authHeader)
+                .build()
+
+            val draweeController: DraweeController = Fresco.newDraweeControllerBuilder()
+                .setOldController(holder.binding.image.controller)
+                .setAutoPlayAnimations(true)
+                .setImageRequest(imageRequest)
+                .build()
+            holder.binding.image.controller = draweeController
+        }
+    }
+
+    override fun getItemCount(): Int {
+        return items.size
+    }
+}

+ 7 - 0
app/src/main/java/com/nextcloud/talk/api/NcApi.java

@@ -27,6 +27,7 @@ package com.nextcloud.talk.api;
 import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall;
 import com.nextcloud.talk.models.json.chat.ChatOverall;
 import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage;
+import com.nextcloud.talk.models.json.chat.ChatShareOverall;
 import com.nextcloud.talk.models.json.conversations.RoomOverall;
 import com.nextcloud.talk.models.json.conversations.RoomsOverall;
 import com.nextcloud.talk.models.json.generic.GenericOverall;
@@ -338,6 +339,12 @@ public interface NcApi {
                                                @Field("actorDisplayName") String actorDisplayName,
                                                @Field("replyTo") Integer replyTo);
 
+    @GET
+    Observable<Response<ChatShareOverall>> getSharedItems(@Header("Authorization") String authorization, @Url String url,
+                                                          @Query("objectType") String objectType,
+                                                          @Nullable @Query("lastKnownMessageId") Integer lastKnownMessageId,
+                                                          @Nullable @Query("limit") Integer limit);
+
     @GET
     Observable<MentionOverall> getMentionAutocompleteSuggestions(@Header("Authorization") String authorization,
                                                                  @Url String url, @Query("search") String query,

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

@@ -46,6 +46,7 @@ import android.os.Build
 import android.os.Build.VERSION_CODES.O
 import android.os.Bundle
 import android.os.Handler
+import android.os.Parcelable
 import android.os.SystemClock
 import android.os.VibrationEffect
 import android.os.Vibrator
@@ -99,6 +100,7 @@ import com.nextcloud.talk.BuildConfig
 import com.nextcloud.talk.R
 import com.nextcloud.talk.activities.CallActivity
 import com.nextcloud.talk.activities.MainActivity
+import com.nextcloud.talk.activities.SharedItemsActivity
 import com.nextcloud.talk.activities.TakePhotoActivity
 import com.nextcloud.talk.adapters.messages.IncomingLocationMessageViewHolder
 import com.nextcloud.talk.adapters.messages.IncomingPreviewMessageViewHolder
@@ -188,9 +190,7 @@ import java.io.File
 import java.io.IOException
 import java.net.HttpURLConnection
 import java.text.SimpleDateFormat
-import java.util.ArrayList
 import java.util.Date
-import java.util.HashMap
 import java.util.Objects
 import java.util.concurrent.ExecutionException
 import javax.inject.Inject
@@ -253,6 +253,7 @@ class ChatController(args: Bundle) :
     var conversationInfoMenuItem: MenuItem? = null
     var conversationVoiceCallMenuItem: MenuItem? = null
     var conversationVideoMenuItem: MenuItem? = null
+    var conversationSharedItemsItem: MenuItem? = null
 
     var magicWebSocketInstance: MagicWebSocketInstance? = null
 
@@ -1464,7 +1465,7 @@ class ChatController(args: Bundle) :
         val bundle = Bundle()
         bundle.putParcelable(BundleKeys.KEY_BROWSER_TYPE, Parcels.wrap<BrowserController.BrowserType>(browserType))
         bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, Parcels.wrap<UserEntity>(conversationUser))
-        bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken)
+        bundle.putString(KEY_ROOM_TOKEN, roomToken)
         router.pushController(
             RouterTransaction.with(BrowserForSharingController(bundle))
                 .pushChangeHandler(VerticalChangeHandler())
@@ -1476,7 +1477,7 @@ class ChatController(args: Bundle) :
         Log.d(TAG, "showShareLocationScreen")
 
         val bundle = Bundle()
-        bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken)
+        bundle.putString(KEY_ROOM_TOKEN, roomToken)
         router.pushController(
             RouterTransaction.with(LocationPickerController(bundle))
                 .pushChangeHandler(HorizontalChangeHandler())
@@ -1487,7 +1488,7 @@ class ChatController(args: Bundle) :
     private fun showConversationInfoScreen() {
         val bundle = Bundle()
         bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, conversationUser)
-        bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken)
+        bundle.putString(KEY_ROOM_TOKEN, roomToken)
         bundle.putBoolean(BundleKeys.KEY_ROOM_ONE_TO_ONE, inOneToOneCall())
         router.pushController(
             RouterTransaction.with(ConversationInfoController(bundle))
@@ -2299,6 +2300,7 @@ class ChatController(args: Bundle) :
             conversationInfoMenuItem = menu.findItem(R.id.conversation_info)
             conversationVoiceCallMenuItem = menu.findItem(R.id.conversation_voice_call)
             conversationVideoMenuItem = menu.findItem(R.id.conversation_video_call)
+            conversationSharedItemsItem = menu.findItem(R.id.shared_items)
 
             loadAvatarForStatusBar()
         }
@@ -2337,10 +2339,21 @@ class ChatController(args: Bundle) :
                 showConversationInfoScreen()
                 return true
             }
+            R.id.shared_items -> {
+                showSharedItems()
+                return true
+            }
             else -> return super.onOptionsItemSelected(item)
         }
     }
 
+    private fun showSharedItems() {
+        val intent = Intent(activity, SharedItemsActivity::class.java)
+        intent.putExtra(KEY_ROOM_TOKEN, roomToken)
+        intent.putExtra(KEY_USER_ENTITY, conversationUser as Parcelable)
+        activity!!.startActivity(intent)
+    }
+
     private fun handleSystemMessages(chatMessageList: List<ChatMessage>): List<ChatMessage> {
         val chatMessageMap = chatMessageList.map { it.id to it }.toMap().toMutableMap()
         val chatMessageIterator = chatMessageMap.iterator()

+ 11 - 3
app/src/main/java/com/nextcloud/talk/controllers/ConversationInfoController.kt

@@ -27,9 +27,11 @@
 package com.nextcloud.talk.controllers
 
 import android.annotation.SuppressLint
+import android.content.Intent
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.LayerDrawable
 import android.os.Bundle
+import android.os.Parcelable
 import android.text.TextUtils
 import android.util.Log
 import android.view.MenuItem
@@ -49,6 +51,7 @@ import com.bluelinelabs.conductor.RouterTransaction
 import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
 import com.facebook.drawee.backends.pipeline.Fresco
 import com.nextcloud.talk.R
+import com.nextcloud.talk.activities.SharedItemsActivity
 import com.nextcloud.talk.adapters.items.ParticipantItem
 import com.nextcloud.talk.api.NcApi
 import com.nextcloud.talk.application.NextcloudTalkApplication
@@ -88,11 +91,8 @@ import io.reactivex.schedulers.Schedulers
 import org.greenrobot.eventbus.EventBus
 import org.greenrobot.eventbus.Subscribe
 import org.greenrobot.eventbus.ThreadMode
-import java.util.ArrayList
 import java.util.Calendar
 import java.util.Collections
-import java.util.Comparator
-import java.util.HashMap
 import java.util.Locale
 import javax.inject.Inject
 
@@ -175,10 +175,18 @@ class ConversationInfoController(args: Bundle) :
         binding.leaveConversationAction.setOnClickListener { leaveConversation() }
         binding.clearConversationHistory.setOnClickListener { showClearHistoryDialog(null) }
         binding.addParticipantsAction.setOnClickListener { addParticipants() }
+        binding.showSharedItemsAction.setOnClickListener { showSharedItems() }
 
         fetchRoomInfo()
     }
 
+    private fun showSharedItems() {
+        val intent = Intent(activity, SharedItemsActivity::class.java)
+        intent.putExtra(BundleKeys.KEY_ROOM_TOKEN, conversationToken)
+        intent.putExtra(BundleKeys.KEY_USER_ENTITY, conversationUser as Parcelable)
+        activity!!.startActivity(intent)
+    }
+
     override fun onViewBound(view: View) {
         super.onViewBound(view)
 

+ 77 - 0
app/src/main/java/com/nextcloud/talk/models/json/chat/ChatShareOCS.java

@@ -0,0 +1,77 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Mario Danic
+ * 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/>.
+ */
+package com.nextcloud.talk.models.json.chat;
+
+import com.bluelinelabs.logansquare.annotation.JsonField;
+import com.bluelinelabs.logansquare.annotation.JsonObject;
+import com.nextcloud.talk.models.json.generic.GenericOCS;
+
+import org.parceler.Parcel;
+
+import java.util.HashMap;
+import java.util.Objects;
+
+@Parcel
+@JsonObject
+public class ChatShareOCS extends GenericOCS {
+    @JsonField(name = "data")
+    public HashMap<String, ChatMessage> data;
+
+    public HashMap<String, ChatMessage> getData() {
+        return this.data;
+    }
+
+    public void setData(HashMap<String, ChatMessage> data) {
+        this.data = data;
+    }
+
+    public boolean equals(final Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (!(o instanceof ChatShareOCS)) {
+            return false;
+        }
+        final ChatShareOCS other = (ChatShareOCS) o;
+        if (!other.canEqual(this)) {
+            return false;
+        }
+        final Object this$data = this.getData();
+        final Object other$data = other.getData();
+
+        return Objects.equals(this$data, other$data);
+    }
+
+    protected boolean canEqual(final Object other) {
+        return other instanceof ChatShareOCS;
+    }
+
+    public int hashCode() {
+        final int PRIME = 59;
+        int result = 1;
+        final Object $data = this.getData();
+        result = result * PRIME + ($data == null ? 43 : $data.hashCode());
+        return result;
+    }
+
+    public String toString() {
+        return "ChatShareOCS(data=" + this.getData() + ")";
+    }
+}

+ 76 - 0
app/src/main/java/com/nextcloud/talk/models/json/chat/ChatShareOverall.java

@@ -0,0 +1,76 @@
+/*
+ * Nextcloud Talk application
+ *
+ * @author Mario Danic
+ * 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/>.
+ */
+
+package com.nextcloud.talk.models.json.chat;
+
+import com.bluelinelabs.logansquare.annotation.JsonField;
+import com.bluelinelabs.logansquare.annotation.JsonObject;
+
+import org.parceler.Parcel;
+
+import java.util.Objects;
+
+@Parcel
+@JsonObject
+public class ChatShareOverall {
+    @JsonField(name = "ocs")
+    public ChatShareOCS ocs;
+
+    public ChatShareOCS getOcs() {
+        return this.ocs;
+    }
+
+    public void setOcs(ChatShareOCS ocs) {
+        this.ocs = ocs;
+    }
+
+    public boolean equals(final Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (!(o instanceof ChatShareOverall)) {
+            return false;
+        }
+        final ChatShareOverall other = (ChatShareOverall) o;
+        if (!other.canEqual(this)) {
+            return false;
+        }
+        final Object this$ocs = this.getOcs();
+        final Object other$ocs = other.getOcs();
+
+        return Objects.equals(this$ocs, other$ocs);
+    }
+
+    protected boolean canEqual(final Object other) {
+        return other instanceof ChatShareOverall;
+    }
+
+    public int hashCode() {
+        final int PRIME = 59;
+        int result = 1;
+        final Object $ocs = this.getOcs();
+        result = result * PRIME + ($ocs == null ? 43 : $ocs.hashCode());
+        return result;
+    }
+
+    public String toString() {
+        return "ChatShareOverall(ocs=" + this.getOcs() + ")";
+    }
+}

+ 10 - 0
app/src/main/java/com/nextcloud/talk/repositories/SharedItem.kt

@@ -0,0 +1,10 @@
+package com.nextcloud.talk.repositories
+
+data class SharedItem(
+    val id: String,
+    val name: String,
+    val mimeType: String,
+    val link: String,
+    val previewAvailable: Boolean,
+    val previewLink: String
+)

+ 53 - 0
app/src/main/java/com/nextcloud/talk/repositories/SharedItemsRepository.kt

@@ -0,0 +1,53 @@
+package com.nextcloud.talk.repositories
+
+import autodagger.AutoInjector
+import com.nextcloud.talk.api.NcApi
+import com.nextcloud.talk.application.NextcloudTalkApplication
+import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
+import com.nextcloud.talk.models.json.chat.ChatShareOverall
+import com.nextcloud.talk.utils.ApiUtils
+import io.reactivex.Observable
+import retrofit2.Response
+import javax.inject.Inject
+
+@AutoInjector(NextcloudTalkApplication::class)
+class SharedItemsRepository {
+
+    companion object {
+        private val TAG = SharedItemsRepository::class.simpleName
+    }
+
+    var parameters: Parameters? = null
+
+    @Inject
+    lateinit var ncApi: NcApi
+
+    init {
+        sharedApplication!!.componentApplication.inject(this)
+    }
+
+    fun media(): Observable<Response<ChatShareOverall>>? {
+        val credentials = ApiUtils.getCredentials(parameters!!.userName, parameters!!.userToken)
+
+        return ncApi.getSharedItems(
+            credentials,
+            ApiUtils.getUrlForChatSharedItems(1, parameters!!.baseUrl, parameters!!.roomToken),
+            "media", null, null
+        )
+    }
+
+    fun authHeader(): Map<String, String> {
+        return mapOf(Pair("Authorization", ApiUtils.getCredentials(parameters!!.userName, parameters!!.userToken)))
+    }
+
+    fun previewLink(fileId: String?): String {
+        return ApiUtils.getUrlForFilePreviewWithFileId(parameters!!.baseUrl, fileId, 100)
+    }
+
+    data class Parameters(
+        val userName: String,
+        val userToken: String,
+        val baseUrl: String,
+        val roomToken: String
+    )
+}

+ 7 - 0
app/src/main/java/com/nextcloud/talk/repositories/SharedMediaItems.kt

@@ -0,0 +1,7 @@
+package com.nextcloud.talk.repositories
+
+class SharedMediaItems(
+    val items: List<SharedItem>,
+    val lastSeenId: String,
+    val authHeader: Map<String, String>
+)

+ 4 - 0
app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java

@@ -260,6 +260,10 @@ public class ApiUtils {
     public static String getUrlForChatMessage(int version, String baseUrl, String token, String messageId) {
         return getUrlForChat(version, baseUrl, token) + "/" + messageId;
     }
+    
+    public static String getUrlForChatSharedItems(int version, String baseUrl, String token) {
+        return getUrlForChat(version, baseUrl, token) + "/share";
+    }
 
     public static String getUrlForSignaling(int version, String baseUrl) {
         return getUrlForApi(version, baseUrl) + "/signaling";

+ 95 - 0
app/src/main/java/com/nextcloud/talk/viewmodels/SharedItemsViewModel.kt

@@ -0,0 +1,95 @@
+package com.nextcloud.talk.viewmodels
+
+import android.util.Log
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.nextcloud.talk.models.database.UserEntity
+import com.nextcloud.talk.models.json.chat.ChatShareOverall
+import com.nextcloud.talk.repositories.SharedItem
+import com.nextcloud.talk.repositories.SharedItemsRepository
+import com.nextcloud.talk.repositories.SharedMediaItems
+import io.reactivex.Observer
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.disposables.Disposable
+import io.reactivex.schedulers.Schedulers
+import retrofit2.Response
+
+class SharedItemsViewModel(private val repository: SharedItemsRepository) : ViewModel() {
+
+    private val _media: MutableLiveData<SharedMediaItems> by lazy {
+        MutableLiveData<SharedMediaItems>().also {
+            loadMediaItems()
+        }
+    }
+
+    val media: LiveData<SharedMediaItems>
+        get() = _media
+
+    private fun loadMediaItems() {
+
+        repository.media()?.subscribeOn(Schedulers.io())
+            ?.observeOn(AndroidSchedulers.mainThread())
+            ?.subscribe(object : Observer<Response<ChatShareOverall>> {
+
+                var chatLastGiven: String = ""
+                val items = mutableListOf<SharedItem>()
+
+                override fun onSubscribe(d: Disposable) = Unit
+
+                override fun onNext(response: Response<ChatShareOverall>) {
+                    chatLastGiven = response.headers()["x-chat-last-given"]!!
+
+                    val mediaItems = response.body()!!.ocs!!.data
+                    mediaItems?.forEach {
+                        val fileParameters = it.value.messageParameters["file"]!!
+
+                        val previewAvailable = "yes".equals(fileParameters["preview-available"]!!, ignoreCase = true)
+
+                        items.add(
+                            SharedItem(
+                                fileParameters["id"]!!, fileParameters["name"]!!,
+                                fileParameters["mimetype"]!!, fileParameters["link"]!!,
+                                previewAvailable,
+                                repository.previewLink(fileParameters["id"])
+                            )
+                        )
+                    }
+                }
+
+                override fun onError(e: Throwable) {
+                    Log.d(TAG, "An error occurred: $e")
+                }
+
+                override fun onComplete() {
+                    this@SharedItemsViewModel._media.value =
+                        SharedMediaItems(items.asReversed(), chatLastGiven, repository.authHeader())
+                }
+            })
+    }
+
+    class Factory(val userEntity: UserEntity, val roomToken: String) : ViewModelProvider.Factory {
+
+        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
+            if (modelClass.isAssignableFrom(SharedItemsViewModel::class.java)) {
+
+                val repository = SharedItemsRepository()
+                repository.parameters = SharedItemsRepository.Parameters(
+                    userEntity.userId,
+                    userEntity.token,
+                    userEntity.baseUrl,
+                    roomToken
+                )
+
+                return SharedItemsViewModel(repository) as T
+            }
+
+            throw IllegalArgumentException("Unknown ViewModel class")
+        }
+    }
+
+    companion object {
+        private val TAG = SharedItemsViewModel::class.simpleName
+    }
+}

+ 22 - 0
app/src/main/res/layout/activity_shared_items.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<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="match_parent"
+    tools:context=".activities.SharedItemsActivity">
+
+    <androidx.core.widget.NestedScrollView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/image_recycler"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
+            app:spanCount="4"
+            tools:listitem="@layout/attachment_item" />
+    </androidx.core.widget.NestedScrollView>
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 14 - 0
app/src/main/res/layout/attachment_item.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.facebook.drawee.view.SimpleDraweeView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:fresco="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/image"
+    android:layout_width="match_parent"
+    android:layout_height="100dp"
+    android:padding="4dp"
+    android:src="@drawable/account_circle_96dp"
+    app:layout_constraintTop_toTopOf="parent"
+    fresco:actualImageScaleType="centerCrop"
+    fresco:failureImage="@drawable/account_circle_96dp"
+    fresco:placeholderImage="@drawable/account_circle_96dp"
+    fresco:roundedCornerRadius="4dp" />

+ 24 - 1
app/src/main/res/layout/controller_conversation_info.xml

@@ -129,7 +129,7 @@
                 android:id="@+id/participants_list_category"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:layout_below="@+id/settings"
+                android:layout_below="@+id/category_shared_items"
                 android:visibility="gone"
                 apc:cardBackgroundColor="@color/bg_default"
                 apc:cardElevation="0dp"
@@ -213,6 +213,29 @@
                     tools:visibility="visible" />
 
             </LinearLayout>
+
+
+            <com.yarolegovich.mp.MaterialPreferenceCategory
+                android:id="@+id/category_shared_items"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@id/settings"
+                android:animateLayoutChanges="true"
+                apc:cardBackgroundColor="@color/bg_default"
+                apc:cardElevation="0dp"
+                apc:mpc_title="Shared Items">
+
+                <com.yarolegovich.mp.MaterialStandardPreference
+                    android:id="@+id/show_shared_items_action"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    apc:mp_icon="@drawable/ic_timer_black_24dp"
+                    apc:mp_icon_tint="@color/grey_600"
+                    apc:mp_summary="See all shared photos, voice messages, files, etc."
+                    apc:mp_title="Shared Items" />
+
+            </com.yarolegovich.mp.MaterialPreferenceCategory>
+
         </RelativeLayout>
     </ScrollView>
 </RelativeLayout>

+ 6 - 1
app/src/main/res/menu/menu_conversation.xml

@@ -37,8 +37,13 @@
 
     <item
         android:id="@+id/conversation_info"
-        android:icon="@drawable/ic_info_white_24dp"
         android:orderInCategory="1"
         android:title="@string/nc_conversation_menu_conversation_info"
         app:showAsAction="never" />
+
+    <item
+        android:id="@+id/shared_items"
+        android:orderInCategory="1"
+        android:title="Shared Items"
+        app:showAsAction="never" />
 </menu>

+ 1 - 0
app/src/main/res/values/strings.xml

@@ -512,6 +512,7 @@
     <string name="audio_output_phone">Phone</string>
     <string name="audio_output_dialog_headline">Audio output</string>
     <string name="audio_output_wired_headset">Wired headset</string>
+    <string name="title_attachments">Attachements</string>
 
     <string name="reactions_tab_all">All</string>