From ccd53b74db5999763112cee3ac45519a0fc844e6 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Sun, 12 May 2024 09:30:57 +0200 Subject: [PATCH] Moved file & media viewer in separated activities --- .gitignore | 1 + .idea/deploymentTargetSelector.xml | 10 - app/src/main/AndroidManifest.xml | 10 + .../FileViewerActivity.kt} | 126 +++----- .../ui/file_viewer/MediaViewerActivity.kt | 252 ++++++++++++++++ .../adapter/MediaListAdapter.kt | 30 +- .../adapter/PdfPagesListAdapter.kt | 4 +- .../fragment/MediaViewerFragment.kt | 20 +- .../viewmodel/FileViewModel.kt | 9 +- .../viewmodel/MediaListViewModel.kt | 96 ++++++ .../viewmodel/MediaViewModel.kt | 12 +- .../chat/adapter/ConversationsFilesAdapter.kt | 2 +- .../ConversationDocumentsListFragment.kt | 11 +- .../chat/fragment/ConversationFragment.kt | 13 +- .../fragment/ConversationMediaListFragment.kt | 17 +- .../fragment/ConversationsListFragment.kt | 32 +- .../ui/main/chat/model/EventLogModel.kt | 2 +- .../linphone/ui/main/chat/model/FileModel.kt | 23 +- .../ui/main/chat/model/MessageModel.kt | 23 +- .../ConversationDocumentsListViewModel.kt | 9 +- .../ConversationMediaListViewModel.kt | 18 +- .../chat/viewmodel/ConversationViewModel.kt | 9 +- .../SendMessageInConversationViewModel.kt | 8 +- .../fragment/MediaListViewerFragment.kt | 279 ------------------ .../ui/main/help/fragment/DebugFragment.kt | 12 +- .../sso/viewmodel/SingleSignOnViewModel.kt | 9 +- .../ui/main/viewmodel/SharedMainViewModel.kt | 2 - .../main/java/org/linphone/utils/FileUtils.kt | 7 +- .../layout/chat_bubble_content_grid_cell.xml | 2 +- .../chat_bubble_single_media_content.xml | 2 +- .../layout/chat_media_content_grid_cell.xml | 2 +- .../main/res/layout/chat_media_fragment.xml | 113 ++++--- ...ent.xml => file_media_viewer_activity.xml} | 26 +- .../file_media_viewer_child_fragment.xml | 9 +- ..._fragment.xml => file_viewer_activity.xml} | 26 +- .../main/res/navigation/main_nav_graph.xml | 48 --- 36 files changed, 654 insertions(+), 620 deletions(-) delete mode 100644 .idea/deploymentTargetSelector.xml rename app/src/main/java/org/linphone/ui/{main/file_media_viewer/fragment/FileViewerFragment.kt => file_viewer/FileViewerActivity.kt} (56%) create mode 100644 app/src/main/java/org/linphone/ui/file_viewer/MediaViewerActivity.kt rename app/src/main/java/org/linphone/ui/{main/file_media_viewer => file_viewer}/adapter/MediaListAdapter.kt (59%) rename app/src/main/java/org/linphone/ui/{main/file_media_viewer => file_viewer}/adapter/PdfPagesListAdapter.kt (94%) rename app/src/main/java/org/linphone/ui/{main/file_media_viewer => file_viewer}/fragment/MediaViewerFragment.kt (87%) rename app/src/main/java/org/linphone/ui/{main/file_media_viewer => file_viewer}/viewmodel/FileViewModel.kt (97%) create mode 100644 app/src/main/java/org/linphone/ui/file_viewer/viewmodel/MediaListViewModel.kt rename app/src/main/java/org/linphone/ui/{main/file_media_viewer => file_viewer}/viewmodel/MediaViewModel.kt (92%) delete mode 100644 app/src/main/java/org/linphone/ui/main/file_media_viewer/fragment/MediaListViewerFragment.kt rename app/src/main/res/layout/{file_media_viewer_fragment.xml => file_media_viewer_activity.xml} (83%) rename app/src/main/res/layout/{file_viewer_fragment.xml => file_viewer_activity.xml} (87%) diff --git a/.gitignore b/.gitignore index db3afa4ff..19acc0284 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ /.idea/navEditor.xml /.idea/assetWizardSettings.xml .idea/deploymentTargetDropDown.xml +.idea/deploymentTargetSelector.xml .DS_Store /build /captures diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml deleted file mode 100644 index b268ef36c..000000000 --- a/.idea/deploymentTargetSelector.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5ed8e2571..725570566 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -104,6 +104,16 @@ + + + + . - */ -package org.linphone.ui.main.file_media_viewer.fragment +package org.linphone.ui.file_viewer import android.app.Activity import android.content.Intent import android.net.Uri import android.os.Bundle import android.util.DisplayMetrics -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup import androidx.annotation.UiThread import androidx.core.content.FileProvider -import androidx.core.view.doOnPreDraw +import androidx.databinding.DataBindingUtil import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope -import androidx.navigation.fragment.findNavController -import androidx.navigation.fragment.navArgs -import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback +import androidx.viewpager2.widget.ViewPager2 import java.io.File import kotlinx.coroutines.launch import org.linphone.R import org.linphone.core.tools.Log -import org.linphone.databinding.FileViewerFragmentBinding -import org.linphone.ui.main.file_media_viewer.adapter.PdfPagesListAdapter -import org.linphone.ui.main.file_media_viewer.viewmodel.FileViewModel -import org.linphone.ui.main.fragment.GenericMainFragment +import org.linphone.databinding.FileViewerActivityBinding +import org.linphone.ui.GenericActivity +import org.linphone.ui.file_viewer.adapter.PdfPagesListAdapter +import org.linphone.ui.file_viewer.viewmodel.FileViewModel import org.linphone.utils.FileUtils @UiThread -class FileViewerFragment : GenericMainFragment() { +class FileViewerActivity : GenericActivity() { companion object { - private const val TAG = "[File Viewer Fragment]" + private const val TAG = "[File Viewer Activity]" private const val EXPORT_FILE_AS_DOCUMENT = 10 } - private lateinit var binding: FileViewerFragmentBinding + private lateinit var binding: FileViewerActivityBinding private lateinit var viewModel: FileViewModel private lateinit var adapter: PdfPagesListAdapter - private val args: FileViewerFragmentArgs by navArgs() - - private val pageChangedListener = object : OnPageChangeCallback() { + private val pageChangedListener = object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { viewModel.pdfCurrentPage.value = (position + 1).toString() } } - private var navBarDefaultColor: Int = -1 + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.navigationBarColor = getColor(R.color.gray_900) - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - binding = FileViewerFragmentBinding.inflate(layoutInflater) - return binding.root - } - - override fun goBack(): Boolean { - return findNavController().popBackStack() - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - postponeEnterTransition() - super.onViewCreated(view, savedInstanceState) - - navBarDefaultColor = requireActivity().window.navigationBarColor + binding = DataBindingUtil.setContentView(this, R.layout.file_viewer_activity) + binding.lifecycleOwner = this + setUpToastsArea(binding.toastsArea) viewModel = ViewModelProvider(this)[FileViewModel::class.java] - - binding.lifecycleOwner = viewLifecycleOwner binding.viewModel = viewModel - observeToastEvents(viewModel) - val path = args.path - val preLoadedContent = args.content + val args = intent.extras + val path = args?.getString("path") + if (path.isNullOrEmpty()) { + finish() + return + } + + val preLoadedContent = args.getString("content") Log.i( "$TAG Path argument is [$path], pre loaded text content is ${if (preLoadedContent.isNullOrEmpty()) "not available" else "available, using it"}" ) viewModel.loadFile(path, preLoadedContent) binding.setBackClickListener { - goBack() + finish() } - viewModel.fileReadyEvent.observe(viewLifecycleOwner) { + viewModel.fileReadyEvent.observe(this) { it.consume { done -> - if (done) { - (view.parent as? ViewGroup)?.doOnPreDraw { - startPostponedEnterTransition() - } - } else { - (view.parent as? ViewGroup)?.doOnPreDraw { - Log.e("$TAG Failed to open file, going back") - goBack() - } + if (!done) { + finish() + Log.e("$TAG Failed to open file, going back") } } } @@ -124,7 +82,7 @@ class FileViewerFragment : GenericMainFragment() { shareFile() } - viewModel.pdfRendererReadyEvent.observe(viewLifecycleOwner) { + viewModel.pdfRendererReadyEvent.observe(this) { it.consume { Log.i("$TAG PDF renderer is ready, attaching adapter to ViewPager") if (viewModel.screenWidth == 0 || viewModel.screenHeight == 0) { @@ -136,7 +94,7 @@ class FileViewerFragment : GenericMainFragment() { } } - viewModel.exportPlainTextFileEvent.observe(viewLifecycleOwner) { + viewModel.exportPlainTextFileEvent.observe(this) { it.consume { name -> val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) @@ -147,7 +105,7 @@ class FileViewerFragment : GenericMainFragment() { } } - viewModel.exportPdfEvent.observe(viewLifecycleOwner) { + viewModel.exportPdfEvent.observe(this) { it.consume { name -> val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) @@ -160,9 +118,6 @@ class FileViewerFragment : GenericMainFragment() { } override fun onResume() { - // Force this navigation bar color - requireActivity().window.navigationBarColor = requireContext().getColor(R.color.gray_900) - super.onResume() updateScreenSize() @@ -174,13 +129,6 @@ class FileViewerFragment : GenericMainFragment() { super.onPause() } - override fun onDestroy() { - // Reset default navigation bar color - requireActivity().window.navigationBarColor = navBarDefaultColor - - super.onDestroy() - } - @Deprecated("Deprecated in Java") override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (requestCode == EXPORT_FILE_AS_DOCUMENT && resultCode == Activity.RESULT_OK) { @@ -194,7 +142,7 @@ class FileViewerFragment : GenericMainFragment() { private fun updateScreenSize() { val displayMetrics = DisplayMetrics() - requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics) + windowManager.defaultDisplay.getMetrics(displayMetrics) viewModel.screenHeight = displayMetrics.heightPixels viewModel.screenWidth = displayMetrics.widthPixels Log.i( @@ -206,15 +154,15 @@ class FileViewerFragment : GenericMainFragment() { lifecycleScope.launch { val filePath = FileUtils.getProperFilePath(viewModel.getFilePath()) val copy = FileUtils.getFilePath( - requireContext(), + baseContext, Uri.parse(filePath), overrideExisting = true, copyToCache = true ) if (!copy.isNullOrEmpty()) { val publicUri = FileProvider.getUriForFile( - requireContext(), - requireContext().getString(R.string.file_provider), + baseContext, + getString(R.string.file_provider), File(copy) ) Log.i("$TAG Public URI for file is [$publicUri], starting intent chooser") diff --git a/app/src/main/java/org/linphone/ui/file_viewer/MediaViewerActivity.kt b/app/src/main/java/org/linphone/ui/file_viewer/MediaViewerActivity.kt new file mode 100644 index 000000000..ece75a774 --- /dev/null +++ b/app/src/main/java/org/linphone/ui/file_viewer/MediaViewerActivity.kt @@ -0,0 +1,252 @@ +package org.linphone.ui.file_viewer + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import androidx.annotation.UiThread +import androidx.core.content.FileProvider +import androidx.databinding.DataBindingUtil +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import androidx.viewpager2.widget.ViewPager2 +import java.io.File +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.linphone.R +import org.linphone.core.tools.Log +import org.linphone.databinding.FileMediaViewerActivityBinding +import org.linphone.ui.GenericActivity +import org.linphone.ui.file_viewer.adapter.MediaListAdapter +import org.linphone.ui.file_viewer.viewmodel.MediaListViewModel +import org.linphone.ui.main.chat.model.FileModel +import org.linphone.utils.AppUtils +import org.linphone.utils.FileUtils + +@UiThread +class MediaViewerActivity : GenericActivity() { + companion object { + private const val TAG = "[Media Viewer Activity]" + } + + private lateinit var binding: FileMediaViewerActivityBinding + + private lateinit var adapter: MediaListAdapter + + private lateinit var viewModel: MediaListViewModel + + private lateinit var viewPager: ViewPager2 + + private val pageListener = object : ViewPager2.OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + val list = viewModel.mediaList.value.orEmpty() + if (position >= 0 && position < list.size) { + val model = list[position] + viewModel.currentlyDisplayedFileName.value = "${model.fileName}\n${model.dateTime}" + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.navigationBarColor = getColor(R.color.gray_900) + + binding = DataBindingUtil.setContentView(this, R.layout.file_media_viewer_activity) + binding.lifecycleOwner = this + setUpToastsArea(binding.toastsArea) + + viewModel = ViewModelProvider(this)[MediaListViewModel::class.java] + binding.viewModel = viewModel + + adapter = MediaListAdapter(this, viewModel) { fullScreen -> + viewModel.fullScreenMode.value = fullScreen + } + + viewPager = binding.mediaViewPager + viewPager.adapter = adapter + viewPager.registerOnPageChangeCallback(pageListener) + + val args = intent.extras + if (args == null) { + finish() + return + } + + val path = args.getString("path", "") + if (path.isNullOrEmpty()) { + finish() + return + } + + val timestamp = args.getLong("timestamp", -1) + val isEncrypted = args.getBoolean("isEncrypted", false) + val originalPath = args.getString("originalPath", "") + viewModel.initTempModel(path, timestamp, isEncrypted, originalPath) + + val localSipUri = args.getString("localSipUri").orEmpty() + val remoteSipUri = args.getString("remoteSipUri").orEmpty() + Log.i( + "$TAG Looking up for conversation with local SIP URI [$localSipUri] and remote SIP URI [$remoteSipUri] trying to display file [$path]" + ) + viewModel.findChatRoom(null, localSipUri, remoteSipUri) + + viewModel.mediaList.observe(this) { + updateMediaList(path, it) + } + + binding.setBackClickListener { + finish() + } + + binding.setShareClickListener { + shareFile() + } + + binding.setExportClickListener { + exportFile() + } + } + + override fun onDestroy() { + if (::viewPager.isInitialized) { + viewPager.unregisterOnPageChangeCallback(pageListener) + } + + super.onDestroy() + } + + private fun updateMediaList(path: String, list: List) { + val count = list.size + Log.i("$TAG Found [$count] media for conversation") + + var index = list.indexOfFirst { model -> + model.path == path + } + + if (index == -1) { + Log.d( + "$TAG Path [$path] not found in media list (expected if VFS is enabled), trying using file name" + ) + val fileName = FileUtils.getNameFromFilePath(path) + val underscore = fileName.indexOf("_") + val originalFileName = if (underscore != -1 && underscore < 2) { + fileName.subSequence(underscore, fileName.length) + } else { + fileName + } + index = list.indexOfFirst { model -> + model.path.endsWith(originalFileName) + } + if (index == -1) { + Log.w( + "$TAG Path [$path] not found in media list using either path or filename [$originalFileName]" + ) + } + } + + val position = if (index == -1) { + Log.e( + "$TAG File [$path] not found, using most recent one instead!" + ) + val message = getString(R.string.conversation_media_not_found_toast) + val icon = R.drawable.warning_circle + showRedToast(message, icon) + + 0 + } else { + index + } + + viewPager.setCurrentItem(position, false) + viewPager.offscreenPageLimit = 1 + } + + private fun exportFile() { + val list = viewModel.mediaList.value.orEmpty() + val currentItem = binding.mediaViewPager.currentItem + val model = if (currentItem >= 0 && currentItem < list.size) list[currentItem] else null + if (model != null) { + val filePath = model.path + lifecycleScope.launch { + withContext(Dispatchers.IO) { + Log.i( + "$TAG Export file [$filePath] to Android's MediaStore" + ) + val mediaStorePath = FileUtils.addContentToMediaStore(filePath) + if (mediaStorePath.isNotEmpty()) { + Log.i( + "$TAG File [$filePath] has been successfully exported to MediaStore" + ) + val message = AppUtils.getString( + R.string.toast_file_successfully_exported_to_media_store + ) + showGreenToast( + message, + R.drawable.check + ) + } else { + Log.e( + "$TAG Failed to export file [$filePath] to MediaStore!" + ) + val message = AppUtils.getString( + R.string.toast_export_file_to_media_store_error + ) + showRedToast( + message, + R.drawable.warning_circle + ) + } + } + } + } else { + Log.e( + "$TAG Failed to get FileModel at index [$currentItem], only [${list.size}] items in list" + ) + } + } + + private fun shareFile() { + val list = viewModel.mediaList.value.orEmpty() + val currentItem = binding.mediaViewPager.currentItem + val model = if (currentItem >= 0 && currentItem < list.size) list[currentItem] else null + if (model != null) { + lifecycleScope.launch { + val filePath = FileUtils.getProperFilePath(model.path) + val copy = FileUtils.getFilePath( + baseContext, + Uri.parse(filePath), + overrideExisting = true, + copyToCache = true + ) + if (!copy.isNullOrEmpty()) { + val publicUri = FileProvider.getUriForFile( + baseContext, + getString(R.string.file_provider), + File(copy) + ) + Log.i( + "$TAG Public URI for file is [$publicUri], starting intent chooser" + ) + + val sendIntent: Intent = Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_STREAM, publicUri) + putExtra(Intent.EXTRA_SUBJECT, model.fileName) + type = model.mimeTypeString + } + + val shareIntent = Intent.createChooser(sendIntent, null) + startActivity(shareIntent) + } else { + Log.e( + "$TAG Failed to copy file [$filePath] to share!" + ) + } + } + } else { + Log.e( + "$TAG Failed to get FileModel at index [$currentItem], only [${list.size}] items in list" + ) + } + } +} diff --git a/app/src/main/java/org/linphone/ui/main/file_media_viewer/adapter/MediaListAdapter.kt b/app/src/main/java/org/linphone/ui/file_viewer/adapter/MediaListAdapter.kt similarity index 59% rename from app/src/main/java/org/linphone/ui/main/file_media_viewer/adapter/MediaListAdapter.kt rename to app/src/main/java/org/linphone/ui/file_viewer/adapter/MediaListAdapter.kt index 1872f9fc1..d5c28dd89 100644 --- a/app/src/main/java/org/linphone/ui/main/file_media_viewer/adapter/MediaListAdapter.kt +++ b/app/src/main/java/org/linphone/ui/file_viewer/adapter/MediaListAdapter.kt @@ -17,18 +17,22 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.ui.main.file_media_viewer.adapter +package org.linphone.ui.file_viewer.adapter import android.os.Bundle import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity import androidx.viewpager2.adapter.FragmentStateAdapter import org.linphone.core.tools.Log -import org.linphone.ui.main.chat.viewmodel.ConversationMediaListViewModel -import org.linphone.ui.main.file_media_viewer.fragment.MediaViewerFragment +import org.linphone.ui.file_viewer.fragment.MediaViewerFragment +import org.linphone.ui.file_viewer.viewmodel.MediaListViewModel -class MediaListAdapter(fragment: Fragment, private val viewModel: ConversationMediaListViewModel) : FragmentStateAdapter( - fragment -) { +class MediaListAdapter( + fragmentActivity: FragmentActivity, + private val viewModel: MediaListViewModel, + private val lambda: ((fullScreen: Boolean) -> Unit) +) : + FragmentStateAdapter(fragmentActivity) { companion object { private const val TAG = "[Media List Adapter]" } @@ -37,12 +41,22 @@ class MediaListAdapter(fragment: Fragment, private val viewModel: ConversationMe return viewModel.mediaList.value.orEmpty().size } + override fun getItemId(position: Int): Long { + return viewModel.mediaList.value.orEmpty().getOrNull(position)?.originalPath.hashCode().toLong() + } + + override fun containsItem(itemId: Long): Boolean { + return viewModel.mediaList.value.orEmpty().any { it.originalPath.hashCode().toLong() == itemId } + } + override fun createFragment(position: Int): Fragment { val fragment = MediaViewerFragment() + fragment.fullScreenChanged = lambda fragment.arguments = Bundle().apply { - val path = viewModel.mediaList.value.orEmpty().getOrNull(position)?.file - Log.i("$TAG Path is [$path] for position [$position]") + val path = viewModel.mediaList.value.orEmpty().getOrNull(position)?.path + Log.d("$TAG Path is [$path] for position [$position]") putString("path", path) + putBoolean("fullScreen", viewModel.fullScreenMode.value == true) } return fragment } diff --git a/app/src/main/java/org/linphone/ui/main/file_media_viewer/adapter/PdfPagesListAdapter.kt b/app/src/main/java/org/linphone/ui/file_viewer/adapter/PdfPagesListAdapter.kt similarity index 94% rename from app/src/main/java/org/linphone/ui/main/file_media_viewer/adapter/PdfPagesListAdapter.kt rename to app/src/main/java/org/linphone/ui/file_viewer/adapter/PdfPagesListAdapter.kt index b960e8ba0..b61ed5b10 100644 --- a/app/src/main/java/org/linphone/ui/main/file_media_viewer/adapter/PdfPagesListAdapter.kt +++ b/app/src/main/java/org/linphone/ui/file_viewer/adapter/PdfPagesListAdapter.kt @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.ui.main.file_media_viewer.adapter +package org.linphone.ui.file_viewer.adapter import android.view.LayoutInflater import android.view.View @@ -25,7 +25,7 @@ import android.view.ViewGroup import android.widget.ImageView import androidx.recyclerview.widget.RecyclerView import org.linphone.R -import org.linphone.ui.main.file_media_viewer.viewmodel.FileViewModel +import org.linphone.ui.file_viewer.viewmodel.FileViewModel class PdfPagesListAdapter(private val viewModel: FileViewModel) : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PdfPageViewHolder { diff --git a/app/src/main/java/org/linphone/ui/main/file_media_viewer/fragment/MediaViewerFragment.kt b/app/src/main/java/org/linphone/ui/file_viewer/fragment/MediaViewerFragment.kt similarity index 87% rename from app/src/main/java/org/linphone/ui/main/file_media_viewer/fragment/MediaViewerFragment.kt rename to app/src/main/java/org/linphone/ui/file_viewer/fragment/MediaViewerFragment.kt index 4784116f3..d635eee83 100644 --- a/app/src/main/java/org/linphone/ui/main/file_media_viewer/fragment/MediaViewerFragment.kt +++ b/app/src/main/java/org/linphone/ui/file_viewer/fragment/MediaViewerFragment.kt @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.ui.main.file_media_viewer.fragment +package org.linphone.ui.file_viewer.fragment import android.os.Bundle import android.view.LayoutInflater @@ -27,9 +27,10 @@ import androidx.annotation.UiThread import androidx.lifecycle.ViewModelProvider import org.linphone.core.tools.Log import org.linphone.databinding.FileMediaViewerChildFragmentBinding -import org.linphone.ui.main.file_media_viewer.viewmodel.MediaViewModel +import org.linphone.ui.file_viewer.viewmodel.MediaViewModel import org.linphone.ui.main.fragment.GenericMainFragment import org.linphone.ui.main.viewmodel.SharedMainViewModel +import org.linphone.utils.FileUtils @UiThread class MediaViewerFragment : GenericMainFragment() { @@ -41,6 +42,8 @@ class MediaViewerFragment : GenericMainFragment() { private lateinit var viewModel: MediaViewModel + var fullScreenChanged: ((fullScreen: Boolean) -> Unit)? = null + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -58,6 +61,7 @@ class MediaViewerFragment : GenericMainFragment() { } viewModel = ViewModelProvider(this)[MediaViewModel::class.java] + viewModel.fullScreenMode.value = arguments?.getBoolean("fullScreen", true) ?: true binding.lifecycleOwner = viewLifecycleOwner binding.viewModel = viewModel @@ -73,7 +77,8 @@ class MediaViewerFragment : GenericMainFragment() { return } - Log.i("$TAG Path argument is [$path]") + val exists = FileUtils.doesFileExist(path) + Log.i("$TAG Path argument is [$path], it ${if (exists) "exists" else "doesn't exist"}") viewModel.loadFile(path) viewModel.isVideo.observe(viewLifecycleOwner) { isVideo -> @@ -94,18 +99,15 @@ class MediaViewerFragment : GenericMainFragment() { } } - viewModel.fullScreenMode.observe(viewLifecycleOwner) { - if (it != sharedViewModel.mediaViewerFullScreenMode.value) { - sharedViewModel.mediaViewerFullScreenMode.value = it - } + binding.setToggleFullScreenModeClickListener { + viewModel.toggleFullScreen() + fullScreenChanged?.invoke(viewModel.fullScreenMode.value == true) } } override fun onResume() { super.onResume() - viewModel.fullScreenMode.value = sharedViewModel.mediaViewerFullScreenMode.value - if (viewModel.isVideo.value == true) { Log.i("$TAG Resumed, starting video player") binding.videoPlayer.start() diff --git a/app/src/main/java/org/linphone/ui/main/file_media_viewer/viewmodel/FileViewModel.kt b/app/src/main/java/org/linphone/ui/file_viewer/viewmodel/FileViewModel.kt similarity index 97% rename from app/src/main/java/org/linphone/ui/main/file_media_viewer/viewmodel/FileViewModel.kt rename to app/src/main/java/org/linphone/ui/file_viewer/viewmodel/FileViewModel.kt index 19d3e419c..1d3d0e219 100644 --- a/app/src/main/java/org/linphone/ui/main/file_media_viewer/viewmodel/FileViewModel.kt +++ b/app/src/main/java/org/linphone/ui/file_viewer/viewmodel/FileViewModel.kt @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.ui.main.file_media_viewer.viewmodel +package org.linphone.ui.file_viewer.viewmodel import android.graphics.Bitmap import android.graphics.pdf.PdfRenderer @@ -119,14 +119,15 @@ class FileViewModel @UiThread constructor() : GenericViewModel() { mimeType.postValue(mime) when (FileUtils.getMimeType(mime)) { FileUtils.MimeType.Pdf -> { - Log.i("$TAG File [$file] seems to be a PDF") + Log.d("$TAG File [$file] seems to be a PDF") loadPdf() } FileUtils.MimeType.PlainText -> { - Log.i("$TAG File [$file] seems to be plain text") + Log.d("$TAG File [$file] seems to be plain text") loadPlainText() } else -> { + Log.e("$TAG Unexpected MIME type [$mime] for file at [$file]") fileReadyEvent.value = Event(false) } } @@ -158,7 +159,7 @@ class FileViewModel @UiThread constructor() : GenericViewModel() { val page: PdfRenderer.Page = pdfRenderer.openPage(index) currentPdfPage = page - Log.i( + Log.d( "$TAG Page size is ${page.width}/${page.height}, screen size is $screenWidth/$screenHeight" ) val bm = Bitmap.createBitmap( diff --git a/app/src/main/java/org/linphone/ui/file_viewer/viewmodel/MediaListViewModel.kt b/app/src/main/java/org/linphone/ui/file_viewer/viewmodel/MediaListViewModel.kt new file mode 100644 index 000000000..61ca1baa4 --- /dev/null +++ b/app/src/main/java/org/linphone/ui/file_viewer/viewmodel/MediaListViewModel.kt @@ -0,0 +1,96 @@ +/* + * 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 . + */ +package org.linphone.ui.file_viewer.viewmodel + +import androidx.annotation.UiThread +import androidx.annotation.WorkerThread +import androidx.lifecycle.MutableLiveData +import org.linphone.core.tools.Log +import org.linphone.ui.main.chat.model.FileModel +import org.linphone.ui.main.chat.viewmodel.AbstractConversationViewModel +import org.linphone.utils.FileUtils +import org.linphone.utils.LinphoneUtils + +class MediaListViewModel @UiThread constructor() : AbstractConversationViewModel() { + companion object { + private const val TAG = "[Media List ViewModel]" + } + + val mediaList = MutableLiveData>() + + val fullScreenMode = MutableLiveData() + + val currentlyDisplayedFileName = MutableLiveData() + + override fun beforeNotifyingChatRoomFound(sameOne: Boolean) { + loadMediaList() + } + + init { + fullScreenMode.value = true + } + + override fun onCleared() { + super.onCleared() + + mediaList.value.orEmpty().forEach(FileModel::destroy) + } + + @UiThread + fun initTempModel(path: String, timestamp: Long, isEncrypted: Boolean, originalPath: String) { + val name = FileUtils.getNameFromFilePath(path) + val model = FileModel(path, name, 0, timestamp, isEncrypted, originalPath) + mediaList.postValue(arrayListOf(model)) + } + + @WorkerThread + private fun loadMediaList() { + val list = arrayListOf() + val chatRoomId = LinphoneUtils.getChatRoomId(chatRoom) + Log.i("$TAG Loading media contents for conversation [$chatRoomId]") + + val media = chatRoom.mediaContents + Log.i("$TAG [${media.size}] media have been fetched") + for (mediaContent in media) { + // Do not display voice recordings here, even if they are media file + if (mediaContent.isVoiceRecording) continue + + val isEncrypted = mediaContent.isFileEncrypted + val originalPath = mediaContent.filePath.orEmpty() + val path = if (isEncrypted) { + Log.d( + "$TAG [VFS] Content is encrypted, requesting plain file path for file [${mediaContent.filePath}]" + ) + mediaContent.exportPlainFile() + } else { + originalPath + } + val name = mediaContent.name.orEmpty() + val size = mediaContent.size.toLong() + val timestamp = mediaContent.creationTimestamp + if (path.isNotEmpty() && name.isNotEmpty()) { + val model = FileModel(path, name, size, timestamp, isEncrypted, originalPath) + list.add(model) + } + } + Log.i("$TAG [${list.size}] media have been processed") + mediaList.postValue(list) + } +} diff --git a/app/src/main/java/org/linphone/ui/main/file_media_viewer/viewmodel/MediaViewModel.kt b/app/src/main/java/org/linphone/ui/file_viewer/viewmodel/MediaViewModel.kt similarity index 92% rename from app/src/main/java/org/linphone/ui/main/file_media_viewer/viewmodel/MediaViewModel.kt rename to app/src/main/java/org/linphone/ui/file_viewer/viewmodel/MediaViewModel.kt index 8339c8901..9c2286fec 100644 --- a/app/src/main/java/org/linphone/ui/main/file_media_viewer/viewmodel/MediaViewModel.kt +++ b/app/src/main/java/org/linphone/ui/file_viewer/viewmodel/MediaViewModel.kt @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.linphone.ui.main.file_media_viewer.viewmodel +package org.linphone.ui.file_viewer.viewmodel import android.media.AudioAttributes import android.media.MediaPlayer @@ -75,22 +75,24 @@ class MediaViewModel @UiThread constructor() : GenericViewModel() { val mime = FileUtils.getMimeTypeFromExtension(extension) when (FileUtils.getMimeType(mime)) { FileUtils.MimeType.Image -> { - Log.i("$TAG File [$file] seems to be an image") + Log.d("$TAG File [$file] seems to be an image") isImage.value = true path.value = file } FileUtils.MimeType.Video -> { - Log.i("$TAG File [$file] seems to be a video") + Log.d("$TAG File [$file] seems to be a video") isVideo.value = true isVideoPlaying.value = false } FileUtils.MimeType.Audio -> { - Log.i("$TAG File [$file] seems to be an audio file") + Log.d("$TAG File [$file] seems to be an audio file") isAudio.value = true initMediaPlayer() } - else -> { } + else -> { + Log.e("$TAG Unexpected MIME type [$mime] for file at [$file]") + } } } diff --git a/app/src/main/java/org/linphone/ui/main/chat/adapter/ConversationsFilesAdapter.kt b/app/src/main/java/org/linphone/ui/main/chat/adapter/ConversationsFilesAdapter.kt index 9c9082a74..298d7af90 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/adapter/ConversationsFilesAdapter.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/adapter/ConversationsFilesAdapter.kt @@ -136,7 +136,7 @@ class ConversationsFilesAdapter : private class FilesDiffCallback : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: FileModel, newItem: FileModel): Boolean { - return oldItem.file == newItem.file && oldItem.fileName == newItem.fileName + return oldItem.path == newItem.path && oldItem.fileName == newItem.fileName } override fun areContentsTheSame(oldItem: FileModel, newItem: FileModel): Boolean { diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationDocumentsListFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationDocumentsListFragment.kt index 7420ca524..516ea89c1 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationDocumentsListFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationDocumentsListFragment.kt @@ -37,6 +37,7 @@ import org.linphone.core.tools.Log import org.linphone.databinding.ChatDocumentsFragmentBinding import org.linphone.ui.GenericActivity import org.linphone.ui.main.chat.adapter.ConversationsFilesAdapter +import org.linphone.ui.main.chat.model.FileModel import org.linphone.ui.main.chat.viewmodel.ConversationDocumentsListViewModel import org.linphone.ui.main.fragment.SlidingPaneChildFragment import org.linphone.utils.Event @@ -126,13 +127,14 @@ class ConversationDocumentsListFragment : SlidingPaneChildFragment() { 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) + Log.i("$TAG User clicked on file [${model.path}], let's display it in file viewer") + goToFileViewer(model) } } } - private fun goToFileViewer(path: String) { + private fun goToFileViewer(fileModel: FileModel) { + val path = fileModel.path Log.i("$TAG Navigating to file viewer fragment with path [$path]") val extension = FileUtils.getExtensionFromFileName(path) val mime = FileUtils.getMimeTypeFromExtension(extension) @@ -142,6 +144,9 @@ class ConversationDocumentsListFragment : SlidingPaneChildFragment() { putString("localSipUri", viewModel.localSipUri) putString("remoteSipUri", viewModel.remoteSipUri) putString("path", path) + putBoolean("isEncrypted", fileModel.isEncrypted) + putLong("timestamp", fileModel.fileCreationTimestamp) + putString("originalPath", fileModel.originalPath) } when (FileUtils.getMimeType(mime)) { FileUtils.MimeType.Pdf, FileUtils.MimeType.PlainText -> { diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt index a51cd6705..f2093ef71 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt @@ -80,6 +80,7 @@ import org.linphone.ui.main.MainActivity import org.linphone.ui.main.chat.ConversationScrollListener import org.linphone.ui.main.chat.adapter.ConversationEventAdapter import org.linphone.ui.main.chat.adapter.MessageBottomSheetAdapter +import org.linphone.ui.main.chat.model.FileModel import org.linphone.ui.main.chat.model.MessageDeliveryModel import org.linphone.ui.main.chat.model.MessageModel import org.linphone.ui.main.chat.model.MessageReactionsModel @@ -614,10 +615,10 @@ class ConversationFragment : SlidingPaneChildFragment() { } viewModel.fileToDisplayEvent.observe(viewLifecycleOwner) { - it.consume { file -> + it.consume { model -> if (messageLongPressDialog != null) return@consume - Log.i("$TAG User clicked on file [$file], let's display it in file viewer") - goToFileViewer(file) + Log.i("$TAG User clicked on file [${model.path}], let's display it in file viewer") + goToFileViewer(model) } } @@ -892,7 +893,8 @@ class ConversationFragment : SlidingPaneChildFragment() { } } - private fun goToFileViewer(path: String) { + private fun goToFileViewer(fileModel: FileModel) { + val path = fileModel.path Log.i("$TAG Navigating to file viewer fragment with path [$path]") val extension = FileUtils.getExtensionFromFileName(path) val mime = FileUtils.getMimeTypeFromExtension(extension) @@ -902,6 +904,9 @@ class ConversationFragment : SlidingPaneChildFragment() { putString("localSipUri", viewModel.localSipUri) putString("remoteSipUri", viewModel.remoteSipUri) putString("path", path) + putBoolean("isEncrypted", fileModel.isEncrypted) + putLong("timestamp", fileModel.fileCreationTimestamp) + putString("originalPath", fileModel.originalPath) } when (FileUtils.getMimeType(mime)) { FileUtils.MimeType.Image, FileUtils.MimeType.Video, FileUtils.MimeType.Audio -> { diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationMediaListFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationMediaListFragment.kt index 7917d06e7..c87e79cd3 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationMediaListFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationMediaListFragment.kt @@ -38,6 +38,7 @@ import org.linphone.core.tools.Log import org.linphone.databinding.ChatMediaFragmentBinding import org.linphone.ui.GenericActivity import org.linphone.ui.main.chat.adapter.ConversationsFilesAdapter +import org.linphone.ui.main.chat.model.FileModel import org.linphone.ui.main.chat.viewmodel.ConversationMediaListViewModel import org.linphone.ui.main.fragment.SlidingPaneChildFragment import org.linphone.utils.Event @@ -83,7 +84,9 @@ class ConversationMediaListFragment : SlidingPaneChildFragment() { binding.lifecycleOwner = viewLifecycleOwner - viewModel = ViewModelProvider(this)[ConversationMediaListViewModel::class.java] + if (!::viewModel.isInitialized) { + viewModel = ViewModelProvider(this)[ConversationMediaListViewModel::class.java] + } binding.viewModel = viewModel observeToastEvents(viewModel) @@ -126,8 +129,6 @@ class ConversationMediaListFragment : SlidingPaneChildFragment() { return 1 } } - // This isn't supported by GridLayoutManager, it will crash - // layoutManager.stackFromEnd = true binding.mediaList.layoutManager = layoutManager if (binding.mediaList.adapter != adapter) { @@ -155,13 +156,14 @@ class ConversationMediaListFragment : SlidingPaneChildFragment() { viewModel.openMediaEvent.observe(viewLifecycleOwner) { it.consume { model -> - Log.i("$TAG User clicked on file [${model.file}], let's display it in file viewer") - goToFileViewer(model.file) + Log.i("$TAG User clicked on file [${model.path}], let's display it in file viewer") + goToFileViewer(model) } } } - private fun goToFileViewer(path: String) { + private fun goToFileViewer(fileModel: FileModel) { + val path = fileModel.path Log.i("$TAG Navigating to file viewer fragment with path [$path]") val extension = FileUtils.getExtensionFromFileName(path) val mime = FileUtils.getMimeTypeFromExtension(extension) @@ -171,6 +173,9 @@ class ConversationMediaListFragment : SlidingPaneChildFragment() { putString("localSipUri", viewModel.localSipUri) putString("remoteSipUri", viewModel.remoteSipUri) putString("path", path) + putBoolean("isEncrypted", fileModel.isEncrypted) + putLong("timestamp", fileModel.fileCreationTimestamp) + putString("originalPath", fileModel.originalPath) } when (FileUtils.getMimeType(mime)) { FileUtils.MimeType.Image, FileUtils.MimeType.Video, FileUtils.MimeType.Audio -> { diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationsListFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationsListFragment.kt index f5f4e8958..7bf1cd67d 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationsListFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationsListFragment.kt @@ -19,6 +19,7 @@ */ package org.linphone.ui.main.chat.fragment +import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -36,6 +37,8 @@ import org.linphone.R import org.linphone.core.tools.Log import org.linphone.databinding.ChatListFragmentBinding import org.linphone.ui.GenericActivity +import org.linphone.ui.file_viewer.FileViewerActivity +import org.linphone.ui.file_viewer.MediaViewerActivity import org.linphone.ui.main.chat.adapter.ConversationsListAdapter import org.linphone.ui.main.chat.viewmodel.ConversationsListViewModel import org.linphone.ui.main.fragment.AbstractMainFragment @@ -85,9 +88,7 @@ class ConversationsListFragment : AbstractMainFragment() { override fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int): Animation? { if ( findNavController().currentDestination?.id == R.id.startConversationFragment || - findNavController().currentDestination?.id == R.id.meetingWaitingRoomFragment || - findNavController().currentDestination?.id == R.id.fileViewerFragment || - findNavController().currentDestination?.id == R.id.mediaListViewerFragment + findNavController().currentDestination?.id == R.id.meetingWaitingRoomFragment ) { // Holds fragment in place while new fragment slides over it return AnimationUtils.loadAnimation(activity, R.anim.hold) @@ -232,24 +233,23 @@ class ConversationsListFragment : AbstractMainFragment() { if (findNavController().currentDestination?.id == R.id.conversationsListFragment) { val path = bundle.getString("path", "") val isMedia = bundle.getBoolean("isMedia", false) + if (path.isEmpty()) { + Log.e("$TAG Can't navigate to file viewer for empty path!") + return@consume + } + Log.i( "$TAG Navigating to ${if (isMedia) "media" else "file"} viewer fragment with path [$path]" ) - val action = if (isMedia) { - val localSipUri = bundle.getString("localSipUri", "") - val remoteSipUri = bundle.getString("remoteSipUri", "") - ConversationsListFragmentDirections.actionConversationsListFragmentToMediaListViewerFragment( - localSipUri = localSipUri, - remoteSipUri = remoteSipUri, - path = path - ) + if (isMedia) { + val intent = Intent(requireActivity(), MediaViewerActivity::class.java) + intent.putExtras(bundle) + startActivity(intent) } else { - ConversationsListFragmentDirections.actionConversationsListFragmentToFileViewerFragment( - path, - null - ) + val intent = Intent(requireActivity(), FileViewerActivity::class.java) + intent.putExtras(bundle) + startActivity(intent) } - findNavController().navigate(action) } } } diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/EventLogModel.kt b/app/src/main/java/org/linphone/ui/main/chat/model/EventLogModel.kt index f53bbbdd1..4d21226e0 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/model/EventLogModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/model/EventLogModel.kt @@ -32,7 +32,7 @@ class EventLogModel @WorkerThread constructor( isFromGroup: Boolean = false, isGroupedWithPreviousOne: Boolean = false, isGroupedWithNextOne: Boolean = false, - onContentClicked: ((file: String) -> Unit)? = null, + onContentClicked: ((fileModel: FileModel) -> Unit)? = null, onJoinConferenceClicked: ((uri: String) -> Unit)? = null, onWebUrlClicked: ((url: String) -> Unit)? = null, onContactClicked: ((friendRefKey: String) -> Unit)? = null, diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/FileModel.kt b/app/src/main/java/org/linphone/ui/main/chat/model/FileModel.kt index 06a307d6f..3b432a058 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/model/FileModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/model/FileModel.kt @@ -35,11 +35,12 @@ import org.linphone.utils.FileUtils import org.linphone.utils.TimestampUtils class FileModel @AnyThread constructor( - val file: String, + val path: String, val fileName: String, val fileSize: Long, - private val fileCreationTimestamp: Long, - private val isEncrypted: Boolean, + val fileCreationTimestamp: Long, + val isEncrypted: Boolean, + val originalPath: String, val isWaitingToBeDownloaded: Boolean = false, private val onClicked: ((model: FileModel) -> Unit)? = null ) { @@ -82,7 +83,7 @@ class FileModel @AnyThread constructor( formattedFileSize.postValue(FileUtils.bytesToDisplayableSize(fileSize)) if (!isWaitingToBeDownloaded) { - val extension = FileUtils.getExtensionFromFileName(file) + val extension = FileUtils.getExtensionFromFileName(path) isPdf = extension == "pdf" val mime = FileUtils.getMimeTypeFromExtension(extension) @@ -113,9 +114,9 @@ class FileModel @AnyThread constructor( @AnyThread fun destroy() { if (isEncrypted) { - Log.i("$TAG [VFS] Deleting plain file in cache: $file") + Log.i("$TAG [VFS] Deleting plain file in cache: $path") scope.launch { - FileUtils.deleteFile(file) + FileUtils.deleteFile(path) } } } @@ -127,22 +128,22 @@ class FileModel @AnyThread constructor( @AnyThread suspend fun deleteFile() { - Log.i("$TAG Deleting file [$file]") - FileUtils.deleteFile(file) + Log.i("$TAG Deleting file [$path]") + FileUtils.deleteFile(path) } private fun getDuration() { try { val retriever = MediaMetadataRetriever() - retriever.setDataSource(coreContext.context, Uri.parse(file)) + retriever.setDataSource(coreContext.context, Uri.parse(path)) val durationInMs = retriever.extractMetadata(METADATA_KEY_DURATION)?.toInt() ?: 0 val seconds = durationInMs / 1000 val duration = TimestampUtils.durationToString(seconds) - Log.d("$TAG Duration for file [$file] is $duration") + Log.d("$TAG Duration for file [$path] is $duration") audioVideoDuration.postValue(duration) retriever.release() } catch (e: Exception) { - Log.e("$TAG Failed to get duration for file [$file]: $e") + Log.e("$TAG Failed to get duration for file [$path]: $e") } } } diff --git a/app/src/main/java/org/linphone/ui/main/chat/model/MessageModel.kt b/app/src/main/java/org/linphone/ui/main/chat/model/MessageModel.kt index dc3bb6d27..2d26e6a68 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/model/MessageModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/model/MessageModel.kt @@ -73,7 +73,7 @@ class MessageModel @WorkerThread constructor( val isForward: Boolean, isGroupedWithPreviousOne: Boolean, isGroupedWithNextOne: Boolean, - private val onContentClicked: ((file: String) -> Unit)? = null, + private val onContentClicked: ((fileModel: FileModel) -> Unit)? = null, private val onJoinConferenceClicked: ((uri: String) -> Unit)? = null, private val onWebUrlClicked: ((url: String) -> Unit)? = null, private val onContactClicked: ((friendRefKey: String) -> Unit)? = null, @@ -355,13 +355,14 @@ class MessageModel @WorkerThread constructor( checkAndRepairFilePathIfNeeded(content) + val originalPath = content.filePath.orEmpty() val path = if (isFileEncrypted) { - Log.i( + Log.d( "$TAG [VFS] Content is encrypted, requesting plain file path for file [${content.filePath}]" ) content.exportPlainFile() } else { - content.filePath ?: "" + originalPath } val name = content.name ?: "" if (path.isNotEmpty()) { @@ -378,9 +379,10 @@ class MessageModel @WorkerThread constructor( name, fileSize, timestamp, - isFileEncrypted + isFileEncrypted, + originalPath ) { model -> - onContentClicked?.invoke(model.file) + onContentClicked?.invoke(model) } filesPath.add(fileModel) @@ -392,9 +394,10 @@ class MessageModel @WorkerThread constructor( name, fileSize, timestamp, - isFileEncrypted + isFileEncrypted, + originalPath ) { model -> - onContentClicked?.invoke(model.file) + onContentClicked?.invoke(model) } filesPath.add(fileModel) @@ -414,16 +417,17 @@ class MessageModel @WorkerThread constructor( val timestamp = content.creationTimestamp if (name.isNotEmpty()) { val fileModel = if (isOutgoing && chatMessage.isFileTransferInProgress) { - val path = content.filePath ?: "" + val path = content.filePath.orEmpty() FileModel( path, name, content.fileSize.toLong(), timestamp, isFileEncrypted, + path, false ) { model -> - onContentClicked?.invoke(model.file) + onContentClicked?.invoke(model) } } else { FileModel( @@ -432,6 +436,7 @@ class MessageModel @WorkerThread constructor( content.fileSize.toLong(), timestamp, isFileEncrypted, + name, true ) { model -> downloadContent(model, content) diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationDocumentsListViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationDocumentsListViewModel.kt index fa974031b..7e4a99a0f 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationDocumentsListViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationDocumentsListViewModel.kt @@ -45,6 +45,8 @@ class ConversationDocumentsListViewModel @UiThread constructor() : AbstractConve } override fun onCleared() { + super.onCleared() + documentsList.value.orEmpty().forEach(FileModel::destroy) } @@ -62,19 +64,20 @@ class ConversationDocumentsListViewModel @UiThread constructor() : AbstractConve Log.i("$TAG [${documents.size}] documents have been fetched") for (documentContent in documents) { val isEncrypted = documentContent.isFileEncrypted + val originalPath = documentContent.filePath.orEmpty() val path = if (isEncrypted) { - Log.i( + Log.d( "$TAG [VFS] Content is encrypted, requesting plain file path for file [${documentContent.filePath}]" ) documentContent.exportPlainFile() } else { - documentContent.filePath.orEmpty() + originalPath } val name = documentContent.name.orEmpty() val size = documentContent.size.toLong() val timestamp = documentContent.creationTimestamp if (path.isNotEmpty() && name.isNotEmpty()) { - val model = FileModel(path, name, size, timestamp, isEncrypted) { + val model = FileModel(path, name, size, timestamp, isEncrypted, originalPath) { openDocumentEvent.postValue(Event(it)) } list.add(model) diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationMediaListViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationMediaListViewModel.kt index cb83fa496..9df1594b5 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationMediaListViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationMediaListViewModel.kt @@ -34,12 +34,6 @@ class ConversationMediaListViewModel @UiThread constructor() : AbstractConversat val mediaList = MutableLiveData>() - val fullScreenMode = MutableLiveData() - - val currentlyDisplayedFileName = MutableLiveData() - - val operationInProgress = MutableLiveData() - val openMediaEvent: MutableLiveData> by lazy { MutableLiveData>() } @@ -49,13 +43,13 @@ class ConversationMediaListViewModel @UiThread constructor() : AbstractConversat } override fun onCleared() { + super.onCleared() + mediaList.value.orEmpty().forEach(FileModel::destroy) } @WorkerThread private fun loadMediaList() { - operationInProgress.postValue(true) - val list = arrayListOf() Log.i( "$TAG Loading media contents for conversation [${LinphoneUtils.getChatRoomId( @@ -69,19 +63,20 @@ class ConversationMediaListViewModel @UiThread constructor() : AbstractConversat if (mediaContent.isVoiceRecording) continue val isEncrypted = mediaContent.isFileEncrypted + val originalPath = mediaContent.filePath.orEmpty() val path = if (isEncrypted) { - Log.i( + Log.d( "$TAG [VFS] Content is encrypted, requesting plain file path for file [${mediaContent.filePath}]" ) mediaContent.exportPlainFile() } else { - mediaContent.filePath.orEmpty() + originalPath } val name = mediaContent.name.orEmpty() val size = mediaContent.size.toLong() val timestamp = mediaContent.creationTimestamp if (path.isNotEmpty() && name.isNotEmpty()) { - val model = FileModel(path, name, size, timestamp, isEncrypted) { + val model = FileModel(path, name, size, timestamp, isEncrypted, originalPath) { openMediaEvent.postValue(Event(it)) } list.add(model) @@ -89,6 +84,5 @@ class ConversationMediaListViewModel @UiThread constructor() : AbstractConversat } Log.i("$TAG [${media.size}] media have been processed") mediaList.postValue(list) - operationInProgress.postValue(false) } } diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt index 7b1ea0ffa..e22aad72f 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt @@ -43,6 +43,7 @@ import org.linphone.core.Participant import org.linphone.core.ParticipantInfo import org.linphone.core.tools.Log import org.linphone.ui.main.chat.model.EventLogModel +import org.linphone.ui.main.chat.model.FileModel import org.linphone.ui.main.chat.model.MessageModel import org.linphone.ui.main.contacts.model.ContactAvatarModel import org.linphone.ui.main.model.isEndToEndEncryptionMandatory @@ -100,8 +101,8 @@ class ConversationViewModel @UiThread constructor() : AbstractConversationViewMo MutableLiveData>() } - val fileToDisplayEvent: MutableLiveData> by lazy { - MutableLiveData>() + val fileToDisplayEvent: MutableLiveData> by lazy { + MutableLiveData>() } val conferenceToJoinEvent: MutableLiveData> by lazy { @@ -664,8 +665,8 @@ class ConversationViewModel @UiThread constructor() : AbstractConversationViewMo groupChatRoom, index > 0, index != groupedEventLogs.size - 1, - { file -> - fileToDisplayEvent.postValue(Event(file)) + { fileModel -> + fileToDisplayEvent.postValue(Event(fileModel)) }, { conferenceUri -> conferenceToJoinEvent.postValue(Event(conferenceUri)) diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt index 744237ab1..a10f1be90 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt @@ -264,7 +264,7 @@ class SendMessageInConversationViewModel @UiThread constructor() : GenericViewMo } content.name = attachment.fileName // Let the file body handler take care of the upload - content.filePath = attachment.file + content.filePath = attachment.path message.addFileContent(content) } @@ -320,8 +320,8 @@ class SendMessageInConversationViewModel @UiThread constructor() : GenericViewMo val fileName = FileUtils.getNameFromFilePath(file) val timestamp = System.currentTimeMillis() / 1000 - val model = FileModel(file, fileName, 0, timestamp, isEncrypted = false) { model -> - removeAttachment(model.file) + val model = FileModel(file, fileName, 0, timestamp, false, file) { model -> + removeAttachment(model.path) } list.add(model) @@ -340,7 +340,7 @@ class SendMessageInConversationViewModel @UiThread constructor() : GenericViewMo val list = arrayListOf() list.addAll(attachments.value.orEmpty()) val found = list.find { - it.file == file + it.path == file } if (found != null) { if (delete) { diff --git a/app/src/main/java/org/linphone/ui/main/file_media_viewer/fragment/MediaListViewerFragment.kt b/app/src/main/java/org/linphone/ui/main/file_media_viewer/fragment/MediaListViewerFragment.kt deleted file mode 100644 index 03c0ccf7f..000000000 --- a/app/src/main/java/org/linphone/ui/main/file_media_viewer/fragment/MediaListViewerFragment.kt +++ /dev/null @@ -1,279 +0,0 @@ -/* - * 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 . - */ -package org.linphone.ui.main.file_media_viewer.fragment - -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.core.content.FileProvider -import androidx.core.view.doOnPreDraw -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.lifecycleScope -import androidx.navigation.fragment.navArgs -import androidx.viewpager2.widget.ViewPager2 -import java.io.File -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.linphone.R -import org.linphone.core.tools.Log -import org.linphone.databinding.FileMediaViewerFragmentBinding -import org.linphone.ui.GenericActivity -import org.linphone.ui.main.chat.viewmodel.ConversationMediaListViewModel -import org.linphone.ui.main.file_media_viewer.adapter.MediaListAdapter -import org.linphone.ui.main.fragment.GenericMainFragment -import org.linphone.utils.AppUtils -import org.linphone.utils.FileUtils - -class MediaListViewerFragment : GenericMainFragment() { - companion object { - private const val TAG = "[Media List Viewer]" - } - - private lateinit var binding: FileMediaViewerFragmentBinding - - private lateinit var adapter: MediaListAdapter - - private lateinit var viewModel: ConversationMediaListViewModel - - private lateinit var viewPager: ViewPager2 - - private val args: MediaListViewerFragmentArgs by navArgs() - - private var navBarDefaultColor: Int = -1 - - private val pageListener = object : ViewPager2.OnPageChangeCallback() { - override fun onPageSelected(position: Int) { - val list = viewModel.mediaList.value.orEmpty() - if (position >= 0 && position < list.size) { - val model = list[position] - viewModel.currentlyDisplayedFileName.value = "${model.fileName}\n${model.dateTime}" - } - } - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - binding = FileMediaViewerFragmentBinding.inflate(layoutInflater) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - postponeEnterTransition() - super.onViewCreated(view, savedInstanceState) - - navBarDefaultColor = requireActivity().window.navigationBarColor - - binding.lifecycleOwner = viewLifecycleOwner - - viewModel = ViewModelProvider(this)[ConversationMediaListViewModel::class.java] - binding.viewModel = viewModel - observeToastEvents(viewModel) - - // Consider full screen mode the default - sharedViewModel.mediaViewerFullScreenMode.value = true - - val localSipUri = args.localSipUri - val remoteSipUri = args.remoteSipUri - val path = args.path - Log.i( - "$TAG Looking up for conversation with local SIP URI [$localSipUri] and remote SIP URI [$remoteSipUri] trying to display file [$path]" - ) - val chatRoom = sharedViewModel.displayedChatRoom - viewModel.findChatRoom(chatRoom, localSipUri, remoteSipUri) - - 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]" - ) - adapter = MediaListAdapter(this, viewModel) - viewPager = binding.mediaViewPager - viewPager.adapter = adapter - - viewPager.registerOnPageChangeCallback(pageListener) - - var index = it.indexOfFirst { model -> - model.file == path - } - - if (index == -1) { - Log.i( - "$TAG Path [$path] not found in media list (expected if VFS is enabled), trying using file name" - ) - val fileName = File(path).name - val underscore = fileName.indexOf("_") - val originalFileName = if (underscore != -1 && underscore < 2) { - fileName.subSequence(underscore, fileName.length) - } else { - fileName - } - index = it.indexOfFirst { model -> - model.file.endsWith(originalFileName) - } - if (index == -1) { - Log.w( - "$TAG Path [$path] not found either using filename [$originalFileName] match" - ) - } - } - - val position = if (index == -1) { - Log.e("$TAG File [$path] not found, using latest one available instead!") - val message = getString(R.string.conversation_media_not_found_toast) - val icon = R.drawable.warning_circle - (requireActivity() as GenericActivity).showRedToast(message, icon) - - count - 1 - } else { - index - } - viewPager.setCurrentItem(position, false) - viewPager.offscreenPageLimit = 1 - - (view.parent as? ViewGroup)?.doOnPreDraw { - startPostponedEnterTransition() - } - } - - binding.setBackClickListener { - goBack() - } - - binding.setShareClickListener { - shareFile() - } - - binding.setExportClickListener { - exportFile() - } - - sharedViewModel.mediaViewerFullScreenMode.observe(viewLifecycleOwner) { - if (it != viewModel.fullScreenMode.value) { - viewModel.fullScreenMode.value = it - } - } - } - - override fun onResume() { - // Force this navigation bar color - requireActivity().window.navigationBarColor = requireContext().getColor(R.color.gray_900) - - super.onResume() - } - - override fun onDestroy() { - // Reset default navigation bar color - requireActivity().window.navigationBarColor = navBarDefaultColor - - if (::viewPager.isInitialized) { - viewPager.unregisterOnPageChangeCallback(pageListener) - } - - super.onDestroy() - } - - private fun exportFile() { - val list = viewModel.mediaList.value.orEmpty() - val currentItem = binding.mediaViewPager.currentItem - val model = if (currentItem >= 0 && currentItem < list.size) list[currentItem] else null - if (model != null) { - val filePath = model.file - lifecycleScope.launch { - withContext(Dispatchers.IO) { - Log.i("$TAG Export file [$filePath] to Android's MediaStore") - val mediaStorePath = FileUtils.addContentToMediaStore(filePath) - if (mediaStorePath.isNotEmpty()) { - Log.i( - "$TAG File [$filePath] has been successfully exported to MediaStore" - ) - val message = AppUtils.getString( - R.string.toast_file_successfully_exported_to_media_store - ) - (requireActivity() as GenericActivity).showGreenToast( - message, - R.drawable.check - ) - } else { - Log.e("$TAG Failed to export file [$filePath] to MediaStore!") - val message = AppUtils.getString( - R.string.toast_export_file_to_media_store_error - ) - (requireActivity() as GenericActivity).showRedToast( - message, - R.drawable.warning_circle - ) - } - } - } - } else { - Log.e( - "$TAG Failed to get FileModel at index [$currentItem], only [${list.size}] items in list" - ) - } - } - - private fun shareFile() { - val list = viewModel.mediaList.value.orEmpty() - val currentItem = binding.mediaViewPager.currentItem - val model = if (currentItem >= 0 && currentItem < list.size) list[currentItem] else null - if (model != null) { - lifecycleScope.launch { - val filePath = FileUtils.getProperFilePath(model.file) - val copy = FileUtils.getFilePath( - requireContext(), - Uri.parse(filePath), - overrideExisting = true, - copyToCache = true - ) - if (!copy.isNullOrEmpty()) { - val publicUri = FileProvider.getUriForFile( - requireContext(), - requireContext().getString(R.string.file_provider), - File(copy) - ) - Log.i("$TAG Public URI for file is [$publicUri], starting intent chooser") - - val sendIntent: Intent = Intent().apply { - action = Intent.ACTION_SEND - putExtra(Intent.EXTRA_STREAM, publicUri) - putExtra(Intent.EXTRA_SUBJECT, model.fileName) - type = model.mimeTypeString - } - - val shareIntent = Intent.createChooser(sendIntent, null) - startActivity(shareIntent) - } else { - Log.e("$TAG Failed to copy file [$filePath] to share!") - } - } - } else { - Log.e( - "$TAG Failed to get FileModel at index [$currentItem], only [${list.size}] items in list" - ) - } - } -} diff --git a/app/src/main/java/org/linphone/ui/main/help/fragment/DebugFragment.kt b/app/src/main/java/org/linphone/ui/main/help/fragment/DebugFragment.kt index 00c494e22..6ea890923 100644 --- a/app/src/main/java/org/linphone/ui/main/help/fragment/DebugFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/help/fragment/DebugFragment.kt @@ -32,6 +32,7 @@ import org.linphone.core.CorePreferences import org.linphone.core.tools.Log import org.linphone.databinding.HelpDebugFragmentBinding import org.linphone.ui.GenericActivity +import org.linphone.ui.file_viewer.FileViewerActivity import org.linphone.ui.main.fragment.GenericMainFragment import org.linphone.ui.main.help.viewmodel.HelpViewModel @@ -117,11 +118,12 @@ class DebugFragment : GenericMainFragment() { viewModel.showConfigFileEvent.observe(viewLifecycleOwner) { it.consume { content -> if (findNavController().currentDestination?.id == R.id.debugFragment) { - val action = DebugFragmentDirections.actionDebugFragmentToFileViewerFragment( - CorePreferences.CONFIG_FILE_NAME, - content - ) - findNavController().navigate(action) + val intent = Intent(requireActivity(), FileViewerActivity::class.java) + val bundle = Bundle() + bundle.putString("path", CorePreferences.CONFIG_FILE_NAME) + bundle.putString("content", content) + intent.putExtras(bundle) + startActivity(intent) } } } diff --git a/app/src/main/java/org/linphone/ui/main/sso/viewmodel/SingleSignOnViewModel.kt b/app/src/main/java/org/linphone/ui/main/sso/viewmodel/SingleSignOnViewModel.kt index a51956f29..d5649ea72 100644 --- a/app/src/main/java/org/linphone/ui/main/sso/viewmodel/SingleSignOnViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/sso/viewmodel/SingleSignOnViewModel.kt @@ -47,6 +47,7 @@ class SingleSignOnViewModel : GenericViewModel() { private const val CLIENT_ID = "linphone" private const val REDIRECT_URI = "org.linphone:/openidcallback" + private const val OPEN_ID_WELL_KNOWN = ".well-known/openid-configuration" } val singleSignOnProcessCompletedEvent = MutableLiveData>() @@ -106,12 +107,12 @@ class SingleSignOnViewModel : GenericViewModel() { Log.e( "$TAG Failed to fetch configuration on [$singleSignOnUrl]: ${ex.errorDescription}" ) - if (!singleSignOnUrl.endsWith(".well-known/openid-configuration")) { - Log.w("$TAG Trying again appending .well-known/openid-configuration to URL") + if (!singleSignOnUrl.endsWith(OPEN_ID_WELL_KNOWN)) { + Log.w("$TAG Trying again appending [$OPEN_ID_WELL_KNOWN] to URL") singleSignOnUrl = if (singleSignOnUrl.endsWith("/")) { - "$singleSignOnUrl.well-known/openid-configuration" + "$singleSignOnUrl$OPEN_ID_WELL_KNOWN" } else { - "$singleSignOnUrl/.well-known/openid-configuration" + "$singleSignOnUrl/$OPEN_ID_WELL_KNOWN" } singleSignOn() return@RetrieveConfigurationCallback diff --git a/app/src/main/java/org/linphone/ui/main/viewmodel/SharedMainViewModel.kt b/app/src/main/java/org/linphone/ui/main/viewmodel/SharedMainViewModel.kt index 7be60a83b..ee27dd051 100644 --- a/app/src/main/java/org/linphone/ui/main/viewmodel/SharedMainViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/viewmodel/SharedMainViewModel.kt @@ -124,8 +124,6 @@ class SharedMainViewModel @UiThread constructor() : ViewModel() { // When using keyboard to share gif or other, see RichContentReceiver & RichEditText classes val richContentUri = MutableLiveData>() - val mediaViewerFullScreenMode = MutableLiveData() - val displayFileEvent: MutableLiveData> by lazy { MutableLiveData>() } diff --git a/app/src/main/java/org/linphone/utils/FileUtils.kt b/app/src/main/java/org/linphone/utils/FileUtils.kt index 4fd215c94..52f97b0ce 100644 --- a/app/src/main/java/org/linphone/utils/FileUtils.kt +++ b/app/src/main/java/org/linphone/utils/FileUtils.kt @@ -118,7 +118,7 @@ class FileUtils { type.startsWith("application/json") -> MimeType.PlainText else -> MimeType.Unknown } - Log.i("$TAG MIME type for [$type] is [$mime]") + Log.d("$TAG MIME type for [$type] is [$mime]") return mime } @@ -388,6 +388,11 @@ class FileUtils { } } + fun doesFileExist(path: String): Boolean { + val file = File(path) + return file.exists() + } + @AnyThread suspend fun dumpStringToFile(data: String, to: File): Boolean { try { diff --git a/app/src/main/res/layout/chat_bubble_content_grid_cell.xml b/app/src/main/res/layout/chat_bubble_content_grid_cell.xml index 5eb698d14..923445249 100644 --- a/app/src/main/res/layout/chat_bubble_content_grid_cell.xml +++ b/app/src/main/res/layout/chat_bubble_content_grid_cell.xml @@ -36,7 +36,7 @@ android:scaleType="centerCrop" android:contentDescription="@{model.isVideoPreview ? @string/content_description_chat_bubble_video : @string/content_description_chat_bubble_image}" android:visibility="@{model.isImage || model.isVideoPreview ? View.VISIBLE : View.GONE}" - coilBubbleGrid="@{model.file}" + coilBubbleGrid="@{model.path}" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"/> diff --git a/app/src/main/res/layout/chat_bubble_single_media_content.xml b/app/src/main/res/layout/chat_bubble_single_media_content.xml index 2b1925f14..ceebd5ebd 100644 --- a/app/src/main/res/layout/chat_bubble_single_media_content.xml +++ b/app/src/main/res/layout/chat_bubble_single_media_content.xml @@ -30,7 +30,7 @@ android:adjustViewBounds="true" android:scaleType="fitCenter" android:contentDescription="@{model.isVideoPreview ? @string/content_description_chat_bubble_video : @string/content_description_chat_bubble_image}" - coilBubble="@{model.file}" + coilBubble="@{model.path}" app:layout_constraintHeight_max="@dimen/chat_bubble_big_image_max_size" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" diff --git a/app/src/main/res/layout/chat_media_content_grid_cell.xml b/app/src/main/res/layout/chat_media_content_grid_cell.xml index df8a48aa8..37a69bb34 100644 --- a/app/src/main/res/layout/chat_media_content_grid_cell.xml +++ b/app/src/main/res/layout/chat_media_content_grid_cell.xml @@ -22,7 +22,7 @@ android:adjustViewBounds="true" android:scaleType="centerCrop" android:contentDescription="@{model.isVideoPreview ? @string/content_description_chat_bubble_video : @string/content_description_chat_bubble_image}" - coilBubbleGrid="@{model.file}" + coilBubbleGrid="@{model.path}" app:layout_constraintDimensionRatio="1:1" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" diff --git a/app/src/main/res/layout/chat_media_fragment.xml b/app/src/main/res/layout/chat_media_fragment.xml index a1442b4ab..dfdb3a55e 100644 --- a/app/src/main/res/layout/chat_media_fragment.xml +++ b/app/src/main/res/layout/chat_media_fragment.xml @@ -1,7 +1,6 @@ + xmlns:app="http://schemas.android.com/apk/res-auto"> @@ -13,71 +12,61 @@ type="org.linphone.ui.main.chat.viewmodel.ConversationMediaListViewModel" /> - + android:layout_height="match_parent" + android:background="?attr/color_main2_000"> - + - + - + - + - - - - - - - + \ No newline at end of file diff --git a/app/src/main/res/layout/file_media_viewer_fragment.xml b/app/src/main/res/layout/file_media_viewer_activity.xml similarity index 83% rename from app/src/main/res/layout/file_media_viewer_fragment.xml rename to app/src/main/res/layout/file_media_viewer_activity.xml index f2150bbb6..b71fe337d 100644 --- a/app/src/main/res/layout/file_media_viewer_fragment.xml +++ b/app/src/main/res/layout/file_media_viewer_activity.xml @@ -15,7 +15,7 @@ type="View.OnClickListener" /> + type="org.linphone.ui.file_viewer.viewmodel.MediaListViewModel" /> + + @@ -68,7 +72,6 @@ android:padding="15dp" android:src="@drawable/share_network" android:contentDescription="@string/content_description_share_file" - android:visibility="@{viewModel.fullScreenMode ? View.GONE : View.VISIBLE}" app:tint="?attr/color_main1_500" app:layout_constraintBottom_toBottomOf="@id/title" app:layout_constraintEnd_toStartOf="@id/save" @@ -84,7 +87,6 @@ android:padding="15dp" android:src="@drawable/download_simple" android:contentDescription="@string/content_description_save_file" - android:visibility="@{viewModel.fullScreenMode ? View.GONE : View.VISIBLE}" app:tint="?attr/color_main1_500" app:layout_constraintBottom_toBottomOf="@id/title" app:layout_constraintEnd_toEndOf="parent" @@ -102,11 +104,23 @@ android:textSize="12sp" android:textColor="?attr/color_main2_600" android:textAlignment="center" - android:visibility="@{viewModel.fullScreenMode ? View.GONE : View.VISIBLE}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"/> + + \ No newline at end of file diff --git a/app/src/main/res/layout/file_media_viewer_child_fragment.xml b/app/src/main/res/layout/file_media_viewer_child_fragment.xml index 3921a0af7..2c02babfa 100644 --- a/app/src/main/res/layout/file_media_viewer_child_fragment.xml +++ b/app/src/main/res/layout/file_media_viewer_child_fragment.xml @@ -5,13 +5,16 @@ + + type="org.linphone.ui.file_viewer.viewmodel.MediaViewModel" /> @@ -58,7 +61,7 @@ + type="org.linphone.ui.file_viewer.viewmodel.FileViewModel" /> + + @@ -109,7 +113,6 @@ android:padding="15dp" android:src="@drawable/share_network" android:contentDescription="@string/content_description_share_file" - android:visibility="@{viewModel.fullScreenMode ? View.GONE : View.VISIBLE}" app:tint="?attr/color_main1_500" app:layout_constraintBottom_toBottomOf="@id/title" app:layout_constraintEnd_toStartOf="@id/save" @@ -125,7 +128,6 @@ android:padding="15dp" android:src="@drawable/download_simple" android:contentDescription="@string/content_description_save_file" - android:visibility="@{viewModel.fullScreenMode ? View.GONE : View.VISIBLE}" app:tint="?attr/color_main1_500" app:layout_constraintBottom_toBottomOf="@id/title" app:layout_constraintEnd_toEndOf="parent" @@ -143,11 +145,23 @@ android:textSize="12sp" android:textColor="?attr/color_main2_600" android:textAlignment="center" - android:visibility="@{viewModel.fullScreenMode ? View.GONE : View.VISIBLE}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"/> + + \ No newline at end of file diff --git a/app/src/main/res/navigation/main_nav_graph.xml b/app/src/main/res/navigation/main_nav_graph.xml index 07d72949d..b44018cc9 100644 --- a/app/src/main/res/navigation/main_nav_graph.xml +++ b/app/src/main/res/navigation/main_nav_graph.xml @@ -216,12 +216,6 @@ app:launchSingleTop="true" app:popUpTo="@id/helpFragment" app:popUpToInclusive="true"/> - - - - - - - - - - - - - -