1 /*
<lambda>null2 * Copyright 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.google.accompanist.permissions
18
19 import androidx.activity.compose.rememberLauncherForActivityResult
20 import androidx.activity.result.ActivityResultLauncher
21 import androidx.activity.result.contract.ActivityResultContracts
22 import androidx.compose.runtime.Composable
23 import androidx.compose.runtime.DisposableEffect
24 import androidx.compose.runtime.Stable
25 import androidx.compose.runtime.derivedStateOf
26 import androidx.compose.runtime.getValue
27 import androidx.compose.runtime.key
28 import androidx.compose.runtime.remember
29 import androidx.compose.ui.platform.LocalContext
30
31 /**
32 * Creates a [MultiplePermissionsState] that is remembered across compositions.
33 *
34 * It's recommended that apps exercise the permissions workflow as described in the
35 * [documentation](https://developer.android.com/training/permissions/requesting#workflow_for_requesting_permissions).
36 *
37 * @param permissions the permissions to control and observe.
38 * @param onPermissionsResult will be called with whether or not the user granted the permissions
39 * after [MultiplePermissionsState.launchMultiplePermissionRequest] is called.
40 */
41 @ExperimentalPermissionsApi
42 @Composable
43 internal fun rememberMutableMultiplePermissionsState(
44 permissions: List<String>,
45 onPermissionsResult: (Map<String, Boolean>) -> Unit = {}
46 ): MultiplePermissionsState {
47 // Create mutable permissions that can be requested individually
48 val mutablePermissions = rememberMutablePermissionsState(permissions)
49 // Refresh permissions when the lifecycle is resumed.
50 PermissionsLifecycleCheckerEffect(mutablePermissions)
51
<lambda>null52 val multiplePermissionsState = remember(permissions) {
53 MutableMultiplePermissionsState(mutablePermissions)
54 }
55
56 // Remember RequestMultiplePermissions launcher and assign it to multiplePermissionsState
57 val launcher = rememberLauncherForActivityResult(
58 ActivityResultContracts.RequestMultiplePermissions()
permissionsResultnull59 ) { permissionsResult ->
60 multiplePermissionsState.updatePermissionsStatus(permissionsResult)
61 onPermissionsResult(permissionsResult)
62 }
<lambda>null63 DisposableEffect(multiplePermissionsState, launcher) {
64 multiplePermissionsState.launcher = launcher
65 onDispose {
66 multiplePermissionsState.launcher = null
67 }
68 }
69
70 return multiplePermissionsState
71 }
72
73 @ExperimentalPermissionsApi
74 @Composable
rememberMutablePermissionsStatenull75 private fun rememberMutablePermissionsState(
76 permissions: List<String>
77 ): List<MutablePermissionState> {
78 // Create list of MutablePermissionState for each permission
79 val context = LocalContext.current
80 val activity = context.findActivity()
81 val mutablePermissions: List<MutablePermissionState> = remember(permissions) {
82 return@remember permissions.map { MutablePermissionState(it, context, activity) }
83 }
84 // Update each permission with its own launcher
85 for (permissionState in mutablePermissions) {
86 key(permissionState.permission) {
87 // Remember launcher and assign it to the permissionState
88 val launcher = rememberLauncherForActivityResult(
89 ActivityResultContracts.RequestPermission()
90 ) {
91 permissionState.refreshPermissionStatus()
92 }
93 DisposableEffect(launcher) {
94 permissionState.launcher = launcher
95 onDispose {
96 permissionState.launcher = null
97 }
98 }
99 }
100 }
101
102 return mutablePermissions
103 }
104
105 /**
106 * A state object that can be hoisted to control and observe multiple permission status changes.
107 *
108 * In most cases, this will be created via [rememberMutableMultiplePermissionsState].
109 *
110 * @param mutablePermissions list of mutable permissions to control and observe.
111 */
112 @ExperimentalPermissionsApi
113 @Stable
114 internal class MutableMultiplePermissionsState(
115 private val mutablePermissions: List<MutablePermissionState>
116 ) : MultiplePermissionsState {
117
118 override val permissions: List<PermissionState> = mutablePermissions
119
<lambda>null120 override val revokedPermissions: List<PermissionState> by derivedStateOf {
121 permissions.filter { it.status != PermissionStatus.Granted }
122 }
123
<lambda>null124 override val allPermissionsGranted: Boolean by derivedStateOf {
125 permissions.all { it.status.isGranted } || // Up to date when the lifecycle is resumed
126 revokedPermissions.isEmpty() // Up to date when the user launches the action
127 }
128
<lambda>null129 override val shouldShowRationale: Boolean by derivedStateOf {
130 permissions.any { it.status.shouldShowRationale }
131 }
132
launchMultiplePermissionRequestnull133 override fun launchMultiplePermissionRequest() {
134 launcher?.launch(
135 permissions.map { it.permission }.toTypedArray()
136 ) ?: throw IllegalStateException("ActivityResultLauncher cannot be null")
137 }
138
139 internal var launcher: ActivityResultLauncher<Array<String>>? = null
140
updatePermissionsStatusnull141 internal fun updatePermissionsStatus(permissionsStatus: Map<String, Boolean>) {
142 // Update all permissions with the result
143 for (permission in permissionsStatus.keys) {
144 mutablePermissions.firstOrNull { it.permission == permission }?.apply {
145 permissionsStatus[permission]?.let {
146 this.refreshPermissionStatus()
147 }
148 }
149 }
150 }
151 }
152