Skip to content

feat(mobile): circle-clip avatar viewer + banner viewer on profile cover photo#14445

Merged
dylanjeffers merged 1 commit into
mainfrom
feat/mobile-viewer-circle-and-banner
Jun 4, 2026
Merged

feat(mobile): circle-clip avatar viewer + banner viewer on profile cover photo#14445
dylanjeffers merged 1 commit into
mainfrom
feat/mobile-viewer-circle-and-banner

Conversation

@dylanjeffers
Copy link
Copy Markdown
Contributor

Follow-ups to #14444.

1. Avatar viewer keeps the circle

The avatar on the profile screen is a circle, so the full-screen viewer should match — not a letterboxed square on black. AvatarViewer now renders the image inside a fixed square wrapper with borderRadius: size / 2 + overflow: 'hidden' and resizeMode='cover', sized to min(windowWidth, windowHeight) - 48 px so it doesn't touch the screen edges on landscape phones.

2. Banner viewer on the profile cover photo

Tapping the cover photo at the top of the profile screen now opens a parallel full-screen viewer for the banner. Uses WidthSizes.SIZE_2000 (the largest cached cover-photo size — the header uses SIZE_640) and resizeMode='contain' to preserve the cover photo's native aspect ratio on the black backdrop. No circular clip.

Refactor: shared FullscreenImageViewer

Rather than duplicate the modal/gesture/close-button machinery between Avatar and Banner viewers, the chrome is factored into a new FullscreenImageViewer that owns:

  • <Modal transparent animationType='fade' statusBarTranslucent> + light-content StatusBar
  • Pan gesture via react-native-gesture-handler — Reanimated shared values follow the finger in any direction, dismiss thresholds at 120 px distance or 800 px/s velocity, 200 ms withTiming spring-back, translation reset on each open
  • X close button at insets.top + 8 px with hitSlop, accessible label/role
  • Black #000 backdrop

AvatarViewer and BannerViewer are now thin wrappers that supply the image content as children and a closeAccessibilityLabel. Same dismiss UX in both.

ProfileCoverPhoto tap wiring

The existing <CoverPhoto> is wrapped in a <Pressable> that flips local isViewerOpen state. The artist-badge overlay (which has no own onPress) sits absolute on top and stays visually unchanged — taps in the badge corner still hit the Pressable underneath.

+ <Pressable onPress={handleOpenViewer} accessibilityRole='imagebutton' …>
    <CoverPhoto userId={user_id}>
      <AnimatedBlurView … />
      {isArtist ? <Animated.View style={styles.darkOverlay} /> : null}
    </CoverPhoto>
+ </Pressable>
  {isArtist ? <Animated.View style={[styles.artistBadge, badgeStyle]}>…</Animated.View> : null}
+ <BannerViewer userId={user_id} isOpen={isViewerOpen} onClose={handleCloseViewer} />

Files

File Change
packages/mobile/src/screens/profile-screen/FullscreenImageViewer.tsx new — shared chrome
packages/mobile/src/screens/profile-screen/AvatarViewer.tsx refactored — circle clip child of FullscreenImageViewer
packages/mobile/src/screens/profile-screen/BannerViewer.tsx newSIZE_2000, resizeMode='contain', child of FullscreenImageViewer
packages/mobile/src/screens/profile-screen/ProfileCoverPhoto.tsx Pressable wrap + BannerViewer mount

Net diff: +288 / −144.

Verification

  • tsc --noEmit clean in packages/mobile
  • Manual: tap an avatar → fades in, image rendered as a circle on black backdrop.
  • Manual: tap the cover photo → fades in, image rendered with contain aspect ratio.
  • Manual: short drag on either viewer → springs back.
  • Manual: longer drag or flick in any of the four directions → dismisses.
  • Manual: tap the X in the top-right → dismisses.
  • Manual: tap the artist badge (where present) → still navigates to CoinDetailsScreen without opening the avatar viewer.
  • Manual: cover-photo parallax/blur on scroll still works the same once the Pressable is in place.

🤖 Generated with Claude Code

…ver photo

Follow-ups to #14444:

1. **Avatar viewer keeps the circle.** The avatar on the profile screen is
   a circle, so the full-screen viewer should clip to a circle too — not a
   letterboxed square on black. AvatarViewer now renders the image inside
   a fixed-size square wrapper with `borderRadius: size / 2` +
   `overflow: 'hidden'` and `resizeMode='cover'`, sized to fit the smaller
   window dimension minus 24px padding so it doesn't touch the edges.

2. **Banner viewer on the profile cover photo.** Tapping the cover photo
   at the top of the profile screen now opens a parallel full-screen
   viewer for the banner. Uses `WidthSizes.SIZE_2000` (the largest cached
   cover-photo size), `resizeMode='contain'` to preserve the cover photo's
   aspect ratio on the black backdrop. No circular clip.

To avoid duplicating the modal/gesture/close-button machinery, factored
the chrome out of the existing AvatarViewer into a shared
**FullscreenImageViewer** that owns:
- `<Modal transparent animationType='fade' statusBarTranslucent>`
- The pan gesture (Reanimated shared values, 120 px / 800 px-s dismiss
  thresholds, 200 ms spring-back, translation reset on open)
- The X close button (top-right, safe-area top inset + 8 px, hitSlop)
- The black `#000` backdrop + `StatusBar light-content`

AvatarViewer and BannerViewer are now thin wrappers around it that
describe their image shape and source. Same dismiss UX in both.

ProfileCoverPhoto wraps the existing `<CoverPhoto>` in a `<Pressable>`
that opens the BannerViewer; the artist-badge overlay (which has no own
press handler) sits absolute on top and stays visually unchanged.

Files:
- packages/mobile/src/screens/profile-screen/FullscreenImageViewer.tsx (new)
- packages/mobile/src/screens/profile-screen/AvatarViewer.tsx (refactor)
- packages/mobile/src/screens/profile-screen/BannerViewer.tsx (new)
- packages/mobile/src/screens/profile-screen/ProfileCoverPhoto.tsx (tap wiring)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jun 4, 2026

⚠️ No Changeset found

Latest commit: a24461f

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@dylanjeffers dylanjeffers merged commit 7de135c into main Jun 4, 2026
3 checks passed
@dylanjeffers dylanjeffers deleted the feat/mobile-viewer-circle-and-banner branch June 4, 2026 03:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant