wip: Big refactor and update of image picker package. Still missing some docs on some parameters

This commit is contained in:
Jacques 2024-02-29 10:34:49 +01:00
parent 898583d1d1
commit fe1dcd6d0d
21 changed files with 385 additions and 278 deletions

View file

@ -1,3 +1,8 @@
## 3.0.0
- Reorder of all parameters of TimelineOptions
- Updated Flutter Image Picker form 1.0.4 to 3.0.0
## 2.1.0 ## 2.1.0
- Fixed multiline textfield not being dismissible. - Fixed multiline textfield not being dismissible.

View file

@ -1,6 +1,6 @@
// import 'package:example/apps/go_router/app.dart'; // import 'package:example/apps/go_router/app.dart';
// import 'package:example/apps/navigator/app.dart'; import 'package:example/apps/navigator/app.dart';
import 'package:example/apps/widgets/app.dart'; // import 'package:example/apps/widgets/app.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/date_symbol_data_local.dart'; import 'package:intl/date_symbol_data_local.dart';
@ -9,7 +9,7 @@ void main() {
// Uncomment any, but only one, of these lines to run the example with specific navigation. // Uncomment any, but only one, of these lines to run the example with specific navigation.
runApp(const WidgetApp()); // runApp(const WidgetApp());
// runApp(const NavigatorApp()); runApp(const NavigatorApp());
// runApp(const GoRouterApp()); // runApp(const GoRouterApp());
} }

View file

@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
name: flutter_timeline name: flutter_timeline
description: Visual elements and interface combined into one package description: Visual elements and interface combined into one package
version: 2.1.0 version: 3.0.0
publish_to: none publish_to: none

View file

@ -4,7 +4,7 @@
name: flutter_timeline_firebase name: flutter_timeline_firebase
description: Implementation of the Flutter Timeline interface for Firebase. description: Implementation of the Flutter Timeline interface for Firebase.
version: 2.1.0 version: 3.0.0
publish_to: none publish_to: none

View file

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

View file

@ -4,6 +4,11 @@
/// ///
library flutter_timeline_view; library flutter_timeline_view;
export 'src/config/icon_timeline_theme.dart';
export 'src/config/post_config.dart';
export 'src/config/post_creation_config.dart';
export 'src/config/post_theme.dart';
export 'src/config/timeline_config.dart';
export 'src/config/timeline_options.dart'; export 'src/config/timeline_options.dart';
export 'src/config/timeline_styles.dart'; export 'src/config/timeline_styles.dart';
export 'src/config/timeline_theme.dart'; export 'src/config/timeline_theme.dart';

View file

@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: 2023 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/material.dart';
@immutable
class IconTimelineTheme {
const IconTimelineTheme({
this.iconColor,
this.likeIcon,
this.commentIcon,
this.likedIcon,
this.sendIcon,
this.moreIcon,
this.deleteIcon,
this.likeAndDislikeIconsForDoubleTap = const (
Icon(
Icons.favorite_rounded,
color: Color(0xFFC3007A),
),
null,
),
});
final Color? iconColor;
/// The icon to display when the post is not yet liked
final Widget? likeIcon;
/// The icon to display to indicate that a post has comments enabled
final Widget? commentIcon;
/// The icon to display when the post is liked
final Widget? likedIcon;
/// The icon to display to submit a comment
final Widget? sendIcon;
/// The icon for more actions (open delete menu)
final Widget? moreIcon;
/// The icon for delete action (delete post)
final Widget? deleteIcon;
/// The icons to display when double tap to like is enabled
final (Icon?, Icon?) likeAndDislikeIconsForDoubleTap;
}

View file

@ -0,0 +1,12 @@
// SPDX-FileCopyrightText: 2024 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
class TimelinePostConfig {
const TimelinePostConfig({
this.doubleTapTolike = false,
});
/// Whether to allow double tap to like
final bool doubleTapTolike;
}

View file

@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: 2024 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
class TimelinePostCreationConfig {
const TimelinePostCreationConfig({
this.requireImageForPost = false,
this.minTitleLength,
this.maxTitleLength,
this.minContentLength,
this.maxContentLength,
});
/// Require image for post
final bool requireImageForPost;
/// Minimum length of the title
final int? minTitleLength;
/// Maximum length of the title
final int? maxTitleLength;
/// Minimum length of the post content
final int? minContentLength;
/// Maximum length of the post content
final int? maxContentLength;
}

View file

@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: 2024 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/material.dart';
import 'package:flutter_timeline_view/src/config/timeline_options.dart';
class TimelinePostCreationTheme {
const TimelinePostCreationTheme({
this.buttonBuilder,
this.textInputBuilder,
this.pagePadding = const EdgeInsets.all(20),
});
final ButtonBuilder? buttonBuilder;
final TextInputBuilder? textInputBuilder;
final EdgeInsets pagePadding;
}

View file

@ -0,0 +1,63 @@
// SPDX-FileCopyrightText: 2024 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
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:intl/intl.dart';
class TimelinePostTheme {
const TimelinePostTheme({
this.timelinePostHeight,
this.iconsWithValues = false,
this.itemInfoBuilder,
this.dateFormat,
this.timeFormat,
this.userAvatarBuilder,
this.anonymousAvatarBuilder,
this.nameBuilder,
this.iconSize = 26,
this.postWidgetHeight,
this.postPadding = const EdgeInsets.all(12.0),
this.pagePadding = const EdgeInsets.all(20),
this.iconTheme = const IconTimelineTheme(),
});
/// The height of a post in the timeline
final double? timelinePostHeight;
/// Whether to display the icons with values
final bool iconsWithValues;
/// The builder for the item info, all below the like and comment buttons
final Widget Function({required TimelinePost post})? itemInfoBuilder;
/// The format to display the post date in
final DateFormat? dateFormat;
/// The format to display the post time in
final DateFormat? timeFormat;
final UserAvatarBuilder? userAvatarBuilder;
/// When the imageUrl is null this anonymousAvatarBuilder will be used
/// You can use it to display a default avatarW
final UserAvatarBuilder? anonymousAvatarBuilder;
final String Function(TimelinePosterUserModel?)? nameBuilder;
/// Size of icons like the comment and like icons. Dafualts to 26
final double iconSize;
/// Sets a predefined height for the postWidget.
final double? postWidgetHeight;
/// Padding of each post
final EdgeInsets postPadding;
final EdgeInsets pagePadding;
/// Theming options for the icons within timeline
final IconTimelineTheme iconTheme;
}

View file

@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: 2024 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
class TimelineConfig {
const TimelineConfig({
this.allowAllDeletion = false,
this.sortCommentsAscending = true,
this.sortPostsAscending,
});
/// Allow all posts to be deleted instead of
/// only the posts of the current user
final bool allowAllDeletion;
/// Whether to sort comments ascending or descending
final bool sortCommentsAscending;
/// Whether to sort posts ascending or descending
final bool? sortPostsAscending;
}

View file

