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.

docs: update changelog with profile context and offline mode

+10 -53
+8
CHANGELOG.md
··· 45 45 46 46 - Starter pack & list views 47 47 48 + #### 2026-03-29 49 + 50 + - Offline/low-connectivity detection and handling with cached data display 51 + 52 + #### 2026-04-01 53 + 54 + - Profile Context (Blocking/Blocked By, Lists, etc.) section accessible from profiles 55 + 48 56 #### 2026-04-11 49 57 50 58 - Follow hygiene feature to identify and unfollow inactive or problematic accounts
+1 -7
docs/tasks/phase-4.md
··· 14 14 15 15 ## M15 — Offline Reading & Network Resilience 16 16 17 - - [x] `ConnectivityCubit` via **connectivity_plus** — expose network state stream 18 - - [x] Cache last-fetched feed page as serialised JSON in Drift 19 - - [x] Display cached data immediately on launch, refresh in background 20 - - [x] "You're offline" banner when connectivity is lost 21 - - [x] Disable network-dependent actions (compose, like, repost, follow) when offline with tooltip 22 - - [x] Notifications and DM screens show "No connection" empty state when offline with no cache 23 - - [x] In Debug/Dev mode, add "Simulate Offline" toggle in settings to test offline UI 17 + Complete [2026-03-29](../../CHANGELOG.md#2026-03-29) 24 18 25 19 ## M16 — Jump to Profile 26 20
+1 -46
docs/tasks/phase-5.md
··· 76 76 77 77 ## M23 - Profile Context (Constellation) 78 78 79 - ### Core - Constellation Client 80 - 81 - - [x] `ConstellationClient` - thin HTTP client (`http` package) targeting configurable base URL (default `https://constellation.microcosm.blue`), 10s timeout, `User-Agent: lazurite` 82 - - [x] `Settings` - add `constellation_url` key with default value; expose in Settings screen under "Advanced" 83 - - [x] `getBacklinksCount(subject, source)` → `int` total 84 - - [x] `getDistinct(subject, source, {limit, cursor})` → `({int total, List<String> dids, String? cursor})` 85 - - [x] `getBacklinks(subject, source, {limit, cursor})` → `({int total, List<ConstellationLinkRecord> records, String? cursor})` 86 - - [x] `getManyToMany(subject, source, pathToOther, {limit, cursor})` → `({List<ManyToManyItem> items, String? cursor})` 87 - - [x] `ConstellationLinkRecord` model - `did`, `collection`, `rkey` 88 - - [x] `ManyToManyItem` model - `linkRecord: ConstellationLinkRecord`, `otherSubject: String` 89 - 90 - ### Core - Profile Context Repository 91 - 92 - - [x] `ProfileContextRepository` - depends on `ConstellationClient` + `Bluesky` 93 - - [x] `getBlockedByCount(did)` - calls `getBacklinksCount(did, 'app.bsky.graph.block:subject')` 94 - - [x] `getBlockedByProfiles(did, {cursor})` - calls `getDistinct`, hydrates DIDs via `bluesky.actor.getProfiles` (batched 25), returns `({List<ProfileView> profiles, String? cursor, int total})` 95 - - [x] `getBlockingProfiles(did, {cursor})` - calls `com.atproto.repo.listRecords(repo: did, collection: 'app.bsky.graph.block')`, extracts subject DIDs, hydrates via `getProfiles`, returns same shape 96 - - [x] `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})` 97 - 98 - ### Cubit 99 - 100 - - [x] `ProfileContextCubit` - manages tab state, loads counts on init for all three tabs 101 - - [x] `ProfileContextState` - fields: `blockedByCount`, `blockingCount`, `listsOnCount`, per-tab `status` (initial/loading/loaded/error), per-tab item list + cursor 102 - - [x] `loadBlockedBy({cursor})` - fetches page of blocked-by profiles, appends to state 103 - - [x] `loadBlocking({cursor})` - fetches page of blocking profiles, appends to state 104 - - [x] `loadListsOn({cursor})` - fetches page of lists, appends to state 105 - - [x] Handle own-profile vs other-profile: blocking tab only available for own profile 106 - 107 - ### UI 108 - 109 - - [x] Profile screen overflow menu - add "Profile Context" entry (available for all profiles) 110 - - [x] Route: `/profile-context?did={DID}` in `app_router.dart` 111 - - [x] `ProfileContextScreen` - `AppBar` (title + handle subtitle), `TabBar` with 3 tabs, `BlocProvider` creating cubit 112 - - [x] **Blocked By tab** - count header, "Show accounts" expand, paginated profile tiles (avatar, name, handle), tap → profile navigation, contextualizing note text 113 - - [x] **Blocking tab** - same layout; hidden or explanatory text when viewing other profiles 114 - - [x] **Lists tab** - list cards (name, owner, purpose badge, member count, description), grouped by purpose, tap → `/list?uri=` 115 - - [x] Per-tab states: skeleton shimmer (loading), contextual empty state, inline error with retry 116 - - [x] Pull-to-refresh per tab 117 - - [x] Infinite scroll pagination per tab 118 - 119 - ### Tests 120 - 121 - - [x] Unit tests: `ConstellationClient` - each endpoint method, error handling, timeout, URL construction 122 - - [x] Unit tests: `ProfileContextRepository` - DID hydration batching, list URI derivation, cursor passthrough 123 - - [x] Unit tests: `ProfileContextCubit` - state transitions for each tab, own-profile vs other-profile logic, pagination appending 124 - - [x] 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 79 + Completed [2026-04-01](../../CHANGELOG.md#2026-04-01)