diff --git a/app/src/main/java/com/google/android/samples/socialite/BubbleActivity.kt b/app/src/main/java/com/google/android/samples/socialite/BubbleActivity.kt index deb8cc47..ba1303ba 100644 --- a/app/src/main/java/com/google/android/samples/socialite/BubbleActivity.kt +++ b/app/src/main/java/com/google/android/samples/socialite/BubbleActivity.kt @@ -20,7 +20,9 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import com.google.android.samples.socialite.ui.Bubble +import dagger.hilt.android.AndroidEntryPoint +@AndroidEntryPoint class BubbleActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { diff --git a/app/src/main/java/com/google/android/samples/socialite/ui/Bubble.kt b/app/src/main/java/com/google/android/samples/socialite/ui/Bubble.kt index 4cb2d4db..4b2af807 100644 --- a/app/src/main/java/com/google/android/samples/socialite/ui/Bubble.kt +++ b/app/src/main/java/com/google/android/samples/socialite/ui/Bubble.kt @@ -16,25 +16,35 @@ package com.google.android.samples.socialite.ui +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionLayout import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.google.android.samples.socialite.ui.chat.ChatScreen +@OptIn(ExperimentalSharedTransitionApi::class) @Composable fun Bubble(chatId: Long) { - SocialTheme { - ChatScreen( - chatId = chatId, - foreground = false, - onBackPressed = null, - // TODO (donovanfm): Hook up camera button in the Bubble composable - onCameraClick = {}, - // TODO (jolandaverhoef): Hook up play video button in the Bubble composable - onVideoClick = {}, - // TODO (mayurikhin): Hook up camera button in the Bubble composable - onPhotoPickerClick = {}, - modifier = Modifier.fillMaxSize(), - ) + SharedTransitionLayout { + AnimatedContent(targetState = 1) { _ -> + SocialTheme { + ChatScreen( + chatId = chatId, + foreground = false, + onBackPressed = null, + // TODO (donovanfm): Hook up camera button in the Bubble composable + onCameraClick = {}, + // TODO (jolandaverhoef): Hook up play video button in the Bubble composable + onVideoClick = {}, + // TODO (mayurikhin): Hook up camera button in the Bubble composable + onPhotoPickerClick = {}, + modifier = Modifier.fillMaxSize(), + // sharedTransitionScope = this@SharedTransitionLayout, + // animatedContentScope = this@AnimatedContent + ) + } + } } } diff --git a/app/src/main/java/com/google/android/samples/socialite/ui/ContactRow.kt b/app/src/main/java/com/google/android/samples/socialite/ui/ContactRow.kt index 0651a6f2..75239d2b 100644 --- a/app/src/main/java/com/google/android/samples/socialite/ui/ContactRow.kt +++ b/app/src/main/java/com/google/android/samples/socialite/ui/ContactRow.kt @@ -16,6 +16,11 @@ package com.google.android.samples.socialite.ui +import androidx.compose.animation.AnimatedContentScope +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -40,6 +45,7 @@ import androidx.compose.ui.unit.sp import com.google.android.samples.socialite.data.utils.toReadableString import com.google.android.samples.socialite.model.ChatDetail +@OptIn(ExperimentalSharedTransitionApi::class) @Composable fun ChatRow( chat: ChatDetail, @@ -61,7 +67,7 @@ fun ChatRow( Image( painter = rememberIconPainter(contentUri = contact.iconUri), contentDescription = null, - modifier = Modifier + modifier = Modifier.Companion .size(48.dp) .clip(CircleShape) .background(Color.LightGray), diff --git a/app/src/main/java/com/google/android/samples/socialite/ui/Main.kt b/app/src/main/java/com/google/android/samples/socialite/ui/Main.kt index 6f923a8c..f5ea95b9 100644 --- a/app/src/main/java/com/google/android/samples/socialite/ui/Main.kt +++ b/app/src/main/java/com/google/android/samples/socialite/ui/Main.kt @@ -20,6 +20,7 @@ import android.app.Activity import android.content.Intent import android.content.pm.ActivityInfo import android.os.Bundle +import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.core.FastOutLinearInEasing import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.tween @@ -60,6 +61,7 @@ fun Main( } } +@OptIn(ExperimentalSharedTransitionApi::class) @Composable fun MainNavigation( modifier: Modifier, @@ -78,7 +80,6 @@ fun MainNavigation( activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED } } - NavHost( navController = navController, startDestination = "home", @@ -94,8 +95,8 @@ fun MainNavigation( route = "home", ) { Home( - modifier = Modifier.fillMaxSize(), onChatClicked = { chatId -> navController.navigate("chat/$chatId") }, + modifier = Modifier.fillMaxSize() ) } composable( @@ -116,12 +117,12 @@ fun MainNavigation( ChatScreen( chatId = chatId, foreground = true, + modifier = Modifier.fillMaxSize(), onBackPressed = { navController.popBackStack() }, onCameraClick = { navController.navigate("chat/$chatId/camera") }, onPhotoPickerClick = { navController.navigateToPhotoPicker(chatId) }, onVideoClick = { uri -> navController.navigate("videoPlayer?uri=$uri") }, prefilledText = text, - modifier = Modifier.fillMaxSize(), ) } composable( @@ -187,7 +188,6 @@ fun MainNavigation( ) } } - if (shortcutParams != null) { val chatId = extractChatId(shortcutParams.shortcutId) val text = shortcutParams.text diff --git a/app/src/main/java/com/google/android/samples/socialite/ui/chat/ChatScreen.kt b/app/src/main/java/com/google/android/samples/socialite/ui/chat/ChatScreen.kt index cbaef7ab..02da3f96 100644 --- a/app/src/main/java/com/google/android/samples/socialite/ui/chat/ChatScreen.kt +++ b/app/src/main/java/com/google/android/samples/socialite/ui/chat/ChatScreen.kt @@ -20,6 +20,12 @@ import android.graphics.Bitmap import android.media.MediaMetadataRetriever import android.net.Uri import android.util.Log +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedContentScope +import androidx.compose.animation.ExperimentalSharedTransitionApi +import androidx.compose.animation.SharedTransitionScope +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -94,7 +100,6 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel import coil.compose.AsyncImage import coil.request.ImageRequest import com.google.android.samples.socialite.R @@ -108,6 +113,7 @@ import kotlinx.coroutines.withContext private const val TAG = "ChatUI" +@OptIn(ExperimentalSharedTransitionApi::class) @Composable fun ChatScreen( chatId: Long, @@ -118,7 +124,7 @@ fun ChatScreen( onPhotoPickerClick: () -> Unit, onVideoClick: (uri: String) -> Unit, prefilledText: String? = null, - viewModel: ChatViewModel = hiltViewModel(), + viewModel: ChatViewModel = hiltViewModel() ) { LaunchedEffect(chatId) { viewModel.setChatId(chatId) @@ -143,7 +149,7 @@ fun ChatScreen( onPhotoPickerClick = onPhotoPickerClick, onVideoClick = onVideoClick, modifier = modifier - .clip(RoundedCornerShape(5)), + .clip(RoundedCornerShape(5)) ) } LifecycleEffect( @@ -175,7 +181,7 @@ private fun LifecycleEffect( } } -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class) @Composable private fun ChatContent( chat: ChatDetail, @@ -188,7 +194,7 @@ private fun ChatContent( onCameraClick: () -> Unit, onPhotoPickerClick: () -> Unit, onVideoClick: (uri: String) -> Unit, - modifier: Modifier = Modifier, + modifier: Modifier = Modifier ) { val topAppBarState = rememberTopAppBarState() val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(topAppBarState) @@ -242,13 +248,14 @@ private fun PaddingValues.copy( bottom = bottom ?: calculateBottomPadding(), ) -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class) @Composable private fun ChatAppBar( chat: ChatDetail, scrollBehavior: TopAppBarScrollBehavior, onBackPressed: (() -> Unit)?, modifier: Modifier = Modifier, + ) { TopAppBar( title = { @@ -258,8 +265,17 @@ private fun ChatAppBar( ) { // This only supports DM for now. val contact = chat.attendees.first() - SmallContactIcon(iconUri = contact.iconUri, size = 32.dp) - Text(text = contact.name) + Image( + painter = rememberIconPainter(contact.iconUri), + contentDescription = null, + modifier = Modifier.Companion + .size(32.dp) + .clip(CircleShape) + .background(Color.LightGray), + ) + Text( + text = contact.name, + ) } }, modifier = modifier, @@ -500,27 +516,32 @@ private fun PreviewInputBar() { } } +@OptIn(ExperimentalSharedTransitionApi::class) @Preview @Composable private fun PreviewChatContent() { - SocialTheme { - ChatContent( - chat = ChatDetail(ChatWithLastMessage(0L), listOf(Contact.CONTACTS[0])), - messages = listOf( - ChatMessage("Hi!", null, null, 0L, false, null), - ChatMessage("Hello", null, null, 0L, true, null), - ChatMessage("world", null, null, 0L, true, null), - ChatMessage("!", null, null, 0L, true, null), - ChatMessage("Hello, world!", null, null, 0L, true, null), - ), - input = "Hello", - sendEnabled = true, - onBackPressed = {}, - onInputChanged = {}, - onSendClick = {}, - onCameraClick = {}, - onPhotoPickerClick = {}, - onVideoClick = {}, - ) + SharedTransitionScope { + AnimatedContent(targetState = 1) {_ -> + SocialTheme { + ChatContent( + chat = ChatDetail(ChatWithLastMessage(0L), listOf(Contact.CONTACTS[0])), + messages = listOf( + ChatMessage("Hi!", null, null, 0L, false, null), + ChatMessage("Hello", null, null, 0L, true, null), + ChatMessage("world", null, null, 0L, true, null), + ChatMessage("!", null, null, 0L, true, null), + ChatMessage("Hello, world!", null, null, 0L, true, null), + ), + input = "Hello", + sendEnabled = true, + onBackPressed = {}, + onInputChanged = {}, + onSendClick = {}, + onCameraClick = {}, + onPhotoPickerClick = {}, + onVideoClick = {} + ) + } + } } } diff --git a/app/src/main/java/com/google/android/samples/socialite/ui/home/ChatList.kt b/app/src/main/java/com/google/android/samples/socialite/ui/home/ChatList.kt index e68a27e4..b7f7bd62 100644 --- a/app/src/main/java/com/google/android/samples/socialite/ui/home/ChatList.kt +++ b/app/src/main/java/com/google/android/samples/socialite/ui/home/ChatList.kt @@ -17,6 +17,7 @@ package com.google.android.samples.socialite.ui.home import android.annotation.SuppressLint +import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth @@ -39,13 +40,13 @@ import com.google.android.samples.socialite.R import com.google.android.samples.socialite.model.ChatDetail import com.google.android.samples.socialite.ui.ChatRow -@OptIn(ExperimentalPermissionsApi::class) +@OptIn(ExperimentalPermissionsApi::class, ExperimentalSharedTransitionApi::class) @Composable internal fun ChatList( chats: List, contentPadding: PaddingValues, onChatClicked: (chatId: Long) -> Unit, - modifier: Modifier = Modifier, + modifier: Modifier = Modifier ) { @SuppressLint("InlinedApi") // Granted at install time on API <33. val notificationPermissionState = rememberPermissionState( diff --git a/app/src/main/java/com/google/android/samples/socialite/ui/home/Home.kt b/app/src/main/java/com/google/android/samples/socialite/ui/home/Home.kt index f82dde11..66fb9349 100644 --- a/app/src/main/java/com/google/android/samples/socialite/ui/home/Home.kt +++ b/app/src/main/java/com/google/android/samples/socialite/ui/home/Home.kt @@ -17,6 +17,7 @@ package com.google.android.samples.socialite.ui.home import androidx.annotation.StringRes +import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ChatBubbleOutline @@ -48,11 +49,13 @@ import com.google.android.samples.socialite.R import com.google.android.samples.socialite.ui.AnimationConstants import com.google.android.samples.socialite.ui.home.timeline.Timeline -@OptIn(ExperimentalMaterial3AdaptiveNavigationSuiteApi::class) +@OptIn(ExperimentalMaterial3AdaptiveNavigationSuiteApi::class, + ExperimentalSharedTransitionApi::class +) @Composable fun Home( onChatClicked: (chatId: Long) -> Unit, - modifier: Modifier = Modifier, + modifier: Modifier = Modifier ) { var currentDestination by rememberSaveable { mutableStateOf(Destination.Chats) } NavigationSuiteScaffold( @@ -75,14 +78,19 @@ fun Home( ) } }, - ) { HomeContent(currentDestination, modifier, onChatClicked) } + ) { HomeContent( + currentDestination, + modifier, + onChatClicked + ) } } +@OptIn(ExperimentalSharedTransitionApi::class) @Composable private fun HomeContent( currentDestination: Destination, modifier: Modifier, - onChatClicked: (chatId: Long) -> Unit, + onChatClicked: (chatId: Long) -> Unit ) { Scaffold( modifier = modifier, @@ -116,7 +124,7 @@ private fun HomeContent( chats = chats, contentPadding = innerPadding, onChatClicked = onChatClicked, - modifier = modifier, + modifier = modifier ) } composable( diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d501f88e..02443a30 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,7 +23,7 @@ cameraViewfinderCompose = "1.0.0-SNAPSHOT" coil = "2.4.0" compose_bom = "2024.04.00" composeCompiler = "1.5.4" # Used in app/build.gradle.kts -compose-foundation = "1.6.0-beta03" +compose-foundation = "1.7.0-beta01" concurrent = "1.1.0" core = "1.12.0" core-splashscreen = "1.0.1"