chore: Follow up new reactions picker

This commit is contained in:
Christian Kußowski 2025-06-17 09:20:22 +02:00
commit 13f27eda9f
No known key found for this signature in database
GPG key ID: E067ECD60F1A0652
5 changed files with 488 additions and 354 deletions

View file

@ -24,7 +24,7 @@ abstract class AppConfig {
static String _privacyUrl = static String _privacyUrl =
'https://github.com/krille-chan/fluffychat/blob/main/PRIVACY.md'; 'https://github.com/krille-chan/fluffychat/blob/main/PRIVACY.md';
static const Set<String> defaultReactions = {'👍', '❤️', '😊'}; static const Set<String> defaultReactions = {'👍', '❤️', '😂', '😮', '😢'};
static String get privacyUrl => _privacyUrl; static String get privacyUrl => _privacyUrl;
static const String website = 'https://fluffychat.im'; static const String website = 'https://fluffychat.im';

View file

@ -891,6 +891,16 @@ class ChatController extends State<ChatPageWithRoom>
return true; return true;
} }
bool get canEditSelectedEvents {
if (isArchived ||
selectedEvents.length != 1 ||
!selectedEvents.first.status.isSent) {
return false;
}
return currentRoomBundle
.any((cl) => selectedEvents.first.senderId == cl!.userID);
}
void forwardEventsAction() async { void forwardEventsAction() async {
if (selectedEvents.isEmpty) return; if (selectedEvents.isEmpty) return;
await showScaffoldDialog( await showScaffoldDialog(

View file

@ -21,6 +21,7 @@ class ChatInputRow extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
const height = 48.0; const height = 48.0;
if (!controller.room.otherPartyCanReceiveMessages) { if (!controller.room.otherPartyCanReceiveMessages) {
@ -39,231 +40,290 @@ class ChatInputRow extends StatelessWidget {
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[ children: controller.selectMode
const SizedBox(width: 4), ? <Widget>[
AnimatedContainer( if (controller.selectedEvents
duration: FluffyThemes.animationDuration, .every((event) => event.status == EventStatus.error))
curve: FluffyThemes.animationCurve, SizedBox(
width: controller.sendController.text.isNotEmpty ? 0 : height, height: height,
height: height, child: TextButton(
alignment: Alignment.center, style: TextButton.styleFrom(
decoration: const BoxDecoration(), foregroundColor: theme.colorScheme.error,
clipBehavior: Clip.hardEdge, ),
child: PopupMenuButton<String>( onPressed: controller.deleteErrorEventsAction,
useRootNavigator: true, child: Row(
enabled: !controller.selectMode, children: <Widget>[
icon: const Icon(Icons.add_circle_outline), const Icon(Icons.delete),
iconColor: theme.colorScheme.onPrimaryContainer, Text(L10n.of(context).delete),
onSelected: controller.onAddPopupMenuButtonSelected, ],
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[ ),
if (PlatformInfos.isMobile) ),
PopupMenuItem<String>( )
value: 'location', else
child: ListTile( SizedBox(
leading: CircleAvatar( height: height,
backgroundColor: theme.colorScheme.onPrimaryContainer, child: TextButton(
foregroundColor: theme.colorScheme.primaryContainer, onPressed: controller.forwardEventsAction,
child: const Icon(Icons.gps_fixed_outlined), child: Row(
children: <Widget>[
const Icon(Icons.keyboard_arrow_left_outlined),
Text(L10n.of(context).forward),
],
), ),
title: Text(L10n.of(context).shareLocation),
contentPadding: const EdgeInsets.all(0),
), ),
), ),
PopupMenuItem<String>( controller.selectedEvents.length == 1
value: 'image', ? controller.selectedEvents.first
child: ListTile( .getDisplayEvent(controller.timeline!)
leading: CircleAvatar( .status
backgroundColor: theme.colorScheme.onPrimaryContainer, .isSent
foregroundColor: theme.colorScheme.primaryContainer, ? SizedBox(
child: const Icon(Icons.photo_outlined), height: height,
), child: TextButton(
title: Text(L10n.of(context).sendImage), onPressed: controller.replyAction,
contentPadding: const EdgeInsets.all(0), child: Row(
children: <Widget>[
Text(L10n.of(context).reply),
const Icon(Icons.keyboard_arrow_right),
],
),
),
)
: SizedBox(
height: height,
child: TextButton(
onPressed: controller.sendAgainAction,
child: Row(
children: <Widget>[
Text(L10n.of(context).tryToSendAgain),
const SizedBox(width: 4),
const Icon(Icons.send_outlined, size: 16),
],
),
),
)
: const SizedBox.shrink(),
]
: <Widget>[
const SizedBox(width: 4),
AnimatedContainer(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
width: controller.sendController.text.isNotEmpty ? 0 : height,
height: height,
alignment: Alignment.center,
decoration: const BoxDecoration(),
clipBehavior: Clip.hardEdge,
child: PopupMenuButton<String>(
useRootNavigator: true,
icon: const Icon(Icons.add_circle_outline),
iconColor: theme.colorScheme.onPrimaryContainer,
onSelected: controller.onAddPopupMenuButtonSelected,
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<String>>[
if (PlatformInfos.isMobile)
PopupMenuItem<String>(
value: 'location',
child: ListTile(
leading: CircleAvatar(
backgroundColor:
theme.colorScheme.onPrimaryContainer,
foregroundColor: theme.colorScheme.primaryContainer,
child: const Icon(Icons.gps_fixed_outlined),
),
title: Text(L10n.of(context).shareLocation),
contentPadding: const EdgeInsets.all(0),
),
),
PopupMenuItem<String>(
value: 'image',
child: ListTile(
leading: CircleAvatar(
backgroundColor: theme.colorScheme.onPrimaryContainer,
foregroundColor: theme.colorScheme.primaryContainer,
child: const Icon(Icons.photo_outlined),
),
title: Text(L10n.of(context).sendImage),
contentPadding: const EdgeInsets.all(0),
),
),
PopupMenuItem<String>(
value: 'video',
child: ListTile(
leading: CircleAvatar(
backgroundColor: theme.colorScheme.onPrimaryContainer,
foregroundColor: theme.colorScheme.primaryContainer,
child: const Icon(Icons.video_camera_back_outlined),
),
title: Text(L10n.of(context).sendVideo),
contentPadding: const EdgeInsets.all(0),
),
),
PopupMenuItem<String>(
value: 'file',
child: ListTile(
leading: CircleAvatar(
backgroundColor: theme.colorScheme.onPrimaryContainer,
foregroundColor: theme.colorScheme.primaryContainer,
child: const Icon(Icons.attachment_outlined),
),
title: Text(L10n.of(context).sendFile),
contentPadding: const EdgeInsets.all(0),
),
),
],
), ),
), ),
PopupMenuItem<String>( if (PlatformInfos.isMobile)
value: 'video', AnimatedContainer(
child: ListTile( duration: FluffyThemes.animationDuration,
leading: CircleAvatar( curve: FluffyThemes.animationCurve,
backgroundColor: theme.colorScheme.onPrimaryContainer, width: controller.sendController.text.isNotEmpty ? 0 : height,
foregroundColor: theme.colorScheme.primaryContainer, height: height,
child: const Icon(Icons.video_camera_back_outlined), alignment: Alignment.center,
decoration: const BoxDecoration(),
clipBehavior: Clip.hardEdge,
child: PopupMenuButton(
useRootNavigator: true,
icon: const Icon(Icons.camera_alt_outlined),
onSelected: controller.onAddPopupMenuButtonSelected,
iconColor: theme.colorScheme.onPrimaryContainer,
itemBuilder: (context) => [
PopupMenuItem<String>(
value: 'camera-video',
child: ListTile(
leading: CircleAvatar(
backgroundColor:
theme.colorScheme.onPrimaryContainer,
foregroundColor: theme.colorScheme.primaryContainer,
child: const Icon(Icons.videocam_outlined),
),
title: Text(L10n.of(context).recordAVideo),
contentPadding: const EdgeInsets.all(0),
),
),
PopupMenuItem<String>(
value: 'camera',
child: ListTile(
leading: CircleAvatar(
backgroundColor:
theme.colorScheme.onPrimaryContainer,
foregroundColor: theme.colorScheme.primaryContainer,
child: const Icon(Icons.camera_alt_outlined),
),
title: Text(L10n.of(context).takeAPhoto),
contentPadding: const EdgeInsets.all(0),
),
),
],
), ),
title: Text(L10n.of(context).sendVideo), ),
contentPadding: const EdgeInsets.all(0), Container(
height: height,
width: height,
alignment: Alignment.center,
child: IconButton(
tooltip: L10n.of(context).emojis,
color: theme.colorScheme.onPrimaryContainer,
icon: PageTransitionSwitcher(
transitionBuilder: (
Widget child,
Animation<double> primaryAnimation,
Animation<double> secondaryAnimation,
) {
return SharedAxisTransition(
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
transitionType: SharedAxisTransitionType.scaled,
fillColor: Colors.transparent,
child: child,
);
},
child: Icon(
controller.showEmojiPicker
? Icons.keyboard
: Icons.add_reaction_outlined,
key: ValueKey(controller.showEmojiPicker),
),
),
onPressed: controller.emojiPickerAction,
), ),
), ),
PopupMenuItem<String>( if (Matrix.of(context).isMultiAccount &&
value: 'file', Matrix.of(context).hasComplexBundles &&
child: ListTile( Matrix.of(context).currentBundle!.length > 1)
leading: CircleAvatar( Container(
backgroundColor: theme.colorScheme.onPrimaryContainer, width: height,
foregroundColor: theme.colorScheme.primaryContainer, height: height,
child: const Icon(Icons.attachment_outlined), alignment: Alignment.center,
), child: _ChatAccountPicker(controller),
title: Text(L10n.of(context).sendFile),
contentPadding: const EdgeInsets.all(0),
), ),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 0.0),
child: InputBar(
room: controller.room,
minLines: 1,
maxLines: 8,
autofocus: !PlatformInfos.isMobile,
keyboardType: TextInputType.multiline,
textInputAction:
AppConfig.sendOnEnter == true && PlatformInfos.isMobile
? TextInputAction.send
: null,
onSubmitted: controller.onInputBarSubmitted,
onSubmitImage: controller.sendImageFromClipBoard,
focusNode: controller.inputFocus,
controller: controller.sendController,
decoration: InputDecoration(
contentPadding: const EdgeInsets.only(
left: 6.0,
right: 6.0,
bottom: 6.0,
top: 3.0,
),
hintText: L10n.of(context).writeAMessage,
hintMaxLines: 1,
border: InputBorder.none,
enabledBorder: InputBorder.none,
filled: false,
),
onChanged: controller.onInputBarChanged,
),
),
),
Container(
height: height,
width: height,
alignment: Alignment.center,
child: PlatformInfos.platformCanRecord &&
controller.sendController.text.isEmpty
? FloatingActionButton.small(
tooltip: L10n.of(context).voiceMessage,
onPressed: controller.voiceMessageAction,
elevation: 0,
heroTag: null,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(height),
),
backgroundColor: theme.bubbleColor,
foregroundColor: theme.onBubbleColor,
child: const Icon(Icons.mic_none_outlined),
)
: FloatingActionButton.small(
tooltip: L10n.of(context).send,
onPressed: controller.send,
elevation: 0,
heroTag: null,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(height),
),
backgroundColor: theme.bubbleColor,
foregroundColor: theme.onBubbleColor,
child: const Icon(Icons.send_outlined),
),
), ),
], ],
),
),
if (PlatformInfos.isMobile)
AnimatedContainer(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
width: controller.sendController.text.isNotEmpty ? 0 : height,
height: height,
alignment: Alignment.center,
decoration: const BoxDecoration(),
clipBehavior: Clip.hardEdge,
child: PopupMenuButton(
enabled: !controller.selectMode,
useRootNavigator: true,
icon: const Icon(Icons.camera_alt_outlined),
onSelected: controller.onAddPopupMenuButtonSelected,
iconColor: theme.colorScheme.onPrimaryContainer,
itemBuilder: (context) => [
PopupMenuItem<String>(
value: 'camera-video',
child: ListTile(
leading: CircleAvatar(
backgroundColor: theme.colorScheme.onPrimaryContainer,
foregroundColor: theme.colorScheme.primaryContainer,
child: const Icon(Icons.videocam_outlined),
),
title: Text(L10n.of(context).recordAVideo),
contentPadding: const EdgeInsets.all(0),
),
),
PopupMenuItem<String>(
value: 'camera',
child: ListTile(
leading: CircleAvatar(
backgroundColor: theme.colorScheme.onPrimaryContainer,
foregroundColor: theme.colorScheme.primaryContainer,
child: const Icon(Icons.camera_alt_outlined),
),
title: Text(L10n.of(context).takeAPhoto),
contentPadding: const EdgeInsets.all(0),
),
),
],
),
),
Container(
height: height,
width: height,
alignment: Alignment.center,
child: IconButton(
tooltip: L10n.of(context).emojis,
color: theme.colorScheme.onPrimaryContainer,
icon: PageTransitionSwitcher(
transitionBuilder: (
Widget child,
Animation<double> primaryAnimation,
Animation<double> secondaryAnimation,
) {
return SharedAxisTransition(
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
transitionType: SharedAxisTransitionType.scaled,
fillColor: Colors.transparent,
child: child,
);
},
child: Icon(
controller.showEmojiPicker
? Icons.keyboard
: Icons.add_reaction_outlined,
key: ValueKey(controller.showEmojiPicker),
),
),
onPressed:
controller.selectMode ? null : controller.emojiPickerAction,
),
),
if (Matrix.of(context).isMultiAccount &&
Matrix.of(context).hasComplexBundles &&
Matrix.of(context).currentBundle!.length > 1)
Container(
width: height,
height: height,
alignment: Alignment.center,
child: _ChatAccountPicker(controller),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 0.0),
child: InputBar(
room: controller.room,
minLines: 1,
readOnly: controller.selectMode,
maxLines: 8,
autofocus: !PlatformInfos.isMobile,
keyboardType: TextInputType.multiline,
textInputAction:
AppConfig.sendOnEnter == true && PlatformInfos.isMobile
? TextInputAction.send
: null,
onSubmitted: controller.onInputBarSubmitted,
onSubmitImage: controller.sendImageFromClipBoard,
focusNode: controller.inputFocus,
controller: controller.sendController,
decoration: InputDecoration(
contentPadding: const EdgeInsets.only(
left: 6.0,
right: 6.0,
bottom: 6.0,
top: 3.0,
),
hintText: L10n.of(context).writeAMessage,
hintMaxLines: 1,
border: InputBorder.none,
enabledBorder: InputBorder.none,
filled: false,
),
onChanged: controller.onInputBarChanged,
),
),
),
Opacity(
opacity: controller.selectMode ? 0.66 : 1,
child: Container(
height: height,
width: height,
alignment: Alignment.center,
child: PlatformInfos.platformCanRecord &&
controller.sendController.text.isEmpty
? FloatingActionButton.small(
tooltip: L10n.of(context).voiceMessage,
onPressed: controller.selectMode
? null
: controller.voiceMessageAction,
elevation: 0,
heroTag: null,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(height),
),
backgroundColor: theme.bubbleColor,
foregroundColor: theme.onBubbleColor,
child: const Icon(Icons.mic_none_outlined),
)
: FloatingActionButton.small(
tooltip: L10n.of(context).send,
onPressed: controller.selectMode ? null : controller.send,
elevation: 0,
heroTag: null,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(height),
),
backgroundColor: theme.bubbleColor,
foregroundColor: theme.onBubbleColor,
child: const Icon(Icons.send_outlined),
),
),
),
],
); );
} }
} }

