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

View file

@ -86,14 +86,21 @@ class ConversationMediaListFragment : SlidingPaneChildFragment() {
goBack() goBack()
} }
viewModel.chatRoomFoundEvent.observe(viewLifecycleOwner) {
it.consume {
(view.parent as? ViewGroup)?.doOnPreDraw {
startPostponedEnterTransition()
viewModel.loadMediaList()
}
}
}
viewModel.mediaList.observe(viewLifecycleOwner) { viewModel.mediaList.observe(viewLifecycleOwner) {
val count = it.size val count = it.size
Log.i( Log.i(
"$TAG Found [$count] media for conversation with local SIP URI [$localSipUri] and remote SIP URI [$remoteSipUri]" "$TAG Found [$count] media for conversation with local SIP URI [$localSipUri] and remote SIP URI [$remoteSipUri]"
) )
(view.parent as? ViewGroup)?.doOnPreDraw { // TODO: FIXME: use Adapter
startPostponedEnterTransition()
}
} }
viewModel.openMediaEvent.observe(viewLifecycleOwner) { viewModel.openMediaEvent.observe(viewLifecycleOwner) {
@ -120,10 +127,6 @@ class ConversationMediaListFragment : SlidingPaneChildFragment() {
bundle.putBoolean("isMedia", true) bundle.putBoolean("isMedia", true)
sharedViewModel.displayFileEvent.value = Event(bundle) sharedViewModel.displayFileEvent.value = Event(bundle)
} }
FileUtils.MimeType.Pdf, FileUtils.MimeType.PlainText -> {
bundle.putBoolean("isMedia", false)
sharedViewModel.displayFileEvent.value = Event(bundle)
}
else -> { else -> {
val intent = Intent(Intent.ACTION_VIEW) val intent = Intent(Intent.ACTION_VIEW)
val contentUri: Uri = 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 package org.linphone.ui.main.chat.viewmodel
import androidx.annotation.UiThread import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.coreContext
@ -41,6 +40,12 @@ class ConversationMediaListViewModel @UiThread constructor() : ViewModel() {
val currentlyDisplayedFileName = MutableLiveData<String>() 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 { val openMediaEvent: MutableLiveData<Event<FileModel>> by lazy {
MutableLiveData<Event<FileModel>>() MutableLiveData<Event<FileModel>>()
} }
@ -62,7 +67,7 @@ class ConversationMediaListViewModel @UiThread constructor() : ViewModel() {
) )
if (room != null && ::chatRoom.isInitialized && chatRoom == room) { if (room != null && ::chatRoom.isInitialized && chatRoom == room) {
Log.i("$TAG Conversation object already in memory, skipping") Log.i("$TAG Conversation object already in memory, skipping")
loadMediaList() chatRoomFoundEvent.postValue(Event(true))
return@postOnCoreThread return@postOnCoreThread
} }
@ -76,7 +81,7 @@ class ConversationMediaListViewModel @UiThread constructor() : ViewModel() {
) { ) {
Log.i("$TAG Conversation object available in sharedViewModel, using it") Log.i("$TAG Conversation object available in sharedViewModel, using it")
chatRoom = room chatRoom = room
loadMediaList() chatRoomFoundEvent.postValue(Event(true))
return@postOnCoreThread return@postOnCoreThread
} }
} }
@ -93,29 +98,35 @@ class ConversationMediaListViewModel @UiThread constructor() : ViewModel() {
) )
if (found != null) { if (found != null) {
chatRoom = found chatRoom = found
loadMediaList() chatRoomFoundEvent.postValue(Event(true))
} }
} }
} }
} }
@WorkerThread @UiThread
private fun loadMediaList() { fun loadMediaList() {
val list = arrayListOf<FileModel>() operationInProgress.value = true
if (::chatRoom.isInitialized) {
val media = chatRoom.mediaContents coreContext.postOnCoreThread {
for (mediaContent in media) { val list = arrayListOf<FileModel>()
val path = mediaContent.filePath.orEmpty() if (::chatRoom.isInitialized) {
val name = mediaContent.name.orEmpty() val media = chatRoom.mediaContents
val size = mediaContent.size.toLong() for (mediaContent in media) {
if (path.isNotEmpty() && name.isNotEmpty()) { val path = mediaContent.filePath.orEmpty()
val model = FileModel(path, name, size) { val name = mediaContent.name.orEmpty()
openMediaEvent.postValue(Event(it)) 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" name="configureEphemeralMessagesClickListener"
type="View.OnClickListener" /> type="View.OnClickListener" />
<variable <variable
name="mediasClickListener" name="mediaClickListener"
type="View.OnClickListener" />
<variable
name="documentsClickListener"
type="View.OnClickListener" /> type="View.OnClickListener" />
<variable <variable
name="conversationMuted" name="conversationMuted"
@ -143,12 +146,12 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/unmute" app:layout_constraintTop_toBottomOf="@id/unmute"
app:layout_constraintBottom_toTopOf="@id/medias"/> app:layout_constraintBottom_toTopOf="@id/media"/>
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
style="@style/default_text_style" style="@style/default_text_style"
android:id="@+id/medias" android:id="@+id/media"
android:onClick="@{mediasClickListener}" android:onClick="@{mediaClickListener}"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/popup_menu_item_top_margin" android:layout_marginTop="@dimen/popup_menu_item_top_margin"
@ -163,6 +166,26 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/ephemeral" 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"/> app:layout_constraintBottom_toTopOf="@id/bottom_anchor"/>
<View <View
@ -173,7 +196,7 @@
app:layout_constraintWidth_max="@dimen/popup_menu_width" app:layout_constraintWidth_max="@dimen/popup_menu_width"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/medias" app:layout_constraintTop_toBottomOf="@id/documents"
app:layout_constraintBottom_toBottomOf="parent" /> app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </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"?> <?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android" <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> <data>
<import type="android.view.View" /> <import type="android.view.View" />
@ -12,74 +13,84 @@
type="org.linphone.ui.main.chat.viewmodel.ConversationMediaListViewModel" /> type="org.linphone.ui.main.chat.viewmodel.ConversationMediaListViewModel" />
</data> </data>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:background="?attr/color_main2_000">
<ImageView <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/back" android:layout_width="match_parent"
android:layout_width="wrap_content" android:layout_height="match_parent"
android:layout_height="0dp" android:background="?attr/color_main2_000">
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 <ImageView
style="@style/main_page_title_style" android:id="@+id/back"
android:id="@+id/title" android:layout_width="wrap_content"
android:layout_width="0dp" android:layout_height="0dp"
android:layout_height="@dimen/top_bar_height" android:adjustViewBounds="true"
android:layout_marginStart="10dp" android:padding="15dp"
android:layout_marginEnd="10dp" android:onClick="@{backClickListener}"
android:text="@string/conversation_media_list_title" android:src="@drawable/caret_left"
app:layout_constraintEnd_toEndOf="parent" android:contentDescription="@string/content_description_go_back_icon"
app:layout_constraintStart_toEndOf="@id/back" app:tint="?attr/color_main1_500"
app:layout_constraintTop_toTopOf="parent"/> app:layout_constraintBottom_toBottomOf="@id/title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/title"/>
<ScrollView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/scrollView" style="@style/main_page_title_style"
android:layout_width="0dp" android:id="@+id/title"
android:layout_height="0dp" android:layout_width="0dp"
android:background="?attr/color_grey_100" android:layout_height="@dimen/top_bar_height"
android:fillViewport="true" android:layout_marginStart="10dp"
app:layout_constraintBottom_toBottomOf="parent" android:layout_marginEnd="10dp"
app:layout_constraintEnd_toEndOf="parent" android:text="@string/conversation_media_list_title"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/title"> app:layout_constraintStart_toEndOf="@id/back"
app:layout_constraintTop_toTopOf="parent"/>
<com.google.android.flexbox.FlexboxLayout <ScrollView
android:id="@+id/files_grid" android:id="@+id/scrollView"
android:layout_width="match_parent" 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" android:layout_height="wrap_content"
app:flexWrap="wrap" android:text="@string/conversation_no_media_found"
app:alignItems="stretch" android:textColor="?attr/color_main2_600"
app:alignContent="flex_start" android:textSize="16sp"
entries="@{viewModel.mediaList}" android:visibility="@{viewModel.mediaList.empty ? View.VISIBLE : View.GONE}"
layout="@{@layout/chat_media_content_grid_cell}"/> 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 <include
style="@style/default_text_style_800" layout="@layout/operation_in_progress"
android:id="@+id/no_result" bind:visibility="@{viewModel.operationInProgress}" />
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"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout> </layout>

View file

@ -43,6 +43,14 @@
app:exitAnim="@anim/slide_out_left" app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left" app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" /> 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> </fragment>
<action <action
@ -108,4 +116,17 @@
app:argType="string" /> app:argType="string" />
</fragment> </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> </navigation>

View file

@ -428,8 +428,10 @@
<string name="conversation_menu_search_in_messages">Search</string> <string name="conversation_menu_search_in_messages">Search</string>
<string name="conversation_menu_go_to_info">Conversation info</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_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_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_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_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> <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_event_ephemeral_messages_lifetime_changed">Ephemeral lifetime is now %s</string>
<string name="conversation_media_list_title">Shared media</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_read_title">Read %s</string>
<string name="message_delivery_info_received_title">Received %s</string> <string name="message_delivery_info_received_title">Received %s</string>