From 882b14356914363b14c0c4a89ba57e2cc79a5be6 Mon Sep 17 00:00:00 2001 From: Maxime Naturel Date: Thu, 24 Feb 2022 16:15:53 +0100 Subject: [PATCH] Permission and error handling --- .../AttachmentViewerActivity.kt | 11 +++-- .../home/room/detail/TimelineFragment.kt | 1 - .../media/VectorAttachmentViewerActivity.kt | 48 +++++++++++++++---- .../media/VectorAttachmentViewerViewEvents.kt | 1 - .../media/VectorAttachmentViewerViewModel.kt | 10 ++-- 5 files changed, 53 insertions(+), 18 deletions(-) diff --git a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentViewerActivity.kt b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentViewerActivity.kt index 573138bf5c..21af114c26 100644 --- a/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentViewerActivity.kt +++ b/library/attachment-viewer/src/main/java/im/vector/lib/attachmentviewer/AttachmentViewerActivity.kt @@ -45,6 +45,8 @@ import kotlin.math.abs abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventListener { + protected val rootView: View + get() = views.rootContainer protected val pager2: ViewPager2 get() = views.attachmentPager protected val imageTransitionView: ImageView @@ -298,10 +300,11 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi private fun createSwipeToDismissHandler(): SwipeToDismissHandler = SwipeToDismissHandler( - swipeView = views.dismissContainer, - shouldAnimateDismiss = { shouldAnimateDismiss() }, - onDismiss = { animateClose() }, - onSwipeViewMove = ::handleSwipeViewMove) + swipeView = views.dismissContainer, + shouldAnimateDismiss = { shouldAnimateDismiss() }, + onDismiss = { animateClose() }, + onSwipeViewMove = ::handleSwipeViewMove + ) private fun createSwipeDirectionDetector() = SwipeDirectionDetector(this) { swipeDirection = it } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 9ca2e28646..1a40018526 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -2111,7 +2111,6 @@ class TimelineFragment @Inject constructor( } } - // TODO mutualize permission checking creating activity extension or delegation interface? private fun onSaveActionClicked(action: EventSharedAction.Save) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && !checkPermissions(PERMISSIONS_FOR_WRITING_FILES, requireActivity(), saveActionActivityResultLauncher)) { diff --git a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt index f051c63828..a52e0f433b 100644 --- a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt @@ -17,6 +17,7 @@ package im.vector.app.features.media import android.content.Context import android.content.Intent +import android.os.Build import android.os.Bundle import android.os.Parcelable import android.view.View @@ -34,7 +35,13 @@ import com.airbnb.mvrx.viewModel import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.core.extensions.singletonEntryPoint import im.vector.app.core.intent.getMimeTypeFromUri +import im.vector.app.core.platform.showOptimizedSnackbar +import im.vector.app.core.utils.PERMISSIONS_FOR_WRITING_FILES +import im.vector.app.core.utils.checkPermissions +import im.vector.app.core.utils.onPermissionDeniedDialog +import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.core.utils.shareMedia import im.vector.app.features.themes.ActivityOtherThemes import im.vector.app.features.themes.ThemeUtils @@ -81,9 +88,20 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), AttachmentInt * ========================================================================================== */ private val viewModel: VectorAttachmentViewerViewModel by viewModel() + private val errorFormatter by lazy(LazyThreadSafetyMode.NONE) { singletonEntryPoint().errorFormatter() } private var initialIndex = 0 private var isAnimatingOut = false private var currentSourceProvider: BaseAttachmentProvider<*>? = null + private val downloadActionResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently -> + if (allGranted) { + viewModel.pendingAction?.let { + viewModel.handle(it) + } + } else if (deniedPermanently) { + onPermissionDeniedDialog(R.string.denied_permission_generic) + } + viewModel.pendingAction = null + } /* ========================================================================================== * Lifecycle @@ -254,14 +272,18 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), AttachmentInt private fun handleViewEvents(event: VectorAttachmentViewerViewEvents) { when (event) { - is VectorAttachmentViewerViewEvents.DownloadingMedia -> Unit // TODO show loader? - is VectorAttachmentViewerViewEvents.ErrorDownloadingMedia -> { - // TODO show snackbar - Timber.e("failure saving file: ${event.error}") - } + is VectorAttachmentViewerViewEvents.ErrorDownloadingMedia -> showSnackBarError(event.error) } } + private fun showSnackBarError(error: Throwable) { + rootView.showOptimizedSnackbar(errorFormatter.toHumanReadable(error)) + } + + private fun hasWritePermission() = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q || + checkPermissions(PERMISSIONS_FOR_WRITING_FILES, this, downloadActionResultLauncher) + /* ========================================================================================== * Specialization AttachmentInteractionListener * ========================================================================================== */ @@ -294,15 +316,23 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), AttachmentInt override fun onDownload() { // TODO - // show message on error event: see TimelineFragment - // check write file permissions: see TimelineFragment - // should we check if media is saveable? + // test snackbar error in OnCreate() + // test write permission checking with Android 9 // check if it is already possible to save from menu with long press on video // check if it works for video or other media type as well + // reorder action for a message according to issue requirements // add unit tests for usecase? what is the used mock library? lifecycleScope.launch(Dispatchers.IO) { + val hasWritePermission = withContext(Dispatchers.Main) { + hasWritePermission() + } + val file = currentSourceProvider?.getFileForSharing(currentPosition) ?: return@launch - viewModel.handle(VectorAttachmentViewerAction.DownloadMedia(file)) + if (hasWritePermission) { + viewModel.handle(VectorAttachmentViewerAction.DownloadMedia(file)) + } else { + viewModel.pendingAction = VectorAttachmentViewerAction.DownloadMedia(file) + } } } diff --git a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerViewEvents.kt b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerViewEvents.kt index 6516da9dcc..29181ee31f 100644 --- a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerViewEvents.kt @@ -19,6 +19,5 @@ package im.vector.app.features.media import im.vector.app.core.platform.VectorViewEvents sealed class VectorAttachmentViewerViewEvents : VectorViewEvents { - object DownloadingMedia : VectorAttachmentViewerViewEvents() data class ErrorDownloadingMedia(val error: Throwable) : VectorAttachmentViewerViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerViewModel.kt b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerViewModel.kt index 9e311c6a1f..b2bbc79e4f 100644 --- a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerViewModel.kt @@ -44,6 +44,12 @@ class VectorAttachmentViewerViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + /* ========================================================================================== + * Public Api + * ========================================================================================== */ + + var pendingAction: VectorAttachmentViewerAction? = null + /* ========================================================================================== * Specialization * ========================================================================================== */ @@ -60,9 +66,7 @@ class VectorAttachmentViewerViewModel @AssistedInject constructor( private fun handleDownloadAction(action: VectorAttachmentViewerAction.DownloadMedia) { viewModelScope.launch { - _viewEvents.post(VectorAttachmentViewerViewEvents.DownloadingMedia) - - // Success event is handled via a notification inside use case + // Success event is handled via a notification inside the use case downloadMediaUseCase.execute(action.file) .onFailure { _viewEvents.post(VectorAttachmentViewerViewEvents.ErrorDownloadingMedia(it)) } }