Merge pull request #29 from Iconica-Development/2.2.0

feat(user-stories): add all routes to gorouter and navigator user stories
This commit is contained in:
Gorter-dev 2024-03-15 09:27:34 +01:00 committed by GitHub
commit 27e3c34b96
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 357 additions and 161 deletions

View file

@ -1,3 +1,9 @@
## 2.2.0
- Add all routes to gorouter and navigator user stories
- Added enablePostOverviewScreen to config
- Update flutter_image_picker to 1.0.5
## 2.1.0 ## 2.1.0
- Fixed multiline textfield not being dismissible. - Fixed multiline textfield not being dismissible.

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,
); );
} }
@ -71,7 +72,7 @@ void createPost(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => Scaffold( builder: (context) => Scaffold(
body: TimelinePostCreationScreen( body: TimelinePostCreationScreen(
postCategory: null, postCategory: 'category1',
userId: 'test_user', userId: 'test_user',
service: service, service: service,
options: options, options: options,
@ -81,6 +82,7 @@ void createPost(
onPostOverview: (post) { onPostOverview: (post) {
navigateToOverview(context, service, options, post); navigateToOverview(context, service, options, post);
}, },
enablePostOverviewScreen: configuration.enablePostOverviewScreen,
), ),
), ),
), ),

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,179 @@
// 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) {
if (config.afterPostCreationGoHome) {
context.go(TimelineUserStoryRoutes.timelineHome);
} else {
context.go(
TimelineUserStoryRoutes.timelinePostOverview,
extra: post,
);
}
}
},
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,26 +44,40 @@ Widget _timelineScreenRoute({
optionsBuilder: (context) => const TimelineOptions(), optionsBuilder: (context) => const TimelineOptions(),
); );
return TimelineScreen( return Scaffold(
service: config.service, appBar: AppBar(),
options: config.optionsBuilder(context), floatingActionButton: FloatingActionButton(
userId: config.userId, onPressed: () async => Navigator.of(context).push(
onPostTap: (post) async => MaterialPageRoute(
config.onPostTap?.call(context, post) ?? builder: (context) => _postCreationScreenRoute(
Navigator.of(context).push( configuration: config,
MaterialPageRoute( context: context,
builder: (context) => _postDetailScreenRoute(
configuration: config,
context: context,
post: post,
),
), ),
), ),
onUserTap: (userId) { ),
config.onUserTap?.call(context, userId); child: const Icon(Icons.add),
}, ),
filterEnabled: config.filterEnabled, body: TimelineScreen(
postWidgetBuilder: config.postWidgetBuilder, 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),
),
);
}
},
);
}

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

@ -55,6 +55,7 @@ class TimelineUserStoryConfiguration {
this.onPostDelete, this.onPostDelete,
this.filterEnabled = false, this.filterEnabled = false,
this.postWidgetBuilder, this.postWidgetBuilder,
this.afterPostCreationGoHome = false,
this.enablePostOverviewScreen = false, this.enablePostOverviewScreen = false,
}); });
@ -88,4 +89,8 @@ class TimelineUserStoryConfiguration {
/// Boolean to enable timeline post overview screen before submitting /// Boolean to enable timeline post overview screen before submitting
final bool enablePostOverviewScreen; final bool enablePostOverviewScreen;
/// Boolean to enable redirect to home after post creation.
/// If false, it will redirect to created post screen
final bool afterPostCreationGoHome;
} }

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

@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
name: flutter_timeline name: flutter_timeline
description: Visual elements and interface combined into one package description: Visual elements and interface combined into one package
version: 2.1.0 version: 2.2.0
publish_to: none publish_to: none
@ -19,13 +19,13 @@ dependencies:
git: git:
url: https://github.com/Iconica-Development/flutter_timeline url: https://github.com/Iconica-Development/flutter_timeline
path: packages/flutter_timeline_view path: packages/flutter_timeline_view
ref: 2.1.0 ref: 2.2.0
flutter_timeline_interface: flutter_timeline_interface:
git: git:
url: https://github.com/Iconica-Development/flutter_timeline url: https://github.com/Iconica-Development/flutter_timeline
path: packages/flutter_timeline_interface path: packages/flutter_timeline_interface
ref: 2.1.0 ref: 2.2.0
dev_dependencies: dev_dependencies:
flutter_lints: ^2.0.0 flutter_lints: ^2.0.0

View file

@ -4,7 +4,7 @@
name: flutter_timeline_firebase name: flutter_timeline_firebase
description: Implementation of the Flutter Timeline interface for Firebase. description: Implementation of the Flutter Timeline interface for Firebase.
version: 2.1.0 version: 2.2.0
publish_to: none publish_to: none
@ -23,7 +23,7 @@ dependencies:
git: git:
url: https://github.com/Iconica-Development/flutter_timeline url: https://github.com/Iconica-Development/flutter_timeline
path: packages/flutter_timeline_interface path: packages/flutter_timeline_interface
ref: 2.1.0 ref: 2.2.0
dev_dependencies: dev_dependencies:
flutter_lints: ^2.0.0 flutter_lints: ^2.0.0

View file

@ -4,7 +4,7 @@
name: flutter_timeline_interface name: flutter_timeline_interface
description: Interface for the service of the Flutter Timeline component description: Interface for the service of the Flutter Timeline component
version: 2.1.0 version: 2.2.0
publish_to: none publish_to: none

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

@ -30,7 +30,9 @@ class TimelinePostOverviewScreen extends StatelessWidget {
userId: timelinePost.creatorId, userId: timelinePost.creatorId,
options: options, options: options,
post: timelinePost, post: timelinePost,
onPostDelete: () {}, onPostDelete: () async {
await service.postService.deletePost(timelinePost);
},
service: service, service: service,
), ),
), ),

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();
unawaited(loadPosts()); // only load the posts after the first frame
WidgetsBinding.instance.addPostFrameCallback((_) {
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(() {

View file

@ -4,7 +4,7 @@
name: flutter_timeline_view name: flutter_timeline_view
description: Visual elements of the Flutter Timeline Component description: Visual elements of the Flutter Timeline Component
version: 2.1.0 version: 2.2.0
publish_to: none publish_to: none
@ -23,11 +23,11 @@ dependencies:
git: git:
url: https://github.com/Iconica-Development/flutter_timeline url: https://github.com/Iconica-Development/flutter_timeline
path: packages/flutter_timeline_interface path: packages/flutter_timeline_interface
ref: 2.1.0 ref: 2.2.0
flutter_image_picker: flutter_image_picker:
git: git:
url: https://github.com/Iconica-Development/flutter_image_picker url: https://github.com/Iconica-Development/flutter_image_picker
ref: 1.0.4 ref: 1.0.5
collection: any collection: any
dev_dependencies: dev_dependencies: