From 0035e1f4fbe02337532ce3dc376e69e2fb79697a Mon Sep 17 00:00:00 2001 From: Freek van de Ven Date: Fri, 1 Dec 2023 07:50:02 +0100 Subject: [PATCH] feat: complete timeline userstory --- .../lib/flutter_timeline.dart | 1 + .../lib/src/flutter_timeline_userstory.dart | 133 ++++++++++++++---- .../service/firebase_timeline_service.dart | 6 + .../lib/src/services/timeline_service.dart | 1 + .../lib/src/config/timeline_options.dart | 4 + .../lib/src/screens/timeline_screen.dart | 6 +- 6 files changed, 121 insertions(+), 30 deletions(-) diff --git a/packages/flutter_timeline/lib/flutter_timeline.dart b/packages/flutter_timeline/lib/flutter_timeline.dart index cdc1254..9de6fe8 100644 --- a/packages/flutter_timeline/lib/flutter_timeline.dart +++ b/packages/flutter_timeline/lib/flutter_timeline.dart @@ -4,5 +4,6 @@ /// Flutter Timeline library library flutter_timeline; +export 'package:flutter_timeline/src/flutter_timeline_userstory.dart'; export 'package:flutter_timeline_interface/flutter_timeline_interface.dart'; export 'package:flutter_timeline_view/flutter_timeline_view.dart'; diff --git a/packages/flutter_timeline/lib/src/flutter_timeline_userstory.dart b/packages/flutter_timeline/lib/src/flutter_timeline_userstory.dart index 7b7be2d..1cae699 100644 --- a/packages/flutter_timeline/lib/src/flutter_timeline_userstory.dart +++ b/packages/flutter_timeline/lib/src/flutter_timeline_userstory.dart @@ -5,9 +5,12 @@ import 'package:go_router/go_router.dart'; mixin TimelineUserStoryRoutes { static const String timelineHome = '/timeline'; - static String timelineCreate(String category) => '/timeline-create/$category'; + static const String timelineCreate = '/timeline-create/:category'; + static String timelineCreatePath(String category) => + '/timeline-create/$category'; static const String timelineSelect = '/timeline-select'; - static String timelineView(String postId) => '/timeline-view/$postId'; + static const String timelineView = '/timeline-view/:post'; + static String timelineViewPath(String postId) => '/timeline-view/$postId'; } class TimelineUserStoryConfiguration { @@ -15,22 +18,31 @@ class TimelineUserStoryConfiguration { required this.optionsBuilder, required this.userId, required this.service, - this.pageBuilder, + required this.userService, + this.mainPageBuilder, + this.postScreenBuilder, + this.postCreationScreenBuilder, + this.postSelectionScreenBuilder, this.onUserTap, - this.timelinePostHeight, }); final String userId; - final double? timelinePostHeight; final Function(String userId)? onUserTap; - final Widget Function(Widget child)? pageBuilder; + final Widget Function(Widget filterBar, Widget child)? mainPageBuilder; + + final Widget Function(Widget child)? postScreenBuilder; + + final Widget Function(Widget child)? postCreationScreenBuilder; + + final Widget Function(Widget child)? postSelectionScreenBuilder; final TimelineService service; - final TimelineOptions Function(BuildContext context) - optionsBuilder; // New callback + final TimelineUserService userService; + + final TimelineOptions Function(BuildContext context) optionsBuilder; } List getTimelineStoryRoutes( @@ -39,22 +51,93 @@ List getTimelineStoryRoutes( [ GoRoute( path: TimelineUserStoryRoutes.timelineHome, - pageBuilder: (context, state) => buildScreenWithoutTransition( - context: context, - state: state, - child: Scaffold( - body: TimelineScreen( - userId: configuration.userId, - onUserTap: configuration.onUserTap, - service: configuration.service, - options: configuration.optionsBuilder(context), - timelinePostHeight: configuration.timelinePostHeight, - onPostTap: (_) async => context.push('/timeline-view/1'), - timelineCategoryFilter: 'category', - ), - ), - ), + pageBuilder: (context, state) { + var timelineScreen = TimelineScreen( + userId: configuration.userId, + onUserTap: configuration.onUserTap, + service: configuration.service, + options: configuration.optionsBuilder(context), + onPostTap: (post) async => + TimelineUserStoryRoutes.timelineViewPath(post.id), + timelineCategoryFilter: 'news', + ); + return buildScreenWithoutTransition( + context: context, + state: state, + child: configuration.mainPageBuilder?.call( + Container(), // TODO(anyone): create a selection widget + timelineScreen, + ) ?? + Scaffold( + body: timelineScreen, + ), + ); + }, + ), + GoRoute( + path: TimelineUserStoryRoutes.timelineSelect, + pageBuilder: (context, state) { + var timelineSelectionWidget = + Container(); // TODO(anyone): create timeline selection screen + return buildScreenWithoutTransition( + context: context, + state: state, + child: configuration.postSelectionScreenBuilder?.call( + timelineSelectionWidget, + ) ?? + Scaffold( + body: timelineSelectionWidget, + ), + ); + }, + ), + GoRoute( + path: TimelineUserStoryRoutes.timelineCreate, + pageBuilder: (context, state) { + var timelineCreateWidget = TimelinePostCreationScreen( + userId: configuration.userId, + options: configuration.optionsBuilder(context), + postCategory: state.pathParameters['category'] ?? '', + service: configuration.service, + onPostCreated: (post) => context.go( + TimelineUserStoryRoutes.timelineViewPath(post.id), + ), + ); + return buildScreenWithoutTransition( + context: context, + state: state, + child: configuration.postCreationScreenBuilder + ?.call(timelineCreateWidget) ?? + Scaffold( + body: timelineCreateWidget, + ), + ); + }, + ), + GoRoute( + path: TimelineUserStoryRoutes.timelineView, + pageBuilder: (context, state) { + var timelinePostWidget = TimelinePostScreen( + userId: configuration.userId, + options: configuration.optionsBuilder(context), + service: configuration.service, + userService: configuration.userService, + post: configuration.service.getPost(state.pathParameters['post']!)!, + onPostDelete: () => context.go( + TimelineUserStoryRoutes.timelineHome, + ), + onUserTap: configuration.onUserTap, + ); + return buildScreenWithoutTransition( + context: context, + state: state, + child: configuration.postScreenBuilder?.call( + timelinePostWidget, + ) ?? + Scaffold( + body: timelinePostWidget, + ), + ); + }, ), - - /// Here come the other timeline screens that all use the same configuration ]; 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 03b76a9..cc91609 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 @@ -115,6 +115,12 @@ class FirebaseTimelineService with ChangeNotifier implements TimelineService { return updatedPost; } + @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) 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 feb456f..9606f43 100644 --- a/packages/flutter_timeline_interface/lib/src/services/timeline_service.dart +++ b/packages/flutter_timeline_interface/lib/src/services/timeline_service.dart @@ -13,6 +13,7 @@ abstract class TimelineService with ChangeNotifier { Future createPost(TimelinePost post); Future> fetchPosts(String? category); Future fetchPost(TimelinePost post); + TimelinePost? getPost(String postId); List getPosts(String? category); Future fetchPostDetails(TimelinePost post); Future reactToPost( diff --git a/packages/flutter_timeline_view/lib/src/config/timeline_options.dart b/packages/flutter_timeline_view/lib/src/config/timeline_options.dart index 5015244..a5d34d5 100644 --- a/packages/flutter_timeline_view/lib/src/config/timeline_options.dart +++ b/packages/flutter_timeline_view/lib/src/config/timeline_options.dart @@ -15,6 +15,7 @@ class TimelineOptions { this.translations = const TimelineTranslations(), this.imagePickerConfig = const ImagePickerConfig(), this.imagePickerTheme = const ImagePickerTheme(), + this.timelinePostHeight, this.allowAllDeletion = false, this.sortCommentsAscending = true, this.sortPostsAscending = false, @@ -44,6 +45,9 @@ class TimelineOptions { /// only the posts of the current user final bool allowAllDeletion; + /// The height of a post in the timeline + final double? timelinePostHeight; + final TimelineTranslations translations; final ButtonBuilder? buttonBuilder; 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 85a60a2..523593f 100644 --- a/packages/flutter_timeline_view/lib/src/screens/timeline_screen.dart +++ b/packages/flutter_timeline_view/lib/src/screens/timeline_screen.dart @@ -19,7 +19,6 @@ class TimelineScreen extends StatefulWidget { this.posts, this.controller, this.timelineCategoryFilter, - this.timelinePostHeight, this.padding = const EdgeInsets.symmetric(vertical: 12.0), super.key, }); @@ -38,9 +37,6 @@ class TimelineScreen extends StatefulWidget { final String? timelineCategoryFilter; - /// The height of a post in the timeline - final double? timelinePostHeight; - /// This is used if you want to pass in a list of posts instead /// of fetching them from the service final List? posts; @@ -106,7 +102,7 @@ class _TimelineScreenState extends State { userId: widget.userId, options: widget.options, post: post, - height: widget.timelinePostHeight, + height: widget.options.timelinePostHeight, onTap: () => widget.onPostTap.call(post), onTapLike: () async => widget.service.likePost(widget.userId, post),