@@ -110,8 +110,15 @@ internal fun MenuScreenContent(viewModel: MenuScreenViewModel) {
110110 }
111111
112112 is ProfileState .Unclaimed -> {
113- item(key = " settings_gear" ) {
114- SettingsGear (onClick = { viewModel.dispatchEvent(Event .OpenScreen (AppRoute .Menu .Settings )) })
113+ // As on iOS: the header keeps its cover, avatar and Edit Profile, with no Share
114+ // (there is nothing to share yet), and the claim prompt takes the stats' place.
115+ item(key = " profile_header" ) {
116+ OwnProfileHeader (
117+ profile = profileState.profile,
118+ onEdit = { viewModel.dispatchEvent(Event .OpenScreen (AppRoute .Menu .Settings )) },
119+ onShare = null ,
120+ onSettings = { viewModel.dispatchEvent(Event .OpenScreen (AppRoute .Menu .Settings )) },
121+ )
115122 }
116123 item(key = " claim_prompt" ) {
117124 Spacer (Modifier .height(UnclaimedTopSpacing ))
@@ -169,42 +176,43 @@ internal fun MenuScreenContent(viewModel: MenuScreenViewModel) {
169176/* * The viewer's profile header with the settings gear over the cover's top trailing corner. */
170177@Composable
171178private fun OwnProfileHeader (
172- profile : UserProfile ,
179+ profile : UserProfile ? ,
173180 onEdit : () -> Unit ,
174- onShare : () -> Unit ,
181+ onShare : (( ) -> Unit ) ? ,
175182 onSettings : () -> Unit ,
176183) {
177184 // The header is what scrolls under the gear, so it is the gear's frosting source.
178185 val hazeState = rememberHazeState()
179186 Box {
180187 ProfileHeader (
181188 modifier = Modifier .hazeSource(hazeState),
182- cover = profile.coverPicture,
189+ cover = profile? .coverPicture,
183190 // The viewer's own blobs, so no profile id is needed to authorize re-minting them.
184191 access = BlobAccessContext .Owned ,
185192 avatar = { modifier ->
186193 ContactAvatar (
187- image = profile.profilePicture,
188- displayName = profile.displayName,
194+ image = profile? .profilePicture,
195+ displayName = profile? .displayName.orEmpty() ,
189196 access = BlobAccessContext .Owned ,
190197 modifier = modifier,
191198 )
192199 },
193- title = profile.displayName,
194- subtitle = profile.username?.takeIf { it.isNotEmpty() }?.let { " @$it " },
195- body = profile.bio.ifEmpty { null },
200+ title = profile? .displayName?.ifEmpty { null } ,
201+ subtitle = profile? .username?.takeIf { it.isNotEmpty() }?.let { " @$it " },
202+ body = profile? .bio? .ifEmpty { null },
196203 actions = {
197204 ProfileActionButton (
198205 text = stringResource(R .string.action_editProfile),
199206 onClick = onEdit,
200207 )
201- // A named profile is the only one that gets here, which is exactly when there is
202- // something to share.
203- ProfileActionButton (
204- icon = ImageVector .vectorResource(R .drawable.ic_share_os),
205- contentDescription = stringResource(R .string.action_share),
206- onClick = onShare,
207- )
208+ // Only a named profile has something to share.
209+ if (onShare != null ) {
210+ ProfileActionButton (
211+ icon = ImageVector .vectorResource(R .drawable.ic_share_os),
212+ contentDescription = stringResource(R .string.action_share),
213+ onClick = onShare,
214+ )
215+ }
208216 },
209217 )
210218 SettingsGear (
@@ -245,18 +253,18 @@ private const val YouCardWidthFraction = 0.60f
245253private val YouCardWidth : Dp
246254 @Composable get() = CodeTheme .dimens.screenWidth * YouCardWidthFraction
247255
248- /* * Gap between the gear and the unclaimed stand-in. */
256+ /* * Gap between the header and the unclaimed stand-in (iOS 24) . */
249257private val UnclaimedTopSpacing : Dp
250- @Composable get() = CodeTheme .dimens.grid.x6
258+ @Composable get() = CodeTheme .dimens.staticGrid.x5
251259
252260/* *
253261 * What the "You" tab shows before the account has a display name: the card it *would* have, blurred
254262 * out behind a prompt to claim it. Mirrors iOS `YouScreen.setupPrompt`.
255263 *
256264 * The stand-in is the account's real scannable payload drawn over an unnamed profile, with the
257265 * card's own fill turned off so the 8% ground shows through — the same construction iOS uses. It is
258- * decoration: not tappable, not expandable, not shareable, and the header, stats and Share button are
259- * absent entirely , because there is nothing yet to show or share.
266+ * decoration: not tappable, not expandable, not shareable. It takes the stats card's place under the
267+ * header, and the header drops its Share button , because there is nothing yet to share.
260268 *
261269 * [blurEnabled] is haze's own API-31 gate, surfaced so a preview can render what an API 29/30
262270 * device draws (see `Preview_UnclaimedTipCardPrompt_NoBlur`). Leave it at the default in app code.
0 commit comments