From 38dd43ab39d6bd68972a92150224550c65787e8d Mon Sep 17 00:00:00 2001 From: mike doornenbal Date: Mon, 29 Jul 2024 16:39:56 +0200 Subject: [PATCH 01/20] fix: timeline_screen --- .../flutter_timeline_gorouter_userstory.dart | 13 +- .../flutter_timeline_navigator_userstory.dart | 13 +- .../lib/src/model/timeline_category.dart | 4 +- .../lib/src/config/timeline_options.dart | 25 +--- .../lib/src/config/timeline_paddings.dart | 2 +- .../lib/src/config/timeline_translations.dart | 2 +- .../lib/src/widgets/category_selector.dart | 61 ++++----- .../src/widgets/category_selector_button.dart | 19 +-- .../lib/src/widgets/timeline_post_widget.dart | 119 ++++++++++-------- packages/flutter_timeline_view/pubspec.yaml | 17 +-- 10 files changed, 134 insertions(+), 141 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 7587b76..0ca7d46 100644 --- a/packages/flutter_timeline/lib/src/flutter_timeline_gorouter_userstory.dart +++ b/packages/flutter_timeline/lib/src/flutter_timeline_gorouter_userstory.dart @@ -45,13 +45,13 @@ List getTimelineStoryRoutes({ filterEnabled: config.filterEnabled, postWidgetBuilder: config.postWidgetBuilder, ); - + var theme = Theme.of(context); var button = FloatingActionButton( backgroundColor: config .optionsBuilder(context) .theme .postCreationFloatingActionButtonColor ?? - Theme.of(context).primaryColor, + theme.colorScheme.primary, onPressed: () async => context.push( TimelineUserStoryRoutes.timelineCategorySelection, ), @@ -59,7 +59,7 @@ List getTimelineStoryRoutes({ child: const Icon( Icons.add, color: Colors.white, - size: 30, + size: 24, ), ); @@ -70,17 +70,12 @@ List getTimelineStoryRoutes({ ?.call(context, timelineScreen, button) ?? Scaffold( appBar: AppBar( - backgroundColor: const Color(0xff212121), title: Text( config .optionsBuilder(context) .translations .timeLineScreenTitle, - style: TextStyle( - color: Theme.of(context).primaryColor, - fontSize: 24, - fontWeight: FontWeight.w800, - ), + style: theme.textTheme.headlineLarge, ), ), body: timelineScreen, 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 89db27a..b84625f 100644 --- a/packages/flutter_timeline/lib/src/flutter_timeline_navigator_userstory.dart +++ b/packages/flutter_timeline/lib/src/flutter_timeline_navigator_userstory.dart @@ -65,13 +65,13 @@ Widget _timelineScreenRoute({ filterEnabled: config.filterEnabled, postWidgetBuilder: config.postWidgetBuilder, ); - + var theme = Theme.of(context); var button = FloatingActionButton( backgroundColor: config .optionsBuilder(context) .theme .postCreationFloatingActionButtonColor ?? - Theme.of(context).primaryColor, + theme.colorScheme.primary, onPressed: () async => Navigator.of(context).push( MaterialPageRoute( builder: (context) => _postCategorySelectionScreen( @@ -84,21 +84,16 @@ Widget _timelineScreenRoute({ child: const Icon( Icons.add, color: Colors.white, - size: 30, + size: 24, ), ); return config.homeOpenPageBuilder?.call(context, timelineScreen, button) ?? Scaffold( appBar: AppBar( - backgroundColor: const Color(0xff212121), title: Text( config.optionsBuilder(context).translations.timeLineScreenTitle, - style: TextStyle( - color: Theme.of(context).primaryColor, - fontSize: 24, - fontWeight: FontWeight.w800, - ), + style: theme.textTheme.headlineLarge, ), ), body: timelineScreen, diff --git a/packages/flutter_timeline_interface/lib/src/model/timeline_category.dart b/packages/flutter_timeline_interface/lib/src/model/timeline_category.dart index b88d1d8..578de8d 100644 --- a/packages/flutter_timeline_interface/lib/src/model/timeline_category.dart +++ b/packages/flutter_timeline_interface/lib/src/model/timeline_category.dart @@ -5,13 +5,13 @@ class TimelineCategory { const TimelineCategory({ required this.key, required this.title, - required this.icon, + this.icon, this.canCreate = true, this.canView = true, }); final String? key; final String title; - final Widget icon; + final Widget? icon; final bool canCreate; final bool canView; } 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 e22f2b0..6adbf10 100644 --- a/packages/flutter_timeline_view/lib/src/config/timeline_options.dart +++ b/packages/flutter_timeline_view/lib/src/config/timeline_options.dart @@ -19,7 +19,7 @@ class TimelineOptions { this.imagePickerTheme = const ImagePickerTheme(), this.timelinePostHeight, this.sortCommentsAscending = true, - this.sortPostsAscending, + this.sortPostsAscending = false, this.doubleTapTolike = false, this.iconsWithValues = false, this.likeAndDislikeIconsForDoubleTap = const ( @@ -38,7 +38,7 @@ class TimelineOptions { this.userAvatarBuilder, this.anonymousAvatarBuilder, this.nameBuilder, - this.iconSize = 26, + this.iconSize = 24, this.postWidgetHeight, this.filterOptions = const FilterOptions(), this.categoriesOptions = const CategoriesOptions(), @@ -178,35 +178,14 @@ 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, - ), - ), ), ]; diff --git a/packages/flutter_timeline_view/lib/src/config/timeline_paddings.dart b/packages/flutter_timeline_view/lib/src/config/timeline_paddings.dart index 33a4ba4..d42a2a4 100644 --- a/packages/flutter_timeline_view/lib/src/config/timeline_paddings.dart +++ b/packages/flutter_timeline_view/lib/src/config/timeline_paddings.dart @@ -6,7 +6,7 @@ class TimelinePaddingOptions { this.mainPadding = const EdgeInsets.only(left: 12.0, top: 24.0, right: 12.0, bottom: 12.0), this.postPadding = - const EdgeInsets.symmetric(vertical: 12.0, horizontal: 12.0), + const EdgeInsets.only(left: 12.0, top: 6, right: 12.0, bottom: 8), this.postOverviewButtonBottomPadding = 30.0, this.categoryButtonTextPadding, }); 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 bdc6985..e14a2ec 100644 --- a/packages/flutter_timeline_view/lib/src/config/timeline_translations.dart +++ b/packages/flutter_timeline_view/lib/src/config/timeline_translations.dart @@ -76,7 +76,7 @@ class TimelineTranslations { this.deleteCancelButton = 'Cancel', this.deleteReaction = 'Delete Reaction', this.viewPost = 'View post', - this.likesTitle = 'Likes', + this.likesTitle = 'likes', this.commentsTitle = 'Are people allowed to comment?', this.firstComment = 'Be the first to comment', this.writeComment = 'Write your comment here...', 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 c9fe02b..e1c58f6 100644 --- a/packages/flutter_timeline_view/lib/src/widgets/category_selector.dart +++ b/packages/flutter_timeline_view/lib/src/widgets/category_selector.dart @@ -32,37 +32,40 @@ class _CategorySelectorState extends State { widget.options.categoriesOptions.categoriesBuilder!(context); return SingleChildScrollView( scrollDirection: Axis.horizontal, - child: Row( - children: [ - SizedBox( - width: widget.options.categoriesOptions - .categorySelectorHorizontalPadding ?? - max(widget.options.paddings.mainPadding.left - 20, 0), - ), - for (var category in categories) ...[ - widget.options.categoriesOptions.categoryButtonBuilder?.call( - category, - () => widget.onTapCategory(category.key), - widget.filter == category.key, - widget.isOnTop, - ) ?? - Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: CategorySelectorButton( - isOnTop: widget.isOnTop, - category: category, - selected: widget.filter == category.key, - onTap: () => widget.onTapCategory(category.key), - options: widget.options, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + children: [ + SizedBox( + width: widget.options.categoriesOptions + .categorySelectorHorizontalPadding ?? + max(widget.options.paddings.mainPadding.left - 20, 0), + ), + for (var category in categories) ...[ + widget.options.categoriesOptions.categoryButtonBuilder?.call( + category, + () => widget.onTapCategory(category.key), + widget.filter == category.key, + widget.isOnTop, + ) ?? + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: CategorySelectorButton( + isOnTop: widget.isOnTop, + category: category, + selected: widget.filter == category.key, + onTap: () => widget.onTapCategory(category.key), + options: widget.options, + ), ), - ), + ], + SizedBox( + width: widget.options.categoriesOptions + .categorySelectorHorizontalPadding ?? + max(widget.options.paddings.mainPadding.right - 4, 0), + ), ], - SizedBox( - width: widget.options.categoriesOptions - .categorySelectorHorizontalPadding ?? - max(widget.options.paddings.mainPadding.right - 4, 0), - ), - ], + ), ), ); } 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 edd7bf5..bfdf33b 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 @@ -23,7 +23,8 @@ class CategorySelectorButton extends StatelessWidget { var theme = Theme.of(context); var size = MediaQuery.of(context).size; - return SizedBox( + return AnimatedContainer( + duration: const Duration(milliseconds: 100), height: isOnTop ? 140 : 40, child: TextButton( onPressed: onTap, @@ -81,11 +82,13 @@ class CategorySelectorButton extends StatelessWidget { Flexible( child: Row( children: [ - category.icon, - SizedBox( - width: - options.paddings.categoryButtonTextPadding ?? 8, - ), + if (category.icon != null) ...[ + category.icon!, + SizedBox( + width: + options.paddings.categoryButtonTextPadding ?? 8, + ), + ], Expanded( child: _CategoryButtonText( category: category, @@ -124,7 +127,9 @@ class _CategoryButtonText extends StatelessWidget { Widget build(BuildContext context) => Text( category.title, style: (options.theme.textStyles.categoryTitleStyle ?? - theme.textTheme.labelLarge) + (selected + ? theme.textTheme.titleMedium + : theme.textTheme.bodyMedium)) ?.copyWith( color: selected ? options.theme.categorySelectionButtonSelectedTextColor ?? 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 1051d42..597309d 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 @@ -52,6 +52,7 @@ class _TimelinePostWidgetState extends State { @override Widget build(BuildContext context) { var theme = Theme.of(context); + var isLikedByUser = widget.post.likedBy?.contains(widget.userId) ?? false; return InkWell( onTap: widget.onTap, child: SizedBox( @@ -103,7 +104,9 @@ class _TimelinePostWidgetState extends State { widget.options.translations.anonymousUser, style: widget.options.theme.textStyles .postCreatorTitleStyle ?? - theme.textTheme.titleMedium, + theme.textTheme.titleSmall!.copyWith( + color: Colors.black, + ), ), ], ), @@ -204,17 +207,16 @@ class _TimelinePostWidgetState extends State { height: 8, ), // post information - if (widget.options.iconsWithValues) + if (widget.options.iconsWithValues) ...[ Row( children: [ - TextButton.icon( + IconButton( + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), onPressed: () async { var userId = widget.userId; - var liked = - widget.post.likedBy?.contains(userId) ?? false; - - if (!liked) { + if (!isLikedByUser) { await widget.service.postService.likePost( userId, widget.post, @@ -228,61 +230,67 @@ class _TimelinePostWidgetState extends State { }, icon: widget.options.theme.likeIcon ?? Icon( - widget.post.likedBy?.contains(widget.userId) ?? false + isLikedByUser ? Icons.favorite_rounded : Icons.favorite_outline_outlined, + color: widget.options.theme.iconColor, + size: widget.options.iconSize, ), - label: Text('${widget.post.likes}'), ), - if (widget.post.reactionEnabled) - TextButton.icon( + const SizedBox( + width: 4, + ), + Text('${widget.post.likes}'), + if (widget.post.reactionEnabled) ...[ + const SizedBox( + width: 8, + ), + IconButton( + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), onPressed: widget.onTap, icon: widget.options.theme.commentIcon ?? - const Icon( + Icon( Icons.chat_bubble_outline_outlined, + color: widget.options.theme.iconColor, + size: widget.options.iconSize, ), - label: Text('${widget.post.reaction}'), ), + const SizedBox( + width: 4, + ), + Text('${widget.post.reaction}'), + ], ], - ) - else + ), + ] else ...[ Row( children: [ - if (widget.post.likedBy?.contains(widget.userId) ?? - false) ...[ - InkWell( - onTap: widget.onTapUnlike, - child: Container( - color: Colors.transparent, - child: widget.options.theme.likedIcon ?? - Icon( - Icons.favorite_rounded, - color: widget.options.theme.iconColor, - size: widget.options.iconSize, - ), - ), - ), - ] else ...[ - InkWell( - onTap: widget.onTapLike, - child: Container( - color: Colors.transparent, - child: widget.options.theme.likeIcon ?? - Icon( - Icons.favorite_outline, - color: widget.options.theme.iconColor, - size: widget.options.iconSize, - ), - ), - ), - ], + IconButton( + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + onPressed: + isLikedByUser ? widget.onTapUnlike : widget.onTapLike, + icon: (isLikedByUser + ? widget.options.theme.likedIcon + : widget.options.theme.likeIcon) ?? + Icon( + isLikedByUser + ? Icons.favorite_rounded + : Icons.favorite_outline, + color: widget.options.theme.iconColor, + size: widget.options.iconSize, + ), + ), const SizedBox(width: 8), if (widget.post.reactionEnabled) ...[ - Container( - color: Colors.transparent, - child: widget.options.theme.commentIcon ?? + IconButton( + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + onPressed: widget.onTap, + icon: widget.options.theme.commentIcon ?? Icon( - Icons.chat_bubble_outline_rounded, + Icons.chat_bubble_outline_outlined, color: widget.options.theme.iconColor, size: widget.options.iconSize, ), @@ -290,6 +298,7 @@ class _TimelinePostWidgetState extends State { ], ], ), + ], const SizedBox( height: 8, @@ -305,23 +314,25 @@ class _TimelinePostWidgetState extends State { '${widget.options.translations.likesTitle}', style: widget .options.theme.textStyles.listPostLikeTitleAndAmount ?? - theme.textTheme.titleSmall, + theme.textTheme.titleSmall!.copyWith( + color: Colors.black, + ), ), - const SizedBox(height: 4), Text.rich( TextSpan( text: widget.options.nameBuilder?.call(widget.post.creator) ?? widget.post.creator?.fullName ?? widget.options.translations.anonymousUser, style: widget.options.theme.textStyles.listCreatorNameStyle ?? - theme.textTheme.titleSmall, + theme.textTheme.titleSmall!.copyWith( + color: Colors.black, + ), children: [ - const TextSpan(text: ' '), TextSpan( text: widget.post.title, style: widget.options.theme.textStyles.listPostTitleStyle ?? - theme.textTheme.bodyMedium, + theme.textTheme.bodySmall, ), ], ), @@ -330,7 +341,9 @@ class _TimelinePostWidgetState extends State { Text( widget.options.translations.viewPost, style: widget.options.theme.textStyles.viewPostStyle ?? - theme.textTheme.bodySmall, + theme.textTheme.titleSmall!.copyWith( + color: const Color(0xFF8D8D8D), + ), ), ], if (widget.options.dividerBuilder != null) diff --git a/packages/flutter_timeline_view/pubspec.yaml b/packages/flutter_timeline_view/pubspec.yaml index e5d8830..a7ddd70 100644 --- a/packages/flutter_timeline_view/pubspec.yaml +++ b/packages/flutter_timeline_view/pubspec.yaml @@ -9,7 +9,7 @@ version: 4.1.0 publish_to: none environment: - sdk: '>=3.1.3 <4.0.0' + sdk: ">=3.1.3 <4.0.0" dependencies: flutter: @@ -18,18 +18,22 @@ dependencies: cached_network_image: ^3.2.2 dotted_border: ^2.1.0 flutter_html: ^3.0.0-beta.2 - + flutter_timeline_interface: - git: - url: https://github.com/Iconica-Development/flutter_timeline - path: packages/flutter_timeline_interface - ref: 4.1.0 + git: + url: https://github.com/Iconica-Development/flutter_timeline + path: packages/flutter_timeline_interface + ref: 4.1.0 flutter_image_picker: git: url: https://github.com/Iconica-Development/flutter_image_picker ref: 1.0.5 collection: any +dependency_overrides: + flutter_timeline_interface: + path: packages/flutter_timeline_interface + dev_dependencies: flutter_lints: ^2.0.0 flutter_iconica_analysis: @@ -38,4 +42,3 @@ dev_dependencies: ref: 6.0.0 flutter: - From 13ae3711914dbc2e2b637be9ff66cdb954c1cfe1 Mon Sep 17 00:00:00 2001 From: mike doornenbal Date: Tue, 30 Jul 2024 10:43:40 +0200 Subject: [PATCH 02/20] fix: timeline_post_detail_screen --- .../flutter_timeline_navigator_userstory.dart | 4 +- .../lib/src/config/timeline_translations.dart | 5 - .../lib/src/screens/timeline_post_screen.dart | 259 ++++++++++-------- .../lib/src/widgets/reaction_bottom.dart | 68 ++--- 4 files changed, 166 insertions(+), 170 deletions(-) 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 b84625f..6b8d487 100644 --- a/packages/flutter_timeline/lib/src/flutter_timeline_navigator_userstory.dart +++ b/packages/flutter_timeline/lib/src/flutter_timeline_navigator_userstory.dart @@ -158,7 +158,9 @@ Widget _postDetailScreenRoute({ leading: backButton, backgroundColor: const Color(0xff212121), title: Text( - category?.title ?? post.category ?? 'Category', + category?.title.toLowerCase() ?? + post.category?.toLowerCase() ?? + 'category', style: TextStyle( color: Theme.of(context).primaryColor, fontSize: 24, 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 e14a2ec..ecf25c1 100644 --- a/packages/flutter_timeline_view/lib/src/config/timeline_translations.dart +++ b/packages/flutter_timeline_view/lib/src/config/timeline_translations.dart @@ -28,7 +28,6 @@ class TimelineTranslations { required this.allowCommentsDescription, required this.commentsTitleOnPost, required this.checkPost, - required this.postAt, required this.deletePost, required this.deleteReaction, required this.deleteConfirmationMessage, @@ -80,7 +79,6 @@ class TimelineTranslations { 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...', @@ -104,7 +102,6 @@ class TimelineTranslations { final String allowComments; final String allowCommentsDescription; final String checkPost; - final String postAt; final String titleHintText; final String contentHintText; @@ -150,7 +147,6 @@ class TimelineTranslations { String? allowCommentsDescription, String? commentsTitleOnPost, String? checkPost, - String? postAt, String? deletePost, String? deleteConfirmationTitle, String? deleteConfirmationMessage, @@ -189,7 +185,6 @@ class TimelineTranslations { allowCommentsDescription ?? this.allowCommentsDescription, commentsTitleOnPost: commentsTitleOnPost ?? this.commentsTitleOnPost, checkPost: checkPost ?? this.checkPost, - postAt: postAt ?? this.postAt, deletePost: deletePost ?? this.deletePost, deleteConfirmationTitle: deleteConfirmationTitle ?? this.deleteConfirmationTitle, 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 491b8b8..2f3e80a 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 @@ -3,12 +3,10 @@ // SPDX-License-Identifier: BSD-3-Clause import 'dart:async'; -import 'dart:typed_data'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_html/flutter_html.dart'; -import 'package:flutter_image_picker/flutter_image_picker.dart'; import 'package:flutter_timeline_interface/flutter_timeline_interface.dart'; import 'package:flutter_timeline_view/src/config/timeline_options.dart'; import 'package:flutter_timeline_view/src/widgets/reaction_bottom.dart'; @@ -60,20 +58,6 @@ class _TimelinePostScreenState extends State { TimelinePost? post; bool isLoading = true; - late var textInputBuilder = widget.options.textInputBuilder ?? - (controller, suffixIcon, hintText) => TextField( - textCapitalization: TextCapitalization.sentences, - controller: controller, - decoration: InputDecoration( - hintText: hintText, - suffixIcon: suffixIcon, - border: OutlineInputBorder( - borderRadius: - BorderRadius.circular(20.0), // Adjust the value as needed - ), - ), - ); - @override void initState() { super.initState(); @@ -108,9 +92,10 @@ class _TimelinePostScreenState extends State { Widget build(BuildContext context) { var theme = Theme.of(context); var dateFormat = widget.options.dateFormat ?? - DateFormat('dd/MM/yyyy', Localizations.localeOf(context).languageCode); - var timeFormat = widget.options.timeFormat ?? DateFormat('HH:mm'); - + DateFormat( + "dd/MM/yyyy 'at' HH:mm", + Localizations.localeOf(context).languageCode, + ); if (isLoading) { return const Center( child: CircularProgressIndicator.adaptive(), @@ -130,6 +115,45 @@ class _TimelinePostScreenState extends State { ? a.createdAt.compareTo(b.createdAt) : b.createdAt.compareTo(a.createdAt), ); + var isLikedByUser = post.likedBy?.contains(widget.userId) ?? false; + + var textInputBuilder = widget.options.textInputBuilder ?? + (controller, suffixIcon, hintText) => TextField( + style: theme.textTheme.bodyMedium, + textCapitalization: TextCapitalization.sentences, + controller: controller, + decoration: InputDecoration( + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(25), + borderSide: const BorderSide( + color: Colors.black, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(25), + borderSide: const BorderSide( + color: Colors.black, + ), + ), + contentPadding: const EdgeInsets.symmetric( + vertical: 0, + horizontal: 16, + ), + hintText: widget.options.translations.writeComment, + hintStyle: theme.textTheme.bodyMedium!.copyWith( + color: theme.textTheme.bodyMedium!.color!.withOpacity(0.5), + ), + fillColor: Colors.white, + filled: true, + border: const OutlineInputBorder( + borderRadius: BorderRadius.all( + Radius.circular(25), + ), + borderSide: BorderSide.none, + ), + suffixIcon: suffixIcon, + ), + ); return Stack( children: [ @@ -191,7 +215,9 @@ class _TimelinePostScreenState extends State { widget.options.translations.anonymousUser, style: widget.options.theme.textStyles .postCreatorTitleStyle ?? - theme.textTheme.titleMedium, + theme.textTheme.titleSmall!.copyWith( + color: Colors.black, + ), ), ], ), @@ -199,7 +225,7 @@ class _TimelinePostScreenState extends State { const Spacer(), if (!(widget.isOverviewScreen ?? false) && (widget.allowAllDeletion || - post.creator?.userId == widget.userId)) + post.creator?.userId == widget.userId)) ...[ PopupMenuButton( onSelected: (value) async { if (value == 'delete') { @@ -238,6 +264,7 @@ class _TimelinePostScreenState extends State { color: widget.options.theme.iconColor, ), ), + ], ], ), // image of the posts @@ -295,53 +322,37 @@ class _TimelinePostScreenState extends State { // post information Row( children: [ - if (post.likedBy?.contains(widget.userId) ?? false) ...[ - InkWell( - onTap: () async { + IconButton( + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + onPressed: () async { + if (widget.isOverviewScreen ?? false) return; + if (isLikedByUser) { updatePost( await widget.service.postService.unlikePost( widget.userId, post, ), ); - }, - child: Container( - color: Colors.transparent, - child: widget.options.theme.likedIcon ?? - Icon( - widget.post.likedBy - ?.contains(widget.userId) ?? - false - ? Icons.favorite_rounded - : Icons.favorite_outline_outlined, - ), - ), - ), - ] else ...[ - InkWell( - onTap: () async { - if (widget.isOverviewScreen ?? false) return; + setState(() {}); + } else { updatePost( await widget.service.postService.likePost( widget.userId, post, ), ); - }, - child: Container( - color: Colors.transparent, - child: widget.options.theme.likeIcon ?? - Icon( - widget.post.likedBy - ?.contains(widget.userId) ?? - false - ? Icons.favorite_rounded - : Icons.favorite_outline_outlined, - size: widget.options.iconSize, - ), - ), + setState(() {}); + } + }, + icon: Icon( + isLikedByUser + ? Icons.favorite_rounded + : Icons.favorite_outline_outlined, + color: widget.options.theme.iconColor, + size: widget.options.iconSize, ), - ], + ), const SizedBox(width: 8), if (post.reactionEnabled) widget.options.theme.commentIcon ?? @@ -360,7 +371,6 @@ class _TimelinePostScreenState extends State { theme.textTheme.titleSmall ?.copyWith(color: Colors.black), ), - const SizedBox(height: 4), Text.rich( TextSpan( text: widget.options.nameBuilder?.call(post.creator) ?? @@ -368,14 +378,17 @@ class _TimelinePostScreenState extends State { widget.options.translations.anonymousUser, style: widget .options.theme.textStyles.postCreatorNameStyle ?? - theme.textTheme.titleSmall, + theme.textTheme.titleSmall! + .copyWith(color: Colors.black), children: [ - const TextSpan(text: ' '), TextSpan( text: post.title, style: widget.options.theme.textStyles.postTitleStyle ?? - theme.textTheme.bodyMedium, + theme.textTheme.titleSmall!.copyWith( + color: Colors.black, + fontWeight: FontWeight.w500, + ), ), ], ), @@ -387,6 +400,7 @@ class _TimelinePostScreenState extends State { 'body': Style( padding: HtmlPaddings.zero, margin: Margins.zero, + fontFamily: theme.textTheme.titleSmall?.fontFamily, ), '#': Style( maxLines: 3, @@ -410,20 +424,19 @@ class _TimelinePostScreenState extends State { ), const SizedBox(height: 4), Text( - '${dateFormat.format(post.createdAt)} ' - '${widget.options.translations.postAt} ' - '${timeFormat.format(post.createdAt)}', - style: theme.textTheme.bodySmall, + '${dateFormat.format(post.createdAt)} ', + style: theme.textTheme.labelSmall, ), - const SizedBox(height: 20), + const SizedBox(height: 16), if (post.reactionEnabled) ...[ Text( widget.options.translations.commentsTitleOnPost, - style: theme.textTheme.titleMedium, + style: theme.textTheme.titleSmall! + .copyWith(color: Colors.black), ), for (var reaction in post.reactions ?? []) ...[ - const SizedBox(height: 16), + const SizedBox(height: 4), GestureDetector( onLongPressStart: (details) async { if (reaction.creatorId == widget.userId || @@ -469,7 +482,7 @@ class _TimelinePostScreenState extends State { reaction.creator!.imageUrl!.isNotEmpty) ...[ widget.options.userAvatarBuilder?.call( reaction.creator!, - 28, + 14, ) ?? CircleAvatar( radius: 14, @@ -480,7 +493,7 @@ class _TimelinePostScreenState extends State { ] else ...[ widget.options.anonymousAvatarBuilder?.call( reaction.creator!, - 28, + 14, ) ?? const CircleAvatar( radius: 14, @@ -501,7 +514,8 @@ class _TimelinePostScreenState extends State { reaction.creator?.fullName ?? widget.options.translations .anonymousUser, - style: theme.textTheme.titleSmall, + style: theme.textTheme.titleSmall! + .copyWith(color: Colors.black), ), Padding( padding: const EdgeInsets.all(8.0), @@ -522,12 +536,13 @@ class _TimelinePostScreenState extends State { reaction.creator?.fullName ?? widget .options.translations.anonymousUser, - style: theme.textTheme.titleSmall, + style: theme.textTheme.titleSmall! + .copyWith(color: Colors.black), children: [ - const TextSpan(text: ' '), + const TextSpan(text: ' '), TextSpan( text: reaction.reaction ?? '', - style: theme.textTheme.bodyMedium, + style: theme.textTheme.bodySmall, ), // text should go to new line ], @@ -538,11 +553,12 @@ class _TimelinePostScreenState extends State { ], ), ), + const SizedBox(height: 4), ], if (post.reactions?.isEmpty ?? true) ...[ - const SizedBox(height: 16), Text( widget.options.translations.firstComment, + style: theme.textTheme.bodySmall, ), ], const SizedBox(height: 120), @@ -553,52 +569,67 @@ class _TimelinePostScreenState extends State { ), ), if (post.reactionEnabled && !(widget.isOverviewScreen ?? false)) - Align( - alignment: Alignment.bottomCenter, - child: ReactionBottom( - messageInputBuilder: textInputBuilder, - onPressSelectImage: () async { - // open the image picker - var result = await showModalBottomSheet( - context: context, - builder: (context) => Container( - padding: const EdgeInsets.all(8.0), - color: theme.colorScheme.surface, - child: ImagePicker( - imagePickerConfig: widget.options.imagePickerConfig, - imagePickerTheme: widget.options.imagePickerTheme, + SafeArea( + bottom: true, + child: Align( + alignment: Alignment.bottomCenter, + child: Container( + constraints: BoxConstraints( + maxWidth: MediaQuery.of(context).size.width, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(left: 8), + child: post.creator!.imageUrl != null + ? widget.options.userAvatarBuilder?.call( + post.creator!, + 28, + ) ?? + CircleAvatar( + radius: 14, + backgroundImage: CachedNetworkImageProvider( + post.creator!.imageUrl!, + ), + ) + : widget.options.anonymousAvatarBuilder?.call( + post.creator!, + 28, + ) ?? + const CircleAvatar( + radius: 14, + child: Icon( + Icons.person, + ), + ), ), - ), - ); - if (result != null) { - updatePost( - await widget.service.postService.reactToPost( - post, - TimelinePostReaction( - id: '', - postId: post.id, - creatorId: widget.userId, - createdAt: DateTime.now(), + Flexible( + child: Padding( + padding: const EdgeInsets.only(left: 8, right: 16), + child: ReactionBottom( + messageInputBuilder: textInputBuilder, + onReactionSubmit: (reaction) async => updatePost( + await widget.service.postService.reactToPost( + post, + TimelinePostReaction( + id: '', + postId: post.id, + reaction: reaction, + creatorId: widget.userId, + createdAt: DateTime.now(), + ), + ), + ), + translations: widget.options.translations, + iconColor: widget.options.theme.iconColor, + ), ), - image: result, ), - ); - } - }, - onReactionSubmit: (reaction) async => updatePost( - await widget.service.postService.reactToPost( - post, - TimelinePostReaction( - id: '', - postId: post.id, - reaction: reaction, - creatorId: widget.userId, - createdAt: DateTime.now(), - ), + ], ), ), - translations: widget.options.translations, - iconColor: widget.options.theme.iconColor, ), ), ], 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 171c597..bbc4d32 100644 --- a/packages/flutter_timeline_view/lib/src/widgets/reaction_bottom.dart +++ b/packages/flutter_timeline_view/lib/src/widgets/reaction_bottom.dart @@ -11,14 +11,12 @@ class ReactionBottom extends StatefulWidget { required this.onReactionSubmit, required this.messageInputBuilder, required this.translations, - this.onPressSelectImage, this.iconColor, super.key, }); final Future Function(String text) onReactionSubmit; final TextInputBuilder messageInputBuilder; - final VoidCallback? onPressSelectImage; final TimelineTranslations translations; final Color? iconColor; @@ -30,56 +28,26 @@ class _ReactionBottomState extends State { final TextEditingController _textEditingController = TextEditingController(); @override - Widget build(BuildContext context) => SafeArea( - bottom: true, - child: Container( - color: Theme.of(context).colorScheme.surface, - child: Container( - margin: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 8, - ), - height: 48, - child: widget.messageInputBuilder( - _textEditingController, - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 4, - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.onPressSelectImage != null) ...[ - IconButton( - onPressed: () async { - _textEditingController.clear(); - widget.onPressSelectImage?.call(); - }, - icon: Icon( - Icons.image, - color: widget.iconColor, - ), - ), - ], - IconButton( - onPressed: () async { - var value = _textEditingController.text; - if (value.isNotEmpty) { - await widget.onReactionSubmit(value); - _textEditingController.clear(); - } - }, - icon: Icon( - Icons.send, - color: widget.iconColor, - ), - ), - ], - ), - ), - widget.translations.writeComment, + Widget build(BuildContext context) => widget.messageInputBuilder( + _textEditingController, + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, + ), + child: IconButton( + onPressed: () async { + var value = _textEditingController.text; + if (value.isNotEmpty) { + await widget.onReactionSubmit(value); + _textEditingController.clear(); + } + }, + icon: Icon( + Icons.send, + color: widget.iconColor, ), ), ), + widget.translations.writeComment, ); } From a7fff5ae91ff26ef1d6fdd0b643c51f81e6f4cd7 Mon Sep 17 00:00:00 2001 From: mike doornenbal Date: Tue, 30 Jul 2024 13:34:15 +0200 Subject: [PATCH 03/20] fix: timeline_selection_screen --- .../src/flutter_timeline_navigator_userstory.dart | 4 ---- .../lib/src/screens/timeline_selection_screen.dart | 14 ++++---------- packages/flutter_timeline_view/pubspec.yaml | 2 +- 3 files changed, 5 insertions(+), 15 deletions(-) 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 6b8d487..23f98a2 100644 --- a/packages/flutter_timeline/lib/src/flutter_timeline_navigator_userstory.dart +++ b/packages/flutter_timeline/lib/src/flutter_timeline_navigator_userstory.dart @@ -156,7 +156,6 @@ Widget _postDetailScreenRoute({ Scaffold( appBar: AppBar( leading: backButton, - backgroundColor: const Color(0xff212121), title: Text( category?.title.toLowerCase() ?? post.category?.toLowerCase() ?? @@ -246,7 +245,6 @@ Widget _postCreationScreenRoute({ ?.call(context, timelinePostCreationScreen, backButton) ?? Scaffold( appBar: AppBar( - backgroundColor: const Color(0xff212121), leading: backButton, title: Text( config.optionsBuilder(context).translations.postCreation, @@ -314,7 +312,6 @@ Widget _postOverviewScreenRoute({ Scaffold( appBar: AppBar( leading: backButton, - backgroundColor: const Color(0xff212121), title: Text( config.optionsBuilder(context).translations.postOverview, style: TextStyle( @@ -375,7 +372,6 @@ Widget _postCategorySelectionScreen({ Scaffold( appBar: AppBar( leading: backButton, - backgroundColor: const Color(0xff212121), title: Text( config.optionsBuilder(context).translations.postCreation, style: TextStyle( 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 8d23418..28fa315 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,6 +19,7 @@ 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, @@ -27,16 +28,12 @@ class TimelineSelectionScreen extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: EdgeInsets.only(top: size.height * 0.05, bottom: 8), + padding: const EdgeInsets.only(top: 20, bottom: 12), child: Text( options.translations.timelineSelectionDescription, - style: const TextStyle( - fontWeight: FontWeight.w800, - fontSize: 20, - ), + style: theme.textTheme.titleLarge, ), ), - const SizedBox(height: 4), for (var category in categories.where( (element) => element.canCreate && element.key != null, )) ...[ @@ -72,10 +69,7 @@ class TimelineSelectionScreen extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 12.0), child: Text( category.title, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w800, - ), + style: theme.textTheme.titleMedium, ), ), ], diff --git a/packages/flutter_timeline_view/pubspec.yaml b/packages/flutter_timeline_view/pubspec.yaml index a7ddd70..018785c 100644 --- a/packages/flutter_timeline_view/pubspec.yaml +++ b/packages/flutter_timeline_view/pubspec.yaml @@ -32,7 +32,7 @@ dependencies: dependency_overrides: flutter_timeline_interface: - path: packages/flutter_timeline_interface + path: ../flutter_timeline_interface dev_dependencies: flutter_lints: ^2.0.0 From 32fe08a7af234f7dccc98ac8bd6ea50988aa8642 Mon Sep 17 00:00:00 2001 From: mike doornenbal Date: Tue, 30 Jul 2024 14:35:29 +0200 Subject: [PATCH 04/20] fix: post_creation_screen --- .../lib/src/config/timeline_paddings.dart | 3 +- .../timeline_post_creation_screen.dart | 146 ++++++++++-------- .../lib/src/screens/timeline_post_screen.dart | 2 +- .../src/widgets/post_creation_textfield.dart | 63 ++++++++ 4 files changed, 144 insertions(+), 70 deletions(-) create mode 100644 packages/flutter_timeline_view/lib/src/widgets/post_creation_textfield.dart diff --git a/packages/flutter_timeline_view/lib/src/config/timeline_paddings.dart b/packages/flutter_timeline_view/lib/src/config/timeline_paddings.dart index d42a2a4..b3dc27f 100644 --- a/packages/flutter_timeline_view/lib/src/config/timeline_paddings.dart +++ b/packages/flutter_timeline_view/lib/src/config/timeline_paddings.dart @@ -3,8 +3,7 @@ import 'package:flutter/material.dart'; /// This class contains the paddings used in the timeline options class TimelinePaddingOptions { const TimelinePaddingOptions({ - this.mainPadding = - const EdgeInsets.only(left: 12.0, top: 24.0, right: 12.0, bottom: 12.0), + this.mainPadding = const EdgeInsets.only(left: 32, top: 20, right: 32), this.postPadding = const EdgeInsets.only(left: 12.0, top: 6, right: 12.0, bottom: 8), this.postOverviewButtonBottomPadding = 30.0, 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 64b45b9..df3c1df 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 @@ -11,6 +11,7 @@ import 'package:flutter_image_picker/flutter_image_picker.dart'; import 'package:flutter_timeline_interface/flutter_timeline_interface.dart'; import 'package:flutter_timeline_view/flutter_timeline_view.dart'; import 'package:flutter_timeline_view/src/config/timeline_options.dart'; +import 'package:flutter_timeline_view/src/widgets/post_creation_textfield.dart'; class TimelinePostCreationScreen extends StatefulWidget { const TimelinePostCreationScreen({ @@ -119,6 +120,7 @@ class _TimelinePostCreationScreenState } var theme = Theme.of(context); + return GestureDetector( onTap: () => FocusScope.of(context).unfocus(), child: Padding( @@ -130,48 +132,47 @@ class _TimelinePostCreationScreenState children: [ Text( widget.options.translations.title, - style: const TextStyle( - fontWeight: FontWeight.w800, - fontSize: 20, - ), + style: theme.textTheme.titleMedium, + ), + const SizedBox( + height: 4, ), widget.options.textInputBuilder?.call( titleController, null, '', ) ?? - TextField( - maxLength: widget.options.maxTitleLength, + PostCreationTextfield( controller: titleController, - decoration: widget.options.contentInputDecoration ?? - InputDecoration( - hintText: widget.options.translations.titleHintText, - ), + hintText: widget.options.translations.titleHintText, + textMaxLength: widget.options.maxTitleLength, + decoration: widget.options.titleInputDecoration, + textCapitalization: null, + expands: null, + minLines: null, + maxLines: 1, ), const SizedBox(height: 16), Text( widget.options.translations.content, - style: const TextStyle( - fontWeight: FontWeight.w800, - fontSize: 20, - ), + style: theme.textTheme.titleMedium, ), - const SizedBox(height: 4), Text( widget.options.translations.contentDescription, - style: theme.textTheme.bodyMedium, + style: theme.textTheme.bodySmall, ), - // input field for the content - TextField( + const SizedBox( + height: 4, + ), + PostCreationTextfield( controller: contentController, + hintText: widget.options.translations.contentHintText, + textMaxLength: null, + decoration: widget.options.contentInputDecoration, textCapitalization: TextCapitalization.sentences, expands: false, - maxLines: null, minLines: null, - decoration: widget.options.contentInputDecoration ?? - InputDecoration( - hintText: widget.options.translations.contentHintText, - ), + maxLines: null, ), const SizedBox( height: 16, @@ -179,14 +180,11 @@ class _TimelinePostCreationScreenState // input field for the content Text( widget.options.translations.uploadImage, - style: const TextStyle( - fontWeight: FontWeight.w800, - fontSize: 20, - ), + style: theme.textTheme.titleMedium, ), Text( widget.options.translations.uploadImageDescription, - style: theme.textTheme.bodyMedium, + style: theme.textTheme.bodySmall, ), // image picker field const SizedBox( @@ -273,19 +271,22 @@ class _TimelinePostCreationScreenState Text( widget.options.translations.commentsTitle, - style: const TextStyle( - fontWeight: FontWeight.w800, - fontSize: 20, - ), + style: theme.textTheme.titleMedium, ), Text( widget.options.translations.allowCommentsDescription, - style: theme.textTheme.bodyMedium, + style: theme.textTheme.bodySmall, + ), + const SizedBox( + height: 8, ), Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Checkbox( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + visualDensity: + const VisualDensity(horizontal: -4, vertical: -4), activeColor: theme.colorScheme.primary, value: allowComments, onChanged: (value) { @@ -294,8 +295,17 @@ class _TimelinePostCreationScreenState }); }, ), - Text(widget.options.translations.yes), + Text( + widget.options.translations.yes, + style: theme.textTheme.bodyMedium, + ), + const SizedBox( + width: 32, + ), Checkbox( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + visualDensity: + const VisualDensity(horizontal: -4, vertical: -4), activeColor: theme.colorScheme.primary, value: !allowComments, onChanged: (value) { @@ -304,47 +314,49 @@ class _TimelinePostCreationScreenState }); }, ), - Text(widget.options.translations.no), + Text( + widget.options.translations.no, + style: theme.textTheme.bodyMedium, + ), ], ), const SizedBox(height: 120), - Align( - alignment: Alignment.bottomCenter, - child: (widget.options.buttonBuilder != null) - ? widget.options.buttonBuilder!( - context, - onPostCreated, - widget.options.translations.checkPost, - enabled: editingDone, - ) - : ElevatedButton( - style: ButtonStyle( - backgroundColor: WidgetStatePropertyAll( - theme.colorScheme.primary, + SafeArea( + bottom: true, + child: Align( + alignment: Alignment.bottomCenter, + child: (widget.options.buttonBuilder != null) + ? widget.options.buttonBuilder!( + context, + onPostCreated, + widget.options.translations.checkPost, + enabled: editingDone, + ) + : FilledButton( + style: ButtonStyle( + backgroundColor: WidgetStatePropertyAll( + theme.colorScheme.primary, + ), ), - ), - onPressed: editingDone - ? () async { - await onPostCreated(); - await widget.service.postService - .fetchPosts(null); - } - : null, - 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, + onPressed: editingDone + ? () async { + await onPostCreated(); + await widget.service.postService + .fetchPosts(null); + } + : null, + child: Padding( + padding: const EdgeInsets.all(8), + child: Text( + widget.enablePostOverviewScreen + ? widget.options.translations.checkPost + : widget.options.translations.postCreation, + style: theme.textTheme.displayLarge, ), ), ), - ), + ), ), ], ), 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 2f3e80a..3fbc855 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 @@ -169,7 +169,7 @@ class _TimelinePostScreenState extends State { }, child: SingleChildScrollView( child: Padding( - padding: widget.options.paddings.mainPadding, + padding: widget.options.paddings.postPadding, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/packages/flutter_timeline_view/lib/src/widgets/post_creation_textfield.dart b/packages/flutter_timeline_view/lib/src/widgets/post_creation_textfield.dart new file mode 100644 index 0000000..96a3263 --- /dev/null +++ b/packages/flutter_timeline_view/lib/src/widgets/post_creation_textfield.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; + +class PostCreationTextfield extends StatelessWidget { + const PostCreationTextfield({ + required this.controller, + required this.hintText, + super.key, + this.textMaxLength, + this.decoration, + this.textCapitalization, + this.expands, + this.minLines, + this.maxLines, + }); + + final TextEditingController controller; + final String hintText; + final int? textMaxLength; + final InputDecoration? decoration; + final TextCapitalization? textCapitalization; + // ignore: avoid_positional_boolean_parameters + final bool? expands; + final int? minLines; + final int? maxLines; + @override + Widget build(BuildContext context) { + var theme = Theme.of(context); + return TextField( + style: theme.textTheme.bodySmall, + controller: controller, + maxLength: textMaxLength, + decoration: decoration ?? + InputDecoration( + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + contentPadding: const EdgeInsets.symmetric( + vertical: 0, + horizontal: 16, + ), + hintText: hintText, + hintStyle: theme.textTheme.bodySmall!.copyWith( + color: theme.textTheme.bodySmall!.color!.withOpacity(0.5), + ), + fillColor: Colors.white, + filled: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + ), + textCapitalization: textCapitalization ?? TextCapitalization.none, + expands: expands ?? false, + minLines: minLines, + maxLines: maxLines, + ); + } +} From 971a030b5c7d63841c0283709ba86d8c330af6e0 Mon Sep 17 00:00:00 2001 From: mike doornenbal Date: Tue, 30 Jul 2024 15:08:45 +0200 Subject: [PATCH 05/20] fix: post_overview_screen --- .../lib/src/config/timeline_paddings.dart | 5 +- .../timeline_post_creation_screen.dart | 3 - .../timeline_post_overview_screen.dart | 11 ++-- .../lib/src/screens/timeline_post_screen.dart | 58 ++++++------------- .../lib/src/widgets/category_selector.dart | 2 +- 5 files changed, 27 insertions(+), 52 deletions(-) diff --git a/packages/flutter_timeline_view/lib/src/config/timeline_paddings.dart b/packages/flutter_timeline_view/lib/src/config/timeline_paddings.dart index b3dc27f..39fc5ac 100644 --- a/packages/flutter_timeline_view/lib/src/config/timeline_paddings.dart +++ b/packages/flutter_timeline_view/lib/src/config/timeline_paddings.dart @@ -3,9 +3,10 @@ import 'package:flutter/material.dart'; /// This class contains the paddings used in the timeline options class TimelinePaddingOptions { const TimelinePaddingOptions({ - this.mainPadding = const EdgeInsets.only(left: 32, top: 20, right: 32), + this.mainPadding = + const EdgeInsets.only(left: 32, top: 20, right: 32, bottom: 40), this.postPadding = - const EdgeInsets.only(left: 12.0, top: 6, right: 12.0, bottom: 8), + const EdgeInsets.only(left: 12.0, top: 12, right: 12.0, bottom: 8), this.postOverviewButtonBottomPadding = 30.0, this.categoryButtonTextPadding, }); 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 df3c1df..98ded87 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 @@ -177,7 +177,6 @@ class _TimelinePostCreationScreenState const SizedBox( height: 16, ), - // input field for the content Text( widget.options.translations.uploadImage, style: theme.textTheme.titleMedium, @@ -186,7 +185,6 @@ class _TimelinePostCreationScreenState widget.options.translations.uploadImageDescription, style: theme.textTheme.bodySmall, ), - // image picker field const SizedBox( height: 8, ), @@ -194,7 +192,6 @@ class _TimelinePostCreationScreenState children: [ GestureDetector( onTap: () async { - // open a dialog to choose between camera and gallery var result = await showModalBottomSheet( context: context, builder: (context) => Container( 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 df34fc4..df34bb1 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 @@ -20,6 +20,7 @@ class TimelinePostOverviewScreen extends StatelessWidget { @override Widget build(BuildContext context) { + var theme = Theme.of(context); // the timelinePost.category is a key so we need to get the category object var timelineCategoryName = options.categoriesOptions.categoriesBuilder ?.call(context) @@ -55,7 +56,7 @@ class TimelinePostOverviewScreen extends StatelessWidget { buttonText, enabled: true, ) ?? - ElevatedButton( + FilledButton( style: ButtonStyle( backgroundColor: WidgetStatePropertyAll(Theme.of(context).primaryColor), @@ -64,14 +65,10 @@ class TimelinePostOverviewScreen extends StatelessWidget { onPostSubmit(timelinePost); }, child: Padding( - padding: const EdgeInsets.all(12.0), + padding: const EdgeInsets.all(8), child: Text( buttonText, - style: const TextStyle( - color: Colors.white, - fontSize: 20, - fontWeight: FontWeight.w800, - ), + style: theme.textTheme.displayLarge, ), ), ), 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 3fbc855..466c0e7 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 @@ -364,13 +364,17 @@ class _TimelinePostScreenState extends State { ], ), const SizedBox(height: 8), - Text( - '${post.likes} ${widget.options.translations.likesTitle}', - style: widget - .options.theme.textStyles.postLikeTitleAndAmount ?? - theme.textTheme.titleSmall - ?.copyWith(color: Colors.black), - ), + if (widget.isOverviewScreen != null + ? !widget.isOverviewScreen! + : false) ...[ + Text( + '${post.likes} ${widget.options.translations.likesTitle}', + style: widget.options.theme.textStyles + .postLikeTitleAndAmount ?? + theme.textTheme.titleSmall + ?.copyWith(color: Colors.black), + ), + ], Text.rich( TextSpan( text: widget.options.nameBuilder?.call(post.creator) ?? @@ -385,42 +389,15 @@ class _TimelinePostScreenState extends State { text: post.title, style: widget.options.theme.textStyles.postTitleStyle ?? - theme.textTheme.titleSmall!.copyWith( - color: Colors.black, - fontWeight: FontWeight.w500, - ), + theme.textTheme.bodySmall, ), ], ), ), const SizedBox(height: 20), - Html( - data: post.content, - style: { - 'body': Style( - padding: HtmlPaddings.zero, - margin: Margins.zero, - fontFamily: theme.textTheme.titleSmall?.fontFamily, - ), - '#': Style( - maxLines: 3, - textOverflow: TextOverflow.ellipsis, - padding: HtmlPaddings.zero, - margin: Margins.zero, - ), - 'H1': Style( - padding: HtmlPaddings.zero, - margin: Margins.zero, - ), - 'H2': Style( - padding: HtmlPaddings.zero, - margin: Margins.zero, - ), - 'H3': Style( - padding: HtmlPaddings.zero, - margin: Margins.zero, - ), - }, + Text( + post.content, + style: theme.textTheme.bodySmall, ), const SizedBox(height: 4), Text( @@ -428,7 +405,10 @@ class _TimelinePostScreenState extends State { style: theme.textTheme.labelSmall, ), const SizedBox(height: 16), - if (post.reactionEnabled) ...[ + // ignore: avoid_bool_literals_in_conditional_expressions + if (post.reactionEnabled || widget.isOverviewScreen != null + ? !widget.isOverviewScreen! + : false) ...[ Text( widget.options.translations.commentsTitleOnPost, style: theme.textTheme.titleSmall! 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 e1c58f6..17d6eba 100644 --- a/packages/flutter_timeline_view/lib/src/widgets/category_selector.dart +++ b/packages/flutter_timeline_view/lib/src/widgets/category_selector.dart @@ -33,7 +33,7 @@ class _CategorySelectorState extends State { return SingleChildScrollView( scrollDirection: Axis.horizontal, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), + padding: const EdgeInsets.symmetric(horizontal: 8), child: Row( children: [ SizedBox( From a1024dac3d8cb60406577ddc9ecf3114cd0fbe74 Mon Sep 17 00:00:00 2001 From: mike doornenbal Date: Tue, 30 Jul 2024 15:59:58 +0200 Subject: [PATCH 06/20] fix: small issues --- .../flutter_timeline_gorouter_userstory.dart | 4 - .../flutter_timeline_navigator_userstory.dart | 2 +- .../lib/src/config/timeline_options.dart | 4 +- .../timeline_post_creation_screen.dart | 76 ++++++++++--------- .../timeline_post_overview_screen.dart | 21 +---- .../lib/src/screens/timeline_post_screen.dart | 19 ++--- .../lib/src/screens/timeline_screen.dart | 6 +- .../src/widgets/default_filled_button.dart | 32 ++++++++ .../lib/src/widgets/reaction_bottom.dart | 38 +++++----- 9 files changed, 111 insertions(+), 91 deletions(-) create mode 100644 packages/flutter_timeline_view/lib/src/widgets/default_filled_button.dart 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 0ca7d46..8bc790f 100644 --- a/packages/flutter_timeline/lib/src/flutter_timeline_gorouter_userstory.dart +++ b/packages/flutter_timeline/lib/src/flutter_timeline_gorouter_userstory.dart @@ -116,7 +116,6 @@ List getTimelineStoryRoutes({ Scaffold( appBar: AppBar( leading: backButton, - backgroundColor: const Color(0xff212121), title: Text( config.optionsBuilder(context).translations.postCreation, style: TextStyle( @@ -180,7 +179,6 @@ List getTimelineStoryRoutes({ Scaffold( appBar: AppBar( leading: backButton, - backgroundColor: const Color(0xff212121), title: Text( category?.title ?? post.category ?? 'Category', style: TextStyle( @@ -238,7 +236,6 @@ List getTimelineStoryRoutes({ ?.call(context, timelinePostCreationWidget, backButton) ?? Scaffold( appBar: AppBar( - backgroundColor: const Color(0xff212121), leading: backButton, title: Text( config.optionsBuilder(context).translations.postCreation, @@ -287,7 +284,6 @@ List getTimelineStoryRoutes({ Scaffold( appBar: AppBar( leading: backButton, - backgroundColor: const Color(0xff212121), title: Text( config.optionsBuilder(context).translations.postOverview, style: TextStyle( 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 23f98a2..fe8ed78 100644 --- a/packages/flutter_timeline/lib/src/flutter_timeline_navigator_userstory.dart +++ b/packages/flutter_timeline/lib/src/flutter_timeline_navigator_userstory.dart @@ -313,7 +313,7 @@ Widget _postOverviewScreenRoute({ appBar: AppBar( leading: backButton, title: Text( - config.optionsBuilder(context).translations.postOverview, + config.optionsBuilder(context).translations.postCreation, style: TextStyle( color: Theme.of(context).primaryColor, fontSize: 24, 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 6adbf10..53c5a77 100644 --- a/packages/flutter_timeline_view/lib/src/config/timeline_options.dart +++ b/packages/flutter_timeline_view/lib/src/config/timeline_options.dart @@ -16,7 +16,7 @@ class TimelineOptions { this.translations = const TimelineTranslations.empty(), this.paddings = const TimelinePaddingOptions(), this.imagePickerConfig = const ImagePickerConfig(), - this.imagePickerTheme = const ImagePickerTheme(), + this.imagePickerTheme, this.timelinePostHeight, this.sortCommentsAscending = true, this.sortPostsAscending = false, @@ -93,7 +93,7 @@ class TimelineOptions { /// ImagePickerTheme can be used to change the UI of the /// Image Picker Widget to change the text/icons to your liking. - final ImagePickerTheme imagePickerTheme; + final ImagePickerTheme? imagePickerTheme; /// ImagePickerConfig can be used to define the /// size and quality for the uploaded image. 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 98ded87..8092286 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 @@ -11,6 +11,7 @@ import 'package:flutter_image_picker/flutter_image_picker.dart'; import 'package:flutter_timeline_interface/flutter_timeline_interface.dart'; import 'package:flutter_timeline_view/flutter_timeline_view.dart'; import 'package:flutter_timeline_view/src/config/timeline_options.dart'; +import 'package:flutter_timeline_view/src/widgets/default_filled_button.dart'; import 'package:flutter_timeline_view/src/widgets/post_creation_textfield.dart'; class TimelinePostCreationScreen extends StatefulWidget { @@ -123,9 +124,9 @@ class _TimelinePostCreationScreenState return GestureDetector( onTap: () => FocusScope.of(context).unfocus(), - child: Padding( - padding: widget.options.paddings.mainPadding, - child: SingleChildScrollView( + child: SingleChildScrollView( + child: Padding( + padding: widget.options.paddings.mainPadding, child: Column( mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.start, @@ -199,7 +200,23 @@ class _TimelinePostCreationScreenState color: theme.colorScheme.surface, child: ImagePicker( imagePickerConfig: widget.options.imagePickerConfig, - imagePickerTheme: widget.options.imagePickerTheme, + imagePickerTheme: widget.options.imagePickerTheme ?? + ImagePickerTheme( + titleAlignment: TextAlign.center, + title: ' Do you want to upload a file' + ' or take a picture? ', + titleTextSize: + theme.textTheme.titleMedium!.fontSize!, + font: + theme.textTheme.titleMedium!.fontFamily!, + iconSize: 40, + selectImageText: 'UPLOAD FILE', + makePhotoText: 'TAKE PICTURE', + selectImageIcon: const Icon( + size: 40, + Icons.insert_drive_file, + ), + ), ), ), ); @@ -263,9 +280,7 @@ class _TimelinePostCreationScreenState ], ], ), - const SizedBox(height: 16), - Text( widget.options.translations.commentsTitle, style: theme.textTheme.titleMedium, @@ -318,41 +333,28 @@ class _TimelinePostCreationScreenState ], ), const SizedBox(height: 120), - SafeArea( bottom: true, child: Align( alignment: Alignment.bottomCenter, - child: (widget.options.buttonBuilder != null) - ? widget.options.buttonBuilder!( - context, - onPostCreated, - widget.options.translations.checkPost, - enabled: editingDone, - ) - : FilledButton( - style: ButtonStyle( - backgroundColor: WidgetStatePropertyAll( - theme.colorScheme.primary, - ), - ), - onPressed: editingDone - ? () async { - await onPostCreated(); - await widget.service.postService - .fetchPosts(null); - } - : null, - child: Padding( - padding: const EdgeInsets.all(8), - child: Text( - widget.enablePostOverviewScreen - ? widget.options.translations.checkPost - : widget.options.translations.postCreation, - style: theme.textTheme.displayLarge, - ), - ), - ), + child: widget.options.buttonBuilder?.call( + context, + onPostCreated, + widget.options.translations.checkPost, + enabled: editingDone, + ) ?? + DefaultFilledButton( + onPressed: editingDone + ? () async { + await onPostCreated(); + await widget.service.postService + .fetchPosts(null); + } + : null, + buttonText: widget.enablePostOverviewScreen + ? widget.options.translations.checkPost + : widget.options.translations.postCreation, + ), ), ), ], 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 df34bb1..3d20c59 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 @@ -4,6 +4,7 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_timeline_interface/flutter_timeline_interface.dart'; import 'package:flutter_timeline_view/flutter_timeline_view.dart'; +import 'package:flutter_timeline_view/src/widgets/default_filled_button.dart'; class TimelinePostOverviewScreen extends StatelessWidget { const TimelinePostOverviewScreen({ @@ -20,8 +21,6 @@ class TimelinePostOverviewScreen extends StatelessWidget { @override Widget build(BuildContext context) { - var theme = Theme.of(context); - // the timelinePost.category is a key so we need to get the category object var timelineCategoryName = options.categoriesOptions.categoriesBuilder ?.call(context) .firstWhereOrNull((element) => element.key == timelinePost.category) @@ -56,21 +55,9 @@ class TimelinePostOverviewScreen extends StatelessWidget { buttonText, enabled: true, ) ?? - FilledButton( - style: ButtonStyle( - backgroundColor: - WidgetStatePropertyAll(Theme.of(context).primaryColor), - ), - onPressed: () { - onPostSubmit(timelinePost); - }, - child: Padding( - padding: const EdgeInsets.all(8), - child: Text( - buttonText, - style: theme.textTheme.displayLarge, - ), - ), + DefaultFilledButton( + onPressed: () async => onPostSubmit(timelinePost), + buttonText: buttonText, ), SizedBox(height: options.paddings.postOverviewButtonBottomPadding), ], 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 466c0e7..ffc1c02 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 @@ -6,7 +6,6 @@ import 'dart:async'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_timeline_interface/flutter_timeline_interface.dart'; import 'package:flutter_timeline_view/src/config/timeline_options.dart'; import 'package:flutter_timeline_view/src/widgets/reaction_bottom.dart'; @@ -364,6 +363,7 @@ class _TimelinePostScreenState extends State { ], ), const SizedBox(height: 8), + // ignore: avoid_bool_literals_in_conditional_expressions if (widget.isOverviewScreen != null ? !widget.isOverviewScreen! : false) ...[ @@ -549,14 +549,15 @@ class _TimelinePostScreenState extends State { ), ), if (post.reactionEnabled && !(widget.isOverviewScreen ?? false)) - SafeArea( - bottom: true, - child: Align( - alignment: Alignment.bottomCenter, - child: Container( - constraints: BoxConstraints( - maxWidth: MediaQuery.of(context).size.width, - ), + Align( + alignment: Alignment.bottomCenter, + child: Container( + color: theme.scaffoldBackgroundColor, + constraints: BoxConstraints( + maxWidth: MediaQuery.of(context).size.width, + ), + child: SafeArea( + bottom: true, child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, 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 3be9c56..e8588eb 100644 --- a/packages/flutter_timeline_view/lib/src/screens/timeline_screen.dart +++ b/packages/flutter_timeline_view/lib/src/screens/timeline_screen.dart @@ -302,14 +302,14 @@ class _TimelineScreenState extends State { ), ), ), + SizedBox( + height: widget.options.paddings.mainPadding.bottom, + ), ], ), ), ), ), - SizedBox( - height: widget.options.paddings.mainPadding.bottom, - ), ], ); }, diff --git a/packages/flutter_timeline_view/lib/src/widgets/default_filled_button.dart b/packages/flutter_timeline_view/lib/src/widgets/default_filled_button.dart new file mode 100644 index 0000000..2eb6d4e --- /dev/null +++ b/packages/flutter_timeline_view/lib/src/widgets/default_filled_button.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; + +class DefaultFilledButton extends StatelessWidget { + const DefaultFilledButton({ + required this.onPressed, + required this.buttonText, + super.key, + }); + + final Future Function()? onPressed; + final String buttonText; + + @override + Widget build(BuildContext context) { + var theme = Theme.of(context); + return FilledButton( + style: ButtonStyle( + backgroundColor: WidgetStatePropertyAll( + theme.colorScheme.primary, + ), + ), + onPressed: onPressed, + child: Padding( + padding: const EdgeInsets.all(8), + child: Text( + buttonText, + style: theme.textTheme.displayLarge, + ), + ), + ); + } +} 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 bbc4d32..1e3f39d 100644 --- a/packages/flutter_timeline_view/lib/src/widgets/reaction_bottom.dart +++ b/packages/flutter_timeline_view/lib/src/widgets/reaction_bottom.dart @@ -28,26 +28,28 @@ class _ReactionBottomState extends State { final TextEditingController _textEditingController = TextEditingController(); @override - Widget build(BuildContext context) => widget.messageInputBuilder( - _textEditingController, - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8, - ), - child: IconButton( - onPressed: () async { - var value = _textEditingController.text; - if (value.isNotEmpty) { - await widget.onReactionSubmit(value); - _textEditingController.clear(); - } - }, - icon: Icon( - Icons.send, - color: widget.iconColor, + Widget build(BuildContext context) => Container( + child: widget.messageInputBuilder( + _textEditingController, + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, + ), + child: IconButton( + onPressed: () async { + var value = _textEditingController.text; + if (value.isNotEmpty) { + await widget.onReactionSubmit(value); + _textEditingController.clear(); + } + }, + icon: Icon( + Icons.send, + color: widget.iconColor, + ), ), ), + widget.translations.writeComment, ), - widget.translations.writeComment, ); } From c572e6cd8b7301be055975e2a192e3a906f32459 Mon Sep 17 00:00:00 2001 From: mike doornenbal Date: Tue, 30 Jul 2024 16:08:49 +0200 Subject: [PATCH 07/20] fix: imagepicker popup --- .../src/screens/timeline_post_creation_screen.dart | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 8092286..0406314 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 @@ -217,6 +217,17 @@ class _TimelinePostCreationScreenState Icons.insert_drive_file, ), ), + customButton: TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: Text( + 'Cancel', + style: theme.textTheme.bodyMedium!.copyWith( + decoration: TextDecoration.underline, + ), + ), + ), ), ), ); From 1f629ddf1f8079fc844648a94134bc5f27f59ef2 Mon Sep 17 00:00:00 2001 From: mike doornenbal Date: Wed, 31 Jul 2024 10:31:56 +0200 Subject: [PATCH 08/20] fix: small ui fixes --- .../flutter_timeline_view/assets/Comment.svg | 3 + .../flutter_timeline_view/assets/send.svg | 3 + .../timeline_post_creation_screen.dart | 27 +- .../timeline_post_overview_screen.dart | 18 +- .../lib/src/screens/timeline_post_screen.dart | 14 +- .../src/widgets/default_filled_button.dart | 1 + .../lib/src/widgets/reaction_bottom.dart | 7 +- .../lib/src/widgets/timeline_post_widget.dart | 520 +++++++++--------- packages/flutter_timeline_view/pubspec.yaml | 5 +- 9 files changed, 315 insertions(+), 283 deletions(-) create mode 100644 packages/flutter_timeline_view/assets/Comment.svg create mode 100644 packages/flutter_timeline_view/assets/send.svg diff --git a/packages/flutter_timeline_view/assets/Comment.svg b/packages/flutter_timeline_view/assets/Comment.svg new file mode 100644 index 0000000..35a7950 --- /dev/null +++ b/packages/flutter_timeline_view/assets/Comment.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/flutter_timeline_view/assets/send.svg b/packages/flutter_timeline_view/assets/send.svg new file mode 100644 index 0000000..0293ec9 --- /dev/null +++ b/packages/flutter_timeline_view/assets/send.svg @@ -0,0 +1,3 @@ + + + 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 0406314..36164b4 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 @@ -148,7 +148,7 @@ class _TimelinePostCreationScreenState hintText: widget.options.translations.titleHintText, textMaxLength: widget.options.maxTitleLength, decoration: widget.options.titleInputDecoration, - textCapitalization: null, + textCapitalization: TextCapitalization.sentences, expands: null, minLines: null, maxLines: 1, @@ -354,17 +354,20 @@ class _TimelinePostCreationScreenState widget.options.translations.checkPost, enabled: editingDone, ) ?? - DefaultFilledButton( - onPressed: editingDone - ? () async { - await onPostCreated(); - await widget.service.postService - .fetchPosts(null); - } - : null, - buttonText: widget.enablePostOverviewScreen - ? widget.options.translations.checkPost - : widget.options.translations.postCreation, + Padding( + padding: const EdgeInsets.symmetric(horizontal: 48), + child: DefaultFilledButton( + onPressed: editingDone + ? () async { + await onPostCreated(); + await widget.service.postService + .fetchPosts(null); + } + : null, + buttonText: widget.enablePostOverviewScreen + ? widget.options.translations.checkPost + : widget.options.translations.postCreation, + ), ), ), ), 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 3d20c59..9336226 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 @@ -27,6 +27,7 @@ class TimelinePostOverviewScreen extends StatelessWidget { ?.title ?? timelinePost.category; var buttonText = '${options.translations.postIn} $timelineCategoryName'; + var isSubmitted = false; return Column( mainAxisSize: MainAxisSize.max, children: [ @@ -55,11 +56,20 @@ class TimelinePostOverviewScreen extends StatelessWidget { buttonText, enabled: true, ) ?? - DefaultFilledButton( - onPressed: () async => onPostSubmit(timelinePost), - buttonText: buttonText, + SafeArea( + bottom: true, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 80), + child: DefaultFilledButton( + onPressed: () async { + if (isSubmitted) return; + isSubmitted = true; + onPostSubmit(timelinePost); + }, + buttonText: buttonText, + ), + ), ), - SizedBox(height: options.paddings.postOverviewButtonBottomPadding), ], ); } 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 ffc1c02..cfde054 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 @@ -6,6 +6,7 @@ import 'dart:async'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:flutter_timeline_interface/flutter_timeline_interface.dart'; import 'package:flutter_timeline_view/src/config/timeline_options.dart'; import 'package:flutter_timeline_view/src/widgets/reaction_bottom.dart'; @@ -355,10 +356,13 @@ class _TimelinePostScreenState extends State { const SizedBox(width: 8), if (post.reactionEnabled) widget.options.theme.commentIcon ?? - Icon( - Icons.chat_bubble_outline_rounded, + SvgPicture.asset( + 'assets/Comment.svg', + package: 'flutter_timeline_view', + // ignore: deprecated_member_use color: widget.options.theme.iconColor, - size: widget.options.iconSize, + width: widget.options.iconSize, + height: widget.options.iconSize, ), ], ), @@ -454,9 +458,7 @@ class _TimelinePostScreenState extends State { } }, child: Row( - crossAxisAlignment: reaction.imageUrl != null - ? CrossAxisAlignment.start - : CrossAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, children: [ if (reaction.creator?.imageUrl != null && reaction.creator!.imageUrl!.isNotEmpty) ...[ diff --git a/packages/flutter_timeline_view/lib/src/widgets/default_filled_button.dart b/packages/flutter_timeline_view/lib/src/widgets/default_filled_button.dart index 2eb6d4e..7581003 100644 --- a/packages/flutter_timeline_view/lib/src/widgets/default_filled_button.dart +++ b/packages/flutter_timeline_view/lib/src/widgets/default_filled_button.dart @@ -24,6 +24,7 @@ class DefaultFilledButton extends StatelessWidget { padding: const EdgeInsets.all(8), child: Text( buttonText, + overflow: TextOverflow.ellipsis, style: theme.textTheme.displayLarge, ), ), 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 1e3f39d..a13dfbe 100644 --- a/packages/flutter_timeline_view/lib/src/widgets/reaction_bottom.dart +++ b/packages/flutter_timeline_view/lib/src/widgets/reaction_bottom.dart @@ -3,6 +3,7 @@ // SPDX-License-Identifier: BSD-3-Clause import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_timeline_view/src/config/timeline_options.dart'; import 'package:flutter_timeline_view/src/config/timeline_translations.dart'; @@ -43,8 +44,10 @@ class _ReactionBottomState extends State { _textEditingController.clear(); } }, - icon: Icon( - Icons.send, + icon: SvgPicture.asset( + 'assets/send.svg', + package: 'flutter_timeline_view', + // ignore: deprecated_member_use color: widget.iconColor, ), ), 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 597309d..07b9c22 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 @@ -4,6 +4,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_timeline_interface/flutter_timeline_interface.dart'; import 'package:flutter_timeline_view/src/config/timeline_options.dart'; import 'package:flutter_timeline_view/src/widgets/tappable_image.dart'; @@ -53,303 +54,306 @@ class _TimelinePostWidgetState extends State { Widget build(BuildContext context) { var theme = Theme.of(context); var isLikedByUser = widget.post.likedBy?.contains(widget.userId) ?? false; - return InkWell( - onTap: widget.onTap, - child: SizedBox( - height: widget.post.imageUrl != null || widget.post.image != null - ? widget.options.postWidgetHeight - : null, - width: double.infinity, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - if (widget.post.creator != null) - InkWell( - onTap: widget.onUserTap != null - ? () => - widget.onUserTap?.call(widget.post.creator!.userId) - : null, - child: Row( - children: [ - if (widget.post.creator!.imageUrl != null) ...[ - widget.options.userAvatarBuilder?.call( - widget.post.creator!, - 28, - ) ?? - CircleAvatar( - radius: 14, - backgroundImage: CachedNetworkImageProvider( - widget.post.creator!.imageUrl!, - ), + return SizedBox( + height: widget.post.imageUrl != null || widget.post.image != null + ? widget.options.postWidgetHeight + : null, + width: double.infinity, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + if (widget.post.creator != null) + InkWell( + onTap: widget.onUserTap != null + ? () => + widget.onUserTap?.call(widget.post.creator!.userId) + : null, + child: Row( + children: [ + if (widget.post.creator!.imageUrl != null) ...[ + widget.options.userAvatarBuilder?.call( + widget.post.creator!, + 28, + ) ?? + CircleAvatar( + radius: 14, + backgroundImage: CachedNetworkImageProvider( + widget.post.creator!.imageUrl!, ), - ] else ...[ - widget.options.anonymousAvatarBuilder?.call( - widget.post.creator!, - 28, - ) ?? - const CircleAvatar( - radius: 14, - child: Icon( - Icons.person, - ), - ), - ], - const SizedBox(width: 10), - Text( - widget.options.nameBuilder - ?.call(widget.post.creator) ?? - widget.post.creator?.fullName ?? - widget.options.translations.anonymousUser, - style: widget.options.theme.textStyles - .postCreatorTitleStyle ?? - theme.textTheme.titleSmall!.copyWith( - color: Colors.black, - ), - ), - ], - ), - ), - const Spacer(), - if (widget.allowAllDeletion || - widget.post.creator?.userId == widget.userId) - PopupMenuButton( - onSelected: (value) async { - if (value == 'delete') { - await showPostDeletionConfirmationDialog( - widget.options, - context, - widget.onPostDelete, - ); - } - }, - itemBuilder: (BuildContext context) => - >[ - PopupMenuItem( - value: 'delete', - child: Row( - children: [ - Text( - widget.options.translations.deletePost, - style: widget.options.theme.textStyles - .deletePostStyle ?? - theme.textTheme.bodyMedium, ), - const SizedBox(width: 8), - widget.options.theme.deleteIcon ?? - Icon( - Icons.delete, - color: widget.options.theme.iconColor, - ), - ], - ), + ] else ...[ + widget.options.anonymousAvatarBuilder?.call( + widget.post.creator!, + 28, + ) ?? + const CircleAvatar( + radius: 14, + child: Icon( + Icons.person, + ), + ), + ], + const SizedBox(width: 10), + Text( + widget.options.nameBuilder?.call(widget.post.creator) ?? + widget.post.creator?.fullName ?? + widget.options.translations.anonymousUser, + style: widget.options.theme.textStyles + .postCreatorTitleStyle ?? + theme.textTheme.titleSmall!.copyWith( + color: Colors.black, + ), ), ], - child: widget.options.theme.moreIcon ?? - Icon( - Icons.more_horiz_rounded, - color: widget.options.theme.iconColor, - ), ), - ], - ), - // image of the post - if (widget.post.imageUrl != null || widget.post.image != null) ...[ - const SizedBox(height: 8), - Flexible( - flex: widget.options.postWidgetHeight != null ? 1 : 0, - child: ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(8)), - child: widget.options.doubleTapTolike - ? TappableImage( - likeAndDislikeIcon: - widget.options.likeAndDislikeIconsForDoubleTap, - post: widget.post, - userId: widget.userId, - onLike: ({required bool liked}) async { - var userId = widget.userId; - - late TimelinePost result; - - if (!liked) { - result = - await widget.service.postService.likePost( - userId, - widget.post, - ); - } else { - result = - await widget.service.postService.unlikePost( - userId, - widget.post, - ); - } - - return result.likedBy?.contains(userId) ?? false; - }, - ) - : 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, - ), ), - ), + const Spacer(), + if (widget.allowAllDeletion || + widget.post.creator?.userId == widget.userId) + PopupMenuButton( + onSelected: (value) async { + if (value == 'delete') { + await showPostDeletionConfirmationDialog( + widget.options, + context, + widget.onPostDelete, + ); + } + }, + itemBuilder: (BuildContext context) => + >[ + PopupMenuItem( + value: 'delete', + child: Row( + children: [ + Text( + widget.options.translations.deletePost, + style: widget + .options.theme.textStyles.deletePostStyle ?? + theme.textTheme.bodyMedium, + ), + const SizedBox(width: 8), + widget.options.theme.deleteIcon ?? + Icon( + Icons.delete, + color: widget.options.theme.iconColor, + ), + ], + ), + ), + ], + child: widget.options.theme.moreIcon ?? + Icon( + Icons.more_horiz_rounded, + color: widget.options.theme.iconColor, + ), + ), ], - const SizedBox( - height: 8, + ), + // image of the post + if (widget.post.imageUrl != null || widget.post.image != null) ...[ + const SizedBox(height: 8), + Flexible( + flex: widget.options.postWidgetHeight != null ? 1 : 0, + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: widget.options.doubleTapTolike + ? TappableImage( + likeAndDislikeIcon: + widget.options.likeAndDislikeIconsForDoubleTap, + post: widget.post, + userId: widget.userId, + onLike: ({required bool liked}) async { + var userId = widget.userId; + + late TimelinePost result; + + if (!liked) { + result = await widget.service.postService.likePost( + userId, + widget.post, + ); + } else { + result = + await widget.service.postService.unlikePost( + userId, + widget.post, + ); + } + + return result.likedBy?.contains(userId) ?? false; + }, + ) + : 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, + ), + ), ), - // post information - if (widget.options.iconsWithValues) ...[ - Row( - children: [ + ], + const SizedBox( + height: 8, + ), + // post information + if (widget.options.iconsWithValues) ...[ + Row( + children: [ + IconButton( + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + onPressed: () async { + var userId = widget.userId; + + if (!isLikedByUser) { + await widget.service.postService.likePost( + userId, + widget.post, + ); + } else { + await widget.service.postService.unlikePost( + userId, + widget.post, + ); + } + }, + icon: widget.options.theme.likeIcon ?? + Icon( + isLikedByUser + ? Icons.favorite_rounded + : Icons.favorite_outline_outlined, + color: widget.options.theme.iconColor, + size: widget.options.iconSize, + ), + ), + const SizedBox( + width: 4, + ), + Text('${widget.post.likes}'), + if (widget.post.reactionEnabled) ...[ + const SizedBox( + width: 8, + ), IconButton( padding: EdgeInsets.zero, constraints: const BoxConstraints(), - onPressed: () async { - var userId = widget.userId; - - if (!isLikedByUser) { - await widget.service.postService.likePost( - userId, - widget.post, - ); - } else { - await widget.service.postService.unlikePost( - userId, - widget.post, - ); - } - }, - icon: widget.options.theme.likeIcon ?? - Icon( - isLikedByUser - ? Icons.favorite_rounded - : Icons.favorite_outline_outlined, + onPressed: widget.onTap, + icon: widget.options.theme.commentIcon ?? + SvgPicture.asset( + 'assets/Comment.svg', + package: 'flutter_timeline_view', + // ignore: deprecated_member_use color: widget.options.theme.iconColor, - size: widget.options.iconSize, + width: widget.options.iconSize, + height: widget.options.iconSize, ), ), const SizedBox( width: 4, ), - Text('${widget.post.likes}'), - if (widget.post.reactionEnabled) ...[ - const SizedBox( - width: 8, - ), - IconButton( - padding: EdgeInsets.zero, - constraints: const BoxConstraints(), - onPressed: widget.onTap, - icon: widget.options.theme.commentIcon ?? - Icon( - Icons.chat_bubble_outline_outlined, - color: widget.options.theme.iconColor, - size: widget.options.iconSize, - ), - ), - const SizedBox( - width: 4, - ), - Text('${widget.post.reaction}'), - ], + Text('${widget.post.reaction}'), ], - ), - ] else ...[ - Row( - children: [ + ], + ), + ] else ...[ + Row( + children: [ + IconButton( + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + onPressed: + isLikedByUser ? widget.onTapUnlike : widget.onTapLike, + icon: (isLikedByUser + ? widget.options.theme.likedIcon + : widget.options.theme.likeIcon) ?? + Icon( + isLikedByUser + ? Icons.favorite_rounded + : Icons.favorite_outline, + color: widget.options.theme.iconColor, + size: widget.options.iconSize, + ), + ), + const SizedBox(width: 8), + if (widget.post.reactionEnabled) ...[ IconButton( padding: EdgeInsets.zero, constraints: const BoxConstraints(), - onPressed: - isLikedByUser ? widget.onTapUnlike : widget.onTapLike, - icon: (isLikedByUser - ? widget.options.theme.likedIcon - : widget.options.theme.likeIcon) ?? - Icon( - isLikedByUser - ? Icons.favorite_rounded - : Icons.favorite_outline, + onPressed: widget.onTap, + icon: widget.options.theme.commentIcon ?? + SvgPicture.asset( + 'assets/Comment.svg', + package: 'flutter_timeline_view', + // ignore: deprecated_member_use color: widget.options.theme.iconColor, - size: widget.options.iconSize, + width: widget.options.iconSize, + height: widget.options.iconSize, ), ), - const SizedBox(width: 8), - if (widget.post.reactionEnabled) ...[ - IconButton( - padding: EdgeInsets.zero, - constraints: const BoxConstraints(), - onPressed: widget.onTap, - icon: widget.options.theme.commentIcon ?? - Icon( - Icons.chat_bubble_outline_outlined, - color: widget.options.theme.iconColor, - size: widget.options.iconSize, - ), - ), - ], ], - ), - ], - - const SizedBox( - height: 8, + ], ), + ], - if (widget.options.itemInfoBuilder != null) ...[ - widget.options.itemInfoBuilder!( - post: widget.post, - ), - ] else ...[ - Text( - '${widget.post.likes} ' - '${widget.options.translations.likesTitle}', - style: widget - .options.theme.textStyles.listPostLikeTitleAndAmount ?? - theme.textTheme.titleSmall!.copyWith( - color: Colors.black, - ), - ), - Text.rich( - TextSpan( - text: widget.options.nameBuilder?.call(widget.post.creator) ?? - widget.post.creator?.fullName ?? - widget.options.translations.anonymousUser, - style: widget.options.theme.textStyles.listCreatorNameStyle ?? + const SizedBox( + height: 8, + ), + + if (widget.options.itemInfoBuilder != null) ...[ + widget.options.itemInfoBuilder!( + post: widget.post, + ), + ] else ...[ + Text( + '${widget.post.likes} ' + '${widget.options.translations.likesTitle}', + style: + widget.options.theme.textStyles.listPostLikeTitleAndAmount ?? theme.textTheme.titleSmall!.copyWith( color: Colors.black, ), - children: [ - TextSpan( - text: widget.post.title, - style: - widget.options.theme.textStyles.listPostTitleStyle ?? - theme.textTheme.bodySmall, + ), + Text.rich( + TextSpan( + text: widget.options.nameBuilder?.call(widget.post.creator) ?? + widget.post.creator?.fullName ?? + widget.options.translations.anonymousUser, + style: widget.options.theme.textStyles.listCreatorNameStyle ?? + theme.textTheme.titleSmall!.copyWith( + color: Colors.black, ), - ], - ), + children: [ + TextSpan( + text: widget.post.title, + style: widget.options.theme.textStyles.listPostTitleStyle ?? + theme.textTheme.bodySmall, + ), + ], ), - const SizedBox(height: 4), - Text( + ), + const SizedBox(height: 4), + InkWell( + onTap: widget.onTap, + child: Text( widget.options.translations.viewPost, style: widget.options.theme.textStyles.viewPostStyle ?? theme.textTheme.titleSmall!.copyWith( color: const Color(0xFF8D8D8D), ), ), - ], - if (widget.options.dividerBuilder != null) - widget.options.dividerBuilder!(), + ), ], - ), + if (widget.options.dividerBuilder != null) + widget.options.dividerBuilder!(), + ], ), ); } diff --git a/packages/flutter_timeline_view/pubspec.yaml b/packages/flutter_timeline_view/pubspec.yaml index 018785c..b16f14c 100644 --- a/packages/flutter_timeline_view/pubspec.yaml +++ b/packages/flutter_timeline_view/pubspec.yaml @@ -17,7 +17,6 @@ dependencies: intl: any cached_network_image: ^3.2.2 dotted_border: ^2.1.0 - flutter_html: ^3.0.0-beta.2 flutter_timeline_interface: git: @@ -29,6 +28,7 @@ dependencies: url: https://github.com/Iconica-Development/flutter_image_picker ref: 1.0.5 collection: any + flutter_svg: ^2.0.10+1 dependency_overrides: flutter_timeline_interface: @@ -42,3 +42,6 @@ dev_dependencies: ref: 6.0.0 flutter: + assets: + - assets/ + From eb953ede0ddc3af5d41a74ef1ecb3780a25b147b Mon Sep 17 00:00:00 2001 From: mike doornenbal Date: Wed, 31 Jul 2024 14:49:33 +0200 Subject: [PATCH 09/20] feat: add category, remove post --- .../flutter_timeline_gorouter_userstory.dart | 19 +-- .../flutter_timeline_navigator_userstory.dart | 49 +++--- packages/flutter_timeline/pubspec.yaml | 14 +- .../src/config/firebase_timeline_options.dart | 2 + .../src/service/firebase_post_service.dart | 49 +++++- .../flutter_timeline_firebase/pubspec.yaml | 17 +- .../lib/src/model/timeline_category.dart | 31 ++++ .../src/services/timeline_post_service.dart | 8 +- .../lib/src/config/timeline_options.dart | 30 +--- .../lib/src/config/timeline_translations.dart | 44 +++++- .../timeline_post_overview_screen.dart | 3 +- .../lib/src/screens/timeline_post_screen.dart | 18 ++- .../lib/src/screens/timeline_screen.dart | 9 ++ .../screens/timeline_selection_screen.dart | 147 ++++++++++++++++-- .../lib/src/services/local_post_service.dart | 31 ++++ .../lib/src/widgets/category_selector.dart | 81 +++++----- .../lib/src/widgets/timeline_post_widget.dart | 62 +++++--- 17 files changed, 455 insertions(+), 159 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 8bc790f..0ad5869 100644 --- a/packages/flutter_timeline/lib/src/flutter_timeline_gorouter_userstory.dart +++ b/packages/flutter_timeline/lib/src/flutter_timeline_gorouter_userstory.dart @@ -88,13 +88,9 @@ List getTimelineStoryRoutes({ path: TimelineUserStoryRoutes.timelineCategorySelection, pageBuilder: (context, state) { var timelineSelectionScreen = TimelineSelectionScreen( + postService: config.service.postService, options: config.optionsBuilder(context), - categories: config - .optionsBuilder(context) - .categoriesOptions - .categoriesBuilder - ?.call(context) ?? - [], + categories: config.service.postService.categories, onCategorySelected: (category) async { await context.push( TimelineUserStoryRoutes.timelinepostCreation(category.key ?? ''), @@ -135,14 +131,9 @@ List getTimelineStoryRoutes({ pageBuilder: (context, state) { var service = config.serviceBuilder?.call(context) ?? config.service; var post = service.postService.getPost(state.pathParameters['post']!); - var category = config.optionsBuilder - .call(context) - .categoriesOptions - .categoriesBuilder - ?.call(context) - .firstWhereOrNull( - (element) => element.key == post?.category, - ); + var category = service.postService.categories.firstWhereOrNull( + (element) => element.key == post?.category, + ); var timelinePostWidget = TimelinePostScreen( userId: config.getUserId?.call(context) ?? config.userId, 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 fe8ed78..0d90d21 100644 --- a/packages/flutter_timeline/lib/src/flutter_timeline_navigator_userstory.dart +++ b/packages/flutter_timeline/lib/src/flutter_timeline_navigator_userstory.dart @@ -72,14 +72,29 @@ Widget _timelineScreenRoute({ .theme .postCreationFloatingActionButtonColor ?? theme.colorScheme.primary, - onPressed: () async => Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => _postCategorySelectionScreen( - configuration: config, - context: context, - ), - ), - ), + onPressed: () async { + var selectedCategory = config.service.postService.selectedCategory; + if (selectedCategory != null && selectedCategory.key != null) { + await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => _postCreationScreenRoute( + configuration: config, + context: context, + category: selectedCategory, + ), + ), + ); + } else { + await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => _postCategorySelectionScreen( + configuration: config, + context: context, + ), + ), + ); + } + }, shape: const CircleBorder(), child: const Icon( Icons.add, @@ -138,11 +153,7 @@ Widget _postDetailScreenRoute({ onUserTap: (user) => config.onUserTap?.call(context, user), ); - var category = config - .optionsBuilder(context) - .categoriesOptions - .categoriesBuilder - ?.call(context) + var category = config.service.postService.categories .firstWhere((element) => element.key == post.category); var backButton = IconButton( @@ -157,9 +168,7 @@ Widget _postDetailScreenRoute({ appBar: AppBar( leading: backButton, title: Text( - category?.title.toLowerCase() ?? - post.category?.toLowerCase() ?? - 'category', + category.title.toLowerCase(), style: TextStyle( color: Theme.of(context).primaryColor, fontSize: 24, @@ -339,13 +348,9 @@ Widget _postCategorySelectionScreen({ ); var timelineSelectionScreen = TimelineSelectionScreen( + postService: config.service.postService, options: config.optionsBuilder(context), - categories: config - .optionsBuilder(context) - .categoriesOptions - .categoriesBuilder - ?.call(context) ?? - [], + categories: config.service.postService.categories, onCategorySelected: (category) async { await Navigator.of(context).push( MaterialPageRoute( diff --git a/packages/flutter_timeline/pubspec.yaml b/packages/flutter_timeline/pubspec.yaml index cc4238c..ffefefb 100644 --- a/packages/flutter_timeline/pubspec.yaml +++ b/packages/flutter_timeline/pubspec.yaml @@ -18,17 +18,21 @@ dependencies: collection: any flutter_timeline_view: - git: - url: https://github.com/Iconica-Development/flutter_timeline - path: packages/flutter_timeline_view - ref: 4.1.0 + git: + url: https://github.com/Iconica-Development/flutter_timeline + path: packages/flutter_timeline_view + ref: 4.1.0 flutter_timeline_interface: git: url: https://github.com/Iconica-Development/flutter_timeline path: packages/flutter_timeline_interface ref: 4.1.0 - +dependency_overrides: + flutter_timeline_view: + path: ../flutter_timeline_view + flutter_timeline_interface: + path: ../flutter_timeline_interface dev_dependencies: flutter_lints: ^2.0.0 flutter_iconica_analysis: diff --git a/packages/flutter_timeline_firebase/lib/src/config/firebase_timeline_options.dart b/packages/flutter_timeline_firebase/lib/src/config/firebase_timeline_options.dart index 0c83cb9..03f32e3 100644 --- a/packages/flutter_timeline_firebase/lib/src/config/firebase_timeline_options.dart +++ b/packages/flutter_timeline_firebase/lib/src/config/firebase_timeline_options.dart @@ -9,8 +9,10 @@ class FirebaseTimelineOptions { const FirebaseTimelineOptions({ this.usersCollectionName = 'users', this.timelineCollectionName = 'timeline', + this.timelineCategoryCollectionName = 'timeline_categories', }); final String usersCollectionName; final String timelineCollectionName; + final String timelineCategoryCollectionName; } diff --git a/packages/flutter_timeline_firebase/lib/src/service/firebase_post_service.dart b/packages/flutter_timeline_firebase/lib/src/service/firebase_post_service.dart index 1c5ec58..51edf98 100644 --- a/packages/flutter_timeline_firebase/lib/src/service/firebase_post_service.dart +++ b/packages/flutter_timeline_firebase/lib/src/service/firebase_post_service.dart @@ -5,6 +5,7 @@ import 'dart:typed_data'; import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:collection/collection.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_storage/firebase_storage.dart'; import 'package:flutter/material.dart'; @@ -38,6 +39,12 @@ class FirebaseTimelinePostService @override List posts = []; + @override + List categories = []; + + @override + TimelineCategory? selectedCategory; + @override Future createPost(TimelinePost post) async { var postId = const Uuid().v4(); @@ -118,7 +125,6 @@ class FirebaseTimelinePostService @override Future> fetchPosts(String? category) async { - debugPrint('fetching posts from firebase with category: $category'); var snapshot = (category != null) ? await _db .collection(_options.timelineCollectionName) @@ -358,4 +364,45 @@ class FirebaseTimelinePostService return user; } + + @override + Future addCategory(TimelineCategory category) async { + var exists = categories.firstWhereOrNull( + (element) => element.title.toLowerCase() == category.title.toLowerCase(), + ); + if (exists != null) return false; + try { + await _db + .collection(_options.timelineCategoryCollectionName) + .add(category.toJson()); + categories.add(category); + notifyListeners(); + return true; + } on Exception catch (_) { + return false; + } + } + + @override + Future> fetchCategories() async { + categories.clear(); + categories.add( + const TimelineCategory( + key: null, + title: 'All', + ), + ); + var categoriesSnapshot = await _db + .collection(_options.timelineCategoryCollectionName) + .withConverter( + fromFirestore: (snapshot, _) => + TimelineCategory.fromJson(snapshot.data()!), + toFirestore: (model, _) => model.toJson(), + ) + .get(); + categories.addAll(categoriesSnapshot.docs.map((e) => e.data())); + + notifyListeners(); + return categories; + } } diff --git a/packages/flutter_timeline_firebase/pubspec.yaml b/packages/flutter_timeline_firebase/pubspec.yaml index cd7c5b6..a9cdf0b 100644 --- a/packages/flutter_timeline_firebase/pubspec.yaml +++ b/packages/flutter_timeline_firebase/pubspec.yaml @@ -9,7 +9,7 @@ version: 4.1.0 publish_to: none environment: - sdk: '>=3.1.3 <4.0.0' + sdk: ">=3.1.3 <4.0.0" dependencies: flutter: @@ -20,10 +20,16 @@ dependencies: uuid: ^4.2.1 flutter_timeline_interface: - git: - url: https://github.com/Iconica-Development/flutter_timeline - path: packages/flutter_timeline_interface - ref: 4.1.0 + git: + url: https://github.com/Iconica-Development/flutter_timeline + path: packages/flutter_timeline_interface + ref: 4.1.0 + collection: ^1.18.0 + +dependency_overrides: + flutter_timeline_interface: + path: ../flutter_timeline_interface + dev_dependencies: flutter_lints: ^2.0.0 @@ -33,4 +39,3 @@ dev_dependencies: ref: 6.0.0 flutter: - diff --git a/packages/flutter_timeline_interface/lib/src/model/timeline_category.dart b/packages/flutter_timeline_interface/lib/src/model/timeline_category.dart index 578de8d..3b9f39c 100644 --- a/packages/flutter_timeline_interface/lib/src/model/timeline_category.dart +++ b/packages/flutter_timeline_interface/lib/src/model/timeline_category.dart @@ -9,9 +9,40 @@ class TimelineCategory { this.canCreate = true, this.canView = true, }); + + TimelineCategory.fromJson(Map json) + : key = json['key'] as String?, + title = json['title'] as String, + icon = json['icon'] as Widget?, + canCreate = json['canCreate'] as bool? ?? true, + canView = json['canView'] as bool? ?? true; + final String? key; final String title; final Widget? icon; final bool canCreate; final bool canView; + + TimelineCategory copyWith({ + String? key, + String? title, + Widget? icon, + bool? canCreate, + bool? canView, + }) => + TimelineCategory( + key: key ?? this.key, + title: title ?? this.title, + icon: icon ?? this.icon, + canCreate: canCreate ?? this.canCreate, + canView: canView ?? this.canView, + ); + + Map toJson() => { + 'key': key, + 'title': title, + 'icon': icon, + 'canCreate': canCreate, + 'canView': canView, + }; } diff --git a/packages/flutter_timeline_interface/lib/src/services/timeline_post_service.dart b/packages/flutter_timeline_interface/lib/src/services/timeline_post_service.dart index 4933c4e..957702a 100644 --- a/packages/flutter_timeline_interface/lib/src/services/timeline_post_service.dart +++ b/packages/flutter_timeline_interface/lib/src/services/timeline_post_service.dart @@ -5,11 +5,12 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; -import 'package:flutter_timeline_interface/src/model/timeline_post.dart'; -import 'package:flutter_timeline_interface/src/model/timeline_reaction.dart'; +import 'package:flutter_timeline_interface/flutter_timeline_interface.dart'; abstract class TimelinePostService with ChangeNotifier { List posts = []; + List categories = []; + TimelineCategory? selectedCategory; Future deletePost(TimelinePost post); Future deletePostReaction(TimelinePost post, String reactionId); @@ -28,4 +29,7 @@ abstract class TimelinePostService with ChangeNotifier { }); Future likePost(String userId, TimelinePost post); Future unlikePost(String userId, TimelinePost post); + + Future> fetchCategories(); + Future addCategory(TimelineCategory category); } 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 53c5a77..d3694cf 100644 --- a/packages/flutter_timeline_view/lib/src/config/timeline_options.dart +++ b/packages/flutter_timeline_view/lib/src/config/timeline_options.dart @@ -174,32 +174,14 @@ class TimelineOptions { final InputDecoration? contentInputDecoration; } -List _getDefaultCategories(context) => [ - const TimelineCategory( - key: null, - title: 'All', - ), - const TimelineCategory( - key: 'Category', - title: 'Category', - ), - const TimelineCategory( - key: 'Category with two lines', - title: 'Category with two lines', - ), - ]; - class CategoriesOptions { const CategoriesOptions({ - this.categoriesBuilder = _getDefaultCategories, this.categoryButtonBuilder, this.categorySelectorHorizontalPadding, }); /// List of categories that the user can select. /// If this is null no categories will be shown. - final List Function(BuildContext context)? - categoriesBuilder; /// Abilty to override the standard category selector final Widget Function( @@ -214,17 +196,11 @@ class CategoriesOptions { final double? categorySelectorHorizontalPadding; TimelineCategory? getCategoryByKey( + List categories, BuildContext context, String? key, - ) { - if (categoriesBuilder == null) { - return null; - } - - return categoriesBuilder! - .call(context) - .firstWhereOrNull((category) => category.key == key); - } + ) => + categories.firstWhereOrNull((category) => category.key == key); } class FilterOptions { 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 ecf25c1..0bb78f5 100644 --- a/packages/flutter_timeline_view/lib/src/config/timeline_translations.dart +++ b/packages/flutter_timeline_view/lib/src/config/timeline_translations.dart @@ -35,7 +35,8 @@ class TimelineTranslations { required this.deleteCancelButton, required this.deleteButton, required this.viewPost, - required this.likesTitle, + required this.oneLikeTitle, + required this.multipleLikesTitle, required this.commentsTitle, required this.firstComment, required this.writeComment, @@ -48,6 +49,11 @@ class TimelineTranslations { required this.yes, required this.no, required this.timeLineScreenTitle, + required this.createCategoryPopuptitle, + required this.addCategoryTitle, + required this.addCategorySubmitButton, + required this.addCategoryCancelButtton, + required this.addCategoryHintText, }); /// Default translations for the timeline component view @@ -75,7 +81,8 @@ class TimelineTranslations { this.deleteCancelButton = 'Cancel', this.deleteReaction = 'Delete Reaction', this.viewPost = 'View post', - this.likesTitle = 'likes', + this.oneLikeTitle = 'like', + this.multipleLikesTitle = 'likes', this.commentsTitle = 'Are people allowed to comment?', this.firstComment = 'Be the first to comment', this.writeComment = 'Write your comment here...', @@ -88,6 +95,11 @@ class TimelineTranslations { this.yes = 'Yes', this.no = 'No', this.timeLineScreenTitle = 'iconinstagram', + this.createCategoryPopuptitle = 'Choose a title for the new category', + this.addCategoryTitle = 'Add category', + this.addCategorySubmitButton = 'Add category', + this.addCategoryCancelButtton = 'Cancel', + this.addCategoryHintText = 'Category name...', }); final String noPosts; @@ -114,7 +126,8 @@ class TimelineTranslations { final String deleteReaction; final String viewPost; - final String likesTitle; + final String oneLikeTitle; + final String multipleLikesTitle; final String commentsTitle; final String commentsTitleOnPost; final String writeComment; @@ -129,6 +142,12 @@ class TimelineTranslations { final String postIn; final String postCreation; + final String createCategoryPopuptitle; + final String addCategoryTitle; + final String addCategorySubmitButton; + final String addCategoryCancelButtton; + final String addCategoryHintText; + final String yes; final String no; final String timeLineScreenTitle; @@ -154,7 +173,8 @@ class TimelineTranslations { String? deleteCancelButton, String? deleteReaction, String? viewPost, - String? likesTitle, + String? oneLikeTitle, + String? multipleLikesTitle, String? commentsTitle, String? writeComment, String? firstComment, @@ -169,6 +189,11 @@ class TimelineTranslations { String? yes, String? no, String? timeLineScreenTitle, + String? createCategoryPopuptitle, + String? addCategoryTitle, + String? addCategorySubmitButton, + String? addCategoryCancelButtton, + String? addCategoryHintText, }) => TimelineTranslations( noPosts: noPosts ?? this.noPosts, @@ -194,7 +219,8 @@ class TimelineTranslations { deleteCancelButton: deleteCancelButton ?? this.deleteCancelButton, deleteReaction: deleteReaction ?? this.deleteReaction, viewPost: viewPost ?? this.viewPost, - likesTitle: likesTitle ?? this.likesTitle, + oneLikeTitle: oneLikeTitle ?? this.oneLikeTitle, + multipleLikesTitle: multipleLikesTitle ?? this.multipleLikesTitle, commentsTitle: commentsTitle ?? this.commentsTitle, writeComment: writeComment ?? this.writeComment, firstComment: firstComment ?? this.firstComment, @@ -210,5 +236,13 @@ class TimelineTranslations { yes: yes ?? this.yes, no: no ?? this.no, timeLineScreenTitle: timeLineScreenTitle ?? this.timeLineScreenTitle, + addCategoryTitle: addCategoryTitle ?? this.addCategoryTitle, + addCategorySubmitButton: + addCategorySubmitButton ?? this.addCategorySubmitButton, + addCategoryCancelButtton: + addCategoryCancelButtton ?? this.addCategoryCancelButtton, + addCategoryHintText: addCategoryHintText ?? this.addCategoryHintText, + createCategoryPopuptitle: + createCategoryPopuptitle ?? this.createCategoryPopuptitle, ); } 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 9336226..c8d833f 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 @@ -21,8 +21,7 @@ class TimelinePostOverviewScreen extends StatelessWidget { @override Widget build(BuildContext context) { - var timelineCategoryName = options.categoriesOptions.categoriesBuilder - ?.call(context) + var timelineCategoryName = service.postService.categories .firstWhereOrNull((element) => element.key == timelinePost.category) ?.title ?? timelinePost.category; 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 cfde054..18f3e10 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 @@ -74,8 +74,7 @@ class _TimelinePostScreenState extends State { post = loadedPost; isLoading = false; }); - } on Exception catch (e) { - debugPrint('Error loading post: $e'); + } on Exception catch (_) { setState(() { isLoading = false; }); @@ -372,7 +371,8 @@ class _TimelinePostScreenState extends State { ? !widget.isOverviewScreen! : false) ...[ Text( - '${post.likes} ${widget.options.translations.likesTitle}', + // ignore: lines_longer_than_80_chars + '${post.likes} ${post.likes > 1 ? widget.options.translations.multipleLikesTitle : widget.options.translations.oneLikeTitle}', style: widget.options.theme.textStyles .postLikeTitleAndAmount ?? theme.textTheme.titleSmall @@ -526,6 +526,18 @@ class _TimelinePostScreenState extends State { text: reaction.reaction ?? '', style: theme.textTheme.bodySmall, ), + const TextSpan(text: '\n'), + TextSpan( + text: dateFormat + .format(reaction.createdAt), + style: theme.textTheme.labelSmall! + .copyWith( + color: theme + .textTheme.labelSmall!.color! + .withOpacity(0.5), + ), + ), + // text should go to new line ], ), 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 e8588eb..d5c91fd 100644 --- a/packages/flutter_timeline_view/lib/src/screens/timeline_screen.dart +++ b/packages/flutter_timeline_view/lib/src/screens/timeline_screen.dart @@ -4,6 +4,7 @@ import 'dart:async'; +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_timeline_interface/flutter_timeline_interface.dart'; import 'package:flutter_timeline_view/flutter_timeline_view.dart'; @@ -148,6 +149,8 @@ class _TimelineScreenState extends State { ); } + var categories = service.postService.categories; + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -215,11 +218,16 @@ class _TimelineScreenState extends State { ), ], CategorySelector( + categories: categories, isOnTop: _isOnTop, filter: category, options: widget.options, onTapCategory: (categoryKey) { setState(() { + service.postService.selectedCategory = + categories.firstWhereOrNull( + (element) => element.key == categoryKey, + ); category = categoryKey; }); }, @@ -319,6 +327,7 @@ class _TimelineScreenState extends State { Future loadPosts() async { if (widget.posts != null || !context.mounted) return; try { + await service.postService.fetchCategories(); await service.postService.fetchPosts(category); setState(() { isLoading = false; 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 28fa315..b5bf4b3 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 @@ -1,12 +1,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_timeline_interface/flutter_timeline_interface.dart'; import 'package:flutter_timeline_view/src/config/timeline_options.dart'; +import 'package:flutter_timeline_view/src/widgets/default_filled_button.dart'; +import 'package:flutter_timeline_view/src/widgets/post_creation_textfield.dart'; -class TimelineSelectionScreen extends StatelessWidget { +class TimelineSelectionScreen extends StatefulWidget { const TimelineSelectionScreen({ required this.options, required this.categories, required this.onCategorySelected, + required this.postService, super.key, }); @@ -16,10 +19,19 @@ class TimelineSelectionScreen extends StatelessWidget { final Function(TimelineCategory) onCategorySelected; + final TimelinePostService postService; + + @override + State createState() => + _TimelineSelectionScreenState(); +} + +class _TimelineSelectionScreenState extends State { @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,35 +42,35 @@ class TimelineSelectionScreen extends StatelessWidget { Padding( padding: const EdgeInsets.only(top: 20, bottom: 12), child: Text( - options.translations.timelineSelectionDescription, + widget.options.translations.timelineSelectionDescription, style: theme.textTheme.titleLarge, ), ), - for (var category in categories.where( + for (var category in widget.categories.where( (element) => element.canCreate && element.key != null, )) ...[ - options.categorySelectorButtonBuilder?.call( + widget.options.categorySelectorButtonBuilder?.call( context, () { - onCategorySelected.call(category); + widget.onCategorySelected.call(category); }, category.title, ) ?? InkWell( - onTap: () => onCategorySelected.call(category), + onTap: () => widget.onCategorySelected.call(category), child: Container( height: 60, width: double.infinity, decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), border: Border.all( - color: - options.theme.categorySelectionButtonBorderColor ?? - Theme.of(context).primaryColor, + color: widget.options.theme + .categorySelectionButtonBorderColor ?? + Theme.of(context).primaryColor, width: 2, ), - color: - options.theme.categorySelectionButtonBackgroundColor, + color: widget + .options.theme.categorySelectionButtonBackgroundColor, ), margin: const EdgeInsets.symmetric(vertical: 4), child: Column( @@ -77,8 +89,121 @@ class TimelineSelectionScreen extends StatelessWidget { ), ), ], + InkWell( + onTap: showCategoryPopup, + child: Container( + height: 60, + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: + widget.options.theme.categorySelectionButtonBorderColor ?? + Theme.of(context).primaryColor, + width: 2, + ), + color: + widget.options.theme.categorySelectionButtonBackgroundColor, + ), + margin: const EdgeInsets.symmetric(vertical: 4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Row( + children: [ + Icon( + Icons.add, + color: theme.textTheme.titleMedium?.color! + .withOpacity(0.5), + ), + const SizedBox(width: 8), + Text( + widget.options.translations.addCategoryTitle, + style: theme.textTheme.titleMedium!.copyWith( + color: theme.textTheme.titleMedium?.color! + .withOpacity(0.5), + ), + ), + ], + ), + ), + ], + ), + ), + ), ], ), ); } + + Future showCategoryPopup() async { + var theme = Theme.of(context); + var controller = TextEditingController(); + await showDialog( + context: context, + builder: (context) => AlertDialog( + backgroundColor: theme.scaffoldBackgroundColor, + insetPadding: const EdgeInsets.symmetric( + horizontal: 16, + ), + contentPadding: + const EdgeInsets.symmetric(horizontal: 50, vertical: 24), + titlePadding: const EdgeInsets.only(left: 44, right: 44, top: 32), + title: Text( + widget.options.translations.createCategoryPopuptitle, + style: theme.textTheme.titleMedium, + textAlign: TextAlign.center, + ), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + PostCreationTextfield( + controller: controller, + hintText: widget.options.translations.addCategoryHintText, + ), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 14), + child: DefaultFilledButton( + onPressed: () async { + if (controller.text.isEmpty) return; + await widget.postService.addCategory( + TimelineCategory( + key: controller.text, + title: controller.text, + ), + ); + setState(() {}); + if (mounted) Navigator.pop(context); + }, + buttonText: + widget.options.translations.addCategorySubmitButton, + ), + ), + ), + ], + ), + TextButton( + onPressed: () { + Navigator.of(context).pop(false); + }, + child: Text( + widget.options.translations.addCategoryCancelButtton, + style: theme.textTheme.bodyMedium!.copyWith( + decoration: TextDecoration.underline, + color: theme.textTheme.bodyMedium?.color!.withOpacity(0.5), + ), + ), + ), + ], + ), + ), + ); + } } 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 f6e44d6..fff97e8 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 @@ -13,6 +13,12 @@ class LocalTimelinePostService @override List posts = []; + @override + List categories = []; + + @override + TimelineCategory? selectedCategory; + @override Future createPost(TimelinePost post) async { posts.add( @@ -241,4 +247,29 @@ class LocalTimelinePostService ), ), ]; + + @override + Future addCategory(TimelineCategory category) async { + categories.add(category); + notifyListeners(); + return true; + } + + @override + Future> fetchCategories() async { + categories = [ + const TimelineCategory(key: null, title: 'All'), + const TimelineCategory( + key: 'Category', + title: 'Category', + ), + const TimelineCategory( + key: 'Category with two lines', + title: 'Category with two lines', + ), + ]; + notifyListeners(); + + return categories; + } } 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 17d6eba..007f016 100644 --- a/packages/flutter_timeline_view/lib/src/widgets/category_selector.dart +++ b/packages/flutter_timeline_view/lib/src/widgets/category_selector.dart @@ -1,6 +1,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import 'package:flutter_timeline_interface/flutter_timeline_interface.dart'; import 'package:flutter_timeline_view/flutter_timeline_view.dart'; class CategorySelector extends StatefulWidget { @@ -9,6 +10,7 @@ class CategorySelector extends StatefulWidget { required this.options, required this.onTapCategory, required this.isOnTop, + required this.categories, super.key, }); @@ -16,6 +18,7 @@ class CategorySelector extends StatefulWidget { final TimelineOptions options; final void Function(String? categoryKey) onTapCategory; final bool isOnTop; + final List categories; @override State createState() => _CategorySelectorState(); @@ -23,50 +26,42 @@ class CategorySelector extends StatefulWidget { class _CategorySelectorState extends State { @override - Widget build(BuildContext context) { - if (widget.options.categoriesOptions.categoriesBuilder == null) { - return const SizedBox.shrink(); - } - - var categories = - widget.options.categoriesOptions.categoriesBuilder!(context); - return SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: Row( - children: [ - SizedBox( - width: widget.options.categoriesOptions - .categorySelectorHorizontalPadding ?? - max(widget.options.paddings.mainPadding.left - 20, 0), - ), - for (var category in categories) ...[ - widget.options.categoriesOptions.categoryButtonBuilder?.call( - category, - () => widget.onTapCategory(category.key), - widget.filter == category.key, - widget.isOnTop, - ) ?? - Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: CategorySelectorButton( - isOnTop: widget.isOnTop, - category: category, - selected: widget.filter == category.key, - onTap: () => widget.onTapCategory(category.key), - options: widget.options, + Widget build(BuildContext context) => SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Row( + children: [ + SizedBox( + width: widget.options.categoriesOptions + .categorySelectorHorizontalPadding ?? + max(widget.options.paddings.mainPadding.left - 20, 0), + ), + for (var category in widget.categories) ...[ + widget.options.categoriesOptions.categoryButtonBuilder?.call( + category, + () => widget.onTapCategory(category.key), + widget.filter == category.key, + widget.isOnTop, + ) ?? + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: CategorySelectorButton( + isOnTop: widget.isOnTop, + category: category, + selected: widget.filter == category.key, + onTap: () => widget.onTapCategory(category.key), + options: widget.options, + ), ), - ), + ], + SizedBox( + width: widget.options.categoriesOptions + .categorySelectorHorizontalPadding ?? + max(widget.options.paddings.mainPadding.right - 4, 0), + ), ], - SizedBox( - width: widget.options.categoriesOptions - .categorySelectorHorizontalPadding ?? - max(widget.options.paddings.mainPadding.right - 4, 0), - ), - ], + ), ), - ), - ); - } + ); } 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 07b9c22..f6b4f3e 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 @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_timeline_interface/flutter_timeline_interface.dart'; import 'package:flutter_timeline_view/src/config/timeline_options.dart'; +import 'package:flutter_timeline_view/src/widgets/default_filled_button.dart'; import 'package:flutter_timeline_view/src/widgets/tappable_image.dart'; class TimelinePostWidget extends StatefulWidget { @@ -314,7 +315,9 @@ class _TimelinePostWidgetState extends State { ] else ...[ Text( '${widget.post.likes} ' - '${widget.options.translations.likesTitle}', + '${widget.post.likes > 1 ? + widget.options.translations.multipleLikesTitle : + widget.options.translations.oneLikeTitle}', style: widget.options.theme.textStyles.listPostLikeTitleAndAmount ?? theme.textTheme.titleSmall!.copyWith( @@ -364,29 +367,52 @@ Future showPostDeletionConfirmationDialog( BuildContext context, Function() onPostDelete, ) async { + var theme = Theme.of(context); var result = await showDialog( context: context, builder: (BuildContext context) => options.deletionDialogBuilder?.call(context) ?? AlertDialog( - title: Text(options.translations.deleteConfirmationTitle), - content: Text(options.translations.deleteConfirmationMessage), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(false); - }, - child: Text(options.translations.deleteCancelButton), - ), - TextButton( - onPressed: () { - Navigator.of(context).pop(true); - }, - child: Text( - options.translations.deleteButton, + insetPadding: const EdgeInsets.symmetric( + horizontal: 16, + ), + contentPadding: + const EdgeInsets.symmetric(horizontal: 64, vertical: 24), + titlePadding: const EdgeInsets.only(left: 44, right: 44, top: 32), + title: Text( + options.translations.deleteConfirmationMessage, + style: theme.textTheme.titleMedium, + textAlign: TextAlign.center, + ), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Expanded( + child: DefaultFilledButton( + onPressed: () async { + Navigator.of(context).pop(true); + }, + buttonText: options.translations.deleteButton, + ), + ), + ], ), - ), - ], + TextButton( + onPressed: () { + Navigator.of(context).pop(false); + }, + child: Text( + options.translations.deleteCancelButton, + style: theme.textTheme.bodyMedium!.copyWith( + decoration: TextDecoration.underline, + color: theme.textTheme.bodyMedium?.color!.withOpacity(0.5), + ), + ), + ), + ], + ), ), ); From a8897242e79cf375327b320e230e6e0808fe3611 Mon Sep 17 00:00:00 2001 From: mike doornenbal Date: Wed, 31 Jul 2024 16:40:12 +0200 Subject: [PATCH 10/20] fix: post creation, reaction like --- .../src/service/firebase_post_service.dart | 77 +++ .../lib/src/model/timeline_reaction.dart | 8 + .../src/services/timeline_post_service.dart | 10 + .../lib/src/config/timeline_translations.dart | 15 + .../timeline_post_creation_screen.dart | 522 +++++++++--------- .../lib/src/screens/timeline_post_screen.dart | 71 ++- .../screens/timeline_selection_screen.dart | 5 +- .../lib/src/services/local_post_service.dart | 56 ++ .../src/widgets/default_filled_button.dart | 12 +- .../src/widgets/post_creation_textfield.dart | 5 +- .../lib/src/widgets/timeline_post_widget.dart | 4 +- 11 files changed, 505 insertions(+), 280 deletions(-) diff --git a/packages/flutter_timeline_firebase/lib/src/service/firebase_post_service.dart b/packages/flutter_timeline_firebase/lib/src/service/firebase_post_service.dart index 51edf98..270a209 100644 --- a/packages/flutter_timeline_firebase/lib/src/service/firebase_post_service.dart +++ b/packages/flutter_timeline_firebase/lib/src/service/firebase_post_service.dart @@ -405,4 +405,81 @@ class FirebaseTimelinePostService notifyListeners(); return categories; } + + @override + Future likeReaction( + String userId, + TimelinePost post, + String reactionId, + ) async { + // update the post with the new like + var updatedPost = post.copyWith( + reactions: post.reactions?.map( + (r) { + if (r.id == reactionId) { + return r.copyWith( + likedBy: (r.likedBy ?? [])..add(userId), + ); + } + return r; + }, + ).toList(), + ); + posts = posts + .map( + (p) => p.id == post.id ? updatedPost : p, + ) + .toList(); + var postRef = _db.collection(_options.timelineCollectionName).doc(post.id); + await postRef.update({ + 'reactions': post.reactions + ?.map( + (r) => + r.id == reactionId ? r.copyWith(likedBy: r.likedBy ?? []) : r, + ) + .map((e) => e.toJson()) + .toList(), + }); + notifyListeners(); + return updatedPost; + } + + @override + Future unlikeReaction( + String userId, + TimelinePost post, + String reactionId, + ) async { + // update the post with the new like + var updatedPost = post.copyWith( + reactions: post.reactions?.map( + (r) { + if (r.id == reactionId) { + return r.copyWith( + likedBy: r.likedBy?..remove(userId), + ); + } + return r; + }, + ).toList(), + ); + posts = posts + .map( + (p) => p.id == post.id ? updatedPost : p, + ) + .toList(); + var postRef = _db.collection(_options.timelineCollectionName).doc(post.id); + await postRef.update({ + 'reactions': post.reactions + ?.map( + (r) => r.id == reactionId + ? r.copyWith(likedBy: r.likedBy?..remove(userId)) + : r, + ) + .map((e) => e.toJson()) + .toList(), + }); + notifyListeners(); + return updatedPost; + } } diff --git a/packages/flutter_timeline_interface/lib/src/model/timeline_reaction.dart b/packages/flutter_timeline_interface/lib/src/model/timeline_reaction.dart index f880362..4fa4c04 100644 --- a/packages/flutter_timeline_interface/lib/src/model/timeline_reaction.dart +++ b/packages/flutter_timeline_interface/lib/src/model/timeline_reaction.dart @@ -16,6 +16,7 @@ class TimelinePostReaction { this.imageUrl, this.creator, this.createdAtString, + this.likedBy, }); factory TimelinePostReaction.fromJson( @@ -31,6 +32,7 @@ class TimelinePostReaction { imageUrl: json['image_url'] as String?, createdAt: DateTime.parse(json['created_at'] as String), createdAtString: json['created_at'] as String, + likedBy: (json['liked_by'] as List?)?.cast() ?? [], ); /// The unique identifier of the reaction. @@ -57,6 +59,8 @@ class TimelinePostReaction { /// Reaction creation date as String with microseconds. final String? createdAtString; + final List? likedBy; + TimelinePostReaction copyWith({ String? id, String? postId, @@ -65,6 +69,7 @@ class TimelinePostReaction { String? reaction, String? imageUrl, DateTime? createdAt, + List? likedBy, }) => TimelinePostReaction( id: id ?? this.id, @@ -74,6 +79,7 @@ class TimelinePostReaction { reaction: reaction ?? this.reaction, imageUrl: imageUrl ?? this.imageUrl, createdAt: createdAt ?? this.createdAt, + likedBy: likedBy ?? this.likedBy, ); Map toJson() => { @@ -82,6 +88,7 @@ class TimelinePostReaction { 'reaction': reaction, 'image_url': imageUrl, 'created_at': createdAt.toIso8601String(), + 'liked_by': likedBy, }, }; @@ -91,6 +98,7 @@ class TimelinePostReaction { 'reaction': reaction, 'image_url': imageUrl, 'created_at': createdAtString, + 'liked_by': likedBy, }, }; } diff --git a/packages/flutter_timeline_interface/lib/src/services/timeline_post_service.dart b/packages/flutter_timeline_interface/lib/src/services/timeline_post_service.dart index 957702a..17f5d38 100644 --- a/packages/flutter_timeline_interface/lib/src/services/timeline_post_service.dart +++ b/packages/flutter_timeline_interface/lib/src/services/timeline_post_service.dart @@ -32,4 +32,14 @@ abstract class TimelinePostService with ChangeNotifier { Future> fetchCategories(); Future addCategory(TimelineCategory category); + Future likeReaction( + String userId, + TimelinePost post, + String reactionId, + ); + Future unlikeReaction( + String userId, + TimelinePost post, + String reactionId, + ); } 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 0bb78f5..41e7a91 100644 --- a/packages/flutter_timeline_view/lib/src/config/timeline_translations.dart +++ b/packages/flutter_timeline_view/lib/src/config/timeline_translations.dart @@ -54,6 +54,9 @@ class TimelineTranslations { required this.addCategorySubmitButton, required this.addCategoryCancelButtton, required this.addCategoryHintText, + required this.addCategoryErrorText, + required this.titleErrorText, + required this.contentErrorText, }); /// Default translations for the timeline component view @@ -100,6 +103,9 @@ class TimelineTranslations { this.addCategorySubmitButton = 'Add category', this.addCategoryCancelButtton = 'Cancel', this.addCategoryHintText = 'Category name...', + this.addCategoryErrorText = 'Please enter a category name', + this.titleErrorText = 'Please enter a title', + this.contentErrorText = 'Please enter content', }); final String noPosts; @@ -117,6 +123,8 @@ class TimelineTranslations { final String titleHintText; final String contentHintText; + final String titleErrorText; + final String contentErrorText; final String deletePost; final String deleteConfirmationTitle; @@ -147,6 +155,7 @@ class TimelineTranslations { final String addCategorySubmitButton; final String addCategoryCancelButtton; final String addCategoryHintText; + final String addCategoryErrorText; final String yes; final String no; @@ -194,6 +203,9 @@ class TimelineTranslations { String? addCategorySubmitButton, String? addCategoryCancelButtton, String? addCategoryHintText, + String? addCategoryErrorText, + String? titleErrorText, + String? contentErrorText, }) => TimelineTranslations( noPosts: noPosts ?? this.noPosts, @@ -244,5 +256,8 @@ class TimelineTranslations { addCategoryHintText: addCategoryHintText ?? this.addCategoryHintText, createCategoryPopuptitle: createCategoryPopuptitle ?? this.createCategoryPopuptitle, + addCategoryErrorText: addCategoryErrorText ?? this.addCategoryErrorText, + titleErrorText: titleErrorText ?? this.titleErrorText, + contentErrorText: contentErrorText ?? this.contentErrorText, ); } 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 36164b4..f2b6992 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 @@ -53,48 +53,23 @@ class _TimelinePostCreationScreenState TextEditingController titleController = TextEditingController(); TextEditingController contentController = TextEditingController(); Uint8List? image; - bool editingDone = false; bool allowComments = false; + bool titleIsValid = false; + bool contentIsValid = false; @override void initState() { + titleController.addListener(_listenForInputs); + contentController.addListener(_listenForInputs); super.initState(); - titleController.addListener(checkIfEditingDone); - contentController.addListener(checkIfEditingDone); } - @override - void dispose() { - titleController.dispose(); - contentController.dispose(); - super.dispose(); + void _listenForInputs() { + titleIsValid = titleController.text.isNotEmpty; + contentIsValid = contentController.text.isNotEmpty; } - void checkIfEditingDone() { - setState(() { - editingDone = - titleController.text.isNotEmpty && contentController.text.isNotEmpty; - if (widget.options.requireImageForPost) { - editingDone = editingDone && image != null; - } - if (widget.options.minTitleLength != null) { - editingDone = editingDone && - titleController.text.length >= widget.options.minTitleLength!; - } - if (widget.options.maxTitleLength != null) { - editingDone = editingDone && - titleController.text.length <= widget.options.maxTitleLength!; - } - if (widget.options.minContentLength != null) { - editingDone = editingDone && - contentController.text.length >= widget.options.minContentLength!; - } - if (widget.options.maxContentLength != null) { - editingDone = editingDone && - contentController.text.length <= widget.options.maxContentLength!; - } - }); - } + var formkey = GlobalKey(); @override Widget build(BuildContext context) { @@ -127,251 +102,274 @@ class _TimelinePostCreationScreenState child: SingleChildScrollView( child: Padding( padding: widget.options.paddings.mainPadding, - child: Column( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - widget.options.translations.title, - style: theme.textTheme.titleMedium, - ), - const SizedBox( - height: 4, - ), - widget.options.textInputBuilder?.call( - titleController, - null, - '', - ) ?? - PostCreationTextfield( - controller: titleController, - hintText: widget.options.translations.titleHintText, - textMaxLength: widget.options.maxTitleLength, - decoration: widget.options.titleInputDecoration, - textCapitalization: TextCapitalization.sentences, - expands: null, - minLines: null, - maxLines: 1, - ), - const SizedBox(height: 16), - Text( - widget.options.translations.content, - style: theme.textTheme.titleMedium, - ), - Text( - widget.options.translations.contentDescription, - style: theme.textTheme.bodySmall, - ), - const SizedBox( - height: 4, - ), - PostCreationTextfield( - controller: contentController, - hintText: widget.options.translations.contentHintText, - textMaxLength: null, - decoration: widget.options.contentInputDecoration, - textCapitalization: TextCapitalization.sentences, - expands: false, - minLines: null, - maxLines: null, - ), - const SizedBox( - height: 16, - ), - Text( - widget.options.translations.uploadImage, - style: theme.textTheme.titleMedium, - ), - Text( - widget.options.translations.uploadImageDescription, - style: theme.textTheme.bodySmall, - ), - const SizedBox( - height: 8, - ), - Stack( - children: [ - GestureDetector( - onTap: () async { - var result = await showModalBottomSheet( - context: context, - builder: (context) => Container( - padding: const EdgeInsets.all(8.0), - color: theme.colorScheme.surface, - child: ImagePicker( - imagePickerConfig: widget.options.imagePickerConfig, - imagePickerTheme: widget.options.imagePickerTheme ?? - ImagePickerTheme( - titleAlignment: TextAlign.center, - title: ' Do you want to upload a file' - ' or take a picture? ', - titleTextSize: - theme.textTheme.titleMedium!.fontSize!, - font: - theme.textTheme.titleMedium!.fontFamily!, - iconSize: 40, - selectImageText: 'UPLOAD FILE', - makePhotoText: 'TAKE PICTURE', - selectImageIcon: const Icon( - size: 40, - Icons.insert_drive_file, + child: Form( + key: formkey, + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.options.translations.title, + style: theme.textTheme.titleMedium, + ), + const SizedBox( + height: 4, + ), + widget.options.textInputBuilder?.call( + titleController, + null, + '', + ) ?? + PostCreationTextfield( + controller: titleController, + hintText: widget.options.translations.titleHintText, + textMaxLength: widget.options.maxTitleLength, + decoration: widget.options.titleInputDecoration, + textCapitalization: TextCapitalization.sentences, + expands: null, + minLines: null, + maxLines: 1, + validator: (value) { + if (value == null || value.isEmpty) { + return widget.options.translations.titleErrorText; + } + if (value.trim().isEmpty) { + return widget.options.translations.titleErrorText; + } + return null; + }, + ), + const SizedBox(height: 16), + Text( + widget.options.translations.content, + style: theme.textTheme.titleMedium, + ), + Text( + widget.options.translations.contentDescription, + style: theme.textTheme.bodySmall, + ), + const SizedBox( + height: 4, + ), + PostCreationTextfield( + controller: contentController, + hintText: widget.options.translations.contentHintText, + textMaxLength: null, + decoration: widget.options.contentInputDecoration, + textCapitalization: TextCapitalization.sentences, + expands: false, + minLines: null, + maxLines: null, + validator: (value) { + if (value == null || value.isEmpty) { + return widget.options.translations.contentErrorText; + } + if (value.trim().isEmpty) { + return widget.options.translations.contentErrorText; + } + return null; + }, + ), + const SizedBox( + height: 16, + ), + Text( + widget.options.translations.uploadImage, + style: theme.textTheme.titleMedium, + ), + Text( + widget.options.translations.uploadImageDescription, + style: theme.textTheme.bodySmall, + ), + const SizedBox( + height: 8, + ), + Stack( + children: [ + GestureDetector( + onTap: () async { + var result = await showModalBottomSheet( + context: context, + builder: (context) => Container( + padding: const EdgeInsets.all(8.0), + color: theme.colorScheme.surface, + child: ImagePicker( + imagePickerConfig: + widget.options.imagePickerConfig, + imagePickerTheme: widget + .options.imagePickerTheme ?? + ImagePickerTheme( + titleAlignment: TextAlign.center, + title: ' Do you want to upload a file' + ' or take a picture? ', + titleTextSize: + theme.textTheme.titleMedium!.fontSize!, + font: theme + .textTheme.titleMedium!.fontFamily!, + iconSize: 40, + selectImageText: 'UPLOAD FILE', + makePhotoText: 'TAKE PICTURE', + selectImageIcon: const Icon( + size: 40, + Icons.insert_drive_file, + ), + ), + customButton: TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: Text( + 'Cancel', + style: theme.textTheme.bodyMedium!.copyWith( + decoration: TextDecoration.underline, ), ), - customButton: TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: Text( - 'Cancel', - style: theme.textTheme.bodyMedium!.copyWith( - decoration: TextDecoration.underline, - ), ), ), ), - ), - ); - if (result != null) { - setState(() { - image = result; - }); - } - checkIfEditingDone(); - }, - child: ClipRRect( - borderRadius: BorderRadius.circular(8.0), - child: image != null - ? Image.memory( - image!, - width: double.infinity, - height: 150.0, - fit: BoxFit.cover, - // give it a rounded border - ) - : DottedBorder( - dashPattern: const [4, 4], - radius: const Radius.circular(8.0), - color: theme.textTheme.displayMedium?.color ?? - Colors.white, - child: const SizedBox( + ); + if (result != null) { + setState(() { + image = result; + }); + } + }, + child: ClipRRect( + borderRadius: BorderRadius.circular(8.0), + child: image != null + ? Image.memory( + image!, width: double.infinity, height: 150.0, - child: Icon( - Icons.image, - size: 50, + fit: BoxFit.cover, + // give it a rounded border + ) + : DottedBorder( + dashPattern: const [4, 4], + radius: const Radius.circular(8.0), + color: theme.textTheme.displayMedium?.color ?? + Colors.white, + child: const SizedBox( + width: double.infinity, + height: 150.0, + child: Icon( + Icons.image, + size: 50, + ), ), ), - ), + ), ), - ), - // if an image is selected, show a delete button - if (image != null) ...[ - Positioned( - top: 8, - right: 8, - child: GestureDetector( - onTap: () { - setState(() { - image = null; - }); - checkIfEditingDone(); - }, - child: Container( - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.5), - borderRadius: BorderRadius.circular(8.0), - ), - child: const Icon( - Icons.delete, - color: Colors.white, + // if an image is selected, show a delete button + if (image != null) ...[ + Positioned( + top: 8, + right: 8, + child: GestureDetector( + onTap: () { + setState(() { + image = null; + }); + }, + child: Container( + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.5), + borderRadius: BorderRadius.circular(8.0), + ), + child: const Icon( + Icons.delete, + color: Colors.white, + ), ), ), ), + ], + ], + ), + const SizedBox(height: 16), + Text( + widget.options.translations.commentsTitle, + style: theme.textTheme.titleMedium, + ), + Text( + widget.options.translations.allowCommentsDescription, + style: theme.textTheme.bodySmall, + ), + const SizedBox( + height: 8, + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Checkbox( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + visualDensity: + const VisualDensity(horizontal: -4, vertical: -4), + activeColor: theme.colorScheme.primary, + value: allowComments, + onChanged: (value) { + setState(() { + allowComments = true; + }); + }, + ), + Text( + widget.options.translations.yes, + style: theme.textTheme.bodyMedium, + ), + const SizedBox( + width: 32, + ), + Checkbox( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + visualDensity: + const VisualDensity(horizontal: -4, vertical: -4), + activeColor: theme.colorScheme.primary, + value: !allowComments, + onChanged: (value) { + setState(() { + allowComments = false; + }); + }, + ), + Text( + widget.options.translations.no, + style: theme.textTheme.bodyMedium, ), ], - ], - ), - const SizedBox(height: 16), - Text( - widget.options.translations.commentsTitle, - style: theme.textTheme.titleMedium, - ), - Text( - widget.options.translations.allowCommentsDescription, - style: theme.textTheme.bodySmall, - ), - const SizedBox( - height: 8, - ), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Checkbox( - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - visualDensity: - const VisualDensity(horizontal: -4, vertical: -4), - activeColor: theme.colorScheme.primary, - value: allowComments, - onChanged: (value) { - setState(() { - allowComments = true; - }); - }, - ), - Text( - widget.options.translations.yes, - style: theme.textTheme.bodyMedium, - ), - const SizedBox( - width: 32, - ), - Checkbox( - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - visualDensity: - const VisualDensity(horizontal: -4, vertical: -4), - activeColor: theme.colorScheme.primary, - value: !allowComments, - onChanged: (value) { - setState(() { - allowComments = false; - }); - }, - ), - Text( - widget.options.translations.no, - style: theme.textTheme.bodyMedium, - ), - ], - ), - const SizedBox(height: 120), - SafeArea( - bottom: true, - child: Align( - alignment: Alignment.bottomCenter, - child: widget.options.buttonBuilder?.call( - context, - onPostCreated, - widget.options.translations.checkPost, - enabled: editingDone, - ) ?? - Padding( - padding: const EdgeInsets.symmetric(horizontal: 48), - child: DefaultFilledButton( - onPressed: editingDone - ? () async { - await onPostCreated(); - await widget.service.postService - .fetchPosts(null); - } - : null, - buttonText: widget.enablePostOverviewScreen - ? widget.options.translations.checkPost - : widget.options.translations.postCreation, - ), - ), ), - ), - ], + const SizedBox(height: 120), + SafeArea( + bottom: true, + child: Align( + alignment: Alignment.bottomCenter, + child: widget.options.buttonBuilder?.call( + context, + onPostCreated, + widget.options.translations.checkPost, + enabled: formkey.currentState!.validate(), + ) ?? + Padding( + padding: const EdgeInsets.symmetric(horizontal: 48), + child: DefaultFilledButton( + onPressed: titleIsValid && contentIsValid + ? () async { + if (formkey.currentState!.validate()) { + await onPostCreated(); + await widget.service.postService + .fetchPosts(null); + } + } + : null, + buttonText: widget.enablePostOverviewScreen + ? widget.options.translations.checkPost + : widget.options.translations.postCreation, + ), + ), + ), + ), + ], + ), ), ), ), 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 18f3e10..225449c 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 @@ -344,13 +344,19 @@ class _TimelinePostScreenState extends State { setState(() {}); } }, - icon: Icon( - isLikedByUser - ? Icons.favorite_rounded - : Icons.favorite_outline_outlined, - color: widget.options.theme.iconColor, - size: widget.options.iconSize, - ), + icon: isLikedByUser + ? widget.options.theme.likedIcon ?? + Icon( + Icons.favorite_rounded, + color: widget.options.theme.iconColor, + size: widget.options.iconSize, + ) + : widget.options.theme.likeIcon ?? + Icon( + Icons.favorite_outline_outlined, + color: widget.options.theme.iconColor, + size: widget.options.iconSize, + ), ), const SizedBox(width: 8), if (post.reactionEnabled) @@ -410,7 +416,7 @@ class _TimelinePostScreenState extends State { ), const SizedBox(height: 16), // ignore: avoid_bool_literals_in_conditional_expressions - if (post.reactionEnabled || widget.isOverviewScreen != null + if (post.reactionEnabled && widget.isOverviewScreen != null ? !widget.isOverviewScreen! : false) ...[ Text( @@ -544,6 +550,55 @@ class _TimelinePostScreenState extends State { ), ), ], + Builder( + builder: (context) { + var isLikedByUser = + reaction.likedBy?.contains(widget.userId) ?? + false; + return IconButton( + padding: const EdgeInsets.only(left: 12), + constraints: const BoxConstraints(), + onPressed: () async { + if (isLikedByUser) { + updatePost( + await widget.service.postService + .unlikeReaction( + widget.userId, + post, + reaction.id, + ), + ); + setState(() {}); + } else { + updatePost( + await widget.service.postService + .likeReaction( + widget.userId, + post, + reaction.id, + ), + ); + setState(() {}); + } + }, + icon: isLikedByUser + ? widget.options.theme.likedIcon ?? + Icon( + Icons.favorite_rounded, + color: + widget.options.theme.iconColor, + size: 14, + ) + : widget.options.theme.likeIcon ?? + Icon( + Icons.favorite_outline_outlined, + color: + widget.options.theme.iconColor, + size: 14, + ), + ); + }, + ), ], ), ), 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 b5bf4b3..2ed8b36 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 @@ -163,6 +163,9 @@ class _TimelineSelectionScreenState extends State { PostCreationTextfield( controller: controller, hintText: widget.options.translations.addCategoryHintText, + validator: (p0) => p0!.isEmpty + ? widget.options.translations.addCategoryErrorText + : null, ), const SizedBox(height: 16), Row( @@ -180,7 +183,7 @@ class _TimelineSelectionScreenState extends State { ), ); setState(() {}); - if (mounted) Navigator.pop(context); + if (context.mounted) Navigator.pop(context); }, buttonText: widget.options.translations.addCategorySubmitButton, 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 fff97e8..f1ee4cf 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 @@ -272,4 +272,60 @@ class LocalTimelinePostService return categories; } + + @override + Future likeReaction( + String userId, + TimelinePost post, + String reactionId, + ) async { + var updatedPost = post.copyWith( + reactions: post.reactions?.map( + (r) { + if (r.id == reactionId) { + return r.copyWith( + likedBy: (r.likedBy ?? [])..add(userId), + ); + } + return r; + }, + ).toList(), + ); + posts = posts + .map( + (p) => p.id == post.id ? updatedPost : p, + ) + .toList(); + + notifyListeners(); + return updatedPost; + } + + @override + Future unlikeReaction( + String userId, + TimelinePost post, + String reactionId, + ) async { + var updatedPost = post.copyWith( + reactions: post.reactions?.map( + (r) { + if (r.id == reactionId) { + return r.copyWith( + likedBy: r.likedBy?..remove(userId), + ); + } + return r; + }, + ).toList(), + ); + posts = posts + .map( + (p) => p.id == post.id ? updatedPost : p, + ) + .toList(); + + notifyListeners(); + return updatedPost; + } } diff --git a/packages/flutter_timeline_view/lib/src/widgets/default_filled_button.dart b/packages/flutter_timeline_view/lib/src/widgets/default_filled_button.dart index 7581003..00ac78b 100644 --- a/packages/flutter_timeline_view/lib/src/widgets/default_filled_button.dart +++ b/packages/flutter_timeline_view/lib/src/widgets/default_filled_button.dart @@ -14,11 +14,13 @@ class DefaultFilledButton extends StatelessWidget { Widget build(BuildContext context) { var theme = Theme.of(context); return FilledButton( - style: ButtonStyle( - backgroundColor: WidgetStatePropertyAll( - theme.colorScheme.primary, - ), - ), + style: onPressed != null + ? ButtonStyle( + backgroundColor: WidgetStatePropertyAll( + theme.colorScheme.primary, + ), + ) + : null, onPressed: onPressed, child: Padding( padding: const EdgeInsets.all(8), diff --git a/packages/flutter_timeline_view/lib/src/widgets/post_creation_textfield.dart b/packages/flutter_timeline_view/lib/src/widgets/post_creation_textfield.dart index 96a3263..381359c 100644 --- a/packages/flutter_timeline_view/lib/src/widgets/post_creation_textfield.dart +++ b/packages/flutter_timeline_view/lib/src/widgets/post_creation_textfield.dart @@ -4,6 +4,7 @@ class PostCreationTextfield extends StatelessWidget { const PostCreationTextfield({ required this.controller, required this.hintText, + required this.validator, super.key, this.textMaxLength, this.decoration, @@ -22,10 +23,12 @@ class PostCreationTextfield extends StatelessWidget { final bool? expands; final int? minLines; final int? maxLines; + final String? Function(String?)? validator; @override Widget build(BuildContext context) { var theme = Theme.of(context); - return TextField( + return TextFormField( + validator: validator, style: theme.textTheme.bodySmall, controller: controller, maxLength: textMaxLength, 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 f6b4f3e..686da59 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 @@ -315,9 +315,7 @@ class _TimelinePostWidgetState extends State { ] else ...[ Text( '${widget.post.likes} ' - '${widget.post.likes > 1 ? - widget.options.translations.multipleLikesTitle : - widget.options.translations.oneLikeTitle}', + '${widget.post.likes > 1 ? widget.options.translations.multipleLikesTitle : widget.options.translations.oneLikeTitle}', style: widget.options.theme.textStyles.listPostLikeTitleAndAmount ?? theme.textTheme.titleSmall!.copyWith( From 3bd7b0951fa2cbd3a36143f4772cc970612edb74 Mon Sep 17 00:00:00 2001 From: mike doornenbal Date: Thu, 1 Aug 2024 09:32:09 +0200 Subject: [PATCH 11/20] fix: remove gorouter --- README.md | 40 +-- .../example/lib/apps/go_router/app.dart | 41 --- .../flutter_timeline/example/lib/main.dart | 10 +- .../flutter_timeline/example/pubspec.yaml | 1 - .../lib/flutter_timeline.dart | 1 - .../flutter_timeline_gorouter_userstory.dart | 293 ------------------ .../flutter_timeline/lib/src/go_router.dart | 30 -- packages/flutter_timeline/pubspec.yaml | 5 +- 8 files changed, 4 insertions(+), 417 deletions(-) delete mode 100644 packages/flutter_timeline/example/lib/apps/go_router/app.dart delete mode 100644 packages/flutter_timeline/lib/src/flutter_timeline_gorouter_userstory.dart delete mode 100644 packages/flutter_timeline/lib/src/go_router.dart diff --git a/README.md b/README.md index 1279960..1ecc8bc 100644 --- a/README.md +++ b/README.md @@ -35,45 +35,7 @@ And import this package: import 'package:intl/date_symbol_data_local.dart'; ``` ## How to use -To use the module within your Flutter-application with predefined `Go_router` routes you should add the following: - -Add go_router as dependency to your project. -Add the following configuration to your flutter_application: - -``` -List getTimelineStoryRoutes() => - getTimelineStoryRoutes( - TimelineUserStoryConfiguration( - service: TimelineService( - postService: LocalTimelinePostService(), - ), - optionsBuilder: (context) { - return const TimelineOptions(); - }, - ), - ); -``` - -Add the `getTimelineStoryRoutes()` to your go_router routes like so: - -``` -final GoRouter _router = GoRouter( - routes: [ - GoRoute( - path: '/', - builder: (BuildContext context, GoRouterState state) { - return const MyHomePage( - title: "home", - ); - }, - ), - ...getTimelineStoryRoutes(configuration: configuration); - ], -); -``` - -The user story can also be used without go router: -Add the following code somewhere in your widget tree: +To use the userstory add the following code somewhere in your widget tree: ```` timeLineNavigatorUserStory(TimelineUserStoryConfiguration, context), diff --git a/packages/flutter_timeline/example/lib/apps/go_router/app.dart b/packages/flutter_timeline/example/lib/apps/go_router/app.dart deleted file mode 100644 index 082edae..0000000 --- a/packages/flutter_timeline/example/lib/apps/go_router/app.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:example/config/config.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_timeline/flutter_timeline.dart'; -import 'package:go_router/go_router.dart'; - -List getTimelineRoutes() => getTimelineStoryRoutes( - configuration: getConfig(TimelineService( - postService: LocalTimelinePostService(), - )), - ); - -final _router = GoRouter( - initialLocation: '/timeline', - routes: [ - ...getTimelineRoutes(), - ], -); - -class GoRouterApp extends StatelessWidget { - const GoRouterApp({super.key}); - - @override - Widget build(BuildContext context) { - return MaterialApp.router( - routerConfig: _router, - title: 'Flutter Timeline', - theme: ThemeData( - textTheme: const TextTheme( - titleLarge: TextStyle( - color: Color(0xffb71c6d), fontFamily: 'Playfair Display')), - colorScheme: ColorScheme.fromSeed( - seedColor: const Color(0xFFB8E2E8), - primary: const Color(0xffb71c6d), - ).copyWith( - surface: const Color(0XFFFAF9F6), - ), - useMaterial3: true, - ), - ); - } -} diff --git a/packages/flutter_timeline/example/lib/main.dart b/packages/flutter_timeline/example/lib/main.dart index 0d097aa..8599ddf 100644 --- a/packages/flutter_timeline/example/lib/main.dart +++ b/packages/flutter_timeline/example/lib/main.dart @@ -1,15 +1,9 @@ -// import 'package:example/apps/go_router/app.dart'; -// import 'package:example/apps/navigator/app.dart'; -import 'package:example/apps/go_router/app.dart'; +import 'package:example/apps/navigator/app.dart'; import 'package:flutter/material.dart'; import 'package:intl/date_symbol_data_local.dart'; void main() { initializeDateFormatting(); - // Uncomment any, but only one, of these lines to run the example with specific navigation. - - // runApp(const WidgetApp()); - // runApp(const NavigatorApp()); - runApp(const GoRouterApp()); + runApp(const NavigatorApp()); } diff --git a/packages/flutter_timeline/example/pubspec.yaml b/packages/flutter_timeline/example/pubspec.yaml index 9008d45..4f0c7fc 100644 --- a/packages/flutter_timeline/example/pubspec.yaml +++ b/packages/flutter_timeline/example/pubspec.yaml @@ -38,7 +38,6 @@ dependencies: flutter_timeline: path: ../ intl: ^0.19.0 - go_router: ^13.0.1 dev_dependencies: flutter_test: diff --git a/packages/flutter_timeline/lib/flutter_timeline.dart b/packages/flutter_timeline/lib/flutter_timeline.dart index f03d1f1..071b8aa 100644 --- a/packages/flutter_timeline/lib/flutter_timeline.dart +++ b/packages/flutter_timeline/lib/flutter_timeline.dart @@ -5,7 +5,6 @@ /// 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/models/timeline_configuration.dart'; export 'package:flutter_timeline/src/routes.dart'; diff --git a/packages/flutter_timeline/lib/src/flutter_timeline_gorouter_userstory.dart b/packages/flutter_timeline/lib/src/flutter_timeline_gorouter_userstory.dart deleted file mode 100644 index 0ad5869..0000000 --- a/packages/flutter_timeline/lib/src/flutter_timeline_gorouter_userstory.dart +++ /dev/null @@ -1,293 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Iconica -// -// SPDX-License-Identifier: BSD-3-Clause - -import 'package:collection/collection.dart'; -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 getTimelineStoryRoutes({ - TimelineUserStoryConfiguration? configuration, -}) { - var config = configuration ?? - TimelineUserStoryConfiguration( - userId: 'test_user', - service: TimelineService( - postService: LocalTimelinePostService(), - ), - optionsBuilder: (context) => const TimelineOptions(), - ); - - return [ - GoRoute( - path: TimelineUserStoryRoutes.timelineHome, - pageBuilder: (context, state) { - var service = config.serviceBuilder?.call(context) ?? config.service; - var timelineScreen = TimelineScreen( - userId: config.getUserId?.call(context) ?? config.userId, - onUserTap: (user) => config.onUserTap?.call(context, user), - allowAllDeletion: config.canDeleteAllPosts?.call(context) ?? false, - onRefresh: config.onRefresh, - service: 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, - ); - var theme = Theme.of(context); - var button = FloatingActionButton( - backgroundColor: config - .optionsBuilder(context) - .theme - .postCreationFloatingActionButtonColor ?? - theme.colorScheme.primary, - onPressed: () async => context.push( - TimelineUserStoryRoutes.timelineCategorySelection, - ), - shape: const CircleBorder(), - child: const Icon( - Icons.add, - color: Colors.white, - size: 24, - ), - ); - - return buildScreenWithoutTransition( - context: context, - state: state, - child: config.homeOpenPageBuilder - ?.call(context, timelineScreen, button) ?? - Scaffold( - appBar: AppBar( - title: Text( - config - .optionsBuilder(context) - .translations - .timeLineScreenTitle, - style: theme.textTheme.headlineLarge, - ), - ), - body: timelineScreen, - floatingActionButton: button, - ), - ); - }, - ), - GoRoute( - path: TimelineUserStoryRoutes.timelineCategorySelection, - pageBuilder: (context, state) { - var timelineSelectionScreen = TimelineSelectionScreen( - postService: config.service.postService, - options: config.optionsBuilder(context), - categories: config.service.postService.categories, - onCategorySelected: (category) async { - await context.push( - TimelineUserStoryRoutes.timelinepostCreation(category.key ?? ''), - ); - }, - ); - - 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, - title: Text( - config.optionsBuilder(context).translations.postCreation, - style: TextStyle( - color: Theme.of(context).primaryColor, - fontSize: 24, - fontWeight: FontWeight.w800, - ), - ), - ), - body: timelineSelectionScreen, - ), - ); - }, - ), - GoRoute( - path: TimelineUserStoryRoutes.timelineView, - pageBuilder: (context, state) { - var service = config.serviceBuilder?.call(context) ?? config.service; - var post = service.postService.getPost(state.pathParameters['post']!); - var category = service.postService.categories.firstWhereOrNull( - (element) => element.key == post?.category, - ); - - var timelinePostWidget = TimelinePostScreen( - userId: config.getUserId?.call(context) ?? config.userId, - allowAllDeletion: config.canDeleteAllPosts?.call(context) ?? false, - options: config.optionsBuilder(context), - service: service, - post: post!, - onPostDelete: () async => - config.onPostDelete?.call(context, post) ?? - () async { - await service.postService.deletePost(post); - if (!context.mounted) return; - context.go(TimelineUserStoryRoutes.timelineHome); - }.call(), - onUserTap: (user) => config.onUserTap?.call(context, user), - ); - - 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.postViewOpenPageBuilder?.call( - context, - timelinePostWidget, - backButton, - post, - category, - ) ?? - Scaffold( - appBar: AppBar( - leading: backButton, - title: Text( - category?.title ?? post.category ?? 'Category', - style: TextStyle( - color: Theme.of(context).primaryColor, - fontSize: 24, - fontWeight: FontWeight.w800, - ), - ), - ), - body: timelinePostWidget, - ), - ); - }, - ), - GoRoute( - path: TimelineUserStoryRoutes.timelinePostCreation, - pageBuilder: (context, state) { - var category = state.pathParameters['category']; - var service = config.serviceBuilder?.call(context) ?? config.service; - var timelinePostCreationWidget = TimelinePostCreationScreen( - userId: config.getUserId?.call(context) ?? config.userId, - options: config.optionsBuilder(context), - service: service, - onPostCreated: (post) async { - var newPost = await service.postService.createPost(post); - if (!context.mounted) return; - if (config.afterPostCreationGoHome) { - context.go(TimelineUserStoryRoutes.timelineHome); - } else { - await context - .push(TimelineUserStoryRoutes.timelineViewPath(newPost.id)); - } - }, - onPostOverview: (post) async => context.push( - TimelineUserStoryRoutes.timelinePostOverview, - extra: post, - ), - enablePostOverviewScreen: config.enablePostOverviewScreen, - postCategory: category, - ); - - var backButton = IconButton( - icon: const Icon( - Icons.arrow_back_ios, - color: Colors.white, - ), - onPressed: () => - context.go(TimelineUserStoryRoutes.timelineCategorySelection), - ); - - return buildScreenWithoutTransition( - context: context, - state: state, - child: config.postCreationOpenPageBuilder - ?.call(context, timelinePostCreationWidget, backButton) ?? - Scaffold( - appBar: AppBar( - leading: backButton, - title: Text( - config.optionsBuilder(context).translations.postCreation, - style: TextStyle( - color: Theme.of(context).primaryColor, - fontSize: 24, - fontWeight: FontWeight.w800, - ), - ), - ), - body: timelinePostCreationWidget, - ), - ); - }, - ), - GoRoute( - path: TimelineUserStoryRoutes.timelinePostOverview, - pageBuilder: (context, state) { - var post = state.extra! as TimelinePost; - var service = config.serviceBuilder?.call(context) ?? config.service; - var timelinePostOverviewWidget = TimelinePostOverviewScreen( - options: config.optionsBuilder(context), - service: service, - timelinePost: post, - onPostSubmit: (post) async { - await service.postService.createPost(post); - if (!context.mounted) return; - context.go(TimelineUserStoryRoutes.timelineHome); - }, - ); - var backButton = IconButton( - icon: const Icon( - Icons.arrow_back_ios, - color: Colors.white, - ), - onPressed: () async => context.pop(), - ); - - return buildScreenWithoutTransition( - context: context, - state: state, - child: config.postOverviewOpenPageBuilder?.call( - context, - timelinePostOverviewWidget, - ) ?? - Scaffold( - appBar: AppBar( - leading: backButton, - title: Text( - config.optionsBuilder(context).translations.postOverview, - style: TextStyle( - color: Theme.of(context).primaryColor, - fontSize: 24, - fontWeight: FontWeight.w800, - ), - ), - ), - body: timelinePostOverviewWidget, - ), - ); - }, - ), - ]; -} diff --git a/packages/flutter_timeline/lib/src/go_router.dart b/packages/flutter_timeline/lib/src/go_router.dart deleted file mode 100644 index c9113db..0000000 --- a/packages/flutter_timeline/lib/src/go_router.dart +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Iconica -// -// SPDX-License-Identifier: BSD-3-Clause - -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; - -CustomTransitionPage buildScreenWithFadeTransition({ - required BuildContext context, - required GoRouterState state, - required Widget child, -}) => - CustomTransitionPage( - key: state.pageKey, - child: child, - transitionsBuilder: (context, animation, secondaryAnimation, child) => - FadeTransition(opacity: animation, child: child), - ); - -CustomTransitionPage buildScreenWithoutTransition({ - required BuildContext context, - required GoRouterState state, - required Widget child, -}) => - CustomTransitionPage( - key: state.pageKey, - child: child, - transitionsBuilder: (context, animation, secondaryAnimation, child) => - child, - ); diff --git a/packages/flutter_timeline/pubspec.yaml b/packages/flutter_timeline/pubspec.yaml index ffefefb..d7fa66d 100644 --- a/packages/flutter_timeline/pubspec.yaml +++ b/packages/flutter_timeline/pubspec.yaml @@ -13,10 +13,6 @@ environment: dependencies: flutter: sdk: flutter - go_router: any - - collection: any - flutter_timeline_view: git: url: https://github.com/Iconica-Development/flutter_timeline @@ -28,6 +24,7 @@ dependencies: url: https://github.com/Iconica-Development/flutter_timeline path: packages/flutter_timeline_interface ref: 4.1.0 + collection: any dependency_overrides: flutter_timeline_view: path: ../flutter_timeline_view From 1dc79b8d74c1a2bb5d02174e1328e1909d608c88 Mon Sep 17 00:00:00 2001 From: mike doornenbal Date: Thu, 1 Aug 2024 09:38:43 +0200 Subject: [PATCH 12/20] fix: button text --- .../lib/src/config/timeline_translations.dart | 4 +-- .../timeline_post_creation_screen.dart | 34 ++++++++++++------- .../timeline_post_overview_screen.dart | 32 ++++++++--------- 3 files changed, 38 insertions(+), 32 deletions(-) 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 41e7a91..9b61753 100644 --- a/packages/flutter_timeline_view/lib/src/config/timeline_translations.dart +++ b/packages/flutter_timeline_view/lib/src/config/timeline_translations.dart @@ -75,7 +75,7 @@ class TimelineTranslations { this.allowCommentsDescription = 'Indicate whether people are allowed to respond', this.commentsTitleOnPost = 'Comments', - this.checkPost = 'Check post overview', + this.checkPost = 'Overview', this.deletePost = 'Delete post', this.deleteConfirmationTitle = 'Delete Post', this.deleteConfirmationMessage = @@ -93,7 +93,7 @@ class TimelineTranslations { this.timelineSelectionDescription = 'Choose a category', this.searchHint = 'Search...', this.postOverview = 'Post Overview', - this.postIn = 'Post in', + this.postIn = 'Post', this.postCreation = 'add post', this.yes = 'Yes', this.no = 'No', 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 f2b6992..d392159 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 @@ -351,19 +351,27 @@ class _TimelinePostCreationScreenState ) ?? Padding( padding: const EdgeInsets.symmetric(horizontal: 48), - child: DefaultFilledButton( - onPressed: titleIsValid && contentIsValid - ? () async { - if (formkey.currentState!.validate()) { - await onPostCreated(); - await widget.service.postService - .fetchPosts(null); - } - } - : null, - buttonText: widget.enablePostOverviewScreen - ? widget.options.translations.checkPost - : widget.options.translations.postCreation, + child: Row( + children: [ + Expanded( + child: DefaultFilledButton( + onPressed: titleIsValid && contentIsValid + ? () async { + if (formkey.currentState! + .validate()) { + await onPostCreated(); + await widget.service.postService + .fetchPosts(null); + } + } + : null, + buttonText: widget.enablePostOverviewScreen + ? widget.options.translations.checkPost + : widget + .options.translations.postCreation, + ), + ), + ], ), ), ), 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 c8d833f..0c3f1b5 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 @@ -1,6 +1,3 @@ -// ignore_for_file: prefer_expression_function_bodies - -import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_timeline_interface/flutter_timeline_interface.dart'; import 'package:flutter_timeline_view/flutter_timeline_view.dart'; @@ -21,11 +18,6 @@ class TimelinePostOverviewScreen extends StatelessWidget { @override Widget build(BuildContext context) { - var timelineCategoryName = service.postService.categories - .firstWhereOrNull((element) => element.key == timelinePost.category) - ?.title ?? - timelinePost.category; - var buttonText = '${options.translations.postIn} $timelineCategoryName'; var isSubmitted = false; return Column( mainAxisSize: MainAxisSize.max, @@ -45,27 +37,33 @@ class TimelinePostOverviewScreen extends StatelessWidget { () { onPostSubmit(timelinePost); }, - buttonText, + options.translations.postIn, ) ?? options.buttonBuilder?.call( context, () { onPostSubmit(timelinePost); }, - buttonText, + options.translations.postIn, enabled: true, ) ?? SafeArea( bottom: true, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 80), - child: DefaultFilledButton( - onPressed: () async { - if (isSubmitted) return; - isSubmitted = true; - onPostSubmit(timelinePost); - }, - buttonText: buttonText, + child: Row( + children: [ + Expanded( + child: DefaultFilledButton( + onPressed: () async { + if (isSubmitted) return; + isSubmitted = true; + onPostSubmit(timelinePost); + }, + buttonText: options.translations.postIn, + ), + ), + ], ), ), ), From 49f0853ccaf01277262ff5ffc4993a0fed24fb64 Mon Sep 17 00:00:00 2001 From: mike doornenbal Date: Thu, 1 Aug 2024 09:57:21 +0200 Subject: [PATCH 13/20] fix: post creation inputfields --- .../lib/src/screens/timeline_post_creation_screen.dart | 9 ++++++++- .../lib/src/widgets/post_creation_textfield.dart | 3 +++ 2 files changed, 11 insertions(+), 1 deletion(-) 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 d392159..90286ab 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 @@ -67,12 +67,15 @@ class _TimelinePostCreationScreenState void _listenForInputs() { titleIsValid = titleController.text.isNotEmpty; contentIsValid = contentController.text.isNotEmpty; + setState(() {}); } var formkey = GlobalKey(); @override Widget build(BuildContext context) { + var imageRequired = widget.options.requireImageForPost; + Future onPostCreated() async { var post = TimelinePost( id: 'Post${Random().nextInt(1000)}', @@ -121,6 +124,7 @@ class _TimelinePostCreationScreenState '', ) ?? PostCreationTextfield( + fieldKey: const ValueKey('title'), controller: titleController, hintText: widget.options.translations.titleHintText, textMaxLength: widget.options.maxTitleLength, @@ -152,6 +156,7 @@ class _TimelinePostCreationScreenState height: 4, ), PostCreationTextfield( + fieldKey: const ValueKey('content'), controller: contentController, hintText: widget.options.translations.contentHintText, textMaxLength: null, @@ -355,7 +360,9 @@ class _TimelinePostCreationScreenState children: [ Expanded( child: DefaultFilledButton( - onPressed: titleIsValid && contentIsValid + onPressed: titleIsValid && + contentIsValid && + (imageRequired ? image != null : true) ? () async { if (formkey.currentState! .validate()) { diff --git a/packages/flutter_timeline_view/lib/src/widgets/post_creation_textfield.dart b/packages/flutter_timeline_view/lib/src/widgets/post_creation_textfield.dart index 381359c..9a7e1a0 100644 --- a/packages/flutter_timeline_view/lib/src/widgets/post_creation_textfield.dart +++ b/packages/flutter_timeline_view/lib/src/widgets/post_creation_textfield.dart @@ -12,6 +12,7 @@ class PostCreationTextfield extends StatelessWidget { this.expands, this.minLines, this.maxLines, + this.fieldKey, }); final TextEditingController controller; @@ -24,10 +25,12 @@ class PostCreationTextfield extends StatelessWidget { final int? minLines; final int? maxLines; final String? Function(String?)? validator; + final Key? fieldKey; @override Widget build(BuildContext context) { var theme = Theme.of(context); return TextFormField( + key: fieldKey, validator: validator, style: theme.textTheme.bodySmall, controller: controller, From 8188c179fb8b9ee73f9aa184936aca8da305a798 Mon Sep 17 00:00:00 2001 From: mike doornenbal Date: Thu, 1 Aug 2024 13:22:36 +0200 Subject: [PATCH 14/20] fix: postModel not including creator --- .../flutter_timeline_navigator_userstory.dart | 87 +++------ .../lib/src/model/timeline_poster.dart | 17 ++ .../lib/src/config/timeline_options.dart | 1 + .../timeline_post_creation_screen.dart | 5 +- .../timeline_post_overview_screen.dart | 5 + .../screens/timeline_selection_screen.dart | 179 +++++++++--------- 6 files changed, 144 insertions(+), 150 deletions(-) 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 0d90d21..b4fb0bf 100644 --- a/packages/flutter_timeline/lib/src/flutter_timeline_navigator_userstory.dart +++ b/packages/flutter_timeline/lib/src/flutter_timeline_navigator_userstory.dart @@ -23,7 +23,7 @@ Widget timeLineNavigatorUserStory({ optionsBuilder: (context) => const TimelineOptions(), ); - return _timelineScreenRoute(configuration: config, context: context); + return _timelineScreenRoute(config: config, context: context); } /// A widget function that creates a timeline screen route. @@ -33,18 +33,11 @@ Widget timeLineNavigatorUserStory({ /// parameters. If no configuration is provided, default values will be used. Widget _timelineScreenRoute({ required BuildContext context, - TimelineUserStoryConfiguration? configuration, + required TimelineUserStoryConfiguration config, + String? initalCategory, }) { - var config = configuration ?? - TimelineUserStoryConfiguration( - userId: 'test_user', - service: TimelineService( - postService: LocalTimelinePostService(), - ), - optionsBuilder: (context) => const TimelineOptions(), - ); - var timelineScreen = TimelineScreen( + timelineCategory: initalCategory, userId: config.getUserId?.call(context) ?? config.userId, allowAllDeletion: config.canDeleteAllPosts?.call(context) ?? false, onUserTap: (user) => config.onUserTap?.call(context, user), @@ -55,7 +48,7 @@ Widget _timelineScreenRoute({ Navigator.of(context).push( MaterialPageRoute( builder: (context) => _postDetailScreenRoute( - configuration: config, + config: config, context: context, post: post, ), @@ -78,7 +71,7 @@ Widget _timelineScreenRoute({ await Navigator.of(context).push( MaterialPageRoute( builder: (context) => _postCreationScreenRoute( - configuration: config, + config: config, context: context, category: selectedCategory, ), @@ -88,7 +81,7 @@ Widget _timelineScreenRoute({ await Navigator.of(context).push( MaterialPageRoute( builder: (context) => _postCategorySelectionScreen( - configuration: config, + config: config, context: context, ), ), @@ -125,17 +118,8 @@ Widget _timelineScreenRoute({ Widget _postDetailScreenRoute({ required BuildContext context, required TimelinePost post, - TimelineUserStoryConfiguration? configuration, + required TimelineUserStoryConfiguration config, }) { - var config = configuration ?? - TimelineUserStoryConfiguration( - userId: 'test_user', - service: TimelineService( - postService: LocalTimelinePostService(), - ), - optionsBuilder: (context) => const TimelineOptions(), - ); - var timelinePostScreen = TimelinePostScreen( userId: config.getUserId?.call(context) ?? config.userId, allowAllDeletion: config.canDeleteAllPosts?.call(context) ?? false, @@ -166,7 +150,7 @@ Widget _postDetailScreenRoute({ ?.call(context, timelinePostScreen, backButton, post, category) ?? Scaffold( appBar: AppBar( - leading: backButton, + iconTheme: Theme.of(context).appBarTheme.iconTheme, title: Text( category.title.toLowerCase(), style: TextStyle( @@ -188,31 +172,24 @@ Widget _postDetailScreenRoute({ Widget _postCreationScreenRoute({ required BuildContext context, required TimelineCategory category, - TimelineUserStoryConfiguration? configuration, + required TimelineUserStoryConfiguration config, }) { - var config = configuration ?? - TimelineUserStoryConfiguration( - userId: 'test_user', - service: TimelineService( - postService: LocalTimelinePostService(), - ), - optionsBuilder: (context) => const TimelineOptions(), - ); - var timelinePostCreationScreen = TimelinePostCreationScreen( userId: config.getUserId?.call(context) ?? config.userId, options: config.optionsBuilder(context), service: config.service, onPostCreated: (post) async { var newPost = await config.service.postService.createPost(post); + if (!context.mounted) return; if (config.afterPostCreationGoHome) { await Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) => _timelineScreenRoute( - configuration: config, + config: config, context: context, + initalCategory: category.title, ), ), ); @@ -221,7 +198,7 @@ Widget _postCreationScreenRoute({ context, MaterialPageRoute( builder: (context) => _postOverviewScreenRoute( - configuration: config, + config: config, context: context, post: newPost, ), @@ -232,7 +209,7 @@ Widget _postCreationScreenRoute({ onPostOverview: (post) async => Navigator.of(context).push( MaterialPageRoute( builder: (context) => _postOverviewScreenRoute( - configuration: config, + config: config, context: context, post: post, ), @@ -254,6 +231,7 @@ Widget _postCreationScreenRoute({ ?.call(context, timelinePostCreationScreen, backButton) ?? Scaffold( appBar: AppBar( + iconTheme: Theme.of(context).appBarTheme.iconTheme, leading: backButton, title: Text( config.optionsBuilder(context).translations.postCreation, @@ -277,17 +255,8 @@ Widget _postCreationScreenRoute({ Widget _postOverviewScreenRoute({ required BuildContext context, required TimelinePost post, - TimelineUserStoryConfiguration? configuration, + required TimelineUserStoryConfiguration config, }) { - var config = configuration ?? - TimelineUserStoryConfiguration( - userId: 'test_user', - service: TimelineService( - postService: LocalTimelinePostService(), - ), - optionsBuilder: (context) => const TimelineOptions(), - ); - var timelinePostOverviewWidget = TimelinePostOverviewScreen( options: config.optionsBuilder(context), service: config.service, @@ -297,8 +266,11 @@ Widget _postOverviewScreenRoute({ if (context.mounted) { await Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute( - builder: (context) => - _timelineScreenRoute(configuration: config, context: context), + builder: (context) => _timelineScreenRoute( + config: config, + context: context, + initalCategory: post.category, + ), ), (route) => false, ); @@ -320,6 +292,7 @@ Widget _postOverviewScreenRoute({ ) ?? Scaffold( appBar: AppBar( + iconTheme: Theme.of(context).appBarTheme.iconTheme, leading: backButton, title: Text( config.optionsBuilder(context).translations.postCreation, @@ -336,17 +309,8 @@ Widget _postOverviewScreenRoute({ Widget _postCategorySelectionScreen({ required BuildContext context, - TimelineUserStoryConfiguration? configuration, + required TimelineUserStoryConfiguration config, }) { - var config = configuration ?? - TimelineUserStoryConfiguration( - userId: 'test_user', - service: TimelineService( - postService: LocalTimelinePostService(), - ), - optionsBuilder: (context) => const TimelineOptions(), - ); - var timelineSelectionScreen = TimelineSelectionScreen( postService: config.service.postService, options: config.optionsBuilder(context), @@ -355,7 +319,7 @@ Widget _postCategorySelectionScreen({ await Navigator.of(context).push( MaterialPageRoute( builder: (context) => _postCreationScreenRoute( - configuration: config, + config: config, context: context, category: category, ), @@ -376,6 +340,7 @@ Widget _postCategorySelectionScreen({ ?.call(context, timelineSelectionScreen) ?? Scaffold( appBar: AppBar( + iconTheme: Theme.of(context).appBarTheme.iconTheme, leading: backButton, title: Text( config.optionsBuilder(context).translations.postCreation, diff --git a/packages/flutter_timeline_interface/lib/src/model/timeline_poster.dart b/packages/flutter_timeline_interface/lib/src/model/timeline_poster.dart index c652508..a07f3fd 100644 --- a/packages/flutter_timeline_interface/lib/src/model/timeline_poster.dart +++ b/packages/flutter_timeline_interface/lib/src/model/timeline_poster.dart @@ -13,11 +13,28 @@ class TimelinePosterUserModel { this.imageUrl, }); + factory TimelinePosterUserModel.fromJson( + Map json, + String userId, + ) => + TimelinePosterUserModel( + userId: userId, + firstName: json['first_name'] as String?, + lastName: json['last_name'] as String?, + imageUrl: json['image_url'] as String?, + ); + final String userId; final String? firstName; final String? lastName; final String? imageUrl; + Map toJson() => { + 'first_name': firstName, + 'last_name': lastName, + 'image_url': imageUrl, + }; + String? get fullName { var fullName = ''; 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 d3694cf..49e35f2 100644 --- a/packages/flutter_timeline_view/lib/src/config/timeline_options.dart +++ b/packages/flutter_timeline_view/lib/src/config/timeline_options.dart @@ -160,6 +160,7 @@ class TimelineOptions { BuildContext context, Function() onPressed, String text, + TimelinePost post, )? postOverviewButtonBuilder; /// Optional builder to override the default alertdialog for post deletion 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 90286ab..0e46e58 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 @@ -61,6 +61,7 @@ class _TimelinePostCreationScreenState void initState() { titleController.addListener(_listenForInputs); contentController.addListener(_listenForInputs); + super.initState(); } @@ -77,6 +78,7 @@ class _TimelinePostCreationScreenState var imageRequired = widget.options.requireImageForPost; Future onPostCreated() async { + var user = await widget.service.userService?.getUser(widget.userId); var post = TimelinePost( id: 'Post${Random().nextInt(1000)}', creatorId: widget.userId, @@ -89,6 +91,7 @@ class _TimelinePostCreationScreenState createdAt: DateTime.now(), reactionEnabled: allowComments, image: image, + creator: user, ); if (widget.enablePostOverviewScreen) { @@ -362,7 +365,7 @@ class _TimelinePostCreationScreenState child: DefaultFilledButton( onPressed: titleIsValid && contentIsValid && - (imageRequired ? image != null : true) + (!imageRequired || image != null) ? () async { if (formkey.currentState! .validate()) { 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 0c3f1b5..89d3e0a 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 @@ -35,13 +35,18 @@ class TimelinePostOverviewScreen extends StatelessWidget { options.postOverviewButtonBuilder?.call( context, () { + if (isSubmitted) return; + isSubmitted = true; onPostSubmit(timelinePost); }, options.translations.postIn, + timelinePost, ) ?? options.buttonBuilder?.call( context, () { + if (isSubmitted) return; + isSubmitted = true; onPostSubmit(timelinePost); }, options.translations.postIn, 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 2ed8b36..0a855ff 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 @@ -36,105 +36,108 @@ class _TimelineSelectionScreenState extends State { padding: EdgeInsets.symmetric( horizontal: size.width * 0.05, ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(top: 20, bottom: 12), - child: Text( - widget.options.translations.timelineSelectionDescription, - style: theme.textTheme.titleLarge, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(top: 20, bottom: 12), + child: Text( + widget.options.translations.timelineSelectionDescription, + style: theme.textTheme.titleLarge, + ), ), - ), - for (var category in widget.categories.where( - (element) => element.canCreate && element.key != null, - )) ...[ - widget.options.categorySelectorButtonBuilder?.call( - context, - () { - widget.onCategorySelected.call(category); - }, - category.title, - ) ?? - InkWell( - onTap: () => widget.onCategorySelected.call(category), - child: Container( - height: 60, - width: double.infinity, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - border: Border.all( - color: widget.options.theme - .categorySelectionButtonBorderColor ?? - Theme.of(context).primaryColor, - width: 2, - ), - color: widget - .options.theme.categorySelectionButtonBackgroundColor, - ), - 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: theme.textTheme.titleMedium, - ), + for (var category in widget.categories.where( + (element) => element.canCreate && element.key != null, + )) ...[ + widget.options.categorySelectorButtonBuilder?.call( + context, + () { + widget.onCategorySelected.call(category); + }, + category.title, + ) ?? + InkWell( + onTap: () => widget.onCategorySelected.call(category), + child: Container( + height: 60, + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: widget.options.theme + .categorySelectionButtonBorderColor ?? + Theme.of(context).primaryColor, + width: 2, ), - ], + color: widget.options.theme + .categorySelectionButtonBackgroundColor, + ), + 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: theme.textTheme.titleMedium, + ), + ), + ], + ), ), ), + ], + InkWell( + onTap: showCategoryPopup, + child: Container( + height: 60, + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: widget + .options.theme.categorySelectionButtonBorderColor ?? + Theme.of(context).primaryColor, + width: 2, + ), + color: widget + .options.theme.categorySelectionButtonBackgroundColor, ), - ], - InkWell( - onTap: showCategoryPopup, - child: Container( - height: 60, - width: double.infinity, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - border: Border.all( - color: - widget.options.theme.categorySelectionButtonBorderColor ?? - Theme.of(context).primaryColor, - width: 2, - ), - color: - widget.options.theme.categorySelectionButtonBackgroundColor, - ), - margin: const EdgeInsets.symmetric(vertical: 4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), - child: Row( - children: [ - Icon( - Icons.add, - color: theme.textTheme.titleMedium?.color! - .withOpacity(0.5), - ), - const SizedBox(width: 8), - Text( - widget.options.translations.addCategoryTitle, - style: theme.textTheme.titleMedium!.copyWith( + margin: const EdgeInsets.symmetric(vertical: 4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Row( + children: [ + Icon( + Icons.add, color: theme.textTheme.titleMedium?.color! .withOpacity(0.5), ), - ), - ], + const SizedBox(width: 8), + Text( + widget.options.translations.addCategoryTitle, + style: theme.textTheme.titleMedium!.copyWith( + color: theme.textTheme.titleMedium?.color! + .withOpacity(0.5), + ), + ), + ], + ), ), - ), - ], + ], + ), ), ), - ), - ], + ], + ), ), ); } From 7aef9d961741aaaefc7eb2260a826be6d2c4d471 Mon Sep 17 00:00:00 2001 From: mike doornenbal Date: Thu, 1 Aug 2024 13:39:38 +0200 Subject: [PATCH 15/20] feat: publish to server --- packages/flutter_timeline/CHANGELOG.md | 1 + packages/flutter_timeline/LICENSE | 1 + packages/flutter_timeline/README.md | 1 + .../example/lib/config/config.dart | 19 --------------- packages/flutter_timeline/pubspec.yaml | 23 ++++++------------ .../flutter_timeline_firebase/CHANGELOG.md | 1 + packages/flutter_timeline_firebase/LICENSE | 1 + packages/flutter_timeline_firebase/README.md | 1 + .../flutter_timeline_firebase/pubspec.yaml | 17 ++++--------- .../flutter_timeline_interface/CHANGELOG.md | 1 + packages/flutter_timeline_interface/LICENSE | 1 + packages/flutter_timeline_interface/README.md | 1 + .../flutter_timeline_interface/pubspec.yaml | 5 ++-- packages/flutter_timeline_view/CHANGELOG.md | 1 + packages/flutter_timeline_view/LICENSE | 1 + packages/flutter_timeline_view/README.md | 1 + .../lib/src/widgets/timeline_post_widget.dart | 4 +++- packages/flutter_timeline_view/pubspec.yaml | 24 ++++++------------- 18 files changed, 35 insertions(+), 69 deletions(-) create mode 100644 packages/flutter_timeline/CHANGELOG.md create mode 100644 packages/flutter_timeline/LICENSE create mode 100644 packages/flutter_timeline/README.md create mode 100644 packages/flutter_timeline_firebase/CHANGELOG.md create mode 100644 packages/flutter_timeline_firebase/LICENSE create mode 100644 packages/flutter_timeline_firebase/README.md create mode 100644 packages/flutter_timeline_interface/CHANGELOG.md create mode 100644 packages/flutter_timeline_interface/LICENSE create mode 100644 packages/flutter_timeline_interface/README.md create mode 100644 packages/flutter_timeline_view/CHANGELOG.md create mode 100644 packages/flutter_timeline_view/LICENSE create mode 100644 packages/flutter_timeline_view/README.md diff --git a/packages/flutter_timeline/CHANGELOG.md b/packages/flutter_timeline/CHANGELOG.md new file mode 100644 index 0000000..699cc9e --- /dev/null +++ b/packages/flutter_timeline/CHANGELOG.md @@ -0,0 +1 @@ +../../CHANGELOG.md \ No newline at end of file diff --git a/packages/flutter_timeline/LICENSE b/packages/flutter_timeline/LICENSE new file mode 100644 index 0000000..30cff74 --- /dev/null +++ b/packages/flutter_timeline/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file diff --git a/packages/flutter_timeline/README.md b/packages/flutter_timeline/README.md new file mode 100644 index 0000000..fe84005 --- /dev/null +++ b/packages/flutter_timeline/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/packages/flutter_timeline/example/lib/config/config.dart b/packages/flutter_timeline/example/lib/config/config.dart index 017e672..3e82841 100644 --- a/packages/flutter_timeline/example/lib/config/config.dart +++ b/packages/flutter_timeline/example/lib/config/config.dart @@ -16,25 +16,6 @@ var options = TimelineOptions( paddings: TimelinePaddingOptions( mainPadding: const EdgeInsets.all(20).copyWith(top: 28), ), - categoriesOptions: CategoriesOptions( - categoriesBuilder: (context) => [ - const TimelineCategory( - key: null, - title: 'All', - icon: SizedBox.shrink(), - ), - const TimelineCategory( - key: 'category1', - title: 'Category 1', - icon: SizedBox.shrink(), - ), - const TimelineCategory( - key: 'category2', - title: 'Category 2', - icon: SizedBox.shrink(), - ), - ], - ), ); void navigateToOverview( diff --git a/packages/flutter_timeline/pubspec.yaml b/packages/flutter_timeline/pubspec.yaml index d7fa66d..e4ba5ad 100644 --- a/packages/flutter_timeline/pubspec.yaml +++ b/packages/flutter_timeline/pubspec.yaml @@ -3,9 +3,8 @@ # SPDX-License-Identifier: GPL-3.0-or-later name: flutter_timeline description: Visual elements and interface combined into one package -version: 4.1.0 - -publish_to: none +version: 5.0.0 +publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub environment: sdk: ">=3.1.3 <4.0.0" @@ -14,22 +13,14 @@ dependencies: flutter: sdk: flutter flutter_timeline_view: - git: - url: https://github.com/Iconica-Development/flutter_timeline - path: packages/flutter_timeline_view - ref: 4.1.0 + hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub + version: ^5.0.0 flutter_timeline_interface: - git: - url: https://github.com/Iconica-Development/flutter_timeline - path: packages/flutter_timeline_interface - ref: 4.1.0 + hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub + version: ^5.0.0 collection: any -dependency_overrides: - flutter_timeline_view: - path: ../flutter_timeline_view - flutter_timeline_interface: - path: ../flutter_timeline_interface + dev_dependencies: flutter_lints: ^2.0.0 flutter_iconica_analysis: diff --git a/packages/flutter_timeline_firebase/CHANGELOG.md b/packages/flutter_timeline_firebase/CHANGELOG.md new file mode 100644 index 0000000..699cc9e --- /dev/null +++ b/packages/flutter_timeline_firebase/CHANGELOG.md @@ -0,0 +1 @@ +../../CHANGELOG.md \ No newline at end of file diff --git a/packages/flutter_timeline_firebase/LICENSE b/packages/flutter_timeline_firebase/LICENSE new file mode 100644 index 0000000..30cff74 --- /dev/null +++ b/packages/flutter_timeline_firebase/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file diff --git a/packages/flutter_timeline_firebase/README.md b/packages/flutter_timeline_firebase/README.md new file mode 100644 index 0000000..fe84005 --- /dev/null +++ b/packages/flutter_timeline_firebase/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/packages/flutter_timeline_firebase/pubspec.yaml b/packages/flutter_timeline_firebase/pubspec.yaml index a9cdf0b..68a1afb 100644 --- a/packages/flutter_timeline_firebase/pubspec.yaml +++ b/packages/flutter_timeline_firebase/pubspec.yaml @@ -4,9 +4,8 @@ name: flutter_timeline_firebase description: Implementation of the Flutter Timeline interface for Firebase. -version: 4.1.0 - -publish_to: none +version: 5.0.0 +publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub environment: sdk: ">=3.1.3 <4.0.0" @@ -18,18 +17,10 @@ dependencies: firebase_core: ^2.22.0 firebase_storage: ^11.5.1 uuid: ^4.2.1 - - flutter_timeline_interface: - git: - url: https://github.com/Iconica-Development/flutter_timeline - path: packages/flutter_timeline_interface - ref: 4.1.0 collection: ^1.18.0 - -dependency_overrides: flutter_timeline_interface: - path: ../flutter_timeline_interface - + hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub + version: ^5.0.0 dev_dependencies: flutter_lints: ^2.0.0 diff --git a/packages/flutter_timeline_interface/CHANGELOG.md b/packages/flutter_timeline_interface/CHANGELOG.md new file mode 100644 index 0000000..699cc9e --- /dev/null +++ b/packages/flutter_timeline_interface/CHANGELOG.md @@ -0,0 +1 @@ +../../CHANGELOG.md \ No newline at end of file diff --git a/packages/flutter_timeline_interface/LICENSE b/packages/flutter_timeline_interface/LICENSE new file mode 100644 index 0000000..30cff74 --- /dev/null +++ b/packages/flutter_timeline_interface/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file diff --git a/packages/flutter_timeline_interface/README.md b/packages/flutter_timeline_interface/README.md new file mode 100644 index 0000000..fe84005 --- /dev/null +++ b/packages/flutter_timeline_interface/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/packages/flutter_timeline_interface/pubspec.yaml b/packages/flutter_timeline_interface/pubspec.yaml index 4d56795..209ca75 100644 --- a/packages/flutter_timeline_interface/pubspec.yaml +++ b/packages/flutter_timeline_interface/pubspec.yaml @@ -4,9 +4,8 @@ name: flutter_timeline_interface description: Interface for the service of the Flutter Timeline component -version: 4.1.0 - -publish_to: none +version: 5.0.0 +publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub environment: sdk: '>=3.1.3 <4.0.0' diff --git a/packages/flutter_timeline_view/CHANGELOG.md b/packages/flutter_timeline_view/CHANGELOG.md new file mode 100644 index 0000000..699cc9e --- /dev/null +++ b/packages/flutter_timeline_view/CHANGELOG.md @@ -0,0 +1 @@ +../../CHANGELOG.md \ No newline at end of file diff --git a/packages/flutter_timeline_view/LICENSE b/packages/flutter_timeline_view/LICENSE new file mode 100644 index 0000000..30cff74 --- /dev/null +++ b/packages/flutter_timeline_view/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file diff --git a/packages/flutter_timeline_view/README.md b/packages/flutter_timeline_view/README.md new file mode 100644 index 0000000..fe84005 --- /dev/null +++ b/packages/flutter_timeline_view/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file 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 686da59..e7760a6 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 @@ -315,7 +315,9 @@ class _TimelinePostWidgetState extends State { ] else ...[ Text( '${widget.post.likes} ' - '${widget.post.likes > 1 ? widget.options.translations.multipleLikesTitle : widget.options.translations.oneLikeTitle}', + '${widget.post.likes > 1 + ? widget.options.translations.multipleLikesTitle + : widget.options.translations.oneLikeTitle}', style: widget.options.theme.textStyles.listPostLikeTitleAndAmount ?? theme.textTheme.titleSmall!.copyWith( diff --git a/packages/flutter_timeline_view/pubspec.yaml b/packages/flutter_timeline_view/pubspec.yaml index b16f14c..bb0ffbb 100644 --- a/packages/flutter_timeline_view/pubspec.yaml +++ b/packages/flutter_timeline_view/pubspec.yaml @@ -4,9 +4,8 @@ name: flutter_timeline_view description: Visual elements of the Flutter Timeline Component -version: 4.1.0 - -publish_to: none +version: 5.0.0 +publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub environment: sdk: ">=3.1.3 <4.0.0" @@ -17,22 +16,14 @@ dependencies: intl: any cached_network_image: ^3.2.2 dotted_border: ^2.1.0 - - flutter_timeline_interface: - git: - url: https://github.com/Iconica-Development/flutter_timeline - path: packages/flutter_timeline_interface - ref: 4.1.0 - flutter_image_picker: - git: - url: https://github.com/Iconica-Development/flutter_image_picker - ref: 1.0.5 collection: any flutter_svg: ^2.0.10+1 - -dependency_overrides: flutter_timeline_interface: - path: ../flutter_timeline_interface + hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub + version: ^5.0.0 + flutter_image_picker: + hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub + version: ^1.0.5 dev_dependencies: flutter_lints: ^2.0.0 @@ -44,4 +35,3 @@ dev_dependencies: flutter: assets: - assets/ - From 02c136d7ead318e14530a23d5e96ea2fbc0b53ac Mon Sep 17 00:00:00 2001 From: mike doornenbal Date: Wed, 7 Aug 2024 13:34:04 +0200 Subject: [PATCH 16/20] fix: update image_picker dependency --- .../timeline_post_creation_screen.dart | 39 ++++++++----------- packages/flutter_timeline_view/pubspec.yaml | 2 +- 2 files changed, 17 insertions(+), 24 deletions(-) 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 0e46e58..eaa7d1f 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 @@ -199,21 +199,13 @@ class _TimelinePostCreationScreenState var result = await showModalBottomSheet( context: context, builder: (context) => Container( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(20), color: theme.colorScheme.surface, child: ImagePicker( - imagePickerConfig: - widget.options.imagePickerConfig, - imagePickerTheme: widget - .options.imagePickerTheme ?? + config: widget.options.imagePickerConfig, + theme: widget.options.imagePickerTheme ?? ImagePickerTheme( - titleAlignment: TextAlign.center, - title: ' Do you want to upload a file' - ' or take a picture? ', - titleTextSize: - theme.textTheme.titleMedium!.fontSize!, - font: theme - .textTheme.titleMedium!.fontFamily!, + titleStyle: theme.textTheme.titleMedium, iconSize: 40, selectImageText: 'UPLOAD FILE', makePhotoText: 'TAKE PICTURE', @@ -221,18 +213,19 @@ class _TimelinePostCreationScreenState size: 40, Icons.insert_drive_file, ), + closeButtonBuilder: (onTap) => TextButton( + onPressed: () { + onTap(); + }, + child: Text( + 'Cancel', + style: theme.textTheme.bodyMedium! + .copyWith( + decoration: TextDecoration.underline, + ), + ), + ), ), - customButton: TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: Text( - 'Cancel', - style: theme.textTheme.bodyMedium!.copyWith( - decoration: TextDecoration.underline, - ), - ), - ), ), ), ); diff --git a/packages/flutter_timeline_view/pubspec.yaml b/packages/flutter_timeline_view/pubspec.yaml index bb0ffbb..bd8e1fb 100644 --- a/packages/flutter_timeline_view/pubspec.yaml +++ b/packages/flutter_timeline_view/pubspec.yaml @@ -23,7 +23,7 @@ dependencies: version: ^5.0.0 flutter_image_picker: hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub - version: ^1.0.5 + version: ^4.0.0 dev_dependencies: flutter_lints: ^2.0.0 From 38bb41ce10110f1e57eb6e567bc3e9ff681e8b0d Mon Sep 17 00:00:00 2001 From: mike doornenbal Date: Wed, 14 Aug 2024 15:10:55 +0200 Subject: [PATCH 17/20] fix: feedback --- CHANGELOG.md | 5 ++++ packages/cd | 1 + packages/flutter_timeline/LICENSE | 2 +- packages/flutter_timeline/README.md | 2 +- .../flutter_timeline_navigator_userstory.dart | 24 ++++++++++++++++--- .../src/models/timeline_configuration.dart | 3 +++ packages/flutter_timeline/pubspec.yaml | 6 ++--- packages/flutter_timeline_firebase/LICENSE | 2 +- packages/flutter_timeline_firebase/README.md | 2 +- .../src/service/firebase_post_service.dart | 18 ++++++++++---- .../flutter_timeline_firebase/pubspec.yaml | 4 ++-- packages/flutter_timeline_interface/LICENSE | 2 +- packages/flutter_timeline_interface/README.md | 2 +- .../src/services/timeline_post_service.dart | 2 +- .../flutter_timeline_interface/pubspec.yaml | 2 +- packages/flutter_timeline_view/CHANGELOG.md | 2 +- packages/flutter_timeline_view/LICENSE | 2 +- packages/flutter_timeline_view/README.md | 2 +- .../timeline_post_creation_screen.dart | 10 ++++++-- .../lib/src/screens/timeline_post_screen.dart | 15 ++++++++---- .../screens/timeline_selection_screen.dart | 2 +- .../lib/src/services/local_post_service.dart | 9 +++---- packages/flutter_timeline_view/pubspec.yaml | 4 ++-- 23 files changed, 87 insertions(+), 36 deletions(-) create mode 120000 packages/cd diff --git a/CHANGELOG.md b/CHANGELOG.md index 95b8c19..c4c4f28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 5.1.0 + +* Added `routeToPostDetail` to the `TimelineUserStory` to allow for navigation to the post detail screen. +* Fixed design issues. + ## 4.1.0 - Migrate to flutter 3.22 which deprecates the background and onBackground properties in the ThemeData and also removes MaterialStatePropertyAll - Add categorySelectionButtonSelectedTextColor and categorySelectionButtonUnselectedTextColor to the timeline theme to allow for the customization of the text color of the category selection buttons diff --git a/packages/cd b/packages/cd new file mode 120000 index 0000000..25038d7 --- /dev/null +++ b/packages/cd @@ -0,0 +1 @@ +cd \ No newline at end of file diff --git a/packages/flutter_timeline/LICENSE b/packages/flutter_timeline/LICENSE index 30cff74..5678cdd 100644 --- a/packages/flutter_timeline/LICENSE +++ b/packages/flutter_timeline/LICENSE @@ -1 +1 @@ -../../LICENSE \ No newline at end of file +[../../LICENSE](https://github.com/Iconica-Development/flutter_timeline/blob/master/LICENSE) \ No newline at end of file diff --git a/packages/flutter_timeline/README.md b/packages/flutter_timeline/README.md index fe84005..0b934f5 100644 --- a/packages/flutter_timeline/README.md +++ b/packages/flutter_timeline/README.md @@ -1 +1 @@ -../../README.md \ No newline at end of file +[../../README.md](https://github.com/Iconica-Development/flutter_timeline/blob/master/README.md) \ No newline at end of file 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 b4fb0bf..aaf36e3 100644 --- a/packages/flutter_timeline/lib/src/flutter_timeline_navigator_userstory.dart +++ b/packages/flutter_timeline/lib/src/flutter_timeline_navigator_userstory.dart @@ -10,11 +10,13 @@ import 'package:flutter_timeline/flutter_timeline.dart'; /// This function creates a navigator for displaying user stories on a timeline. /// It takes a [BuildContext] and an optional [TimelineUserStoryConfiguration] /// as parameters. If no configuration is provided, default values will be used. +late TimelineUserStoryConfiguration timelineUserStoryConfiguration; + Widget timeLineNavigatorUserStory({ required BuildContext context, TimelineUserStoryConfiguration? configuration, }) { - var config = configuration ?? + timelineUserStoryConfiguration = configuration ?? TimelineUserStoryConfiguration( userId: 'test_user', service: TimelineService( @@ -23,7 +25,10 @@ Widget timeLineNavigatorUserStory({ optionsBuilder: (context) => const TimelineOptions(), ); - return _timelineScreenRoute(config: config, context: context); + return _timelineScreenRoute( + config: timelineUserStoryConfiguration, + context: context, + ); } /// A widget function that creates a timeline screen route. @@ -262,7 +267,8 @@ Widget _postOverviewScreenRoute({ service: config.service, timelinePost: post, onPostSubmit: (post) async { - await config.service.postService.createPost(post); + var createdPost = await config.service.postService.createPost(post); + config.onPostCreate?.call(createdPost); if (context.mounted) { await Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute( @@ -354,3 +360,15 @@ Widget _postCategorySelectionScreen({ body: timelineSelectionScreen, ); } + +Future routeToPostDetail(BuildContext context, TimelinePost post) async { + await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => _postDetailScreenRoute( + config: timelineUserStoryConfiguration, + context: context, + post: post, + ), + ), + ); +} diff --git a/packages/flutter_timeline/lib/src/models/timeline_configuration.dart b/packages/flutter_timeline/lib/src/models/timeline_configuration.dart index 884bcff..8dcc2a5 100644 --- a/packages/flutter_timeline/lib/src/models/timeline_configuration.dart +++ b/packages/flutter_timeline/lib/src/models/timeline_configuration.dart @@ -65,6 +65,7 @@ class TimelineUserStoryConfiguration { this.afterPostCreationGoHome = false, this.enablePostOverviewScreen = true, this.categorySelectionOpenPageBuilder, + this.onPostCreate, }); /// The ID of the user associated with this user story configuration. @@ -159,4 +160,6 @@ class TimelineUserStoryConfiguration { BuildContext context, Widget child, )? categorySelectionOpenPageBuilder; + + final Function(TimelinePost post)? onPostCreate; } diff --git a/packages/flutter_timeline/pubspec.yaml b/packages/flutter_timeline/pubspec.yaml index e4ba5ad..a152f39 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: 5.0.0 +version: 5.1.0 publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub environment: @@ -14,11 +14,11 @@ dependencies: sdk: flutter flutter_timeline_view: hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub - version: ^5.0.0 + version: ^5.1.0 flutter_timeline_interface: hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub - version: ^5.0.0 + version: ^5.1.0 collection: any dev_dependencies: diff --git a/packages/flutter_timeline_firebase/LICENSE b/packages/flutter_timeline_firebase/LICENSE index 30cff74..5678cdd 100644 --- a/packages/flutter_timeline_firebase/LICENSE +++ b/packages/flutter_timeline_firebase/LICENSE @@ -1 +1 @@ -../../LICENSE \ No newline at end of file +[../../LICENSE](https://github.com/Iconica-Development/flutter_timeline/blob/master/LICENSE) \ No newline at end of file diff --git a/packages/flutter_timeline_firebase/README.md b/packages/flutter_timeline_firebase/README.md index fe84005..0b934f5 100644 --- a/packages/flutter_timeline_firebase/README.md +++ b/packages/flutter_timeline_firebase/README.md @@ -1 +1 @@ -../../README.md \ No newline at end of file +[../../README.md](https://github.com/Iconica-Development/flutter_timeline/blob/master/README.md) \ No newline at end of file diff --git a/packages/flutter_timeline_firebase/lib/src/service/firebase_post_service.dart b/packages/flutter_timeline_firebase/lib/src/service/firebase_post_service.dart index 270a209..aa27af4 100644 --- a/packages/flutter_timeline_firebase/lib/src/service/firebase_post_service.dart +++ b/packages/flutter_timeline_firebase/lib/src/service/firebase_post_service.dart @@ -245,10 +245,20 @@ class FirebaseTimelinePostService } @override - TimelinePost? getPost(String postId) => - (posts.any((element) => element.id == postId)) - ? posts.firstWhere((element) => element.id == postId) - : null; + Future getPost(String postId) async { + var post = await _db + .collection(_options.timelineCollectionName) + .doc(postId) + .withConverter( + fromFirestore: (snapshot, _) => TimelinePost.fromJson( + snapshot.id, + snapshot.data()!, + ), + toFirestore: (user, _) => user.toJson(), + ) + .get(); + return post.data(); + } @override List getPosts(String? category) => posts diff --git a/packages/flutter_timeline_firebase/pubspec.yaml b/packages/flutter_timeline_firebase/pubspec.yaml index 68a1afb..d094194 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: 5.0.0 +version: 5.1.0 publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub environment: @@ -20,7 +20,7 @@ dependencies: collection: ^1.18.0 flutter_timeline_interface: hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub - version: ^5.0.0 + version: ^5.1.0 dev_dependencies: flutter_lints: ^2.0.0 diff --git a/packages/flutter_timeline_interface/LICENSE b/packages/flutter_timeline_interface/LICENSE index 30cff74..5678cdd 100644 --- a/packages/flutter_timeline_interface/LICENSE +++ b/packages/flutter_timeline_interface/LICENSE @@ -1 +1 @@ -../../LICENSE \ No newline at end of file +[../../LICENSE](https://github.com/Iconica-Development/flutter_timeline/blob/master/LICENSE) \ No newline at end of file diff --git a/packages/flutter_timeline_interface/README.md b/packages/flutter_timeline_interface/README.md index fe84005..0b934f5 100644 --- a/packages/flutter_timeline_interface/README.md +++ b/packages/flutter_timeline_interface/README.md @@ -1 +1 @@ -../../README.md \ No newline at end of file +[../../README.md](https://github.com/Iconica-Development/flutter_timeline/blob/master/README.md) \ No newline at end of file diff --git a/packages/flutter_timeline_interface/lib/src/services/timeline_post_service.dart b/packages/flutter_timeline_interface/lib/src/services/timeline_post_service.dart index 17f5d38..9a464f3 100644 --- a/packages/flutter_timeline_interface/lib/src/services/timeline_post_service.dart +++ b/packages/flutter_timeline_interface/lib/src/services/timeline_post_service.dart @@ -18,7 +18,7 @@ abstract class TimelinePostService with ChangeNotifier { Future> fetchPosts(String? category); Future fetchPost(TimelinePost post); Future> fetchPostsPaginated(String? category, int limit); - TimelinePost? getPost(String postId); + Future getPost(String postId); List getPosts(String? category); Future> refreshPosts(String? category); Future fetchPostDetails(TimelinePost post); diff --git a/packages/flutter_timeline_interface/pubspec.yaml b/packages/flutter_timeline_interface/pubspec.yaml index 209ca75..d1e82e5 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: 5.0.0 +version: 5.1.0 publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub environment: diff --git a/packages/flutter_timeline_view/CHANGELOG.md b/packages/flutter_timeline_view/CHANGELOG.md index 699cc9e..fe84005 100644 --- a/packages/flutter_timeline_view/CHANGELOG.md +++ b/packages/flutter_timeline_view/CHANGELOG.md @@ -1 +1 @@ -../../CHANGELOG.md \ No newline at end of file +../../README.md \ No newline at end of file diff --git a/packages/flutter_timeline_view/LICENSE b/packages/flutter_timeline_view/LICENSE index 30cff74..5678cdd 100644 --- a/packages/flutter_timeline_view/LICENSE +++ b/packages/flutter_timeline_view/LICENSE @@ -1 +1 @@ -../../LICENSE \ No newline at end of file +[../../LICENSE](https://github.com/Iconica-Development/flutter_timeline/blob/master/LICENSE) \ No newline at end of file diff --git a/packages/flutter_timeline_view/README.md b/packages/flutter_timeline_view/README.md index fe84005..0b934f5 100644 --- a/packages/flutter_timeline_view/README.md +++ b/packages/flutter_timeline_view/README.md @@ -1 +1 @@ -../../README.md \ No newline at end of file +[../../README.md](https://github.com/Iconica-Development/flutter_timeline/blob/master/README.md) \ No newline at end of file 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 eaa7d1f..7d1cf1c 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 @@ -146,7 +146,7 @@ class _TimelinePostCreationScreenState return null; }, ), - const SizedBox(height: 16), + const SizedBox(height: 24), Text( widget.options.translations.content, style: theme.textTheme.titleMedium, @@ -179,7 +179,7 @@ class _TimelinePostCreationScreenState }, ), const SizedBox( - height: 16, + height: 24, ), Text( widget.options.translations.uploadImage, @@ -314,6 +314,9 @@ class _TimelinePostCreationScreenState }); }, ), + const SizedBox( + width: 4, + ), Text( widget.options.translations.yes, style: theme.textTheme.bodyMedium, @@ -333,6 +336,9 @@ class _TimelinePostCreationScreenState }); }, ), + const SizedBox( + width: 4, + ), Text( widget.options.translations.no, style: theme.textTheme.bodyMedium, 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 225449c..450f207 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 @@ -409,12 +409,13 @@ class _TimelinePostScreenState extends State { post.content, style: theme.textTheme.bodySmall, ), - const SizedBox(height: 4), Text( '${dateFormat.format(post.createdAt)} ', - style: theme.textTheme.labelSmall, + style: theme.textTheme.labelSmall?.copyWith( + letterSpacing: 0.5, + ), ), - const SizedBox(height: 16), + const SizedBox(height: 8), // ignore: avoid_bool_literals_in_conditional_expressions if (post.reactionEnabled && widget.isOverviewScreen != null ? !widget.isOverviewScreen! @@ -541,6 +542,7 @@ class _TimelinePostScreenState extends State { color: theme .textTheme.labelSmall!.color! .withOpacity(0.5), + letterSpacing: 0.5, ), ), @@ -657,7 +659,12 @@ class _TimelinePostScreenState extends State { ), Flexible( child: Padding( - padding: const EdgeInsets.only(left: 8, right: 16), + padding: const EdgeInsets.only( + left: 8, + right: 16, + top: 8, + bottom: 8, + ), child: ReactionBottom( messageInputBuilder: textInputBuilder, onReactionSubmit: (reaction) async => updatePost( 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 0a855ff..3700e10 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 @@ -101,7 +101,7 @@ class _TimelineSelectionScreenState extends State { border: Border.all( color: widget .options.theme.categorySelectionButtonBorderColor ?? - Theme.of(context).primaryColor, + const Color(0xFF9E9E9E), width: 2, ), color: widget 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 f1ee4cf..aecf6fd 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 @@ -124,10 +124,11 @@ class LocalTimelinePostService } @override - TimelinePost? getPost(String postId) => - (posts.any((element) => element.id == postId)) - ? posts.firstWhere((element) => element.id == postId) - : null; + Future getPost(String postId) => Future.value( + (posts.any((element) => element.id == postId)) + ? posts.firstWhere((element) => element.id == postId) + : null, + ); @override List getPosts(String? category) => posts diff --git a/packages/flutter_timeline_view/pubspec.yaml b/packages/flutter_timeline_view/pubspec.yaml index bd8e1fb..a0b4580 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: 5.0.0 +version: 5.1.0 publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub environment: @@ -20,7 +20,7 @@ dependencies: flutter_svg: ^2.0.10+1 flutter_timeline_interface: hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub - version: ^5.0.0 + version: ^5.1.0 flutter_image_picker: hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub version: ^4.0.0 From f9525c60b5c4764af705986f18da7827fad92db1 Mon Sep 17 00:00:00 2001 From: mike doornenbal Date: Thu, 22 Aug 2024 13:48:15 +0200 Subject: [PATCH 18/20] feat: add symlinks --- packages/cd | 1 - packages/flutter_timeline/CHANGELOG.md | 0 packages/flutter_timeline/LICENSE | 2 +- packages/flutter_timeline/README.md | 2 +- packages/flutter_timeline_firebase/CHANGELOG.md | 0 packages/flutter_timeline_firebase/LICENSE | 2 +- packages/flutter_timeline_firebase/README.md | 2 +- packages/flutter_timeline_interface/CHANGELOG.md | 0 packages/flutter_timeline_interface/LICENSE | 2 +- packages/flutter_timeline_interface/README.md | 2 +- packages/flutter_timeline_view/CHANGELOG.md | 2 +- packages/flutter_timeline_view/LICENSE | 2 +- packages/flutter_timeline_view/README.md | 2 +- 13 files changed, 9 insertions(+), 10 deletions(-) delete mode 120000 packages/cd mode change 100644 => 120000 packages/flutter_timeline/CHANGELOG.md mode change 100644 => 120000 packages/flutter_timeline/LICENSE mode change 100644 => 120000 packages/flutter_timeline/README.md mode change 100644 => 120000 packages/flutter_timeline_firebase/CHANGELOG.md mode change 100644 => 120000 packages/flutter_timeline_firebase/LICENSE mode change 100644 => 120000 packages/flutter_timeline_firebase/README.md mode change 100644 => 120000 packages/flutter_timeline_interface/CHANGELOG.md mode change 100644 => 120000 packages/flutter_timeline_interface/LICENSE mode change 100644 => 120000 packages/flutter_timeline_interface/README.md mode change 100644 => 120000 packages/flutter_timeline_view/CHANGELOG.md mode change 100644 => 120000 packages/flutter_timeline_view/LICENSE mode change 100644 => 120000 packages/flutter_timeline_view/README.md diff --git a/packages/cd b/packages/cd deleted file mode 120000 index 25038d7..0000000 --- a/packages/cd +++ /dev/null @@ -1 +0,0 @@ -cd \ No newline at end of file diff --git a/packages/flutter_timeline/CHANGELOG.md b/packages/flutter_timeline/CHANGELOG.md deleted file mode 100644 index 699cc9e..0000000 --- a/packages/flutter_timeline/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -../../CHANGELOG.md \ No newline at end of file diff --git a/packages/flutter_timeline/CHANGELOG.md b/packages/flutter_timeline/CHANGELOG.md new file mode 120000 index 0000000..699cc9e --- /dev/null +++ b/packages/flutter_timeline/CHANGELOG.md @@ -0,0 +1 @@ +../../CHANGELOG.md \ No newline at end of file diff --git a/packages/flutter_timeline/LICENSE b/packages/flutter_timeline/LICENSE deleted file mode 100644 index 5678cdd..0000000 --- a/packages/flutter_timeline/LICENSE +++ /dev/null @@ -1 +0,0 @@ -[../../LICENSE](https://github.com/Iconica-Development/flutter_timeline/blob/master/LICENSE) \ No newline at end of file diff --git a/packages/flutter_timeline/LICENSE b/packages/flutter_timeline/LICENSE new file mode 120000 index 0000000..30cff74 --- /dev/null +++ b/packages/flutter_timeline/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file diff --git a/packages/flutter_timeline/README.md b/packages/flutter_timeline/README.md deleted file mode 100644 index 0b934f5..0000000 --- a/packages/flutter_timeline/README.md +++ /dev/null @@ -1 +0,0 @@ -[../../README.md](https://github.com/Iconica-Development/flutter_timeline/blob/master/README.md) \ No newline at end of file diff --git a/packages/flutter_timeline/README.md b/packages/flutter_timeline/README.md new file mode 120000 index 0000000..fe84005 --- /dev/null +++ b/packages/flutter_timeline/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/packages/flutter_timeline_firebase/CHANGELOG.md b/packages/flutter_timeline_firebase/CHANGELOG.md deleted file mode 100644 index 699cc9e..0000000 --- a/packages/flutter_timeline_firebase/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -../../CHANGELOG.md \ No newline at end of file diff --git a/packages/flutter_timeline_firebase/CHANGELOG.md b/packages/flutter_timeline_firebase/CHANGELOG.md new file mode 120000 index 0000000..699cc9e --- /dev/null +++ b/packages/flutter_timeline_firebase/CHANGELOG.md @@ -0,0 +1 @@ +../../CHANGELOG.md \ No newline at end of file diff --git a/packages/flutter_timeline_firebase/LICENSE b/packages/flutter_timeline_firebase/LICENSE deleted file mode 100644 index 5678cdd..0000000 --- a/packages/flutter_timeline_firebase/LICENSE +++ /dev/null @@ -1 +0,0 @@ -[../../LICENSE](https://github.com/Iconica-Development/flutter_timeline/blob/master/LICENSE) \ No newline at end of file diff --git a/packages/flutter_timeline_firebase/LICENSE b/packages/flutter_timeline_firebase/LICENSE new file mode 120000 index 0000000..30cff74 --- /dev/null +++ b/packages/flutter_timeline_firebase/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file diff --git a/packages/flutter_timeline_firebase/README.md b/packages/flutter_timeline_firebase/README.md deleted file mode 100644 index 0b934f5..0000000 --- a/packages/flutter_timeline_firebase/README.md +++ /dev/null @@ -1 +0,0 @@ -[../../README.md](https://github.com/Iconica-Development/flutter_timeline/blob/master/README.md) \ No newline at end of file diff --git a/packages/flutter_timeline_firebase/README.md b/packages/flutter_timeline_firebase/README.md new file mode 120000 index 0000000..fe84005 --- /dev/null +++ b/packages/flutter_timeline_firebase/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/packages/flutter_timeline_interface/CHANGELOG.md b/packages/flutter_timeline_interface/CHANGELOG.md deleted file mode 100644 index 699cc9e..0000000 --- a/packages/flutter_timeline_interface/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -../../CHANGELOG.md \ No newline at end of file diff --git a/packages/flutter_timeline_interface/CHANGELOG.md b/packages/flutter_timeline_interface/CHANGELOG.md new file mode 120000 index 0000000..699cc9e --- /dev/null +++ b/packages/flutter_timeline_interface/CHANGELOG.md @@ -0,0 +1 @@ +../../CHANGELOG.md \ No newline at end of file diff --git a/packages/flutter_timeline_interface/LICENSE b/packages/flutter_timeline_interface/LICENSE deleted file mode 100644 index 5678cdd..0000000 --- a/packages/flutter_timeline_interface/LICENSE +++ /dev/null @@ -1 +0,0 @@ -[../../LICENSE](https://github.com/Iconica-Development/flutter_timeline/blob/master/LICENSE) \ No newline at end of file diff --git a/packages/flutter_timeline_interface/LICENSE b/packages/flutter_timeline_interface/LICENSE new file mode 120000 index 0000000..30cff74 --- /dev/null +++ b/packages/flutter_timeline_interface/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file diff --git a/packages/flutter_timeline_interface/README.md b/packages/flutter_timeline_interface/README.md deleted file mode 100644 index 0b934f5..0000000 --- a/packages/flutter_timeline_interface/README.md +++ /dev/null @@ -1 +0,0 @@ -[../../README.md](https://github.com/Iconica-Development/flutter_timeline/blob/master/README.md) \ No newline at end of file diff --git a/packages/flutter_timeline_interface/README.md b/packages/flutter_timeline_interface/README.md new file mode 120000 index 0000000..fe84005 --- /dev/null +++ b/packages/flutter_timeline_interface/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/packages/flutter_timeline_view/CHANGELOG.md b/packages/flutter_timeline_view/CHANGELOG.md deleted file mode 100644 index fe84005..0000000 --- a/packages/flutter_timeline_view/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -../../README.md \ No newline at end of file diff --git a/packages/flutter_timeline_view/CHANGELOG.md b/packages/flutter_timeline_view/CHANGELOG.md new file mode 120000 index 0000000..699cc9e --- /dev/null +++ b/packages/flutter_timeline_view/CHANGELOG.md @@ -0,0 +1 @@ +../../CHANGELOG.md \ No newline at end of file diff --git a/packages/flutter_timeline_view/LICENSE b/packages/flutter_timeline_view/LICENSE deleted file mode 100644 index 5678cdd..0000000 --- a/packages/flutter_timeline_view/LICENSE +++ /dev/null @@ -1 +0,0 @@ -[../../LICENSE](https://github.com/Iconica-Development/flutter_timeline/blob/master/LICENSE) \ No newline at end of file diff --git a/packages/flutter_timeline_view/LICENSE b/packages/flutter_timeline_view/LICENSE new file mode 120000 index 0000000..30cff74 --- /dev/null +++ b/packages/flutter_timeline_view/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file diff --git a/packages/flutter_timeline_view/README.md b/packages/flutter_timeline_view/README.md deleted file mode 100644 index 0b934f5..0000000 --- a/packages/flutter_timeline_view/README.md +++ /dev/null @@ -1 +0,0 @@ -[../../README.md](https://github.com/Iconica-Development/flutter_timeline/blob/master/README.md) \ No newline at end of file diff --git a/packages/flutter_timeline_view/README.md b/packages/flutter_timeline_view/README.md new file mode 120000 index 0000000..fe84005 --- /dev/null +++ b/packages/flutter_timeline_view/README.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file From 09b11dbbc7f0bebfc64fc131bcfe448bc9286b6b Mon Sep 17 00:00:00 2001 From: mike doornenbal Date: Thu, 22 Aug 2024 13:57:52 +0200 Subject: [PATCH 19/20] fix: update github action --- .github/workflows/melos-component-ci.yml | 2 +- .../flutter_timeline_view/lib/src/widgets/tappable_image.dart | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/melos-component-ci.yml b/.github/workflows/melos-component-ci.yml index 98255ce..2df0d4c 100644 --- a/.github/workflows/melos-component-ci.yml +++ b/.github/workflows/melos-component-ci.yml @@ -11,4 +11,4 @@ jobs: secrets: inherit permissions: write-all with: - flutter_version: 3.19.6 \ No newline at end of file + flutter_version: 3.24 \ No newline at end of file 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 9217c08..fcf2b48 100644 --- a/packages/flutter_timeline_view/lib/src/widgets/tappable_image.dart +++ b/packages/flutter_timeline_view/lib/src/widgets/tappable_image.dart @@ -141,6 +141,7 @@ class _HeartAnimationState extends State { unawaited( Future.delayed(const Duration(milliseconds: 100)).then((value) async { active = widget.liked; + // ignore: use_build_context_synchronously var navigator = Navigator.of(context); await Future.delayed(widget.duration); navigator.pop(); From c41f43bb2a0c39b30dfce0d08092c6e24bf198a7 Mon Sep 17 00:00:00 2001 From: mike doornenbal Date: Thu, 22 Aug 2024 14:42:13 +0200 Subject: [PATCH 20/20] fix: first item scrolling under categories --- .github/workflows/melos-component-ci.yml | 4 +- .../lib/src/screens/timeline_screen.dart | 2 +- .../lib/src/widgets/timeline_post_widget.dart | 46 ++++++++++++++----- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/.github/workflows/melos-component-ci.yml b/.github/workflows/melos-component-ci.yml index 2df0d4c..869bed9 100644 --- a/.github/workflows/melos-component-ci.yml +++ b/.github/workflows/melos-component-ci.yml @@ -9,6 +9,4 @@ jobs: call-global-iconica-workflow: uses: Iconica-Development/.github/.github/workflows/melos-ci.yml@master secrets: inherit - permissions: write-all - with: - flutter_version: 3.24 \ No newline at end of file + permissions: write-all \ No newline at end of file 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 d5c91fd..f7db082 100644 --- a/packages/flutter_timeline_view/lib/src/screens/timeline_screen.dart +++ b/packages/flutter_timeline_view/lib/src/screens/timeline_screen.dart @@ -95,7 +95,7 @@ class _TimelineScreenState extends State { void _updateIsOnTop() { setState(() { - _isOnTop = controller.position.pixels < 40; + _isOnTop = controller.position.pixels < 0.1; }); } 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 e7760a6..103aac9 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 @@ -55,6 +55,7 @@ class _TimelinePostWidgetState extends State { Widget build(BuildContext context) { var theme = Theme.of(context); var isLikedByUser = widget.post.likedBy?.contains(widget.userId) ?? false; + return SizedBox( height: widget.post.imageUrl != null || widget.post.image != null ? widget.options.postWidgetHeight @@ -65,7 +66,7 @@ class _TimelinePostWidgetState extends State { children: [ Row( children: [ - if (widget.post.creator != null) + if (widget.post.creator != null) ...[ InkWell( onTap: widget.onUserTap != null ? () => @@ -110,9 +111,10 @@ class _TimelinePostWidgetState extends State { ], ), ), + ], const Spacer(), if (widget.allowAllDeletion || - widget.post.creator?.userId == widget.userId) + widget.post.creator?.userId == widget.userId) ...[ PopupMenuButton( onSelected: (value) async { if (value == 'delete') { @@ -151,6 +153,7 @@ class _TimelinePostWidgetState extends State { color: widget.options.theme.iconColor, ), ), + ], ], ), // image of the post @@ -313,16 +316,9 @@ class _TimelinePostWidgetState extends State { post: widget.post, ), ] else ...[ - Text( - '${widget.post.likes} ' - '${widget.post.likes > 1 - ? widget.options.translations.multipleLikesTitle - : widget.options.translations.oneLikeTitle}', - style: - widget.options.theme.textStyles.listPostLikeTitleAndAmount ?? - theme.textTheme.titleSmall!.copyWith( - color: Colors.black, - ), + _PostLikeCountText( + post: widget.post, + options: widget.options, ), Text.rich( TextSpan( @@ -362,6 +358,32 @@ class _TimelinePostWidgetState extends State { } } +class _PostLikeCountText extends StatelessWidget { + const _PostLikeCountText({ + required this.post, + required this.options, + }); + + final TimelineOptions options; + final TimelinePost post; + @override + Widget build(BuildContext context) { + var theme = Theme.of(context); + var likeTranslation = post.likes > 1 + ? options.translations.multipleLikesTitle + : options.translations.oneLikeTitle; + + return Text( + '${post.likes} ' + '$likeTranslation', + style: options.theme.textStyles.listPostLikeTitleAndAmount ?? + theme.textTheme.titleSmall!.copyWith( + color: Colors.black, + ), + ); + } +} + Future showPostDeletionConfirmationDialog( TimelineOptions options, BuildContext context,