From b818bf01550618562c29d55d16f4cb3c80e7690a Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 28 Jan 2021 21:08:18 +0100 Subject: [PATCH] Show files shared by chat message in app if possible --- .../linphone/activities/GenericActivity.kt | 14 +++ .../org/linphone/activities/Navigation.kt | 36 ++++++ .../activities/call/OutgoingCallActivity.kt | 7 +- .../call/fragments/ControlsFragment.kt | 7 +- .../linphone/activities/main/MainActivity.kt | 9 ++ .../chat/fragments/DetailChatRoomFragment.kt | 24 ++-- .../files/fragments/ImageViewerFragment.kt | 116 ++++++++++++++++++ .../main/files/fragments/PdfViewerFragment.kt | 57 +++++++++ .../files/fragments/TextViewerFragment.kt | 57 +++++++++ .../main/files/fragments/TopBarFragment.kt | 40 ++++++ .../files/fragments/VideoViewerFragment.kt | 65 ++++++++++ .../files/viewmodels/ImageFileViewModel.kt | 34 +++++ .../main/files/viewmodels/PdfFileViewModel.kt | 108 ++++++++++++++++ .../files/viewmodels/TextFileViewModel.kt | 78 ++++++++++++ .../files/viewmodels/VideoFileViewModel.kt | 60 +++++++++ .../main/viewmodels/SharedMainViewModel.kt | 2 + .../java/org/linphone/core/CoreContext.kt | 3 + .../main/java/org/linphone/utils/FileUtils.kt | 12 ++ .../layout/file_viewer_top_bar_fragment.xml | 38 ++++++ .../main/res/layout/image_viewer_fragment.xml | 43 +++++++ app/src/main/res/layout/pdf_viewer_cell.xml | 23 ++++ .../main/res/layout/pdf_viewer_fragment.xml | 46 +++++++ .../main/res/layout/text_viewer_fragment.xml | 46 +++++++ .../main/res/layout/video_viewer_fragment.xml | 40 ++++++ .../main/res/navigation/main_nav_graph.xml | 20 +++ app/src/main/res/values/strings.xml | 1 + 26 files changed, 965 insertions(+), 21 deletions(-) create mode 100644 app/src/main/java/org/linphone/activities/main/files/fragments/ImageViewerFragment.kt create mode 100644 app/src/main/java/org/linphone/activities/main/files/fragments/PdfViewerFragment.kt create mode 100644 app/src/main/java/org/linphone/activities/main/files/fragments/TextViewerFragment.kt create mode 100644 app/src/main/java/org/linphone/activities/main/files/fragments/TopBarFragment.kt create mode 100644 app/src/main/java/org/linphone/activities/main/files/fragments/VideoViewerFragment.kt create mode 100644 app/src/main/java/org/linphone/activities/main/files/viewmodels/ImageFileViewModel.kt create mode 100644 app/src/main/java/org/linphone/activities/main/files/viewmodels/PdfFileViewModel.kt create mode 100644 app/src/main/java/org/linphone/activities/main/files/viewmodels/TextFileViewModel.kt create mode 100644 app/src/main/java/org/linphone/activities/main/files/viewmodels/VideoFileViewModel.kt create mode 100644 app/src/main/res/layout/file_viewer_top_bar_fragment.xml create mode 100644 app/src/main/res/layout/image_viewer_fragment.xml create mode 100644 app/src/main/res/layout/pdf_viewer_cell.xml create mode 100644 app/src/main/res/layout/pdf_viewer_fragment.xml create mode 100644 app/src/main/res/layout/text_viewer_fragment.xml create mode 100644 app/src/main/res/layout/video_viewer_fragment.xml diff --git a/app/src/main/java/org/linphone/activities/GenericActivity.kt b/app/src/main/java/org/linphone/activities/GenericActivity.kt index e22ee764e..5441ca9e9 100644 --- a/app/src/main/java/org/linphone/activities/GenericActivity.kt +++ b/app/src/main/java/org/linphone/activities/GenericActivity.kt @@ -23,6 +23,8 @@ import android.annotation.SuppressLint import android.content.pm.ActivityInfo import android.content.res.Configuration import android.os.Bundle +import android.util.DisplayMetrics +import android.view.Display import android.view.Surface import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate @@ -73,6 +75,8 @@ abstract class GenericActivity : AppCompatActivity() { } } } + + updateScreenSize() } override fun onResume() { @@ -102,4 +106,14 @@ abstract class GenericActivity : AppCompatActivity() { fun isTablet(): Boolean { return resources.getBoolean(R.bool.isTablet) } + + private fun updateScreenSize() { + val metrics = DisplayMetrics() + val display: Display = windowManager.defaultDisplay + display.getRealMetrics(metrics) + val screenWidth = metrics.widthPixels.toFloat() + val screenHeight = metrics.heightPixels.toFloat() + coreContext.screenWidth = screenWidth + coreContext.screenHeight = screenHeight + } } diff --git a/app/src/main/java/org/linphone/activities/Navigation.kt b/app/src/main/java/org/linphone/activities/Navigation.kt index 0bf8df5db..5d0f39793 100644 --- a/app/src/main/java/org/linphone/activities/Navigation.kt +++ b/app/src/main/java/org/linphone/activities/Navigation.kt @@ -348,6 +348,42 @@ internal fun DetailChatRoomFragment.navigateToEphemeralInfo() { } } +internal fun DetailChatRoomFragment.navigateToTextFileViewer(secure: Boolean) { + val bundle = bundleOf("Secure" to secure) + findMasterNavController().navigate( + R.id.action_global_textViewerFragment, + bundle, + getRightToLeftAnimationNavOptions() + ) +} + +internal fun DetailChatRoomFragment.navigateToPdfFileViewer(secure: Boolean) { + val bundle = bundleOf("Secure" to secure) + findMasterNavController().navigate( + R.id.action_global_pdfViewerFragment, + bundle, + getRightToLeftAnimationNavOptions() + ) +} + +internal fun DetailChatRoomFragment.navigateToImageFileViewer(secure: Boolean) { + val bundle = bundleOf("Secure" to secure) + findMasterNavController().navigate( + R.id.action_global_imageViewerFragment, + bundle, + getRightToLeftAnimationNavOptions() + ) +} + +internal fun DetailChatRoomFragment.navigateToVideoFileViewer(secure: Boolean) { + val bundle = bundleOf("Secure" to secure) + findMasterNavController().navigate( + R.id.action_global_videoViewerFragment, + bundle, + getRightToLeftAnimationNavOptions() + ) +} + internal fun ChatRoomCreationFragment.navigateToGroupInfo() { if (findNavController().currentDestination?.id == R.id.chatRoomCreationFragment) { findNavController().navigate( diff --git a/app/src/main/java/org/linphone/activities/call/OutgoingCallActivity.kt b/app/src/main/java/org/linphone/activities/call/OutgoingCallActivity.kt index dd3983728..b4677d506 100644 --- a/app/src/main/java/org/linphone/activities/call/OutgoingCallActivity.kt +++ b/app/src/main/java/org/linphone/activities/call/OutgoingCallActivity.kt @@ -24,8 +24,6 @@ import android.animation.ValueAnimator import android.annotation.TargetApi import android.content.pm.PackageManager import android.os.Bundle -import android.util.DisplayMetrics -import android.view.Display import androidx.databinding.DataBindingUtil import androidx.lifecycle.ViewModelProvider import com.google.android.flexbox.FlexboxLayout @@ -179,10 +177,7 @@ class OutgoingCallActivity : ProximitySensorActivity() { } private fun initNumpadLayout() { - val metrics = DisplayMetrics() - val display: Display = windowManager.defaultDisplay - display.getRealMetrics(metrics) - val screenWidth = metrics.widthPixels.toFloat() + val screenWidth = coreContext.screenWidth numpadAnimator = ValueAnimator.ofFloat(screenWidth, 0f).apply { addUpdateListener { val value = it.animatedValue as Float diff --git a/app/src/main/java/org/linphone/activities/call/fragments/ControlsFragment.kt b/app/src/main/java/org/linphone/activities/call/fragments/ControlsFragment.kt index 69f5a05a8..b3e4db70d 100644 --- a/app/src/main/java/org/linphone/activities/call/fragments/ControlsFragment.kt +++ b/app/src/main/java/org/linphone/activities/call/fragments/ControlsFragment.kt @@ -27,8 +27,6 @@ import android.content.Intent import android.content.pm.PackageManager.PERMISSION_GRANTED import android.os.Bundle import android.os.SystemClock -import android.util.DisplayMetrics -import android.view.Display import androidx.lifecycle.ViewModelProvider import com.google.android.flexbox.FlexboxLayout import org.linphone.LinphoneApplication.Companion.coreContext @@ -242,10 +240,7 @@ class ControlsFragment : GenericFragment() { } private fun initNumpadLayout() { - val metrics = DisplayMetrics() - val display: Display = requireActivity().windowManager.defaultDisplay - display.getRealMetrics(metrics) - val screenWidth = metrics.widthPixels.toFloat() + val screenWidth = coreContext.screenWidth numpadAnimator = ValueAnimator.ofFloat(screenWidth, 0f).apply { addUpdateListener { val value = it.animatedValue as Float diff --git a/app/src/main/java/org/linphone/activities/main/MainActivity.kt b/app/src/main/java/org/linphone/activities/main/MainActivity.kt index 2721215e3..85ed627b8 100644 --- a/app/src/main/java/org/linphone/activities/main/MainActivity.kt +++ b/app/src/main/java/org/linphone/activities/main/MainActivity.kt @@ -70,6 +70,7 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin } private lateinit var tabsFragment: FragmentContainerView + private lateinit var statusFragment: FragmentContainerView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -110,6 +111,7 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin } tabsFragment = findViewById(R.id.tabs_fragment) + statusFragment = findViewById(R.id.status_fragment) } override fun onNewIntent(intent: Intent?) { @@ -154,6 +156,9 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin arguments: Bundle? ) { currentFocus?.hideKeyboard() + if (statusFragment.visibility == View.GONE) { + statusFragment.visibility = View.VISIBLE + } val motionLayout: MotionLayout = binding.content as MotionLayout if (corePreferences.enableAnimations) { @@ -171,6 +176,10 @@ class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestin } } + fun hideStatusFragment() { + statusFragment.visibility = View.GONE + } + private fun View.hideKeyboard() { val imm = context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager imm.hideSoftInputFromWindow(windowToken, 0) diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt index afaa56fa3..ce914cb5b 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt @@ -41,6 +41,7 @@ import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import java.io.File import kotlinx.coroutines.launch import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.corePreferences @@ -225,7 +226,19 @@ class DetailChatRoomFragment : MasterFragment - openFile(path) + if (!File(path).exists()) { + (requireActivity() as MainActivity).showSnackBar(R.string.chat_room_file_not_found) + } else { + Log.i("[Chat Message] Opening file: $path") + sharedViewModel.fileToOpen.value = path + when { + FileUtils.isExtensionImage(path) -> navigateToImageFileViewer(viewModel.chatRoom.currentParams.encryptionEnabled()) + FileUtils.isExtensionVideo(path) -> navigateToVideoFileViewer(viewModel.chatRoom.currentParams.encryptionEnabled()) + FileUtils.isExtensionPdf(path) -> navigateToPdfFileViewer(viewModel.chatRoom.currentParams.encryptionEnabled()) + FileUtils.isPlainTextFile(path) -> navigateToTextFileViewer(viewModel.chatRoom.currentParams.encryptionEnabled()) + else -> openFile(path) + } + } } }) @@ -581,14 +594,7 @@ class DetailChatRoomFragment : MasterFragment. + */ +package org.linphone.activities.main.files.fragments + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.GestureDetector +import android.view.MotionEvent +import android.view.ScaleGestureDetector +import androidx.lifecycle.ViewModelProvider +import org.linphone.R +import org.linphone.activities.main.files.viewmodels.ImageFileViewModel +import org.linphone.activities.main.files.viewmodels.ImageFileViewModelFactory +import org.linphone.activities.main.fragments.SecureFragment +import org.linphone.activities.main.viewmodels.SharedMainViewModel +import org.linphone.databinding.ImageViewerFragmentBinding + +class ImageViewerFragment : SecureFragment() { + private lateinit var viewModel: ImageFileViewModel + private lateinit var sharedViewModel: SharedMainViewModel + + private lateinit var gestureDetector: GestureDetector + private lateinit var scaleGestureDetector: ScaleGestureDetector + private var scaleFactor = 1.0f + + override fun getLayoutId(): Int = R.layout.image_viewer_fragment + + @SuppressLint("ClickableViewAccessibility") + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + + binding.lifecycleOwner = this + + sharedViewModel = requireActivity().run { + ViewModelProvider(this).get(SharedMainViewModel::class.java) + } + + val filePath = sharedViewModel.fileToOpen.value + filePath ?: return + + viewModel = ViewModelProvider( + this, + ImageFileViewModelFactory(filePath) + )[ImageFileViewModel::class.java] + binding.viewModel = viewModel + + isSecure = arguments?.getBoolean("Secure") ?: false + + gestureDetector = GestureDetector(requireContext(), object : + GestureDetector.SimpleOnGestureListener() { + override fun onDoubleTap(e: MotionEvent?): Boolean { + scaleFactor = if (scaleFactor == 1.0f) 2.0f else 1.0f + binding.imageView.scaleX = scaleFactor + binding.imageView.scaleY = scaleFactor + return true + } + + override fun onScroll( + e1: MotionEvent?, + e2: MotionEvent?, + distanceX: Float, + distanceY: Float + ): Boolean { + if (scaleFactor <= 1.0f) return false + + val scrollX = binding.imageView.scrollX + distanceX.toInt() + binding.imageView.scrollX = scrollX + + val scrollY = binding.imageView.scrollY + distanceY.toInt() + binding.imageView.scrollY = scrollY + return true + } + }) + + scaleGestureDetector = ScaleGestureDetector(requireContext(), object : + ScaleGestureDetector.SimpleOnScaleGestureListener() { + override fun onScale(detector: ScaleGestureDetector): Boolean { + scaleFactor *= detector.scaleFactor + scaleFactor = scaleFactor.coerceIn(0.1f, 5.0f) + binding.imageView.scaleX = scaleFactor + binding.imageView.scaleY = scaleFactor + return false + } + }) + + binding.imageView.setOnTouchListener { _, event -> + val previousScaleFactor = scaleFactor + scaleGestureDetector.onTouchEvent(event) + + if (previousScaleFactor != scaleFactor) { + // Prevent touch event from going further + return@setOnTouchListener true + } + + gestureDetector.onTouchEvent(event) + return@setOnTouchListener true + } + } +} diff --git a/app/src/main/java/org/linphone/activities/main/files/fragments/PdfViewerFragment.kt b/app/src/main/java/org/linphone/activities/main/files/fragments/PdfViewerFragment.kt new file mode 100644 index 000000000..825719fe7 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/main/files/fragments/PdfViewerFragment.kt @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.linphone.activities.main.files.fragments + +import android.os.Bundle +import androidx.lifecycle.ViewModelProvider +import org.linphone.R +import org.linphone.activities.main.files.viewmodels.PdfFileViewModel +import org.linphone.activities.main.files.viewmodels.PdfFileViewModelFactory +import org.linphone.activities.main.fragments.SecureFragment +import org.linphone.activities.main.viewmodels.SharedMainViewModel +import org.linphone.databinding.PdfViewerFragmentBinding + +class PdfViewerFragment : SecureFragment() { + private lateinit var viewModel: PdfFileViewModel + private lateinit var sharedViewModel: SharedMainViewModel + + override fun getLayoutId(): Int = R.layout.pdf_viewer_fragment + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + + binding.lifecycleOwner = this + + sharedViewModel = requireActivity().run { + ViewModelProvider(this).get(SharedMainViewModel::class.java) + } + + val filePath = sharedViewModel.fileToOpen.value + filePath ?: return + + viewModel = ViewModelProvider( + this, + PdfFileViewModelFactory(filePath) + )[PdfFileViewModel::class.java] + binding.viewModel = viewModel + + isSecure = arguments?.getBoolean("Secure") ?: false + } +} diff --git a/app/src/main/java/org/linphone/activities/main/files/fragments/TextViewerFragment.kt b/app/src/main/java/org/linphone/activities/main/files/fragments/TextViewerFragment.kt new file mode 100644 index 000000000..aa3dd7553 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/main/files/fragments/TextViewerFragment.kt @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.linphone.activities.main.files.fragments + +import android.os.Bundle +import androidx.lifecycle.ViewModelProvider +import org.linphone.R +import org.linphone.activities.main.files.viewmodels.TextFileViewModel +import org.linphone.activities.main.files.viewmodels.TextFileViewModelFactory +import org.linphone.activities.main.fragments.SecureFragment +import org.linphone.activities.main.viewmodels.SharedMainViewModel +import org.linphone.databinding.TextViewerFragmentBinding + +class TextViewerFragment : SecureFragment() { + private lateinit var viewModel: TextFileViewModel + private lateinit var sharedViewModel: SharedMainViewModel + + override fun getLayoutId(): Int = R.layout.text_viewer_fragment + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + + binding.lifecycleOwner = this + + sharedViewModel = requireActivity().run { + ViewModelProvider(this).get(SharedMainViewModel::class.java) + } + + val filePath = sharedViewModel.fileToOpen.value + filePath ?: return + + viewModel = ViewModelProvider( + this, + TextFileViewModelFactory(filePath) + )[TextFileViewModel::class.java] + binding.viewModel = viewModel + + isSecure = arguments?.getBoolean("Secure") ?: false + } +} diff --git a/app/src/main/java/org/linphone/activities/main/files/fragments/TopBarFragment.kt b/app/src/main/java/org/linphone/activities/main/files/fragments/TopBarFragment.kt new file mode 100644 index 000000000..572af0027 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/main/files/fragments/TopBarFragment.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.linphone.activities.main.files.fragments + +import android.os.Bundle +import androidx.navigation.fragment.findNavController +import org.linphone.R +import org.linphone.activities.GenericFragment +import org.linphone.databinding.FileViewerTopBarFragmentBinding + +class TopBarFragment : GenericFragment() { + override fun getLayoutId(): Int = R.layout.file_viewer_top_bar_fragment + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + + binding.lifecycleOwner = this + + binding.setBackClickListener { + findNavController().popBackStack() + } + } +} diff --git a/app/src/main/java/org/linphone/activities/main/files/fragments/VideoViewerFragment.kt b/app/src/main/java/org/linphone/activities/main/files/fragments/VideoViewerFragment.kt new file mode 100644 index 000000000..806dbf034 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/main/files/fragments/VideoViewerFragment.kt @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.linphone.activities.main.files.fragments + +import android.os.Bundle +import android.widget.MediaController +import androidx.lifecycle.ViewModelProvider +import org.linphone.R +import org.linphone.activities.main.files.viewmodels.VideoFileViewModel +import org.linphone.activities.main.files.viewmodels.VideoFileViewModelFactory +import org.linphone.activities.main.fragments.SecureFragment +import org.linphone.activities.main.viewmodels.SharedMainViewModel +import org.linphone.databinding.VideoViewerFragmentBinding + +class VideoViewerFragment : SecureFragment() { + private lateinit var viewModel: VideoFileViewModel + private lateinit var sharedViewModel: SharedMainViewModel + + override fun getLayoutId(): Int = R.layout.video_viewer_fragment + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + + binding.lifecycleOwner = this + + sharedViewModel = requireActivity().run { + ViewModelProvider(this).get(SharedMainViewModel::class.java) + } + + val filePath = sharedViewModel.fileToOpen.value + filePath ?: return + + viewModel = ViewModelProvider( + this, + VideoFileViewModelFactory(filePath) + )[VideoFileViewModel::class.java] + binding.viewModel = viewModel + + initVideoControls() + + isSecure = arguments?.getBoolean("Secure") ?: false + } + + private fun initVideoControls() { + val mediaController = MediaController(requireContext()) + viewModel.initMediaController(mediaController, binding.videoView) + } +} diff --git a/app/src/main/java/org/linphone/activities/main/files/viewmodels/ImageFileViewModel.kt b/app/src/main/java/org/linphone/activities/main/files/viewmodels/ImageFileViewModel.kt new file mode 100644 index 000000000..736bc3143 --- /dev/null +++ b/app/src/main/java/org/linphone/activities/main/files/viewmodels/ImageFileViewModel.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.linphone.activities.main.files.viewmodels + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider + +class ImageFileViewModelFactory(private val filePath: String) : + ViewModelProvider.NewInstanceFactory() { + + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T { + return ImageFileViewModel(filePath) as T + } +} + +class ImageFileViewModel(val filePath: String) : ViewModel() diff --git a/app/src/main/java/org/linphone/activities/main/files/viewmodels/PdfFileViewModel.kt b/app/src/main/java/org/linphone/activities/main/files/viewmodels/PdfFileViewModel.kt new file mode 100644 index 000000000..bb188a7ca --- /dev/null +++ b/app/src/main/java/org/linphone/activities/main/files/viewmodels/PdfFileViewModel.kt @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.linphone.activities.main.files.viewmodels + +import android.graphics.Bitmap +import android.graphics.pdf.PdfRenderer +import android.os.ParcelFileDescriptor +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import java.io.File +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.core.tools.Log + +class PdfFileViewModelFactory(private val filePath: String) : + ViewModelProvider.NewInstanceFactory() { + + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T { + return PdfFileViewModel(filePath) as T + } +} + +class PdfFileViewModel(filePath: String) : ViewModel() { + val operationInProgress = MutableLiveData() + + val pages = MutableLiveData>() + + private val pdfRenderer: PdfRenderer + + init { + operationInProgress.value = false + + val input = ParcelFileDescriptor.open(File(filePath), ParcelFileDescriptor.MODE_READ_ONLY) + pdfRenderer = PdfRenderer(input) + Log.i("[PDF Viewer] ${pdfRenderer.pageCount} pages in file $filePath") + + loadPdf() + } + + override fun onCleared() { + for (page in pages.value.orEmpty()) { + page.destroy() + } + pages.value?.clear() + super.onCleared() + } + + private fun loadPdf() { + viewModelScope.launch { + withContext(Dispatchers.IO) { + try { + operationInProgress.postValue(true) + + for (index in 0 until pdfRenderer.pageCount) { + val page: PdfRenderer.Page = pdfRenderer.openPage(index) + val width = if (coreContext.screenWidth <= coreContext.screenHeight) coreContext.screenWidth else coreContext.screenHeight + val bm = Bitmap.createBitmap( + width.toInt(), + (width / page.width * page.height).toInt(), + Bitmap.Config.ARGB_8888 + ) + page.render(bm, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY) + page.close() + + val pageViewModel = PdfPageViewModel(bm) + val list = arrayListOf() + list.addAll(pages.value.orEmpty()) + list.add(pageViewModel) + pages.postValue(list) + } + + operationInProgress.postValue(false) + } catch (e: Exception) { + Log.e("[PDF Viewer] Exception: $e") + operationInProgress.postValue(false) + } + } + } + } + + class PdfPageViewModel(val bitmap: Bitmap) { + fun destroy() { + bitmap.recycle() + } + } +} diff --git a/app/src/main/java/org/linphone/activities/main/files/viewmodels/TextFileViewModel.kt b/app/src/main/java/org/linphone/activities/main/files/viewmodels/TextFileViewModel.kt new file mode 100644 index 000000000..03f37ffde --- /dev/null +++ b/app/src/main/java/org/linphone/activities/main/files/viewmodels/TextFileViewModel.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.linphone.activities.main.files.viewmodels + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import java.io.BufferedReader +import java.io.FileReader +import java.lang.StringBuilder +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.linphone.core.tools.Log + +class TextFileViewModelFactory(private val filePath: String) : + ViewModelProvider.NewInstanceFactory() { + + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T { + return TextFileViewModel(filePath) as T + } +} + +class TextFileViewModel(filePath: String) : ViewModel() { + val operationInProgress = MutableLiveData() + + val text = MutableLiveData() + + init { + operationInProgress.value = false + + openFile(filePath) + } + + private fun openFile(filePath: String) { + viewModelScope.launch { + withContext(Dispatchers.IO) { + operationInProgress.postValue(true) + + try { + val br = BufferedReader(FileReader(filePath)) + var line: String? + var textBuilder = StringBuilder() + while (br.readLine().also { line = it } != null) { + textBuilder.append(line) + textBuilder.append('\n') + } + br.close() + + text.postValue(textBuilder.toString()) + operationInProgress.postValue(false) + } catch (e: Exception) { + Log.e("[Text Viewer] Exception: $e") + operationInProgress.postValue(false) + } + } + } + } +} diff --git a/app/src/main/java/org/linphone/activities/main/files/viewmodels/VideoFileViewModel.kt b/app/src/main/java/org/linphone/activities/main/files/viewmodels/VideoFileViewModel.kt new file mode 100644 index 000000000..82d4fdf0f --- /dev/null +++ b/app/src/main/java/org/linphone/activities/main/files/viewmodels/VideoFileViewModel.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.linphone.activities.main.files.viewmodels + +import android.widget.MediaController +import android.widget.VideoView +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import org.linphone.core.tools.Log + +class VideoFileViewModelFactory(private val filePath: String) : + ViewModelProvider.NewInstanceFactory() { + + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T { + return VideoFileViewModel(filePath) as T + } +} + +class VideoFileViewModel(val filePath: String) : ViewModel() { + + fun initMediaController(mediaController: MediaController, videoView: VideoView) { + videoView.setOnPreparedListener { mediaPlayer -> + mediaPlayer.setOnVideoSizeChangedListener { _, _, _ -> + videoView.setMediaController(mediaController) + // The following will make the video controls above the video + // mediaController.setAnchorView(videoView) + } + } + + videoView.setOnCompletionListener { mediaPlayer -> + mediaPlayer.release() + } + + videoView.setOnErrorListener { _, what, extra -> + Log.e("[Video Viewer] Error: $what ($extra)") + false + } + + videoView.setVideoPath(filePath) + videoView.start() + } +} diff --git a/app/src/main/java/org/linphone/activities/main/viewmodels/SharedMainViewModel.kt b/app/src/main/java/org/linphone/activities/main/viewmodels/SharedMainViewModel.kt index b5de92c5f..abbb9a8b4 100644 --- a/app/src/main/java/org/linphone/activities/main/viewmodels/SharedMainViewModel.kt +++ b/app/src/main/java/org/linphone/activities/main/viewmodels/SharedMainViewModel.kt @@ -48,6 +48,8 @@ class SharedMainViewModel : ViewModel() { MutableLiveData>() } + val fileToOpen = MutableLiveData() + var createEncryptedChatRoom: Boolean = false val chatRoomParticipants = MutableLiveData>() diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt index 8386cc359..ee6d4bd97 100644 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ b/app/src/main/java/org/linphone/core/CoreContext.kt @@ -56,6 +56,9 @@ class CoreContext(val context: Context, coreConfig: Config) { val core: Core val handler: Handler = Handler(Looper.getMainLooper()) + var screenWidth: Float = 0f + var screenHeight: Float = 0f + val appVersion: String by lazy { val appVersion = org.linphone.BuildConfig.VERSION_NAME val appBranch = context.getString(R.string.linphone_app_branch) diff --git a/app/src/main/java/org/linphone/utils/FileUtils.kt b/app/src/main/java/org/linphone/utils/FileUtils.kt index 20635f276..268fec0d7 100644 --- a/app/src/main/java/org/linphone/utils/FileUtils.kt +++ b/app/src/main/java/org/linphone/utils/FileUtils.kt @@ -59,6 +59,18 @@ class FileUtils { return extension } + fun isPlainTextFile(path: String): Boolean { + val extension = getExtensionFromFileName(path).toLowerCase(Locale.getDefault()) + val type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) + return type?.startsWith("text/plain") ?: false + } + + fun isExtensionPdf(path: String): Boolean { + val extension = getExtensionFromFileName(path).toLowerCase(Locale.getDefault()) + val type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) + return type?.startsWith("application/pdf") ?: false + } + fun isExtensionImage(path: String): Boolean { val extension = getExtensionFromFileName(path).toLowerCase(Locale.getDefault()) val type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) diff --git a/app/src/main/res/layout/file_viewer_top_bar_fragment.xml b/app/src/main/res/layout/file_viewer_top_bar_fragment.xml new file mode 100644 index 000000000..667c1b55d --- /dev/null +++ b/app/src/main/res/layout/file_viewer_top_bar_fragment.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/image_viewer_fragment.xml b/app/src/main/res/layout/image_viewer_fragment.xml new file mode 100644 index 000000000..6832a62e0 --- /dev/null +++ b/app/src/main/res/layout/image_viewer_fragment.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/pdf_viewer_cell.xml b/app/src/main/res/layout/pdf_viewer_cell.xml new file mode 100644 index 000000000..325f8823d --- /dev/null +++ b/app/src/main/res/layout/pdf_viewer_cell.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/pdf_viewer_fragment.xml b/app/src/main/res/layout/pdf_viewer_fragment.xml new file mode 100644 index 000000000..cae38dd46 --- /dev/null +++ b/app/src/main/res/layout/pdf_viewer_fragment.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/text_viewer_fragment.xml b/app/src/main/res/layout/text_viewer_fragment.xml new file mode 100644 index 000000000..6c3e696df --- /dev/null +++ b/app/src/main/res/layout/text_viewer_fragment.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/video_viewer_fragment.xml b/app/src/main/res/layout/video_viewer_fragment.xml new file mode 100644 index 000000000..3f9e06abe --- /dev/null +++ b/app/src/main/res/layout/video_viewer_fragment.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/main_nav_graph.xml b/app/src/main/res/navigation/main_nav_graph.xml index e12877da2..f5f53da02 100644 --- a/app/src/main/res/navigation/main_nav_graph.xml +++ b/app/src/main/res/navigation/main_nav_graph.xml @@ -320,5 +320,25 @@ android:name="IsLinking" app:argType="boolean" /> + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a36d43ba0..3c57e7b16 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -173,6 +173,7 @@ dummy subject Select a conversation or create a new one Can\'t open file, no application available for this format. + File not found Chat room creation failed Group info Conversation\'s devices