From 93a184802de0e860f3c7d9d32b85a5ae0e9a9b8f Mon Sep 17 00:00:00 2001 From: Jacques Date: Mon, 29 Jan 2024 11:52:23 +0100 Subject: [PATCH 01/10] fix: Fix comments like readme and one service instead of multiple --- README.md | 5 ++ .../example/lib/apps/go_router/app.dart | 7 +- .../example/lib/apps/navigator/app.dart | 10 ++- .../example/lib/apps/widgets/app.dart | 34 ++------ .../example/lib/config/config.dart | 6 +- .../flutter_timeline_navigator_userstory.dart | 2 +- .../lib/src/flutter_timeline_userstory.dart | 4 +- .../src/models/timeline_configuration.dart | 3 - packages/flutter_timeline/pubspec.yaml | 18 +++-- .../service/firebase_timeline_service.dart | 3 +- .../flutter_timeline_firebase/pubspec.yaml | 9 ++- .../lib/flutter_timeline_interface.dart | 1 + .../lib/src/services/filter_service.dart | 2 +- .../src/services/timeline_post_service.dart | 31 +++++++ .../lib/src/services/timeline_service.dart | 37 +++------ .../lib/flutter_timeline_view.dart | 1 + .../timeline_post_creation_screen.dart | 2 +- .../lib/src/screens/timeline_post_screen.dart | 23 +++--- .../lib/src/screens/timeline_screen.dart | 61 ++++++++++---- .../lib/src/services/local_post_service.dart} | 80 +++++++++++++------ .../lib/src/widgets/timeline_post_widget.dart | 10 ++- packages/flutter_timeline_view/pubspec.yaml | 9 ++- 22 files changed, 211 insertions(+), 147 deletions(-) create mode 100644 packages/flutter_timeline_interface/lib/src/services/timeline_post_service.dart rename packages/{flutter_timeline/example/lib/services/timeline_service.dart => flutter_timeline_view/lib/src/services/local_post_service.dart} (68%) diff --git a/README.md b/README.md index 83062cf..2ea41d3 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,11 @@ If you are going to use Firebase as the back-end of the Timeline, you should als path: packages/flutter_timeline_firebase ``` +Add the following code in your `main` function, before the runApp(). +``` + initializeDateFormatting(); +``` + ## How to use To use the module within your Flutter-application with predefined `Go_router` routes you should add the following: diff --git a/packages/flutter_timeline/example/lib/apps/go_router/app.dart b/packages/flutter_timeline/example/lib/apps/go_router/app.dart index ef18fef..a4ccbe9 100644 --- a/packages/flutter_timeline/example/lib/apps/go_router/app.dart +++ b/packages/flutter_timeline/example/lib/apps/go_router/app.dart @@ -1,13 +1,12 @@ import 'package:example/config/config.dart'; -import 'package:example/services/timeline_service.dart'; import 'package:flutter/material.dart'; import 'package:flutter_timeline/flutter_timeline.dart'; import 'package:go_router/go_router.dart'; List getTimelineRoutes() => getTimelineStoryRoutes( - getConfig( - TestTimelineService(), - ), + getConfig(TimelineService( + postService: LocalTimelinePostService(), + )), ); final _router = GoRouter( diff --git a/packages/flutter_timeline/example/lib/apps/navigator/app.dart b/packages/flutter_timeline/example/lib/apps/navigator/app.dart index 2473d64..36e3c44 100644 --- a/packages/flutter_timeline/example/lib/apps/navigator/app.dart +++ b/packages/flutter_timeline/example/lib/apps/navigator/app.dart @@ -1,5 +1,4 @@ import 'package:example/config/config.dart'; -import 'package:example/services/timeline_service.dart'; import 'package:flutter/material.dart'; import 'package:flutter_timeline/flutter_timeline.dart'; @@ -32,7 +31,8 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { - var timelineService = TestTimelineService(); + var timelineService = + TimelineService(postService: LocalTimelinePostService()); var timelineOptions = options; @override @@ -64,7 +64,11 @@ class _MyHomePageState extends State { ], ), body: SafeArea( - child: timeLineNavigatorUserStory(getConfig(timelineService), context), + child: timeLineNavigatorUserStory( + getConfig( + timelineService, + ), + context), ), ); } diff --git a/packages/flutter_timeline/example/lib/apps/widgets/app.dart b/packages/flutter_timeline/example/lib/apps/widgets/app.dart index 8367742..2292e17 100644 --- a/packages/flutter_timeline/example/lib/apps/widgets/app.dart +++ b/packages/flutter_timeline/example/lib/apps/widgets/app.dart @@ -1,5 +1,5 @@ import 'package:example/config/config.dart'; -import 'package:example/services/timeline_service.dart'; + import 'package:flutter/material.dart'; import 'package:flutter_timeline/flutter_timeline.dart'; @@ -32,7 +32,8 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { - var timelineService = TestTimelineService(); + var timelineService = + TimelineService(postService: LocalTimelinePostService()); var timelineOptions = options; @override @@ -42,6 +43,7 @@ class _MyHomePageState extends State { mainAxisAlignment: MainAxisAlignment.end, children: [ FloatingActionButton( + heroTag: 'btn1', onPressed: () { createPost(context, timelineService, timelineOptions); }, @@ -54,6 +56,7 @@ class _MyHomePageState extends State { height: 8, ), FloatingActionButton( + heroTag: 'btn2', onPressed: () { generatePost(timelineService); }, @@ -64,31 +67,8 @@ class _MyHomePageState extends State { ), ], ), - body: SafeArea( - child: TimelineScreen( - userId: 'test_user', - service: timelineService, - options: timelineOptions, - onPostTap: (post) async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => Scaffold( - body: TimelinePostScreen( - userId: 'test_user', - service: timelineService, - options: timelineOptions, - post: post, - onPostDelete: () { - timelineService.deletePost(post); - Navigator.of(context).pop(); - }, - ), - ), - ), - ); - }, - ), + body: const SafeArea( + child: TimelineScreen(), ), ); } diff --git a/packages/flutter_timeline/example/lib/config/config.dart b/packages/flutter_timeline/example/lib/config/config.dart index 734c1eb..0728bdf 100644 --- a/packages/flutter_timeline/example/lib/config/config.dart +++ b/packages/flutter_timeline/example/lib/config/config.dart @@ -1,11 +1,9 @@ -import 'package:example/apps/widgets/screens/post_screen.dart'; import 'package:flutter/material.dart'; import 'package:flutter_timeline/flutter_timeline.dart'; TimelineUserStoryConfiguration getConfig(TimelineService service) { return TimelineUserStoryConfiguration( service: service, - userService: TestUserService(), userId: 'test_user', optionsBuilder: (context) => options); } @@ -56,9 +54,9 @@ void createPost(BuildContext context, TimelineService service, } void generatePost(TimelineService service) { - var amountOfPosts = service.getPosts(null).length; + var amountOfPosts = service.postService.getPosts(null).length; - service.createPost( + service.postService.createPost( TimelinePost( id: 'Post$amountOfPosts', creatorId: 'test_user', diff --git a/packages/flutter_timeline/lib/src/flutter_timeline_navigator_userstory.dart b/packages/flutter_timeline/lib/src/flutter_timeline_navigator_userstory.dart index 468b521..341b73a 100644 --- a/packages/flutter_timeline/lib/src/flutter_timeline_navigator_userstory.dart +++ b/packages/flutter_timeline/lib/src/flutter_timeline_navigator_userstory.dart @@ -46,6 +46,6 @@ Widget _postDetailScreenRoute( post: post, onPostDelete: () async { configuration.onPostDelete?.call(context, post) ?? - await configuration.service.deletePost(post); + await configuration.service.postService.deletePost(post); }, ); diff --git a/packages/flutter_timeline/lib/src/flutter_timeline_userstory.dart b/packages/flutter_timeline/lib/src/flutter_timeline_userstory.dart index cdd4ba5..1609338 100644 --- a/packages/flutter_timeline/lib/src/flutter_timeline_userstory.dart +++ b/packages/flutter_timeline/lib/src/flutter_timeline_userstory.dart @@ -46,8 +46,8 @@ List getTimelineStoryRoutes( GoRoute( path: TimelineUserStoryRoutes.timelineView, pageBuilder: (context, state) { - var post = - configuration.service.getPost(state.pathParameters['post']!)!; + var post = configuration.service.postService + .getPost(state.pathParameters['post']!)!; var timelinePostWidget = TimelinePostScreen( userId: configuration.userId, diff --git a/packages/flutter_timeline/lib/src/models/timeline_configuration.dart b/packages/flutter_timeline/lib/src/models/timeline_configuration.dart index 8708d3b..02718e6 100644 --- a/packages/flutter_timeline/lib/src/models/timeline_configuration.dart +++ b/packages/flutter_timeline/lib/src/models/timeline_configuration.dart @@ -11,7 +11,6 @@ class TimelineUserStoryConfiguration { const TimelineUserStoryConfiguration({ required this.userId, required this.service, - required this.userService, required this.optionsBuilder, this.openPageBuilder, this.onPostTap, @@ -25,8 +24,6 @@ class TimelineUserStoryConfiguration { final TimelineService service; - final TimelineUserService userService; - final TimelineOptions Function(BuildContext context) optionsBuilder; final Function(BuildContext context, String userId)? onUserTap; diff --git a/packages/flutter_timeline/pubspec.yaml b/packages/flutter_timeline/pubspec.yaml index 83e93f5..354e81e 100644 --- a/packages/flutter_timeline/pubspec.yaml +++ b/packages/flutter_timeline/pubspec.yaml @@ -16,16 +16,18 @@ dependencies: go_router: any flutter_timeline_view: - git: - url: https://github.com/Iconica-Development/flutter_timeline - path: packages/flutter_timeline_view - ref: 2.0.0 + path: ../flutter_timeline_view + # git: + # url: https://github.com/Iconica-Development/flutter_timeline + # path: packages/flutter_timeline_view + # ref: 2.0.0 flutter_timeline_interface: - git: - url: https://github.com/Iconica-Development/flutter_timeline - path: packages/flutter_timeline_interface - ref: 2.0.0 + path: ../flutter_timeline_interface + # git: + # url: https://github.com/Iconica-Development/flutter_timeline + # path: packages/flutter_timeline_view + # ref: 2.0.0 dev_dependencies: flutter_lints: ^2.0.0 diff --git a/packages/flutter_timeline_firebase/lib/src/service/firebase_timeline_service.dart b/packages/flutter_timeline_firebase/lib/src/service/firebase_timeline_service.dart index b74ff5d..eef4c34 100644 --- a/packages/flutter_timeline_firebase/lib/src/service/firebase_timeline_service.dart +++ b/packages/flutter_timeline_firebase/lib/src/service/firebase_timeline_service.dart @@ -13,7 +13,8 @@ import 'package:flutter_timeline_firebase/src/models/firebase_user_document.dart import 'package:flutter_timeline_interface/flutter_timeline_interface.dart'; import 'package:uuid/uuid.dart'; -class FirebaseTimelineService extends TimelineService with TimelineUserService { +class FirebaseTimelineService extends TimelinePostService + with TimelineUserService { FirebaseTimelineService({ required TimelineUserService userService, FirebaseApp? app, diff --git a/packages/flutter_timeline_firebase/pubspec.yaml b/packages/flutter_timeline_firebase/pubspec.yaml index cefbac5..00d6428 100644 --- a/packages/flutter_timeline_firebase/pubspec.yaml +++ b/packages/flutter_timeline_firebase/pubspec.yaml @@ -20,10 +20,11 @@ dependencies: uuid: ^4.2.1 flutter_timeline_interface: - git: - url: https://github.com/Iconica-Development/flutter_timeline - path: packages/flutter_timeline_interface - ref: 2.0.0 + path: ../flutter_timeline_interface + # git: + # url: https://github.com/Iconica-Development/flutter_timeline + # path: packages/flutter_timeline_interface + # ref: 2.0.0 dev_dependencies: flutter_lints: ^2.0.0 diff --git a/packages/flutter_timeline_interface/lib/flutter_timeline_interface.dart b/packages/flutter_timeline_interface/lib/flutter_timeline_interface.dart index d0da25d..8fb0bf9 100644 --- a/packages/flutter_timeline_interface/lib/flutter_timeline_interface.dart +++ b/packages/flutter_timeline_interface/lib/flutter_timeline_interface.dart @@ -9,5 +9,6 @@ export 'src/model/timeline_post.dart'; export 'src/model/timeline_poster.dart'; export 'src/model/timeline_reaction.dart'; export 'src/services/filter_service.dart'; +export 'src/services/timeline_post_service.dart'; export 'src/services/timeline_service.dart'; export 'src/services/user_service.dart'; diff --git a/packages/flutter_timeline_interface/lib/src/services/filter_service.dart b/packages/flutter_timeline_interface/lib/src/services/filter_service.dart index dc3441f..029dbec 100644 --- a/packages/flutter_timeline_interface/lib/src/services/filter_service.dart +++ b/packages/flutter_timeline_interface/lib/src/services/filter_service.dart @@ -4,7 +4,7 @@ import 'package:flutter_timeline_interface/flutter_timeline_interface.dart'; -mixin TimelineFilterService on TimelineService { +mixin TimelineFilterService on TimelinePostService { List filterPosts( String filterWord, Map options, diff --git a/packages/flutter_timeline_interface/lib/src/services/timeline_post_service.dart b/packages/flutter_timeline_interface/lib/src/services/timeline_post_service.dart new file mode 100644 index 0000000..4933c4e --- /dev/null +++ b/packages/flutter_timeline_interface/lib/src/services/timeline_post_service.dart @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2023 Iconica +// +// SPDX-License-Identifier: BSD-3-Clause + +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:flutter_timeline_interface/src/model/timeline_post.dart'; +import 'package:flutter_timeline_interface/src/model/timeline_reaction.dart'; + +abstract class TimelinePostService with ChangeNotifier { + List posts = []; + + Future deletePost(TimelinePost post); + Future deletePostReaction(TimelinePost post, String reactionId); + Future createPost(TimelinePost post); + Future> fetchPosts(String? category); + Future fetchPost(TimelinePost post); + Future> fetchPostsPaginated(String? category, int limit); + TimelinePost? getPost(String postId); + List getPosts(String? category); + Future> refreshPosts(String? category); + Future fetchPostDetails(TimelinePost post); + Future reactToPost( + TimelinePost post, + TimelinePostReaction reaction, { + Uint8List image, + }); + Future likePost(String userId, TimelinePost post); + Future unlikePost(String userId, TimelinePost post); +} diff --git a/packages/flutter_timeline_interface/lib/src/services/timeline_service.dart b/packages/flutter_timeline_interface/lib/src/services/timeline_service.dart index 6f4907d..4e06b2c 100644 --- a/packages/flutter_timeline_interface/lib/src/services/timeline_service.dart +++ b/packages/flutter_timeline_interface/lib/src/services/timeline_service.dart @@ -1,31 +1,12 @@ -// SPDX-FileCopyrightText: 2023 Iconica -// -// SPDX-License-Identifier: BSD-3-Clause +import 'package:flutter_timeline_interface/src/services/timeline_post_service.dart'; +import 'package:flutter_timeline_interface/src/services/user_service.dart'; -import 'dart:typed_data'; - -import 'package:flutter/material.dart'; -import 'package:flutter_timeline_interface/src/model/timeline_post.dart'; -import 'package:flutter_timeline_interface/src/model/timeline_reaction.dart'; - -abstract class TimelineService with ChangeNotifier { - List posts = []; - - Future deletePost(TimelinePost post); - Future deletePostReaction(TimelinePost post, String reactionId); - Future createPost(TimelinePost post); - Future> fetchPosts(String? category); - Future fetchPost(TimelinePost post); - Future> fetchPostsPaginated(String? category, int limit); - TimelinePost? getPost(String postId); - List getPosts(String? category); - Future> refreshPosts(String? category); - Future fetchPostDetails(TimelinePost post); - Future reactToPost( - TimelinePost post, - TimelinePostReaction reaction, { - Uint8List image, +class TimelineService { + TimelineService({ + required this.postService, + this.userService, }); - Future likePost(String userId, TimelinePost post); - Future unlikePost(String userId, TimelinePost post); + + final TimelinePostService postService; + final TimelineUserService? userService; } diff --git a/packages/flutter_timeline_view/lib/flutter_timeline_view.dart b/packages/flutter_timeline_view/lib/flutter_timeline_view.dart index 88cdb20..1ef76bd 100644 --- a/packages/flutter_timeline_view/lib/flutter_timeline_view.dart +++ b/packages/flutter_timeline_view/lib/flutter_timeline_view.dart @@ -12,6 +12,7 @@ export 'src/screens/timeline_post_creation_screen.dart'; export 'src/screens/timeline_post_screen.dart'; export 'src/screens/timeline_screen.dart'; export 'src/screens/timeline_selection_screen.dart'; +export 'src/services/local_post_service.dart'; export 'src/widgets/category_selector.dart'; export 'src/widgets/category_selector_button.dart'; export 'src/widgets/timeline_post_widget.dart'; diff --git a/packages/flutter_timeline_view/lib/src/screens/timeline_post_creation_screen.dart b/packages/flutter_timeline_view/lib/src/screens/timeline_post_creation_screen.dart index 2238f4e..2a7c409 100644 --- a/packages/flutter_timeline_view/lib/src/screens/timeline_post_creation_screen.dart +++ b/packages/flutter_timeline_view/lib/src/screens/timeline_post_creation_screen.dart @@ -82,7 +82,7 @@ class _TimelinePostCreationScreenState reactionEnabled: allowComments, image: image, ); - var newPost = await widget.service.createPost(post); + var newPost = await widget.service.postService.createPost(post); widget.onPostCreated.call(newPost); } diff --git a/packages/flutter_timeline_view/lib/src/screens/timeline_post_screen.dart b/packages/flutter_timeline_view/lib/src/screens/timeline_post_screen.dart index 467da65..1c810e9 100644 --- a/packages/flutter_timeline_view/lib/src/screens/timeline_post_screen.dart +++ b/packages/flutter_timeline_view/lib/src/screens/timeline_post_screen.dart @@ -106,7 +106,8 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> { Future loadPostDetails() async { try { - var loadedPost = await widget.service.fetchPostDetails(widget.post); + var loadedPost = + await widget.service.postService.fetchPostDetails(widget.post); setState(() { post = loadedPost; isLoading = false; @@ -157,8 +158,8 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> { RefreshIndicator( onRefresh: () async { updatePost( - await widget.service.fetchPostDetails( - await widget.service.fetchPost( + await widget.service.postService.fetchPostDetails( + await widget.service.postService.fetchPost( post, ), ), @@ -269,12 +270,14 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> { late TimelinePost result; if (!liked) { - result = await widget.service.likePost( + result = + await widget.service.postService.likePost( userId, post, ); } else { - result = await widget.service.unlikePost( + result = await widget.service.postService + .unlikePost( userId, post, ); @@ -303,7 +306,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> { InkWell( onTap: () async { updatePost( - await widget.service.unlikePost( + await widget.service.postService.unlikePost( widget.userId, post, ), @@ -322,7 +325,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> { InkWell( onTap: () async { updatePost( - await widget.service.likePost( + await widget.service.postService.likePost( widget.userId, post, ), @@ -450,7 +453,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> { if (value == 'delete') { // Call service to delete reaction updatePost( - await widget.service + await widget.service.postService .deletePostReaction(post, reaction.id), ); } @@ -568,7 +571,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> { ); if (result != null) { updatePost( - await widget.service.reactToPost( + await widget.service.postService.reactToPost( post, TimelinePostReaction( id: '', @@ -582,7 +585,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> { } }, onReactionSubmit: (reaction) async => updatePost( - await widget.service.reactToPost( + await widget.service.postService.reactToPost( post, TimelinePostReaction( id: '', diff --git a/packages/flutter_timeline_view/lib/src/screens/timeline_screen.dart b/packages/flutter_timeline_view/lib/src/screens/timeline_screen.dart index d367d87..30c6ac0 100644 --- a/packages/flutter_timeline_view/lib/src/screens/timeline_screen.dart +++ b/packages/flutter_timeline_view/lib/src/screens/timeline_screen.dart @@ -10,10 +10,10 @@ import 'package:flutter_timeline_view/flutter_timeline_view.dart'; class TimelineScreen extends StatefulWidget { const TimelineScreen({ - required this.userId, - required this.service, - required this.options, - required this.onPostTap, + this.userId = 'test_user', + this.service, + this.options = const TimelineOptions(), + this.onPostTap, this.scrollController, this.onUserTap, this.posts, @@ -27,7 +27,7 @@ class TimelineScreen extends StatefulWidget { final String userId; /// The service to use for fetching and manipulating posts - final TimelineService service; + final TimelineService? service; /// All the configuration options for the timelinescreens and widgets final TimelineOptions options; @@ -43,7 +43,7 @@ class TimelineScreen extends StatefulWidget { final List? posts; /// Called when a post is tapped - final Function(TimelinePost) onPostTap; + final Function(TimelinePost)? onPostTap; /// If this is not null, the user can tap on the user avatar or name final Function(String userId)? onUserTap; @@ -63,7 +63,10 @@ class _TimelineScreenState extends State { late var textFieldController = TextEditingController( text: widget.options.filterOptions.initialFilterWord, ); - late var service = widget.service; + late var service = widget.service ?? + TimelineService( + postService: LocalTimelinePostService(), + ); bool isLoading = true; @@ -86,9 +89,9 @@ class _TimelineScreenState extends State { // Build the list of posts return ListenableBuilder( - listenable: service, + listenable: service.postService, builder: (context, _) { - var posts = widget.posts ?? service.getPosts(category); + var posts = widget.posts ?? service.postService.getPosts(category); if (widget.filterEnabled && filterWord != null) { if (service is TimelineFilterService?) { @@ -203,17 +206,41 @@ class _TimelineScreenState extends State { padding: widget.options.postPadding, child: widget.postWidgetBuilder?.call(post) ?? TimelinePostWidget( - service: widget.service, + service: service, userId: widget.userId, options: widget.options, post: post, - onTap: () => widget.onPostTap(post), - onTapLike: () async => - service.likePost(widget.userId, post), - onTapUnlike: () async => - service.unlikePost(widget.userId, post), + onTap: () async { + if (widget.onPostTap != null) { + widget.onPostTap!.call(post); + + return; + } + + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => Scaffold( + body: TimelinePostScreen( + userId: 'test_user', + service: service, + options: widget.options, + post: post, + onPostDelete: () { + service.postService.deletePost(post); + Navigator.of(context).pop(); + }, + ), + ), + ), + ); + }, + onTapLike: () async => service.postService + .likePost(widget.userId, post), + onTapUnlike: () async => service.postService + .unlikePost(widget.userId, post), onPostDelete: () async => - service.deletePost(post), + service.postService.deletePost(post), onUserTap: widget.onUserTap, ), ), @@ -246,7 +273,7 @@ class _TimelineScreenState extends State { Future loadPosts() async { if (widget.posts != null) return; try { - await service.fetchPosts(category); + await service.postService.fetchPosts(category); setState(() { isLoading = false; }); diff --git a/packages/flutter_timeline/example/lib/services/timeline_service.dart b/packages/flutter_timeline_view/lib/src/services/local_post_service.dart similarity index 68% rename from packages/flutter_timeline/example/lib/services/timeline_service.dart rename to packages/flutter_timeline_view/lib/src/services/local_post_service.dart index 3788e41..3989df9 100644 --- a/packages/flutter_timeline/example/lib/services/timeline_service.dart +++ b/packages/flutter_timeline_view/lib/src/services/local_post_service.dart @@ -5,12 +5,11 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; -import 'package:flutter_timeline/flutter_timeline.dart'; +import 'package:flutter_timeline_interface/flutter_timeline_interface.dart'; -// ignore: depend_on_referenced_packages -import 'package:uuid/uuid.dart'; - -class TestTimelineService with ChangeNotifier implements TimelineService { +class LocalTimelinePostService + with ChangeNotifier + implements TimelinePostService { @override List posts = []; @@ -61,8 +60,11 @@ class TestTimelineService with ChangeNotifier implements TimelineService { var reactions = post.reactions ?? []; var updatedReactions = []; for (var reaction in reactions) { - updatedReactions.add(reaction.copyWith( - creator: const TimelinePosterUserModel(userId: 'test_user'))); + updatedReactions.add( + reaction.copyWith( + creator: const TimelinePosterUserModel(userId: 'test_user'), + ), + ); } var updatedPost = post.copyWith(reactions: updatedReactions); posts = posts.map((p) => (p.id == post.id) ? updatedPost : p).toList(); @@ -150,10 +152,12 @@ class TestTimelineService with ChangeNotifier implements TimelineService { TimelinePostReaction reaction, { Uint8List? image, }) async { - var reactionId = const Uuid().v4(); + var reactionId = DateTime.now().millisecondsSinceEpoch.toString(); + var updatedReaction = reaction.copyWith( - id: reactionId, - creator: const TimelinePosterUserModel(userId: 'test_user')); + id: reactionId, + creator: const TimelinePosterUserModel(userId: 'test_user'), + ); var updatedPost = post.copyWith( reaction: post.reaction + 1, @@ -169,19 +173,45 @@ class TestTimelineService with ChangeNotifier implements TimelineService { return updatedPost; } - List getMockedPosts() { - return [ - TimelinePost( - id: 'Post0', - creatorId: 'test_user', - title: 'Post 0', - category: null, - content: "Post 0 content", - likes: 0, - reaction: 0, - createdAt: DateTime.now(), - reactionEnabled: false, - ) - ]; - } + List getMockedPosts() => [ + TimelinePost( + id: 'Post0', + creatorId: 'test_user', + title: 'Post 0', + category: null, + content: 'Standard post without image made by the current user', + likes: 0, + reaction: 0, + createdAt: DateTime.now(), + reactionEnabled: false, + ), + TimelinePost( + id: 'Post1', + creatorId: 'test_user2', + title: 'Post 1', + category: null, + content: 'Standard post with image made by a different user and ' + 'reactions enabled', + likes: 0, + reaction: 0, + createdAt: DateTime.now(), + reactionEnabled: false, + imageUrl: + 'https://s3-eu-west-1.amazonaws.com/sortlist-core-api/6qpvvqjtmniirpkvp8eg83bicnc2', + ), + TimelinePost( + id: 'Post2', + creatorId: 'test_user', + title: 'Post 2', + category: null, + content: 'Standard post with image made by the current user and' + ' reactions enabled', + likes: 0, + reaction: 0, + createdAt: DateTime.now(), + reactionEnabled: true, + imageUrl: + 'https://s3-eu-west-1.amazonaws.com/sortlist-core-api/6qpvvqjtmniirpkvp8eg83bicnc2', + ), + ]; } diff --git a/packages/flutter_timeline_view/lib/src/widgets/timeline_post_widget.dart b/packages/flutter_timeline_view/lib/src/widgets/timeline_post_widget.dart index 513fd89..ff1976b 100644 --- a/packages/flutter_timeline_view/lib/src/widgets/timeline_post_widget.dart +++ b/packages/flutter_timeline_view/lib/src/widgets/timeline_post_widget.dart @@ -160,12 +160,14 @@ class _TimelinePostWidgetState extends State { late TimelinePost result; if (!liked) { - result = await widget.service.likePost( + result = + await widget.service.postService.likePost( userId, widget.post, ); } else { - result = await widget.service.unlikePost( + result = + await widget.service.postService.unlikePost( userId, widget.post, ); @@ -197,12 +199,12 @@ class _TimelinePostWidgetState extends State { widget.post.likedBy?.contains(userId) ?? false; if (!liked) { - await widget.service.likePost( + await widget.service.postService.likePost( userId, widget.post, ); } else { - await widget.service.unlikePost( + await widget.service.postService.unlikePost( userId, widget.post, ); diff --git a/packages/flutter_timeline_view/pubspec.yaml b/packages/flutter_timeline_view/pubspec.yaml index 3b1cbce..581ac46 100644 --- a/packages/flutter_timeline_view/pubspec.yaml +++ b/packages/flutter_timeline_view/pubspec.yaml @@ -20,10 +20,11 @@ dependencies: flutter_html: ^3.0.0-beta.2 flutter_timeline_interface: - git: - url: https://github.com/Iconica-Development/flutter_timeline - path: packages/flutter_timeline_interface - ref: 2.0.0 + path: ../flutter_timeline_interface + # git: + # url: https://github.com/Iconica-Development/flutter_timeline + # path: packages/flutter_timeline_interface + # ref: 2.0.0 flutter_image_picker: git: url: https://github.com/Iconica-Development/flutter_image_picker From 03901aaa2b2639f4c792b83a6515341df3480fab Mon Sep 17 00:00:00 2001 From: Jacques Date: Mon, 29 Jan 2024 11:53:41 +0100 Subject: [PATCH 02/10] fix: dependencies --- packages/flutter_timeline/pubspec.yaml | 18 ++++++++---------- .../flutter_timeline_firebase/pubspec.yaml | 9 ++++----- packages/flutter_timeline_view/pubspec.yaml | 9 ++++----- 3 files changed, 16 insertions(+), 20 deletions(-) diff --git a/packages/flutter_timeline/pubspec.yaml b/packages/flutter_timeline/pubspec.yaml index 354e81e..925bb51 100644 --- a/packages/flutter_timeline/pubspec.yaml +++ b/packages/flutter_timeline/pubspec.yaml @@ -16,18 +16,16 @@ dependencies: go_router: any flutter_timeline_view: - path: ../flutter_timeline_view - # git: - # url: https://github.com/Iconica-Development/flutter_timeline - # path: packages/flutter_timeline_view - # ref: 2.0.0 + git: + url: https://github.com/Iconica-Development/flutter_timeline + path: packages/flutter_timeline_view + ref: 2.0.0 flutter_timeline_interface: - path: ../flutter_timeline_interface - # git: - # url: https://github.com/Iconica-Development/flutter_timeline - # path: packages/flutter_timeline_view - # ref: 2.0.0 + git: + url: https://github.com/Iconica-Development/flutter_timeline + path: packages/flutter_timeline_view + ref: 2.0.0 dev_dependencies: flutter_lints: ^2.0.0 diff --git a/packages/flutter_timeline_firebase/pubspec.yaml b/packages/flutter_timeline_firebase/pubspec.yaml index 00d6428..cefbac5 100644 --- a/packages/flutter_timeline_firebase/pubspec.yaml +++ b/packages/flutter_timeline_firebase/pubspec.yaml @@ -20,11 +20,10 @@ dependencies: uuid: ^4.2.1 flutter_timeline_interface: - path: ../flutter_timeline_interface - # git: - # url: https://github.com/Iconica-Development/flutter_timeline - # path: packages/flutter_timeline_interface - # ref: 2.0.0 + git: + url: https://github.com/Iconica-Development/flutter_timeline + path: packages/flutter_timeline_interface + ref: 2.0.0 dev_dependencies: flutter_lints: ^2.0.0 diff --git a/packages/flutter_timeline_view/pubspec.yaml b/packages/flutter_timeline_view/pubspec.yaml index 581ac46..3b1cbce 100644 --- a/packages/flutter_timeline_view/pubspec.yaml +++ b/packages/flutter_timeline_view/pubspec.yaml @@ -20,11 +20,10 @@ dependencies: flutter_html: ^3.0.0-beta.2 flutter_timeline_interface: - path: ../flutter_timeline_interface - # git: - # url: https://github.com/Iconica-Development/flutter_timeline - # path: packages/flutter_timeline_interface - # ref: 2.0.0 + git: + url: https://github.com/Iconica-Development/flutter_timeline + path: packages/flutter_timeline_interface + ref: 2.0.0 flutter_image_picker: git: url: https://github.com/Iconica-Development/flutter_image_picker From d0b4db1eb06c6e5e8be052ef6483069c4ddef383 Mon Sep 17 00:00:00 2001 From: Jacques Date: Mon, 29 Jan 2024 11:58:02 +0100 Subject: [PATCH 03/10] fix: Fix dependency --- packages/flutter_timeline/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter_timeline/pubspec.yaml b/packages/flutter_timeline/pubspec.yaml index 925bb51..83e93f5 100644 --- a/packages/flutter_timeline/pubspec.yaml +++ b/packages/flutter_timeline/pubspec.yaml @@ -24,7 +24,7 @@ dependencies: flutter_timeline_interface: git: url: https://github.com/Iconica-Development/flutter_timeline - path: packages/flutter_timeline_view + path: packages/flutter_timeline_interface ref: 2.0.0 dev_dependencies: From 179841f93039a5e4a4743f07b7dceef0cafd0db1 Mon Sep 17 00:00:00 2001 From: Jacques Date: Mon, 29 Jan 2024 15:30:26 +0100 Subject: [PATCH 04/10] fix: Made parameters nullable --- README.md | 5 +- .../example/lib/apps/go_router/app.dart | 2 +- .../example/lib/apps/navigator/app.dart | 3 +- .../example/lib/apps/widgets/app.dart | 3 +- .../flutter_timeline_navigator_userstory.dart | 113 ++++-- .../lib/src/flutter_timeline_userstory.dart | 133 ++++--- .../lib/flutter_timeline_firebase.dart | 2 +- .../src/service/firebase_post_service.dart | 352 +++++++++++++++++ .../service/firebase_timeline_service.dart | 370 ++---------------- 9 files changed, 537 insertions(+), 446 deletions(-) create mode 100644 packages/flutter_timeline_firebase/lib/src/service/firebase_post_service.dart diff --git a/README.md b/README.md index 2ea41d3..7242285 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ List getTimelineStoryRoutes() => getTimelineStoryRoutes( service: FirebaseTimelineService(), userService: FirebaseUserService(), userId: currentUserId, - optionsBuilder: (context) {}, + optionsBuilder: (context) => FirebaseOptions(), ), ); ``` @@ -79,13 +79,12 @@ TimelineScreen( userId: currentUserId, service: timelineService, options: timelineOptions, - onPostTap: (post) {} ), ```` `TimelineScreen` is supplied with a standard `TimelinePostScreen` which opens the detail page of the selected post. Needed parameter like `TimelineService` and `TimelineOptions` will be the same as the ones supplied to the `TimelineScreen`. -The standard `TimelinePostScreen` can be overridden by supplying `onPostTap` as shown below. +The standard `TimelinePostScreen` can be overridden by defining `onPostTap` as shown below. ``` TimelineScreen( diff --git a/packages/flutter_timeline/example/lib/apps/go_router/app.dart b/packages/flutter_timeline/example/lib/apps/go_router/app.dart index a4ccbe9..7f222a7 100644 --- a/packages/flutter_timeline/example/lib/apps/go_router/app.dart +++ b/packages/flutter_timeline/example/lib/apps/go_router/app.dart @@ -4,7 +4,7 @@ import 'package:flutter_timeline/flutter_timeline.dart'; import 'package:go_router/go_router.dart'; List getTimelineRoutes() => getTimelineStoryRoutes( - getConfig(TimelineService( + configuration: getConfig(TimelineService( postService: LocalTimelinePostService(), )), ); diff --git a/packages/flutter_timeline/example/lib/apps/navigator/app.dart b/packages/flutter_timeline/example/lib/apps/navigator/app.dart index 36e3c44..aed79d5 100644 --- a/packages/flutter_timeline/example/lib/apps/navigator/app.dart +++ b/packages/flutter_timeline/example/lib/apps/navigator/app.dart @@ -31,8 +31,7 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { - var timelineService = - TimelineService(postService: LocalTimelinePostService()); + var timelineService = TimelineService(postService: LocalTimelinePostService()); var timelineOptions = options; @override diff --git a/packages/flutter_timeline/example/lib/apps/widgets/app.dart b/packages/flutter_timeline/example/lib/apps/widgets/app.dart index 2292e17..1ac4aad 100644 --- a/packages/flutter_timeline/example/lib/apps/widgets/app.dart +++ b/packages/flutter_timeline/example/lib/apps/widgets/app.dart @@ -32,8 +32,7 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { - var timelineService = - TimelineService(postService: LocalTimelinePostService()); + var timelineService = TimelineService(postService: LocalTimelinePostService()); var timelineOptions = options; @override diff --git a/packages/flutter_timeline/lib/src/flutter_timeline_navigator_userstory.dart b/packages/flutter_timeline/lib/src/flutter_timeline_navigator_userstory.dart index 341b73a..6c708f4 100644 --- a/packages/flutter_timeline/lib/src/flutter_timeline_navigator_userstory.dart +++ b/packages/flutter_timeline/lib/src/flutter_timeline_navigator_userstory.dart @@ -5,47 +5,80 @@ import 'package:flutter/material.dart'; import 'package:flutter_timeline/flutter_timeline.dart'; -Widget timeLineNavigatorUserStory( - TimelineUserStoryConfiguration configuration, - BuildContext context, -) => - _timelineScreenRoute(configuration, context); +Widget timeLineNavigatorUserStory({ + required BuildContext context, + TimelineUserStoryConfiguration? configuration, +}) { + var config = configuration ?? + TimelineUserStoryConfiguration( + userId: 'test_user', + service: TimelineService( + postService: LocalTimelinePostService(), + ), + optionsBuilder: (context) => const TimelineOptions(), + ); -Widget _timelineScreenRoute( - TimelineUserStoryConfiguration configuration, - BuildContext context, -) => - TimelineScreen( - service: configuration.service, - options: configuration.optionsBuilder(context), - userId: configuration.userId, - onPostTap: (post) async => - configuration.onPostTap?.call(context, post) ?? - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => - _postDetailScreenRoute(configuration, context, post), + return _timelineScreenRoute(configuration: config, context: context); +} + +Widget _timelineScreenRoute({ + required BuildContext context, + TimelineUserStoryConfiguration? configuration, +}) { + var config = configuration ?? + TimelineUserStoryConfiguration( + userId: 'test_user', + service: TimelineService( + postService: LocalTimelinePostService(), + ), + optionsBuilder: (context) => const TimelineOptions(), + ); + + return TimelineScreen( + service: config.service, + options: config.optionsBuilder(context), + userId: config.userId, + onPostTap: (post) async => + config.onPostTap?.call(context, post) ?? + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => _postDetailScreenRoute( + configuration: config, + context: context, + post: post, ), ), - onUserTap: (userId) { - configuration.onUserTap?.call(context, userId); - }, - filterEnabled: configuration.filterEnabled, - postWidgetBuilder: configuration.postWidgetBuilder, - ); + ), + onUserTap: (userId) { + config.onUserTap?.call(context, userId); + }, + filterEnabled: config.filterEnabled, + postWidgetBuilder: config.postWidgetBuilder, + ); +} -Widget _postDetailScreenRoute( - TimelineUserStoryConfiguration configuration, - BuildContext context, - TimelinePost post, -) => - TimelinePostScreen( - userId: configuration.userId, - service: configuration.service, - options: configuration.optionsBuilder(context), - post: post, - onPostDelete: () async { - configuration.onPostDelete?.call(context, post) ?? - await configuration.service.postService.deletePost(post); - }, - ); +Widget _postDetailScreenRoute({ + required BuildContext context, + required TimelinePost post, + TimelineUserStoryConfiguration? configuration, +}) { + var config = configuration ?? + TimelineUserStoryConfiguration( + userId: 'test_user', + service: TimelineService( + postService: LocalTimelinePostService(), + ), + optionsBuilder: (context) => const TimelineOptions(), + ); + + return TimelinePostScreen( + userId: config.userId, + service: config.service, + options: config.optionsBuilder(context), + post: post, + onPostDelete: () async { + config.onPostDelete?.call(context, post) ?? + await config.service.postService.deletePost(post); + }, + ); +} diff --git a/packages/flutter_timeline/lib/src/flutter_timeline_userstory.dart b/packages/flutter_timeline/lib/src/flutter_timeline_userstory.dart index 1609338..0023867 100644 --- a/packages/flutter_timeline/lib/src/flutter_timeline_userstory.dart +++ b/packages/flutter_timeline/lib/src/flutter_timeline_userstory.dart @@ -3,72 +3,79 @@ // SPDX-License-Identifier: BSD-3-Clause import 'package:flutter/material.dart'; +import 'package:flutter_timeline/flutter_timeline.dart'; import 'package:flutter_timeline/src/go_router.dart'; -import 'package:flutter_timeline/src/models/timeline_configuration.dart'; -import 'package:flutter_timeline/src/routes.dart'; -import 'package:flutter_timeline_view/flutter_timeline_view.dart'; import 'package:go_router/go_router.dart'; -List getTimelineStoryRoutes( - TimelineUserStoryConfiguration configuration, -) => - [ - GoRoute( - path: TimelineUserStoryRoutes.timelineHome, - pageBuilder: (context, state) { - var timelineScreen = TimelineScreen( - userId: configuration.userId, - onUserTap: (user) => configuration.onUserTap?.call(context, user), - service: configuration.service, - options: configuration.optionsBuilder(context), - onPostTap: (post) async => - configuration.onPostTap?.call(context, post) ?? - await context.push( - TimelineUserStoryRoutes.timelineViewPath(post.id), - ), - filterEnabled: configuration.filterEnabled, - postWidgetBuilder: configuration.postWidgetBuilder, - ); +List getTimelineStoryRoutes({ + TimelineUserStoryConfiguration? configuration, +}) { + var config = configuration ??= TimelineUserStoryConfiguration( + userId: 'test_user', + service: TimelineService( + postService: LocalTimelinePostService(), + ), + optionsBuilder: (context) => const TimelineOptions(), + ); - return buildScreenWithoutTransition( - context: context, - state: state, - child: configuration.openPageBuilder?.call( - context, - timelineScreen, - ) ?? - Scaffold( - body: timelineScreen, - ), - ); - }, - ), - GoRoute( - path: TimelineUserStoryRoutes.timelineView, - pageBuilder: (context, state) { - var post = configuration.service.postService - .getPost(state.pathParameters['post']!)!; + return [ + GoRoute( + path: TimelineUserStoryRoutes.timelineHome, + pageBuilder: (context, state) { + var timelineScreen = TimelineScreen( + userId: config.userId, + onUserTap: (user) => config.onUserTap?.call(context, user), + service: config.service, + options: config.optionsBuilder(context), + onPostTap: (post) async => + config.onPostTap?.call(context, post) ?? + await context.push( + TimelineUserStoryRoutes.timelineViewPath(post.id), + ), + filterEnabled: config.filterEnabled, + postWidgetBuilder: config.postWidgetBuilder, + ); - var timelinePostWidget = TimelinePostScreen( - userId: configuration.userId, - options: configuration.optionsBuilder(context), - service: configuration.service, - post: post, - onPostDelete: () => configuration.onPostDelete?.call(context, post), - onUserTap: (user) => configuration.onUserTap?.call(context, user), - ); + return buildScreenWithoutTransition( + context: context, + state: state, + child: config.openPageBuilder?.call( + context, + timelineScreen, + ) ?? + Scaffold( + body: timelineScreen, + ), + ); + }, + ), + GoRoute( + path: TimelineUserStoryRoutes.timelineView, + pageBuilder: (context, state) { + var post = + config.service.postService.getPost(state.pathParameters['post']!)!; - return buildScreenWithoutTransition( - context: context, - state: state, - child: configuration.openPageBuilder?.call( - context, - timelinePostWidget, - ) ?? - Scaffold( - body: timelinePostWidget, - ), - ); - }, - ), - ]; + var timelinePostWidget = TimelinePostScreen( + userId: config.userId, + options: config.optionsBuilder(context), + service: config.service, + post: post, + onPostDelete: () => config.onPostDelete?.call(context, post), + onUserTap: (user) => config.onUserTap?.call(context, user), + ); + + return buildScreenWithoutTransition( + context: context, + state: state, + child: config.openPageBuilder?.call( + context, + timelinePostWidget, + ) ?? + Scaffold( + body: timelinePostWidget, + ), + ); + }, + ), + ]; +} diff --git a/packages/flutter_timeline_firebase/lib/flutter_timeline_firebase.dart b/packages/flutter_timeline_firebase/lib/flutter_timeline_firebase.dart index 9ad1f86..84481b0 100644 --- a/packages/flutter_timeline_firebase/lib/flutter_timeline_firebase.dart +++ b/packages/flutter_timeline_firebase/lib/flutter_timeline_firebase.dart @@ -6,5 +6,5 @@ library flutter_timeline_firebase; export 'src/config/firebase_timeline_options.dart'; -export 'src/service/firebase_timeline_service.dart'; +export 'src/service/firebase_post_service.dart'; export 'src/service/firebase_user_service.dart'; diff --git a/packages/flutter_timeline_firebase/lib/src/service/firebase_post_service.dart b/packages/flutter_timeline_firebase/lib/src/service/firebase_post_service.dart new file mode 100644 index 0000000..8215e71 --- /dev/null +++ b/packages/flutter_timeline_firebase/lib/src/service/firebase_post_service.dart @@ -0,0 +1,352 @@ +// SPDX-FileCopyrightText: 2023 Iconica +// +// SPDX-License-Identifier: BSD-3-Clause + +import 'dart:typed_data'; + +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_storage/firebase_storage.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_timeline_firebase/src/config/firebase_timeline_options.dart'; +import 'package:flutter_timeline_firebase/src/models/firebase_user_document.dart'; +import 'package:flutter_timeline_interface/flutter_timeline_interface.dart'; +import 'package:uuid/uuid.dart'; + +class FirebaseTimelinePostService extends TimelinePostService + with TimelineUserService { + FirebaseTimelinePostService({ + required TimelineUserService userService, + FirebaseApp? app, + options = const FirebaseTimelineOptions(), + }) { + var appInstance = app ?? Firebase.app(); + _db = FirebaseFirestore.instanceFor(app: appInstance); + _storage = FirebaseStorage.instanceFor(app: appInstance); + _userService = userService; + _options = options; + } + + late FirebaseFirestore _db; + late FirebaseStorage _storage; + late TimelineUserService _userService; + late FirebaseTimelineOptions _options; + + final Map _users = {}; + + @override + Future createPost(TimelinePost post) async { + var postId = const Uuid().v4(); + var user = await _userService.getUser(post.creatorId); + var updatedPost = post.copyWith(id: postId, creator: user); + if (post.image != null) { + var imageRef = + _storage.ref().child('${_options.timelineCollectionName}/$postId'); + var result = await imageRef.putData(post.image!); + var imageUrl = await result.ref.getDownloadURL(); + updatedPost = updatedPost.copyWith(imageUrl: imageUrl); + } + var postRef = + _db.collection(_options.timelineCollectionName).doc(updatedPost.id); + await postRef.set(updatedPost.toJson()); + posts.add(updatedPost); + notifyListeners(); + return updatedPost; + } + + @override + Future deletePost(TimelinePost post) async { + posts = posts.where((element) => element.id != post.id).toList(); + var postRef = _db.collection(_options.timelineCollectionName).doc(post.id); + await postRef.delete(); + notifyListeners(); + } + + @override + Future deletePostReaction( + TimelinePost post, + String reactionId, + ) async { + if (post.reactions != null && post.reactions!.isNotEmpty) { + var reaction = + post.reactions!.firstWhere((element) => element.id == reactionId); + var updatedPost = post.copyWith( + reaction: post.reaction - 1, + reactions: (post.reactions ?? [])..remove(reaction), + ); + posts = posts + .map( + (p) => p.id == post.id ? updatedPost : p, + ) + .toList(); + var postRef = + _db.collection(_options.timelineCollectionName).doc(post.id); + await postRef.update({ + 'reaction': FieldValue.increment(-1), + 'reactions': FieldValue.arrayRemove( + [reaction.toJsonWithMicroseconds()], + ), + }); + notifyListeners(); + return updatedPost; + } + return post; + } + + @override + Future fetchPostDetails(TimelinePost post) async { + var reactions = post.reactions ?? []; + var updatedReactions = []; + for (var reaction in reactions) { + var user = await _userService.getUser(reaction.creatorId); + if (user != null) { + updatedReactions.add(reaction.copyWith(creator: user)); + } + } + var updatedPost = post.copyWith(reactions: updatedReactions); + posts = posts.map((p) => (p.id == post.id) ? updatedPost : p).toList(); + notifyListeners(); + return updatedPost; + } + + @override + Future> fetchPosts(String? category) async { + debugPrint('fetching posts from firebase with category: $category'); + var snapshot = (category != null) + ? await _db + .collection(_options.timelineCollectionName) + .where('category', isEqualTo: category) + .get() + : await _db.collection(_options.timelineCollectionName).get(); + + var posts = []; + for (var doc in snapshot.docs) { + var data = doc.data(); + var user = await _userService.getUser(data['creator_id']); + var post = TimelinePost.fromJson(doc.id, data).copyWith(creator: user); + posts.add(post); + } + + notifyListeners(); + return posts; + } + + @override + Future> fetchPostsPaginated( + String? category, + int limit, + ) async { + // only take posts that are in our category + var oldestPost = posts + .where( + (element) => category == null || element.category == category, + ) + .fold( + posts.first, + (previousValue, element) => + (previousValue.createdAt.isBefore(element.createdAt)) + ? previousValue + : element, + ); + var snapshot = (category != null) + ? await _db + .collection(_options.timelineCollectionName) + .where('category', isEqualTo: category) + .orderBy('created_at', descending: true) + .startAfter([oldestPost]) + .limit(limit) + .get() + : await _db + .collection(_options.timelineCollectionName) + .orderBy('created_at', descending: true) + .startAfter([oldestPost.createdAt]) + .limit(limit) + .get(); + // add the new posts to the list + var newPosts = []; + for (var doc in snapshot.docs) { + var data = doc.data(); + var user = await _userService.getUser(data['creator_id']); + var post = TimelinePost.fromJson(doc.id, data).copyWith(creator: user); + newPosts.add(post); + } + posts = [...posts, ...newPosts]; + notifyListeners(); + return newPosts; + } + + @override + Future fetchPost(TimelinePost post) async { + var doc = await _db + .collection(_options.timelineCollectionName) + .doc(post.id) + .get(); + var data = doc.data(); + if (data == null) return post; + var user = await _userService.getUser(data['creator_id']); + var updatedPost = TimelinePost.fromJson(doc.id, data).copyWith( + creator: user, + ); + posts = posts.map((p) => (p.id == post.id) ? updatedPost : p).toList(); + notifyListeners(); + return updatedPost; + } + + @override + Future> refreshPosts(String? category) async { + // fetch all posts between now and the newest posts we have + var newestPostWeHave = posts + .where( + (element) => category == null || element.category == category, + ) + .fold( + posts.first, + (previousValue, element) => + (previousValue.createdAt.isAfter(element.createdAt)) + ? previousValue + : element, + ); + var snapshot = (category != null) + ? await _db + .collection(_options.timelineCollectionName) + .where('category', isEqualTo: category) + .orderBy('created_at', descending: true) + .endBefore([newestPostWeHave.createdAt]).get() + : await _db + .collection(_options.timelineCollectionName) + .orderBy('created_at', descending: true) + .endBefore([newestPostWeHave.createdAt]).get(); + // add the new posts to the list + var newPosts = []; + for (var doc in snapshot.docs) { + var data = doc.data(); + var user = await _userService.getUser(data['creator_id']); + var post = TimelinePost.fromJson(doc.id, data).copyWith(creator: user); + newPosts.add(post); + } + posts = [...posts, ...newPosts]; + notifyListeners(); + return newPosts; + } + + @override + TimelinePost? getPost(String postId) => + (posts.any((element) => element.id == postId)) + ? posts.firstWhere((element) => element.id == postId) + : null; + + @override + List getPosts(String? category) => posts + .where((element) => category == null || element.category == category) + .toList(); + + @override + Future likePost(String userId, TimelinePost post) async { + // update the post with the new like + var updatedPost = post.copyWith( + likes: post.likes + 1, + likedBy: post.likedBy?..add(userId), + ); + posts = posts + .map( + (p) => p.id == post.id ? updatedPost : p, + ) + .toList(); + var postRef = _db.collection(_options.timelineCollectionName).doc(post.id); + await postRef.update({ + 'likes': FieldValue.increment(1), + 'liked_by': FieldValue.arrayUnion([userId]), + }); + notifyListeners(); + return updatedPost; + } + + @override + Future unlikePost(String userId, TimelinePost post) async { + // update the post with the new like + var updatedPost = post.copyWith( + likes: post.likes - 1, + likedBy: post.likedBy?..remove(userId), + ); + posts = posts + .map( + (p) => p.id == post.id ? updatedPost : p, + ) + .toList(); + var postRef = _db.collection(_options.timelineCollectionName).doc(post.id); + await postRef.update({ + 'likes': FieldValue.increment(-1), + 'liked_by': FieldValue.arrayRemove([userId]), + }); + notifyListeners(); + return updatedPost; + } + + @override + Future reactToPost( + TimelinePost post, + TimelinePostReaction reaction, { + Uint8List? image, + }) async { + var reactionId = const Uuid().v4(); + // also fetch the user information and add it to the reaction + var user = await _userService.getUser(reaction.creatorId); + var updatedReaction = reaction.copyWith(id: reactionId, creator: user); + if (image != null) { + var imageRef = _storage + .ref() + .child('${_options.timelineCollectionName}/${post.id}/$reactionId}'); + var result = await imageRef.putData(image); + var imageUrl = await result.ref.getDownloadURL(); + updatedReaction = updatedReaction.copyWith(imageUrl: imageUrl); + } + + var updatedPost = post.copyWith( + reaction: post.reaction + 1, + reactions: post.reactions?..add(updatedReaction), + ); + + var postRef = _db.collection(_options.timelineCollectionName).doc(post.id); + await postRef.update({ + 'reaction': FieldValue.increment(1), + 'reactions': FieldValue.arrayUnion([updatedReaction.toJson()]), + }); + posts = posts + .map( + (p) => p.id == post.id ? updatedPost : p, + ) + .toList(); + notifyListeners(); + return updatedPost; + } + + CollectionReference get _userCollection => _db + .collection(_options.usersCollectionName) + .withConverter( + fromFirestore: (snapshot, _) => FirebaseUserDocument.fromJson( + snapshot.data()!, + snapshot.id, + ), + toFirestore: (user, _) => user.toJson(), + ); + @override + Future getUser(String userId) async { + if (_users.containsKey(userId)) { + return _users[userId]!; + } + var data = (await _userCollection.doc(userId).get()).data(); + + var user = data == null + ? TimelinePosterUserModel(userId: userId) + : TimelinePosterUserModel( + userId: userId, + firstName: data.firstName, + lastName: data.lastName, + imageUrl: data.imageUrl, + ); + + _users[userId] = user; + + return user; + } +} diff --git a/packages/flutter_timeline_firebase/lib/src/service/firebase_timeline_service.dart b/packages/flutter_timeline_firebase/lib/src/service/firebase_timeline_service.dart index eef4c34..9fe9d61 100644 --- a/packages/flutter_timeline_firebase/lib/src/service/firebase_timeline_service.dart +++ b/packages/flutter_timeline_firebase/lib/src/service/firebase_timeline_service.dart @@ -1,352 +1,54 @@ -// SPDX-FileCopyrightText: 2023 Iconica -// -// SPDX-License-Identifier: BSD-3-Clause - -import 'dart:typed_data'; - -import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_core/firebase_core.dart'; -import 'package:firebase_storage/firebase_storage.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_timeline_firebase/src/config/firebase_timeline_options.dart'; -import 'package:flutter_timeline_firebase/src/models/firebase_user_document.dart'; +import 'package:flutter_timeline_firebase/flutter_timeline_firebase.dart'; import 'package:flutter_timeline_interface/flutter_timeline_interface.dart'; -import 'package:uuid/uuid.dart'; -class FirebaseTimelineService extends TimelinePostService - with TimelineUserService { +class FirebaseTimelineService implements TimelineService { FirebaseTimelineService({ - required TimelineUserService userService, - FirebaseApp? app, - options = const FirebaseTimelineOptions(), + this.options, + this.app, + this.firebasePostService, + this.firebaseUserService, }) { - var appInstance = app ?? Firebase.app(); - _db = FirebaseFirestore.instanceFor(app: appInstance); - _storage = FirebaseStorage.instanceFor(app: appInstance); - _userService = userService; - _options = options; + firebaseUserService ??= FirebaseTimelinePostService( + userService: userService, + options: options, + app: app, + ); + + firebasePostService ??= FirebaseTimelinePostService( + userService: userService, + options: options, + app: app, + ); } - late FirebaseFirestore _db; - late FirebaseStorage _storage; - late TimelineUserService _userService; - late FirebaseTimelineOptions _options; - - final Map _users = {}; + final FirebaseTimelineOptions? options; + final FirebaseApp? app; + TimelinePostService? firebasePostService; + TimelineUserService? firebaseUserService; @override - Future createPost(TimelinePost post) async { - var postId = const Uuid().v4(); - var user = await _userService.getUser(post.creatorId); - var updatedPost = post.copyWith(id: postId, creator: user); - if (post.image != null) { - var imageRef = - _storage.ref().child('${_options.timelineCollectionName}/$postId'); - var result = await imageRef.putData(post.image!); - var imageUrl = await result.ref.getDownloadURL(); - updatedPost = updatedPost.copyWith(imageUrl: imageUrl); - } - var postRef = - _db.collection(_options.timelineCollectionName).doc(updatedPost.id); - await postRef.set(updatedPost.toJson()); - posts.add(updatedPost); - notifyListeners(); - return updatedPost; - } - - @override - Future deletePost(TimelinePost post) async { - posts = posts.where((element) => element.id != post.id).toList(); - var postRef = _db.collection(_options.timelineCollectionName).doc(post.id); - await postRef.delete(); - notifyListeners(); - } - - @override - Future deletePostReaction( - TimelinePost post, - String reactionId, - ) async { - if (post.reactions != null && post.reactions!.isNotEmpty) { - var reaction = - post.reactions!.firstWhere((element) => element.id == reactionId); - var updatedPost = post.copyWith( - reaction: post.reaction - 1, - reactions: (post.reactions ?? [])..remove(reaction), + TimelinePostService get postService { + if (firebasePostService != null) { + return firebasePostService!; + } else { + return FirebaseTimelinePostService( + userService: userService, + options: options, + app: app, ); - posts = posts - .map( - (p) => p.id == post.id ? updatedPost : p, - ) - .toList(); - var postRef = - _db.collection(_options.timelineCollectionName).doc(post.id); - await postRef.update({ - 'reaction': FieldValue.increment(-1), - 'reactions': FieldValue.arrayRemove( - [reaction.toJsonWithMicroseconds()], - ), - }); - notifyListeners(); - return updatedPost; } - return post; } @override - Future fetchPostDetails(TimelinePost post) async { - var reactions = post.reactions ?? []; - var updatedReactions = []; - for (var reaction in reactions) { - var user = await _userService.getUser(reaction.creatorId); - if (user != null) { - updatedReactions.add(reaction.copyWith(creator: user)); - } - } - var updatedPost = post.copyWith(reactions: updatedReactions); - posts = posts.map((p) => (p.id == post.id) ? updatedPost : p).toList(); - notifyListeners(); - return updatedPost; - } - - @override - Future> fetchPosts(String? category) async { - debugPrint('fetching posts from firebase with category: $category'); - var snapshot = (category != null) - ? await _db - .collection(_options.timelineCollectionName) - .where('category', isEqualTo: category) - .get() - : await _db.collection(_options.timelineCollectionName).get(); - - var posts = []; - for (var doc in snapshot.docs) { - var data = doc.data(); - var user = await _userService.getUser(data['creator_id']); - var post = TimelinePost.fromJson(doc.id, data).copyWith(creator: user); - posts.add(post); - } - - notifyListeners(); - return posts; - } - - @override - Future> fetchPostsPaginated( - String? category, - int limit, - ) async { - // only take posts that are in our category - var oldestPost = posts - .where( - (element) => category == null || element.category == category, - ) - .fold( - posts.first, - (previousValue, element) => - (previousValue.createdAt.isBefore(element.createdAt)) - ? previousValue - : element, - ); - var snapshot = (category != null) - ? await _db - .collection(_options.timelineCollectionName) - .where('category', isEqualTo: category) - .orderBy('created_at', descending: true) - .startAfter([oldestPost]) - .limit(limit) - .get() - : await _db - .collection(_options.timelineCollectionName) - .orderBy('created_at', descending: true) - .startAfter([oldestPost.createdAt]) - .limit(limit) - .get(); - // add the new posts to the list - var newPosts = []; - for (var doc in snapshot.docs) { - var data = doc.data(); - var user = await _userService.getUser(data['creator_id']); - var post = TimelinePost.fromJson(doc.id, data).copyWith(creator: user); - newPosts.add(post); - } - posts = [...posts, ...newPosts]; - notifyListeners(); - return newPosts; - } - - @override - Future fetchPost(TimelinePost post) async { - var doc = await _db - .collection(_options.timelineCollectionName) - .doc(post.id) - .get(); - var data = doc.data(); - if (data == null) return post; - var user = await _userService.getUser(data['creator_id']); - var updatedPost = TimelinePost.fromJson(doc.id, data).copyWith( - creator: user, - ); - posts = posts.map((p) => (p.id == post.id) ? updatedPost : p).toList(); - notifyListeners(); - return updatedPost; - } - - @override - Future> refreshPosts(String? category) async { - // fetch all posts between now and the newest posts we have - var newestPostWeHave = posts - .where( - (element) => category == null || element.category == category, - ) - .fold( - posts.first, - (previousValue, element) => - (previousValue.createdAt.isAfter(element.createdAt)) - ? previousValue - : element, - ); - var snapshot = (category != null) - ? await _db - .collection(_options.timelineCollectionName) - .where('category', isEqualTo: category) - .orderBy('created_at', descending: true) - .endBefore([newestPostWeHave.createdAt]).get() - : await _db - .collection(_options.timelineCollectionName) - .orderBy('created_at', descending: true) - .endBefore([newestPostWeHave.createdAt]).get(); - // add the new posts to the list - var newPosts = []; - for (var doc in snapshot.docs) { - var data = doc.data(); - var user = await _userService.getUser(data['creator_id']); - var post = TimelinePost.fromJson(doc.id, data).copyWith(creator: user); - newPosts.add(post); - } - posts = [...posts, ...newPosts]; - notifyListeners(); - return newPosts; - } - - @override - TimelinePost? getPost(String postId) => - (posts.any((element) => element.id == postId)) - ? posts.firstWhere((element) => element.id == postId) - : null; - - @override - List getPosts(String? category) => posts - .where((element) => category == null || element.category == category) - .toList(); - - @override - Future likePost(String userId, TimelinePost post) async { - // update the post with the new like - var updatedPost = post.copyWith( - likes: post.likes + 1, - likedBy: post.likedBy?..add(userId), - ); - posts = posts - .map( - (p) => p.id == post.id ? updatedPost : p, - ) - .toList(); - var postRef = _db.collection(_options.timelineCollectionName).doc(post.id); - await postRef.update({ - 'likes': FieldValue.increment(1), - 'liked_by': FieldValue.arrayUnion([userId]), - }); - notifyListeners(); - return updatedPost; - } - - @override - Future unlikePost(String userId, TimelinePost post) async { - // update the post with the new like - var updatedPost = post.copyWith( - likes: post.likes - 1, - likedBy: post.likedBy?..remove(userId), - ); - posts = posts - .map( - (p) => p.id == post.id ? updatedPost : p, - ) - .toList(); - var postRef = _db.collection(_options.timelineCollectionName).doc(post.id); - await postRef.update({ - 'likes': FieldValue.increment(-1), - 'liked_by': FieldValue.arrayRemove([userId]), - }); - notifyListeners(); - return updatedPost; - } - - @override - Future reactToPost( - TimelinePost post, - TimelinePostReaction reaction, { - Uint8List? image, - }) async { - var reactionId = const Uuid().v4(); - // also fetch the user information and add it to the reaction - var user = await _userService.getUser(reaction.creatorId); - var updatedReaction = reaction.copyWith(id: reactionId, creator: user); - if (image != null) { - var imageRef = _storage - .ref() - .child('${_options.timelineCollectionName}/${post.id}/$reactionId}'); - var result = await imageRef.putData(image); - var imageUrl = await result.ref.getDownloadURL(); - updatedReaction = updatedReaction.copyWith(imageUrl: imageUrl); - } - - var updatedPost = post.copyWith( - reaction: post.reaction + 1, - reactions: post.reactions?..add(updatedReaction), - ); - - var postRef = _db.collection(_options.timelineCollectionName).doc(post.id); - await postRef.update({ - 'reaction': FieldValue.increment(1), - 'reactions': FieldValue.arrayUnion([updatedReaction.toJson()]), - }); - posts = posts - .map( - (p) => p.id == post.id ? updatedPost : p, - ) - .toList(); - notifyListeners(); - return updatedPost; - } - - CollectionReference get _userCollection => _db - .collection(_options.usersCollectionName) - .withConverter( - fromFirestore: (snapshot, _) => FirebaseUserDocument.fromJson( - snapshot.data()!, - snapshot.id, - ), - toFirestore: (user, _) => user.toJson(), + TimelineUserService get userService { + if (firebaseUserService != null) { + return firebaseUserService!; + } else { + return FirebaseUserService( + options: options, + app: app, ); - @override - Future getUser(String userId) async { - if (_users.containsKey(userId)) { - return _users[userId]!; } - var data = (await _userCollection.doc(userId).get()).data(); - - var user = data == null - ? TimelinePosterUserModel(userId: userId) - : TimelinePosterUserModel( - userId: userId, - firstName: data.firstName, - lastName: data.lastName, - imageUrl: data.imageUrl, - ); - - _users[userId] = user; - - return user; } } From 1fc7c8d2deac1784dc43cb79df98d47e36fbb90a Mon Sep 17 00:00:00 2001 From: Jacques Date: Mon, 29 Jan 2024 16:07:16 +0100 Subject: [PATCH 05/10] fix: Firebase implementation --- .../flutter_timeline/lib/src/flutter_timeline_userstory.dart | 2 +- .../lib/src/models/timeline_configuration.dart | 2 +- .../lib/flutter_timeline_firebase.dart | 1 + .../lib/src/service/firebase_timeline_service.dart | 5 ++--- .../lib/src/service/firebase_user_service.dart | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/flutter_timeline/lib/src/flutter_timeline_userstory.dart b/packages/flutter_timeline/lib/src/flutter_timeline_userstory.dart index 0023867..a40b7b1 100644 --- a/packages/flutter_timeline/lib/src/flutter_timeline_userstory.dart +++ b/packages/flutter_timeline/lib/src/flutter_timeline_userstory.dart @@ -10,7 +10,7 @@ import 'package:go_router/go_router.dart'; List getTimelineStoryRoutes({ TimelineUserStoryConfiguration? configuration, }) { - var config = configuration ??= TimelineUserStoryConfiguration( + var config = configuration ?? TimelineUserStoryConfiguration( userId: 'test_user', service: TimelineService( postService: LocalTimelinePostService(), diff --git a/packages/flutter_timeline/lib/src/models/timeline_configuration.dart b/packages/flutter_timeline/lib/src/models/timeline_configuration.dart index 02718e6..650f19d 100644 --- a/packages/flutter_timeline/lib/src/models/timeline_configuration.dart +++ b/packages/flutter_timeline/lib/src/models/timeline_configuration.dart @@ -9,9 +9,9 @@ import 'package:flutter_timeline_view/flutter_timeline_view.dart'; @immutable class TimelineUserStoryConfiguration { const TimelineUserStoryConfiguration({ - required this.userId, required this.service, required this.optionsBuilder, + this.userId = 'test_user', this.openPageBuilder, this.onPostTap, this.onUserTap, diff --git a/packages/flutter_timeline_firebase/lib/flutter_timeline_firebase.dart b/packages/flutter_timeline_firebase/lib/flutter_timeline_firebase.dart index 84481b0..b138166 100644 --- a/packages/flutter_timeline_firebase/lib/flutter_timeline_firebase.dart +++ b/packages/flutter_timeline_firebase/lib/flutter_timeline_firebase.dart @@ -7,4 +7,5 @@ library flutter_timeline_firebase; export 'src/config/firebase_timeline_options.dart'; export 'src/service/firebase_post_service.dart'; +export 'src/service/firebase_timeline_service.dart'; export 'src/service/firebase_user_service.dart'; diff --git a/packages/flutter_timeline_firebase/lib/src/service/firebase_timeline_service.dart b/packages/flutter_timeline_firebase/lib/src/service/firebase_timeline_service.dart index 9fe9d61..2e56f8d 100644 --- a/packages/flutter_timeline_firebase/lib/src/service/firebase_timeline_service.dart +++ b/packages/flutter_timeline_firebase/lib/src/service/firebase_timeline_service.dart @@ -9,8 +9,7 @@ class FirebaseTimelineService implements TimelineService { this.firebasePostService, this.firebaseUserService, }) { - firebaseUserService ??= FirebaseTimelinePostService( - userService: userService, + firebaseUserService ??= FirebaseTimelineUserService( options: options, app: app, ); @@ -45,7 +44,7 @@ class FirebaseTimelineService implements TimelineService { if (firebaseUserService != null) { return firebaseUserService!; } else { - return FirebaseUserService( + return FirebaseTimelineUserService( options: options, app: app, ); diff --git a/packages/flutter_timeline_firebase/lib/src/service/firebase_user_service.dart b/packages/flutter_timeline_firebase/lib/src/service/firebase_user_service.dart index fb1da17..e034116 100644 --- a/packages/flutter_timeline_firebase/lib/src/service/firebase_user_service.dart +++ b/packages/flutter_timeline_firebase/lib/src/service/firebase_user_service.dart @@ -8,8 +8,8 @@ import 'package:flutter_timeline_firebase/src/config/firebase_timeline_options.d import 'package:flutter_timeline_firebase/src/models/firebase_user_document.dart'; import 'package:flutter_timeline_interface/flutter_timeline_interface.dart'; -class FirebaseUserService implements TimelineUserService { - FirebaseUserService({ +class FirebaseTimelineUserService implements TimelineUserService { + FirebaseTimelineUserService({ FirebaseApp? app, options = const FirebaseTimelineOptions(), }) { From 6a27f26fc9513afa1485adba60aec60dffebc15a Mon Sep 17 00:00:00 2001 From: Jacques Date: Tue, 30 Jan 2024 08:55:19 +0100 Subject: [PATCH 06/10] fix: Fix firebase options --- .../flutter_timeline/example/lib/apps/navigator/app.dart | 7 ++++--- .../lib/src/service/firebase_post_service.dart | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/flutter_timeline/example/lib/apps/navigator/app.dart b/packages/flutter_timeline/example/lib/apps/navigator/app.dart index aed79d5..434d595 100644 --- a/packages/flutter_timeline/example/lib/apps/navigator/app.dart +++ b/packages/flutter_timeline/example/lib/apps/navigator/app.dart @@ -31,7 +31,8 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { - var timelineService = TimelineService(postService: LocalTimelinePostService()); + var timelineService = + TimelineService(postService: LocalTimelinePostService()); var timelineOptions = options; @override @@ -64,10 +65,10 @@ class _MyHomePageState extends State { ), body: SafeArea( child: timeLineNavigatorUserStory( - getConfig( + configuration: getConfig( timelineService, ), - context), + context: context), ), ); } diff --git a/packages/flutter_timeline_firebase/lib/src/service/firebase_post_service.dart b/packages/flutter_timeline_firebase/lib/src/service/firebase_post_service.dart index 8215e71..d01ae83 100644 --- a/packages/flutter_timeline_firebase/lib/src/service/firebase_post_service.dart +++ b/packages/flutter_timeline_firebase/lib/src/service/firebase_post_service.dart @@ -18,13 +18,13 @@ class FirebaseTimelinePostService extends TimelinePostService FirebaseTimelinePostService({ required TimelineUserService userService, FirebaseApp? app, - options = const FirebaseTimelineOptions(), + FirebaseTimelineOptions? options, }) { var appInstance = app ?? Firebase.app(); _db = FirebaseFirestore.instanceFor(app: appInstance); _storage = FirebaseStorage.instanceFor(app: appInstance); _userService = userService; - _options = options; + _options = options ?? const FirebaseTimelineOptions(); } late FirebaseFirestore _db; From 4aa4c1e291a446ef7fdff3caf32bcaf42d3cd8a1 Mon Sep 17 00:00:00 2001 From: Jacques Date: Tue, 30 Jan 2024 09:05:48 +0100 Subject: [PATCH 07/10] fix: Fix options for firebaseTimelineUserService --- .../lib/src/service/firebase_user_service.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/flutter_timeline_firebase/lib/src/service/firebase_user_service.dart b/packages/flutter_timeline_firebase/lib/src/service/firebase_user_service.dart index e034116..bfde3d5 100644 --- a/packages/flutter_timeline_firebase/lib/src/service/firebase_user_service.dart +++ b/packages/flutter_timeline_firebase/lib/src/service/firebase_user_service.dart @@ -11,11 +11,11 @@ import 'package:flutter_timeline_interface/flutter_timeline_interface.dart'; class FirebaseTimelineUserService implements TimelineUserService { FirebaseTimelineUserService({ FirebaseApp? app, - options = const FirebaseTimelineOptions(), + FirebaseTimelineOptions? options, }) { var appInstance = app ?? Firebase.app(); _db = FirebaseFirestore.instanceFor(app: appInstance); - _options = options; + _options = options ?? const FirebaseTimelineOptions(); } late FirebaseFirestore _db; From 7e7f74a02b812f06cf60b7b016b41b3b0f8985e4 Mon Sep 17 00:00:00 2001 From: Jacques Date: Tue, 30 Jan 2024 09:20:10 +0100 Subject: [PATCH 08/10] fix: Fix fetching posts in firebase service --- .../lib/src/service/firebase_post_service.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter_timeline_firebase/lib/src/service/firebase_post_service.dart b/packages/flutter_timeline_firebase/lib/src/service/firebase_post_service.dart index d01ae83..2ec3e87 100644 --- a/packages/flutter_timeline_firebase/lib/src/service/firebase_post_service.dart +++ b/packages/flutter_timeline_firebase/lib/src/service/firebase_post_service.dart @@ -119,7 +119,7 @@ class FirebaseTimelinePostService extends TimelinePostService .get() : await _db.collection(_options.timelineCollectionName).get(); - var posts = []; + posts = []; for (var doc in snapshot.docs) { var data = doc.data(); var user = await _userService.getUser(data['creator_id']); From 525d8d3be6833eac1f6717893f068373bb99b77d Mon Sep 17 00:00:00 2001 From: Jacques Date: Tue, 30 Jan 2024 09:32:23 +0100 Subject: [PATCH 09/10] fix: fix FirebaseTimelineService now proper implements --- .../lib/src/service/firebase_post_service.dart | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/flutter_timeline_firebase/lib/src/service/firebase_post_service.dart b/packages/flutter_timeline_firebase/lib/src/service/firebase_post_service.dart index 2ec3e87..76a6c54 100644 --- a/packages/flutter_timeline_firebase/lib/src/service/firebase_post_service.dart +++ b/packages/flutter_timeline_firebase/lib/src/service/firebase_post_service.dart @@ -13,8 +13,9 @@ import 'package:flutter_timeline_firebase/src/models/firebase_user_document.dart import 'package:flutter_timeline_interface/flutter_timeline_interface.dart'; import 'package:uuid/uuid.dart'; -class FirebaseTimelinePostService extends TimelinePostService - with TimelineUserService { +class FirebaseTimelinePostService + with TimelineUserService, ChangeNotifier + implements TimelinePostService { FirebaseTimelinePostService({ required TimelineUserService userService, FirebaseApp? app, @@ -34,6 +35,9 @@ class FirebaseTimelinePostService extends TimelinePostService final Map _users = {}; + @override + List posts = []; + @override Future createPost(TimelinePost post) async { var postId = const Uuid().v4(); @@ -119,14 +123,16 @@ class FirebaseTimelinePostService extends TimelinePostService .get() : await _db.collection(_options.timelineCollectionName).get(); - posts = []; + var fetchedPosts = []; for (var doc in snapshot.docs) { var data = doc.data(); var user = await _userService.getUser(data['creator_id']); var post = TimelinePost.fromJson(doc.id, data).copyWith(creator: user); - posts.add(post); + fetchedPosts.add(post); } + posts = fetchedPosts; + notifyListeners(); return posts; } From d16cd74a33b9d61ded0cbed436a0368df3275838 Mon Sep 17 00:00:00 2001 From: Jacques Date: Tue, 30 Jan 2024 10:58:19 +0100 Subject: [PATCH 10/10] fix: Proper service is being used for filtering --- .../lib/src/screens/timeline_screen.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/flutter_timeline_view/lib/src/screens/timeline_screen.dart b/packages/flutter_timeline_view/lib/src/screens/timeline_screen.dart index 30c6ac0..9bd5a82 100644 --- a/packages/flutter_timeline_view/lib/src/screens/timeline_screen.dart +++ b/packages/flutter_timeline_view/lib/src/screens/timeline_screen.dart @@ -94,9 +94,9 @@ class _TimelineScreenState extends State { var posts = widget.posts ?? service.postService.getPosts(category); if (widget.filterEnabled && filterWord != null) { - if (service is TimelineFilterService?) { - posts = - (service as TimelineFilterService).filterPosts(filterWord!, {}); + if (service.postService is TimelineFilterService) { + posts = (service.postService as TimelineFilterService) + .filterPosts(filterWord!, {}); } else { debugPrint('Timeline service needs to mixin' ' with TimelineFilterService');