From 09b66e99211c13472df4713f63fe5aae77f76752 Mon Sep 17 00:00:00 2001 From: mike doornenbal Date: Thu, 25 Apr 2024 14:47:16 +0200 Subject: [PATCH] feat: default styling and flow --- .../flutter_timeline_gorouter_userstory.dart | 121 ++++++- .../flutter_timeline_navigator_userstory.dart | 328 +++++++++++++----- .../src/models/timeline_configuration.dart | 10 +- packages/flutter_timeline/lib/src/routes.dart | 7 +- packages/flutter_timeline/pubspec.yaml | 6 +- .../flutter_timeline_firebase/pubspec.yaml | 4 +- .../flutter_timeline_interface/pubspec.yaml | 2 +- .../lib/src/config/timeline_options.dart | 83 ++++- .../lib/src/config/timeline_translations.dart | 158 ++++----- .../timeline_post_creation_screen.dart | 95 +++-- .../timeline_post_overview_screen.dart | 66 ++-- .../lib/src/screens/timeline_post_screen.dart | 58 ++-- .../lib/src/screens/timeline_screen.dart | 5 +- .../screens/timeline_selection_screen.dart | 64 ++-- .../lib/src/services/local_post_service.dart | 70 ++-- .../lib/src/widgets/category_selector.dart | 9 +- .../src/widgets/category_selector_button.dart | 69 ++-- .../lib/src/widgets/reaction_bottom.dart | 2 +- .../lib/src/widgets/tappable_image.dart | 16 +- .../lib/src/widgets/timeline_post_widget.dart | 26 +- packages/flutter_timeline_view/pubspec.yaml | 4 +- 21 files changed, 789 insertions(+), 414 deletions(-) diff --git a/packages/flutter_timeline/lib/src/flutter_timeline_gorouter_userstory.dart b/packages/flutter_timeline/lib/src/flutter_timeline_gorouter_userstory.dart index 441b70e..41e68fc 100644 --- a/packages/flutter_timeline/lib/src/flutter_timeline_gorouter_userstory.dart +++ b/packages/flutter_timeline/lib/src/flutter_timeline_gorouter_userstory.dart @@ -43,9 +43,9 @@ List getTimelineStoryRoutes({ ); var button = FloatingActionButton( - backgroundColor: Theme.of(context).primaryColor, - onPressed: () async => context.go( - TimelineUserStoryRoutes.timelinePostCreation, + backgroundColor: const Color(0xff71C6D1), + onPressed: () async => context.push( + TimelineUserStoryRoutes.timelineCategorySelection, ), shape: const CircleBorder(), child: const Icon( @@ -62,10 +62,17 @@ List getTimelineStoryRoutes({ ?.call(context, timelineScreen, button) ?? Scaffold( appBar: AppBar( - backgroundColor: Colors.black, + backgroundColor: const Color(0xff212121), title: Text( - 'Iconinstagram', - style: Theme.of(context).textTheme.titleLarge, + config + .optionsBuilder(context) + .translations + .timeLineScreenTitle!, + style: const TextStyle( + color: Color(0xff71C6D1), + fontSize: 24, + fontWeight: FontWeight.w800, + ), ), ), body: timelineScreen, @@ -74,6 +81,51 @@ List getTimelineStoryRoutes({ ); }, ), + GoRoute( + path: TimelineUserStoryRoutes.timelineCategorySelection, + pageBuilder: (context, state) { + var timelineSelectionScreen = TimelineSelectionScreen( + options: config.optionsBuilder(context), + categories: config + .optionsBuilder(context) + .categoriesOptions + .categoriesBuilder!(context), + onCategorySelected: (category) async { + await context.push( + TimelineUserStoryRoutes.timelinepostCreation(category.title), + ); + }, + ); + + var backButton = IconButton( + color: Colors.white, + icon: const Icon(Icons.arrow_back_ios), + onPressed: () => context.go(TimelineUserStoryRoutes.timelineHome), + ); + + return buildScreenWithoutTransition( + context: context, + state: state, + child: config.categorySelectionOpenPageBuilder + ?.call(context, timelineSelectionScreen) ?? + Scaffold( + appBar: AppBar( + leading: backButton, + backgroundColor: const Color(0xff212121), + title: Text( + config.optionsBuilder(context).translations.postCreation!, + style: const TextStyle( + color: Color(0xff71C6D1), + fontSize: 24, + fontWeight: FontWeight.w800, + ), + ), + ), + body: timelineSelectionScreen, + ), + ); + }, + ), GoRoute( path: TimelineUserStoryRoutes.timelineView, pageBuilder: (context, state) { @@ -103,10 +155,14 @@ List getTimelineStoryRoutes({ Scaffold( appBar: AppBar( leading: backButton, - backgroundColor: Colors.black, + backgroundColor: const Color(0xff212121), title: Text( - 'Category', - style: Theme.of(context).textTheme.titleLarge, + post.category ?? 'Category', + style: const TextStyle( + color: Color(0xff71C6D1), + fontSize: 24, + fontWeight: FontWeight.w800, + ), ), ), body: timelinePostWidget, @@ -117,6 +173,7 @@ List getTimelineStoryRoutes({ GoRoute( path: TimelineUserStoryRoutes.timelinePostCreation, pageBuilder: (context, state) { + var category = state.pathParameters['category']; var timelinePostCreationWidget = TimelinePostCreationScreen( userId: config.userId, options: config.optionsBuilder(context), @@ -137,11 +194,16 @@ List getTimelineStoryRoutes({ extra: post, ), enablePostOverviewScreen: config.enablePostOverviewScreen, + postCategory: category, ); var backButton = IconButton( - icon: const Icon(Icons.arrow_back_ios), - onPressed: () => context.go(TimelineUserStoryRoutes.timelineHome), + icon: const Icon( + Icons.arrow_back_ios, + color: Colors.white, + ), + onPressed: () => + context.go(TimelineUserStoryRoutes.timelineCategorySelection), ); return buildScreenWithoutTransition( @@ -151,12 +213,16 @@ List getTimelineStoryRoutes({ ?.call(context, timelinePostCreationWidget, backButton) ?? Scaffold( appBar: AppBar( - backgroundColor: Colors.black, - title: Text( - config.optionsBuilder(context).translations.postCreation, - style: Theme.of(context).textTheme.titleLarge, - ), + backgroundColor: const Color(0xff212121), leading: backButton, + title: Text( + config.optionsBuilder(context).translations.postCreation!, + style: const TextStyle( + color: Color(0xff71C6D1), + fontSize: 24, + fontWeight: FontWeight.w800, + ), + ), ), body: timelinePostCreationWidget, ), @@ -179,6 +245,13 @@ List getTimelineStoryRoutes({ } }, ); + var backButton = IconButton( + icon: const Icon( + Icons.arrow_back_ios, + color: Colors.white, + ), + onPressed: () async => context.pop(), + ); return buildScreenWithoutTransition( context: context, @@ -187,7 +260,21 @@ List getTimelineStoryRoutes({ context, timelinePostOverviewWidget, ) ?? - timelinePostOverviewWidget, + Scaffold( + appBar: AppBar( + leading: backButton, + backgroundColor: const Color(0xff212121), + title: Text( + config.optionsBuilder(context).translations.postOverview!, + style: const TextStyle( + color: Color(0xff71C6D1), + fontSize: 24, + fontWeight: FontWeight.w800, + ), + ), + ), + body: timelinePostOverviewWidget, + ), ); }, ), 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 3e3c9ee..b9a2c4d 100644 --- a/packages/flutter_timeline/lib/src/flutter_timeline_navigator_userstory.dart +++ b/packages/flutter_timeline/lib/src/flutter_timeline_navigator_userstory.dart @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: BSD-3-Clause +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_timeline/flutter_timeline.dart'; @@ -44,41 +45,60 @@ Widget _timelineScreenRoute({ optionsBuilder: (context) => const TimelineOptions(), ); - 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, - 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, - ), + 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) ?? + 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, + ), + filterEnabled: config.filterEnabled, + postWidgetBuilder: config.postWidgetBuilder, + ); + + var button = FloatingActionButton( + backgroundColor: const Color(0xff71C6D1), + onPressed: () async => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => _postCategorySelectionScreen( + configuration: config, + context: context, + ), + ), + ), + shape: const CircleBorder(), + child: const Icon( + Icons.add, + color: Colors.white, + size: 30, ), ); + + return config.homeOpenPageBuilder?.call(context, timelineScreen, button) ?? + Scaffold( + appBar: AppBar( + backgroundColor: const Color(0xff212121), + title: Text( + config.optionsBuilder(context).translations.timeLineScreenTitle!, + style: const TextStyle( + color: Color(0xff71C6D1), + fontSize: 24, + fontWeight: FontWeight.w800, + ), + ), + ), + body: timelineScreen, + floatingActionButton: button, + ); } /// A widget function that creates a post detail screen route. @@ -101,16 +121,40 @@ Widget _postDetailScreenRoute({ optionsBuilder: (context) => const TimelineOptions(), ); - return TimelinePostScreen( + var timelinePostScreen = TimelinePostScreen( userId: config.userId, - service: config.service, options: config.optionsBuilder(context), + service: config.service, post: post, - onPostDelete: () async { - config.onPostDelete?.call(context, post) ?? - await config.service.postService.deletePost(post); - }, + onPostDelete: () async => + config.onPostDelete?.call(context, post) ?? + await config.service.postService.deletePost(post), + onUserTap: (user) => config.onUserTap?.call(context, user), ); + + var backButton = IconButton( + color: Colors.white, + icon: const Icon(Icons.arrow_back_ios), + onPressed: () => Navigator.of(context).pop(), + ); + + return config.postViewOpenPageBuilder + ?.call(context, timelinePostScreen, backButton) ?? + Scaffold( + appBar: AppBar( + leading: backButton, + backgroundColor: const Color(0xff212121), + title: Text( + post.category ?? 'Category', + style: const TextStyle( + color: Color(0xff71C6D1), + fontSize: 24, + fontWeight: FontWeight.w800, + ), + ), + ), + body: timelinePostScreen, + ); } /// A widget function that creates a post creation screen route. @@ -120,6 +164,7 @@ Widget _postDetailScreenRoute({ /// as parameters. If no configuration is provided, default values will be used. Widget _postCreationScreenRoute({ required BuildContext context, + required TimelineCategory category, TimelineUserStoryConfiguration? configuration, }) { var config = configuration ?? @@ -131,58 +176,75 @@ Widget _postCreationScreenRoute({ optionsBuilder: (context) => const TimelineOptions(), ); - return Scaffold( - appBar: AppBar( - title: Text( - style: Theme.of(context).textTheme.titleLarge, - config.optionsBuilder(context).translations.postCreation, + var timelinePostCreationScreen = TimelinePostCreationScreen( + userId: config.userId, + options: config.optionsBuilder(context), + service: config.service, + onPostCreated: (post) async { + var newPost = await config.service.postService.createPost(post); + if (context.mounted) { + if (config.afterPostCreationGoHome) { + await Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => _timelineScreenRoute( + configuration: config, + context: context, + ), + ), + ); + } else { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => _postOverviewScreenRoute( + configuration: config, + context: context, + post: newPost, + ), + ), + ); + } + } + }, + onPostOverview: (post) async => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => _postOverviewScreenRoute( + configuration: config, + context: context, + post: post, + ), ), ), - body: TimelinePostCreationScreen( - userId: config.userId, - service: config.service, - options: config.optionsBuilder(context), - onPostCreated: (post) async { - await config.service.postService.createPost(post); - if (context.mounted) { - if (config.afterPostCreationGoHome) { - await Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) => _timelineScreenRoute( - configuration: config, - context: context, - ), - ), - ); - } else { - await Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) => _postDetailScreenRoute( - configuration: config, - context: context, - post: post, - ), - ), - ); - } - } - }, - onPostOverview: (post) async { - await Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => _postOverviewScreenRoute( - configuration: config, - context: context, - post: post, + enablePostOverviewScreen: config.enablePostOverviewScreen, + postCategory: category.title, + ); + + var backButton = IconButton( + icon: const Icon( + Icons.arrow_back_ios, + color: Colors.white, + ), + onPressed: () => Navigator.of(context).pop(), + ); + + return config.postCreationOpenPageBuilder + ?.call(context, timelinePostCreationScreen, backButton) ?? + Scaffold( + appBar: AppBar( + backgroundColor: const Color(0xff212121), + leading: backButton, + title: Text( + config.optionsBuilder(context).translations.postCreation!, + style: const TextStyle( + color: Color(0xff71C6D1), + fontSize: 24, + fontWeight: FontWeight.w800, ), ), - ); - }, - enablePostOverviewScreen: config.enablePostOverviewScreen, - ), - ); + ), + body: timelinePostCreationScreen, + ); } /// A widget function that creates a post overview screen route. @@ -205,21 +267,109 @@ Widget _postOverviewScreenRoute({ optionsBuilder: (context) => const TimelineOptions(), ); - return TimelinePostOverviewScreen( - timelinePost: post, + var timelinePostOverviewWidget = TimelinePostOverviewScreen( options: config.optionsBuilder(context), service: config.service, + timelinePost: post, onPostSubmit: (post) async { await config.service.postService.createPost(post); if (context.mounted) { - await Navigator.pushReplacement( - context, + await Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute( builder: (context) => _timelineScreenRoute(configuration: config, context: context), ), + (route) => false, ); } }, + isOverviewScreen: true, ); + + var backButton = IconButton( + icon: const Icon( + Icons.arrow_back_ios, + color: Colors.white, + ), + onPressed: () async => Navigator.of(context).pop(), + ); + + return config.postOverviewOpenPageBuilder?.call( + context, + timelinePostOverviewWidget, + ) ?? + Scaffold( + appBar: AppBar( + leading: backButton, + backgroundColor: const Color(0xff212121), + title: Text( + config.optionsBuilder(context).translations.postOverview!, + style: const TextStyle( + color: Color(0xff71C6D1), + fontSize: 24, + fontWeight: FontWeight.w800, + ), + ), + ), + body: timelinePostOverviewWidget, + ); +} + +Widget _postCategorySelectionScreen({ + required BuildContext context, + TimelineUserStoryConfiguration? configuration, +}) { + var config = configuration ?? + TimelineUserStoryConfiguration( + userId: 'test_user', + service: TimelineService( + postService: LocalTimelinePostService(), + ), + optionsBuilder: (context) => const TimelineOptions(), + ); + + var timelineSelectionScreen = TimelineSelectionScreen( + options: config.optionsBuilder(context), + categories: config + .optionsBuilder(context) + .categoriesOptions + .categoriesBuilder!(context), + onCategorySelected: (category) async { + await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => _postCreationScreenRoute( + configuration: config, + context: context, + category: category, + ), + ), + ); + }, + ); + + var backButton = IconButton( + color: Colors.white, + icon: const Icon(Icons.arrow_back_ios), + onPressed: () async { + Navigator.of(context).pop(); + }, + ); + + return config.categorySelectionOpenPageBuilder + ?.call(context, timelineSelectionScreen) ?? + Scaffold( + appBar: AppBar( + leading: backButton, + backgroundColor: const Color(0xff212121), + title: Text( + config.optionsBuilder(context).translations.postCreation!, + style: const TextStyle( + color: Color(0xff71C6D1), + fontSize: 24, + fontWeight: FontWeight.w800, + ), + ), + ), + body: timelineSelectionScreen, + ); } diff --git a/packages/flutter_timeline/lib/src/models/timeline_configuration.dart b/packages/flutter_timeline/lib/src/models/timeline_configuration.dart index bec248d..264be11 100644 --- a/packages/flutter_timeline/lib/src/models/timeline_configuration.dart +++ b/packages/flutter_timeline/lib/src/models/timeline_configuration.dart @@ -59,7 +59,8 @@ class TimelineUserStoryConfiguration { this.filterEnabled = false, this.postWidgetBuilder, this.afterPostCreationGoHome = false, - this.enablePostOverviewScreen = false, + this.enablePostOverviewScreen = true, + this.categorySelectionOpenPageBuilder, }); /// The ID of the user associated with this user story configuration. @@ -132,4 +133,11 @@ class TimelineUserStoryConfiguration { /// Boolean to enable redirect to home after post creation. /// If false, it will redirect to created post screen final bool afterPostCreationGoHome; + + /// Open page builder function for the category selection page. This function + /// accepts a [BuildContext] and a child widget. + final Function( + BuildContext context, + Widget child, + )? categorySelectionOpenPageBuilder; } diff --git a/packages/flutter_timeline/lib/src/routes.dart b/packages/flutter_timeline/lib/src/routes.dart index 9900ad5..12e8712 100644 --- a/packages/flutter_timeline/lib/src/routes.dart +++ b/packages/flutter_timeline/lib/src/routes.dart @@ -6,6 +6,11 @@ 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 timelinepostCreation(String category) => + '/timeline-post-creation/$category'; + + static const String timelinePostCreation = + '/timeline-post-creation/:category'; static String timelinePostOverview = '/timeline-post-overview'; + static String timelineCategorySelection = '/timeline-category-selection'; } diff --git a/packages/flutter_timeline/pubspec.yaml b/packages/flutter_timeline/pubspec.yaml index 199bac3..6f443eb 100644 --- a/packages/flutter_timeline/pubspec.yaml +++ b/packages/flutter_timeline/pubspec.yaml @@ -3,7 +3,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later name: flutter_timeline description: Visual elements and interface combined into one package -version: 2.3.1 +version: 3.0.0 publish_to: none @@ -19,13 +19,13 @@ dependencies: git: url: https://github.com/Iconica-Development/flutter_timeline path: packages/flutter_timeline_view - ref: 2.3.1 + ref: 3.0.0 flutter_timeline_interface: git: url: https://github.com/Iconica-Development/flutter_timeline path: packages/flutter_timeline_interface - ref: 2.3.1 + ref: 3.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 cf9e59a..f25b071 100644 --- a/packages/flutter_timeline_firebase/pubspec.yaml +++ b/packages/flutter_timeline_firebase/pubspec.yaml @@ -4,7 +4,7 @@ name: flutter_timeline_firebase description: Implementation of the Flutter Timeline interface for Firebase. -version: 2.3.1 +version: 3.0.0 publish_to: none @@ -23,7 +23,7 @@ dependencies: git: url: https://github.com/Iconica-Development/flutter_timeline path: packages/flutter_timeline_interface - ref: 2.3.1 + ref: 3.0.0 dev_dependencies: flutter_lints: ^2.0.0 diff --git a/packages/flutter_timeline_interface/pubspec.yaml b/packages/flutter_timeline_interface/pubspec.yaml index ad6e991..05b3d0a 100644 --- a/packages/flutter_timeline_interface/pubspec.yaml +++ b/packages/flutter_timeline_interface/pubspec.yaml @@ -4,7 +4,7 @@ name: flutter_timeline_interface description: Interface for the service of the Flutter Timeline component -version: 2.3.1 +version: 3.0.0 publish_to: none 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 0f9aa93..e7c7c9b 100644 --- a/packages/flutter_timeline_view/lib/src/config/timeline_options.dart +++ b/packages/flutter_timeline_view/lib/src/config/timeline_options.dart @@ -12,7 +12,7 @@ import 'package:intl/intl.dart'; class TimelineOptions { const TimelineOptions({ this.theme = const TimelineTheme(), - this.translations = const TimelineTranslations.empty(), + this.translations = const TimelineTranslations(), this.imagePickerConfig = const ImagePickerConfig(), this.imagePickerTheme = const ImagePickerTheme(), this.timelinePostHeight, @@ -37,7 +37,8 @@ class TimelineOptions { this.userAvatarBuilder, this.anonymousAvatarBuilder, this.nameBuilder, - this.padding = const EdgeInsets.symmetric(vertical: 12.0), + this.padding = + const EdgeInsets.only(left: 12.0, top: 24.0, right: 12.0, bottom: 12.0), this.iconSize = 26, this.postWidgetHeight, this.postPadding = @@ -49,6 +50,10 @@ class TimelineOptions { this.maxTitleLength, this.minContentLength, this.maxContentLength, + this.categorySelectorButtonBuilder, + this.postOverviewButtonBuilder, + this.titleInputDecoration, + this.contentInputDecoration, }); /// Theming options for the timeline @@ -142,11 +147,69 @@ class TimelineOptions { /// Maximum length of the post content final int? maxContentLength; + + /// Builder for the category selector button + /// on the timeline category selection screen + final Widget Function( + BuildContext context, + Function() onPressed, + String text, + )? categorySelectorButtonBuilder; + + /// Builder for the post overview button + /// on the timeline post overview screen + final Widget Function( + BuildContext context, + Function() onPressed, + String text, + )? postOverviewButtonBuilder; + + /// inputdecoration for the title textfield + final InputDecoration? titleInputDecoration; + + /// inputdecoration for the content textfield + final InputDecoration? contentInputDecoration; } +List _getDefaultCategories(context) => [ + const TimelineCategory( + key: null, + title: 'All', + icon: Padding( + padding: EdgeInsets.only(right: 8.0), + child: Icon( + Icons.apps, + color: Colors.black, + ), + ), + ), + const TimelineCategory( + key: 'Category', + title: 'Category', + icon: Padding( + padding: EdgeInsets.only(right: 8.0), + child: Icon( + Icons.category, + color: Colors.black, + ), + ), + ), + const TimelineCategory( + key: 'Category with two lines', + title: 'Category with two lines', + icon: Padding( + padding: EdgeInsets.only(right: 8.0), + child: Icon( + Icons.category, + color: Colors.black, + ), + ), + ), + ]; + class CategoriesOptions { const CategoriesOptions({ - this.categoriesBuilder, + this.categoriesBuilder = _getDefaultCategories, this.categoryButtonBuilder, this.categorySelectorHorizontalPadding, }); @@ -157,12 +220,14 @@ class CategoriesOptions { categoriesBuilder; /// Abilty to override the standard category selector - final Widget Function({ - required String? categoryKey, - required String categoryName, - required Function onTap, - required bool selected, - })? categoryButtonBuilder; + final Widget Function( + String? categoryKey, + String categoryName, + Function() onTap, + // ignore: avoid_positional_boolean_parameters + bool selected, + bool isOnTop, + )? categoryButtonBuilder; /// Overides the standard horizontal padding of the whole category selector. final double? categorySelectorHorizontalPadding; diff --git a/packages/flutter_timeline_view/lib/src/config/timeline_translations.dart b/packages/flutter_timeline_view/lib/src/config/timeline_translations.dart index 1da4d9b..61f2d27 100644 --- a/packages/flutter_timeline_view/lib/src/config/timeline_translations.dart +++ b/packages/flutter_timeline_view/lib/src/config/timeline_translations.dart @@ -7,108 +7,78 @@ import 'package:flutter/material.dart'; @immutable class TimelineTranslations { const TimelineTranslations({ - required this.anonymousUser, - required this.noPosts, - required this.noPostsWithFilter, - required this.title, - required this.titleHintText, - required this.content, - required this.contentHintText, - required this.contentDescription, - required this.uploadImage, - required this.uploadImageDescription, - required this.allowComments, - required this.allowCommentsDescription, - required this.commentsTitleOnPost, - required this.checkPost, - required this.deletePost, - required this.deleteReaction, - required this.viewPost, - required this.likesTitle, - required this.commentsTitle, - required this.firstComment, - required this.writeComment, - required this.postAt, - required this.postLoadingError, - required this.timelineSelectionDescription, - required this.searchHint, - required this.postOverview, - required this.postIn, - required this.postCreation, - required this.yes, - required this.no, + this.anonymousUser = 'Anonymous user', + this.noPosts = 'No posts yet', + this.noPostsWithFilter = 'No posts with this filter', + this.title = 'Title', + this.titleHintText = 'Title...', + this.content = 'Content', + this.contentHintText = 'Content...', + this.contentDescription = 'What do you want to share?', + this.uploadImage = 'Upload image', + this.uploadImageDescription = 'Upload an image to your message (optional)', + this.allowComments = 'Are people allowed to comment?', + this.allowCommentsDescription = + 'Indicate whether people are allowed to respond', + this.commentsTitleOnPost = 'Comments', + this.checkPost = 'Check post overview', + this.deletePost = 'Delete post', + this.deleteReaction = 'Delete Reaction', + this.viewPost = 'View post', + this.likesTitle = 'Likes', + this.commentsTitle = 'Are people allowed to comment?', + this.firstComment = 'Be the first to comment', + this.writeComment = 'Write your comment here...', + this.postAt = 'at', + this.postLoadingError = 'Something went wrong while loading the post', + this.timelineSelectionDescription = 'Choose a category', + this.searchHint = 'Search...', + this.postOverview = 'Post Overview', + this.postIn = 'Post in', + this.postCreation = 'add post', + this.yes = 'Yes', + this.no = 'No', + this.timeLineScreenTitle = 'iconinstagram', }); - const TimelineTranslations.empty() - : anonymousUser = 'Anonymous user', - noPosts = 'No posts yet', - noPostsWithFilter = 'No posts with this filter', - title = 'Title', - content = 'Content', - contentDescription = 'What do you want to share?', - uploadImage = 'Upload image', - uploadImageDescription = 'Upload an image to your message (optional)', - allowComments = 'Are people allowed to comment?', - allowCommentsDescription = - 'Indicate whether people are allowed to respond', - commentsTitleOnPost = 'Comments', - checkPost = 'Check post overview', - deletePost = 'Delete post', - deleteReaction = 'Delete Reaction', - viewPost = 'View post', - likesTitle = 'Likes', - commentsTitle = 'Are people allowed to comment?', - firstComment = 'Be the first to comment', - writeComment = 'Write your comment here...', - postAt = 'at', - postLoadingError = 'Something went wrong while loading the post', - timelineSelectionDescription = 'Choose a category', - searchHint = 'Search...', - postOverview = 'Post Overview', - postIn = 'Post in', - postCreation = 'Create Post', - titleHintText = 'Title...', - contentHintText = 'Context...', - yes = 'Yes', - no = 'No'; + final String? noPosts; + final String? noPostsWithFilter; + final String? anonymousUser; - final String noPosts; - final String noPostsWithFilter; - final String anonymousUser; + final String? title; + final String? content; + final String? contentDescription; + final String? uploadImage; + final String? uploadImageDescription; + final String? allowComments; + final String? allowCommentsDescription; + final String? checkPost; + final String? postAt; - final String title; - final String content; - final String contentDescription; - final String uploadImage; - final String uploadImageDescription; - final String allowComments; - final String allowCommentsDescription; - final String checkPost; - final String postAt; + final String? titleHintText; + final String? contentHintText; - final String titleHintText; - final String contentHintText; + final String? deletePost; + final String? deleteReaction; + final String? viewPost; + final String? likesTitle; + final String? commentsTitle; + final String? commentsTitleOnPost; + final String? writeComment; + final String? firstComment; + final String? postLoadingError; - final String deletePost; - final String deleteReaction; - final String viewPost; - final String likesTitle; - final String commentsTitle; - final String commentsTitleOnPost; - final String writeComment; - final String firstComment; - final String postLoadingError; + final String? timelineSelectionDescription; - final String timelineSelectionDescription; + final String? searchHint; - final String searchHint; + final String? postOverview; + final String? postIn; + final String? postCreation; - final String postOverview; - final String postIn; - final String postCreation; - - final String yes; - final String no; + final String? yes; + final String? no; + final String? timeLineScreenTitle; TimelineTranslations copyWith({ String? noPosts, @@ -141,6 +111,7 @@ class TimelineTranslations { String? contentHintText, String? yes, String? no, + String? timeLineScreenTitle, }) => TimelineTranslations( noPosts: noPosts ?? this.noPosts, @@ -176,5 +147,6 @@ class TimelineTranslations { contentHintText: contentHintText ?? this.contentHintText, yes: yes ?? this.yes, no: no ?? this.no, + timeLineScreenTitle: timeLineScreenTitle ?? this.timeLineScreenTitle, ); } 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 ee5b6a8..2623912 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 @@ -124,12 +124,15 @@ class _TimelinePostCreationScreenState padding: widget.options.padding, child: SingleChildScrollView( child: Column( - mainAxisSize: MainAxisSize.min, + mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - widget.options.translations.title, - style: theme.textTheme.titleMedium, + widget.options.translations.title!, + style: const TextStyle( + fontWeight: FontWeight.w800, + fontSize: 20, + ), ), widget.options.textInputBuilder?.call( titleController, @@ -138,44 +141,49 @@ class _TimelinePostCreationScreenState ) ?? TextField( controller: titleController, - decoration: InputDecoration( - hintText: widget.options.translations.titleHintText, - ), + decoration: widget.options.contentInputDecoration ?? + InputDecoration( + hintText: widget.options.translations.titleHintText, + ), ), const SizedBox(height: 16), Text( - widget.options.translations.content, - style: theme.textTheme.titleMedium, + widget.options.translations.content!, + style: const TextStyle( + fontWeight: FontWeight.w800, + fontSize: 20, + ), ), const SizedBox(height: 4), Text( - widget.options.translations.contentDescription, + widget.options.translations.contentDescription!, style: theme.textTheme.bodyMedium, ), // input field for the content - SizedBox( - height: 100, - child: TextField( - controller: contentController, - textCapitalization: TextCapitalization.sentences, - expands: true, - maxLines: null, - minLines: null, - decoration: InputDecoration( - hintText: widget.options.translations.contentHintText, - ), - ), + TextField( + controller: contentController, + textCapitalization: TextCapitalization.sentences, + expands: false, + maxLines: null, + minLines: null, + decoration: widget.options.contentInputDecoration ?? + InputDecoration( + hintText: widget.options.translations.contentHintText, + ), ), const SizedBox( height: 16, ), // input field for the content Text( - widget.options.translations.uploadImage, - style: theme.textTheme.titleMedium, + widget.options.translations.uploadImage!, + style: const TextStyle( + fontWeight: FontWeight.w800, + fontSize: 20, + ), ), Text( - widget.options.translations.uploadImageDescription, + widget.options.translations.uploadImageDescription!, style: theme.textTheme.bodyMedium, ), // image picker field @@ -262,17 +270,21 @@ class _TimelinePostCreationScreenState const SizedBox(height: 16), Text( - widget.options.translations.commentsTitle, - style: theme.textTheme.titleMedium, + widget.options.translations.commentsTitle!, + style: const TextStyle( + fontWeight: FontWeight.w800, + fontSize: 20, + ), ), Text( - widget.options.translations.allowCommentsDescription, + widget.options.translations.allowCommentsDescription!, style: theme.textTheme.bodyMedium, ), Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Checkbox( + activeColor: const Color(0xff71C6D1), value: allowComments, onChanged: (value) { setState(() { @@ -280,8 +292,9 @@ class _TimelinePostCreationScreenState }); }, ), - Text(widget.options.translations.yes), + Text(widget.options.translations.yes!), Checkbox( + activeColor: const Color(0xff71C6D1), value: !allowComments, onChanged: (value) { setState(() { @@ -289,9 +302,10 @@ class _TimelinePostCreationScreenState }); }, ), - Text(widget.options.translations.no), + Text(widget.options.translations.no!), ], ), + const SizedBox(height: 120), Align( alignment: Alignment.bottomCenter, @@ -299,10 +313,14 @@ class _TimelinePostCreationScreenState ? widget.options.buttonBuilder!( context, onPostCreated, - widget.options.translations.checkPost, + widget.options.translations.checkPost!, enabled: editingDone, ) : ElevatedButton( + style: const ButtonStyle( + backgroundColor: + MaterialStatePropertyAll(Color(0xff71C6D1)), + ), onPressed: editingDone ? () async { await onPostCreated(); @@ -310,11 +328,18 @@ class _TimelinePostCreationScreenState .fetchPosts(null); } : null, - child: Text( - widget.enablePostOverviewScreen - ? widget.options.translations.checkPost - : widget.options.translations.postCreation, - style: theme.textTheme.bodyMedium, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Text( + widget.enablePostOverviewScreen + ? widget.options.translations.checkPost! + : widget.options.translations.postCreation!, + style: const TextStyle( + color: Colors.white, + fontSize: 20, + fontWeight: FontWeight.w800, + ), + ), ), ), ), diff --git a/packages/flutter_timeline_view/lib/src/screens/timeline_post_overview_screen.dart b/packages/flutter_timeline_view/lib/src/screens/timeline_post_overview_screen.dart index 347ab1b..3d7e949 100644 --- a/packages/flutter_timeline_view/lib/src/screens/timeline_post_overview_screen.dart +++ b/packages/flutter_timeline_view/lib/src/screens/timeline_post_overview_screen.dart @@ -10,47 +10,59 @@ class TimelinePostOverviewScreen extends StatelessWidget { required this.options, required this.service, required this.onPostSubmit, + this.isOverviewScreen, super.key, }); final TimelinePost timelinePost; final TimelineOptions options; final TimelineService service; final void Function(TimelinePost) onPostSubmit; + final bool? isOverviewScreen; @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: Colors.black, - title: Text( - options.translations.postOverview, - style: TextStyle(color: Theme.of(context).primaryColor), - ), - ), - body: Column( - children: [ - Flexible( - child: TimelinePostScreen( - userId: timelinePost.creatorId, - options: options, - post: timelinePost, - onPostDelete: () async {}, - service: service, - ), + return Column( + children: [ + Flexible( + child: TimelinePostScreen( + userId: timelinePost.creatorId, + options: options, + post: timelinePost, + onPostDelete: () async {}, + service: service, + isOverviewScreen: isOverviewScreen, ), - Padding( - padding: const EdgeInsets.only(bottom: 30.0), - child: ElevatedButton( - onPressed: () { + ), + options.postOverviewButtonBuilder?.call( + context, + () { onPostSubmit(timelinePost); }, - child: Text( - '${options.translations.postIn} ${timelinePost.category}', + '${options.translations.postIn} ${timelinePost.category}', + ) ?? + Padding( + padding: const EdgeInsets.only(bottom: 30.0), + child: ElevatedButton( + style: const ButtonStyle( + backgroundColor: MaterialStatePropertyAll(Color(0xff71C6D1)), + ), + onPressed: () { + onPostSubmit(timelinePost); + }, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Text( + '${options.translations.postIn} ${timelinePost.category}', + style: const TextStyle( + color: Colors.white, + fontSize: 20, + fontWeight: FontWeight.w800, + ), + ), + ), ), ), - ), - ], - ), + ], ); } } 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 c6a10a9..6d0c1bd 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 @@ -22,6 +22,7 @@ class TimelinePostScreen extends StatelessWidget { required this.options, required this.post, required this.onPostDelete, + this.isOverviewScreen = false, this.onUserTap, super.key, }); @@ -43,6 +44,8 @@ class TimelinePostScreen extends StatelessWidget { final VoidCallback onPostDelete; + final bool? isOverviewScreen; + @override Widget build(BuildContext context) => Scaffold( body: _TimelinePostScreen( @@ -52,6 +55,7 @@ class TimelinePostScreen extends StatelessWidget { post: post, onPostDelete: onPostDelete, onUserTap: onUserTap, + isOverviewScreen: isOverviewScreen, ), ); } @@ -64,6 +68,7 @@ class _TimelinePostScreen extends StatefulWidget { required this.post, required this.onPostDelete, this.onUserTap, + this.isOverviewScreen, }); final String userId; @@ -78,6 +83,8 @@ class _TimelinePostScreen extends StatefulWidget { final VoidCallback onPostDelete; + final bool? isOverviewScreen; + @override State<_TimelinePostScreen> createState() => _TimelinePostScreenState(); } @@ -145,7 +152,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> { if (this.post == null) { return Center( child: Text( - widget.options.translations.postLoadingError, + widget.options.translations.postLoadingError!, style: widget.options.theme.textStyles.errorTextStyle, ), ); @@ -214,7 +221,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> { widget.options.nameBuilder ?.call(post.creator) ?? post.creator?.fullName ?? - widget.options.translations.anonymousUser, + widget.options.translations.anonymousUser!, style: widget.options.theme.textStyles .postCreatorTitleStyle ?? theme.textTheme.titleMedium, @@ -234,7 +241,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> { child: Row( children: [ Text( - widget.options.translations.deletePost, + widget.options.translations.deletePost!, style: widget.options.theme.textStyles .deletePostStyle ?? theme.textTheme.bodyMedium, @@ -257,8 +264,8 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> { ), ], ), - // image of the post - if (post.imageUrl != null) ...[ + // image of the posts + if (post.imageUrl != null || post.image != null) ...[ const SizedBox(height: 8), ClipRRect( borderRadius: const BorderRadius.all(Radius.circular(8)), @@ -293,11 +300,17 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> { false; }, ) - : CachedNetworkImage( - width: double.infinity, - imageUrl: post.imageUrl!, - fit: BoxFit.fitHeight, - ), + : post.image != null + ? Image.memory( + width: double.infinity, + post.image!, + fit: BoxFit.fitHeight, + ) + : CachedNetworkImage( + width: double.infinity, + imageUrl: post.imageUrl!, + fit: BoxFit.fitHeight, + ), ), ], const SizedBox( @@ -320,9 +333,11 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> { color: Colors.transparent, child: widget.options.theme.likedIcon ?? Icon( - Icons.thumb_up_rounded, - color: widget.options.theme.iconColor, - size: widget.options.iconSize, + widget.post.likedBy + ?.contains(widget.userId) ?? + false + ? Icons.favorite_rounded + : Icons.favorite_outline_outlined, ), ), ), @@ -340,8 +355,11 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> { color: Colors.transparent, child: widget.options.theme.likeIcon ?? Icon( - Icons.thumb_up_alt_outlined, - color: widget.options.theme.iconColor, + widget.post.likedBy + ?.contains(widget.userId) ?? + false + ? Icons.favorite_rounded + : Icons.favorite_outline_outlined, size: widget.options.iconSize, ), ), @@ -423,7 +441,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> { const SizedBox(height: 20), if (post.reactionEnabled) ...[ Text( - widget.options.translations.commentsTitleOnPost, + widget.options.translations.commentsTitleOnPost!, style: theme.textTheme.titleMedium, ), for (var reaction @@ -451,7 +469,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> { PopupMenuItem( value: 'delete', child: Text( - widget.options.translations.deleteReaction, + widget.options.translations.deleteReaction!, ), ), ], @@ -505,7 +523,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> { ?.call(post.creator) ?? reaction.creator?.fullName ?? widget.options.translations - .anonymousUser, + .anonymousUser!, style: theme.textTheme.titleSmall, ), Padding( @@ -547,7 +565,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> { if (post.reactions?.isEmpty ?? true) ...[ const SizedBox(height: 16), Text( - widget.options.translations.firstComment, + widget.options.translations.firstComment!, ), ], const SizedBox(height: 120), @@ -557,7 +575,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> { ), ), ), - if (post.reactionEnabled) + if (post.reactionEnabled && !widget.isOverviewScreen!) Align( alignment: Alignment.bottomCenter, child: ReactionBottom( 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 d7a256c..1d25524 100644 --- a/packages/flutter_timeline_view/lib/src/screens/timeline_screen.dart +++ b/packages/flutter_timeline_view/lib/src/screens/timeline_screen.dart @@ -273,8 +273,9 @@ class _TimelineScreenState extends State { padding: const EdgeInsets.all(8.0), child: Text( category == null - ? widget.options.translations.noPosts - : widget.options.translations.noPostsWithFilter, + ? widget.options.translations.noPosts! + : widget + .options.translations.noPostsWithFilter!, style: widget.options.theme.textStyles.noPostsStyle, ), ), diff --git a/packages/flutter_timeline_view/lib/src/screens/timeline_selection_screen.dart b/packages/flutter_timeline_view/lib/src/screens/timeline_selection_screen.dart index 789923e..e9e16d0 100644 --- a/packages/flutter_timeline_view/lib/src/screens/timeline_selection_screen.dart +++ b/packages/flutter_timeline_view/lib/src/screens/timeline_selection_screen.dart @@ -19,7 +19,6 @@ class TimelineSelectionScreen extends StatelessWidget { @override Widget build(BuildContext context) { var size = MediaQuery.of(context).size; - var theme = Theme.of(context); return Padding( padding: EdgeInsets.symmetric( horizontal: size.width * 0.05, @@ -30,36 +29,55 @@ class TimelineSelectionScreen extends StatelessWidget { Padding( padding: EdgeInsets.only(top: size.height * 0.05, bottom: 8), child: Text( - options.translations.timelineSelectionDescription, - style: - options.theme.textStyles.categorySelectionDescriptionStyle ?? - theme.textTheme.displayMedium, + options.translations.timelineSelectionDescription!, + style: const TextStyle( + fontWeight: FontWeight.w800, + fontSize: 20, + ), ), ), const SizedBox(height: 4), for (var category in categories.where( (element) => element.canCreate, )) ...[ - InkWell( - onTap: () => onCategorySelected.call(category), - child: Container( - width: double.infinity, - decoration: BoxDecoration( - color: Colors.grey, - borderRadius: BorderRadius.circular(10), - ), - padding: const EdgeInsets.symmetric( - vertical: 26, - horizontal: 16, - ), - margin: const EdgeInsets.symmetric(vertical: 8), - child: Text( + options.categorySelectorButtonBuilder?.call( + context, + () { + onCategorySelected.call(category); + }, category.title, - style: options.theme.textStyles.categorySelectionTitleStyle ?? - theme.textTheme.displaySmall, + ) ?? + InkWell( + onTap: () => onCategorySelected.call(category), + child: Container( + height: 60, + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: const Color(0xff71C6D1), + width: 2, + ), + ), + margin: const EdgeInsets.symmetric(vertical: 4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Text( + category.title, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w800, + ), + ), + ), + ], + ), + ), ), - ), - ), ], ], ), diff --git a/packages/flutter_timeline_view/lib/src/services/local_post_service.dart b/packages/flutter_timeline_view/lib/src/services/local_post_service.dart index 2f54ac9..f6e44d6 100644 --- a/packages/flutter_timeline_view/lib/src/services/local_post_service.dart +++ b/packages/flutter_timeline_view/lib/src/services/local_post_service.dart @@ -21,8 +21,8 @@ class LocalTimelinePostService userId: 'test_user', imageUrl: 'https://cdn.britannica.com/68/143568-050-5246474F/Donkey.jpg?w=400&h=300&c=crop', - firstName: 'Dirk', - lastName: 'lukassen', + firstName: 'Ico', + lastName: 'Nica', ), ), ); @@ -174,8 +174,8 @@ class LocalTimelinePostService userId: 'test_user', imageUrl: 'https://cdn.britannica.com/68/143568-050-5246474F/Donkey.jpg?w=400&h=300&c=crop', - firstName: 'Dirk', - lastName: 'lukassen', + firstName: 'Ico', + lastName: 'Nica', ), ); @@ -197,63 +197,47 @@ class LocalTimelinePostService TimelinePost( id: 'Post0', creatorId: 'test_user', - title: 'Post 0', - category: null, + title: 'De topper van de maand september', + category: 'Category', imageUrl: - 'https://t4.ftcdn.net/jpg/02/77/71/45/240_F_277714513_fQ0akmI3TQxa0wkPCLeO12Rx3cL2AuIf.jpg', - content: 'Standard post without image made by the current user', - likes: 0, + 'https://firebasestorage.googleapis.com/v0/b/appshell-demo.appspot.com/o/do_not_delete_1.png?alt=media&token=e4b2f9f3-c81f-4ac7-a938-e846691399f7', + content: 'Dit is onze topper van de maand september! Gefeliciteerd!', + likes: 72, reaction: 0, createdAt: DateTime.now(), - reactionEnabled: false, + reactionEnabled: true, creator: const TimelinePosterUserModel( userId: 'test_user', imageUrl: - 'https://cdn.britannica.com/68/143568-050-5246474F/Donkey.jpg?w=400&h=300&c=crop', - firstName: 'Dirk', - lastName: 'lukassen', + 'https://firebasestorage.googleapis.com/v0/b/appshell-demo.appspot.com/o/do_not_delete_3.png?alt=media&token=cd7c156d-0dda-43be-9199-f7d31c30132e', + firstName: 'Robin', + lastName: 'De Vries', ), ), 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://t4.ftcdn.net/jpg/02/77/71/45/240_F_277714513_fQ0akmI3TQxa0wkPCLeO12Rx3cL2AuIf.jpg', - creator: const TimelinePosterUserModel( - userId: 'test_user', - imageUrl: - 'https://cdn.britannica.com/68/143568-050-5246474F/Donkey.jpg?w=400&h=300&c=crop', - firstName: 'Dirk', - lastName: 'lukassen', - ), - ), - 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, + title: 'De soep van de week is: Aspergesoep', + category: 'Category with two lines', + content: + 'Aspergesoep is echt een heerlijke delicatesse. Deze soep wordt' + ' vaak gemaakt met verse asperges, bouillon en wat kruiden voor' + ' smaak. Het is een perfecte keuze voor een lichte en smaakvolle' + ' maaltijd, vooral in het voorjaar wanneer asperges in seizoen' + ' zijn. We serveren het met een vleugje room en wat knapperige' + ' croutons voor die extra touch.', + likes: 72, reaction: 0, createdAt: DateTime.now(), reactionEnabled: true, imageUrl: - 'https://t4.ftcdn.net/jpg/02/77/71/45/240_F_277714513_fQ0akmI3TQxa0wkPCLeO12Rx3cL2AuIf.jpg', + 'https://firebasestorage.googleapis.com/v0/b/appshell-demo.appspot.com/o/do_not_delete_2.png?alt=media&token=ee4a8771-531f-4d1d-8613-a2366771e775', creator: const TimelinePosterUserModel( userId: 'test_user', imageUrl: - 'https://cdn.britannica.com/68/143568-050-5246474F/Donkey.jpg?w=400&h=300&c=crop', - firstName: 'Dirk', - lastName: 'lukassen', + 'https://firebasestorage.googleapis.com/v0/b/appshell-demo.appspot.com/o/do_not_delete_4.png?alt=media&token=775d4d10-6d2b-4aef-a51b-ba746b7b137f', + firstName: 'Elise', + lastName: 'Welling', ), ), ]; diff --git a/packages/flutter_timeline_view/lib/src/widgets/category_selector.dart b/packages/flutter_timeline_view/lib/src/widgets/category_selector.dart index 4d9dc4b..ce91903 100644 --- a/packages/flutter_timeline_view/lib/src/widgets/category_selector.dart +++ b/packages/flutter_timeline_view/lib/src/widgets/category_selector.dart @@ -41,10 +41,11 @@ class _CategorySelectorState extends State { ), for (var category in categories) ...[ widget.options.categoriesOptions.categoryButtonBuilder?.call( - categoryKey: category.key, - categoryName: category.title, - onTap: () => widget.onTapCategory(category.key), - selected: widget.filter == category.key, + category.key, + category.title, + () => widget.onTapCategory(category.key), + widget.filter == category.key, + widget.isOnTop, ) ?? Padding( padding: const EdgeInsets.symmetric(horizontal: 4), diff --git a/packages/flutter_timeline_view/lib/src/widgets/category_selector_button.dart b/packages/flutter_timeline_view/lib/src/widgets/category_selector_button.dart index e5f7840..d05c33d 100644 --- a/packages/flutter_timeline_view/lib/src/widgets/category_selector_button.dart +++ b/packages/flutter_timeline_view/lib/src/widgets/category_selector_button.dart @@ -22,9 +22,8 @@ class CategorySelectorButton extends StatelessWidget { Widget build(BuildContext context) { var theme = Theme.of(context); - return AnimatedContainer( + return SizedBox( height: isOnTop ? 140 : 40, - duration: const Duration(milliseconds: 100), child: TextButton( onPressed: onTap, style: ButtonStyle( @@ -37,41 +36,59 @@ class CategorySelectorButton extends StatelessWidget { ), fixedSize: MaterialStatePropertyAll(Size(140, isOnTop ? 140 : 20)), backgroundColor: MaterialStatePropertyAll( - selected ? theme.colorScheme.primary : Colors.transparent, + selected ? const Color(0xff71C6D1) : Colors.transparent, ), - shape: MaterialStatePropertyAll( + shape: const MaterialStatePropertyAll( RoundedRectangleBorder( - borderRadius: const BorderRadius.all( + borderRadius: BorderRadius.all( Radius.circular(8), ), side: BorderSide( - color: theme.colorScheme.primary, + color: Color(0xff71C6D1), width: 2, ), ), ), ), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Column( - mainAxisAlignment: - isOnTop ? MainAxisAlignment.end : MainAxisAlignment.center, - children: [ - Text( - category.title, - style: (options.theme.textStyles.categoryTitleStyle ?? - theme.textTheme.labelLarge) - ?.copyWith( - color: selected - ? theme.colorScheme.onPrimary - : theme.colorScheme.onSurface, - ), + child: isOnTop + ? SizedBox( + width: MediaQuery.of(context).size.width, + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + category.title, + style: (options.theme.textStyles.categoryTitleStyle ?? + theme.textTheme.labelLarge) + ?.copyWith( + color: selected + ? theme.colorScheme.onPrimary + : theme.colorScheme.onSurface, + ), + textAlign: TextAlign.start, + ), + ], ), - ], - ), - ], - ), + ) + : Row( + children: [ + Flexible( + child: Text( + category.title, + style: (options.theme.textStyles.categoryTitleStyle ?? + theme.textTheme.labelLarge) + ?.copyWith( + color: selected + ? theme.colorScheme.onPrimary + : theme.colorScheme.onSurface, + ), + textAlign: TextAlign.start, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), ), ); } diff --git a/packages/flutter_timeline_view/lib/src/widgets/reaction_bottom.dart b/packages/flutter_timeline_view/lib/src/widgets/reaction_bottom.dart index 2118f60..8a62f7c 100644 --- a/packages/flutter_timeline_view/lib/src/widgets/reaction_bottom.dart +++ b/packages/flutter_timeline_view/lib/src/widgets/reaction_bottom.dart @@ -65,7 +65,7 @@ class _ReactionBottomState extends State { ], ), ), - widget.translations.writeComment, + widget.translations.writeComment!, ), ), ), diff --git a/packages/flutter_timeline_view/lib/src/widgets/tappable_image.dart b/packages/flutter_timeline_view/lib/src/widgets/tappable_image.dart index 24fe999..9217c08 100644 --- a/packages/flutter_timeline_view/lib/src/widgets/tappable_image.dart +++ b/packages/flutter_timeline_view/lib/src/widgets/tappable_image.dart @@ -99,11 +99,17 @@ class _TappableImageState extends State offset: Offset(0, animation.value * -32), child: Transform.scale( scale: 1 + animation.value * 0.1, - child: CachedNetworkImage( - imageUrl: widget.post.imageUrl ?? '', - width: double.infinity, - fit: BoxFit.fitHeight, - ), + child: widget.post.imageUrl != null + ? CachedNetworkImage( + imageUrl: widget.post.imageUrl ?? '', + width: double.infinity, + fit: BoxFit.fitHeight, + ) + : Image.memory( + width: double.infinity, + widget.post.image!, + fit: BoxFit.fitHeight, + ), ), ), ); 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 ffff6c9..abbb8a4 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 @@ -49,7 +49,7 @@ class _TimelinePostWidgetState extends State { return InkWell( onTap: widget.onTap, child: SizedBox( - height: widget.post.imageUrl != null + height: widget.post.imageUrl != null || widget.post.image != null ? widget.options.postWidgetHeight : null, width: double.infinity, @@ -94,7 +94,7 @@ class _TimelinePostWidgetState extends State { widget.options.nameBuilder ?.call(widget.post.creator) ?? widget.post.creator?.fullName ?? - widget.options.translations.anonymousUser, + widget.options.translations.anonymousUser!, style: widget.options.theme.textStyles .postCreatorTitleStyle ?? theme.textTheme.titleMedium, @@ -118,7 +118,7 @@ class _TimelinePostWidgetState extends State { child: Row( children: [ Text( - widget.options.translations.deletePost, + widget.options.translations.deletePost!, style: widget.options.theme.textStyles .deletePostStyle ?? theme.textTheme.bodyMedium, @@ -142,7 +142,7 @@ class _TimelinePostWidgetState extends State { ], ), // image of the post - if (widget.post.imageUrl != null) ...[ + if (widget.post.imageUrl != null || widget.post.image != null) ...[ const SizedBox(height: 8), Flexible( flex: widget.options.postWidgetHeight != null ? 1 : 0, @@ -176,11 +176,17 @@ class _TimelinePostWidgetState extends State { return result.likedBy?.contains(userId) ?? false; }, ) - : CachedNetworkImage( - width: double.infinity, - imageUrl: widget.post.imageUrl!, - fit: BoxFit.fitWidth, - ), + : widget.post.imageUrl != null + ? CachedNetworkImage( + width: double.infinity, + imageUrl: widget.post.imageUrl!, + fit: BoxFit.fitWidth, + ) + : Image.memory( + width: double.infinity, + widget.post.image!, + fit: BoxFit.fitWidth, + ), ), ), ], @@ -312,7 +318,7 @@ class _TimelinePostWidgetState extends State { ), const SizedBox(height: 4), Text( - widget.options.translations.viewPost, + widget.options.translations.viewPost!, style: widget.options.theme.textStyles.viewPostStyle ?? theme.textTheme.bodySmall, ), diff --git a/packages/flutter_timeline_view/pubspec.yaml b/packages/flutter_timeline_view/pubspec.yaml index 1cd4bec..6f416b5 100644 --- a/packages/flutter_timeline_view/pubspec.yaml +++ b/packages/flutter_timeline_view/pubspec.yaml @@ -4,7 +4,7 @@ name: flutter_timeline_view description: Visual elements of the Flutter Timeline Component -version: 2.3.1 +version: 3.0.0 publish_to: none @@ -23,7 +23,7 @@ dependencies: git: url: https://github.com/Iconica-Development/flutter_timeline path: packages/flutter_timeline_interface - ref: 2.3.1 + ref: 3.0.0 flutter_image_picker: git: url: https://github.com/Iconica-Development/flutter_image_picker