Handle permanently denied camera permission with Settings redirect
Detect when camera permission is permanently denied (not granted and rationale not shown) and offer an "Open Settings" button instead of a non-functional "Grant" button. Use centralized AppColors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
527c8fd0bb
commit
ee2b11941a
1 changed files with 43 additions and 10 deletions
|
|
@ -1,7 +1,10 @@
|
||||||
package no.naiv.tiltshift
|
package no.naiv.tiltshift
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.provider.Settings
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
|
@ -25,6 +28,7 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
|
@ -38,7 +42,9 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
import com.google.accompanist.permissions.isGranted
|
import com.google.accompanist.permissions.isGranted
|
||||||
import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
||||||
import com.google.accompanist.permissions.rememberPermissionState
|
import com.google.accompanist.permissions.rememberPermissionState
|
||||||
|
import com.google.accompanist.permissions.shouldShowRationale
|
||||||
import no.naiv.tiltshift.ui.CameraScreen
|
import no.naiv.tiltshift.ui.CameraScreen
|
||||||
|
import no.naiv.tiltshift.ui.theme.AppColors
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
|
|
@ -96,12 +102,17 @@ private fun TiltShiftApp() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
|
// Permanently denied: not granted AND rationale not shown
|
||||||
|
val cameraPermanentlyDenied = !cameraPermission.status.isGranted &&
|
||||||
|
!cameraPermission.status.shouldShowRationale
|
||||||
|
|
||||||
// Show permission request UI
|
// Show permission request UI
|
||||||
PermissionRequestScreen(
|
PermissionRequestScreen(
|
||||||
onRequestCamera = { cameraPermission.launchPermissionRequest() },
|
onRequestCamera = { cameraPermission.launchPermissionRequest() },
|
||||||
onRequestLocation = { locationPermissions.launchMultiplePermissionRequest() },
|
onRequestLocation = { locationPermissions.launchMultiplePermissionRequest() },
|
||||||
cameraGranted = cameraPermission.status.isGranted,
|
cameraGranted = cameraPermission.status.isGranted,
|
||||||
locationGranted = locationPermissions.allPermissionsGranted
|
locationGranted = locationPermissions.allPermissionsGranted,
|
||||||
|
cameraPermanentlyDenied = cameraPermanentlyDenied
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -113,7 +124,8 @@ private fun PermissionRequestScreen(
|
||||||
onRequestCamera: () -> Unit,
|
onRequestCamera: () -> Unit,
|
||||||
onRequestLocation: () -> Unit,
|
onRequestLocation: () -> Unit,
|
||||||
cameraGranted: Boolean,
|
cameraGranted: Boolean,
|
||||||
locationGranted: Boolean
|
locationGranted: Boolean,
|
||||||
|
cameraPermanentlyDenied: Boolean
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -146,6 +158,7 @@ private fun PermissionRequestScreen(
|
||||||
title = "Camera",
|
title = "Camera",
|
||||||
description = "Required to take photos",
|
description = "Required to take photos",
|
||||||
isGranted = cameraGranted,
|
isGranted = cameraGranted,
|
||||||
|
isPermanentlyDenied = cameraPermanentlyDenied,
|
||||||
onRequest = onRequestCamera
|
onRequest = onRequestCamera
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -168,8 +181,10 @@ private fun PermissionItem(
|
||||||
title: String,
|
title: String,
|
||||||
description: String,
|
description: String,
|
||||||
isGranted: Boolean,
|
isGranted: Boolean,
|
||||||
|
isPermanentlyDenied: Boolean = false,
|
||||||
onRequest: () -> Unit
|
onRequest: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(RoundedCornerShape(16.dp))
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
|
@ -182,7 +197,7 @@ private fun PermissionItem(
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = icon,
|
imageVector = icon,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = if (isGranted) Color(0xFF4CAF50) else Color(0xFFFFB300),
|
tint = if (isGranted) AppColors.Success else AppColors.Accent,
|
||||||
modifier = Modifier.padding(bottom = 8.dp)
|
modifier = Modifier.padding(bottom = 8.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -201,13 +216,31 @@ private fun PermissionItem(
|
||||||
|
|
||||||
if (!isGranted) {
|
if (!isGranted) {
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
Button(
|
if (isPermanentlyDenied) {
|
||||||
onClick = onRequest,
|
Button(
|
||||||
colors = ButtonDefaults.buttonColors(
|
onClick = {
|
||||||
containerColor = Color(0xFFFFB300)
|
context.startActivity(
|
||||||
)
|
Intent(
|
||||||
) {
|
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
|
||||||
Text("Grant", color = Color.Black)
|
Uri.fromParts("package", context.packageName, null)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = AppColors.Accent
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text("Open Settings", color = Color.Black)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Button(
|
||||||
|
onClick = onRequest,
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = AppColors.Accent
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text("Grant", color = Color.Black)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue