From 706ea932b454452edab1d7d4219d279d04e2ed25 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 6 Jan 2025 15:48:31 +0100 Subject: [PATCH] add streamThreadListTile theme --- .../stream_thread_list_tile.dart | 47 ++-- .../lib/src/theme/stream_chat_theme.dart | 34 +++ .../lib/src/theme/thread_list_tile_theme.dart | 225 ++++++++++++++++++ 3 files changed, 279 insertions(+), 27 deletions(-) create mode 100644 packages/stream_chat_flutter/lib/src/theme/thread_list_tile_theme.dart diff --git a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_tile.dart b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_tile.dart index 0f27c70fe..f484e6952 100644 --- a/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_tile.dart +++ b/packages/stream_chat_flutter/lib/src/scroll_view/thread_scroll_view/stream_thread_list_tile.dart @@ -1,6 +1,7 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/misc/timestamp.dart'; +import 'package:stream_chat_flutter/src/theme/thread_list_tile_theme.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// {@template streamThreadListTile} @@ -35,6 +36,8 @@ class StreamThreadListTile extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = StreamThreadListTileTheme.of(context); + final language = currentUser?.language; final unreadMessageCount = thread.read ?.firstWhereOrNull((read) => read.user.id == currentUser?.id) @@ -43,11 +46,8 @@ class StreamThreadListTile extends StatelessWidget { return InkWell( onTap: onTap, onLongPress: onLongPress, - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 14, - horizontal: 8, - ), + child: Container( + padding: theme.padding, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -97,14 +97,15 @@ class ThreadTitle extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); + final theme = StreamThreadListTileTheme.of(context); + return Row( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.message_outlined, size: 16, - color: theme.colorTheme.textHighEmphasis, + color: theme.threadChannelNameStyle?.color, ), const SizedBox(width: 4), Flexible( @@ -112,7 +113,7 @@ class ThreadTitle extends StatelessWidget { channelName ?? context.translations.noTitleText, maxLines: 1, overflow: TextOverflow.ellipsis, - style: theme.textTheme.bodyBold, + style: theme.threadChannelNameStyle, ), ), ], @@ -145,25 +146,21 @@ class ThreadReplyToContent extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); + final theme = StreamThreadListTileTheme.of(context); return Row( mainAxisSize: MainAxisSize.min, children: [ Text( prefix, - style: theme.textTheme.footnote.copyWith( - color: theme.colorTheme.textLowEmphasis, - ), + style: theme.threadReplyToMessageStyle, ), const SizedBox(width: 4), Flexible( child: StreamMessagePreviewText( language: language, message: parentMessage, - textStyle: theme.textTheme.footnote.copyWith( - color: theme.colorTheme.textLowEmphasis, - ), + textStyle: theme.threadReplyToMessageStyle, ), ), ], @@ -186,12 +183,12 @@ class ThreadUnreadCount extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); + final theme = StreamThreadListTileTheme.of(context); return Badge( - textColor: Colors.white, - textStyle: theme.textTheme.footnoteBold, - backgroundColor: theme.channelPreviewTheme.unreadCounterColor, + textStyle: theme.threadUnreadMessageCountStyle, + textColor: theme.threadUnreadMessageCountStyle?.color, + backgroundColor: theme.threadUnreadMessageCountBackgroundColor, label: Text('$unreadCount'), ); } @@ -216,7 +213,7 @@ class ThreadLatestReply extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = StreamChatTheme.of(context); + final theme = StreamThreadListTileTheme.of(context); return Row( children: [ @@ -228,7 +225,7 @@ class ThreadLatestReply extends StatelessWidget { children: [ Text( latestReply.user!.name, - style: theme.textTheme.bodyBold, + style: theme.threadLatestReplyUsernameStyle, ), Row( children: [ @@ -236,16 +233,12 @@ class ThreadLatestReply extends StatelessWidget { child: StreamMessagePreviewText( language: language, message: latestReply, - textStyle: theme.textTheme.body.copyWith( - color: theme.colorTheme.textLowEmphasis, - ), + textStyle: theme.threadLatestReplyMessageStyle, ), ), StreamTimestamp( date: latestReply.createdAt.toLocal(), - style: theme.textTheme.body.copyWith( - color: theme.colorTheme.textLowEmphasis, - ), + style: theme.threadLatestReplyTimestampStyle, ), ], ), diff --git a/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart index 3651018e0..e1bb3e514 100644 --- a/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/stream_chat_theme.dart @@ -4,6 +4,7 @@ import 'package:stream_chat_flutter/src/theme/poll_interactor_theme.dart'; import 'package:stream_chat_flutter/src/theme/poll_option_votes_dialog_theme.dart'; import 'package:stream_chat_flutter/src/theme/poll_options_dialog_theme.dart'; import 'package:stream_chat_flutter/src/theme/poll_results_dialog_theme.dart'; +import 'package:stream_chat_flutter/src/theme/thread_list_tile_theme.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// {@template streamChatTheme} @@ -67,6 +68,7 @@ class StreamChatThemeData { StreamPollResultsDialogThemeData? pollResultsDialogTheme, StreamPollCommentsDialogThemeData? pollCommentsDialogTheme, StreamPollOptionVotesDialogThemeData? pollOptionVotesDialogTheme, + StreamThreadListTileThemeData? threadListTileTheme, }) { brightness ??= colorTheme?.brightness ?? Brightness.light; final isDark = brightness == Brightness.dark; @@ -100,6 +102,7 @@ class StreamChatThemeData { pollResultsDialogTheme: pollResultsDialogTheme, pollCommentsDialogTheme: pollCommentsDialogTheme, pollOptionVotesDialogTheme: pollOptionVotesDialogTheme, + threadListTileTheme: threadListTileTheme, ); return defaultData.merge(customizedData); @@ -134,6 +137,7 @@ class StreamChatThemeData { required this.pollOptionsDialogTheme, required this.pollCommentsDialogTheme, required this.pollOptionVotesDialogTheme, + required this.threadListTileTheme, }); /// Creates a theme from a Material [Theme] @@ -479,6 +483,30 @@ class StreamChatThemeData { pollOptionVoteItemBackgroundColor: colorTheme.inputBg, pollOptionVoteItemBorderRadius: BorderRadius.circular(12), ), + threadListTileTheme: StreamThreadListTileThemeData( + backgroundColor: colorTheme.barsBg, + padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 8), + threadUnreadMessageCountStyle: textTheme.footnoteBold.copyWith( + color: Colors.white, + ), + threadUnreadMessageCountBackgroundColor: + channelPreviewTheme.unreadCounterColor, + threadChannelNameStyle: textTheme.bodyBold.copyWith( + color: colorTheme.textHighEmphasis, + ), + threadReplyToMessageStyle: textTheme.footnote.copyWith( + color: colorTheme.textLowEmphasis, + ), + threadLatestReplyTimestampStyle: textTheme.footnote.copyWith( + color: colorTheme.textLowEmphasis, + ), + threadLatestReplyUsernameStyle: textTheme.bodyBold.copyWith( + color: colorTheme.textHighEmphasis, + ), + threadLatestReplyMessageStyle: textTheme.body.copyWith( + color: colorTheme.textLowEmphasis, + ), + ), ); } @@ -541,6 +569,9 @@ class StreamChatThemeData { /// Theme configuration for the [StreamPollOptionVotesDialog] widget. final StreamPollOptionVotesDialogThemeData pollOptionVotesDialogTheme; + /// Theme configuration for the [StreamThreadListTile] widget. + final StreamThreadListTileThemeData threadListTileTheme; + /// Creates a copy of [StreamChatThemeData] with specified attributes /// overridden. StreamChatThemeData copyWith({ @@ -567,6 +598,7 @@ class StreamChatThemeData { StreamPollOptionsDialogThemeData? pollOptionsDialogTheme, StreamPollCommentsDialogThemeData? pollCommentsDialogTheme, StreamPollOptionVotesDialogThemeData? pollOptionVotesDialogTheme, + StreamThreadListTileThemeData? threadListTileTheme, }) => StreamChatThemeData.raw( channelListHeaderTheme: @@ -594,6 +626,7 @@ class StreamChatThemeData { pollCommentsDialogTheme ?? this.pollCommentsDialogTheme, pollOptionVotesDialogTheme: pollOptionVotesDialogTheme ?? this.pollOptionVotesDialogTheme, + threadListTileTheme: threadListTileTheme ?? this.threadListTileTheme, ); /// Merge themes @@ -625,6 +658,7 @@ class StreamChatThemeData { pollCommentsDialogTheme.merge(other.pollCommentsDialogTheme), pollOptionVotesDialogTheme: pollOptionVotesDialogTheme.merge(other.pollOptionVotesDialogTheme), + threadListTileTheme: threadListTileTheme.merge(other.threadListTileTheme), ); } } diff --git a/packages/stream_chat_flutter/lib/src/theme/thread_list_tile_theme.dart b/packages/stream_chat_flutter/lib/src/theme/thread_list_tile_theme.dart new file mode 100644 index 000000000..1aee782fb --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/theme/thread_list_tile_theme.dart @@ -0,0 +1,225 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:stream_chat_flutter/src/theme/stream_chat_theme.dart'; + +/// {@template streamThreadListTileTheme} +/// Overrides the default style of [StreamThreadListTile] descendants. +/// +/// See also: +/// +/// * [StreamPollOptionVotesDialogThemeData], which is used to configure this +/// theme. +/// {@endtemplate} +class StreamThreadListTileTheme extends InheritedTheme { + /// Creates a [StreamThreadListTileTheme]. + /// + /// The [data] parameter must not be null. + const StreamThreadListTileTheme({ + super.key, + required this.data, + required super.child, + }); + + /// The configuration of this theme. + final StreamThreadListTileThemeData data; + + /// The closest instance of this class that encloses the given context. + /// + /// If there is no enclosing [StreamPollOptionVotesDialogTheme] widget, then + /// [StreamChatThemeData.pollOptionVotesDialogTheme] is used. + static StreamThreadListTileThemeData of(BuildContext context) { + final threadListTileTheme = + context.dependOnInheritedWidgetOfExactType(); + return threadListTileTheme?.data ?? + StreamChatTheme.of(context).threadListTileTheme; + } + + @override + Widget wrap(BuildContext context, Widget child) => + StreamThreadListTileTheme(data: data, child: child); + + @override + bool updateShouldNotify(StreamThreadListTileTheme oldWidget) => + data != oldWidget.data; +} + +/// {@template streamThreadListTileThemeData} +/// A style that overrides the default appearance of +/// [StreamPollOptionVotesDialog] widgets when used with +/// [StreamPollCommentsDialogTheme] or with the overall [StreamChatTheme]'s +/// [StreamChatThemeData.pollOptionVotesDialogTheme]. +/// {@endtemplate} +class StreamThreadListTileThemeData with Diagnosticable { + /// {@macro streamThreadListTileThemeData} + const StreamThreadListTileThemeData({ + this.padding, + this.backgroundColor, + this.threadChannelNameStyle, + this.threadReplyToMessageStyle, + this.threadLatestReplyUsernameStyle, + this.threadLatestReplyMessageStyle, + this.threadLatestReplyTimestampStyle, + this.threadUnreadMessageCountStyle, + this.threadUnreadMessageCountBackgroundColor, + }); + + /// The padding around the [StreamThreadListTile] widget. + final EdgeInsetsGeometry? padding; + + /// The background color of the [StreamThreadListTile] widget. + final Color? backgroundColor; + + /// The style of the channel name in the [StreamThreadListTile] widget. + final TextStyle? threadChannelNameStyle; + + /// The style of the message the thread is replying to in the + /// [StreamThreadListTile] widget. + final TextStyle? threadReplyToMessageStyle; + + /// The style of the latest reply author username in the + /// [StreamThreadListTile] widget. + final TextStyle? threadLatestReplyUsernameStyle; + + /// The style of the latest reply message in the [StreamThreadListTile]. + /// widget. + final TextStyle? threadLatestReplyMessageStyle; + + /// The style of the latest reply timestamp in the [StreamThreadListTile]. + final TextStyle? threadLatestReplyTimestampStyle; + + /// The style of the unread message count in the [StreamThreadListTile]. + final TextStyle? threadUnreadMessageCountStyle; + + /// The background color of the unread message count in the + /// [StreamThreadListTile]. + final Color? threadUnreadMessageCountBackgroundColor; + + /// A copy of [StreamThreadListTileThemeData] with specified attributes + /// overridden. + StreamThreadListTileThemeData copyWith({ + EdgeInsetsGeometry? padding, + Color? backgroundColor, + TextStyle? threadChannelNameStyle, + TextStyle? threadReplyToMessageStyle, + TextStyle? threadLatestReplyUsernameStyle, + TextStyle? threadLatestReplyMessageStyle, + TextStyle? threadLatestReplyTimestampStyle, + TextStyle? threadUnreadMessageCountStyle, + Color? threadUnreadMessageCountBackgroundColor, + }) => + StreamThreadListTileThemeData( + padding: padding ?? this.padding, + backgroundColor: backgroundColor ?? this.backgroundColor, + threadChannelNameStyle: + threadChannelNameStyle ?? this.threadChannelNameStyle, + threadReplyToMessageStyle: + threadReplyToMessageStyle ?? this.threadReplyToMessageStyle, + threadLatestReplyUsernameStyle: threadLatestReplyUsernameStyle ?? + this.threadLatestReplyUsernameStyle, + threadLatestReplyMessageStyle: + threadLatestReplyMessageStyle ?? this.threadLatestReplyMessageStyle, + threadLatestReplyTimestampStyle: threadLatestReplyTimestampStyle ?? + this.threadLatestReplyTimestampStyle, + threadUnreadMessageCountStyle: + threadUnreadMessageCountStyle ?? this.threadUnreadMessageCountStyle, + threadUnreadMessageCountBackgroundColor: + threadUnreadMessageCountBackgroundColor ?? + this.threadUnreadMessageCountBackgroundColor, + ); + + /// Merges this [StreamThreadListTileThemeData] with the [other]. + StreamThreadListTileThemeData merge( + StreamThreadListTileThemeData? other, + ) { + if (other == null) return this; + return copyWith( + padding: other.padding, + backgroundColor: other.backgroundColor, + threadChannelNameStyle: other.threadChannelNameStyle, + threadReplyToMessageStyle: other.threadReplyToMessageStyle, + threadLatestReplyUsernameStyle: other.threadLatestReplyUsernameStyle, + threadLatestReplyMessageStyle: other.threadLatestReplyMessageStyle, + threadLatestReplyTimestampStyle: other.threadLatestReplyTimestampStyle, + threadUnreadMessageCountStyle: other.threadUnreadMessageCountStyle, + threadUnreadMessageCountBackgroundColor: + other.threadUnreadMessageCountBackgroundColor, + ); + } + + /// Linearly interpolate between two [StreamThreadListTileThemeData]. + StreamThreadListTileThemeData lerp( + StreamThreadListTileThemeData? a, + StreamThreadListTileThemeData? b, + double t, + ) => + StreamThreadListTileThemeData( + padding: EdgeInsetsGeometry.lerp(a?.padding, b?.padding, t), + backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t), + threadChannelNameStyle: TextStyle.lerp( + a?.threadChannelNameStyle, + b?.threadChannelNameStyle, + t, + ), + threadReplyToMessageStyle: TextStyle.lerp( + a?.threadReplyToMessageStyle, + b?.threadReplyToMessageStyle, + t, + ), + threadLatestReplyUsernameStyle: TextStyle.lerp( + a?.threadLatestReplyUsernameStyle, + b?.threadLatestReplyUsernameStyle, + t, + ), + threadLatestReplyMessageStyle: TextStyle.lerp( + a?.threadLatestReplyMessageStyle, + b?.threadLatestReplyMessageStyle, + t, + ), + threadLatestReplyTimestampStyle: TextStyle.lerp( + a?.threadLatestReplyTimestampStyle, + b?.threadLatestReplyTimestampStyle, + t, + ), + threadUnreadMessageCountStyle: TextStyle.lerp( + a?.threadUnreadMessageCountStyle, + b?.threadUnreadMessageCountStyle, + t, + ), + threadUnreadMessageCountBackgroundColor: Color.lerp( + a?.threadUnreadMessageCountBackgroundColor, + b?.threadUnreadMessageCountBackgroundColor, + t, + ), + ); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is StreamThreadListTileThemeData && + other.padding == padding && + other.backgroundColor == backgroundColor && + other.threadChannelNameStyle == threadChannelNameStyle && + other.threadReplyToMessageStyle == threadReplyToMessageStyle && + other.threadLatestReplyUsernameStyle == + threadLatestReplyUsernameStyle && + other.threadLatestReplyMessageStyle == + threadLatestReplyMessageStyle && + other.threadLatestReplyTimestampStyle == + threadLatestReplyTimestampStyle && + other.threadUnreadMessageCountStyle == + threadUnreadMessageCountStyle && + other.threadUnreadMessageCountBackgroundColor == + threadUnreadMessageCountBackgroundColor; + + @override + int get hashCode => + padding.hashCode ^ + backgroundColor.hashCode ^ + threadChannelNameStyle.hashCode ^ + threadReplyToMessageStyle.hashCode ^ + threadLatestReplyUsernameStyle.hashCode ^ + threadLatestReplyMessageStyle.hashCode ^ + threadLatestReplyTimestampStyle.hashCode ^ + threadUnreadMessageCountStyle.hashCode ^ + threadUnreadMessageCountBackgroundColor.hashCode; +}