import 'package:logging/logging.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:stack_trace/stack_trace.dart'; import '../models/auth/profile.dart'; import '../models/timeline.dart'; import '../models/timeline_identifiers.dart'; import 'entry_tree_item_services.dart'; part 'timeline_services.g.dart'; enum TimelineRefreshType { refresh, loadOlder, loadNewer, } final _tcLogger = Logger('TimelineCleanerProvider'); @Riverpod(keepAlive: true) class TimelineMaintainer extends _$TimelineMaintainer { @override List build(Profile profile) { return []; } void add(TimelineIdentifiers timelineId) { _tcLogger.fine('Adding timeline: $timelineId'); state.add(timelineId); ref.notifyListeners(); } void sweep(String statusId) { for (final t in state) { final result = ref .read(timelineManagerProvider(profile, t).notifier) .removeTimelineEntry(statusId); _tcLogger.finest( 'Attempting to remove $statusId from $t but was it there? $result'); } } Future loadNewerForPersonalTimelines() async { const standardMyTimelines = [ TimelineType.home, TimelineType.global, TimelineType.local, TimelineType.self, ]; final openMyTimelines = state.where((i) => standardMyTimelines.contains(i.timeline)).toList(); if (openMyTimelines.isEmpty) { _tcLogger.finest( 'No personal timelines open that need checking for new entries'); } for (final t in openMyTimelines) { await ref .read(timelineManagerProvider(profile, t).notifier) .updateTimeline(TimelineRefreshType.loadNewer); } } } final _tmpLogger = Logger('TimelineManagerProvider'); @Riverpod(keepAlive: true) class TimelineManager extends _$TimelineManager { @override Timeline build(Profile profile, TimelineIdentifiers timelineId) { _tmpLogger.info('Building for $profile for $timelineId'); Future.microtask(() async => ref.read(timelineMaintainerProvider(profile).notifier).add(timelineId)); return Timeline(timelineId); } Future updateTimeline( TimelineRefreshType refreshType, ) async { _tmpLogger.info( 'Updating w/$refreshType for timeline $timelineId for profile $profile '); late final int lowestId; late final int highestId; late final InsertionType insertionType; switch (refreshType) { case TimelineRefreshType.refresh: lowestId = 0; highestId = 0; insertionType = InsertionType.end; break; case TimelineRefreshType.loadOlder: lowestId = state.lowestStatusId; highestId = 0; insertionType = InsertionType.end; break; case TimelineRefreshType.loadNewer: lowestId = 0; highestId = state.highestStatusId; insertionType = InsertionType.beginning; break; } (await ref .read(timelineUpdaterProvider(profile).notifier) .updateTimeline(timelineId, lowestId, highestId)) .match(onSuccess: (posts) { _tmpLogger .finest('Posts returned for adding to $timelineId: ${posts.length}'); final oldState = state; final newState = state = state.addOrUpdate( posts.map((p) => p.id).toList(), insertionType: insertionType, ); _tmpLogger.finest( 'Old post count: ${oldState.posts.length}, New Post count: ${newState.posts.length}'); _tmpLogger.finest('Old state == New state? ${oldState == newState}'); }, onError: (error) { _tmpLogger.severe( 'Error updating timeline: $timelineId}', Trace.current(), ); }); } bool removeTimelineEntry(String id) { final hadItem = state.removeTimelineEntry(id); if (hadItem) { ref.notifyListeners(); } _tmpLogger.finest('Remove entry $id? $hadItem'); return hadItem; } }