mirror of
https://gitlab.com/mysocialportal/fediverse-archiving-tools.git
synced 2024-10-18 08:53:31 +00:00
Add reaction and comment processing for Diaspora data.
This commit is contained in:
parent
4a9d57f4c7
commit
1ec9939f9b
13 changed files with 322 additions and 50 deletions
|
@ -1,8 +1,8 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:friendica_archive_browser/src/screens/media_slideshow_screen.dart';
|
||||
import 'package:friendica_archive_browser/src/models/media_attachment.dart';
|
||||
import 'package:friendica_archive_browser/src/screens/media_slideshow_screen.dart';
|
||||
import 'package:friendica_archive_browser/src/services/archive_service_provider.dart';
|
||||
import 'package:friendica_archive_browser/src/settings/settings_controller.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
|
|
@ -40,7 +40,7 @@ class TreeEntryCard extends StatelessWidget {
|
|||
? entry.title
|
||||
: entry.parentId.isEmpty
|
||||
? (entry.isReshare ? 'Reshare' : 'Post')
|
||||
: 'Comment on post by ${entry.parentAuthor}';
|
||||
: 'Comment on post by ${entry.author}';
|
||||
final dateStamp = ' At ' +
|
||||
formatter.format(
|
||||
DateTime.fromMillisecondsSinceEpoch(entry.creationTimestamp * 1000)
|
||||
|
@ -81,7 +81,8 @@ class TreeEntryCard extends StatelessWidget {
|
|||
icon: const Icon(Icons.copy)),
|
||||
),
|
||||
Tooltip(
|
||||
message: 'Open link to original item',
|
||||
message:
|
||||
'Open link to original item (${entry.externalLink})',
|
||||
child: IconButton(
|
||||
onPressed: () async {
|
||||
await canLaunch(entry.externalLink)
|
||||
|
@ -130,10 +131,9 @@ class TreeEntryCard extends StatelessWidget {
|
|||
),
|
||||
if (entry.locationData.hasData())
|
||||
entry.locationData.toWidget(spacingHeight),
|
||||
if (treeEntry.entry.externalLink.isNotEmpty) ...[
|
||||
if (treeEntry.entry.links.isNotEmpty) ...[
|
||||
const SizedBox(height: spacingHeight),
|
||||
LinkElementsComponent(
|
||||
links: [Uri.parse(treeEntry.entry.externalLink)])
|
||||
LinkElementsComponent(links: treeEntry.entry.links)
|
||||
],
|
||||
if (entry.mediaAttachments.isNotEmpty) ...[
|
||||
const SizedBox(height: spacingHeight),
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
class DiasporaReaction {
|
||||
final String authorString;
|
||||
final String guid;
|
||||
final String parentGuid;
|
||||
final ParentType parentType;
|
||||
final ReactionType reactionType;
|
||||
|
||||
DiasporaReaction(
|
||||
{required this.authorString,
|
||||
this.guid = '',
|
||||
required this.parentGuid,
|
||||
this.parentType = ParentType.post,
|
||||
required this.reactionType});
|
||||
|
||||
static DiasporaReaction fromJson(Map<String, dynamic> json) =>
|
||||
DiasporaReaction(
|
||||
authorString: json['author'] ?? '',
|
||||
guid: json['guid'] ?? '',
|
||||
parentGuid: json['parent_guid'] ?? '',
|
||||
parentType: _parentTypeFromString(json['parent_type'] ?? ''),
|
||||
reactionType: _reactionTypeFromBool(json['positive'] ?? true));
|
||||
}
|
||||
|
||||
enum ParentType {
|
||||
unknown,
|
||||
comment,
|
||||
post,
|
||||
}
|
||||
|
||||
ParentType _parentTypeFromString(String string) {
|
||||
final stringLower = string.toLowerCase();
|
||||
if (stringLower == 'post') {
|
||||
return ParentType.post;
|
||||
}
|
||||
|
||||
if (stringLower == 'comment') {
|
||||
return ParentType.comment;
|
||||
}
|
||||
|
||||
return ParentType.unknown;
|
||||
}
|
||||
|
||||
enum ReactionType {
|
||||
unknown,
|
||||
dislike,
|
||||
like,
|
||||
}
|
||||
|
||||
ReactionType _reactionTypeFromBool(bool value) {
|
||||
if (value) {
|
||||
return ReactionType.like;
|
||||
}
|
||||
|
||||
return ReactionType.dislike;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import 'package:friendica_archive_browser/src/diaspora/models/diaspora_reaction.dart';
|
||||
import 'package:friendica_archive_browser/src/models/timeline_entry.dart';
|
||||
|
||||
class DiasporaRelayable {
|
||||
final DiasporaReaction? reaction;
|
||||
final TimelineEntry? comment;
|
||||
|
||||
bool get isReaction => reaction != null;
|
||||
|
||||
bool get isComment => comment != null;
|
||||
|
||||
DiasporaRelayable({this.reaction, this.comment});
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:friendica_archive_browser/src/models/connection.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
Connection contactFromDiasporaJson(Map<String, dynamic> json) {
|
||||
const network = "Diaspora";
|
||||
|
@ -25,6 +26,11 @@ Connection contactFromDiasporaJson(Map<String, dynamic> json) {
|
|||
network: network);
|
||||
}
|
||||
|
||||
Connection contactFromDiasporaId(String accountId) => Connection(
|
||||
id: 'generated_${const Uuid().v4}',
|
||||
status: ConnectionStatus.none,
|
||||
profileUrl: _profileUrlFromAccountId(accountId));
|
||||
|
||||
Uri _profileUrlFromAccountId(String accountId) {
|
||||
if (accountId.isEmpty) {
|
||||
return Uri();
|
||||
|
|
|
@ -46,7 +46,9 @@ Result<TimelineEntry, ExecError> _buildReshareMessageType(
|
|||
final String parentGuid = entityData['root_guid'] ?? '';
|
||||
final String parentName = entityData['root_author'] ?? '';
|
||||
final deletedPost = parentGuid.isEmpty || parentName.isEmpty;
|
||||
final externalLink = deletedPost ? '' : _buildReshareUrl(authorName, parentName, parentGuid);
|
||||
final reshareLink = deletedPost
|
||||
? ''
|
||||
: _buildPostOrReshareUrl(authorName, parentName, parentGuid);
|
||||
final text = deletedPost ? 'Original post deleted by author' : '';
|
||||
final author =
|
||||
connections.getByName(authorName).getValueOrElse(() => Connection());
|
||||
|
@ -54,16 +56,16 @@ Result<TimelineEntry, ExecError> _buildReshareMessageType(
|
|||
.getByName(parentName)
|
||||
.getValueOrElse(() => Connection(name: parentName));
|
||||
final timelineEntry = TimelineEntry(
|
||||
id: postId,
|
||||
creationTimestamp: epochTime,
|
||||
author: author.name,
|
||||
authorId: author.id,
|
||||
isReshare: true,
|
||||
parentAuthor: parentAuthor.name,
|
||||
parentAuthorId: parentAuthor.id,
|
||||
externalLink: externalLink,
|
||||
body: text,
|
||||
);
|
||||
id: postId,
|
||||
creationTimestamp: epochTime,
|
||||
author: author.name,
|
||||
authorId: author.id,
|
||||
externalLink: _buildPostOrReshareUrl(authorName, '', postId),
|
||||
isReshare: true,
|
||||
parentAuthor: parentAuthor.name,
|
||||
parentAuthorId: parentAuthor.id,
|
||||
body: text,
|
||||
links: [Uri.parse(reshareLink)]);
|
||||
return Result.ok(timelineEntry);
|
||||
}
|
||||
|
||||
|
@ -83,16 +85,19 @@ Result<TimelineEntry, ExecError> _buildStatusMessageType(
|
|||
.map((e) => mediaAttachmentfromDiasporaJson(e));
|
||||
|
||||
final timelineEntry = TimelineEntry(
|
||||
id: postId,
|
||||
creationTimestamp: epochTime,
|
||||
body: postHtml,
|
||||
author: author.name,
|
||||
mediaAttachments: photos.toList(),
|
||||
authorId: author.id);
|
||||
id: postId,
|
||||
creationTimestamp: epochTime,
|
||||
body: postHtml,
|
||||
author: author.name,
|
||||
externalLink: _buildPostOrReshareUrl(authorName, '', postId),
|
||||
mediaAttachments: photos.toList(),
|
||||
authorId: author.id,
|
||||
);
|
||||
return Result.ok(timelineEntry);
|
||||
}
|
||||
|
||||
String _buildReshareUrl(String author, String rootAuthor, String rootGuid) {
|
||||
String _buildPostOrReshareUrl(
|
||||
String author, String rootAuthor, String rootGuid) {
|
||||
final accountId = rootAuthor.isNotEmpty ? rootAuthor : author;
|
||||
final accountIdPieces = accountId.split('@');
|
||||
if (accountIdPieces.length != 2) {
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
import 'package:friendica_archive_browser/src/diaspora/models/diaspora_relayables.dart';
|
||||
import 'package:friendica_archive_browser/src/models/timeline_entry.dart';
|
||||
import 'package:friendica_archive_browser/src/utils/exec_error.dart';
|
||||
import 'package:friendica_archive_browser/src/utils/offsetdatetime_utils.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:markdown/markdown.dart';
|
||||
import 'package:result_monad/result_monad.dart';
|
||||
|
||||
import '../models/diaspora_reaction.dart';
|
||||
|
||||
final _logger = Logger('DiasporaPostsSerializer');
|
||||
const _commentType = 'comment';
|
||||
const _likeType = 'like';
|
||||
const _knownRelayableTypes = [_commentType, _likeType];
|
||||
|
||||
Result<DiasporaRelayable, ExecError> relayableFromDiasporaPostJson(
|
||||
Map<String, dynamic> json) {
|
||||
if (!json.containsKey('entity_data')) {
|
||||
return Result.error(ExecError.message('Relayable item has no entity data'));
|
||||
}
|
||||
final entityType = json['entity_type'] ?? '';
|
||||
final entityData = json['entity_data'] ?? {};
|
||||
if (!_knownRelayableTypes.contains(entityType)) {
|
||||
final guid = entityData['guid'];
|
||||
final error = 'Unknown entity type $entityType for Relayable ID: $guid';
|
||||
_logger.severe(error);
|
||||
return Result.error(ExecError.message(error));
|
||||
}
|
||||
|
||||
if (entityType == _commentType) {
|
||||
return _buildCommentRelayable(entityData);
|
||||
}
|
||||
|
||||
if (entityType == _likeType) {
|
||||
return _buildReactionRelayable(entityData);
|
||||
}
|
||||
|
||||
return Result.error(ExecError.message('Unknown type: $entityType'));
|
||||
}
|
||||
|
||||
Result<DiasporaRelayable, ExecError> _buildCommentRelayable(
|
||||
Map<String, dynamic> entityData) {
|
||||
final author = entityData['author'] ?? '';
|
||||
final guid = entityData['guid'] ?? '';
|
||||
final parentGuid = entityData['parent_guid'] ?? '';
|
||||
final commentMarkdown = entityData['text'] ?? '';
|
||||
final commentHtml = markdownToHtml(commentMarkdown);
|
||||
final epochTime = OffsetDateTimeUtils.epochSecTimeFromTimeZoneString(
|
||||
entityData['created_at'] ?? '')
|
||||
.getValueOrElse(() => -1);
|
||||
|
||||
final timelineEntry = TimelineEntry(
|
||||
id: guid,
|
||||
creationTimestamp: epochTime,
|
||||
body: commentHtml,
|
||||
author: author,
|
||||
parentId: parentGuid,
|
||||
externalLink: _buildCommentUrl(author, parentGuid, guid),
|
||||
);
|
||||
return Result.ok(DiasporaRelayable(comment: timelineEntry));
|
||||
}
|
||||
|
||||
Result<DiasporaRelayable, ExecError> _buildReactionRelayable(
|
||||
Map<String, dynamic> entityData) {
|
||||
final reaction = DiasporaReaction.fromJson(entityData);
|
||||
return Result.ok(DiasporaRelayable(reaction: reaction));
|
||||
}
|
||||
|
||||
String _buildCommentUrl(String author, String parentGuid, String commentGuid) {
|
||||
final accountIdPieces = author.split('@');
|
||||
if (accountIdPieces.length != 2) {
|
||||
return commentGuid;
|
||||
}
|
||||
|
||||
final server = accountIdPieces[1];
|
||||
|
||||
return 'https://$server/p/$parentGuid#$commentGuid';
|
||||
}
|
|
@ -2,6 +2,7 @@ import 'dart:io';
|
|||
|
||||
import 'package:friendica_archive_browser/src/diaspora/services/diaspora_path_mapping_service.dart';
|
||||
import 'package:friendica_archive_browser/src/diaspora/services/diaspora_profile_json_reader.dart';
|
||||
import 'package:friendica_archive_browser/src/models/connection.dart';
|
||||
import 'package:friendica_archive_browser/src/services/archive_service_interface.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:result_monad/result_monad.dart';
|
||||
|
@ -10,6 +11,8 @@ import '../../models/entry_tree_item.dart';
|
|||
import '../../models/local_image_archive_entry.dart';
|
||||
import '../../services/connections_manager.dart';
|
||||
import '../../utils/exec_error.dart';
|
||||
import '../models/diaspora_reaction.dart';
|
||||
import '../serializers/diaspora_contact_serializer.dart';
|
||||
|
||||
class DiasporaArchiveService implements ArchiveService {
|
||||
@override
|
||||
|
@ -94,15 +97,84 @@ class DiasporaArchiveService implements ArchiveService {
|
|||
if (_ownersName.isEmpty) {
|
||||
_ownersName = reader.readOwnersName();
|
||||
reader.readContacts();
|
||||
final newPosts = reader.readPosts().map((e) => EntryTreeItem(e, false));
|
||||
final entryTree = <String, EntryTreeItem>{};
|
||||
final newPosts =
|
||||
reader.readPosts().map((e) => EntryTreeItem(e, false)).toList();
|
||||
|
||||
for (final post in newPosts) {
|
||||
entryTree[post.id] = post;
|
||||
}
|
||||
|
||||
final userComments = reader
|
||||
.readUserRelayables()
|
||||
.where((r) => r.isComment)
|
||||
.map((r) => r.comment!);
|
||||
final othersRelayables = reader.readOthersRelayables();
|
||||
final othersComments =
|
||||
othersRelayables.where((r) => r.isComment).map((r) => r.comment!);
|
||||
final othersReactions =
|
||||
othersRelayables.where((r) => r.isReaction).map((r) => r.reaction!);
|
||||
|
||||
final allComments = [...userComments, ...othersComments];
|
||||
allComments.sort(
|
||||
(c1, c2) => c1.creationTimestamp.compareTo(c2.creationTimestamp));
|
||||
|
||||
for (final comment in allComments) {
|
||||
final parentId = comment.parentId;
|
||||
final parent = entryTree[parentId];
|
||||
if (parent == null) {
|
||||
final newEntry = EntryTreeItem(comment, true);
|
||||
entryTree[comment.id] = newEntry;
|
||||
if (userComments.contains(comment)) {
|
||||
_orphanedCommentEntries.add(newEntry);
|
||||
}
|
||||
} else {
|
||||
parent.addChild(EntryTreeItem(comment, false));
|
||||
}
|
||||
}
|
||||
|
||||
for (final reaction in othersReactions) {
|
||||
final treeEntry = entryTree[reaction.parentGuid];
|
||||
if (treeEntry == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final builtConnections = <String, Connection>{};
|
||||
final builtConnection = builtConnections[reaction.authorString] ??
|
||||
contactFromDiasporaId(reaction.authorString);
|
||||
builtConnections[reaction.authorString] = builtConnection;
|
||||
final result =
|
||||
connectionsManager.getByProfileUrl(builtConnection.profileUrl);
|
||||
final connection = result.fold(
|
||||
onSuccess: (c) => c,
|
||||
onError: (error) {
|
||||
connectionsManager.addConnection(builtConnection);
|
||||
return builtConnection;
|
||||
});
|
||||
|
||||
switch (reaction.reactionType) {
|
||||
case ReactionType.unknown:
|
||||
break;
|
||||
case ReactionType.dislike:
|
||||
treeEntry.entry.dislikes.add(connection);
|
||||
break;
|
||||
case ReactionType.like:
|
||||
treeEntry.entry.likes.add(connection);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_postEntries.addAll(newPosts);
|
||||
}
|
||||
}
|
||||
|
||||
_postEntries.sort((p1, p2) =>
|
||||
p2.entry.creationTimestamp.compareTo(p1.entry.creationTimestamp));
|
||||
_postEntries.sort((p1, p2) => _reverseChronologicalSort(p1, p2));
|
||||
_orphanedCommentEntries.sort((p1, p2) => _reverseChronologicalSort(p1, p2));
|
||||
}
|
||||
|
||||
int _reverseChronologicalSort(EntryTreeItem p1, EntryTreeItem p2) =>
|
||||
p2.entry.creationTimestamp.compareTo(p1.entry.creationTimestamp);
|
||||
|
||||
void _populateTopLevelSubDirectory() {
|
||||
final topLevelDirectories = Directory(_baseArchiveFolder)
|
||||
.listSync(recursive: false)
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:friendica_archive_browser/src/diaspora/models/diaspora_relayables.dart';
|
||||
import 'package:friendica_archive_browser/src/diaspora/serializers/diaspora_contact_serializer.dart';
|
||||
import 'package:friendica_archive_browser/src/diaspora/serializers/diaspora_relayables_serializer.dart';
|
||||
import 'package:friendica_archive_browser/src/models/connection.dart';
|
||||
import 'package:friendica_archive_browser/src/models/timeline_entry.dart';
|
||||
import 'package:friendica_archive_browser/src/services/connections_manager.dart';
|
||||
|
@ -61,4 +63,27 @@ class DiasporaProfileJsonReader {
|
|||
.sort((p1, p2) => p2.creationTimestamp.compareTo(p1.creationTimestamp));
|
||||
return posts;
|
||||
}
|
||||
|
||||
List<DiasporaRelayable> readUserRelayables() {
|
||||
if (connectionsManager.length == 0) {
|
||||
readContacts();
|
||||
}
|
||||
|
||||
return _readRelayableJson(jsonData['user']?['relayables'] ?? []);
|
||||
}
|
||||
|
||||
List<DiasporaRelayable> readOthersRelayables() {
|
||||
if (connectionsManager.length == 0) {
|
||||
readContacts();
|
||||
}
|
||||
|
||||
return _readRelayableJson(jsonData['others_data']?['relayables'] ?? []);
|
||||
}
|
||||
|
||||
List<DiasporaRelayable> _readRelayableJson(List<dynamic> relayableJsonList) =>
|
||||
relayableJsonList
|
||||
.map((e) => relayableFromDiasporaPostJson(e))
|
||||
.where((r) => r.isSuccess)
|
||||
.map((r) => r.value)
|
||||
.toList();
|
||||
}
|
||||
|
|
|
@ -55,12 +55,12 @@ TimelineEntry timelineEntryFromFriendicaJson(
|
|||
modificationTimestamp: modificationTimestamp,
|
||||
backdatedTimestamp: backdatedTimestamp,
|
||||
locationData: actualLocationData,
|
||||
externalLink: externalLink,
|
||||
body: body,
|
||||
isReshare: isReshare,
|
||||
id: id,
|
||||
parentId: parentId,
|
||||
parentAuthorId: parentAuthorId,
|
||||
externalLink: externalLink,
|
||||
author: author,
|
||||
authorId: authorId,
|
||||
parentAuthor: parentAuthor,
|
||||
|
|
|
@ -41,6 +41,8 @@ class TimelineEntry {
|
|||
|
||||
final List<Connection> dislikes;
|
||||
|
||||
final List<Uri> links;
|
||||
|
||||
TimelineEntry({
|
||||
this.id = '',
|
||||
this.parentId = '',
|
||||
|
@ -56,10 +58,13 @@ class TimelineEntry {
|
|||
this.parentAuthorId = '',
|
||||
this.externalLink = '',
|
||||
this.locationData = const LocationData(),
|
||||
this.likes = const <Connection>[],
|
||||
this.dislikes = const <Connection>[],
|
||||
this.links = const [],
|
||||
List<Connection>? likes,
|
||||
List<Connection>? dislikes,
|
||||
List<MediaAttachment>? mediaAttachments,
|
||||
}) : mediaAttachments = mediaAttachments ?? <MediaAttachment>[];
|
||||
}) : mediaAttachments = mediaAttachments ?? <MediaAttachment>[],
|
||||
likes = likes ?? <Connection>[],
|
||||
dislikes = dislikes ?? <Connection>[];
|
||||
|
||||
TimelineEntry.randomBuilt()
|
||||
: creationTimestamp = DateTime.now().millisecondsSinceEpoch,
|
||||
|
@ -78,6 +83,7 @@ class TimelineEntry {
|
|||
locationData = LocationData.randomBuilt(),
|
||||
likes = const <Connection>[],
|
||||
dislikes = const <Connection>[],
|
||||
links = [],
|
||||
mediaAttachments = [
|
||||
MediaAttachment.randomBuilt(),
|
||||
MediaAttachment.randomBuilt()
|
||||
|
@ -103,24 +109,26 @@ class TimelineEntry {
|
|||
List<Connection>? dislikes,
|
||||
List<Uri>? links}) {
|
||||
return TimelineEntry(
|
||||
creationTimestamp: creationTimestamp ?? this.creationTimestamp,
|
||||
backdatedTimestamp: backdatedTimestamp ?? this.backdatedTimestamp,
|
||||
modificationTimestamp:
|
||||
modificationTimestamp ?? this.modificationTimestamp,
|
||||
id: id ?? this.id,
|
||||
isReshare: isReshare ?? this.isReshare,
|
||||
parentId: parentId ?? this.parentId,
|
||||
externalLink: externalLink ?? this.externalLink,
|
||||
body: body ?? this.body,
|
||||
title: title ?? this.title,
|
||||
author: author ?? this.author,
|
||||
authorId: authorId ?? this.authorId,
|
||||
parentAuthor: parentAuthor ?? this.parentAuthor,
|
||||
parentAuthorId: parentAuthorId ?? this.parentAuthorId,
|
||||
locationData: locationData ?? this.locationData,
|
||||
mediaAttachments: mediaAttachments ?? this.mediaAttachments,
|
||||
likes: likes ?? this.likes,
|
||||
dislikes: dislikes ?? this.dislikes);
|
||||
creationTimestamp: creationTimestamp ?? this.creationTimestamp,
|
||||
backdatedTimestamp: backdatedTimestamp ?? this.backdatedTimestamp,
|
||||
modificationTimestamp:
|
||||
modificationTimestamp ?? this.modificationTimestamp,
|
||||
id: id ?? this.id,
|
||||
isReshare: isReshare ?? this.isReshare,
|
||||
parentId: parentId ?? this.parentId,
|
||||
externalLink: externalLink ?? this.externalLink,
|
||||
body: body ?? this.body,
|
||||
title: title ?? this.title,
|
||||
author: author ?? this.author,
|
||||
authorId: authorId ?? this.authorId,
|
||||
parentAuthor: parentAuthor ?? this.parentAuthor,
|
||||
parentAuthorId: parentAuthorId ?? this.parentAuthorId,
|
||||
locationData: locationData ?? this.locationData,
|
||||
mediaAttachments: mediaAttachments ?? this.mediaAttachments,
|
||||
likes: likes ?? this.likes,
|
||||
dislikes: dislikes ?? this.dislikes,
|
||||
links: links ?? this.links,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -147,6 +155,7 @@ class TimelineEntry {
|
|||
if (mediaAttachments.isNotEmpty) 'Photos and Videos:',
|
||||
...mediaAttachments.map((e) => e.toHumanString(mapper)),
|
||||
if (locationData.hasPosition) locationData.toHumanString(),
|
||||
if (links.isNotEmpty) ...['Links:', ...links.map((e) => e.toString())]
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:friendica_archive_browser/src/components/filter_control_component.dart';
|
||||
import 'package:friendica_archive_browser/src/components/heatmap_widget.dart';
|
||||
import 'package:friendica_archive_browser/src/components/timechart_widget.dart';
|
||||
import 'package:friendica_archive_browser/src/components/top_interactactors_widget.dart';
|
||||
import 'package:friendica_archive_browser/src/components/word_frequency_widget.dart';
|
||||
import 'package:friendica_archive_browser/src/components/filter_control_component.dart';
|
||||
import 'package:friendica_archive_browser/src/models/model_utils.dart';
|
||||
import 'package:friendica_archive_browser/src/models/time_element.dart';
|
||||
import 'package:friendica_archive_browser/src/screens/standin_status_screen.dart';
|
||||
|
|
|
@ -4,12 +4,14 @@ import 'package:result_monad/result_monad.dart';
|
|||
class ConnectionsManager {
|
||||
final _connectionsById = <String, Connection>{};
|
||||
final _connectionsByName = <String, Connection>{};
|
||||
final _connectionsByProfileUrl = <Uri, Connection>{};
|
||||
|
||||
int get length => _connectionsById.length;
|
||||
|
||||
void clearCaches() {
|
||||
_connectionsById.clear();
|
||||
_connectionsByName.clear();
|
||||
_connectionsByProfileUrl.clear();
|
||||
}
|
||||
|
||||
bool addConnection(Connection connection) {
|
||||
|
@ -18,6 +20,7 @@ class ConnectionsManager {
|
|||
}
|
||||
_connectionsById[connection.id] = connection;
|
||||
_connectionsByName[connection.name] = connection;
|
||||
_connectionsByProfileUrl[connection.profileUrl] = connection;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -43,4 +46,10 @@ class ConnectionsManager {
|
|||
|
||||
return result != null ? Result.ok(result) : Result.error('$name not found');
|
||||
}
|
||||
|
||||
Result<Connection, String> getByProfileUrl(Uri url) {
|
||||
final result = _connectionsByProfileUrl[url];
|
||||
|
||||
return result != null ? Result.ok(result) : Result.error('$url not found');
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue