mirror of
https://gitlab.com/mysocialportal/fediverse-archiving-tools.git
synced 2024-10-18 08:53:31 +00:00
D* (minus photos) and Friendica loading in UI.
This commit is contained in:
parent
b8b40ebe5b
commit
1deaebd94e
47 changed files with 970 additions and 581 deletions
|
@ -2,12 +2,11 @@ import 'package:desktop_window/desktop_window.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/services/friendica_archive_service.dart';
|
||||
import 'package:friendica_archive_browser/src/services/archive_service_provider.dart';
|
||||
import 'package:friendica_archive_browser/src/themes.dart';
|
||||
import 'package:friendica_archive_browser/src/utils/scrolling_behavior.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'friendica/services/friendica_path_mapping_service.dart';
|
||||
import 'home.dart';
|
||||
import 'settings/settings_controller.dart';
|
||||
|
||||
|
@ -25,12 +24,9 @@ class FriendicaArchiveBrowser extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
DesktopWindow.setMinWindowSize(minAppSize);
|
||||
final pathMappingService = FriendicaPathMappingService(settingsController);
|
||||
final friendicaArchiveService =
|
||||
FriendicaArchiveService(pathMappingService: pathMappingService);
|
||||
final archiveService = ArchiveServiceProvider(settingsController);
|
||||
settingsController.addListener(() {
|
||||
friendicaArchiveService.clearCaches();
|
||||
pathMappingService.refresh();
|
||||
archiveService.clearCaches();
|
||||
});
|
||||
return AnimatedBuilder(
|
||||
animation: settingsController,
|
||||
|
@ -55,13 +51,11 @@ class FriendicaArchiveBrowser extends StatelessWidget {
|
|||
home: MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(create: (context) => settingsController),
|
||||
ChangeNotifierProvider(
|
||||
create: (context) => friendicaArchiveService),
|
||||
Provider(create: (context) => pathMappingService),
|
||||
ChangeNotifierProvider(create: (context) => archiveService),
|
||||
],
|
||||
child: Home(
|
||||
settingsController: settingsController,
|
||||
archiveService: friendicaArchiveService),
|
||||
archiveService: archiveService),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:friendica_archive_browser/src/models/time_element.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/services/friendica_connections.dart';
|
||||
import 'package:friendica_archive_browser/src/services/connections_manager.dart';
|
||||
import 'package:friendica_archive_browser/src/utils/snackbar_status_builder.dart';
|
||||
import 'package:friendica_archive_browser/src/utils/top_interactors_generator.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
@ -8,7 +8,7 @@ import 'package:url_launcher/url_launcher.dart';
|
|||
|
||||
class TopInteractorsWidget extends StatefulWidget {
|
||||
final List<TimeElement> entries;
|
||||
final FriendicaConnections connections;
|
||||
final ConnectionsManager connections;
|
||||
|
||||
const TopInteractorsWidget(this.entries, this.connections, {Key? key})
|
||||
: super(key: key);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:friendica_archive_browser/src/models/friendica_contact.dart';
|
||||
import 'package:friendica_archive_browser/src/models/connection.dart';
|
||||
|
||||
Contact friendicaContactFromDiasporaJson(Map<String, dynamic> json) {
|
||||
Connection contactFromDiasporaJson(Map<String, dynamic> json) {
|
||||
const network = "Diaspora";
|
||||
final accountId = json['account_id'] ?? '';
|
||||
final profileUrl = _profileUrlFromAccountId(accountId);
|
||||
|
@ -17,7 +17,7 @@ Contact friendicaContactFromDiasporaJson(Map<String, dynamic> json) {
|
|||
status = ConnectionStatus.theyFollowYou;
|
||||
}
|
||||
|
||||
return Contact(
|
||||
return Connection(
|
||||
status: status,
|
||||
name: name,
|
||||
id: id,
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
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/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 '../../services/connections_manager.dart';
|
||||
|
||||
final _logger = Logger('DiasporaPostsSerializer');
|
||||
const _statusMessageType = 'status_message';
|
||||
const _reshareType = 'reshare';
|
||||
const _knownPostTypes = [_statusMessageType, _reshareType];
|
||||
|
||||
Result<TimelineEntry, ExecError> timelineItemFromDiasporaPostJson(
|
||||
Map<String, dynamic> json, ConnectionsManager connections) {
|
||||
if (!json.containsKey('entity_data')) {
|
||||
return Result.error(ExecError.message('Timeline item has no entity data'));
|
||||
}
|
||||
final entityData = json['entity_data'] ?? {};
|
||||
final postId = entityData['guid'] ?? '';
|
||||
final entityType = json['entity_type'] ?? '';
|
||||
if (!_knownPostTypes.contains(entityType)) {
|
||||
final error = 'Unknown entity type $entityType for Post ID: $postId';
|
||||
_logger.severe(error);
|
||||
return Result.error(ExecError.message(error));
|
||||
}
|
||||
|
||||
if (entityType == _statusMessageType) {
|
||||
return _buildStatusMessageType(entityData, connections);
|
||||
} else {
|
||||
return _buildReshareMessageType(entityData, connections);
|
||||
}
|
||||
}
|
||||
|
||||
Result<TimelineEntry, ExecError> _buildReshareMessageType(
|
||||
entityData, ConnectionsManager connections) {
|
||||
final createdAtString = entityData['created_at'] ?? '';
|
||||
final epochTime =
|
||||
OffsetDateTimeUtils.epochSecTimeFromTimeZoneString(createdAtString)
|
||||
.getValueOrElse(() => -1);
|
||||
final postId = entityData['guid'] ?? '';
|
||||
final authorName = entityData['author'] ?? '';
|
||||
final parentGuid = entityData['root_guid'] ?? '';
|
||||
final parentName = entityData['root_author'] ?? '';
|
||||
final externalLink = _buildReshareUrl(authorName, parentName, parentGuid);
|
||||
final author =
|
||||
connections.getByName(authorName).getValueOrElse(() => Connection());
|
||||
final parentAuthor = connections
|
||||
.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,
|
||||
);
|
||||
return Result.ok(timelineEntry);
|
||||
}
|
||||
|
||||
Result<TimelineEntry, ExecError> _buildStatusMessageType(
|
||||
entityData, ConnectionsManager connections) {
|
||||
final createdAtString = entityData['created_at'] ?? '';
|
||||
final epochTime =
|
||||
OffsetDateTimeUtils.epochSecTimeFromTimeZoneString(createdAtString)
|
||||
.getValueOrElse(() => -1);
|
||||
final postId = entityData['guid'] ?? '';
|
||||
final postMarkdown = entityData['text'] ?? '';
|
||||
final postHtml = markdownToHtml(postMarkdown);
|
||||
final authorName = entityData['author'] ?? '';
|
||||
final author =
|
||||
connections.getByName(authorName).getValueOrElse(() => Connection());
|
||||
final timelineEntry = TimelineEntry(
|
||||
id: postId,
|
||||
creationTimestamp: epochTime,
|
||||
body: postHtml,
|
||||
author: author.name,
|
||||
authorId: author.id);
|
||||
return Result.ok(timelineEntry);
|
||||
}
|
||||
|
||||
String _buildReshareUrl(String author, String rootAuthor, String rootGuid) {
|
||||
final accountId = rootAuthor.isNotEmpty ? rootAuthor : author;
|
||||
final accountIdPieces = accountId.split('@');
|
||||
if (accountIdPieces.length != 2) {
|
||||
return rootGuid;
|
||||
}
|
||||
|
||||
final server = accountIdPieces[1];
|
||||
|
||||
return 'https://$server/p/$rootGuid';
|
||||
}
|
|
@ -1,23 +1,28 @@
|
|||
import 'dart:convert';
|
||||
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/services/archive_service_interface.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:result_monad/result_monad.dart';
|
||||
|
||||
import '../../friendica/models/friendica_entry_tree_item.dart';
|
||||
import '../../friendica/models/friendica_timeline_entry.dart';
|
||||
import '../../friendica/services/friendica_path_mapping_service.dart';
|
||||
import '../../models/entry_tree_item.dart';
|
||||
import '../../models/local_image_archive_entry.dart';
|
||||
import '../../services/connections_manager.dart';
|
||||
import '../../utils/exec_error.dart';
|
||||
import '../../friendica/services/friendica_connections.dart';
|
||||
|
||||
class DiasporaArchiveService {
|
||||
final FriendicaPathMappingService pathMappingService;
|
||||
class DiasporaArchiveService implements ArchiveService {
|
||||
@override
|
||||
final DiasporaPathMappingService pathMappingService;
|
||||
|
||||
final Map<String, ImageEntry> _imagesByRequestUrl = {};
|
||||
final List<FriendicaEntryTreeItem> _postEntries = [];
|
||||
final List<FriendicaEntryTreeItem> _orphanedCommentEntries = [];
|
||||
final List<FriendicaEntryTreeItem> _allComments = [];
|
||||
final FriendicaConnections connections = FriendicaConnections();
|
||||
final List<EntryTreeItem> _postEntries = [];
|
||||
final List<EntryTreeItem> _orphanedCommentEntries = [];
|
||||
final List<EntryTreeItem> _allComments = [];
|
||||
|
||||
@override
|
||||
final ConnectionsManager connectionsManager = ConnectionsManager();
|
||||
String _ownersName = '';
|
||||
|
||||
DiasporaArchiveService({required this.pathMappingService});
|
||||
|
@ -25,33 +30,32 @@ class DiasporaArchiveService {
|
|||
String get ownersName => _ownersName;
|
||||
|
||||
void clearCaches() {
|
||||
connections.clearCaches();
|
||||
connectionsManager.clearCaches();
|
||||
_imagesByRequestUrl.clear();
|
||||
_orphanedCommentEntries.clear();
|
||||
_allComments.clear();
|
||||
_postEntries.clear();
|
||||
}
|
||||
|
||||
FutureResult<List<FriendicaEntryTreeItem>, ExecError> getPosts() async {
|
||||
FutureResult<List<EntryTreeItem>, ExecError> getPosts() async {
|
||||
if (_postEntries.isEmpty && _allComments.isEmpty) {
|
||||
_loadEntries();
|
||||
_loadProfileFile();
|
||||
}
|
||||
|
||||
return Result.ok(_postEntries);
|
||||
}
|
||||
|
||||
FutureResult<List<FriendicaEntryTreeItem>, ExecError> getAllComments() async {
|
||||
FutureResult<List<EntryTreeItem>, ExecError> getAllComments() async {
|
||||
if (_postEntries.isEmpty && _allComments.isEmpty) {
|
||||
_loadEntries();
|
||||
_loadProfileFile();
|
||||
}
|
||||
|
||||
return Result.ok(_allComments);
|
||||
}
|
||||
|
||||
FutureResult<List<FriendicaEntryTreeItem>, ExecError>
|
||||
getOrphanedComments() async {
|
||||
FutureResult<List<EntryTreeItem>, ExecError> getOrphanedComments() async {
|
||||
if (_postEntries.isEmpty && _allComments.isEmpty) {
|
||||
_loadEntries();
|
||||
_loadProfileFile();
|
||||
}
|
||||
|
||||
return Result.ok(_orphanedCommentEntries);
|
||||
|
@ -70,46 +74,21 @@ class DiasporaArchiveService {
|
|||
|
||||
String get _baseArchiveFolder => pathMappingService.rootFolder;
|
||||
|
||||
void _loadEntries() {
|
||||
final entriesJsonPath = p.join(_baseArchiveFolder, 'postsAndComments.json');
|
||||
final jsonFile = File(entriesJsonPath);
|
||||
if (jsonFile.existsSync()) {
|
||||
final json = jsonDecode(jsonFile.readAsStringSync()) as List<dynamic>;
|
||||
final entries =
|
||||
json.map((j) => FriendicaTimelineEntry.fromJson(j, connections));
|
||||
final topLevelEntries =
|
||||
entries.where((element) => element.parentId.isEmpty);
|
||||
final commentEntries =
|
||||
entries.where((element) => element.parentId.isNotEmpty).toList();
|
||||
final entryTrees = <String, FriendicaEntryTreeItem>{};
|
||||
|
||||
final postTreeEntries = <FriendicaEntryTreeItem>[];
|
||||
for (final entry in topLevelEntries) {
|
||||
final treeEntry = FriendicaEntryTreeItem(entry, false);
|
||||
entryTrees[entry.id] = treeEntry;
|
||||
postTreeEntries.add(treeEntry);
|
||||
void _loadProfileFile() {
|
||||
_ownersName = '';
|
||||
final archiveDir = Directory(_baseArchiveFolder);
|
||||
final jsonFiles = archiveDir.listSync().where((element) =>
|
||||
element.statSync().type == FileSystemEntityType.file &&
|
||||
element.path.toLowerCase().endsWith('json'));
|
||||
for (final file in jsonFiles) {
|
||||
final reader =
|
||||
DiasporaProfileJsonReader(file.absolute.path, connectionsManager);
|
||||
if (_ownersName.isEmpty) {
|
||||
_ownersName = reader.readOwnersName();
|
||||
reader.readContacts();
|
||||
final newPosts = reader.readPosts().map((e) => EntryTreeItem(e, false));
|
||||
_postEntries.addAll(newPosts);
|
||||
}
|
||||
|
||||
final commentTreeEntries = <FriendicaEntryTreeItem>[];
|
||||
commentEntries.sort(
|
||||
(c1, c2) => c1.creationTimestamp.compareTo(c2.creationTimestamp));
|
||||
for (final entry in commentEntries) {
|
||||
final parent = entryTrees[entry.parentId];
|
||||
final treeEntry = FriendicaEntryTreeItem(entry, parent == null);
|
||||
parent?.addChild(treeEntry);
|
||||
entryTrees[entry.id] = treeEntry;
|
||||
commentTreeEntries.add(treeEntry);
|
||||
}
|
||||
|
||||
_postEntries.clear();
|
||||
_postEntries.addAll(postTreeEntries);
|
||||
|
||||
_allComments.clear();
|
||||
_allComments.addAll(commentTreeEntries);
|
||||
|
||||
_orphanedCommentEntries.clear();
|
||||
_orphanedCommentEntries
|
||||
.addAll(entryTrees.values.where((element) => element.isOrphaned));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../../services/path_mapper_service_interface.dart';
|
||||
import '../../settings/settings_controller.dart';
|
||||
|
||||
class DiasporaPathMappingService implements PathMappingService {
|
||||
static final _logger = Logger('$DiasporaPathMappingService');
|
||||
final SettingsController settings;
|
||||
final _photoDirectories = <FileSystemEntity>[];
|
||||
|
||||
DiasporaPathMappingService(this.settings) {
|
||||
refresh();
|
||||
}
|
||||
|
||||
@override
|
||||
String get rootFolder => settings.rootFolder;
|
||||
|
||||
@override
|
||||
List<FileSystemEntity> get archiveDirectories =>
|
||||
List.unmodifiable(_photoDirectories);
|
||||
|
||||
@override
|
||||
void refresh() {
|
||||
_logger.fine('Refreshing path mapping service directory data.');
|
||||
if (!Directory(settings.rootFolder).existsSync()) {
|
||||
_logger.severe(
|
||||
"Base directory does not exist! can't do mapping of ${settings.rootFolder}");
|
||||
return;
|
||||
}
|
||||
_photoDirectories.clear();
|
||||
|
||||
_photoDirectories.addAll(Directory(settings.rootFolder)
|
||||
.listSync(recursive: false)
|
||||
.where((element) =>
|
||||
element.statSync().type == FileSystemEntityType.directory));
|
||||
}
|
||||
|
||||
@override
|
||||
String toFullPath(String relPath) {
|
||||
for (final file in _photoDirectories) {
|
||||
final fullPath = p.join(file.path, relPath);
|
||||
if (File(fullPath).existsSync()) {
|
||||
return fullPath;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.fine(
|
||||
'Did not find a file with this relPath anywhere therefore returning the relPath');
|
||||
return relPath;
|
||||
}
|
||||
}
|
|
@ -2,24 +2,63 @@ import 'dart:convert';
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:friendica_archive_browser/src/diaspora/serializers/diaspora_contact_serializer.dart';
|
||||
import 'package:friendica_archive_browser/src/models/friendica_contact.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';
|
||||
|
||||
import '../serializers/diaspora_posts_serializer.dart';
|
||||
|
||||
class DiasporaProfileJsonReader {
|
||||
final String jsonFilePath;
|
||||
final ConnectionsManager connectionsManager;
|
||||
final _jsonData = <String, dynamic>{};
|
||||
|
||||
DiasporaProfileJsonReader(this.jsonFilePath);
|
||||
DiasporaProfileJsonReader(this.jsonFilePath, this.connectionsManager);
|
||||
|
||||
Map<String, dynamic> get jsonData {
|
||||
if (_jsonData.isNotEmpty) {
|
||||
return _jsonData;
|
||||
}
|
||||
|
||||
List<Contact> readContacts() {
|
||||
final jsonFile = File(jsonFilePath);
|
||||
if (jsonFile.existsSync()) {
|
||||
final json =
|
||||
jsonDecode(jsonFile.readAsStringSync()) as Map<String, dynamic>;
|
||||
final contactsJson = json['user']?['contacts'] as List<dynamic>;
|
||||
final contacts =
|
||||
contactsJson.map((j) => friendicaContactFromDiasporaJson(j)).toList();
|
||||
return contacts;
|
||||
_jsonData.addAll(json);
|
||||
}
|
||||
|
||||
return [];
|
||||
return _jsonData;
|
||||
}
|
||||
|
||||
String readOwnersName() =>
|
||||
jsonData['user']?['profile']?['entity_data']?['author'] ?? 'Unknown';
|
||||
|
||||
List<Connection> readContacts() {
|
||||
final json = jsonData;
|
||||
final userName = json['user']?['profile']?['entity_data']?['author'] ?? '';
|
||||
final userContact = Connection(name: userName, id: '0');
|
||||
connectionsManager.addConnection(userContact);
|
||||
final contactsJson = json['user']?['contacts'] as List<dynamic>;
|
||||
final contacts = contactsJson.map((j) => contactFromDiasporaJson(j));
|
||||
connectionsManager.addAllConnections(contacts);
|
||||
return contacts.toList();
|
||||
}
|
||||
|
||||
List<TimelineEntry> readPosts() {
|
||||
if (connectionsManager.length == 0) {
|
||||
readContacts();
|
||||
}
|
||||
|
||||
final json = jsonData;
|
||||
final postsJson = json['user']?['posts'] as List<dynamic>;
|
||||
final posts = postsJson
|
||||
.map((j) => timelineItemFromDiasporaPostJson(j, connectionsManager))
|
||||
.where((element) => element.isSuccess)
|
||||
.map((e) => e.value)
|
||||
.toList();
|
||||
|
||||
posts
|
||||
.sort((p1, p2) => p2.creationTimestamp.compareTo(p1.creationTimestamp));
|
||||
return posts;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import 'dart:ui';
|
||||
|
||||
import 'package:friendica_archive_browser/src/friendica/models/friendica_timeline_entry.dart';
|
||||
import 'package:friendica_archive_browser/src/models/timeline_entry.dart';
|
||||
import 'package:latlng/latlng.dart';
|
||||
import 'package:map/map.dart';
|
||||
|
||||
import 'marker_data.dart';
|
||||
|
||||
extension GeoSpatialPostExtensions on FriendicaTimelineEntry {
|
||||
extension GeoSpatialPostExtensions on TimelineEntry {
|
||||
MarkerData toMarkerData(MapTransformer transformer, Color color) {
|
||||
final latLon = LatLng(locationData.latitude, locationData.longitude);
|
||||
final offset = transformer.fromLatLngToXYCoords(latLon);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import 'dart:ui';
|
||||
|
||||
import 'package:friendica_archive_browser/src/friendica/models/friendica_timeline_entry.dart';
|
||||
import 'package:friendica_archive_browser/src/models/timeline_entry.dart';
|
||||
|
||||
class MarkerData {
|
||||
final List<FriendicaTimelineEntry> posts;
|
||||
final List<TimelineEntry> posts;
|
||||
final Offset pos;
|
||||
final Color color;
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/models/friendica_media_attachment.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/screens/media_slideshow_screen.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/services/friendica_path_mapping_service.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/services/friendica_archive_service.dart';
|
||||
import 'package:friendica_archive_browser/src/models/media_attachment.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';
|
||||
|
||||
|
@ -13,7 +12,7 @@ import 'media_wrapper_component.dart';
|
|||
class MediaTimelineComponent extends StatelessWidget {
|
||||
static const double _maxHeightWidth = 400.0;
|
||||
|
||||
final List<FriendicaMediaAttachment> mediaAttachments;
|
||||
final List<MediaAttachment> mediaAttachments;
|
||||
|
||||
const MediaTimelineComponent({Key? key, required this.mediaAttachments})
|
||||
: super(key: key);
|
||||
|
@ -28,8 +27,7 @@ class MediaTimelineComponent extends StatelessWidget {
|
|||
final double singleWidth = MediaQuery.of(context).size.width / 2.0;
|
||||
final double threeAcrossWidth = MediaQuery.of(context).size.width / 3.0;
|
||||
final double preferredMultiWidth = min(threeAcrossWidth, _maxHeightWidth);
|
||||
final pathMapper = Provider.of<FriendicaPathMappingService>(context);
|
||||
final archiveService = Provider.of<FriendicaArchiveService>(context);
|
||||
final archiveService = Provider.of<ArchiveServiceProvider>(context);
|
||||
final settingsController = Provider.of<SettingsController>(context);
|
||||
|
||||
return Container(
|
||||
|
@ -48,7 +46,6 @@ class MediaTimelineComponent extends StatelessWidget {
|
|||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider.value(value: settingsController),
|
||||
Provider.value(value: pathMapper),
|
||||
ChangeNotifierProvider.value(value: archiveService),
|
||||
],
|
||||
child: MediaSlideShowScreen(
|
||||
|
|
|
@ -2,9 +2,9 @@ import 'dart:async';
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/models/friendica_media_attachment.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/services/friendica_path_mapping_service.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/services/friendica_archive_service.dart';
|
||||
import 'package:friendica_archive_browser/src/models/media_attachment.dart';
|
||||
import 'package:friendica_archive_browser/src/services/archive_service_provider.dart';
|
||||
import 'package:friendica_archive_browser/src/services/path_mapper_service_interface.dart';
|
||||
import 'package:friendica_archive_browser/src/settings/settings_controller.dart';
|
||||
import 'package:friendica_archive_browser/src/utils/snackbar_status_builder.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
@ -14,7 +14,7 @@ class MediaWrapperComponent extends StatelessWidget {
|
|||
static final _logger = Logger('$MediaWrapperComponent');
|
||||
|
||||
static const double _noPreferredValue = -1.0;
|
||||
final FriendicaMediaAttachment mediaAttachment;
|
||||
final MediaAttachment mediaAttachment;
|
||||
final double preferredWidth;
|
||||
final double preferredHeight;
|
||||
|
||||
|
@ -28,24 +28,23 @@ class MediaWrapperComponent extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final settingsController = Provider.of<SettingsController>(context);
|
||||
final pathMapper = Provider.of<FriendicaPathMappingService>(context);
|
||||
final archiveService = Provider.of<FriendicaArchiveService>(context);
|
||||
final archiveService = Provider.of<ArchiveServiceProvider>(context);
|
||||
final videoPlayerCommand = settingsController.videoPlayerCommand;
|
||||
final path = _calculatePath(pathMapper, archiveService);
|
||||
final path = _calculatePath(archiveService);
|
||||
final width =
|
||||
preferredWidth > 0 ? preferredWidth : MediaQuery.of(context).size.width;
|
||||
final height = preferredHeight > 0
|
||||
? preferredHeight
|
||||
: MediaQuery.of(context).size.height;
|
||||
|
||||
if (mediaAttachment.explicitType == FriendicaAttachmentMediaType.unknown) {
|
||||
if (mediaAttachment.explicitType == AttachmentMediaType.unknown) {
|
||||
return Text('Unable to resolve type for ${mediaAttachment.uri.path}');
|
||||
}
|
||||
|
||||
if (mediaAttachment.explicitType == FriendicaAttachmentMediaType.video) {
|
||||
if (mediaAttachment.explicitType == AttachmentMediaType.video) {
|
||||
final title = "Video (click to play): " + mediaAttachment.title;
|
||||
final thumbnailImageResult = _uriToImage(
|
||||
mediaAttachment.thumbnailUri, pathMapper,
|
||||
mediaAttachment.thumbnailUri, archiveService.pathMappingService,
|
||||
imageTypeName: 'thumbnail image');
|
||||
if (thumbnailImageResult.image != null) {
|
||||
return _createFinalWidget(
|
||||
|
@ -74,8 +73,9 @@ class MediaWrapperComponent extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
if (mediaAttachment.explicitType == FriendicaAttachmentMediaType.image) {
|
||||
final imageResult = _uriToImage(mediaAttachment.uri, pathMapper);
|
||||
if (mediaAttachment.explicitType == AttachmentMediaType.image) {
|
||||
final imageResult =
|
||||
_uriToImage(mediaAttachment.uri, archiveService.pathMappingService);
|
||||
if (imageResult.image == null) {
|
||||
final errorPath = imageResult.path.isNotEmpty
|
||||
? imageResult.path
|
||||
|
@ -111,7 +111,7 @@ class MediaWrapperComponent extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
_ImageAndPathResult _uriToImage(Uri uri, FriendicaPathMappingService mapper,
|
||||
_ImageAndPathResult _uriToImage(Uri uri, PathMappingService mapper,
|
||||
{String imageTypeName = 'image'}) {
|
||||
if (uri.toString().startsWith('https://interncache')) {
|
||||
return _ImageAndPathResult.none();
|
||||
|
@ -172,8 +172,7 @@ class MediaWrapperComponent extends StatelessWidget {
|
|||
return InkWell(onTap: onTap, child: imageWidget);
|
||||
}
|
||||
|
||||
String _calculatePath(
|
||||
FriendicaPathMappingService pathMapper, FriendicaArchiveService archiveService) {
|
||||
String _calculatePath(ArchiveServiceProvider archiveService) {
|
||||
final url = mediaAttachment.uri.toString();
|
||||
String basePath = '';
|
||||
if (url.startsWith('http')) {
|
||||
|
@ -187,7 +186,7 @@ class MediaWrapperComponent extends StatelessWidget {
|
|||
basePath = mediaAttachment.uri.path;
|
||||
}
|
||||
|
||||
return pathMapper.toFullPath(basePath);
|
||||
return archiveService.pathMappingService.toFullPath(basePath);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/models/friendica_entry_tree_item.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/models/location_data.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/services/friendica_path_mapping_service.dart';
|
||||
import 'package:friendica_archive_browser/src/models/entry_tree_item.dart';
|
||||
import 'package:friendica_archive_browser/src/models/location_data.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/utils/clipboard_helper.dart';
|
||||
import 'package:friendica_archive_browser/src/utils/snackbar_status_builder.dart';
|
||||
|
@ -16,7 +16,7 @@ import 'media_timeline_component.dart';
|
|||
|
||||
class TreeEntryCard extends StatelessWidget {
|
||||
static final _logger = Logger("$TreeEntryCard");
|
||||
final FriendicaEntryTreeItem treeEntry;
|
||||
final EntryTreeItem treeEntry;
|
||||
final bool isTopLevel;
|
||||
|
||||
const TreeEntryCard(
|
||||
|
@ -32,7 +32,7 @@ class TreeEntryCard extends StatelessWidget {
|
|||
const double spacingHeight = 5.0;
|
||||
final formatter =
|
||||
Provider.of<SettingsController>(context).dateTimeFormatter;
|
||||
final mapper = Provider.of<FriendicaPathMappingService>(context);
|
||||
final archiveService = Provider.of<ArchiveServiceProvider>(context);
|
||||
|
||||
final entry = treeEntry.entry;
|
||||
|
||||
|
@ -75,7 +75,8 @@ class TreeEntryCard extends StatelessWidget {
|
|||
child: IconButton(
|
||||
onPressed: () async => await copyToClipboard(
|
||||
context: context,
|
||||
text: entry.toHumanString(mapper, formatter),
|
||||
text: entry.toHumanString(
|
||||
archiveService.pathMappingService, formatter),
|
||||
snackbarMessage: 'Copied Post to clipboard'),
|
||||
icon: const Icon(Icons.copy)),
|
||||
),
|
||||
|
@ -129,9 +130,10 @@ class TreeEntryCard extends StatelessWidget {
|
|||
),
|
||||
if (entry.locationData.hasData())
|
||||
entry.locationData.toWidget(spacingHeight),
|
||||
if (entry.links.isNotEmpty) ...[
|
||||
if (treeEntry.entry.externalLink.isNotEmpty) ...[
|
||||
const SizedBox(height: spacingHeight),
|
||||
LinkElementsComponent(links: entry.links)
|
||||
LinkElementsComponent(
|
||||
links: [Uri.parse(treeEntry.entry.externalLink)])
|
||||
],
|
||||
if (entry.mediaAttachments.isNotEmpty) ...[
|
||||
const SizedBox(height: spacingHeight),
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
import 'package:friendica_archive_browser/src/friendica/models/friendica_timeline_entry.dart';
|
||||
|
||||
class FriendicaEntryTreeItem {
|
||||
final FriendicaTimelineEntry entry;
|
||||
final bool isOrphaned;
|
||||
|
||||
final _children = <String, FriendicaEntryTreeItem>{};
|
||||
|
||||
FriendicaEntryTreeItem(this.entry, this.isOrphaned);
|
||||
|
||||
String get id => entry.id;
|
||||
|
||||
void addChild(FriendicaEntryTreeItem child) {
|
||||
_children[child.id] = child;
|
||||
}
|
||||
|
||||
List<FriendicaEntryTreeItem> get children =>
|
||||
List.unmodifiable(_children.values);
|
||||
}
|
|
@ -1,246 +0,0 @@
|
|||
import 'package:friendica_archive_browser/src/friendica/services/friendica_path_mapping_service.dart';
|
||||
import 'package:friendica_archive_browser/src/models/friendica_contact.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/services/friendica_connections.dart';
|
||||
import 'package:friendica_archive_browser/src/utils/offsetdatetime_utils.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
import 'friendica_media_attachment.dart';
|
||||
import 'location_data.dart';
|
||||
import 'model_utils.dart';
|
||||
|
||||
class FriendicaTimelineEntry {
|
||||
static final _logger = Logger('$FriendicaTimelineEntry');
|
||||
|
||||
final String id;
|
||||
|
||||
final String parentId;
|
||||
|
||||
final String parentAuthor;
|
||||
|
||||
final String parentAuthorId;
|
||||
|
||||
final int creationTimestamp;
|
||||
|
||||
final int backdatedTimestamp;
|
||||
|
||||
final int modificationTimestamp;
|
||||
|
||||
final String body;
|
||||
|
||||
final String title;
|
||||
|
||||
final bool isReshare;
|
||||
|
||||
final String author;
|
||||
|
||||
final String authorId;
|
||||
|
||||
final String externalLink;
|
||||
|
||||
final List<FriendicaMediaAttachment> mediaAttachments;
|
||||
|
||||
final LocationData locationData;
|
||||
|
||||
final List<Uri> links;
|
||||
|
||||
final List<Contact> likes;
|
||||
|
||||
final List<Contact> dislikes;
|
||||
|
||||
FriendicaTimelineEntry(
|
||||
{this.id = '',
|
||||
this.parentId = '',
|
||||
this.creationTimestamp = 0,
|
||||
this.backdatedTimestamp = 0,
|
||||
this.modificationTimestamp = 0,
|
||||
this.isReshare = false,
|
||||
this.body = '',
|
||||
this.title = '',
|
||||
this.author = '',
|
||||
this.authorId = '',
|
||||
this.parentAuthor = '',
|
||||
this.parentAuthorId = '',
|
||||
this.externalLink = '',
|
||||
this.locationData = const LocationData(),
|
||||
this.likes = const <Contact>[],
|
||||
this.dislikes = const <Contact>[],
|
||||
List<FriendicaMediaAttachment>? mediaAttachments,
|
||||
List<Uri>? links})
|
||||
: mediaAttachments = mediaAttachments ?? <FriendicaMediaAttachment>[],
|
||||
links = links ?? <Uri>[];
|
||||
|
||||
FriendicaTimelineEntry.randomBuilt()
|
||||
: creationTimestamp = DateTime.now().millisecondsSinceEpoch,
|
||||
backdatedTimestamp = DateTime.now().millisecondsSinceEpoch,
|
||||
modificationTimestamp = DateTime.now().millisecondsSinceEpoch,
|
||||
id = randomId(),
|
||||
isReshare = false,
|
||||
parentId = 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()}',
|
||||
parentAuthorId = 'Random parent author id ${randomId()}',
|
||||
locationData = LocationData.randomBuilt(),
|
||||
likes = const <Contact>[],
|
||||
dislikes = const <Contact>[],
|
||||
links = [
|
||||
Uri.parse('http://localhost/${randomId()}'),
|
||||
Uri.parse('http://localhost/${randomId()}')
|
||||
],
|
||||
mediaAttachments = [
|
||||
FriendicaMediaAttachment.randomBuilt(),
|
||||
FriendicaMediaAttachment.randomBuilt()
|
||||
];
|
||||
|
||||
FriendicaTimelineEntry copy(
|
||||
{int? creationTimestamp,
|
||||
int? backdatedTimestamp,
|
||||
int? modificationTimestamp,
|
||||
bool? isReshare,
|
||||
String? id,
|
||||
String? parentId,
|
||||
String? externalLink,
|
||||
String? body,
|
||||
String? title,
|
||||
String? author,
|
||||
String? authorId,
|
||||
String? parentAuthor,
|
||||
String? parentAuthorId,
|
||||
LocationData? locationData,
|
||||
List<FriendicaMediaAttachment>? mediaAttachments,
|
||||
List<Contact>? likes,
|
||||
List<Contact>? dislikes,
|
||||
List<Uri>? links}) {
|
||||
return FriendicaTimelineEntry(
|
||||
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
|
||||
String toString() {
|
||||
return 'FriendicaTimelineEntry{id: $id, isReshare: $isReshare, parentId: $parentId, creationTimestamp: $creationTimestamp, modificationTimestamp: $modificationTimestamp, backdatedTimeStamp: $backdatedTimestamp, post: $body, title: $title, author: $author, parentAuthor: $parentAuthor mediaAttachments: $mediaAttachments, links: $links}';
|
||||
}
|
||||
|
||||
String toHumanString(FriendicaPathMappingService mapper, DateFormat formatter) {
|
||||
final creationDateString = formatter.format(
|
||||
DateTime.fromMillisecondsSinceEpoch(creationTimestamp * 1000)
|
||||
.toLocal());
|
||||
return [
|
||||
'Title: $title',
|
||||
'Creation At: $creationDateString',
|
||||
'Text:',
|
||||
'Author: $author',
|
||||
'Reshare: $isReshare',
|
||||
if (externalLink.isNotEmpty) 'External Link: $externalLink',
|
||||
body,
|
||||
'',
|
||||
if (parentId.isNotEmpty)
|
||||
"Comment on post/comment by ${parentAuthor.isNotEmpty ? parentAuthor : 'unknown author'}",
|
||||
if (links.isNotEmpty) 'Links:',
|
||||
...links.map((e) => e.toString()),
|
||||
'',
|
||||
if (mediaAttachments.isNotEmpty) 'Photos and Videos:',
|
||||
...mediaAttachments.map((e) => e.toHumanString(mapper)),
|
||||
if (locationData.hasPosition) locationData.toHumanString(),
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
bool hasImages() => mediaAttachments
|
||||
.where((element) =>
|
||||
element.explicitType == FriendicaAttachmentMediaType.image)
|
||||
.isNotEmpty;
|
||||
|
||||
bool hasVideos() => mediaAttachments
|
||||
.where((element) =>
|
||||
element.explicitType == FriendicaAttachmentMediaType.video)
|
||||
.isNotEmpty;
|
||||
|
||||
static FriendicaTimelineEntry fromJson(
|
||||
Map<String, dynamic> json, FriendicaConnections connections) {
|
||||
final int timestamp = json.containsKey('created_at')
|
||||
? OffsetDateTimeUtils.epochSecTimeFromFriendicaString(
|
||||
json['created_at'])
|
||||
.fold(
|
||||
onSuccess: (value) => value,
|
||||
onError: (error) {
|
||||
_logger.severe("Couldn't read date time string: $error");
|
||||
return 0;
|
||||
})
|
||||
: 0;
|
||||
final id = json['id_str'] ?? '';
|
||||
final isReshare = json.containsKey('retweeted_status');
|
||||
final parentId = json['in_reply_to_status_id_str'] ?? '';
|
||||
final parentAuthor = json['in_reply_to_screen_name'] ?? '';
|
||||
final parentAuthorId = json['in_reply_to_user_id_str'] ?? '';
|
||||
final body = 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;
|
||||
final links = <Uri>[];
|
||||
final mediaAttachments = (json['attachments'] as List<dynamic>? ?? [])
|
||||
.map((j) => FriendicaMediaAttachment.fromJson(j))
|
||||
.toList();
|
||||
final likes =
|
||||
(json['friendica_activities']?['like'] as List<dynamic>? ?? [])
|
||||
.map((json) => Contact.fromJson(json))
|
||||
.toList();
|
||||
final dislikes =
|
||||
(json['friendica_activities']?['dislike'] as List<dynamic>? ?? [])
|
||||
.map((json) => Contact.fromJson(json))
|
||||
.toList();
|
||||
final announce =
|
||||
(json['friendica_activities']?['announce'] as List<dynamic>? ?? [])
|
||||
.map((json) => Contact.fromJson(json))
|
||||
.toList();
|
||||
|
||||
for (final contact in [...likes, ...dislikes, ...announce]) {
|
||||
connections.addConnection(contact);
|
||||
}
|
||||
|
||||
return FriendicaTimelineEntry(
|
||||
creationTimestamp: timestamp,
|
||||
modificationTimestamp: modificationTimestamp,
|
||||
backdatedTimestamp: backdatedTimestamp,
|
||||
locationData: actualLocationData,
|
||||
externalLink: externalLink,
|
||||
body: body,
|
||||
isReshare: isReshare,
|
||||
id: id,
|
||||
parentId: parentId,
|
||||
parentAuthorId: parentAuthorId,
|
||||
author: author,
|
||||
authorId: authorId,
|
||||
parentAuthor: parentAuthor,
|
||||
title: title,
|
||||
links: links,
|
||||
likes: likes,
|
||||
dislikes: dislikes,
|
||||
mediaAttachments: mediaAttachments,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/components/filter_control_component.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/components/tree_entry_card.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/models/friendica_entry_tree_item.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/models/model_utils.dart';
|
||||
import 'package:friendica_archive_browser/src/models/entry_tree_item.dart';
|
||||
import 'package:friendica_archive_browser/src/models/model_utils.dart';
|
||||
import 'package:friendica_archive_browser/src/screens/error_screen.dart';
|
||||
import 'package:friendica_archive_browser/src/settings/settings_controller.dart';
|
||||
import 'package:friendica_archive_browser/src/utils/exec_error.dart';
|
||||
|
@ -15,8 +15,7 @@ import '../../screens/standin_status_screen.dart';
|
|||
|
||||
class EntriesScreen extends StatelessWidget {
|
||||
static final _logger = Logger('$EntriesScreen');
|
||||
final FutureResult<List<FriendicaEntryTreeItem>, ExecError> Function()
|
||||
populator;
|
||||
final FutureResult<List<EntryTreeItem>, ExecError> Function() populator;
|
||||
|
||||
const EntriesScreen({Key? key, required this.populator}) : super(key: key);
|
||||
|
||||
|
@ -25,7 +24,7 @@ class EntriesScreen extends StatelessWidget {
|
|||
_logger.info('Build FriendicaEntriesScreen');
|
||||
Provider.of<SettingsController>(context);
|
||||
|
||||
return FutureBuilder<Result<List<FriendicaEntryTreeItem>, ExecError>>(
|
||||
return FutureBuilder<Result<List<EntryTreeItem>, ExecError>>(
|
||||
future: populator(),
|
||||
builder: (context, snapshot) {
|
||||
_logger.info('FriendicaEntriesScreen Future builder called');
|
||||
|
@ -58,7 +57,7 @@ class EntriesScreen extends StatelessWidget {
|
|||
class _FriendicaEntriesScreenWidget extends StatelessWidget {
|
||||
static final _logger = Logger('$_FriendicaEntriesScreenWidget');
|
||||
|
||||
final List<FriendicaEntryTreeItem> posts;
|
||||
final List<EntryTreeItem> posts;
|
||||
|
||||
const _FriendicaEntriesScreenWidget({Key? key, required this.posts})
|
||||
: super(key: key);
|
||||
|
@ -66,7 +65,7 @@ class _FriendicaEntriesScreenWidget extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_logger.fine('Redrawing');
|
||||
return FilterControl<FriendicaEntryTreeItem, dynamic>(
|
||||
return FilterControl<EntryTreeItem, dynamic>(
|
||||
allItems: posts,
|
||||
commentsOnlyFilterFunction: (post) => post.children.isNotEmpty,
|
||||
imagesOnlyFilterFunction: (post) => post.entry.hasImages(),
|
||||
|
|
|
@ -6,13 +6,13 @@ import 'package:friendica_archive_browser/src/friendica/components/geo/geo_exten
|
|||
import 'package:friendica_archive_browser/src/friendica/components/geo/map_bounds.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/components/geo/marker_data.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/components/tree_entry_card.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/services/friendica_path_mapping_service.dart';
|
||||
import 'package:friendica_archive_browser/src/models/entry_tree_item.dart';
|
||||
import 'package:friendica_archive_browser/src/models/timeline_entry.dart';
|
||||
import 'package:friendica_archive_browser/src/screens/error_screen.dart';
|
||||
import 'package:friendica_archive_browser/src/screens/loading_status_screen.dart';
|
||||
import 'package:friendica_archive_browser/src/screens/standin_status_screen.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/services/friendica_archive_service.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/utils/exec_error.dart';
|
||||
import 'package:friendica_archive_browser/src/utils/temp_file_builder.dart';
|
||||
|
@ -33,9 +33,9 @@ class GeospatialViewScreen extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_logger.info('Build GeospatialViewScreen');
|
||||
final service = Provider.of<FriendicaArchiveService>(context);
|
||||
final service = Provider.of<ArchiveServiceProvider>(context);
|
||||
|
||||
return FutureBuilder<Result<List<FriendicaEntryTreeItem>, ExecError>>(
|
||||
return FutureBuilder<Result<List<EntryTreeItem>, ExecError>>(
|
||||
future: service.getPosts(),
|
||||
builder: (context, snapshot) {
|
||||
_logger.info('GeospatialViewScreen Future builder called');
|
||||
|
@ -66,7 +66,7 @@ class GeospatialViewScreen extends StatelessWidget {
|
|||
}
|
||||
|
||||
class GeospatialView extends StatefulWidget {
|
||||
final List<FriendicaTimelineEntry> posts;
|
||||
final List<TimelineEntry> posts;
|
||||
|
||||
const GeospatialView({Key? key, required this.posts}) : super(key: key);
|
||||
|
||||
|
@ -88,8 +88,8 @@ class _GeospatialViewState extends State<GeospatialView> {
|
|||
);
|
||||
|
||||
Offset? dragStart;
|
||||
final postsInList = <FriendicaTimelineEntry>[];
|
||||
final postsInView = <FriendicaTimelineEntry>[];
|
||||
final postsInList = <TimelineEntry>[];
|
||||
final postsInView = <TimelineEntry>[];
|
||||
double scaleStart = 1.0;
|
||||
|
||||
@override
|
||||
|
@ -203,8 +203,8 @@ class _GeospatialViewState extends State<GeospatialView> {
|
|||
highlightedColor: Colors.indigo[900]!)));
|
||||
}
|
||||
|
||||
Widget _buildPostList(
|
||||
BuildContext context, DateFormat formatter, FriendicaPathMappingService mapper) {
|
||||
Widget _buildPostList(BuildContext context, DateFormat formatter,
|
||||
FriendicaPathMappingService mapper) {
|
||||
_logger.finest(() => 'Building PostList with ${postsInList.length} items');
|
||||
if (postsInList.isEmpty) {
|
||||
return const StandInStatusScreen(
|
||||
|
@ -218,14 +218,14 @@ class _GeospatialViewState extends State<GeospatialView> {
|
|||
behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
|
||||
child: ListView.separated(
|
||||
itemBuilder: (context, index) => TreeEntryCard(
|
||||
treeEntry: FriendicaEntryTreeItem(postsInList[index], false)),
|
||||
treeEntry: EntryTreeItem(postsInList[index], false)),
|
||||
separatorBuilder: (context, index) => const Divider(height: 1),
|
||||
itemCount: postsInList.length),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMap(
|
||||
BuildContext context, DateFormat formatter, FriendicaPathMappingService mapper) {
|
||||
Widget _buildMap(BuildContext context, DateFormat formatter,
|
||||
FriendicaPathMappingService mapper) {
|
||||
final settings = Provider.of<SettingsController>(context);
|
||||
|
||||
final shouldDebugCache =
|
||||
|
@ -332,8 +332,8 @@ class _GeospatialViewState extends State<GeospatialView> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildMarkerWidget(
|
||||
MarkerData data, DateFormat formatter, FriendicaPathMappingService mapper) {
|
||||
Widget _buildMarkerWidget(MarkerData data, DateFormat formatter,
|
||||
FriendicaPathMappingService mapper) {
|
||||
return Positioned(
|
||||
left: data.pos.dx - 16,
|
||||
top: data.pos.dy - 16,
|
||||
|
|
|
@ -4,8 +4,8 @@ import 'package:file_picker/file_picker.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/components/media_wrapper_component.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/models/friendica_media_attachment.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/services/friendica_path_mapping_service.dart';
|
||||
import 'package:friendica_archive_browser/src/models/media_attachment.dart';
|
||||
import 'package:friendica_archive_browser/src/settings/settings_controller.dart';
|
||||
import 'package:friendica_archive_browser/src/themes.dart';
|
||||
import 'package:friendica_archive_browser/src/utils/snackbar_status_builder.dart';
|
||||
|
@ -14,7 +14,7 @@ import 'package:provider/provider.dart';
|
|||
class MediaSlideShowScreen extends StatefulWidget {
|
||||
static const _spacing = 5.0;
|
||||
|
||||
final List<FriendicaMediaAttachment> mediaAttachments;
|
||||
final List<MediaAttachment> mediaAttachments;
|
||||
final int initialIndex;
|
||||
|
||||
const MediaSlideShowScreen(
|
||||
|
@ -27,7 +27,7 @@ class MediaSlideShowScreen extends StatefulWidget {
|
|||
|
||||
class _MediaSlideShowScreenState extends State<MediaSlideShowScreen> {
|
||||
static const fastestChangeMS = 250;
|
||||
FriendicaMediaAttachment media = FriendicaMediaAttachment.blank();
|
||||
MediaAttachment media = MediaAttachment.blank();
|
||||
int index = 0;
|
||||
int lastKeyInducedIndexChange = 0;
|
||||
|
||||
|
@ -155,7 +155,8 @@ class _MediaSlideShowScreenState extends State<MediaSlideShowScreen> {
|
|||
}
|
||||
|
||||
Future<void> _saveFile(BuildContext context) async {
|
||||
final pathMapper = Provider.of<FriendicaPathMappingService>(context, listen: false);
|
||||
final pathMapper =
|
||||
Provider.of<FriendicaPathMappingService>(context, listen: false);
|
||||
|
||||
final filename = media.uri.pathSegments.last;
|
||||
final initialPath = pathMapper.toFullPath(media.uri.toFilePath());
|
||||
|
|
|
@ -4,10 +4,10 @@ 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/friendica/components/filter_control_component.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/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/screens/standin_status_screen.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/services/friendica_archive_service.dart';
|
||||
import 'package:friendica_archive_browser/src/services/archive_service_provider.dart';
|
||||
import 'package:friendica_archive_browser/src/utils/snackbar_status_builder.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
@ -21,7 +21,7 @@ class StatsScreen extends StatefulWidget {
|
|||
|
||||
class _StatsScreenState extends State<StatsScreen> {
|
||||
static final _logger = Logger("$_StatsScreenState");
|
||||
FriendicaArchiveService? archiveDataService;
|
||||
ArchiveServiceProvider? archiveDataService;
|
||||
final allItems = <TimeElement>[];
|
||||
StatType statType = StatType.selectType;
|
||||
bool hasText = true;
|
||||
|
@ -78,7 +78,7 @@ class _StatsScreenState extends State<StatsScreen> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
archiveDataService = Provider.of<FriendicaArchiveService>(context);
|
||||
archiveDataService = Provider.of<ArchiveServiceProvider>(context);
|
||||
|
||||
return FilterControl<TimeElement, dynamic>(
|
||||
allItems: allItems,
|
||||
|
@ -147,7 +147,7 @@ class _StatsScreenState extends State<StatsScreen> {
|
|||
child: Column(children: [
|
||||
..._buildGraphScreens(context, items),
|
||||
const Divider(),
|
||||
TopInteractorsWidget(items, archiveDataService!.connections),
|
||||
TopInteractorsWidget(items, archiveDataService!.connectionsManager),
|
||||
const Divider(),
|
||||
WordFrequencyWidget(items),
|
||||
]),
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import '../../models/connection.dart';
|
||||
|
||||
Connection contactFromFriendicaJson(Map<String, dynamic> json) {
|
||||
final status = (json['following'] ?? '') == 'true'
|
||||
? ConnectionStatus.youFollowThem
|
||||
: ConnectionStatus.none;
|
||||
final name = json['name'] ?? '';
|
||||
final id = json['id_str'] ?? '';
|
||||
final profileUrl = Uri.parse(json['url'] ?? '');
|
||||
final network = json['network'] ?? 'unkn';
|
||||
|
||||
return Connection(
|
||||
status: status,
|
||||
name: name,
|
||||
id: id,
|
||||
profileUrl: profileUrl,
|
||||
network: network);
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import '../../models/media_attachment.dart';
|
||||
|
||||
MediaAttachment mediaAttachmentfromFriendicaJson(Map<String, dynamic> json) {
|
||||
final uri = Uri.parse(json['url']);
|
||||
const creationTimestamp = 0;
|
||||
final metadata = (json['metadata'] as Map<String, dynamic>? ?? {})
|
||||
.map((key, value) => MapEntry(key, value.toString()));
|
||||
final explicitType = (json['mimetype'] ?? '').startsWith('image')
|
||||
? AttachmentMediaType.image
|
||||
: (json['mimetype'] ?? '').startsWith('video')
|
||||
? AttachmentMediaType.video
|
||||
: AttachmentMediaType.unknown;
|
||||
final thumbnailUri = Uri();
|
||||
const title = '';
|
||||
const description = '';
|
||||
|
||||
return MediaAttachment(
|
||||
uri: uri,
|
||||
creationTimestamp: creationTimestamp,
|
||||
metadata: metadata,
|
||||
thumbnailUri: thumbnailUri,
|
||||
title: title,
|
||||
explicitType: explicitType,
|
||||
description: description);
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
import 'package:friendica_archive_browser/src/friendica/serializers/friendica_contact_serializer.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/serializers/friendica_media_attachment_serializer.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
import '../../models/location_data.dart';
|
||||
import '../../models/timeline_entry.dart';
|
||||
import '../../services/connections_manager.dart';
|
||||
import '../../utils/offsetdatetime_utils.dart';
|
||||
|
||||
final _logger = Logger('FriendicaTimelineEntrySerializer');
|
||||
|
||||
TimelineEntry timelineEntryFromFriendicaJson(
|
||||
Map<String, dynamic> json, ConnectionsManager connections) {
|
||||
final int timestamp = json.containsKey('created_at')
|
||||
? OffsetDateTimeUtils.epochSecTimeFromFriendicaString(json['created_at'])
|
||||
.fold(
|
||||
onSuccess: (value) => value,
|
||||
onError: (error) {
|
||||
_logger.severe("Couldn't read date time string: $error");
|
||||
return 0;
|
||||
})
|
||||
: 0;
|
||||
final id = json['id_str'] ?? '';
|
||||
final isReshare = json.containsKey('retweeted_status');
|
||||
final parentId = json['in_reply_to_status_id_str'] ?? '';
|
||||
final parentAuthor = json['in_reply_to_screen_name'] ?? '';
|
||||
final parentAuthorId = json['in_reply_to_user_id_str'] ?? '';
|
||||
final body = 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;
|
||||
final mediaAttachments = (json['attachments'] as List<dynamic>? ?? [])
|
||||
.map((j) => mediaAttachmentfromFriendicaJson(j))
|
||||
.toList();
|
||||
final likes = (json['friendica_activities']?['like'] as List<dynamic>? ?? [])
|
||||
.map((json) => contactFromFriendicaJson(json))
|
||||
.toList();
|
||||
final dislikes =
|
||||
(json['friendica_activities']?['dislike'] as List<dynamic>? ?? [])
|
||||
.map((json) => contactFromFriendicaJson(json))
|
||||
.toList();
|
||||
final announce =
|
||||
(json['friendica_activities']?['announce'] as List<dynamic>? ?? [])
|
||||
.map((json) => contactFromFriendicaJson(json))
|
||||
.toList();
|
||||
|
||||
connections.addAllConnections([...likes, ...dislikes, ...announce]);
|
||||
|
||||
return TimelineEntry(
|
||||
creationTimestamp: timestamp,
|
||||
modificationTimestamp: modificationTimestamp,
|
||||
backdatedTimestamp: backdatedTimestamp,
|
||||
locationData: actualLocationData,
|
||||
externalLink: externalLink,
|
||||
body: body,
|
||||
isReshare: isReshare,
|
||||
id: id,
|
||||
parentId: parentId,
|
||||
parentAuthorId: parentAuthorId,
|
||||
author: author,
|
||||
authorId: authorId,
|
||||
parentAuthor: parentAuthor,
|
||||
title: title,
|
||||
likes: likes,
|
||||
dislikes: dislikes,
|
||||
mediaAttachments: mediaAttachments,
|
||||
);
|
||||
}
|
|
@ -1,23 +1,27 @@
|
|||
import 'dart:convert';
|
||||
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/serializers/friendica_timeline_entry_serializer.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/services/friendica_path_mapping_service.dart';
|
||||
import 'package:friendica_archive_browser/src/models/entry_tree_item.dart';
|
||||
import 'package:friendica_archive_browser/src/models/local_image_archive_entry.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/services/friendica_connections.dart';
|
||||
import 'package:friendica_archive_browser/src/services/archive_service_interface.dart';
|
||||
import 'package:friendica_archive_browser/src/services/connections_manager.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';
|
||||
|
||||
class FriendicaArchiveService extends ChangeNotifier {
|
||||
class FriendicaArchiveService implements ArchiveService {
|
||||
@override
|
||||
final FriendicaPathMappingService pathMappingService;
|
||||
|
||||
final Map<String, ImageEntry> _imagesByRequestUrl = {};
|
||||
final List<FriendicaEntryTreeItem> _postEntries = [];
|
||||
final List<FriendicaEntryTreeItem> _orphanedCommentEntries = [];
|
||||
final List<FriendicaEntryTreeItem> _allComments = [];
|
||||
final FriendicaConnections connections = FriendicaConnections();
|
||||
final List<EntryTreeItem> _postEntries = [];
|
||||
final List<EntryTreeItem> _orphanedCommentEntries = [];
|
||||
final List<EntryTreeItem> _allComments = [];
|
||||
@override
|
||||
final ConnectionsManager connectionsManager = ConnectionsManager();
|
||||
|
||||
String _ownersName = '';
|
||||
|
||||
FriendicaArchiveService({required this.pathMappingService});
|
||||
|
@ -34,14 +38,14 @@ class FriendicaArchiveService extends ChangeNotifier {
|
|||
}
|
||||
|
||||
void clearCaches() {
|
||||
connections.clearCaches();
|
||||
connectionsManager.clearCaches();
|
||||
_imagesByRequestUrl.clear();
|
||||
_orphanedCommentEntries.clear();
|
||||
_allComments.clear();
|
||||
_postEntries.clear();
|
||||
}
|
||||
|
||||
FutureResult<List<FriendicaEntryTreeItem>, ExecError> getPosts() async {
|
||||
FutureResult<List<EntryTreeItem>, ExecError> getPosts() async {
|
||||
if (_postEntries.isEmpty && _allComments.isEmpty) {
|
||||
_loadEntries();
|
||||
}
|
||||
|
@ -49,7 +53,7 @@ class FriendicaArchiveService extends ChangeNotifier {
|
|||
return Result.ok(_postEntries);
|
||||
}
|
||||
|
||||
FutureResult<List<FriendicaEntryTreeItem>, ExecError> getAllComments() async {
|
||||
FutureResult<List<EntryTreeItem>, ExecError> getAllComments() async {
|
||||
if (_postEntries.isEmpty && _allComments.isEmpty) {
|
||||
_loadEntries();
|
||||
}
|
||||
|
@ -57,8 +61,7 @@ class FriendicaArchiveService extends ChangeNotifier {
|
|||
return Result.ok(_allComments);
|
||||
}
|
||||
|
||||
FutureResult<List<FriendicaEntryTreeItem>, ExecError>
|
||||
getOrphanedComments() async {
|
||||
FutureResult<List<EntryTreeItem>, ExecError> getOrphanedComments() async {
|
||||
if (_postEntries.isEmpty && _allComments.isEmpty) {
|
||||
_loadEntries();
|
||||
}
|
||||
|
@ -84,27 +87,27 @@ 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, connections));
|
||||
final entries = json
|
||||
.map((j) => timelineEntryFromFriendicaJson(j, connectionsManager));
|
||||
final topLevelEntries =
|
||||
entries.where((element) => element.parentId.isEmpty);
|
||||
final commentEntries =
|
||||
entries.where((element) => element.parentId.isNotEmpty).toList();
|
||||
final entryTrees = <String, FriendicaEntryTreeItem>{};
|
||||
final entryTrees = <String, EntryTreeItem>{};
|
||||
|
||||
final postTreeEntries = <FriendicaEntryTreeItem>[];
|
||||
final postTreeEntries = <EntryTreeItem>[];
|
||||
for (final entry in topLevelEntries) {
|
||||
final treeEntry = FriendicaEntryTreeItem(entry, false);
|
||||
final treeEntry = EntryTreeItem(entry, false);
|
||||
entryTrees[entry.id] = treeEntry;
|
||||
postTreeEntries.add(treeEntry);
|
||||
}
|
||||
|
||||
final commentTreeEntries = <FriendicaEntryTreeItem>[];
|
||||
final commentTreeEntries = <EntryTreeItem>[];
|
||||
commentEntries.sort(
|
||||
(c1, c2) => c1.creationTimestamp.compareTo(c2.creationTimestamp));
|
||||
for (final entry in commentEntries) {
|
||||
final parent = entryTrees[entry.parentId];
|
||||
final treeEntry = FriendicaEntryTreeItem(entry, parent == null);
|
||||
final treeEntry = EntryTreeItem(entry, parent == null);
|
||||
parent?.addChild(treeEntry);
|
||||
entryTrees[entry.id] = treeEntry;
|
||||
commentTreeEntries.add(treeEntry);
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
import 'package:friendica_archive_browser/src/models/friendica_contact.dart';
|
||||
import 'package:result_monad/result_monad.dart';
|
||||
|
||||
class FriendicaConnections {
|
||||
final _connectionsById = <String, Contact>{};
|
||||
final _connectionsByName = <String, Contact>{};
|
||||
|
||||
void clearCaches() {
|
||||
_connectionsById.clear();
|
||||
_connectionsByName.clear();
|
||||
}
|
||||
|
||||
bool addConnection(Contact contact) {
|
||||
if (_connectionsById.containsKey(contact.id)) {
|
||||
return false;
|
||||
}
|
||||
_connectionsById[contact.id] = contact;
|
||||
_connectionsByName[contact.name] = contact;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Result<Contact, String> getById(String id) {
|
||||
final result = _connectionsById[id];
|
||||
|
||||
return result != null ? Result.ok(result) : Result.error('$id not found');
|
||||
}
|
||||
|
||||
Result<Contact, String> getByName(String name) {
|
||||
final result = _connectionsByName[name];
|
||||
|
||||
return result != null ? Result.ok(result) : Result.error('$name not found');
|
||||
}
|
||||
}
|
|
@ -4,7 +4,9 @@ import 'package:friendica_archive_browser/src/settings/settings_controller.dart'
|
|||
import 'package:logging/logging.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
class FriendicaPathMappingService {
|
||||
import '../../services/path_mapper_service_interface.dart';
|
||||
|
||||
class FriendicaPathMappingService implements PathMappingService {
|
||||
static final _logger = Logger('$FriendicaPathMappingService');
|
||||
final SettingsController settings;
|
||||
final _archiveDirectories = <FileSystemEntity>[];
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/services/friendica_archive_service.dart';
|
||||
import 'package:friendica_archive_browser/src/services/archive_service_provider.dart';
|
||||
|
||||
import 'friendica/screens/entries_screen.dart';
|
||||
import 'friendica/screens/stats_screen.dart';
|
||||
|
@ -10,7 +10,7 @@ import 'settings/settings_view.dart';
|
|||
|
||||
class Home extends StatefulWidget {
|
||||
final SettingsController settingsController;
|
||||
final FriendicaArchiveService archiveService;
|
||||
final ArchiveServiceProvider archiveService;
|
||||
|
||||
const Home(
|
||||
{Key? key,
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
enum ArchiveType {
|
||||
unknown,
|
||||
diaspora,
|
||||
friendica,
|
||||
}
|
46
friendica_archive_browser/lib/src/models/connection.dart
Normal file
46
friendica_archive_browser/lib/src/models/connection.dart
Normal file
|
@ -0,0 +1,46 @@
|
|||
class Connection {
|
||||
final ConnectionStatus status;
|
||||
|
||||
final String name;
|
||||
|
||||
final String id;
|
||||
|
||||
final Uri profileUrl;
|
||||
|
||||
final String network;
|
||||
|
||||
Connection(
|
||||
{this.status = ConnectionStatus.none,
|
||||
this.name = '',
|
||||
this.id = '',
|
||||
profileUrl,
|
||||
this.network = ''})
|
||||
: profileUrl = profileUrl ?? Uri();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Connection{status: $status, name: $name, id: $id, profileUrl: $profileUrl, network: $network}';
|
||||
}
|
||||
}
|
||||
|
||||
enum ConnectionStatus {
|
||||
youFollowThem,
|
||||
theyFollowYou,
|
||||
mutual,
|
||||
none,
|
||||
}
|
||||
|
||||
extension FriendStatusWriter on ConnectionStatus {
|
||||
String name() {
|
||||
switch (this) {
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
import 'package:friendica_archive_browser/src/models/timeline_entry.dart';
|
||||
|
||||
class EntryTreeItem {
|
||||
final TimelineEntry entry;
|
||||
final bool isOrphaned;
|
||||
|
||||
final _children = <String, EntryTreeItem>{};
|
||||
|
||||
EntryTreeItem(this.entry, this.isOrphaned);
|
||||
|
||||
String get id => entry.id;
|
||||
|
||||
void addChild(EntryTreeItem child) {
|
||||
_children[child.id] = child;
|
||||
}
|
||||
|
||||
List<EntryTreeItem> get children => List.unmodifiable(_children.values);
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
class Contact {
|
||||
final ConnectionStatus status;
|
||||
|
||||
final String name;
|
||||
|
||||
final String id;
|
||||
|
||||
final Uri profileUrl;
|
||||
|
||||
final String network;
|
||||
|
||||
Contact(
|
||||
{required this.status,
|
||||
required this.name,
|
||||
required this.id,
|
||||
required this.profileUrl,
|
||||
required this.network});
|
||||
|
||||
static Contact fromJson(Map<String, dynamic> json) {
|
||||
final status = (json['following'] ?? '') == 'true'
|
||||
? ConnectionStatus.youFollowThem
|
||||
: ConnectionStatus.none;
|
||||
final name = json['name'] ?? '';
|
||||
final id = json['id_str'] ?? '';
|
||||
final profileUrl = Uri.parse(json['url'] ?? '');
|
||||
final network = json['network'] ?? 'unkn';
|
||||
|
||||
return Contact(
|
||||
status: status,
|
||||
name: name,
|
||||
id: id,
|
||||
profileUrl: profileUrl,
|
||||
network: network);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'FriendicaContact{status: $status, name: $name, id: $id, profileUrl: $profileUrl, network: $network}';
|
||||
}
|
||||
}
|
||||
|
||||
enum ConnectionStatus {
|
||||
youFollowThem,
|
||||
theyFollowYou,
|
||||
mutual,
|
||||
none,
|
||||
}
|
||||
|
||||
extension FriendStatusWriter on ConnectionStatus {
|
||||
String name() {
|
||||
switch (this) {
|
||||
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,12 +1,12 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:friendica_archive_browser/src/friendica/services/friendica_path_mapping_service.dart';
|
||||
import 'package:friendica_archive_browser/src/services/path_mapper_service_interface.dart';
|
||||
|
||||
import 'model_utils.dart';
|
||||
|
||||
enum FriendicaAttachmentMediaType { unknown, image, video }
|
||||
enum AttachmentMediaType { unknown, image, video }
|
||||
|
||||
class FriendicaMediaAttachment {
|
||||
class MediaAttachment {
|
||||
static final _graphicsExtensions = ['jpg', 'png', 'gif', 'tif'];
|
||||
static final _movieExtensions = ['avi', 'mp4', 'mpg', 'wmv'];
|
||||
|
||||
|
@ -16,7 +16,7 @@ class FriendicaMediaAttachment {
|
|||
|
||||
final Map<String, String> metadata;
|
||||
|
||||
final FriendicaAttachmentMediaType explicitType;
|
||||
final AttachmentMediaType explicitType;
|
||||
|
||||
final Uri thumbnailUri;
|
||||
|
||||
|
@ -24,7 +24,7 @@ class FriendicaMediaAttachment {
|
|||
|
||||
final String description;
|
||||
|
||||
FriendicaMediaAttachment(
|
||||
MediaAttachment(
|
||||
{required this.uri,
|
||||
required this.creationTimestamp,
|
||||
required this.metadata,
|
||||
|
@ -33,16 +33,16 @@ class FriendicaMediaAttachment {
|
|||
required this.explicitType,
|
||||
required this.description});
|
||||
|
||||
FriendicaMediaAttachment.randomBuilt()
|
||||
MediaAttachment.randomBuilt()
|
||||
: uri = Uri.parse('http://localhost/${randomId()}'),
|
||||
creationTimestamp = DateTime.now().millisecondsSinceEpoch,
|
||||
title = 'Random title ${randomId()}',
|
||||
thumbnailUri = Uri.parse('${randomId()}.jpg'),
|
||||
description = 'Random description ${randomId()}',
|
||||
explicitType = FriendicaAttachmentMediaType.image,
|
||||
explicitType = AttachmentMediaType.image,
|
||||
metadata = {'value1': randomId(), 'value2': randomId()};
|
||||
|
||||
FriendicaMediaAttachment.fromUriOnly(this.uri)
|
||||
MediaAttachment.fromUriOnly(this.uri)
|
||||
: creationTimestamp = 0,
|
||||
thumbnailUri = Uri.file(''),
|
||||
title = '',
|
||||
|
@ -50,18 +50,18 @@ class FriendicaMediaAttachment {
|
|||
description = '',
|
||||
metadata = {};
|
||||
|
||||
FriendicaMediaAttachment.fromUriAndTime(this.uri, this.creationTimestamp)
|
||||
MediaAttachment.fromUriAndTime(this.uri, this.creationTimestamp)
|
||||
: thumbnailUri = Uri.file(''),
|
||||
title = '',
|
||||
explicitType = mediaTypeFromString(uri.path),
|
||||
description = '',
|
||||
metadata = {};
|
||||
|
||||
FriendicaMediaAttachment.blank()
|
||||
MediaAttachment.blank()
|
||||
: uri = Uri(),
|
||||
creationTimestamp = 0,
|
||||
thumbnailUri = Uri.file(''),
|
||||
explicitType = FriendicaAttachmentMediaType.unknown,
|
||||
explicitType = AttachmentMediaType.unknown,
|
||||
title = '',
|
||||
description = '',
|
||||
metadata = {};
|
||||
|
@ -71,7 +71,7 @@ class FriendicaMediaAttachment {
|
|||
return 'FriendicaMediaAttachment{uri: $uri, creationTimestamp: $creationTimestamp, type: $explicitType, metadata: $metadata, title: $title, description: $description}';
|
||||
}
|
||||
|
||||
String toHumanString(FriendicaPathMappingService mapper) {
|
||||
String toHumanString(PathMappingService mapper) {
|
||||
if (uri.scheme.startsWith('http')) {
|
||||
return uri.toString();
|
||||
}
|
||||
|
@ -79,20 +79,6 @@ class FriendicaMediaAttachment {
|
|||
return mapper.toFullPath(uri.toString());
|
||||
}
|
||||
|
||||
FriendicaMediaAttachment.fromJson(Map<String, dynamic> json)
|
||||
: uri = Uri.parse(json['url']),
|
||||
creationTimestamp = 0,
|
||||
metadata = (json['metadata'] as Map<String, dynamic>? ?? {})
|
||||
.map((key, value) => MapEntry(key, value.toString())),
|
||||
explicitType = (json['mimetype'] ?? '').startsWith('image')
|
||||
? FriendicaAttachmentMediaType.image
|
||||
: (json['mimetype'] ?? '').startsWith('video')
|
||||
? FriendicaAttachmentMediaType.video
|
||||
: FriendicaAttachmentMediaType.unknown,
|
||||
thumbnailUri = Uri(),
|
||||
title = '',
|
||||
description = '';
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'uri': uri.toString(),
|
||||
'creationTimestamp': creationTimestamp,
|
||||
|
@ -103,25 +89,25 @@ class FriendicaMediaAttachment {
|
|||
'description': description,
|
||||
};
|
||||
|
||||
static FriendicaAttachmentMediaType mediaTypeFromString(String path) {
|
||||
static AttachmentMediaType mediaTypeFromString(String path) {
|
||||
final separator = Platform.isWindows ? '\\' : '/';
|
||||
final lastSlash = path.lastIndexOf(separator) + 1;
|
||||
final filename = path.substring(lastSlash);
|
||||
final lastPeriod = filename.lastIndexOf('.') + 1;
|
||||
if (lastPeriod == 0) {
|
||||
return FriendicaAttachmentMediaType.unknown;
|
||||
return AttachmentMediaType.unknown;
|
||||
}
|
||||
|
||||
final extension = filename.substring(lastPeriod).toLowerCase();
|
||||
|
||||
if (_graphicsExtensions.contains(extension)) {
|
||||
return FriendicaAttachmentMediaType.image;
|
||||
return AttachmentMediaType.image;
|
||||
}
|
||||
|
||||
if (_movieExtensions.contains(extension)) {
|
||||
return FriendicaAttachmentMediaType.video;
|
||||
return AttachmentMediaType.video;
|
||||
}
|
||||
|
||||
return FriendicaAttachmentMediaType.unknown;
|
||||
return AttachmentMediaType.unknown;
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
import 'package:friendica_archive_browser/src/friendica/models/friendica_timeline_entry.dart';
|
||||
import 'package:friendica_archive_browser/src/models/timeline_entry.dart';
|
||||
|
||||
class TimeElement {
|
||||
final DateTime timestamp;
|
||||
final FriendicaTimelineEntry entry;
|
||||
final TimelineEntry entry;
|
||||
|
||||
TimeElement({int timeInMS = 0, required this.entry})
|
||||
: timestamp = DateTime.fromMillisecondsSinceEpoch(timeInMS);
|
||||
|
|
160
friendica_archive_browser/lib/src/models/timeline_entry.dart
Normal file
160
friendica_archive_browser/lib/src/models/timeline_entry.dart
Normal file
|
@ -0,0 +1,160 @@
|
|||
import 'package:friendica_archive_browser/src/models/connection.dart';
|
||||
import 'package:friendica_archive_browser/src/services/path_mapper_service_interface.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import 'location_data.dart';
|
||||
import 'media_attachment.dart';
|
||||
import 'model_utils.dart';
|
||||
|
||||
class TimelineEntry {
|
||||
final String id;
|
||||
|
||||
final String parentId;
|
||||
|
||||
final String parentAuthor;
|
||||
|
||||
final String parentAuthorId;
|
||||
|
||||
final int creationTimestamp;
|
||||
|
||||
final int backdatedTimestamp;
|
||||
|
||||
final int modificationTimestamp;
|
||||
|
||||
final String body;
|
||||
|
||||
final String title;
|
||||
|
||||
final bool isReshare;
|
||||
|
||||
final String author;
|
||||
|
||||
final String authorId;
|
||||
|
||||
final String externalLink;
|
||||
|
||||
final List<MediaAttachment> mediaAttachments;
|
||||
|
||||
final LocationData locationData;
|
||||
|
||||
final List<Connection> likes;
|
||||
|
||||
final List<Connection> dislikes;
|
||||
|
||||
TimelineEntry({
|
||||
this.id = '',
|
||||
this.parentId = '',
|
||||
this.creationTimestamp = 0,
|
||||
this.backdatedTimestamp = 0,
|
||||
this.modificationTimestamp = 0,
|
||||
this.isReshare = false,
|
||||
this.body = '',
|
||||
this.title = '',
|
||||
this.author = '',
|
||||
this.authorId = '',
|
||||
this.parentAuthor = '',
|
||||
this.parentAuthorId = '',
|
||||
this.externalLink = '',
|
||||
this.locationData = const LocationData(),
|
||||
this.likes = const <Connection>[],
|
||||
this.dislikes = const <Connection>[],
|
||||
List<MediaAttachment>? mediaAttachments,
|
||||
}) : mediaAttachments = mediaAttachments ?? <MediaAttachment>[];
|
||||
|
||||
TimelineEntry.randomBuilt()
|
||||
: creationTimestamp = DateTime.now().millisecondsSinceEpoch,
|
||||
backdatedTimestamp = DateTime.now().millisecondsSinceEpoch,
|
||||
modificationTimestamp = DateTime.now().millisecondsSinceEpoch,
|
||||
id = randomId(),
|
||||
isReshare = false,
|
||||
parentId = 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()}',
|
||||
parentAuthorId = 'Random parent author id ${randomId()}',
|
||||
locationData = LocationData.randomBuilt(),
|
||||
likes = const <Connection>[],
|
||||
dislikes = const <Connection>[],
|
||||
mediaAttachments = [
|
||||
MediaAttachment.randomBuilt(),
|
||||
MediaAttachment.randomBuilt()
|
||||
];
|
||||
|
||||
TimelineEntry copy(
|
||||
{int? creationTimestamp,
|
||||
int? backdatedTimestamp,
|
||||
int? modificationTimestamp,
|
||||
bool? isReshare,
|
||||
String? id,
|
||||
String? parentId,
|
||||
String? externalLink,
|
||||
String? body,
|
||||
String? title,
|
||||
String? author,
|
||||
String? authorId,
|
||||
String? parentAuthor,
|
||||
String? parentAuthorId,
|
||||
LocationData? locationData,
|
||||
List<MediaAttachment>? mediaAttachments,
|
||||
List<Connection>? likes,
|
||||
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);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'TimelineEntry{id: $id, isReshare: $isReshare, parentId: $parentId, creationTimestamp: $creationTimestamp, modificationTimestamp: $modificationTimestamp, backdatedTimeStamp: $backdatedTimestamp, post: $body, title: $title, author: $author, parentAuthor: $parentAuthor mediaAttachments: $mediaAttachments, externalLink:$externalLink}';
|
||||
}
|
||||
|
||||
String toHumanString(PathMappingService mapper, DateFormat formatter) {
|
||||
final creationDateString = formatter.format(
|
||||
DateTime.fromMillisecondsSinceEpoch(creationTimestamp * 1000)
|
||||
.toLocal());
|
||||
return [
|
||||
'Title: $title',
|
||||
'Creation At: $creationDateString',
|
||||
'Text:',
|
||||
'Author: $author',
|
||||
'Reshare: $isReshare',
|
||||
if (externalLink.isNotEmpty) 'External Link: $externalLink',
|
||||
body,
|
||||
'',
|
||||
if (parentId.isNotEmpty)
|
||||
"Comment on post/comment by ${parentAuthor.isNotEmpty ? parentAuthor : 'unknown author'}",
|
||||
'',
|
||||
if (mediaAttachments.isNotEmpty) 'Photos and Videos:',
|
||||
...mediaAttachments.map((e) => e.toHumanString(mapper)),
|
||||
if (locationData.hasPosition) locationData.toHumanString(),
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
bool hasImages() => mediaAttachments
|
||||
.where((element) => element.explicitType == AttachmentMediaType.image)
|
||||
.isNotEmpty;
|
||||
|
||||
bool hasVideos() => mediaAttachments
|
||||
.where((element) => element.explicitType == AttachmentMediaType.video)
|
||||
.isNotEmpty;
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import 'package:friendica_archive_browser/src/services/path_mapper_service_interface.dart';
|
||||
import 'package:result_monad/result_monad.dart';
|
||||
|
||||
import '../models/entry_tree_item.dart';
|
||||
import '../models/local_image_archive_entry.dart';
|
||||
import '../utils/exec_error.dart';
|
||||
import 'connections_manager.dart';
|
||||
|
||||
class ArchiveService {
|
||||
ConnectionsManager get connectionsManager =>
|
||||
throw Exception('Not implemented');
|
||||
|
||||
String get ownersName => throw Exception('Not implemented');
|
||||
|
||||
PathMappingService get pathMappingService =>
|
||||
throw Exception('Not Implemented');
|
||||
|
||||
void clearCaches() => throw Exception('Not implemented');
|
||||
|
||||
FutureResult<List<EntryTreeItem>, ExecError> getPosts() async =>
|
||||
throw Exception('Not implemented');
|
||||
|
||||
FutureResult<List<EntryTreeItem>, ExecError> getAllComments() =>
|
||||
throw Exception('Not implemented');
|
||||
|
||||
FutureResult<List<EntryTreeItem>, ExecError> getOrphanedComments() =>
|
||||
throw Exception('Not implemented');
|
||||
|
||||
Result<ImageEntry, ExecError> getImageByUrl(String url) =>
|
||||
throw Exception('Not implemented');
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:friendica_archive_browser/src/diaspora/services/diaspora_archive_service.dart';
|
||||
import 'package:friendica_archive_browser/src/diaspora/services/diaspora_path_mapping_service.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/services/friendica_archive_service.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/services/friendica_path_mapping_service.dart';
|
||||
import 'package:friendica_archive_browser/src/services/archive_service_interface.dart';
|
||||
import 'package:friendica_archive_browser/src/services/connections_manager.dart';
|
||||
import 'package:friendica_archive_browser/src/services/path_mapper_service_interface.dart';
|
||||
import 'package:friendica_archive_browser/src/settings/settings_controller.dart';
|
||||
import 'package:result_monad/result_monad.dart';
|
||||
|
||||
import '../models/archive_types_enum.dart';
|
||||
import '../models/entry_tree_item.dart';
|
||||
import '../models/local_image_archive_entry.dart';
|
||||
import '../utils/exec_error.dart';
|
||||
|
||||
class ArchiveServiceProvider extends ChangeNotifier implements ArchiveService {
|
||||
final SettingsController settings;
|
||||
late DiasporaArchiveService _diasporaArchiveService;
|
||||
late FriendicaArchiveService _friendicaArchiveService;
|
||||
|
||||
@override
|
||||
ConnectionsManager get connectionsManager =>
|
||||
_archiveService.connectionsManager;
|
||||
|
||||
ArchiveServiceProvider(this.settings) {
|
||||
_diasporaArchiveService = DiasporaArchiveService(
|
||||
pathMappingService: DiasporaPathMappingService(settings));
|
||||
_friendicaArchiveService = FriendicaArchiveService(
|
||||
pathMappingService: FriendicaPathMappingService(settings));
|
||||
}
|
||||
|
||||
String get ownersName => _archiveService.ownersName;
|
||||
|
||||
void clearCaches() {
|
||||
_friendicaArchiveService.clearCaches();
|
||||
_diasporaArchiveService.clearCaches();
|
||||
}
|
||||
|
||||
FutureResult<List<EntryTreeItem>, ExecError> getPosts() async {
|
||||
return _archiveService.getPosts();
|
||||
}
|
||||
|
||||
FutureResult<List<EntryTreeItem>, ExecError> getAllComments() async {
|
||||
return _archiveService.getAllComments();
|
||||
}
|
||||
|
||||
FutureResult<List<EntryTreeItem>, ExecError> getOrphanedComments() async {
|
||||
return _archiveService.getOrphanedComments();
|
||||
}
|
||||
|
||||
Result<ImageEntry, ExecError> getImageByUrl(String url) {
|
||||
return _archiveService.getImageByUrl(url);
|
||||
}
|
||||
|
||||
ArchiveService get _archiveService {
|
||||
switch (settings.archiveType) {
|
||||
case ArchiveType.diaspora:
|
||||
return _diasporaArchiveService;
|
||||
case ArchiveType.friendica:
|
||||
return _friendicaArchiveService;
|
||||
default:
|
||||
throw Exception('Unknown archive type');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
PathMappingService get pathMappingService =>
|
||||
_archiveService.pathMappingService;
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
import 'package:friendica_archive_browser/src/models/connection.dart';
|
||||
import 'package:result_monad/result_monad.dart';
|
||||
|
||||
class ConnectionsManager {
|
||||
final _connectionsById = <String, Connection>{};
|
||||
final _connectionsByName = <String, Connection>{};
|
||||
|
||||
int get length => _connectionsById.length;
|
||||
|
||||
void clearCaches() {
|
||||
_connectionsById.clear();
|
||||
_connectionsByName.clear();
|
||||
}
|
||||
|
||||
bool addConnection(Connection connection) {
|
||||
if (_connectionsById.containsKey(connection.id)) {
|
||||
return false;
|
||||
}
|
||||
_connectionsById[connection.id] = connection;
|
||||
_connectionsByName[connection.name] = connection;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool addAllConnections(Iterable<Connection> newConnections) {
|
||||
bool result = true;
|
||||
|
||||
for (final connection in newConnections) {
|
||||
result &= addConnection(connection);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Result<Connection, String> getById(String id) {
|
||||
final result = _connectionsById[id];
|
||||
|
||||
return result != null ? Result.ok(result) : Result.error('$id not found');
|
||||
}
|
||||
|
||||
Result<Connection, String> getByName(String name) {
|
||||
final result = _connectionsByName[name];
|
||||
|
||||
return result != null ? Result.ok(result) : Result.error('$name not found');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import 'dart:io';
|
||||
|
||||
class PathMappingService {
|
||||
String get rootFolder => throw Exception('Not implemented');
|
||||
|
||||
List<FileSystemEntity> get archiveDirectories =>
|
||||
throw Exception('Not implemented');
|
||||
|
||||
void refresh() => throw Exception('Not implemented');
|
||||
|
||||
String toFullPath(String relPath) => throw Exception('Not implemented');
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:friendica_archive_browser/src/models/archive_types_enum.dart';
|
||||
import 'package:friendica_archive_browser/src/settings/video_player_settings.dart';
|
||||
import 'package:friendica_archive_browser/src/utils/temp_file_builder.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
@ -18,9 +19,12 @@ class SettingsController with ChangeNotifier {
|
|||
: _settingsService = SettingsService();
|
||||
|
||||
Future<void> loadSettings() async {
|
||||
_archiveType = await _settingsService.archiveType();
|
||||
_themeMode = await _settingsService.themeMode();
|
||||
_rootFolder = await _settingsService.rootFolder();
|
||||
var canReadRootDir = runCatching(()=>Result.ok(Directory(_rootFolder).listSync())).isSuccess;
|
||||
var canReadRootDir =
|
||||
runCatching(() => Result.ok(Directory(_rootFolder).listSync()))
|
||||
.isSuccess;
|
||||
if (!canReadRootDir) {
|
||||
_rootFolder = '';
|
||||
}
|
||||
|
@ -74,6 +78,17 @@ class SettingsController with ChangeNotifier {
|
|||
await _settingsService.updateRootFolder(newPath);
|
||||
}
|
||||
|
||||
late ArchiveType _archiveType;
|
||||
|
||||
ArchiveType get archiveType => _archiveType;
|
||||
|
||||
Future<void> updateArchiveType(ArchiveType newArchiveType) async {
|
||||
if (newArchiveType == _archiveType) return;
|
||||
_archiveType = newArchiveType;
|
||||
notifyListeners();
|
||||
await _settingsService.updateArchiveType(newArchiveType);
|
||||
}
|
||||
|
||||
late ThemeMode _themeMode;
|
||||
|
||||
ThemeMode get themeMode => _themeMode;
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:friendica_archive_browser/src/models/archive_types_enum.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'video_player_settings.dart';
|
||||
|
||||
class SettingsService {
|
||||
static const archiveTypeKey = "archiveType";
|
||||
static const themeDarknessKey = 'themeDarkness';
|
||||
static const rootFolderKey = 'rootFolder';
|
||||
static const videoPlayerSettingTypeKey = 'videoPlayerSettingType';
|
||||
|
@ -45,6 +47,22 @@ class SettingsService {
|
|||
prefs.setInt(themeDarknessKey, theme.index);
|
||||
}
|
||||
|
||||
Future<ArchiveType> archiveType() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final archiveTypeIndex = prefs.getInt(archiveTypeKey) ?? 0;
|
||||
if (archiveTypeIndex > ArchiveType.values.length - 1 ||
|
||||
archiveTypeIndex < 0) {
|
||||
return ArchiveType.unknown;
|
||||
}
|
||||
|
||||
return ArchiveType.values[archiveTypeIndex];
|
||||
}
|
||||
|
||||
Future<void> updateArchiveType(ArchiveType archiveType) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
prefs.setInt(archiveTypeKey, archiveType.index);
|
||||
}
|
||||
|
||||
Future<String> rootFolder() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final result = prefs.getString(rootFolderKey) ?? '';
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:io';
|
|||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:friendica_archive_browser/src/models/archive_types_enum.dart';
|
||||
import 'package:friendica_archive_browser/src/settings/video_player_settings.dart';
|
||||
import 'package:friendica_archive_browser/src/utils/clipboard_helper.dart';
|
||||
import 'package:friendica_archive_browser/src/utils/snackbar_status_builder.dart';
|
||||
|
@ -157,6 +158,20 @@ class _SettingsViewState extends State<SettingsView> {
|
|||
Text('Archive Folder: ',
|
||||
style: Theme.of(context).textTheme.bodyText1),
|
||||
const SizedBox(width: 10),
|
||||
DropdownButton<ArchiveType>(
|
||||
value: widget._settingsController.archiveType,
|
||||
onChanged: (newArchiveType) async {
|
||||
await widget._settingsController
|
||||
.updateArchiveType(newArchiveType!);
|
||||
setState(() {});
|
||||
},
|
||||
items: ArchiveType.values
|
||||
.map((e) => DropdownMenuItem(
|
||||
value: e,
|
||||
child: Text(e.name),
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _folderPathController,
|
||||
|
|
|
@ -3,12 +3,26 @@ import 'package:result_monad/result_monad.dart';
|
|||
import 'package:time_machine/time_machine_text_patterns.dart';
|
||||
|
||||
class OffsetDateTimeUtils {
|
||||
static final _parser = OffsetDateTimePattern.createWithInvariantCulture(
|
||||
static final _offsetTimeParser =
|
||||
OffsetDateTimePattern.createWithInvariantCulture(
|
||||
'ddd MMM dd HH:mm:ss o<+HHmm> yyyy');
|
||||
|
||||
static Result<int, ExecError> epochSecTimeFromFriendicaString(
|
||||
String dateString) {
|
||||
final offsetDateTime = _parser.parse(dateString);
|
||||
final offsetDateTime = _offsetTimeParser.parse(dateString);
|
||||
if (!offsetDateTime.success) {
|
||||
return Result.error(ExecError.message(offsetDateTime.error.toString()));
|
||||
}
|
||||
|
||||
return Result.ok(offsetDateTime.value.localDateTime
|
||||
.toDateTimeLocal()
|
||||
.millisecondsSinceEpoch ~/
|
||||
1000);
|
||||
}
|
||||
|
||||
static Result<int, ExecError> epochSecTimeFromTimeZoneString(
|
||||
String dateString) {
|
||||
final offsetDateTime = OffsetDateTimePattern.generalIso.parse(dateString);
|
||||
if (!offsetDateTime.success) {
|
||||
return Result.error(ExecError.message(offsetDateTime.error.toString()));
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:friendica_archive_browser/src/friendica/models/friendica_timeline_entry.dart';
|
||||
import 'package:friendica_archive_browser/src/models/friendica_contact.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/services/friendica_connections.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';
|
||||
|
||||
class TopInteractorsGenerator {
|
||||
final _interactors = <String, InteractorItem>{};
|
||||
|
@ -11,8 +11,7 @@ class TopInteractorsGenerator {
|
|||
_processedEntryIds.clear();
|
||||
}
|
||||
|
||||
void processEntry(
|
||||
FriendicaTimelineEntry item, FriendicaConnections contacts) {
|
||||
void processEntry(TimelineEntry item, ConnectionsManager contacts) {
|
||||
if (_processedEntryIds.contains(item.id)) {
|
||||
return;
|
||||
}
|
||||
|
@ -59,14 +58,14 @@ class TopInteractorsGenerator {
|
|||
}
|
||||
|
||||
InteractorItem _getInteractorItemById(
|
||||
String id, FriendicaConnections contacts) {
|
||||
String id, ConnectionsManager contacts) {
|
||||
if (_interactors.containsKey(id)) {
|
||||
return _interactors[id]!;
|
||||
}
|
||||
|
||||
final contact = contacts.getById(id).fold(
|
||||
onSuccess: (contact) => contact,
|
||||
onError: (error) => Contact(
|
||||
onError: (error) => Connection(
|
||||
status: ConnectionStatus.none,
|
||||
name: '',
|
||||
id: id,
|
||||
|
@ -77,7 +76,7 @@ class TopInteractorsGenerator {
|
|||
}
|
||||
|
||||
class InteractorItem {
|
||||
final Contact contact;
|
||||
final Connection contact;
|
||||
final int resharedOrCommentedOn;
|
||||
final int likeCount;
|
||||
final int dislikeCount;
|
||||
|
@ -94,7 +93,7 @@ class InteractorItem {
|
|||
}
|
||||
|
||||
InteractorItem copy(
|
||||
{Contact? contact,
|
||||
{Connection? contact,
|
||||
int? resharedOrCommentedOn,
|
||||
int? likeCount,
|
||||
int? dislikeCount}) {
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -217,6 +224,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
markdown:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: markdown
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.0.1"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -22,6 +22,7 @@ dependencies:
|
|||
logging: ^1.0.2
|
||||
latlng: ^0.1.0
|
||||
map: ^1.0.0
|
||||
markdown: ^4.0.1
|
||||
metadata_fetch: ^0.4.1
|
||||
multi_split_view: ^1.10.0+1
|
||||
path: ^1.8.0
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// ignore_for_file: avoid_print
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:friendica_archive_browser/src/friendica/models/model_utils.dart';
|
||||
import 'package:friendica_archive_browser/src/models/model_utils.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
void main() {
|
||||
|
|
|
@ -2,13 +2,32 @@
|
|||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:friendica_archive_browser/src/diaspora/services/diaspora_profile_json_reader.dart';
|
||||
import 'package:friendica_archive_browser/src/models/timeline_entry.dart';
|
||||
import 'package:friendica_archive_browser/src/services/connections_manager.dart';
|
||||
|
||||
const jsonPath = '/Users/hankdev/Desktop/diaspora_pretty.json';
|
||||
|
||||
void main() {
|
||||
test('Diaspora Connections Test', () {
|
||||
final reader = DiasporaProfileJsonReader(
|
||||
'/Users/hankdev/Desktop/diaspora_pretty.json');
|
||||
final reader = DiasporaProfileJsonReader(jsonPath, ConnectionsManager());
|
||||
final contacts = reader.readContacts();
|
||||
print(contacts.length);
|
||||
print(contacts.first);
|
||||
});
|
||||
|
||||
test('Diaspora Posts Test', () {
|
||||
final reader = DiasporaProfileJsonReader(jsonPath, ConnectionsManager());
|
||||
final posts = reader.readPosts();
|
||||
|
||||
print(posts.length);
|
||||
print(posts.first);
|
||||
|
||||
final postsWithImage = posts.firstWhere((element) => element.mediaAttachments.isNotEmpty, orElse: ()=>TimelineEntry());
|
||||
print(postsWithImage);
|
||||
|
||||
final resharePost = posts.firstWhere((element) => element.externalLink.isNotEmpty, orElse: ()=>TimelineEntry());
|
||||
print(resharePost);
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue