Skip to content

Commit

Permalink
feat: new theming (#703)
Browse files Browse the repository at this point in the history
  • Loading branch information
demchenkoalex authored Jan 25, 2025
1 parent 22025bb commit 25fe1e2
Show file tree
Hide file tree
Showing 33 changed files with 1,127 additions and 1,172 deletions.
Binary file added examples/flyer_chat/assets/pattern.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 1 addition & 2 deletions examples/flyer_chat/lib/api/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,11 @@ class ApiState extends State<Api> {
chatController: _chatController,
crossCache: _crossCache,
currentUserId: widget.currentUserId,
darkTheme: ChatTheme.fromThemeData(theme),
onAttachmentTap: _handleAttachmentTap,
onMessageSend: _addItem,
onMessageTap: _removeItem,
theme: ChatTheme.fromThemeData(theme),
resolveUser: (id) => Future.value(users[id]),
theme: ChatTheme.fromThemeData(theme),
),
Positioned(
top: 16,
Expand Down
3 changes: 3 additions & 0 deletions examples/flyer_chat/lib/gemini.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ class GeminiState extends State<Gemini> {

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);

return Scaffold(
appBar: AppBar(
title: const Text('Gemini'),
Expand Down Expand Up @@ -105,6 +107,7 @@ class GeminiState extends State<Gemini> {
_ => null,
},
),
theme: ChatTheme.fromThemeData(theme),
),
);
}
Expand Down
26 changes: 22 additions & 4 deletions examples/flyer_chat/lib/local.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,24 @@ class LocalState extends State<Local> {

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);

return Scaffold(
appBar: AppBar(
title: const Text('Local'),
),
body: Chat(
backgroundColor: null,
builders: Builders(
customMessageBuilder: (context, message, index) => Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 10,
),
decoration: BoxDecoration(
color: Color(0xFFF0F0F0),
color: theme.brightness == Brightness.dark
? ChatColors.dark().surfaceContainer
: ChatColors.light().surfaceContainer,
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
child: IsTypingIndicator(),
Expand Down Expand Up @@ -85,9 +90,19 @@ class LocalState extends State<Local> {
),
chatController: _chatController,
currentUserId: _currentUser.id,
darkTheme: ChatTheme.dark(
isTypingTheme: IsTypingTheme.dark(
color: Color(0xFF101010),
decoration: BoxDecoration(
color: theme.brightness == Brightness.dark
? ChatColors.dark().surface
: ChatColors.light().surface,
image: DecorationImage(
image: AssetImage('assets/pattern.png'),
repeat: ImageRepeat.repeat,
colorFilter: ColorFilter.mode(
theme.brightness == Brightness.dark
? ChatColors.dark().surfaceContainerLow
: ChatColors.light().surfaceContainerLow,
BlendMode.srcIn,
),
),
),
onAttachmentTap: _handleAttachmentTap,
Expand All @@ -101,6 +116,9 @@ class LocalState extends State<Local> {
_ => null,
},
),
theme: theme.brightness == Brightness.dark
? ChatTheme.dark()
: ChatTheme.light(),
),
);
}
Expand Down
1 change: 1 addition & 0 deletions examples/flyer_chat/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ flutter:
# To add assets to your application, add an assets section, like this:
assets:
- .env
- assets/pattern.png

# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
Expand Down
5 changes: 1 addition & 4 deletions packages/flutter_chat_core/lib/flutter_chat_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ export 'src/models/link_preview.dart';
export 'src/models/message.dart';
export 'src/models/user.dart';
export 'src/theme/chat_theme.dart';
export 'src/theme/input_theme.dart';
export 'src/theme/is_typing_theme.dart';
export 'src/theme/scroll_to_bottom_theme.dart';
export 'src/theme/text_message_theme.dart';
export 'src/theme/chat_theme_extension.dart';
export 'src/utils/is_only_emoji.dart';
export 'src/utils/typedefs.dart';
253 changes: 147 additions & 106 deletions packages/flutter_chat_core/lib/src/theme/chat_theme.dart
Original file line number Diff line number Diff line change
@@ -1,127 +1,168 @@
import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'chat_theme_constants.dart';
import 'image_message_theme.dart';
import 'input_theme.dart';
import 'is_typing_theme.dart';
import 'scroll_to_bottom_theme.dart';
import 'text_message_theme.dart';

part 'chat_theme.freezed.dart';

@Freezed(fromJson: false, toJson: false, copyWith: false)
@freezed
class ChatTheme with _$ChatTheme {
factory ChatTheme.light({
Color? backgroundColor,
String? fontFamily,
ImageMessageTheme? imageMessageTheme,
InputTheme? inputTheme,
IsTypingTheme? isTypingTheme,
ScrollToBottomTheme? scrollToBottomTheme,
TextMessageTheme? textMessageTheme,
}) {
return ChatTheme(
backgroundColor: backgroundColor ?? defaultChatBackgroundColor.light,
fontFamily: fontFamily ?? defaultChatFontFamily,
imageMessageTheme: ImageMessageTheme.light().merge(imageMessageTheme),
inputTheme:
InputTheme.light(fontFamily: fontFamily ?? defaultChatFontFamily)
.merge(inputTheme),
isTypingTheme: IsTypingTheme.light().merge(isTypingTheme),
scrollToBottomTheme:
ScrollToBottomTheme.light().merge(scrollToBottomTheme),
textMessageTheme: TextMessageTheme.light(
fontFamily: fontFamily ?? defaultChatFontFamily,
).merge(textMessageTheme),
);
}
const factory ChatTheme({
required ChatColors colors,
required ChatTypography typography,
required BorderRadiusGeometry shape,
}) = _ChatTheme;

factory ChatTheme.dark({
Color? backgroundColor,
String? fontFamily,
ImageMessageTheme? imageMessageTheme,
InputTheme? inputTheme,
IsTypingTheme? isTypingTheme,
ScrollToBottomTheme? scrollToBottomTheme,
TextMessageTheme? textMessageTheme,
}) {
return ChatTheme(
backgroundColor: backgroundColor ?? defaultChatBackgroundColor.dark,
fontFamily: fontFamily ?? defaultChatFontFamily,
imageMessageTheme: ImageMessageTheme.dark().merge(imageMessageTheme),
inputTheme:
InputTheme.dark(fontFamily: fontFamily ?? defaultChatFontFamily)
.merge(inputTheme),
isTypingTheme: IsTypingTheme.dark().merge(isTypingTheme),
scrollToBottomTheme:
ScrollToBottomTheme.dark().merge(scrollToBottomTheme),
textMessageTheme: TextMessageTheme.dark(
fontFamily: fontFamily ?? defaultChatFontFamily,
).merge(textMessageTheme),
);
}
const ChatTheme._();

factory ChatTheme.fromThemeData(ThemeData themeData, {String? fontFamily}) {
final family = fontFamily ??
themeData.textTheme.bodyMedium?.fontFamily ??
defaultChatFontFamily;

return ChatTheme(
backgroundColor: themeData.colorScheme.surface,
fontFamily: family,
imageMessageTheme: ImageMessageTheme.fromThemeData(themeData),
inputTheme: InputTheme.fromThemeData(themeData, fontFamily: family),
isTypingTheme: IsTypingTheme.fromThemeData(themeData),
scrollToBottomTheme: ScrollToBottomTheme.fromThemeData(themeData),
textMessageTheme: TextMessageTheme.fromThemeData(
themeData,
fontFamily: family,
),
factory ChatTheme.light({String? fontFamily}) => ChatTheme(
colors: ChatColors.light(),
typography: ChatTypography.standard(fontFamily: fontFamily),
shape: const BorderRadius.all(Radius.circular(12)),
);

factory ChatTheme.dark({String? fontFamily}) => ChatTheme(
colors: ChatColors.dark(),
typography: ChatTypography.standard(fontFamily: fontFamily),
shape: const BorderRadius.all(Radius.circular(12)),
);

factory ChatTheme.fromThemeData(ThemeData themeData) => ChatTheme(
colors: ChatColors.fromThemeData(themeData),
typography: ChatTypography.fromThemeData(themeData),
shape: const BorderRadius.all(Radius.circular(12)),
);

ChatTheme merge(ChatTheme? other) {
if (other == null) return this;
return copyWith(
colors: colors.merge(other.colors),
typography: typography.merge(other.typography),
shape: other.shape,
);
}
}

const factory ChatTheme({
required Color backgroundColor,
required String fontFamily,
required ImageMessageTheme imageMessageTheme,
required InputTheme inputTheme,
required IsTypingTheme isTypingTheme,
required ScrollToBottomTheme scrollToBottomTheme,
required TextMessageTheme textMessageTheme,
}) = _ChatTheme;
@freezed
class ChatColors with _$ChatColors {
const factory ChatColors({
required Color primary,
required Color onPrimary,
required Color surface,
required Color onSurface,
required Color surfaceContainer,
required Color surfaceContainerLow,
required Color surfaceContainerHigh,
}) = _ChatColors;

const ChatTheme._();
const ChatColors._();

factory ChatColors.light() => const ChatColors(
primary: Color(0xff4169e1),
onPrimary: Colors.white,
surface: Colors.white,
onSurface: Color(0xff101010),
surfaceContainerLow: Color(0xfffafafa),
surfaceContainer: Color(0xfff5f5f5),
surfaceContainerHigh: Color(0xffeeeeee),
);

ChatTheme copyWith({
Color? backgroundColor,
String? fontFamily,
ImageMessageTheme? imageMessageTheme,
InputTheme? inputTheme,
IsTypingTheme? isTypingTheme,
ScrollToBottomTheme? scrollToBottomTheme,
TextMessageTheme? textMessageTheme,
}) {
return ChatTheme(
backgroundColor: backgroundColor ?? this.backgroundColor,
fontFamily: fontFamily ?? this.fontFamily,
imageMessageTheme: this.imageMessageTheme.merge(imageMessageTheme),
inputTheme: this.inputTheme.merge(inputTheme),
isTypingTheme: this.isTypingTheme.merge(isTypingTheme),
scrollToBottomTheme: this.scrollToBottomTheme.merge(scrollToBottomTheme),
textMessageTheme: this.textMessageTheme.merge(textMessageTheme),
factory ChatColors.dark() => const ChatColors(
primary: Color(0xff4169e1),
onPrimary: Colors.white,
surface: Color(0xff101010),
onSurface: Colors.white,
surfaceContainerLow: Color(0xff121212),
surfaceContainer: Color(0xff1c1c1c),
surfaceContainerHigh: Color(0xff242424),
);

factory ChatColors.fromThemeData(ThemeData themeData) => ChatColors(
primary: themeData.colorScheme.primary,
onPrimary: themeData.colorScheme.onPrimary,
surface: themeData.colorScheme.surface,
onSurface: themeData.colorScheme.onSurface,
surfaceContainerLow: themeData.colorScheme.surfaceContainerLow,
surfaceContainer: themeData.colorScheme.surfaceContainer,
surfaceContainerHigh: themeData.colorScheme.surfaceContainerHigh,
);

ChatColors merge(ChatColors? other) {
if (other == null) return this;
return copyWith(
primary: other.primary,
onPrimary: other.onPrimary,
surface: other.surface,
onSurface: other.onSurface,
surfaceContainerLow: other.surfaceContainerLow,
surfaceContainer: other.surfaceContainer,
surfaceContainerHigh: other.surfaceContainerHigh,
);
}
}

ChatTheme merge(ChatTheme? other) {
@freezed
class ChatTypography with _$ChatTypography {
const factory ChatTypography({
required TextStyle bodyLarge,
required TextStyle bodyMedium,
required TextStyle bodySmall,
required TextStyle labelLarge,
required TextStyle labelMedium,
required TextStyle labelSmall,
}) = _ChatTypography;

const ChatTypography._();

factory ChatTypography.standard({String? fontFamily}) => ChatTypography(
bodyLarge: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
fontFamily: fontFamily,
),
bodyMedium: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
fontFamily: fontFamily,
),
bodySmall: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
fontFamily: fontFamily,
),
labelLarge: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
fontFamily: fontFamily,
),
labelMedium: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
fontFamily: fontFamily,
),
labelSmall: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w500,
fontFamily: fontFamily,
),
);

factory ChatTypography.fromThemeData(ThemeData themeData) => ChatTypography(
bodyLarge: themeData.textTheme.bodyLarge!,
bodyMedium: themeData.textTheme.bodyMedium!,
bodySmall: themeData.textTheme.bodySmall!,
labelLarge: themeData.textTheme.labelLarge!,
labelMedium: themeData.textTheme.labelMedium!,
labelSmall: themeData.textTheme.labelSmall!,
);

ChatTypography merge(ChatTypography? other) {
if (other == null) return this;
return copyWith(
backgroundColor: other.backgroundColor,
fontFamily: other.fontFamily,
imageMessageTheme: other.imageMessageTheme,
inputTheme: other.inputTheme,
isTypingTheme: other.isTypingTheme,
scrollToBottomTheme: other.scrollToBottomTheme,
textMessageTheme: other.textMessageTheme,
bodyLarge: other.bodyLarge,
bodyMedium: other.bodyMedium,
bodySmall: other.bodySmall,
labelLarge: other.labelLarge,
labelMedium: other.labelMedium,
labelSmall: other.labelSmall,
);
}
}
Loading

0 comments on commit 25fe1e2

Please sign in to comment.