relatica/lib/services/timeline_manager.dart

242 wiersze
7.4 KiB
Dart

import 'package:flutter/material.dart' hide Visibility;
import 'package:logging/logging.dart';
import 'package:result_monad/result_monad.dart';
import '../data/interfaces/circles_repo_intf.dart';
import '../friendica_client/friendica_client.dart';
import '../models/TimelineIdentifiers.dart';
import '../models/auth/profile.dart';
import '../models/circle_data.dart';
import '../models/entry_tree_item.dart';
import '../models/exec_error.dart';
import '../models/image_entry.dart';
import '../models/media_attachment_uploads/new_entry_media_items.dart';
import '../models/timeline.dart';
import '../models/timeline_entry.dart';
import '../models/visibility.dart';
import 'entry_manager_service.dart';
enum TimelineRefreshType {
refresh,
loadOlder,
loadNewer,
}
class TimelineManager extends ChangeNotifier {
static final _logger = Logger('$TimelineManager');
final ICirclesRepo circlesRepo;
final EntryManagerService entryManagerService;
var circlesNotInitialized = true;
final Profile profile;
final cachedTimelines = <TimelineIdentifiers, Timeline>{};
TimelineManager(this.profile, this.circlesRepo, this.entryManagerService);
void clear() {
circlesNotInitialized = true;
cachedTimelines.clear();
entryManagerService.clear();
circlesRepo.clear();
notifyListeners();
}
Result<List<CircleData>, ExecError> getCircles() {
if (circlesNotInitialized) {
_refreshCircleData();
circlesNotInitialized = false;
return Result.ok([]);
}
return Result.ok(circlesRepo.getMyCircles());
}
Future<void> _refreshCircleData() async {
_logger.finer('Refreshing member circle data ');
await CirclesClient(profile).getCircles().match(
onSuccess: (circles) {
circlesRepo.addAllCircles(circles);
notifyListeners();
},
onError: (error) {
_logger.severe('Error getting list data: $error');
},
);
}
FutureResult<bool, ExecError> createNewStatus(
String text, {
String spoilerText = '',
String inReplyToId = '',
required NewEntryMediaItems newMediaItems,
required List<ImageEntry> existingMediaItems,
required Visibility visibility,
}) async {
final result = await entryManagerService.createNewStatus(
text,
spoilerText: spoilerText,
inReplyToId: inReplyToId,
mediaItems: newMediaItems,
existingMediaItems: existingMediaItems,
visibility: visibility,
);
if (result.isSuccess) {
_logger.finest('Notifying listeners of new status created');
notifyListeners();
}
return result;
}
FutureResult<bool, ExecError> editStatus(
String id,
String text, {
String spoilerText = '',
String inReplyToId = '',
required NewEntryMediaItems newMediaItems,
required List<ImageEntry> existingMediaItems,
required Visibility newMediaItemVisibility,
}) async {
final result = await entryManagerService.editStatus(id, text,
spoilerText: spoilerText,
mediaItems: newMediaItems,
existingMediaItems: existingMediaItems,
newMediaItemVisibility: newMediaItemVisibility);
if (result.isSuccess) {
_logger.finest('Notifying listeners of updated status');
notifyListeners();
}
return result;
}
Result<TimelineEntry, ExecError> getEntryById(String id) {
_logger.finest('Getting entry for $id');
return entryManagerService.getEntryById(id);
}
FutureResult<bool, ExecError> deleteEntryById(String id) async {
_logger.finest('Delete entry for $id');
final result = await entryManagerService.deleteEntryById(id);
if (result.isSuccess) {
for (final t in cachedTimelines.values) {
t.removeTimelineEntry(id);
}
}
notifyListeners();
return result;
}
Result<EntryTreeItem, ExecError> getPostTreeEntryBy(String id) {
_logger.finest('Getting post for $id');
return entryManagerService.getPostTreeEntryBy(id);
}
// refresh timeline gets statuses newer than the newest in that timeline
List<EntryTreeItem> getTimeline(TimelineIdentifiers type) {
_logger.finest('Getting timeline $type');
return cachedTimelines.putIfAbsent(type, () => Timeline(type)).posts;
}
///
/// id is the id of a post or comment in the chain, including the original post.
FutureResult<EntryTreeItem, ExecError> refreshStatusChain(String id) async {
_logger.finest('Refreshing post $id');
final result = await entryManagerService.refreshStatusChain(id);
if (result.isSuccess) {
for (final t in cachedTimelines.values) {
t.addOrUpdate([result.value]);
}
notifyListeners();
}
return result;
}
FutureResult<EntryTreeItem, ExecError> resharePost(String id) async {
final result = await entryManagerService.resharePost(id);
final empty = EntryTreeItem.empty();
if (result.isSuccess) {
for (final t in cachedTimelines.values) {
if (t.posts.firstWhere((p) => p.id == id, orElse: () => empty) !=
empty) {
t.addOrUpdate([result.value]);
}
}
notifyListeners();
}
return result;
}
FutureResult<EntryTreeItem, ExecError> unResharePost(String id) async {
final result = await entryManagerService.unResharePost(id);
final empty = EntryTreeItem.empty();
if (result.isSuccess) {
for (final t in cachedTimelines.values) {
if (t.posts.firstWhere((p) => p.id == id, orElse: () => empty) !=
empty) {
t.addOrUpdate([result.value]);
}
}
notifyListeners();
}
return result;
}
Future<void> 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:
timeline.clear();
lowestId = 0;
highestId = 0;
break;
case TimelineRefreshType.loadOlder:
lowestId = timeline.lowestStatusId;
highestId = 0;
break;
case TimelineRefreshType.loadNewer:
lowestId = 0;
highestId = timeline.highestStatusId;
break;
}
(await entryManagerService.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<EntryTreeItem, ExecError> toggleFavorited(
String id, bool newStatus) async {
_logger.finer('Attempting toggling favorite $id to $newStatus');
final result = await entryManagerService.toggleFavorited(id, newStatus);
if (result.isFailure) {
_logger.info('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
}