mirror of
https://github.com/Iconica-Development/flutter_timeline.git
synced 2025-05-19 02:23:46 +02:00
feat(user-stories): add all routes to gorouter and navigator user stories
This commit is contained in:
parent
898583d1d1
commit
07f5872ec6
10 changed files with 325 additions and 150 deletions
|
@ -37,43 +37,9 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
floatingActionButton: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
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),
|
||||
),
|
||||
return timeLineNavigatorUserStory(
|
||||
context: context,
|
||||
configuration: getConfig(timelineService),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ TimelineUserStoryConfiguration getConfig(TimelineService service) {
|
|||
service: service,
|
||||
userId: 'test_user',
|
||||
optionsBuilder: (context) => options,
|
||||
enablePostOverviewScreen: false,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
/// Flutter Timeline library
|
||||
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_userstory.dart';
|
||||
export 'package:flutter_timeline/src/models/timeline_configuration.dart';
|
||||
export 'package:flutter_timeline/src/routes.dart';
|
||||
export 'package:flutter_timeline_interface/flutter_timeline_interface.dart';
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
),
|
||||
];
|
||||
}
|
|
@ -44,26 +44,40 @@ Widget _timelineScreenRoute({
|
|||
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,
|
||||
),
|
||||
return Scaffold(
|
||||
appBar: AppBar(),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () async => Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => _postCreationScreenRoute(
|
||||
configuration: config,
|
||||
context: context,
|
||||
),
|
||||
),
|
||||
onUserTap: (userId) {
|
||||
config.onUserTap?.call(context, userId);
|
||||
},
|
||||
filterEnabled: config.filterEnabled,
|
||||
postWidgetBuilder: config.postWidgetBuilder,
|
||||
),
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
body: 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) {
|
||||
config.onUserTap?.call(context, userId);
|
||||
},
|
||||
filterEnabled: config.filterEnabled,
|
||||
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),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
];
|
||||
}
|
|
@ -6,4 +6,6 @@ mixin TimelineUserStoryRoutes {
|
|||
static const String timelineHome = '/timeline';
|
||||
static const String timelineView = '/timeline-view/:post';
|
||||
static String timelineViewPath(String postId) => '/timeline-view/$postId';
|
||||
static const String timelinePostCreation = '/timeline-post-creation';
|
||||
static String timelinePostOverview = '/timeline-post-overview';
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ class TimelineTranslations {
|
|||
required this.searchHint,
|
||||
required this.postOverview,
|
||||
required this.postIn,
|
||||
required this.postCreation,
|
||||
});
|
||||
|
||||
const TimelineTranslations.empty()
|
||||
|
@ -58,7 +59,8 @@ class TimelineTranslations {
|
|||
timelineSelectionDescription = 'Choose a category',
|
||||
searchHint = 'Search...',
|
||||
postOverview = 'Post Overview',
|
||||
postIn = 'Post in';
|
||||
postIn = 'Post in',
|
||||
postCreation = 'Create Post';
|
||||
|
||||
final String noPosts;
|
||||
final String noPostsWithFilter;
|
||||
|
@ -89,6 +91,7 @@ class TimelineTranslations {
|
|||
|
||||
final String postOverview;
|
||||
final String postIn;
|
||||
final String postCreation;
|
||||
|
||||
TimelineTranslations copyWith({
|
||||
String? noPosts,
|
||||
|
@ -115,6 +118,7 @@ class TimelineTranslations {
|
|||
String? searchHint,
|
||||
String? postOverview,
|
||||
String? postIn,
|
||||
String? postCreation,
|
||||
}) =>
|
||||
TimelineTranslations(
|
||||
noPosts: noPosts ?? this.noPosts,
|
||||
|
@ -144,5 +148,6 @@ class TimelineTranslations {
|
|||
searchHint: searchHint ?? this.searchHint,
|
||||
postOverview: postOverview ?? this.postOverview,
|
||||
postIn: postIn ?? this.postIn,
|
||||
postCreation: postCreation ?? this.postCreation,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ class TimelinePostCreationScreen extends StatefulWidget {
|
|||
required this.options,
|
||||
this.postCategory,
|
||||
this.onPostOverview,
|
||||
this.enablePostOverviewScreen = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
@ -37,6 +38,7 @@ class TimelinePostCreationScreen extends StatefulWidget {
|
|||
|
||||
/// Nullable callback for routing to the post overview
|
||||
final void Function(TimelinePost)? onPostOverview;
|
||||
final bool enablePostOverviewScreen;
|
||||
|
||||
@override
|
||||
State<TimelinePostCreationScreen> createState() =>
|
||||
|
@ -107,11 +109,10 @@ class _TimelinePostCreationScreenState
|
|||
image: image,
|
||||
);
|
||||
|
||||
if (widget.onPostOverview != null) {
|
||||
if (widget.enablePostOverviewScreen) {
|
||||
widget.onPostOverview?.call(post);
|
||||
} else {
|
||||
var newPost = await widget.service.postService.createPost(post);
|
||||
widget.onPostCreated.call(newPost);
|
||||
widget.onPostCreated.call(post);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -287,7 +288,9 @@ class _TimelinePostCreationScreenState
|
|||
}
|
||||
: null,
|
||||
child: Text(
|
||||
widget.options.translations.checkPost,
|
||||
widget.enablePostOverviewScreen
|
||||
? widget.options.translations.checkPost
|
||||
: widget.options.translations.postCreation,
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
|
|
|
@ -78,7 +78,10 @@ class _TimelineScreenState extends State<TimelineScreen> {
|
|||
void initState() {
|
||||
super.initState();
|
||||
controller = widget.scrollController ?? ScrollController();
|
||||
unawaited(loadPosts());
|
||||
// only load the posts after the first frame
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
unawaited(loadPosts());
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -91,6 +94,7 @@ class _TimelineScreenState extends State<TimelineScreen> {
|
|||
return ListenableBuilder(
|
||||
listenable: service.postService,
|
||||
builder: (context, _) {
|
||||
if (!context.mounted) return const SizedBox();
|
||||
var posts = widget.posts ?? service.postService.getPosts(category);
|
||||
|
||||
if (widget.filterEnabled && filterWord != null) {
|
||||
|
@ -271,7 +275,7 @@ class _TimelineScreenState extends State<TimelineScreen> {
|
|||
}
|
||||
|
||||
Future<void> loadPosts() async {
|
||||
if (widget.posts != null) return;
|
||||
if (widget.posts != null || !context.mounted) return;
|
||||
try {
|
||||
await service.postService.fetchPosts(category);
|
||||
setState(() {
|
||||
|
|
Loading…
Reference in a new issue