mirror of
https://gitlab.com/mysocialportal/fediverse-archiving-tools.git
synced 2024-10-18 08:53:31 +00:00
Add external links, likes, dislikes, and tracking known other users.
This commit is contained in:
parent
33dfb509f0
commit
8a5636ab54
8 changed files with 179 additions and 133 deletions
|
@ -79,11 +79,22 @@ class TreeEntryCard extends StatelessWidget {
|
|||
snackbarMessage: 'Copied Post to clipboard'),
|
||||
icon: const Icon(Icons.copy)),
|
||||
),
|
||||
Tooltip(
|
||||
message: 'Open link to original item',
|
||||
child: IconButton(
|
||||
onPressed: () async {
|
||||
await canLaunch(entry.externalLink)
|
||||
? await launch(entry.externalLink)
|
||||
: _logger.info(
|
||||
'Failed to launch ${entry.externalLink}');
|
||||
},
|
||||
icon: const Icon(Icons.link)),
|
||||
),
|
||||
]),
|
||||
if (entry.post.isNotEmpty) ...[
|
||||
if (entry.body.isNotEmpty) ...[
|
||||
const SizedBox(height: spacingHeight),
|
||||
HtmlWidget(
|
||||
entry.post,
|
||||
entry.body,
|
||||
onTapUrl: (url) async {
|
||||
bool canLaunchResult = await canLaunch(url);
|
||||
if (!canLaunchResult) {
|
||||
|
@ -100,6 +111,22 @@ class TreeEntryCard extends StatelessWidget {
|
|||
},
|
||||
)
|
||||
],
|
||||
const SizedBox(height: spacingHeight * 2),
|
||||
Row(
|
||||
children: [
|
||||
Tooltip(
|
||||
message: entry.likes.map((e) => e.name).join(', '),
|
||||
child: const Icon(Icons.thumb_up_alt_outlined)),
|
||||
Text('${entry.likes.length}'),
|
||||
SizedBox(
|
||||
width: 3,
|
||||
),
|
||||
Tooltip(
|
||||
message: entry.dislikes.map((e) => e.name).join(', '),
|
||||
child: const Icon(Icons.thumb_down_alt_outlined)),
|
||||
Text('${entry.dislikes.length}'),
|
||||
],
|
||||
),
|
||||
if (entry.locationData.hasData())
|
||||
entry.locationData.toWidget(spacingHeight),
|
||||
if (entry.links.isNotEmpty) ...[
|
||||
|
|
|
@ -1,123 +1,57 @@
|
|||
import 'package:logging/logging.dart';
|
||||
|
||||
import 'model_utils.dart';
|
||||
|
||||
class FriendicaContact {
|
||||
static final _logger = Logger('$FriendicaContact');
|
||||
final ConnectionStatus status;
|
||||
|
||||
final FriendStatus status;
|
||||
final String name;
|
||||
final String contactInfo;
|
||||
final int friendSinceTimestamp;
|
||||
final int receivedTimestamp;
|
||||
final int rejectedTimestamp;
|
||||
final int removeTimestamp;
|
||||
final int sentTimestamp;
|
||||
final bool markedAsSpam;
|
||||
|
||||
final String id;
|
||||
|
||||
final Uri profileUrl;
|
||||
|
||||
final String network;
|
||||
|
||||
FriendicaContact(
|
||||
{this.status = FriendStatus.unknown,
|
||||
{required this.status,
|
||||
required this.name,
|
||||
this.contactInfo = '',
|
||||
this.friendSinceTimestamp = 0,
|
||||
this.receivedTimestamp = 0,
|
||||
this.rejectedTimestamp = 0,
|
||||
this.removeTimestamp = 0,
|
||||
this.sentTimestamp = 0,
|
||||
this.markedAsSpam = false});
|
||||
required this.id,
|
||||
required this.profileUrl,
|
||||
required this.network});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'FriendicaFriend{status: $status, name: $name, contactInfo: $contactInfo, friendSinceTimestamp: $friendSinceTimestamp, receivedTimestamp: $receivedTimestamp, rejectedTimestamp: $rejectedTimestamp, removeTimestamp: $removeTimestamp, sentTimestamp: $sentTimestamp, markedAsSpam: $markedAsSpam}';
|
||||
}
|
||||
|
||||
static FriendicaContact fromJson(
|
||||
Map<String, dynamic> json, FriendStatus status) {
|
||||
final knownTopLevelKeys = [
|
||||
'timestamp',
|
||||
'name',
|
||||
'contact_info',
|
||||
'marked_as_spam'
|
||||
];
|
||||
int timestamp = json['timestamp'] ?? 0;
|
||||
static FriendicaContact fromJson(Map<String, dynamic> json) {
|
||||
final status = (json['following'] ?? '') == 'true'
|
||||
? ConnectionStatus.youFollowThem
|
||||
: ConnectionStatus.none;
|
||||
final name = json['name'] ?? '';
|
||||
final contactInfo = json['contact_info'] ?? '';
|
||||
final markedAsSpam = json['marked_as_spam'] ?? false;
|
||||
final id = json['id_str'] ?? '';
|
||||
final profileUrl = Uri.parse(json['url'] ?? '');
|
||||
final network = json['network'] ?? 'unkn';
|
||||
|
||||
logAdditionalKeys(knownTopLevelKeys, json.keys, _logger, Level.WARNING,
|
||||
'Unknown top level friend keys');
|
||||
|
||||
switch (status) {
|
||||
case FriendStatus.friends:
|
||||
return FriendicaContact(
|
||||
name: name,
|
||||
status: status,
|
||||
contactInfo: contactInfo,
|
||||
markedAsSpam: markedAsSpam,
|
||||
friendSinceTimestamp: timestamp);
|
||||
case FriendStatus.requestReceived:
|
||||
return FriendicaContact(
|
||||
name: name,
|
||||
status: status,
|
||||
contactInfo: contactInfo,
|
||||
markedAsSpam: markedAsSpam,
|
||||
receivedTimestamp: timestamp);
|
||||
case FriendStatus.rejectedRequest:
|
||||
return FriendicaContact(
|
||||
name: name,
|
||||
status: status,
|
||||
contactInfo: contactInfo,
|
||||
markedAsSpam: markedAsSpam,
|
||||
rejectedTimestamp: timestamp);
|
||||
case FriendStatus.removed:
|
||||
return FriendicaContact(
|
||||
name: name,
|
||||
status: status,
|
||||
contactInfo: contactInfo,
|
||||
markedAsSpam: markedAsSpam,
|
||||
removeTimestamp: timestamp);
|
||||
case FriendStatus.sentFriendRequest:
|
||||
return FriendicaContact(
|
||||
name: name,
|
||||
status: status,
|
||||
contactInfo: contactInfo,
|
||||
markedAsSpam: markedAsSpam,
|
||||
sentTimestamp: timestamp);
|
||||
case FriendStatus.unknown:
|
||||
return FriendicaContact(
|
||||
name: name,
|
||||
status: status,
|
||||
contactInfo: contactInfo,
|
||||
markedAsSpam: markedAsSpam,
|
||||
);
|
||||
}
|
||||
return FriendicaContact(
|
||||
status: status,
|
||||
name: name,
|
||||
id: id,
|
||||
profileUrl: profileUrl,
|
||||
network: network);
|
||||
}
|
||||
}
|
||||
|
||||
enum FriendStatus {
|
||||
friends,
|
||||
requestReceived,
|
||||
rejectedRequest,
|
||||
removed,
|
||||
sentFriendRequest,
|
||||
unknown,
|
||||
enum ConnectionStatus {
|
||||
youFollowThem,
|
||||
theyFollowYou,
|
||||
mutual,
|
||||
none,
|
||||
}
|
||||
|
||||
extension FriendStatusWriter on FriendStatus {
|
||||
extension FriendStatusWriter on ConnectionStatus {
|
||||
String name() {
|
||||
switch (this) {
|
||||
case FriendStatus.friends:
|
||||
return "Friends";
|
||||
case FriendStatus.requestReceived:
|
||||
return "Requested";
|
||||
case FriendStatus.rejectedRequest:
|
||||
return "Rejected";
|
||||
case FriendStatus.removed:
|
||||
return "Removed";
|
||||
case FriendStatus.sentFriendRequest:
|
||||
return "Sent Request";
|
||||
case FriendStatus.unknown:
|
||||
return "Unknown";
|
||||
case ConnectionStatus.youFollowThem:
|
||||
return "You Follow Them";
|
||||
case ConnectionStatus.theyFollowYou:
|
||||
return "They Follow You";
|
||||
case ConnectionStatus.mutual:
|
||||
return "Follow each other";
|
||||
case ConnectionStatus.none:
|
||||
return "Not connected";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import 'package:friendica_archive_browser/src/friendica/models/friendica_contact.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/services/path_mapping_service.dart';
|
||||
import 'package:friendica_archive_browser/src/services/friendica_connections.dart';
|
||||
import 'package:friendica_archive_browser/src/utils/offsetdatetime_utils.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
@ -6,7 +8,6 @@ import 'package:logging/logging.dart';
|
|||
import 'friendica_media_attachment.dart';
|
||||
import 'location_data.dart';
|
||||
import 'model_utils.dart';
|
||||
import 'timeline_type.dart';
|
||||
|
||||
class FriendicaTimelineEntry {
|
||||
static final _logger = Logger('$FriendicaTimelineEntry');
|
||||
|
@ -23,19 +24,25 @@ class FriendicaTimelineEntry {
|
|||
|
||||
final int modificationTimestamp;
|
||||
|
||||
final String post;
|
||||
final String body;
|
||||
|
||||
final String title;
|
||||
|
||||
final String author;
|
||||
|
||||
final String authorId;
|
||||
|
||||
final String externalLink;
|
||||
|
||||
final List<FriendicaMediaAttachment> mediaAttachments;
|
||||
|
||||
final LocationData locationData;
|
||||
|
||||
final List<Uri> links;
|
||||
|
||||
final TimelineType timelineType;
|
||||
final List<FriendicaContact> likes;
|
||||
|
||||
final List<FriendicaContact> dislikes;
|
||||
|
||||
FriendicaTimelineEntry(
|
||||
{this.id = '',
|
||||
|
@ -43,12 +50,15 @@ class FriendicaTimelineEntry {
|
|||
this.creationTimestamp = 0,
|
||||
this.backdatedTimestamp = 0,
|
||||
this.modificationTimestamp = 0,
|
||||
this.post = '',
|
||||
this.body = '',
|
||||
this.title = '',
|
||||
this.author = '',
|
||||
this.authorId = '',
|
||||
this.parentAuthor = '',
|
||||
this.externalLink = '',
|
||||
this.locationData = const LocationData(),
|
||||
required this.timelineType,
|
||||
this.likes = const <FriendicaContact>[],
|
||||
this.dislikes = const <FriendicaContact>[],
|
||||
List<FriendicaMediaAttachment>? mediaAttachments,
|
||||
List<Uri>? links})
|
||||
: mediaAttachments = mediaAttachments ?? <FriendicaMediaAttachment>[],
|
||||
|
@ -60,12 +70,15 @@ class FriendicaTimelineEntry {
|
|||
modificationTimestamp = DateTime.now().millisecondsSinceEpoch,
|
||||
id = randomId(),
|
||||
parentId = randomId(),
|
||||
post = 'Random post text ${randomId()}',
|
||||
externalLink = 'Random external link ${randomId()}',
|
||||
body = 'Random post text ${randomId()}',
|
||||
title = 'Random title ${randomId()}',
|
||||
author = 'Random author ${randomId()}',
|
||||
authorId = 'Random authorId ${randomId()}',
|
||||
parentAuthor = 'Random parent author ${randomId()}',
|
||||
locationData = LocationData.randomBuilt(),
|
||||
timelineType = TimelineType.active,
|
||||
likes = const <FriendicaContact>[],
|
||||
dislikes = const <FriendicaContact>[],
|
||||
links = [
|
||||
Uri.parse('http://localhost/${randomId()}'),
|
||||
Uri.parse('http://localhost/${randomId()}')
|
||||
|
@ -81,13 +94,16 @@ class FriendicaTimelineEntry {
|
|||
int? modificationTimestamp,
|
||||
String? id,
|
||||
String? parentId,
|
||||
String? post,
|
||||
String? externalLink,
|
||||
String? body,
|
||||
String? title,
|
||||
String? author,
|
||||
String? authorId,
|
||||
String? parentAuthor,
|
||||
LocationData? locationData,
|
||||
List<FriendicaMediaAttachment>? mediaAttachments,
|
||||
TimelineType? timelineType,
|
||||
List<FriendicaContact>? likes,
|
||||
List<FriendicaContact>? dislikes,
|
||||
List<Uri>? links}) {
|
||||
return FriendicaTimelineEntry(
|
||||
creationTimestamp: creationTimestamp ?? this.creationTimestamp,
|
||||
|
@ -96,19 +112,22 @@ class FriendicaTimelineEntry {
|
|||
modificationTimestamp ?? this.modificationTimestamp,
|
||||
id: id ?? this.id,
|
||||
parentId: parentId ?? this.parentId,
|
||||
post: post ?? this.post,
|
||||
externalLink: externalLink ?? this.externalLink,
|
||||
body: body ?? this.body,
|
||||
title: title ?? this.title,
|
||||
author: author ?? this.author,
|
||||
authorId: authorId ?? this.authorId,
|
||||
parentAuthor: parentAuthor ?? this.parentAuthor,
|
||||
locationData: locationData ?? this.locationData,
|
||||
mediaAttachments: mediaAttachments ?? this.mediaAttachments,
|
||||
timelineType: timelineType ?? this.timelineType,
|
||||
likes: likes ?? this.likes,
|
||||
dislikes: dislikes ?? this.dislikes,
|
||||
links: links ?? this.links);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'FriendicaTimelineEntry{id: $id, parentId: $parentId, creationTimestamp: $creationTimestamp, modificationTimestamp: $modificationTimestamp, backdatedTimeStamp: $backdatedTimestamp, timelineType: $timelineType, post: $post, title: $title, author: $author, parentAuthor: $parentAuthor mediaAttachments: $mediaAttachments, links: $links}';
|
||||
return 'FriendicaTimelineEntry{id: $id, parentId: $parentId, creationTimestamp: $creationTimestamp, modificationTimestamp: $modificationTimestamp, backdatedTimeStamp: $backdatedTimestamp, post: $body, title: $title, author: $author, parentAuthor: $parentAuthor mediaAttachments: $mediaAttachments, links: $links}';
|
||||
}
|
||||
|
||||
String toHumanString(PathMappingService mapper, DateFormat formatter) {
|
||||
|
@ -120,7 +139,8 @@ class FriendicaTimelineEntry {
|
|||
'Creation At: $creationDateString',
|
||||
'Text:',
|
||||
'Author: $author',
|
||||
post,
|
||||
if (externalLink.isNotEmpty) 'External Link: $externalLink',
|
||||
body,
|
||||
'',
|
||||
if (parentId.isEmpty)
|
||||
"Comment on post/comment by ${parentAuthor.isNotEmpty ? parentAuthor : 'unknown author'}",
|
||||
|
@ -144,7 +164,7 @@ class FriendicaTimelineEntry {
|
|||
.isNotEmpty;
|
||||
|
||||
static FriendicaTimelineEntry fromJson(
|
||||
Map<String, dynamic> json, TimelineType timelineType) {
|
||||
Map<String, dynamic> json, FriendicaConnections connections) {
|
||||
final int timestamp = json.containsKey('created_at')
|
||||
? OffsetDateTimeUtils.epochSecTimeFromFriendicaString(
|
||||
json['created_at'])
|
||||
|
@ -160,7 +180,9 @@ class FriendicaTimelineEntry {
|
|||
final parentAuthor = json['in_reply_to_screen_name'] ?? '';
|
||||
final post = json['friendica_html'] ?? '';
|
||||
final author = json['user']['name'];
|
||||
final authorId = json['user']['id_str'];
|
||||
final title = json['friendica_title'] ?? '';
|
||||
final externalLink = json['external_url'] ?? '';
|
||||
final actualLocationData = LocationData();
|
||||
final modificationTimestamp = timestamp;
|
||||
final backdatedTimestamp = timestamp;
|
||||
|
@ -168,21 +190,40 @@ class FriendicaTimelineEntry {
|
|||
final mediaAttachments = (json['attachments'] as List<dynamic>? ?? [])
|
||||
.map((j) => FriendicaMediaAttachment.fromJson(j))
|
||||
.toList();
|
||||
final likes =
|
||||
(json['friendica_activities']?['like'] as List<dynamic>? ?? [])
|
||||
.map((json) => FriendicaContact.fromJson(json))
|
||||
.toList();
|
||||
final dislikes =
|
||||
(json['friendica_activities']?['dislike'] as List<dynamic>? ?? [])
|
||||
.map((json) => FriendicaContact.fromJson(json))
|
||||
.toList();
|
||||
final announce =
|
||||
(json['friendica_activities']?['announce'] as List<dynamic>? ?? [])
|
||||
.map((json) => FriendicaContact.fromJson(json))
|
||||
.toList();
|
||||
|
||||
for (final contact in [...likes, ...dislikes, ...announce]) {
|
||||
connections.addConnection(contact);
|
||||
}
|
||||
|
||||
return FriendicaTimelineEntry(
|
||||
creationTimestamp: timestamp,
|
||||
modificationTimestamp: modificationTimestamp,
|
||||
backdatedTimestamp: backdatedTimestamp,
|
||||
locationData: actualLocationData,
|
||||
post: post,
|
||||
externalLink: externalLink,
|
||||
body: post,
|
||||
id: id,
|
||||
parentId: parentId,
|
||||
author: author,
|
||||
authorId: authorId,
|
||||
parentAuthor: parentAuthor,
|
||||
title: title,
|
||||
links: links,
|
||||
likes: likes,
|
||||
dislikes: dislikes,
|
||||
mediaAttachments: mediaAttachments,
|
||||
timelineType: timelineType,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
enum TimelineType {
|
||||
active,
|
||||
archive,
|
||||
trash,
|
||||
}
|
|
@ -69,7 +69,7 @@ class _FriendicaEntriesScreenWidget extends StatelessWidget {
|
|||
imagesOnlyFilterFunction: (post) => post.entry.hasImages(),
|
||||
videosOnlyFilterFunction: (post) => post.entry.hasVideos(),
|
||||
textSearchFilterFunction: (post, text) =>
|
||||
post.entry.title.contains(text) || post.entry.post.contains(text),
|
||||
post.entry.title.contains(text) || post.entry.body.contains(text),
|
||||
itemToDateTimeFunction: (post) => DateTime.fromMillisecondsSinceEpoch(
|
||||
post.entry.creationTimestamp * 1000),
|
||||
dateRangeFilterFunction: (post, start, stop) =>
|
||||
|
|
|
@ -51,7 +51,7 @@ class _StatsScreenState extends State<StatsScreen> {
|
|||
hasImages: e.entry.hasImages(),
|
||||
hasVideos: e.entry.hasVideos(),
|
||||
title: e.entry.title,
|
||||
text: e.entry.post)),
|
||||
text: e.entry.body)),
|
||||
onError: (error) {
|
||||
_logger.severe('Error getting posts: $error');
|
||||
return [];
|
||||
|
@ -64,7 +64,7 @@ class _StatsScreenState extends State<StatsScreen> {
|
|||
hasImages: e.entry.hasImages(),
|
||||
hasVideos: e.entry.hasVideos(),
|
||||
title: e.entry.title,
|
||||
text: e.entry.post)),
|
||||
text: e.entry.body)),
|
||||
onError: (error) {
|
||||
_logger.severe('Error getting oprhaned comments: $error');
|
||||
return [];
|
||||
|
|
|
@ -4,9 +4,9 @@ import 'dart:io';
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/models/friendica_entry_tree_item.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/models/friendica_timeline_entry.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/models/timeline_type.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/services/path_mapping_service.dart';
|
||||
import 'package:friendica_archive_browser/src/models/local_image_archive_entry.dart';
|
||||
import 'package:friendica_archive_browser/src/services/friendica_connections.dart';
|
||||
import 'package:friendica_archive_browser/src/utils/exec_error.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:result_monad/result_monad.dart';
|
||||
|
@ -16,10 +16,24 @@ class FriendicaArchiveService extends ChangeNotifier {
|
|||
final Map<String, ImageEntry> _imagesByRequestUrl = {};
|
||||
final List<FriendicaEntryTreeItem> _postEntries = [];
|
||||
final List<FriendicaEntryTreeItem> _orphanedCommentEntries = [];
|
||||
final FriendicaConnections _connections = FriendicaConnections();
|
||||
String _ownersName = '';
|
||||
|
||||
FriendicaArchiveService({required this.pathMappingService});
|
||||
|
||||
String get ownersName {
|
||||
if (_ownersName.isNotEmpty) {
|
||||
return _ownersName;
|
||||
}
|
||||
|
||||
final uniqueNames = _postEntries.map((e) => e.entry.author).toSet();
|
||||
_ownersName = uniqueNames.isNotEmpty ? uniqueNames.first : '';
|
||||
|
||||
return _ownersName;
|
||||
}
|
||||
|
||||
void clearCaches() {
|
||||
_connections.clearCaches();
|
||||
_imagesByRequestUrl.clear();
|
||||
_orphanedCommentEntries.clear();
|
||||
_postEntries.clear();
|
||||
|
@ -60,8 +74,8 @@ class FriendicaArchiveService extends ChangeNotifier {
|
|||
final jsonFile = File(entriesJsonPath);
|
||||
if (jsonFile.existsSync()) {
|
||||
final json = jsonDecode(jsonFile.readAsStringSync()) as List<dynamic>;
|
||||
final entries = json
|
||||
.map((j) => FriendicaTimelineEntry.fromJson(j, TimelineType.active));
|
||||
final entries =
|
||||
json.map((j) => FriendicaTimelineEntry.fromJson(j, _connections));
|
||||
final topLevelEntries =
|
||||
entries.where((element) => element.parentId.isEmpty);
|
||||
final ids = entries.map((e) => e.id).toSet();
|
||||
|
@ -88,6 +102,7 @@ class FriendicaArchiveService extends ChangeNotifier {
|
|||
_postEntries.clear();
|
||||
_postEntries
|
||||
.addAll(entryTrees.values.where((element) => !element.isOrphaned));
|
||||
|
||||
_orphanedCommentEntries.clear();
|
||||
_orphanedCommentEntries
|
||||
.addAll(entryTrees.values.where((element) => element.isOrphaned));
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import 'package:friendica_archive_browser/src/friendica/models/friendica_contact.dart';
|
||||
import 'package:result_monad/result_monad.dart';
|
||||
|
||||
class FriendicaConnections {
|
||||
final _connectionsById = <String, FriendicaContact>{};
|
||||
final _connectionsByName = <String, FriendicaContact>{};
|
||||
|
||||
void clearCaches() {
|
||||
_connectionsById.clear();
|
||||
_connectionsByName.clear();
|
||||
}
|
||||
|
||||
bool addConnection(FriendicaContact contact) {
|
||||
if (_connectionsById.containsKey(contact.id)) {
|
||||
return false;
|
||||
}
|
||||
_connectionsById[contact.id] = contact;
|
||||
_connectionsByName[contact.name] = contact;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Result<FriendicaContact, String> getById(String id) {
|
||||
final result = _connectionsById[id];
|
||||
|
||||
return result != null ? Result.ok(result) : Result.error('$id not found');
|
||||
}
|
||||
|
||||
Result<FriendicaContact, String> getByName(String name) {
|
||||
final result = _connectionsByName[name];
|
||||
|
||||
return result != null ? Result.ok(result) : Result.error('$name not found');
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue