···11+import 'package:atproto_core/atproto_core.dart' show AtUri;
22+import 'package:bluesky/app_bsky_actor_defs.dart';
33+import 'package:bluesky/app_bsky_graph_defs.dart';
44+import 'package:lazurite/core/network/constellation_client.dart';
55+66+class ProfileContextRepository {
77+ ProfileContextRepository({required dynamic bluesky, required ConstellationClient constellationClient})
88+ : _bluesky = bluesky,
99+ _constellation = constellationClient;
1010+1111+ final dynamic _bluesky;
1212+ final ConstellationClient _constellation;
1313+1414+ /// Returns the number of accounts that have blocked [did].
1515+ Future<int> getBlockedByCount(String did) async {
1616+ return _constellation.getBacklinksCount(did, 'app.bsky.graph.block:subject');
1717+ }
1818+1919+ /// Returns a page of profiles that have blocked [did], along with the total
2020+ /// count and a cursor for the next page.
2121+ Future<({List<ProfileView> profiles, String? cursor, int total})> getBlockedByProfiles(
2222+ String did, {
2323+ String? cursor,
2424+ }) async {
2525+ final result = await _constellation.getDistinct(
2626+ did,
2727+ 'app.bsky.graph.block:subject',
2828+ cursor: cursor,
2929+ );
3030+ final profiles = await _hydrateProfiles(result.dids);
3131+ return (profiles: profiles, cursor: result.cursor, total: result.total);
3232+ }
3333+3434+ /// Returns a page of profiles that [did] is blocking, along with a cursor.
3535+ /// Uses `com.atproto.repo.listRecords` on the actor's own repo.
3636+ /// [total] reflects the number of profiles hydrated in this page.
3737+ Future<({List<ProfileView> profiles, String? cursor, int total})> getBlockingProfiles(
3838+ String did, {
3939+ String? cursor,
4040+ }) async {
4141+ final response = await _bluesky.atproto.repo.listRecords(
4242+ repo: did,
4343+ collection: 'app.bsky.graph.block',
4444+ limit: 50,
4545+ cursor: cursor,
4646+ );
4747+4848+ final subjectDids = (response.data.records as List<dynamic>)
4949+ .map((r) => r.value['subject'] as String)
5050+ .toList();
5151+ final profiles = await _hydrateProfiles(subjectDids);
5252+ return (profiles: profiles, cursor: response.data.cursor as String?, total: profiles.length);
5353+ }
5454+5555+ /// Returns a page of lists that [did] is a member of, along with the total
5656+ /// count and a cursor for the next page.
5757+ Future<({List<ListView> lists, String? cursor, int total})> getListsOn(String did, {String? cursor}) async {
5858+ final total = await _constellation.getBacklinksCount(did, 'app.bsky.graph.listitem:subject');
5959+ final result = await _constellation.getManyToMany(
6060+ did,
6161+ 'app.bsky.graph.listitem:subject',
6262+ 'list',
6363+ cursor: cursor,
6464+ );
6565+6666+ final lists = await Future.wait(
6767+ result.items.map((item) async {
6868+ final uri = AtUri.parse(item.otherSubject);
6969+ final response = await _bluesky.graph.getList(list: uri, limit: 1);
7070+ return response.data.list as ListView;
7171+ }),
7272+ );
7373+7474+ return (lists: lists, cursor: result.cursor, total: total);
7575+ }
7676+7777+ /// Hydrates [dids] into [ProfileView] objects in batches of 25.
7878+ Future<List<ProfileView>> _hydrateProfiles(List<String> dids) async {
7979+ if (dids.isEmpty) return [];
8080+ final allProfiles = <ProfileView>[];
8181+ for (var i = 0; i < dids.length; i += 25) {
8282+ final batch = dids.sublist(i, (i + 25).clamp(0, dids.length));
8383+ final response = await _bluesky.actor.getProfiles(actors: batch);
8484+ allProfiles.addAll(response.data.profiles as List<ProfileView>);
8585+ }
8686+ return allProfiles;
8787+ }
8888+}