From 6efd843dab1a9b18cb235950f749813c56d4cf09 Mon Sep 17 00:00:00 2001 From: Oussama Hassine Date: Thu, 1 Feb 2024 16:55:53 +0100 Subject: [PATCH 1/8] feat: improve permission handling --- .../ui/calling/SharedCallingViewModel.kt | 15 ++++ .../MicrophonePermissionDeniedDialog.kt | 26 ++---- .../controlbuttons/CallOptionsControls.kt | 9 +- .../ui/calling/controlbuttons/CameraButton.kt | 72 ++++++++-------- .../ui/calling/controlbuttons/JoinButton.kt | 11 ++- .../calling/controlbuttons/StartCallButton.kt | 11 ++- .../ui/calling/incoming/IncomingCallScreen.kt | 46 +++++++---- .../ui/calling/incoming/IncomingCallState.kt | 5 +- .../calling/incoming/IncomingCallViewModel.kt | 8 -- .../initiating/InitiatingCallScreen.kt | 24 +++++- .../ui/calling/ongoing/OngoingCallScreen.kt | 26 +++++- .../PermissionPermanentlyDeniedDialog.kt | 36 ++++++++ .../common/imagepreview/AvatarPickerFlow.kt | 14 ++-- .../home/conversations/ConversationScreen.kt | 62 ++++++++++---- .../conversations/ConversationTopAppBar.kt | 29 +++---- .../conversations/DownloadedAssetDialog.kt | 7 +- .../conversations/MessageComposerViewModel.kt | 13 +++ .../conversations/MessageComposerViewState.kt | 13 ++- .../call/ConversationCallViewModel.kt | 8 -- .../call/ConversationCallViewState.kt | 3 +- .../media/ConversationMediaScreen.kt | 14 +++- .../messages/ConversationMessagesViewModel.kt | 15 ++++ .../ConversationListViewModel.kt | 19 +++-- .../conversationslist/ConversationRouter.kt | 30 ++++--- .../all/AllConversationScreen.kt | 9 +- .../conversationslist/call/CallsScreen.kt | 4 +- .../common/ConversationItemFactory.kt | 9 +- .../common/ConversationList.kt | 5 +- .../mention/MentionScreen.kt | 4 +- .../search/SearchConversationScreen.kt | 5 +- .../ui/home/gallery/MediaGalleryScreen.kt | 18 +++- .../ui/home/gallery/MediaGalleryViewModel.kt | 15 ++++ .../home/messagecomposer/AdditionalOptions.kt | 6 +- .../home/messagecomposer/AttachmentOptions.kt | 82 +++++++++++++------ .../messagecomposer/EnabledMessageComposer.kt | 6 +- .../home/messagecomposer/MessageComposer.kt | 4 + .../location/LocationPickerComponent.kt | 22 ++--- .../location/LocationPickerViewModel.kt | 2 +- .../recordaudio/RecordAudioComponent.kt | 11 ++- .../backup/BackUpAndRestoreStateHolder.kt | 17 ++++ .../settings/backup/BackupAndRestoreScreen.kt | 15 ++++ .../dialog/create/CreateBackupDialogFlow.kt | 13 ++- .../dialog/create/CreateBackupDialogs.kt | 3 + .../dialog/restore/RestoreBackupDialogs.kt | 4 +- .../android/ui/sharing/ImportMediaScreen.kt | 2 +- .../userprofile/avatarpicker/AvatarPicker.kt | 24 +++++- .../avatarpicker/AvatarPickerState.kt | 9 +- .../avatarpicker/AvatarPickerViewModel.kt | 16 ++++ .../permission/CallingCameraRequestFlow.kt | 73 +++++++++++++++++ .../CallingRecordAudioRequestFlow.kt | 6 +- .../permission/CaptureVideoRequestFlow.kt | 50 +++++++++-- .../util/permission/CreateFileRequestFlow.kt | 26 +++++- .../permission/CurrentLocationRequestFlow.kt | 14 +++- .../permission/OpenFileBrowserRequestFlow.kt | 28 +++++-- .../util/permission/OpenGalleryRequestFlow.kt | 21 +++-- .../util/permission/PermissionDenialType.kt | 27 ++++++ .../util/permission/RecordAudioRequestFlow.kt | 16 +++- .../util/permission/TakePictureRequestFlow.kt | 10 ++- .../util/permission/UseStorageRequestFlow.kt | 14 ++++ .../permission/WriteStorageRequestFlow.kt | 25 +++++- app/src/main/res/values/strings.xml | 10 ++- .../incoming/IncomingCallViewModelTest.kt | 20 ----- .../call/ConversationCallViewModelTest.kt | 20 ----- .../ConversationListViewModelTest.kt | 20 ----- 64 files changed, 845 insertions(+), 356 deletions(-) create mode 100644 app/src/main/kotlin/com/wire/android/ui/common/dialogs/PermissionPermanentlyDeniedDialog.kt create mode 100644 app/src/main/kotlin/com/wire/android/util/permission/CallingCameraRequestFlow.kt create mode 100644 app/src/main/kotlin/com/wire/android/util/permission/PermissionDenialType.kt diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/SharedCallingViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/calling/SharedCallingViewModel.kt index 4eccb51976d..36200c89a19 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/SharedCallingViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/SharedCallingViewModel.kt @@ -30,6 +30,7 @@ import com.wire.android.mapper.UICallParticipantMapper import com.wire.android.mapper.UserTypeMapper import com.wire.android.media.CallRinger import com.wire.android.model.ImageAsset +import com.wire.android.ui.home.conversations.PermissionPermanentlyDeniedDialogState import com.wire.android.ui.navArgs import com.wire.android.util.CurrentScreen import com.wire.android.util.CurrentScreenManager @@ -97,6 +98,10 @@ class SharedCallingViewModel @Inject constructor( var callState by mutableStateOf(CallState(conversationId)) + var permissionPermanentlyDeniedDialogState: PermissionPermanentlyDeniedDialogState by mutableStateOf( + PermissionPermanentlyDeniedDialogState.Hidden + ) + init { viewModelScope.launch { val allCallsSharedFlow = allCalls().map { @@ -309,4 +314,14 @@ class SharedCallingViewModel @Inject constructor( } } } + + fun showPermissionPermanentlyDeniedDialog(title: Int, description: Int) { + permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Visible( + title = title, + description = description + ) + } + fun hidePermissionPermanentlyDeniedDialog() { + permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Hidden + } } diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/common/MicrophonePermissionDeniedDialog.kt b/app/src/main/kotlin/com/wire/android/ui/calling/common/MicrophonePermissionDeniedDialog.kt index 00b252c6bd0..2cb2d38f570 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/common/MicrophonePermissionDeniedDialog.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/common/MicrophonePermissionDeniedDialog.kt @@ -24,30 +24,19 @@ import com.wire.android.ui.common.WireDialog import com.wire.android.ui.common.WireDialogButtonProperties import com.wire.android.ui.common.WireDialogButtonType import com.wire.android.ui.common.button.WireButtonState +import com.wire.android.util.permission.PermissionsDeniedRequestDialog import com.wire.android.util.ui.PreviewMultipleThemes @Composable fun MicrophonePermissionDeniedDialog( shouldShow: Boolean, - onDismiss: () -> Unit, - onOpenSettings: () -> Unit + onDismiss: () -> Unit ) { if (shouldShow) { - WireDialog( - title = stringResource(id = R.string.call_permission_dialog_title), - text = stringResource(id = R.string.call_permission_dialog_description), - onDismiss = onDismiss, - dismissButtonProperties = WireDialogButtonProperties( - onClick = onDismiss, - text = stringResource(id = R.string.label_not_now), - state = WireButtonState.Default - ), - optionButton1Properties = WireDialogButtonProperties( - onClick = onOpenSettings, - text = stringResource(id = R.string.record_audio_permission_denied_dialog_settings_button), - type = WireDialogButtonType.Primary, - state = WireButtonState.Default - ) + PermissionsDeniedRequestDialog( + title = R.string.app_permission_dialog_title, + body = R.string.call_permission_dialog_description, + onDismiss = onDismiss ) } } @@ -56,7 +45,6 @@ fun MicrophonePermissionDeniedDialog( fun PreviewMicrophonePermissionDeniedDialog() { MicrophonePermissionDeniedDialog( shouldShow = true, - onDismiss = {}, - onOpenSettings = {} + onDismiss = {} ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/controlbuttons/CallOptionsControls.kt b/app/src/main/kotlin/com/wire/android/ui/calling/controlbuttons/CallOptionsControls.kt index 9aec3abb00e..64d5f4dbfd1 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/controlbuttons/CallOptionsControls.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/controlbuttons/CallOptionsControls.kt @@ -35,6 +35,7 @@ import com.wire.android.ui.common.colorsScheme import com.wire.android.ui.common.dimensions import com.wire.android.ui.theme.wireDimensions import com.wire.android.ui.theme.wireTypography +import com.wire.android.util.permission.PermissionDenialType @Composable fun CallOptionsControls( @@ -43,7 +44,8 @@ fun CallOptionsControls( isSpeakerOn: Boolean, toggleSpeaker: () -> Unit, toggleMute: () -> Unit, - toggleVideo: () -> Unit + toggleVideo: () -> Unit, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit ) { ConstraintLayout( modifier = Modifier @@ -70,7 +72,7 @@ fun CallOptionsControls( end.linkTo(speakerIcon.start) }, isCameraOn = isCameraOn, - onCameraPermissionDenied = { }, + onPermissionPermanentlyDenied = onPermissionPermanentlyDenied, onCameraButtonClicked = toggleVideo ) CallControlLabel(stringResource(id = R.string.calling_button_label_camera), cameraText, cameraIcon) @@ -117,6 +119,7 @@ fun PreviewCallOptionsControls() { isSpeakerOn = false, toggleSpeaker = { }, toggleMute = { }, - toggleVideo = { } + toggleVideo = { }, + onPermissionPermanentlyDenied = {} ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/controlbuttons/CameraButton.kt b/app/src/main/kotlin/com/wire/android/ui/calling/controlbuttons/CameraButton.kt index 1b4830cf4fa..33c903fe45e 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/controlbuttons/CameraButton.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/controlbuttons/CameraButton.kt @@ -18,10 +18,6 @@ package com.wire.android.ui.calling.controlbuttons -import android.content.Context -import androidx.activity.compose.ManagedActivityResultLauncher -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.size @@ -31,34 +27,31 @@ import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.tooling.preview.Preview import com.wire.android.R +import com.wire.android.appLogger import com.wire.android.ui.common.dimensions -import com.wire.android.util.extension.checkPermission +import com.wire.android.util.permission.PermissionDenialType +import com.wire.android.util.permission.rememberCallingCameraRequestFlow @Composable fun CameraButton( modifier: Modifier = Modifier.size(dimensions().defaultCallingControlsSize), isCameraOn: Boolean = false, - onCameraPermissionDenied: () -> Unit, - onCameraButtonClicked: () -> Unit + onCameraButtonClicked: () -> Unit, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit ) { - val context = LocalContext.current - - val cameraPermissionLauncher = rememberLauncherForActivityResult( - ActivityResultContracts.RequestPermission() - ) { isGranted -> - if (isGranted) { - onCameraButtonClicked() - } else { - onCameraPermissionDenied() + val cameraPermissionCheck = CameraPermissionCheckFlow( + onPermissionGranted = onCameraButtonClicked, + onPermanentPermissionDecline = { + onPermissionPermanentlyDenied( + PermissionDenialType.CallingCamera + ) } - } + ) WireCallControlButton( isSelected = isCameraOn, @@ -67,16 +60,15 @@ fun CameraButton( Icon( modifier = Modifier .wrapContentSize() - .clickable(interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(bounded = false, radius = dimensions().defaultCallingControlsSize / 2), + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = rememberRipple( + bounded = false, + radius = dimensions().defaultCallingControlsSize / 2 + ), role = Role.Button, - onClick = { - verifyCameraPermission( - context = context, - cameraPermissionLauncher = cameraPermissionLauncher, - onCameraButtonClicked = onCameraButtonClicked - ) - }), + onClick = cameraPermissionCheck::launch, + ), painter = painterResource( id = if (isCameraOn) { R.drawable.ic_camera_on @@ -93,18 +85,22 @@ fun CameraButton( } } -private fun verifyCameraPermission( - context: Context, cameraPermissionLauncher: ManagedActivityResultLauncher, onCameraButtonClicked: () -> Unit -) { - if (context.checkPermission(android.Manifest.permission.CAMERA).not()) { - cameraPermissionLauncher.launch(android.Manifest.permission.CAMERA) - } else { - onCameraButtonClicked() - } -} +@Composable +private fun CameraPermissionCheckFlow( + onPermissionGranted: () -> Unit, + onPermanentPermissionDecline: () -> Unit +) = rememberCallingCameraRequestFlow( + onPermissionGranted = { + appLogger.d("Camera permission granted") + onPermissionGranted() + }, + onPermissionDenied = { }, + onPermissionPermanentlyDenied = onPermanentPermissionDecline +) + @Preview @Composable fun PreviewComposableCameraButton() { - CameraButton(onCameraPermissionDenied = { }, onCameraButtonClicked = { }) + CameraButton(onCameraButtonClicked = { }, onPermissionPermanentlyDenied = { }) } diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/controlbuttons/JoinButton.kt b/app/src/main/kotlin/com/wire/android/ui/calling/controlbuttons/JoinButton.kt index 040d388b144..01efc16c697 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/controlbuttons/JoinButton.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/controlbuttons/JoinButton.kt @@ -35,19 +35,24 @@ import com.wire.android.ui.common.button.WirePrimaryButton import com.wire.android.ui.common.dimensions import com.wire.android.ui.theme.wireDimensions import com.wire.android.ui.theme.wireTypography +import com.wire.android.util.permission.PermissionDenialType import com.wire.android.util.permission.rememberCallingRecordAudioRequestFlow @Composable fun JoinButton( buttonClick: () -> Unit, - onPermanentPermissionDecline: () -> Unit, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit, minSize: DpSize = MaterialTheme.wireDimensions.buttonMediumMinSize, minClickableSize: DpSize = MaterialTheme.wireDimensions.buttonMinClickableSize, horizontalPadding: Dp = MaterialTheme.wireDimensions.spacing8x, ) { val audioPermissionCheck = AudioPermissionCheckFlow( onJoinCall = buttonClick, - onPermanentPermissionDecline = onPermanentPermissionDecline + onPermanentPermissionDecline = { + onPermissionPermanentlyDenied( + PermissionDenialType.CallingMicrophone + ) + } ) WirePrimaryButton( @@ -87,6 +92,6 @@ private fun AudioPermissionCheckFlow( fun PreviewJoinButton() { JoinButton( buttonClick = {}, - onPermanentPermissionDecline = {} + onPermissionPermanentlyDenied = {} ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/controlbuttons/StartCallButton.kt b/app/src/main/kotlin/com/wire/android/ui/calling/controlbuttons/StartCallButton.kt index 2050131ce0a..31c1d2ec828 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/controlbuttons/StartCallButton.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/controlbuttons/StartCallButton.kt @@ -33,18 +33,23 @@ import com.wire.android.ui.common.button.WireButtonState import com.wire.android.ui.common.button.WireSecondaryButton import com.wire.android.ui.common.dimensions import com.wire.android.ui.theme.wireDimensions +import com.wire.android.util.permission.PermissionDenialType import com.wire.android.util.permission.rememberCallingRecordAudioRequestFlow import com.wire.android.util.ui.PreviewMultipleThemes @Composable fun StartCallButton( onPhoneButtonClick: () -> Unit, - onPermanentPermissionDecline: () -> Unit, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit, isCallingEnabled: Boolean ) { val audioPermissionCheck = AudioPermissionCheckFlow( startCall = onPhoneButtonClick, - onPermanentPermissionDecline = onPermanentPermissionDecline + onPermanentPermissionDecline = { + onPermissionPermanentlyDenied( + PermissionDenialType.CallingMicrophone + ) + } ) WireSecondaryButton( @@ -86,7 +91,7 @@ private fun AudioPermissionCheckFlow( fun PreviewStartCallButton() { StartCallButton( onPhoneButtonClick = {}, - onPermanentPermissionDecline = {}, + onPermissionPermanentlyDenied = {}, isCallingEnabled = true ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/incoming/IncomingCallScreen.kt b/app/src/main/kotlin/com/wire/android/ui/calling/incoming/IncomingCallScreen.kt index b84e739c851..edcf641e05f 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/incoming/IncomingCallScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/incoming/IncomingCallScreen.kt @@ -33,7 +33,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.hilt.navigation.compose.hiltViewModel @@ -50,17 +49,17 @@ import com.wire.android.ui.calling.CallingNavArgs import com.wire.android.ui.calling.SharedCallingViewModel import com.wire.android.ui.calling.common.CallVideoPreview import com.wire.android.ui.calling.common.CallerDetails -import com.wire.android.ui.calling.common.MicrophonePermissionDeniedDialog import com.wire.android.ui.calling.controlbuttons.AcceptButton import com.wire.android.ui.calling.controlbuttons.CallOptionsControls import com.wire.android.ui.calling.controlbuttons.HangUpButton import com.wire.android.ui.common.bottomsheet.WireBottomSheetScaffold import com.wire.android.ui.common.colorsScheme +import com.wire.android.ui.common.dialogs.PermissionPermanentlyDeniedDialog import com.wire.android.ui.common.dialogs.calling.JoinAnywayDialog import com.wire.android.ui.common.dimensions import com.wire.android.ui.destinations.OngoingCallScreenDestination import com.wire.android.ui.theme.wireTypography -import com.wire.android.util.extension.openAppInfoScreen +import com.wire.android.util.permission.PermissionDenialType import com.wire.android.util.permission.rememberCallingRecordAudioRequestFlow import com.wire.kalium.logic.data.call.ConversationType import com.wire.kalium.logic.data.id.ConversationId @@ -76,18 +75,14 @@ fun IncomingCallScreen( sharedCallingViewModel: SharedCallingViewModel = hiltViewModel(), incomingCallViewModel: IncomingCallViewModel = hiltViewModel() ) { - val context = LocalContext.current val audioPermissionCheck = AudioPermissionCheckFlow( - incomingCallViewModel::acceptCall, - incomingCallViewModel::showPermissionDialog - ) - - MicrophonePermissionDeniedDialog( - shouldShow = incomingCallViewModel.incomingCallState.shouldShowPermissionDialog, - onDismiss = incomingCallViewModel::dismissPermissionDialog, - onOpenSettings = { - context.openAppInfoScreen() + onAcceptCall = incomingCallViewModel::acceptCall, + onPermanentPermissionDecline = { + sharedCallingViewModel.showPermissionPermanentlyDeniedDialog( + title = R.string.app_permission_dialog_title, + description = R.string.call_permission_dialog_description + ) } ) @@ -122,9 +117,22 @@ fun IncomingCallScreen( declineCall = incomingCallViewModel::declineCall, acceptCall = audioPermissionCheck::launch, onVideoPreviewCreated = ::setVideoPreview, - onSelfClearVideoPreview = ::clearVideoPreview + onSelfClearVideoPreview = ::clearVideoPreview, + onPermissionPermanentlyDenied = { + if (it is PermissionDenialType.CallingCamera) { + sharedCallingViewModel.showPermissionPermanentlyDeniedDialog( + title = R.string.app_permission_dialog_title, + description = R.string.camera_permission_dialog_description + ) + } + } ) } + + PermissionPermanentlyDeniedDialog( + dialogState = sharedCallingViewModel.permissionPermanentlyDeniedDialogState, + hideDialog = sharedCallingViewModel::hidePermissionPermanentlyDeniedDialog + ) } @OptIn(ExperimentalMaterial3Api::class) @@ -137,7 +145,8 @@ private fun IncomingCallContent( declineCall: () -> Unit, acceptCall: () -> Unit, onVideoPreviewCreated: (view: View) -> Unit, - onSelfClearVideoPreview: () -> Unit + onSelfClearVideoPreview: () -> Unit, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit ) { BackHandler { // DO NOTHING @@ -156,7 +165,8 @@ private fun IncomingCallContent( isSpeakerOn = callState.isSpeakerOn, toggleSpeaker = toggleSpeaker, toggleMute = toggleMute, - toggleVideo = toggleVideo + toggleVideo = toggleVideo, + onPermissionPermanentlyDenied = onPermissionPermanentlyDenied ) Box( modifier = Modifier @@ -242,12 +252,12 @@ fun AudioPermissionCheckFlow( appLogger.d("IncomingCall - Audio permission granted") onAcceptCall() }, - onAudioPermissionDenied = { }, + onAudioPermissionDenied = { /* Nothing to do */ }, onAudioPermissionPermanentlyDenied = onPermanentPermissionDecline ) @Preview @Composable fun PreviewIncomingCallScreen() { - IncomingCallContent(CallState(ConversationId("value", "domain")), {}, {}, {}, {}, {}, {}, {}) + IncomingCallContent(CallState(ConversationId("value", "domain")), {}, {}, {}, {}, {}, {}, {}, {}) } diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/incoming/IncomingCallState.kt b/app/src/main/kotlin/com/wire/android/ui/calling/incoming/IncomingCallState.kt index a2818ca2d50..21ef153d41c 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/incoming/IncomingCallState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/incoming/IncomingCallState.kt @@ -22,12 +22,11 @@ import com.wire.kalium.logic.data.id.ConversationId data class IncomingCallState( val hasEstablishedCall: Boolean = false, val shouldShowJoinCallAnywayDialog: Boolean = false, - val shouldShowPermissionDialog: Boolean = false, val flowState: FlowState = FlowState.Default ) { sealed interface FlowState { - object Default : FlowState - object CallClosed : FlowState + data object Default : FlowState + data object CallClosed : FlowState data class CallAccepted(val conversationId: ConversationId) : FlowState } } diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/incoming/IncomingCallViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/calling/incoming/IncomingCallViewModel.kt index c4680476ee3..ddf6c5b3e8f 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/incoming/IncomingCallViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/incoming/IncomingCallViewModel.kt @@ -111,14 +111,6 @@ class IncomingCallViewModel @Inject constructor( } } - fun showPermissionDialog() { - incomingCallState = incomingCallState.copy(shouldShowPermissionDialog = true) - } - - fun dismissPermissionDialog() { - incomingCallState = incomingCallState.copy(shouldShowPermissionDialog = false) - } - private fun showJoinCallAnywayDialog() { incomingCallState = incomingCallState.copy(shouldShowJoinCallAnywayDialog = true) } diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/initiating/InitiatingCallScreen.kt b/app/src/main/kotlin/com/wire/android/ui/calling/initiating/InitiatingCallScreen.kt index 113083aa8be..900726395c8 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/initiating/InitiatingCallScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/initiating/InitiatingCallScreen.kt @@ -53,9 +53,11 @@ import com.wire.android.ui.calling.common.CallerDetails import com.wire.android.ui.calling.controlbuttons.CallOptionsControls import com.wire.android.ui.calling.controlbuttons.HangUpButton import com.wire.android.ui.common.bottomsheet.WireBottomSheetScaffold +import com.wire.android.ui.common.dialogs.PermissionPermanentlyDeniedDialog import com.wire.android.ui.common.dimensions import com.wire.android.ui.destinations.OngoingCallScreenDestination import com.wire.android.ui.theme.wireDimensions +import com.wire.android.util.permission.PermissionDenialType import com.wire.kalium.logic.data.id.ConversationId @RootNavGraph @@ -88,9 +90,21 @@ fun InitiatingCallScreen( toggleVideo = ::toggleVideo, onHangUpCall = initiatingCallViewModel::hangUpCall, onVideoPreviewCreated = ::setVideoPreview, - onSelfClearVideoPreview = ::clearVideoPreview + onSelfClearVideoPreview = ::clearVideoPreview, + onPermissionPermanentlyDenied = { + if (it is PermissionDenialType.CallingCamera) { + sharedCallingViewModel.showPermissionPermanentlyDeniedDialog( + title = R.string.app_permission_dialog_title, + description = R.string.camera_permission_dialog_description + ) + } + } ) } + PermissionPermanentlyDeniedDialog( + dialogState = sharedCallingViewModel.permissionPermanentlyDeniedDialogState, + hideDialog = sharedCallingViewModel::hidePermissionPermanentlyDeniedDialog + ) } @OptIn(ExperimentalMaterial3Api::class) @@ -102,7 +116,8 @@ private fun InitiatingCallContent( toggleVideo: () -> Unit, onHangUpCall: () -> Unit, onVideoPreviewCreated: (view: View) -> Unit, - onSelfClearVideoPreview: () -> Unit + onSelfClearVideoPreview: () -> Unit, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit ) { BackHandler { // DO NOTHING @@ -126,7 +141,8 @@ private fun InitiatingCallContent( isSpeakerOn = callState.isSpeakerOn, toggleSpeaker = toggleSpeaker, toggleMute = toggleMute, - toggleVideo = toggleVideo + toggleVideo = toggleVideo, + onPermissionPermanentlyDenied = onPermissionPermanentlyDenied ) Spacer( modifier = Modifier @@ -168,5 +184,5 @@ private fun InitiatingCallContent( @Preview @Composable fun PreviewInitiatingCallScreen() { - InitiatingCallContent(CallState(ConversationId("value", "domain")), {}, {}, {}, {}, {}, {}) + InitiatingCallContent(CallState(ConversationId("value", "domain")), {}, {}, {}, {}, {}, {}, {}) } diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/OngoingCallScreen.kt b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/OngoingCallScreen.kt index 1696dd17dcf..ed2f9f7b770 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/OngoingCallScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/OngoingCallScreen.kt @@ -73,6 +73,7 @@ import com.wire.android.ui.common.ConversationVerificationIcons import com.wire.android.ui.common.banner.SecurityClassificationBannerForConversation import com.wire.android.ui.common.bottomsheet.WireBottomSheetScaffold import com.wire.android.ui.common.colorsScheme +import com.wire.android.ui.common.dialogs.PermissionPermanentlyDeniedDialog import com.wire.android.ui.common.dimensions import com.wire.android.ui.common.progress.WireCircularProgressIndicator import com.wire.android.ui.common.topappbar.NavigationIconType @@ -80,6 +81,7 @@ import com.wire.android.ui.common.topappbar.WireCenterAlignedTopAppBar import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.theme.wireDimensions import com.wire.android.ui.theme.wireTypography +import com.wire.android.util.permission.PermissionDenialType import com.wire.android.util.ui.PreviewMultipleThemes import com.wire.kalium.logic.data.conversation.Conversation import com.wire.kalium.logic.data.id.ConversationId @@ -126,10 +128,23 @@ fun OngoingCallScreen( clearVideoPreview = sharedCallingViewModel::clearVideoPreview, navigateBack = navigator::navigateBack, requestVideoStreams = ongoingCallViewModel::requestVideoStreams, - hideDoubleTapToast = ongoingCallViewModel::hideDoubleTapToast + hideDoubleTapToast = ongoingCallViewModel::hideDoubleTapToast, + onPermissionPermanentlyDenied = { + if (it is PermissionDenialType.CallingCamera) { + sharedCallingViewModel.showPermissionPermanentlyDeniedDialog( + title = R.string.app_permission_dialog_title, + description = R.string.camera_permission_dialog_description + ) + } + } ) BackHandler(enabled = isCameraOn, navigator::navigateBack) } + + PermissionPermanentlyDeniedDialog( + dialogState = sharedCallingViewModel.permissionPermanentlyDeniedDialogState, + hideDialog = sharedCallingViewModel::hidePermissionPermanentlyDeniedDialog + ) } @OptIn(ExperimentalMaterial3Api::class) @@ -156,6 +171,7 @@ private fun OngoingCallContent( clearVideoPreview: () -> Unit, navigateBack: () -> Unit, hideDoubleTapToast: () -> Unit, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit, requestVideoStreams: (participants: List) -> Unit ) { @@ -200,7 +216,8 @@ private fun OngoingCallContent( toggleMute = toggleMute, onHangUpCall = hangUpCall, onToggleVideo = toggleVideo, - flipCamera = flipCamera + flipCamera = flipCamera, + onPermissionPermanentlyDenied = onPermissionPermanentlyDenied ) }, ) { @@ -342,7 +359,8 @@ private fun CallingControls( toggleMute: () -> Unit, onHangUpCall: () -> Unit, onToggleVideo: () -> Unit, - flipCamera: () -> Unit + flipCamera: () -> Unit, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit ) { Column( modifier = Modifier.height(dimensions().defaultSheetPeekHeight) @@ -358,7 +376,7 @@ private fun CallingControls( MicrophoneButton(isMuted = isMuted) { toggleMute() } CameraButton( isCameraOn = isCameraOn, - onCameraPermissionDenied = { }, + onPermissionPermanentlyDenied = onPermissionPermanentlyDenied, onCameraButtonClicked = onToggleVideo ) diff --git a/app/src/main/kotlin/com/wire/android/ui/common/dialogs/PermissionPermanentlyDeniedDialog.kt b/app/src/main/kotlin/com/wire/android/ui/common/dialogs/PermissionPermanentlyDeniedDialog.kt new file mode 100644 index 00000000000..21e1750f21c --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/common/dialogs/PermissionPermanentlyDeniedDialog.kt @@ -0,0 +1,36 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * 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 http://www.gnu.org/licenses/. + */ +package com.wire.android.ui.common.dialogs + +import androidx.compose.runtime.Composable +import com.wire.android.ui.home.conversations.PermissionPermanentlyDeniedDialogState +import com.wire.android.util.permission.PermissionsDeniedRequestDialog + +@Composable +fun PermissionPermanentlyDeniedDialog( + dialogState: PermissionPermanentlyDeniedDialogState, + hideDialog: () -> Unit +) { + if (dialogState is PermissionPermanentlyDeniedDialogState.Visible) { + PermissionsDeniedRequestDialog( + title = dialogState.title, + body = dialogState.description, + onDismiss = hideDialog + ) + } +} diff --git a/app/src/main/kotlin/com/wire/android/ui/common/imagepreview/AvatarPickerFlow.kt b/app/src/main/kotlin/com/wire/android/ui/common/imagepreview/AvatarPickerFlow.kt index 84226d61fa1..6cc8d954f23 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/imagepreview/AvatarPickerFlow.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/imagepreview/AvatarPickerFlow.kt @@ -22,6 +22,7 @@ import android.net.Uri import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import com.wire.android.ui.userprofile.avatarpicker.ImageSource +import com.wire.android.util.permission.PermissionDenialType import com.wire.android.util.permission.UseCameraRequestFlow import com.wire.android.util.permission.UseStorageRequestFlow import com.wire.android.util.permission.rememberOpenGalleryFlow @@ -43,22 +44,21 @@ class AvatarPickerFlow( fun rememberPickPictureState( onImageSelected: (Uri) -> Unit, onPictureTaken: () -> Unit, - targetPictureFileUri: Uri + targetPictureFileUri: Uri, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit ): AvatarPickerFlow { val takePictureFLow = rememberTakePictureFlow( onPictureTaken = { wasSaved -> if (wasSaved) onPictureTaken() }, - onPermissionDenied = { - // TODO: Implement denied permission rationale - }, + onPermissionDenied = { /* Nothing to do */ }, + onPermissionPermanentlyDenied = onPermissionPermanentlyDenied, targetPictureFileUri = targetPictureFileUri ) val openGalleryFlow = rememberOpenGalleryFlow( onGalleryItemPicked = { pickedPictureUri -> onImageSelected(pickedPictureUri) }, - onPermissionDenied = { - // TODO: Implement denied permission rationale - } + onPermissionDenied = { /* Nothing to do */ }, + onPermissionPermanentlyDenied = onPermissionPermanentlyDenied ) return remember { diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt index bedb879b5e8..db89b099f8a 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt @@ -81,11 +81,11 @@ import com.wire.android.model.SnackBarMessage import com.wire.android.navigation.BackStackMode import com.wire.android.navigation.NavigationCommand import com.wire.android.navigation.Navigator -import com.wire.android.ui.calling.common.MicrophonePermissionDeniedDialog import com.wire.android.ui.common.bottomsheet.MenuModalSheetHeader import com.wire.android.ui.common.bottomsheet.MenuModalSheetLayout import com.wire.android.ui.common.colorsScheme import com.wire.android.ui.common.dialogs.InvalidLinkDialog +import com.wire.android.ui.common.dialogs.PermissionPermanentlyDeniedDialog import com.wire.android.ui.common.dialogs.SureAboutMessagingInDegradedConversationDialog import com.wire.android.ui.common.dialogs.VisitLinkDialog import com.wire.android.ui.common.dialogs.calling.CallingFeatureUnavailableDialog @@ -132,8 +132,8 @@ import com.wire.android.ui.home.messagecomposer.state.MessageComposerStateHolder import com.wire.android.ui.home.messagecomposer.state.rememberMessageComposerStateHolder import com.wire.android.ui.legalhold.dialog.subject.LegalHoldSubjectMessageDialog import com.wire.android.ui.theme.wireColorScheme -import com.wire.android.util.extension.openAppInfoScreen import com.wire.android.util.normalizeLink +import com.wire.android.util.permission.PermissionDenialType import com.wire.android.util.ui.UIText import com.wire.android.util.ui.openDownloadFolder import com.wire.kalium.logic.NetworkFailure @@ -204,7 +204,6 @@ fun ConversationScreen( conversationInfoViewModel.observeConversationDetails(navigator::navigateBack) } } - val context = LocalContext.current conversationMigrationViewModel.migratedConversationId?.let { migratedConversationId -> navigator.navigate( @@ -223,14 +222,6 @@ fun ConversationScreen( onConfirm = { joinAnyway { navigator.navigate(NavigationCommand(OngoingCallScreenDestination(it))) } } ) } - - MicrophonePermissionDeniedDialog( - shouldShow = conversationCallViewState.shouldShowCallingPermissionDialog, - onDismiss = ::dismissCallingPermissionDialog, - onOpenSettings = { - context.openAppInfoScreen() - } - ) } when (showDialog.value) { @@ -351,7 +342,6 @@ fun ConversationScreen( onJoinCall = { conversationCallViewModel.joinOngoingCall { navigator.navigate(NavigationCommand(OngoingCallScreenDestination(it))) } }, - onPermanentPermissionDecline = conversationCallViewModel::showCallingPermissionDialog, onReactionClick = { messageId, emoji -> conversationMessagesViewModel.toggleReaction(messageId, emoji) }, @@ -384,6 +374,32 @@ fun ConversationScreen( onFailedMessageRetryClicked = messageComposerViewModel::retrySendingMessage, requestMentions = messageComposerViewModel::searchMembersToMention, onClearMentionSearchResult = messageComposerViewModel::clearMentionSearchResult, + onPermissionPermanentlyDenied = { + val (title, description) = when (it) { + is PermissionDenialType.CaptureVideo -> { + R.string.app_permission_dialog_title to R.string.record_video_permission_dialog_description + } + is PermissionDenialType.TakePicture -> { + R.string.app_permission_dialog_title to R.string.take_picture_permission_dialog_description + } + is PermissionDenialType.Gallery -> { + R.string.app_permission_dialog_title to R.string.open_gallery_permission_dialog_description + } + is PermissionDenialType.File -> { + R.string.app_permission_dialog_title to R.string.attach_file_permission_dialog_description + } + is PermissionDenialType.CallingMicrophone -> { + R.string.app_permission_dialog_title to R.string.call_permission_dialog_description + } + else -> { + R.string.app_permission_dialog_title to R.string.app_permission_dialog_title + } + } + messageComposerViewModel.showPermissionPermanentlyDeniedDialog( + title = title, + description = description + ) + }, conversationScreenState = conversationScreenState, messageComposerStateHolder = messageComposerStateHolder, onLinkClick = { link -> @@ -411,7 +427,13 @@ fun ConversationScreen( downloadedAssetDialogState = conversationMessagesViewModel.conversationViewState.downloadedAssetDialogState, onSaveFileToExternalStorage = conversationMessagesViewModel::downloadAssetExternally, onOpenFileWithExternalApp = conversationMessagesViewModel::downloadAndOpenAsset, - hideOnAssetDownloadedDialog = conversationMessagesViewModel::hideOnAssetDownloadedDialog + hideOnAssetDownloadedDialog = conversationMessagesViewModel::hideOnAssetDownloadedDialog, + onPermissionPermanentlyDenied = { + messageComposerViewModel.showPermissionPermanentlyDeniedDialog( + title = R.string.app_permission_dialog_title, + description = R.string.save_asset_permission_dialog_description + ) + } ) AssetTooLargeDialog( dialogState = messageComposerViewModel.assetTooLargeDialogState, @@ -427,6 +449,11 @@ fun ConversationScreen( hideDialog = messageComposerViewModel::hideInvalidLinkError ) + PermissionPermanentlyDeniedDialog( + dialogState = messageComposerViewModel.permissionPermanentlyDeniedDialogState, + hideDialog = messageComposerViewModel::hidePermissionPermanentlyDeniedDialog + ) + SureAboutMessagingInDegradedConversationDialog( dialogState = messageComposerViewModel.sureAboutMessagingDialogState, sendAnyway = messageComposerViewModel::acceptSureAboutSendingMessage, @@ -568,7 +595,6 @@ private fun ConversationScreen( onImageFullScreenMode: (UIMessage.Regular, Boolean) -> Unit, onStartCall: () -> Unit, onJoinCall: () -> Unit, - onPermanentPermissionDecline: () -> Unit, onReactionClick: (messageId: String, reactionEmoji: String) -> Unit, onResetSessionClick: (senderUserId: UserId, clientId: String?) -> Unit, onUpdateConversationReadDate: (String) -> Unit, @@ -584,6 +610,7 @@ private fun ConversationScreen( onFailedMessageRetryClicked: (String) -> Unit, requestMentions: (String) -> Unit, onClearMentionSearchResult: () -> Unit, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit, conversationScreenState: ConversationScreenState, messageComposerStateHolder: MessageComposerStateHolder, onLinkClick: (String) -> Unit, @@ -645,7 +672,7 @@ private fun ConversationScreen( onPhoneButtonClick = onStartCall, hasOngoingCall = conversationCallViewState.hasOngoingCall, onJoinCallButtonClick = onJoinCall, - onPermanentPermissionDecline = onPermanentPermissionDecline, + onPermissionPermanentlyDenied = onPermissionPermanentlyDenied, isInteractionEnabled = messageComposerViewState.value.interactionAvailability == InteractionAvailability.ENABLED ) ConversationBanner(bannerMessage) @@ -690,6 +717,7 @@ private fun ConversationScreen( onChangeSelfDeletionClicked = { conversationScreenState.showSelfDeletionContextMenu() }, onSearchMentionQueryChanged = requestMentions, onClearMentionSearchResult = onClearMentionSearchResult, + onCaptureVideoPermissionPermanentlyDenied = onPermissionPermanentlyDenied, tempWritableImageUri = tempWritableImageUri, tempWritableVideoUri = tempWritableVideoUri, onLinkClick = onLinkClick, @@ -734,6 +762,7 @@ private fun ConversationScreenContent( onChangeSelfDeletionClicked: () -> Unit, onSearchMentionQueryChanged: (String) -> Unit, onClearMentionSearchResult: () -> Unit, + onCaptureVideoPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit, tempWritableImageUri: Uri?, tempWritableVideoUri: Uri?, onLinkClick: (String) -> Unit, @@ -775,6 +804,7 @@ private fun ConversationScreenContent( onSearchMentionQueryChanged = onSearchMentionQueryChanged, onClearMentionSearchResult = onClearMentionSearchResult, onSendMessageBundle = onSendMessage, + onCaptureVideoPermissionPermanentlyDenied = onCaptureVideoPermissionPermanentlyDenied, tempWritableVideoUri = tempWritableVideoUri, tempWritableImageUri = tempWritableImageUri, onTypingEvent = onTypingEvent @@ -989,7 +1019,6 @@ fun PreviewConversationScreen() { onImageFullScreenMode = { _, _ -> }, onStartCall = { }, onJoinCall = { }, - onPermanentPermissionDecline = { }, onReactionClick = { _, _ -> }, onChangeAudioPosition = { _, _ -> }, onAudioClick = { }, @@ -1007,6 +1036,7 @@ fun PreviewConversationScreen() { onFailedMessageRetryClicked = {}, requestMentions = {}, onClearMentionSearchResult = {}, + onPermissionPermanentlyDenied = {}, conversationScreenState = conversationScreenState, messageComposerStateHolder = messageComposerStateHolder, onLinkClick = { _ -> }, diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationTopAppBar.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationTopAppBar.kt index b83c7ba4a97..35d4907abb2 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationTopAppBar.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationTopAppBar.kt @@ -63,6 +63,7 @@ import com.wire.android.ui.home.conversationslist.common.GroupConversationAvatar import com.wire.android.ui.theme.wireDimensions import com.wire.android.ui.theme.wireTypography import com.wire.android.util.debug.LocalFeatureVisibilityFlags +import com.wire.android.util.permission.PermissionDenialType import com.wire.android.util.ui.UIText import com.wire.kalium.logic.data.conversation.Conversation import com.wire.kalium.logic.data.id.ConversationId @@ -79,7 +80,7 @@ fun ConversationScreenTopAppBar( onPhoneButtonClick: () -> Unit, hasOngoingCall: Boolean, onJoinCallButtonClick: () -> Unit, - onPermanentPermissionDecline: () -> Unit, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit, isInteractionEnabled: Boolean, ) { val featureVisibilityFlags = LocalFeatureVisibilityFlags.current @@ -92,7 +93,7 @@ fun ConversationScreenTopAppBar( onPhoneButtonClick, hasOngoingCall, onJoinCallButtonClick, - onPermanentPermissionDecline, + onPermissionPermanentlyDenied, isInteractionEnabled, featureVisibilityFlags.ConversationSearchIcon ) @@ -109,7 +110,7 @@ private fun ConversationScreenTopAppBarContent( onPhoneButtonClick: () -> Unit, hasOngoingCall: Boolean, onJoinCallButtonClick: () -> Unit, - onPermanentPermissionDecline: () -> Unit, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit, isInteractionEnabled: Boolean, isSearchEnabled: Boolean, ) { @@ -178,7 +179,7 @@ private fun ConversationScreenTopAppBarContent( onJoinCallButtonClick = onJoinCallButtonClick, onPhoneButtonClick = onPhoneButtonClick, isCallingEnabled = isInteractionEnabled, - onPermanentPermissionDecline = onPermanentPermissionDecline, + onPermissionPermanentlyDenied = onPermissionPermanentlyDenied, ) } }, colors = TopAppBarDefaults.centerAlignedTopAppBarColors( @@ -222,20 +223,20 @@ private fun Avatar( private fun CallControlButton( hasOngoingCall: Boolean, onJoinCallButtonClick: () -> Unit, - onPermanentPermissionDecline: () -> Unit, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit, onPhoneButtonClick: () -> Unit, isCallingEnabled: Boolean ) { if (hasOngoingCall) { JoinButton( buttonClick = onJoinCallButtonClick, - onPermanentPermissionDecline = onPermanentPermissionDecline, + onPermissionPermanentlyDenied = onPermissionPermanentlyDenied, horizontalPadding = dimensions().spacing0x, ) } else { StartCallButton( onPhoneButtonClick = onPhoneButtonClick, - onPermanentPermissionDecline = onPermanentPermissionDecline, + onPermissionPermanentlyDenied = onPermissionPermanentlyDenied, isCallingEnabled = isCallingEnabled ) } @@ -260,7 +261,7 @@ fun PreviewConversationScreenTopAppBarLongTitle() { onPhoneButtonClick = {}, hasOngoingCall = false, onJoinCallButtonClick = {}, - onPermanentPermissionDecline = {}, + onPermissionPermanentlyDenied = {}, isInteractionEnabled = true, isSearchEnabled = false ) @@ -285,7 +286,7 @@ fun PreviewConversationScreenTopAppBarLongTitleWithSearch() { onPhoneButtonClick = {}, hasOngoingCall = false, onJoinCallButtonClick = {}, - onPermanentPermissionDecline = {}, + onPermissionPermanentlyDenied = {}, isInteractionEnabled = true, isSearchEnabled = true ) @@ -310,7 +311,7 @@ fun PreviewConversationScreenTopAppBarLongTitleWithSearchAndOngoingCall() { onPhoneButtonClick = {}, hasOngoingCall = true, onJoinCallButtonClick = {}, - onPermanentPermissionDecline = {}, + onPermissionPermanentlyDenied = {}, isInteractionEnabled = true, isSearchEnabled = true ) @@ -334,7 +335,7 @@ fun PreviewConversationScreenTopAppBarShortTitle() { onPhoneButtonClick = {}, hasOngoingCall = false, onJoinCallButtonClick = {}, - onPermanentPermissionDecline = {}, + onPermissionPermanentlyDenied = {}, isInteractionEnabled = true, isSearchEnabled = false ) @@ -358,7 +359,7 @@ fun PreviewConversationScreenTopAppBarShortTitleWithOngoingCall() { onPhoneButtonClick = {}, hasOngoingCall = true, onJoinCallButtonClick = {}, - onPermanentPermissionDecline = {}, + onPermissionPermanentlyDenied = {}, isInteractionEnabled = true, isSearchEnabled = false ) @@ -385,7 +386,7 @@ fun PreviewConversationScreenTopAppBarShortTitleWithVerified() { onPhoneButtonClick = {}, hasOngoingCall = false, onJoinCallButtonClick = {}, - onPermanentPermissionDecline = {}, + onPermissionPermanentlyDenied = {}, isInteractionEnabled = true, isSearchEnabled = false ) @@ -411,7 +412,7 @@ fun PreviewConversationScreenTopAppBarShortTitleWithLegalHold() { onPhoneButtonClick = {}, hasOngoingCall = false, onJoinCallButtonClick = {}, - onPermanentPermissionDecline = {}, + onPermissionPermanentlyDenied = {}, isInteractionEnabled = true, isSearchEnabled = false ) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/DownloadedAssetDialog.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/DownloadedAssetDialog.kt index 6a5463b2da0..24e821cb11d 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/DownloadedAssetDialog.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/DownloadedAssetDialog.kt @@ -25,6 +25,7 @@ import com.wire.android.ui.common.WireDialog import com.wire.android.ui.common.WireDialogButtonProperties import com.wire.android.ui.common.WireDialogButtonType import com.wire.android.ui.home.conversations.messages.DownloadedAssetDialogVisibilityState +import com.wire.android.util.permission.PermissionDenialType import com.wire.android.util.permission.rememberWriteStorageRequestFlow @Composable @@ -32,7 +33,8 @@ fun DownloadedAssetDialog( downloadedAssetDialogState: DownloadedAssetDialogVisibilityState, onSaveFileToExternalStorage: (String) -> Unit, onOpenFileWithExternalApp: (String) -> Unit, - hideOnAssetDownloadedDialog: () -> Unit + hideOnAssetDownloadedDialog: () -> Unit, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit ) { if (downloadedAssetDialogState is DownloadedAssetDialogVisibilityState.Displayed) { val assetName = downloadedAssetDialogState.assetData.fileName @@ -40,7 +42,8 @@ fun DownloadedAssetDialog( val onSaveFileWriteStorageRequest = rememberWriteStorageRequestFlow( onGranted = { onSaveFileToExternalStorage(messageId) }, - onDenied = { /** TODO: Show a dialog rationale explaining why the permission is needed **/ } + onPermissionDenied = { /** Nothing to do **/ }, + onPermissionPermanentlyDenied = onPermissionPermanentlyDenied ) WireDialog( diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModel.kt index fbe5d4155cf..c764da73e9d 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModel.kt @@ -171,6 +171,10 @@ class MessageComposerViewModel @Inject constructor( InvalidLinkDialogState.Hidden ) + var permissionPermanentlyDeniedDialogState: PermissionPermanentlyDeniedDialogState by mutableStateOf( + PermissionPermanentlyDeniedDialogState.Hidden + ) + var sureAboutMessagingDialogState: SureAboutMessagingDialogState by mutableStateOf( SureAboutMessagingDialogState.Hidden ) @@ -499,6 +503,15 @@ class MessageComposerViewModel @Inject constructor( fun hideInvalidLinkError() { invalidLinkDialogState = InvalidLinkDialogState.Hidden } + fun showPermissionPermanentlyDeniedDialog(title: Int, description: Int) { + permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Visible( + title = title, + description = description + ) + } + fun hidePermissionPermanentlyDeniedDialog() { + permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Hidden + } fun sendTypingEvent(typingIndicatorMode: TypingIndicatorMode) { viewModelScope.launch { diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewState.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewState.kt index 01a24265ce5..9d9cc8ea075 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewState.kt @@ -34,18 +34,18 @@ data class MessageComposerViewState( ) sealed class AssetTooLargeDialogState { - object Hidden : AssetTooLargeDialogState() + data object Hidden : AssetTooLargeDialogState() data class Visible(val assetType: AttachmentType, val maxLimitInMB: Int, val savedToDevice: Boolean) : AssetTooLargeDialogState() } sealed class VisitLinkDialogState { - object Hidden : VisitLinkDialogState() + data object Hidden : VisitLinkDialogState() data class Visible(val link: String, val openLink: () -> Unit) : VisitLinkDialogState() } sealed class InvalidLinkDialogState { - object Hidden : InvalidLinkDialogState() - object Visible : InvalidLinkDialogState() + data object Hidden : InvalidLinkDialogState() + data object Visible : InvalidLinkDialogState() } sealed class SureAboutMessagingDialogState { @@ -58,3 +58,8 @@ sealed class SureAboutMessagingDialogState { } } } + +sealed class PermissionPermanentlyDeniedDialogState { + data object Hidden : PermissionPermanentlyDeniedDialogState() + data class Visible(val title: Int, val description: Int) : PermissionPermanentlyDeniedDialogState() +} diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/call/ConversationCallViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/call/ConversationCallViewModel.kt index 7850cdd67f2..76bb7116b5f 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/call/ConversationCallViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/call/ConversationCallViewModel.kt @@ -147,14 +147,6 @@ class ConversationCallViewModel @Inject constructor( } } - fun showCallingPermissionDialog() { - conversationCallViewState = conversationCallViewState.copy(shouldShowCallingPermissionDialog = true) - } - - fun dismissCallingPermissionDialog() { - conversationCallViewState = conversationCallViewState.copy(shouldShowCallingPermissionDialog = false) - } - private fun showJoinCallAnywayDialog() { conversationCallViewState = conversationCallViewState.copy(shouldShowJoinAnywayDialog = true) } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/call/ConversationCallViewState.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/call/ConversationCallViewState.kt index 02266c1857b..0c14052bdb5 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/call/ConversationCallViewState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/call/ConversationCallViewState.kt @@ -22,6 +22,5 @@ data class ConversationCallViewState( val participantsCount: Int = 0, val hasOngoingCall: Boolean = false, val hasEstablishedCall: Boolean = false, - val shouldShowJoinAnywayDialog: Boolean = false, - val shouldShowCallingPermissionDialog: Boolean = false + val shouldShowJoinAnywayDialog: Boolean = false ) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/media/ConversationMediaScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/media/ConversationMediaScreen.kt index 1734890f00f..2eacf4603bc 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/media/ConversationMediaScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/media/ConversationMediaScreen.kt @@ -52,6 +52,7 @@ import com.wire.android.ui.common.TabItem import com.wire.android.ui.common.WireTabRow import com.wire.android.ui.common.calculateCurrentTab import com.wire.android.ui.common.colorsScheme +import com.wire.android.ui.common.dialogs.PermissionPermanentlyDeniedDialog import com.wire.android.ui.common.scaffold.WireScaffold import com.wire.android.ui.common.topBarElevation import com.wire.android.ui.common.topappbar.NavigationIconType @@ -105,7 +106,18 @@ fun ConversationMediaScreen( downloadedAssetDialogState = conversationMessagesViewModel.conversationViewState.downloadedAssetDialogState, onSaveFileToExternalStorage = conversationMessagesViewModel::downloadAssetExternally, onOpenFileWithExternalApp = conversationMessagesViewModel::downloadAndOpenAsset, - hideOnAssetDownloadedDialog = conversationMessagesViewModel::hideOnAssetDownloadedDialog + hideOnAssetDownloadedDialog = conversationMessagesViewModel::hideOnAssetDownloadedDialog, + onPermissionPermanentlyDenied = { + conversationMessagesViewModel.showPermissionPermanentlyDeniedDialog( + title = R.string.app_permission_dialog_title, + description = R.string.save_asset_permission_dialog_description + ) + } + ) + + PermissionPermanentlyDeniedDialog( + dialogState = conversationMessagesViewModel.permissionPermanentlyDeniedDialogState, + hideDialog = conversationMessagesViewModel::hidePermissionPermanentlyDeniedDialog ) SnackBarMessage( diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt index f4be3695369..15bbe118dd5 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt @@ -32,6 +32,7 @@ import com.wire.android.navigation.SavedStateViewModel import com.wire.android.ui.home.conversations.ConversationNavArgs import com.wire.android.ui.home.conversations.ConversationSnackbarMessages import com.wire.android.ui.home.conversations.ConversationSnackbarMessages.OnResetSession +import com.wire.android.ui.home.conversations.PermissionPermanentlyDeniedDialogState import com.wire.android.ui.home.conversations.model.AssetBundle import com.wire.android.ui.home.conversations.model.UIMessage import com.wire.android.ui.home.conversations.usecase.GetMessagesForConversationUseCase @@ -93,6 +94,10 @@ class ConversationMessagesViewModel @Inject constructor( val conversationId: QualifiedID = conversationNavArgs.conversationId private val searchedMessageIdNavArgs: String? = conversationNavArgs.searchedMessageId + var permissionPermanentlyDeniedDialogState: PermissionPermanentlyDeniedDialogState by mutableStateOf( + PermissionPermanentlyDeniedDialogState.Hidden + ) + var conversationViewState by mutableStateOf( ConversationMessagesViewState( searchedMessageId = searchedMessageIdNavArgs @@ -335,6 +340,16 @@ class ConversationMessagesViewModel @Inject constructor( return null } + fun showPermissionPermanentlyDeniedDialog(title: Int, description: Int) { + permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Visible( + title = title, + description = description + ) + } + fun hidePermissionPermanentlyDeniedDialog() { + permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Hidden + } + override fun onCleared() { super.onCleared() conversationAudioMessagePlayer.close() diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt index 9010ada16bf..6de18961cb2 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt @@ -32,6 +32,7 @@ import com.wire.android.model.UserAvatarData import com.wire.android.ui.common.bottomsheet.conversation.ConversationTypeDetail import com.wire.android.ui.common.dialogs.BlockUserDialogState import com.wire.android.ui.home.HomeSnackbarState +import com.wire.android.ui.home.conversations.PermissionPermanentlyDeniedDialogState import com.wire.android.ui.home.conversations.model.UILastMessageContent import com.wire.android.ui.home.conversations.search.DEFAULT_SEARCH_QUERY_DEBOUNCE import com.wire.android.ui.home.conversationslist.model.BadgeEventType @@ -149,6 +150,10 @@ class ConversationListViewModel @Inject constructor( var establishedCallConversationId: QualifiedID? = null private var conversationId: QualifiedID? = null + var permissionPermanentlyDeniedDialogState: PermissionPermanentlyDeniedDialogState by mutableStateOf( + PermissionPermanentlyDeniedDialogState.Hidden + ) + private suspend fun observeEstablishedCall() { observeEstablishedCalls() .distinctUntilChanged() @@ -209,16 +214,14 @@ class ConversationListViewModel @Inject constructor( } } - fun showCallingPermissionDialog() { - conversationListCallState = conversationListCallState.copy( - shouldShowCallingPermissionDialog = true + fun showPermissionPermanentlyDeniedDialog(title: Int, description: Int) { + permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Visible( + title = title, + description = description ) } - - fun dismissCallingPermissionDialog() { - conversationListCallState = conversationListCallState.copy( - shouldShowCallingPermissionDialog = false - ) + fun hidePermissionPermanentlyDeniedDialog() { + permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Hidden } // Mateusz : First iteration, just filter stuff diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationRouter.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationRouter.kt index e3c752ea244..02b3cafb94e 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationRouter.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationRouter.kt @@ -26,17 +26,17 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.platform.LocalContext import androidx.hilt.navigation.compose.hiltViewModel +import com.wire.android.R import com.wire.android.navigation.NavigationCommand import com.wire.android.navigation.Navigator -import com.wire.android.ui.calling.common.MicrophonePermissionDeniedDialog import com.wire.android.ui.common.bottomsheet.conversation.ConversationOptionNavigation import com.wire.android.ui.common.bottomsheet.conversation.ConversationSheetContent import com.wire.android.ui.common.bottomsheet.conversation.rememberConversationSheetState import com.wire.android.ui.common.dialogs.ArchiveConversationDialog import com.wire.android.ui.common.dialogs.BlockUserDialogContent import com.wire.android.ui.common.dialogs.BlockUserDialogState +import com.wire.android.ui.common.dialogs.PermissionPermanentlyDeniedDialog import com.wire.android.ui.common.dialogs.UnblockUserDialogContent import com.wire.android.ui.common.dialogs.UnblockUserDialogState import com.wire.android.ui.common.topappbar.search.SearchBarState @@ -60,6 +60,7 @@ import com.wire.android.ui.home.conversationslist.model.GroupDialogState import com.wire.android.ui.home.conversationslist.model.isArchive import com.wire.android.ui.home.conversationslist.search.SearchConversationScreen import com.wire.android.util.extension.openAppInfoScreen +import com.wire.android.util.permission.PermissionDenialType import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.user.UserId import kotlinx.coroutines.flow.MutableSharedFlow @@ -81,20 +82,11 @@ fun ConversationRouterHomeBridge( conversationsSource: ConversationsSource = ConversationsSource.MAIN ) { val viewModel: ConversationListViewModel = hiltViewModel() - val context = LocalContext.current LaunchedEffect(conversationsSource) { viewModel.updateConversationsSource(conversationsSource) } - MicrophonePermissionDeniedDialog( - shouldShow = viewModel.conversationListCallState.shouldShowCallingPermissionDialog, - onDismiss = viewModel::dismissCallingPermissionDialog, - onOpenSettings = { - context.openAppInfoScreen() - } - ) - val conversationRouterHomeState = rememberConversationRouterState( initialConversationItemType = conversationItemType, homeSnackBarState = viewModel.homeSnackBarState, @@ -217,7 +209,14 @@ fun ConversationRouterHomeBridge( onOpenConversation = onOpenConversation, onOpenUserProfile = onOpenUserProfile, onJoinedCall = onJoinedCall, - onPermanentPermissionDecline = viewModel::showCallingPermissionDialog + onPermissionPermanentlyDenied = { + if (it == PermissionDenialType.CallingMicrophone) { + viewModel.showPermissionPermanentlyDeniedDialog( + R.string.app_permission_dialog_title, + R.string.call_permission_dialog_description + ) + } + } ) ConversationItemType.CALLS -> @@ -249,12 +248,17 @@ fun ConversationRouterHomeBridge( onEditConversation = onEditConversationItem, onOpenUserProfile = onOpenUserProfile, onJoinCall = { viewModel.joinOngoingCall(it, onJoinedCall) }, - onPermanentPermissionDecline = viewModel::showCallingPermissionDialog + onPermissionPermanentlyDenied = { } ) } } } + PermissionPermanentlyDeniedDialog( + dialogState = viewModel.permissionPermanentlyDeniedDialogState, + hideDialog = viewModel::hidePermissionPermanentlyDeniedDialog + ) + BlockUserDialogContent( isLoading = requestInProgress, dialogState = blockUserDialogState, diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/all/AllConversationScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/all/AllConversationScreen.kt index ae3cd91dffa..a2cf3dccfc4 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/all/AllConversationScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/all/AllConversationScreen.kt @@ -52,6 +52,7 @@ import com.wire.android.ui.home.conversationslist.model.ConversationFolder import com.wire.android.ui.home.conversationslist.model.ConversationItem import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.theme.wireTypography +import com.wire.android.util.permission.PermissionDenialType import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.user.UserId import kotlinx.collections.immutable.ImmutableMap @@ -85,7 +86,7 @@ fun AllConversationScreenContent( onOpenConversation: (ConversationId) -> Unit, onOpenUserProfile: (UserId) -> Unit, onJoinedCall: (ConversationId) -> Unit, - onPermanentPermissionDecline: () -> Unit + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit ) { val lazyListState = rememberLazyListState() val callConversationIdToJoin = remember { mutableStateOf(ConversationId("", "")) } @@ -115,7 +116,7 @@ fun AllConversationScreenContent( callConversationIdToJoin.value = it viewModel.joinOngoingCall(it, onJoinedCall) }, - onPermanentPermissionDecline = onPermanentPermissionDecline + onPermissionPermanentlyDenied = onPermissionPermanentlyDenied ) } } @@ -167,7 +168,7 @@ fun PreviewAllConversationScreen() { onOpenConversation = {}, onOpenUserProfile = {}, onJoinedCall = {}, - onPermanentPermissionDecline = {} + onPermissionPermanentlyDenied = {} ) } @@ -181,7 +182,7 @@ fun ConversationListEmptyStateScreenPreview() { onOpenConversation = {}, onOpenUserProfile = {}, onJoinedCall = {}, - onPermanentPermissionDecline = {} + onPermissionPermanentlyDenied = {} ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/call/CallsScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/call/CallsScreen.kt index d9c9af7b3e6..5c6dce95ef2 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/call/CallsScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/call/CallsScreen.kt @@ -102,7 +102,7 @@ fun CallContent( openMenu = onEditConversationItem, openUserProfile = onOpenUserProfile, joinCall = { }, - onPermanentPermissionDecline = {}, + onPermissionPermanentlyDenied = {}, searchQuery = "" ) } @@ -117,7 +117,7 @@ fun CallContent( openMenu = onEditConversationItem, openUserProfile = onOpenUserProfile, joinCall = { }, - onPermanentPermissionDecline = {}, + onPermissionPermanentlyDenied = {}, searchQuery = " " ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/common/ConversationItemFactory.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/common/ConversationItemFactory.kt index 6f5636852ac..b7aa6bea877 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/common/ConversationItemFactory.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/common/ConversationItemFactory.kt @@ -42,6 +42,7 @@ import com.wire.android.ui.home.conversationslist.model.BlockingState import com.wire.android.ui.home.conversationslist.model.ConversationInfo import com.wire.android.ui.home.conversationslist.model.ConversationItem import com.wire.android.ui.home.conversationslist.model.toUserInfoLabel +import com.wire.android.util.permission.PermissionDenialType import com.wire.android.util.ui.UIText import com.wire.kalium.logic.data.conversation.Conversation import com.wire.kalium.logic.data.conversation.MutedConversationStatus @@ -60,7 +61,7 @@ fun ConversationItemFactory( openMenu: (ConversationItem) -> Unit, openUserProfile: (UserId) -> Unit, joinCall: (ConversationId) -> Unit, - onPermanentPermissionDecline: () -> Unit + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit = { } ) { val onConversationItemClick = remember(conversation) { Clickable( @@ -107,7 +108,7 @@ fun ConversationItemFactory( onJoinCallClick = { joinCall(conversation.conversationId) }, - onPermanentPermissionDecline = onPermanentPermissionDecline + onPermissionPermanentlyDenied = onPermissionPermanentlyDenied ) } @@ -122,7 +123,7 @@ private fun GeneralConversationItem( subTitle: @Composable () -> Unit = {}, onConversationItemClick: Clickable, onJoinCallClick: () -> Unit, - onPermanentPermissionDecline: () -> Unit + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit ) { when (conversation) { is ConversationItem.GroupConversation -> { @@ -152,7 +153,7 @@ private fun GeneralConversationItem( if (hasOnGoingCall) { JoinButton( buttonClick = onJoinCallClick, - onPermanentPermissionDecline = onPermanentPermissionDecline + onPermissionPermanentlyDenied = onPermissionPermanentlyDenied ) } else { Row( diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/common/ConversationList.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/common/ConversationList.kt index 56ac62faf23..f8909dc3b75 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/common/ConversationList.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/common/ConversationList.kt @@ -31,6 +31,7 @@ import androidx.compose.ui.unit.Dp import com.wire.android.ui.home.conversationslist.model.ConversationFolder import com.wire.android.ui.home.conversationslist.model.ConversationItem import com.wire.android.util.extension.folderWithElements +import com.wire.android.util.permission.PermissionDenialType import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.user.UserId import kotlinx.collections.immutable.ImmutableMap @@ -49,7 +50,7 @@ fun ConversationList( onEditConversation: (ConversationItem) -> Unit, onOpenUserProfile: (UserId) -> Unit, onJoinCall: (ConversationId) -> Unit, - onPermanentPermissionDecline: () -> Unit + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit ) { val context = LocalContext.current @@ -91,7 +92,7 @@ fun ConversationList( openMenu = onEditConversation, openUserProfile = onOpenUserProfile, joinCall = onJoinCall, - onPermanentPermissionDecline = onPermanentPermissionDecline + onPermissionPermanentlyDenied = onPermissionPermanentlyDenied ) } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/mention/MentionScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/mention/MentionScreen.kt index 0740951d8c9..1ebede26463 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/mention/MentionScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/mention/MentionScreen.kt @@ -102,7 +102,7 @@ private fun MentionContent( openMenu = onEditConversationItem, openUserProfile = onOpenUserProfile, joinCall = {}, - onPermanentPermissionDecline = {}, + onPermissionPermanentlyDenied = {}, searchQuery = "" ) } @@ -117,7 +117,7 @@ private fun MentionContent( openMenu = onEditConversationItem, openUserProfile = onOpenUserProfile, joinCall = {}, - onPermanentPermissionDecline = {}, + onPermissionPermanentlyDenied = {}, searchQuery = "" ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/search/SearchConversationScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/search/SearchConversationScreen.kt index c65a2509b45..43048b4fd2a 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/search/SearchConversationScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/search/SearchConversationScreen.kt @@ -41,6 +41,7 @@ import com.wire.android.ui.home.conversationslist.model.ConversationItem import com.wire.android.ui.theme.WireTheme import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.theme.wireTypography +import com.wire.android.util.permission.PermissionDenialType import com.wire.android.util.ui.PreviewMultipleThemes import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.user.UserId @@ -55,7 +56,7 @@ fun SearchConversationScreen( onEditConversation: (ConversationItem) -> Unit, onOpenUserProfile: (UserId) -> Unit, onJoinCall: (ConversationId) -> Unit, - onPermanentPermissionDecline: () -> Unit + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit ) { Box(Modifier.fillMaxSize()) { if (conversationSearchResult.values.isEmpty()) { @@ -68,7 +69,7 @@ fun SearchConversationScreen( onEditConversation = onEditConversation, onOpenUserProfile = onOpenUserProfile, onJoinCall = onJoinCall, - onPermanentPermissionDecline = onPermanentPermissionDecline + onPermissionPermanentlyDenied = onPermissionPermanentlyDenied ) } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/gallery/MediaGalleryScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/gallery/MediaGalleryScreen.kt index e9f2ae1ff17..d156514b4ab 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/gallery/MediaGalleryScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/gallery/MediaGalleryScreen.kt @@ -40,6 +40,7 @@ import com.wire.android.navigation.Navigator import com.wire.android.navigation.style.PopUpNavigationAnimation import com.wire.android.ui.common.bottomsheet.MenuModalSheetLayout import com.wire.android.ui.common.colorsScheme +import com.wire.android.ui.common.dialogs.PermissionPermanentlyDeniedDialog import com.wire.android.ui.common.scaffold.WireScaffold import com.wire.android.ui.home.conversations.MediaGallerySnackbarMessages import com.wire.android.ui.home.conversations.delete.DeleteMessageDialog @@ -66,9 +67,20 @@ fun MediaGalleryScreen( onGranted = { mediaGalleryScreenState.showContextualMenu(false) mediaGalleryViewModel.saveImageToExternalStorage() - }, onDenied = { - // TODO - }) + }, + onPermissionDenied = { /** Nothing to do **/ }, + onPermissionPermanentlyDenied = { + mediaGalleryViewModel.showPermissionPermanentlyDeniedDialog( + title = R.string.app_permission_dialog_title, + description = R.string.save_asset_permission_dialog_description + ) + } + ) + + PermissionPermanentlyDeniedDialog( + dialogState = mediaGalleryViewModel.permissionPermanentlyDeniedDialogState, + hideDialog = mediaGalleryViewModel::hidePermissionPermanentlyDeniedDialog + ) with(viewModelState) { WireScaffold( diff --git a/app/src/main/kotlin/com/wire/android/ui/home/gallery/MediaGalleryViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/gallery/MediaGalleryViewModel.kt index b304007c046..5699382501e 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/gallery/MediaGalleryViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/gallery/MediaGalleryViewModel.kt @@ -27,6 +27,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.wire.android.model.ImageAsset import com.wire.android.ui.home.conversations.MediaGallerySnackbarMessages +import com.wire.android.ui.home.conversations.PermissionPermanentlyDeniedDialogState import com.wire.android.ui.home.conversations.delete.DeleteMessageDialogActiveState import com.wire.android.ui.home.conversations.delete.DeleteMessageDialogHelper import com.wire.android.ui.home.conversations.delete.DeleteMessageDialogsState @@ -74,6 +75,10 @@ class MediaGalleryViewModel @Inject constructor( mediaGalleryNavArgs.isEphemeral ) + var permissionPermanentlyDeniedDialogState: PermissionPermanentlyDeniedDialogState by mutableStateOf( + PermissionPermanentlyDeniedDialogState.Hidden + ) + private val messageId = imageAsset.messageId private val conversationId = imageAsset.conversationId var mediaGalleryViewState by mutableStateOf(MediaGalleryViewState(isEphemeral = imageAsset.isEphemeral)) @@ -194,4 +199,14 @@ class MediaGalleryViewModel @Inject constructor( mediaGalleryViewState = mediaGalleryViewState.copy(deleteMessageDialogsState = newValue(it)) } } + + fun showPermissionPermanentlyDeniedDialog(title: Int, description: Int) { + permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Visible( + title = title, + description = description + ) + } + fun hidePermissionPermanentlyDeniedDialog() { + permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Hidden + } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/AdditionalOptions.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/AdditionalOptions.kt index d09232a4110..93c5e212dbb 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/AdditionalOptions.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/AdditionalOptions.kt @@ -38,6 +38,7 @@ import com.wire.android.ui.home.messagecomposer.state.AdditionalOptionSelectItem import com.wire.android.ui.home.messagecomposer.state.AdditionalOptionSubMenuState import com.wire.android.ui.home.messagecomposer.state.RichTextMarkdown import com.wire.android.ui.theme.wireColorScheme +import com.wire.android.util.permission.PermissionDenialType @Composable fun AdditionalOptionsMenu( @@ -89,10 +90,10 @@ fun AdditionalOptionsMenu( } } -@OptIn(ExperimentalMaterial3Api::class) @Composable fun AdditionalOptionSubMenu( isFileSharingEnabled: Boolean, + onCaptureVideoPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit, onLocationPickerClicked: () -> Unit, onCloseAdditionalAttachment: () -> Unit, onRecordAudioMessageClicked: () -> Unit, @@ -111,7 +112,8 @@ fun AdditionalOptionSubMenu( tempWritableVideoUri = tempWritableVideoUri, isFileSharingEnabled = isFileSharingEnabled, onRecordAudioMessageClicked = onRecordAudioMessageClicked, - onLocationPickerClicked = onLocationPickerClicked + onLocationPickerClicked = onLocationPickerClicked, + onCaptureVideoPermissionPermanentlyDenied = onCaptureVideoPermissionPermanentlyDenied ) when (additionalOptionsState) { AdditionalOptionSubMenuState.AttachFile -> { diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/AttachmentOptions.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/AttachmentOptions.kt index 750d8f21a96..c9fe3c9fbf2 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/AttachmentOptions.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/AttachmentOptions.kt @@ -48,6 +48,7 @@ import com.wire.android.ui.common.dimensions import com.wire.android.ui.home.conversations.model.UriAsset import com.wire.android.ui.theme.wireTypography import com.wire.android.util.debug.LocalFeatureVisibilityFlags +import com.wire.android.util.permission.PermissionDenialType import com.wire.android.util.permission.UseCameraAndWriteStorageRequestFlow import com.wire.android.util.permission.UseCameraRequestFlow import com.wire.android.util.permission.UseStorageRequestFlow @@ -64,7 +65,8 @@ fun AttachmentOptionsComponent( tempWritableImageUri: Uri?, tempWritableVideoUri: Uri?, isFileSharingEnabled: Boolean, - onLocationPickerClicked: () -> Unit + onLocationPickerClicked: () -> Unit, + onCaptureVideoPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit ) { val density = LocalDensity.current val textMeasurer = rememberTextMeasurer() @@ -75,7 +77,8 @@ fun AttachmentOptionsComponent( tempWritableVideoUri, onAttachmentPicked, onRecordAudioMessageClicked, - onLocationPickerClicked + onLocationPickerClicked, + onCaptureVideoPermissionPermanentlyDenied ) val labelStyle = MaterialTheme.wireTypography.button03 @@ -150,25 +153,34 @@ private fun calculateGridParams( } @Composable -fun FileBrowserFlow(onFilePicked: (Uri) -> Unit): UseStorageRequestFlow { +fun FileBrowserFlow( + onFilePicked: (Uri) -> Unit, + onCaptureVideoPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit +): UseStorageRequestFlow { return rememberOpenFileBrowserFlow( onFileBrowserItemPicked = onFilePicked, - onPermissionDenied = { /* TODO: Implement denied permission rationale */ } + onPermissionDenied = { /* Nothing to do */ }, + onPermissionPermanentlyDenied = onCaptureVideoPermissionPermanentlyDenied ) } @Composable -private fun GalleryFlow(onFilePicked: (Uri) -> Unit): UseStorageRequestFlow { +private fun GalleryFlow( + onFilePicked: (Uri) -> Unit, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit +): UseStorageRequestFlow { return rememberOpenGalleryFlow( onGalleryItemPicked = onFilePicked, - onPermissionDenied = { /* TODO: Implement denied permission rationale */ } + onPermissionDenied = { /* Nothing to do */ }, + onPermissionPermanentlyDenied = onPermissionPermanentlyDenied ) } @Composable private fun TakePictureFlow( tempWritableVideoUri: Uri?, - onPictureTaken: (Uri) -> Unit + onPictureTaken: (Uri) -> Unit, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit ): UseCameraRequestFlow? { tempWritableVideoUri?.let { return rememberTakePictureFlow( @@ -178,7 +190,8 @@ private fun TakePictureFlow( } }, targetPictureFileUri = it, - onPermissionDenied = { /* TODO: Implement denied permission rationale */ } + onPermissionDenied = { /* Nothing to do */ }, + onPermissionPermanentlyDenied = onPermissionPermanentlyDenied ) } return null @@ -187,17 +200,15 @@ private fun TakePictureFlow( @Composable private fun CaptureVideoFlow( tempWritableVideoUri: Uri?, - onVideoCaptured: (Uri) -> Unit + onVideoCaptured: (Uri) -> Unit, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit, ): UseCameraAndWriteStorageRequestFlow? { - tempWritableVideoUri?.let { + tempWritableVideoUri?.let { uri -> return rememberCaptureVideoFlow( - onVideoRecorded = { hasCapturedVideo -> - if (hasCapturedVideo) { - onVideoCaptured(it) - } - }, - targetVideoFileUri = it, - onPermissionDenied = { /* TODO: Implement denied permission rationale */ } + onVideoRecorded = { onVideoCaptured(uri)}, + targetVideoFileUri = uri, + onPermissionDenied = { /** Nothing to do here when permission is denied once */ }, + onPermissionPermanentlyDenied = onPermissionPermanentlyDenied ) } return null @@ -210,12 +221,27 @@ private fun buildAttachmentOptionItems( tempWritableVideoUri: Uri?, onFilePicked: (UriAsset) -> Unit, onRecordAudioMessageClicked: () -> Unit, - onLocationPickerClicked: () -> Unit + onLocationPickerClicked: () -> Unit, + onCaptureVideoPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit ): List { - val fileFlow = FileBrowserFlow(remember { { onFilePicked(UriAsset(it, false)) } }) - val galleryFlow = GalleryFlow(remember { { onFilePicked(UriAsset(it, false)) } }) - val cameraFlow = TakePictureFlow(tempWritableImageUri, remember { { onFilePicked(UriAsset(it, false)) } }) - val captureVideoFlow = CaptureVideoFlow(tempWritableVideoUri, remember { { onFilePicked(UriAsset(it, true)) } }) + val fileFlow = FileBrowserFlow( + remember { { onFilePicked(UriAsset(it, false)) } }, + onCaptureVideoPermissionPermanentlyDenied + ) + val galleryFlow = GalleryFlow( + remember { { onFilePicked(UriAsset(it, false)) } }, + onCaptureVideoPermissionPermanentlyDenied + ) + val cameraFlow = TakePictureFlow( + tempWritableImageUri, + remember { { onFilePicked(UriAsset(it, false)) } }, + onCaptureVideoPermissionPermanentlyDenied + ) + val captureVideoFlow = CaptureVideoFlow( + tempWritableVideoUri, + remember { { onFilePicked(UriAsset(it, true)) } }, + onCaptureVideoPermissionPermanentlyDenied + ) return buildList { val localFeatureVisibilityFlags = LocalFeatureVisibilityFlags.current @@ -289,7 +315,8 @@ fun PreviewAttachmentComponents() { tempWritableImageUri = null, tempWritableVideoUri = null, onRecordAudioMessageClicked = {}, - onLocationPickerClicked = {} + onLocationPickerClicked = {}, + onCaptureVideoPermissionPermanentlyDenied = {} ) } @@ -307,7 +334,8 @@ fun PreviewAttachmentOptionsComponentSmallScreen() { tempWritableImageUri = null, tempWritableVideoUri = null, onRecordAudioMessageClicked = {}, - onLocationPickerClicked = {} + onLocationPickerClicked = {}, + onCaptureVideoPermissionPermanentlyDenied = {} ) } } @@ -327,7 +355,8 @@ fun PreviewAttachmentOptionsComponentNormalScreen() { tempWritableImageUri = null, tempWritableVideoUri = null, onRecordAudioMessageClicked = {}, - onLocationPickerClicked = {} + onLocationPickerClicked = {}, + onCaptureVideoPermissionPermanentlyDenied = {}, ) } } @@ -347,7 +376,8 @@ fun PreviewAttachmentOptionsComponentTabledScreen() { tempWritableImageUri = null, tempWritableVideoUri = null, onRecordAudioMessageClicked = {}, - onLocationPickerClicked = {} + onLocationPickerClicked = {}, + onCaptureVideoPermissionPermanentlyDenied = {}, ) } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt index 8cdec9c3d7a..7c43c4ebd16 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt @@ -43,7 +43,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp @@ -57,11 +56,12 @@ import com.wire.android.ui.home.messagecomposer.state.AdditionalOptionSelectItem import com.wire.android.ui.home.messagecomposer.state.AdditionalOptionSubMenuState import com.wire.android.ui.home.messagecomposer.state.MessageComposerStateHolder import com.wire.android.ui.home.messagecomposer.state.MessageCompositionType +import com.wire.android.util.permission.PermissionDenialType import com.wire.kalium.logic.data.conversation.Conversation import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.util.isPositiveNotNull -@OptIn(ExperimentalLayoutApi::class, ExperimentalComposeUiApi::class) +@OptIn(ExperimentalLayoutApi::class) @Suppress("ComplexMethod") @Composable fun EnabledMessageComposer( @@ -75,6 +75,7 @@ fun EnabledMessageComposer( onAttachmentPicked: (UriAsset) -> Unit, onAudioRecorded: (UriAsset) -> Unit, onLocationPicked: (GeoLocatedAddress) -> Unit, + onCaptureVideoPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit, onPingOptionClicked: () -> Unit, onClearMentionSearchResult: () -> Unit, tempWritableVideoUri: Uri?, @@ -271,6 +272,7 @@ fun EnabledMessageComposer( onAttachmentPicked = onAttachmentPicked, onAudioRecorded = onAudioRecorded, onLocationPicked = onLocationPicked, + onCaptureVideoPermissionPermanentlyDenied = onCaptureVideoPermissionPermanentlyDenied, tempWritableImageUri = tempWritableImageUri, tempWritableVideoUri = tempWritableVideoUri, modifier = Modifier diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposer.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposer.kt index 5e687363eb5..2b114c61ccd 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposer.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposer.kt @@ -61,6 +61,7 @@ import com.wire.android.ui.home.messagecomposer.state.Ping import com.wire.android.ui.theme.WireTheme import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.theme.wireTypography +import com.wire.android.util.permission.PermissionDenialType import com.wire.android.util.ui.PreviewMultipleThemes import com.wire.android.util.ui.stringWithStyledArgs import com.wire.kalium.logic.data.conversation.Conversation.TypingIndicatorMode @@ -78,6 +79,7 @@ fun MessageComposer( onChangeSelfDeletionClicked: () -> Unit, onSearchMentionQueryChanged: (String) -> Unit, onClearMentionSearchResult: () -> Unit, + onCaptureVideoPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit, tempWritableVideoUri: Uri?, tempWritableImageUri: Uri?, onTypingEvent: (TypingIndicatorMode) -> Unit @@ -146,6 +148,7 @@ fun MessageComposer( onChangeSelfDeletionClicked = onChangeSelfDeletionClicked, onSearchMentionQueryChanged = onSearchMentionQueryChanged, onClearMentionSearchResult = onClearMentionSearchResult, + onCaptureVideoPermissionPermanentlyDenied = onCaptureVideoPermissionPermanentlyDenied, tempWritableVideoUri = tempWritableVideoUri, tempWritableImageUri = tempWritableImageUri, onTypingEvent = onTypingEvent @@ -242,6 +245,7 @@ private fun BaseComposerPreview( onChangeSelfDeletionClicked = { }, onSearchMentionQueryChanged = { }, onClearMentionSearchResult = { }, + onCaptureVideoPermissionPermanentlyDenied = { }, onSendMessageBundle = { }, tempWritableVideoUri = null, tempWritableImageUri = null, diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerComponent.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerComponent.kt index 75f6fc587dc..1adc9bc2084 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerComponent.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerComponent.kt @@ -74,16 +74,18 @@ import kotlinx.coroutines.launch @Composable fun LocationPickerComponent( onLocationPicked: (GeoLocatedAddress) -> Unit, - onLocationClosed: () -> Unit + onLocationClosed: () -> Unit, + viewModel : LocationPickerViewModel = hiltViewModel() ) { - val viewModel = hiltViewModel() val coroutineScope = rememberCoroutineScope() val sheetState = rememberDismissibleWireModalSheetState(initialValue = SheetValue.Expanded, onLocationClosed) - val locationFlow = LocationFlow( - onCurrentLocationPicked = viewModel::getCurrentLocation, - onLocationDenied = viewModel::onPermissionsDenied + val locationFlow = rememberCurrentLocationFlow( + onPermissionAllowed = viewModel::getCurrentLocation, + onPermissionDenied = { /* do nothing */ }, + onPermissionPermanentlyDenied = viewModel::onPermissionPermanentlyDenied ) + LaunchedEffect(Unit) { locationFlow.launch() } @@ -228,13 +230,3 @@ private fun RowScope.LoadingLocation() { textAlign = TextAlign.Start ) } - -@Composable -private fun LocationFlow( - onCurrentLocationPicked: () -> Unit, - onLocationDenied: () -> Unit, -) = - rememberCurrentLocationFlow( - onPermissionAllowed = onCurrentLocationPicked, - onPermissionDenied = onLocationDenied - ) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerViewModel.kt index 353929d758f..2d9158587b4 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerViewModel.kt @@ -40,7 +40,7 @@ class LocationPickerViewModel @Inject constructor(private val locationPickerHelp state = state.copy(showLocationSharingError = false) } - fun onPermissionsDenied() { + fun onPermissionPermanentlyDenied() { state = state.copy(showPermissionDeniedDialog = true) } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/recordaudio/RecordAudioComponent.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/recordaudio/RecordAudioComponent.kt index c59cc9db6b9..6db417fd624 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/recordaudio/RecordAudioComponent.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/recordaudio/RecordAudioComponent.kt @@ -37,8 +37,8 @@ import androidx.lifecycle.LifecycleOwner import com.sebaslogen.resaca.hilt.hiltViewModelScoped import com.wire.android.ui.common.colorsScheme import com.wire.android.ui.common.dimensions -import com.wire.android.ui.home.conversations.model.UriAsset import com.wire.android.ui.common.snackbar.LocalSnackbarHostState +import com.wire.android.ui.home.conversations.model.UriAsset import com.wire.android.ui.theme.wireColorScheme import com.wire.android.util.extension.openAppInfoScreen import com.wire.android.util.permission.rememberRecordAudioRequestFlow @@ -55,7 +55,7 @@ fun RecordAudioComponent( val recordAudioFlow = RecordAudioFlow( startRecording = { viewModel.startRecording() }, - showPermissionsDeniedDialog = viewModel::showPermissionsDeniedDialog + onAudioPermissionPermanentlyDenied = viewModel::showPermissionsDeniedDialog ) LaunchedEffect(Unit) { @@ -155,12 +155,11 @@ fun RecordAudioComponent( @Composable private fun RecordAudioFlow( startRecording: () -> Unit, - showPermissionsDeniedDialog: () -> Unit + onAudioPermissionPermanentlyDenied: () -> Unit ) = rememberRecordAudioRequestFlow( onPermissionAllowed = { startRecording() }, - onPermissionDenied = { - showPermissionsDeniedDialog() - } + onPermissionDenied = { /** Nothing to do **/ }, + onAudioPermissionPermanentlyDenied = onAudioPermissionPermanentlyDenied ) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackUpAndRestoreStateHolder.kt b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackUpAndRestoreStateHolder.kt index 8694bf070bf..42566f14e38 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackUpAndRestoreStateHolder.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackUpAndRestoreStateHolder.kt @@ -23,6 +23,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import com.wire.android.ui.home.conversations.PermissionPermanentlyDeniedDialogState class BackUpAndRestoreStateHolder { @@ -30,6 +31,10 @@ class BackUpAndRestoreStateHolder { BackupAndRestoreDialog.None ) + var permissionPermanentlyDeniedDialogState: PermissionPermanentlyDeniedDialogState by mutableStateOf( + PermissionPermanentlyDeniedDialogState.Hidden + ) + fun showBackupDialog() { dialogState = BackupAndRestoreDialog.CreateBackup } @@ -41,6 +46,18 @@ class BackUpAndRestoreStateHolder { fun dismissDialog() { dialogState = BackupAndRestoreDialog.None } + + fun showPermissionPermanentlyDeniedDialog(title: Int, description: Int) { + permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Visible( + title = title, + description = description + ) + } + + fun hidePermissionPermanentlyDeniedDialog() { + permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Hidden + } + } @Composable diff --git a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackupAndRestoreScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackupAndRestoreScreen.kt index f2505adfd5a..e9bd0cc5783 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackupAndRestoreScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackupAndRestoreScreen.kt @@ -43,6 +43,7 @@ import com.wire.android.navigation.BackStackMode import com.wire.android.navigation.NavigationCommand import com.wire.android.navigation.Navigator import com.wire.android.ui.common.button.WirePrimaryButton +import com.wire.android.ui.common.dialogs.PermissionPermanentlyDeniedDialog import com.wire.android.ui.common.spacers.VerticalSpace import com.wire.android.ui.common.topappbar.WireCenterAlignedTopAppBar import com.wire.android.ui.destinations.HomeScreenDestination @@ -51,6 +52,7 @@ import com.wire.android.ui.home.settings.backup.dialog.restore.RestoreBackupDial import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.theme.wireDimensions import com.wire.android.ui.theme.wireTypography +import com.wire.android.util.permission.PermissionDenialType @RootNavGraph @Destination @@ -149,6 +151,14 @@ fun BackupAndRestoreContent( onCancelCreateBackup = { backupAndRestoreStateHolder.dismissDialog() onCancelBackupCreation() + }, + onPermissionPermanentlyDenied = { + if (it == PermissionDenialType.File) { + backupAndRestoreStateHolder.showPermissionPermanentlyDeniedDialog( + R.string.app_permission_dialog_title, + R.string.save_file_permission_dialog_description + ) + } } ) } @@ -168,6 +178,11 @@ fun BackupAndRestoreContent( BackupAndRestoreDialog.None -> {} } + + PermissionPermanentlyDeniedDialog( + dialogState = backupAndRestoreStateHolder.permissionPermanentlyDeniedDialogState, + hideDialog = backupAndRestoreStateHolder::hidePermissionPermanentlyDeniedDialog + ) } @Preview diff --git a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/dialog/create/CreateBackupDialogFlow.kt b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/dialog/create/CreateBackupDialogFlow.kt index de40c96d78f..87387ba1ed1 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/dialog/create/CreateBackupDialogFlow.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/dialog/create/CreateBackupDialogFlow.kt @@ -28,6 +28,7 @@ import com.wire.android.R import com.wire.android.ui.home.settings.backup.BackupAndRestoreState import com.wire.android.ui.home.settings.backup.BackupCreationProgress import com.wire.android.ui.home.settings.backup.dialog.common.FailureDialog +import com.wire.android.util.permission.PermissionDenialType @Composable fun CreateBackupDialogFlow( @@ -36,7 +37,8 @@ fun CreateBackupDialogFlow( onCreateBackup: (String) -> Unit, onSaveBackup: (Uri) -> Unit, onShareBackup: () -> Unit, - onCancelCreateBackup: () -> Unit + onCancelCreateBackup: () -> Unit, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit ) { val backupDialogStateHolder = rememberBackUpDialogState() @@ -61,7 +63,8 @@ fun CreateBackupDialogFlow( backupDialogStateHolder = backupDialogStateHolder, onSaveBackup = onSaveBackup, onShareBackup = onShareBackup, - onCancelCreateBackup = onCancelCreateBackup + onCancelCreateBackup = onCancelCreateBackup, + onPermissionPermanentlyDenied = onPermissionPermanentlyDenied ) } @@ -86,7 +89,8 @@ private fun CreateBackupStep( backupDialogStateHolder: CreateBackupDialogStateHolder, onSaveBackup: (Uri) -> Unit, onShareBackup: () -> Unit, - onCancelCreateBackup: () -> Unit + onCancelCreateBackup: () -> Unit, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit, ) { with(backupDialogStateHolder) { LaunchedEffect(backUpAndRestoreState.backupCreationProgress) { @@ -103,7 +107,8 @@ private fun CreateBackupStep( onSaveBackup = onSaveBackup, onShareBackup = onShareBackup, backupFileName = backupFileName, - onDismissDialog = onCancelCreateBackup + onDismissDialog = onCancelCreateBackup, + onPermissionPermanentlyDenied = onPermissionPermanentlyDenied ) } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/dialog/create/CreateBackupDialogs.kt b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/dialog/create/CreateBackupDialogs.kt index e2971a247e0..9ec742e015c 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/dialog/create/CreateBackupDialogs.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/dialog/create/CreateBackupDialogs.kt @@ -47,6 +47,7 @@ import com.wire.android.ui.common.textfield.WirePasswordTextField import com.wire.android.ui.common.textfield.WireTextFieldState import com.wire.android.ui.common.wireDialogPropertiesBuilder import com.wire.android.ui.theme.wireTypography +import com.wire.android.util.permission.PermissionDenialType import com.wire.android.util.permission.rememberCreateFileFlow import com.wire.kalium.logic.feature.auth.ValidatePasswordResult import java.util.Locale @@ -97,6 +98,7 @@ fun CreateBackupDialog( isBackupCreationCompleted: Boolean, createBackupProgress: Float, backupFileName: String, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit, onSaveBackup: (Uri) -> Unit, onShareBackup: () -> Unit, onDismissDialog: () -> Unit @@ -108,6 +110,7 @@ fun CreateBackupDialog( onDismissDialog() }, onPermissionDenied = { /* do nothing */ }, + onPermissionPermanentlyDenied = onPermissionPermanentlyDenied, fileName = backupFileName ) WireDialog( diff --git a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/dialog/restore/RestoreBackupDialogs.kt b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/dialog/restore/RestoreBackupDialogs.kt index 58fb631bb94..a7bf314e07e 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/dialog/restore/RestoreBackupDialogs.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/dialog/restore/RestoreBackupDialogs.kt @@ -53,7 +53,9 @@ fun PickRestoreFileDialog( onChooseBackupFile: (Uri) -> Unit, onCancelBackupRestore: () -> Unit ) { - val fileFlow = FileBrowserFlow(onChooseBackupFile) + val fileFlow = FileBrowserFlow(onChooseBackupFile, { + + }) WireDialog( title = stringResource(R.string.backup_dialog_restore_backup_title), diff --git a/app/src/main/kotlin/com/wire/android/ui/sharing/ImportMediaScreen.kt b/app/src/main/kotlin/com/wire/android/ui/sharing/ImportMediaScreen.kt index 97eedec1540..82ed7a227c5 100644 --- a/app/src/main/kotlin/com/wire/android/ui/sharing/ImportMediaScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/sharing/ImportMediaScreen.kt @@ -435,7 +435,7 @@ private fun ImportMediaContent( onEditConversation = {}, onOpenUserProfile = {}, onJoinCall = {}, - onPermanentPermissionDecline = {} + onPermissionPermanentlyDenied = {} ) } BackHandler(enabled = searchBarState.isSearchActive) { diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPicker.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPicker.kt index b62900d1761..6b80675d5c3 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPicker.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPicker.kt @@ -54,6 +54,7 @@ import com.wire.android.ui.common.bottomsheet.MenuModalSheetLayout import com.wire.android.ui.common.button.WireButtonState import com.wire.android.ui.common.button.WirePrimaryButton import com.wire.android.ui.common.button.WireSecondaryButton +import com.wire.android.ui.common.dialogs.PermissionPermanentlyDeniedDialog import com.wire.android.ui.common.dimensions import com.wire.android.ui.common.imagepreview.BulletHoleImagePreview import com.wire.android.ui.common.scaffold.WireScaffold @@ -61,6 +62,7 @@ import com.wire.android.ui.common.topappbar.WireCenterAlignedTopAppBar import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.userprofile.avatarpicker.AvatarPickerViewModel.PictureState import com.wire.android.util.ImageUtil +import com.wire.android.util.permission.PermissionDenialType import com.wire.android.util.resampleImageAndCopyToTempPath import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -89,7 +91,22 @@ fun AvatarPickerScreen( onPictureTaken = { onNewAvatarPicked(targetAvatarUri, targetAvatarPath, scope, context, viewModel) }, - targetPictureFileUri = targetAvatarUri + targetPictureFileUri = targetAvatarUri, + onPermissionPermanentlyDenied = { + val (title, description) = when(it) { + PermissionDenialType.Gallery -> { + R.string.app_permission_dialog_title to R.string.open_gallery_permission_dialog_description + } + PermissionDenialType.TakePicture -> { + R.string.app_permission_dialog_title to R.string.take_picture_permission_dialog_description + } + else -> { 0 to 0 } + } + viewModel.showPermissionPermanentlyDeniedDialog( + title = title, + description = description + ) + } ) AvatarPickerContent( @@ -103,6 +120,11 @@ fun AvatarPickerScreen( } } ) + + PermissionPermanentlyDeniedDialog( + dialogState = viewModel.permissionPermanentlyDeniedDialogState, + hideDialog = viewModel::hidePermissionPermanentlyDeniedDialog + ) } // TODO: Mateusz: I think we should refactor this, it takes some values from the ViewModel, part of the logic is executed inside diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPickerState.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPickerState.kt index fcd41f4efc5..eb1d25afe0f 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPickerState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPickerState.kt @@ -31,6 +31,7 @@ import com.wire.android.ui.common.bottomsheet.rememberWireModalSheetState import com.wire.android.ui.common.imagepreview.AvatarPickerFlow import com.wire.android.ui.common.imagepreview.rememberPickPictureState import com.wire.android.ui.common.snackbar.LocalSnackbarHostState +import com.wire.android.util.permission.PermissionDenialType import com.wire.android.util.ui.UIText import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -41,13 +42,19 @@ fun rememberAvatarPickerState( coroutineScope: CoroutineScope = rememberCoroutineScope(), modalBottomSheetState: WireModalSheetState = rememberWireModalSheetState(), onImageSelected: (Uri) -> Unit, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit, onPictureTaken: () -> Unit, targetPictureFileUri: Uri ): AvatarPickerState { val context = LocalContext.current val snackbarHostState = LocalSnackbarHostState.current - val avatarPickerFlow: AvatarPickerFlow = rememberPickPictureState(onImageSelected, onPictureTaken, targetPictureFileUri) + val avatarPickerFlow: AvatarPickerFlow = rememberPickPictureState( + onImageSelected, + onPictureTaken, + targetPictureFileUri, + onPermissionPermanentlyDenied + ) return remember(avatarPickerFlow) { AvatarPickerState( diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPickerViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPickerViewModel.kt index 16eab63472e..2e416c28577 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPickerViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPickerViewModel.kt @@ -31,6 +31,7 @@ import com.wire.android.R import com.wire.android.appLogger import com.wire.android.datastore.UserDataStore import com.wire.android.model.SnackBarMessage +import com.wire.android.ui.home.conversations.PermissionPermanentlyDeniedDialogState import com.wire.android.util.AvatarImageManager import com.wire.android.util.dispatchers.DispatcherProvider import com.wire.android.util.toByteArray @@ -77,6 +78,10 @@ class AvatarPickerViewModel @Inject constructor( private lateinit var currentAvatarUri: Uri + var permissionPermanentlyDeniedDialogState: PermissionPermanentlyDeniedDialogState by mutableStateOf( + PermissionPermanentlyDeniedDialogState.Hidden + ) + init { loadInitialAvatarState() } @@ -130,6 +135,17 @@ class AvatarPickerViewModel @Inject constructor( _infoMessage.emit(type.uiText) } + fun showPermissionPermanentlyDeniedDialog(title: Int, description: Int) { + permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Visible( + title = title, + description = description + ) + } + + fun hidePermissionPermanentlyDeniedDialog() { + permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Hidden + } + @Stable sealed class PictureState(open val avatarUri: Uri) { data class Uploading(override val avatarUri: Uri) : PictureState(avatarUri) diff --git a/app/src/main/kotlin/com/wire/android/util/permission/CallingCameraRequestFlow.kt b/app/src/main/kotlin/com/wire/android/util/permission/CallingCameraRequestFlow.kt new file mode 100644 index 00000000000..9df76296ea8 --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/util/permission/CallingCameraRequestFlow.kt @@ -0,0 +1,73 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * 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 http://www.gnu.org/licenses/. + */ +package com.wire.android.util.permission + +import android.Manifest +import android.content.Context +import androidx.activity.compose.ManagedActivityResultLauncher +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import com.wire.android.util.extension.checkPermission +import com.wire.android.util.extension.getActivity + +@Composable +fun rememberCallingCameraRequestFlow( + onPermissionGranted: () -> Unit, + onPermissionDenied: () -> Unit, + onPermissionPermanentlyDenied: () -> Unit +): CallingCameraRequestFlow { + val context = LocalContext.current + + val requestPermissionLauncher: ManagedActivityResultLauncher = + rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> + if (isGranted) { + onPermissionGranted() + } else { + context.getActivity()?.let { + if (it.shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) { + onPermissionDenied() + } else { + onPermissionPermanentlyDenied() + } + } + } + } + + return remember { + CallingCameraRequestFlow(context, onPermissionGranted, requestPermissionLauncher) + } +} + +class CallingCameraRequestFlow( + private val context: Context, + private val permissionGranted: () -> Unit, + private val audioRecordPermissionLauncher: ManagedActivityResultLauncher +) { + fun launch() { + val cameraPermissionEnabled = context.checkPermission(Manifest.permission.CAMERA) + + if (cameraPermissionEnabled) { + permissionGranted() + } else { + audioRecordPermissionLauncher.launch(Manifest.permission.CAMERA) + } + } +} diff --git a/app/src/main/kotlin/com/wire/android/util/permission/CallingRecordAudioRequestFlow.kt b/app/src/main/kotlin/com/wire/android/util/permission/CallingRecordAudioRequestFlow.kt index a76e660d4b6..198f541a165 100644 --- a/app/src/main/kotlin/com/wire/android/util/permission/CallingRecordAudioRequestFlow.kt +++ b/app/src/main/kotlin/com/wire/android/util/permission/CallingRecordAudioRequestFlow.kt @@ -32,7 +32,7 @@ import com.wire.android.util.extension.getActivity fun rememberCallingRecordAudioRequestFlow( onAudioPermissionGranted: () -> Unit, onAudioPermissionDenied: () -> Unit, - onAudioPermissionPermanentlyDenied: () -> Unit, + onAudioPermissionPermanentlyDenied: () -> Unit ): CallingAudioRequestFlow { val context = LocalContext.current @@ -65,10 +65,6 @@ class CallingAudioRequestFlow( val audioPermissionEnabled = context.checkPermission(android.Manifest.permission.RECORD_AUDIO) - val neededPermissions = mutableListOf( - android.Manifest.permission.RECORD_AUDIO - ) - if (audioPermissionEnabled) { permissionGranted() } else { diff --git a/app/src/main/kotlin/com/wire/android/util/permission/CaptureVideoRequestFlow.kt b/app/src/main/kotlin/com/wire/android/util/permission/CaptureVideoRequestFlow.kt index 7d65179eb26..0714e0d6699 100644 --- a/app/src/main/kotlin/com/wire/android/util/permission/CaptureVideoRequestFlow.kt +++ b/app/src/main/kotlin/com/wire/android/util/permission/CaptureVideoRequestFlow.kt @@ -19,45 +19,79 @@ package com.wire.android.util.permission import android.net.Uri +import android.os.Build import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext +import com.wire.android.util.extension.getActivity /** * Flow that will launch the camera to record a video. * This will handle the permissions request in case there is no permission granted for the camera. * * @param onVideoRecorded action that will be executed for Camera's [ActivityResultContract] - * @param onPermissionDenied action to be executed when the permissions is denied + * @param onPermissionDenied action to be executed when the permission is denied + * @param onPermissionPermanentlyDenied action to be executed when the permission is permanently denied * @param targetVideoFileUri target file where the media will be stored */ @Composable fun rememberCaptureVideoFlow( onVideoRecorded: (Boolean) -> Unit, onPermissionDenied: () -> Unit, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit, targetVideoFileUri: Uri ): UseCameraAndWriteStorageRequestFlow { val context = LocalContext.current - val captureVideoLauncher: ManagedActivityResultLauncher = rememberLauncherForActivityResult( - ActivityResultContracts.CaptureVideo() - ) { hasCapturedVideo -> - onVideoRecorded(hasCapturedVideo) - } + val captureVideoLauncher: ManagedActivityResultLauncher = + rememberLauncherForActivityResult( + ActivityResultContracts.CaptureVideo() + ) { hasCapturedVideo -> + if (hasCapturedVideo) { + onVideoRecorded(true) + } + } val requestVideoPermissionLauncher: ManagedActivityResultLauncher, Map> = rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { areGranted -> if (areGranted.all { it.value }) { captureVideoLauncher.launch(targetVideoFileUri) } else { - onPermissionDenied() + context.getActivity()?.let { + it.checkCameraWithStoragePermission(onPermissionDenied) { + onPermissionPermanentlyDenied( + PermissionDenialType.CaptureVideo + ) + } + } } } return remember { - UseCameraAndWriteStorageRequestFlow(context, targetVideoFileUri, captureVideoLauncher, requestVideoPermissionLauncher) + UseCameraAndWriteStorageRequestFlow( + context, + targetVideoFileUri, + captureVideoLauncher, + requestVideoPermissionLauncher + ) + } +} + +fun AppCompatActivity.checkCameraWithStoragePermission( + onPermissionDenied: () -> Unit, + onPermissionPermanentlyDenied: () -> Unit +) { + if (shouldShowRequestPermissionRationale(android.Manifest.permission.CAMERA) || + (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && shouldShowRequestPermissionRationale( + android.Manifest.permission.WRITE_EXTERNAL_STORAGE + )) + ) { + onPermissionDenied() + } else { + onPermissionPermanentlyDenied() } } diff --git a/app/src/main/kotlin/com/wire/android/util/permission/CreateFileRequestFlow.kt b/app/src/main/kotlin/com/wire/android/util/permission/CreateFileRequestFlow.kt index 672412861d2..6a08428b58b 100644 --- a/app/src/main/kotlin/com/wire/android/util/permission/CreateFileRequestFlow.kt +++ b/app/src/main/kotlin/com/wire/android/util/permission/CreateFileRequestFlow.kt @@ -18,13 +18,17 @@ package com.wire.android.util.permission +import android.Manifest import android.net.Uri +import android.os.Build import androidx.activity.compose.ManagedActivityResultLauncher import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext +import com.wire.android.util.extension.getActivity /** * Flow that will launch file browser to select a path where new file has to be created. @@ -37,6 +41,7 @@ import androidx.compose.ui.platform.LocalContext fun rememberCreateFileFlow( onFileCreated: (Uri) -> Unit, onPermissionDenied: () -> Unit, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit, fileName: String, fileMimeType: String = "*/*" ): WriteStorageRequestFlow { @@ -54,7 +59,13 @@ fun rememberCreateFileFlow( if (isGranted) { actionIfGranted() } else { - onPermissionDenied() + context.getActivity()?.let { + it.checkWriteStoragePermission(onPermissionDenied) { + onPermissionPermanentlyDenied( + PermissionDenialType.File + ) + } + } } } @@ -62,3 +73,16 @@ fun rememberCreateFileFlow( WriteStorageRequestFlow(context, actionIfGranted, requestPermissionLauncher) } } + +fun AppCompatActivity.checkWriteStoragePermission( + onPermissionDenied: () -> Unit, + onPermissionPermanentlyDenied: () -> Unit +) { + if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) && + shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE) + ) { + onPermissionDenied() + } else { + onPermissionPermanentlyDenied() + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/wire/android/util/permission/CurrentLocationRequestFlow.kt b/app/src/main/kotlin/com/wire/android/util/permission/CurrentLocationRequestFlow.kt index a8e89516f0a..6a0cfa7bcdc 100644 --- a/app/src/main/kotlin/com/wire/android/util/permission/CurrentLocationRequestFlow.kt +++ b/app/src/main/kotlin/com/wire/android/util/permission/CurrentLocationRequestFlow.kt @@ -26,11 +26,13 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext import com.wire.android.util.extension.checkPermission +import com.wire.android.util.extension.getActivity @Composable fun rememberCurrentLocationFlow( onPermissionAllowed: () -> Unit, - onPermissionDenied: () -> Unit + onPermissionDenied: () -> Unit, + onPermissionPermanentlyDenied: () -> Unit ): CurrentLocationRequestFlow { val context = LocalContext.current @@ -40,7 +42,15 @@ fun rememberCurrentLocationFlow( if (allPermissionGranted) { onPermissionAllowed() } else { - onPermissionDenied() + context.getActivity()?.let { + if (it.shouldShowRequestPermissionRationale(android.Manifest.permission.ACCESS_FINE_LOCATION) || + it.shouldShowRequestPermissionRationale(android.Manifest.permission.ACCESS_COARSE_LOCATION) + ) { + onPermissionDenied() + } else { + onPermissionPermanentlyDenied() + } + } } } diff --git a/app/src/main/kotlin/com/wire/android/util/permission/OpenFileBrowserRequestFlow.kt b/app/src/main/kotlin/com/wire/android/util/permission/OpenFileBrowserRequestFlow.kt index 17d408b9f7d..ae28b4068ac 100644 --- a/app/src/main/kotlin/com/wire/android/util/permission/OpenFileBrowserRequestFlow.kt +++ b/app/src/main/kotlin/com/wire/android/util/permission/OpenFileBrowserRequestFlow.kt @@ -25,6 +25,7 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext +import com.wire.android.util.extension.getActivity /** * Flow that will launch file browser to select a file. @@ -36,27 +37,38 @@ import androidx.compose.ui.platform.LocalContext @Composable fun rememberOpenFileBrowserFlow( onFileBrowserItemPicked: (Uri) -> Unit, - onPermissionDenied: () -> Unit + onPermissionDenied: () -> Unit, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit ): UseStorageRequestFlow { val context = LocalContext.current - val openFileBrowserLauncher: ManagedActivityResultLauncher = rememberLauncherForActivityResult( - ActivityResultContracts.GetContent() - ) { onChosenFileUri -> - onChosenFileUri?.let { onFileBrowserItemPicked(it) } - } + val openFileBrowserLauncher: ManagedActivityResultLauncher = + rememberLauncherForActivityResult( + ActivityResultContracts.GetContent() + ) { onChosenFileUri -> + onChosenFileUri?.let { onFileBrowserItemPicked(it) } + } val requestPermissionLauncher: ManagedActivityResultLauncher = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> if (isGranted) { openFileBrowserLauncher.launch(MIME_TYPE) } else { - onPermissionDenied() + context.getActivity()?.let { + it.checkStoragePermission(onPermissionDenied) { + onPermissionPermanentlyDenied(PermissionDenialType.File) + } + } } } return remember { - UseStorageRequestFlow(MIME_TYPE, context, openFileBrowserLauncher, requestPermissionLauncher) + UseStorageRequestFlow( + MIME_TYPE, + context, + openFileBrowserLauncher, + requestPermissionLauncher + ) } } diff --git a/app/src/main/kotlin/com/wire/android/util/permission/OpenGalleryRequestFlow.kt b/app/src/main/kotlin/com/wire/android/util/permission/OpenGalleryRequestFlow.kt index 9b43dab7fbe..017d464ab58 100644 --- a/app/src/main/kotlin/com/wire/android/util/permission/OpenGalleryRequestFlow.kt +++ b/app/src/main/kotlin/com/wire/android/util/permission/OpenGalleryRequestFlow.kt @@ -25,6 +25,7 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext +import com.wire.android.util.extension.getActivity /** * Flow that will launch gallery browser to select a picture. @@ -36,21 +37,27 @@ import androidx.compose.ui.platform.LocalContext @Composable fun rememberOpenGalleryFlow( onGalleryItemPicked: (Uri) -> Unit, - onPermissionDenied: () -> Unit + onPermissionDenied: () -> Unit, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit, ): UseStorageRequestFlow { val context = LocalContext.current - val openGalleryLauncher: ManagedActivityResultLauncher = rememberLauncherForActivityResult( - ActivityResultContracts.GetContent() - ) { onChosenPictureUri -> - onChosenPictureUri?.let { onGalleryItemPicked(it) } - } + val openGalleryLauncher: ManagedActivityResultLauncher = + rememberLauncherForActivityResult( + ActivityResultContracts.GetContent() + ) { onChosenPictureUri -> + onChosenPictureUri?.let { onGalleryItemPicked(it) } + } val requestPermissionLauncher: ManagedActivityResultLauncher = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> if (isGranted) { openGalleryLauncher.launch(MIME_TYPE) } else { - onPermissionDenied() + context.getActivity()?.let { + it.checkStoragePermission(onPermissionDenied) { + onPermissionPermanentlyDenied(PermissionDenialType.Gallery) + } + } } } diff --git a/app/src/main/kotlin/com/wire/android/util/permission/PermissionDenialType.kt b/app/src/main/kotlin/com/wire/android/util/permission/PermissionDenialType.kt new file mode 100644 index 00000000000..8bf366f765f --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/util/permission/PermissionDenialType.kt @@ -0,0 +1,27 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * 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 http://www.gnu.org/licenses/. + */ +package com.wire.android.util.permission + +sealed class PermissionDenialType { + data object CaptureVideo : PermissionDenialType() + data object TakePicture : PermissionDenialType() + data object CallingCamera : PermissionDenialType() + data object CallingMicrophone : PermissionDenialType() + data object File : PermissionDenialType() + data object Gallery : PermissionDenialType() +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/wire/android/util/permission/RecordAudioRequestFlow.kt b/app/src/main/kotlin/com/wire/android/util/permission/RecordAudioRequestFlow.kt index e883b358b3b..835ebb289ea 100644 --- a/app/src/main/kotlin/com/wire/android/util/permission/RecordAudioRequestFlow.kt +++ b/app/src/main/kotlin/com/wire/android/util/permission/RecordAudioRequestFlow.kt @@ -27,11 +27,13 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext import com.wire.android.util.extension.checkPermission +import com.wire.android.util.extension.getActivity @Composable fun rememberRecordAudioRequestFlow( onPermissionAllowed: () -> Unit, - onPermissionDenied: () -> Unit + onPermissionDenied: () -> Unit, + onAudioPermissionPermanentlyDenied: () -> Unit ): RecordAudioRequestFlow { val context = LocalContext.current @@ -41,7 +43,17 @@ fun rememberRecordAudioRequestFlow( if (allPermissionGranted) { onPermissionAllowed() } else { - onPermissionDenied() + context.getActivity()?.let { + if (it.shouldShowRequestPermissionRationale(android.Manifest.permission.RECORD_AUDIO) || + (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU && it.shouldShowRequestPermissionRationale( + android.Manifest.permission.READ_EXTERNAL_STORAGE + )) + ) { + onPermissionDenied() + } else { + onAudioPermissionPermanentlyDenied() + } + } } } diff --git a/app/src/main/kotlin/com/wire/android/util/permission/TakePictureRequestFlow.kt b/app/src/main/kotlin/com/wire/android/util/permission/TakePictureRequestFlow.kt index cb9501b27ac..00fbf032b4b 100644 --- a/app/src/main/kotlin/com/wire/android/util/permission/TakePictureRequestFlow.kt +++ b/app/src/main/kotlin/com/wire/android/util/permission/TakePictureRequestFlow.kt @@ -25,6 +25,7 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext +import com.wire.android.util.extension.getActivity /** * Flow that will launch the camera for taking a photo. @@ -38,6 +39,7 @@ import androidx.compose.ui.platform.LocalContext fun rememberTakePictureFlow( onPictureTaken: (Boolean) -> Unit, onPermissionDenied: () -> Unit, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit, targetPictureFileUri: Uri ): UseCameraRequestFlow { val context = LocalContext.current @@ -53,7 +55,13 @@ fun rememberTakePictureFlow( if (isGranted) { takePictureLauncher.launch(targetPictureFileUri) } else { - onPermissionDenied() + context.getActivity()?.let { + it.checkCameraWithStoragePermission(onPermissionDenied) { + onPermissionPermanentlyDenied( + PermissionDenialType.TakePicture + ) + } + } } } diff --git a/app/src/main/kotlin/com/wire/android/util/permission/UseStorageRequestFlow.kt b/app/src/main/kotlin/com/wire/android/util/permission/UseStorageRequestFlow.kt index e4033093003..c5b9696530d 100644 --- a/app/src/main/kotlin/com/wire/android/util/permission/UseStorageRequestFlow.kt +++ b/app/src/main/kotlin/com/wire/android/util/permission/UseStorageRequestFlow.kt @@ -23,6 +23,7 @@ import android.content.Context import android.net.Uri import android.os.Build import androidx.activity.compose.ManagedActivityResultLauncher +import androidx.appcompat.app.AppCompatActivity import com.wire.android.util.extension.checkPermission class UseStorageRequestFlow( @@ -43,3 +44,16 @@ class UseStorageRequestFlow( } } } + +fun AppCompatActivity.checkStoragePermission( + onPermissionDenied: () -> Unit, + onPermissionPermanentlyDenied: () -> Unit +) { + if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) && + shouldShowRequestPermissionRationale(READ_EXTERNAL_STORAGE) + ) { + onPermissionDenied() + } else { + onPermissionPermanentlyDenied() + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/wire/android/util/permission/WriteStorageRequestFlow.kt b/app/src/main/kotlin/com/wire/android/util/permission/WriteStorageRequestFlow.kt index 0281d44a522..1c141a3e246 100644 --- a/app/src/main/kotlin/com/wire/android/util/permission/WriteStorageRequestFlow.kt +++ b/app/src/main/kotlin/com/wire/android/util/permission/WriteStorageRequestFlow.kt @@ -28,6 +28,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext import com.wire.android.util.extension.checkPermission +import com.wire.android.util.extension.getActivity class WriteStorageRequestFlow( private val context: Context, @@ -48,12 +49,30 @@ class WriteStorageRequestFlow( } @Composable -fun rememberWriteStorageRequestFlow(onGranted: () -> Unit, onDenied: () -> Unit): WriteStorageRequestFlow { +fun rememberWriteStorageRequestFlow( + onGranted: () -> Unit, + onPermissionDenied: () -> Unit, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit, +): WriteStorageRequestFlow { val context = LocalContext.current val requestWriteStoragePermissionLauncher: ManagedActivityResultLauncher = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> if (isGranted) onGranted() - else onDenied() + else { + context.getActivity()?.let { + it.checkWriteStoragePermission(onPermissionDenied) { + onPermissionPermanentlyDenied( + PermissionDenialType.File + ) + } + } + } } - return remember { WriteStorageRequestFlow(context, onGranted, requestWriteStoragePermissionLauncher) } + return remember { + WriteStorageRequestFlow( + context, + onGranted, + requestWriteStoragePermissionLauncher + ) + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 98ca13e53a6..4c59905813b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1291,8 +1291,6 @@ Recording Stopped File size for audio messages is limited to %1$d MB. You can’t record an audio message during a call. - App permission - To make a call, allow Wire to access your microphone in your device settings. Not Now sent an interactive message Conversation Password @@ -1368,6 +1366,14 @@ App permissions Settings Not Now + To make a call, allow Wire to access your microphone in your device settings. + To record a video, allow Wire to access your Camera in your device settings. + To take a picture, allow Wire to access your Camera in your device settings. + To open Gallery, allow Wire to access your Storage in your device settings. + To attach a file, allow Wire to access your Storage in your device settings. + To save an asset, allow Wire to access your Storage in your device settings. + To save a file, allow Wire to access your Storage in your device settings. + To turn camera on, allow Wire to access your Camera in your device settings. Share Location diff --git a/app/src/test/kotlin/com/wire/android/ui/calling/incoming/IncomingCallViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/calling/incoming/IncomingCallViewModelTest.kt index 9508380a479..7b6ae3ab5db 100644 --- a/app/src/test/kotlin/com/wire/android/ui/calling/incoming/IncomingCallViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/calling/incoming/IncomingCallViewModelTest.kt @@ -187,26 +187,6 @@ class IncomingCallViewModelTest { assertEquals(false, viewModel.incomingCallState.shouldShowJoinCallAnywayDialog) } - @Test - fun `given permission dialog default state is false, when calling showPermissionDialog, then update the state to true`() = runTest { - val (_, viewModel) = Arrangement() - .arrange() - - viewModel.showPermissionDialog() - - assertEquals(true, viewModel.incomingCallState.shouldShowPermissionDialog) - } - - @Test - fun `given default permission dialog state, when calling dismissPermissionDialog, then update the state to false`() = runTest { - val (_, viewModel) = Arrangement() - .arrange() - - viewModel.dismissPermissionDialog() - - assertEquals(false, viewModel.incomingCallState.shouldShowPermissionDialog) - } - companion object { val dummyConversationId = ConversationId("some-dummy-value", "some.dummy.domain") diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/call/ConversationCallViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/call/ConversationCallViewModelTest.kt index 1f97c96cfc4..68f0819e0fb 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/call/ConversationCallViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/call/ConversationCallViewModelTest.kt @@ -162,24 +162,4 @@ class ConversationCallViewModelTest { coVerify(exactly = 1) { endCall(any()) } } - - @Test - fun `given permission dialog default state is false, when calling showPermissionDialog, then update the state to true`() = runTest { - conversationCallViewModel.conversationCallViewState = - conversationCallViewModel.conversationCallViewState.copy(shouldShowCallingPermissionDialog = false) - - conversationCallViewModel.showCallingPermissionDialog() - - assertEquals(true, conversationCallViewModel.conversationCallViewState.shouldShowCallingPermissionDialog) - } - - @Test - fun `given default permission dialog state, when calling dismissPermissionDialog, then update the state to false`() = runTest { - conversationCallViewModel.conversationCallViewState = - conversationCallViewModel.conversationCallViewState.copy(shouldShowCallingPermissionDialog = true) - - conversationCallViewModel.dismissCallingPermissionDialog() - - assertEquals(false, conversationCallViewModel.conversationCallViewState.shouldShowCallingPermissionDialog) - } } diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModelTest.kt index c90a61a0207..a0977037769 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModelTest.kt @@ -310,26 +310,6 @@ class ConversationListViewModelTest { coVerify(exactly = 1) { endCall(any()) } } - @Test - fun `given permission dialog default state is false, when calling showPermissionDialog, then update the state to true`() = runTest { - conversationListViewModel.conversationListCallState = - conversationListViewModel.conversationListCallState.copy(shouldShowCallingPermissionDialog = false) - - conversationListViewModel.showCallingPermissionDialog() - - assertEquals(true, conversationListViewModel.conversationListCallState.shouldShowCallingPermissionDialog) - } - - @Test - fun `given default permission dialog state, when calling dismissPermissionDialog, then update the state to false`() = runTest { - conversationListViewModel.conversationListCallState = - conversationListViewModel.conversationListCallState.copy(shouldShowCallingPermissionDialog = true) - - conversationListViewModel.dismissCallingPermissionDialog() - - assertEquals(false, conversationListViewModel.conversationListCallState.shouldShowCallingPermissionDialog) - } - @Test fun `given a valid conversation state, when archiving it correctly, then the right success message is shown`() = runTest { val isArchiving = true From d140257d723c8a13fbe3eacabae2bfe5e2b778c2 Mon Sep 17 00:00:00 2001 From: Oussama Hassine Date: Thu, 1 Feb 2024 17:31:42 +0100 Subject: [PATCH 2/8] chore: detekt --- .../MicrophonePermissionDeniedDialog.kt | 50 ------------------- .../ui/calling/controlbuttons/CameraButton.kt | 1 - .../home/conversations/ConversationScreen.kt | 2 +- .../conversationslist/ConversationRouter.kt | 1 - .../home/messagecomposer/AdditionalOptions.kt | 1 - .../home/messagecomposer/AttachmentOptions.kt | 16 +++--- .../location/LocationPickerComponent.kt | 2 +- .../backup/BackUpAndRestoreStateHolder.kt | 2 - .../settings/backup/BackupAndRestoreScreen.kt | 12 ++++- .../dialog/restore/RestoreBackupDialogFlow.kt | 13 +++-- .../dialog/restore/RestoreBackupDialogs.kt | 8 +-- .../userprofile/avatarpicker/AvatarPicker.kt | 2 +- .../util/permission/CreateFileRequestFlow.kt | 4 +- .../permission/OpenFileBrowserRequestFlow.kt | 2 +- .../util/permission/PermissionDenialType.kt | 5 +- .../util/permission/UseStorageRequestFlow.kt | 2 +- .../permission/WriteStorageRequestFlow.kt | 2 +- app/src/main/res/values/strings.xml | 1 + .../call/ConversationCallViewModelTest.kt | 1 - 19 files changed, 43 insertions(+), 84 deletions(-) delete mode 100644 app/src/main/kotlin/com/wire/android/ui/calling/common/MicrophonePermissionDeniedDialog.kt diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/common/MicrophonePermissionDeniedDialog.kt b/app/src/main/kotlin/com/wire/android/ui/calling/common/MicrophonePermissionDeniedDialog.kt deleted file mode 100644 index 2cb2d38f570..00000000000 --- a/app/src/main/kotlin/com/wire/android/ui/calling/common/MicrophonePermissionDeniedDialog.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Wire - * Copyright (C) 2024 Wire Swiss GmbH - * - * 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 http://www.gnu.org/licenses/. - */ -package com.wire.android.ui.calling.common - -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import com.wire.android.R -import com.wire.android.ui.common.WireDialog -import com.wire.android.ui.common.WireDialogButtonProperties -import com.wire.android.ui.common.WireDialogButtonType -import com.wire.android.ui.common.button.WireButtonState -import com.wire.android.util.permission.PermissionsDeniedRequestDialog -import com.wire.android.util.ui.PreviewMultipleThemes - -@Composable -fun MicrophonePermissionDeniedDialog( - shouldShow: Boolean, - onDismiss: () -> Unit -) { - if (shouldShow) { - PermissionsDeniedRequestDialog( - title = R.string.app_permission_dialog_title, - body = R.string.call_permission_dialog_description, - onDismiss = onDismiss - ) - } -} -@PreviewMultipleThemes -@Composable -fun PreviewMicrophonePermissionDeniedDialog() { - MicrophonePermissionDeniedDialog( - shouldShow = true, - onDismiss = {} - ) -} diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/controlbuttons/CameraButton.kt b/app/src/main/kotlin/com/wire/android/ui/calling/controlbuttons/CameraButton.kt index 33c903fe45e..aec1317a0fa 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/controlbuttons/CameraButton.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/controlbuttons/CameraButton.kt @@ -98,7 +98,6 @@ private fun CameraPermissionCheckFlow( onPermissionPermanentlyDenied = onPermanentPermissionDecline ) - @Preview @Composable fun PreviewComposableCameraButton() { diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt index db89b099f8a..2e6c031f954 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt @@ -385,7 +385,7 @@ fun ConversationScreen( is PermissionDenialType.Gallery -> { R.string.app_permission_dialog_title to R.string.open_gallery_permission_dialog_description } - is PermissionDenialType.File -> { + is PermissionDenialType.ReadFile -> { R.string.app_permission_dialog_title to R.string.attach_file_permission_dialog_description } is PermissionDenialType.CallingMicrophone -> { diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationRouter.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationRouter.kt index 02b3cafb94e..da2455e6b21 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationRouter.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationRouter.kt @@ -59,7 +59,6 @@ import com.wire.android.ui.home.conversationslist.model.DialogState import com.wire.android.ui.home.conversationslist.model.GroupDialogState import com.wire.android.ui.home.conversationslist.model.isArchive import com.wire.android.ui.home.conversationslist.search.SearchConversationScreen -import com.wire.android.util.extension.openAppInfoScreen import com.wire.android.util.permission.PermissionDenialType import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.user.UserId diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/AdditionalOptions.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/AdditionalOptions.kt index 93c5e212dbb..e252740fc84 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/AdditionalOptions.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/AdditionalOptions.kt @@ -24,7 +24,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material3.Divider -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/AttachmentOptions.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/AttachmentOptions.kt index c9fe3c9fbf2..6148d7bafa4 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/AttachmentOptions.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/AttachmentOptions.kt @@ -155,12 +155,12 @@ private fun calculateGridParams( @Composable fun FileBrowserFlow( onFilePicked: (Uri) -> Unit, - onCaptureVideoPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit ): UseStorageRequestFlow { return rememberOpenFileBrowserFlow( onFileBrowserItemPicked = onFilePicked, onPermissionDenied = { /* Nothing to do */ }, - onPermissionPermanentlyDenied = onCaptureVideoPermissionPermanentlyDenied + onPermissionPermanentlyDenied = onPermissionPermanentlyDenied ) } @@ -205,7 +205,7 @@ private fun CaptureVideoFlow( ): UseCameraAndWriteStorageRequestFlow? { tempWritableVideoUri?.let { uri -> return rememberCaptureVideoFlow( - onVideoRecorded = { onVideoCaptured(uri)}, + onVideoRecorded = { onVideoCaptured(uri) }, targetVideoFileUri = uri, onPermissionDenied = { /** Nothing to do here when permission is denied once */ }, onPermissionPermanentlyDenied = onPermissionPermanentlyDenied @@ -222,25 +222,25 @@ private fun buildAttachmentOptionItems( onFilePicked: (UriAsset) -> Unit, onRecordAudioMessageClicked: () -> Unit, onLocationPickerClicked: () -> Unit, - onCaptureVideoPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit ): List { val fileFlow = FileBrowserFlow( remember { { onFilePicked(UriAsset(it, false)) } }, - onCaptureVideoPermissionPermanentlyDenied + onPermissionPermanentlyDenied ) val galleryFlow = GalleryFlow( remember { { onFilePicked(UriAsset(it, false)) } }, - onCaptureVideoPermissionPermanentlyDenied + onPermissionPermanentlyDenied ) val cameraFlow = TakePictureFlow( tempWritableImageUri, remember { { onFilePicked(UriAsset(it, false)) } }, - onCaptureVideoPermissionPermanentlyDenied + onPermissionPermanentlyDenied ) val captureVideoFlow = CaptureVideoFlow( tempWritableVideoUri, remember { { onFilePicked(UriAsset(it, true)) } }, - onCaptureVideoPermissionPermanentlyDenied + onPermissionPermanentlyDenied ) return buildList { diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerComponent.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerComponent.kt index 1adc9bc2084..90e83f717f2 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerComponent.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerComponent.kt @@ -75,7 +75,7 @@ import kotlinx.coroutines.launch fun LocationPickerComponent( onLocationPicked: (GeoLocatedAddress) -> Unit, onLocationClosed: () -> Unit, - viewModel : LocationPickerViewModel = hiltViewModel() + viewModel: LocationPickerViewModel = hiltViewModel() ) { val coroutineScope = rememberCoroutineScope() val sheetState = rememberDismissibleWireModalSheetState(initialValue = SheetValue.Expanded, onLocationClosed) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackUpAndRestoreStateHolder.kt b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackUpAndRestoreStateHolder.kt index 42566f14e38..dedbbe854d6 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackUpAndRestoreStateHolder.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackUpAndRestoreStateHolder.kt @@ -57,7 +57,6 @@ class BackUpAndRestoreStateHolder { fun hidePermissionPermanentlyDeniedDialog() { permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Hidden } - } @Composable @@ -72,4 +71,3 @@ sealed class BackupAndRestoreDialog { object CreateBackup : BackupAndRestoreDialog() object RestoreBackup : BackupAndRestoreDialog() } - diff --git a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackupAndRestoreScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackupAndRestoreScreen.kt index e9bd0cc5783..9644142600c 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackupAndRestoreScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackupAndRestoreScreen.kt @@ -153,7 +153,7 @@ fun BackupAndRestoreContent( onCancelBackupCreation() }, onPermissionPermanentlyDenied = { - if (it == PermissionDenialType.File) { + if (it == PermissionDenialType.WriteFile) { backupAndRestoreStateHolder.showPermissionPermanentlyDeniedDialog( R.string.app_permission_dialog_title, R.string.save_file_permission_dialog_description @@ -172,7 +172,15 @@ fun BackupAndRestoreContent( backupAndRestoreStateHolder.dismissDialog() onCancelBackupRestore() }, - onOpenConversations = onOpenConversations + onOpenConversations = onOpenConversations, + onPermissionPermanentlyDenied = { + if (it == PermissionDenialType.ReadFile) { + backupAndRestoreStateHolder.showPermissionPermanentlyDeniedDialog( + R.string.app_permission_dialog_title, + R.string.restore_backup_permission_dialog_description + ) + } + } ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/dialog/restore/RestoreBackupDialogFlow.kt b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/dialog/restore/RestoreBackupDialogFlow.kt index bf89e4b8ca3..83a3ef682c9 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/dialog/restore/RestoreBackupDialogFlow.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/dialog/restore/RestoreBackupDialogFlow.kt @@ -33,6 +33,7 @@ import com.wire.android.ui.home.settings.backup.BackupRestoreProgress import com.wire.android.ui.home.settings.backup.PasswordValidation import com.wire.android.ui.home.settings.backup.RestoreFileValidation import com.wire.android.ui.home.settings.backup.dialog.common.FailureDialog +import com.wire.android.util.permission.PermissionDenialType @Composable fun RestoreBackupDialogFlow( @@ -40,7 +41,8 @@ fun RestoreBackupDialogFlow( onChooseBackupFile: (Uri) -> Unit, onRestoreBackup: (String) -> Unit, onOpenConversations: () -> Unit, - onCancelBackupRestore: () -> Unit + onCancelBackupRestore: () -> Unit, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit ) { val restoreDialogStateHolder = rememberRestoreDialogState() @@ -51,7 +53,8 @@ fun RestoreBackupDialogFlow( backUpAndRestoreState = backUpAndRestoreState, restoreDialogStateHolder = restoreDialogStateHolder, onChooseBackupFile = onChooseBackupFile, - onCancelBackupRestore = onCancelBackupRestore + onCancelBackupRestore = onCancelBackupRestore, + onPermissionPermanentlyDenied = onPermissionPermanentlyDenied ) } @@ -93,7 +96,8 @@ private fun ChooseBackupFileStep( backUpAndRestoreState: BackupAndRestoreState, restoreDialogStateHolder: RestoreDialogStateHolder, onChooseBackupFile: (Uri) -> Unit, - onCancelBackupRestore: () -> Unit + onCancelBackupRestore: () -> Unit, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit ) { LaunchedEffect(backUpAndRestoreState.restoreFileValidation) { when (backUpAndRestoreState.restoreFileValidation) { @@ -121,7 +125,8 @@ private fun ChooseBackupFileStep( PickRestoreFileDialog( onChooseBackupFile = onChooseBackupFile, - onCancelBackupRestore = onCancelBackupRestore + onCancelBackupRestore = onCancelBackupRestore, + onPermissionPermanentlyDenied = onPermissionPermanentlyDenied ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/dialog/restore/RestoreBackupDialogs.kt b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/dialog/restore/RestoreBackupDialogs.kt index a7bf314e07e..579b2f5e597 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/dialog/restore/RestoreBackupDialogs.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/dialog/restore/RestoreBackupDialogs.kt @@ -46,16 +46,16 @@ import com.wire.android.ui.common.textfield.WirePasswordTextField import com.wire.android.ui.common.wireDialogPropertiesBuilder import com.wire.android.ui.home.messagecomposer.FileBrowserFlow import com.wire.android.ui.theme.wireTypography +import com.wire.android.util.permission.PermissionDenialType import kotlin.math.roundToInt @Composable fun PickRestoreFileDialog( onChooseBackupFile: (Uri) -> Unit, - onCancelBackupRestore: () -> Unit + onCancelBackupRestore: () -> Unit, + onPermissionPermanentlyDenied: (type: PermissionDenialType) -> Unit ) { - val fileFlow = FileBrowserFlow(onChooseBackupFile, { - - }) + val fileFlow = FileBrowserFlow(onChooseBackupFile, onPermissionPermanentlyDenied) WireDialog( title = stringResource(R.string.backup_dialog_restore_backup_title), diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPicker.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPicker.kt index 6b80675d5c3..9a058f53823 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPicker.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPicker.kt @@ -93,7 +93,7 @@ fun AvatarPickerScreen( }, targetPictureFileUri = targetAvatarUri, onPermissionPermanentlyDenied = { - val (title, description) = when(it) { + val (title, description) = when (it) { PermissionDenialType.Gallery -> { R.string.app_permission_dialog_title to R.string.open_gallery_permission_dialog_description } diff --git a/app/src/main/kotlin/com/wire/android/util/permission/CreateFileRequestFlow.kt b/app/src/main/kotlin/com/wire/android/util/permission/CreateFileRequestFlow.kt index 6a08428b58b..b3f071d62b4 100644 --- a/app/src/main/kotlin/com/wire/android/util/permission/CreateFileRequestFlow.kt +++ b/app/src/main/kotlin/com/wire/android/util/permission/CreateFileRequestFlow.kt @@ -62,7 +62,7 @@ fun rememberCreateFileFlow( context.getActivity()?.let { it.checkWriteStoragePermission(onPermissionDenied) { onPermissionPermanentlyDenied( - PermissionDenialType.File + PermissionDenialType.WriteFile ) } } @@ -85,4 +85,4 @@ fun AppCompatActivity.checkWriteStoragePermission( } else { onPermissionPermanentlyDenied() } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/com/wire/android/util/permission/OpenFileBrowserRequestFlow.kt b/app/src/main/kotlin/com/wire/android/util/permission/OpenFileBrowserRequestFlow.kt index ae28b4068ac..fc43c0f132b 100644 --- a/app/src/main/kotlin/com/wire/android/util/permission/OpenFileBrowserRequestFlow.kt +++ b/app/src/main/kotlin/com/wire/android/util/permission/OpenFileBrowserRequestFlow.kt @@ -56,7 +56,7 @@ fun rememberOpenFileBrowserFlow( } else { context.getActivity()?.let { it.checkStoragePermission(onPermissionDenied) { - onPermissionPermanentlyDenied(PermissionDenialType.File) + onPermissionPermanentlyDenied(PermissionDenialType.ReadFile) } } } diff --git a/app/src/main/kotlin/com/wire/android/util/permission/PermissionDenialType.kt b/app/src/main/kotlin/com/wire/android/util/permission/PermissionDenialType.kt index 8bf366f765f..e0f52a72abb 100644 --- a/app/src/main/kotlin/com/wire/android/util/permission/PermissionDenialType.kt +++ b/app/src/main/kotlin/com/wire/android/util/permission/PermissionDenialType.kt @@ -22,6 +22,7 @@ sealed class PermissionDenialType { data object TakePicture : PermissionDenialType() data object CallingCamera : PermissionDenialType() data object CallingMicrophone : PermissionDenialType() - data object File : PermissionDenialType() + data object WriteFile : PermissionDenialType() + data object ReadFile : PermissionDenialType() data object Gallery : PermissionDenialType() -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/com/wire/android/util/permission/UseStorageRequestFlow.kt b/app/src/main/kotlin/com/wire/android/util/permission/UseStorageRequestFlow.kt index c5b9696530d..7ed6c7d6c2c 100644 --- a/app/src/main/kotlin/com/wire/android/util/permission/UseStorageRequestFlow.kt +++ b/app/src/main/kotlin/com/wire/android/util/permission/UseStorageRequestFlow.kt @@ -56,4 +56,4 @@ fun AppCompatActivity.checkStoragePermission( } else { onPermissionPermanentlyDenied() } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/com/wire/android/util/permission/WriteStorageRequestFlow.kt b/app/src/main/kotlin/com/wire/android/util/permission/WriteStorageRequestFlow.kt index 1c141a3e246..a25de356e8d 100644 --- a/app/src/main/kotlin/com/wire/android/util/permission/WriteStorageRequestFlow.kt +++ b/app/src/main/kotlin/com/wire/android/util/permission/WriteStorageRequestFlow.kt @@ -62,7 +62,7 @@ fun rememberWriteStorageRequestFlow( context.getActivity()?.let { it.checkWriteStoragePermission(onPermissionDenied) { onPermissionPermanentlyDenied( - PermissionDenialType.File + PermissionDenialType.WriteFile ) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4c59905813b..116cf438a18 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1373,6 +1373,7 @@ To attach a file, allow Wire to access your Storage in your device settings. To save an asset, allow Wire to access your Storage in your device settings. To save a file, allow Wire to access your Storage in your device settings. + To restore conversations, allow Wire to access your Storage in your device settings. To turn camera on, allow Wire to access your Camera in your device settings. diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/call/ConversationCallViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/call/ConversationCallViewModelTest.kt index 68f0819e0fb..1995f2e3596 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/call/ConversationCallViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/call/ConversationCallViewModelTest.kt @@ -40,7 +40,6 @@ import io.mockk.every import io.mockk.impl.annotations.MockK import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.test.runTest import org.amshove.kluent.internal.assertEquals import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test From d53a5dc1084470ad72771fe082e883b8355ef9c3 Mon Sep 17 00:00:00 2001 From: Oussama Hassine Date: Tue, 6 Feb 2024 15:07:02 +0100 Subject: [PATCH 3/8] chore: address comment --- .../home/conversations/ConversationScreen.kt | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt index 2e6c031f954..b9aed0d1908 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt @@ -375,28 +375,33 @@ fun ConversationScreen( requestMentions = messageComposerViewModel::searchMembersToMention, onClearMentionSearchResult = messageComposerViewModel::clearMentionSearchResult, onPermissionPermanentlyDenied = { - val (title, description) = when (it) { + val description = when (it) { is PermissionDenialType.CaptureVideo -> { - R.string.app_permission_dialog_title to R.string.record_video_permission_dialog_description + R.string.record_video_permission_dialog_description } + is PermissionDenialType.TakePicture -> { - R.string.app_permission_dialog_title to R.string.take_picture_permission_dialog_description + R.string.take_picture_permission_dialog_description } + is PermissionDenialType.Gallery -> { - R.string.app_permission_dialog_title to R.string.open_gallery_permission_dialog_description + R.string.open_gallery_permission_dialog_description } + is PermissionDenialType.ReadFile -> { - R.string.app_permission_dialog_title to R.string.attach_file_permission_dialog_description + R.string.attach_file_permission_dialog_description } + is PermissionDenialType.CallingMicrophone -> { - R.string.app_permission_dialog_title to R.string.call_permission_dialog_description + R.string.call_permission_dialog_description } + else -> { - R.string.app_permission_dialog_title to R.string.app_permission_dialog_title + R.string.app_permission_dialog_title } } messageComposerViewModel.showPermissionPermanentlyDeniedDialog( - title = title, + title = R.string.app_permission_dialog_title, description = description ) }, From f764d805cf44d6186c6318d3094ed8348308e55f Mon Sep 17 00:00:00 2001 From: Oussama Hassine Date: Tue, 6 Feb 2024 15:29:09 +0100 Subject: [PATCH 4/8] chore: update strings --- .../ui/home/conversations/ConversationScreen.kt | 2 +- .../media/ConversationMediaScreen.kt | 2 +- .../ui/home/gallery/MediaGalleryScreen.kt | 2 +- .../settings/backup/BackupAndRestoreScreen.kt | 2 +- app/src/main/res/values/strings.xml | 16 ++++++++-------- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt index b9aed0d1908..d46b0a0cb8f 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt @@ -436,7 +436,7 @@ fun ConversationScreen( onPermissionPermanentlyDenied = { messageComposerViewModel.showPermissionPermanentlyDeniedDialog( title = R.string.app_permission_dialog_title, - description = R.string.save_asset_permission_dialog_description + description = R.string.save_permission_dialog_description ) } ) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/media/ConversationMediaScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/media/ConversationMediaScreen.kt index 2eacf4603bc..58b4ad9b221 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/media/ConversationMediaScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/media/ConversationMediaScreen.kt @@ -110,7 +110,7 @@ fun ConversationMediaScreen( onPermissionPermanentlyDenied = { conversationMessagesViewModel.showPermissionPermanentlyDeniedDialog( title = R.string.app_permission_dialog_title, - description = R.string.save_asset_permission_dialog_description + description = R.string.save_permission_dialog_description ) } ) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/gallery/MediaGalleryScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/gallery/MediaGalleryScreen.kt index d156514b4ab..4777bb2f3fe 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/gallery/MediaGalleryScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/gallery/MediaGalleryScreen.kt @@ -72,7 +72,7 @@ fun MediaGalleryScreen( onPermissionPermanentlyDenied = { mediaGalleryViewModel.showPermissionPermanentlyDeniedDialog( title = R.string.app_permission_dialog_title, - description = R.string.save_asset_permission_dialog_description + description = R.string.save_permission_dialog_description ) } ) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackupAndRestoreScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackupAndRestoreScreen.kt index 9644142600c..32e1dacad68 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackupAndRestoreScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackupAndRestoreScreen.kt @@ -156,7 +156,7 @@ fun BackupAndRestoreContent( if (it == PermissionDenialType.WriteFile) { backupAndRestoreStateHolder.showPermissionPermanentlyDeniedDialog( R.string.app_permission_dialog_title, - R.string.save_file_permission_dialog_description + R.string.save_backup_file_permission_dialog_description ) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 116cf438a18..e9a849f5f47 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1367,14 +1367,14 @@ Settings Not Now To make a call, allow Wire to access your microphone in your device settings. - To record a video, allow Wire to access your Camera in your device settings. - To take a picture, allow Wire to access your Camera in your device settings. - To open Gallery, allow Wire to access your Storage in your device settings. - To attach a file, allow Wire to access your Storage in your device settings. - To save an asset, allow Wire to access your Storage in your device settings. - To save a file, allow Wire to access your Storage in your device settings. - To restore conversations, allow Wire to access your Storage in your device settings. - To turn camera on, allow Wire to access your Camera in your device settings. + To record a video, allow Wire to access your camera in your device settings. + To take a picture, allow Wire to access your camera in your device settings. + To send a picture, allow Wire to access Photos in your device settings. + To send a file, allow Wire to access your files in your device settings. + To save this file, allow Wire to access your Downloads folder in your device settings. + To save this backup file, allow Wire to access your Downloads folder in your device settings. + To restore conversations, allow Wire to access your files in your device settings. + To turn the camera on, allow Wire to access your camera in your device settings. Share Location From d581e52fb674bf270df68081edecdbbf885a551a Mon Sep 17 00:00:00 2001 From: Oussama Hassine Date: Thu, 8 Feb 2024 17:53:32 +0100 Subject: [PATCH 5/8] chore: resolve conflicts --- .../ui/calling/SharedCallingViewModel.kt | 15 ------ .../ui/calling/incoming/IncomingCallScreen.kt | 23 +++++---- .../initiating/InitiatingCallScreen.kt | 17 +++++-- .../ui/calling/ongoing/OngoingCallScreen.kt | 17 +++++-- .../PermissionPermanentlyDeniedDialog.kt | 19 +++++--- .../home/conversations/ConversationScreen.kt | 24 ++++++---- .../conversations/MessageComposerViewModel.kt | 13 ----- .../media/ConversationMediaScreen.kt | 17 +++++-- .../messages/ConversationMessagesViewModel.kt | 15 ------ .../ConversationListViewModel.kt | 16 ------- .../conversationslist/ConversationRouter.kt | 16 +++++-- .../ui/home/gallery/MediaGalleryScreen.kt | 17 +++++-- .../ui/home/gallery/MediaGalleryViewModel.kt | 15 ------ .../backup/BackUpAndRestoreStateHolder.kt | 16 ------- .../settings/backup/BackupAndRestoreScreen.kt | 25 ++++++---- .../userprofile/avatarpicker/AvatarPicker.kt | 17 +++++-- .../avatarpicker/AvatarPickerViewModel.kt | 48 +++++++++---------- kalium | 2 +- 18 files changed, 155 insertions(+), 177 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/SharedCallingViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/calling/SharedCallingViewModel.kt index 36200c89a19..4eccb51976d 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/SharedCallingViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/SharedCallingViewModel.kt @@ -30,7 +30,6 @@ import com.wire.android.mapper.UICallParticipantMapper import com.wire.android.mapper.UserTypeMapper import com.wire.android.media.CallRinger import com.wire.android.model.ImageAsset -import com.wire.android.ui.home.conversations.PermissionPermanentlyDeniedDialogState import com.wire.android.ui.navArgs import com.wire.android.util.CurrentScreen import com.wire.android.util.CurrentScreenManager @@ -98,10 +97,6 @@ class SharedCallingViewModel @Inject constructor( var callState by mutableStateOf(CallState(conversationId)) - var permissionPermanentlyDeniedDialogState: PermissionPermanentlyDeniedDialogState by mutableStateOf( - PermissionPermanentlyDeniedDialogState.Hidden - ) - init { viewModelScope.launch { val allCallsSharedFlow = allCalls().map { @@ -314,14 +309,4 @@ class SharedCallingViewModel @Inject constructor( } } } - - fun showPermissionPermanentlyDeniedDialog(title: Int, description: Int) { - permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Visible( - title = title, - description = description - ) - } - fun hidePermissionPermanentlyDeniedDialog() { - permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Hidden - } } diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/incoming/IncomingCallScreen.kt b/app/src/main/kotlin/com/wire/android/ui/calling/incoming/IncomingCallScreen.kt index edcf641e05f..b222ffe6876 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/incoming/IncomingCallScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/incoming/IncomingCallScreen.kt @@ -57,7 +57,9 @@ import com.wire.android.ui.common.colorsScheme import com.wire.android.ui.common.dialogs.PermissionPermanentlyDeniedDialog import com.wire.android.ui.common.dialogs.calling.JoinAnywayDialog import com.wire.android.ui.common.dimensions +import com.wire.android.ui.common.visbility.rememberVisibilityState import com.wire.android.ui.destinations.OngoingCallScreenDestination +import com.wire.android.ui.home.conversations.PermissionPermanentlyDeniedDialogState import com.wire.android.ui.theme.wireTypography import com.wire.android.util.permission.PermissionDenialType import com.wire.android.util.permission.rememberCallingRecordAudioRequestFlow @@ -75,13 +77,16 @@ fun IncomingCallScreen( sharedCallingViewModel: SharedCallingViewModel = hiltViewModel(), incomingCallViewModel: IncomingCallViewModel = hiltViewModel() ) { + val permissionPermanentlyDeniedDialogState = rememberVisibilityState() val audioPermissionCheck = AudioPermissionCheckFlow( onAcceptCall = incomingCallViewModel::acceptCall, onPermanentPermissionDecline = { - sharedCallingViewModel.showPermissionPermanentlyDeniedDialog( - title = R.string.app_permission_dialog_title, - description = R.string.call_permission_dialog_description + permissionPermanentlyDeniedDialogState.show( + PermissionPermanentlyDeniedDialogState.Visible( + title = R.string.app_permission_dialog_title, + description = R.string.call_permission_dialog_description + ) ) } ) @@ -120,9 +125,11 @@ fun IncomingCallScreen( onSelfClearVideoPreview = ::clearVideoPreview, onPermissionPermanentlyDenied = { if (it is PermissionDenialType.CallingCamera) { - sharedCallingViewModel.showPermissionPermanentlyDeniedDialog( - title = R.string.app_permission_dialog_title, - description = R.string.camera_permission_dialog_description + permissionPermanentlyDeniedDialogState.show( + PermissionPermanentlyDeniedDialogState.Visible( + title = R.string.app_permission_dialog_title, + description = R.string.camera_permission_dialog_description + ) ) } } @@ -130,8 +137,8 @@ fun IncomingCallScreen( } PermissionPermanentlyDeniedDialog( - dialogState = sharedCallingViewModel.permissionPermanentlyDeniedDialogState, - hideDialog = sharedCallingViewModel::hidePermissionPermanentlyDeniedDialog + dialogState = permissionPermanentlyDeniedDialogState, + hideDialog = permissionPermanentlyDeniedDialogState::dismiss ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/initiating/InitiatingCallScreen.kt b/app/src/main/kotlin/com/wire/android/ui/calling/initiating/InitiatingCallScreen.kt index 900726395c8..7d6440df304 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/initiating/InitiatingCallScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/initiating/InitiatingCallScreen.kt @@ -55,7 +55,9 @@ import com.wire.android.ui.calling.controlbuttons.HangUpButton import com.wire.android.ui.common.bottomsheet.WireBottomSheetScaffold import com.wire.android.ui.common.dialogs.PermissionPermanentlyDeniedDialog import com.wire.android.ui.common.dimensions +import com.wire.android.ui.common.visbility.rememberVisibilityState import com.wire.android.ui.destinations.OngoingCallScreenDestination +import com.wire.android.ui.home.conversations.PermissionPermanentlyDeniedDialogState import com.wire.android.ui.theme.wireDimensions import com.wire.android.util.permission.PermissionDenialType import com.wire.kalium.logic.data.id.ConversationId @@ -72,6 +74,8 @@ fun InitiatingCallScreen( sharedCallingViewModel: SharedCallingViewModel = hiltViewModel(), initiatingCallViewModel: InitiatingCallViewModel = hiltViewModel() ) { + val permissionPermanentlyDeniedDialogState = rememberVisibilityState() + LaunchedEffect(initiatingCallViewModel.state.flowState) { when (initiatingCallViewModel.state.flowState) { InitiatingCallState.FlowState.CallClosed -> navigator.navigateBack() @@ -93,17 +97,20 @@ fun InitiatingCallScreen( onSelfClearVideoPreview = ::clearVideoPreview, onPermissionPermanentlyDenied = { if (it is PermissionDenialType.CallingCamera) { - sharedCallingViewModel.showPermissionPermanentlyDeniedDialog( - title = R.string.app_permission_dialog_title, - description = R.string.camera_permission_dialog_description + permissionPermanentlyDeniedDialogState.show( + PermissionPermanentlyDeniedDialogState.Visible( + title = R.string.app_permission_dialog_title, + description = R.string.camera_permission_dialog_description + ) ) } } ) } + PermissionPermanentlyDeniedDialog( - dialogState = sharedCallingViewModel.permissionPermanentlyDeniedDialogState, - hideDialog = sharedCallingViewModel::hidePermissionPermanentlyDeniedDialog + dialogState = permissionPermanentlyDeniedDialogState, + hideDialog = permissionPermanentlyDeniedDialogState::dismiss ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/OngoingCallScreen.kt b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/OngoingCallScreen.kt index ed2f9f7b770..85bf0c51f04 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/OngoingCallScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/ongoing/OngoingCallScreen.kt @@ -78,6 +78,8 @@ import com.wire.android.ui.common.dimensions import com.wire.android.ui.common.progress.WireCircularProgressIndicator import com.wire.android.ui.common.topappbar.NavigationIconType import com.wire.android.ui.common.topappbar.WireCenterAlignedTopAppBar +import com.wire.android.ui.common.visbility.rememberVisibilityState +import com.wire.android.ui.home.conversations.PermissionPermanentlyDeniedDialogState import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.theme.wireDimensions import com.wire.android.ui.theme.wireTypography @@ -98,6 +100,9 @@ fun OngoingCallScreen( ongoingCallViewModel: OngoingCallViewModel = hiltViewModel(), sharedCallingViewModel: SharedCallingViewModel = hiltViewModel(), ) { + val permissionPermanentlyDeniedDialogState = + rememberVisibilityState() + LaunchedEffect(ongoingCallViewModel.state.flowState) { when (ongoingCallViewModel.state.flowState) { OngoingCallState.FlowState.CallClosed -> navigator.navigateBack() @@ -131,9 +136,11 @@ fun OngoingCallScreen( hideDoubleTapToast = ongoingCallViewModel::hideDoubleTapToast, onPermissionPermanentlyDenied = { if (it is PermissionDenialType.CallingCamera) { - sharedCallingViewModel.showPermissionPermanentlyDeniedDialog( - title = R.string.app_permission_dialog_title, - description = R.string.camera_permission_dialog_description + permissionPermanentlyDeniedDialogState.show( + PermissionPermanentlyDeniedDialogState.Visible( + title = R.string.app_permission_dialog_title, + description = R.string.camera_permission_dialog_description + ) ) } } @@ -142,8 +149,8 @@ fun OngoingCallScreen( } PermissionPermanentlyDeniedDialog( - dialogState = sharedCallingViewModel.permissionPermanentlyDeniedDialogState, - hideDialog = sharedCallingViewModel::hidePermissionPermanentlyDeniedDialog + dialogState = permissionPermanentlyDeniedDialogState, + hideDialog = permissionPermanentlyDeniedDialogState::dismiss ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/common/dialogs/PermissionPermanentlyDeniedDialog.kt b/app/src/main/kotlin/com/wire/android/ui/common/dialogs/PermissionPermanentlyDeniedDialog.kt index 21e1750f21c..84d2d1c106f 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/dialogs/PermissionPermanentlyDeniedDialog.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/dialogs/PermissionPermanentlyDeniedDialog.kt @@ -18,19 +18,24 @@ package com.wire.android.ui.common.dialogs import androidx.compose.runtime.Composable +import com.wire.android.ui.common.VisibilityState +import com.wire.android.ui.common.visbility.VisibilityState import com.wire.android.ui.home.conversations.PermissionPermanentlyDeniedDialogState +import com.wire.android.ui.userprofile.self.dialog.LogoutOptionsDialogState import com.wire.android.util.permission.PermissionsDeniedRequestDialog @Composable fun PermissionPermanentlyDeniedDialog( - dialogState: PermissionPermanentlyDeniedDialogState, + dialogState: VisibilityState, hideDialog: () -> Unit ) { - if (dialogState is PermissionPermanentlyDeniedDialogState.Visible) { - PermissionsDeniedRequestDialog( - title = dialogState.title, - body = dialogState.description, - onDismiss = hideDialog - ) + VisibilityState(dialogState) { state -> + if (state is PermissionPermanentlyDeniedDialogState.Visible) { + PermissionsDeniedRequestDialog( + title = state.title, + body = state.description, + onDismiss = hideDialog + ) + } } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt index d46b0a0cb8f..1c5837fa87d 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt @@ -96,6 +96,7 @@ import com.wire.android.ui.common.dialogs.calling.SureAboutCallingInDegradedConv import com.wire.android.ui.common.dimensions import com.wire.android.ui.common.error.CoreFailureErrorDialog import com.wire.android.ui.common.snackbar.LocalSnackbarHostState +import com.wire.android.ui.common.visbility.rememberVisibilityState import com.wire.android.ui.destinations.ConversationScreenDestination import com.wire.android.ui.destinations.GroupConversationDetailsScreenDestination import com.wire.android.ui.destinations.InitiatingCallScreenDestination @@ -194,6 +195,9 @@ fun ConversationScreen( messageComposerViewState = messageComposerViewState, modalBottomSheetState = conversationScreenState.modalBottomSheetState ) + val permissionPermanentlyDeniedDialogState = + rememberVisibilityState() + // this is to prevent from double navigating back after user deletes a group on group details screen // then ViewModel also detects it's removed and calls onNotFound which can execute navigateBack again and close the app @@ -400,9 +404,11 @@ fun ConversationScreen( R.string.app_permission_dialog_title } } - messageComposerViewModel.showPermissionPermanentlyDeniedDialog( - title = R.string.app_permission_dialog_title, - description = description + permissionPermanentlyDeniedDialogState.show( + PermissionPermanentlyDeniedDialogState.Visible( + title = R.string.app_permission_dialog_title, + description = description + ) ) }, conversationScreenState = conversationScreenState, @@ -434,9 +440,11 @@ fun ConversationScreen( onOpenFileWithExternalApp = conversationMessagesViewModel::downloadAndOpenAsset, hideOnAssetDownloadedDialog = conversationMessagesViewModel::hideOnAssetDownloadedDialog, onPermissionPermanentlyDenied = { - messageComposerViewModel.showPermissionPermanentlyDeniedDialog( - title = R.string.app_permission_dialog_title, - description = R.string.save_permission_dialog_description + permissionPermanentlyDeniedDialogState.show( + PermissionPermanentlyDeniedDialogState.Visible( + title = R.string.app_permission_dialog_title, + description = R.string.save_permission_dialog_description + ) ) } ) @@ -455,8 +463,8 @@ fun ConversationScreen( ) PermissionPermanentlyDeniedDialog( - dialogState = messageComposerViewModel.permissionPermanentlyDeniedDialogState, - hideDialog = messageComposerViewModel::hidePermissionPermanentlyDeniedDialog + dialogState = permissionPermanentlyDeniedDialogState, + hideDialog = permissionPermanentlyDeniedDialogState::dismiss ) SureAboutMessagingInDegradedConversationDialog( diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModel.kt index c764da73e9d..fbe5d4155cf 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModel.kt @@ -171,10 +171,6 @@ class MessageComposerViewModel @Inject constructor( InvalidLinkDialogState.Hidden ) - var permissionPermanentlyDeniedDialogState: PermissionPermanentlyDeniedDialogState by mutableStateOf( - PermissionPermanentlyDeniedDialogState.Hidden - ) - var sureAboutMessagingDialogState: SureAboutMessagingDialogState by mutableStateOf( SureAboutMessagingDialogState.Hidden ) @@ -503,15 +499,6 @@ class MessageComposerViewModel @Inject constructor( fun hideInvalidLinkError() { invalidLinkDialogState = InvalidLinkDialogState.Hidden } - fun showPermissionPermanentlyDeniedDialog(title: Int, description: Int) { - permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Visible( - title = title, - description = description - ) - } - fun hidePermissionPermanentlyDeniedDialog() { - permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Hidden - } fun sendTypingEvent(typingIndicatorMode: TypingIndicatorMode) { viewModelScope.launch { diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/media/ConversationMediaScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/media/ConversationMediaScreen.kt index 58b4ad9b221..2068330203f 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/media/ConversationMediaScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/media/ConversationMediaScreen.kt @@ -57,9 +57,11 @@ import com.wire.android.ui.common.scaffold.WireScaffold import com.wire.android.ui.common.topBarElevation import com.wire.android.ui.common.topappbar.NavigationIconType import com.wire.android.ui.common.topappbar.WireCenterAlignedTopAppBar +import com.wire.android.ui.common.visbility.rememberVisibilityState import com.wire.android.ui.destinations.MediaGalleryScreenDestination import com.wire.android.ui.home.conversations.DownloadedAssetDialog import com.wire.android.ui.home.conversations.MessageComposerViewModel +import com.wire.android.ui.home.conversations.PermissionPermanentlyDeniedDialogState import com.wire.android.ui.home.conversations.SnackBarMessage import com.wire.android.ui.home.conversations.messages.ConversationMessagesViewModel import com.wire.android.ui.theme.WireTheme @@ -80,6 +82,9 @@ fun ConversationMediaScreen( conversationMessagesViewModel: ConversationMessagesViewModel = hiltViewModel(), messageComposerViewModel: MessageComposerViewModel = hiltViewModel() ) { + val permissionPermanentlyDeniedDialogState = + rememberVisibilityState() + val state: ConversationAssetMessagesViewState = conversationAssetMessagesViewModel.viewState Content( @@ -108,16 +113,18 @@ fun ConversationMediaScreen( onOpenFileWithExternalApp = conversationMessagesViewModel::downloadAndOpenAsset, hideOnAssetDownloadedDialog = conversationMessagesViewModel::hideOnAssetDownloadedDialog, onPermissionPermanentlyDenied = { - conversationMessagesViewModel.showPermissionPermanentlyDeniedDialog( - title = R.string.app_permission_dialog_title, - description = R.string.save_permission_dialog_description + permissionPermanentlyDeniedDialogState.show( + PermissionPermanentlyDeniedDialogState.Visible( + title = R.string.app_permission_dialog_title, + description = R.string.save_permission_dialog_description + ) ) } ) PermissionPermanentlyDeniedDialog( - dialogState = conversationMessagesViewModel.permissionPermanentlyDeniedDialogState, - hideDialog = conversationMessagesViewModel::hidePermissionPermanentlyDeniedDialog + dialogState = permissionPermanentlyDeniedDialogState, + hideDialog = permissionPermanentlyDeniedDialogState::dismiss ) SnackBarMessage( diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt index 15bbe118dd5..f4be3695369 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/messages/ConversationMessagesViewModel.kt @@ -32,7 +32,6 @@ import com.wire.android.navigation.SavedStateViewModel import com.wire.android.ui.home.conversations.ConversationNavArgs import com.wire.android.ui.home.conversations.ConversationSnackbarMessages import com.wire.android.ui.home.conversations.ConversationSnackbarMessages.OnResetSession -import com.wire.android.ui.home.conversations.PermissionPermanentlyDeniedDialogState import com.wire.android.ui.home.conversations.model.AssetBundle import com.wire.android.ui.home.conversations.model.UIMessage import com.wire.android.ui.home.conversations.usecase.GetMessagesForConversationUseCase @@ -94,10 +93,6 @@ class ConversationMessagesViewModel @Inject constructor( val conversationId: QualifiedID = conversationNavArgs.conversationId private val searchedMessageIdNavArgs: String? = conversationNavArgs.searchedMessageId - var permissionPermanentlyDeniedDialogState: PermissionPermanentlyDeniedDialogState by mutableStateOf( - PermissionPermanentlyDeniedDialogState.Hidden - ) - var conversationViewState by mutableStateOf( ConversationMessagesViewState( searchedMessageId = searchedMessageIdNavArgs @@ -340,16 +335,6 @@ class ConversationMessagesViewModel @Inject constructor( return null } - fun showPermissionPermanentlyDeniedDialog(title: Int, description: Int) { - permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Visible( - title = title, - description = description - ) - } - fun hidePermissionPermanentlyDeniedDialog() { - permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Hidden - } - override fun onCleared() { super.onCleared() conversationAudioMessagePlayer.close() diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt index 6de18961cb2..3bc7fd71920 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt @@ -32,7 +32,6 @@ import com.wire.android.model.UserAvatarData import com.wire.android.ui.common.bottomsheet.conversation.ConversationTypeDetail import com.wire.android.ui.common.dialogs.BlockUserDialogState import com.wire.android.ui.home.HomeSnackbarState -import com.wire.android.ui.home.conversations.PermissionPermanentlyDeniedDialogState import com.wire.android.ui.home.conversations.model.UILastMessageContent import com.wire.android.ui.home.conversations.search.DEFAULT_SEARCH_QUERY_DEBOUNCE import com.wire.android.ui.home.conversationslist.model.BadgeEventType @@ -59,7 +58,6 @@ import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.id.QualifiedID import com.wire.kalium.logic.data.message.UnreadEventType import com.wire.kalium.logic.data.user.ConnectionState -import com.wire.kalium.logic.data.user.LegalHoldStatus import com.wire.kalium.logic.data.user.UserAvailabilityStatus import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.feature.call.usecase.AnswerCallUseCase @@ -150,10 +148,6 @@ class ConversationListViewModel @Inject constructor( var establishedCallConversationId: QualifiedID? = null private var conversationId: QualifiedID? = null - var permissionPermanentlyDeniedDialogState: PermissionPermanentlyDeniedDialogState by mutableStateOf( - PermissionPermanentlyDeniedDialogState.Hidden - ) - private suspend fun observeEstablishedCall() { observeEstablishedCalls() .distinctUntilChanged() @@ -214,16 +208,6 @@ class ConversationListViewModel @Inject constructor( } } - fun showPermissionPermanentlyDeniedDialog(title: Int, description: Int) { - permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Visible( - title = title, - description = description - ) - } - fun hidePermissionPermanentlyDeniedDialog() { - permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Hidden - } - // Mateusz : First iteration, just filter stuff // next iteration : SQL- query ? private fun searchConversation( diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationRouter.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationRouter.kt index da2455e6b21..668e09e7cc3 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationRouter.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationRouter.kt @@ -47,6 +47,7 @@ import com.wire.android.ui.destinations.NewConversationSearchPeopleScreenDestina import com.wire.android.ui.destinations.OngoingCallScreenDestination import com.wire.android.ui.destinations.OtherUserProfileScreenDestination import com.wire.android.ui.home.HomeSnackbarState +import com.wire.android.ui.home.conversations.PermissionPermanentlyDeniedDialogState import com.wire.android.ui.home.conversations.details.dialog.ClearConversationContentDialog import com.wire.android.ui.home.conversations.details.menu.DeleteConversationGroupDialog import com.wire.android.ui.home.conversations.details.menu.LeaveConversationGroupDialog @@ -80,6 +81,9 @@ fun ConversationRouterHomeBridge( isBottomSheetVisible: () -> Boolean, conversationsSource: ConversationsSource = ConversationsSource.MAIN ) { + val permissionPermanentlyDeniedDialogState = + rememberVisibilityState() + val viewModel: ConversationListViewModel = hiltViewModel() LaunchedEffect(conversationsSource) { @@ -210,9 +214,11 @@ fun ConversationRouterHomeBridge( onJoinedCall = onJoinedCall, onPermissionPermanentlyDenied = { if (it == PermissionDenialType.CallingMicrophone) { - viewModel.showPermissionPermanentlyDeniedDialog( - R.string.app_permission_dialog_title, - R.string.call_permission_dialog_description + permissionPermanentlyDeniedDialogState.show( + PermissionPermanentlyDeniedDialogState.Visible( + R.string.app_permission_dialog_title, + R.string.call_permission_dialog_description + ) ) } } @@ -254,8 +260,8 @@ fun ConversationRouterHomeBridge( } PermissionPermanentlyDeniedDialog( - dialogState = viewModel.permissionPermanentlyDeniedDialogState, - hideDialog = viewModel::hidePermissionPermanentlyDeniedDialog + dialogState = permissionPermanentlyDeniedDialogState, + hideDialog = permissionPermanentlyDeniedDialogState::dismiss ) BlockUserDialogContent( diff --git a/app/src/main/kotlin/com/wire/android/ui/home/gallery/MediaGalleryScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/gallery/MediaGalleryScreen.kt index 4777bb2f3fe..9f25f540f78 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/gallery/MediaGalleryScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/gallery/MediaGalleryScreen.kt @@ -42,7 +42,9 @@ import com.wire.android.ui.common.bottomsheet.MenuModalSheetLayout import com.wire.android.ui.common.colorsScheme import com.wire.android.ui.common.dialogs.PermissionPermanentlyDeniedDialog import com.wire.android.ui.common.scaffold.WireScaffold +import com.wire.android.ui.common.visbility.rememberVisibilityState import com.wire.android.ui.home.conversations.MediaGallerySnackbarMessages +import com.wire.android.ui.home.conversations.PermissionPermanentlyDeniedDialogState import com.wire.android.ui.home.conversations.delete.DeleteMessageDialog import com.wire.android.ui.home.conversations.edit.AssetEditMenuItems import com.wire.android.util.permission.rememberWriteStorageRequestFlow @@ -59,6 +61,9 @@ fun MediaGalleryScreen( mediaGalleryViewModel: MediaGalleryViewModel = hiltViewModel(), resultNavigator: ResultBackNavigator ) { + val permissionPermanentlyDeniedDialogState = + rememberVisibilityState() + val viewModelState = mediaGalleryViewModel.mediaGalleryViewState val mediaGalleryScreenState = rememberMediaGalleryScreenState() val scope = rememberCoroutineScope() @@ -70,16 +75,18 @@ fun MediaGalleryScreen( }, onPermissionDenied = { /** Nothing to do **/ }, onPermissionPermanentlyDenied = { - mediaGalleryViewModel.showPermissionPermanentlyDeniedDialog( - title = R.string.app_permission_dialog_title, - description = R.string.save_permission_dialog_description + permissionPermanentlyDeniedDialogState.show( + PermissionPermanentlyDeniedDialogState.Visible( + title = R.string.app_permission_dialog_title, + description = R.string.save_permission_dialog_description + ) ) } ) PermissionPermanentlyDeniedDialog( - dialogState = mediaGalleryViewModel.permissionPermanentlyDeniedDialogState, - hideDialog = mediaGalleryViewModel::hidePermissionPermanentlyDeniedDialog + dialogState = permissionPermanentlyDeniedDialogState, + hideDialog = permissionPermanentlyDeniedDialogState::dismiss ) with(viewModelState) { diff --git a/app/src/main/kotlin/com/wire/android/ui/home/gallery/MediaGalleryViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/gallery/MediaGalleryViewModel.kt index 5699382501e..b304007c046 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/gallery/MediaGalleryViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/gallery/MediaGalleryViewModel.kt @@ -27,7 +27,6 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.wire.android.model.ImageAsset import com.wire.android.ui.home.conversations.MediaGallerySnackbarMessages -import com.wire.android.ui.home.conversations.PermissionPermanentlyDeniedDialogState import com.wire.android.ui.home.conversations.delete.DeleteMessageDialogActiveState import com.wire.android.ui.home.conversations.delete.DeleteMessageDialogHelper import com.wire.android.ui.home.conversations.delete.DeleteMessageDialogsState @@ -75,10 +74,6 @@ class MediaGalleryViewModel @Inject constructor( mediaGalleryNavArgs.isEphemeral ) - var permissionPermanentlyDeniedDialogState: PermissionPermanentlyDeniedDialogState by mutableStateOf( - PermissionPermanentlyDeniedDialogState.Hidden - ) - private val messageId = imageAsset.messageId private val conversationId = imageAsset.conversationId var mediaGalleryViewState by mutableStateOf(MediaGalleryViewState(isEphemeral = imageAsset.isEphemeral)) @@ -199,14 +194,4 @@ class MediaGalleryViewModel @Inject constructor( mediaGalleryViewState = mediaGalleryViewState.copy(deleteMessageDialogsState = newValue(it)) } } - - fun showPermissionPermanentlyDeniedDialog(title: Int, description: Int) { - permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Visible( - title = title, - description = description - ) - } - fun hidePermissionPermanentlyDeniedDialog() { - permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Hidden - } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackUpAndRestoreStateHolder.kt b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackUpAndRestoreStateHolder.kt index dedbbe854d6..efb247d6dce 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackUpAndRestoreStateHolder.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackUpAndRestoreStateHolder.kt @@ -23,7 +23,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import com.wire.android.ui.home.conversations.PermissionPermanentlyDeniedDialogState class BackUpAndRestoreStateHolder { @@ -31,10 +30,6 @@ class BackUpAndRestoreStateHolder { BackupAndRestoreDialog.None ) - var permissionPermanentlyDeniedDialogState: PermissionPermanentlyDeniedDialogState by mutableStateOf( - PermissionPermanentlyDeniedDialogState.Hidden - ) - fun showBackupDialog() { dialogState = BackupAndRestoreDialog.CreateBackup } @@ -46,17 +41,6 @@ class BackUpAndRestoreStateHolder { fun dismissDialog() { dialogState = BackupAndRestoreDialog.None } - - fun showPermissionPermanentlyDeniedDialog(title: Int, description: Int) { - permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Visible( - title = title, - description = description - ) - } - - fun hidePermissionPermanentlyDeniedDialog() { - permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Hidden - } } @Composable diff --git a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackupAndRestoreScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackupAndRestoreScreen.kt index 32e1dacad68..72ef6489e4e 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackupAndRestoreScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/settings/backup/BackupAndRestoreScreen.kt @@ -46,7 +46,9 @@ import com.wire.android.ui.common.button.WirePrimaryButton import com.wire.android.ui.common.dialogs.PermissionPermanentlyDeniedDialog import com.wire.android.ui.common.spacers.VerticalSpace import com.wire.android.ui.common.topappbar.WireCenterAlignedTopAppBar +import com.wire.android.ui.common.visbility.rememberVisibilityState import com.wire.android.ui.destinations.HomeScreenDestination +import com.wire.android.ui.home.conversations.PermissionPermanentlyDeniedDialogState import com.wire.android.ui.home.settings.backup.dialog.create.CreateBackupDialogFlow import com.wire.android.ui.home.settings.backup.dialog.restore.RestoreBackupDialogFlow import com.wire.android.ui.theme.wireColorScheme @@ -90,6 +92,9 @@ fun BackupAndRestoreContent( onOpenConversations: () -> Unit, onBackPressed: () -> Unit ) { + val permissionPermanentlyDeniedDialogState = + rememberVisibilityState() + val backupAndRestoreStateHolder = rememberBackUpAndRestoreStateHolder() WireScaffold(topBar = { WireCenterAlignedTopAppBar( @@ -154,9 +159,11 @@ fun BackupAndRestoreContent( }, onPermissionPermanentlyDenied = { if (it == PermissionDenialType.WriteFile) { - backupAndRestoreStateHolder.showPermissionPermanentlyDeniedDialog( - R.string.app_permission_dialog_title, - R.string.save_backup_file_permission_dialog_description + permissionPermanentlyDeniedDialogState.show( + PermissionPermanentlyDeniedDialogState.Visible( + R.string.app_permission_dialog_title, + R.string.save_backup_file_permission_dialog_description + ) ) } } @@ -175,9 +182,11 @@ fun BackupAndRestoreContent( onOpenConversations = onOpenConversations, onPermissionPermanentlyDenied = { if (it == PermissionDenialType.ReadFile) { - backupAndRestoreStateHolder.showPermissionPermanentlyDeniedDialog( - R.string.app_permission_dialog_title, - R.string.restore_backup_permission_dialog_description + permissionPermanentlyDeniedDialogState.show( + PermissionPermanentlyDeniedDialogState.Visible( + R.string.app_permission_dialog_title, + R.string.restore_backup_permission_dialog_description + ) ) } } @@ -188,8 +197,8 @@ fun BackupAndRestoreContent( } PermissionPermanentlyDeniedDialog( - dialogState = backupAndRestoreStateHolder.permissionPermanentlyDeniedDialogState, - hideDialog = backupAndRestoreStateHolder::hidePermissionPermanentlyDeniedDialog + dialogState = permissionPermanentlyDeniedDialogState, + hideDialog = permissionPermanentlyDeniedDialogState::dismiss ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPicker.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPicker.kt index 9a058f53823..fdb6a8f2c32 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPicker.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPicker.kt @@ -59,6 +59,8 @@ import com.wire.android.ui.common.dimensions import com.wire.android.ui.common.imagepreview.BulletHoleImagePreview import com.wire.android.ui.common.scaffold.WireScaffold import com.wire.android.ui.common.topappbar.WireCenterAlignedTopAppBar +import com.wire.android.ui.common.visbility.rememberVisibilityState +import com.wire.android.ui.home.conversations.PermissionPermanentlyDeniedDialogState import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.userprofile.avatarpicker.AvatarPickerViewModel.PictureState import com.wire.android.util.ImageUtil @@ -78,6 +80,9 @@ fun AvatarPickerScreen( viewModel: AvatarPickerViewModel = hiltViewModel(), resultNavigator: ResultBackNavigator ) { + val permissionPermanentlyDeniedDialogState = + rememberVisibilityState() + val context = LocalContext.current val targetAvatarPath = viewModel.defaultAvatarPath @@ -102,9 +107,11 @@ fun AvatarPickerScreen( } else -> { 0 to 0 } } - viewModel.showPermissionPermanentlyDeniedDialog( - title = title, - description = description + permissionPermanentlyDeniedDialogState.show( + PermissionPermanentlyDeniedDialogState.Visible( + title = title, + description = description + ) ) } ) @@ -122,8 +129,8 @@ fun AvatarPickerScreen( ) PermissionPermanentlyDeniedDialog( - dialogState = viewModel.permissionPermanentlyDeniedDialogState, - hideDialog = viewModel::hidePermissionPermanentlyDeniedDialog + dialogState = permissionPermanentlyDeniedDialogState, + hideDialog = permissionPermanentlyDeniedDialogState::dismiss ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPickerViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPickerViewModel.kt index 2e416c28577..68c557c06ce 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPickerViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/avatarpicker/AvatarPickerViewModel.kt @@ -31,7 +31,6 @@ import com.wire.android.R import com.wire.android.appLogger import com.wire.android.datastore.UserDataStore import com.wire.android.model.SnackBarMessage -import com.wire.android.ui.home.conversations.PermissionPermanentlyDeniedDialogState import com.wire.android.util.AvatarImageManager import com.wire.android.util.dispatchers.DispatcherProvider import com.wire.android.util.toByteArray @@ -76,28 +75,27 @@ class AvatarPickerViewModel @Inject constructor( val temporaryAvatarUri: Uri = avatarImageManager.getShareableTempAvatarUri(defaultAvatarPath) - private lateinit var currentAvatarUri: Uri - - var permissionPermanentlyDeniedDialogState: PermissionPermanentlyDeniedDialogState by mutableStateOf( - PermissionPermanentlyDeniedDialogState.Hidden - ) - init { loadInitialAvatarState() } + @Suppress("TooGenericExceptionCaught") fun loadInitialAvatarState() { viewModelScope.launch { + initialPictureLoadingState = InitialPictureLoadingState.Loading try { dataStore.avatarAssetId.first()?.apply { val qualifiedAsset = qualifiedIdMapper.fromStringToQualifiedID(this) val avatarRawPath = (getAvatarAsset(assetKey = qualifiedAsset) as PublicAssetResult.Success).assetPath - currentAvatarUri = avatarImageManager.getWritableAvatarUri(avatarRawPath) - - pictureState = PictureState.Initial(currentAvatarUri) + val currentAvatarUri = avatarImageManager.getWritableAvatarUri(avatarRawPath) + initialPictureLoadingState = InitialPictureLoadingState.Loaded(currentAvatarUri) + if (pictureState is PictureState.Empty) { + pictureState = PictureState.Initial(currentAvatarUri) + } } - } catch (e: ClassCastException) { + } catch (e: Exception) { appLogger.e("There was an error loading the user avatar", e) + initialPictureLoadingState = InitialPictureLoadingState.None } } } @@ -114,7 +112,6 @@ class AvatarPickerViewModel @Inject constructor( val avatarPath = defaultAvatarPath val imageDataSize = imgUri.toByteArray(appContext, dispatchers).size.toLong() - when (val result = uploadUserAvatar(avatarPath, imageDataSize)) { is UploadAvatarResult.Success -> { dataStore.updateUserAvatarAssetId(result.userAssetId.toString()) @@ -125,7 +122,12 @@ class AvatarPickerViewModel @Inject constructor( is NetworkFailure.NoNetworkConnection -> showInfoMessage(InfoMessageType.NoNetworkError) else -> showInfoMessage(InfoMessageType.UploadAvatarError) } - pictureState = PictureState.Initial(currentAvatarUri) + with(initialPictureLoadingState) { + pictureState = when (this) { + is InitialPictureLoadingState.Loaded -> PictureState.Initial(avatarUri) + else -> PictureState.Empty + } + } } } } @@ -135,15 +137,11 @@ class AvatarPickerViewModel @Inject constructor( _infoMessage.emit(type.uiText) } - fun showPermissionPermanentlyDeniedDialog(title: Int, description: Int) { - permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Visible( - title = title, - description = description - ) - } - - fun hidePermissionPermanentlyDeniedDialog() { - permissionPermanentlyDeniedDialogState = PermissionPermanentlyDeniedDialogState.Hidden + @Stable + private sealed class InitialPictureLoadingState { + data object None : InitialPictureLoadingState() + data object Loading : InitialPictureLoadingState() + data class Loaded(val avatarUri: Uri) : InitialPictureLoadingState() } @Stable @@ -151,11 +149,11 @@ class AvatarPickerViewModel @Inject constructor( data class Uploading(override val avatarUri: Uri) : PictureState(avatarUri) data class Initial(override val avatarUri: Uri) : PictureState(avatarUri) data class Picked(override val avatarUri: Uri) : PictureState(avatarUri) - object Empty : PictureState("".toUri()) + data object Empty : PictureState("".toUri()) } sealed class InfoMessageType(override val uiText: UIText) : SnackBarMessage { - object UploadAvatarError : InfoMessageType(UIText.StringResource(R.string.error_uploading_user_avatar)) - object NoNetworkError : InfoMessageType(UIText.StringResource(R.string.error_no_network_message)) + data object UploadAvatarError : InfoMessageType(UIText.StringResource(R.string.error_uploading_user_avatar)) + data object NoNetworkError : InfoMessageType(UIText.StringResource(R.string.error_no_network_message)) } } diff --git a/kalium b/kalium index d6c73552a4f..65f9293521b 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit d6c73552a4f2327b72bb255ebc79adddfd0e519d +Subproject commit 65f9293521b612f83a27353e6b04456c877f7aad From 2a0be5ebf9ba3505cd76c5e6c892b12a5b5e7d3d Mon Sep 17 00:00:00 2001 From: Oussama Hassine Date: Fri, 9 Feb 2024 11:00:14 +0100 Subject: [PATCH 6/8] chore: kalium reference --- kalium | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kalium b/kalium index 65f9293521b..98d2d4f4d42 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit 65f9293521b612f83a27353e6b04456c877f7aad +Subproject commit 98d2d4f4d425cc02fa0dd5da27f5f6c8add12b3d From 1133a0e6feb90f92f2f65f4fb4908cf96b7d3ae0 Mon Sep 17 00:00:00 2001 From: Oussama Hassine Date: Fri, 9 Feb 2024 11:16:45 +0100 Subject: [PATCH 7/8] chore: detekt --- .../ui/common/dialogs/PermissionPermanentlyDeniedDialog.kt | 1 - .../com/wire/android/ui/home/conversations/ConversationScreen.kt | 1 - 2 files changed, 2 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/common/dialogs/PermissionPermanentlyDeniedDialog.kt b/app/src/main/kotlin/com/wire/android/ui/common/dialogs/PermissionPermanentlyDeniedDialog.kt index 84d2d1c106f..fd442f7a099 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/dialogs/PermissionPermanentlyDeniedDialog.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/dialogs/PermissionPermanentlyDeniedDialog.kt @@ -21,7 +21,6 @@ import androidx.compose.runtime.Composable import com.wire.android.ui.common.VisibilityState import com.wire.android.ui.common.visbility.VisibilityState import com.wire.android.ui.home.conversations.PermissionPermanentlyDeniedDialogState -import com.wire.android.ui.userprofile.self.dialog.LogoutOptionsDialogState import com.wire.android.util.permission.PermissionsDeniedRequestDialog @Composable diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt index ee003607193..9c0ebc4aa59 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt @@ -200,7 +200,6 @@ fun ConversationScreen( val permissionPermanentlyDeniedDialogState = rememberVisibilityState() - // this is to prevent from double navigating back after user deletes a group on group details screen // then ViewModel also detects it's removed and calls onNotFound which can execute navigateBack again and close the app var alreadyDeletedByUser by rememberSaveable { mutableStateOf(false) } From 8e28835f6e253b7937dbd80af702ccb3d9597140 Mon Sep 17 00:00:00 2001 From: Oussama Hassine Date: Mon, 12 Feb 2024 11:50:24 +0100 Subject: [PATCH 8/8] chore: kalium reference --- kalium | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kalium b/kalium index 98d2d4f4d42..efb46f123eb 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit 98d2d4f4d425cc02fa0dd5da27f5f6c8add12b3d +Subproject commit efb46f123eb4187f1b3b66b6a8ce1d7a5a05942c