summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/LocationProviderDialogScreen.kt10
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearGrantPermissionsScreen.kt37
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/AnnotatedText.kt18
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Button.kt133
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt38
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChip.kt43
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChipToggleControl.kt39
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButton.kt120
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButtonStyle.kt74
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionIconBuilder.kt101
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionScaffold.kt298
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionToggleControl.kt165
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionToggleControlStyle.kt158
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearComposeMaterial3ColorScheme.kt32
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearMaterialBridgedLegacyTheme.kt82
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearOverlayableMaterial3Theme.kt38
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTheme.kt54
17 files changed, 1208 insertions, 232 deletions
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/LocationProviderDialogScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/LocationProviderDialogScreen.kt
index 6af62e01f..510d19706 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/LocationProviderDialogScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/LocationProviderDialogScreen.kt
@@ -27,8 +27,9 @@ import androidx.wear.compose.material.ChipDefaults
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.SwipeToDismissBox
import com.android.permissioncontroller.permission.ui.wear.elements.Chip
-import com.android.permissioncontroller.permission.ui.wear.elements.Scaffold
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionScaffold
import com.android.permissioncontroller.permission.ui.wear.model.LocationProviderInterceptDialogArgs
+import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion
@Composable
fun LocationProviderDialogScreen(args: LocationProviderInterceptDialogArgs?) {
@@ -41,7 +42,8 @@ fun LocationProviderDialogScreen(args: LocationProviderInterceptDialogArgs?) {
}
}
SwipeToDismissBox(state = state) { isBackground ->
- Scaffold(
+ WearPermissionScaffold(
+ materialUIVersion = WearPermissionMaterialUIVersion.MATERIAL2_5,
showTimeText = false,
image = iconId,
title = stringResource(titleId),
@@ -54,7 +56,7 @@ fun LocationProviderDialogScreen(args: LocationProviderInterceptDialogArgs?) {
onClick = onLocationSettingsClick,
modifier = Modifier.fillMaxWidth(),
textColor = MaterialTheme.colors.surface,
- colors = ChipDefaults.primaryChipColors()
+ colors = ChipDefaults.primaryChipColors(),
)
}
item {
@@ -64,7 +66,7 @@ fun LocationProviderDialogScreen(args: LocationProviderInterceptDialogArgs?) {
modifier = Modifier.fillMaxWidth(),
)
}
- }
+ },
)
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearGrantPermissionsScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearGrantPermissionsScreen.kt
index 950353f52..50a19e571 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearGrantPermissionsScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearGrantPermissionsScreen.kt
@@ -22,6 +22,7 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.stringResource
+import com.android.permission.flags.Flags
import com.android.permissioncontroller.R
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_ALWAYS_BUTTON
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_BUTTON
@@ -37,17 +38,19 @@ import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.N
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_OT_BUTTON
import com.android.permissioncontroller.permission.ui.wear.GrantPermissionsWearViewHandler.BUTTON_RES_ID_TO_NUM
-import com.android.permissioncontroller.permission.ui.wear.elements.Chip
import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen
-import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChip
import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChipToggleControl
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionButton
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionToggleControl
import com.android.permissioncontroller.permission.ui.wear.model.WearGrantPermissionsViewModel
+import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL2_5
+import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL3
@Composable
fun WearGrantPermissionsScreen(
viewModel: WearGrantPermissionsViewModel,
onButtonClicked: (Int) -> Unit,
- onLocationSwitchChanged: (Boolean) -> Unit
+ onLocationSwitchChanged: (Boolean) -> Unit,
) {
val groupMessage = viewModel.groupMessageLiveData.observeAsState("")
val icon = viewModel.iconLiveData.observeAsState(null)
@@ -55,8 +58,16 @@ fun WearGrantPermissionsScreen(
val locationVisibilities = viewModel.locationVisibilitiesLiveData.observeAsState(emptyList())
val preciseLocationChecked = viewModel.preciseLocationCheckedLiveData.observeAsState(false)
val buttonVisibilities = viewModel.buttonVisibilitiesLiveData.observeAsState(emptyList())
+ val useMaterial3Controls = Flags.wearComposeMaterial3()
+ val materialUIVersion =
+ if (useMaterial3Controls) {
+ MATERIAL3
+ } else {
+ MATERIAL2_5
+ }
ScrollableScreen(
+ materialUIVersion = materialUIVersion,
showTimeText = false,
image = icon.value,
title = groupMessage.value,
@@ -69,13 +80,14 @@ fun WearGrantPermissionsScreen(
locationVisibilities.value.getOrElse(DIALOG_WITH_BOTH_LOCATIONS) { false }
) {
item {
- ToggleChip(
+ WearPermissionToggleControl(
checked = preciseLocationChecked.value,
- onCheckedChanged = { onLocationSwitchChanged(it) },
+ onCheckedChanged = onLocationSwitchChanged,
label = stringResource(R.string.app_permission_location_accuracy),
toggleControl = ToggleChipToggleControl.Switch,
modifier = Modifier.fillMaxWidth(),
- labelMaxLine = Integer.MAX_VALUE
+ labelMaxLines = Integer.MAX_VALUE,
+ materialUIVersion = materialUIVersion,
)
}
}
@@ -87,16 +99,17 @@ fun WearGrantPermissionsScreen(
}
if (buttonVisibilities.value[pos]) {
item {
- Chip(
+ WearPermissionButton(
label =
getPrimaryText(
- pos,
- locationVisibilities.value,
- labelsByButton(BUTTON_RES_ID_TO_NUM.valueAt(i))
+ pos = pos,
+ locationVisibilities = locationVisibilities.value,
+ default = labelsByButton(BUTTON_RES_ID_TO_NUM.valueAt(i)),
),
onClick = { onButtonClicked(BUTTON_RES_ID_TO_NUM.keyAt(i)) },
modifier = Modifier.fillMaxWidth(),
- labelMaxLines = Integer.MAX_VALUE
+ labelMaxLines = Integer.MAX_VALUE,
+ materialUIVersion = materialUIVersion,
)
}
}
@@ -108,7 +121,7 @@ fun setContent(
composeView: ComposeView,
viewModel: WearGrantPermissionsViewModel,
onButtonClicked: (Int) -> Unit,
- onLocationSwitchChanged: (Boolean) -> Unit
+ onLocationSwitchChanged: (Boolean) -> Unit,
) {
composeView.setContent {
WearGrantPermissionsScreen(viewModel, onButtonClicked, onLocationSwitchChanged)
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/AnnotatedText.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/AnnotatedText.kt
index 34c7cee9a..07bb88e80 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/AnnotatedText.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/AnnotatedText.kt
@@ -41,7 +41,7 @@ fun AnnotatedText(
text: CharSequence,
style: TextStyle,
modifier: Modifier = Modifier,
- shouldCapitalize: Boolean
+ shouldCapitalize: Boolean,
) {
val onClickCallbacks = mutableMapOf<String, (View) -> Unit>()
val context = LocalContext.current
@@ -56,7 +56,7 @@ fun AnnotatedText(
text,
shouldCapitalize,
onClickCallbacks,
- listener = listener
+ listener = listener,
)
BasicText(text = annotatedString, style = style, modifier = modifier)
}
@@ -67,7 +67,7 @@ private fun spannableStringToAnnotatedString(
shouldCapitalize: Boolean,
onClickCallbacks: MutableMap<String, (View) -> Unit>,
spanColor: Color = MaterialTheme.colors.primary,
- listener: LinkInteractionListener
+ listener: LinkInteractionListener,
): AnnotatedString {
val finalString = if (shouldCapitalize) text.toString().capitalize() else text.toString()
val annotatedString =
@@ -85,14 +85,14 @@ private fun spannableStringToAnnotatedString(
start,
end,
onClickCallbacks,
- listener
+ listener,
)
else -> addStyle(SpanStyle(), start, end)
}
}
}
} else {
- AnnotatedString(text.toString())
+ AnnotatedString(finalString)
}
return annotatedString
}
@@ -103,14 +103,10 @@ private fun AnnotatedString.Builder.addClickableSpan(
start: Int,
end: Int,
onClickCallbacks: MutableMap<String, (View) -> Unit>,
- listener: LinkInteractionListener
+ listener: LinkInteractionListener,
) {
val key = "${CLICKABLE_SPAN_TAG}:$start:$end"
onClickCallbacks[key] = span::onClick
addLink(LinkAnnotation.Clickable(key, linkInteractionListener = listener), start, end)
- addStyle(
- SpanStyle(color = spanColor, textDecoration = TextDecoration.Underline),
- start,
- end,
- )
+ addStyle(SpanStyle(color = spanColor, textDecoration = TextDecoration.Underline), start, end)
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Button.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Button.kt
deleted file mode 100644
index 1394c56ea..000000000
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Button.kt
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.permissioncontroller.permission.ui.wear.elements
-
-import androidx.annotation.DrawableRes
-import androidx.compose.foundation.layout.size
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.unit.Dp
-import androidx.wear.compose.material.Button
-import androidx.wear.compose.material.ButtonColors
-import androidx.wear.compose.material.ButtonDefaults
-import androidx.wear.compose.material.ButtonDefaults.DefaultButtonSize
-import androidx.wear.compose.material.ButtonDefaults.DefaultIconSize
-import androidx.wear.compose.material.ButtonDefaults.LargeButtonSize
-import androidx.wear.compose.material.ButtonDefaults.LargeIconSize
-import androidx.wear.compose.material.ButtonDefaults.SmallButtonSize
-import androidx.wear.compose.material.ButtonDefaults.SmallIconSize
-
-/**
- * This component is an alternative to [Button], providing the following:
- * - a convenient way of providing an icon and choosing its size from a range of sizes recommended
- * by the Wear guidelines;
- */
-@Composable
-public fun Button(
- imageVector: ImageVector,
- contentDescription: String,
- onClick: () -> Unit,
- modifier: Modifier = Modifier,
- colors: ButtonColors = ButtonDefaults.primaryButtonColors(),
- buttonSize: ButtonSize = ButtonSize.Default,
- iconRtlMode: IconRtlMode = IconRtlMode.Default,
- enabled: Boolean = true
-) {
- Button(
- icon = imageVector,
- contentDescription = contentDescription,
- onClick = onClick,
- modifier = modifier,
- colors = colors,
- buttonSize = buttonSize,
- iconRtlMode = iconRtlMode,
- enabled = enabled
- )
-}
-
-/**
- * This component is an alternative to [Button], providing the following:
- * - a convenient way of providing an icon and choosing its size from a range of sizes recommended
- * by the Wear guidelines;
- */
-@Composable
-public fun Button(
- @DrawableRes id: Int,
- contentDescription: String,
- onClick: () -> Unit,
- modifier: Modifier = Modifier,
- colors: ButtonColors = ButtonDefaults.primaryButtonColors(),
- buttonSize: ButtonSize = ButtonSize.Default,
- iconRtlMode: IconRtlMode = IconRtlMode.Default,
- enabled: Boolean = true
-) {
- Button(
- icon = id,
- contentDescription = contentDescription,
- onClick = onClick,
- modifier = modifier,
- colors = colors,
- buttonSize = buttonSize,
- iconRtlMode = iconRtlMode,
- enabled = enabled
- )
-}
-
-@Composable
-internal fun Button(
- icon: Any,
- contentDescription: String,
- onClick: () -> Unit,
- modifier: Modifier = Modifier,
- colors: ButtonColors = ButtonDefaults.primaryButtonColors(),
- buttonSize: ButtonSize = ButtonSize.Default,
- iconRtlMode: IconRtlMode = IconRtlMode.Default,
- enabled: Boolean = true
-) {
- Button(
- onClick = onClick,
- modifier = modifier.size(buttonSize.tapTargetSize),
- enabled = enabled,
- colors = colors
- ) {
- val iconModifier = Modifier.size(buttonSize.iconSize).align(Alignment.Center)
-
- Icon(
- icon = icon,
- contentDescription = contentDescription,
- modifier = iconModifier,
- rtlMode = iconRtlMode
- )
- }
-}
-
-public sealed class ButtonSize(public val iconSize: Dp, public val tapTargetSize: Dp) {
- public object Default :
- ButtonSize(iconSize = DefaultIconSize, tapTargetSize = DefaultButtonSize)
-
- public object Large : ButtonSize(iconSize = LargeIconSize, tapTargetSize = LargeButtonSize)
- public object Small : ButtonSize(iconSize = SmallIconSize, tapTargetSize = SmallButtonSize)
-
- /**
- * Custom sizes should follow the
- * [accessibility principles and guidance for touch targets](https://developer.android.com/training/wearables/accessibility#set-minimum).
- */
- public data class Custom(val customIconSize: Dp, val customTapTargetSize: Dp) :
- ButtonSize(iconSize = customIconSize, tapTargetSize = customTapTargetSize)
-}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt
index d8f340a7b..d1b7e899b 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt
@@ -63,7 +63,10 @@ import androidx.wear.compose.material.TimeText
import androidx.wear.compose.material.Vignette
import androidx.wear.compose.material.VignettePosition
import androidx.wear.compose.material.scrollAway
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionScaffold
import com.android.permissioncontroller.permission.ui.wear.elements.rotaryinput.rotaryWithScroll
+import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion
+import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL2_5
import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionTheme
/**
@@ -74,6 +77,7 @@ import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionT
*/
@Composable
fun ScrollableScreen(
+ materialUIVersion: WearPermissionMaterialUIVersion = MATERIAL2_5,
showTimeText: Boolean = true,
title: String? = null,
subtitle: CharSequence? = null,
@@ -103,7 +107,8 @@ fun ScrollableScreen(
if (getBackStackEntryCount(activity) > 0) {
SwipeToDismissBox(state = state) { isBackground ->
- Scaffold(
+ WearPermissionScaffold(
+ materialUIVersion,
showTimeText,
title,
subtitle,
@@ -111,11 +116,12 @@ fun ScrollableScreen(
isLoading = isLoading || isBackground || dismissed,
content,
titleTestTag,
- subtitleTestTag
+ subtitleTestTag,
)
}
} else {
- Scaffold(
+ WearPermissionScaffold(
+ materialUIVersion,
showTimeText,
title,
subtitle,
@@ -123,13 +129,13 @@ fun ScrollableScreen(
isLoading,
content,
titleTestTag,
- subtitleTestTag
+ subtitleTestTag,
)
}
}
@Composable
-internal fun Scaffold(
+internal fun Wear2Scaffold(
showTimeText: Boolean,
title: String?,
subtitle: CharSequence?,
@@ -165,14 +171,14 @@ internal fun Scaffold(
start = titleHorizontalPadding,
top = 4.dp,
bottom = titleBottomPadding,
- end = titleHorizontalPadding
+ end = titleHorizontalPadding,
)
val subTitlePaddingValues =
PaddingValues(
start = subtitleHorizontalPadding,
top = 4.dp,
bottom = subtitleBottomPadding,
- end = subtitleHorizontalPadding
+ end = subtitleHorizontalPadding,
)
val initialCenterIndex = 0
val centerHeightDp = Dp(LocalConfiguration.current.screenHeightDp / 2.0f)
@@ -191,14 +197,14 @@ internal fun Scaffold(
modifier =
Modifier.rotaryWithScroll(
scrollableState = listState,
- focusRequester = focusRequester
+ focusRequester = focusRequester,
),
timeText = {
if (showTimeText && !isLoading) {
TimeText(
modifier =
Modifier.scrollAway(listState, initialCenterIndex, scrollAwayOffset)
- .padding(top = timeTextTopPadding),
+ .padding(top = timeTextTopPadding)
)
}
},
@@ -208,7 +214,7 @@ internal fun Scaffold(
{ PositionIndicator(scalingLazyListState = listState) }
} else {
null
- }
+ },
) {
Box(modifier = Modifier.fillMaxSize()) {
if (isLoading) {
@@ -225,8 +231,8 @@ internal fun Scaffold(
start = scrollContentHorizontalPadding,
end = scrollContentHorizontalPadding,
top = scrollContentTopPadding,
- bottom = scrollContentBottomPadding
- )
+ bottom = scrollContentBottomPadding,
+ ),
) {
staticItem()
image?.let {
@@ -238,7 +244,7 @@ internal fun Scaffold(
painter = painterResource(id = image),
contentDescription = null,
contentScale = ContentScale.Crop,
- modifier = imageModifier
+ modifier = imageModifier,
)
}
is Drawable ->
@@ -247,7 +253,7 @@ internal fun Scaffold(
painter = rememberDrawablePainter(image),
contentDescription = null,
contentScale = ContentScale.Crop,
- modifier = imageModifier
+ modifier = imageModifier,
)
}
else -> {}
@@ -263,7 +269,7 @@ internal fun Scaffold(
Text(
text = title,
textAlign = TextAlign.Center,
- modifier = modifier
+ modifier = modifier,
)
}
}
@@ -282,7 +288,7 @@ internal fun Scaffold(
color = MaterialTheme.colors.onSurfaceVariant
),
modifier = modifier,
- shouldCapitalize = true
+ shouldCapitalize = true,
)
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChip.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChip.kt
index a21a9d015..4f4201748 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChip.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChip.kt
@@ -29,11 +29,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.compositeOver
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.semantics.Role
-import androidx.compose.ui.semantics.role
-import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.wear.compose.material.ChipDefaults
@@ -44,7 +39,6 @@ import androidx.wear.compose.material.ToggleChip
import androidx.wear.compose.material.ToggleChipColors
import androidx.wear.compose.material.ToggleChipDefaults
import androidx.wear.compose.material.contentColorFor
-import com.android.permissioncontroller.R
/**
* This component is an alternative to [ToggleChip], providing the following:
@@ -67,7 +61,7 @@ fun ToggleChip(
secondaryLabelMaxLine: Int? = null,
colors: ToggleChipColors = ToggleChipDefaults.toggleChipColors(),
enabled: Boolean = true,
- interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
+ interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
) {
val hasSecondaryLabel = secondaryLabel != null
@@ -78,7 +72,7 @@ fun ToggleChip(
textAlign = TextAlign.Start,
overflow = TextOverflow.Ellipsis,
maxLines = labelMaxLine ?: if (hasSecondaryLabel) 1 else 2,
- style = MaterialTheme.typography.button
+ style = MaterialTheme.typography.button,
)
}
@@ -89,7 +83,7 @@ fun ToggleChip(
text = secondaryLabel,
overflow = TextOverflow.Ellipsis,
maxLines = secondaryLabelMaxLine ?: 1,
- style = MaterialTheme.typography.caption2
+ style = MaterialTheme.typography.caption2,
)
}
}
@@ -110,7 +104,7 @@ fun ToggleChip(
IconRtlMode.Mirrored
} else {
IconRtlMode.Default
- }
+ },
)
}
@@ -123,42 +117,23 @@ fun ToggleChip(
tint = iconColor,
contentDescription = null,
modifier = Modifier.size(ChipDefaults.IconSize).clip(CircleShape),
- rtlMode = iconRtlMode
+ rtlMode = iconRtlMode,
)
}
}
}
- val semanticsRole =
- when (toggleControl) {
- ToggleChipToggleControl.Switch -> Role.Switch
- ToggleChipToggleControl.Radio -> Role.RadioButton
- ToggleChipToggleControl.Checkbox -> Role.Checkbox
- }
-
- val stateDescriptionSemantics =
- stringResource(
- if (checked) {
- R.string.on
- } else {
- R.string.off
- }
- )
ToggleChip(
checked = checked,
onCheckedChange = onCheckedChanged,
label = labelParam,
toggleControl = toggleControlParam,
- modifier =
- modifier.fillMaxWidth().semantics {
- role = semanticsRole
- stateDescription = stateDescriptionSemantics
- },
+ modifier = modifier.fillMaxWidth().toggleControlSemantics(toggleControl, checked),
appIcon = iconParam,
secondaryLabel = secondaryLabelParam,
colors = colors,
enabled = enabled,
- interactionSource = interactionSource
+ interactionSource = interactionSource,
)
}
@@ -198,7 +173,7 @@ fun toggleChipDisabledColors(): ToggleChipColors {
uncheckedSecondaryContentColor =
uncheckedSecondaryContentColor.copy(alpha = ContentAlpha.disabled),
uncheckedToggleControlColor =
- uncheckedToggleControlColor.copy(alpha = ContentAlpha.disabled)
+ uncheckedToggleControlColor.copy(alpha = ContentAlpha.disabled),
)
}
@@ -236,6 +211,6 @@ fun toggleChipBackgroundColors(): ToggleChipColors {
uncheckedEndBackgroundColor = uncheckedEndBackgroundColor,
uncheckedContentColor = uncheckedContentColor,
uncheckedSecondaryContentColor = uncheckedSecondaryContentColor,
- uncheckedToggleControlColor = uncheckedToggleControlColor
+ uncheckedToggleControlColor = uncheckedToggleControlColor,
)
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChipToggleControl.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChipToggleControl.kt
index a4ce4e764..b6f6db4d3 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChipToggleControl.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChipToggleControl.kt
@@ -16,8 +16,43 @@
package com.android.permissioncontroller.permission.ui.wear.elements
-public enum class ToggleChipToggleControl {
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.stateDescription
+import com.android.permissioncontroller.R
+
+enum class ToggleChipToggleControl {
Switch,
Radio,
- Checkbox
+ Checkbox,
+}
+
+@Composable
+fun Modifier.toggleControlSemantics(
+ toggleControl: ToggleChipToggleControl,
+ checked: Boolean,
+): Modifier {
+ val semanticsRole =
+ when (toggleControl) {
+ ToggleChipToggleControl.Switch -> Role.Switch
+ ToggleChipToggleControl.Radio -> Role.RadioButton
+ ToggleChipToggleControl.Checkbox -> Role.Checkbox
+ }
+ val stateDescriptionSemantics =
+ stringResource(
+ if (checked) {
+ R.string.on
+ } else {
+ R.string.off
+ }
+ )
+
+ return semantics {
+ role = semanticsRole
+ stateDescription = stateDescriptionSemantics
+ }
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButton.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButton.kt
new file mode 100644
index 000000000..4ed9e92b9
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButton.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.permissioncontroller.permission.ui.wear.elements.material3
+
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.wear.compose.material3.Button
+import androidx.wear.compose.material3.ButtonColors
+import androidx.wear.compose.material3.ButtonDefaults
+import androidx.wear.compose.material3.LocalTextConfiguration
+import androidx.wear.compose.material3.Text
+import com.android.permissioncontroller.permission.ui.wear.elements.Chip
+import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion
+
+/**
+ * This component is wrapper on material Button component
+ * 1. It takes icon, primary, secondary label resources and construct them applying permission app
+ * defaults
+ */
+@Composable
+fun WearPermissionButton(
+ label: String,
+ modifier: Modifier = Modifier,
+ materialUIVersion: WearPermissionMaterialUIVersion =
+ WearPermissionMaterialUIVersion.MATERIAL2_5,
+ iconBuilder: WearPermissionIconBuilder? = null,
+ labelMaxLines: Int? = null,
+ secondaryLabel: String? = null,
+ secondaryLabelMaxLines: Int? = null,
+ onClick: () -> Unit,
+ enabled: Boolean = true,
+ style: WearPermissionButtonStyle = WearPermissionButtonStyle.Secondary,
+) {
+ if (materialUIVersion == WearPermissionMaterialUIVersion.MATERIAL2_5) {
+ Chip(
+ label = label,
+ labelMaxLines = labelMaxLines,
+ onClick = onClick,
+ modifier = modifier,
+ secondaryLabel = secondaryLabel,
+ secondaryLabelMaxLines = secondaryLabelMaxLines,
+ icon = { iconBuilder?.build() },
+ largeIcon = false,
+ colors = style.material2ChipColors(),
+ enabled = enabled,
+ )
+ } else {
+ WearPermissionButtonInternal(
+ iconBuilder = iconBuilder,
+ label = label,
+ labelMaxLines = labelMaxLines,
+ secondaryLabel = secondaryLabel,
+ secondaryLabelMaxLines = secondaryLabelMaxLines,
+ onClick = onClick,
+ modifier = modifier,
+ enabled = enabled,
+ colors = style.material3ButtonColors(),
+ )
+ }
+}
+
+@Composable
+private fun WearPermissionButtonInternal(
+ label: String,
+ modifier: Modifier = Modifier,
+ iconBuilder: WearPermissionIconBuilder? = null,
+ labelMaxLines: Int? = null,
+ secondaryLabel: String? = null,
+ secondaryLabelMaxLines: Int? = null,
+ onClick: () -> Unit,
+ enabled: Boolean = true,
+ colors: ButtonColors = ButtonDefaults.filledTonalButtonColors(),
+) {
+ val iconParam: (@Composable BoxScope.() -> Unit)? = iconBuilder?.let { { it.build() } }
+
+ val labelParam: (@Composable RowScope.() -> Unit) = {
+ Text(
+ text = label,
+ modifier = Modifier.fillMaxWidth(),
+ maxLines = labelMaxLines ?: LocalTextConfiguration.current.maxLines,
+ )
+ }
+
+ val secondaryLabelParam: (@Composable RowScope.() -> Unit)? =
+ secondaryLabel?.let {
+ {
+ Text(
+ text = secondaryLabel,
+ modifier = Modifier.fillMaxWidth(),
+ maxLines = secondaryLabelMaxLines ?: LocalTextConfiguration.current.maxLines,
+ )
+ }
+ }
+
+ Button(
+ icon = iconParam,
+ label = labelParam,
+ secondaryLabel = secondaryLabelParam,
+ enabled = enabled,
+ onClick = onClick,
+ modifier = modifier.fillMaxWidth(),
+ colors = colors,
+ )
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButtonStyle.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButtonStyle.kt
new file mode 100644
index 000000000..5a91ae46c
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButtonStyle.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.ui.wear.elements.material3
+
+import androidx.compose.runtime.Composable
+import androidx.wear.compose.material.ChipColors
+import androidx.wear.compose.material.ChipDefaults
+import androidx.wear.compose.material3.ButtonColors
+import androidx.wear.compose.material3.ButtonDefaults
+import com.android.permissioncontroller.permission.ui.wear.elements.chipDisabledColors
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionButtonStyle.DisabledLike
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionButtonStyle.Primary
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionButtonStyle.Secondary
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionButtonStyle.Transparent
+
+/**
+ * This component is wrapper on material control colors, It applies the right colors based material
+ * ui version.
+ */
+enum class WearPermissionButtonStyle {
+ Primary,
+ Secondary,
+ Transparent,
+ DisabledLike,
+}
+
+@Composable
+internal fun WearPermissionButtonStyle.material2ChipColors(): ChipColors {
+ return when (this) {
+ Primary -> ChipDefaults.primaryChipColors()
+ Secondary -> ChipDefaults.secondaryChipColors()
+ Transparent -> ChipDefaults.childChipColors()
+ DisabledLike -> chipDisabledColors()
+ }
+}
+
+@Composable
+internal fun WearPermissionButtonStyle.material3ButtonColors(): ButtonColors {
+ return when (this) {
+ Primary -> ButtonDefaults.buttonColors()
+ Secondary -> ButtonDefaults.filledTonalButtonColors()
+ Transparent -> ButtonDefaults.childButtonColors()
+ DisabledLike -> ButtonDefaults.disabledLikeColors()
+ }
+}
+
+@Composable
+private fun ButtonDefaults.disabledLikeColors() =
+ filledTonalButtonColors().run {
+ ButtonColors(
+ containerPainter = disabledContainerPainter,
+ contentColor = disabledContentColor,
+ secondaryContentColor = disabledSecondaryContentColor,
+ iconColor = disabledIconColor,
+ disabledContainerPainter = disabledContainerPainter,
+ disabledContentColor = disabledContentColor,
+ disabledSecondaryContentColor = disabledSecondaryContentColor,
+ disabledIconColor = disabledIconColor,
+ )
+ }
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionIconBuilder.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionIconBuilder.kt
new file mode 100644
index 000000000..65a85db7e
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionIconBuilder.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.permissioncontroller.permission.ui.wear.elements.material3
+
+import android.graphics.drawable.Drawable
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.painterResource
+import androidx.wear.compose.material3.ButtonDefaults
+import androidx.wear.compose.material3.Icon
+import com.android.permissioncontroller.permission.ui.wear.elements.rememberDrawablePainter
+
+/**
+ * This class simplifies the construction of icons with various attributes like resource type,
+ * content description, modifier, and tint. It supports different icon resource types, including:
+ * - ImageVector
+ * - Resource ID (Int)
+ * - Drawable
+ * - ImageBitmap
+ *
+ * Usage:
+ * ```
+ * val icon = WearPermissionIconBuilder.builder(IconResourceId)
+ * .contentDescription("Location Permission")
+ * .modifier(Modifier.size(24.dp))
+ * .tint(Color.Red)
+ * .build()
+ * ```
+ *
+ * Note: This builder uses a private constructor and is initialized through the `builder()`
+ * companion object method.
+ */
+class WearPermissionIconBuilder private constructor() {
+ var iconResource: Any? = null
+ private set
+
+ var contentDescription: String? = null
+ private set
+
+ var modifier: Modifier = Modifier.size(ButtonDefaults.IconSize)
+ private set
+
+ var tint: Color = Color.Unspecified
+ private set
+
+ fun contentDescription(description: String?): WearPermissionIconBuilder {
+ contentDescription = description
+ return this
+ }
+
+ fun modifier(modifier: Modifier): WearPermissionIconBuilder {
+ this.modifier then modifier
+ return this
+ }
+
+ fun tint(tint: Color): WearPermissionIconBuilder {
+ this.tint = tint
+ return this
+ }
+
+ @Composable
+ fun build() {
+ when (iconResource) {
+ is ImageVector -> Icon(iconResource as ImageVector, contentDescription, modifier, tint)
+ is Int ->
+ Icon(painterResource(id = iconResource as Int), contentDescription, modifier, tint)
+
+ is Drawable ->
+ Icon(
+ rememberDrawablePainter(iconResource as Drawable),
+ contentDescription,
+ modifier,
+ tint,
+ )
+
+ is ImageBitmap -> Icon(iconResource as ImageBitmap, contentDescription, modifier, tint)
+ else -> throw IllegalArgumentException("Type not supported.")
+ }
+ }
+
+ companion object {
+ fun builder(icon: Any) = WearPermissionIconBuilder().apply { iconResource = icon }
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionScaffold.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionScaffold.kt
new file mode 100644
index 000000000..bd7636273
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionScaffold.kt
@@ -0,0 +1,298 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.permissioncontroller.permission.ui.wear.elements.material3
+
+import android.graphics.drawable.Drawable
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredHeightIn
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.foundation.ScrollInfoProvider
+import androidx.wear.compose.foundation.lazy.ScalingLazyListScope
+import androidx.wear.compose.material3.AppScaffold
+import androidx.wear.compose.material3.CircularProgressIndicator
+import androidx.wear.compose.material3.ListHeader
+import androidx.wear.compose.material3.MaterialTheme
+import androidx.wear.compose.material3.ScreenScaffold
+import androidx.wear.compose.material3.ScrollIndicator
+import androidx.wear.compose.material3.Text
+import androidx.wear.compose.material3.TimeText
+import com.android.permissioncontroller.permission.ui.wear.elements.AnnotatedText
+import com.android.permissioncontroller.permission.ui.wear.elements.Wear2Scaffold
+import com.android.permissioncontroller.permission.ui.wear.elements.layout.ScalingLazyColumn
+import com.android.permissioncontroller.permission.ui.wear.elements.layout.ScalingLazyColumnState
+import com.android.permissioncontroller.permission.ui.wear.elements.layout.rememberResponsiveColumnState
+import com.android.permissioncontroller.permission.ui.wear.elements.rememberDrawablePainter
+import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion
+import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL2_5
+import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionTheme
+
+/**
+ * This component is wrapper on material scaffold component. It helps with time text, scroll
+ * indicator and standard list elements like title, icon and subtitle.
+ */
+@Composable
+internal fun WearPermissionScaffold(
+ materialUIVersion: WearPermissionMaterialUIVersion = MATERIAL2_5,
+ showTimeText: Boolean,
+ title: String?,
+ subtitle: CharSequence?,
+ image: Any?,
+ isLoading: Boolean,
+ content: ScalingLazyListScope.() -> Unit,
+ titleTestTag: String? = null,
+ subtitleTestTag: String? = null,
+) {
+
+ if (materialUIVersion == MATERIAL2_5) {
+ Wear2Scaffold(
+ showTimeText,
+ title,
+ subtitle,
+ image,
+ isLoading,
+ content,
+ titleTestTag,
+ subtitleTestTag,
+ )
+ } else {
+ WearPermissionScaffoldInternal(
+ showTimeText,
+ title,
+ subtitle,
+ image,
+ isLoading,
+ content,
+ titleTestTag,
+ subtitleTestTag,
+ )
+ }
+}
+
+@Composable
+private fun WearPermissionScaffoldInternal(
+ showTimeText: Boolean,
+ title: String?,
+ subtitle: CharSequence?,
+ image: Any?,
+ isLoading: Boolean,
+ content: ScalingLazyListScope.() -> Unit,
+ titleTestTag: String? = null,
+ subtitleTestTag: String? = null,
+) {
+ val screenWidth = LocalConfiguration.current.screenWidthDp
+ val screenHeight = LocalConfiguration.current.screenHeightDp
+ val paddingDefaults =
+ WearPermissionScaffoldPaddingDefaults(
+ screenWidth = screenWidth,
+ screenHeight = screenHeight,
+ titleNeedsLargePadding = subtitle == null,
+ )
+ val columnState =
+ rememberResponsiveColumnState(contentPadding = { paddingDefaults.scrollContentPadding })
+ WearPermissionTheme(version = WearPermissionMaterialUIVersion.MATERIAL3) {
+ AppScaffold(timeText = wearPermissionTimeText(showTimeText && !isLoading)) {
+ ScreenScaffold(
+ scrollInfoProvider = ScrollInfoProvider(columnState.state),
+ scrollIndicator = wearPermissionScrollIndicator(!isLoading, columnState),
+ ) {
+ Box(modifier = Modifier.fillMaxSize()) {
+ if (isLoading) {
+ CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
+ } else {
+ ScrollingView(
+ columnState = columnState,
+ icon = painterFromImage(image),
+ title = title,
+ titleTestTag = titleTestTag,
+ titlePaddingValues = paddingDefaults.titlePaddingValues,
+ subtitle = subtitle,
+ subtitleTestTag = subtitleTestTag,
+ subTitlePaddingValues = paddingDefaults.subTitlePaddingValues,
+ content = content,
+ )
+ }
+ }
+ }
+ }
+ }
+}
+
+private class WearPermissionScaffoldPaddingDefaults(
+ screenWidth: Int,
+ screenHeight: Int,
+ titleNeedsLargePadding: Boolean,
+) {
+ private val firstSpacerItemHeight = 0.dp
+ private val scrollContentHorizontalPadding = (screenWidth * 0.052).dp
+ private val titleHorizontalPadding = (screenWidth * 0.0884).dp
+ private val subtitleHorizontalPadding = (screenWidth * 0.0416).dp
+ private val scrollContentTopPadding = (screenHeight * 0.1456).dp - firstSpacerItemHeight
+ private val scrollContentBottomPadding = (screenHeight * 0.3636).dp
+ private val defaultItemPadding = 4.dp
+ private val largeItemPadding = 8.dp
+ val titlePaddingValues =
+ PaddingValues(
+ start = titleHorizontalPadding,
+ top = defaultItemPadding,
+ bottom = if (titleNeedsLargePadding) largeItemPadding else defaultItemPadding,
+ end = titleHorizontalPadding,
+ )
+ val subTitlePaddingValues =
+ PaddingValues(
+ start = subtitleHorizontalPadding,
+ top = defaultItemPadding,
+ bottom = largeItemPadding,
+ end = subtitleHorizontalPadding,
+ )
+ val scrollContentPadding =
+ PaddingValues(
+ start = scrollContentHorizontalPadding,
+ end = scrollContentHorizontalPadding,
+ top = scrollContentTopPadding,
+ bottom = scrollContentBottomPadding,
+ )
+}
+
+@Composable
+private fun BoxScope.ScrollingView(
+ columnState: ScalingLazyColumnState,
+ icon: Painter?,
+ title: String?,
+ titleTestTag: String?,
+ subtitle: CharSequence?,
+ subtitleTestTag: String?,
+ titlePaddingValues: PaddingValues,
+ subTitlePaddingValues: PaddingValues,
+ content: ScalingLazyListScope.() -> Unit,
+) {
+ ScalingLazyColumn(columnState = columnState) {
+ iconItem(icon, Modifier.size(24.dp))
+ titleItem(text = title, testTag = titleTestTag, contentPaddingValues = titlePaddingValues)
+ subtitleItem(
+ text = subtitle,
+ testTag = subtitleTestTag,
+ modifier = Modifier.align(Alignment.Center).padding(subTitlePaddingValues),
+ )
+ content()
+ }
+}
+
+private fun wearPermissionTimeText(showTime: Boolean): @Composable () -> Unit {
+ return if (showTime) {
+ { TimeText { time() } }
+ } else {
+ {}
+ }
+}
+
+private fun wearPermissionScrollIndicator(
+ showIndicator: Boolean,
+ columnState: ScalingLazyColumnState,
+): @Composable (BoxScope.() -> Unit)? {
+ return if (showIndicator) {
+ {
+ ScrollIndicator(
+ modifier = Modifier.align(Alignment.CenterEnd),
+ state = columnState.state,
+ )
+ }
+ } else {
+ null
+ }
+}
+
+@Composable
+private fun painterFromImage(image: Any?): Painter? {
+ return when (image) {
+ is Int -> painterResource(id = image)
+ is Drawable -> rememberDrawablePainter(image)
+ else -> null
+ }
+}
+
+private fun Modifier.optionalTestTag(tag: String?): Modifier {
+ if (tag == null) {
+ return this
+ }
+ return this then testTag(tag)
+}
+
+private fun ScalingLazyListScope.iconItem(painter: Painter?, modifier: Modifier = Modifier) =
+ painter?.let {
+ item {
+ Image(
+ painter = it,
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier = modifier,
+ )
+ }
+ }
+
+private fun ScalingLazyListScope.titleItem(
+ text: String?,
+ testTag: String?,
+ contentPaddingValues: PaddingValues,
+ modifier: Modifier = Modifier,
+) =
+ text?.let {
+ item {
+ ListHeader(
+ modifier = modifier.requiredHeightIn(1.dp), // We do not want default min height
+ contentPadding = contentPaddingValues,
+ ) {
+ Text(
+ text = it,
+ textAlign = TextAlign.Center,
+ modifier = Modifier.optionalTestTag(testTag),
+ )
+ }
+ }
+ }
+
+private fun ScalingLazyListScope.subtitleItem(
+ text: CharSequence?,
+ testTag: String?,
+ modifier: Modifier = Modifier,
+) =
+ text?.let {
+ item {
+ AnnotatedText(
+ text = it,
+ style =
+ MaterialTheme.typography.bodyMedium.copy(
+ color = MaterialTheme.colorScheme.onSurfaceVariant
+ ),
+ modifier = modifier.optionalTestTag(testTag),
+ shouldCapitalize = true,
+ )
+ }
+ }
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionToggleControl.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionToggleControl.kt
new file mode 100644
index 000000000..4a139f91f
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionToggleControl.kt
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.permissioncontroller.permission.ui.wear.elements.material3
+
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.wear.compose.material3.CheckboxButton
+import androidx.wear.compose.material3.LocalTextConfiguration
+import androidx.wear.compose.material3.RadioButton
+import androidx.wear.compose.material3.SwitchButton
+import androidx.wear.compose.material3.Text
+import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChip
+import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChipToggleControl
+import com.android.permissioncontroller.permission.ui.wear.elements.toggleControlSemantics
+import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion
+
+/**
+ * The custom component is a wrapper on different material3 toggle controls.
+ * 1. It provides an unified interface for RadioButton,CheckButton and SwitchButton.
+ * 2. It takes icon, primary, secondary label resources and construct them applying permission app
+ * defaults
+ * 3. Applies custom semantics for based on the toggle control type
+ */
+@Composable
+fun WearPermissionToggleControl(
+ toggleControl: ToggleChipToggleControl,
+ label: String,
+ checked: Boolean,
+ onCheckedChanged: (Boolean) -> Unit,
+ modifier: Modifier = Modifier,
+ labelMaxLines: Int? = null,
+ materialUIVersion: WearPermissionMaterialUIVersion =
+ WearPermissionMaterialUIVersion.MATERIAL2_5,
+ iconBuilder: WearPermissionIconBuilder? = null,
+ secondaryLabel: String? = null,
+ secondaryLabelMaxLines: Int? = null,
+ enabled: Boolean = true,
+ style: WearPermissionToggleControlStyle = WearPermissionToggleControlStyle.Default,
+) {
+ if (materialUIVersion == WearPermissionMaterialUIVersion.MATERIAL2_5) {
+ ToggleChip(
+ toggleControl = toggleControl,
+ label = label,
+ labelMaxLine = labelMaxLines,
+ checked = checked,
+ onCheckedChanged = onCheckedChanged,
+ modifier = modifier,
+ icon = iconBuilder?.iconResource,
+ secondaryLabel = secondaryLabel,
+ secondaryLabelMaxLine = secondaryLabelMaxLines,
+ enabled = enabled,
+ colors = style.material2ToggleControlColors(),
+ )
+ } else {
+ WearPermissionToggleControlInternal(
+ label = label,
+ toggleControl = toggleControl,
+ checked = checked,
+ onCheckedChanged = onCheckedChanged,
+ modifier = modifier,
+ iconBuilder = iconBuilder,
+ labelMaxLines = labelMaxLines,
+ secondaryLabel = secondaryLabel,
+ secondaryLabelMaxLines = secondaryLabelMaxLines,
+ enabled = enabled,
+ style = style,
+ )
+ }
+}
+
+@Composable
+private fun WearPermissionToggleControlInternal(
+ label: String,
+ toggleControl: ToggleChipToggleControl,
+ checked: Boolean,
+ onCheckedChanged: (Boolean) -> Unit,
+ modifier: Modifier = Modifier,
+ iconBuilder: WearPermissionIconBuilder? = null,
+ labelMaxLines: Int? = null,
+ secondaryLabel: String? = null,
+ secondaryLabelMaxLines: Int? = null,
+ enabled: Boolean = true,
+ style: WearPermissionToggleControlStyle = WearPermissionToggleControlStyle.Default,
+) {
+ val labelParam: (@Composable RowScope.() -> Unit) = {
+ Text(
+ text = label,
+ modifier = Modifier.fillMaxWidth(),
+ maxLines = labelMaxLines ?: LocalTextConfiguration.current.maxLines,
+ )
+ }
+
+ val secondaryLabelParam: (@Composable RowScope.() -> Unit)? =
+ secondaryLabel?.let {
+ {
+ Text(
+ text = it,
+ modifier = Modifier.fillMaxWidth(),
+ maxLines = secondaryLabelMaxLines ?: LocalTextConfiguration.current.maxLines,
+ )
+ }
+ }
+
+ val iconParam: (@Composable BoxScope.() -> Unit)? = iconBuilder?.let { { it.build() } }
+
+ val updatedModifier =
+ modifier
+ .fillMaxWidth()
+ // .heightIn(min = 58.dp) // TODO(b/370783358): This should be a overlaid value
+ .toggleControlSemantics(toggleControl, checked)
+
+ when (toggleControl) {
+ ToggleChipToggleControl.Radio ->
+ RadioButton(
+ selected = checked,
+ onSelect = { onCheckedChanged(true) },
+ modifier = updatedModifier,
+ enabled = enabled,
+ icon = iconParam,
+ secondaryLabel = secondaryLabelParam,
+ label = labelParam,
+ colors = style.radioButtonColorScheme(),
+ )
+
+ ToggleChipToggleControl.Checkbox ->
+ CheckboxButton(
+ checked = checked,
+ onCheckedChange = onCheckedChanged,
+ modifier = updatedModifier,
+ enabled = enabled,
+ icon = iconParam,
+ secondaryLabel = secondaryLabelParam,
+ label = labelParam,
+ colors = style.checkboxColorScheme(),
+ )
+
+ ToggleChipToggleControl.Switch ->
+ SwitchButton(
+ checked = checked,
+ onCheckedChange = onCheckedChanged,
+ modifier = updatedModifier,
+ enabled = enabled,
+ icon = iconParam,
+ secondaryLabel = secondaryLabelParam,
+ label = labelParam,
+ colors = style.switchButtonColorScheme(),
+ )
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionToggleControlStyle.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionToggleControlStyle.kt
new file mode 100644
index 000000000..b5746f019
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionToggleControlStyle.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.permissioncontroller.permission.ui.wear.elements.material3
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.Color
+import androidx.wear.compose.material.ToggleChipColors
+import androidx.wear.compose.material.ToggleChipDefaults.toggleChipColors
+import androidx.wear.compose.material3.CheckboxButtonColors
+import androidx.wear.compose.material3.CheckboxButtonDefaults.checkboxButtonColors
+import androidx.wear.compose.material3.RadioButtonColors
+import androidx.wear.compose.material3.RadioButtonDefaults.radioButtonColors
+import androidx.wear.compose.material3.SwitchButtonColors
+import androidx.wear.compose.material3.SwitchButtonDefaults.switchButtonColors
+import com.android.permissioncontroller.permission.ui.wear.elements.toggleChipBackgroundColors
+import com.android.permissioncontroller.permission.ui.wear.elements.toggleChipDisabledColors
+
+/**
+ * Defines toggle control styles, It helps in setting the right colors scheme to a toggle control.
+ */
+enum class WearPermissionToggleControlStyle {
+ Default,
+ Transparent,
+ DisabledLike,
+}
+
+@Composable
+internal fun WearPermissionToggleControlStyle.radioButtonColorScheme(): RadioButtonColors {
+ return when (this) {
+ WearPermissionToggleControlStyle.Default -> radioButtonColors()
+ WearPermissionToggleControlStyle.Transparent -> radioButtonTransparentColors()
+ WearPermissionToggleControlStyle.DisabledLike -> radioButtonDisabledLikeColors()
+ }
+}
+
+@Composable
+internal fun WearPermissionToggleControlStyle.checkboxColorScheme(): CheckboxButtonColors {
+ return when (this) {
+ WearPermissionToggleControlStyle.Default -> checkboxButtonColors()
+ WearPermissionToggleControlStyle.Transparent -> checkButtonTransparentColors()
+ WearPermissionToggleControlStyle.DisabledLike -> checkboxDisabledLikeColors()
+ }
+}
+
+@Composable
+internal fun WearPermissionToggleControlStyle.switchButtonColorScheme(): SwitchButtonColors {
+ return when (this) {
+ WearPermissionToggleControlStyle.Default -> switchButtonColors()
+ WearPermissionToggleControlStyle.Transparent -> switchButtonTransparentColors()
+ WearPermissionToggleControlStyle.DisabledLike -> switchButtonDisabledLikeColors()
+ }
+}
+
+@Composable
+internal fun WearPermissionToggleControlStyle.material2ToggleControlColors(): ToggleChipColors {
+ return when (this) {
+ WearPermissionToggleControlStyle.Default -> toggleChipColors()
+ WearPermissionToggleControlStyle.Transparent -> toggleChipBackgroundColors()
+ WearPermissionToggleControlStyle.DisabledLike -> toggleChipDisabledColors()
+ }
+}
+
+@Composable
+private fun checkButtonTransparentColors() =
+ checkboxButtonColors(
+ checkedContainerColor = Color.Transparent,
+ uncheckedContainerColor = Color.Transparent,
+ disabledCheckedContainerColor = Color.Transparent,
+ disabledUncheckedContainerColor = Color.Transparent,
+ )
+
+@Composable
+private fun radioButtonTransparentColors() =
+ radioButtonColors(
+ selectedContainerColor = Color.Transparent,
+ unselectedContainerColor = Color.Transparent,
+ disabledSelectedContainerColor = Color.Transparent,
+ disabledUnselectedContainerColor = Color.Transparent,
+ )
+
+@Composable
+private fun switchButtonTransparentColors() =
+ switchButtonColors(
+ checkedContainerColor = Color.Transparent,
+ uncheckedContainerColor = Color.Transparent,
+ disabledCheckedContainerColor = Color.Transparent,
+ disabledUncheckedContainerColor = Color.Transparent,
+ )
+
+@Composable
+private fun checkboxDisabledLikeColors(): CheckboxButtonColors {
+ val defaultColors = checkboxButtonColors()
+ return checkboxButtonColors(
+ checkedContainerColor = defaultColors.disabledCheckedContainerColor,
+ checkedContentColor = defaultColors.disabledCheckedContentColor,
+ checkedSecondaryContentColor = defaultColors.disabledCheckedSecondaryContentColor,
+ checkedIconColor = defaultColors.disabledCheckedIconColor,
+ checkedBoxColor = defaultColors.disabledCheckedBoxColor,
+ checkedCheckmarkColor = defaultColors.disabledCheckedCheckmarkColor,
+ uncheckedContainerColor = defaultColors.disabledUncheckedContainerColor,
+ uncheckedContentColor = defaultColors.disabledUncheckedContentColor,
+ uncheckedSecondaryContentColor = defaultColors.disabledUncheckedSecondaryContentColor,
+ uncheckedIconColor = defaultColors.disabledUncheckedIconColor,
+ uncheckedBoxColor = defaultColors.disabledUncheckedBoxColor,
+ )
+}
+
+@Composable
+private fun radioButtonDisabledLikeColors(): RadioButtonColors {
+ val defaultColors = radioButtonColors()
+ return radioButtonColors(
+ selectedContainerColor = defaultColors.disabledSelectedContainerColor,
+ selectedContentColor = defaultColors.disabledSelectedContentColor,
+ selectedSecondaryContentColor = defaultColors.disabledSelectedSecondaryContentColor,
+ selectedIconColor = defaultColors.disabledSelectedIconColor,
+ selectedControlColor = defaultColors.disabledSelectedControlColor,
+ unselectedContentColor = defaultColors.disabledUnselectedContentColor,
+ unselectedContainerColor = defaultColors.disabledUnselectedContainerColor,
+ unselectedSecondaryContentColor = defaultColors.disabledUnselectedSecondaryContentColor,
+ unselectedIconColor = defaultColors.disabledUnselectedIconColor,
+ unselectedControlColor = defaultColors.disabledUnselectedControlColor,
+ )
+}
+
+@Composable
+private fun switchButtonDisabledLikeColors(): SwitchButtonColors {
+ val defaultColors = switchButtonColors()
+ return switchButtonColors(
+ checkedContainerColor = defaultColors.disabledCheckedContainerColor,
+ checkedContentColor = defaultColors.disabledCheckedContentColor,
+ checkedSecondaryContentColor = defaultColors.disabledCheckedSecondaryContentColor,
+ checkedIconColor = defaultColors.disabledCheckedIconColor,
+ checkedThumbColor = defaultColors.disabledCheckedThumbColor,
+ checkedThumbIconColor = defaultColors.disabledCheckedThumbIconColor,
+ checkedTrackColor = defaultColors.disabledCheckedTrackColor,
+ checkedTrackBorderColor = defaultColors.disabledCheckedTrackBorderColor,
+ uncheckedContainerColor = defaultColors.disabledUncheckedContainerColor,
+ uncheckedContentColor = defaultColors.disabledUncheckedContentColor,
+ uncheckedSecondaryContentColor = defaultColors.disabledUncheckedSecondaryContentColor,
+ uncheckedIconColor = defaultColors.disabledUncheckedIconColor,
+ uncheckedThumbColor = defaultColors.disabledUncheckedThumbColor,
+ uncheckedTrackColor = defaultColors.checkedTrackColor.run { copy(alpha = alpha * 0.12f) },
+ uncheckedTrackBorderColor = defaultColors.disabledUncheckedTrackBorderColor,
+ )
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearComposeMaterial3ColorScheme.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearComposeMaterial3ColorScheme.kt
index 6af9a28ec..7ac6c8114 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearComposeMaterial3ColorScheme.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearComposeMaterial3ColorScheme.kt
@@ -29,11 +29,41 @@ import androidx.wear.compose.material3.ColorScheme
*/
internal object WearComposeMaterial3ColorScheme {
+ @RequiresApi(Build.VERSION_CODES.S)
+ fun tonalColorScheme(context: Context): ColorScheme {
+ val tonalPalette = dynamicTonalPalette(context)
+ return ColorScheme(
+ background = tonalPalette.neutral0,
+ onBackground = tonalPalette.neutral100,
+ onPrimary = tonalPalette.primary10,
+ onPrimaryContainer = tonalPalette.primary90,
+ onSecondary = tonalPalette.secondary10,
+ onSecondaryContainer = tonalPalette.secondary90,
+ onSurface = tonalPalette.neutral95,
+ onSurfaceVariant = tonalPalette.neutralVariant80,
+ onTertiary = tonalPalette.tertiary10,
+ onTertiaryContainer = tonalPalette.tertiary90,
+ outline = tonalPalette.neutralVariant60,
+ outlineVariant = tonalPalette.neutralVariant40,
+ primary = tonalPalette.primary90,
+ primaryContainer = tonalPalette.primary30,
+ primaryDim = tonalPalette.primary80,
+ secondary = tonalPalette.secondary90,
+ secondaryContainer = tonalPalette.secondary30,
+ secondaryDim = tonalPalette.secondary80,
+ surfaceContainer = tonalPalette.neutral20,
+ surfaceContainerHigh = tonalPalette.neutral30,
+ tertiary = tonalPalette.tertiary90,
+ tertiaryContainer = tonalPalette.tertiary30,
+ tertiaryDim = tonalPalette.tertiary80,
+ )
+ }
+
private fun Color.updatedColor(context: Context, @ColorRes colorRes: Int): Color {
return ResourceHelper.getColor(context, colorRes) ?: this
}
- @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @RequiresApi(36)
fun dynamicColorScheme(context: Context): ColorScheme {
val defaultColorScheme = ColorScheme()
return ColorScheme(
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearMaterialBridgedLegacyTheme.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearMaterialBridgedLegacyTheme.kt
new file mode 100644
index 000000000..160dc2e93
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearMaterialBridgedLegacyTheme.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.permissioncontroller.permission.ui.wear.theme
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.compose.ui.unit.sp
+import androidx.wear.compose.material.Colors
+import androidx.wear.compose.material.Shapes
+import androidx.wear.compose.material.Typography
+
+/**
+ * This exists to support Permission Controller screens that may still use Material 2.5 components
+ * to maintain consistency with the settings screens.
+ *
+ * However to avoid maintaining two sets of resources for overlays, this class construct 2.5 theme
+ * from 3.0
+ */
+@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+internal class WearMaterialBridgedLegacyTheme
+private constructor(newTheme: WearOverlayableMaterial3Theme) {
+
+ val colors =
+ newTheme.colorScheme.run {
+ Colors(
+ background = background,
+ onBackground = onBackground,
+ primary = onPrimaryContainer, // primary90
+ primaryVariant = primaryDim, // primary80
+ onPrimary = onPrimary, // primary10
+ secondary = tertiary, // Tertiary90
+ secondaryVariant = tertiaryDim, // Tertiary60 - Tertiary80 BestFit.
+ onSecondary = onTertiary, // Tertiary10
+ surface = surfaceContainer, // neutral20
+ onSurface = onSurface, // neutral95
+ onSurfaceVariant = onSurfaceVariant, // neutralVariant80
+ )
+ }
+
+ // Based on:
+ // Material 2:
+ // wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Typography.kt
+ // Material 3:
+ // wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TypeScaleTokens.kt
+ val typography =
+ newTheme.typography.run {
+ Typography(
+ display1 = displayLarge, // 40.sp
+ display2 = displayMedium.copy(fontSize = 34.sp, lineHeight = 40.sp),
+ display3 = displayMedium, // 30.sp
+ title1 = displaySmall, // 24.sp
+ title2 = titleLarge, // 20.sp
+ title3 = titleMedium, // 16.sp
+ body1 = bodyLarge, // 16.sp
+ body2 = bodyMedium, // 14.sp
+ caption1 = bodyMedium, // 14.sp
+ caption2 = bodySmall, // 12.sp
+ caption3 = bodyExtraSmall, // 10.sp
+ button = labelMedium, // 15.sp
+ )
+ }
+
+ val shapes = newTheme.shapes.run { Shapes(large = large, medium = medium, small = small) }
+
+ companion object {
+ fun createFrom(newTheme: WearOverlayableMaterial3Theme) =
+ WearMaterialBridgedLegacyTheme(newTheme)
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearOverlayableMaterial3Theme.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearOverlayableMaterial3Theme.kt
index d2b2324ea..8aeb5f74d 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearOverlayableMaterial3Theme.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearOverlayableMaterial3Theme.kt
@@ -15,23 +15,27 @@
*/
package com.android.permissioncontroller.permission.ui.wear.theme
+import android.content.Context
import android.os.Build
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.platform.LocalContext
-import androidx.wear.compose.material3.MaterialTheme
+import androidx.annotation.RequiresApi
-/** The Material 3 Theme Wrapper for Supporting RRO. */
-@Composable
-fun WearOverlayableMaterial3Theme(content: @Composable () -> Unit) {
- val context = LocalContext.current
- if (Build.VERSION.SDK_INT >= 36) {
- MaterialTheme(
- colorScheme = WearComposeMaterial3ColorScheme.dynamicColorScheme(context),
- typography = WearComposeMaterial3Typography.dynamicTypography(context),
- shapes = WearComposeMaterial3Shapes.dynamicShapes(context),
- content = content,
- )
- } else {
- MaterialTheme(content = content)
- }
+/**
+ * Theme wrapper providing Material 3 styling while maintaining compatibility with Runtime Resource
+ * Overlay (RRO).
+ *
+ * Uses the tonal palette from the previous Material Design version until dynamic color tokens are
+ * available in SDK 36.
+ */
+@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+internal class WearOverlayableMaterial3Theme(context: Context) {
+ val colorScheme =
+ if (Build.VERSION.SDK_INT >= 36) {
+ WearComposeMaterial3ColorScheme.dynamicColorScheme(context)
+ } else {
+ WearComposeMaterial3ColorScheme.tonalColorScheme(context)
+ }
+
+ val typography = WearComposeMaterial3Typography.dynamicTypography(context)
+
+ val shapes = WearComposeMaterial3Shapes.dynamicShapes(context)
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTheme.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTheme.kt
index 1d6d25ab1..cfaaa0df9 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTheme.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTheme.kt
@@ -29,11 +29,61 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.wear.compose.material.Colors
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.Typography
+import androidx.wear.compose.material3.MaterialTheme as Material3Theme
+import com.android.permission.flags.Flags
import com.android.permissioncontroller.R
+import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL2_5
+import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL3
-/** The Material 2.5 Theme Wrapper for Supporting RRO. */
+enum class WearPermissionMaterialUIVersion {
+ MATERIAL2_5,
+ MATERIAL3,
+}
+
+/**
+ * Supports both Material 3 and Material 2 theme. default version for permission theme will be
+ * LEGACY until we migrate enough screens to 3. LEGACY version will use material 3 overlay resources
+ * by default.
+ */
+@Composable
+fun WearPermissionTheme(
+ version: WearPermissionMaterialUIVersion = MATERIAL2_5,
+ content: @Composable () -> Unit,
+) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ WearPermissionLegacyTheme(content)
+ } else {
+ val useBridgedTheme = Flags.wearComposeMaterial3()
+ if (version == MATERIAL3) {
+ val material3Theme = WearOverlayableMaterial3Theme(LocalContext.current)
+ Material3Theme(
+ colorScheme = material3Theme.colorScheme,
+ typography = material3Theme.typography,
+ shapes = material3Theme.shapes,
+ content = content,
+ )
+ } else if (version == MATERIAL2_5 && useBridgedTheme) {
+ val material3Theme = WearOverlayableMaterial3Theme(LocalContext.current)
+ val bridgedLegacyTheme = WearMaterialBridgedLegacyTheme.createFrom(material3Theme)
+ MaterialTheme(
+ colors = bridgedLegacyTheme.colors,
+ typography = bridgedLegacyTheme.typography,
+ shapes = bridgedLegacyTheme.shapes,
+ content = content,
+ )
+ } else {
+ WearPermissionLegacyTheme(content)
+ }
+ }
+}
+
+/**
+ * The Material 2.5 Theme Wrapper for Supporting RRO with legacy resources. This theme is kept here
+ * for backward compatibility. When grant screen is updated to material3 will clean up legacy
+ * resources.
+ */
@Composable
-fun WearPermissionTheme(content: @Composable () -> Unit) {
+fun WearPermissionLegacyTheme(content: @Composable () -> Unit) {
val context = LocalContext.current
val colors =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {