Skip to content

Commit 9aba2a8

Browse files
committed
emoji [nfc]: Add reusable EmojiWidget; use it in ReactionChip
There are several places where we switch on EmojiDisplay, mostly with TODOs to factor the switch out to something central. Here's a widget to accomplish that factoring, used first in ReactionChip.
1 parent 3c8f1e0 commit 9aba2a8

File tree

2 files changed

+97
-16
lines changed

2 files changed

+97
-16
lines changed

lib/widgets/emoji.dart

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,96 @@ import '../api/model/model.dart';
55
import '../model/emoji.dart';
66
import 'content.dart';
77

8+
/// A widget showing an emoji.
9+
class EmojiWidget extends StatelessWidget {
10+
const EmojiWidget({
11+
super.key,
12+
required this.emojiDisplay,
13+
required this.squareDimension,
14+
this.squareDimensionScaler = TextScaler.noScaling,
15+
this.imagePlaceholderStyle = EmojiImagePlaceholderStyle.square,
16+
this.neverAnimateImage = false,
17+
this.buildCustomTextEmoji,
18+
});
19+
20+
final EmojiDisplay emojiDisplay;
21+
22+
/// The base width and height to use for the emoji square.
23+
///
24+
/// This will be scaled by [squareDimensionScaler].
25+
///
26+
/// This is ignored when using the plain-text emoji style.
27+
final double squareDimension;
28+
29+
/// A [TextScaler] to apply to [squareDimension].
30+
///
31+
/// Defaults to [TextScaler.noScaling].
32+
///
33+
/// This is ignored when using the plain-text emoji style.
34+
final TextScaler squareDimensionScaler;
35+
36+
final EmojiImagePlaceholderStyle imagePlaceholderStyle;
37+
38+
/// Whether to show an animated emoji in its still (non-animated) variant
39+
/// only, even if device settings permit animation.
40+
///
41+
/// Defaults to false.
42+
final bool neverAnimateImage;
43+
44+
/// An optional callback to specify a custom plain-text emoji style.
45+
///
46+
/// If this is not passed, a simple [Text] widget with no added styling
47+
/// is used.
48+
final Widget Function()? buildCustomTextEmoji;
49+
50+
Widget _buildTextEmoji() {
51+
return buildCustomTextEmoji?.call()
52+
?? Text(textEmojiForEmojiName(emojiDisplay.emojiName));
53+
}
54+
55+
ImageErrorWidgetBuilder _getImageErrorBuilder(double effectiveSize) {
56+
return (_, _, _) => switch (imagePlaceholderStyle) {
57+
EmojiImagePlaceholderStyle.square => SizedBox.square(dimension: effectiveSize),
58+
EmojiImagePlaceholderStyle.nothing => SizedBox.shrink(),
59+
EmojiImagePlaceholderStyle.text => _buildTextEmoji(),
60+
};
61+
}
62+
63+
@override
64+
Widget build(BuildContext context) {
65+
final effectiveSquareDimension = squareDimensionScaler.scale(squareDimension);
66+
67+
final emojiDisplay = this.emojiDisplay;
68+
return switch (emojiDisplay) {
69+
ImageEmojiDisplay() => ImageEmojiWidget(
70+
emojiDisplay: emojiDisplay,
71+
size: squareDimension,
72+
textScaler: squareDimensionScaler,
73+
errorBuilder: _getImageErrorBuilder(effectiveSquareDimension)),
74+
UnicodeEmojiDisplay() => UnicodeEmojiWidget(
75+
size: squareDimension,
76+
emojiDisplay: emojiDisplay),
77+
TextEmojiDisplay() => _buildTextEmoji(),
78+
};
79+
}
80+
}
81+
82+
/// In [EmojiWidget], how to present an image emoji when we don't have the image.
83+
enum EmojiImagePlaceholderStyle {
84+
/// A square of [EmojiWidget.squareDimension]
85+
/// scaled by [EmojiWidget.squareDimensionScaler].
86+
square,
87+
88+
/// A [SizedBox.shrink].
89+
nothing,
90+
91+
/// A plain-text emoji.
92+
///
93+
/// See [EmojiWidget.buildCustomTextEmoji] for how plain-text emojis are
94+
/// styled.
95+
text,
96+
}
97+
898
class UnicodeEmojiWidget extends StatelessWidget {
999
const UnicodeEmojiWidget({
10100
super.key,
@@ -91,7 +181,6 @@ class UnicodeEmojiWidget extends StatelessWidget {
91181
}
92182
}
93183

94-
95184
class ImageEmojiWidget extends StatelessWidget {
96185
const ImageEmojiWidget({
97186
super.key,

lib/widgets/emoji_reaction.dart

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -224,22 +224,14 @@ class ReactionChip extends StatelessWidget {
224224
emojiName: emojiName,
225225
).resolve(store.userSettings);
226226

227-
final emoji = switch (emojiDisplay) {
228-
UnicodeEmojiDisplay() => UnicodeEmojiWidget(
229-
size: _squareEmojiSize,
230-
textScaler: _squareEmojiScalerClamped(context),
231-
emojiDisplay: emojiDisplay),
232-
ImageEmojiDisplay() => ImageEmojiWidget(
233-
size: _squareEmojiSize,
234-
// Unicode and text emoji get scaled; it would look weird if image emoji didn't.
235-
textScaler: _squareEmojiScalerClamped(context),
236-
emojiDisplay: emojiDisplay,
237-
errorBuilder: (context, _, _) => _TextEmoji(
238-
emojiName: emojiName, selected: selfVoted),
239-
),
240-
TextEmojiDisplay() => _TextEmoji(
227+
final emoji = EmojiWidget(
228+
emojiDisplay: emojiDisplay,
229+
squareDimension: _squareEmojiSize,
230+
squareDimensionScaler: _squareEmojiScalerClamped(context),
231+
imagePlaceholderStyle: EmojiImagePlaceholderStyle.text,
232+
buildCustomTextEmoji: () => _TextEmoji(
241233
emojiName: emojiName, selected: selfVoted),
242-
};
234+
);
243235

244236
Widget result = Material(
245237
color: backgroundColor,

0 commit comments

Comments
 (0)