feat(user-stories): add all routes to gorouter and navigator user stories

This commit is contained in:
FahadFahim71 2024-03-05 15:27:09 +01:00
parent 898583d1d1
commit 07f5872ec6
10 changed files with 325 additions and 150 deletions

View file

@ -37,43 +37,9 @@ class _MyHomePageState extends State<MyHomePage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return timeLineNavigatorUserStory(
floatingActionButton: Column( context: context,
mainAxisAlignment: MainAxisAlignment.end, configuration: getConfig(timelineService),
children: [
FloatingActionButton(
heroTag: 'btn1',
onPressed: () => createPost(
context,
timelineService,
timelineOptions,
getConfig(timelineService),
),
child: const Icon(
Icons.edit,
color: Colors.white,
),
),
const SizedBox(
height: 8,
),
FloatingActionButton(
heroTag: 'btn2',
onPressed: () => generatePost(timelineService),
child: const Icon(
Icons.add,
color: Colors.white,
),
),
],
),
body: SafeArea(
child: timeLineNavigatorUserStory(
configuration: getConfig(
timelineService,
),
context: context),
),
); );
} }
} }

View file

@ -6,6 +6,7 @@ TimelineUserStoryConfiguration getConfig(TimelineService service) {
service: service, service: service,
userId: 'test_user', userId: 'test_user',
optionsBuilder: (context) => options, optionsBuilder: (context) => options,
enablePostOverviewScreen: false,
); );
} }

View file

@ -5,8 +5,8 @@
/// Flutter Timeline library /// Flutter Timeline library
library flutter_timeline; library flutter_timeline;
export 'package:flutter_timeline/src/flutter_timeline_gorouter_userstory.dart';
export 'package:flutter_timeline/src/flutter_timeline_navigator_userstory.dart'; export 'package:flutter_timeline/src/flutter_timeline_navigator_userstory.dart';
export 'package:flutter_timeline/src/flutter_timeline_userstory.dart';
export 'package:flutter_timeline/src/models/timeline_configuration.dart'; export 'package:flutter_timeline/src/models/timeline_configuration.dart';
export 'package:flutter_timeline/src/routes.dart'; export 'package:flutter_timeline/src/routes.dart';
export 'package:flutter_timeline_interface/flutter_timeline_interface.dart'; export 'package:flutter_timeline_interface/flutter_timeline_interface.dart';

View file

@ -0,0 +1,172 @@
// SPDX-FileCopyrightText: 2023 Iconica
//
// 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:go_router/go_router.dart';
/// Retrieves a list of GoRouter routes for timeline stories.
///
/// This function retrieves a list of GoRouter routes for displaying timeline
/// stories. It takes an optional [TimelineUserStoryConfiguration] as parameter.
/// If no configuration is provided, default values will be used.
List<GoRoute> getTimelineStoryRoutes({
TimelineUserStoryConfiguration? configuration,
}) {
var config = configuration ??
TimelineUserStoryConfiguration(
userId: 'test_user',
service: TimelineService(
postService: LocalTimelinePostService(),
),
optionsBuilder: (context) => const TimelineOptions(),
);
return <GoRoute>[
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,
);
return buildScreenWithoutTransition(
context: context,
state: state,
child: config.openPageBuilder?.call(
context,
timelineScreen,
) ??
Scaffold(
appBar: AppBar(),
body: timelineScreen,
floatingActionButton: FloatingActionButton(
onPressed: () async => context.go(
TimelineUserStoryRoutes.timelinePostCreation,
),
child: const Icon(Icons.add),
),
),
);
},
),
GoRoute(
path: TimelineUserStoryRoutes.timelineView,
pageBuilder: (context, state) {
var post =
config.service.postService.getPost(state.pathParameters['post']!)!;
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(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios),
onPressed: () =>
context.go(TimelineUserStoryRoutes.timelineHome),
),
),
body: timelinePostWidget,
),
);
},
),
GoRoute(
path: TimelineUserStoryRoutes.timelinePostCreation,
pageBuilder: (context, state) {
var timelinePostCreationWidget = TimelinePostCreationScreen(
userId: config.userId,
options: config.optionsBuilder(context),
service: config.service,
onPostCreated: (post) async {
await config.service.postService.createPost(post);
if (context.mounted) {
context.go(TimelineUserStoryRoutes.timelineHome);
}
},
onPostOverview: (post) async => context.push(
TimelineUserStoryRoutes.timelinePostOverview,
extra: post,
),
enablePostOverviewScreen: config.enablePostOverviewScreen,
);
return buildScreenWithoutTransition(
context: context,
state: state,
child: config.openPageBuilder?.call(
context,
timelinePostCreationWidget,
) ??
Scaffold(
appBar: AppBar(
title: Text(
config.optionsBuilder(context).translations.postCreation,
),
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios),
onPressed: () =>
context.go(TimelineUserStoryRoutes.timelineHome),
),
),
body: timelinePostCreationWidget,
),
);
},
),
GoRoute(
path: TimelineUserStoryRoutes.timelinePostOverview,
pageBuilder: (context, state) {
var post = state.extra! as TimelinePost;
var timelinePostOverviewWidget = TimelinePostOverviewScreen(
options: config.optionsBuilder(context),
service: config.service,
timelinePost: post,
onPostSubmit: (post) async {
await config.service.postService.createPost(post);
if (context.mounted) {
context.go(TimelineUserStoryRoutes.timelineHome);
}
},
);
return buildScreenWithoutTransition(
context: context,
state: state,
child: config.openPageBuilder?.call(
context,
timelinePostOverviewWidget,
) ??
timelinePostOverviewWidget,
);
},
),
];
}

View file

@ -44,7 +44,20 @@ Widget _timelineScreenRoute({
optionsBuilder: (context) => const TimelineOptions(), optionsBuilder: (context) => const TimelineOptions(),
); );
return TimelineScreen( return Scaffold(
appBar: AppBar(),
floatingActionButton: FloatingActionButton(
onPressed: () async => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => _postCreationScreenRoute(
configuration: config,
context: context,
),
),
),
child: const Icon(Icons.add),
),
body: TimelineScreen(
service: config.service, service: config.service,
options: config.optionsBuilder(context), options: config.optionsBuilder(context),
userId: config.userId, userId: config.userId,
@ -64,6 +77,7 @@ Widget _timelineScreenRoute({
}, },
filterEnabled: config.filterEnabled, filterEnabled: config.filterEnabled,
postWidgetBuilder: config.postWidgetBuilder, postWidgetBuilder: config.postWidgetBuilder,
),
); );
} }
@ -98,3 +112,98 @@ Widget _postDetailScreenRoute({
}, },
); );
} }
/// A widget function that creates a post creation screen route.
///
/// This function creates a route for displaying a post creation screen. It takes
/// a [BuildContext] and an optional [TimelineUserStoryConfiguration] as
/// parameters. If no configuration is provided, default values will be used.
Widget _postCreationScreenRoute({
required BuildContext context,
TimelineUserStoryConfiguration? configuration,
}) {
var config = configuration ??
TimelineUserStoryConfiguration(
userId: 'test_user',
service: TimelineService(
postService: LocalTimelinePostService(),
),
optionsBuilder: (context) => const TimelineOptions(),
);
return Scaffold(
appBar: AppBar(
title: Text(
config.optionsBuilder(context).translations.postCreation,
),
),
body: TimelinePostCreationScreen(
userId: config.userId,
service: config.service,
options: config.optionsBuilder(context),
onPostCreated: (post) async {
await config.service.postService.createPost(post);
if (context.mounted) {
await Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) =>
_timelineScreenRoute(configuration: config, context: context),
),
);
}
},
onPostOverview: (post) async {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => _postOverviewScreenRoute(
configuration: config,
context: context,
post: post,
),
),
);
},
enablePostOverviewScreen: config.enablePostOverviewScreen,
),
);
}
/// A widget function that creates a post overview screen route.
///
/// This function creates a route for displaying a post overview screen. It takes
/// a [BuildContext], a [TimelinePost], and an optional
/// [TimelineUserStoryConfiguration] as parameters. If no configuration is
/// provided, default values will be used.
Widget _postOverviewScreenRoute({
required BuildContext context,
required TimelinePost post,
TimelineUserStoryConfiguration? configuration,
}) {
var config = configuration ??
TimelineUserStoryConfiguration(
userId: 'test_user',
service: TimelineService(
postService: LocalTimelinePostService(),
),
optionsBuilder: (context) => const TimelineOptions(),
);
return TimelinePostOverviewScreen(
timelinePost: post,
options: config.optionsBuilder(context),
service: config.service,
onPostSubmit: (post) async {
await config.service.postService.createPost(post);
if (context.mounted) {
await Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) =>
_timelineScreenRoute(configuration: config, context: context),
),
);
}
},
);
}

View file

@ -1,87 +0,0 @@
// SPDX-FileCopyrightText: 2023 Iconica
//
// 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:go_router/go_router.dart';
/// Retrieves a list of GoRouter routes for timeline stories.
///
/// This function retrieves a list of GoRouter routes for displaying timeline
/// stories. It takes an optional [TimelineUserStoryConfiguration] as parameter.
/// If no configuration is provided, default values will be used.
List<GoRoute> getTimelineStoryRoutes({
TimelineUserStoryConfiguration? configuration,
}) {
var config = configuration ??
TimelineUserStoryConfiguration(
userId: 'test_user',
service: TimelineService(
postService: LocalTimelinePostService(),
),
optionsBuilder: (context) => const TimelineOptions(),
);
return <GoRoute>[
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,
);
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']!)!;
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,
),
);
},
),
];
}

View file

@ -6,4 +6,6 @@ mixin TimelineUserStoryRoutes {
static const String timelineHome = '/timeline'; static const String timelineHome = '/timeline';
static const String timelineView = '/timeline-view/:post'; static const String timelineView = '/timeline-view/:post';
static String timelineViewPath(String postId) => '/timeline-view/$postId'; static String timelineViewPath(String postId) => '/timeline-view/$postId';
static const String timelinePostCreation = '/timeline-post-creation';
static String timelinePostOverview = '/timeline-post-overview';
} }

View file

@ -31,6 +31,7 @@ class TimelineTranslations {
required this.searchHint, required this.searchHint,
required this.postOverview, required this.postOverview,
required this.postIn, required this.postIn,
required this.postCreation,
}); });
const TimelineTranslations.empty() const TimelineTranslations.empty()
@ -58,7 +59,8 @@ class TimelineTranslations {
timelineSelectionDescription = 'Choose a category', timelineSelectionDescription = 'Choose a category',
searchHint = 'Search...', searchHint = 'Search...',
postOverview = 'Post Overview', postOverview = 'Post Overview',
postIn = 'Post in'; postIn = 'Post in',
postCreation = 'Create Post';
final String noPosts; final String noPosts;
final String noPostsWithFilter; final String noPostsWithFilter;
@ -89,6 +91,7 @@ class TimelineTranslations {
final String postOverview; final String postOverview;
final String postIn; final String postIn;
final String postCreation;
TimelineTranslations copyWith({ TimelineTranslations copyWith({
String? noPosts, String? noPosts,
@ -115,6 +118,7 @@ class TimelineTranslations {
String? searchHint, String? searchHint,
String? postOverview, String? postOverview,
String? postIn, String? postIn,
String? postCreation,
}) => }) =>
TimelineTranslations( TimelineTranslations(
noPosts: noPosts ?? this.noPosts, noPosts: noPosts ?? this.noPosts,
@ -144,5 +148,6 @@ class TimelineTranslations {
searchHint: searchHint ?? this.searchHint, searchHint: searchHint ?? this.searchHint,
postOverview: postOverview ?? this.postOverview, postOverview: postOverview ?? this.postOverview,
postIn: postIn ?? this.postIn, postIn: postIn ?? this.postIn,
postCreation: postCreation ?? this.postCreation,
); );
} }

View file

@ -19,6 +19,7 @@ class TimelinePostCreationScreen extends StatefulWidget {
required this.options, required this.options,
this.postCategory, this.postCategory,
this.onPostOverview, this.onPostOverview,
this.enablePostOverviewScreen = false,
super.key, super.key,
}); });
@ -37,6 +38,7 @@ class TimelinePostCreationScreen extends StatefulWidget {
/// Nullable callback for routing to the post overview /// Nullable callback for routing to the post overview
final void Function(TimelinePost)? onPostOverview; final void Function(TimelinePost)? onPostOverview;
final bool enablePostOverviewScreen;
@override @override
State<TimelinePostCreationScreen> createState() => State<TimelinePostCreationScreen> createState() =>
@ -107,11 +109,10 @@ class _TimelinePostCreationScreenState
image: image, image: image,
); );
if (widget.onPostOverview != null) { if (widget.enablePostOverviewScreen) {
widget.onPostOverview?.call(post); widget.onPostOverview?.call(post);
} else { } else {
var newPost = await widget.service.postService.createPost(post); widget.onPostCreated.call(post);
widget.onPostCreated.call(newPost);
} }
} }
@ -287,7 +288,9 @@ class _TimelinePostCreationScreenState
} }
: null, : null,
child: Text( child: Text(
widget.options.translations.checkPost, widget.enablePostOverviewScreen
? widget.options.translations.checkPost
: widget.options.translations.postCreation,
style: theme.textTheme.bodyMedium, style: theme.textTheme.bodyMedium,
), ),
), ),

View file

@ -78,7 +78,10 @@ class _TimelineScreenState extends State<TimelineScreen> {
void initState() { void initState() {
super.initState(); super.initState();
controller = widget.scrollController ?? ScrollController(); controller = widget.scrollController ?? ScrollController();
// only load the posts after the first frame
WidgetsBinding.instance.addPostFrameCallback((_) {
unawaited(loadPosts()); unawaited(loadPosts());
});
} }
@override @override
@ -91,6 +94,7 @@ class _TimelineScreenState extends State<TimelineScreen> {
return ListenableBuilder( return ListenableBuilder(
listenable: service.postService, listenable: service.postService,
builder: (context, _) { builder: (context, _) {
if (!context.mounted) return const SizedBox();
var posts = widget.posts ?? service.postService.getPosts(category); var posts = widget.posts ?? service.postService.getPosts(category);
if (widget.filterEnabled && filterWord != null) { if (widget.filterEnabled && filterWord != null) {
@ -271,7 +275,7 @@ class _TimelineScreenState extends State<TimelineScreen> {
} }
Future<void> loadPosts() async { Future<void> loadPosts() async {
if (widget.posts != null) return; if (widget.posts != null || !context.mounted) return;
try { try {
await service.postService.fetchPosts(category); await service.postService.fetchPosts(category);
setState(() { setState(() {