mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-08 23:54:39 +00:00
Merge pull request #2942 from vitorpamplona/claude/fix-call-button-permission-63OLL
Handle denied call permissions with settings dialog
This commit is contained in:
@@ -22,13 +22,24 @@ package com.vitorpamplona.amethyst.ui.call
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
|
||||
fun hasPermission(
|
||||
context: Context,
|
||||
@@ -55,6 +66,17 @@ fun buildCallPermissions(isVideo: Boolean): Array<String> {
|
||||
return permissions.toTypedArray()
|
||||
}
|
||||
|
||||
fun openAppSettings(context: Context) {
|
||||
runCatching {
|
||||
context.startActivity(
|
||||
Intent(
|
||||
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
|
||||
Uri.fromParts("package", context.packageName, null),
|
||||
).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberCallWithPermission(
|
||||
context: Context,
|
||||
@@ -62,23 +84,50 @@ fun rememberCallWithPermission(
|
||||
onCall: () -> Unit,
|
||||
): () -> Unit {
|
||||
val permissions = remember(isVideo) { buildCallPermissions(isVideo) }
|
||||
var showDeniedDialog by remember { mutableStateOf(false) }
|
||||
|
||||
val launcher =
|
||||
rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.RequestMultiplePermissions(),
|
||||
) { _ ->
|
||||
// Bluetooth is optional — proceed if core permissions are granted
|
||||
if (hasCallPermissions(context, isVideo)) onCall()
|
||||
// Bluetooth is optional — proceed if core permissions are granted.
|
||||
// If core permissions are still denied (including the silent
|
||||
// permanently-denied case where Android skips the dialog), surface
|
||||
// the deep-link dialog instead of failing silently.
|
||||
if (hasCallPermissions(context, isVideo)) {
|
||||
onCall()
|
||||
} else {
|
||||
showDeniedDialog = true
|
||||
}
|
||||
}
|
||||
|
||||
val bluetoothLauncher =
|
||||
rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.RequestMultiplePermissions(),
|
||||
) { _ ->
|
||||
// BT result is best-effort and never blocks the call.
|
||||
}
|
||||
|
||||
if (showDeniedDialog) {
|
||||
CallPermissionDeniedDialog(
|
||||
isVideo = isVideo,
|
||||
onDismiss = { showDeniedDialog = false },
|
||||
onOpenSettings = {
|
||||
showDeniedDialog = false
|
||||
openAppSettings(context)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return remember(onCall, isVideo) {
|
||||
{
|
||||
if (hasCallPermissions(context, isVideo)) {
|
||||
// Core permissions granted; still request BT if missing
|
||||
// Core permissions granted; request BT separately so the
|
||||
// result callback doesn't double-fire onCall().
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
|
||||
!hasPermission(context, Manifest.permission.BLUETOOTH_CONNECT)
|
||||
) {
|
||||
launcher.launch(arrayOf(Manifest.permission.BLUETOOTH_CONNECT))
|
||||
bluetoothLauncher.launch(arrayOf(Manifest.permission.BLUETOOTH_CONNECT))
|
||||
}
|
||||
onCall()
|
||||
} else {
|
||||
@@ -87,3 +136,36 @@ fun rememberCallWithPermission(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CallPermissionDeniedDialog(
|
||||
isVideo: Boolean,
|
||||
onDismiss: () -> Unit,
|
||||
onOpenSettings: () -> Unit,
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text(stringRes(R.string.call_permission_denied_title)) },
|
||||
text = {
|
||||
Text(
|
||||
stringRes(
|
||||
if (isVideo) {
|
||||
R.string.call_permission_denied_video
|
||||
} else {
|
||||
R.string.call_permission_denied_voice
|
||||
},
|
||||
),
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = onOpenSettings) {
|
||||
Text(stringRes(R.string.call_permission_denied_open_settings))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(stringRes(R.string.call_permission_denied_cancel))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1172,6 +1172,11 @@
|
||||
<string name="call_failed_start">Failed to start call</string>
|
||||
<string name="call_failed_accept">Failed to accept call</string>
|
||||
<string name="call_failed_session">Failed to create call session</string>
|
||||
<string name="call_permission_denied_title">Permission needed</string>
|
||||
<string name="call_permission_denied_voice">Amethyst needs microphone access to start a voice call. Please enable it in the app settings.</string>
|
||||
<string name="call_permission_denied_video">Amethyst needs camera and microphone access to start a video call. Please enable them in the app settings.</string>
|
||||
<string name="call_permission_denied_open_settings">Open settings</string>
|
||||
<string name="call_permission_denied_cancel">Cancel</string>
|
||||
<string name="call_settings">Call Settings</string>
|
||||
<string name="call_settings_enable_calls">Enable voice and video calls</string>
|
||||
<string name="call_settings_enable_calls_description">When disabled, call buttons are hidden from chat screens and all incoming calls are silently ignored.</string>
|
||||
|
||||
Reference in New Issue
Block a user