~ knotless Search/
↑↓ move↵ open⇧↵ all results

grain.social/grain-ios

1 collaborator
0
1
main
90 lines · 3.8 KB Raw History
# Stories Feature Design

**Goal:** Add Instagram-style ephemeral stories to the native iOS app, matching the web client's functionality for viewing, browsing, and creating stories.

**Scope:** Story strip on feed, story viewer, profile story indicator, story creation. Story archive is out of scope.

---

## 1. Story Strip (Feed)

Horizontal `ScrollView` at the top of `FeedView`, above gallery cards.

- First item: "+" button (opens `StoryCreateView` sheet) for authenticated users
- Remaining items: circular avatars with gradient ring (purple > accent > cyan) for users with active stories (24h window)
- Display name label below each avatar
- Tapping an avatar opens `StoryViewer` as a fullscreen cover, starting at that author
- Data: `getStoryAuthors()` endpoint, fetched on load and pull-to-refresh

**New files:**
- `Grain/Views/Stories/StoryStripView.swift` — horizontal avatar strip component
- `Grain/ViewModels/StoryStripViewModel.swift` — fetches story authors

**Shared component:**
- `Grain/Views/Stories/StoryRingView.swift` — gradient ring wrapper, reused on profile avatar

## 2. Story Viewer

Wire up the existing `StoryViewer.swift` and present it from feed and profile.

- Presented as `.fullScreenCover`
- Receives full authors list + starting index to enable swiping between authors
- Each author's stories fetched on demand via `getStories(actor:)`
- Auto-advance: 5-second timer per story, animated progress bars at top
- Navigation: tap left 1/3 = previous, tap right 2/3 = next
- Swipe left/right to jump between authors
- Dismisses after last story of last author
- Delete button on own stories via `deleteRecord`
- Status bar hidden for immersive experience

**Modified files:**
- `Grain/Views/Stories/StoryViewer.swift` — enhance with auto-advance timer, author list navigation
- `Grain/Views/Feed/FeedView.swift` — add fullscreen cover presentation
- `Grain/Views/Profile/ProfileView.swift` — add fullscreen cover on avatar tap

## 3. Profile Story Indicator

When a user has active stories, their profile avatar shows a gradient ring and becomes tappable to open the viewer.

- `ProfileDetailViewModel` already fetches stories — use `viewModel.stories.isEmpty` to determine ring visibility
- Wrap `AvatarView` with `StoryRingView` when stories exist
- Tap avatar to open `StoryViewer` as fullscreen cover
- No ring and no tap action when no stories

**Modified files:**
- `Grain/Views/Profile/ProfileView.swift` — conditional ring + tap gesture on avatar

## 4. Story Creation

Sheet presented from the "+" button in the story strip. Reuses photo processing from `CreateGalleryView`.

- Single photo selection via `PhotosPicker`
- Photo preview after selection
- Image resize: max 2000px, max 5MB with JPEG quality binary search
- Location auto-populated from EXIF GPS via reverse geocode (Nominatim), converted to H3 index
- Editable location text field
- Optional "Post to Bluesky" toggle
- Post: `uploadBlob` then `createRecord` with collection `social.grain.story`
- Record fields: media (blob ref), aspectRatio, location (H3 value + name), address, createdAt
- On success: refresh story strip, dismiss sheet

**New files:**
- `Grain/Views/Stories/StoryCreateView.swift`

**Reused from gallery creation:**
- Photo processing / resize utilities
- EXIF GPS extraction
- Reverse geocoding
- H3 conversion (SwiftyH3)

---

## Existing Infrastructure

Already built and ready to use:

- **Models:** `GrainStory`, `GrainStoryAuthor`, `StoryRecord` (in `Grain/Models/`)
- **API endpoints:** `getStories`, `getStory`, `getStoryArchive`, `getStoryAuthors` (in `Grain/API/Endpoints/StoryEndpoints.swift`)
- **Viewer component:** `StoryViewer.swift` (needs enhancement but base exists)
- **OAuth scope:** `repo:social.grain.story` already included
- **Photo processing:** resize, EXIF extraction, blob upload all exist in `CreateGalleryView`