@@ -8,10 +8,12 @@ import androidx.compose.foundation.layout.Box
88import androidx.compose.foundation.layout.Column
99import androidx.compose.foundation.layout.Row
1010import androidx.compose.foundation.layout.Spacer
11+ import androidx.compose.foundation.layout.WindowInsets
1112import androidx.compose.foundation.layout.fillMaxSize
1213import androidx.compose.foundation.layout.fillMaxWidth
1314import androidx.compose.foundation.layout.height
1415import androidx.compose.foundation.layout.padding
16+ import androidx.compose.foundation.layout.statusBarsPadding
1517import androidx.compose.foundation.rememberScrollState
1618import androidx.compose.foundation.verticalScroll
1719import androidx.compose.material.icons.Icons
@@ -26,10 +28,11 @@ import androidx.compose.material3.ExperimentalMaterial3Api
2628import androidx.compose.material3.Icon
2729import androidx.compose.material3.IconButton
2830import androidx.compose.material3.MaterialTheme
29- import androidx.compose.material3.Scaffold
3031import androidx.compose.material3.Text
3132import androidx.compose.material3.TopAppBar
33+ import androidx.compose.material3.TopAppBarDefaults
3234import androidx.compose.runtime.Composable
35+ import androidx.compose.runtime.DisposableEffect
3336import androidx.compose.runtime.collectAsState
3437import androidx.compose.runtime.getValue
3538import androidx.compose.runtime.mutableStateOf
@@ -41,6 +44,8 @@ import androidx.compose.ui.draw.blur
4144import androidx.compose.ui.graphics.Brush
4245import androidx.compose.ui.graphics.Color
4346import androidx.compose.ui.platform.LocalContext
47+ import androidx.compose.ui.platform.LocalView
48+ import androidx.core.view.WindowCompat
4449import androidx.compose.ui.unit.dp
4550import com.purchasely.shaker.ui.components.CocktailImage
4651import io.purchasely.ext.PLYPresentationProperties
@@ -63,75 +68,49 @@ fun DetailScreen(
6368 val isFavorite = favoriteIds.contains(cocktailId)
6469 val context = LocalContext .current
6570
66- Scaffold (
67- topBar = {
68- TopAppBar (
69- title = { Text (cocktail?.name ? : " " ) },
70- navigationIcon = {
71- IconButton (onClick = onBack) {
72- Icon (Icons .AutoMirrored .Filled .ArrowBack , contentDescription = " Back" )
73- }
74- },
75- actions = {
76- IconButton (onClick = {
77- if (isPremium) {
78- viewModel.toggleFavorite()
79- } else {
80- // Free user: show favorites paywall
81- val activity = context as ? Activity ? : return @IconButton
82- // PURCHASELY: Fetch and display the paywall for the "favorites" placement
83- // Shown when a free user tries to favorite a cocktail from the detail screen
84- // Docs: https://docs.purchasely.com/quick-start/sdk-implementation/display-placements
85- Purchasely .fetchPresentation(" favorites" ) { presentation, error ->
86- if (presentation != null && presentation.type != PLYPresentationType .DEACTIVATED ) {
87- if (presentation.type == PLYPresentationType .CLIENT ) {
88- // PURCHASELY: CLIENT type — app builds its own paywall UI
89- // The presentation contains plan data but no server-built screen
90- // Docs: https://docs.purchasely.com/advanced-features/customize-screens/custom-paywall
91- Log .d(" DetailScreen" , " [Shaker] CLIENT presentation received for favorites placement — build custom UI here" )
92- // In a real app, extract plans from presentation and build native UI
93- } else {
94- presentation.display(activity) { result, plan ->
95- when (result) {
96- PLYProductViewResult .PURCHASED ,
97- PLYProductViewResult .RESTORED -> {
98- Log .d(" DetailScreen" , " [Shaker] Purchased/Restored from favorites: ${plan?.name} " )
99- viewModel.onPaywallDismissed()
100- }
101- else -> {}
102- }
103- }
104- }
105- }
106- }
107- }
108- }) {
109- Icon (
110- imageVector = if (isFavorite) Icons .Filled .Favorite else Icons .Outlined .FavoriteBorder ,
111- contentDescription = if (isFavorite) " Remove from favorites" else " Add to favorites" ,
112- tint = if (isFavorite) Color .Red else MaterialTheme .colorScheme.onSurface
113- )
114- }
115- }
116- )
71+ // Force light (white) status bar icons over the hero image
72+ val view = LocalView .current
73+ DisposableEffect (Unit ) {
74+ val window = (context as Activity ).window
75+ val controller = WindowCompat .getInsetsController(window, view)
76+ controller.isAppearanceLightStatusBars = false // white icons
77+ onDispose {
78+ controller.isAppearanceLightStatusBars = true // restore dark icons
11779 }
118- ) { innerPadding ->
80+ }
81+
82+ Box (modifier = Modifier .fillMaxSize()) {
11983 cocktail?.let { c ->
12084 Column (
12185 modifier = Modifier
12286 .fillMaxSize()
123- .padding(innerPadding)
12487 .verticalScroll(rememberScrollState())
12588 ) {
126- // Hero image
127- CocktailImage (
128- cocktail = c,
129- modifier = Modifier
130- .fillMaxWidth()
131- .height(300 .dp)
132- )
89+ // Hero image — full bleed behind status bar, with top scrim
90+ Box {
91+ CocktailImage (
92+ cocktail = c,
93+ modifier = Modifier
94+ .fillMaxWidth()
95+ .height(420 .dp)
96+ )
97+ // Dark gradient scrim for status bar / nav bar readability
98+ Box (
99+ modifier = Modifier
100+ .fillMaxWidth()
101+ .height(160 .dp)
102+ .background(
103+ Brush .verticalGradient(
104+ colors = listOf (
105+ Color .Black .copy(alpha = 0.4f ),
106+ Color .Transparent
107+ )
108+ )
109+ )
110+ )
111+ }
133112
134- Column (modifier = Modifier .padding(16 .dp)) {
113+ Column (modifier = Modifier .padding(horizontal = 20 .dp, vertical = 16 .dp)) {
135114 // Name and description
136115 Text (
137116 text = c.name,
@@ -289,5 +268,57 @@ fun DetailScreen(
289268 }
290269 }
291270 }
271+
272+ // Transparent TopAppBar overlay — behind status bar
273+ TopAppBar (
274+ title = { },
275+ windowInsets = WindowInsets (0 , 0 , 0 , 0 ),
276+ modifier = Modifier .statusBarsPadding(),
277+ navigationIcon = {
278+ IconButton (onClick = onBack) {
279+ Icon (
280+ Icons .AutoMirrored .Filled .ArrowBack ,
281+ contentDescription = " Back" ,
282+ tint = Color .White
283+ )
284+ }
285+ },
286+ actions = {
287+ IconButton (onClick = {
288+ if (isPremium) {
289+ viewModel.toggleFavorite()
290+ } else {
291+ val activity = context as ? Activity ? : return @IconButton
292+ Purchasely .fetchPresentation(" favorites" ) { presentation, error ->
293+ if (presentation != null && presentation.type != PLYPresentationType .DEACTIVATED ) {
294+ if (presentation.type == PLYPresentationType .CLIENT ) {
295+ Log .d(" DetailScreen" , " [Shaker] CLIENT presentation received for favorites placement — build custom UI here" )
296+ } else {
297+ presentation.display(activity) { result, plan ->
298+ when (result) {
299+ PLYProductViewResult .PURCHASED ,
300+ PLYProductViewResult .RESTORED -> {
301+ Log .d(" DetailScreen" , " [Shaker] Purchased/Restored from favorites: ${plan?.name} " )
302+ viewModel.onPaywallDismissed()
303+ }
304+ else -> {}
305+ }
306+ }
307+ }
308+ }
309+ }
310+ }
311+ }) {
312+ Icon (
313+ imageVector = if (isFavorite) Icons .Filled .Favorite else Icons .Outlined .FavoriteBorder ,
314+ contentDescription = if (isFavorite) " Remove from favorites" else " Add to favorites" ,
315+ tint = if (isFavorite) Color .Red else Color .White
316+ )
317+ }
318+ },
319+ colors = TopAppBarDefaults .topAppBarColors(
320+ containerColor = Color .Transparent
321+ )
322+ )
292323 }
293324}
0 commit comments