mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-01-17 11:28:06 +00:00
Added plain text file viewer
This commit is contained in:
parent
de2f247c5f
commit
5d3d8eeedc
12 changed files with 122 additions and 35 deletions
|
|
@ -30,7 +30,6 @@ import android.content.Intent
|
|||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.webkit.MimeTypeMap
|
||||
import androidx.annotation.AnyThread
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.annotation.WorkerThread
|
||||
|
|
@ -749,7 +748,7 @@ class NotificationsManager @MainThread constructor(private val context: Context)
|
|||
val filePath = contentUri.toString()
|
||||
val extension = FileUtils.getExtensionFromFileName(filePath)
|
||||
if (extension.isNotEmpty()) {
|
||||
val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
|
||||
val mime = FileUtils.getMimeTypeFromExtension(extension)
|
||||
notifiableMessage.filePath = contentUri
|
||||
notifiableMessage.fileMime = mime
|
||||
Log.i("$TAG Added file $contentUri with MIME $mime to notification")
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.view.animation.Animation
|
||||
import android.view.animation.AnimationUtils
|
||||
import android.webkit.MimeTypeMap
|
||||
import androidx.annotation.UiThread
|
||||
import androidx.core.view.doOnPreDraw
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
|
|
@ -244,9 +243,9 @@ class ConversationsListFragment : AbstractTopBarFragment() {
|
|||
if (findNavController().currentDestination?.id == R.id.conversationsListFragment) {
|
||||
Log.i("$TAG Navigating to file viewer fragment with path [$path]")
|
||||
val extension = FileUtils.getExtensionFromFileName(path)
|
||||
val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
|
||||
val mime = FileUtils.getMimeTypeFromExtension(extension)
|
||||
when (FileUtils.getMimeType(mime)) {
|
||||
FileUtils.MimeType.Image, FileUtils.MimeType.Video, FileUtils.MimeType.Pdf -> {
|
||||
FileUtils.MimeType.Image, FileUtils.MimeType.Video, FileUtils.MimeType.Pdf, FileUtils.MimeType.PlainText -> {
|
||||
val action =
|
||||
FileViewerFragmentDirections.actionGlobalFileViewerFragment(path)
|
||||
findNavController().navigate(action)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package org.linphone.ui.main.chat.model
|
||||
|
||||
import android.webkit.MimeTypeMap
|
||||
import androidx.annotation.AnyThread
|
||||
import androidx.annotation.UiThread
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
|
|
@ -41,7 +40,7 @@ class FileModel @AnyThread constructor(
|
|||
val extension = FileUtils.getExtensionFromFileName(file)
|
||||
isPdf = extension == "pdf"
|
||||
|
||||
val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
|
||||
val mime = FileUtils.getMimeTypeFromExtension(extension)
|
||||
mimeType = FileUtils.getMimeType(mime)
|
||||
isImage = mimeType == FileUtils.MimeType.Image
|
||||
isVideoPreview = mimeType == FileUtils.MimeType.Video
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ class FileViewerFragment : GenericFragment() {
|
|||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
postponeEnterTransition()
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewModel = ViewModelProvider(this)[FileViewModel::class.java]
|
||||
|
|
@ -75,6 +76,21 @@ class FileViewerFragment : GenericFragment() {
|
|||
goBack()
|
||||
}
|
||||
|
||||
viewModel.fileReadyEvent.observe(viewLifecycleOwner) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.setShareClickListener {
|
||||
lifecycleScope.launch {
|
||||
val filePath = FileUtils.getProperFilePath(path)
|
||||
|
|
|
|||
|
|
@ -8,14 +8,17 @@ import android.net.Uri
|
|||
import android.os.Environment
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.provider.MediaStore
|
||||
import android.webkit.MimeTypeMap
|
||||
import android.text.PrecomputedText
|
||||
import android.widget.ImageView
|
||||
import androidx.annotation.UiThread
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import java.io.BufferedReader
|
||||
import java.io.File
|
||||
import java.io.FileReader
|
||||
import java.lang.IllegalStateException
|
||||
import java.lang.StringBuilder
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -50,6 +53,12 @@ class FileViewModel @UiThread constructor() : ViewModel() {
|
|||
|
||||
val isVideoPlaying = MutableLiveData<Boolean>()
|
||||
|
||||
val isText = MutableLiveData<Boolean>()
|
||||
|
||||
val text = MutableLiveData<String>()
|
||||
|
||||
val fileReadyEvent = MutableLiveData<Event<Boolean>>()
|
||||
|
||||
val pdfRendererReadyEvent: MutableLiveData<Event<Boolean>> by lazy {
|
||||
MutableLiveData<Event<Boolean>>()
|
||||
}
|
||||
|
|
@ -100,45 +109,34 @@ class FileViewModel @UiThread constructor() : ViewModel() {
|
|||
fileName.value = name
|
||||
|
||||
val extension = FileUtils.getExtensionFromFileName(name)
|
||||
val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
|
||||
val mime = FileUtils.getMimeTypeFromExtension(extension)
|
||||
when (FileUtils.getMimeType(mime)) {
|
||||
FileUtils.MimeType.Pdf -> {
|
||||
Log.i("$TAG File [$file] seems to be a PDF")
|
||||
isPdf.value = true
|
||||
|
||||
viewModelScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
val input = ParcelFileDescriptor.open(
|
||||
File(file),
|
||||
ParcelFileDescriptor.MODE_READ_ONLY
|
||||
)
|
||||
pdfRenderer = PdfRenderer(input)
|
||||
val count = pdfRenderer.pageCount
|
||||
Log.i("$TAG $count pages in file $file")
|
||||
pdfPages.postValue(count.toString())
|
||||
pdfCurrentPage.postValue("1")
|
||||
pdfRendererReadyEvent.postValue(Event(true))
|
||||
}
|
||||
}
|
||||
loadPdf()
|
||||
}
|
||||
FileUtils.MimeType.Image -> {
|
||||
Log.i("$TAG File [$file] seems to be an image")
|
||||
isImage.value = true
|
||||
path.value = file
|
||||
fileReadyEvent.value = Event(true)
|
||||
}
|
||||
FileUtils.MimeType.Video -> {
|
||||
Log.i("$TAG File [$file] seems to be a video")
|
||||
isVideo.value = true
|
||||
isVideoPlaying.value = false
|
||||
fileReadyEvent.value = Event(true)
|
||||
}
|
||||
FileUtils.MimeType.Audio -> {
|
||||
// TODO: handle audio files
|
||||
fileReadyEvent.value = Event(true)
|
||||
}
|
||||
FileUtils.MimeType.PlainText -> {
|
||||
// TODO: handle plain text files
|
||||
Log.i("$TAG File [$file] seems to be plain text")
|
||||
loadPlainText()
|
||||
}
|
||||
else -> {
|
||||
// TODO: open native app for unsupported files
|
||||
fileReadyEvent.value = Event(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -261,6 +259,51 @@ class FileViewModel @UiThread constructor() : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun loadPdf() {
|
||||
isPdf.value = true
|
||||
|
||||
viewModelScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
val input = ParcelFileDescriptor.open(
|
||||
File(filePath),
|
||||
ParcelFileDescriptor.MODE_READ_ONLY
|
||||
)
|
||||
pdfRenderer = PdfRenderer(input)
|
||||
val count = pdfRenderer.pageCount
|
||||
Log.i("$TAG $count pages in file $filePath")
|
||||
pdfPages.postValue(count.toString())
|
||||
pdfCurrentPage.postValue("1")
|
||||
pdfRendererReadyEvent.postValue(Event(true))
|
||||
fileReadyEvent.postValue(Event(true))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadPlainText() {
|
||||
isText.value = true
|
||||
|
||||
viewModelScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val br = BufferedReader(FileReader(filePath))
|
||||
var line: String?
|
||||
val textBuilder = StringBuilder()
|
||||
while (br.readLine().also { line = it } != null) {
|
||||
textBuilder.append(line)
|
||||
textBuilder.append('\n')
|
||||
}
|
||||
br.close()
|
||||
text.postValue(textBuilder.toString())
|
||||
Log.i("$TAG Finished reading file [$filePath]")
|
||||
fileReadyEvent.postValue(Event(true))
|
||||
// TODO FIXME : improve performances !
|
||||
} catch (e: Exception) {
|
||||
Log.e("$TAG Exception trying to read file [$filePath] as text: $e")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private suspend fun addContentToMediaStore(
|
||||
path: String
|
||||
|
|
@ -285,7 +328,7 @@ class FileViewModel @UiThread constructor() : ViewModel() {
|
|||
val relativePath = "$directory/$appName"
|
||||
val fileName = FileUtils.getNameFromFilePath(path)
|
||||
val extension = FileUtils.getExtensionFromFileName(fileName)
|
||||
val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
|
||||
val mime = FileUtils.getMimeTypeFromExtension(extension)
|
||||
|
||||
val context = coreContext.context
|
||||
val mediaStoreFilePath = when {
|
||||
|
|
|
|||
|
|
@ -65,21 +65,21 @@ class FileUtils {
|
|||
@AnyThread
|
||||
fun isExtensionImage(path: String): Boolean {
|
||||
val extension = getExtensionFromFileName(path)
|
||||
val type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
|
||||
val type = getMimeTypeFromExtension(extension)
|
||||
return getMimeType(type) == MimeType.Image
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
fun isExtensionVideo(path: String): Boolean {
|
||||
val extension = getExtensionFromFileName(path)
|
||||
val type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
|
||||
val type = getMimeTypeFromExtension(extension)
|
||||
return getMimeType(type) == MimeType.Video
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
fun isExtensionAudio(path: String): Boolean {
|
||||
val extension = getExtensionFromFileName(path)
|
||||
val type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
|
||||
val type = getMimeTypeFromExtension(extension)
|
||||
return getMimeType(type) == MimeType.Audio
|
||||
}
|
||||
|
||||
|
|
@ -96,12 +96,18 @@ class FileUtils {
|
|||
return extension.lowercase(Locale.getDefault())
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
fun getMimeTypeFromExtension(extension: String): String {
|
||||
return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) ?: "file/$extension"
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
fun getMimeType(type: String?): MimeType {
|
||||
if (type.isNullOrEmpty()) return MimeType.Unknown
|
||||
return when {
|
||||
type.startsWith("image/") -> MimeType.Image
|
||||
type.startsWith("text/plain") -> MimeType.PlainText
|
||||
type.startsWith("text/") -> MimeType.PlainText
|
||||
type.endsWith("/log") -> MimeType.PlainText
|
||||
type.startsWith("video/") -> MimeType.Video
|
||||
type.startsWith("audio/") -> MimeType.Audio
|
||||
type.startsWith("application/pdf") -> MimeType.Pdf
|
||||
|
|
@ -220,7 +226,7 @@ class FileUtils {
|
|||
}
|
||||
|
||||
val extension = getExtensionFromFileName(name)
|
||||
val type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
|
||||
val type = getMimeTypeFromExtension(extension)
|
||||
val isImage = getMimeType(type) == MimeType.Image
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@
|
|||
android:layout_height="@dimen/chat_bubble_big_image_max_size"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:visibility="@{model.filesList.size() == 1 && model.firstImagePath.length() >= 0 ? View.VISIBLE : View.GONE, default=gone}"
|
||||
android:visibility="@{model.filesList.size() == 1 && model.firstImagePath.length() > 0 ? View.VISIBLE : View.GONE, default=gone}"
|
||||
coilBubble="@{model.firstImagePath}"/>
|
||||
|
||||
<ViewStub
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@
|
|||
android:layout_height="@dimen/chat_bubble_big_image_max_size"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:visibility="@{model.filesList.size() == 1 && model.firstImagePath.length() >= 0 ? View.VISIBLE : View.GONE, default=gone}"
|
||||
android:visibility="@{model.filesList.size() == 1 && model.firstImagePath.length() > 0 ? View.VISIBLE : View.GONE, default=gone}"
|
||||
coilBubble="@{model.firstImagePath}"/>
|
||||
|
||||
<ViewStub
|
||||
|
|
|
|||
|
|
@ -82,6 +82,27 @@
|
|||
coilFile="@{viewModel.path}"
|
||||
android:visibility="@{viewModel.isImage ? View.VISIBLE : View.GONE, default=gone}" />
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/text"
|
||||
android:background="?attr/color_main2_000"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="@{viewModel.isText ? View.VISIBLE : View.GONE, default=gone}" >
|
||||
|
||||
<TextView
|
||||
style="@style/default_text_style"
|
||||
android:onClick="@{() -> viewModel.toggleFullScreen()}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:singleLine="false"
|
||||
android:textIsSelectable="true"
|
||||
android:textColor="?attr/color_main2_900"
|
||||
android:text="@{viewModel.text}"/>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/back"
|
||||
android:onClick="@{backClickListener}"
|
||||
|
|
@ -159,4 +180,5 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</layout>
|
||||
|
|
@ -28,6 +28,7 @@
|
|||
<item name="color_main2_600">@color/gray_main2_400</item>
|
||||
<item name="color_main2_700">@color/gray_main2_300</item>
|
||||
<item name="color_main2_800">@color/gray_main2_200</item>
|
||||
<item name="color_main2_900">@color/white</item>
|
||||
|
||||
<item name="color_grey_100">@color/gray_900</item>
|
||||
<item name="color_grey_200">@color/gray_800</item>
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
<attr name="color_main2_600" format="color"/>
|
||||
<attr name="color_main2_700" format="color"/>
|
||||
<attr name="color_main2_800" format="color"/>
|
||||
<attr name="color_main2_900" format="color"/>
|
||||
|
||||
<attr name="color_grey_100" format="color"/>
|
||||
<attr name="color_grey_200" format="color"/>
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
<item name="color_main2_600">@color/gray_main2_600</item>
|
||||
<item name="color_main2_700">@color/gray_main2_700</item>
|
||||
<item name="color_main2_800">@color/gray_main2_800</item>
|
||||
<item name="color_main2_900">@color/black</item>
|
||||
|
||||
<item name="color_grey_100">@color/gray_100</item>
|
||||
<item name="color_grey_200">@color/gray_200</item>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue