Added documents menu (like media)

This commit is contained in:
Sylvain Berfini 2024-02-13 16:41:31 +01:00
parent 109b5e71e2
commit 598ac6cbd3
11 changed files with 615 additions and 93 deletions

View file

@ -0,0 +1,149 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of linphone-android
* (see https://www.linphone.org).
*
* 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 org.linphone.ui.main.chat.fragment
import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.UiThread
import androidx.core.view.doOnPreDraw
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import org.linphone.R
import org.linphone.core.tools.Log
import org.linphone.databinding.ChatDocumentsFragmentBinding
import org.linphone.ui.main.MainActivity
import org.linphone.ui.main.chat.viewmodel.ConversationDocumentsListViewModel
import org.linphone.ui.main.fragment.SlidingPaneChildFragment
import org.linphone.utils.Event
import org.linphone.utils.FileUtils
@UiThread
class ConversationDocumentsListFragment : SlidingPaneChildFragment() {
companion object {
private const val TAG = "[Conversation Documents List Fragment]"
}
private lateinit var binding: ChatDocumentsFragmentBinding
private lateinit var viewModel: ConversationDocumentsListViewModel
private val args: ConversationMediaListFragmentArgs by navArgs()
override fun goBack(): Boolean {
return findNavController().popBackStack()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = ChatDocumentsFragmentBinding.inflate(layoutInflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
postponeEnterTransition()
super.onViewCreated(view, savedInstanceState)
binding.lifecycleOwner = viewLifecycleOwner
viewModel = ViewModelProvider(this)[ConversationDocumentsListViewModel::class.java]
binding.viewModel = viewModel
val localSipUri = args.localSipUri
val remoteSipUri = args.remoteSipUri
Log.i(
"$TAG Looking up for conversation with local SIP URI [$localSipUri] and remote SIP URI [$remoteSipUri]"
)
val chatRoom = sharedViewModel.displayedChatRoom
viewModel.findChatRoom(chatRoom, localSipUri, remoteSipUri)
binding.setBackClickListener {
goBack()
}
viewModel.chatRoomFoundEvent.observe(viewLifecycleOwner) {
it.consume {
(view.parent as? ViewGroup)?.doOnPreDraw {
startPostponedEnterTransition()
viewModel.loadDocumentsList()
}
}
}
viewModel.documentsList.observe(viewLifecycleOwner) {
val count = it.size
Log.i(
"$TAG Found [$count] documents for conversation with local SIP URI [$localSipUri] and remote SIP URI [$remoteSipUri]"
)
// TODO: FIXME: use Adapter
}
viewModel.openDocumentEvent.observe(viewLifecycleOwner) {
it.consume { model ->
Log.i("$TAG User clicked on file [${model.file}], let's display it in file viewer")
goToFileViewer(model.file)
}
}
}
private fun goToFileViewer(path: String) {
Log.i("$TAG Navigating to file viewer fragment with path [$path]")
val extension = FileUtils.getExtensionFromFileName(path)
val mime = FileUtils.getMimeTypeFromExtension(extension)
val bundle = Bundle()
bundle.apply {
putString("localSipUri", viewModel.localSipUri)
putString("remoteSipUri", viewModel.remoteSipUri)
putString("path", path)
}
when (FileUtils.getMimeType(mime)) {
FileUtils.MimeType.Pdf, FileUtils.MimeType.PlainText -> {
bundle.putBoolean("isMedia", false)
sharedViewModel.displayFileEvent.value = Event(bundle)
}
else -> {
val intent = Intent(Intent.ACTION_VIEW)
val contentUri: Uri =
FileUtils.getPublicFilePath(requireContext(), path)
intent.setDataAndType(contentUri, "file/$mime")
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
try {
requireContext().startActivity(intent)
} catch (anfe: ActivityNotFoundException) {
Log.e("$TAG Can't open file [$path] in third party app: $anfe")
val message = getString(
R.string.toast_no_app_registered_to_handle_content_type_error
)
val icon = R.drawable.file
(requireActivity() as MainActivity).showRedToast(message, icon)
}
}
}
}
}

View file

@ -880,7 +880,7 @@ class ConversationFragment : SlidingPaneChildFragment() {
popupWindow.dismiss()
}
popupView.setMediasClickListener {
popupView.setMediaClickListener {
if (findNavController().currentDestination?.id == R.id.conversationFragment) {
val action =
ConversationFragmentDirections.actionConversationFragmentToConversationMediaListFragment(
@ -892,6 +892,18 @@ class ConversationFragment : SlidingPaneChildFragment() {
popupWindow.dismiss()
}
popupView.setDocumentsClickListener {
if (findNavController().currentDestination?.id == R.id.conversationFragment) {
val action =
ConversationFragmentDirections.actionConversationFragmentToConversationDocumentsListFragment(
localSipUri = viewModel.localSipUri,
remoteSipUri = viewModel.remoteSipUri
)
findNavController().navigate(action)
}
popupWindow.dismiss()
}
// Elevation is for showing a shadow around the popup
popupWindow.elevation = 20f
popupWindow.showAsDropDown(view, 0, 0, Gravity.BOTTOM)

View file

@ -86,14 +86,21 @@ class ConversationMediaListFragment : SlidingPaneChildFragment() {
goBack()
}
viewModel.chatRoomFoundEvent.observe(viewLifecycleOwner) {
it.consume {
(view.parent as? ViewGroup)?.doOnPreDraw {
startPostponedEnterTransition()
viewModel.loadMediaList()
}
}
}
viewModel.mediaList.observe(viewLifecycleOwner) {
val count = it.size
Log.i(
"$TAG Found [$count] media for conversation with local SIP URI [$localSipUri] and remote SIP URI [$remoteSipUri]"
)
(view.parent as? ViewGroup)?.doOnPreDraw {
startPostponedEnterTransition()
}
// TODO: FIXME: use Adapter
}
viewModel.openMediaEvent.observe(viewLifecycleOwner) {
@ -120,10 +127,6 @@ class ConversationMediaListFragment : SlidingPaneChildFragment() {
bundle.putBoolean("isMedia", true)
sharedViewModel.displayFileEvent.value = Event(bundle)
}
FileUtils.MimeType.Pdf, FileUtils.MimeType.PlainText -> {
bundle.putBoolean("isMedia", false)
sharedViewModel.displayFileEvent.value = Event(bundle)
}
else -> {
val intent = Intent(Intent.ACTION_VIEW)
val contentUri: Uri =

View file

@ -0,0 +1,128 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of linphone-android
* (see https://www.linphone.org).
*
* 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 org.linphone.ui.main.chat.viewmodel
import androidx.annotation.UiThread
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.core.ChatRoom
import org.linphone.core.Factory
import org.linphone.core.tools.Log
import org.linphone.ui.main.chat.model.FileModel
import org.linphone.utils.Event
class ConversationDocumentsListViewModel @UiThread constructor() : ViewModel() {
companion object {
private const val TAG = "[Conversation Documents List ViewModel]"
}
val documentsList = MutableLiveData<ArrayList<FileModel>>()
val operationInProgress = MutableLiveData<Boolean>()
val chatRoomFoundEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
val openDocumentEvent: MutableLiveData<Event<FileModel>> by lazy {
MutableLiveData<Event<FileModel>>()
}
private lateinit var chatRoom: ChatRoom
lateinit var localSipUri: String
lateinit var remoteSipUri: String
@UiThread
fun findChatRoom(room: ChatRoom?, localSipUri: String, remoteSipUri: String) {
this.localSipUri = localSipUri
this.remoteSipUri = remoteSipUri
coreContext.postOnCoreThread { core ->
Log.i(
"$TAG Looking for conversation with local SIP URI [$localSipUri] and remote SIP URI [$remoteSipUri]"
)
if (room != null && ::chatRoom.isInitialized && chatRoom == room) {
Log.i("$TAG Conversation object already in memory, skipping")
chatRoomFoundEvent.postValue(Event(true))
return@postOnCoreThread
}
val localAddress = Factory.instance().createAddress(localSipUri)
val remoteAddress = Factory.instance().createAddress(remoteSipUri)
if (room != null && (!::chatRoom.isInitialized || chatRoom != room)) {
if (localAddress?.weakEqual(room.localAddress) == true && remoteAddress?.weakEqual(
room.peerAddress
) == true
) {
Log.i("$TAG Conversation object available in sharedViewModel, using it")
chatRoom = room
chatRoomFoundEvent.postValue(Event(true))
return@postOnCoreThread
}
}
if (localAddress != null && remoteAddress != null) {
Log.i("$TAG Searching for conversation in Core using local & peer SIP addresses")
val found = core.searchChatRoom(
null,
localAddress,
remoteAddress,
arrayOfNulls(
0
)
)
if (found != null) {
chatRoom = found
chatRoomFoundEvent.postValue(Event(true))
}
}
}
}
@UiThread
fun loadDocumentsList() {
operationInProgress.value = true
coreContext.postOnCoreThread {
val list = arrayListOf<FileModel>()
if (::chatRoom.isInitialized) {
val documents = chatRoom.documentContents
for (documentContent in documents) {
val path = documentContent.filePath.orEmpty()
val name = documentContent.name.orEmpty()
val size = documentContent.size.toLong()
if (path.isNotEmpty() && name.isNotEmpty()) {
val model = FileModel(path, name, size) {
openDocumentEvent.postValue(Event(it))
}
list.add(model)
}
}
}
documentsList.postValue(list)
operationInProgress.postValue(false)
}
}
}

View file

@ -20,7 +20,6 @@
package org.linphone.ui.main.chat.viewmodel
import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import org.linphone.LinphoneApplication.Companion.coreContext
@ -41,6 +40,12 @@ class ConversationMediaListViewModel @UiThread constructor() : ViewModel() {
val currentlyDisplayedFileName = MutableLiveData<String>()
val operationInProgress = MutableLiveData<Boolean>()
val chatRoomFoundEvent: MutableLiveData<Event<Boolean>> by lazy {
MutableLiveData<Event<Boolean>>()
}
val openMediaEvent: MutableLiveData<Event<FileModel>> by lazy {
MutableLiveData<Event<FileModel>>()
}
@ -62,7 +67,7 @@ class ConversationMediaListViewModel @UiThread constructor() : ViewModel() {
)
if (room != null && ::chatRoom.isInitialized && chatRoom == room) {
Log.i("$TAG Conversation object already in memory, skipping")
loadMediaList()
chatRoomFoundEvent.postValue(Event(true))
return@postOnCoreThread
}
@ -76,7 +81,7 @@ class ConversationMediaListViewModel @UiThread constructor() : ViewModel() {
) {
Log.i("$TAG Conversation object available in sharedViewModel, using it")
chatRoom = room
loadMediaList()
chatRoomFoundEvent.postValue(Event(true))
return@postOnCoreThread
}
}
@ -93,29 +98,35 @@ class ConversationMediaListViewModel @UiThread constructor() : ViewModel() {
)
if (found != null) {
chatRoom = found
loadMediaList()
chatRoomFoundEvent.postValue(Event(true))
}
}
}
}
@WorkerThread
private fun loadMediaList() {
val list = arrayListOf<FileModel>()
if (::chatRoom.isInitialized) {
val media = chatRoom.mediaContents
for (mediaContent in media) {
val path = mediaContent.filePath.orEmpty()
val name = mediaContent.name.orEmpty()
val size = mediaContent.size.toLong()
if (path.isNotEmpty() && name.isNotEmpty()) {
val model = FileModel(path, name, size) {
openMediaEvent.postValue(Event(it))
@UiThread
fun loadMediaList() {
operationInProgress.value = true
coreContext.postOnCoreThread {
val list = arrayListOf<FileModel>()
if (::chatRoom.isInitialized) {
val media = chatRoom.mediaContents
for (mediaContent in media) {
val path = mediaContent.filePath.orEmpty()
val name = mediaContent.name.orEmpty()
val size = mediaContent.size.toLong()
if (path.isNotEmpty() && name.isNotEmpty()) {
val model = FileModel(path, name, size) {
openMediaEvent.postValue(Event(it))
}
list.add(model)
}
list.add(model)
}
}
mediaList.postValue(list)
operationInProgress.postValue(false)
}
mediaList.postValue(list)
}
}

View file

@ -22,7 +22,10 @@
name="configureEphemeralMessagesClickListener"
type="View.OnClickListener" />
<variable
name="mediasClickListener"
name="mediaClickListener"
type="View.OnClickListener" />
<variable
name="documentsClickListener"
type="View.OnClickListener" />
<variable
name="conversationMuted"
@ -143,12 +146,12 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/unmute"
app:layout_constraintBottom_toTopOf="@id/medias"/>
app:layout_constraintBottom_toTopOf="@id/media"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/medias"
android:onClick="@{mediasClickListener}"
android:id="@+id/media"
android:onClick="@{mediaClickListener}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/popup_menu_item_top_margin"
@ -163,6 +166,26 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/ephemeral"
app:layout_constraintBottom_toTopOf="@id/documents"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style"
android:id="@+id/documents"
android:onClick="@{documentsClickListener}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/popup_menu_item_top_margin"
android:text="@string/conversation_menu_documents_files"
android:textSize="14sp"
android:textColor="?attr/color_main2_500"
android:maxLines="1"
android:ellipsize="end"
android:drawableStart="@drawable/file_pdf"
android:drawablePadding="5dp"
app:drawableTint="?attr/color_main2_700"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/media"
app:layout_constraintBottom_toTopOf="@id/bottom_anchor"/>
<View
@ -173,7 +196,7 @@
app:layout_constraintWidth_max="@dimen/popup_menu_width"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/medias"
app:layout_constraintTop_toBottomOf="@id/documents"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View" />
<variable
name="model"
type="org.linphone.ui.main.chat.model.FileModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp">
<ImageView
android:id="@+id/file_icon"
android:layout_width="@dimen/chat_bubble_grid_image_size"
android:layout_height="@dimen/chat_bubble_grid_image_size"
android:onClick="@{() -> model.onClick()}"
android:adjustViewBounds="true"
android:padding="18dp"
android:src="@{model.isWaitingToBeDownloaded ? @drawable/download_simple : model.isPdf ? @drawable/file_pdf : model.isAudio ? @drawable/file_audio : @drawable/file, default=@drawable/file_pdf}"
android:background="@drawable/shape_squircle_main2_200_left"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:tint="?attr/color_main2_600" />
<View
android:id="@+id/file_background"
android:onClick="@{() -> model.onClick()}"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@drawable/shape_squircle_white_right"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/file_icon"
app:layout_constraintEnd_toEndOf="parent"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_700"
android:id="@+id/file_name"
android:onClick="@{() -> model.onClick()}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="@{model.fileName, default=`Lorem_ipsum.pdf`}"
android:textColor="?attr/color_main2_600"
android:textSize="13sp"
android:maxLines="1"
android:ellipsize="middle"
android:background="@drawable/shape_squircle_white_r10_background"
app:layout_constraintStart_toEndOf="@id/file_icon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:bind="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<variable
name="backClickListener"
type="View.OnClickListener" />
<variable
name="viewModel"
type="org.linphone.ui.main.chat.viewmodel.ConversationDocumentsListViewModel" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/color_main2_000">
<ImageView
android:id="@+id/back"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:adjustViewBounds="true"
android:padding="15dp"
android:onClick="@{backClickListener}"
android:src="@drawable/caret_left"
android:contentDescription="@string/content_description_go_back_icon"
app:tint="?attr/color_main1_500"
app:layout_constraintBottom_toBottomOf="@id/title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/title"/>
<androidx.appcompat.widget.AppCompatTextView
style="@style/main_page_title_style"
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="@dimen/top_bar_height"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:text="@string/conversation_document_list_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/back"
app:layout_constraintTop_toTopOf="parent"/>
<ScrollView
android:id="@+id/scrollView"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?attr/color_grey_100"
android:fillViewport="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title">
<LinearLayout
android:id="@+id/files_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
entries="@{viewModel.documentsList}"
layout="@{@layout/chat_document_content_list_cell}"/>
</ScrollView>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_800"
android:id="@+id/no_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/conversation_no_document_found"
android:textColor="?attr/color_main2_600"
android:textSize="16sp"
android:visibility="@{viewModel.documentsList.empty ? View.VISIBLE : View.GONE}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<include
layout="@layout/operation_in_progress"
bind:visibility="@{viewModel.operationInProgress}" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:bind="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
@ -12,74 +13,84 @@
type="org.linphone.ui.main.chat.viewmodel.ConversationMediaListViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/color_main2_000">
android:layout_height="match_parent">
<ImageView
android:id="@+id/back"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:adjustViewBounds="true"
android:padding="15dp"
android:onClick="@{backClickListener}"
android:src="@drawable/caret_left"
android:contentDescription="@string/content_description_go_back_icon"
app:tint="?attr/color_main1_500"
app:layout_constraintBottom_toBottomOf="@id/title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/title"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/color_main2_000">
<androidx.appcompat.widget.AppCompatTextView
style="@style/main_page_title_style"
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="@dimen/top_bar_height"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:text="@string/conversation_media_list_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/back"
app:layout_constraintTop_toTopOf="parent"/>
<ImageView
android:id="@+id/back"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:adjustViewBounds="true"
android:padding="15dp"
android:onClick="@{backClickListener}"
android:src="@drawable/caret_left"
android:contentDescription="@string/content_description_go_back_icon"
app:tint="?attr/color_main1_500"
app:layout_constraintBottom_toBottomOf="@id/title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/title"/>
<ScrollView
android:id="@+id/scrollView"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?attr/color_grey_100"
android:fillViewport="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title">
<androidx.appcompat.widget.AppCompatTextView
style="@style/main_page_title_style"
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="@dimen/top_bar_height"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:text="@string/conversation_media_list_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/back"
app:layout_constraintTop_toTopOf="parent"/>
<com.google.android.flexbox.FlexboxLayout
android:id="@+id/files_grid"
android:layout_width="match_parent"
<ScrollView
android:id="@+id/scrollView"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?attr/color_grey_100"
android:fillViewport="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title">
<com.google.android.flexbox.FlexboxLayout
android:id="@+id/files_grid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:flexWrap="wrap"
app:alignItems="stretch"
app:alignContent="flex_start"
entries="@{viewModel.mediaList}"
layout="@{@layout/chat_media_content_grid_cell}"/>
</ScrollView>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_800"
android:id="@+id/no_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:flexWrap="wrap"
app:alignItems="stretch"
app:alignContent="flex_start"
entries="@{viewModel.mediaList}"
layout="@{@layout/chat_media_content_grid_cell}"/>
android:text="@string/conversation_no_media_found"
android:textColor="?attr/color_main2_600"
android:textSize="16sp"
android:visibility="@{viewModel.mediaList.empty ? View.VISIBLE : View.GONE}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style_800"
android:id="@+id/no_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/conversation_no_media_found"
android:textColor="?attr/color_main2_600"
android:textSize="16sp"
android:visibility="@{viewModel.mediaList.empty ? View.VISIBLE : View.GONE}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<include
layout="@layout/operation_in_progress"
bind:visibility="@{viewModel.operationInProgress}" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View file

@ -43,6 +43,14 @@
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" />
<action
android:id="@+id/action_conversationFragment_to_conversationDocumentsListFragment"
app:destination="@id/conversationDocumentsListFragment"
app:launchSingleTop="true"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" />
</fragment>
<action
@ -108,4 +116,17 @@
app:argType="string" />
</fragment>
<fragment
android:id="@+id/conversationDocumentsListFragment"
android:name="org.linphone.ui.main.chat.fragment.ConversationDocumentsListFragment"
android:label="ConversationDocumentsListFragment"
tools:layout="@layout/chat_documents_fragment">
<argument
android:name="localSipUri"
app:argType="string" />
<argument
android:name="remoteSipUri"
app:argType="string" />
</fragment>
</navigation>

View file

@ -428,8 +428,10 @@
<string name="conversation_menu_search_in_messages">Search</string>
<string name="conversation_menu_go_to_info">Conversation info</string>
<string name="conversation_menu_configure_ephemeral_messages">Ephemeral messages</string>
<string name="conversation_menu_media_files">Medias</string>
<string name="conversation_menu_media_files">Media</string>
<string name="conversation_menu_documents_files">Documents</string>
<string name="conversation_no_media_found">No media found</string>
<string name="conversation_no_document_found">No document found</string>
<string name="conversation_filter_no_matching_result">No matching result</string>
<string name="conversation_end_to_end_encrypted_event_title">End-to-end encrypted conversation</string>
<string name="conversation_end_to_end_encrypted_event_subtitle">Messages in this conversation are e2e encrypted. Only your correspondent can decrypt them.</string>
@ -462,6 +464,7 @@
<string name="conversation_event_ephemeral_messages_lifetime_changed">Ephemeral lifetime is now %s</string>
<string name="conversation_media_list_title">Shared media</string>
<string name="conversation_document_list_title">Shared documents</string>
<string name="message_delivery_info_read_title">Read %s</string>
<string name="message_delivery_info_received_title">Received %s</string>