@ -5,87 +5,35 @@ import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_image_picker/flutter_image_picker.dart'; import 'package:flutter_image_picker/flutter_image_picker.dart';
import 'package:flutter_timeline_interface/flutter_timeline_interface.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/post_config.dart';
import 'package:flutter_timeline_view/src/config/post_creation_config.dart';
import 'package:flutter_timeline_view/src/config/post_creation_theme.dart';
import 'package:flutter_timeline_view/src/config/post_theme.dart';
import 'package:flutter_timeline_view/src/config/timeline_config.dart';
import 'package:flutter_timeline_view/src/config/timeline_theme.dart'; import 'package:flutter_timeline_view/src/config/timeline_theme.dart';
import 'package:flutter_timeline_view/src/config/timeline_translations.dart';
import 'package:intl/intl.dart';
class TimelineOptions { class TimelineOptions {
const TimelineOptions({ const TimelineOptions({
this.theme = const TimelineTheme(), this.textStyles = const TimelineTextStyles(),
this.translations = const TimelineTranslations.empty(), this.translations = const TimelineTranslations.empty(),
this.imagePickerConfig = const ImagePickerConfig(), this.imagePickerConfig = const ImagePickerConfig(),
this.imagePickerTheme = const ImagePickerTheme(), this.imagePickerTheme = const ImagePickerTheme(),
this.timelinePostHeight,
this.allowAllDeletion = false,
this.sortCommentsAscending = true,
this.sortPostsAscending,
this.doubleTapTolike = false,
this.iconsWithValues = false,
this.likeAndDislikeIconsForDoubleTap = const (
Icon(
Icons.favorite_rounded,
color: Color(0xFFC3007A),
),
null,
),
this.itemInfoBuilder,
this.dateFormat,
this.timeFormat,
this.buttonBuilder,
this.textInputBuilder,
this.dividerBuilder,
this.userAvatarBuilder,
this.anonymousAvatarBuilder,
this.nameBuilder,
this.padding = const EdgeInsets.symmetric(vertical: 12.0),
this.iconSize = 26,
this.postWidgetHeight,
this.postPadding = const EdgeInsets.all(12.0),
this.filterOptions = const FilterOptions(), this.filterOptions = const FilterOptions(),
this.categoriesOptions = const CategoriesOptions(), this.categoriesOptions = const CategoriesOptions(),
this.requireImageForPost = false, this.postTheme = const TimelinePostTheme(),
this.minTitleLength, this.config = const TimelineConfig(),
this.maxTitleLength, this.postConfig = const TimelinePostConfig(),
this.minContentLength, this.theme = const TimelineTheme(),
this.maxContentLength, this.postCreationTheme = const TimelinePostCreationTheme(),
this.postCreationConfig = const TimelinePostCreationConfig(),
}); });
/// Theming options for the timeline /// Parameter to set all textstyles within timeline
final TimelineTheme theme; final TimelineTextStyles textStyles;
/// The format to display the post date in
final DateFormat? dateFormat;
/// The format to display the post time in
final DateFormat? timeFormat;
/// Whether to sort comments ascending or descending
final bool sortCommentsAscending;
/// Whether to sort posts ascending or descending
final bool? sortPostsAscending;
/// Allow all posts to be deleted instead of
/// only the posts of the current user
final bool allowAllDeletion;
/// The height of a post in the timeline
final double? timelinePostHeight;
final TimelineTranslations translations; final TimelineTranslations translations;
final ButtonBuilder? buttonBuilder;
final TextInputBuilder? textInputBuilder;
final UserAvatarBuilder? userAvatarBuilder;
/// When the imageUrl is null this anonymousAvatarBuilder will be used
/// You can use it to display a default avatarW
final UserAvatarBuilder? anonymousAvatarBuilder;
final String Function(TimelinePosterUserModel?)? nameBuilder;
/// ImagePickerTheme can be used to change the UI of the /// ImagePickerTheme can be used to change the UI of the
/// Image Picker Widget to change the text/icons to your liking. /// Image Picker Widget to change the text/icons to your liking.
final ImagePickerTheme imagePickerTheme; final ImagePickerTheme imagePickerTheme;
@ -94,53 +42,20 @@ class TimelineOptions {
/// size and quality for the uploaded image. /// size and quality for the uploaded image.
final ImagePickerConfig imagePickerConfig; final ImagePickerConfig imagePickerConfig;
/// Whether to allow double tap to like
final bool doubleTapTolike;
/// The icons to display when double tap to like is enabled
final (Icon?, Icon?) likeAndDislikeIconsForDoubleTap;
/// Whether to display the icons with values
final bool iconsWithValues;
/// The builder for the item info, all below the like and comment buttons
final Widget Function({required TimelinePost post})? itemInfoBuilder;
/// The builder for the divider
final Widget Function()? dividerBuilder;
/// The padding between posts in the timeline
final EdgeInsets padding;
/// Size of icons like the comment and like icons. Dafualts to 26
final double iconSize;
/// Sets a predefined height for the postWidget.
final double? postWidgetHeight;
/// Padding of each post
final EdgeInsets postPadding;
/// Options for filtering /// Options for filtering
final FilterOptions filterOptions; final FilterOptions filterOptions;
/// Options for using the category selector. /// Options for using the category selector.
final CategoriesOptions categoriesOptions; final CategoriesOptions categoriesOptions;
/// Require image for post final TimelineConfig config;
final bool requireImageForPost; final TimelineTheme theme;
/// Minimum length of the title final TimelinePostConfig postConfig;
final int? minTitleLength; final TimelinePostTheme postTheme;
/// Maximum length of the title final TimelinePostCreationTheme postCreationTheme;
final int? maxTitleLength; final TimelinePostCreationConfig postCreationConfig;
/// Minimum length of the post content
final int? minContentLength;
/// Maximum length of the post content
final int? maxContentLength;
} }
class CategoriesOptions { class CategoriesOptions {

View file

@ -1,42 +1,17 @@
// SPDX-FileCopyrightText: 2023 Iconica // SPDX-FileCopyrightText: 2024 Iconica
// //
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_timeline_view/src/config/timeline_styles.dart';
@immutable
class TimelineTheme { class TimelineTheme {
const TimelineTheme({ const TimelineTheme({
this.iconColor, this.dividerBuilder,
this.likeIcon, this.pagePadding = const EdgeInsets.all(20),
this.commentIcon,
this.likedIcon,
this.sendIcon,
this.moreIcon,
this.deleteIcon,
this.textStyles = const TimelineTextStyles(),
}); });
final Color? iconColor; /// The builder for the divider
final Widget Function()? dividerBuilder;
/// The icon to display when the post is not yet liked final EdgeInsets pagePadding;
final Widget? likeIcon;
/// The icon to display to indicate that a post has comments enabled
final Widget? commentIcon;
/// The icon to display when the post is liked
final Widget? likedIcon;
/// The icon to display to submit a comment
final Widget? sendIcon;
/// The icon for more actions (open delete menu)
final Widget? moreIcon;
/// The icon for delete action (delete post)
final Widget? deleteIcon;
final TimelineTextStyles textStyles;
} }

View file

@ -69,24 +69,28 @@ class _TimelinePostCreationScreenState
setState(() { setState(() {
editingDone = editingDone =
titleController.text.isNotEmpty && contentController.text.isNotEmpty; titleController.text.isNotEmpty && contentController.text.isNotEmpty;
if (widget.options.requireImageForPost) { if (widget.options.postCreationConfig.requireImageForPost) {
editingDone = editingDone && image != null; editingDone = editingDone && image != null;
} }
if (widget.options.minTitleLength != null) { if (widget.options.postCreationConfig.minTitleLength != null) {
editingDone = editingDone && editingDone = editingDone &&
titleController.text.length >= widget.options.minTitleLength!; titleController.text.length >=
widget.options.postCreationConfig.minTitleLength!;
} }
if (widget.options.maxTitleLength != null) { if (widget.options.postCreationConfig.maxTitleLength != null) {
editingDone = editingDone && editingDone = editingDone &&
titleController.text.length <= widget.options.maxTitleLength!; titleController.text.length <=
widget.options.postCreationConfig.maxTitleLength!;
} }
if (widget.options.minContentLength != null) { if (widget.options.postCreationConfig.minContentLength != null) {
editingDone = editingDone && editingDone = editingDone &&
contentController.text.length >= widget.options.minContentLength!; contentController.text.length >=
widget.options.postCreationConfig.minContentLength!;
} }
if (widget.options.maxContentLength != null) { if (widget.options.postCreationConfig.maxContentLength != null) {
editingDone = editingDone && editingDone = editingDone &&
contentController.text.length <= widget.options.maxContentLength!; contentController.text.length <=
widget.options.postCreationConfig.maxContentLength!;
} }
}); });
} }
@ -119,7 +123,7 @@ class _TimelinePostCreationScreenState
return GestureDetector( return GestureDetector(
onTap: () => FocusScope.of(context).unfocus(), onTap: () => FocusScope.of(context).unfocus(),
child: Padding( child: Padding(
padding: widget.options.padding, padding: widget.options.postCreationTheme.pagePadding,
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -129,7 +133,7 @@ class _TimelinePostCreationScreenState
widget.options.translations.title, widget.options.translations.title,
style: theme.textTheme.displaySmall, style: theme.textTheme.displaySmall,
), ),
widget.options.textInputBuilder?.call( widget.options.postCreationTheme.textInputBuilder?.call(
titleController, titleController,
null, null,
'', '',
@ -185,8 +189,8 @@ class _TimelinePostCreationScreenState
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
color: theme.colorScheme.background, color: theme.colorScheme.background,
child: ImagePicker( child: ImagePicker(
imagePickerConfig: widget.options.imagePickerConfig, config: widget.options.imagePickerConfig,
imagePickerTheme: widget.options.imagePickerTheme, theme: widget.options.imagePickerTheme,
), ),
), ),
); );
@ -271,8 +275,8 @@ class _TimelinePostCreationScreenState
), ),
Align( Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: (widget.options.buttonBuilder != null) child: (widget.options.postCreationTheme.buttonBuilder != null)
? widget.options.buttonBuilder!( ? widget.options.postCreationTheme.buttonBuilder!(
context, context,
onPostCreated, onPostCreated,
widget.options.translations.checkPost, widget.options.translations.checkPost,

View file

@ -86,15 +86,16 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
TimelinePost? post; TimelinePost? post;
bool isLoading = true; bool isLoading = true;
late var textInputBuilder = widget.options.textInputBuilder ?? late var textInputBuilder =
(controller, suffixIcon, hintText) => TextField( widget.options.postCreationTheme.textInputBuilder ??
textCapitalization: TextCapitalization.sentences, (controller, suffixIcon, hintText) => TextField(
controller: controller, textCapitalization: TextCapitalization.sentences,
decoration: InputDecoration( controller: controller,
hintText: hintText, decoration: InputDecoration(
suffixIcon: suffixIcon, hintText: hintText,
), suffixIcon: suffixIcon,
); ),
);
@override @override
void initState() { void initState() {
@ -129,9 +130,9 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var theme = Theme.of(context); var theme = Theme.of(context);
var dateFormat = widget.options.dateFormat ?? var dateFormat = widget.options.postTheme.dateFormat ??
DateFormat('dd/MM/yyyy', Localizations.localeOf(context).languageCode); DateFormat('dd/MM/yyyy', Localizations.localeOf(context).languageCode);
var timeFormat = widget.options.timeFormat ?? DateFormat('HH:mm'); var timeFormat = widget.options.postTheme.timeFormat ?? DateFormat('HH:mm');
if (isLoading) { if (isLoading) {
const Center( const Center(
@ -142,13 +143,13 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
return Center( return Center(
child: Text( child: Text(
widget.options.translations.postLoadingError, widget.options.translations.postLoadingError,
style: widget.options.theme.textStyles.errorTextStyle, style: widget.options.textStyles.errorTextStyle,
), ),
); );
} }
var post = this.post!; var post = this.post!;
post.reactions?.sort( post.reactions?.sort(
(a, b) => widget.options.sortCommentsAscending (a, b) => widget.options.config.sortCommentsAscending
? a.createdAt.compareTo(b.createdAt) ? a.createdAt.compareTo(b.createdAt)
: b.createdAt.compareTo(a.createdAt), : b.createdAt.compareTo(a.createdAt),
); );
@ -167,7 +168,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
}, },
child: SingleChildScrollView( child: SingleChildScrollView(
child: Padding( child: Padding(
padding: widget.options.padding, padding: widget.options.postTheme.pagePadding,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -182,7 +183,8 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
child: Row( child: Row(
children: [ children: [
if (post.creator!.imageUrl != null) ...[ if (post.creator!.imageUrl != null) ...[
widget.options.userAvatarBuilder?.call( widget.options.postTheme.userAvatarBuilder
?.call(
post.creator!, post.creator!,
40, 40,
) ?? ) ??
@ -194,7 +196,8 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
), ),
), ),
] else ...[ ] else ...[
widget.options.anonymousAvatarBuilder?.call( widget.options.postTheme.anonymousAvatarBuilder
?.call(
post.creator!, post.creator!,
40, 40,
) ?? ) ??
@ -207,11 +210,11 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
], ],
const SizedBox(width: 10), const SizedBox(width: 10),
Text( Text(
widget.options.nameBuilder widget.options.postTheme.nameBuilder
?.call(post.creator) ?? ?.call(post.creator) ??
post.creator?.fullName ?? post.creator?.fullName ??
widget.options.translations.anonymousUser, widget.options.translations.anonymousUser,
style: widget.options.theme.textStyles style: widget.options.textStyles
.postCreatorTitleStyle ?? .postCreatorTitleStyle ??
theme.textTheme.titleMedium, theme.textTheme.titleMedium,
), ),
@ -219,7 +222,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
), ),
), ),
const Spacer(), const Spacer(),
if (widget.options.allowAllDeletion || if (widget.options.config.allowAllDeletion ||
post.creator?.userId == widget.userId) post.creator?.userId == widget.userId)
PopupMenuButton( PopupMenuButton(
onSelected: (value) => widget.onPostDelete(), onSelected: (value) => widget.onPostDelete(),
@ -231,24 +234,27 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
children: [ children: [
Text( Text(
widget.options.translations.deletePost, widget.options.translations.deletePost,
style: widget.options.theme.textStyles style: widget.options.textStyles
.deletePostStyle ?? .deletePostStyle ??
theme.textTheme.bodyMedium, theme.textTheme.bodyMedium,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
widget.options.theme.deleteIcon ?? widget.options.postTheme.iconTheme
.deleteIcon ??
Icon( Icon(
Icons.delete, Icons.delete,
color: widget.options.theme.iconColor, color: widget.options.postTheme
.iconTheme.iconColor,
), ),
], ],
), ),
), ),
], ],
child: widget.options.theme.moreIcon ?? child: widget.options.postTheme.iconTheme.moreIcon ??
Icon( Icon(
Icons.more_horiz_rounded, Icons.more_horiz_rounded,
color: widget.options.theme.iconColor, color: widget
.options.postTheme.iconTheme.iconColor,
), ),
), ),
], ],
@ -258,10 +264,10 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
const SizedBox(height: 8), const SizedBox(height: 8),
ClipRRect( ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)), borderRadius: const BorderRadius.all(Radius.circular(8)),
child: widget.options.doubleTapTolike child: widget.options.postConfig.doubleTapTolike
? TappableImage( ? TappableImage(
likeAndDislikeIcon: widget likeAndDislikeIcon: widget.options.postTheme
.options.likeAndDislikeIconsForDoubleTap, .iconTheme.likeAndDislikeIconsForDoubleTap,
post: post, post: post,
userId: widget.userId, userId: widget.userId,
onLike: ({required bool liked}) async { onLike: ({required bool liked}) async {
@ -314,11 +320,13 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
}, },
child: Container( child: Container(
color: Colors.transparent, color: Colors.transparent,
child: widget.options.theme.likedIcon ?? child:
Icon( widget.options.postTheme.iconTheme.likedIcon ??
Icons.thumb_up_rounded, Icon(
color: widget.options.theme.iconColor, Icons.thumb_up_rounded,
), color: widget.options.postTheme.iconTheme
.iconColor,
),
), ),
), ),
] else ...[ ] else ...[
@ -333,49 +341,50 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
}, },
child: Container( child: Container(
color: Colors.transparent, color: Colors.transparent,
child: widget.options.theme.likeIcon ?? child:
Icon( widget.options.postTheme.iconTheme.likeIcon ??
Icons.thumb_up_alt_outlined, Icon(
color: widget.options.theme.iconColor, Icons.thumb_up_alt_outlined,
size: widget.options.iconSize, color: widget.options.postTheme.iconTheme
), .iconColor,
size: widget.options.postTheme.iconSize,
),
), ),
), ),
], ],
const SizedBox(width: 8), const SizedBox(width: 8),
if (post.reactionEnabled) if (post.reactionEnabled)
widget.options.theme.commentIcon ?? widget.options.postTheme.iconTheme.commentIcon ??
Icon( Icon(
Icons.chat_bubble_outline_rounded, Icons.chat_bubble_outline_rounded,
color: widget.options.theme.iconColor, color:
size: widget.options.iconSize, widget.options.postTheme.iconTheme.iconColor,
size: widget.options.postTheme.iconSize,
), ),
], ],
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'${post.likes} ${widget.options.translations.likesTitle}', '${post.likes} ${widget.options.translations.likesTitle}',
style: widget style: widget.options.textStyles.postLikeTitleAndAmount ??
.options.theme.textStyles.postLikeTitleAndAmount ??
theme.textTheme.titleSmall theme.textTheme.titleSmall
?.copyWith(color: Colors.black), ?.copyWith(color: Colors.black),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text.rich( Text.rich(
TextSpan( TextSpan(
text: widget.options.nameBuilder?.call(post.creator) ?? text: widget.options.postTheme.nameBuilder
?.call(post.creator) ??
post.creator?.fullName ?? post.creator?.fullName ??
widget.options.translations.anonymousUser, widget.options.translations.anonymousUser,
style: widget style: widget.options.textStyles.postCreatorNameStyle ??
.options.theme.textStyles.postCreatorNameStyle ??
theme.textTheme.titleSmall, theme.textTheme.titleSmall,
children: [ children: [
const TextSpan(text: ' '), const TextSpan(text: ' '),
TextSpan( TextSpan(
text: post.title, text: post.title,
style: style: widget.options.textStyles.postTitleStyle ??
widget.options.theme.textStyles.postTitleStyle ?? theme.textTheme.bodyMedium,
theme.textTheme.bodyMedium,
), ),
], ],
), ),
@ -427,7 +436,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
GestureDetector( GestureDetector(
onLongPressStart: (details) async { onLongPressStart: (details) async {
if (reaction.creatorId == widget.userId || if (reaction.creatorId == widget.userId ||
widget.options.allowAllDeletion) { widget.options.config.allowAllDeletion) {
var overlay = Overlay.of(context) var overlay = Overlay.of(context)
.context .context
.findRenderObject()! as RenderBox; .findRenderObject()! as RenderBox;
@ -467,7 +476,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
children: [ children: [
if (reaction.creator?.imageUrl != null && if (reaction.creator?.imageUrl != null &&
reaction.creator!.imageUrl!.isNotEmpty) ...[ reaction.creator!.imageUrl!.isNotEmpty) ...[
widget.options.userAvatarBuilder?.call( widget.options.postTheme.userAvatarBuilder?.call(
reaction.creator!, reaction.creator!,
25, 25,
) ?? ) ??
@ -478,7 +487,8 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
), ),
), ),
] else ...[ ] else ...[
widget.options.anonymousAvatarBuilder?.call( widget.options.postTheme.anonymousAvatarBuilder
?.call(
reaction.creator!, reaction.creator!,
25, 25,
) ?? ) ??
@ -496,7 +506,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
widget.options.nameBuilder widget.options.postTheme.nameBuilder
?.call(post.creator) ?? ?.call(post.creator) ??
reaction.creator?.fullName ?? reaction.creator?.fullName ??
widget.options.translations widget.options.translations
@ -517,7 +527,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
Expanded( Expanded(
child: Text.rich( child: Text.rich(
TextSpan( TextSpan(
text: widget.options.nameBuilder text: widget.options.postTheme.nameBuilder
?.call(post.creator) ?? ?.call(post.creator) ??
reaction.creator?.fullName ?? reaction.creator?.fullName ??
widget widget
@ -565,8 +575,8 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
color: theme.colorScheme.background, color: theme.colorScheme.background,
child: ImagePicker( child: ImagePicker(
imagePickerConfig: widget.options.imagePickerConfig, config: widget.options.imagePickerConfig,
imagePickerTheme: widget.options.imagePickerTheme, theme: widget.options.imagePickerTheme,
), ),
), ),
); );
@ -598,7 +608,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
), ),
), ),
translations: widget.options.translations, translations: widget.options.translations,
iconColor: widget.options.theme.iconColor, iconColor: widget.options.postTheme.iconTheme.iconColor,
), ),
), ),
], ],

View file

@ -110,9 +110,9 @@ class _TimelineScreenState extends State<TimelineScreen> {
.toList(); .toList();
// sort posts by date // sort posts by date
if (widget.options.sortPostsAscending != null) { if (widget.options.config.sortPostsAscending != null) {
posts.sort( posts.sort(
(a, b) => widget.options.sortPostsAscending! (a, b) => widget.options.config.sortPostsAscending!
? a.createdAt.compareTo(b.createdAt) ? a.createdAt.compareTo(b.createdAt)
: b.createdAt.compareTo(a.createdAt), : b.createdAt.compareTo(a.createdAt),
); );
@ -122,12 +122,12 @@ class _TimelineScreenState extends State<TimelineScreen> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox( SizedBox(
height: widget.options.padding.top, height: widget.options.postTheme.postPadding.top,
), ),
if (widget.filterEnabled) ...[ if (widget.filterEnabled) ...[
Padding( Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: widget.options.padding.horizontal, horizontal: widget.options.postTheme.postPadding.horizontal,
), ),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
@ -203,7 +203,7 @@ class _TimelineScreenState extends State<TimelineScreen> {
children: [ children: [
...posts.map( ...posts.map(
(post) => Padding( (post) => Padding(
padding: widget.options.postPadding, padding: widget.options.postTheme.postPadding,
child: widget.postWidgetBuilder?.call(post) ?? child: widget.postWidgetBuilder?.call(post) ??
TimelinePostWidget( TimelinePostWidget(
service: service, service: service,
@ -253,7 +253,7 @@ class _TimelineScreenState extends State<TimelineScreen> {
category == null category == null
? widget.options.translations.noPosts ? widget.options.translations.noPosts
: widget.options.translations.noPostsWithFilter, : widget.options.translations.noPostsWithFilter,
style: widget.options.theme.textStyles.noPostsStyle, style: widget.options.textStyles.noPostsStyle,
), ),
), ),
), ),
@ -262,7 +262,7 @@ class _TimelineScreenState extends State<TimelineScreen> {
), ),
), ),
SizedBox( SizedBox(
height: widget.options.padding.bottom, height: widget.options.postTheme.postPadding.bottom,
), ),
], ],
); );

View file

@ -31,9 +31,8 @@ class TimelineSelectionScreen extends StatelessWidget {
padding: EdgeInsets.only(top: size.height * 0.05, bottom: 8), padding: EdgeInsets.only(top: size.height * 0.05, bottom: 8),
child: Text( child: Text(
options.translations.timelineSelectionDescription, options.translations.timelineSelectionDescription,
style: style: options.textStyles.categorySelectionDescriptionStyle ??
options.theme.textStyles.categorySelectionDescriptionStyle ?? theme.textTheme.displayMedium,
theme.textTheme.displayMedium,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
@ -55,7 +54,7 @@ class TimelineSelectionScreen extends StatelessWidget {
margin: const EdgeInsets.symmetric(vertical: 8), margin: const EdgeInsets.symmetric(vertical: 8),
child: Text( child: Text(
category.title, category.title,
style: options.theme.textStyles.categorySelectionTitleStyle ?? style: options.textStyles.categorySelectionTitleStyle ??
theme.textTheme.displaySmall, theme.textTheme.displaySmall,
), ),
), ),

View file

@ -28,9 +28,9 @@ class CategorySelector extends StatelessWidget {
child: Row( child: Row(
children: [ children: [
SizedBox( SizedBox(
width: width: options
options.categoriesOptions.categorySelectorHorizontalPadding ?? .categoriesOptions.categorySelectorHorizontalPadding ??
max(options.padding.horizontal - 4, 0), max(options.postCreationTheme.pagePadding.horizontal - 4, 0),
), ),
for (var category in categories) ...[ for (var category in categories) ...[
options.categoriesOptions.categoryButtonBuilder?.call( options.categoriesOptions.categoryButtonBuilder?.call(
@ -49,9 +49,9 @@ class CategorySelector extends StatelessWidget {
), ),
], ],
SizedBox( SizedBox(
width: width: options
options.categoriesOptions.categorySelectorHorizontalPadding ?? .categoriesOptions.categorySelectorHorizontalPadding ??
max(options.padding.horizontal - 4, 0), max(options.postCreationTheme.pagePadding.horizontal - 4, 0),
), ),
], ],
), ),

View file

@ -50,7 +50,7 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
onTap: widget.onTap, onTap: widget.onTap,
child: SizedBox( child: SizedBox(
height: widget.post.imageUrl != null height: widget.post.imageUrl != null
? widget.options.postWidgetHeight ? widget.options.postTheme.postWidgetHeight
: null, : null,
width: double.infinity, width: double.infinity,
child: Column( child: Column(
@ -67,7 +67,7 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
child: Row( child: Row(
children: [ children: [
if (widget.post.creator!.imageUrl != null) ...[ if (widget.post.creator!.imageUrl != null) ...[
widget.options.userAvatarBuilder?.call( widget.options.postTheme.userAvatarBuilder?.call(
widget.post.creator!, widget.post.creator!,
40, 40,
) ?? ) ??
@ -78,7 +78,7 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
), ),
), ),
] else ...[ ] else ...[
widget.options.anonymousAvatarBuilder?.call( widget.options.postTheme.anonymousAvatarBuilder?.call(
widget.post.creator!, widget.post.creator!,
40, 40,
) ?? ) ??
@ -91,19 +91,19 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
], ],
const SizedBox(width: 10), const SizedBox(width: 10),
Text( Text(
widget.options.nameBuilder widget.options.postTheme.nameBuilder
?.call(widget.post.creator) ?? ?.call(widget.post.creator) ??
widget.post.creator?.fullName ?? widget.post.creator?.fullName ??
widget.options.translations.anonymousUser, widget.options.translations.anonymousUser,
style: widget.options.theme.textStyles style:
.postCreatorTitleStyle ?? widget.options.textStyles.postCreatorTitleStyle ??
theme.textTheme.titleMedium, theme.textTheme.titleMedium,
), ),
], ],
), ),
), ),
const Spacer(), const Spacer(),
if (widget.options.allowAllDeletion || if (widget.options.config.allowAllDeletion ||
widget.post.creator?.userId == widget.userId) widget.post.creator?.userId == widget.userId)
PopupMenuButton( PopupMenuButton(
onSelected: (value) { onSelected: (value) {
@ -119,24 +119,25 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
children: [ children: [
Text( Text(
widget.options.translations.deletePost, widget.options.translations.deletePost,
style: widget.options.theme.textStyles style:
.deletePostStyle ?? widget.options.textStyles.deletePostStyle ??
theme.textTheme.bodyMedium, theme.textTheme.bodyMedium,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
widget.options.theme.deleteIcon ?? widget.options.postTheme.iconTheme.deleteIcon ??
Icon( Icon(
Icons.delete, Icons.delete,
color: widget.options.theme.iconColor, color: widget
.options.postTheme.iconTheme.iconColor,
), ),
], ],
), ),
), ),
], ],
child: widget.options.theme.moreIcon ?? child: widget.options.postTheme.iconTheme.moreIcon ??
Icon( Icon(
Icons.more_horiz_rounded, Icons.more_horiz_rounded,
color: widget.options.theme.iconColor, color: widget.options.postTheme.iconTheme.iconColor,
), ),
), ),
], ],
@ -145,13 +146,13 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
if (widget.post.imageUrl != null) ...[ if (widget.post.imageUrl != null) ...[
const SizedBox(height: 8), const SizedBox(height: 8),
Flexible( Flexible(
flex: widget.options.postWidgetHeight != null ? 1 : 0, flex: widget.options.postTheme.postWidgetHeight != null ? 1 : 0,
child: ClipRRect( child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)), borderRadius: const BorderRadius.all(Radius.circular(8)),
child: widget.options.doubleTapTolike child: widget.options.postConfig.doubleTapTolike
? TappableImage( ? TappableImage(
likeAndDislikeIcon: likeAndDislikeIcon: widget.options.postTheme.iconTheme
widget.options.likeAndDislikeIconsForDoubleTap, .likeAndDislikeIconsForDoubleTap,
post: widget.post, post: widget.post,
userId: widget.userId, userId: widget.userId,
onLike: ({required bool liked}) async { onLike: ({required bool liked}) async {
@ -188,7 +189,7 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
height: 8, height: 8,
), ),
// post information // post information
if (widget.options.iconsWithValues) if (widget.options.postTheme.iconsWithValues)
Row( Row(
children: [ children: [
TextButton.icon( TextButton.icon(
@ -210,7 +211,7 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
); );
} }
}, },
icon: widget.options.theme.likeIcon ?? icon: widget.options.postTheme.iconTheme.likeIcon ??
Icon( Icon(
widget.post.likedBy?.contains(widget.userId) ?? false widget.post.likedBy?.contains(widget.userId) ?? false
? Icons.favorite ? Icons.favorite
@ -221,7 +222,7 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
if (widget.post.reactionEnabled) if (widget.post.reactionEnabled)
TextButton.icon( TextButton.icon(
onPressed: widget.onTap, onPressed: widget.onTap,
icon: widget.options.theme.commentIcon ?? icon: widget.options.postTheme.iconTheme.commentIcon ??
const Icon( const Icon(
Icons.chat_bubble_outline_outlined, Icons.chat_bubble_outline_outlined,
), ),
@ -238,11 +239,12 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
onTap: widget.onTapUnlike, onTap: widget.onTapUnlike,
child: Container( child: Container(
color: Colors.transparent, color: Colors.transparent,
child: widget.options.theme.likedIcon ?? child: widget.options.postTheme.iconTheme.likedIcon ??
Icon( Icon(
Icons.thumb_up_rounded, Icons.thumb_up_rounded,
color: widget.options.theme.iconColor, color:
size: widget.options.iconSize, widget.options.postTheme.iconTheme.iconColor,
size: widget.options.postTheme.iconSize,
), ),
), ),
), ),
@ -251,11 +253,12 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
onTap: widget.onTapLike, onTap: widget.onTapLike,
child: Container( child: Container(
color: Colors.transparent, color: Colors.transparent,
child: widget.options.theme.likedIcon ?? child: widget.options.postTheme.iconTheme.likedIcon ??
Icon( Icon(
Icons.thumb_up_rounded, Icons.thumb_up_rounded,
color: widget.options.theme.iconColor, color:
size: widget.options.iconSize, widget.options.postTheme.iconTheme.iconColor,
size: widget.options.postTheme.iconSize,
), ),
), ),
), ),
@ -264,11 +267,11 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
if (widget.post.reactionEnabled) ...[ if (widget.post.reactionEnabled) ...[
Container( Container(
color: Colors.transparent, color: Colors.transparent,
child: widget.options.theme.commentIcon ?? child: widget.options.postTheme.iconTheme.commentIcon ??
Icon( Icon(
Icons.chat_bubble_outline_rounded, Icons.chat_bubble_outline_rounded,
color: widget.options.theme.iconColor, color: widget.options.postTheme.iconTheme.iconColor,
size: widget.options.iconSize, size: widget.options.postTheme.iconSize,
), ),
), ),
], ],
@ -279,33 +282,32 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
height: 8, height: 8,
), ),
if (widget.options.itemInfoBuilder != null) ...[ if (widget.options.postTheme.itemInfoBuilder != null) ...[
widget.options.itemInfoBuilder!( widget.options.postTheme.itemInfoBuilder!(
post: widget.post, post: widget.post,
), ),
] else ...[ ] else ...[
Text( Text(
'${widget.post.likes} ' '${widget.post.likes} '
'${widget.options.translations.likesTitle}', '${widget.options.translations.likesTitle}',
style: widget style: widget.options.textStyles.listPostLikeTitleAndAmount ??
.options.theme.textStyles.listPostLikeTitleAndAmount ??
theme.textTheme.titleSmall, theme.textTheme.titleSmall,
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text.rich( Text.rich(
TextSpan( TextSpan(
text: widget.options.nameBuilder?.call(widget.post.creator) ?? text: widget.options.postTheme.nameBuilder
?.call(widget.post.creator) ??
widget.post.creator?.fullName ?? widget.post.creator?.fullName ??
widget.options.translations.anonymousUser, widget.options.translations.anonymousUser,
style: widget.options.theme.textStyles.listCreatorNameStyle ?? style: widget.options.textStyles.listCreatorNameStyle ??
theme.textTheme.titleSmall, theme.textTheme.titleSmall,
children: [ children: [
const TextSpan(text: ' '), const TextSpan(text: ' '),
TextSpan( TextSpan(
text: widget.post.title, text: widget.post.title,
style: style: widget.options.textStyles.listPostTitleStyle ??
widget.options.theme.textStyles.listPostTitleStyle ?? theme.textTheme.bodyMedium,
theme.textTheme.bodyMedium,
), ),
], ],
), ),
@ -313,12 +315,12 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
widget.options.translations.viewPost, widget.options.translations.viewPost,
style: widget.options.theme.textStyles.viewPostStyle ?? style: widget.options.textStyles.viewPostStyle ??
theme.textTheme.bodySmall, theme.textTheme.bodySmall,
), ),
], ],
if (widget.options.dividerBuilder != null) if (widget.options.theme.dividerBuilder != null)
widget.options.dividerBuilder!(), widget.options.theme.dividerBuilder!(),
], ],
), ),
), ),

View file

@ -4,7 +4,7 @@
name: flutter_timeline_view name: flutter_timeline_view
description: Visual elements of the Flutter Timeline Component description: Visual elements of the Flutter Timeline Component
version: 2.1.0 version: 3.0.0
publish_to: none publish_to: none
@ -27,7 +27,7 @@ dependencies:
flutter_image_picker: flutter_image_picker:
git: git:
url: https://github.com/Iconica-Development/flutter_image_picker url: https://github.com/Iconica-Development/flutter_image_picker
ref: 1.0.4 ref: 3.0.0
collection: any collection: any
dev_dependencies: dev_dependencies: