···9595 _logger.d('Getting posts on bluesky API for: ${uris.length} URIs');
9696 final blueskyClient = bsky.Bluesky.fromSession(_client.authRepository.session!);
9797 final posts = await blueskyClient.feed.getPosts(uris: uris);
9898- final filteredPosts = filter ? _parseAndFilterPosts<PostView>(
9999- rawPosts: posts.data.posts,
100100- fromJson: PostView.fromJson,
101101- getPostView: (post) => post,
102102- source: 'bsky',
103103- ) : posts.data.posts.map((post) => PostView.fromJson(post.toJson())).toList();
9898+ final filteredPosts = filter
9999+ ? _parseAndFilterPosts<PostView>(
100100+ rawPosts: posts.data.posts,
101101+ fromJson: PostView.fromJson,
102102+ getPostView: (post) => post,
103103+ source: 'bsky',
104104+ )
105105+ : posts.data.posts.map((post) => PostView.fromJson(post.toJson())).toList();
104106 return filteredPosts;
105107 }
106108 return _client.executeWithRetry(() async {
···705707706708 List<Label> labels = [];
707709710710+ final List<String> labelers = sources?.isNotEmpty == true ? sources! : ['did:plc:pbgyr67hftvpoqtvaurpsctc'];
711711+712712+ final parameters = {'uriPatterns': uris, 'sources': labelers, 'limit': limit, 'cursor': cursor};
713713+708714 final response = await atproto.get(
709715 NSID.parse('com.atproto.label.queryLabels'),
710710- parameters: {'uriPatterns': uris, 'sources': sources, 'limit': limit, 'cursor': cursor},
716716+ headers: {'atproto-proxy': 'did:plc:pbgyr67hftvpoqtvaurpsctc#atproto_labeler'},
717717+ parameters: parameters,
718718+ to: (jsonMap) => jsonMap,
719719+ adaptor: (uint8) => jsonDecode(utf8.decode(uint8)),
711720 );
712712-713713- if (response.data case EmptyData()) {
714714- return (labels: labels, cursor: null);
715715- }
721721+ _logger.d('parameters: $parameters');
722722+ _logger.d('Labels retrieved: ${response.data}');
716723717724 for (final label in response.data['labels'] as List<dynamic>) {
718718- labels.add(Label.fromJson(label as Map<String, Object?>));
725725+ final cleanLabel = label as Map<String, Object?>;
726726+ cleanLabel.remove('sig'); // i am NOT going to convert that sig string into a UInt8List i am going to PASS OUT and DIE
727727+ cleanLabel.putIfAbsent(
728728+ 'src',
729729+ () => 'did:plc:pbgyr67hftvpoqtvaurpsctc',
730730+ ); // fix this when there's multiple labelers support. for now idgaf. src is null for some reason in the response
731731+ labels.add(Label.fromJson(cleanLabel));
719732 }
720733721734 return (labels: labels, cursor: response.data['cursor'] as String?);
-1
lib/src/core/routing/app_router.dart
···6363 page: FeedSettingsRoute.page,
6464 path: '/settings',
6565 customRouteBuilder: feedSettingsBuilder,
6666- children: [AutoRoute(page: FeedListRoute.page, path: 'list')], // settings tabs
6766 ),
68676968 // Deep linking routes or routes that will be pushed on top of everything
···2121 _activeFeed = await GetIt.instance<SettingsRepository>().getActiveFeed();
2222 }
23232424+ @override
2525+ bool get poolFull => _tasks.length >= FeedState.poolSize;
2626+2427 late final SQLCacheInterface _sqlCache;
2528 late final SparkLogger _logger;
2629 late final CacheManagerInterface _cacheManager;
···6969 /// This should be called when the manager is no longer needed to prevent
7070 /// resource leaks and ensure graceful shutdown of ongoing operations.
7171 Future<void> dispose();
7272+7373+ bool get poolFull;
7274}
···1717import 'package:sparksocial/src/features/feed/providers/feed_state.dart';
1818import 'package:sparksocial/src/features/settings/providers/settings_provider.dart';
1919import 'package:sparksocial/src/core/storage/cache/cache_manager_interface.dart';
2020+import 'package:sparksocial/src/core/network/atproto/data/models/labeler_models.dart';
20212122part 'feed_provider.g.dart';
2223···253254 loadingFirstLoad = fetchedCount != 0;
254255 }
255256257257+ // Filter out posts that should be hidden based on label preferences
258258+ final filteredUris = await _filterHiddenPosts(uris, extraInfo);
259259+256260 state = state.copyWith(
257257- loadedPosts: uris,
261261+ loadedPosts: filteredUris,
258262 freshPostCount: 0, // Set to 0 as per strategy
259263 extraInfo: extraInfo,
260264 cursor: newCursor, // Store the cursor from fetch
261265 loadingFirstLoad: loadingFirstLoad,
262266 );
263267 _isWaitingForFreshPostsAtEnd = state.length <= 1;
264264- _logger.d('First load finished with ${uris.length} posts');
268268+ _logger.d('First load finished with ${filteredUris.length} posts (${uris.length - filteredUris.length} hidden)');
265269 } catch (e, stackTrace) {
266270 _logger.e('Error in loadAndUpdateFirstLoad: $e', stackTrace: stackTrace);
267267- state = state.copyWith(loadingFirstLoad: false, error: true, isEndOfNetworkFeed: true);
268268- _isWaitingForFreshPostsAtEnd = true;
271271+ state = state.copyWith(loadingFirstLoad: false, error: true);
269272 } finally {
270273 _isLoadingInProgress = false;
271274 }
···347350 }
348351 _logger.d('Downloading ${nonExistingUris.length} new posts');
349352 final nonExistingPosts = await _feedRepository.getPosts(nonExistingUris, bluesky: _shouldUseBlueskyAPI());
353353+354354+ // gets the subscribed labels for the new posts
355355+ final followedLabelers = await _settingsRepository.getFollowedLabelers();
356356+ List<Label> newPostLabels = [];
357357+ try {
358358+ final (cursor: _, labels: fetchedLabels) = await _feedRepository.getLabels(nonExistingUris, sources: followedLabelers);
359359+ newPostLabels = fetchedLabels;
360360+ } catch (e) {
361361+ _logger.e('Error getting labels for new posts: $e');
362362+ newPostLabels = [];
363363+ }
364364+365365+ List<PostView> postsWithLabels = [];
366366+ for (var post in nonExistingPosts) {
367367+ newPostLabels.addAll(post.labels ?? []); // labels from the post
368368+ if (post.record.selfLabels != null) {
369369+ final recordLabels = <Label>[];
370370+ for (SelfLabel selfLabel in post.record.selfLabels!) {
371371+ recordLabels.add(
372372+ Label(uri: post.uri.toString(), value: selfLabel.value, src: post.uri.toString(), createdAt: post.indexedAt),
373373+ );
374374+ }
375375+ newPostLabels.addAll(recordLabels); // self labels
376376+ }
377377+ postsWithLabels.add(post.copyWith(labels: newPostLabels));
378378+ }
379379+350380 int newPostsCached = 0;
351381 int errorCount = 0;
352352- for (PostView post in nonExistingPosts) {
382382+ for (PostView post in postsWithLabels) {
353383 // concurrent execution
354384 _downloadManager.submitTask(
355385 DownloadTask(
···370400 // it is divided in half to prevent the feed from getting stuck loading big files
371401 // (the other half will keep being downloaded, but you can start downloading another batch to be more efficient)
372402 // should use pool to have a limit on the number of concurrent downloads
373373- if (newPostsCached == (nonExistingPosts.length - errorCount) >> 1) {
403403+ if (newPostsCached == (nonExistingPosts.length - errorCount) >> 1 && !_downloadManager.poolFull) {
374404 _isCaching = false;
375405 _logger.d('Set isCaching to false after downloading $newPostsCached posts');
376406 }
···493523 extraInfo.updateAll((key, value) => (postLabels: value.postLabels, hardcodedFeedExtraInfo: newExtraInfos[key]));
494524 }
495525 }
526526+527527+ // Filter out posts that should be hidden based on label preferences
528528+ final filteredUris = await _filterHiddenPosts(uris, extraInfo);
529529+496530 state = state.copyWith(
497497- loadedPosts: [...state.loadedPosts, ...uris],
498498- freshPostCount: state.freshPostCount - uris.length, // Only subtract the actual new posts loaded
531531+ loadedPosts: [...state.loadedPosts, ...filteredUris],
532532+ freshPostCount: state.freshPostCount - filteredUris.length, // Only subtract the actual new posts loaded
499533 extraInfo: extraInfo,
500534 loadingFirstLoad: false,
501535 );
502536503503- _logger.d('Load complete. Total loaded posts: ${state.loadedPosts.length}, remaining fresh: ${state.freshPostCount}');
537537+ _logger.d(
538538+ 'Load complete. Total loaded posts: ${state.loadedPosts.length}, remaining fresh: ${state.freshPostCount} (${uris.length - filteredUris.length} hidden)',
539539+ );
504540 } else {
505541 _logger.d('No fresh posts available to load (freshPostCount: ${state.freshPostCount})');
506542 }
···577613578614 _logger.d('Removing post ${uri.toString()}, adjusting index from $currentIndex to $newIndex');
579615 state = state.copyWith(loadedPosts: updatedPosts, index: newIndex);
616616+ }
617617+618618+ /// Checks if a post should be hidden based on its labels and user preferences
619619+ Future<bool> _shouldHidePost(AtUri uri, List<Label> postLabels) async {
620620+ final hideAdultContent = await _settingsRepository.getHideAdultContent();
621621+ for (final label in postLabels) {
622622+ try {
623623+ final labelPreference = await _settingsRepository.getLabelPreference(label.value);
624624+ if (labelPreference.setting == Setting.hide || (labelPreference.adultOnly && hideAdultContent)) {
625625+ _logger.d('Hiding post ${uri.toString()} due to label: ${label.value}');
626626+ return true;
627627+ }
628628+ } catch (e) {
629629+ // Label preference not found, continue checking other labels
630630+ continue;
631631+ }
632632+ }
633633+ return false;
634634+ }
635635+636636+ /// Filters URIs based on label preferences, removing posts that should be hidden
637637+ Future<List<AtUri>> _filterHiddenPosts(
638638+ List<AtUri> uris,
639639+ LinkedHashMap<AtUri, ({List<Label> postLabels, HardcodedFeedExtraInfo? hardcodedFeedExtraInfo})> extraInfo,
640640+ ) async {
641641+ final filteredUris = <AtUri>[];
642642+643643+ for (final uri in uris) {
644644+ final postExtraInfo = extraInfo[uri];
645645+ if (postExtraInfo != null) {
646646+ final shouldHide = await _shouldHidePost(uri, postExtraInfo.postLabels);
647647+ if (!shouldHide) {
648648+ filteredUris.add(uri);
649649+ }
650650+ } else {
651651+ // No extra info means no labels, so include the post
652652+ filteredUris.add(uri);
653653+ }
654654+ }
655655+656656+ return filteredUris;
580657 }
581658}