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/ +