Skip to content

Commit d043566

Browse files
authored
Merge pull request #556 from code-payments/fix/permissions-handle-rationale
chore: properly handle permission rationale presence for permanently …
2 parents 1c5179d + 9637e96 commit d043566

9 files changed

Lines changed: 190 additions & 92 deletions

File tree

app/src/main/java/com/getcode/Session.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ import com.getcode.network.repository.hexEncodedString
8181
import com.getcode.network.repository.toPublicKey
8282
import com.getcode.solana.organizer.GiftCardAccount
8383
import com.getcode.solana.organizer.Organizer
84+
import com.getcode.ui.components.PermissionResult
8485
import com.getcode.util.CurrencyUtils
8586
import com.getcode.util.IntentUtils
8687
import com.getcode.util.Kin
@@ -465,8 +466,8 @@ class Session @Inject constructor(
465466
uiFlow.update { it.copy(isCameraScanEnabled = scanning) }
466467
}
467468

468-
fun onCameraPermissionChanged(isGranted: Boolean) {
469-
uiFlow.update { it.copy(isCameraPermissionGranted = isGranted) }
469+
fun onCameraPermissionResult(result: PermissionResult) {
470+
uiFlow.update { it.copy(isCameraPermissionGranted = result == PermissionResult.Granted) }
470471
}
471472

472473
fun showBill(

app/src/main/java/com/getcode/ui/components/PermissionCheck.kt

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,85 @@
11
package com.getcode.ui.components
22

3+
import android.app.Activity
34
import android.content.Context
45
import android.content.pm.PackageManager
56
import androidx.activity.compose.ManagedActivityResultLauncher
67
import androidx.activity.compose.rememberLauncherForActivityResult
78
import androidx.activity.result.contract.ActivityResultContracts
89
import androidx.compose.runtime.Composable
10+
import androidx.compose.runtime.remember
11+
import androidx.compose.ui.platform.LocalContext
12+
import androidx.core.app.ActivityCompat
913
import androidx.core.content.ContextCompat
14+
import com.getcode.ui.utils.getActivity
15+
16+
enum class PermissionResult {
17+
Granted, Denied, ShouldShowRationale
18+
}
1019

1120
typealias PermissionsLauncher = ManagedActivityResultLauncher<String, Boolean>
21+
1222
@Composable
13-
fun getPermissionLauncher(onPermissionResult: (isGranted: Boolean) -> Unit) =
14-
rememberLauncherForActivityResult(
23+
fun getPermissionLauncher(
24+
permission: String,
25+
onPermissionResult: (result: PermissionResult) -> Unit
26+
): PermissionsLauncher {
27+
val context = LocalContext.current
28+
val activity = context as Activity
29+
30+
val launcher = rememberLauncherForActivityResult(
1531
ActivityResultContracts.RequestPermission()
1632
) { isGranted: Boolean ->
17-
onPermissionResult(isGranted)
33+
// This block will be triggered after the user chooses to grant or deny the permission
34+
// and we can tell if the user permanently declines or if we need to show rational
35+
val permissionPermanentlyDenied = !ActivityCompat.shouldShowRequestPermissionRationale(
36+
activity, permission
37+
) && !isGranted
38+
39+
when {
40+
permissionPermanentlyDenied -> {
41+
onPermissionResult(PermissionResult.ShouldShowRationale)
42+
}
43+
!isGranted -> onPermissionResult(PermissionResult.Denied)
44+
else -> onPermissionResult(PermissionResult.Granted)
45+
}
1846
}
1947

20-
object PermissionCheck {
21-
fun requestPermission(
22-
context: Context,
48+
return launcher
49+
}
50+
51+
@Composable
52+
fun rememberPermissionChecker(): PermissionChecker {
53+
val context = LocalContext.current
54+
return remember(context) {
55+
PermissionChecker(context)
56+
}
57+
}
58+
59+
class PermissionChecker(private val context: Context) {
60+
fun request(
2361
permission: String,
24-
shouldRequest: Boolean,
25-
onPermissionResult: (isGranted: Boolean) -> Unit,
26-
launcher: PermissionsLauncher
62+
shouldRequest: Boolean = true,
63+
launcher: PermissionsLauncher,
64+
onPermissionResult: (result: PermissionResult) -> Unit = { },
2765
) {
66+
val activity = context.getActivity()
67+
2868
when (ContextCompat.checkSelfPermission(context, permission)) {
2969
PackageManager.PERMISSION_GRANTED -> {
30-
onPermissionResult(true)
70+
onPermissionResult(PermissionResult.Granted)
3171
}
3272
PackageManager.PERMISSION_DENIED -> {
3373
if (shouldRequest) {
3474
launcher.launch(permission)
3575
} else {
36-
onPermissionResult(false)
76+
if (activity != null) {
77+
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
78+
onPermissionResult(PermissionResult.ShouldShowRationale)
79+
return
80+
}
81+
}
82+
onPermissionResult(PermissionResult.Denied)
3783
}
3884
}
3985
}

app/src/main/java/com/getcode/view/login/AccessKey.kt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,16 @@ import com.getcode.navigation.core.LocalCodeNavigator
5252
import com.getcode.navigation.screens.LoginArgs
5353
import com.getcode.theme.CodeTheme
5454
import com.getcode.theme.White
55-
import com.getcode.ui.utils.measured
56-
import com.getcode.ui.components.SelectionContainer
5755
import com.getcode.ui.components.ButtonState
5856
import com.getcode.ui.components.Cloudy
5957
import com.getcode.ui.components.CodeButton
60-
import com.getcode.ui.components.PermissionCheck
58+
import com.getcode.ui.components.PermissionResult
59+
import com.getcode.ui.components.SelectionContainer
6160
import com.getcode.ui.components.getPermissionLauncher
61+
import com.getcode.ui.components.rememberPermissionChecker
6262
import com.getcode.ui.components.rememberSelectionState
6363
import com.getcode.ui.utils.addIf
64+
import com.getcode.ui.utils.measured
6465
import com.getcode.util.launchAppSettings
6566

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

83-
val onPermissionResult = { isSuccess: Boolean ->
84-
isStoragePermissionGranted = isSuccess
84+
val onPermissionResult = { result: PermissionResult ->
85+
isStoragePermissionGranted = result == PermissionResult.Granted
8586

8687
if (!isStoragePermissionGranted) {
8788
TopBarManager.showMessage(
@@ -96,7 +97,8 @@ fun AccessKey(
9697
}
9798
}
9899

99-
val launcher = getPermissionLauncher(onPermissionResult)
100+
val launcher = getPermissionLauncher(Manifest.permission.WRITE_EXTERNAL_STORAGE, onPermissionResult)
101+
val permissionChecker = rememberPermissionChecker()
100102

101103
if (isExportSeedRequested && isStoragePermissionGranted) {
102104
viewModel.onSubmit(navigator, true)
@@ -109,10 +111,8 @@ fun AccessKey(
109111
if (Build.VERSION.SDK_INT > 29) {
110112
isStoragePermissionGranted = true
111113
} else {
112-
PermissionCheck.requestPermission(
113-
context = context,
114+
permissionChecker.request(
114115
permission = Manifest.permission.WRITE_EXTERNAL_STORAGE,
115-
shouldRequest = true,
116116
onPermissionResult = onPermissionResult,
117117
launcher = launcher
118118
)

app/src/main/java/com/getcode/view/login/CameraPermissionCheck.kt

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
package com.getcode.view.login
22

33
import android.Manifest
4-
import androidx.compose.runtime.*
4+
import androidx.compose.runtime.Composable
5+
import androidx.compose.runtime.getValue
6+
import androidx.compose.runtime.mutableStateOf
7+
import androidx.compose.runtime.remember
8+
import androidx.compose.runtime.setValue
59
import androidx.compose.ui.platform.LocalContext
6-
import com.getcode.App
710
import com.getcode.R
811
import com.getcode.manager.TopBarManager
9-
import com.getcode.ui.components.PermissionCheck
12+
import com.getcode.ui.components.PermissionResult
1013
import com.getcode.ui.components.getPermissionLauncher
14+
import com.getcode.ui.components.rememberPermissionChecker
1115
import com.getcode.util.launchAppSettings
1216

1317
@Composable
1418
fun cameraPermissionCheck(isShowError: Boolean = true, onResult: (Boolean) -> Unit): (Boolean) -> Unit {
19+
val permissionChecker = rememberPermissionChecker()
1520
val context = LocalContext.current
1621
var permissionRequested by remember { mutableStateOf(false) }
1722
val onPermissionError = {
@@ -25,18 +30,18 @@ fun cameraPermissionCheck(isShowError: Boolean = true, onResult: (Boolean) -> Un
2530
)
2631
)
2732
}
28-
val onPermissionResult = { isGranted: Boolean ->
33+
val onPermissionResult = { result: PermissionResult ->
34+
val isGranted = result == PermissionResult.Granted
2935
onResult(isGranted)
3036
if (!isGranted && permissionRequested && isShowError) {
3137
onPermissionError()
3238
}
3339
Unit
3440
}
35-
val launcher = getPermissionLauncher(onPermissionResult)
41+
val launcher = getPermissionLauncher(Manifest.permission.CAMERA, onPermissionResult)
3642
val permissionCheck = { shouldRequest: Boolean ->
3743
permissionRequested = shouldRequest
38-
PermissionCheck.requestPermission(
39-
context = context,
44+
permissionChecker.request(
4045
permission = Manifest.permission.CAMERA,
4146
shouldRequest = shouldRequest,
4247
onPermissionResult = onPermissionResult,

app/src/main/java/com/getcode/view/login/NotificationPermissionCheck.kt

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,37 @@ package com.getcode.view.login
22

33
import android.Manifest
44
import android.os.Build
5-
import androidx.compose.runtime.*
5+
import androidx.annotation.RequiresApi
6+
import androidx.compose.runtime.Composable
7+
import androidx.compose.runtime.getValue
8+
import androidx.compose.runtime.mutableStateOf
9+
import androidx.compose.runtime.remember
10+
import androidx.compose.runtime.setValue
611
import androidx.compose.ui.platform.LocalContext
7-
import com.getcode.App
812
import com.getcode.R
913
import com.getcode.manager.TopBarManager
10-
import com.getcode.ui.components.PermissionCheck
14+
import com.getcode.ui.components.PermissionResult
1115
import com.getcode.ui.components.getPermissionLauncher
16+
import com.getcode.ui.components.rememberPermissionChecker
1217
import com.getcode.util.launchAppSettings
1318

1419
@Composable
15-
fun notificationPermissionCheck(isShowError: Boolean = true, onResult: (Boolean) -> Unit): (Boolean) -> Unit {
20+
fun notificationPermissionCheck(isShowError: Boolean = true, onResult: (Boolean) -> Unit): (shouldRequest: Boolean) -> Unit {
21+
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
22+
notificationPermissionCheckApi33(isShowError, onResult)
23+
} else {
24+
notificationPermissionCheckApiLegacy(isShowError, onResult)
25+
}
26+
}
27+
28+
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
29+
@Composable
30+
private fun notificationPermissionCheckApi33(
31+
isShowError: Boolean = true,
32+
onResult: (Boolean) -> Unit
33+
): (shouldRequest: Boolean) -> Unit {
1634
val context = LocalContext.current
35+
val permissionChecker = rememberPermissionChecker()
1736
var permissionRequested by remember { mutableStateOf(false) }
1837
val onPermissionError = {
1938
TopBarManager.showMessage(
@@ -26,29 +45,65 @@ fun notificationPermissionCheck(isShowError: Boolean = true, onResult: (Boolean)
2645
)
2746
)
2847
}
29-
val onPermissionResult = { isGranted: Boolean ->
48+
val onPermissionResult = { result: PermissionResult ->
49+
val isGranted = result == PermissionResult.Granted
3050
onResult(isGranted)
3151
if (!isGranted && permissionRequested && isShowError) {
3252
onPermissionError()
3353
}
3454
Unit
3555
}
36-
val launcher = getPermissionLauncher(onPermissionResult)
56+
57+
val launcher = getPermissionLauncher(
58+
Manifest.permission.POST_NOTIFICATIONS, onPermissionResult
59+
)
60+
3761
val permissionCheck = { shouldRequest: Boolean ->
3862
if (Build.VERSION.SDK_INT < 33) {
39-
onPermissionResult(true)
63+
onPermissionResult(PermissionResult.Granted)
4064
} else {
4165
permissionRequested = shouldRequest
42-
PermissionCheck.requestPermission(
43-
context = context,
66+
permissionChecker.request(
4467
permission = Manifest.permission.POST_NOTIFICATIONS,
4568
shouldRequest = shouldRequest,
4669
onPermissionResult = onPermissionResult,
4770
launcher = launcher
4871
)
4972
}
73+
}
74+
75+
return permissionCheck
76+
}
5077

78+
@Composable
79+
private fun notificationPermissionCheckApiLegacy(
80+
isShowError: Boolean = true,
81+
onResult: (Boolean) -> Unit
82+
): (shouldRequest: Boolean) -> Unit {
83+
val context = LocalContext.current
84+
var permissionRequested by remember { mutableStateOf(false) }
85+
val onPermissionError = {
86+
TopBarManager.showMessage(
87+
TopBarManager.TopBarMessage(
88+
title = context.getString(R.string.action_allowPushNotifications),
89+
message = context.getString(R.string.permissions_description_push),
90+
type = TopBarManager.TopBarMessageType.ERROR,
91+
secondaryText = context.getString(R.string.action_openSettings),
92+
secondaryAction = { context.launchAppSettings() }
93+
)
94+
)
95+
}
96+
val onPermissionResult = { result: PermissionResult ->
97+
val isGranted = result == PermissionResult.Granted
98+
onResult(isGranted)
99+
if (!isGranted && permissionRequested && isShowError) {
100+
onPermissionError()
101+
}
102+
Unit
103+
}
51104

105+
val permissionCheck = { _: Boolean ->
106+
onPermissionResult(PermissionResult.Granted)
52107
}
53108

54109
return permissionCheck

app/src/main/java/com/getcode/view/login/SeedInput.kt

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@ fun SeedInput(
4444
val focusManager = LocalFocusManager.current
4545
val focusRequester = FocusRequester()
4646

47-
val context = LocalContext.current
48-
val launcher = getPermissionLauncher {}
47+
val notificationPermissionCheck = notificationPermissionCheck(isShowError = false) { }
4948

5049
Column(
5150
modifier = Modifier
@@ -123,13 +122,7 @@ fun SeedInput(
123122
}
124123

125124
if (dataState.isSuccess) {
126-
PermissionCheck.requestPermission(
127-
context = context,
128-
permission = Manifest.permission.POST_NOTIFICATIONS,
129-
shouldRequest = true,
130-
onPermissionResult = {},
131-
launcher = launcher
132-
)
125+
notificationPermissionCheck(true)
133126
}
134127

135128
CodeButton(

0 commit comments

Comments
 (0)