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