Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
8327838
Create package
nicolasbraun Jul 17, 2025
664493f
Create ChatTheme extension
nicolasbraun Jul 17, 2025
d40608b
Typedef message's reactions
nicolasbraun Jul 17, 2025
c32ad1d
Create Reaction model
nicolasbraun Jul 17, 2025
1f23225
Create MenuItem model
nicolasbraun Jul 17, 2025
5264e31
Create ReactionTile (little bubble for one reaction under the message)
nicolasbraun Jul 17, 2025
af2f921
Create the ReactionList (bottomSheet with all the reactions on a mess…
nicolasbraun Jul 17, 2025
e0b343c
Create FlyerChatReactRow (adaptative row for all reactions)
nicolasbraun Jul 17, 2025
2035c3d
Add isSentByme to the tap callbacks
nicolasbraun Jul 17, 2025
ba4a782
Create the ChatProvider class that allow to easily pass all chat prov…
nicolasbraun Jul 17, 2025
21ffdb9
Expose the method to build the "inner" message widget
nicolasbraun Jul 17, 2025
0647867
Create the ReactionDialog
nicolasbraun Jul 17, 2025
fe4bbe5
Add a ReactionsBuilder to Builders
nicolasbraun Jul 17, 2025
b414f0f
Add surfaceContainerHighest to ChatTheme
nicolasbraun Jul 17, 2025
f77ea78
Add the reactions (from builder) in the ChatMessage Widget
nicolasbraun Jul 17, 2025
baf5137
Expose class and widgets
nicolasbraun Jul 17, 2025
f078776
Add example
nicolasbraun Jul 17, 2025
a45415f
List: fix total count + filter out reactions at 0
nicolasbraun Jul 18, 2025
0f60fdf
Fix Tile count not updating when Reaction row is updated
nicolasbraun Jul 18, 2025
929f014
Filter count 0 reactions from FlyerChatReactionsRow
nicolasbraun Jul 18, 2025
85e9956
Also reset Tile tapped state on widget Update
nicolasbraun Jul 18, 2025
f7af945
Lighter color for surfaceContainerHighest in dark theme
nicolasbraun Jul 18, 2025
2ee1b18
Lighter colors for elevated elements
nicolasbraun Jul 23, 2025
78ceecc
get dependencies
demchenkoalex Jul 26, 2025
a1c90ff
Simplify widget alignment
nicolasbraun Jul 29, 2025
9627ac2
Use pull_down_button
nicolasbraun Jul 29, 2025
e70d065
CursorBot comments fix
nicolasbraun Jul 29, 2025
13d8a03
Fix comment
nicolasbraun Jul 29, 2025
8b0c2e7
Add HoverFloatEffect
nicolasbraun Jul 30, 2025
d6fea1f
Rename alignment parameter
nicolasbraun Jul 30, 2025
ce0d231
Fix type for onReactionTap in showDialog
nicolasbraun Jul 30, 2025
f5921d8
Dialog ) Use a builder for the more reaction widget
nicolasbraun Jul 30, 2025
65f282a
Also rename alignment in the show method + specify it's horizontal in…
nicolasbraun Jul 30, 2025
ec5bff7
Use a builder for context menu to give user flexibility
nicolasbraun Aug 11, 2025
16d61bb
Disable long press on system messages
nicolasbraun Aug 11, 2025
94925f8
Fix import order
nicolasbraun Aug 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions examples/flyer_chat/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ PODS:
- DKPhotoGallery/Resource (0.0.19):
- SDWebImage
- SwiftyGif
- emoji_picker_flutter (0.0.1):
- Flutter
- file_picker (0.0.1):
- DKImagePickerController/PhotoGallery
- Flutter
Expand All @@ -43,20 +45,25 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- SDWebImage (5.21.0):
- SDWebImage/Core (= 5.21.0)
- SDWebImage/Core (5.21.0)
- SDWebImage (5.21.1):
- SDWebImage/Core (= 5.21.1)
- SDWebImage/Core (5.21.1)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- SwiftyGif (5.4.5)
- url_launcher_ios (0.0.1):
- Flutter

DEPENDENCIES:
- emoji_picker_flutter (from `.symlinks/plugins/emoji_picker_flutter/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- integration_test (from `.symlinks/plugins/integration_test/ios`)
- isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)

SPEC REPOS:
Expand All @@ -67,6 +74,8 @@ SPEC REPOS:
- SwiftyGif

EXTERNAL SOURCES:
emoji_picker_flutter:
:path: ".symlinks/plugins/emoji_picker_flutter/ios"
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
Flutter:
Expand All @@ -79,19 +88,23 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/isar_flutter_libs/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"

SPEC CHECKSUMS:
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
emoji_picker_flutter: ece213fc274bdddefb77d502d33080dc54e616cc
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
isar_flutter_libs: 9fc2cfb928c539e1b76c481ba5d143d556d94920
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868
SDWebImage: f29024626962457f3470184232766516dee8dfea
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d

Expand Down
1 change: 1 addition & 0 deletions examples/flyer_chat/lib/api/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ class ApiState extends State<Api> {
Message item, {
int? index,
TapUpDetails? details,
required bool isSentByMe,
}) async {
await _chatController.removeMessage(item);

Expand Down
21 changes: 21 additions & 0 deletions examples/flyer_chat/lib/create_message.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ Future<Message> createMessage(
sentAt: localOnly == true ? DateTime.now().toUtc() : null,
text: text ?? lorem(paragraphs: 1, words: Random().nextInt(30) + 1),
metadata: isOnlyEmoji(text ?? '') ? {'isOnlyEmoji': true} : null,
reactions: {
'👍': [authorId, 'someOtherId'],
'👎': ['someOtherId'],
'👏': [authorId],
'👌': [authorId],
'👊': [authorId],
},
);
} else {
final orientation = ['portrait', 'square', 'wide'][Random().nextInt(3)];
Expand Down Expand Up @@ -61,6 +68,13 @@ Future<Message> createMessage(
source: response.data['img'],
thumbhash: response.data['thumbhash'],
blurhash: response.data['blurhash'],
reactions: {
'👍': [authorId, 'someOtherId'],
'👎': ['someOtherId'],
'👏': [authorId],
'👌': [authorId],
'👊': [authorId],
},
);
} else {
message = FileMessage(
Expand All @@ -71,6 +85,13 @@ Future<Message> createMessage(
sentAt: localOnly == true ? DateTime.now().toUtc() : null,
source: response.data['img'],
size: 1000000,
reactions: {
'👍': [authorId, 'someOtherId'],
'👎': ['someOtherId'],
'👏': [authorId],
'👌': [authorId],
'👊': [authorId],
},
);
}
}
Expand Down
105 changes: 94 additions & 11 deletions examples/flyer_chat/lib/local.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:math';

import 'package:dio/dio.dart';
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
Expand All @@ -10,6 +11,7 @@ import 'package:flutter_chat_ui/flutter_chat_ui.dart';
import 'package:flutter_link_previewer/flutter_link_previewer.dart';
import 'package:flyer_chat_file_message/flyer_chat_file_message.dart';
import 'package:flyer_chat_image_message/flyer_chat_image_message.dart';
import 'package:flyer_chat_reactions/flyer_chat_reactions.dart';
import 'package:flyer_chat_system_message/flyer_chat_system_message.dart';
import 'package:flyer_chat_text_message/flyer_chat_text_message.dart';
import 'package:image_picker/image_picker.dart';
Expand Down Expand Up @@ -233,6 +235,30 @@ class LocalState extends State<Local> {
child: child,
);
},
reactionsBuilder: (context, message, isSentByMe) {
final reactions = reactionsFromMessageReactions(
reactions: message.reactions,
currentUserId: _currentUser.id,
);
return FlyerChatReactionsRow(
reactions: reactions,
alignment: isSentByMe
? MainAxisAlignment.start
: MainAxisAlignment.end,

/// Open List on tap (WhatsApp Style)
// onReactionTap: (reaction) =>
// showReactionsList(context: context, reactions: reactions),
/// Or react on tap (Slack Style)
onReactionTap: (reaction) =>
_handleReactionTap(message, reaction),
removeOrAddLocallyOnTap: true,
onReactionLongPress: (reaction) =>
showReactionsList(context: context, reactions: reactions),
onSurplusReactionTap: () =>
showReactionsList(context: context, reactions: reactions),
);
},
),
chatController: _chatController,
currentUserId: _currentUser.id,
Expand Down Expand Up @@ -272,19 +298,75 @@ class LocalState extends State<Local> {
Message message, {
int? index,
LongPressStartDetails? details,
required bool isSentByMe,
}) async {
// Skip showing menu for system messages
if (message.authorId == 'system' || details == null) return;
if (message.authorId == 'system') return;
showReactionsDialog(
context,
message,
isSentByMe: isSentByMe,
// reactions: ['📌'], // The default reactions to propose in the dialog
userReactions: getUserReactions(message.reactions, _currentUser.id),
onReactionTap: (reaction) => _handleReactionTap(message, reaction),
onMoreReactionsTap: () async {
// Use whichever emoji picker you want
final picked = await _showEmojiPicker();
if (picked != null) {
_handleReactionTap(message, picked);
}
},
bottomWidgetBuilder: (context) => _buildContextualMenu(message),
);
}

Future<String?> _showEmojiPicker() {
return showModalBottomSheet(
context: context,
useSafeArea: true,
builder: (context) => EmojiPicker(
onEmojiSelected: (Category? category, Emoji emoji) {
Navigator.of(context).pop(emoji.emoji);
},
config: Config(
height: 250,
checkPlatformCompatibility: false,
viewOrderConfig: const ViewOrderConfig(),
skinToneConfig: const SkinToneConfig(),
categoryViewConfig: const CategoryViewConfig(),
bottomActionBarConfig: const BottomActionBarConfig(enabled: false),
searchViewConfig: const SearchViewConfig(),
),
),
);
}

// Calculate position for the menu
final position = details.globalPosition;
void _handleReactionTap(Message message, String reaction) {
debugPrint('reaction Tapped: $reaction');
// Maybe the lib could expose if it's a removal or at least helpers methods
final reactions = Map<String, List<String>>.from(message.reactions ?? {});
final userId = _currentUser.id;

// Create a Rect for the menu position (small area around tap point)
final menuRect = Rect.fromCenter(
center: position,
width: 0, // Width and height of 0 means show exactly at the point
height: 0,
final users = List<String>.from(reactions[reaction] ?? []);
if (users.contains(userId)) {
users.remove(userId);
if (users.isEmpty) {
reactions.remove(reaction); // Remove the key if no users left
} else {
reactions[reaction] = users;
}
} else {
users.add(userId);
reactions[reaction] = users;
}

_chatController.updateMessage(
message,
message.copyWith(reactions: reactions),
);
}

Widget _buildContextualMenu(Message message) {
if (message.authorId == 'system') return SizedBox.shrink();

final items = [
if (message is TextMessage)
Expand All @@ -293,6 +375,7 @@ class LocalState extends State<Local> {
icon: CupertinoIcons.doc_on_doc,
onTap: () {
_copyMessage(message);
Navigator.of(context).pop();
},
),
PullDownMenuItem(
Expand All @@ -301,11 +384,11 @@ class LocalState extends State<Local> {
isDestructive: true,
onTap: () {
_removeItem(message);
Navigator.of(context).pop();
},
),
];

await showPullDownMenu(context: context, position: menuRect, items: items);
return PullDownMenu(items: items);
}

void _copyMessage(TextMessage message) async {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@

#include "generated_plugin_registrant.h"

#include <emoji_picker_flutter/emoji_picker_flutter_plugin.h>
#include <file_selector_linux/file_selector_plugin.h>
#include <isar_flutter_libs/isar_flutter_libs_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>

void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) emoji_picker_flutter_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "EmojiPickerFlutterPlugin");
emoji_picker_flutter_plugin_register_with_registrar(emoji_picker_flutter_registrar);
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
Expand Down
1 change: 1 addition & 0 deletions examples/flyer_chat/linux/flutter/generated_plugins.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#

list(APPEND FLUTTER_PLUGIN_LIST
emoji_picker_flutter
file_selector_linux
isar_flutter_libs
url_launcher_linux
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@
import FlutterMacOS
import Foundation

import emoji_picker_flutter
import file_picker
import file_selector_macos
import isar_flutter_libs
import path_provider_foundation
import shared_preferences_foundation
import url_launcher_macos

func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
EmojiPickerFlutterPlugin.register(with: registry.registrar(forPlugin: "EmojiPickerFlutterPlugin"))
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
IsarFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "IsarFlutterLibsPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
}
13 changes: 13 additions & 0 deletions examples/flyer_chat/macos/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
PODS:
- emoji_picker_flutter (0.0.1):
- FlutterMacOS
- file_picker (0.0.1):
- FlutterMacOS
- file_selector_macos (0.0.1):
Expand All @@ -9,18 +11,25 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- url_launcher_macos (0.0.1):
- FlutterMacOS

DEPENDENCIES:
- emoji_picker_flutter (from `Flutter/ephemeral/.symlinks/plugins/emoji_picker_flutter/macos`)
- file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`)
- file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
- isar_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/isar_flutter_libs/macos`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)

EXTERNAL SOURCES:
emoji_picker_flutter:
:path: Flutter/ephemeral/.symlinks/plugins/emoji_picker_flutter/macos
file_picker:
:path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos
file_selector_macos:
Expand All @@ -31,15 +40,19 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/isar_flutter_libs/macos
path_provider_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
url_launcher_macos:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos

SPEC CHECKSUMS:
emoji_picker_flutter: 51ca408e289d84d1e460016b2a28721ec754fcf7
file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a
file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
isar_flutter_libs: a65381780401f81ad6bf3f2e7cd0de5698fb98c4
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673

PODFILE CHECKSUM: 7eb978b976557c8c1cd717d8185ec483fd090a82
Expand Down
Loading