mobile bluesky app made with flutter lazurite.stormlightlabs.org/
mobile bluesky flutter
3
fork

Configure Feed

Select the types of activity you want to include in your feed.


title: Phase 5 Task Breakdown updated: 2026-04-01#

Phase 5 Milestones#

Core#

  • SearchRepository.searchStarterPacks() - call bluesky.graph.searchStarterPacks(q:, limit:, cursor:), return result with List<StarterPackViewBasic> and cursor
  • Add starterPacks value to SearchTab enum, update SearchTabLabel extension

Cubit#

  • SearchBloc - handle starter packs tab: dispatch search on tab switch if query present, handle LoadMoreRequested with cursor pagination
  • SearchState - add starterPacks list and starterPacksCursor fields

UI#

  • Search screen UI - add third "Starter Packs" tab pill in _buildTab row
  • Starter pack result tile widget - show name, creator handle, member count, joined stats; reuse pattern from profile starter packs tab
  • Tap result → navigate to existing starter pack detail screen (/starter-pack?uri=)
  • Infinite scroll pagination for starter packs tab

Tests#

  • Unit tests: SearchRepository.searchStarterPacks, bloc events for new tab, pagination
  • Widget tests: third tab renders, results display, empty state, tap navigation

M21 - Suggested Follows Sheet#

Core#

  • ProfileRepository.getSuggestedFollows() - call bluesky.graph.getSuggestedFollowsByActor(actor:), return List<ProfileView>

Cubit#

  • SuggestedFollowsCubit - load(actor:) fetches suggestions, exposes loaded/loading/error states

UI#

  • Suggested follows sheet widget - DraggableScrollableSheet listing ProfileView tiles with follow/unfollow toggle buttons
  • Profile screen overflow menu - add "Suggested Follows" ListTile entry; hide when viewing own profile
  • Tap entry → create cubit, show sheet with BlocProvider.value, close cubit on sheet dismiss via .whenComplete
  • Tap profile tile → pop sheet, navigate to profile screen
  • Empty state when no suggestions returned

Tests#

  • Unit tests: repository method, cubit state transitions
  • Widget tests: sheet renders profiles, follow button toggles, own-profile menu hides entry, empty state

M22 - Video Upload Limits#

Core#

  • VideoRepository (or extend settings repository) - getUploadLimits() calling bluesky.video.getUploadLimits(), return typed result

Cubit#

  • VideoUploadLimitsCubit - fetch on init, expose canUpload, remaining counts, message/error

UI#

  • Settings screen - new tile in Account section: "Video Upload Limits"
  • Tile UI - show remaining daily video count, remaining bytes formatted as MB/GB, canUpload status badge
  • Loading state while fetching, error state if request fails
  • Display server message if present; show error text with warning styling if canUpload is false

Tests#

  • Unit tests: repository method, cubit state transitions and formatting
  • Widget tests: tile renders limits, loading indicator, error state, message display

M23 - Profile Context (Constellation)#

Core - Constellation Client#

  • ConstellationClient - thin HTTP client (http package) targeting configurable base URL (default https://constellation.microcosm.blue), 10s timeout, User-Agent: lazurite
  • Settings - add constellation_url key with default value; expose in Settings screen under "Advanced"
  • getBacklinksCount(subject, source)int total
  • getDistinct(subject, source, {limit, cursor})({int total, List<String> dids, String? cursor})
  • getBacklinks(subject, source, {limit, cursor})({int total, List<ConstellationLinkRecord> records, String? cursor})
  • getManyToMany(subject, source, pathToOther, {limit, cursor})({List<ManyToManyItem> items, String? cursor})
  • ConstellationLinkRecord model - did, collection, rkey
  • ManyToManyItem model - linkRecord: ConstellationLinkRecord, otherSubject: String

Core - Profile Context Repository#

  • ProfileContextRepository - depends on ConstellationClient + Bluesky
  • getBlockedByCount(did) - calls getBacklinksCount(did, 'app.bsky.graph.block:subject')
  • getBlockedByProfiles(did, {cursor}) - calls getDistinct, hydrates DIDs via bluesky.actor.getProfiles (batched 25), returns ({List<ProfileView> profiles, String? cursor, int total})
  • getBlockingProfiles(did, {cursor}) - calls com.atproto.repo.listRecords(repo: did, collection: 'app.bsky.graph.block'), extracts subject DIDs, hydrates via getProfiles, returns same shape
  • getListsOn(did, {cursor}) - calls getManyToMany(did, 'app.bsky.graph.listitem:subject', 'list'), derives list AT-URIs from otherSubject, hydrates via bluesky.graph.getList, returns ({List<ListView> lists, String? cursor, int total})

Cubit#

  • ProfileContextCubit - manages tab state, loads counts on init for all three tabs
  • ProfileContextState - fields: blockedByCount, blockingCount, listsOnCount, per-tab status (initial/loading/loaded/error), per-tab item list + cursor
  • loadBlockedBy({cursor}) - fetches page of blocked-by profiles, appends to state
  • loadBlocking({cursor}) - fetches page of blocking profiles, appends to state
  • loadListsOn({cursor}) - fetches page of lists, appends to state
  • Handle own-profile vs other-profile: blocking tab only available for own profile

UI#

  • Profile screen overflow menu - add "Profile Context" entry (available for all profiles)
  • Route: /profile-context?did={DID} in app_router.dart
  • ProfileContextScreen - AppBar (title + handle subtitle), TabBar with 3 tabs, BlocProvider creating cubit
  • Blocked By tab - count header, "Show accounts" expand, paginated profile tiles (avatar, name, handle), tap → profile navigation, contextualizing note text
  • Blocking tab - same layout; hidden or explanatory text when viewing other profiles
  • Lists tab - list cards (name, owner, purpose badge, member count, description), grouped by purpose, tap → /list?uri=
  • Per-tab states: skeleton shimmer (loading), contextual empty state, inline error with retry
  • Pull-to-refresh per tab
  • Infinite scroll pagination per tab

Tests#

  • Unit tests: ConstellationClient - each endpoint method, error handling, timeout, URL construction
  • Unit tests: ProfileContextRepository - DID hydration batching, list URI derivation, cursor passthrough
  • Unit tests: ProfileContextCubit - state transitions for each tab, own-profile vs other-profile logic, pagination appending
  • Widget tests: screen renders 3 tabs, blocked-by count + expand, profile tiles render and navigate, list cards render and navigate, empty states, error + retry, blocking tab hidden for non-own profiles