Skip to content

refactor(profile): key the profile header and cover on what they render - #993

Merged
bmc08gt merged 1 commit into
mainfrom
refactor/profile-header-render-inputs
Oct 6, 2026
Merged

bmc08gt merged 1 commit into
mainfrom
refactor/profile-header-render-inputs

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator

ProfileCoverBanner and ProfileHeaderView took a UserID and derived the cover subject and the avatar from it, so a group profile could not use either. This changes them to take their render inputs, with no visual change for user profiles.

  • Cover: the banner takes a ProfileCover, which is a picture plus the AvatarSubject it loads under. Build it with .user(_:picture:) or .group(_:picture:). The load, blurhash and placeholder paths are the same as before.
  • Header: takes cover, an avatar view slot, and title / subtitle / bodyText, matching Android's ProfileHeader(cover, avatar, title, subtitle, body, …). The ring and the 42pt overlap stay in the header. User callers pass ProfileHeaderAvatar, which draws the same ContactAvatarView at 84pt. The banner-controls, row-actions and under-subtitle slots are unchanged, and so are the accessibility identifiers.
  • AvatarSubject.groupCover(ConversationID): loads the original blob under .chatProfile and caches it in the cover pool, not the thumbnail pool. cache(for:) is now an exhaustive switch, so a new subject can't fall through to the thumbnail cache.
  • ProfileHeaderMetrics: the layout constants move to this non-generic enum, so callers below the header no longer spell out ProfileHeaderView<EmptyView, EmptyView, EmptyView>.inset.

Callers updated: UserProfileScreen, YouScreen, EditProfileScreen, ChangeCoverPictureScreen, ProfileStatsCard.

The group profile screen will build on this in a separate PR. It also needs Conversation.coverPicture, which is on main (881cdc4) but not yet on this stack.

ProfileCoverBanner and ProfileHeaderView took a UserID and derived the
cover subject and the avatar from it, so a group profile could not use
either.

The banner now takes a ProfileCover (picture plus the subject it loads
under), built with .user(_:picture:) or .group(_:picture:). The header
takes that cover, an avatar view slot, and title/subtitle/bodyText,
matching Android's ProfileHeader(cover, avatar, title, subtitle, body).
The ring and overlap stay in the header. User callers pass a
ProfileHeaderAvatar, which draws the same ContactAvatarView at 84pt.

AvatarSubject gains groupCover(ConversationID): the original blob,
authorized as .chatProfile, cached in the cover pool. Layout constants
move to a non-generic ProfileHeaderMetrics so callers below the header
don't spell out its generic parameters.
@bmc08gt
bmc08gt changed the base branch from feat/profile-refresh-copy to main October 6, 2026 20:15
@bmc08gt
bmc08gt force-pushed the refactor/profile-header-render-inputs branch from 79ad9ef to d1e5c5e Compare October 6, 2026 20:15
@bmc08gt
bmc08gt added this pull request to stack #997 October 6, 2026 21:56
@bmc08gt
bmc08gt merged commit 82240bc into main Oct 6, 2026
2 of 3 checks passed
bmc08gt added a commit that referenced this pull request Oct 6, 2026
…-groups

* origin/main:
  feat(chat): rebuild the group profile on the profile refresh (#995)
  refactor(profile): key the profile header and cover on what they render (#993)
bmc08gt added a commit that referenced this pull request Oct 6, 2026
#993 moved the profile inset to ProfileHeaderMetrics and gave
ProfileHeaderView a fourth generic parameter, so the old
ProfileHeaderView<EmptyView, EmptyView, EmptyView>.inset no longer compiles.
bmc08gt added a commit that referenced this pull request Oct 6, 2026
* feat(profile): favorite public groups on profiles

Edit Profile gets a "Favorite Public Groups" card that opens a picker of
the user's public groups, saved with SetFeaturedGroups. The You tab and
other users' profiles list those groups under the stats card and open the
chat on tap; the section hides when there are none.

The picker's candidates are the currently featured groups plus joined
public groups, so a group the user has since left can still be removed.
Selection keeps the server's order, appends new picks, and caps at 10,
which the contract enforces as a validation error rather than a result
code. A denied save (a group went private) gets its own dialog.

Own-profile state lives in a FeaturedGroups unit on SessionContainer,
shared by the You tab, the Edit Profile card and the picker. Featured
groups stay out of ConversationStore: they are list-shaped and may belong
to chats the viewer is not in.

* fix(profile): draw favorite public groups as Figma does

The section now sits under a divider with a heading-weight title, and its
rows drop the card and separators for a 48pt rounded-square picture beside
the title and description (node 10961:111). ContactAvatarView takes an
optional corner radius for that; every existing caller stays a circle.

Another person's featured groups now report a failed load instead of
dropping it silently, matching the own-profile path.

* fix(profile): match the favorite groups picker to Figma

Titles the screen "Favorite Groups", drops the row separators, and adds
the footer line from node 10961:188. The search prompt now reads "Search
your public groups".

* fix(profile): tighten the favorite groups list and its tokens

Group descriptions use appTextSmall, the subtitle token RecipientRow uses,
instead of the smaller appTextCaption, and wrap to two lines. Rows drop the
66pt minimum height that stacked on the list's own insets.

The picker shows a spinner until the group feed is read, so the list
appears whole instead of growing from the featured groups. Save sits below
the list, as in the other Edit Profile editors, rather than over it.

* fix(profile): keep the group search field in place and space out favorite groups

The picker swapped a spinner for the List while loading, which rebuilt the
List and dropped the search field attached to it. The List now stays, with
the spinner drawn over it, and the field is pinned with
navigationBarDrawer(displayMode: .always) so it shows before any scroll.

Profile rows get 16pt between them (was 2pt) and 24pt under the heading
(was 12pt).

* fix(profile): blur the group list under a pinned Save button

Save is pinned with scrollEdgeBar(.bottom), so rows scroll under it. A new
hardScrollEdge(for:) modifier in FlipcashUI swaps the app-wide soft edge for
the hard one on this list, which blurs the rows out instead of leaving them
readable. The disabled filled style is a 10% tint, so the button gets a
backgroundMain backing in its own shape to stop rows showing through it.

* fix(profile): drop the hard scroll edge under the group picker's Save

The hard edge drew a full-width blurred band with a line across it behind
the button. The picker goes back to the app-wide soft edge, and the
button's own backing keeps rows from showing through it. hardScrollEdge
had no other caller, so it goes too.

* fix(profile): read the favorite groups inset from ProfileHeaderMetrics

#993 moved the profile inset to ProfileHeaderMetrics and gave
ProfileHeaderView a fourth generic parameter, so the old
ProfileHeaderView<EmptyView, EmptyView, EmptyView>.inset no longer compiles.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant