feat: add textstyling options

This commit is contained in:
Freek van de Ven 2023-12-06 16:17:49 +01:00
parent 7e89ba9c85
commit cf129c05c0
9 changed files with 221 additions and 39 deletions

View file

@ -5,6 +5,7 @@
library flutter_timeline_view; library flutter_timeline_view;
export 'src/config/timeline_options.dart'; export 'src/config/timeline_options.dart';
export 'src/config/timeline_styles.dart';
export 'src/config/timeline_theme.dart'; export 'src/config/timeline_theme.dart';
export 'src/config/timeline_translations.dart'; export 'src/config/timeline_translations.dart';
export 'src/screens/timeline_post_creation_screen.dart'; export 'src/screens/timeline_post_creation_screen.dart';

View file

@ -12,7 +12,7 @@ import 'package:intl/intl.dart';
class TimelineOptions { class TimelineOptions {
const TimelineOptions({ const TimelineOptions({
this.theme = const TimelineTheme(), this.theme = const TimelineTheme(),
this.translations = const TimelineTranslations(), this.translations = const TimelineTranslations.empty(),
this.imagePickerConfig = const ImagePickerConfig(), this.imagePickerConfig = const ImagePickerConfig(),
this.imagePickerTheme = const ImagePickerTheme(), this.imagePickerTheme = const ImagePickerTheme(),
this.timelinePostHeight, this.timelinePostHeight,

View file

@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
@immutable
class TimelineTextStyles {
/// Options to update all the texts in the timeline view
/// with different textstyles
const TimelineTextStyles({
this.viewPostStyle,
this.listPostTitleStyle,
this.listPostCreatorTitleStyle,
this.listCreatorNameStyle,
this.listPostLikeTitleAndAmount,
this.deletePostStyle,
this.categorySelectionDescriptionStyle,
this.categorySelectionTitleStyle,
this.noPostsStyle,
this.errorTextStyle,
this.postCreatorTitleStyle,
this.postCreatorNameStyle,
this.postTitleStyle,
this.postLikeTitleAndAmount,
this.postCreatedAtStyle,
});
/// The TextStyle for the text indicating that you can view a post
final TextStyle? viewPostStyle;
/// The TextStyle for the creatorname at the top of the card
/// when it is in the list
final TextStyle? listPostCreatorTitleStyle;
/// The TextStyle for the post title when it is in the list
final TextStyle? listPostTitleStyle;
/// The TextStyle for the creatorname at the bottom of the card
/// when it is in the list
final TextStyle? listCreatorNameStyle;
/// The TextStyle for the amount of like and name of the likes at
/// the bottom of the card when it is in the list
final TextStyle? listPostLikeTitleAndAmount;
/// The TextStyle for the deletion text that shows in the popupmenu
final TextStyle? deletePostStyle;
/// The TextStyle for the category explainer on the selection page
final TextStyle? categorySelectionDescriptionStyle;
/// The TextStyle for the category items in the list on the selection page
final TextStyle? categorySelectionTitleStyle;
/// The TextStyle for the text when there are no posts
final TextStyle? noPostsStyle;
/// The TextStyle for all error texts
final TextStyle? errorTextStyle;
/// The TextStyle for the creatorname at the top of the post page
final TextStyle? postCreatorTitleStyle;
/// The TextStyle for the creatorname at the bottom of the post page
final TextStyle? postCreatorNameStyle;
/// The TextStyle for the title of the post on the post page
final TextStyle? postTitleStyle;
/// The TextStyle for the amount of likes and name of the likes
/// on the post page
final TextStyle? postLikeTitleAndAmount;
/// The TextStyle for the creation time of the post
final TextStyle? postCreatedAtStyle;
}

View file

@ -3,6 +3,7 @@
// 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 @immutable
class TimelineTheme { class TimelineTheme {
@ -14,6 +15,7 @@ class TimelineTheme {
this.sendIcon, this.sendIcon,
this.moreIcon, this.moreIcon,
this.deleteIcon, this.deleteIcon,
this.textStyles = const TimelineTextStyles(),
}); });
final Color? iconColor; final Color? iconColor;
@ -35,4 +37,6 @@ class TimelineTheme {
/// The icon for delete action (delete post) /// The icon for delete action (delete post)
final Widget? deleteIcon; final Widget? deleteIcon;
final TimelineTextStyles textStyles;
} }

View file

@ -7,30 +7,53 @@ import 'package:flutter/material.dart';
@immutable @immutable
class TimelineTranslations { class TimelineTranslations {
const TimelineTranslations({ const TimelineTranslations({
this.anonymousUser = 'Anonymous user', required this.anonymousUser,
this.noPosts = 'No posts yet', required this.noPosts,
this.noPostsWithFilter = 'No posts with this filter', required this.noPostsWithFilter,
this.title = 'Title', required this.title,
this.content = 'Content', required this.content,
this.contentDescription = 'What do you want to share?', required this.contentDescription,
this.uploadImage = 'Upload image', required this.uploadImage,
this.uploadImageDescription = 'Upload an image to your message (optional)', required this.uploadImageDescription,
this.allowComments = 'Are people allowed to comment?', required this.allowComments,
this.allowCommentsDescription = required this.allowCommentsDescription,
'Indicate whether people are allowed to respond', required this.checkPost,
this.checkPost = 'Check post overview', required this.deletePost,
this.deletePost = 'Delete post', required this.deleteReaction,
this.deleteReaction = 'Delete Reaction', required this.viewPost,
this.viewPost = 'View post', required this.likesTitle,
this.likesTitle = 'Likes', required this.commentsTitle,
this.commentsTitle = 'Comments', required this.firstComment,
this.firstComment = 'Be the first to comment', required this.writeComment,
this.writeComment = 'Write your comment here...', required this.postAt,
this.postAt = 'at', required this.postLoadingError,
this.postLoadingError = 'Something went wrong while loading the post', required this.timelineSelectionDescription,
this.timelineSelectionDescription = 'Choose a category',
}); });
const TimelineTranslations.empty()
: anonymousUser = 'Anonymous user',
noPosts = 'No posts yet',
noPostsWithFilter = 'No posts with this filter',
title = 'Title',
content = 'Content',
contentDescription = 'What do you want to share?',
uploadImage = 'Upload image',
uploadImageDescription = 'Upload an image to your message (optional)',
allowComments = 'Are people allowed to comment?',
allowCommentsDescription =
'Indicate whether people are allowed to respond',
checkPost = 'Check post overview',
deletePost = 'Delete post',
deleteReaction = 'Delete Reaction',
viewPost = 'View post',
likesTitle = 'Likes',
commentsTitle = 'Comments',
firstComment = 'Be the first to comment',
writeComment = 'Write your comment here...',
postAt = 'at',
postLoadingError = 'Something went wrong while loading the post',
timelineSelectionDescription = 'Choose a category';
final String noPosts; final String noPosts;
final String noPostsWithFilter; final String noPostsWithFilter;
final String anonymousUser; final String anonymousUser;
@ -55,4 +78,54 @@ class TimelineTranslations {
final String postLoadingError; final String postLoadingError;
final String timelineSelectionDescription; final String timelineSelectionDescription;
TimelineTranslations copyWith({
String? noPosts,
String? noPostsWithFilter,
String? anonymousUser,
String? title,
String? content,
String? contentDescription,
String? uploadImage,
String? uploadImageDescription,
String? allowComments,
String? allowCommentsDescription,
String? checkPost,
String? postAt,
String? deletePost,
String? deleteReaction,
String? viewPost,
String? likesTitle,
String? commentsTitle,
String? writeComment,
String? firstComment,
String? postLoadingError,
String? timelineSelectionDescription,
}) =>
TimelineTranslations(
noPosts: noPosts ?? this.noPosts,
noPostsWithFilter: noPostsWithFilter ?? this.noPostsWithFilter,
anonymousUser: anonymousUser ?? this.anonymousUser,
title: title ?? this.title,
content: content ?? this.content,
contentDescription: contentDescription ?? this.contentDescription,
uploadImage: uploadImage ?? this.uploadImage,
uploadImageDescription:
uploadImageDescription ?? this.uploadImageDescription,
allowComments: allowComments ?? this.allowComments,
allowCommentsDescription:
allowCommentsDescription ?? this.allowCommentsDescription,
checkPost: checkPost ?? this.checkPost,
postAt: postAt ?? this.postAt,
deletePost: deletePost ?? this.deletePost,
deleteReaction: deleteReaction ?? this.deleteReaction,
viewPost: viewPost ?? this.viewPost,
likesTitle: likesTitle ?? this.likesTitle,
commentsTitle: commentsTitle ?? this.commentsTitle,
writeComment: writeComment ?? this.writeComment,
firstComment: firstComment ?? this.firstComment,
postLoadingError: postLoadingError ?? this.postLoadingError,
timelineSelectionDescription:
timelineSelectionDescription ?? this.timelineSelectionDescription,
);
} }

View file

@ -100,7 +100,10 @@ class _TimelinePostScreenState extends State<TimelinePostScreen> {
} }
if (this.post == null) { if (this.post == null) {
return Center( return Center(
child: Text(widget.options.translations.postLoadingError), child: Text(
widget.options.translations.postLoadingError,
style: widget.options.theme.textStyles.errorTextStyle,
),
); );
} }
var post = this.post!; var post = this.post!;
@ -155,7 +158,9 @@ class _TimelinePostScreenState extends State<TimelinePostScreen> {
if (post.creator!.fullName != null) ...[ if (post.creator!.fullName != null) ...[
Text( Text(
post.creator!.fullName!, post.creator!.fullName!,
style: theme.textTheme.titleMedium, style: widget.options.theme.textStyles
.postCreatorTitleStyle ??
theme.textTheme.titleMedium,
), ),
], ],
], ],
@ -177,7 +182,12 @@ class _TimelinePostScreenState extends State<TimelinePostScreen> {
value: 'delete', value: 'delete',
child: Row( child: Row(
children: [ children: [
Text(widget.options.translations.deletePost), Text(
widget.options.translations.deletePost,
style: widget.options.theme.textStyles
.deletePostStyle ??
theme.textTheme.bodyMedium,
),
const SizedBox(width: 8), const SizedBox(width: 8),
widget.options.theme.deleteIcon ?? widget.options.theme.deleteIcon ??
Icon( Icon(
@ -255,19 +265,25 @@ class _TimelinePostScreenState extends State<TimelinePostScreen> {
), ),
Text( Text(
'${post.likes} ${widget.options.translations.likesTitle}', '${post.likes} ${widget.options.translations.likesTitle}',
style: theme.textTheme.titleSmall, style: widget
.options.theme.textStyles.postLikeTitleAndAmount ??
theme.textTheme.titleSmall,
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text.rich( Text.rich(
TextSpan( TextSpan(
text: post.creator?.fullName ?? text: post.creator?.fullName ??
widget.options.translations.anonymousUser, widget.options.translations.anonymousUser,
style: theme.textTheme.titleSmall, style: widget
.options.theme.textStyles.postCreatorNameStyle ??
theme.textTheme.titleSmall,
children: [ children: [
const TextSpan(text: ' '), const TextSpan(text: ' '),
TextSpan( TextSpan(
text: post.title, text: post.title,
style: theme.textTheme.bodyMedium, style:
widget.options.theme.textStyles.postTitleStyle ??
theme.textTheme.bodyMedium,
), ),
], ],
), ),

View file

@ -35,6 +35,7 @@ class TimelineScreen extends StatefulWidget {
/// The controller for the scroll view /// The controller for the scroll view
final ScrollController? controller; final ScrollController? controller;
/// The string to filter the timeline by category
final String? timelineCategoryFilter; final String? timelineCategoryFilter;
/// This is used if you want to pass in a list of posts instead /// This is used if you want to pass in a list of posts instead
@ -121,6 +122,7 @@ class _TimelineScreenState extends State<TimelineScreen> {
widget.timelineCategoryFilter == null widget.timelineCategoryFilter == null
? widget.options.translations.noPosts ? widget.options.translations.noPosts
: widget.options.translations.noPostsWithFilter, : widget.options.translations.noPostsWithFilter,
style: widget.options.theme.textStyles.noPostsStyle,
), ),
), ),
), ),

View file

@ -31,7 +31,9 @@ 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: theme.textTheme.displayMedium, style:
options.theme.textStyles.categorySelectionDescriptionStyle ??
theme.textTheme.displayMedium,
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
@ -53,7 +55,8 @@ 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: theme.textTheme.displaySmall, style: options.theme.textStyles.categorySelectionTitleStyle ??
theme.textTheme.displaySmall,
), ),
), ),
), ),

View file

@ -43,7 +43,6 @@ class TimelinePostWidget extends StatelessWidget {
return InkWell( return InkWell(
onTap: onTap, onTap: onTap,
child: SizedBox( child: SizedBox(
// TODO(anyone): should posts with text have a max height?
height: post.imageUrl != null ? height : null, height: post.imageUrl != null ? height : null,
width: double.infinity, width: double.infinity,
child: Column( child: Column(
@ -74,7 +73,9 @@ class TimelinePostWidget extends StatelessWidget {
if (post.creator!.fullName != null) ...[ if (post.creator!.fullName != null) ...[
Text( Text(
post.creator!.fullName!, post.creator!.fullName!,
style: theme.textTheme.titleMedium, style: options.theme.textStyles
.listPostCreatorTitleStyle ??
theme.textTheme.titleMedium,
), ),
], ],
], ],
@ -94,7 +95,11 @@ class TimelinePostWidget extends StatelessWidget {
value: 'delete', value: 'delete',
child: Row( child: Row(
children: [ children: [
Text(options.translations.deletePost), Text(
options.translations.deletePost,
style: options.theme.textStyles.deletePostStyle ??
theme.textTheme.bodyMedium,
),
const SizedBox(width: 8), const SizedBox(width: 8),
options.theme.deleteIcon ?? options.theme.deleteIcon ??
Icon( Icon(
@ -152,27 +157,31 @@ class TimelinePostWidget extends StatelessWidget {
const SizedBox(width: 8), const SizedBox(width: 8),
if (post.reactionEnabled) if (post.reactionEnabled)
options.theme.commentIcon ?? options.theme.commentIcon ??
const Icon( Icon(
Icons.chat_bubble_outline_rounded, Icons.chat_bubble_outline_rounded,
color: options.theme.iconColor,
), ),
], ],
), ),
), ),
Text( Text(
'${post.likes} ${options.translations.likesTitle}', '${post.likes} ${options.translations.likesTitle}',
style: theme.textTheme.titleSmall, style: options.theme.textStyles.listPostLikeTitleAndAmount ??
theme.textTheme.titleSmall,
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text.rich( Text.rich(
TextSpan( TextSpan(
text: post.creator?.fullName ?? text: post.creator?.fullName ??
options.translations.anonymousUser, options.translations.anonymousUser,
style: theme.textTheme.titleSmall, style: options.theme.textStyles.listCreatorNameStyle ??
theme.textTheme.titleSmall,
children: [ children: [
const TextSpan(text: ' '), const TextSpan(text: ' '),
TextSpan( TextSpan(
text: post.title, text: post.title,
style: theme.textTheme.bodyMedium, style: options.theme.textStyles.listPostTitleStyle ??
theme.textTheme.bodyMedium,
), ),
], ],
), ),
@ -180,7 +189,8 @@ class TimelinePostWidget extends StatelessWidget {
), ),
Text( Text(
options.translations.viewPost, options.translations.viewPost,
style: theme.textTheme.bodySmall, style: options.theme.textStyles.viewPostStyle ??
theme.textTheme.bodySmall,
), ),
], ],
), ),