From c68b5433e6e207fc3cd9e1b3843af753324e3f97 Mon Sep 17 00:00:00 2001 From: davidjiagoogle Date: Thu, 28 May 2026 21:36:29 +0000 Subject: [PATCH] Tidy up test tags by stripping them in release builds Introduced debugTestTag extension on Modifier to replace testTag. In debug builds, it behaves normally. In release builds, it is inlined as a no-op to optimize compilation and execution overhead. Updated UI components across all screens to use debugTestTag. --- .../ui/platform/PermissionsModifierExt.kt | 23 +++++ .../ui/PermissionsScreenComponents.kt | 5 +- .../ui/platform/PermissionsModifierExt.kt | 23 +++++ .../ui/platform/PostCaptureModifierExt.kt | 23 +++++ .../feature/postcapture/PostCaptureScreen.kt | 3 +- .../ui/PostCaptureScreenComponents.kt | 14 +-- .../ui/platform/PostCaptureModifierExt.kt | 23 +++++ .../feature/preview/PreviewScreen.kt | 11 +-- .../ui/platform/SettingsModifierExt.kt | 23 +++++ .../jetpackcamera/settings/SettingsScreen.kt | 4 +- .../settings/ui/SettingsComponents.kt | 86 ++++++++++--------- .../ui/platform/SettingsModifierExt.kt | 23 +++++ .../compose/ui/platform/CaptureModifierExt.kt | 23 +++++ .../capture/CaptureScreenComponents.kt | 11 +-- .../ui/components/capture/ImageWell.kt | 4 +- .../capture/ScreenFlashComponents.kt | 4 +- .../capture/debug/DebugOverlayComponents.kt | 17 ++-- .../quicksettings/QuickSettingsScreen.kt | 18 ++-- .../ui/QuickSettingsComponents.kt | 28 +++--- .../compose/ui/platform/CaptureModifierExt.kt | 23 +++++ 20 files changed, 291 insertions(+), 98 deletions(-) create mode 100644 feature/permissions/src/debug/java/androidx/compose/ui/platform/PermissionsModifierExt.kt create mode 100644 feature/permissions/src/release/java/androidx/compose/ui/platform/PermissionsModifierExt.kt create mode 100644 feature/postcapture/src/debug/java/androidx/compose/ui/platform/PostCaptureModifierExt.kt create mode 100644 feature/postcapture/src/release/java/androidx/compose/ui/platform/PostCaptureModifierExt.kt create mode 100644 feature/settings/src/debug/java/androidx/compose/ui/platform/SettingsModifierExt.kt create mode 100644 feature/settings/src/release/java/androidx/compose/ui/platform/SettingsModifierExt.kt create mode 100644 ui/components/capture/src/debug/java/androidx/compose/ui/platform/CaptureModifierExt.kt create mode 100644 ui/components/capture/src/release/java/androidx/compose/ui/platform/CaptureModifierExt.kt diff --git a/feature/permissions/src/debug/java/androidx/compose/ui/platform/PermissionsModifierExt.kt b/feature/permissions/src/debug/java/androidx/compose/ui/platform/PermissionsModifierExt.kt new file mode 100644 index 000000000..19a8b41b2 --- /dev/null +++ b/feature/permissions/src/debug/java/androidx/compose/ui/platform/PermissionsModifierExt.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2026 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 androidx.compose.ui.platform + +import androidx.compose.ui.Modifier + +/** + * Applies the test tag in debug builds. + */ +fun Modifier.debugTestTag(tag: String): Modifier = this.testTag(tag) diff --git a/feature/permissions/src/main/java/com/google/jetpackcamera/permissions/ui/PermissionsScreenComponents.kt b/feature/permissions/src/main/java/com/google/jetpackcamera/permissions/ui/PermissionsScreenComponents.kt index e18c241e1..6e84f9ab4 100644 --- a/feature/permissions/src/main/java/com/google/jetpackcamera/permissions/ui/PermissionsScreenComponents.kt +++ b/feature/permissions/src/main/java/com/google/jetpackcamera/permissions/ui/PermissionsScreenComponents.kt @@ -38,6 +38,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.debugTestTag import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight @@ -128,7 +129,7 @@ fun PermissionTemplate( modifier = Modifier .height(IntrinsicSize.Min) .align(Alignment.CenterHorizontally) - .testTag(testTag), + .debugTestTag(testTag), painter = painter, accessibilityText = iconAccessibilityText ) @@ -144,7 +145,7 @@ fun PermissionTemplate( // permission button section PermissionButtonSection( modifier = Modifier - .testTag(REQUEST_PERMISSION_BUTTON) + .debugTestTag(REQUEST_PERMISSION_BUTTON) .fillMaxWidth() .align(Alignment.CenterHorizontally) .height(IntrinsicSize.Min), diff --git a/feature/permissions/src/release/java/androidx/compose/ui/platform/PermissionsModifierExt.kt b/feature/permissions/src/release/java/androidx/compose/ui/platform/PermissionsModifierExt.kt new file mode 100644 index 000000000..504cb32e5 --- /dev/null +++ b/feature/permissions/src/release/java/androidx/compose/ui/platform/PermissionsModifierExt.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2026 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 androidx.compose.ui.platform + +import androidx.compose.ui.Modifier + +/** + * No-op in release builds (inlined to prevent execution and string overhead). + */ +inline fun Modifier.debugTestTag(tag: String): Modifier = this diff --git a/feature/postcapture/src/debug/java/androidx/compose/ui/platform/PostCaptureModifierExt.kt b/feature/postcapture/src/debug/java/androidx/compose/ui/platform/PostCaptureModifierExt.kt new file mode 100644 index 000000000..19a8b41b2 --- /dev/null +++ b/feature/postcapture/src/debug/java/androidx/compose/ui/platform/PostCaptureModifierExt.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2026 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 androidx.compose.ui.platform + +import androidx.compose.ui.Modifier + +/** + * Applies the test tag in debug builds. + */ +fun Modifier.debugTestTag(tag: String): Modifier = this.testTag(tag) diff --git a/feature/postcapture/src/main/java/com/google/jetpackcamera/feature/postcapture/PostCaptureScreen.kt b/feature/postcapture/src/main/java/com/google/jetpackcamera/feature/postcapture/PostCaptureScreen.kt index f339fba3c..8ac057083 100644 --- a/feature/postcapture/src/main/java/com/google/jetpackcamera/feature/postcapture/PostCaptureScreen.kt +++ b/feature/postcapture/src/main/java/com/google/jetpackcamera/feature/postcapture/PostCaptureScreen.kt @@ -23,6 +23,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.debugTestTag import androidx.compose.ui.platform.testTag import androidx.hilt.navigation.compose.hiltViewModel import androidx.media3.common.util.UnstableApi @@ -142,7 +143,7 @@ fun PostCaptureComponent( if (snackBarData != null) { snackBarController?.let { TestableSnackbar( - modifier = modifier.testTag(snackBarData.testTag), + modifier = modifier.debugTestTag(snackBarData.testTag), snackbarToShow = snackBarData, snackbarHostState = snackbarHostState, snackBarController = snackBarController diff --git a/feature/postcapture/src/main/java/com/google/jetpackcamera/feature/postcapture/ui/PostCaptureScreenComponents.kt b/feature/postcapture/src/main/java/com/google/jetpackcamera/feature/postcapture/ui/PostCaptureScreenComponents.kt index e2c35d198..ce37b841e 100644 --- a/feature/postcapture/src/main/java/com/google/jetpackcamera/feature/postcapture/ui/PostCaptureScreenComponents.kt +++ b/feature/postcapture/src/main/java/com/google/jetpackcamera/feature/postcapture/ui/PostCaptureScreenComponents.kt @@ -33,7 +33,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.testTag +import androidx.compose.ui.platform.debugTestTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -82,7 +82,7 @@ fun ImageFromBitmap(modifier: Modifier, bitmap: Bitmap) { Image( bitmap = bitmap.asImageBitmap(), contentDescription = stringResource(R.string.post_capture_image_description), - modifier = modifier.testTag(VIEWER_POST_CAPTURE_IMAGE) + modifier = modifier.debugTestTag(VIEWER_POST_CAPTURE_IMAGE) ) } @@ -92,7 +92,7 @@ fun VideoPlayer(modifier: Modifier, player: Player?) { val presentationState = rememberPresentationState(player) ContentFrame( modifier = modifier - .testTag(VIEWER_POST_CAPTURE_VIDEO) + .debugTestTag(VIEWER_POST_CAPTURE_VIDEO) .resizeWithContentScale( ContentScale.Fit, presentationState.videoSizeDp @@ -111,7 +111,7 @@ fun VideoPlayer(modifier: Modifier, player: Player?) { fun ExitPostCaptureButton(onExitPostCapture: () -> Unit, modifier: Modifier = Modifier) { PostCaptureIconButton( modifier = modifier - .testTag(BUTTON_POST_CAPTURE_EXIT), + .debugTestTag(BUTTON_POST_CAPTURE_EXIT), onClick = onExitPostCapture ) { Icon( @@ -134,7 +134,7 @@ fun ShareCurrentMediaButton( PostCaptureIconButton( onClick = onClick, modifier = modifier - .testTag(BUTTON_POST_CAPTURE_SHARE), + .debugTestTag(BUTTON_POST_CAPTURE_SHARE), enabled = enabled ) { @@ -158,7 +158,7 @@ fun ShareCurrentMediaButton( fun SaveCurrentMediaButton(onClick: () -> Unit, modifier: Modifier = Modifier) { PostCaptureIconButton( modifier = modifier - .testTag(BUTTON_POST_CAPTURE_SAVE), + .debugTestTag(BUTTON_POST_CAPTURE_SAVE), onClick = onClick ) { Icon( @@ -177,7 +177,7 @@ fun DeleteCurrentMediaButton( enabled: Boolean = true ) { PostCaptureIconButton( - modifier = modifier.testTag(BUTTON_POST_CAPTURE_DELETE), + modifier = modifier.debugTestTag(BUTTON_POST_CAPTURE_DELETE), onClick = onClick, enabled = enabled ) { diff --git a/feature/postcapture/src/release/java/androidx/compose/ui/platform/PostCaptureModifierExt.kt b/feature/postcapture/src/release/java/androidx/compose/ui/platform/PostCaptureModifierExt.kt new file mode 100644 index 000000000..504cb32e5 --- /dev/null +++ b/feature/postcapture/src/release/java/androidx/compose/ui/platform/PostCaptureModifierExt.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2026 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 androidx.compose.ui.platform + +import androidx.compose.ui.Modifier + +/** + * No-op in release builds (inlined to prevent execution and string overhead). + */ +inline fun Modifier.debugTestTag(tag: String): Modifier = this diff --git a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewScreen.kt b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewScreen.kt index 90cfbe02a..e6dda6c67 100644 --- a/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewScreen.kt +++ b/feature/preview/src/main/java/com/google/jetpackcamera/feature/preview/PreviewScreen.kt @@ -51,6 +51,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.debugTestTag import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -394,7 +395,7 @@ private fun ContentScreen( videoQualityIndicator = { VideoQualityIcon( captureUiState.videoQuality, - Modifier.testTag(VIDEO_QUALITY_TAG) + Modifier.debugTestTag(VIDEO_QUALITY_TAG) ) }, stabilizationIndicator = { @@ -453,7 +454,7 @@ private fun ContentScreen( }, flipCameraButton = { FlipCameraButton( - modifier = Modifier.testTag(FLIP_CAMERA_BUTTON), + modifier = Modifier.debugTestTag(FLIP_CAMERA_BUTTON), onClick = onFlipCamera, flipLensUiState = captureUiState.flipLensUiState, // enable only when phone has front and rear camera @@ -480,7 +481,7 @@ private fun ContentScreen( exit = fadeOut(animationSpec = tween(delayMillis = 1_500)) ) { ElapsedTimeText( - modifier = Modifier.testTag(ELAPSED_TIME_TAG), + modifier = Modifier.debugTestTag(ELAPSED_TIME_TAG), elapsedTimeUiState = captureUiState.elapsedTimeUiState ) } @@ -519,7 +520,7 @@ private fun ContentScreen( quickSettingsController = quickSettingsController, snackBarController = snackBarController, - modifier = it.testTag(CAPTURE_MODE_TOGGLE_BUTTON) + modifier = it.debugTestTag(CAPTURE_MODE_TOGGLE_BUTTON) ) } }, @@ -576,7 +577,7 @@ private fun ContentScreen( if (snackBarData != null) { snackBarController?.let { snackBarController -> TestableSnackbar( - modifier = modifier.testTag(snackBarData.testTag), + modifier = modifier.debugTestTag(snackBarData.testTag), snackbarToShow = snackBarData, snackbarHostState = snackbarHostState, snackBarController = snackBarController diff --git a/feature/settings/src/debug/java/androidx/compose/ui/platform/SettingsModifierExt.kt b/feature/settings/src/debug/java/androidx/compose/ui/platform/SettingsModifierExt.kt new file mode 100644 index 000000000..19a8b41b2 --- /dev/null +++ b/feature/settings/src/debug/java/androidx/compose/ui/platform/SettingsModifierExt.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2026 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 androidx.compose.ui.platform + +import androidx.compose.ui.Modifier + +/** + * Applies the test tag in debug builds. + */ +fun Modifier.debugTestTag(tag: String): Modifier = this.testTag(tag) diff --git a/feature/settings/src/main/java/com/google/jetpackcamera/settings/SettingsScreen.kt b/feature/settings/src/main/java/com/google/jetpackcamera/settings/SettingsScreen.kt index 813c469f3..7fb605fcb 100644 --- a/feature/settings/src/main/java/com/google/jetpackcamera/settings/SettingsScreen.kt +++ b/feature/settings/src/main/java/com/google/jetpackcamera/settings/SettingsScreen.kt @@ -32,7 +32,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.testTag +import androidx.compose.ui.platform.debugTestTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.hilt.navigation.compose.hiltViewModel @@ -130,7 +130,7 @@ private fun SettingsScreen( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { SettingsPageHeader( - modifier = Modifier.testTag(SETTINGS_TITLE), + modifier = Modifier.debugTestTag(SETTINGS_TITLE), title = stringResource(id = R.string.settings_title), navBack = onNavigateBack, scrollBehavior = scrollBehavior diff --git a/feature/settings/src/main/java/com/google/jetpackcamera/settings/ui/SettingsComponents.kt b/feature/settings/src/main/java/com/google/jetpackcamera/settings/ui/SettingsComponents.kt index 2290e154d..6f9250376 100644 --- a/feature/settings/src/main/java/com/google/jetpackcamera/settings/ui/SettingsComponents.kt +++ b/feature/settings/src/main/java/com/google/jetpackcamera/settings/ui/SettingsComponents.kt @@ -46,7 +46,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.testTag +import androidx.compose.ui.platform.debugTestTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role @@ -112,7 +112,7 @@ fun SettingsPageHeader( }, navigationIcon = { IconButton( - modifier = Modifier.testTag(BACK_BUTTON), + modifier = Modifier.debugTestTag(BACK_BUTTON), onClick = { navBack() } ) { Icon( @@ -143,7 +143,7 @@ fun DarkModeSetting( modifier: Modifier = Modifier ) { BasicPopupSetting( - modifier = modifier.testTag(BTN_OPEN_DIALOG_SETTING_DARK_MODE_TAG), + modifier = modifier.debugTestTag(BTN_OPEN_DIALOG_SETTING_DARK_MODE_TAG), title = stringResource(id = R.string.dark_mode_title), leadingIcon = null, enabled = true, @@ -159,21 +159,21 @@ fun DarkModeSetting( popupContents = { Column(Modifier.selectableGroup()) { SingleChoiceSelector( - modifier = modifier.testTag(BTN_DIALOG_DARK_MODE_OPTION_ON_TAG), + modifier = modifier.debugTestTag(BTN_DIALOG_DARK_MODE_OPTION_ON_TAG), text = stringResource(id = R.string.dark_mode_selector_dark), selected = darkModeUiState.currentDarkMode == DarkMode.DARK, enabled = true, onClick = { setDarkMode(DarkMode.DARK) } ) SingleChoiceSelector( - modifier = modifier.testTag(BTN_DIALOG_DARK_MODE_OPTION_OFF_TAG), + modifier = modifier.debugTestTag(BTN_DIALOG_DARK_MODE_OPTION_OFF_TAG), text = stringResource(id = R.string.dark_mode_selector_light), selected = darkModeUiState.currentDarkMode == DarkMode.LIGHT, enabled = true, onClick = { setDarkMode(DarkMode.LIGHT) } ) SingleChoiceSelector( - modifier = modifier.testTag(BTN_DIALOG_DARK_MODE_OPTION_SYSTEM_TAG), + modifier = modifier.debugTestTag(BTN_DIALOG_DARK_MODE_OPTION_SYSTEM_TAG), text = stringResource(id = R.string.dark_mode_selector_system), selected = darkModeUiState.currentDarkMode == DarkMode.SYSTEM, enabled = true, @@ -198,7 +198,7 @@ fun DefaultCameraFacing( ) SwitchSettingUI( modifier = modifier - .testTag(BTN_SWITCH_SETTING_LENS_FACING_TAG) + .debugTestTag(BTN_SWITCH_SETTING_LENS_FACING_TAG) .semantics { stateDescription = description }, @@ -228,7 +228,7 @@ fun FlashModeSetting( modifier: Modifier = Modifier ) { BasicPopupSetting( - modifier = modifier.testTag(BTN_OPEN_DIALOG_SETTING_FLASH_TAG), + modifier = modifier.debugTestTag(BTN_OPEN_DIALOG_SETTING_FLASH_TAG), title = stringResource(id = R.string.flash_mode_title), leadingIcon = null, enabled = flashUiState is FlashUiState.Enabled, @@ -252,7 +252,7 @@ fun FlashModeSetting( if (flashUiState is FlashUiState.Enabled) { Column(Modifier.selectableGroup()) { SingleChoiceSelector( - modifier = Modifier.testTag(BTN_DIALOG_FLASH_OPTION_AUTO_TAG), + modifier = Modifier.debugTestTag(BTN_DIALOG_FLASH_OPTION_AUTO_TAG), text = stringResource(id = R.string.flash_mode_selector_auto), selected = flashUiState.currentFlashMode == FlashMode.AUTO, enabled = flashUiState.autoSelectableState is @@ -261,7 +261,7 @@ fun FlashModeSetting( ) SingleChoiceSelector( - modifier = Modifier.testTag(BTN_DIALOG_FLASH_OPTION_ON_TAG), + modifier = Modifier.debugTestTag(BTN_DIALOG_FLASH_OPTION_ON_TAG), text = stringResource(id = R.string.flash_mode_selector_on), selected = flashUiState.currentFlashMode == FlashMode.ON, enabled = flashUiState.onSelectableState is @@ -270,7 +270,7 @@ fun FlashModeSetting( ) SingleChoiceSelector( - modifier = Modifier.testTag(BTN_DIALOG_FLASH_OPTION_LLB_TAG), + modifier = Modifier.debugTestTag(BTN_DIALOG_FLASH_OPTION_LLB_TAG), text = stringResource(id = R.string.flash_mode_selector_llb), selected = flashUiState.currentFlashMode == FlashMode.LOW_LIGHT_BOOST, enabled = flashUiState.lowLightSelectableState is @@ -279,7 +279,7 @@ fun FlashModeSetting( ) SingleChoiceSelector( - modifier = Modifier.testTag(BTN_DIALOG_FLASH_OPTION_OFF_TAG), + modifier = Modifier.debugTestTag(BTN_DIALOG_FLASH_OPTION_OFF_TAG), text = stringResource(id = R.string.flash_mode_selector_off), selected = flashUiState.currentFlashMode == FlashMode.OFF, enabled = true, @@ -298,7 +298,7 @@ fun AspectRatioSetting( modifier: Modifier = Modifier ) { BasicPopupSetting( - modifier = modifier.testTag(BTN_OPEN_DIALOG_SETTING_ASPECT_RATIO_TAG), + modifier = modifier.debugTestTag(BTN_OPEN_DIALOG_SETTING_ASPECT_RATIO_TAG), title = stringResource(id = R.string.aspect_ratio_title), leadingIcon = null, description = @@ -322,21 +322,21 @@ fun AspectRatioSetting( popupContents = { Column(Modifier.selectableGroup()) { SingleChoiceSelector( - modifier = Modifier.testTag(BTN_DIALOG_ASPECT_RATIO_OPTION_9_16_TAG), + modifier = Modifier.debugTestTag(BTN_DIALOG_ASPECT_RATIO_OPTION_9_16_TAG), text = stringResource(id = R.string.aspect_ratio_selector_9_16), selected = aspectRatioUiState.currentAspectRatio == AspectRatio.NINE_SIXTEEN, enabled = true, onClick = { setAspectRatio(AspectRatio.NINE_SIXTEEN) } ) SingleChoiceSelector( - modifier = Modifier.testTag(BTN_DIALOG_ASPECT_RATIO_OPTION_3_4_TAG), + modifier = Modifier.debugTestTag(BTN_DIALOG_ASPECT_RATIO_OPTION_3_4_TAG), text = stringResource(id = R.string.aspect_ratio_selector_3_4), selected = aspectRatioUiState.currentAspectRatio == AspectRatio.THREE_FOUR, enabled = true, onClick = { setAspectRatio(AspectRatio.THREE_FOUR) } ) SingleChoiceSelector( - modifier = Modifier.testTag(BTN_DIALOG_ASPECT_RATIO_OPTION_1_1_TAG), + modifier = Modifier.debugTestTag(BTN_DIALOG_ASPECT_RATIO_OPTION_1_1_TAG), text = stringResource(id = R.string.aspect_ratio_selector_1_1), selected = aspectRatioUiState.currentAspectRatio == AspectRatio.ONE_ONE, enabled = true, @@ -354,7 +354,7 @@ fun StreamConfigSetting( modifier: Modifier = Modifier ) { BasicPopupSetting( - modifier = modifier.testTag(BTN_OPEN_DIALOG_SETTING_STREAM_CONFIG_TAG), + modifier = modifier.debugTestTag(BTN_OPEN_DIALOG_SETTING_STREAM_CONFIG_TAG), title = stringResource(R.string.stream_config_title), leadingIcon = null, enabled = true, @@ -375,7 +375,7 @@ fun StreamConfigSetting( popupContents = { Column(Modifier.selectableGroup()) { SingleChoiceSelector( - modifier = Modifier.testTag( + modifier = Modifier.debugTestTag( BTN_DIALOG_STREAM_CONFIG_OPTION_MULTI_STREAM_CAPTURE_TAG ), text = stringResource(id = R.string.stream_config_selector_multi_stream), @@ -384,7 +384,9 @@ fun StreamConfigSetting( onClick = { setStreamConfig(StreamConfig.MULTI_STREAM) } ) SingleChoiceSelector( - modifier = Modifier.testTag(BTN_DIALOG_STREAM_CONFIG_OPTION_SINGLE_STREAM_TAG), + modifier = Modifier.debugTestTag( + BTN_DIALOG_STREAM_CONFIG_OPTION_SINGLE_STREAM_TAG + ), text = stringResource(id = R.string.stream_config_description_single_stream), selected = streamConfigUiState.currentStreamConfig == StreamConfig.SINGLE_STREAM, @@ -403,7 +405,7 @@ fun LowLightBoostPrioritySetting( modifier: Modifier = Modifier ) { BasicPopupSetting( - modifier = modifier.testTag(BTN_OPEN_DIALOG_SETTING_LOW_LIGHT_BOOST_PRIORITY_TAG), + modifier = modifier.debugTestTag(BTN_OPEN_DIALOG_SETTING_LOW_LIGHT_BOOST_PRIORITY_TAG), title = stringResource(R.string.low_light_boost_priority_title), leadingIcon = null, enabled = true, @@ -424,7 +426,7 @@ fun LowLightBoostPrioritySetting( popupContents = { Column(Modifier.selectableGroup()) { SingleChoiceSelector( - modifier = Modifier.testTag( + modifier = Modifier.debugTestTag( BTN_DIALOG_LOW_LIGHT_BOOST_PRIORITY_OPTION_AE_MODE_TAG ), text = stringResource(id = R.string.low_light_boost_priority_selector_ae_mode), @@ -434,7 +436,7 @@ fun LowLightBoostPrioritySetting( onClick = { setLowLightBoostPriority(LowLightBoostPriority.PRIORITIZE_AE_MODE) } ) SingleChoiceSelector( - modifier = Modifier.testTag( + modifier = Modifier.debugTestTag( BTN_DIALOG_LOW_LIGHT_BOOST_PRIORITY_OPTION_GOOGLE_PLAY_SERVICES_TAG ), text = stringResource( @@ -470,7 +472,7 @@ fun MaxVideoDurationSetting( modifier: Modifier = Modifier ) { BasicPopupSetting( - modifier = modifier.testTag(BTN_OPEN_DIALOG_SETTING_VIDEO_DURATION_TAG), + modifier = modifier.debugTestTag(BTN_OPEN_DIALOG_SETTING_VIDEO_DURATION_TAG), enabled = true, title = stringResource(R.string.duration_title), leadingIcon = null, @@ -481,7 +483,7 @@ fun MaxVideoDurationSetting( popupContents = { Column(Modifier.selectableGroup()) { SingleChoiceSelector( - modifier = modifier.testTag( + modifier = modifier.debugTestTag( getMaxVideoDurationTestTag( UNLIMITED_VIDEO_DURATION ) @@ -499,7 +501,7 @@ fun MaxVideoDurationSetting( SIXTY_SECONDS_DURATION ).forEach { maxDuration -> SingleChoiceSelector( - modifier = Modifier.testTag(getMaxVideoDurationTestTag(maxDuration)), + modifier = Modifier.debugTestTag(getMaxVideoDurationTestTag(maxDuration)), enabled = true, text = stringResource( R.string.duration_description_seconds, @@ -529,7 +531,7 @@ fun TargetFpsSetting( modifier: Modifier = Modifier ) { BasicPopupSetting( - modifier = modifier.testTag(BTN_OPEN_DIALOG_SETTING_FPS_TAG), + modifier = modifier.debugTestTag(BTN_OPEN_DIALOG_SETTING_FPS_TAG), title = stringResource(id = R.string.fps_title), enabled = fpsUiState is FpsUiState.Enabled, leadingIcon = null, @@ -548,7 +550,7 @@ fun TargetFpsSetting( if (fpsUiState is FpsUiState.Enabled) { Column(Modifier.selectableGroup()) { Text( - modifier = Modifier.testTag(getTargetFpsTestTag(TARGET_FPS_AUTO)), + modifier = Modifier.debugTestTag(getTargetFpsTestTag(TARGET_FPS_AUTO)), text = stringResource(id = R.string.fps_stabilization_disclaimer), fontStyle = FontStyle.Italic, color = MaterialTheme.colorScheme.onPrimaryContainer @@ -562,7 +564,7 @@ fun TargetFpsSetting( ) listOf(TARGET_FPS_15, TARGET_FPS_30, TARGET_FPS_60).forEach { fpsOption -> SingleChoiceSelector( - modifier = Modifier.testTag(getTargetFpsTestTag(fpsOption)), + modifier = Modifier.debugTestTag(getTargetFpsTestTag(fpsOption)), text = "%d".format(fpsOption), selected = fpsUiState.currentSelection == fpsOption, onClick = { setTargetFps(fpsOption) }, @@ -647,7 +649,7 @@ fun StabilizationSetting( // entire setting disabled when no available fps or target fps = 60 // stabilization is unsupported >30 fps BasicPopupSetting( - modifier = modifier.testTag(BTN_OPEN_DIALOG_SETTING_VIDEO_STABILIZATION_TAG), + modifier = modifier.debugTestTag(BTN_OPEN_DIALOG_SETTING_VIDEO_STABILIZATION_TAG), title = stringResource(R.string.video_stabilization_title), leadingIcon = null, enabled = stabilizationUiState is StabilizationUiState.Enabled, @@ -677,7 +679,7 @@ fun StabilizationSetting( when (stabilizationUiState) { is StabilizationUiState.Enabled -> { SingleChoiceSelector( - modifier = Modifier.testTag( + modifier = Modifier.debugTestTag( BTN_DIALOG_VIDEO_STABILIZATION_OPTION_AUTO_TAG ), text = stringResource(id = R.string.stabilization_selector_auto), @@ -694,7 +696,7 @@ fun StabilizationSetting( ) SingleChoiceSelector( - modifier = Modifier.testTag( + modifier = Modifier.debugTestTag( BTN_DIALOG_VIDEO_STABILIZATION_OPTION_ON_TAG ), text = stringResource(id = R.string.stabilization_selector_on), @@ -713,7 +715,7 @@ fun StabilizationSetting( // high quality selector // disabled if target fps = 60 (see VideoCapabilities.isStabilizationSupported) SingleChoiceSelector( - modifier = Modifier.testTag( + modifier = Modifier.debugTestTag( BTN_DIALOG_VIDEO_STABILIZATION_OPTION_HIGH_QUALITY_TAG ), text = stringResource( @@ -734,7 +736,7 @@ fun StabilizationSetting( // optical selector SingleChoiceSelector( - modifier = Modifier.testTag( + modifier = Modifier.debugTestTag( BTN_DIALOG_VIDEO_STABILIZATION_OPTION_OPTICAL_TAG ), text = stringResource( @@ -755,7 +757,7 @@ fun StabilizationSetting( // off selector SingleChoiceSelector( - modifier = Modifier.testTag( + modifier = Modifier.debugTestTag( BTN_DIALOG_VIDEO_STABILIZATION_OPTION_OFF_TAG ), text = stringResource(id = R.string.stabilization_selector_off), @@ -782,7 +784,7 @@ fun VideoQualitySetting( modifier: Modifier = Modifier ) { BasicPopupSetting( - modifier = modifier.testTag(BTN_OPEN_DIALOG_SETTING_VIDEO_QUALITY_TAG), + modifier = modifier.debugTestTag(BTN_OPEN_DIALOG_SETTING_VIDEO_QUALITY_TAG), title = stringResource(R.string.video_quality_title), leadingIcon = null, enabled = videQualityUiState is VideoQualityUiState.Enabled, @@ -799,7 +801,7 @@ fun VideoQualitySetting( popupContents = { Column(Modifier.selectableGroup()) { SingleChoiceSelector( - modifier = Modifier.testTag( + modifier = Modifier.debugTestTag( getVideoQualityOptionTestTag(VideoQuality.UNSPECIFIED) ), text = stringResource(getVideoQualityStringRes(VideoQuality.UNSPECIFIED)), @@ -817,7 +819,9 @@ fun VideoQualitySetting( listOf(VideoQuality.SD, VideoQuality.HD, VideoQuality.FHD, VideoQuality.UHD) .forEach { videoQuality -> SingleChoiceSelector( - modifier = Modifier.testTag(getVideoQualityOptionTestTag(videoQuality)), + modifier = Modifier.debugTestTag( + getVideoQualityOptionTestTag(videoQuality) + ), text = stringResource(getVideoQualityStringRes(videoQuality)), secondaryText = stringResource( getVideoQualitySecondaryStringRes( @@ -842,7 +846,7 @@ fun RecordingAudioSetting( setDefaultAudio: (Boolean) -> Unit ) { SwitchSettingUI( - modifier = modifier.testTag(BTN_SWITCH_SETTING_ENABLE_AUDIO_TAG), + modifier = modifier.debugTestTag(BTN_SWITCH_SETTING_ENABLE_AUDIO_TAG), title = stringResource(id = R.string.audio_title), description = when (audioUiState) { is AudioUiState.Enabled.On -> { @@ -881,7 +885,7 @@ fun VersionInfo(versionName: String, modifier: Modifier = Modifier, buildType: S } else { "" } - Text(text = versionString, modifier = Modifier.testTag(TEXT_SETTING_APP_VERSION_TAG)) + Text(text = versionString, modifier = Modifier.debugTestTag(TEXT_SETTING_APP_VERSION_TAG)) } } @@ -919,7 +923,7 @@ fun BasicPopupSetting( Text( text = "Close", modifier = Modifier - .testTag(CLOSE_BUTTON) + .debugTestTag(CLOSE_BUTTON) .clickable { popupStatus.value = false } ) }, @@ -929,7 +933,7 @@ fun BasicPopupSetting( val scrollState = rememberScrollState() Column( modifier = Modifier - .testTag(CONTAINER_DIALOG_CONTENTS) + .debugTestTag(CONTAINER_DIALOG_CONTENTS) .verticalScroll(scrollState) ) { MaterialTheme( diff --git a/feature/settings/src/release/java/androidx/compose/ui/platform/SettingsModifierExt.kt b/feature/settings/src/release/java/androidx/compose/ui/platform/SettingsModifierExt.kt new file mode 100644 index 000000000..504cb32e5 --- /dev/null +++ b/feature/settings/src/release/java/androidx/compose/ui/platform/SettingsModifierExt.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2026 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 androidx.compose.ui.platform + +import androidx.compose.ui.Modifier + +/** + * No-op in release builds (inlined to prevent execution and string overhead). + */ +inline fun Modifier.debugTestTag(tag: String): Modifier = this diff --git a/ui/components/capture/src/debug/java/androidx/compose/ui/platform/CaptureModifierExt.kt b/ui/components/capture/src/debug/java/androidx/compose/ui/platform/CaptureModifierExt.kt new file mode 100644 index 000000000..19a8b41b2 --- /dev/null +++ b/ui/components/capture/src/debug/java/androidx/compose/ui/platform/CaptureModifierExt.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2026 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 androidx.compose.ui.platform + +import androidx.compose.ui.Modifier + +/** + * Applies the test tag in debug builds. + */ +fun Modifier.debugTestTag(tag: String): Modifier = this.testTag(tag) diff --git a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/CaptureScreenComponents.kt b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/CaptureScreenComponents.kt index 59578e45a..72d1f9fb4 100644 --- a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/CaptureScreenComponents.kt +++ b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/CaptureScreenComponents.kt @@ -85,6 +85,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Matrix import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.debugTestTag import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -290,7 +291,7 @@ fun CaptureModeToggleButton( ) && uiState.selectedCaptureMode != CaptureMode.STANDARD ToggleSwitch( - modifier = modifier.testTag(CAPTURE_MODE_TOGGLE_BUTTON), + modifier = modifier.debugTestTag(CAPTURE_MODE_TOGGLE_BUTTON), checked = toggleState, onCheckedChange = { isChecked -> val newCaptureMode = if (isChecked) CaptureMode.VIDEO_ONLY else CaptureMode.IMAGE_ONLY @@ -357,7 +358,7 @@ fun TestableSnackbar( // box seems to need to have some size to be detected by UiAutomator modifier = modifier .size(20.dp) - .testTag(snackbarToShow.testTag) + .debugTestTag(snackbarToShow.testTag) ) { val context = LocalContext.current LaunchedEffect(snackbarToShow) { @@ -479,7 +480,7 @@ fun PreviewDisplay( surfaceRequest?.let { BoxWithConstraints( modifier - .testTag(PREVIEW_DISPLAY) + .debugTestTag(PREVIEW_DISPLAY) .fillMaxSize() .background(Color.Black), contentAlignment = Alignment.TopCenter @@ -604,7 +605,7 @@ fun CaptureButton( val context = LocalContext.current CaptureButton( - modifier = modifier.testTag(CAPTURE_BUTTON), + modifier = modifier.debugTestTag(CAPTURE_BUTTON), onIncrementZoom = onIncrementZoom, onImageCapture = { if (captureButtonUiState is CaptureButtonUiState.Enabled && @@ -896,7 +897,7 @@ private fun FocusMeteringIndicator( ) { Box( Modifier - .testTag(FOCUS_METERING_INDICATOR_TAG) + .debugTestTag(FOCUS_METERING_INDICATOR_TAG) .alpha( if (focusMeteringUiState.status == FocusMeteringUiState.Status.SUCCESS) { 1f diff --git a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/ImageWell.kt b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/ImageWell.kt index d98b9e437..7f316edc0 100644 --- a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/ImageWell.kt +++ b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/ImageWell.kt @@ -40,7 +40,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.testTag +import androidx.compose.ui.platform.debugTestTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -71,7 +71,7 @@ fun ImageWell( Box( modifier = modifier - .testTag(IMAGE_WELL_TAG) + .debugTestTag(IMAGE_WELL_TAG) .size(IconButtonDefaults.mediumContainerSize()) .border(2.dp, Color.White, shape) .clip(shape) diff --git a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/ScreenFlashComponents.kt b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/ScreenFlashComponents.kt index e87cc4e36..d393eb8ea 100644 --- a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/ScreenFlashComponents.kt +++ b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/ScreenFlashComponents.kt @@ -31,7 +31,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.testTag +import androidx.compose.ui.platform.debugTestTag import com.google.jetpackcamera.ui.uistate.capture.ScreenFlashUiState private const val TAG = "ScreenFlashComponents" @@ -87,7 +87,7 @@ private fun ScreenFlashOverlay( modifier = modifier .run { if (screenFlashUiState.enabled) { - this.testTag(SCREEN_FLASH_OVERLAY) + this.debugTestTag(SCREEN_FLASH_OVERLAY) } else { this } diff --git a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/debug/DebugOverlayComponents.kt b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/debug/DebugOverlayComponents.kt index 8567c46aa..f0e232cd9 100644 --- a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/debug/DebugOverlayComponents.kt +++ b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/debug/DebugOverlayComponents.kt @@ -53,6 +53,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.debugTestTag import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -83,7 +84,7 @@ private const val TAG = "DebugOverlayComponents" @Composable fun DebugDialogContainerToggle(toggleIsOpen: () -> Unit, modifier: Modifier = Modifier) { - Button(modifier = modifier.testTag(DEBUG_OVERLAY_BUTTON), onClick = { toggleIsOpen() }) { + Button(modifier = modifier.debugTestTag(DEBUG_OVERLAY_BUTTON), onClick = { toggleIsOpen() }) { Text(text = stringResource(R.string.debug_overlay_toggle_btn_text)) } } @@ -125,7 +126,7 @@ private fun DebugTextBar(modifier: Modifier = Modifier, title: String, value: St Text( modifier = modifier .background(Color.Black.copy(alpha = .4f)) - .testTag(tag), + .debugTestTag(tag), text = value ) } @@ -321,7 +322,7 @@ private fun DebugDialogOptionsMenuDialog( abs(size.height).toString() + "x" + abs(size.width).toString() } Text( - modifier = Modifier.testTag( + modifier = Modifier.debugTestTag( DEBUG_OVERLAY_VIDEO_RESOLUTION_TAG ), text = videoResText @@ -330,7 +331,7 @@ private fun DebugDialogOptionsMenuDialog( // show camera properties json button Button( - modifier = Modifier.testTag( + modifier = Modifier.debugTestTag( DEBUG_OVERLAY_SHOW_CAMERA_PROPERTIES_BUTTON ), onClick = { @@ -342,7 +343,7 @@ private fun DebugDialogOptionsMenuDialog( // set zoom ratio Button( - modifier = Modifier.testTag( + modifier = Modifier.debugTestTag( DEBUG_OVERLAY_SET_ZOOM_RATIO_BUTTON ), onClick = { @@ -375,7 +376,7 @@ private fun CameraPropertiesJSONDialog(cameraPropertiesJSON: String, onClose: () .noIndicationClickable(onClick = onClose) ) { Text( - modifier = Modifier.testTag(DEBUG_OVERLAY_CAMERA_PROPERTIES_TAG), + modifier = Modifier.debugTestTag(DEBUG_OVERLAY_CAMERA_PROPERTIES_TAG), text = cameraPropertiesJSON, fontSize = 10.sp ) @@ -395,13 +396,13 @@ private fun SetZoomRatioDialog(onChangeZoomRatio: (Float) -> Unit, onClose: () - Column(horizontalAlignment = Alignment.CenterHorizontally) { Text(text = "Enter and confirm zoom ratio") TextField( - modifier = Modifier.testTag(DEBUG_OVERLAY_SET_ZOOM_RATIO_TEXT_FIELD), + modifier = Modifier.debugTestTag(DEBUG_OVERLAY_SET_ZOOM_RATIO_TEXT_FIELD), value = zoomRatioText.value, onValueChange = { zoomRatioText.value = it }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number) ) Button( - modifier = Modifier.testTag( + modifier = Modifier.debugTestTag( DEBUG_OVERLAY_SET_ZOOM_RATIO_SET_BUTTON ), onClick = { diff --git a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/quicksettings/QuickSettingsScreen.kt b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/quicksettings/QuickSettingsScreen.kt index f7eff696e..e80613e2b 100644 --- a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/quicksettings/QuickSettingsScreen.kt +++ b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/quicksettings/QuickSettingsScreen.kt @@ -20,7 +20,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.testTag +import androidx.compose.ui.platform.debugTestTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.stateDescription @@ -98,7 +98,7 @@ fun QuickSettingsBottomSheet( // todo(kc): change flash to expanded setting? add { QuickSetFlash( - modifier = Modifier.testTag(QUICK_SETTINGS_FLASH_BUTTON), + modifier = Modifier.debugTestTag(QUICK_SETTINGS_FLASH_BUTTON), onClick = { f: FlashMode -> quickSettingsController.setFlash(f) }, flashModeUiState = quickSettingsUiState.flashModeUiState ) @@ -112,7 +112,7 @@ fun QuickSettingsBottomSheet( } ToggleFocusedQuickSetCaptureMode( modifier = Modifier - .testTag(BTN_QUICK_SETTINGS_FOCUS_CAPTURE_MODE) + .debugTestTag(BTN_QUICK_SETTINGS_FOCUS_CAPTURE_MODE) .semantics { description?.let { stateDescription = it } }, setCaptureMode = { quickSettingsController.setFocusedSetting( @@ -125,7 +125,7 @@ fun QuickSettingsBottomSheet( add { QuickFlipCamera( - modifier = Modifier.testTag(QUICK_SETTINGS_FLIP_CAMERA_BUTTON), + modifier = Modifier.debugTestTag(QUICK_SETTINGS_FLIP_CAMERA_BUTTON), setLensFacing = { l: LensFacing -> quickSettingsController.setLensFacing(l) }, @@ -135,7 +135,7 @@ fun QuickSettingsBottomSheet( add { ToggleFocusedQuickSetRatio( - modifier = Modifier.testTag(QUICK_SETTINGS_RATIO_BUTTON), + modifier = Modifier.debugTestTag(QUICK_SETTINGS_RATIO_BUTTON), setRatio = { quickSettingsController.setFocusedSetting( FocusedQuickSetting.ASPECT_RATIO @@ -148,7 +148,7 @@ fun QuickSettingsBottomSheet( add { QuickSetStreamConfig( - modifier = Modifier.testTag( + modifier = Modifier.debugTestTag( QUICK_SETTINGS_STREAM_CONFIG_BUTTON ), setStreamConfig = { c: StreamConfig -> @@ -160,7 +160,7 @@ fun QuickSettingsBottomSheet( add { QuickSetHdr( - modifier = Modifier.testTag(QUICK_SETTINGS_HDR_BUTTON), + modifier = Modifier.debugTestTag(QUICK_SETTINGS_HDR_BUTTON), onClick = { d: DynamicRange, i: ImageOutputFormat -> quickSettingsController.setDynamicRange(d) quickSettingsController.setImageFormat(i) @@ -172,7 +172,7 @@ fun QuickSettingsBottomSheet( add { QuickSetConcurrentCamera( modifier = - Modifier.testTag(QUICK_SETTINGS_CONCURRENT_CAMERA_MODE_BUTTON), + Modifier.debugTestTag(QUICK_SETTINGS_CONCURRENT_CAMERA_MODE_BUTTON), setConcurrentCameraMode = { c: ConcurrentCameraMode -> quickSettingsController.setConcurrentCameraMode(c) }, @@ -184,7 +184,7 @@ fun QuickSettingsBottomSheet( add { QuickNavSettings( modifier = Modifier - .testTag(SETTINGS_BUTTON), + .debugTestTag(SETTINGS_BUTTON), onNavigateToSettings = onNavigateToSettings ) } diff --git a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/quicksettings/ui/QuickSettingsComponents.kt b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/quicksettings/ui/QuickSettingsComponents.kt index de1613c65..3659d20c3 100644 --- a/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/quicksettings/ui/QuickSettingsComponents.kt +++ b/ui/components/capture/src/main/java/com/google/jetpackcamera/ui/components/capture/quicksettings/ui/QuickSettingsComponents.kt @@ -54,6 +54,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.debugTestTag import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource @@ -146,7 +147,7 @@ fun FocusedQuickSetCaptureMode( { QuickSetCaptureMode( modifier = Modifier - .testTag(BTN_QUICK_SETTINGS_FOCUSED_CAPTURE_MODE_OPTION_STANDARD), + .debugTestTag(BTN_QUICK_SETTINGS_FOCUSED_CAPTURE_MODE_OPTION_STANDARD), onClick = { onSetCaptureMode(CaptureMode.STANDARD) }, assignedCaptureMode = CaptureMode.STANDARD, captureModeUiState = captureModeUiState, @@ -156,7 +157,7 @@ fun FocusedQuickSetCaptureMode( { QuickSetCaptureMode( modifier = Modifier - .testTag(BTN_QUICK_SETTINGS_FOCUSED_CAPTURE_MODE_IMAGE_ONLY), + .debugTestTag(BTN_QUICK_SETTINGS_FOCUSED_CAPTURE_MODE_IMAGE_ONLY), onClick = { onSetCaptureMode(CaptureMode.IMAGE_ONLY) }, assignedCaptureMode = CaptureMode.IMAGE_ONLY, captureModeUiState = captureModeUiState, @@ -166,7 +167,7 @@ fun FocusedQuickSetCaptureMode( { QuickSetCaptureMode( modifier = Modifier - .testTag(BTN_QUICK_SETTINGS_FOCUSED_CAPTURE_MODE_VIDEO_ONLY), + .debugTestTag(BTN_QUICK_SETTINGS_FOCUSED_CAPTURE_MODE_VIDEO_ONLY), onClick = { onSetCaptureMode(CaptureMode.VIDEO_ONLY) }, assignedCaptureMode = CaptureMode.VIDEO_ONLY, captureModeUiState = captureModeUiState, @@ -233,7 +234,7 @@ fun QuickNavSettings(onNavigateToSettings: () -> Unit, modifier: Modifier = Modi text = stringResource(R.string.quick_settings_more_text), accessibilityText = stringResource(R.string.quick_settings_more_description), painter = painterResource(R.drawable.ic_more_horiz), - modifier = modifier.testTag(SETTINGS_BUTTON) + modifier = modifier.debugTestTag(SETTINGS_BUTTON) ) } @@ -478,9 +479,8 @@ fun ToggleQuickSettingsButton( IconButton( modifier = modifier .size(buttonSize) - .testTag(QUICK_SETTINGS_DROP_DOWN) + .debugTestTag(QUICK_SETTINGS_DROP_DOWN) .semantics { - testTag = QUICK_SETTINGS_DROP_DOWN contentDescription = if (isOpen) { openDescription } else { @@ -578,7 +578,7 @@ fun focusedRatioButtons( }, { QuickSetRatio( - modifier = Modifier.testTag(QUICK_SETTINGS_RATIO_3_4_BUTTON), + modifier = Modifier.debugTestTag(QUICK_SETTINGS_RATIO_3_4_BUTTON), onClick = { onSetAspectRatio(AspectRatio.THREE_FOUR) }, assignedRatio = AspectRatio.THREE_FOUR, aspectRatioUiState = aspectRatioUiState, @@ -587,7 +587,7 @@ fun focusedRatioButtons( }, { QuickSetRatio( - modifier = Modifier.testTag(QUICK_SETTINGS_RATIO_9_16_BUTTON), + modifier = Modifier.debugTestTag(QUICK_SETTINGS_RATIO_9_16_BUTTON), onClick = { onSetAspectRatio(AspectRatio.NINE_SIXTEEN) }, assignedRatio = AspectRatio.NINE_SIXTEEN, aspectRatioUiState = aspectRatioUiState, @@ -596,7 +596,7 @@ fun focusedRatioButtons( }, { QuickSetRatio( - modifier = Modifier.testTag(QUICK_SETTINGS_RATIO_1_1_BUTTON), + modifier = Modifier.debugTestTag(QUICK_SETTINGS_RATIO_1_1_BUTTON), onClick = { onSetAspectRatio(AspectRatio.ONE_ONE) }, assignedRatio = AspectRatio.ONE_ONE, aspectRatioUiState = aspectRatioUiState, @@ -618,7 +618,7 @@ fun focusedCaptureModeButtons( { QuickSetCaptureMode( modifier = Modifier - .testTag(BTN_QUICK_SETTINGS_FOCUSED_CAPTURE_MODE_OPTION_STANDARD), + .debugTestTag(BTN_QUICK_SETTINGS_FOCUSED_CAPTURE_MODE_OPTION_STANDARD), onClick = { onSetCaptureMode(CaptureMode.STANDARD) }, assignedCaptureMode = CaptureMode.STANDARD, captureModeUiState = captureModeUiState, @@ -628,7 +628,7 @@ fun focusedCaptureModeButtons( { QuickSetCaptureMode( modifier = Modifier - .testTag(BTN_QUICK_SETTINGS_FOCUSED_CAPTURE_MODE_IMAGE_ONLY), + .debugTestTag(BTN_QUICK_SETTINGS_FOCUSED_CAPTURE_MODE_IMAGE_ONLY), onClick = { onSetCaptureMode(CaptureMode.IMAGE_ONLY) }, assignedCaptureMode = CaptureMode.IMAGE_ONLY, captureModeUiState = captureModeUiState, @@ -638,7 +638,7 @@ fun focusedCaptureModeButtons( { QuickSetCaptureMode( modifier = Modifier - .testTag(BTN_QUICK_SETTINGS_FOCUSED_CAPTURE_MODE_VIDEO_ONLY), + .debugTestTag(BTN_QUICK_SETTINGS_FOCUSED_CAPTURE_MODE_VIDEO_ONLY), onClick = { onSetCaptureMode(CaptureMode.VIDEO_ONLY) }, assignedCaptureMode = CaptureMode.VIDEO_ONLY, captureModeUiState = captureModeUiState, @@ -650,7 +650,7 @@ fun focusedCaptureModeButtons( @Composable private fun CloseExpandedSettingsButton(onUnFocus: () -> Unit, modifier: Modifier = Modifier) { FilledIconButton( - modifier = modifier.testTag(QUICK_SETTINGS_CLOSE_EXPANDED_BUTTON), + modifier = modifier.debugTestTag(QUICK_SETTINGS_CLOSE_EXPANDED_BUTTON), onClick = onUnFocus ) { Icon( @@ -678,7 +678,7 @@ private fun QuickSettingsBottomSheetRow( // It handles the "overflow to the right" behavior by default. LazyRow( modifier = modifier - .testTag(QUICK_SETTINGS_SCROLL_CONTAINER) + .debugTestTag(QUICK_SETTINGS_SCROLL_CONTAINER) .fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally), contentPadding = PaddingValues(horizontal = 16.dp) diff --git a/ui/components/capture/src/release/java/androidx/compose/ui/platform/CaptureModifierExt.kt b/ui/components/capture/src/release/java/androidx/compose/ui/platform/CaptureModifierExt.kt new file mode 100644 index 000000000..504cb32e5 --- /dev/null +++ b/ui/components/capture/src/release/java/androidx/compose/ui/platform/CaptureModifierExt.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2026 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 androidx.compose.ui.platform + +import androidx.compose.ui.Modifier + +/** + * No-op in release builds (inlined to prevent execution and string overhead). + */ +inline fun Modifier.debugTestTag(tag: String): Modifier = this