diff --git a/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/profile/ChatProfileScreen.kt b/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/profile/ChatProfileScreen.kt index c2bc9c2dd..5d9b368b4 100644 --- a/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/profile/ChatProfileScreen.kt +++ b/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/profile/ChatProfileScreen.kt @@ -138,11 +138,12 @@ internal fun PersonProfileScreen( Column( modifier = Modifier .fillMaxSize() + // The whole page, so the pinned bar blurs whatever scrolls under it. + .hazeSource(hazeState) .verticalScroll(rememberScrollState()) .padding(bottom = clearance), ) { ProfileHeader( - modifier = Modifier.hazeSource(hazeState), cover = person?.profile?.coverPicture, access = person?.let { BlobAccessContext.profile(it.userId) } ?: BlobAccessContext.Owned, avatar = { modifier -> @@ -287,6 +288,7 @@ internal fun PersonProfileScreen( ) } } else null, + hazeState = hazeState, onHeightChanged = { pinnedHeight = it }, ) } diff --git a/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/profile/GroupProfileScreen.kt b/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/profile/GroupProfileScreen.kt index e3feb5883..44c640b42 100644 --- a/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/profile/GroupProfileScreen.kt +++ b/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/profile/GroupProfileScreen.kt @@ -150,18 +150,18 @@ internal fun GroupProfileScreen(viewModel: ChatViewModel) { Column( modifier = Modifier .fillMaxSize() - // The page fades out under the app bar and into the pinned bar, as on iOS. + // The page fades out under the app bar. At the bottom it runs on under the pinned + // bar, which blurs it, as on iOS. .verticalScrollStateGradient( scrollState = scrollState, - showAtEnd = hasPinned, + showAtEnd = false, startInset = WindowInsets.statusBars.asPaddingValues().calculateTopPadding(), - endInset = clearance, ) + .hazeSource(hazeState) .verticalScroll(scrollState) .padding(bottom = clearance), ) { ProfileHeader( - modifier = Modifier.hazeSource(hazeState), cover = group?.coverPicture, access = group?.let { BlobAccessContext.ChatProfile(it.chatId) } ?: BlobAccessContext.Owned, avatar = { modifier -> @@ -361,6 +361,7 @@ internal fun GroupProfileScreen(viewModel: ChatViewModel) { secondaryText = if (isMember) stringResource(R.string.action_leaveChat) else null, onSecondaryClick = { viewModel.dispatchEvent(ChatViewModel.Event.LeaveChat) }, isSecondaryLoading = state.leaving, + hazeState = hazeState, onHeightChanged = { pinnedHeight = it }, ) } diff --git a/apps/flipcash/shared/common-ui/build.gradle.kts b/apps/flipcash/shared/common-ui/build.gradle.kts index 62850f8aa..2e01ea4fa 100644 --- a/apps/flipcash/shared/common-ui/build.gradle.kts +++ b/apps/flipcash/shared/common-ui/build.gradle.kts @@ -17,6 +17,7 @@ dependencies { implementation(project(":apps:flipcash:shared:theme")) implementation(libs.compose.material) + implementation(libs.bundles.haze) api(libs.compose.material.icons.extended) testImplementation(libs.robolectric) } diff --git a/apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/profile/ProfilePinnedActionBar.kt b/apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/profile/ProfilePinnedActionBar.kt index 00bd79ba9..e6f2b3b95 100644 --- a/apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/profile/ProfilePinnedActionBar.kt +++ b/apps/flipcash/shared/common-ui/src/main/kotlin/com/flipcash/shared/common/ui/profile/ProfilePinnedActionBar.kt @@ -20,13 +20,22 @@ import com.getcode.theme.CodeTheme import com.getcode.ui.components.toast.ToastBottomClearance import com.getcode.ui.theme.ButtonState import com.getcode.ui.theme.CodeButton +import dev.chrisbanes.haze.HazeInput +import dev.chrisbanes.haze.HazeProgressive +import dev.chrisbanes.haze.HazeState +import dev.chrisbanes.haze.blur.hazeBlur +import dev.chrisbanes.haze.blur.materials.HazeMaterials /** * The one action a profile pins to the bottom of its screen: a filled button, with an optional * [above] line over it (a footer, a shortfall) and an optional text action ([secondaryText]) * under it. * - * The bar sits on the screen's background, clears the navigation bar, and measures itself, + * Given the [hazeState] the screen's scroll content draws into, the bar is see-through: the content + * runs on under it and blurs in from clear at its top edge, as iOS's soft scroll edge does. Without + * one it sits on the screen's background. + * + * The bar clears the navigation bar and measures itself, * system bar included. While it is composed it asks the root toast host to rest toasts above that * height, and withdraws the request when it leaves, so show it only when something is pinned. The * same height goes to [onHeightChanged] for the caller's scroll content to leave room for; it is @@ -45,8 +54,12 @@ fun ProfilePinnedActionBar( secondaryText: String? = null, onSecondaryClick: (() -> Unit)? = null, isSecondaryLoading: Boolean = false, + hazeState: HazeState? = null, onHeightChanged: (Dp) -> Unit = {}, ) { + val background = CodeTheme.colors.background + val material = HazeMaterials.ultraThin(containerColor = background) + val density = LocalDensity.current var height by remember { mutableStateOf(0.dp) } ToastBottomClearance(height) @@ -60,7 +73,18 @@ fun ProfilePinnedActionBar( height = measured onHeightChanged(measured) } - .background(CodeTheme.colors.background) + .then( + if (hazeState != null) { + Modifier.hazeBlur( + HazeInput.Sources(hazeState), + material.then { + progressive(HazeProgressive.verticalGradient(startIntensity = 0f, endIntensity = 1f)) + }, + ) + } else { + Modifier.background(background) + }, + ) .navigationBarsPadding() // iOS: 12 above, 8 below and 8 between, on the 5dp grid. .padding(vertical = CodeTheme.dimens.staticGrid.x2),