mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-01-17 11:28:06 +00:00
Moved file & media viewer in separated activities
This commit is contained in:
parent
eb0748df7f
commit
ccd53b74db
36 changed files with 654 additions and 620 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -8,6 +8,7 @@
|
|||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.idea/deploymentTargetDropDown.xml
|
||||
.idea/deploymentTargetSelector.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
|
|
|
|||
10
.idea/deploymentTargetSelector.xml
generated
10
.idea/deploymentTargetSelector.xml
generated
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="deploymentTargetSelector">
|
||||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
</SelectionState>
|
||||
</selectionStates>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -104,6 +104,16 @@
|
|||
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ui.file_viewer.MediaViewerActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:resizeableActivity="true" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.file_viewer.FileViewerActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:resizeableActivity="true" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.welcome.WelcomeActivity"
|
||||
android:launchMode="singleTask"
|
||||
|
|
|
|||
|
|
@ -1,121 +1,79 @@
|
|||
/*
|
||||
* 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.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")
|
||||
|
|
@ -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<FileModel>) {
|
||||
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"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,18 +17,22 @@
|
|||
* 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.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
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
* 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.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<PdfPagesListAdapter.PdfPageViewHolder>() {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PdfPageViewHolder {
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
* 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.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()
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
* 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.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(
|
||||
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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<List<FileModel>>()
|
||||
|
||||
val fullScreenMode = MutableLiveData<Boolean>()
|
||||
|
||||
val currentlyDisplayedFileName = MutableLiveData<String>()
|
||||
|
||||
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<FileModel>()
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
* 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.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]")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +136,7 @@ class ConversationsFilesAdapter :
|
|||
|
||||
private class FilesDiffCallback : DiffUtil.ItemCallback<FileModel>() {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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 -> {
|
||||
|
|
|
|||
|
|
@ -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 -> {
|
||||
|
|
|
|||
|
|
@ -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 -> {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -34,12 +34,6 @@ class ConversationMediaListViewModel @UiThread constructor() : AbstractConversat
|
|||
|
||||
val mediaList = MutableLiveData<List<FileModel>>()
|
||||
|
||||
val fullScreenMode = MutableLiveData<Boolean>()
|
||||
|
||||
val currentlyDisplayedFileName = MutableLiveData<String>()
|
||||
|
||||
val operationInProgress = MutableLiveData<Boolean>()
|
||||
|
||||
val openMediaEvent: MutableLiveData<Event<FileModel>> by lazy {
|
||||
MutableLiveData<Event<FileModel>>()
|
||||
}
|
||||
|
|
@ -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<FileModel>()
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Event<Boolean>>()
|
||||
}
|
||||
|
||||
val fileToDisplayEvent: MutableLiveData<Event<String>> by lazy {
|
||||
MutableLiveData<Event<String>>()
|
||||
val fileToDisplayEvent: MutableLiveData<Event<FileModel>> by lazy {
|
||||
MutableLiveData<Event<FileModel>>()
|
||||
}
|
||||
|
||||
val conferenceToJoinEvent: MutableLiveData<Event<String>> 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))
|
||||
|
|
|
|||
|
|
@ -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<FileModel>()
|
||||
list.addAll(attachments.value.orEmpty())
|
||||
val found = list.find {
|
||||
it.file == file
|
||||
it.path == file
|
||||
}
|
||||
if (found != null) {
|
||||
if (delete) {
|
||||
|
|
|
|||
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Event<Boolean>>()
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -124,8 +124,6 @@ class SharedMainViewModel @UiThread constructor() : ViewModel() {
|
|||
// When using keyboard to share gif or other, see RichContentReceiver & RichEditText classes
|
||||
val richContentUri = MutableLiveData<Event<Uri>>()
|
||||
|
||||
val mediaViewerFullScreenMode = MutableLiveData<Boolean>()
|
||||
|
||||
val displayFileEvent: MutableLiveData<Event<Bundle>> by lazy {
|
||||
MutableLiveData<Event<Bundle>>()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<?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">
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<data>
|
||||
<import type="android.view.View" />
|
||||
|
|
@ -13,71 +12,61 @@
|
|||
type="org.linphone.ui.main.chat.viewmodel.ConversationMediaListViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/color_main2_000">
|
||||
|
||||
<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"/>
|
||||
|
||||
<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_media_list_title"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/back"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/main_page_title_style"
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="@dimen/top_bar_height"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:text="@string/conversation_media_list_title"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/back"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/mediaList"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="?attr/color_grey_100"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/title" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/mediaList"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="?attr/color_grey_100"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/title" />
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/default_text_style_800"
|
||||
android:id="@+id/no_result"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/conversation_no_media_found"
|
||||
android:textColor="?attr/color_main2_600"
|
||||
android:textSize="16sp"
|
||||
android:visibility="@{viewModel.mediaList.empty ? View.VISIBLE : View.GONE}"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/title"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/default_text_style_800"
|
||||
android:id="@+id/no_result"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/conversation_no_media_found"
|
||||
android:textColor="?attr/color_main2_600"
|
||||
android:textSize="16sp"
|
||||
android:visibility="@{viewModel.mediaList.empty ? View.VISIBLE : View.GONE}"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/title"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<include
|
||||
layout="@layout/operation_in_progress"
|
||||
bind:visibility="@{viewModel.operationInProgress}" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</layout>
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
type="View.OnClickListener" />
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="org.linphone.ui.main.chat.viewmodel.ConversationMediaListViewModel" />
|
||||
type="org.linphone.ui.file_viewer.viewmodel.MediaListViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
|
|
@ -23,6 +23,12 @@
|
|||
android:layout_height="match_parent"
|
||||
android:background="@color/black">
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="@{viewModel.fullScreenMode ? View.GONE : View.VISIBLE}"
|
||||
app:constraint_referenced_ids="back, title, share, save, file_name"/>
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/media_view_pager"
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -39,7 +45,6 @@
|
|||
android:padding="15dp"
|
||||
android:src="@drawable/caret_left"
|
||||
android:contentDescription="@string/content_description_go_back_icon"
|
||||
android:visibility="@{viewModel.fullScreenMode ? View.GONE : View.VISIBLE}"
|
||||
app:tint="?attr/color_main1_500"
|
||||
app:layout_constraintBottom_toBottomOf="@id/title"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
|
@ -53,7 +58,6 @@
|
|||
android:background="?attr/color_main2_000"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:visibility="@{viewModel.fullScreenMode ? View.GONE : View.VISIBLE}"
|
||||
app:layout_constraintEnd_toStartOf="@id/share"
|
||||
app:layout_constraintStart_toEndOf="@id/back"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
|
@ -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"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/toasts_area"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginTop="@dimen/toast_top_margin"
|
||||
android:layout_marginStart="15dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
app:layout_constraintWidth_max="@dimen/toast_max_width"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</layout>
|
||||
|
|
@ -5,13 +5,16 @@
|
|||
|
||||
<data>
|
||||
<import type="android.view.View" />
|
||||
<variable
|
||||
name="toggleFullScreenModeClickListener"
|
||||
type="View.OnClickListener" />
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="org.linphone.ui.main.file_media_viewer.viewmodel.MediaViewModel" />
|
||||
type="org.linphone.ui.file_viewer.viewmodel.MediaViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:onClick="@{() -> viewModel.toggleFullScreen()}"
|
||||
android:onClick="@{toggleFullScreenModeClickListener}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/black">
|
||||
|
|
@ -58,7 +61,7 @@
|
|||
|
||||
<com.github.chrisbanes.photoview.PhotoView
|
||||
android:id="@+id/image"
|
||||
android:onClick="@{() -> viewModel.toggleFullScreen()}"
|
||||
android:onClick="@{toggleFullScreenModeClickListener}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:src="@drawable/illu"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
type="View.OnClickListener" />
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="org.linphone.ui.main.file_media_viewer.viewmodel.FileViewModel" />
|
||||
type="org.linphone.ui.file_viewer.viewmodel.FileViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
|
|
@ -21,6 +21,12 @@
|
|||
android:layout_height="match_parent"
|
||||
android:background="@color/black">
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="@{viewModel.fullScreenMode ? View.GONE : View.VISIBLE}"
|
||||
app:constraint_referenced_ids="back, title, share, save, file_name"/>
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/pdf_view_pager"
|
||||
android:layout_width="match_parent"
|
||||
|
|
@ -79,7 +85,6 @@
|
|||
android:padding="15dp"
|
||||
android:src="@drawable/caret_left"
|
||||
android:contentDescription="@string/content_description_go_back_icon"
|
||||
android:visibility="@{viewModel.fullScreenMode ? View.GONE : View.VISIBLE}"
|
||||
app:tint="?attr/color_main1_500"
|
||||
app:layout_constraintBottom_toBottomOf="@id/title"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
|
@ -94,7 +99,6 @@
|
|||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:text=""
|
||||
android:visibility="@{viewModel.fullScreenMode ? View.GONE : View.VISIBLE}"
|
||||
app:layout_constraintEnd_toStartOf="@id/share"
|
||||
app:layout_constraintStart_toEndOf="@id/back"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
|
@ -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"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/toasts_area"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginTop="@dimen/toast_top_margin"
|
||||
android:layout_marginStart="15dp"
|
||||
android:layout_marginEnd="15dp"
|
||||
app:layout_constraintWidth_max="@dimen/toast_max_width"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</layout>
|
||||
|
|
@ -216,12 +216,6 @@
|
|||
app:launchSingleTop="true"
|
||||
app:popUpTo="@id/helpFragment"
|
||||
app:popUpToInclusive="true"/>
|
||||
<action
|
||||
android:id="@+id/action_debugFragment_to_fileViewerFragment"
|
||||
app:destination="@id/fileViewerFragment"
|
||||
app:launchSingleTop="true"
|
||||
app:enterAnim="@anim/slide_in"
|
||||
app:popExitAnim="@anim/slide_out" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
|
|
@ -269,18 +263,6 @@
|
|||
app:enterAnim="@anim/slide_in"
|
||||
app:popExitAnim="@anim/slide_out"
|
||||
app:launchSingleTop="true" />
|
||||
<action
|
||||
android:id="@+id/action_conversationsListFragment_to_mediaListViewerFragment"
|
||||
app:destination="@id/mediaListViewerFragment"
|
||||
app:launchSingleTop="true"
|
||||
app:enterAnim="@anim/slide_in"
|
||||
app:popExitAnim="@anim/slide_out" />
|
||||
<action
|
||||
android:id="@+id/action_conversationsListFragment_to_fileViewerFragment"
|
||||
app:destination="@id/fileViewerFragment"
|
||||
app:launchSingleTop="true"
|
||||
app:enterAnim="@anim/slide_in"
|
||||
app:popExitAnim="@anim/slide_out" />
|
||||
<action
|
||||
android:id="@+id/action_conversationsListFragment_to_accountProfileFragment"
|
||||
app:destination="@id/accountProfileFragment"
|
||||
|
|
@ -379,36 +361,6 @@
|
|||
app:argType="string" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/fileViewerFragment"
|
||||
android:name="org.linphone.ui.main.file_media_viewer.fragment.FileViewerFragment"
|
||||
android:label="FileViewerFragment"
|
||||
tools:layout="@layout/file_viewer_fragment">
|
||||
<argument
|
||||
android:name="path"
|
||||
app:argType="string" />
|
||||
<argument
|
||||
android:name="content"
|
||||
app:argType="string"
|
||||
app:nullable="true" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/mediaListViewerFragment"
|
||||
android:name="org.linphone.ui.main.file_media_viewer.fragment.MediaListViewerFragment"
|
||||
android:label="MediaListViewerFragment"
|
||||
tools:layout="@layout/file_media_viewer_fragment">
|
||||
<argument
|
||||
android:name="localSipUri"
|
||||
app:argType="string" />
|
||||
<argument
|
||||
android:name="remoteSipUri"
|
||||
app:argType="string" />
|
||||
<argument
|
||||
android:name="path"
|
||||
app:argType="string" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/ldapServerConfigurationFragment"
|
||||
android:name="org.linphone.ui.main.settings.fragment.LdapServerConfigurationFragment"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue