Files
stream-chat-flutter/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart
T

501 lines
19 KiB
Dart

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart' hide TextTheme;
import 'package:stream_chat_flutter/src/channel_preview.dart';
import 'package:stream_chat_flutter/src/gradient_avatar.dart';
import 'package:stream_chat_flutter/src/message_input.dart';
import 'package:stream_chat_flutter/src/reaction_icon.dart';
import 'package:stream_chat_flutter/src/theme/themes.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
/// Inherited widget providing the [StreamChatThemeData] to the widget tree
class StreamChatTheme extends InheritedWidget {
/// Constructor for creating a [StreamChatTheme]
const StreamChatTheme({
Key? key,
required this.data,
required Widget child,
}) : super(
key: key,
child: child,
);
/// Theme data
final StreamChatThemeData data;
@override
bool updateShouldNotify(StreamChatTheme old) => data != old.data;
/// Use this method to get the current [StreamChatThemeData] instance
static StreamChatThemeData of(BuildContext context) {
final streamChatTheme =
context.dependOnInheritedWidgetOfExactType<StreamChatTheme>();
assert(
streamChatTheme != null,
'You must have a StreamChatTheme widget at the top of your widget tree',
);
return streamChatTheme!.data;
}
}
/// Theme data
class StreamChatThemeData {
/// Create a theme from scratch
factory StreamChatThemeData({
Brightness? brightness,
TextTheme? textTheme,
ColorTheme? colorTheme,
ChannelListHeaderThemeData? channelListHeaderTheme,
ChannelPreviewThemeData? channelPreviewTheme,
ChannelHeaderThemeData? channelHeaderTheme,
MessageThemeData? otherMessageTheme,
MessageThemeData? ownMessageTheme,
MessageInputThemeData? messageInputTheme,
Widget Function(BuildContext, User)? defaultUserImage,
Widget Function(BuildContext, User)? placeholderUserImage,
IconThemeData? primaryIconTheme,
List<ReactionIcon>? reactionIcons,
GalleryHeaderThemeData? imageHeaderTheme,
GalleryFooterThemeData? imageFooterTheme,
MessageListViewThemeData? messageListViewTheme,
ChannelListViewThemeData? channelListViewTheme,
UserListViewThemeData? userListViewTheme,
MessageSearchListViewThemeData? messageSearchListViewTheme,
}) {
brightness ??= colorTheme?.brightness ?? Brightness.light;
final isDark = brightness == Brightness.dark;
textTheme ??= isDark ? TextTheme.dark() : TextTheme.light();
colorTheme ??= isDark ? ColorTheme.dark() : ColorTheme.light();
final defaultData = StreamChatThemeData.fromColorAndTextTheme(
colorTheme,
textTheme,
);
final customizedData = defaultData.copyWith(
channelListHeaderTheme: channelListHeaderTheme,
channelPreviewTheme: channelPreviewTheme,
channelHeaderTheme: channelHeaderTheme,
otherMessageTheme: otherMessageTheme,
ownMessageTheme: ownMessageTheme,
messageInputTheme: messageInputTheme,
defaultUserImage: defaultUserImage,
placeholderUserImage: placeholderUserImage,
primaryIconTheme: primaryIconTheme,
reactionIcons: reactionIcons,
galleryHeaderTheme: imageHeaderTheme,
galleryFooterTheme: imageFooterTheme,
messageListViewTheme: messageListViewTheme,
channelListViewTheme: channelListViewTheme,
userListViewTheme: userListViewTheme,
messageSearchListViewTheme: messageSearchListViewTheme,
);
return defaultData.merge(customizedData);
}
/// Theme initialised with light
factory StreamChatThemeData.light() =>
StreamChatThemeData(brightness: Brightness.light);
/// Theme initialised with dark
factory StreamChatThemeData.dark() =>
StreamChatThemeData(brightness: Brightness.dark);
/// Raw theme init
const StreamChatThemeData.raw({
required this.textTheme,
required this.colorTheme,
required this.channelListHeaderTheme,
required this.channelPreviewTheme,
required this.channelHeaderTheme,
required this.otherMessageTheme,
required this.ownMessageTheme,
required this.messageInputTheme,
required this.defaultUserImage,
this.placeholderUserImage,
required this.primaryIconTheme,
required this.reactionIcons,
required this.galleryHeaderTheme,
required this.galleryFooterTheme,
required this.messageListViewTheme,
required this.channelListViewTheme,
required this.userListViewTheme,
required this.messageSearchListViewTheme,
});
/// Create a theme from a Material [Theme]
factory StreamChatThemeData.fromTheme(ThemeData theme) {
final defaultTheme = StreamChatThemeData(brightness: theme.brightness);
final customizedTheme = StreamChatThemeData.fromColorAndTextTheme(
defaultTheme.colorTheme.copyWith(
accentPrimary: theme.colorScheme.secondary,
),
defaultTheme.textTheme,
);
return defaultTheme.merge(customizedTheme);
}
/// Create theme from color and text theme
factory StreamChatThemeData.fromColorAndTextTheme(
ColorTheme colorTheme,
TextTheme textTheme,
) {
final accentColor = colorTheme.accentPrimary;
final iconTheme =
IconThemeData(color: colorTheme.textHighEmphasis.withOpacity(0.5));
final channelHeaderTheme = ChannelHeaderThemeData(
avatarTheme: AvatarThemeData(
borderRadius: BorderRadius.circular(20),
constraints: const BoxConstraints.tightFor(
height: 40,
width: 40,
),
),
color: colorTheme.barsBg,
titleStyle: textTheme.headlineBold,
subtitleStyle: textTheme.footnote.copyWith(
color: const Color(0xff7A7A7A),
),
);
final channelPreviewTheme = ChannelPreviewThemeData(
unreadCounterColor: colorTheme.accentError,
avatarTheme: AvatarThemeData(
borderRadius: BorderRadius.circular(20),
constraints: const BoxConstraints.tightFor(
height: 40,
width: 40,
),
),
titleStyle: textTheme.bodyBold,
subtitleStyle: textTheme.footnote.copyWith(
color: const Color(0xff7A7A7A),
),
lastMessageAtStyle: textTheme.footnote.copyWith(
color: colorTheme.textHighEmphasis.withOpacity(0.5),
),
indicatorIconSize: 16,
);
return StreamChatThemeData.raw(
textTheme: textTheme,
colorTheme: colorTheme,
primaryIconTheme: iconTheme,
defaultUserImage: (context, user) => Center(
child: GradientAvatar(
name: user.name,
userId: user.id,
),
),
channelPreviewTheme: channelPreviewTheme,
channelListHeaderTheme: ChannelListHeaderThemeData(
avatarTheme: AvatarThemeData(
borderRadius: BorderRadius.circular(20),
constraints: const BoxConstraints.tightFor(
height: 40,
width: 40,
),
),
color: colorTheme.barsBg,
titleStyle: textTheme.headlineBold,
),
channelHeaderTheme: channelHeaderTheme,
ownMessageTheme: MessageThemeData(
messageAuthorStyle:
textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis),
messageTextStyle: textTheme.body,
createdAtStyle:
textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis),
repliesStyle: textTheme.footnoteBold.copyWith(color: accentColor),
messageBackgroundColor: colorTheme.disabled,
reactionsBackgroundColor: colorTheme.barsBg,
reactionsBorderColor: colorTheme.borders,
reactionsMaskColor: colorTheme.appBg,
messageBorderColor: colorTheme.disabled,
avatarTheme: AvatarThemeData(
borderRadius: BorderRadius.circular(20),
constraints: const BoxConstraints.tightFor(
height: 32,
width: 32,
),
),
messageLinksStyle: TextStyle(
color: accentColor,
),
linkBackgroundColor: colorTheme.linkBg,
),
otherMessageTheme: MessageThemeData(
reactionsBackgroundColor: colorTheme.disabled,
reactionsBorderColor: colorTheme.barsBg,
reactionsMaskColor: colorTheme.appBg,
messageTextStyle: textTheme.body,
createdAtStyle:
textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis),
messageAuthorStyle:
textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis),
repliesStyle: textTheme.footnoteBold.copyWith(color: accentColor),
messageLinksStyle: TextStyle(
color: accentColor,
),
messageBackgroundColor: colorTheme.barsBg,
messageBorderColor: colorTheme.borders,
avatarTheme: AvatarThemeData(
borderRadius: BorderRadius.circular(20),
constraints: const BoxConstraints.tightFor(
height: 32,
width: 32,
),
),
linkBackgroundColor: colorTheme.linkBg,
),
messageInputTheme: MessageInputThemeData(
borderRadius: BorderRadius.circular(20),
sendAnimationDuration: const Duration(milliseconds: 300),
actionButtonColor: colorTheme.accentPrimary,
actionButtonIdleColor: colorTheme.textLowEmphasis,
expandButtonColor: colorTheme.accentPrimary,
sendButtonColor: colorTheme.accentPrimary,
sendButtonIdleColor: colorTheme.disabled,
inputBackgroundColor: colorTheme.barsBg,
inputTextStyle: textTheme.body,
idleBorderGradient: LinearGradient(
colors: [
colorTheme.disabled,
colorTheme.disabled,
],
),
activeBorderGradient: LinearGradient(
colors: [
colorTheme.disabled,
colorTheme.disabled,
],
),
),
reactionIcons: [
ReactionIcon(
type: 'love',
builder: (context, highlighted, size) {
final theme = StreamChatTheme.of(context);
return StreamSvgIcon.loveReaction(
color: highlighted
? theme.colorTheme.accentPrimary
: theme.primaryIconTheme.color!.withOpacity(0.5),
size: size,
);
},
),
ReactionIcon(
type: 'like',
builder: (context, highlighted, size) {
final theme = StreamChatTheme.of(context);
return StreamSvgIcon.thumbsUpReaction(
color: highlighted
? theme.colorTheme.accentPrimary
: theme.primaryIconTheme.color!.withOpacity(0.5),
size: size,
);
},
),
ReactionIcon(
type: 'sad',
builder: (context, highlighted, size) {
final theme = StreamChatTheme.of(context);
return StreamSvgIcon.thumbsDownReaction(
color: highlighted
? theme.colorTheme.accentPrimary
: theme.primaryIconTheme.color!.withOpacity(0.5),
size: size,
);
},
),
ReactionIcon(
type: 'haha',
builder: (context, highlighted, size) {
final theme = StreamChatTheme.of(context);
return StreamSvgIcon.lolReaction(
color: highlighted
? theme.colorTheme.accentPrimary
: theme.primaryIconTheme.color!.withOpacity(0.5),
size: size,
);
},
),
ReactionIcon(
type: 'wow',
builder: (context, highlighted, size) {
final theme = StreamChatTheme.of(context);
return StreamSvgIcon.wutReaction(
color: highlighted
? theme.colorTheme.accentPrimary
: theme.primaryIconTheme.color!.withOpacity(0.5),
size: size,
);
},
),
],
galleryHeaderTheme: GalleryHeaderThemeData(
closeButtonColor: colorTheme.textHighEmphasis,
backgroundColor: channelHeaderTheme.color,
iconMenuPointColor: colorTheme.textHighEmphasis,
titleTextStyle: textTheme.headlineBold,
subtitleTextStyle: channelPreviewTheme.subtitleStyle,
bottomSheetBarrierColor: colorTheme.overlay,
),
galleryFooterTheme: GalleryFooterThemeData(
backgroundColor: colorTheme.barsBg,
shareIconColor: colorTheme.textHighEmphasis,
titleTextStyle: textTheme.headlineBold,
gridIconButtonColor: colorTheme.textHighEmphasis,
bottomSheetBarrierColor: colorTheme.overlay,
bottomSheetBackgroundColor: colorTheme.barsBg,
bottomSheetPhotosTextStyle: textTheme.headlineBold,
bottomSheetCloseIconColor: colorTheme.textHighEmphasis,
),
messageListViewTheme: MessageListViewThemeData(
backgroundColor: colorTheme.barsBg,
),
channelListViewTheme: ChannelListViewThemeData(
backgroundColor: colorTheme.appBg,
),
userListViewTheme: UserListViewThemeData(
backgroundColor: colorTheme.appBg,
),
messageSearchListViewTheme: MessageSearchListViewThemeData(
backgroundColor: colorTheme.appBg,
),
);
}
/// The text themes used in the widgets
final TextTheme textTheme;
/// The color themes used in the widgets
final ColorTheme colorTheme;
/// Theme of the [ChannelPreview]
final ChannelPreviewThemeData channelPreviewTheme;
/// Theme of the [ChannelListHeader]
final ChannelListHeaderThemeData channelListHeaderTheme;
/// Theme of the chat widgets dedicated to a channel header
final ChannelHeaderThemeData channelHeaderTheme;
/// The default style for [GalleryHeader]s below the overall
/// [StreamChatTheme].
final GalleryHeaderThemeData galleryHeaderTheme;
/// The default style for [GalleryFooter]s below the overall
/// [StreamChatTheme].
final GalleryFooterThemeData galleryFooterTheme;
/// Theme of the current user messages
final MessageThemeData ownMessageTheme;
/// Theme of other users messages
final MessageThemeData otherMessageTheme;
/// Theme dedicated to the [MessageInput] widget
final MessageInputThemeData messageInputTheme;
/// The widget that will be built when the user image is unavailable
final Widget Function(BuildContext, User) defaultUserImage;
/// The widget that will be built when the user image is loading
final Widget Function(BuildContext, User)? placeholderUserImage;
/// Primary icon theme
final IconThemeData primaryIconTheme;
/// Assets used for rendering reactions
final List<ReactionIcon> reactionIcons;
/// Theme configuration for the [MessageListView] widget.
final MessageListViewThemeData messageListViewTheme;
/// Theme configuration for the [ChannelListView] widget.
final ChannelListViewThemeData channelListViewTheme;
/// Theme configuration for the [UserListView] widget.
final UserListViewThemeData userListViewTheme;
/// Theme configuration for the [MessageSearchListView] widget.
final MessageSearchListViewThemeData messageSearchListViewTheme;
/// Creates a copy of [StreamChatThemeData] with specified attributes
/// overridden.
StreamChatThemeData copyWith({
TextTheme? textTheme,
ColorTheme? colorTheme,
ChannelPreviewThemeData? channelPreviewTheme,
ChannelHeaderThemeData? channelHeaderTheme,
MessageThemeData? ownMessageTheme,
MessageThemeData? otherMessageTheme,
MessageInputThemeData? messageInputTheme,
Widget Function(BuildContext, User)? defaultUserImage,
Widget Function(BuildContext, User)? placeholderUserImage,
IconThemeData? primaryIconTheme,
ChannelListHeaderThemeData? channelListHeaderTheme,
List<ReactionIcon>? reactionIcons,
GalleryHeaderThemeData? galleryHeaderTheme,
GalleryFooterThemeData? galleryFooterTheme,
MessageListViewThemeData? messageListViewTheme,
ChannelListViewThemeData? channelListViewTheme,
UserListViewThemeData? userListViewTheme,
MessageSearchListViewThemeData? messageSearchListViewTheme,
}) =>
StreamChatThemeData.raw(
channelListHeaderTheme:
this.channelListHeaderTheme.merge(channelListHeaderTheme),
textTheme: this.textTheme.merge(textTheme),
colorTheme: this.colorTheme.merge(colorTheme),
primaryIconTheme: this.primaryIconTheme.merge(primaryIconTheme),
defaultUserImage: defaultUserImage ?? this.defaultUserImage,
placeholderUserImage: placeholderUserImage ?? this.placeholderUserImage,
channelPreviewTheme:
this.channelPreviewTheme.merge(channelPreviewTheme),
channelHeaderTheme: this.channelHeaderTheme.merge(channelHeaderTheme),
ownMessageTheme: this.ownMessageTheme.merge(ownMessageTheme),
otherMessageTheme: this.otherMessageTheme.merge(otherMessageTheme),
messageInputTheme: this.messageInputTheme.merge(messageInputTheme),
reactionIcons: reactionIcons ?? this.reactionIcons,
galleryHeaderTheme: galleryHeaderTheme ?? this.galleryHeaderTheme,
galleryFooterTheme: galleryFooterTheme ?? this.galleryFooterTheme,
messageListViewTheme: messageListViewTheme ?? this.messageListViewTheme,
channelListViewTheme: channelListViewTheme ?? this.channelListViewTheme,
userListViewTheme: userListViewTheme ?? this.userListViewTheme,
messageSearchListViewTheme:
messageSearchListViewTheme ?? this.messageSearchListViewTheme,
);
/// Merge themes
StreamChatThemeData merge(StreamChatThemeData? other) {
if (other == null) return this;
return copyWith(
channelListHeaderTheme:
channelListHeaderTheme.merge(other.channelListHeaderTheme),
textTheme: textTheme.merge(other.textTheme),
colorTheme: colorTheme.merge(other.colorTheme),
primaryIconTheme: other.primaryIconTheme,
defaultUserImage: other.defaultUserImage,
placeholderUserImage: other.placeholderUserImage,
channelPreviewTheme: channelPreviewTheme.merge(other.channelPreviewTheme),
channelHeaderTheme: channelHeaderTheme.merge(other.channelHeaderTheme),
ownMessageTheme: ownMessageTheme.merge(other.ownMessageTheme),
otherMessageTheme: otherMessageTheme.merge(other.otherMessageTheme),
messageInputTheme: messageInputTheme.merge(other.messageInputTheme),
reactionIcons: other.reactionIcons,
galleryHeaderTheme: galleryHeaderTheme.merge(other.galleryHeaderTheme),
galleryFooterTheme: galleryFooterTheme.merge(other.galleryFooterTheme),
messageListViewTheme:
messageListViewTheme.merge(other.messageListViewTheme),
channelListViewTheme:
channelListViewTheme.merge(other.channelListViewTheme),
userListViewTheme: userListViewTheme.merge(other.userListViewTheme),
messageSearchListViewTheme:
messageSearchListViewTheme.merge(other.messageSearchListViewTheme),
);
}
}