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.

at main 306 lines 9.9 kB view raw
1import 'dart:typed_data'; 2 3import 'package:atproto_core/atproto_core.dart' show AtUri, BlobRef; 4import 'package:bluesky/app_bsky_actor_defs.dart'; 5import 'package:bluesky/app_bsky_feed_defs.dart'; 6import 'package:bluesky/app_bsky_graph_defs.dart'; 7import 'package:bluesky/app_bsky_graph_getlists.dart'; 8import 'package:bluesky/app_bsky_graph_getlistswithmembership.dart'; 9import 'package:lazurite/core/network/app_view_request_context.dart'; 10import 'package:lazurite/features/moderation/data/moderation_service.dart'; 11 12class ListRepository { 13 ListRepository({ 14 required dynamic bluesky, 15 ModerationService? moderationService, 16 String? appViewProvider, 17 String Function()? appViewProviderResolver, 18 }) : _bluesky = bluesky, 19 _moderationService = moderationService, 20 _appViewContext = AppViewRequestContext( 21 appViewProvider: appViewProvider, 22 appViewProviderResolver: appViewProviderResolver, 23 ); 24 25 final dynamic _bluesky; 26 final ModerationService? _moderationService; 27 final AppViewRequestContext _appViewContext; 28 29 Future<ListsResult> getLists({ 30 required String actor, 31 String? cursor, 32 int limit = 50, 33 bool includeReference = false, 34 }) async { 35 final response = await _bluesky.graph.getLists( 36 actor: actor, 37 cursor: cursor, 38 limit: limit, 39 purposes: includeReference ? null : _listPurposes, 40 $headers: _appViewContext.appBskyHeadersForEndpoint( 41 'app.bsky.graph.getLists', 42 await _moderationService?.headersForRequest(), 43 ), 44 ); 45 46 return ListsResult(lists: _filterLists(response.data.lists), cursor: response.data.cursor); 47 } 48 49 Future<ListDetailResult> getList({required AtUri listUri, String? cursor, int limit = 50}) async { 50 final response = await _bluesky.graph.getList( 51 list: listUri, 52 cursor: cursor, 53 limit: limit, 54 $headers: _appViewContext.appBskyHeadersForEndpoint( 55 'app.bsky.graph.getList', 56 await _moderationService?.headersForRequest(), 57 ), 58 ); 59 60 return ListDetailResult( 61 list: response.data.list, 62 items: _filterListItems(response.data.items), 63 cursor: response.data.cursor, 64 ); 65 } 66 67 Future<ListFeedResult> getListFeed({required AtUri listUri, String? cursor, int limit = 50}) async { 68 final response = await _bluesky.feed.getListFeed( 69 list: listUri, 70 cursor: cursor, 71 limit: limit, 72 $headers: _appViewContext.appBskyHeadersForEndpoint( 73 'app.bsky.feed.getListFeed', 74 await _moderationService?.headersForRequest(), 75 ), 76 ); 77 78 return ListFeedResult(posts: _filterFeedPosts(response.data.feed), cursor: response.data.cursor); 79 } 80 81 Future<ListsWithMembershipResult> getListsWithMembership({ 82 required String actor, 83 String? cursor, 84 int limit = 50, 85 }) async { 86 final response = await _bluesky.graph.getListsWithMembership( 87 actor: actor, 88 cursor: cursor, 89 limit: limit, 90 purposes: _membershipPurposes, 91 $headers: _appViewContext.appBskyHeadersForEndpoint( 92 'app.bsky.graph.getListsWithMembership', 93 await _moderationService?.headersForRequest(), 94 ), 95 ); 96 97 return ListsWithMembershipResult( 98 lists: response.data.listsWithMembership.where((entry) => !_shouldFilterList(entry.list)).toList(growable: false), 99 cursor: response.data.cursor, 100 ); 101 } 102 103 Future<List<ProfileViewBasic>> searchActorsTypeahead({required String query, int limit = 10}) async { 104 final response = await _bluesky.actor.searchActorsTypeahead( 105 q: query, 106 limit: limit, 107 $headers: _appViewContext.appBskyHeadersForEndpoint( 108 'app.bsky.actor.searchActorsTypeahead', 109 await _moderationService?.headersForRequest(), 110 ), 111 ); 112 113 return _filterProfiles(response.data.actors); 114 } 115 116 Future<String> addListItem({required AtUri listUri, required String subjectDid}) async { 117 final response = await _bluesky.graph.listitem.create( 118 list: listUri, 119 subject: subjectDid, 120 createdAt: DateTime.now(), 121 $headers: _appViewContext.appBskyHeadersWithoutProxy(await _moderationService?.headersForRequest()), 122 ); 123 124 return response.data.uri.toString(); 125 } 126 127 Future<void> removeListItem({required AtUri listItemUri}) async { 128 await _bluesky.graph.listitem.delete( 129 rkey: listItemUri.rkey, 130 $headers: _appViewContext.appBskyHeadersWithoutProxy(await _moderationService?.headersForRequest()), 131 ); 132 } 133 134 Future<void> muteList({required AtUri listUri}) async { 135 await _bluesky.graph.muteActorList( 136 list: listUri, 137 $headers: _appViewContext.appBskyHeadersWithoutProxy(await _moderationService?.headersForRequest()), 138 ); 139 } 140 141 Future<void> unmuteList({required AtUri listUri}) async { 142 await _bluesky.graph.unmuteActorList( 143 list: listUri, 144 $headers: _appViewContext.appBskyHeadersWithoutProxy(await _moderationService?.headersForRequest()), 145 ); 146 } 147 148 Future<String> blockList({required AtUri listUri}) async { 149 final response = await _bluesky.graph.listblock.create( 150 subject: listUri, 151 createdAt: DateTime.now(), 152 $headers: _appViewContext.appBskyHeadersWithoutProxy(await _moderationService?.headersForRequest()), 153 ); 154 155 return response.data.uri.toString(); 156 } 157 158 Future<void> unblockList({required AtUri blockUri}) async { 159 await _bluesky.graph.listblock.delete( 160 rkey: blockUri.rkey, 161 $headers: _appViewContext.appBskyHeadersWithoutProxy(await _moderationService?.headersForRequest()), 162 ); 163 } 164 165 Future<BlobRef?> uploadListAvatar({required List<int> bytes, String mimeType = 'image/jpeg'}) async { 166 final response = await _bluesky.atproto.repo.uploadBlob( 167 bytes: Uint8List.fromList(bytes), 168 $headers: {'Content-Type': mimeType}, 169 ); 170 return response.data.blob.ref; 171 } 172 173 Future<AtUri> createList({ 174 required String userDid, 175 required String name, 176 required String purpose, 177 String? description, 178 BlobRef? avatarBlob, 179 }) async { 180 final record = <String, dynamic>{ 181 r'$type': 'app.bsky.graph.list', 182 'purpose': purpose, 183 'name': name, 184 'createdAt': DateTime.now().toUtc().toIso8601String(), 185 }; 186 if (description != null) record['description'] = description; 187 if (avatarBlob != null) record['avatar'] = avatarBlob.toJson(); 188 189 final response = await _bluesky.atproto.repo.createRecord( 190 repo: userDid, 191 collection: 'app.bsky.graph.list', 192 record: record, 193 ); 194 return response.data.uri; 195 } 196 197 Future<void> updateList({ 198 required AtUri listUri, 199 required String userDid, 200 required String name, 201 required String purpose, 202 String? description, 203 BlobRef? avatarBlob, 204 }) async { 205 final record = <String, dynamic>{ 206 r'$type': 'app.bsky.graph.list', 207 'purpose': purpose, 208 'name': name, 209 'createdAt': DateTime.now().toUtc().toIso8601String(), 210 }; 211 if (description != null) record['description'] = description; 212 if (avatarBlob != null) record['avatar'] = avatarBlob.toJson(); 213 214 await _bluesky.atproto.repo.putRecord( 215 repo: userDid, 216 collection: 'app.bsky.graph.list', 217 rkey: listUri.rkey, 218 record: record, 219 ); 220 } 221 222 Future<void> deleteList({required AtUri listUri, required String userDid}) async { 223 await _bluesky.atproto.repo.deleteRecord(repo: userDid, collection: 'app.bsky.graph.list', rkey: listUri.rkey); 224 } 225 226 List<ListView> _filterLists(List<ListView> lists) { 227 return lists.where((list) => !_shouldFilterList(list)).toList(growable: false); 228 } 229 230 List<ListItemView> _filterListItems(List<ListItemView> items) { 231 final moderationService = _moderationService; 232 if (moderationService == null) { 233 return items; 234 } 235 236 return items.where((item) => !moderationService.shouldFilterProfileInList(item.subject)).toList(growable: false); 237 } 238 239 List<ProfileViewBasic> _filterProfiles(List<ProfileViewBasic> profiles) { 240 final moderationService = _moderationService; 241 if (moderationService == null) { 242 return profiles; 243 } 244 245 return profiles 246 .where((profile) => !moderationService.shouldFilterProfileBasicInList(profile)) 247 .toList(growable: false); 248 } 249 250 List<FeedViewPost> _filterFeedPosts(List<FeedViewPost> posts) { 251 final moderationService = _moderationService; 252 if (moderationService == null) { 253 return posts; 254 } 255 256 return posts.where((post) => !moderationService.shouldFilterFeedViewPostInList(post)).toList(growable: false); 257 } 258 259 bool _shouldFilterList(ListView list) { 260 final moderationService = _moderationService; 261 if (moderationService == null) { 262 return false; 263 } 264 265 return moderationService.shouldFilterProfileInList(list.creator); 266 } 267 268 static const List<GraphGetListsPurposes> _listPurposes = [ 269 GraphGetListsPurposes.knownValue(data: KnownGraphGetListsPurposes.curatelist), 270 GraphGetListsPurposes.knownValue(data: KnownGraphGetListsPurposes.modlist), 271 ]; 272 273 static const List<GraphGetListsWithMembershipPurposes> _membershipPurposes = [ 274 GraphGetListsWithMembershipPurposes.knownValue(data: KnownGraphGetListsWithMembershipPurposes.curatelist), 275 GraphGetListsWithMembershipPurposes.knownValue(data: KnownGraphGetListsWithMembershipPurposes.modlist), 276 ]; 277} 278 279class ListsResult { 280 const ListsResult({required this.lists, this.cursor}); 281 282 final List<ListView> lists; 283 final String? cursor; 284} 285 286class ListDetailResult { 287 const ListDetailResult({required this.list, required this.items, this.cursor}); 288 289 final ListView list; 290 final List<ListItemView> items; 291 final String? cursor; 292} 293 294class ListFeedResult { 295 const ListFeedResult({required this.posts, this.cursor}); 296 297 final List<FeedViewPost> posts; 298 final String? cursor; 299} 300 301class ListsWithMembershipResult { 302 const ListsWithMembershipResult({required this.lists, this.cursor}); 303 304 final List<ListWithMembership> lists; 305 final String? cursor; 306}