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: media playback/dl plan

+108 -24
+75
docs/specs/phase-4.md
··· 54 54 Build a `MessageBloc` with events: `MessagesRequested`, `MessagesPageLoaded`, 55 55 `MessageSent`, `MessageDeleted`, `ConvoMarkedRead`. 56 56 57 + ## Media Playback & Download 58 + 59 + Currently images open in an external browser and videos launch an external app 60 + via `url_launcher`. This milestone adds in-app media viewing and the ability to 61 + save media to the device gallery. 62 + 63 + ### Packages 64 + 65 + | Package | Purpose | 66 + | -------------------- | --------------------------------------------------------------- | 67 + | `photo_view` | Pinch-to-zoom and pan for full-screen images | 68 + | `video_player` | Flutter's official video playback plugin (HLS support built-in) | 69 + | `chewie` | Material-styled controls wrapper around `video_player` | 70 + | `dio` | HTTP downloads with progress callbacks | 71 + | `gal` | Save images and videos to the device gallery | 72 + | `permission_handler` | Request photo-library / storage write permissions | 73 + 74 + ### Image Viewer 75 + 76 + Tapping an image in a post opens a full-screen `ImageViewerScreen`. The screen 77 + is a `PageView` so multi-image posts are swipeable. Each page contains a 78 + `PhotoView` widget wrapping an `Image.network` of the `fullsize` URL. A hero 79 + animation on the thumbnail provides a smooth transition. 80 + 81 + The viewer has a transparent app bar with a close button, a download button, and 82 + a share button. Swiping down dismisses the viewer. The current page indicator 83 + appears at the bottom for multi-image posts. 84 + 85 + Alt text, when present, is shown in a semi-transparent bar at the bottom of 86 + each page. 87 + 88 + ### Video Player 89 + 90 + Tapping a video embed opens a `VideoPlayerScreen`. The player uses `chewie` 91 + wrapping Flutter's `VideoPlayerController.networkUrl` pointed at the HLS 92 + `playlist` URL. Controls include play/pause, seek bar, elapsed/total time, 93 + fullscreen toggle, and a mute button. 94 + 95 + The video thumbnail is shown as a placeholder until the player initialises. If 96 + the embed has an `aspectRatio`, the player container uses it; otherwise it 97 + defaults to 16:9. The player disposes its controller on screen pop. 98 + 99 + For GIF-style videos (`presentation: "gif"`), the player auto-plays in a loop 100 + with controls hidden and audio muted. 101 + 102 + ### Downloading Media 103 + 104 + A download button appears in the image viewer toolbar and the video player 105 + toolbar. The download flow: 106 + 107 + 1. Check and request write permission via `permission_handler` (photo library 108 + on iOS, storage or media-store on Android). 109 + 2. Download the file using `dio` with a progress callback driving a circular 110 + progress indicator on the button. 111 + 3. Save the file to the device gallery via `gal`. 112 + 4. Show a snackbar confirming success or displaying the error. 113 + 114 + For images, the download URL is the `fullsize` URL. For videos, download the 115 + highest-quality variant from the HLS playlist. Parse the `.m3u8` manifest to 116 + find the highest-bandwidth variant URL, then download that MP4 stream. 117 + 118 + Long-press on an image thumbnail in a post (without entering the viewer) should 119 + show a context menu with "Save image" and "Share" options. 120 + 121 + ### Permissions 122 + 123 + | Platform | Permission | When Requested | 124 + | ----------- | --------------------------------------- | ---------------------- | 125 + | iOS | `NSPhotoLibraryAddUsageDescription` | First download attempt | 126 + | Android 13+ | `READ_MEDIA_IMAGES`, `READ_MEDIA_VIDEO` | First download attempt | 127 + | Android <13 | `WRITE_EXTERNAL_STORAGE` | First download attempt | 128 + 129 + Declare permissions in `AndroidManifest.xml` and `Info.plist`. The app only 130 + requests permission at the moment of download, not on launch. 131 + 57 132 ## Account Switching 58 133 59 134 Support multiple authenticated accounts with full data isolation. The
+21 -4
docs/tasks/phase-4.md
··· 14 14 - [x] Mute / unmute conversations 15 15 - [x] Mark conversation as read via `chat.bsky.convo.updateRead` 16 16 17 - ## M13 — Account Switching 17 + ## M13 — Media Playback & Download 18 + 19 + - [ ] Add `photo_view`, `video_player`, `chewie`, `dio`, `gal`, `permission_handler` to `pubspec.yaml` 20 + - [ ] `ImageViewerScreen` — full-screen `PageView` of `PhotoView` widgets loading `fullsize` URLs with hero animation from thumbnail 21 + - [ ] Page indicator for multi-image posts; alt text bar at the bottom of each page 22 + - [ ] Swipe-down-to-dismiss gesture on image viewer 23 + - [ ] Download button in image viewer toolbar — request permission, download via `dio` with progress indicator, save via `gal`, show snackbar result 24 + - [ ] Share button in image viewer toolbar via `share_plus` 25 + - [ ] Long-press context menu on image thumbnails in post cards — "Save image" and "Share" options 26 + - [ ] `VideoPlayerScreen` — `chewie` wrapping `VideoPlayerController.networkUrl` with HLS `playlist` URL 27 + - [ ] Video player uses embed `aspectRatio` when available, defaults to 16:9 28 + - [ ] Video thumbnail as placeholder until player initialises; controller disposed on screen pop 29 + - [ ] GIF-presentation mode — auto-play, loop, muted, controls hidden when `presentation` is `"gif"` 30 + - [ ] Download button in video player toolbar — parse `.m3u8` for highest-bandwidth variant URL, download MP4 via `dio` with progress, save via `gal` 31 + - [ ] Declare `NSPhotoLibraryAddUsageDescription` in `Info.plist` and storage permissions in `AndroidManifest.xml` 32 + - [ ] Replace `_launchExternal` calls for image/video embeds in `PostCard` with navigation to the new viewer screens 33 + 34 + ## M14 — Account Switching 18 35 19 36 - [x] `AccountSwitcherCubit` exposing account list and active DID 20 37 - [ ] Account switcher bottom sheet UI — list accounts with avatars and handles ··· 25 42 - [ ] "Add Account" button triggers OAuth flow, inserts new `accounts` row 26 43 - [ ] Silent token refresh on account switch; navigate to login on failure 27 44 28 - ## M14 — Offline Reading & Network Resilience 45 + ## M15 — Offline Reading & Network Resilience 29 46 30 47 - [x] `ConnectivityCubit` via **connectivity_plus** — expose network state stream 31 48 - [ ] Cache last-fetched feed page as serialised JSON in Drift ··· 34 51 - [ ] Disable network-dependent actions (compose, like, repost, follow) when offline with tooltip 35 52 - [ ] Notifications and DM screens show "No connection" empty state when offline with no cache 36 53 37 - ## M15 — Jump to Profile 54 + ## M16 — Jump to Profile 38 55 39 56 - [ ] Floating action button on search screen 40 57 - [ ] Handle input dialog with autocomplete via `searchActorsTypeahead` 41 58 - [ ] Navigate to profile screen on selection or enter 42 59 - [ ] Update bottom navigation to include Notifications and Messages tabs (5-tab layout) 43 60 44 - ## M16 — Labelers & Content Moderation 61 + ## M17 — Labelers & Content Moderation 45 62 46 63 - [x] Fetch user's labeler subscriptions from preferences via `app.bsky.actor.getPreferences` (`labelersPref`) 47 64 - [ ] Include subscribed labeler DIDs in `atproto-accept-labelers` header on all XRPC requests
+12 -20
pubspec.lock
··· 181 181 dependency: "direct main" 182 182 description: 183 183 name: characters 184 - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 184 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b 185 185 url: "https://pub.dev" 186 186 source: hosted 187 - version: "1.4.0" 187 + version: "1.4.1" 188 188 charcode: 189 189 dependency: transitive 190 190 description: ··· 640 640 url: "https://pub.dev" 641 641 source: hosted 642 642 version: "1.0.5" 643 - js: 644 - dependency: transitive 645 - description: 646 - name: js 647 - sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" 648 - url: "https://pub.dev" 649 - source: hosted 650 - version: "0.7.2" 651 643 json_annotation: 652 644 dependency: "direct main" 653 645 description: ··· 716 708 dependency: transitive 717 709 description: 718 710 name: matcher 719 - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 711 + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 720 712 url: "https://pub.dev" 721 713 source: hosted 722 - version: "0.12.17" 714 + version: "0.12.19" 723 715 material_color_utilities: 724 716 dependency: transitive 725 717 description: 726 718 name: material_color_utilities 727 - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec 719 + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" 728 720 url: "https://pub.dev" 729 721 source: hosted 730 - version: "0.11.1" 722 + version: "0.13.0" 731 723 meta: 732 724 dependency: transitive 733 725 description: ··· 1097 1089 dependency: transitive 1098 1090 description: 1099 1091 name: test 1100 - sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" 1092 + sha256: "280d6d890011ca966ad08df7e8a4ddfab0fb3aa49f96ed6de56e3521347a9ae7" 1101 1093 url: "https://pub.dev" 1102 1094 source: hosted 1103 - version: "1.26.3" 1095 + version: "1.30.0" 1104 1096 test_api: 1105 1097 dependency: transitive 1106 1098 description: 1107 1099 name: test_api 1108 - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 1100 + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" 1109 1101 url: "https://pub.dev" 1110 1102 source: hosted 1111 - version: "0.7.7" 1103 + version: "0.7.10" 1112 1104 test_core: 1113 1105 dependency: transitive 1114 1106 description: 1115 1107 name: test_core 1116 - sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" 1108 + sha256: "0381bd1585d1a924763c308100f2138205252fb90c9d4eeaf28489ee65ccde51" 1117 1109 url: "https://pub.dev" 1118 1110 source: hosted 1119 - version: "0.6.12" 1111 + version: "0.6.16" 1120 1112 typed_data: 1121 1113 dependency: transitive 1122 1114 description: