diff --git a/Jetchat/app/build.gradle.kts b/Jetchat/app/build.gradle.kts index 4d325b2b74..c8790f17d0 100644 --- a/Jetchat/app/build.gradle.kts +++ b/Jetchat/app/build.gradle.kts @@ -24,6 +24,7 @@ plugins { android { compileSdk = libs.versions.compileSdk.get().toInt() + compileSdkMinor = 1 namespace = "com.example.compose.jetchat" defaultConfig { @@ -88,6 +89,9 @@ android { } dependencies { + + implementation("androidx.compose.ui:ui-graphics:1.13.0-alpha03") + val composeBom = platform(libs.androidx.compose.bom) implementation(composeBom) androidTestImplementation(composeBom) diff --git a/Jetchat/app/src/main/java/com/example/compose/jetchat/data/FakeData.kt b/Jetchat/app/src/main/java/com/example/compose/jetchat/data/FakeData.kt index ad724a1438..92fbe07b3f 100644 --- a/Jetchat/app/src/main/java/com/example/compose/jetchat/data/FakeData.kt +++ b/Jetchat/app/src/main/java/com/example/compose/jetchat/data/FakeData.kt @@ -114,12 +114,12 @@ val colleagueProfile = ProfileScreenState( */ val meProfile = ProfileScreenState( userId = "me", - photo = R.drawable.ali, - name = "Ali Conors", + photo = R.drawable.img, + name = "Taylor Brooks", status = "Online", - displayName = "aliconors", - position = "Senior Android Dev at Yearin\nGoogle Developer Expert", - twitter = "twitter.com/aliconors", + displayName = "taylor", + position = "Senior Android Dev at Openlane", + twitter = "twitter.com/taylor", timeZone = "In your timezone", commonChannels = null, ) diff --git a/Jetchat/app/src/main/java/com/example/compose/jetchat/profile/Profile.kt b/Jetchat/app/src/main/java/com/example/compose/jetchat/profile/Profile.kt index 94e54a9208..c4f77e2e5b 100644 --- a/Jetchat/app/src/main/java/com/example/compose/jetchat/profile/Profile.kt +++ b/Jetchat/app/src/main/java/com/example/compose/jetchat/profile/Profile.kt @@ -17,21 +17,18 @@ package com.example.compose.jetchat.profile import androidx.compose.foundation.Image -import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.background import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FloatingActionButton @@ -41,26 +38,27 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.input.nestedscroll.NestedScrollConnection -import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.draw.BlurredEdgeTreatment +import androidx.compose.ui.draw.blur +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.blur.BlurRadiusSpec +import androidx.compose.ui.graphics.blur.BlurStop import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.rememberNestedScrollInteropConnection import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import com.example.compose.jetchat.FunctionalityNotAvailablePopup import com.example.compose.jetchat.R import com.example.compose.jetchat.components.AnimatingFabContent @@ -71,10 +69,7 @@ import com.example.compose.jetchat.theme.JetchatTheme @OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) @Composable -fun ProfileScreen( - userData: ProfileScreenState, - nestedScrollInteropConnection: NestedScrollConnection = rememberNestedScrollInteropConnection(), -) { +fun ProfileScreen(userData: ProfileScreenState) { var functionalityNotAvailablePopupShown by remember { mutableStateOf(false) } if (functionalityNotAvailablePopupShown) { FunctionalityNotAvailablePopup { functionalityNotAvailablePopupShown = false } @@ -84,42 +79,26 @@ fun ProfileScreen( BoxWithConstraints( modifier = Modifier - .fillMaxSize() - .nestedScroll(nestedScrollInteropConnection) - .systemBarsPadding(), + .fillMaxSize(), ) { Surface { Column( modifier = Modifier .fillMaxSize() + .background(color = Color(0xFFEAE9FC)) .verticalScroll(scrollState), ) { - ProfileHeader( - scrollState, - userData, - this@BoxWithConstraints.maxHeight, - ) + ProfileHeader(userData) UserInfoFields(userData, this@BoxWithConstraints.maxHeight) } } - - val fabExtended by remember { derivedStateOf { scrollState.value == 0 } } - ProfileFab( - extended = fabExtended, - userIsMe = userData.isMe(), - modifier = Modifier - .align(Alignment.BottomEnd) - // Offsets the FAB to compensate for CoordinatorLayout collapsing behaviour - .offset(y = ((-100).dp)), - onFabClicked = { functionalityNotAvailablePopupShown = true }, - ) } } @Composable private fun UserInfoFields(userData: ProfileScreenState, containerHeight: Dp) { Column { - Spacer(modifier = Modifier.height(8.dp)) + Spacer(modifier = Modifier.height(40.dp)) NameAndPosition(userData) @@ -160,7 +139,12 @@ private fun Name(userData: ProfileScreenState, modifier: Modifier = Modifier) { Text( text = userData.name, modifier = modifier, - style = MaterialTheme.typography.headlineSmall, + style = TextStyle( + fontSize = 74.sp, + lineHeight = 70.82.sp, + fontWeight = FontWeight(800), + color = Color(0xFF1D1B1F), + ), ) } @@ -169,28 +153,33 @@ private fun Position(userData: ProfileScreenState, modifier: Modifier = Modifier Text( text = userData.position, modifier = modifier, - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurfaceVariant, + style = TextStyle( + fontSize = 16.sp, + lineHeight = 24.sp, + fontWeight = FontWeight(400), + color = Color(0xFF48454F), + ), ) } @Composable -private fun ProfileHeader(scrollState: ScrollState, data: ProfileScreenState, containerHeight: Dp) { - val offset = (scrollState.value / 2) - val offsetDp = with(LocalDensity.current) { offset.toDp() } +private fun ProfileHeader(data: ProfileScreenState) { data.photo?.let { Image( modifier = Modifier - .heightIn(max = containerHeight / 2) .fillMaxWidth() - // TODO: Update to use offset to avoid recomposition - .padding( - start = 16.dp, - top = offsetDp, - end = 16.dp, - ) - .clip(CircleShape), + .aspectRatio(0.96f) + .blur { + radius = BlurRadiusSpec.verticalGradient( + listOf( + BlurStop(0.0f, 0.dp), + BlurStop(0.5f, 0.dp), + BlurStop(1.0f, 32.dp), + ), + ) + edgeTreatment = BlurredEdgeTreatment.Unbounded + }, painter = painterResource(id = it), contentScale = ContentScale.Crop, contentDescription = null, @@ -205,8 +194,13 @@ fun ProfileProperty(label: String, value: String, isLink: Boolean = false) { Text( text = label, modifier = Modifier.baselineHeight(24.dp), - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, + style = TextStyle( + fontSize = 12.sp, + lineHeight = 16.sp, + fontWeight = FontWeight(700), + color = Color(0xFF48454F), + letterSpacing = 0.15.sp, + ), ) val style = if (isLink) { MaterialTheme.typography.bodyLarge.copy(color = MaterialTheme.colorScheme.primary) @@ -216,7 +210,12 @@ fun ProfileProperty(label: String, value: String, isLink: Boolean = false) { Text( text = value, modifier = Modifier.baselineHeight(24.dp), - style = style, + style = TextStyle( + fontSize = 16.sp, + lineHeight = 24.sp, + fontWeight = FontWeight(400), + color = Color(0xFF1D1B1F), + ), ) } } diff --git a/Jetchat/app/src/main/java/com/example/compose/jetchat/profile/ProfileFragment.kt b/Jetchat/app/src/main/java/com/example/compose/jetchat/profile/ProfileFragment.kt index daeaed63db..aa737a7830 100644 --- a/Jetchat/app/src/main/java/com/example/compose/jetchat/profile/ProfileFragment.kt +++ b/Jetchat/app/src/main/java/com/example/compose/jetchat/profile/ProfileFragment.kt @@ -21,32 +21,15 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.ExperimentalComposeUiApi -import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.platform.rememberNestedScrollInteropConnection -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels -import com.example.compose.jetchat.FunctionalityNotAvailablePopup import com.example.compose.jetchat.MainViewModel -import com.example.compose.jetchat.R -import com.example.compose.jetchat.components.JetchatAppBar import com.example.compose.jetchat.theme.JetchatTheme class ProfileFragment : Fragment() { @@ -62,45 +45,15 @@ class ProfileFragment : Fragment() { } @OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class) - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - val rootView: View = inflater.inflate(R.layout.fragment_profile, container, false) + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = + ComposeView(inflater.context).apply { + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + ) - rootView.findViewById(R.id.toolbar_compose_view).apply { - setContent { - var functionalityNotAvailablePopupShown by remember { mutableStateOf(false) } - if (functionalityNotAvailablePopupShown) { - FunctionalityNotAvailablePopup { functionalityNotAvailablePopupShown = false } - } - - JetchatTheme { - JetchatAppBar( - // Reset the minimum bounds that are passed to the root of a compose tree - modifier = Modifier.wrapContentSize(), - onNavIconPressed = { activityViewModel.openDrawer() }, - title = { }, - actions = { - // More icon - Icon( - painter = painterResource(id = R.drawable.ic_more_vert), - tint = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier - .clickable(onClick = { - functionalityNotAvailablePopupShown = true - }) - .padding(horizontal = 12.dp, vertical = 16.dp) - .height(24.dp), - contentDescription = stringResource(id = R.string.more_options), - ) - }, - ) - } - } - } - - rootView.findViewById(R.id.profile_compose_view).apply { setContent { val userData by viewModel.userData.observeAsState() - val nestedScrollInteropConnection = rememberNestedScrollInteropConnection() JetchatTheme { if (userData == null) { @@ -108,12 +61,9 @@ class ProfileFragment : Fragment() { } else { ProfileScreen( userData = userData!!, - nestedScrollInteropConnection = nestedScrollInteropConnection, ) } } } } - return rootView - } } diff --git a/Jetchat/app/src/main/res/drawable/img.png b/Jetchat/app/src/main/res/drawable/img.png new file mode 100644 index 0000000000..cc02960d89 Binary files /dev/null and b/Jetchat/app/src/main/res/drawable/img.png differ diff --git a/Jetchat/app/src/main/res/layout/fragment_profile.xml b/Jetchat/app/src/main/res/layout/fragment_profile.xml deleted file mode 100644 index 3cdf5eae3b..0000000000 --- a/Jetchat/app/src/main/res/layout/fragment_profile.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/Jetchat/gradle/libs.versions.toml b/Jetchat/gradle/libs.versions.toml index b92422ef02..6bd5fa9c8e 100644 --- a/Jetchat/gradle/libs.versions.toml +++ b/Jetchat/gradle/libs.versions.toml @@ -48,7 +48,7 @@ maps-compose = "8.4.0" media3Exoplayer = "1.11.0" media3ExoplayerHls = "1.11.0" media3UiCompose = "1.11.0" -minSdk = "23" +minSdk = "24" okhttp = "5.4.0" play-services-wearable = "20.0.1" robolectric = "4.16.1"