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 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
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/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/services/archive_service_provider.dart';
|
||||||
import 'package:friendica_archive_browser/src/settings/settings_controller.dart';
|
import 'package:friendica_archive_browser/src/settings/settings_controller.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
|
@ -40,7 +40,7 @@ class TreeEntryCard extends StatelessWidget {
|
||||||
? entry.title
|
? entry.title
|
||||||
: entry.parentId.isEmpty
|
: entry.parentId.isEmpty
|
||||||
? (entry.isReshare ? 'Reshare' : 'Post')
|
? (entry.isReshare ? 'Reshare' : 'Post')
|
||||||
: 'Comment on post by ${entry.parentAuthor}';
|
: 'Comment on post by ${entry.author}';
|
||||||
final dateStamp = ' At ' +
|
final dateStamp = ' At ' +
|
||||||
formatter.format(
|
formatter.format(
|
||||||
DateTime.fromMillisecondsSinceEpoch(entry.creationTimestamp * 1000)
|
DateTime.fromMillisecondsSinceEpoch(entry.creationTimestamp * 1000)
|
||||||
|
@ -81,7 +81,8 @@ class TreeEntryCard extends StatelessWidget {
|
||||||
icon: const Icon(Icons.copy)),
|
icon: const Icon(Icons.copy)),
|
||||||
),
|
),
|
||||||
Tooltip(
|
Tooltip(
|
||||||
message: 'Open link to original item',
|
message:
|
||||||
|
'Open link to original item (${entry.externalLink})',
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await canLaunch(entry.externalLink)
|
await canLaunch(entry.externalLink)
|
||||||
|
@ -130,10 +131,9 @@ class TreeEntryCard extends StatelessWidget {
|
||||||
),
|
),
|
||||||
if (entry.locationData.hasData())
|
if (entry.locationData.hasData())
|
||||||
entry.locationData.toWidget(spacingHeight),
|
entry.locationData.toWidget(spacingHeight),
|
||||||
if (treeEntry.entry.externalLink.isNotEmpty) ...[
|
if (treeEntry.entry.links.isNotEmpty) ...[
|
||||||
const SizedBox(height: spacingHeight),
|
const SizedBox(height: spacingHeight),
|
||||||
LinkElementsComponent(
|
LinkElementsComponent(links: treeEntry.entry.links)
|
||||||
links: [Uri.parse(treeEntry.entry.externalLink)])
|
|
||||||
],
|
],
|
||||||
if (entry.mediaAttachments.isNotEmpty) ...[
|
if (entry.mediaAttachments.isNotEmpty) ...[
|
||||||
const SizedBox(height: spacingHeight),
|
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:friendica_archive_browser/src/models/connection.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
Connection contactFromDiasporaJson(Map<String, dynamic> json) {
|
Connection contactFromDiasporaJson(Map<String, dynamic> json) {
|
||||||
const network = "Diaspora";
|
const network = "Diaspora";
|
||||||
|
@ -25,6 +26,11 @@ Connection contactFromDiasporaJson(Map<String, dynamic> json) {
|
||||||
network: network);
|
network: network);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connection contactFromDiasporaId(String accountId) => Connection(
|
||||||
|
id: 'generated_${const Uuid().v4}',
|
||||||
|
status: ConnectionStatus.none,
|
||||||
|
profileUrl: _profileUrlFromAccountId(accountId));
|
||||||
|
|
||||||
Uri _profileUrlFromAccountId(String accountId) {
|
Uri _profileUrlFromAccountId(String accountId) {
|
||||||
if (accountId.isEmpty) {
|
if (accountId.isEmpty) {
|
||||||
return Uri();
|
return Uri();
|
||||||
|
|
|
@ -46,7 +46,9 @@ Result<TimelineEntry, ExecError> _buildReshareMessageType(
|
||||||
final String parentGuid = entityData['root_guid'] ?? '';
|
final String parentGuid = entityData['root_guid'] ?? '';
|
||||||
final String parentName = entityData['root_author'] ?? '';
|
final String parentName = entityData['root_author'] ?? '';
|
||||||
final deletedPost = parentGuid.isEmpty || parentName.isEmpty;
|
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 text = deletedPost ? 'Original post deleted by author' : '';
|
||||||
final author =
|
final author =
|
||||||
connections.getByName(authorName).getValueOrElse(() => Connection());
|
connections.getByName(authorName).getValueOrElse(() => Connection());
|
||||||
|
@ -54,16 +56,16 @@ Result<TimelineEntry, ExecError> _buildReshareMessageType(
|
||||||
.getByName(parentName)
|
.getByName(parentName)
|
||||||
.getValueOrElse(() => Connection(name: parentName));
|
.getValueOrElse(() => Connection(name: parentName));
|
||||||
final timelineEntry = TimelineEntry(
|
final timelineEntry = TimelineEntry(
|
||||||
id: postId,
|
id: postId,
|
||||||
creationTimestamp: epochTime,
|
creationTimestamp: epochTime,
|
||||||
author: author.name,
|
author: author.name,
|
||||||
authorId: author.id,
|
authorId: author.id,
|
||||||
isReshare: true,
|
externalLink: _buildPostOrReshareUrl(authorName, '', postId),
|
||||||
parentAuthor: parentAuthor.name,
|
isReshare: true,
|
||||||
parentAuthorId: parentAuthor.id,
|
parentAuthor: parentAuthor.name,
|
||||||
externalLink: externalLink,
|
parentAuthorId: parentAuthor.id,
|
||||||
body: text,
|
body: text,
|
||||||
);
|
links: [Uri.parse(reshareLink)]);
|
||||||
return Result.ok(timelineEntry);
|
return Result.ok(timelineEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,16 +85,19 @@ Result<TimelineEntry, ExecError> _buildStatusMessageType(
|
||||||
.map((e) => mediaAttachmentfromDiasporaJson(e));
|
.map((e) => mediaAttachmentfromDiasporaJson(e));
|
||||||
|
|
||||||
final timelineEntry = TimelineEntry(
|
final timelineEntry = TimelineEntry(
|
||||||
id: postId,
|
id: postId,
|
||||||
creationTimestamp: epochTime,
|
creationTimestamp: epochTime,
|
||||||
body: postHtml,
|
body: postHtml,
|
||||||
author: author.name,
|
author: author.name,
|
||||||
mediaAttachments: photos.toList(),
|
externalLink: _buildPostOrReshareUrl(authorName, '', postId),
|
||||||
authorId: author.id);
|
mediaAttachments: photos.toList(),
|
||||||
|
authorId: author.id,
|
||||||
|
);
|
||||||
return Result.ok(timelineEntry);
|
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 accountId = rootAuthor.isNotEmpty ? rootAuthor : author;
|
||||||
final accountIdPieces = accountId.split('@');
|
final accountIdPieces = accountId.split('@');
|
||||||
if (accountIdPieces.length != 2) {
|
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_path_mapping_service.dart';
|
||||||
import 'package:friendica_archive_browser/src/diaspora/services/diaspora_profile_json_reader.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:friendica_archive_browser/src/services/archive_service_interface.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:result_monad/result_monad.dart';
|
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 '../../models/local_image_archive_entry.dart';
|
||||||
import '../../services/connections_manager.dart';
|
import '../../services/connections_manager.dart';
|
||||||
import '../../utils/exec_error.dart';
|
import '../../utils/exec_error.dart';
|
||||||
|
import '../models/diaspora_reaction.dart';
|
||||||
|
import '../serializers/diaspora_contact_serializer.dart';
|
||||||
|
|
||||||
class DiasporaArchiveService implements ArchiveService {
|
class DiasporaArchiveService implements ArchiveService {
|
||||||
@override
|
@override
|
||||||
|
@ -94,15 +97,84 @@ class DiasporaArchiveService implements ArchiveService {
|
||||||
if (_ownersName.isEmpty) {
|
if (_ownersName.isEmpty) {
|
||||||
_ownersName = reader.readOwnersName();
|
_ownersName = reader.readOwnersName();
|
||||||
reader.readContacts();
|
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.addAll(newPosts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_postEntries.sort((p1, p2) =>
|
_postEntries.sort((p1, p2) => _reverseChronologicalSort(p1, p2));
|
||||||
p2.entry.creationTimestamp.compareTo(p1.entry.creationTimestamp));
|
_orphanedCommentEntries.sort((p1, p2) => _reverseChronologicalSort(p1, p2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int _reverseChronologicalSort(EntryTreeItem p1, EntryTreeItem p2) =>
|
||||||
|
p2.entry.creationTimestamp.compareTo(p1.entry.creationTimestamp);
|
||||||
|
|
||||||
void _populateTopLevelSubDirectory() {
|
void _populateTopLevelSubDirectory() {
|
||||||
final topLevelDirectories = Directory(_baseArchiveFolder)
|
final topLevelDirectories = Directory(_baseArchiveFolder)
|
||||||
.listSync(recursive: false)
|
.listSync(recursive: false)
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
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_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/connection.dart';
|
||||||
import 'package:friendica_archive_browser/src/models/timeline_entry.dart';
|
import 'package:friendica_archive_browser/src/models/timeline_entry.dart';
|
||||||
import 'package:friendica_archive_browser/src/services/connections_manager.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));
|
.sort((p1, p2) => p2.creationTimestamp.compareTo(p1.creationTimestamp));
|
||||||
return posts;
|
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,
|
modificationTimestamp: modificationTimestamp,
|
||||||
backdatedTimestamp: backdatedTimestamp,
|
backdatedTimestamp: backdatedTimestamp,
|
||||||
locationData: actualLocationData,
|
locationData: actualLocationData,
|
||||||
externalLink: externalLink,
|
|
||||||
body: body,
|
body: body,
|
||||||
isReshare: isReshare,
|
isReshare: isReshare,
|
||||||
id: id,
|
id: id,
|
||||||
parentId: parentId,
|
parentId: parentId,
|
||||||
parentAuthorId: parentAuthorId,
|
parentAuthorId: parentAuthorId,
|
||||||
|
externalLink: externalLink,
|
||||||
author: author,
|
author: author,
|
||||||
authorId: authorId,
|
authorId: authorId,
|
||||||
parentAuthor: parentAuthor,
|
parentAuthor: parentAuthor,
|
||||||
|
|
|
@ -41,6 +41,8 @@ class TimelineEntry {
|
||||||
|
|
||||||
final List<Connection> dislikes;
|
final List<Connection> dislikes;
|
||||||
|
|
||||||
|
final List<Uri> links;
|
||||||
|
|
||||||
TimelineEntry({
|
TimelineEntry({
|
||||||
this.id = '',
|
this.id = '',
|
||||||
this.parentId = '',
|
this.parentId = '',
|
||||||
|
@ -56,10 +58,13 @@ class TimelineEntry {
|
||||||
this.parentAuthorId = '',
|
this.parentAuthorId = '',
|
||||||
this.externalLink = '',
|
this.externalLink = '',
|
||||||
this.locationData = const LocationData(),
|
this.locationData = const LocationData(),
|
||||||
this.likes = const <Connection>[],
|
this.links = const [],
|
||||||
this.dislikes = const <Connection>[],
|
List<Connection>? likes,
|
||||||
|
List<Connection>? dislikes,
|
||||||
List<MediaAttachment>? mediaAttachments,
|
List<MediaAttachment>? mediaAttachments,
|
||||||
}) : mediaAttachments = mediaAttachments ?? <MediaAttachment>[];
|
}) : mediaAttachments = mediaAttachments ?? <MediaAttachment>[],
|
||||||
|
likes = likes ?? <Connection>[],
|
||||||
|
dislikes = dislikes ?? <Connection>[];
|
||||||
|
|
||||||
TimelineEntry.randomBuilt()
|
TimelineEntry.randomBuilt()
|
||||||
: creationTimestamp = DateTime.now().millisecondsSinceEpoch,
|
: creationTimestamp = DateTime.now().millisecondsSinceEpoch,
|
||||||
|
@ -78,6 +83,7 @@ class TimelineEntry {
|
||||||
locationData = LocationData.randomBuilt(),
|
locationData = LocationData.randomBuilt(),
|
||||||
likes = const <Connection>[],
|
likes = const <Connection>[],
|
||||||
dislikes = const <Connection>[],
|
dislikes = const <Connection>[],
|
||||||
|
links = [],
|
||||||
mediaAttachments = [
|
mediaAttachments = [
|
||||||
MediaAttachment.randomBuilt(),
|
MediaAttachment.randomBuilt(),
|
||||||
MediaAttachment.randomBuilt()
|
MediaAttachment.randomBuilt()
|
||||||
|
@ -103,24 +109,26 @@ class TimelineEntry {
|
||||||
List<Connection>? dislikes,
|
List<Connection>? dislikes,
|
||||||
List<Uri>? links}) {
|
List<Uri>? links}) {
|
||||||
return TimelineEntry(
|
return TimelineEntry(
|
||||||
creationTimestamp: creationTimestamp ?? this.creationTimestamp,
|
creationTimestamp: creationTimestamp ?? this.creationTimestamp,
|
||||||
backdatedTimestamp: backdatedTimestamp ?? this.backdatedTimestamp,
|
backdatedTimestamp: backdatedTimestamp ?? this.backdatedTimestamp,
|
||||||
modificationTimestamp:
|
modificationTimestamp:
|
||||||
modificationTimestamp ?? this.modificationTimestamp,
|
modificationTimestamp ?? this.modificationTimestamp,
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
isReshare: isReshare ?? this.isReshare,
|
isReshare: isReshare ?? this.isReshare,
|
||||||
parentId: parentId ?? this.parentId,
|
parentId: parentId ?? this.parentId,
|
||||||
externalLink: externalLink ?? this.externalLink,
|
externalLink: externalLink ?? this.externalLink,
|
||||||
body: body ?? this.body,
|
body: body ?? this.body,
|
||||||
title: title ?? this.title,
|
title: title ?? this.title,
|
||||||
author: author ?? this.author,
|
author: author ?? this.author,
|
||||||
authorId: authorId ?? this.authorId,
|
authorId: authorId ?? this.authorId,
|
||||||
parentAuthor: parentAuthor ?? this.parentAuthor,
|
parentAuthor: parentAuthor ?? this.parentAuthor,
|
||||||
parentAuthorId: parentAuthorId ?? this.parentAuthorId,
|
parentAuthorId: parentAuthorId ?? this.parentAuthorId,
|
||||||
locationData: locationData ?? this.locationData,
|
locationData: locationData ?? this.locationData,
|
||||||
mediaAttachments: mediaAttachments ?? this.mediaAttachments,
|
mediaAttachments: mediaAttachments ?? this.mediaAttachments,
|
||||||
likes: likes ?? this.likes,
|
likes: likes ?? this.likes,
|
||||||
dislikes: dislikes ?? this.dislikes);
|
dislikes: dislikes ?? this.dislikes,
|
||||||
|
links: links ?? this.links,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -147,6 +155,7 @@ class TimelineEntry {
|
||||||
if (mediaAttachments.isNotEmpty) 'Photos and Videos:',
|
if (mediaAttachments.isNotEmpty) 'Photos and Videos:',
|
||||||
...mediaAttachments.map((e) => e.toHumanString(mapper)),
|
...mediaAttachments.map((e) => e.toHumanString(mapper)),
|
||||||
if (locationData.hasPosition) locationData.toHumanString(),
|
if (locationData.hasPosition) locationData.toHumanString(),
|
||||||
|
if (links.isNotEmpty) ...['Links:', ...links.map((e) => e.toString())]
|
||||||
].join('\n');
|
].join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import 'package:flutter/material.dart';
|
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/heatmap_widget.dart';
|
||||||
import 'package:friendica_archive_browser/src/components/timechart_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/top_interactactors_widget.dart';
|
||||||
import 'package:friendica_archive_browser/src/components/word_frequency_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/model_utils.dart';
|
||||||
import 'package:friendica_archive_browser/src/models/time_element.dart';
|
import 'package:friendica_archive_browser/src/models/time_element.dart';
|
||||||
import 'package:friendica_archive_browser/src/screens/standin_status_screen.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 {
|
class ConnectionsManager {
|
||||||
final _connectionsById = <String, Connection>{};
|
final _connectionsById = <String, Connection>{};
|
||||||
final _connectionsByName = <String, Connection>{};
|
final _connectionsByName = <String, Connection>{};
|
||||||
|
final _connectionsByProfileUrl = <Uri, Connection>{};
|
||||||
|
|
||||||
int get length => _connectionsById.length;
|
int get length => _connectionsById.length;
|
||||||
|
|
||||||
void clearCaches() {
|
void clearCaches() {
|
||||||
_connectionsById.clear();
|
_connectionsById.clear();
|
||||||
_connectionsByName.clear();
|
_connectionsByName.clear();
|
||||||
|
_connectionsByProfileUrl.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool addConnection(Connection connection) {
|
bool addConnection(Connection connection) {
|
||||||
|
@ -18,6 +20,7 @@ class ConnectionsManager {
|
||||||
}
|
}
|
||||||
_connectionsById[connection.id] = connection;
|
_connectionsById[connection.id] = connection;
|
||||||
_connectionsByName[connection.name] = connection;
|
_connectionsByName[connection.name] = connection;
|
||||||
|
_connectionsByProfileUrl[connection.profileUrl] = connection;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -43,4 +46,10 @@ class ConnectionsManager {
|
||||||
|
|
||||||
return result != null ? Result.ok(result) : Result.error('$name not found');
|
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