View file

@ -37,6 +37,12 @@ class ChatView extends StatelessWidget {
List<Widget> _appBarActions(BuildContext context) { List<Widget> _appBarActions(BuildContext context) {
if (controller.selectMode) { if (controller.selectMode) {
return [ return [
if (controller.canEditSelectedEvents)
IconButton(
icon: const Icon(Icons.edit_outlined),
tooltip: L10n.of(context).edit,
onPressed: controller.editSelectedEventAction,
),
IconButton( IconButton(
icon: const Icon(Icons.copy_outlined), icon: const Icon(Icons.copy_outlined),
tooltip: L10n.of(context).copy, tooltip: L10n.of(context).copy,

View file

@ -187,6 +187,9 @@ class Message extends StatelessWidget {
final showReceiptsRow = final showReceiptsRow =
event.hasAggregatedEvents(timeline, RelationshipTypes.reaction); event.hasAggregatedEvents(timeline, RelationshipTypes.reaction);
final showReactionPicker =
singleSelected && event.room.canSendDefaultMessages;
return Center( return Center(
child: Swipeable( child: Swipeable(
key: ValueKey(event.eventId), key: ValueKey(event.eventId),
@ -263,6 +266,7 @@ class Message extends StatelessWidget {
child: animateIn child: animateIn
? const SizedBox(height: 0, width: double.infinity) ? const SizedBox(height: 0, width: double.infinity)
: Stack( : Stack(
clipBehavior: Clip.none,
children: [ children: [
Positioned( Positioned(
top: 0, top: 0,
@ -290,7 +294,7 @@ class Message extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: rowMainAxisAlignment, mainAxisAlignment: rowMainAxisAlignment,
children: [ children: [
if (longPressSelect) if (longPressSelect && !event.redacted)
SizedBox( SizedBox(
height: 32, height: 32,
width: Avatar.defaultSize, width: Avatar.defaultSize,
@ -413,8 +417,10 @@ class Message extends StatelessWidget {
), ),
Container( Container(
alignment: alignment, alignment: alignment,
padding: padding: EdgeInsets.only(
const EdgeInsets.only(left: 8), left: 8,
bottom: showReactionPicker ? 40 : 0,
),
child: GestureDetector( child: GestureDetector(
onLongPress: longPressSelect onLongPress: longPressSelect
? null ? null
@ -625,156 +631,208 @@ class Message extends StatelessWidget {
), ),
], ],
), ),
Positioned(
left:
ownMessage ? null : Avatar.defaultSize + 8,
right: ownMessage ? 0 : null,
bottom: 0,
child: AnimatedSize(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
alignment: Alignment.bottomCenter,
child: showReactionPicker
? Padding(
padding: const EdgeInsets.only(
top: 8.0,
bottom: 4.0,
),
child: Material(
elevation: 4,
borderRadius: BorderRadius.circular(
AppConfig.borderRadius,
),
shadowColor: theme
.colorScheme.surface
.withAlpha(128),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
...AppConfig.defaultReactions
.map(
(emoji) => IconButton(
padding: EdgeInsets.zero,
icon: Center(
child: Opacity(
opacity: sentReactions
.contains(emoji)
? 0.33
: 1,
child: Text(
emoji,
style:
const TextStyle(
fontSize: 20,
),
textAlign:
TextAlign.center,
),
),
),
onPressed: sentReactions
.contains(emoji)
? null
: () {
onSelect(event);
event.room
.sendReaction(
event.eventId,
emoji,
);
},
),
),
IconButton(
icon: const Icon(
Icons.add_reaction_outlined,
),
tooltip: L10n.of(context)
.customReaction,
onPressed: () async {
final emoji =
await showDialog<
String>(
context: context,
builder: (context) =>
AlertDialog(
title: Row(
mainAxisSize:
MainAxisSize.min,
spacing: 4,
children: [
CloseButton(
onPressed: () =>
Navigator.of(
context,
).pop(
null,
),
),
Text(
L10n.of(context)
.customReaction,
),
],
),
titlePadding:
const EdgeInsets
.all(8),
contentPadding:
const EdgeInsets
.all(0),
clipBehavior:
Clip.hardEdge,
content: SizedBox(
width: 350,
height: 350,
child: EmojiPicker(
onEmojiSelected: (
_,
emoji,
) =>
Navigator.of(
context,
).pop(
emoji.emoji,
),
config: Config(
emojiViewConfig:
const EmojiViewConfig(
backgroundColor:
Colors
.transparent,
),
bottomActionBarConfig:
const BottomActionBarConfig(
enabled: false,
),
categoryViewConfig:
CategoryViewConfig(
initCategory:
Category
.SMILEYS,
backspaceColor: theme
.colorScheme
.primary,
iconColor: theme
.colorScheme
.primary
.withAlpha(
128,
),
iconColorSelected:
theme
.colorScheme
.primary,
indicatorColor: theme
.colorScheme
.primary,
backgroundColor:
theme
.colorScheme
.surface,
),
skinToneConfig:
SkinToneConfig(
dialogBackgroundColor:
Color.lerp(
theme
.colorScheme
.surface,
theme
.colorScheme
.primaryContainer,
0.75,
)!,
indicatorColor: theme
.colorScheme
.onSurface,
),
),
),
),
),
);
if (emoji == null) return;
if (sentReactions.contains(
emoji,
)) {
return;
}
onSelect(event);
await event.room
.sendReaction(
event.eventId,
emoji,
);
},
),
],
),
),
)
: const SizedBox.shrink(),
),
),
], ],
), ),
); );
}, },
), ),
Padding(
padding: const EdgeInsets.only(left: Avatar.defaultSize + 8.0),
child: AnimatedSize(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
alignment: Alignment.bottomCenter,
child: singleSelected && event.room.canSendDefaultMessages
? Padding(
padding: const EdgeInsets.only(bottom: 4.0),
child: Material(
elevation: 4,
borderRadius:
BorderRadius.circular(AppConfig.borderRadius),
shadowColor: theme.appBarTheme.shadowColor,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.reply_outlined),
tooltip: L10n.of(context).reply,
onPressed: onSwipe,
),
if (ownMessage)
IconButton(
icon: const Icon(Icons.edit_outlined),
tooltip: L10n.of(context).edit,
onPressed: onEdit,
),
IconButton(
icon: const Icon(Icons.add_reaction_outlined),
tooltip: L10n.of(context).customReaction,
onPressed: () async {
final emoji = await showDialog<String>(
context: context,
builder: (context) => AlertDialog(
title: Row(
mainAxisSize: MainAxisSize.min,
spacing: 4,
children: [
CloseButton(
onPressed: () =>
Navigator.of(context)
.pop(null),
),
Text(
L10n.of(context).customReaction,
),
],
),
titlePadding: const EdgeInsets.all(8),
contentPadding: const EdgeInsets.all(0),
clipBehavior: Clip.hardEdge,
content: SizedBox(
width: 350,
height: 350,
child: EmojiPicker(
onEmojiSelected: (_, emoji) =>
Navigator.of(context)
.pop(emoji.emoji),
config: Config(
emojiViewConfig:
const EmojiViewConfig(
backgroundColor:
Colors.transparent,
),
bottomActionBarConfig:
const BottomActionBarConfig(
enabled: false,
),
categoryViewConfig:
CategoryViewConfig(
initCategory: Category.SMILEYS,
backspaceColor:
theme.colorScheme.primary,
iconColor: theme
.colorScheme.primary
.withAlpha(128),
iconColorSelected:
theme.colorScheme.primary,
indicatorColor:
theme.colorScheme.primary,
backgroundColor:
theme.colorScheme.surface,
),
skinToneConfig: SkinToneConfig(
dialogBackgroundColor:
Color.lerp(
theme.colorScheme.surface,
theme.colorScheme
.primaryContainer,
0.75,
)!,
indicatorColor:
theme.colorScheme.onSurface,
),
),
),
),
),
);
if (emoji == null) return;
if (sentReactions.contains(emoji)) return;
await event.room.sendReaction(
event.eventId,
emoji,
);
},
),
...AppConfig.defaultReactions.map(
(emoji) => IconButton(
padding: EdgeInsets.zero,
icon: Center(
child: Opacity(
opacity: sentReactions.contains(emoji)
? 0.33
: 1,
child: Text(
emoji,
style: const TextStyle(fontSize: 20),
textAlign: TextAlign.center,
),
),
),
onPressed: sentReactions.contains(emoji)
? null
: () {
onSelect(event);
event.room.sendReaction(
event.eventId,
emoji,
);
},
),
),
],
),
),
)
: const SizedBox.shrink(),
),
),
AnimatedSize( AnimatedSize(
duration: FluffyThemes.animationDuration, duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve, curve: FluffyThemes.animationCurve,
alignment: Alignment.bottomCenter,
child: !showReceiptsRow child: !showReceiptsRow
? const SizedBox.shrink() ? const SizedBox.shrink()
: Padding( : Padding(