mirror of
https://gitlab.com/mysocialportal/relatica
synced 2024-10-18 21:43:31 +00:00
325 lines
9.9 KiB
Dart
325 lines
9.9 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:logging/logging.dart';
|
|
import 'package:result_monad/result_monad.dart';
|
|
|
|
import '../friendica_client.dart';
|
|
import '../globals.dart';
|
|
import '../models/TimelineIdentifiers.dart';
|
|
import '../models/entry_tree_item.dart';
|
|
import '../models/exec_error.dart';
|
|
import '../models/timeline_entry.dart';
|
|
import 'auth_service.dart';
|
|
|
|
class EntryManagerService extends ChangeNotifier {
|
|
static final _logger = Logger('$EntryManagerService');
|
|
final _entries = <String, TimelineEntry>{};
|
|
final _parentPostIds = <String, String>{};
|
|
final _postNodes = <String, _Node>{};
|
|
|
|
void clear() {
|
|
_entries.clear();
|
|
_parentPostIds.clear();
|
|
}
|
|
|
|
FutureResult<EntryTreeItem, ExecError> createNewPost(String text,
|
|
{String spoilerText = ''}) async {
|
|
_logger.finest('Creating new post: $text');
|
|
final auth = getIt<AuthService>();
|
|
final clientResult = auth.currentClient;
|
|
if (clientResult.isFailure) {
|
|
_logger.severe('Error getting Friendica client: ${clientResult.error}');
|
|
return clientResult.errorCast();
|
|
}
|
|
|
|
final client = clientResult.value;
|
|
final result = await client
|
|
.createNewPost(
|
|
text: text,
|
|
spoilerText: spoilerText,
|
|
)
|
|
.andThenSuccessAsync((item) async {
|
|
await processNewItems([item], client.credentials.username, null);
|
|
return item;
|
|
});
|
|
|
|
return result.mapValue((post) {
|
|
_logger.finest('${post.id} post updated after reshare');
|
|
return _nodeToTreeItem(_postNodes[post.id]!);
|
|
}).mapError(
|
|
(error) {
|
|
_logger.finest('Error creating post: $error');
|
|
return ExecError(
|
|
type: ErrorType.localError,
|
|
message: error.toString(),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
FutureResult<List<EntryTreeItem>, ExecError> updateTimeline(
|
|
TimelineIdentifiers type, int maxId, int sinceId) async {
|
|
_logger.fine(() => 'Updating timeline');
|
|
final auth = getIt<AuthService>();
|
|
final clientResult = auth.currentClient;
|
|
if (clientResult.isFailure) {
|
|
_logger.severe('Error getting Friendica client: ${clientResult.error}');
|
|
return clientResult.errorCast();
|
|
}
|
|
|
|
final client = clientResult.value;
|
|
final itemsResult =
|
|
await client.getTimeline(type: type, maxId: maxId, sinceId: sinceId);
|
|
final myHandle = client.credentials.username;
|
|
if (itemsResult.isFailure) {
|
|
_logger.severe('Error getting timeline: ${itemsResult.error}');
|
|
return itemsResult.errorCast();
|
|
}
|
|
|
|
itemsResult.value.sort((t1, t2) => t1.id.compareTo(t2.id));
|
|
final updatedPosts =
|
|
await processNewItems(itemsResult.value, myHandle, client);
|
|
_logger.finest(() {
|
|
final postCount = _entries.values.where((e) => e.parentId.isEmpty).length;
|
|
final commentCount = _entries.length - postCount;
|
|
final orphanCount = _entries.values
|
|
.where(
|
|
(e) => e.parentId.isNotEmpty && !_entries.containsKey(e.parentId))
|
|
.length;
|
|
return 'End of update # posts: $postCount, #comments: $commentCount, #orphans: $orphanCount';
|
|
});
|
|
return Result.ok(updatedPosts);
|
|
}
|
|
|
|
Future<List<EntryTreeItem>> processNewItems(
|
|
List<TimelineEntry> items,
|
|
String myHandle,
|
|
FriendicaClient? client,
|
|
) async {
|
|
final allSeenItems = [...items];
|
|
for (final item in items) {
|
|
_entries[item.id] = item;
|
|
}
|
|
|
|
final orphans = <TimelineEntry>[];
|
|
for (final item in items) {
|
|
if (item.parentId.isEmpty) {
|
|
continue;
|
|
}
|
|
|
|
final parent = _entries[item.parentId];
|
|
if (parent == null) {
|
|
orphans.add(item);
|
|
} else {
|
|
if (parent.parentId.isEmpty) {
|
|
_parentPostIds[item.id] = parent.id;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (final o in orphans) {
|
|
await client
|
|
?.getPostOrComment(o.id, fullContext: true)
|
|
.andThenSuccessAsync((items) async {
|
|
final parentPostId = items.firstWhere((e) => e.parentId.isEmpty).id;
|
|
_parentPostIds[o.id] = parentPostId;
|
|
allSeenItems.addAll(items);
|
|
for (final item in items) {
|
|
_entries[item.id] = item;
|
|
_parentPostIds[item.id] = parentPostId;
|
|
}
|
|
;
|
|
});
|
|
}
|
|
|
|
allSeenItems.sort((i1, i2) => int.parse(i1.id).compareTo(int.parse(i2.id)));
|
|
final postNodesToReturn = <_Node>{};
|
|
for (final item in allSeenItems) {
|
|
if (item.parentId.isEmpty) {
|
|
final postNode = _postNodes.putIfAbsent(item.id, () => _Node(item.id));
|
|
postNodesToReturn.add(postNode);
|
|
} else {
|
|
final parentPostNode = _postNodes[_parentPostIds[item.id]]!;
|
|
postNodesToReturn.add(parentPostNode);
|
|
if (parentPostNode.getChildById(item.id) == null) {
|
|
final newNode = _Node(item.id);
|
|
final injectionNode = parentPostNode.id == item.parentId
|
|
? parentPostNode
|
|
: parentPostNode.getChildById(item.parentId)!;
|
|
injectionNode.addChild(newNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
final updatedPosts =
|
|
postNodesToReturn.map((node) => _nodeToTreeItem(node)).toList();
|
|
|
|
_logger.finest(
|
|
'Completed processing new items ${client == null ? 'sub level' : 'top level'}');
|
|
return updatedPosts;
|
|
}
|
|
|
|
FutureResult<EntryTreeItem, ExecError> refreshPost(String id) async {
|
|
_logger.finest('Refreshing post: $id');
|
|
final auth = getIt<AuthService>();
|
|
final clientResult = auth.currentClient;
|
|
if (clientResult.isFailure) {
|
|
_logger.severe('Error getting Friendica client: ${clientResult.error}');
|
|
return clientResult.errorCast();
|
|
}
|
|
|
|
final client = clientResult.value;
|
|
final result = await client
|
|
.getPostOrComment(id, fullContext: false)
|
|
.andThenSuccessAsync((items) async {
|
|
await processNewItems(items, client.credentials.username, null);
|
|
})
|
|
.andThenAsync(
|
|
(_) async => await client.getPostOrComment(id, fullContext: true))
|
|
.andThenSuccessAsync((items) async {
|
|
await processNewItems(items, client.credentials.username, null);
|
|
});
|
|
|
|
return result.mapValue((_) {
|
|
_logger.finest('$id post updated');
|
|
return _nodeToTreeItem(_postNodes[id]!);
|
|
}).mapError(
|
|
(error) {
|
|
_logger.finest('$id error updating: $error');
|
|
return ExecError(
|
|
type: ErrorType.localError,
|
|
message: error.toString(),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
FutureResult<EntryTreeItem, ExecError> resharePost(String id) async {
|
|
_logger.finest('Resharing post: $id');
|
|
final auth = getIt<AuthService>();
|
|
final clientResult = auth.currentClient;
|
|
if (clientResult.isFailure) {
|
|
_logger.severe('Error getting Friendica client: ${clientResult.error}');
|
|
return clientResult.errorCast();
|
|
}
|
|
|
|
final client = clientResult.value;
|
|
final result =
|
|
await client.resharePost(id).andThenSuccessAsync((item) async {
|
|
await processNewItems([item], client.credentials.username, null);
|
|
});
|
|
|
|
return result.mapValue((_) {
|
|
_logger.finest('$id post updated after reshare');
|
|
return _nodeToTreeItem(_postNodes[id]!);
|
|
}).mapError(
|
|
(error) {
|
|
_logger.finest('$id error updating: $error');
|
|
return ExecError(
|
|
type: ErrorType.localError,
|
|
message: error.toString(),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
FutureResult<EntryTreeItem, ExecError> unResharePost(String id) async {
|
|
_logger.finest('Unresharing post: $id');
|
|
final auth = getIt<AuthService>();
|
|
final clientResult = auth.currentClient;
|
|
if (clientResult.isFailure) {
|
|
_logger.severe('Error getting Friendica client: ${clientResult.error}');
|
|
return clientResult.errorCast();
|
|
}
|
|
|
|
final client = clientResult.value;
|
|
final result =
|
|
await client.unResharePost(id).andThenSuccessAsync((item) async {
|
|
await processNewItems([item], client.credentials.username, null);
|
|
});
|
|
|
|
return result.mapValue((_) {
|
|
_logger.finest('$id post updated after unreshare');
|
|
return _nodeToTreeItem(_postNodes[id]!);
|
|
}).mapError(
|
|
(error) {
|
|
_logger.finest('$id error updating: $error');
|
|
return ExecError(
|
|
type: ErrorType.localError,
|
|
message: error.toString(),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
FutureResult<EntryTreeItem, ExecError> toggleFavorited(
|
|
String id, bool newStatus) async {
|
|
final auth = getIt<AuthService>();
|
|
final clientResult = auth.currentClient;
|
|
if (clientResult.isFailure) {
|
|
_logger.severe('Error getting Friendica client: ${clientResult.error}');
|
|
return clientResult.errorCast();
|
|
}
|
|
final client = clientResult.value;
|
|
final result = await client.changeFavoriteStatus(id, newStatus);
|
|
if (result.isFailure) {
|
|
return result.errorCast();
|
|
}
|
|
|
|
final update = result.value;
|
|
_entries[update.id] = update;
|
|
final node = update.parentId.isEmpty
|
|
? _postNodes[update.id]!
|
|
: _postNodes[update.parentId]!.getChildById(update.id)!;
|
|
|
|
notifyListeners();
|
|
return Result.ok(_nodeToTreeItem(node));
|
|
}
|
|
|
|
EntryTreeItem _nodeToTreeItem(_Node node) {
|
|
final childenEntries = <String, EntryTreeItem>{};
|
|
for (final c in node.children) {
|
|
childenEntries[c.id] = _nodeToTreeItem(c);
|
|
}
|
|
return EntryTreeItem(_entries[node.id]!, initialChildren: childenEntries);
|
|
}
|
|
}
|
|
|
|
class _Node {
|
|
final String id;
|
|
final _children = <String, _Node>{};
|
|
|
|
List<_Node> get children => _children.values.toList();
|
|
|
|
_Node(this.id, {Map<String, _Node>? initialChildren}) {
|
|
if (initialChildren != null) {
|
|
_children.addAll(initialChildren);
|
|
}
|
|
}
|
|
|
|
void addChild(_Node node) {
|
|
_children[node.id] = node;
|
|
}
|
|
|
|
_Node? getChildById(String id) {
|
|
if (_children.containsKey(id)) {
|
|
return _children[id]!;
|
|
}
|
|
|
|
for (final c in _children.values) {
|
|
final result = c.getChildById(id);
|
|
if (result != null) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
@override
|
|
bool operator ==(Object other) =>
|
|
identical(this, other) ||
|
|
other is _Node && runtimeType == other.runtimeType && id == other.id;
|
|
|
|
@override
|
|
int get hashCode => id.hashCode;
|
|
}
|