2022-11-17 16:04:14 +00:00
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:logging/logging.dart';
|
|
|
|
import 'package:result_monad/result_monad.dart';
|
|
|
|
|
2023-02-24 21:36:20 +00:00
|
|
|
import '../friendica_client/friendica_client.dart';
|
2022-11-17 16:04:14 +00:00
|
|
|
import '../globals.dart';
|
|
|
|
import '../models/TimelineIdentifiers.dart';
|
|
|
|
import '../models/entry_tree_item.dart';
|
|
|
|
import '../models/exec_error.dart';
|
2022-12-08 18:37:30 +00:00
|
|
|
import '../models/group_data.dart';
|
2022-12-27 03:00:28 +00:00
|
|
|
import '../models/image_entry.dart';
|
|
|
|
import '../models/media_attachment_uploads/new_entry_media_items.dart';
|
2022-11-17 16:04:14 +00:00
|
|
|
import '../models/timeline.dart';
|
2022-11-23 02:59:08 +00:00
|
|
|
import '../models/timeline_entry.dart';
|
2022-12-08 18:37:30 +00:00
|
|
|
import 'auth_service.dart';
|
2022-11-17 16:04:14 +00:00
|
|
|
import 'entry_manager_service.dart';
|
|
|
|
|
2022-11-22 14:54:10 +00:00
|
|
|
enum TimelineRefreshType {
|
|
|
|
refresh,
|
|
|
|
loadOlder,
|
|
|
|
loadNewer,
|
|
|
|
}
|
|
|
|
|
2022-11-17 16:04:14 +00:00
|
|
|
class TimelineManager extends ChangeNotifier {
|
|
|
|
static final _logger = Logger('$TimelineManager');
|
|
|
|
|
|
|
|
final cachedTimelines = <TimelineIdentifiers, Timeline>{};
|
|
|
|
|
2022-12-08 18:37:30 +00:00
|
|
|
final _groups = <String, GroupData>{};
|
|
|
|
|
2022-11-21 03:26:49 +00:00
|
|
|
void clear() {
|
|
|
|
cachedTimelines.clear();
|
2022-12-08 18:37:30 +00:00
|
|
|
_groups.clear();
|
2022-11-21 03:26:49 +00:00
|
|
|
getIt<EntryManagerService>().clear();
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
2022-12-08 18:37:30 +00:00
|
|
|
Result<List<GroupData>, ExecError> getGroups() {
|
|
|
|
if (_groups.isEmpty) {
|
|
|
|
_refreshGroupData();
|
|
|
|
return Result.ok([]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Result.ok(_groups.values.toList());
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> _refreshGroupData() async {
|
|
|
|
_logger.finest('Refreshing member group data ');
|
2023-02-27 03:12:40 +00:00
|
|
|
await GroupsClient(getIt<AccountsService>().currentProfile)
|
2023-02-24 21:36:20 +00:00
|
|
|
.getGroups()
|
|
|
|
.match(
|
2022-12-08 18:37:30 +00:00
|
|
|
onSuccess: (groups) {
|
|
|
|
_groups.clear();
|
|
|
|
for (final group in groups) {
|
|
|
|
_groups[group.id] = group;
|
|
|
|
}
|
|
|
|
notifyListeners();
|
|
|
|
},
|
|
|
|
onError: (error) {
|
|
|
|
_logger.severe('Error getting list data: $error');
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-12-26 20:26:30 +00:00
|
|
|
FutureResult<bool, ExecError> createNewStatus(
|
|
|
|
String text, {
|
|
|
|
String spoilerText = '',
|
|
|
|
String inReplyToId = '',
|
2022-12-27 03:00:28 +00:00
|
|
|
required NewEntryMediaItems newMediaItems,
|
|
|
|
required List<ImageEntry> existingMediaItems,
|
2022-12-26 20:26:30 +00:00
|
|
|
}) async {
|
2022-11-23 02:59:08 +00:00
|
|
|
final result = await getIt<EntryManagerService>().createNewStatus(
|
2022-11-22 18:55:50 +00:00
|
|
|
text,
|
|
|
|
spoilerText: spoilerText,
|
2022-11-23 02:59:08 +00:00
|
|
|
inReplyToId: inReplyToId,
|
2022-12-27 03:00:28 +00:00
|
|
|
mediaItems: newMediaItems,
|
|
|
|
existingMediaItems: existingMediaItems,
|
2022-11-22 18:55:50 +00:00
|
|
|
);
|
|
|
|
if (result.isSuccess) {
|
2022-11-23 02:59:08 +00:00
|
|
|
_logger.finest('Notifying listeners of new status created');
|
2022-11-22 18:55:50 +00:00
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2022-11-23 02:59:08 +00:00
|
|
|
Result<TimelineEntry, ExecError> getEntryById(String id) {
|
|
|
|
_logger.finest('Getting entry for $id');
|
|
|
|
return getIt<EntryManagerService>().getEntryById(id);
|
|
|
|
}
|
|
|
|
|
2022-12-27 04:36:04 +00:00
|
|
|
FutureResult<bool, ExecError> deleteEntryById(String id) async {
|
|
|
|
_logger.finest('Delete entry for $id');
|
|
|
|
final result = await getIt<EntryManagerService>().deleteEntryById(id);
|
|
|
|
|
|
|
|
if (result.isSuccess) {
|
|
|
|
for (final t in cachedTimelines.values) {
|
|
|
|
t.removeTimelineEntry(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
notifyListeners();
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2022-11-23 20:48:09 +00:00
|
|
|
Result<EntryTreeItem, ExecError> getPostTreeEntryBy(String id) {
|
|
|
|
_logger.finest('Getting post for $id');
|
|
|
|
return getIt<EntryManagerService>().getPostTreeEntryBy(id);
|
|
|
|
}
|
|
|
|
|
2022-11-17 16:04:14 +00:00
|
|
|
// refresh timeline gets statuses newer than the newest in that timeline
|
|
|
|
Result<List<EntryTreeItem>, ExecError> getTimeline(TimelineIdentifiers type) {
|
2022-11-21 21:21:45 +00:00
|
|
|
_logger.finest('Getting timeline $type');
|
2022-11-17 16:04:14 +00:00
|
|
|
final posts = cachedTimelines[type]?.posts;
|
|
|
|
if (posts != null) {
|
|
|
|
return Result.ok(posts);
|
|
|
|
}
|
|
|
|
|
2022-11-22 14:54:10 +00:00
|
|
|
updateTimeline(type, TimelineRefreshType.refresh);
|
2022-11-17 16:04:14 +00:00
|
|
|
|
|
|
|
return Result.ok([]);
|
|
|
|
}
|
|
|
|
|
2022-11-30 00:56:14 +00:00
|
|
|
///
|
|
|
|
/// id is the id of a post or comment in the chain, including the original post.
|
|
|
|
FutureResult<EntryTreeItem, ExecError> refreshStatusChain(String id) async {
|
2022-11-22 14:54:10 +00:00
|
|
|
_logger.finest('Refreshing post $id');
|
2022-11-30 00:56:14 +00:00
|
|
|
final result = await getIt<EntryManagerService>().refreshStatusChain(id);
|
2022-11-21 21:21:45 +00:00
|
|
|
if (result.isSuccess) {
|
|
|
|
for (final t in cachedTimelines.values) {
|
|
|
|
t.addOrUpdate([result.value]);
|
|
|
|
}
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2022-11-22 16:43:16 +00:00
|
|
|
FutureResult<EntryTreeItem, ExecError> resharePost(String id) async {
|
|
|
|
final result = await getIt<EntryManagerService>().resharePost(id);
|
|
|
|
if (result.isSuccess) {
|
|
|
|
for (final t in cachedTimelines.values) {
|
|
|
|
t.addOrUpdate([result.value]);
|
|
|
|
}
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2022-12-27 04:36:04 +00:00
|
|
|
FutureResult<bool, ExecError> unResharePost(String id) async {
|
2022-11-22 16:43:16 +00:00
|
|
|
final result = await getIt<EntryManagerService>().unResharePost(id);
|
|
|
|
if (result.isSuccess) {
|
|
|
|
for (final t in cachedTimelines.values) {
|
2022-12-27 04:36:04 +00:00
|
|
|
t.removeTimelineEntry(id);
|
2022-11-22 16:43:16 +00:00
|
|
|
}
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2022-11-22 14:54:10 +00:00
|
|
|
Future<void> updateTimeline(
|
|
|
|
TimelineIdentifiers type,
|
|
|
|
TimelineRefreshType refreshType,
|
|
|
|
) async {
|
|
|
|
_logger.finest('Updating w/$refreshType for timeline $type ');
|
2022-11-18 23:31:28 +00:00
|
|
|
final timeline = cachedTimelines.putIfAbsent(type, () => Timeline(type));
|
2022-11-22 14:54:10 +00:00
|
|
|
late final int lowestId;
|
|
|
|
late final int highestId;
|
|
|
|
switch (refreshType) {
|
|
|
|
case TimelineRefreshType.refresh:
|
2022-12-08 18:37:30 +00:00
|
|
|
lowestId = timeline.highestStatusId == 0
|
|
|
|
? timeline.highestStatusId
|
|
|
|
: timeline.highestStatusId + 1;
|
2022-11-22 14:54:10 +00:00
|
|
|
highestId = 0;
|
|
|
|
break;
|
|
|
|
case TimelineRefreshType.loadOlder:
|
|
|
|
lowestId = timeline.lowestStatusId;
|
|
|
|
highestId = 0;
|
|
|
|
break;
|
|
|
|
case TimelineRefreshType.loadNewer:
|
|
|
|
lowestId = 0;
|
|
|
|
highestId = timeline.highestStatusId;
|
|
|
|
break;
|
|
|
|
}
|
2022-11-18 23:31:28 +00:00
|
|
|
(await getIt<EntryManagerService>()
|
2022-11-22 14:54:10 +00:00
|
|
|
.updateTimeline(type, lowestId, highestId))
|
2022-11-18 23:31:28 +00:00
|
|
|
.match(onSuccess: (posts) {
|
2022-11-18 21:50:15 +00:00
|
|
|
_logger.finest('Posts returned for adding to $type: ${posts.length}');
|
2022-11-21 03:26:49 +00:00
|
|
|
timeline.addOrUpdate(posts);
|
2022-11-17 16:04:14 +00:00
|
|
|
notifyListeners();
|
|
|
|
}, onError: (error) {
|
|
|
|
_logger.severe('Error getting timeline: $type}');
|
|
|
|
});
|
2022-11-21 21:21:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
FutureResult<EntryTreeItem, ExecError> toggleFavorited(
|
|
|
|
String id, bool newStatus) async {
|
|
|
|
_logger.finest('Attempting toggling favorite $id to $newStatus');
|
|
|
|
final result =
|
|
|
|
await getIt<EntryManagerService>().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);
|
|
|
|
}
|
2022-11-17 16:04:14 +00:00
|
|
|
|
|
|
|
notifyListeners();
|
2022-11-21 21:21:45 +00:00
|
|
|
return result;
|
2022-11-17 16:04:14 +00:00
|
|
|
}
|
2022-11-21 21:21:45 +00:00
|
|
|
// 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
|
2022-11-17 16:04:14 +00:00
|
|
|
}
|