diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/common/MicrophoneBTPermissionsDeniedDialog.kt b/app/src/main/kotlin/com/wire/android/ui/calling/common/MicrophoneBTPermissionsDeniedDialog.kt new file mode 100644 index 00000000000..7848b597a38 --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/calling/common/MicrophoneBTPermissionsDeniedDialog.kt @@ -0,0 +1,62 @@ +/* + * Wire + * Copyright (C) 2023 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.ui.PreviewMultipleThemes + +@Composable +fun MicrophoneBTPermissionsDeniedDialog( + shouldShow: Boolean, + onDismiss: () -> Unit, + onOpenSettings: () -> 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_decline), + 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 + ) + ) + } +} +@PreviewMultipleThemes +@Composable +fun PreviewMicrophoneBTPermissionsDeniedDialog() { + MicrophoneBTPermissionsDeniedDialog( + shouldShow = true, + onDismiss = {}, + onOpenSettings = {} + ) +} 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 3a3d19d308f..e26b638cc79 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 @@ -32,8 +32,8 @@ import androidx.compose.ui.unit.Dp import com.wire.android.R import com.wire.android.appLogger import com.wire.android.ui.common.button.WireButtonState -import com.wire.android.ui.common.dimensions 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.rememberCallingRecordAudioBluetoothRequestFlow @@ -41,14 +41,18 @@ import com.wire.android.util.permission.rememberCallingRecordAudioBluetoothReque @Composable fun JoinButton( buttonClick: () -> Unit, + onPermanentPermissionDecline: () -> Unit, modifier: Modifier = Modifier, minHeight: Dp = MaterialTheme.wireDimensions.buttonMediumMinSize.height, minWidth: Dp = MaterialTheme.wireDimensions.buttonMediumMinSize.width ) { - val audioPermissionCheck = AudioBluetoothPermissionCheckFlow { buttonClick() } + val audioBTPermissionCheck = AudioBluetoothPermissionCheckFlow( + onJoinCall = buttonClick, + onPermanentPermissionDecline = onPermanentPermissionDecline + ) WirePrimaryButton( - onClick = audioPermissionCheck::launch, + onClick = audioBTPermissionCheck::launch, fillMaxWidth = false, shape = RoundedCornerShape(size = MaterialTheme.wireDimensions.corner12x), text = stringResource(R.string.calling_button_label_join_call), @@ -68,19 +72,22 @@ fun JoinButton( @Composable private fun AudioBluetoothPermissionCheckFlow( - onJoinCall: () -> Unit -) = rememberCallingRecordAudioBluetoothRequestFlow(onAudioBluetoothPermissionGranted = { - appLogger.d("Join Call Button - Permissions granted") - onJoinCall() -}) { - appLogger.d("Join Call Button - Permissions denied") - // TODO: Add a message that user needs permission to join call? -} + onJoinCall: () -> Unit, + onPermanentPermissionDecline: () -> Unit +) = rememberCallingRecordAudioBluetoothRequestFlow( + onAudioBluetoothPermissionGranted = { + appLogger.d("IncomingCall - Permissions granted") + onJoinCall() + }, + onAudioBluetoothPermissionDenied = { }, + onAudioBluetoothPermissionPermanentlyDenied = onPermanentPermissionDecline +) @Preview @Composable fun PreviewJoinButton() { JoinButton( - buttonClick = {} + buttonClick = {}, + onPermanentPermissionDecline = {} ) } 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 new file mode 100644 index 00000000000..265a8e28a51 --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/calling/controlbuttons/StartCallButton.kt @@ -0,0 +1,87 @@ +/* + * Wire + * Copyright (C) 2023 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.controlbuttons + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.wire.android.R +import com.wire.android.appLogger +import com.wire.android.model.ClickBlockParams +import com.wire.android.ui.common.button.WireButtonState +import com.wire.android.ui.common.button.WireSecondaryButton +import com.wire.android.ui.theme.wireDimensions +import com.wire.android.util.permission.rememberCallingRecordAudioBluetoothRequestFlow +import com.wire.android.util.ui.PreviewMultipleThemes + +@Composable +fun StartCallButton( + onPhoneButtonClick: () -> Unit, + onPermanentPermissionDecline: () -> Unit, + isCallingEnabled: Boolean +) { + val audioBTPermissionCheck = AudioBluetoothPermissionCheckFlow( + startCall = onPhoneButtonClick, + onPermanentPermissionDecline = onPermanentPermissionDecline + ) + + WireSecondaryButton( + onClick = audioBTPermissionCheck::launch, + leadingIcon = { + Icon( + painter = painterResource(id = R.drawable.ic_phone), + contentDescription = stringResource(R.string.content_description_conversation_phone_icon), + ) + }, + state = if (isCallingEnabled) WireButtonState.Default else WireButtonState.Disabled, + fillMaxWidth = false, + minHeight = MaterialTheme.wireDimensions.spacing32x, + minWidth = MaterialTheme.wireDimensions.spacing40x, + clickBlockParams = ClickBlockParams(blockWhenSyncing = true, blockWhenConnecting = true), + shape = RoundedCornerShape(size = MaterialTheme.wireDimensions.corner12x), + contentPadding = PaddingValues(0.dp) + ) +} + +@Composable +private fun AudioBluetoothPermissionCheckFlow( + startCall: () -> Unit, + onPermanentPermissionDecline: () -> Unit +) = rememberCallingRecordAudioBluetoothRequestFlow( + onAudioBluetoothPermissionGranted = { + appLogger.d("startCall - Permissions granted") + startCall() + }, + onAudioBluetoothPermissionDenied = { }, + onAudioBluetoothPermissionPermanentlyDenied = onPermanentPermissionDecline +) + +@PreviewMultipleThemes +@Composable +fun PreviewStartCallButton() { + StartCallButton( + onPhoneButtonClick = {}, + onPermanentPermissionDecline = {}, + 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 9ff4a0ecaca..4972ff2b9da 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 @@ -35,6 +35,7 @@ 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 @@ -51,6 +52,7 @@ 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.MicrophoneBTPermissionsDeniedDialog import com.wire.android.ui.calling.controlbuttons.AcceptButton import com.wire.android.ui.calling.controlbuttons.CallOptionsControls import com.wire.android.ui.calling.controlbuttons.HangUpButton @@ -60,6 +62,7 @@ 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.rememberCallingRecordAudioBluetoothRequestFlow import com.wire.kalium.logic.data.call.ConversationType import com.wire.kalium.logic.data.id.ConversationId @@ -75,9 +78,19 @@ fun IncomingCallScreen( sharedCallingViewModel: SharedCallingViewModel = hiltViewModel(), incomingCallViewModel: IncomingCallViewModel = hiltViewModel() ) { + val context = LocalContext.current + val audioPermissionCheck = AudioBluetoothPermissionCheckFlow( incomingCallViewModel::acceptCall, - incomingCallViewModel::declineCall + incomingCallViewModel::showPermissionDialog + ) + + MicrophoneBTPermissionsDeniedDialog( + shouldShow = incomingCallViewModel.incomingCallState.shouldShowPermissionDialog, + onDismiss = incomingCallViewModel::dismissPermissionDialog, + onOpenSettings = { + context.openAppInfoScreen() + } ) with(incomingCallViewModel) { @@ -220,16 +233,17 @@ private fun IncomingCallContent( } @Composable -private fun AudioBluetoothPermissionCheckFlow( +fun AudioBluetoothPermissionCheckFlow( onAcceptCall: () -> Unit, - onDeclineCall: () -> Unit -) = rememberCallingRecordAudioBluetoothRequestFlow(onAudioBluetoothPermissionGranted = { - appLogger.d("IncomingCall - Permissions granted") - onAcceptCall() -}) { - appLogger.d("IncomingCall - Permissions denied") - onDeclineCall() -} + onPermanentPermissionDecline: () -> Unit, +) = rememberCallingRecordAudioBluetoothRequestFlow( + onAudioBluetoothPermissionGranted = { + appLogger.d("IncomingCall - Permissions granted") + onAcceptCall() + }, + onAudioBluetoothPermissionDenied = { }, + onAudioBluetoothPermissionPermanentlyDenied = onPermanentPermissionDecline +) @Preview @Composable 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 7d04a5f7b77..bb500429dfc 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,6 +22,7 @@ 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 { 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 a482acea5e9..e6112984ef4 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 @@ -113,6 +113,14 @@ 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/home/conversations/ConversationScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt index 2fa7073d8f4..3553f6455e1 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 @@ -62,6 +62,7 @@ import com.wire.android.media.audiomessage.AudioState import com.wire.android.model.SnackBarMessage import com.wire.android.navigation.NavigationCommand import com.wire.android.navigation.Navigator +import com.wire.android.ui.calling.common.MicrophoneBTPermissionsDeniedDialog import com.wire.android.ui.common.bottomsheet.MenuModalSheetHeader import com.wire.android.ui.common.bottomsheet.MenuModalSheetLayout import com.wire.android.ui.common.dialogs.InvalidLinkDialog @@ -101,9 +102,8 @@ import com.wire.android.ui.home.messagecomposer.MessageComposer import com.wire.android.ui.home.messagecomposer.state.MessageBundle import com.wire.android.ui.home.messagecomposer.state.MessageComposerStateHolder import com.wire.android.ui.home.messagecomposer.state.rememberMessageComposerStateHolder +import com.wire.android.util.extension.openAppInfoScreen import com.wire.android.util.normalizeLink -import com.wire.android.util.permission.CallingAudioRequestFlow -import com.wire.android.util.permission.rememberCallingRecordAudioBluetoothRequestFlow import com.wire.android.util.ui.UIText import com.wire.android.util.ui.openDownloadFolder import com.wire.kalium.logic.NetworkFailure @@ -157,11 +157,6 @@ fun ConversationScreen( modalBottomSheetState = conversationScreenState.modalBottomSheetState ) - val startCallAudioPermissionCheck = StartCallAudioBluetoothPermissionCheckFlow { - conversationCallViewModel.endEstablishedCallIfAny { - navigator.navigate(NavigationCommand(InitiatingCallScreenDestination(conversationCallViewModel.conversationId))) - } - } // 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) } @@ -171,6 +166,7 @@ fun ConversationScreen( conversationInfoViewModel.observeConversationDetails(navigator::navigateBack) } } + val context = LocalContext.current with(conversationCallViewModel) { if (conversationCallViewState.shouldShowJoinAnywayDialog) { @@ -180,6 +176,14 @@ fun ConversationScreen( onConfirm = { joinAnyway { navigator.navigate(NavigationCommand(OngoingCallScreenDestination(it))) } } ) } + + MicrophoneBTPermissionsDeniedDialog( + shouldShow = conversationCallViewState.shouldShowCallingPermissionDialog, + onDismiss = ::dismissCallingPermissionDialog, + onOpenSettings = { + context.openAppInfoScreen() + } + ) } when (showDialog.value) { @@ -252,14 +256,17 @@ fun ConversationScreen( startCallIfPossible( conversationCallViewModel, showDialog, - startCallAudioPermissionCheck, coroutineScope, - conversationInfoViewModel.conversationInfoViewState.conversationType + conversationInfoViewModel.conversationInfoViewState.conversationType, + onOpenInitiatingCallScreen = { + navigator.navigate(NavigationCommand(InitiatingCallScreenDestination(it))) + } ) { navigator.navigate(NavigationCommand(OngoingCallScreenDestination(it))) } }, onJoinCall = { conversationCallViewModel.joinOngoingCall { navigator.navigate(NavigationCommand(OngoingCallScreenDestination(it))) } }, + onPermanentPermissionDecline = conversationCallViewModel::showCallingPermissionDialog, onReactionClick = { messageId, emoji -> conversationMessagesViewModel.toggleReaction(messageId, emoji) }, @@ -392,9 +399,9 @@ fun ConversationScreen( private fun startCallIfPossible( conversationCallViewModel: ConversationCallViewModel, showDialog: MutableState, - startCallAudioPermissionCheck: CallingAudioRequestFlow, coroutineScope: CoroutineScope, conversationType: Conversation.Type, + onOpenInitiatingCallScreen: (ConversationId) -> Unit, onOpenOngoingCallScreen: (ConversationId) -> Unit ) { coroutineScope.launch { @@ -403,7 +410,9 @@ private fun startCallIfPossible( } else { val dialogValue = when (conversationCallViewModel.isConferenceCallingEnabled(conversationType)) { ConferenceCallingResult.Enabled -> { - startCallAudioPermissionCheck.launch() + conversationCallViewModel.endEstablishedCallIfAny { + onOpenInitiatingCallScreen(conversationCallViewModel.conversationId) + } ConversationScreenDialogType.NONE } @@ -421,15 +430,6 @@ private fun startCallIfPossible( } } -@Composable -private fun StartCallAudioBluetoothPermissionCheckFlow( - onStartCall: () -> Unit -) = rememberCallingRecordAudioBluetoothRequestFlow(onAudioBluetoothPermissionGranted = { - onStartCall() -}) { - // TODO display an error dialog -} - @Suppress("LongParameterList") @Composable private fun ConversationScreen( @@ -448,6 +448,7 @@ 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, @@ -521,6 +522,7 @@ private fun ConversationScreen( onPhoneButtonClick = onStartCall, hasOngoingCall = conversationCallViewState.hasOngoingCall, onJoinCallButtonClick = onJoinCall, + onPermanentPermissionDecline = onPermanentPermissionDecline, isInteractionEnabled = messageComposerViewState.value.interactionAvailability == InteractionAvailability.ENABLED ) ConversationBanner(bannerMessage) @@ -820,6 +822,7 @@ fun PreviewConversationScreen() { onImageFullScreenMode = { _, _ -> }, onStartCall = { }, onJoinCall = { }, + onPermanentPermissionDecline = { }, onReactionClick = { _, _ -> }, onChangeAudioPosition = { _, _ -> }, onAudioClick = { }, 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 a522c2ef3dc..4091ae1ae45 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 @@ -46,11 +46,10 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.wire.android.R -import com.wire.android.model.ClickBlockParams import com.wire.android.model.UserAvatarData import com.wire.android.ui.calling.controlbuttons.JoinButton +import com.wire.android.ui.calling.controlbuttons.StartCallButton import com.wire.android.ui.common.UserProfileAvatar -import com.wire.android.ui.common.button.WireButtonState import com.wire.android.ui.common.button.WireSecondaryButton import com.wire.android.ui.common.colorsScheme import com.wire.android.ui.common.conversationColor @@ -81,6 +80,7 @@ fun ConversationScreenTopAppBar( onPhoneButtonClick: () -> Unit, hasOngoingCall: Boolean, onJoinCallButtonClick: () -> Unit, + onPermanentPermissionDecline: () -> Unit, isInteractionEnabled: Boolean, ) { SmallTopAppBar( @@ -146,9 +146,10 @@ fun ConversationScreenTopAppBar( ) } - callControlButton( + CallControlButton( hasOngoingCall = hasOngoingCall, onJoinCallButtonClick = onJoinCallButtonClick, + onPermanentPermissionDecline = onPermanentPermissionDecline, onPhoneButtonClick = onPhoneButtonClick, isCallingEnabled = isInteractionEnabled ) @@ -186,33 +187,24 @@ private fun Avatar( } @Composable -private fun callControlButton( +private fun CallControlButton( hasOngoingCall: Boolean, onJoinCallButtonClick: () -> Unit, + onPermanentPermissionDecline: () -> Unit, onPhoneButtonClick: () -> Unit, isCallingEnabled: Boolean ) { if (hasOngoingCall) { JoinButton( buttonClick = onJoinCallButtonClick, + onPermanentPermissionDecline = onPermanentPermissionDecline, minHeight = MaterialTheme.wireDimensions.spacing28x ) } else { - WireSecondaryButton( - onClick = onPhoneButtonClick, - leadingIcon = { - Icon( - painter = painterResource(id = R.drawable.ic_phone), - contentDescription = stringResource(R.string.content_description_conversation_phone_icon), - ) - }, - state = if (isCallingEnabled) WireButtonState.Default else WireButtonState.Disabled, - fillMaxWidth = false, - minHeight = MaterialTheme.wireDimensions.spacing32x, - minWidth = MaterialTheme.wireDimensions.spacing40x, - clickBlockParams = ClickBlockParams(blockWhenSyncing = true, blockWhenConnecting = true), - shape = RoundedCornerShape(size = MaterialTheme.wireDimensions.corner12x), - contentPadding = PaddingValues(0.dp) + StartCallButton( + onPhoneButtonClick = onPhoneButtonClick, + onPermanentPermissionDecline = onPermanentPermissionDecline, + isCallingEnabled = isCallingEnabled ) } } @@ -236,6 +228,7 @@ fun PreviewConversationScreenTopAppBarLongTitle() { onPhoneButtonClick = {}, hasOngoingCall = false, onJoinCallButtonClick = {}, + onPermanentPermissionDecline = {}, isInteractionEnabled = true ) } @@ -258,6 +251,7 @@ fun PreviewConversationScreenTopAppBarShortTitle() { onPhoneButtonClick = {}, hasOngoingCall = false, onJoinCallButtonClick = {}, + onPermanentPermissionDecline = {}, isInteractionEnabled = true ) } @@ -280,6 +274,7 @@ fun PreviewConversationScreenTopAppBarShortTitleWithOngoingCall() { onPhoneButtonClick = {}, hasOngoingCall = true, onJoinCallButtonClick = {}, + onPermanentPermissionDecline = {}, isInteractionEnabled = true ) } 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 c3e0081b93f..b50bf25d21b 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 @@ -127,6 +127,14 @@ 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 9afe3cf5998..a5ee559de63 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 @@ -23,5 +23,6 @@ package com.wire.android.ui.home.conversations.call data class ConversationCallViewState( val hasOngoingCall: Boolean = false, val hasEstablishedCall: Boolean = false, - val shouldShowJoinAnywayDialog: Boolean = false + val shouldShowJoinAnywayDialog: Boolean = false, + val shouldShowCallingPermissionDialog: Boolean = false ) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListState.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListState.kt index 02e3e7085f4..d672962a602 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListState.kt @@ -41,7 +41,8 @@ data class ConversationListState( val missedCallsCount: Long = 0, val unreadMentionsCount: Long = 0, val hasEstablishedCall: Boolean = false, - val shouldShowJoinAnywayDialog: Boolean = false + val shouldShowJoinAnywayDialog: Boolean = false, + val shouldShowCallingPermissionDialog: Boolean = false ) { fun findConversationById(conversationId: ConversationId): ConversationItem? = foldersWithConversations.values.flatten().firstOrNull { it.conversationId == conversationId } 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 587e274cb01..da76577eb6b 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 @@ -178,6 +178,14 @@ class ConversationListViewModel @Inject constructor( } } + fun showCallingPermissionDialog() { + conversationListState = conversationListState.copy(shouldShowCallingPermissionDialog = true) + } + + fun dismissCallingPermissionDialog() { + conversationListState = conversationListState.copy(shouldShowCallingPermissionDialog = false) + } + // Mateusz : First iteration, just filter stuff // next iteration : SQL- query ? private fun searchConversation(conversationDetails: List, searchQuery: String): List { 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 b8bb0349043..1f8010ef5d6 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 @@ -28,9 +28,11 @@ 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.navigation.NavigationCommand import com.wire.android.navigation.Navigator +import com.wire.android.ui.calling.common.MicrophoneBTPermissionsDeniedDialog 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 @@ -56,6 +58,7 @@ import com.wire.android.ui.home.conversationslist.model.ConversationItem 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.search.SearchConversationScreen +import com.wire.android.util.extension.openAppInfoScreen import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.user.UserId import kotlinx.coroutines.flow.MutableSharedFlow @@ -75,6 +78,15 @@ fun ConversationRouterHomeBridge( isBottomSheetVisible: () -> Boolean ) { val viewModel: ConversationListViewModel = hiltViewModel() + val context = LocalContext.current + + MicrophoneBTPermissionsDeniedDialog( + shouldShow = viewModel.conversationListState.shouldShowCallingPermissionDialog, + onDismiss = viewModel::dismissCallingPermissionDialog, + onOpenSettings = { + context.openAppInfoScreen() + } + ) val conversationRouterHomeState = rememberConversationRouterState( initialConversationItemType = conversationItemType, @@ -187,7 +199,8 @@ fun ConversationRouterHomeBridge( onOpenConversationNotificationsSettings = onEditNotifications, onOpenConversation = onOpenConversation, onOpenUserProfile = onOpenUserProfile, - onJoinedCall = onJoinedCall + onJoinedCall = onJoinedCall, + onPermanentPermissionDecline = viewModel::showCallingPermissionDialog ) ConversationItemType.CALLS -> @@ -197,8 +210,7 @@ fun ConversationRouterHomeBridge( onCallItemClick = onOpenConversation, onEditConversationItem = onEditConversationItem, onOpenUserProfile = onOpenUserProfile, - openConversationNotificationsSettings = onEditNotifications, - onJoinCall = { viewModel.joinOngoingCall(it, onJoinedCall) } + openConversationNotificationsSettings = onEditNotifications ) ConversationItemType.MENTIONS -> @@ -208,8 +220,7 @@ fun ConversationRouterHomeBridge( onMentionItemClick = onOpenConversation, onEditConversationItem = onEditConversationItem, onOpenUserProfile = onOpenUserProfile, - openConversationNotificationsSettings = onEditNotifications, - onJoinCall = { viewModel.joinOngoingCall(it, onJoinedCall) } + openConversationNotificationsSettings = onEditNotifications ) ConversationItemType.SEARCH -> { @@ -221,7 +232,8 @@ fun ConversationRouterHomeBridge( onEditConversation = onEditConversationItem, onOpenUserProfile = onOpenUserProfile, onOpenConversationNotificationsSettings = onEditNotifications, - onJoinCall = { viewModel.joinOngoingCall(it, onJoinedCall) } + onJoinCall = { viewModel.joinOngoingCall(it, onJoinedCall) }, + onPermanentPermissionDecline = viewModel::showCallingPermissionDialog ) } } 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 2e3292dbf20..e090d438ef5 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 @@ -85,7 +85,8 @@ fun AllConversationScreenContent( onOpenConversationNotificationsSettings: (ConversationItem) -> Unit, onOpenConversation: (ConversationId) -> Unit, onOpenUserProfile: (UserId) -> Unit, - onJoinedCall: (ConversationId) -> Unit + onJoinedCall: (ConversationId) -> Unit, + onPermanentPermissionDecline: () -> Unit ) { val lazyListState = rememberLazyListState() val callConversationIdToJoin = remember { mutableStateOf(ConversationId("", "")) } @@ -111,7 +112,8 @@ fun AllConversationScreenContent( onJoinCall = { callConversationIdToJoin.value = it viewModel.joinOngoingCall(it, onJoinedCall) - } + }, + onPermanentPermissionDecline = onPermanentPermissionDecline ) } } @@ -162,7 +164,8 @@ fun PreviewAllConversationScreen() { onOpenConversationNotificationsSettings = {}, onOpenConversation = {}, onOpenUserProfile = {}, - onJoinedCall = {} + onJoinedCall = {}, + onPermanentPermissionDecline = {} ) } @@ -176,7 +179,8 @@ fun ConversationListEmptyStateScreenPreview() { onOpenConversationNotificationsSettings = {}, onOpenConversation = {}, onOpenUserProfile = {}, - onJoinedCall = {} + onJoinedCall = {}, + onPermanentPermissionDecline = {} ) } 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 002a94ba28e..3e2f800a085 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 @@ -65,7 +65,6 @@ fun CallsScreenContent( onEditConversationItem: (ConversationItem) -> Unit, onOpenUserProfile: (UserId) -> Unit, openConversationNotificationsSettings: (ConversationItem) -> Unit, - onJoinCall: (ConversationId) -> Unit ) { val lazyListState = rememberLazyListState() @@ -77,7 +76,6 @@ fun CallsScreenContent( onEditConversationItem = onEditConversationItem, onOpenUserProfile = onOpenUserProfile, openConversationNotificationsSettings = openConversationNotificationsSettings, - onJoinCall = onJoinCall ) } @@ -89,8 +87,7 @@ fun CallContent( onCallItemClick: (ConversationId) -> Unit, onEditConversationItem: (ConversationItem) -> Unit, onOpenUserProfile: (UserId) -> Unit, - openConversationNotificationsSettings: (ConversationItem) -> Unit, - onJoinCall: (ConversationId) -> Unit + openConversationNotificationsSettings: (ConversationItem) -> Unit ) { val context = LocalContext.current LazyColumn( @@ -107,7 +104,8 @@ fun CallContent( openMenu = onEditConversationItem, openUserProfile = onOpenUserProfile, openNotificationsOptions = openConversationNotificationsSettings, - joinCall = onJoinCall, + joinCall = { }, + onPermanentPermissionDecline = {}, searchQuery = "" ) } @@ -122,7 +120,8 @@ fun CallContent( openMenu = onEditConversationItem, openUserProfile = onOpenUserProfile, openNotificationsOptions = openConversationNotificationsSettings, - joinCall = onJoinCall, + joinCall = { }, + onPermanentPermissionDecline = {}, 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 b2530f35355..07c08f5e120 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 @@ -61,7 +61,8 @@ fun ConversationItemFactory( openMenu: (ConversationItem) -> Unit, openUserProfile: (UserId) -> Unit, openNotificationsOptions: (ConversationItem) -> Unit, - joinCall: (ConversationId) -> Unit + joinCall: (ConversationId) -> Unit, + onPermanentPermissionDecline: () -> Unit ) { val onConversationItemClick = remember(conversation) { Clickable( @@ -108,7 +109,8 @@ fun ConversationItemFactory( }, onJoinCallClick = { joinCall(conversation.conversationId) - } + }, + onPermanentPermissionDecline = onPermanentPermissionDecline ) } @@ -122,7 +124,8 @@ private fun GeneralConversationItem( subTitle: @Composable () -> Unit = {}, onConversationItemClick: Clickable, onMutedIconClick: () -> Unit, - onJoinCallClick: () -> Unit + onJoinCallClick: () -> Unit, + onPermanentPermissionDecline: () -> Unit ) { when (conversation) { is ConversationItem.GroupConversation -> { @@ -150,7 +153,10 @@ private fun GeneralConversationItem( trailingIcon = { if (!isSelectable) { if (hasOnGoingCall) { - JoinButton(buttonClick = onJoinCallClick) + JoinButton( + buttonClick = onJoinCallClick, + onPermanentPermissionDecline = onPermanentPermissionDecline + ) } else { Row( modifier = Modifier.padding(horizontal = dimensions().spacing8x), @@ -248,7 +254,7 @@ fun PreviewGroupConversationItemWithUnreadCount() { searchQuery = "", isSelectableItem = false, isChecked = false, - {}, {}, {}, {}, {}, {} + {}, {}, {}, {}, {}, {}, {} ) } @@ -270,7 +276,7 @@ fun PreviewGroupConversationItemWithNoBadges() { searchQuery = "", isSelectableItem = false, isChecked = false, - {}, {}, {}, {}, {}, {} + {}, {}, {}, {}, {}, {}, {} ) } @@ -292,7 +298,7 @@ fun PreviewGroupConversationItemWithMutedBadgeAndUnreadMentionBadge() { searchQuery = "", isSelectableItem = false, isChecked = false, - {}, {}, {}, {}, {}, {} + {}, {}, {}, {}, {}, {}, {} ) } @@ -315,7 +321,7 @@ fun PreviewGroupConversationItemWithOngoingCall() { searchQuery = "", isSelectableItem = false, isChecked = false, - {}, {}, {}, {}, {}, {} + {}, {}, {}, {}, {}, {}, {} ) } @@ -334,7 +340,7 @@ fun PreviewConnectionConversationItemWithReceivedConnectionRequestBadge() { searchQuery = "", isSelectableItem = false, isChecked = false, - {}, {}, {}, {}, {}, {} + {}, {}, {}, {}, {}, {}, {} ) } @@ -353,7 +359,7 @@ fun PreviewConnectionConversationItemWithSentConnectRequestBadge() { searchQuery = "", isSelectableItem = false, isChecked = false, - {}, {}, {}, {}, {}, {} + {}, {}, {}, {}, {}, {}, {} ) } @@ -375,6 +381,6 @@ fun PreviewPrivateConversationItemWithBlockedBadge() { searchQuery = "", isSelectableItem = false, isChecked = false, - {}, {}, {}, {}, {}, {} + {}, {}, {}, {}, {}, {}, {} ) } 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 9a21ae48bc6..c0c54eac768 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 @@ -51,7 +51,8 @@ fun ConversationList( onEditConversation: (ConversationItem) -> Unit, onOpenUserProfile: (UserId) -> Unit, onOpenConversationNotificationsSettings: (ConversationItem) -> Unit, - onJoinCall: (ConversationId) -> Unit + onJoinCall: (ConversationId) -> Unit, + onPermanentPermissionDecline: () -> Unit ) { val context = LocalContext.current @@ -93,7 +94,8 @@ fun ConversationList( openMenu = onEditConversation, openUserProfile = onOpenUserProfile, openNotificationsOptions = onOpenConversationNotificationsSettings, - joinCall = onJoinCall + joinCall = onJoinCall, + onPermanentPermissionDecline = onPermanentPermissionDecline ) } } 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 8bc8a53f162..f64bcb6eade 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 @@ -23,21 +23,21 @@ package com.wire.android.ui.home.conversationslist.mention import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import com.ramcosta.composedestinations.annotation.Destination import com.wire.android.R +import com.wire.android.navigation.HomeNavGraph +import com.wire.android.ui.home.HomeStateHolder +import com.wire.android.ui.home.conversationslist.ConversationItemType +import com.wire.android.ui.home.conversationslist.ConversationRouterHomeBridge import com.wire.android.ui.home.conversationslist.common.ConversationItemFactory import com.wire.android.ui.home.conversationslist.model.ConversationItem import com.wire.android.util.extension.folderWithElements import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.user.UserId -import androidx.compose.foundation.lazy.rememberLazyListState -import com.ramcosta.composedestinations.annotation.Destination -import com.wire.android.navigation.HomeNavGraph -import com.wire.android.ui.home.HomeStateHolder -import com.wire.android.ui.home.conversationslist.ConversationItemType -import com.wire.android.ui.home.conversationslist.ConversationRouterHomeBridge @HomeNavGraph @Destination @@ -64,8 +64,7 @@ fun MentionScreenContent( onMentionItemClick: (ConversationId) -> Unit, onEditConversationItem: (ConversationItem) -> Unit, onOpenUserProfile: (UserId) -> Unit, - openConversationNotificationsSettings: (ConversationItem) -> Unit, - onJoinCall: (ConversationId) -> Unit + openConversationNotificationsSettings: (ConversationItem) -> Unit ) { val lazyListState = rememberLazyListState() @@ -77,7 +76,6 @@ fun MentionScreenContent( onEditConversationItem = onEditConversationItem, onOpenUserProfile = onOpenUserProfile, openConversationNotificationsSettings = openConversationNotificationsSettings, - onJoinCall = onJoinCall ) } @@ -90,7 +88,6 @@ private fun MentionContent( onEditConversationItem: (ConversationItem) -> Unit, onOpenUserProfile: (UserId) -> Unit, openConversationNotificationsSettings: (ConversationItem) -> Unit, - onJoinCall: (ConversationId) -> Unit ) { val context = LocalContext.current LazyColumn( @@ -107,7 +104,8 @@ private fun MentionContent( openMenu = onEditConversationItem, openUserProfile = onOpenUserProfile, openNotificationsOptions = openConversationNotificationsSettings, - joinCall = onJoinCall, + joinCall = {}, + onPermanentPermissionDecline = {}, searchQuery = "" ) } @@ -122,7 +120,8 @@ private fun MentionContent( openMenu = onEditConversationItem, openUserProfile = onOpenUserProfile, openNotificationsOptions = openConversationNotificationsSettings, - joinCall = onJoinCall, + joinCall = {}, + onPermanentPermissionDecline = {}, 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 d349cda9c4b..532dfc1c889 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 @@ -56,7 +56,8 @@ fun SearchConversationScreen( onEditConversation: (ConversationItem) -> Unit, onOpenUserProfile: (UserId) -> Unit, onOpenConversationNotificationsSettings: (ConversationItem) -> Unit, - onJoinCall: (ConversationId) -> Unit + onJoinCall: (ConversationId) -> Unit, + onPermanentPermissionDecline: () -> Unit ) { Box(Modifier.fillMaxSize()) { if (conversationSearchResult.values.isEmpty()) { @@ -69,7 +70,8 @@ fun SearchConversationScreen( onEditConversation = onEditConversation, onOpenUserProfile = onOpenUserProfile, onOpenConversationNotificationsSettings = onOpenConversationNotificationsSettings, - onJoinCall = onJoinCall + onJoinCall = onJoinCall, + onPermanentPermissionDecline = onPermanentPermissionDecline ) } } 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 726de8ff0e2..33be6b87bcd 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 @@ -17,8 +17,6 @@ */ package com.wire.android.ui.home.messagecomposer.recordaudio -import android.content.Intent -import android.provider.Settings import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth @@ -43,6 +41,7 @@ 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.theme.wireColorScheme +import com.wire.android.util.extension.openAppInfoScreen import com.wire.android.util.permission.rememberRecordAudioRequestFlow import com.wire.android.util.ui.KeyboardHeight @@ -145,7 +144,7 @@ fun RecordAudioComponent( dialogState = viewModel.getPermissionsDeniedDialogState(), onDismiss = viewModel::onDismissPermissionsDeniedDialog, onOpenSettings = { - context.startActivity(Intent(Settings.ACTION_SETTINGS)) + context.openAppInfoScreen() } ) 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 087997c0a73..07dd39d782b 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 @@ -410,7 +410,8 @@ private fun ImportMediaContent( onEditConversation = {}, onOpenUserProfile = {}, onOpenConversationNotificationsSettings = {}, - onJoinCall = {} + onJoinCall = {}, + onPermanentPermissionDecline = {} ) } BackHandler(enabled = searchBarState.isSearchActive) { diff --git a/app/src/main/kotlin/com/wire/android/util/extension/OpenAppInfo.kt b/app/src/main/kotlin/com/wire/android/util/extension/OpenAppInfo.kt new file mode 100644 index 00000000000..e7fe962daee --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/util/extension/OpenAppInfo.kt @@ -0,0 +1,33 @@ +/* + * Wire + * Copyright (C) 2023 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.extension + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.provider.Settings + +fun Context.openAppInfoScreen() { + Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + val uri = Uri.fromParts("package", packageName, null) + data = uri + }.let { + startActivity(it) + } +} 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 ba4dc5d1d4a..d95e826e458 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 @@ -28,13 +28,14 @@ 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.appLogger import com.wire.android.util.extension.checkPermission +import com.wire.android.util.extension.getActivity @Composable fun rememberCallingRecordAudioBluetoothRequestFlow( onAudioBluetoothPermissionGranted: () -> Unit, onAudioBluetoothPermissionDenied: () -> Unit, + onAudioBluetoothPermissionPermanentlyDenied: () -> Unit, ): CallingAudioRequestFlow { val context = LocalContext.current @@ -46,7 +47,15 @@ fun rememberCallingRecordAudioBluetoothRequestFlow( if (permissionsGranted) { onAudioBluetoothPermissionGranted() } else { - onAudioBluetoothPermissionDenied() + context.getActivity()?.let { + if (it.shouldShowRequestPermissionRationale(android.Manifest.permission.RECORD_AUDIO) || + it.shouldShowRequestPermissionRationale(android.Manifest.permission.BLUETOOTH_CONNECT) + ) { + onAudioBluetoothPermissionDenied() + } else { + onAudioBluetoothPermissionPermanentlyDenied() + } + } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e4be1186136..3901b294463 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1162,6 +1162,10 @@ File size for audio messages is limited to %1$d MB. You can’t record an audio message during a call. + Call on Wire + Allow Wire access to your microphone and bluetooth to make this call. + Decline + sent an interactive message Conversation Password Enter password 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 8d847a58e3a..4888059073d 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 @@ -189,6 +189,26 @@ 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 3c12dd9fbdd..b6bb160c60d 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 @@ -37,6 +37,7 @@ 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 @@ -143,4 +144,24 @@ 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 0b198a47fe4..2e5b97cf801 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 @@ -228,6 +228,26 @@ 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.conversationListState = + conversationListViewModel.conversationListState.copy(shouldShowCallingPermissionDialog = false) + + conversationListViewModel.showCallingPermissionDialog() + + assertEquals(true, conversationListViewModel.conversationListState.shouldShowCallingPermissionDialog) + } + + @Test + fun `given default permission dialog state, when calling dismissPermissionDialog, then update the state to false`() = runTest { + conversationListViewModel.conversationListState = + conversationListViewModel.conversationListState.copy(shouldShowCallingPermissionDialog = true) + + conversationListViewModel.dismissCallingPermissionDialog() + + assertEquals(false, conversationListViewModel.conversationListState.shouldShowCallingPermissionDialog) + } + companion object { private val conversationId = ConversationId("some_id", "some_domain") private val userId: UserId = UserId("someUser", "some_domain")