mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-01-17 11:28:06 +00:00
Added save on disk for plain text file, reworked share button to use native sharing
This commit is contained in:
parent
70e25b7792
commit
c35025aedb
5 changed files with 168 additions and 77 deletions
|
|
@ -33,6 +33,8 @@ class FileModel @AnyThread constructor(
|
|||
|
||||
val mimeType: FileUtils.MimeType
|
||||
|
||||
val mimeTypeString: String
|
||||
|
||||
val isMedia: Boolean
|
||||
|
||||
val isImage: Boolean
|
||||
|
|
@ -56,6 +58,8 @@ class FileModel @AnyThread constructor(
|
|||
isPdf = extension == "pdf"
|
||||
|
||||
val mime = FileUtils.getMimeTypeFromExtension(extension)
|
||||
mimeTypeString = mime
|
||||
|
||||
mimeType = FileUtils.getMimeType(mime)
|
||||
isImage = mimeType == FileUtils.MimeType.Image
|
||||
isVideoPreview = mimeType == FileUtils.MimeType.Video
|
||||
|
|
@ -68,6 +72,7 @@ class FileModel @AnyThread constructor(
|
|||
)
|
||||
} else {
|
||||
mimeType = FileUtils.MimeType.Unknown
|
||||
mimeTypeString = "application/octet-stream"
|
||||
isPdf = false
|
||||
isImage = false
|
||||
isVideoPreview = false
|
||||
|
|
|
|||
|
|
@ -9,12 +9,14 @@ 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.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
|
||||
import java.io.File
|
||||
import kotlinx.coroutines.launch
|
||||
import org.linphone.R
|
||||
import org.linphone.core.tools.Log
|
||||
|
|
@ -23,7 +25,6 @@ import org.linphone.ui.main.MainActivity
|
|||
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.GenericFragment
|
||||
import org.linphone.utils.Event
|
||||
import org.linphone.utils.FileUtils
|
||||
|
||||
@UiThread
|
||||
|
|
@ -31,7 +32,7 @@ class FileViewerFragment : GenericFragment() {
|
|||
companion object {
|
||||
private const val TAG = "[File Viewer Fragment]"
|
||||
|
||||
private const val EXPORT_PDF = 10
|
||||
private const val EXPORT_FILE_AS_DOCUMENT = 10
|
||||
}
|
||||
|
||||
private lateinit var binding: FileViewerFragmentBinding
|
||||
|
|
@ -101,17 +102,7 @@ class FileViewerFragment : GenericFragment() {
|
|||
}
|
||||
|
||||
binding.setShareClickListener {
|
||||
lifecycleScope.launch {
|
||||
val filePath = FileUtils.getProperFilePath(path)
|
||||
val copy = FileUtils.getFilePath(requireContext(), Uri.parse(filePath), false)
|
||||
if (!copy.isNullOrEmpty()) {
|
||||
sharedViewModel.filesToShareFromIntent.value = arrayListOf(copy)
|
||||
Log.i("$TAG Sharing file [$copy], going back to conversations list")
|
||||
sharedViewModel.closeSlidingPaneEvent.value = Event(true)
|
||||
} else {
|
||||
Log.e("$TAG Failed to copy file [$filePath] to share!")
|
||||
}
|
||||
}
|
||||
shareFile()
|
||||
}
|
||||
|
||||
viewModel.pdfRendererReadyEvent.observe(viewLifecycleOwner) {
|
||||
|
|
@ -126,6 +117,17 @@ class FileViewerFragment : GenericFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
viewModel.exportPlainTextFileEvent.observe(viewLifecycleOwner) {
|
||||
it.consume { name ->
|
||||
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "text/plain"
|
||||
putExtra(Intent.EXTRA_TITLE, name)
|
||||
}
|
||||
startActivityForResult(intent, EXPORT_FILE_AS_DOCUMENT)
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.exportPdfEvent.observe(viewLifecycleOwner) {
|
||||
it.consume { name ->
|
||||
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
|
|
@ -133,7 +135,7 @@ class FileViewerFragment : GenericFragment() {
|
|||
type = "application/pdf"
|
||||
putExtra(Intent.EXTRA_TITLE, name)
|
||||
}
|
||||
startActivityForResult(intent, EXPORT_PDF)
|
||||
startActivityForResult(intent, EXPORT_FILE_AS_DOCUMENT)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -178,10 +180,10 @@ class FileViewerFragment : GenericFragment() {
|
|||
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (requestCode == EXPORT_PDF && resultCode == Activity.RESULT_OK) {
|
||||
if (requestCode == EXPORT_FILE_AS_DOCUMENT && resultCode == Activity.RESULT_OK) {
|
||||
data?.data?.also { documentUri ->
|
||||
Log.i("$TAG Exported PDF should be stored in URI [$documentUri]")
|
||||
viewModel.copyPdfToUri(documentUri)
|
||||
Log.i("$TAG Exported file should be stored in URI [$documentUri]")
|
||||
viewModel.copyFileToUri(documentUri)
|
||||
}
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
|
|
@ -196,4 +198,36 @@ class FileViewerFragment : GenericFragment() {
|
|||
"$TAG Setting screen size ${viewModel.screenWidth}/${viewModel.screenHeight} for PDF renderer"
|
||||
)
|
||||
}
|
||||
|
||||
private fun shareFile() {
|
||||
lifecycleScope.launch {
|
||||
val filePath = FileUtils.getProperFilePath(viewModel.getFilePath())
|
||||
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, viewModel.fileName.value.orEmpty())
|
||||
type = viewModel.mimeType.value.orEmpty()
|
||||
}
|
||||
|
||||
val shareIntent = Intent.createChooser(sendIntent, null)
|
||||
startActivity(shareIntent)
|
||||
} else {
|
||||
Log.e("$TAG Failed to copy file [$filePath] to share!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,16 +19,19 @@
|
|||
*/
|
||||
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
|
||||
|
|
@ -40,7 +43,6 @@ import org.linphone.ui.main.chat.viewmodel.ConversationMediaListViewModel
|
|||
import org.linphone.ui.main.file_media_viewer.adapter.MediaListAdapter
|
||||
import org.linphone.ui.main.fragment.GenericFragment
|
||||
import org.linphone.utils.AppUtils
|
||||
import org.linphone.utils.Event
|
||||
import org.linphone.utils.FileUtils
|
||||
|
||||
class MediaListViewerFragment : GenericFragment() {
|
||||
|
|
@ -135,26 +137,7 @@ class MediaListViewerFragment : GenericFragment() {
|
|||
}
|
||||
|
||||
binding.setShareClickListener {
|
||||
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), false)
|
||||
if (!copy.isNullOrEmpty()) {
|
||||
sharedViewModel.filesToShareFromIntent.value = arrayListOf(copy)
|
||||
Log.i("$TAG Sharing file [$copy], going back to conversations list")
|
||||
sharedViewModel.closeSlidingPaneEvent.value = Event(true)
|
||||
} 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"
|
||||
)
|
||||
}
|
||||
shareFile()
|
||||
}
|
||||
|
||||
binding.setExportClickListener {
|
||||
|
|
@ -218,4 +201,45 @@ class MediaListViewerFragment : GenericFragment() {
|
|||
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
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"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ class FileViewModel @UiThread constructor() : ViewModel() {
|
|||
|
||||
val fileName = MutableLiveData<String>()
|
||||
|
||||
val mimeType = MutableLiveData<String>()
|
||||
|
||||
val fullScreenMode = MutableLiveData<Boolean>()
|
||||
|
||||
val isPdf = MutableLiveData<Boolean>()
|
||||
|
|
@ -38,14 +40,16 @@ class FileViewModel @UiThread constructor() : ViewModel() {
|
|||
|
||||
val pdfPages = MutableLiveData<String>()
|
||||
|
||||
val isAudio = MutableLiveData<Boolean>()
|
||||
|
||||
val isText = MutableLiveData<Boolean>()
|
||||
|
||||
val text = MutableLiveData<String>()
|
||||
|
||||
val fileReadyEvent = MutableLiveData<Event<Boolean>>()
|
||||
|
||||
val exportPlainTextFileEvent: MutableLiveData<Event<String>> by lazy {
|
||||
MutableLiveData<Event<String>>()
|
||||
}
|
||||
|
||||
val pdfRendererReadyEvent: MutableLiveData<Event<Boolean>> by lazy {
|
||||
MutableLiveData<Event<Boolean>>()
|
||||
}
|
||||
|
|
@ -87,31 +91,27 @@ class FileViewModel @UiThread constructor() : ViewModel() {
|
|||
fun loadFile(file: String, content: String? = null) {
|
||||
fullScreenMode.value = true
|
||||
|
||||
filePath = file
|
||||
val name = FileUtils.getNameFromFilePath(file)
|
||||
fileName.value = name
|
||||
|
||||
if (!content.isNullOrEmpty()) {
|
||||
isText.value = true
|
||||
text.postValue(content)
|
||||
mimeType.postValue("text/plain")
|
||||
Log.i("$TAG Using pre-loaded content as PlainText")
|
||||
fileReadyEvent.postValue(Event(true))
|
||||
return
|
||||
}
|
||||
|
||||
filePath = file
|
||||
val extension = FileUtils.getExtensionFromFileName(name)
|
||||
val mime = FileUtils.getMimeTypeFromExtension(extension)
|
||||
mimeType.postValue(mime)
|
||||
when (FileUtils.getMimeType(mime)) {
|
||||
FileUtils.MimeType.Pdf -> {
|
||||
Log.i("$TAG File [$file] seems to be a PDF")
|
||||
loadPdf()
|
||||
}
|
||||
FileUtils.MimeType.Audio -> {
|
||||
Log.i("$TAG File [$file] seems to be an audio")
|
||||
// TODO: handle audio files
|
||||
isAudio.value = true
|
||||
fileReadyEvent.value = Event(true)
|
||||
}
|
||||
FileUtils.MimeType.PlainText -> {
|
||||
Log.i("$TAG File [$file] seems to be plain text")
|
||||
loadPlainText()
|
||||
|
|
@ -174,42 +174,36 @@ class FileViewModel @UiThread constructor() : ViewModel() {
|
|||
}
|
||||
|
||||
@UiThread
|
||||
fun exportToMediaStore() {
|
||||
fun getFilePath(): String {
|
||||
if (::filePath.isInitialized) {
|
||||
if (isPdf.value == true) {
|
||||
Log.i("$TAG Exporting PDF as document")
|
||||
exportPdfEvent.postValue(Event(fileName.value.orEmpty()))
|
||||
} else {
|
||||
viewModelScope.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
|
||||
)
|
||||
showGreenToastEvent.postValue(Event(Pair(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
|
||||
)
|
||||
showRedToastEvent.postValue(Event(Pair(message, R.drawable.x)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return filePath
|
||||
}
|
||||
|
||||
Log.i("$TAG File path wasn't initialized, storing memory content as file")
|
||||
val name = fileName.value.orEmpty()
|
||||
val file = FileUtils.getFileStorageCacheDir(
|
||||
fileName = name,
|
||||
overrideExisting = true
|
||||
)
|
||||
savePlainTextFileToUri(file)
|
||||
filePath = file.absolutePath
|
||||
return filePath
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun exportToMediaStore() {
|
||||
if (isPdf.value == true) {
|
||||
Log.i("$TAG Exporting PDF as document")
|
||||
exportPdfEvent.postValue(Event(fileName.value.orEmpty()))
|
||||
} else {
|
||||
Log.e("$TAG Filepath wasn't initialized!")
|
||||
Log.i("$TAG Exporting plain text content as document")
|
||||
exportPlainTextFileEvent.postValue(Event(fileName.value.orEmpty()))
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun copyPdfToUri(dest: Uri) {
|
||||
val source = Uri.parse(FileUtils.getProperFilePath(filePath))
|
||||
fun copyFileToUri(dest: Uri) {
|
||||
val source = Uri.parse(FileUtils.getProperFilePath(getFilePath()))
|
||||
Log.i("$TAG Copying file URI [$source] to [$dest]")
|
||||
viewModelScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
|
|
@ -233,6 +227,32 @@ class FileViewModel @UiThread constructor() : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private fun savePlainTextFileToUri(dest: File) {
|
||||
Log.i("$TAG Saving text to file [${dest.absolutePath}]")
|
||||
viewModelScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
val result = FileUtils.dumpStringToFile(text.value.orEmpty(), dest)
|
||||
if (result) {
|
||||
Log.i(
|
||||
"$TAG Text has been successfully exported to documents"
|
||||
)
|
||||
val message = AppUtils.getString(
|
||||
R.string.toast_file_successfully_exported_to_documents
|
||||
)
|
||||
showGreenToastEvent.postValue(Event(Pair(message, R.drawable.check)))
|
||||
} else {
|
||||
Log.e("$TAG Failed to save text to documents!")
|
||||
val message = AppUtils.getString(
|
||||
R.string.toast_export_file_to_documents_error
|
||||
)
|
||||
showRedToastEvent.postValue(Event(Pair(message, R.drawable.x)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private fun loadPdf() {
|
||||
isPdf.value = true
|
||||
|
||||
|
|
@ -253,6 +273,7 @@ class FileViewModel @UiThread constructor() : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private fun loadPlainText() {
|
||||
isText.value = true
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ class MediaViewModel @UiThread constructor() : ViewModel() {
|
|||
|
||||
val isVideoPlaying = MutableLiveData<Boolean>()
|
||||
|
||||
val isAudio = MutableLiveData<Boolean>()
|
||||
|
||||
val toggleVideoPlayPauseEvent: MutableLiveData<Event<Boolean>> by lazy {
|
||||
MutableLiveData<Event<Boolean>>()
|
||||
}
|
||||
|
|
@ -49,6 +51,11 @@ class MediaViewModel @UiThread constructor() : ViewModel() {
|
|||
isVideo.value = true
|
||||
isVideoPlaying.value = false
|
||||
}
|
||||
FileUtils.MimeType.Audio -> {
|
||||
Log.i("$TAG File [$file] seems to be an audio file")
|
||||
isAudio.value = true
|
||||
// TODO: handle audio files
|
||||
}
|
||||
else -> { }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue