Merge "Revert "Fix the take picture failure issue with unbind preview"" into androidx-main
diff --git a/camera/integration-tests/uiwidgetstestapp/build.gradle b/camera/integration-tests/uiwidgetstestapp/build.gradle
index 57c2ba8..4879ddf 100644
--- a/camera/integration-tests/uiwidgetstestapp/build.gradle
+++ b/camera/integration-tests/uiwidgetstestapp/build.gradle
@@ -90,6 +90,8 @@
implementation 'androidx.compose.animation:animation:1.1.1'
implementation 'androidx.compose.ui:ui-tooling:1.1.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.4.1'
+ implementation 'androidx.navigation:navigation-compose:2.4.2'
+ implementation 'androidx.compose.material:material-icons-extended:1.1.1'
androidTestImplementation 'androidx.compose.ui:ui-test-junit4:1.1.1'
// Testing framework
diff --git a/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/compose/ComposeCameraActivity.kt b/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/compose/ComposeCameraActivity.kt
index e2795b5..3dcab7a 100644
--- a/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/compose/ComposeCameraActivity.kt
+++ b/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/compose/ComposeCameraActivity.kt
@@ -19,32 +19,13 @@
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Surface
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.tooling.preview.Preview
+import androidx.camera.integration.uiwidgets.compose.ui.ComposeCameraApp
class ComposeCameraActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
- MyApp()
+ ComposeCameraApp()
}
}
}
-
-@Preview
-@Composable
-fun MyApp() {
- MaterialTheme {
- Surface {
- Greeting()
- }
- }
-}
-
-@Composable
-fun Greeting() {
- Text("Hello")
-}
\ No newline at end of file
diff --git a/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/compose/ui/ComposeCameraApp.kt b/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/compose/ui/ComposeCameraApp.kt
new file mode 100644
index 0000000..59c6979
--- /dev/null
+++ b/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/compose/ui/ComposeCameraApp.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2022 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.camera.integration.uiwidgets.compose.ui
+
+import androidx.camera.integration.uiwidgets.compose.ui.navigation.ComposeCameraNavHost
+import androidx.camera.integration.uiwidgets.compose.ui.navigation.ComposeCameraScreen
+import androidx.camera.integration.uiwidgets.compose.ui.screen.components.ComposeCameraScreenTabRow
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Scaffold
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.navigation.compose.currentBackStackEntryAsState
+import androidx.navigation.compose.rememberNavController
+
+@Composable
+fun ComposeCameraApp() {
+ MaterialTheme {
+ val allScreens = ComposeCameraScreen.values().toList()
+ val navController = rememberNavController()
+ val backstackEntry = navController.currentBackStackEntryAsState()
+ val currentScreen = ComposeCameraScreen.fromRoute(
+ route = backstackEntry.value?.destination?.route,
+ defaultRoute = ComposeCameraScreen.ImageCapture
+ )
+
+ Scaffold(
+ topBar = {
+ ComposeCameraScreenTabRow(
+ allScreens = allScreens,
+ onTabSelected = { screen ->
+ navController.navigate(screen.name)
+ },
+ currentScreen = currentScreen
+ )
+ }
+ ) { innerPadding ->
+ ComposeCameraNavHost(
+ navController = navController,
+ modifier = Modifier.padding(innerPadding)
+ )
+ }
+ }
+}
+
+@Composable
+fun Greeting() {
+ Text("Hello")
+}
\ No newline at end of file
diff --git a/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/compose/ui/navigation/ComposeCameraNavHost.kt b/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/compose/ui/navigation/ComposeCameraNavHost.kt
new file mode 100644
index 0000000..7516248
--- /dev/null
+++ b/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/compose/ui/navigation/ComposeCameraNavHost.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2022 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.camera.integration.uiwidgets.compose.ui.navigation
+
+import androidx.camera.integration.uiwidgets.compose.ui.Greeting
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+
+@Composable
+fun ComposeCameraNavHost(
+ navController: NavHostController,
+ modifier: Modifier = Modifier
+) {
+ NavHost(
+ navController = navController,
+ startDestination = ComposeCameraScreen.ImageCapture.name,
+ modifier = modifier
+ ) {
+ composable(ComposeCameraScreen.ImageCapture.name) {
+ Greeting()
+ }
+
+ composable(ComposeCameraScreen.VideoCapture.name) {
+ Greeting()
+ }
+
+ composable(ComposeCameraScreen.Gallery.name) {
+ Greeting()
+ }
+ }
+}
\ No newline at end of file
diff --git a/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/compose/ui/navigation/ComposeCameraScreen.kt b/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/compose/ui/navigation/ComposeCameraScreen.kt
new file mode 100644
index 0000000..8923506
--- /dev/null
+++ b/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/compose/ui/navigation/ComposeCameraScreen.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2022 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.camera.integration.uiwidgets.compose.ui.navigation
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.CameraAlt
+import androidx.compose.material.icons.filled.PhotoLibrary
+import androidx.compose.material.icons.filled.Videocam
+import androidx.compose.ui.graphics.vector.ImageVector
+
+// Contains each destination screen as an enum
+// and associates an icon to show in the navigation tabs
+enum class ComposeCameraScreen(
+ val icon: ImageVector
+) {
+ ImageCapture(
+ icon = Icons.Filled.CameraAlt
+ ),
+ VideoCapture(
+ icon = Icons.Filled.Videocam
+ ),
+ Gallery(
+ icon = Icons.Filled.PhotoLibrary
+ );
+
+ companion object {
+ fun fromRoute(route: String?, defaultRoute: ComposeCameraScreen): ComposeCameraScreen {
+ return when (route?.substringBefore("/")) {
+ ImageCapture.name -> ImageCapture
+ VideoCapture.name -> VideoCapture
+ Gallery.name -> Gallery
+ null -> defaultRoute
+ else -> throw IllegalArgumentException("Route $route is not recognized.")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/compose/ui/screen/components/TabRow.kt b/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/compose/ui/screen/components/TabRow.kt
new file mode 100644
index 0000000..40f5ed1
--- /dev/null
+++ b/camera/integration-tests/uiwidgetstestapp/src/main/java/androidx/camera/integration/uiwidgets/compose/ui/screen/components/TabRow.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2022 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.camera.integration.uiwidgets.compose.ui.screen.components
+
+import androidx.camera.integration.uiwidgets.compose.ui.navigation.ComposeCameraScreen
+import androidx.compose.animation.animateColorAsState
+import androidx.compose.animation.animateContentSize
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.selection.selectable
+import androidx.compose.foundation.selection.selectableGroup
+import androidx.compose.material.Icon
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Surface
+import androidx.compose.material.Text
+import androidx.compose.material.ripple.rememberRipple
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import java.util.Locale
+
+private val TabHeight = 56.dp
+private val TabPadding = 16.dp
+private val SpacerWidth = 12.dp
+private const val InactiveTabOpacity = 0.60f
+
+private const val TabFadeInAnimationDuration = 150
+private const val TabFadeInAnimationDelay = 100
+private const val TabFadeOutAnimationDuration = 100
+
+// References: Navigation CodeLab for Android
+
+// Header Tab Row for Navigation
+@Composable
+fun ComposeCameraScreenTabRow(
+ allScreens: List<ComposeCameraScreen>,
+ onTabSelected: (ComposeCameraScreen) -> Unit,
+ currentScreen: ComposeCameraScreen
+) {
+ Surface(
+ Modifier
+ .height(TabHeight)
+ .fillMaxWidth()
+ ) {
+ Row(Modifier.selectableGroup()) {
+ allScreens.forEach { screen ->
+ ComposeCameraTab(
+ text = screen.name,
+ icon = screen.icon,
+ onSelected = { onTabSelected(screen) },
+ selected = currentScreen == screen
+ )
+ }
+ }
+ }
+}
+
+// Individual Tab items for Tab Row
+@Composable
+private fun ComposeCameraTab(
+ text: String,
+ icon: ImageVector,
+ onSelected: () -> Unit,
+ selected: Boolean
+) {
+ val color = MaterialTheme.colors.onSurface
+ val durationMillis = if (selected) TabFadeInAnimationDuration else TabFadeOutAnimationDuration
+ val animSpec = remember {
+ tween<Color>(
+ durationMillis = durationMillis,
+ easing = LinearEasing,
+ delayMillis = TabFadeInAnimationDelay
+ )
+ }
+
+ val tabTintColor by animateColorAsState(
+ targetValue = if (selected) color else color.copy(alpha = InactiveTabOpacity),
+ animationSpec = animSpec
+ )
+
+ Row(
+ modifier = Modifier
+ .padding(TabPadding)
+ .animateContentSize()
+ .height(TabHeight)
+ .selectable(
+ selected = selected,
+ onClick = onSelected,
+ role = Role.Tab,
+ interactionSource = remember { MutableInteractionSource() },
+ indication = rememberRipple(
+ bounded = false,
+ radius = Dp.Unspecified,
+ color = Color.Unspecified
+ )
+ )
+ .clearAndSetSemantics { contentDescription = text }
+ ) {
+ Icon(imageVector = icon, contentDescription = text, tint = tabTintColor)
+ if (selected) {
+ Spacer(Modifier.width(SpacerWidth))
+ Text(text.uppercase(Locale.getDefault()), color = tabTintColor)
+ }
+ }
+}
\ No newline at end of file