From 67a6fa1dd77952650299bb5569b58d9ef4fc84ca Mon Sep 17 00:00:00 2001 From: AdamGrzybkowski Date: Wed, 13 Aug 2025 14:14:11 +0200 Subject: [PATCH 1/5] Change the background overlay darkness on API 11 and lower --- .../home/components/BlurredHeaderBackground.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/components/BlurredHeaderBackground.kt b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/components/BlurredHeaderBackground.kt index f5906135..32851da1 100644 --- a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/components/BlurredHeaderBackground.kt +++ b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/components/BlurredHeaderBackground.kt @@ -1,12 +1,12 @@ package com.gravatar.app.homeUi.presentation.home.components +import android.os.Build import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.BlurredEdgeTreatment -import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.blur import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp @@ -27,8 +27,14 @@ internal fun BlurredHeaderBackground( Box( modifier = Modifier .matchParentSize() - .background(Color.Black.copy(alpha = 0.15f)) + .background(Color.Black.copy(alpha = scrimAlpha)) ) content() } } + +private val scrimAlpha = if (Build.VERSION.SDK_INT >= 32) { + 0.15f +} else { + 0.6f +} From 011dbb24145de4fbc839d52da91024735e618b61 Mon Sep 17 00:00:00 2001 From: AdamGrzybkowski Date: Wed, 13 Aug 2025 14:28:14 +0200 Subject: [PATCH 2/5] Wrap TopBarPickerPopup in GravatarTheme to override fixed dark theme --- .../home/components/topbar/TopBarPickerPopup.kt | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/components/topbar/TopBarPickerPopup.kt b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/components/topbar/TopBarPickerPopup.kt index fce8e4f8..bcdc4395 100644 --- a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/components/topbar/TopBarPickerPopup.kt +++ b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/components/topbar/TopBarPickerPopup.kt @@ -52,13 +52,15 @@ internal fun TopBarPickerPopup( } } - TopBarPickerPopup( - anchorAlignment = anchorAlignment, - offset = offset, - onAboutAppClicked = onAboutAppClicked, - onDismissRequest = onDismissRequest, - onEvent = viewModel::onEvent - ) + GravatarTheme { + TopBarPickerPopup( + anchorAlignment = anchorAlignment, + offset = offset, + onAboutAppClicked = onAboutAppClicked, + onDismissRequest = onDismissRequest, + onEvent = viewModel::onEvent + ) + } } @OptIn(ExperimentalMaterial3Api::class) From 52c814273fdf32b5673134a30ffc8704a240f926 Mon Sep 17 00:00:00 2001 From: AdamGrzybkowski Date: Wed, 13 Aug 2025 14:38:32 +0200 Subject: [PATCH 3/5] Introduce Screen component with system bar theming and use it in other screens --- .../gravatar/app/design/components/Screen.kt | 21 +++++++++++++++ .../home/gravatar/GravatarScreen.kt | 27 +++++++++++-------- .../home/profile/ProfileScreen.kt | 17 +++++++----- .../presentation/home/share/ShareScreen.kt | 17 +++++++----- .../loginUi/presentation/login/LoginScreen.kt | 9 ++++--- 5 files changed, 65 insertions(+), 26 deletions(-) create mode 100644 design/src/main/kotlin/com/gravatar/app/design/components/Screen.kt diff --git a/design/src/main/kotlin/com/gravatar/app/design/components/Screen.kt b/design/src/main/kotlin/com/gravatar/app/design/components/Screen.kt new file mode 100644 index 00000000..f3a9891a --- /dev/null +++ b/design/src/main/kotlin/com/gravatar/app/design/components/Screen.kt @@ -0,0 +1,21 @@ +package com.gravatar.app.design.components + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.platform.LocalView +import androidx.core.view.WindowCompat + +@Composable +fun Screen( + appearanceLightStatusBars: Boolean = !isSystemInDarkTheme(), + content: @Composable () -> Unit, +) { + val view = LocalView.current + SideEffect { + val window = (view.context as? android.app.Activity)?.window + val controller = window?.let { WindowCompat.getInsetsController(it, view) } + controller?.isAppearanceLightStatusBars = appearanceLightStatusBars + } + content() +} diff --git a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/gravatar/GravatarScreen.kt b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/gravatar/GravatarScreen.kt index 59fb7261..802024e1 100644 --- a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/gravatar/GravatarScreen.kt +++ b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/gravatar/GravatarScreen.kt @@ -47,6 +47,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelStoreOwner import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.repeatOnLifecycle +import com.gravatar.app.design.components.Screen import com.gravatar.app.design.components.snackbar.SnackbarType import com.gravatar.app.design.components.snackbar.showGravatarSnackbar import com.gravatar.app.design.theme.GravatarAppTheme @@ -139,17 +140,21 @@ internal fun GravatarScreen( } } - GravatarScreen( - uiState = uiState, - onEvent = viewModel::onEvent, - onTakePictureClicked = takePhotoCallback, - onPickMediaClicked = { - if (!mediaPickerLaunched) { - pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) - mediaPickerLaunched = true - } - }, - ) + Screen( + appearanceLightStatusBars = false + ) { + GravatarScreen( + uiState = uiState, + onEvent = viewModel::onEvent, + onTakePictureClicked = takePhotoCallback, + onPickMediaClicked = { + if (!mediaPickerLaunched) { + pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) + mediaPickerLaunched = true + } + }, + ) + } } @OptIn(ExperimentalMaterial3Api::class) diff --git a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/profile/ProfileScreen.kt b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/profile/ProfileScreen.kt index 46d2cc03..aa17ef98 100644 --- a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/profile/ProfileScreen.kt +++ b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/profile/ProfileScreen.kt @@ -36,6 +36,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelStoreOwner import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.repeatOnLifecycle +import com.gravatar.app.design.components.Screen import com.gravatar.app.design.components.snackbar.SnackbarType import com.gravatar.app.design.components.snackbar.showGravatarSnackbar import com.gravatar.app.homeUi.R @@ -79,12 +80,16 @@ internal fun ProfileScreen( } } - ProfileScreen( - uiState = uiState, - onEvent = { event -> - viewModel.onEvent(profileEvent = event) - } - ) + Screen( + appearanceLightStatusBars = false + ) { + ProfileScreen( + uiState = uiState, + onEvent = { event -> + viewModel.onEvent(profileEvent = event) + } + ) + } } @OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) diff --git a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareScreen.kt b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareScreen.kt index 40797d0e..ddc112c6 100644 --- a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareScreen.kt +++ b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareScreen.kt @@ -28,6 +28,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelStoreOwner import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.repeatOnLifecycle +import com.gravatar.app.design.components.Screen import com.gravatar.app.design.theme.GravatarAppTheme import com.gravatar.app.homeUi.GravatarFileProvider import com.gravatar.app.homeUi.presentation.home.components.topbar.components.about.AboutAppDialog @@ -73,12 +74,16 @@ internal fun ShareScreen( } } - ShareScreen( - uiState = uiState, - onEvent = { event -> - viewModel.onEvent(event) - } - ) + Screen( + appearanceLightStatusBars = false + ) { + ShareScreen( + uiState = uiState, + onEvent = { event -> + viewModel.onEvent(event) + } + ) + } } @Composable diff --git a/loginUi/src/main/kotlin/com/gravatar/app/loginUi/presentation/login/LoginScreen.kt b/loginUi/src/main/kotlin/com/gravatar/app/loginUi/presentation/login/LoginScreen.kt index 0cdb1b08..c31a0ed9 100644 --- a/loginUi/src/main/kotlin/com/gravatar/app/loginUi/presentation/login/LoginScreen.kt +++ b/loginUi/src/main/kotlin/com/gravatar/app/loginUi/presentation/login/LoginScreen.kt @@ -38,6 +38,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.repeatOnLifecycle +import com.gravatar.app.design.components.Screen import com.gravatar.app.design.components.snackbar.GravatarSnackbarHost import com.gravatar.app.design.components.snackbar.SnackbarType import com.gravatar.app.design.components.snackbar.showGravatarSnackbar @@ -54,9 +55,11 @@ import org.koin.androidx.compose.koinViewModel @Composable fun LoginScreen() { - LoginScreen( - viewModel = koinViewModel(), - ) + Screen { + LoginScreen( + viewModel = koinViewModel(), + ) + } } @Composable From 56afbdd86d7b164ee6ca27189e1fbc2e908f29c6 Mon Sep 17 00:00:00 2001 From: AdamGrzybkowski Date: Wed, 13 Aug 2025 14:39:01 +0200 Subject: [PATCH 4/5] Force dark theme on other headers --- .../gravatar/components/GravatarHeader.kt | 91 ++++++------- .../home/share/components/ShareHeader.kt | 120 +++++++++--------- 2 files changed, 108 insertions(+), 103 deletions(-) diff --git a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/gravatar/components/GravatarHeader.kt b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/gravatar/components/GravatarHeader.kt index 7a00a2f4..135d9fab 100644 --- a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/gravatar/components/GravatarHeader.kt +++ b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/gravatar/components/GravatarHeader.kt @@ -28,6 +28,7 @@ import androidx.constraintlayout.compose.ExperimentalMotionApi import androidx.constraintlayout.compose.MotionLayout import androidx.constraintlayout.compose.MotionScene import androidx.constraintlayout.compose.layoutId +import com.gravatar.app.design.theme.GravatarAppTheme import com.gravatar.app.homeUi.R import com.gravatar.app.homeUi.presentation.home.components.BlurredHeaderBackground import com.gravatar.app.homeUi.presentation.home.components.GravatarAvatarWithShadow @@ -59,56 +60,58 @@ internal fun GravatarHeader( .decodeToString() } - BlurredHeaderBackground( - avatarUrl = avatarUrl, - modifier = modifier.fillMaxWidth(), - ) { - MotionLayout( - motionScene = MotionScene(content = motionScene), - progress = progress, - modifier = modifier - .systemBarsPadding() - .fillMaxWidth() - .height(expandedHeight) + GravatarAppTheme(darkTheme = true) { + BlurredHeaderBackground( + avatarUrl = avatarUrl, + modifier = modifier.fillMaxWidth(), ) { - // Main circular avatar - GravatarAvatarWithShadow( - url = avatarUrl, - borderShape = CircleShape, - modifier = Modifier - .layoutId("avatar1") - ) - - // Secondary square avatar - GravatarAvatarWithShadow( - url = avatarUrl, - borderShape = RoundedCornerShape(8.dp), - modifier = Modifier - .layoutId("avatar2") - ) - - // Menu button - IconButton( - onClick = { - topBarMenuVisible = true - }, - modifier = Modifier - .layoutId("menuButton") + MotionLayout( + motionScene = MotionScene(content = motionScene), + progress = progress, + modifier = modifier + .systemBarsPadding() + .fillMaxWidth() + .height(expandedHeight) ) { - Image( - painter = painterResource(id = R.drawable.more_button), - contentDescription = stringResource(R.string.gravatar_tab_header_more_options), + // Main circular avatar + GravatarAvatarWithShadow( + url = avatarUrl, + borderShape = CircleShape, + modifier = Modifier + .layoutId("avatar1") ) - } - Box(modifier = Modifier.layoutId("menuPopup")) { - if (topBarMenuVisible) { - TopBarPickerPopup( - anchorAlignment = Alignment.End, - onDismissRequest = { topBarMenuVisible = false }, - onAboutAppClicked = onAboutAppClicked + // Secondary square avatar + GravatarAvatarWithShadow( + url = avatarUrl, + borderShape = RoundedCornerShape(8.dp), + modifier = Modifier + .layoutId("avatar2") + ) + + // Menu button + IconButton( + onClick = { + topBarMenuVisible = true + }, + modifier = Modifier + .layoutId("menuButton") + ) { + Image( + painter = painterResource(id = R.drawable.more_button), + contentDescription = stringResource(R.string.gravatar_tab_header_more_options), ) } + + Box(modifier = Modifier.layoutId("menuPopup")) { + if (topBarMenuVisible) { + TopBarPickerPopup( + anchorAlignment = Alignment.End, + onDismissRequest = { topBarMenuVisible = false }, + onAboutAppClicked = onAboutAppClicked + ) + } + } } } } diff --git a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/components/ShareHeader.kt b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/components/ShareHeader.kt index 683cdd01..92eb6982 100644 --- a/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/components/ShareHeader.kt +++ b/homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/components/ShareHeader.kt @@ -42,80 +42,82 @@ internal fun ShareHeader( ) { var topBarMenuVisible by remember { mutableStateOf(false) } - BlurredHeaderBackground( - avatarUrl = avatarUrl, - modifier = modifier.fillMaxWidth() - ) { - Row( - modifier = Modifier - .statusBarsPadding() - .padding(16.dp), - horizontalArrangement = Arrangement.spacedBy(22.dp), - verticalAlignment = Alignment.Top + GravatarAppTheme(darkTheme = true) { + BlurredHeaderBackground( + avatarUrl = avatarUrl, + modifier = modifier.fillMaxWidth() ) { - Column( - verticalArrangement = Arrangement.spacedBy(16.dp), + Row( modifier = Modifier - .padding(top = 6.dp) - .weight(1f), + .statusBarsPadding() + .padding(16.dp), + horizontalArrangement = Arrangement.spacedBy(22.dp), + verticalAlignment = Alignment.Top ) { - QrCode(vCardQrCodeData) - Text( - text = stringResource(R.string.share_tab_scan_qr_code), - style = MaterialTheme.typography.bodyMedium, - color = Color.White, - ) - } - Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { - Box { + Column( + verticalArrangement = Arrangement.spacedBy(16.dp), + modifier = Modifier + .padding(top = 6.dp) + .weight(1f), + ) { + QrCode(vCardQrCodeData) + Text( + text = stringResource(R.string.share_tab_scan_qr_code), + style = MaterialTheme.typography.bodyMedium, + color = Color.White, + ) + } + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Box { + IconButton( + onClick = { + topBarMenuVisible = true + }, + modifier = Modifier + .size(MENU_BUTTON_SIZE) + ) { + Image( + painter = painterResource(id = R.drawable.more_button), + contentDescription = stringResource(R.string.gravatar_tab_header_more_options), + ) + } + if (topBarMenuVisible) { + TopBarPickerPopup( + anchorAlignment = Alignment.End, + offset = DpOffset(0.dp, 6.dp), + onDismissRequest = { topBarMenuVisible = false }, + onAboutAppClicked = { + topBarMenuVisible = false + onAboutAppClicked() + } + ) + } + } IconButton( onClick = { - topBarMenuVisible = true + onShareClick() }, modifier = Modifier .size(MENU_BUTTON_SIZE) ) { Image( - painter = painterResource(id = R.drawable.more_button), - contentDescription = stringResource(R.string.gravatar_tab_header_more_options), + painter = painterResource(id = R.drawable.share_button), + contentDescription = stringResource(R.string.share_tab_share_contact_information_button) ) } - if (topBarMenuVisible) { - TopBarPickerPopup( - anchorAlignment = Alignment.End, - offset = DpOffset(0.dp, 6.dp), - onDismissRequest = { topBarMenuVisible = false }, - onAboutAppClicked = { - topBarMenuVisible = false - onAboutAppClicked() - } + IconButton( + onClick = { + onExpandQrCodeClick() + }, + modifier = Modifier + .size(MENU_BUTTON_SIZE) + ) { + Image( + painter = painterResource(id = R.drawable.expand_button), + contentDescription = stringResource(R.string.share_tab_expand_qr_code) ) } } - IconButton( - onClick = { - onShareClick() - }, - modifier = Modifier - .size(MENU_BUTTON_SIZE) - ) { - Image( - painter = painterResource(id = R.drawable.share_button), - contentDescription = stringResource(R.string.share_tab_share_contact_information_button) - ) - } - IconButton( - onClick = { - onExpandQrCodeClick() - }, - modifier = Modifier - .size(MENU_BUTTON_SIZE) - ) { - Image( - painter = painterResource(id = R.drawable.expand_button), - contentDescription = stringResource(R.string.share_tab_expand_qr_code) - ) - } } } } From 33bc5264bdcee9ebebf0b54aee62516eaac71f9e Mon Sep 17 00:00:00 2001 From: AdamGrzybkowski Date: Thu, 14 Aug 2025 11:14:56 +0200 Subject: [PATCH 5/5] Add missing dependencies that were causing the release build to fail --- design/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/design/build.gradle.kts b/design/build.gradle.kts index 0d4c6223..f6d7783f 100644 --- a/design/build.gradle.kts +++ b/design/build.gradle.kts @@ -9,6 +9,7 @@ android { dependencies { implementation(libs.androidx.material3) + implementation(libs.androidx.core.ktx) testImplementation(libs.junit) testImplementation(project(":testUtils"))