add onSelection callback

This commit is contained in:
Henry Hiles 2026-09-16 12:22:21 -04:00
commit 457299ad81
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
3 changed files with 57 additions and 19 deletions

View file

@ -7,12 +7,19 @@ part "emoji.g.dart";
@Freezed(toJson: false, fromJson: false)
@JsonSerializable(createToJson: false)
class Emoji({
@JsonKey(fromJson: Emoji.widgetFromJson, name: "emoji")
@JsonKey(
fromJson: Emoji.widgetFromJson,
readValue: Emoji.readWidgetValueFromJson,
)
required final Widget widget,
@JsonKey(name: "emoji") required final String value,
required final IList<String> aliases,
required final String description,
required final IList<String> tags,
}) with _$Emoji {
static Widget widgetFromJson(String emoji) => Text(emoji);
static String readWidgetValueFromJson(Map<dynamic, dynamic> json, _) =>
json["emoji"];
factory Emoji.fromJson(Map<String, Object?> json) => _$EmojiFromJson(json);
}

View file

@ -1,3 +1,5 @@
import "dart:async";
import "package:collection/collection.dart";
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter_hooks/flutter_hooks.dart";
@ -11,14 +13,19 @@ import "package:super_sliver_list/super_sliver_list.dart";
final class const EmojiPicker({
final IList<EmojiCategory> extraCategories = const IList.empty(),
required final FutureOr<void> Function(String value) onSelection,
super.key,
}) extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final selectedCategory = useState<int?>(null);
final scrollController = useScrollController();
final listController = useMemoized(ListController.new);
final categoryScrollController = useScrollController();
final categoryListController = useMemoized(ListController.new);
useEffect(() {
void onChange() {
final range = listController.visibleRange;
@ -27,6 +34,13 @@ final class const EmojiPicker({
if (selectedCategory.value != firstVisible) {
WidgetsBinding.instance.addPostFrameCallback((_) {
selectedCategory.value = firstVisible;
categoryListController.animateToItem(
index: firstVisible,
scrollController: categoryScrollController,
alignment: 0,
duration: (_) => Duration(milliseconds: 300),
curve: (_) => Curves.easeInOut,
);
});
}
}
@ -43,7 +57,9 @@ final class const EmojiPicker({
.watch(EmojiController.provider)
.betterWhen(
data: (categories) {
final combined = categories.addAll(extraCategories);
final combined = useMemoized(
() => categories.addAll(extraCategories),
);
return Column(
spacing: 4,
@ -56,8 +72,10 @@ final class const EmojiPicker({
),
SizedBox(
height: 48,
child: ListView(
child: SuperListView(
scrollDirection: .horizontal,
controller: categoryScrollController,
listController: categoryListController,
children: combined
.mapIndexed(
(index, category) => Padding(
@ -97,21 +115,30 @@ final class const EmojiPicker({
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DividerText(category.name),
Wrap(
children: category.emojis
.map(
(emoji) => IconButton(
onPressed: () {},
icon: SizedBox.square(
dimension: 36,
child: FittedBox(
fit: .contain,
child: emoji.widget,
),
),
),
)
.toList(),
GridView.builder(
gridDelegate:
const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 56,
mainAxisExtent: 56,
),
itemCount: category.emojis.length,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
final emoji = category.emojis[index];
return IconButton(
padding: EdgeInsets.zero,
onPressed: () async {
Navigator.of(context).pop();
await onSelection(emoji.value);
},
icon: SizedBox.square(
dimension: 36,
child: FittedBox(child: emoji.widget),
),
);
},
),
],
);

View file

@ -244,7 +244,11 @@ final class const RoomChat({
showDragHandle: true,
isScrollControlled: true,
context: context,
builder: (context) => EmojiPicker(),
builder: (context) => EmojiPicker(
onSelection: (value) => notifier
.sendReaction(value, event)
.onError(showError),
),
);
},
),