Fix some misunderstanding about the permissions request - step 2

This commit is contained in:
Benoit Marty 2021-07-13 00:10:09 +02:00
parent 80657251a5
commit 067349f602
13 changed files with 87 additions and 24 deletions

View File

@ -26,6 +26,8 @@ import im.vector.app.R
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.PERMISSIONS_ALL import im.vector.app.core.utils.PERMISSIONS_ALL
import im.vector.app.core.utils.checkPermissions import im.vector.app.core.utils.checkPermissions
import im.vector.app.core.utils.onPermissionDeniedDialog
import im.vector.app.core.utils.onPermissionDeniedSnackbar
import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.databinding.ActivityDebugPermissionBinding import im.vector.app.databinding.ActivityDebugPermissionBinding
import timber.log.Timber import timber.log.Timber
@ -34,6 +36,8 @@ class DebugPermissionActivity : VectorBaseActivity<ActivityDebugPermissionBindin
override fun getBinding() = ActivityDebugPermissionBinding.inflate(layoutInflater) override fun getBinding() = ActivityDebugPermissionBinding.inflate(layoutInflater)
override fun getCoordinatorLayout() = views.coordinatorLayout
private var lastPermissions = emptyList<String>() private var lastPermissions = emptyList<String>()
override fun initUiAndData() { override fun initUiAndData() {
@ -67,12 +71,19 @@ class DebugPermissionActivity : VectorBaseActivity<ActivityDebugPermissionBindin
} }
} }
private var dialogOrSnackbar = false
private val launcher = registerForPermissionsResult { allGranted, deniedPermanently -> private val launcher = registerForPermissionsResult { allGranted, deniedPermanently ->
if (allGranted) { if (allGranted) {
Toast.makeText(this, "All granted", Toast.LENGTH_SHORT).show() Toast.makeText(this, "All granted", Toast.LENGTH_SHORT).show()
} else { } else {
if (deniedPermanently) { if (deniedPermanently) {
Toast.makeText(this, "Denied forever", Toast.LENGTH_SHORT).show() dialogOrSnackbar = !dialogOrSnackbar
if (dialogOrSnackbar) {
onPermissionDeniedDialog(R.string.denied_permission_camera)
} else {
onPermissionDeniedSnackbar(R.string.denied_permission_camera)
}
} else { } else {
Toast.makeText(this, "Denied", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Denied", Toast.LENGTH_SHORT).show()
} }

View File

@ -29,6 +29,7 @@ import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
import im.vector.app.core.utils.checkPermissions 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.registerForPermissionsResult
import im.vector.app.features.media.createUCropWithDefaultSettings import im.vector.app.features.media.createUCropWithDefaultSettings
import im.vector.lib.multipicker.MultiPicker import im.vector.lib.multipicker.MultiPicker
@ -55,9 +56,11 @@ class GalleryOrCameraDialogHelper(
private val listener = fragment as? Listener ?: error("Fragment must implement GalleryOrCameraDialogHelper.Listener") private val listener = fragment as? Listener ?: error("Fragment must implement GalleryOrCameraDialogHelper.Listener")
private val takePhotoPermissionActivityResultLauncher = fragment.registerForPermissionsResult { allGranted -> private val takePhotoPermissionActivityResultLauncher = fragment.registerForPermissionsResult { allGranted, deniedPermanently ->
if (allGranted) { if (allGranted) {
doOpenCamera() doOpenCamera()
} else if (deniedPermanently) {
activity.onPermissionDeniedDialog(R.string.denied_permission_camera)
} }
} }
@ -116,7 +119,7 @@ class GalleryOrCameraDialogHelper(
private fun onAvatarTypeSelected(type: Type) { private fun onAvatarTypeSelected(type: Type) {
when (type) { when (type) {
Type.Camera -> Type.Camera ->
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, activity, takePhotoPermissionActivityResultLauncher)) { if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, activity, takePhotoPermissionActivityResultLauncher)) {
doOpenCamera() doOpenCamera()
} }

View File

@ -26,6 +26,7 @@ import androidx.annotation.StringRes
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
@ -78,9 +79,22 @@ fun ComponentActivity.registerForPermissionsResult(lambda: (allGranted: Boolean,
} }
} }
fun Fragment.registerForPermissionsResult(allGranted: (Boolean) -> Unit): ActivityResultLauncher<Array<String>> { fun Fragment.registerForPermissionsResult(lambda: (allGranted: Boolean, deniedPermanently: Boolean) -> Unit): ActivityResultLauncher<Array<String>> {
return registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result -> return registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
allGranted.invoke(result.keys.all { result[it] == true }) if (result.keys.all { result[it] == true }) {
lambda(true, /* not used */ false)
} else {
if (permissionDialogDisplayed) {
// A permission dialog has been displayed, so even if the user has checked the do not ask again button, we do
// not tell the user to open the app settings
lambda(false, false)
} else {
// No dialog has been displayed, so tell the user to go to the system setting
lambda(false, true)
}
}
// Reset
permissionDialogDisplayed = false
} }
} }
@ -163,3 +177,14 @@ fun VectorBaseActivity<*>.onPermissionDeniedSnackbar(@StringRes rationaleMessage
openAppSettingsPage(this) openAppSettingsPage(this)
} }
} }
fun FragmentActivity.onPermissionDeniedDialog(@StringRes rationaleMessage: Int) {
MaterialAlertDialogBuilder(this)
.setTitle(R.string.missing_permissions_title)
.setMessage(rationaleMessage)
.setPositiveButton(R.string.open_settings) { _, _ ->
openAppSettingsPage(this)
}
.setNegativeButton(R.string.cancel, null)
.show()
}

View File

@ -27,6 +27,7 @@ import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
import im.vector.app.core.utils.checkPermissions 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.registerForPermissionsResult
import im.vector.app.databinding.FragmentQrCodeScannerBinding import im.vector.app.databinding.FragmentQrCodeScannerBinding
import im.vector.app.features.userdirectory.PendingSelection import im.vector.app.features.userdirectory.PendingSelection
@ -44,9 +45,11 @@ class CreateDirectRoomByQrCodeFragment @Inject constructor() : VectorBaseFragmen
return FragmentQrCodeScannerBinding.inflate(inflater, container, false) return FragmentQrCodeScannerBinding.inflate(inflater, container, false)
} }
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted -> private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
if (allGranted) { if (allGranted) {
startCamera() startCamera()
} else if (deniedPermanently) {
activity?.onPermissionDeniedDialog(R.string.denied_permission_camera)
} }
} }

View File

@ -23,12 +23,14 @@ import android.view.ViewGroup
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.app.R
import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith import im.vector.app.core.extensions.configureWith
import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
import im.vector.app.core.utils.checkPermissions 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.registerForPermissionsResult
import im.vector.app.databinding.BottomSheetVerificationChildFragmentBinding import im.vector.app.databinding.BottomSheetVerificationChildFragmentBinding
import im.vector.app.features.crypto.verification.VerificationAction import im.vector.app.features.crypto.verification.VerificationAction
@ -79,9 +81,11 @@ class VerificationChooseMethodFragment @Inject constructor(
state.pendingRequest.invoke()?.transactionId ?: "")) state.pendingRequest.invoke()?.transactionId ?: ""))
} }
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted -> private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
if (allGranted) { if (allGranted) {
doOpenQRCodeScanner() doOpenQRCodeScanner()
} else if (deniedPermanently) {
activity?.onPermissionDeniedDialog(R.string.denied_permission_camera)
} }
} }

View File

@ -101,6 +101,7 @@ import im.vector.app.core.utils.copyToClipboard
import im.vector.app.core.utils.createJSonViewerStyleProvider import im.vector.app.core.utils.createJSonViewerStyleProvider
import im.vector.app.core.utils.createUIHandler import im.vector.app.core.utils.createUIHandler
import im.vector.app.core.utils.isValidUrl import im.vector.app.core.utils.isValidUrl
import im.vector.app.core.utils.onPermissionDeniedDialog
import im.vector.app.core.utils.openUrlInExternalBrowser import im.vector.app.core.utils.openUrlInExternalBrowser
import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.core.utils.saveMedia import im.vector.app.core.utils.saveMedia
@ -1062,14 +1063,16 @@ class RoomDetailFragment @Inject constructor(
} }
} }
private val startCallActivityResultLauncher = registerForPermissionsResult { allGranted -> private val startCallActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
if (allGranted) { if (allGranted) {
(roomDetailViewModel.pendingAction as? RoomDetailAction.StartCall)?.let { (roomDetailViewModel.pendingAction as? RoomDetailAction.StartCall)?.let {
roomDetailViewModel.pendingAction = null roomDetailViewModel.pendingAction = null
roomDetailViewModel.handle(it) roomDetailViewModel.handle(it)
} }
} else { } else {
context?.toast(R.string.permissions_action_not_performed_missing_permissions) if (deniedPermanently) {
activity?.onPermissionDeniedDialog(R.string.denied_permission_generic)
}
cleanUpAfterPermissionNotGranted() cleanUpAfterPermissionNotGranted()
} }
} }
@ -1738,13 +1741,16 @@ class RoomDetailFragment @Inject constructor(
} }
} }
private val saveActionActivityResultLauncher = registerForPermissionsResult { allGranted -> private val saveActionActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
if (allGranted) { if (allGranted) {
sharedActionViewModel.pendingAction?.let { sharedActionViewModel.pendingAction?.let {
handleActions(it) handleActions(it)
sharedActionViewModel.pendingAction = null sharedActionViewModel.pendingAction = null
} }
} else { } else {
if (deniedPermanently) {
activity?.onPermissionDeniedDialog(R.string.denied_permission_generic)
}
cleanUpAfterPermissionNotGranted() cleanUpAfterPermissionNotGranted()
} }
} }
@ -1977,7 +1983,7 @@ class RoomDetailFragment @Inject constructor(
// AttachmentTypeSelectorView.Callback // AttachmentTypeSelectorView.Callback
private val typeSelectedActivityResultLauncher = registerForPermissionsResult { allGranted -> private val typeSelectedActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
if (allGranted) { if (allGranted) {
val pendingType = attachmentsHelper.pendingType val pendingType = attachmentsHelper.pendingType
if (pendingType != null) { if (pendingType != null) {
@ -1985,6 +1991,9 @@ class RoomDetailFragment @Inject constructor(
launchAttachmentProcess(pendingType) launchAttachmentProcess(pendingType)
} }
} else { } else {
if (deniedPermanently) {
activity?.onPermissionDeniedDialog(R.string.denied_permission_generic)
}
cleanUpAfterPermissionNotGranted() cleanUpAfterPermissionNotGranted()
} }
} }

View File

@ -65,7 +65,7 @@ class ScanUserCodeFragment @Inject constructor()
} }
} }
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted -> private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted, _ ->
if (allGranted) { if (allGranted) {
startCamera() startCamera()
} else { } else {

View File

@ -43,11 +43,11 @@ class ShowUserCodeFragment @Inject constructor(
val sharedViewModel: UserCodeSharedViewModel by activityViewModel() val sharedViewModel: UserCodeSharedViewModel by activityViewModel()
private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted -> private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
if (allGranted) { if (allGranted) {
doOpenQRCodeScanner() doOpenQRCodeScanner()
} else { } else {
sharedViewModel.handle(UserCodeActions.CameraPermissionNotGranted) sharedViewModel.handle(UserCodeActions.CameraPermissionNotGranted(deniedPermanently))
} }
} }

View File

@ -24,6 +24,6 @@ sealed class UserCodeActions : VectorViewModelAction {
data class SwitchMode(val mode: UserCodeState.Mode) : UserCodeActions() data class SwitchMode(val mode: UserCodeState.Mode) : UserCodeActions()
data class DecodedQRCode(val code: String) : UserCodeActions() data class DecodedQRCode(val code: String) : UserCodeActions()
data class StartChattingWithUser(val matrixItem: MatrixItem) : UserCodeActions() data class StartChattingWithUser(val matrixItem: MatrixItem) : UserCodeActions()
object CameraPermissionNotGranted : UserCodeActions() data class CameraPermissionNotGranted(val deniedPermanently: Boolean) : UserCodeActions()
object ShareByText : UserCodeActions() object ShareByText : UserCodeActions()
} }

View File

@ -81,13 +81,17 @@ class UserCodeActivity : VectorBaseActivity<ActivitySimpleBinding>(),
sharedViewModel.observeViewEvents { sharedViewModel.observeViewEvents {
when (it) { when (it) {
UserCodeShareViewEvents.Dismiss -> ActivityCompat.finishAfterTransition(this) UserCodeShareViewEvents.Dismiss -> ActivityCompat.finishAfterTransition(this)
UserCodeShareViewEvents.ShowWaitingScreen -> views.simpleActivityWaitingView.isVisible = true UserCodeShareViewEvents.ShowWaitingScreen -> views.simpleActivityWaitingView.isVisible = true
UserCodeShareViewEvents.HideWaitingScreen -> views.simpleActivityWaitingView.isVisible = false UserCodeShareViewEvents.HideWaitingScreen -> views.simpleActivityWaitingView.isVisible = false
is UserCodeShareViewEvents.ToastMessage -> Toast.makeText(this, it.message, Toast.LENGTH_LONG).show() is UserCodeShareViewEvents.ToastMessage -> Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
is UserCodeShareViewEvents.NavigateToRoom -> navigator.openRoom(this, it.roomId) is UserCodeShareViewEvents.NavigateToRoom -> navigator.openRoom(this, it.roomId)
UserCodeShareViewEvents.CameraPermissionNotGranted -> onPermissionDeniedSnackbar(R.string.permissions_denied_qr_code) is UserCodeShareViewEvents.CameraPermissionNotGranted -> {
else -> { if (it.deniedPermanently) {
onPermissionDeniedSnackbar(R.string.permissions_denied_qr_code)
}
}
else -> {
} }
} }
} }

View File

@ -24,6 +24,6 @@ sealed class UserCodeShareViewEvents : VectorViewEvents {
object HideWaitingScreen : UserCodeShareViewEvents() object HideWaitingScreen : UserCodeShareViewEvents()
data class ToastMessage(val message: String) : UserCodeShareViewEvents() data class ToastMessage(val message: String) : UserCodeShareViewEvents()
data class NavigateToRoom(val roomId: String) : UserCodeShareViewEvents() data class NavigateToRoom(val roomId: String) : UserCodeShareViewEvents()
object CameraPermissionNotGranted : UserCodeShareViewEvents() data class CameraPermissionNotGranted(val deniedPermanently: Boolean) : UserCodeShareViewEvents()
data class SharePlainText(val text: String, val title: String, val richPlainText: String) : UserCodeShareViewEvents() data class SharePlainText(val text: String, val title: String, val richPlainText: String) : UserCodeShareViewEvents()
} }

View File

@ -76,7 +76,7 @@ class UserCodeSharedViewModel @AssistedInject constructor(
is UserCodeActions.SwitchMode -> setState { copy(mode = action.mode) } is UserCodeActions.SwitchMode -> setState { copy(mode = action.mode) }
is UserCodeActions.DecodedQRCode -> handleQrCodeDecoded(action) is UserCodeActions.DecodedQRCode -> handleQrCodeDecoded(action)
is UserCodeActions.StartChattingWithUser -> handleStartChatting(action) is UserCodeActions.StartChattingWithUser -> handleStartChatting(action)
UserCodeActions.CameraPermissionNotGranted -> _viewEvents.post(UserCodeShareViewEvents.CameraPermissionNotGranted) is UserCodeActions.CameraPermissionNotGranted -> _viewEvents.post(UserCodeShareViewEvents.CameraPermissionNotGranted(action.deniedPermanently))
UserCodeActions.ShareByText -> handleShareByText() UserCodeActions.ShareByText -> handleShareByText()
} }
} }

View File

@ -386,12 +386,16 @@
<string name="reset">Reset</string> <string name="reset">Reset</string>
<string name="start_chatting">Start Chatting</string> <string name="start_chatting">Start Chatting</string>
<!-- Permissions denied forever -->
<string name="denied_permission_generic">Some permissions are missing to perform this action, please grant the permissions from the system settings.</string>
<string name="denied_permission_camera">To perform this action, please grant the Camera permission from the system settings.</string>
<!-- First param will be replace by the value of ongoing_conference_call_voice, and second one by the value of ongoing_conference_call_video --> <!-- First param will be replace by the value of ongoing_conference_call_voice, and second one by the value of ongoing_conference_call_video -->
<string name="ongoing_conference_call">Ongoing conference call.\nJoin as %1$s or %2$s</string> <string name="ongoing_conference_call">Ongoing conference call.\nJoin as %1$s or %2$s</string>
<string name="ongoing_conference_call_voice">Voice</string> <string name="ongoing_conference_call_voice">Voice</string>
<string name="ongoing_conference_call_video">Video</string> <string name="ongoing_conference_call_video">Video</string>
<string name="cannot_start_call">Cannot start the call, please try later</string> <string name="cannot_start_call">Cannot start the call, please try later</string>
<string name="missing_permissions_title">Missing permissions</string>
<string name="missing_permissions_warning">"Due to missing permissions, some features may be missing…</string> <string name="missing_permissions_warning">"Due to missing permissions, some features may be missing…</string>
<string name="missing_permissions_error">"Due to missing permissions, this action is not possible.</string> <string name="missing_permissions_error">"Due to missing permissions, this action is not possible.</string>
<string name="missing_permissions_to_start_conf_call">You need permission to invite to start a conference in this room</string> <string name="missing_permissions_to_start_conf_call">You need permission to invite to start a conference in this room</string>