diff --git a/README.md b/README.md index bce62b2..3cb70b2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,15 @@ A Flutter application for interfacing with the Friendica social network. -For Linux development be sure that libsecret-1-dev and libjsoncpp-dev are installed on the machine. For running only make sure the non-dev versions are... +For Linux development be sure that libsecret-1-dev and libjsoncpp-dev are installed on the machine. For running only +make sure the non-dev versions are... +## Development Notes + +Whenever a model is changed that is stored in ObjectBox it is necessary to execute the command: + +```bash +flutter pub run build_runner build +``` Licensed with the Mozilla Public License 2.0 copyleft license. diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index c29ba58..5c9e0d1 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -5,4 +5,5 @@ to allow setting breakpoints, to provide hot reload, etc. --> + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index dd26b82..831fd53 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -37,4 +37,6 @@ + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml index c29ba58..5c9e0d1 100644 --- a/android/app/src/profile/AndroidManifest.xml +++ b/android/app/src/profile/AndroidManifest.xml @@ -5,4 +5,5 @@ to allow setting breakpoints, to provide hot reload, etc. --> + diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 7caad5f..f7df94d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -43,6 +43,10 @@ PODS: - FMDB/standard (2.7.5) - image_picker_ios (0.0.1): - Flutter + - ObjectBox (1.8.1-rc) + - objectbox_flutter_libs (0.0.1): + - Flutter + - ObjectBox (= 1.8.1-rc) - path_provider_ios (0.0.1): - Flutter - SDWebImage (5.13.2): @@ -66,6 +70,7 @@ DEPENDENCIES: - flutter_file_dialog (from `.symlinks/plugins/flutter_file_dialog/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) + - objectbox_flutter_libs (from `.symlinks/plugins/objectbox_flutter_libs/ios`) - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`) @@ -77,6 +82,7 @@ SPEC REPOS: - DKImagePickerController - DKPhotoGallery - FMDB + - ObjectBox - SDWebImage - SwiftyGif @@ -91,6 +97,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_secure_storage/ios" image_picker_ios: :path: ".symlinks/plugins/image_picker_ios/ios" + objectbox_flutter_libs: + :path: ".symlinks/plugins/objectbox_flutter_libs/ios" path_provider_ios: :path: ".symlinks/plugins/path_provider_ios/ios" shared_preferences_foundation: @@ -111,6 +119,8 @@ SPEC CHECKSUMS: flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a image_picker_ios: b786a5dcf033a8336a657191401bfdf12017dabb + ObjectBox: 7615cc462f2976cb45127d301a17cb473edf3f9e + objectbox_flutter_libs: 55835e03ff76bf9d5ce0a41a2ef3902d2e8c1f20 path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 SDWebImage: 72f86271a6f3139cc7e4a89220946489d4b9a866 shared_preferences_foundation: 297b3ebca31b34ec92be11acd7fb0ba932c822ca diff --git a/lib/controls/timeline/interactions_bar_control.dart b/lib/controls/timeline/interactions_bar_control.dart index 9dc8653..247a73e 100644 --- a/lib/controls/timeline/interactions_bar_control.dart +++ b/lib/controls/timeline/interactions_bar_control.dart @@ -133,7 +133,7 @@ class _InteractionsBarControlState extends State { ); } } else { - context.push('/post/view/${widget.entry.id}'); + context.push('/post/view/${widget.entry.id}/${widget.entry.id}'); } } diff --git a/lib/data/interfaces/connections_repo_intf.dart b/lib/data/interfaces/connections_repo_intf.dart new file mode 100644 index 0000000..36beb28 --- /dev/null +++ b/lib/data/interfaces/connections_repo_intf.dart @@ -0,0 +1,34 @@ +import 'package:result_monad/result_monad.dart'; + +import '../../models/connection.dart'; +import '../../models/exec_error.dart'; + +class IConnectionsRepo { + bool addConnection(Connection connection) { + throw UnimplementedError(); + } + + bool addAllConnections(Iterable newConnections) { + throw UnimplementedError(); + } + + bool updateConnection(Connection connection) { + throw UnimplementedError(); + } + + Result getById(String id) { + throw UnimplementedError(); + } + + Result getByName(String name) { + throw UnimplementedError(); + } + + List getMyContacts() { + throw UnimplementedError(); + } + + List getKnownUsersByName(String name) { + throw UnimplementedError(); + } +} diff --git a/lib/data/interfaces/groups_repo.intf.dart b/lib/data/interfaces/groups_repo.intf.dart new file mode 100644 index 0000000..1ab1437 --- /dev/null +++ b/lib/data/interfaces/groups_repo.intf.dart @@ -0,0 +1,26 @@ +import 'package:result_monad/result_monad.dart'; + +import '../../models/exec_error.dart'; +import '../../models/group_data.dart'; + +class IGroupsRepo { + void addAllGroups(List groups) { + throw UnimplementedError(); + } + + void clearGroups() { + throw UnimplementedError(); + } + + List getMyGroups() { + throw UnimplementedError(); + } + + Result, ExecError> getGroupsForUser(String id) { + throw UnimplementedError(); + } + + bool updateConnectionGroupData(String id, List currentGroups) { + throw UnimplementedError(); + } +} diff --git a/lib/data/interfaces/hashtag_repo_intf.dart b/lib/data/interfaces/hashtag_repo_intf.dart new file mode 100644 index 0000000..ae9f17d --- /dev/null +++ b/lib/data/interfaces/hashtag_repo_intf.dart @@ -0,0 +1,11 @@ +import '../../models/hashtag.dart'; + +class IHashtagRepo { + void add(Hashtag tag) { + throw UnimplementedError(); + } + + List getMatchingHashTags(String text) { + throw UnimplementedError(); + } +} diff --git a/lib/data/memory/memory_connections_repo.dart b/lib/data/memory/memory_connections_repo.dart new file mode 100644 index 0000000..bd43aa8 --- /dev/null +++ b/lib/data/memory/memory_connections_repo.dart @@ -0,0 +1,97 @@ +import 'package:result_monad/result_monad.dart'; + +import '../../models/connection.dart'; +import '../../models/exec_error.dart'; +import '../interfaces/connections_repo_intf.dart'; + +class MemoryConnectionsRepo implements IConnectionsRepo { + final _connectionsById = {}; + final _connectionsByName = {}; + final _myContacts = []; + + @override + bool addAllConnections(Iterable newConnections) { + bool result = true; + + for (final connection in newConnections) { + result &= addConnection(connection); + } + + return result; + } + + @override + bool addConnection(Connection connection) { + if (_connectionsById.containsKey(connection.id)) { + return false; + } + return updateConnection(connection); + } + + @override + List getKnownUsersByName(String name) { + return _connectionsByName.values.where((it) { + final normalizedHandle = it.handle.toLowerCase(); + final normalizedName = it.name.toLowerCase(); + final normalizedQuery = name.toLowerCase(); + return normalizedHandle.contains(normalizedQuery) || + normalizedName.contains(normalizedQuery); + }).toList(); + } + + @override + bool updateConnection(Connection connection) { + _connectionsById[connection.id] = connection; + _connectionsByName[connection.name] = connection; + int index = _myContacts.indexWhere((c) => c.id == connection.id); + if (index >= 0) { + _myContacts.removeAt(index); + } + switch (connection.status) { + case ConnectionStatus.youFollowThem: + case ConnectionStatus.theyFollowYou: + case ConnectionStatus.mutual: + if (index > 0) { + _myContacts.insert(index, connection); + } else { + _myContacts.add(connection); + } + break; + default: + break; + } + + return true; + } + + @override + List getMyContacts() { + return _myContacts; + } + + @override + Result getById(String id) { + final result = _connectionsById[id]; + if (result == null) { + return Result.error(ExecError( + type: ErrorType.notFound, + message: '$id not found', + )); + } + + return Result.ok(result); + } + + @override + Result getByName(String name) { + final result = _connectionsByName[name]; + if (result == null) { + return Result.error(ExecError( + type: ErrorType.notFound, + message: '$name not found', + )); + } + + return Result.ok(result); + } +} diff --git a/lib/data/memory/memory_groups_repo.dart b/lib/data/memory/memory_groups_repo.dart new file mode 100644 index 0000000..a3ce883 --- /dev/null +++ b/lib/data/memory/memory_groups_repo.dart @@ -0,0 +1,43 @@ +import 'package:result_monad/result_monad.dart'; + +import '../../models/exec_error.dart'; +import '../../models/group_data.dart'; +import '../interfaces/groups_repo.intf.dart'; + +class MemoryGroupsRepo implements IGroupsRepo { + final _groupsForConnection = >{}; + final _myGroups = {}; + + @override + Result, ExecError> getGroupsForUser(String id) { + if (!_groupsForConnection.containsKey(id)) { + return Result.error(ExecError( + type: ErrorType.notFound, + message: '$id not a known user ID', + )); + } + + return Result.ok(_groupsForConnection[id]!); + } + + @override + List getMyGroups() { + return _myGroups.toList(); + } + + @override + void clearGroups() { + _myGroups.clear(); + } + + @override + void addAllGroups(List groups) { + _myGroups.addAll(groups); + } + + @override + bool updateConnectionGroupData(String id, List currentGroups) { + _groupsForConnection[id] = currentGroups; + return true; + } +} diff --git a/lib/data/objectbox/objectbox_cache.dart b/lib/data/objectbox/objectbox_cache.dart new file mode 100644 index 0000000..29cc714 --- /dev/null +++ b/lib/data/objectbox/objectbox_cache.dart @@ -0,0 +1,23 @@ +import 'package:logging/logging.dart'; +import 'package:objectbox/objectbox.dart'; +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; + +import '../../objectbox.g.dart'; + +class ObjectBoxCache { + static final _logger = Logger('ObjectBoxCache'); + late final Store store; + + ObjectBoxCache._create(this.store); + + static Future create() async { + final docsDir = await getApplicationSupportDirectory(); + + final path = p.join(docsDir.path, 'objectboxcache'); + _logger.info('ObjectBoxCache path: $path'); + final store = await openStore( + directory: path, macosApplicationGroup: 'friendica_portal'); + return ObjectBoxCache._create(store); + } +} diff --git a/lib/data/objectbox/objectbox_connections_repo.dart b/lib/data/objectbox/objectbox_connections_repo.dart new file mode 100644 index 0000000..734918c --- /dev/null +++ b/lib/data/objectbox/objectbox_connections_repo.dart @@ -0,0 +1,104 @@ +import 'package:result_monad/result_monad.dart'; + +import '../../globals.dart'; +import '../../models/connection.dart'; +import '../../models/exec_error.dart'; +import '../../objectbox.g.dart'; +import '../interfaces/connections_repo_intf.dart'; +import '../memory/memory_connections_repo.dart'; +import 'objectbox_cache.dart'; + +class ObjectBoxConnectionsRepo implements IConnectionsRepo { + late final Box box; + final memCache = MemoryConnectionsRepo(); + + ObjectBoxConnectionsRepo() { + box = getIt().store.box(); + } + + @override + bool addAllConnections(Iterable newConnections) { + memCache.addAllConnections(newConnections); + final result = box.putMany(newConnections.toList()); + return result.length == newConnections.length; + } + + @override + bool addConnection(Connection connection) { + memCache.addConnection(connection); + box.putAsync(connection); + return true; + } + + @override + Result getById(String id) { + final result = memCache.getById(id); + if (result.isSuccess) { + return result; + } + + return _getConnection( + Connection_.id.equals(id), + ).mapError( + (error) => error.copy( + message: "Can't find Connection for ID: $id", + ), + ); + } + + @override + Result getByName(String name) { + final result = memCache.getByName(name); + if (result.isSuccess) { + return result; + } + return _getConnection( + Connection_.name.equals(name), + ).mapError( + (error) => error.copy( + message: "Can't find Connection for ID: $name", + ), + ); + } + + @override + List getKnownUsersByName(String name) { + return box + .query( + Connection_.name.contains(name, caseSensitive: false) | + Connection_.handle.contains(name, caseSensitive: false), + ) + .build() + .find(); + } + + @override + List getMyContacts() { + final connectedStatuses = [ + ConnectionStatus.youFollowThem.code, + ConnectionStatus.theyFollowYou.code, + ConnectionStatus.mutual.code, + ]; + return box + .query(Connection_.dbStatus.oneOf(connectedStatuses)) + .build() + .find(); + } + + @override + bool updateConnection(Connection connection) { + memCache.updateConnection(connection); + box.put(connection); + return true; + } + + Result _getConnection(Condition query) { + final connection = box.query(query).build().findFirst(); + if (connection == null) { + return buildErrorResult(type: ErrorType.notFound); + } + + memCache.addConnection(connection); + return Result.ok(connection); + } +} diff --git a/lib/data/objectbox/objectbox_hashtag_repo.dart b/lib/data/objectbox/objectbox_hashtag_repo.dart new file mode 100644 index 0000000..46c741b --- /dev/null +++ b/lib/data/objectbox/objectbox_hashtag_repo.dart @@ -0,0 +1,34 @@ +import '../../globals.dart'; +import '../../models/hashtag.dart'; +import '../../objectbox.g.dart'; +import '../interfaces/hashtag_repo_intf.dart'; +import 'objectbox_cache.dart'; + +class ObjectBoxHashtagRepo implements IHashtagRepo { + late final Box box; + + ObjectBoxHashtagRepo() { + box = getIt().store.box(); + } + + @override + void add(Hashtag tag) { + box.putAsync(tag); + } + + @override + List getMatchingHashTags(String text) { + return (box + .query( + text.length <= 2 + ? Hashtag_.tag.startsWith(text, caseSensitive: false) + : Hashtag_.tag.contains(text, caseSensitive: false), + ) + .order(Hashtag_.tag) + .build() + ..limit = 100) + .find() + .map((h) => h.tag) + .toList(); + } +} diff --git a/lib/main.dart b/lib/main.dart index 3bc46fd..f9e016f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,6 +5,13 @@ import 'package:multi_trigger_autocomplete/multi_trigger_autocomplete.dart'; import 'package:provider/provider.dart'; import 'package:result_monad/result_monad.dart'; +import 'data/interfaces/connections_repo_intf.dart'; +import 'data/interfaces/groups_repo.intf.dart'; +import 'data/interfaces/hashtag_repo_intf.dart'; +import 'data/memory/memory_groups_repo.dart'; +import 'data/objectbox/objectbox_cache.dart'; +import 'data/objectbox/objectbox_connections_repo.dart'; +import 'data/objectbox/objectbox_hashtag_repo.dart'; import 'globals.dart'; import 'models/TimelineIdentifiers.dart'; import 'routes.dart'; @@ -39,11 +46,18 @@ void main() async { final entryManagerService = EntryManagerService(); final timelineManager = TimelineManager(); final galleryService = GalleryService(); + getIt.registerSingletonAsync(() async { final service = SettingsService(); await service.initialize(); return service; }); + + final objectBoxCache = await ObjectBoxCache.create(); + getIt.registerSingleton(objectBoxCache); + getIt.registerSingleton(ObjectBoxConnectionsRepo()); + getIt.registerSingleton(ObjectBoxHashtagRepo()); + getIt.registerSingleton(MemoryGroupsRepo()); getIt.registerLazySingleton(() => ConnectionsManager()); getIt.registerLazySingleton(() => HashtagService()); getIt.registerSingleton(galleryService); diff --git a/lib/models/connection.dart b/lib/models/connection.dart index 43b13c2..00b9e6d 100644 --- a/lib/models/connection.dart +++ b/lib/models/connection.dart @@ -1,44 +1,67 @@ +import 'package:objectbox/objectbox.dart'; + +@Entity() class Connection { - final ConnectionStatus status; + @Id() + int obId; + + ConnectionStatus status; + + int get dbStatus => status.code; + + set dbStatus(int value) => status = ConnectionStatus.fromValue(value); final String name; final String handle; + @Unique(onConflict: ConflictStrategy.replace) final String id; - final Uri profileUrl; + final String profileUrl; final String network; - final Uri avatarUrl; + final String avatarUrl; - Connection({this.status = ConnectionStatus.unknown, - this.name = '', - this.handle = '', - this.id = '', - Uri? profileUrl, - this.network = '', - Uri? avatarUrl}) - : profileUrl = profileUrl ?? Uri(), - avatarUrl = avatarUrl ?? Uri(); + @Property(type: PropertyType.date) + final DateTime lastUpdateTime; + + Connection( + {this.obId = 0, + this.status = ConnectionStatus.unknown, + this.name = '', + this.handle = '', + this.id = '', + String? profileUrl, + this.network = '', + String? avatarUrl, + DateTime? lastUpdateTime}) + : profileUrl = profileUrl ?? '', + avatarUrl = avatarUrl ?? '', + lastUpdateTime = lastUpdateTime ?? DateTime.now(); bool get isEmpty => name.isEmpty && - id.isEmpty && - network.isEmpty && - status == ConnectionStatus.unknown; + id.isEmpty && + network.isEmpty && + status == ConnectionStatus.unknown; bool get isNotEmpty => !isEmpty; - Connection copy({ConnectionStatus? status, + Connection copy({ + int? obId, + ConnectionStatus? status, String? name, String? handle, String? id, - Uri? profileUrl, + String? profileUrl, String? network, - Uri? avatarUrl}) => + String? avatarUrl, + DateTime? lastUpdateTime, + }) => Connection( + obId: obId ?? this.obId, status: status ?? this.status, name: name ?? this.name, handle: handle ?? this.handle, @@ -46,6 +69,7 @@ class Connection { profileUrl: profileUrl ?? this.profileUrl, network: network ?? this.network, avatarUrl: avatarUrl ?? this.avatarUrl, + lastUpdateTime: lastUpdateTime ?? this.lastUpdateTime, ); @override @@ -56,20 +80,28 @@ class Connection { @override bool operator ==(Object other) => identical(this, other) || - other is Connection && runtimeType == other.runtimeType && - id == other.id; + other is Connection && runtimeType == other.runtimeType && id == other.id; @override int get hashCode => id.hashCode; } enum ConnectionStatus { - youFollowThem, - theyFollowYou, - mutual, - you, - none, - unknown, + youFollowThem(1), + theyFollowYou(2), + mutual(3), + you(4), + none(5), + unknown(6), + ; + + final int code; + + const ConnectionStatus(this.code); + + factory ConnectionStatus.fromValue(int value) { + return ConnectionStatus.values.where((e) => e.code == value).first; + } } extension FriendStatusWriter on ConnectionStatus { diff --git a/lib/models/exec_error.dart b/lib/models/exec_error.dart index b0d0f68..961ef7c 100644 --- a/lib/models/exec_error.dart +++ b/lib/models/exec_error.dart @@ -1,11 +1,31 @@ import 'package:result_monad/result_monad.dart'; +Result buildErrorResult({ + required ErrorType type, + String message = '', +}) => + Result.error( + ExecError( + type: type, + message: message, + ), + ); + class ExecError { final ErrorType type; final String message; ExecError({required this.type, this.message = ''}); + ExecError copy({ + ErrorType? type, + String? message, + }) => + ExecError( + type: type ?? this.type, + message: message ?? this.message, + ); + @override String toString() { return 'ExecError{type: $type, message: $message}'; diff --git a/lib/models/hashtag.dart b/lib/models/hashtag.dart new file mode 100644 index 0000000..6d2afa5 --- /dev/null +++ b/lib/models/hashtag.dart @@ -0,0 +1,30 @@ +import 'package:objectbox/objectbox.dart'; + +@Entity() +class Hashtag { + @Id() + int id; + + @Unique(onConflict: ConflictStrategy.replace) + String tag; + + String url; + + @Property(type: PropertyType.date) + DateTime lastUpdateTime; + + Hashtag({ + this.id = 0, + required this.tag, + required this.url, + DateTime? updateTime, + }) : lastUpdateTime = updateTime ?? DateTime.now(); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is Hashtag && runtimeType == other.runtimeType && tag == other.tag; + + @override + int get hashCode => tag.hashCode; +} diff --git a/lib/objectbox-model.json b/lib/objectbox-model.json new file mode 100644 index 0000000..2597277 --- /dev/null +++ b/lib/objectbox-model.json @@ -0,0 +1,105 @@ +{ + "_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.", + "_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.", + "_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.", + "entities": [ + { + "id": "1:1213035855270739890", + "lastPropertyId": "9:7727190023732579468", + "name": "Connection", + "properties": [ + { + "id": "1:4133343279264917280", + "name": "obId", + "type": 6, + "flags": 1 + }, + { + "id": "2:3393770296096844708", + "name": "name", + "type": 9 + }, + { + "id": "3:5864801995210079539", + "name": "handle", + "type": 9 + }, + { + "id": "4:2926904168461994523", + "name": "id", + "type": 9, + "flags": 34848, + "indexId": "1:8342366639839511243" + }, + { + "id": "5:3621370552742492695", + "name": "network", + "type": 9 + }, + { + "id": "6:3054748457893853359", + "name": "profileUrl", + "type": 9 + }, + { + "id": "7:3716471511430220806", + "name": "avatarUrl", + "type": 9 + }, + { + "id": "8:3334077197732145885", + "name": "dbStatus", + "type": 6 + }, + { + "id": "9:7727190023732579468", + "name": "lastUpdateTime", + "type": 10 + } + ], + "relations": [] + }, + { + "id": "2:8060242331335522964", + "lastPropertyId": "4:985152873657204249", + "name": "Hashtag", + "properties": [ + { + "id": "1:3633001791521338712", + "name": "id", + "type": 6, + "flags": 1 + }, + { + "id": "2:3468373950035339457", + "name": "tag", + "type": 9, + "flags": 34848, + "indexId": "2:6156017341759176249" + }, + { + "id": "3:5102584273729210526", + "name": "url", + "type": 9 + }, + { + "id": "4:985152873657204249", + "name": "lastUpdateTime", + "type": 10 + } + ], + "relations": [] + } + ], + "lastEntityId": "2:8060242331335522964", + "lastIndexId": "2:6156017341759176249", + "lastRelationId": "0:0", + "lastSequenceId": "0:0", + "modelVersion": 5, + "modelVersionParserMinimum": 5, + "retiredEntityUids": [], + "retiredIndexUids": [], + "retiredPropertyUids": [], + "retiredRelationUids": [], + "version": 1 +} \ No newline at end of file diff --git a/lib/objectbox.g.dart b/lib/objectbox.g.dart new file mode 100644 index 0000000..82ec189 --- /dev/null +++ b/lib/objectbox.g.dart @@ -0,0 +1,287 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// This code was generated by ObjectBox. To update it run the generator again: +// With a Flutter package, run `flutter pub run build_runner build`. +// With a Dart package, run `dart run build_runner build`. +// See also https://docs.objectbox.io/getting-started#generate-objectbox-code + +// ignore_for_file: camel_case_types +// coverage:ignore-file + +import 'dart:typed_data'; + +import 'package:flat_buffers/flat_buffers.dart' as fb; +import 'package:objectbox/internal.dart'; // generated code can access "internal" functionality +import 'package:objectbox/objectbox.dart'; +import 'package:objectbox_flutter_libs/objectbox_flutter_libs.dart'; + +import 'models/connection.dart'; +import 'models/hashtag.dart'; + +export 'package:objectbox/objectbox.dart'; // so that callers only have to import this file + +final _entities = [ + ModelEntity( + id: const IdUid(1, 1213035855270739890), + name: 'Connection', + lastPropertyId: const IdUid(9, 7727190023732579468), + flags: 0, + properties: [ + ModelProperty( + id: const IdUid(1, 4133343279264917280), + name: 'obId', + type: 6, + flags: 1), + ModelProperty( + id: const IdUid(2, 3393770296096844708), + name: 'name', + type: 9, + flags: 0), + ModelProperty( + id: const IdUid(3, 5864801995210079539), + name: 'handle', + type: 9, + flags: 0), + ModelProperty( + id: const IdUid(4, 2926904168461994523), + name: 'id', + type: 9, + flags: 34848, + indexId: const IdUid(1, 8342366639839511243)), + ModelProperty( + id: const IdUid(5, 3621370552742492695), + name: 'network', + type: 9, + flags: 0), + ModelProperty( + id: const IdUid(6, 3054748457893853359), + name: 'profileUrl', + type: 9, + flags: 0), + ModelProperty( + id: const IdUid(7, 3716471511430220806), + name: 'avatarUrl', + type: 9, + flags: 0), + ModelProperty( + id: const IdUid(8, 3334077197732145885), + name: 'dbStatus', + type: 6, + flags: 0), + ModelProperty( + id: const IdUid(9, 7727190023732579468), + name: 'lastUpdateTime', + type: 10, + flags: 0) + ], + relations: [], + backlinks: []), + ModelEntity( + id: const IdUid(2, 8060242331335522964), + name: 'Hashtag', + lastPropertyId: const IdUid(4, 985152873657204249), + flags: 0, + properties: [ + ModelProperty( + id: const IdUid(1, 3633001791521338712), + name: 'id', + type: 6, + flags: 1), + ModelProperty( + id: const IdUid(2, 3468373950035339457), + name: 'tag', + type: 9, + flags: 34848, + indexId: const IdUid(2, 6156017341759176249)), + ModelProperty( + id: const IdUid(3, 5102584273729210526), + name: 'url', + type: 9, + flags: 0), + ModelProperty( + id: const IdUid(4, 985152873657204249), + name: 'lastUpdateTime', + type: 10, + flags: 0) + ], + relations: [], + backlinks: []) +]; + +/// Open an ObjectBox store with the model declared in this file. +Future openStore( + {String? directory, + int? maxDBSizeInKB, + int? fileMode, + int? maxReaders, + bool queriesCaseSensitiveDefault = true, + String? macosApplicationGroup}) async => + Store(getObjectBoxModel(), + directory: directory ?? (await defaultStoreDirectory()).path, + maxDBSizeInKB: maxDBSizeInKB, + fileMode: fileMode, + maxReaders: maxReaders, + queriesCaseSensitiveDefault: queriesCaseSensitiveDefault, + macosApplicationGroup: macosApplicationGroup); + +/// ObjectBox model definition, pass it to [Store] - Store(getObjectBoxModel()) +ModelDefinition getObjectBoxModel() { + final model = ModelInfo( + entities: _entities, + lastEntityId: const IdUid(2, 8060242331335522964), + lastIndexId: const IdUid(2, 6156017341759176249), + lastRelationId: const IdUid(0, 0), + lastSequenceId: const IdUid(0, 0), + retiredEntityUids: const [], + retiredIndexUids: const [], + retiredPropertyUids: const [], + retiredRelationUids: const [], + modelVersion: 5, + modelVersionParserMinimum: 5, + version: 1); + + final bindings = { + Connection: EntityDefinition( + model: _entities[0], + toOneRelations: (Connection object) => [], + toManyRelations: (Connection object) => {}, + getId: (Connection object) => object.obId, + setId: (Connection object, int id) { + object.obId = id; + }, + objectToFB: (Connection object, fb.Builder fbb) { + final nameOffset = fbb.writeString(object.name); + final handleOffset = fbb.writeString(object.handle); + final idOffset = fbb.writeString(object.id); + final networkOffset = fbb.writeString(object.network); + final profileUrlOffset = fbb.writeString(object.profileUrl); + final avatarUrlOffset = fbb.writeString(object.avatarUrl); + fbb.startTable(10); + fbb.addInt64(0, object.obId); + fbb.addOffset(1, nameOffset); + fbb.addOffset(2, handleOffset); + fbb.addOffset(3, idOffset); + fbb.addOffset(4, networkOffset); + fbb.addOffset(5, profileUrlOffset); + fbb.addOffset(6, avatarUrlOffset); + fbb.addInt64(7, object.dbStatus); + fbb.addInt64(8, object.lastUpdateTime.millisecondsSinceEpoch); + fbb.finish(fbb.endTable()); + return object.obId; + }, + objectFromFB: (Store store, ByteData fbData) { + final buffer = fb.BufferContext(fbData); + final rootOffset = buffer.derefObject(0); + + final object = Connection( + obId: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0), + name: const fb.StringReader(asciiOptimization: true) + .vTableGet(buffer, rootOffset, 6, ''), + handle: const fb.StringReader(asciiOptimization: true) + .vTableGet(buffer, rootOffset, 8, ''), + id: const fb.StringReader(asciiOptimization: true) + .vTableGet(buffer, rootOffset, 10, ''), + profileUrl: const fb.StringReader(asciiOptimization: true) + .vTableGet(buffer, rootOffset, 14, ''), + network: const fb.StringReader(asciiOptimization: true) + .vTableGet(buffer, rootOffset, 12, ''), + avatarUrl: const fb.StringReader(asciiOptimization: true) + .vTableGet(buffer, rootOffset, 16, ''), + lastUpdateTime: DateTime.fromMillisecondsSinceEpoch( + const fb.Int64Reader().vTableGet(buffer, rootOffset, 20, 0))) + ..dbStatus = + const fb.Int64Reader().vTableGet(buffer, rootOffset, 18, 0); + + return object; + }), + Hashtag: EntityDefinition( + model: _entities[1], + toOneRelations: (Hashtag object) => [], + toManyRelations: (Hashtag object) => {}, + getId: (Hashtag object) => object.id, + setId: (Hashtag object, int id) { + object.id = id; + }, + objectToFB: (Hashtag object, fb.Builder fbb) { + final tagOffset = fbb.writeString(object.tag); + final urlOffset = fbb.writeString(object.url); + fbb.startTable(5); + fbb.addInt64(0, object.id); + fbb.addOffset(1, tagOffset); + fbb.addOffset(2, urlOffset); + fbb.addInt64(3, object.lastUpdateTime.millisecondsSinceEpoch); + fbb.finish(fbb.endTable()); + return object.id; + }, + objectFromFB: (Store store, ByteData fbData) { + final buffer = fb.BufferContext(fbData); + final rootOffset = buffer.derefObject(0); + + final object = Hashtag( + id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0), + tag: const fb.StringReader(asciiOptimization: true) + .vTableGet(buffer, rootOffset, 6, ''), + url: const fb.StringReader(asciiOptimization: true) + .vTableGet(buffer, rootOffset, 8, '')) + ..lastUpdateTime = DateTime.fromMillisecondsSinceEpoch( + const fb.Int64Reader().vTableGet(buffer, rootOffset, 10, 0)); + + return object; + }) + }; + + return ModelDefinition(model, bindings); +} + +/// [Connection] entity fields to define ObjectBox queries. +class Connection_ { + /// see [Connection.obId] + static final obId = + QueryIntegerProperty(_entities[0].properties[0]); + + /// see [Connection.name] + static final name = + QueryStringProperty(_entities[0].properties[1]); + + /// see [Connection.handle] + static final handle = + QueryStringProperty(_entities[0].properties[2]); + + /// see [Connection.id] + static final id = QueryStringProperty(_entities[0].properties[3]); + + /// see [Connection.network] + static final network = + QueryStringProperty(_entities[0].properties[4]); + + /// see [Connection.profileUrl] + static final profileUrl = + QueryStringProperty(_entities[0].properties[5]); + + /// see [Connection.avatarUrl] + static final avatarUrl = + QueryStringProperty(_entities[0].properties[6]); + + /// see [Connection.dbStatus] + static final dbStatus = + QueryIntegerProperty(_entities[0].properties[7]); + + /// see [Connection.lastUpdateTime] + static final lastUpdateTime = + QueryIntegerProperty(_entities[0].properties[8]); +} + +/// [Hashtag] entity fields to define ObjectBox queries. +class Hashtag_ { + /// see [Hashtag.id] + static final id = QueryIntegerProperty(_entities[1].properties[0]); + + /// see [Hashtag.tag] + static final tag = QueryStringProperty(_entities[1].properties[1]); + + /// see [Hashtag.url] + static final url = QueryStringProperty(_entities[1].properties[2]); + + /// see [Hashtag.lastUpdateTime] + static final lastUpdateTime = + QueryIntegerProperty(_entities[1].properties[3]); +} diff --git a/lib/serializers/friendica/connection_friendica_extensions.dart b/lib/serializers/friendica/connection_friendica_extensions.dart index fa3f19a..8440808 100644 --- a/lib/serializers/friendica/connection_friendica_extensions.dart +++ b/lib/serializers/friendica/connection_friendica_extensions.dart @@ -14,7 +14,7 @@ extension ConnectionFriendicaExtensions on Connection { status: status, name: name, id: id, - profileUrl: profileUrl, + profileUrl: profileUrl.toString(), network: network); } } diff --git a/lib/serializers/mastodon/connection_mastodon_extensions.dart b/lib/serializers/mastodon/connection_mastodon_extensions.dart index 95e537d..b135dee 100644 --- a/lib/serializers/mastodon/connection_mastodon_extensions.dart +++ b/lib/serializers/mastodon/connection_mastodon_extensions.dart @@ -23,9 +23,9 @@ extension ConnectionMastodonExtensions on Connection { name: name, id: id, handle: handle, - profileUrl: profileUrl, + profileUrl: profileUrl.toString(), network: network, - avatarUrl: avatar, + avatarUrl: avatar.toString(), ); } } diff --git a/lib/serializers/mastodon/hashtag_mastodon_extensions.dart b/lib/serializers/mastodon/hashtag_mastodon_extensions.dart new file mode 100644 index 0000000..b325584 --- /dev/null +++ b/lib/serializers/mastodon/hashtag_mastodon_extensions.dart @@ -0,0 +1,12 @@ +import '../../models/hashtag.dart'; + +extension HashtagMastodonExtensions on Hashtag { + static Hashtag fromJson(Map json) { + final tag = json['name']; + final url = json['url']; + return Hashtag( + tag: tag, + url: url, + ); + } +} diff --git a/lib/serializers/mastodon/timeline_entry_mastodon_extensions.dart b/lib/serializers/mastodon/timeline_entry_mastodon_extensions.dart index 31e09f6..2522da9 100644 --- a/lib/serializers/mastodon/timeline_entry_mastodon_extensions.dart +++ b/lib/serializers/mastodon/timeline_entry_mastodon_extensions.dart @@ -10,6 +10,7 @@ import '../../services/connections_manager.dart'; import '../../services/hashtag_service.dart'; import '../../utils/dateutils.dart'; import 'connection_mastodon_extensions.dart'; +import 'hashtag_mastodon_extensions.dart'; final _logger = Logger('TimelineEntryMastodonExtensions'); @@ -75,9 +76,9 @@ extension TimelineEntryMastodonExtensions on TimelineEntry { final List? tags = json['tags']; if (tags?.isNotEmpty ?? false) { final tagManager = getIt(); - for (final tag in tags!) { - final tagName = tag['name']; - tagManager.addHashtage(tagName); + for (final tagJson in tags!) { + final tag = HashtagMastodonExtensions.fromJson(tagJson); + tagManager.add(tag); } } diff --git a/lib/services/connections_manager.dart b/lib/services/connections_manager.dart index 9e7ca19..09e7472 100644 --- a/lib/services/connections_manager.dart +++ b/lib/services/connections_manager.dart @@ -1,9 +1,12 @@ +import 'dart:collection'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'package:result_monad/result_monad.dart'; +import '../data/interfaces/connections_repo_intf.dart'; +import '../data/interfaces/groups_repo.intf.dart'; import '../globals.dart'; import '../models/connection.dart'; import '../models/exec_error.dart'; @@ -12,75 +15,28 @@ import 'auth_service.dart'; class ConnectionsManager extends ChangeNotifier { static final _logger = Logger('$ConnectionsManager'); - final _connectionsById = {}; - final _connectionsByName = {}; - final _connectionsByProfileUrl = {}; - final _groupsForConnection = >{}; - final _myGroups = {}; - final _myContacts = []; - var _myContactsInitialized = false; + late final IConnectionsRepo conRepo; + late final IGroupsRepo groupsRepo; - int get length => _connectionsById.length; - - void clearCaches() { - _connectionsById.clear(); - _connectionsByName.clear(); - _connectionsByProfileUrl.clear(); - _groupsForConnection.clear(); - _myGroups.clear(); - _myContacts.clear(); + ConnectionsManager() { + conRepo = getIt(); + groupsRepo = getIt(); } bool addConnection(Connection connection) { - if (_connectionsById.containsKey(connection.id)) { - return false; - } - return updateConnection(connection); + return conRepo.addConnection(connection); } List getKnownUsersByName(String name) { - return _connectionsByName.values.where((it) { - final normalizedHandle = it.handle.toLowerCase(); - final normalizedName = it.name.toLowerCase(); - final normalizedQuery = name.toLowerCase(); - return normalizedHandle.contains(normalizedQuery) || - normalizedName.contains(normalizedQuery); - }).toList(); + return conRepo.getKnownUsersByName(name); } bool updateConnection(Connection connection) { - _connectionsById[connection.id] = connection; - _connectionsByName[connection.name] = connection; - _connectionsByProfileUrl[connection.profileUrl] = connection; - int index = _myContacts.indexWhere((c) => c.id == connection.id); - if (index >= 0) { - _myContacts.removeAt(index); - } - switch (connection.status) { - case ConnectionStatus.youFollowThem: - case ConnectionStatus.theyFollowYou: - case ConnectionStatus.mutual: - if (index > 0) { - _myContacts.insert(index, connection); - } else { - _myContacts.add(connection); - } - break; - default: - break; - } - - return true; + return conRepo.updateConnection(connection); } bool addAllConnections(Iterable newConnections) { - bool result = true; - - for (final connection in newConnections) { - result &= addConnection(connection); - } - - return result; + return conRepo.addAllConnections(newConnections); } Future acceptFollowRequest(Connection connection) async { @@ -179,12 +135,7 @@ class ConnectionsManager extends ChangeNotifier { } List getMyContacts() { - if (!_myContactsInitialized) { - updateAllContacts(); - _myContactsInitialized = true; - } - - return _myContacts.toList(growable: false); + return conRepo.getMyContacts(); } Future updateAllContacts() async { @@ -234,29 +185,34 @@ class ConnectionsManager extends ChangeNotifier { }); } - _myContacts.clear(); - _myContacts.addAll(results.values); addAllConnections(results.values); - _myContacts.sort((c1, c2) => c1.name.compareTo(c2.name)); - _logger.finest('# Contacts:${_myContacts.length}'); + final myContacts = conRepo.getMyContacts().toList(); + myContacts.sort((c1, c2) => c1.name.compareTo(c2.name)); + _logger.finest('# Contacts:${myContacts.length}'); notifyListeners(); } List getMyGroups() { - if (_myGroups.isNotEmpty) { - return _myGroups.toList(growable: false); + final myGroups = groupsRepo.getMyGroups(); + if (myGroups.isEmpty) { + _updateMyGroups(true); } - _updateMyGroups(true); - return []; + + return myGroups; } Result, ExecError> getGroupsForUser(String id) { - if (!_groupsForConnection.containsKey(id)) { - _refreshGroupListData(id, true); - return Result.ok([]); + final result = groupsRepo.getGroupsForUser(id); + if (result.isSuccess) { + return result; } - return Result.ok(_groupsForConnection[id]!); + if (result.isFailure && result.error.type != ErrorType.notFound) { + return result; + } + + _refreshGroupListData(id, true); + return Result.ok(UnmodifiableListView([])); } FutureResult addUserToGroup( @@ -291,37 +247,22 @@ class ConnectionsManager extends ChangeNotifier { return result.execErrorCast(); } - Result getById(String id) { - final result = _connectionsById[id]; - if (result == null) { - return Result.error('$id not found'); - } - if (result.status == ConnectionStatus.unknown) { - _refreshConnection(result, true); - } - return Result.ok(result); + Result getById(String id) { + return conRepo.getById(id).andThenSuccess((c) { + if (c.status == ConnectionStatus.unknown) { + _refreshConnection(c, true); + } + return c; + }).execErrorCast(); } - Result getByName(String name) { - final result = _connectionsByName[name]; - if (result == null) { - Result.error('$name not found'); - } - if (result!.status == ConnectionStatus.unknown) { - _refreshConnection(result, true); - } - return Result.ok(result); - } - - Result getByProfileUrl(Uri url) { - final result = _connectionsByProfileUrl[url]; - if (result == null) { - Result.error('$url not found'); - } - if (result!.status == ConnectionStatus.unknown) { - _refreshConnection(result, true); - } - return Result.ok(result); + Result getByName(String name) { + return conRepo.getByName(name).andThenSuccess((c) { + if (c.status == ConnectionStatus.unknown) { + _refreshConnection(c, true); + } + return c; + }).execErrorCast(); } Future fullRefresh(Connection connection) async { @@ -337,8 +278,8 @@ class ConnectionsManager extends ChangeNotifier { .currentClient .andThenAsync((client) => client.getMemberGroupsForConnection(id)) .match( - onSuccess: (lists) { - _groupsForConnection[id] = lists; + onSuccess: (groups) { + groupsRepo.updateConnectionGroupData(id, groups); if (withNotification) { notifyListeners(); } @@ -376,8 +317,8 @@ class ConnectionsManager extends ChangeNotifier { .match( onSuccess: (groups) { _logger.finest('Got updated groups:${groups.map((e) => e.name)}'); - _myGroups.clear(); - _myGroups.addAll(groups); + groupsRepo.clearGroups(); + groupsRepo.addAllGroups(groups); if (withNotification) { notifyListeners(); } diff --git a/lib/services/hashtag_service.dart b/lib/services/hashtag_service.dart index 70fec9a..a9c6ea9 100644 --- a/lib/services/hashtag_service.dart +++ b/lib/services/hashtag_service.dart @@ -1,21 +1,22 @@ import 'package:flutter/foundation.dart'; -class HashtagService extends ChangeNotifier { - final _hashTags = {}; +import '../data/interfaces/hashtag_repo_intf.dart'; +import '../globals.dart'; +import '../models/hashtag.dart'; - void clear() { - _hashTags.clear(); - notifyListeners(); +class HashtagService extends ChangeNotifier { + late final IHashtagRepo repo; + + HashtagService() { + repo = getIt(); } - void addHashtage(String hashtag) { - _hashTags.add(hashtag); + void add(Hashtag tag) { + repo.add(tag); notifyListeners(); } List getMatchingHashTags(String searchString) { - return _hashTags - .where((tag) => tag.toLowerCase().contains(searchString.toLowerCase())) - .toList(); + return repo.getMatchingHashTags(searchString); } } diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 241dc8b..c1e3da3 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -8,6 +8,7 @@ #include #include +#include #include void fl_register_plugins(FlPluginRegistry* registry) { @@ -17,6 +18,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); + g_autoptr(FlPluginRegistrar) objectbox_flutter_libs_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "ObjectboxFlutterLibsPlugin"); + objectbox_flutter_libs_plugin_register_with_registrar(objectbox_flutter_libs_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index fd2ab75..28dcfea 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST desktop_window flutter_secure_storage_linux + objectbox_flutter_libs url_launcher_linux ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 808f3fb..a3accbe 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,6 +7,7 @@ import Foundation import desktop_window import flutter_secure_storage_macos +import objectbox_flutter_libs import path_provider_macos import shared_preferences_foundation import sqflite @@ -15,6 +16,7 @@ import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DesktopWindowPlugin.register(with: registry.registrar(forPlugin: "DesktopWindowPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) + ObjectboxFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "ObjectboxFlutterLibsPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 4b4a807..fb1b443 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -7,6 +7,10 @@ PODS: - FMDB (2.7.5): - FMDB/standard (= 2.7.5) - FMDB/standard (2.7.5) + - ObjectBox (1.8.1-rc) + - objectbox_flutter_libs (0.0.1): + - FlutterMacOS + - ObjectBox (= 1.8.1-rc) - path_provider_macos (0.0.1): - FlutterMacOS - shared_preferences_foundation (0.0.1): @@ -22,6 +26,7 @@ DEPENDENCIES: - desktop_window (from `Flutter/ephemeral/.symlinks/plugins/desktop_window/macos`) - flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`) - FlutterMacOS (from `Flutter/ephemeral`) + - objectbox_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/objectbox_flutter_libs/macos`) - path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`) - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/macos`) - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`) @@ -30,6 +35,7 @@ DEPENDENCIES: SPEC REPOS: trunk: - FMDB + - ObjectBox EXTERNAL SOURCES: desktop_window: @@ -38,6 +44,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos FlutterMacOS: :path: Flutter/ephemeral + objectbox_flutter_libs: + :path: Flutter/ephemeral/.symlinks/plugins/objectbox_flutter_libs/macos path_provider_macos: :path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos shared_preferences_foundation: @@ -52,6 +60,8 @@ SPEC CHECKSUMS: flutter_secure_storage_macos: 75c8cadfdba05ca007c0fa4ea0c16e5cf85e521b FlutterMacOS: ae6af50a8ea7d6103d888583d46bd8328a7e9811 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a + ObjectBox: 7615cc462f2976cb45127d301a17cb473edf3f9e + objectbox_flutter_libs: 6c6e858d1df4a7cd5cb664c2108e6fa0b412be38 path_provider_macos: 05fb0ef0cedf3e5bd179b9e41a638682b37133ea shared_preferences_foundation: 297b3ebca31b34ec92be11acd7fb0ba932c822ca sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements index 225aa48..ef5d0fe 100644 --- a/macos/Runner/Release.entitlements +++ b/macos/Runner/Release.entitlements @@ -8,5 +8,9 @@ keychain-access-groups + com.apple.security.application-groups + + friendica_portal + diff --git a/macos/Runner/RunnerDebug.entitlements b/macos/Runner/RunnerDebug.entitlements index 1fbcb4e..2a0b6a2 100644 --- a/macos/Runner/RunnerDebug.entitlements +++ b/macos/Runner/RunnerDebug.entitlements @@ -12,5 +12,9 @@ keychain-access-groups + com.apple.security.application-groups + + friendica_portal + diff --git a/pubspec.lock b/pubspec.lock index 50f7094..b5c14b2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,20 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + url: "https://pub.dartlang.org" + source: hosted + version: "50.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + url: "https://pub.dartlang.org" + source: hosted + version: "5.2.0" archive: dependency: transitive description: @@ -29,6 +43,62 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + build: + dependency: transitive + description: + name: build + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.1" + build_config: + dependency: transitive + description: + name: build_config + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + build_runner: + dependency: "direct dev" + description: + name: build_runner + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.3" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + url: "https://pub.dartlang.org" + source: hosted + version: "7.2.7" + built_collection: + dependency: transitive + description: + name: built_collection + url: "https://pub.dartlang.org" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + url: "https://pub.dartlang.org" + source: hosted + version: "8.4.3" cached_network_image: dependency: "direct main" description: @@ -57,6 +127,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" clock: dependency: transitive description: @@ -64,6 +141,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + url: "https://pub.dartlang.org" + source: hosted + version: "4.4.0" collection: dependency: transitive description: @@ -92,6 +176,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.2" + cryptography: + dependency: transitive + description: + name: cryptography + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" csslib: dependency: transitive description: @@ -106,6 +197,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.5" + dart_style: + dependency: transitive + description: + name: dart_style + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.4" desktop_window: dependency: "direct main" description: @@ -148,6 +246,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "5.2.5" + fixnum: + dependency: transitive + description: + name: fixnum + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + flat_buffers: + dependency: transitive + description: + name: flat_buffers + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" flutter: dependency: "direct main" description: flutter @@ -261,6 +373,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.9.1" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.0" functional_listener: dependency: transitive description: @@ -289,6 +408,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.1.4+2" + glob: + dependency: transitive + description: + name: glob + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" go_router: dependency: "direct main" description: @@ -296,6 +422,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "5.2.4" + graphs: + dependency: transitive + description: + name: graphs + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" html: dependency: transitive description: @@ -310,6 +443,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.13.5" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.1" http_parser: dependency: transitive description: @@ -359,6 +499,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.6.2" + io: + dependency: transitive + description: + name: io + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" js: dependency: transitive description: @@ -366,6 +513,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.6.4" + json_annotation: + dependency: transitive + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "4.7.0" lints: dependency: transitive description: @@ -415,6 +569,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.4.1" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" multi_trigger_autocomplete: dependency: "direct main" description: @@ -436,6 +597,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.0.1" + objectbox: + dependency: "direct main" + description: + name: objectbox + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.1" + objectbox_flutter_libs: + dependency: "direct main" + description: + name: objectbox_flutter_libs + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.1" + objectbox_generator: + dependency: "direct dev" + description: + name: objectbox_generator + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.1" octo_image: dependency: transitive description: @@ -443,6 +625,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.2" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" path: dependency: "direct main" description: @@ -451,7 +640,7 @@ packages: source: hosted version: "1.8.2" path_provider: - dependency: transitive + dependency: "direct main" description: name: path_provider url: "https://pub.dartlang.org" @@ -534,6 +723,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.6.2" + pool: + dependency: transitive + description: + name: pool + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.1" process: dependency: transitive description: @@ -548,6 +744,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "6.0.5" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" result_monad: dependency: "direct main" description: @@ -618,11 +828,32 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.2" + shelf: + dependency: transitive + description: + name: shelf + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.0" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.6" source_span: dependency: transitive description: @@ -644,6 +875,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.4.1" + sqlite3: + dependency: "direct main" + description: + name: sqlite3 + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.1" stack_trace: dependency: transitive description: @@ -658,6 +896,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + stream_transform: + dependency: transitive + description: + name: stream_transform + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" string_scanner: dependency: transitive description: @@ -700,6 +945,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.9.17" + timing: + dependency: transitive + description: + name: timing + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" typed_data: dependency: transitive description: @@ -812,6 +1064,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.13" + watcher: + dependency: transitive + description: + name: watcher + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.0" win32: dependency: transitive description: @@ -833,6 +1099,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "6.1.0" + yaml: + dependency: transitive + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.1" sdks: dart: ">=2.18.2 <3.0.0" flutter: ">=3.3.0" diff --git a/pubspec.yaml b/pubspec.yaml index 0ff792c..05b221d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,15 +34,21 @@ dependencies: result_monad: ^2.0.2 scrollable_positioned_list: ^0.3.5 shared_preferences: ^2.0.15 + sqlite3: ^1.9.1 time_machine: ^0.9.17 url_launcher: ^6.1.6 uuid: ^3.0.6 video_player: ^2.4.10 + objectbox: ^1.7.1 + objectbox_flutter_libs: ^1.7.1 + path_provider: ^2.0.11 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^2.0.0 + build_runner: ^2.3.3 + objectbox_generator: ^1.7.1 flutter: uses-material-design: true diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index f50c00b..4c170c8 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -8,6 +8,7 @@ #include #include +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { @@ -15,6 +16,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("DesktopWindowPlugin")); FlutterSecureStorageWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); + ObjectboxFlutterLibsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ObjectboxFlutterLibsPlugin")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index ddb2474..5a12988 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST desktop_window flutter_secure_storage_windows + objectbox_flutter_libs url_launcher_windows )