Skip to content

Commit

Permalink
Merge pull request #556 from code-payments/fix/permissions-handle-rat…
Browse files Browse the repository at this point in the history
…ionale

chore: properly handle permission rationale presence for permanently …
  • Loading branch information
bmc08gt authored Sep 10, 2024
2 parents 1c5179d + 9637e96 commit d043566
Show file tree
Hide file tree
Showing 9 changed files with 190 additions and 92 deletions.
5 changes: 3 additions & 2 deletions app/src/main/java/com/getcode/Session.kt
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ import com.getcode.network.repository.hexEncodedString
import com.getcode.network.repository.toPublicKey
import com.getcode.solana.organizer.GiftCardAccount
import com.getcode.solana.organizer.Organizer
import com.getcode.ui.components.PermissionResult
import com.getcode.util.CurrencyUtils
import com.getcode.util.IntentUtils
import com.getcode.util.Kin
Expand Down Expand Up @@ -465,8 +466,8 @@ class Session @Inject constructor(
uiFlow.update { it.copy(isCameraScanEnabled = scanning) }
}

fun onCameraPermissionChanged(isGranted: Boolean) {
uiFlow.update { it.copy(isCameraPermissionGranted = isGranted) }
fun onCameraPermissionResult(result: PermissionResult) {
uiFlow.update { it.copy(isCameraPermissionGranted = result == PermissionResult.Granted) }
}

fun showBill(
Expand Down
68 changes: 57 additions & 11 deletions app/src/main/java/com/getcode/ui/components/PermissionCheck.kt
Original file line number Diff line number Diff line change
@@ -1,39 +1,85 @@
package com.getcode.ui.components

import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
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 androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.getcode.ui.utils.getActivity

enum class PermissionResult {
Granted, Denied, ShouldShowRationale
}

typealias PermissionsLauncher = ManagedActivityResultLauncher<String, Boolean>

@Composable
fun getPermissionLauncher(onPermissionResult: (isGranted: Boolean) -> Unit) =
rememberLauncherForActivityResult(
fun getPermissionLauncher(
permission: String,
onPermissionResult: (result: PermissionResult) -> Unit
): PermissionsLauncher {
val context = LocalContext.current
val activity = context as Activity

val launcher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
onPermissionResult(isGranted)
// This block will be triggered after the user chooses to grant or deny the permission
// and we can tell if the user permanently declines or if we need to show rational
val permissionPermanentlyDenied = !ActivityCompat.shouldShowRequestPermissionRationale(
activity, permission
) && !isGranted

when {
permissionPermanentlyDenied -> {
onPermissionResult(PermissionResult.ShouldShowRationale)
}
!isGranted -> onPermissionResult(PermissionResult.Denied)
else -> onPermissionResult(PermissionResult.Granted)
}
}

object PermissionCheck {
fun requestPermission(
context: Context,
return launcher
}

@Composable
fun rememberPermissionChecker(): PermissionChecker {
val context = LocalContext.current
return remember(context) {
PermissionChecker(context)
}
}

class PermissionChecker(private val context: Context) {
fun request(
permission: String,
shouldRequest: Boolean,
onPermissionResult: (isGranted: Boolean) -> Unit,
launcher: PermissionsLauncher
shouldRequest: Boolean = true,
launcher: PermissionsLauncher,
onPermissionResult: (result: PermissionResult) -> Unit = { },
) {
val activity = context.getActivity()

when (ContextCompat.checkSelfPermission(context, permission)) {
PackageManager.PERMISSION_GRANTED -> {
onPermissionResult(true)
onPermissionResult(PermissionResult.Granted)
}
PackageManager.PERMISSION_DENIED -> {
if (shouldRequest) {
launcher.launch(permission)
} else {
onPermissionResult(false)
if (activity != null) {
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
onPermissionResult(PermissionResult.ShouldShowRationale)
return
}
}
onPermissionResult(PermissionResult.Denied)
}
}
}
Expand Down
18 changes: 9 additions & 9 deletions app/src/main/java/com/getcode/view/login/AccessKey.kt
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,16 @@ import com.getcode.navigation.core.LocalCodeNavigator
import com.getcode.navigation.screens.LoginArgs
import com.getcode.theme.CodeTheme
import com.getcode.theme.White
import com.getcode.ui.utils.measured
import com.getcode.ui.components.SelectionContainer
import com.getcode.ui.components.ButtonState
import com.getcode.ui.components.Cloudy
import com.getcode.ui.components.CodeButton
import com.getcode.ui.components.PermissionCheck
import com.getcode.ui.components.PermissionResult
import com.getcode.ui.components.SelectionContainer
import com.getcode.ui.components.getPermissionLauncher
import com.getcode.ui.components.rememberPermissionChecker
import com.getcode.ui.components.rememberSelectionState
import com.getcode.ui.utils.addIf
import com.getcode.ui.utils.measured
import com.getcode.util.launchAppSettings

@OptIn(ExperimentalComposeUiApi::class)
Expand All @@ -80,8 +81,8 @@ fun AccessKey(
var isStoragePermissionGranted by remember { mutableStateOf(false) }
val isAccessKeyVisible = remember { MutableTransitionState(false) }

val onPermissionResult = { isSuccess: Boolean ->
isStoragePermissionGranted = isSuccess
val onPermissionResult = { result: PermissionResult ->
isStoragePermissionGranted = result == PermissionResult.Granted

if (!isStoragePermissionGranted) {
TopBarManager.showMessage(
Expand All @@ -96,7 +97,8 @@ fun AccessKey(
}
}

val launcher = getPermissionLauncher(onPermissionResult)
val launcher = getPermissionLauncher(Manifest.permission.WRITE_EXTERNAL_STORAGE, onPermissionResult)
val permissionChecker = rememberPermissionChecker()

if (isExportSeedRequested && isStoragePermissionGranted) {
viewModel.onSubmit(navigator, true)
Expand All @@ -109,10 +111,8 @@ fun AccessKey(
if (Build.VERSION.SDK_INT > 29) {
isStoragePermissionGranted = true
} else {
PermissionCheck.requestPermission(
context = context,
permissionChecker.request(
permission = Manifest.permission.WRITE_EXTERNAL_STORAGE,
shouldRequest = true,
onPermissionResult = onPermissionResult,
launcher = launcher
)
Expand Down
19 changes: 12 additions & 7 deletions app/src/main/java/com/getcode/view/login/CameraPermissionCheck.kt
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
package com.getcode.view.login

import android.Manifest
import androidx.compose.runtime.*
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.compose.ui.platform.LocalContext
import com.getcode.App
import com.getcode.R
import com.getcode.manager.TopBarManager
import com.getcode.ui.components.PermissionCheck
import com.getcode.ui.components.PermissionResult
import com.getcode.ui.components.getPermissionLauncher
import com.getcode.ui.components.rememberPermissionChecker
import com.getcode.util.launchAppSettings

@Composable
fun cameraPermissionCheck(isShowError: Boolean = true, onResult: (Boolean) -> Unit): (Boolean) -> Unit {
val permissionChecker = rememberPermissionChecker()
val context = LocalContext.current
var permissionRequested by remember { mutableStateOf(false) }
val onPermissionError = {
Expand All @@ -25,18 +30,18 @@ fun cameraPermissionCheck(isShowError: Boolean = true, onResult: (Boolean) -> Un
)
)
}
val onPermissionResult = { isGranted: Boolean ->
val onPermissionResult = { result: PermissionResult ->
val isGranted = result == PermissionResult.Granted
onResult(isGranted)
if (!isGranted && permissionRequested && isShowError) {
onPermissionError()
}
Unit
}
val launcher = getPermissionLauncher(onPermissionResult)
val launcher = getPermissionLauncher(Manifest.permission.CAMERA, onPermissionResult)
val permissionCheck = { shouldRequest: Boolean ->
permissionRequested = shouldRequest
PermissionCheck.requestPermission(
context = context,
permissionChecker.request(
permission = Manifest.permission.CAMERA,
shouldRequest = shouldRequest,
onPermissionResult = onPermissionResult,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,37 @@ package com.getcode.view.login

import android.Manifest
import android.os.Build
import androidx.compose.runtime.*
import androidx.annotation.RequiresApi
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.compose.ui.platform.LocalContext
import com.getcode.App
import com.getcode.R
import com.getcode.manager.TopBarManager
import com.getcode.ui.components.PermissionCheck
import com.getcode.ui.components.PermissionResult
import com.getcode.ui.components.getPermissionLauncher
import com.getcode.ui.components.rememberPermissionChecker
import com.getcode.util.launchAppSettings

@Composable
fun notificationPermissionCheck(isShowError: Boolean = true, onResult: (Boolean) -> Unit): (Boolean) -> Unit {
fun notificationPermissionCheck(isShowError: Boolean = true, onResult: (Boolean) -> Unit): (shouldRequest: Boolean) -> Unit {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
notificationPermissionCheckApi33(isShowError, onResult)
} else {
notificationPermissionCheckApiLegacy(isShowError, onResult)
}
}

@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@Composable
private fun notificationPermissionCheckApi33(
isShowError: Boolean = true,
onResult: (Boolean) -> Unit
): (shouldRequest: Boolean) -> Unit {
val context = LocalContext.current
val permissionChecker = rememberPermissionChecker()
var permissionRequested by remember { mutableStateOf(false) }
val onPermissionError = {
TopBarManager.showMessage(
Expand All @@ -26,29 +45,65 @@ fun notificationPermissionCheck(isShowError: Boolean = true, onResult: (Boolean)
)
)
}
val onPermissionResult = { isGranted: Boolean ->
val onPermissionResult = { result: PermissionResult ->
val isGranted = result == PermissionResult.Granted
onResult(isGranted)
if (!isGranted && permissionRequested && isShowError) {
onPermissionError()
}
Unit
}
val launcher = getPermissionLauncher(onPermissionResult)

val launcher = getPermissionLauncher(
Manifest.permission.POST_NOTIFICATIONS, onPermissionResult
)

val permissionCheck = { shouldRequest: Boolean ->
if (Build.VERSION.SDK_INT < 33) {
onPermissionResult(true)
onPermissionResult(PermissionResult.Granted)
} else {
permissionRequested = shouldRequest
PermissionCheck.requestPermission(
context = context,
permissionChecker.request(
permission = Manifest.permission.POST_NOTIFICATIONS,
shouldRequest = shouldRequest,
onPermissionResult = onPermissionResult,
launcher = launcher
)
}
}

return permissionCheck
}

@Composable
private fun notificationPermissionCheckApiLegacy(
isShowError: Boolean = true,
onResult: (Boolean) -> Unit
): (shouldRequest: Boolean) -> Unit {
val context = LocalContext.current
var permissionRequested by remember { mutableStateOf(false) }
val onPermissionError = {
TopBarManager.showMessage(
TopBarManager.TopBarMessage(
title = context.getString(R.string.action_allowPushNotifications),
message = context.getString(R.string.permissions_description_push),
type = TopBarManager.TopBarMessageType.ERROR,
secondaryText = context.getString(R.string.action_openSettings),
secondaryAction = { context.launchAppSettings() }
)
)
}
val onPermissionResult = { result: PermissionResult ->
val isGranted = result == PermissionResult.Granted
onResult(isGranted)
if (!isGranted && permissionRequested && isShowError) {
onPermissionError()
}
Unit
}

val permissionCheck = { _: Boolean ->
onPermissionResult(PermissionResult.Granted)
}

return permissionCheck
Expand Down
11 changes: 2 additions & 9 deletions app/src/main/java/com/getcode/view/login/SeedInput.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ fun SeedInput(
val focusManager = LocalFocusManager.current
val focusRequester = FocusRequester()

val context = LocalContext.current
val launcher = getPermissionLauncher {}
val notificationPermissionCheck = notificationPermissionCheck(isShowError = false) { }

Column(
modifier = Modifier
Expand Down Expand Up @@ -123,13 +122,7 @@ fun SeedInput(
}

if (dataState.isSuccess) {
PermissionCheck.requestPermission(
context = context,
permission = Manifest.permission.POST_NOTIFICATIONS,
shouldRequest = true,
onPermissionResult = {},
launcher = launcher
)
notificationPermissionCheck(true)
}

CodeButton(
Expand Down
Loading

0 comments on commit d043566

Please sign in to comment.