1 /*
2  * Copyright (C) 2023 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  *      http://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.android.permissioncontroller.permission.ui.wear.elements
18 
19 import androidx.compose.foundation.layout.fillMaxWidth
20 import androidx.compose.runtime.Composable
21 import androidx.compose.runtime.remember
22 import androidx.compose.ui.Modifier
23 import androidx.compose.ui.platform.LocalConfiguration
24 import androidx.compose.ui.platform.LocalDensity
25 import androidx.compose.ui.res.painterResource
26 import androidx.compose.ui.res.stringResource
27 import androidx.compose.ui.text.rememberTextMeasurer
28 import androidx.compose.ui.text.style.TextAlign
29 import androidx.compose.ui.text.style.TextOverflow
30 import androidx.compose.ui.unit.Constraints
31 import androidx.compose.ui.unit.dp
32 import androidx.wear.compose.foundation.lazy.ScalingLazyListScope
33 import androidx.wear.compose.foundation.lazy.ScalingLazyListState
34 import androidx.wear.compose.material.Icon
35 import androidx.wear.compose.material.LocalTextStyle
36 import androidx.wear.compose.material.MaterialTheme
37 import androidx.wear.compose.material.Text
38 import androidx.wear.compose.material.dialog.Alert
39 import androidx.wear.compose.material.dialog.Dialog
40 import com.android.permissioncontroller.permission.ui.wear.elements.layout.ScalingLazyColumnDefaults
41 import com.android.permissioncontroller.permission.ui.wear.elements.layout.ScalingLazyColumnState
42 import com.android.permissioncontroller.permission.ui.wear.elements.layout.rememberColumnState
43 
44 /**
45  * This component is an alternative to [AlertContent], providing the following:
46  * - a convenient way of passing a title and a message;
47  * - additional content can be specified between the message and the buttons
48  * - default positive and negative buttons;
49  * - wrapped in a [Dialog];
50  */
51 @Composable
AlertDialognull52 fun AlertDialog(
53     message: String,
54     iconRes: Int? = null,
55     onCancelButtonClick: () -> Unit,
56     onOKButtonClick: () -> Unit,
57     showDialog: Boolean,
58     scalingLazyListState: ScalingLazyListState,
59     modifier: Modifier = Modifier,
60     title: String? = null,
61     okButtonContentDescription: String = stringResource(android.R.string.ok),
62     cancelButtonContentDescription: String = stringResource(android.R.string.cancel)
63 ) {
64     Dialog(
65         showDialog = showDialog,
66         onDismissRequest = onCancelButtonClick,
67         scrollState = scalingLazyListState,
68         modifier = modifier
69     ) {
70         AlertContent(
71             title = title,
72             icon = { AlertIcon(iconRes) },
73             message = message,
74             onCancel = onCancelButtonClick,
75             onOk = onOKButtonClick,
76             okButtonContentDescription = okButtonContentDescription,
77             cancelButtonContentDescription = cancelButtonContentDescription
78         )
79     }
80 }
81 
82 /**
83  * This component is an alternative to [Alert], providing the following:
84  * - a convenient way of passing a title and a message;
85  * - default one button;
86  * - wrapped in a [Dialog];
87  */
88 @Composable
SingleButtonAlertDialognull89 fun SingleButtonAlertDialog(
90     message: String,
91     iconRes: Int? = null,
92     onButtonClick: () -> Unit,
93     showDialog: Boolean,
94     scalingLazyListState: ScalingLazyListState,
95     modifier: Modifier = Modifier,
96     title: String? = null,
97     buttonContentDescription: String = stringResource(android.R.string.ok)
98 ) {
99     Dialog(
100         showDialog = showDialog,
101         onDismissRequest = {},
102         scrollState = scalingLazyListState,
103         modifier = modifier
104     ) {
105         AlertContent(
106             title = title,
107             icon = { AlertIcon(iconRes) },
108             message = message,
109             onOk = onButtonClick,
110             okButtonContentDescription = buttonContentDescription
111         )
112     }
113 }
114 
115 @Composable
AlertContentnull116 public fun AlertContent(
117     onCancel: (() -> Unit)? = null,
118     onOk: (() -> Unit)? = null,
119     icon: @Composable (() -> Unit)? = null,
120     title: String? = null,
121     message: String? = null,
122     okButtonContentDescription: String = stringResource(android.R.string.ok),
123     cancelButtonContentDescription: String = stringResource(android.R.string.cancel),
124     state: ScalingLazyColumnState =
125         rememberColumnState(
126             ScalingLazyColumnDefaults.responsive(
127                 additionalPaddingAtBottom = 0.dp,
128             ),
129         ),
130     showPositionIndicator: Boolean = true,
131     content: (ScalingLazyListScope.() -> Unit)? = null,
132 ) {
133     val density = LocalDensity.current
134     val maxScreenWidthPx = with(density) { LocalConfiguration.current.screenWidthDp.dp.toPx() }
135 
136     ResponsiveDialogContent(
137         icon = icon,
138         title =
139             title?.let {
140                 {
141                     Text(
142                         modifier = Modifier.fillMaxWidth(),
143                         text = it,
144                         color = MaterialTheme.colors.onBackground,
145                         textAlign = TextAlign.Center,
146                         maxLines = if (icon == null) 3 else 2,
147                         overflow = TextOverflow.Ellipsis,
148                     )
149                 }
150             },
151         message =
152             message?.let {
153                 {
154                     // Should message be start or center aligned?
155                     val textMeasurer = rememberTextMeasurer()
156                     val textStyle = LocalTextStyle.current
157                     val totalPaddingPercentage =
158                         globalHorizontalPadding + messageExtraHorizontalPadding
159                     val lineCount =
160                         remember(it, density, textStyle, textMeasurer) {
161                             textMeasurer
162                                 .measure(
163                                     text = it,
164                                     style = textStyle,
165                                     constraints =
166                                         Constraints(
167                                             // Available width is reduced by responsive dialog
168                                             // horizontal
169                                             // padding.
170                                             maxWidth =
171                                                 (maxScreenWidthPx *
172                                                         (1f - totalPaddingPercentage * 2f / 100f))
173                                                     .toInt(),
174                                         ),
175                                 )
176                                 .lineCount
177                         }
178                     val textAlign = if (lineCount <= 3) TextAlign.Center else TextAlign.Start
179                     Text(
180                         modifier = Modifier.fillMaxWidth(),
181                         text = it,
182                         color = MaterialTheme.colors.onBackground,
183                         textAlign = textAlign,
184                     )
185                 }
186             },
187         content = content,
188         onOk = onOk,
189         onCancel = onCancel,
190         okButtonContentDescription = okButtonContentDescription,
191         cancelButtonContentDescription = cancelButtonContentDescription,
192         state = state,
193         showPositionIndicator = showPositionIndicator,
194     )
195 }
196 
197 @Composable
AlertIconnull198 private fun AlertIcon(iconRes: Int?) =
199     if (iconRes != null && iconRes != 0) {
200         Icon(painter = painterResource(iconRes), contentDescription = null)
201     } else {
202         null
203     }
204