import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'package:result_monad/result_monad.dart'; import '../globals.dart'; import '../models/TimelineIdentifiers.dart'; import '../models/entry_tree_item.dart'; import '../models/exec_error.dart'; import '../models/group_data.dart'; import '../models/timeline.dart'; import '../models/timeline_entry.dart'; import 'auth_service.dart'; import 'entry_manager_service.dart'; enum TimelineRefreshType { refresh, loadOlder, loadNewer, } class TimelineManager extends ChangeNotifier { static final _logger = Logger('$TimelineManager'); final cachedTimelines = {}; final _groups = {}; void clear() { cachedTimelines.clear(); _groups.clear(); getIt().clear(); notifyListeners(); } Result, ExecError> getGroups() { if (_groups.isEmpty) { _refreshGroupData(); return Result.ok([]); } return Result.ok(_groups.values.toList()); } Future _refreshGroupData() async { _logger.finest('Refreshing member group data '); await getIt() .currentClient .andThenAsync((client) => client.getGroups()) .match( onSuccess: (groups) { _groups.clear(); for (final group in groups) { _groups[group.id] = group; } notifyListeners(); }, onError: (error) { _logger.severe('Error getting list data: $error'); }, ); } FutureResult createNewStatus(String text, {String spoilerText = '', String inReplyToId = ''}) async { final result = await getIt().createNewStatus( text, spoilerText: spoilerText, inReplyToId: inReplyToId, ); if (result.isSuccess) { _logger.finest('Notifying listeners of new status created'); notifyListeners(); } return result; } Result getEntryById(String id) { _logger.finest('Getting entry for $id'); return getIt().getEntryById(id); } Result getPostTreeEntryBy(String id) { _logger.finest('Getting post for $id'); return getIt().getPostTreeEntryBy(id); } // refresh timeline gets statuses newer than the newest in that timeline Result, ExecError> getTimeline(TimelineIdentifiers type) { _logger.finest('Getting timeline $type'); final posts = cachedTimelines[type]?.posts; if (posts != null) { return Result.ok(posts); } updateTimeline(type, TimelineRefreshType.refresh); return Result.ok([]); } /// /// id is the id of a post or comment in the chain, including the original post. FutureResult refreshStatusChain(String id) async { _logger.finest('Refreshing post $id'); final result = await getIt().refreshStatusChain(id); if (result.isSuccess) { for (final t in cachedTimelines.values) { t.addOrUpdate([result.value]); } notifyListeners(); } return result; } FutureResult resharePost(String id) async { final result = await getIt().resharePost(id); if (result.isSuccess) { for (final t in cachedTimelines.values) { t.addOrUpdate([result.value]); } notifyListeners(); } return result; } FutureResult unResharePost(String id) async { final result = await getIt().unResharePost(id); if (result.isSuccess) { for (final t in cachedTimelines.values) { t.addOrUpdate([result.value]); } notifyListeners(); } return result; } Future updateTimeline( TimelineIdentifiers type, TimelineRefreshType refreshType, ) async { _logger.finest('Updating w/$refreshType for timeline $type '); final timeline = cachedTimelines.putIfAbsent(type, () => Timeline(type)); late final int lowestId; late final int highestId; switch (refreshType) { case TimelineRefreshType.refresh: lowestId = timeline.highestStatusId == 0 ? timeline.highestStatusId : timeline.highestStatusId + 1; highestId = 0; break; case TimelineRefreshType.loadOlder: lowestId = timeline.lowestStatusId; highestId = 0; break; case TimelineRefreshType.loadNewer: lowestId = 0; highestId = timeline.highestStatusId; break; } (await getIt() .updateTimeline(type, lowestId, highestId)) .match(onSuccess: (posts) { _logger.finest('Posts returned for adding to $type: ${posts.length}'); timeline.addOrUpdate(posts); notifyListeners(); }, onError: (error) { _logger.severe('Error getting timeline: $type}'); }); } FutureResult toggleFavorited( String id, bool newStatus) async { _logger.finest('Attempting toggling favorite $id to $newStatus'); final result = await getIt().toggleFavorited(id, newStatus); if (result.isFailure) { _logger.finest('Error toggling favorite $id: ${result.error}'); return result; } final update = result.value; for (final timeline in cachedTimelines.values) { update.entry.parentId.isEmpty ? timeline.addOrUpdate([update]) : timeline.tryUpdateComment(update); } notifyListeners(); return result; } // Should put backing store on timelines and entity manager so can recover from restart faster // Have a purge caches button to start that over from scratch // Should have a contacts manager with backing store as well // If our own has delete }