diff --git a/lib/controllers/emoji.dart b/lib/controllers/emoji.dart index f67471f..fc265e5 100644 --- a/lib/controllers/emoji.dart +++ b/lib/controllers/emoji.dart @@ -4,7 +4,6 @@ import "package:collection/collection.dart"; import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:flutter/services.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:material_ui/material_ui.dart"; import "package:nexus/models/emoji.dart"; import "package:nexus/models/emoji_category.dart"; @@ -25,7 +24,7 @@ class EmojiController extends AsyncNotifier> { return .new( grouped.entries.map( (entry) => .new( - icon: Text(entry.value.first.emoji.emoji), + icon: entry.value.first.emoji.widget, name: entry.key, emojis: .new(entry.value.map((e) => e.emoji)), ), diff --git a/lib/models/emoji.dart b/lib/models/emoji.dart index 951a193..0bba3cd 100644 --- a/lib/models/emoji.dart +++ b/lib/models/emoji.dart @@ -1,17 +1,18 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:freezed_annotation/freezed_annotation.dart"; +import "package:flutter/widgets.dart"; part "emoji.freezed.dart"; part "emoji.g.dart"; @Freezed(toJson: false, fromJson: false) -@JsonSerializable() +@JsonSerializable(createToJson: false) class Emoji({ - required final String emoji, + @JsonKey(fromJson: Emoji.widgetFromJson, name: "emoji") + required final Widget widget, required final IList aliases, required final String description, required final IList tags, }) with _$Emoji { - Map toJson() => _$EmojiToJson(this); - + static Widget widgetFromJson(String emoji) => Text(emoji); factory Emoji.fromJson(Map json) => _$EmojiFromJson(json); } diff --git a/lib/widgets/emoji_picker.dart b/lib/widgets/emoji_picker.dart index 51b397d..7b8933c 100644 --- a/lib/widgets/emoji_picker.dart +++ b/lib/widgets/emoji_picker.dart @@ -1,8 +1,131 @@ -import "package:flutter/widgets.dart"; - -class EmojiPicker extends StatelessWidget { - const new({super.key}); +import "package:collection/collection.dart"; +import "package:fast_immutable_collections/fast_immutable_collections.dart"; +import "package:flutter_hooks/flutter_hooks.dart"; +import "package:hooks_riverpod/hooks_riverpod.dart"; +import "package:material_ui/material_ui.dart"; +import "package:nexus/controllers/emoji.dart"; +import "package:nexus/helpers/extensions/better_when.dart"; +import "package:nexus/models/emoji_category.dart"; +import "package:nexus/widgets/divider_text.dart"; +import "package:super_sliver_list/super_sliver_list.dart"; +final class const EmojiPicker({ + final IList extraCategories = const IList.empty(), + super.key, +}) extends HookConsumerWidget { @override - Widget build(BuildContext context) => Placeholder(); // TODO: Emoji picker + Widget build(BuildContext context, WidgetRef ref) => Container( + padding: .symmetric(horizontal: 16), + constraints: .loose(.new(500, 700)), + child: ref + .watch(EmojiController.provider) + .betterWhen( + data: (categories) { + final combined = categories.addAll(extraCategories); + + return HookBuilder( + builder: (context) { + final selectedCategory = useState(null); + final scrollController = useScrollController(); + final listController = useMemoized(ListController.new); + + useEffect(() { + void onChange() { + final range = listController.visibleRange; + if (range != null) { + final firstVisible = range.$1; + if (selectedCategory.value != firstVisible) { + WidgetsBinding.instance.addPostFrameCallback((_) { + selectedCategory.value = firstVisible; + }); + } + } + } + + listController.addListener(onChange); + return () => listController.removeListener(onChange); + }, [listController]); + + return Column( + spacing: 4, + children: [ + SearchAnchor.bar( + barHintText: "Search emoji", + suggestionsBuilder: (context, controller) { + return []; + }, + ), + SizedBox( + height: 48, + child: ListView( + scrollDirection: .horizontal, + children: combined + .mapIndexed( + (index, category) => Padding( + padding: .symmetric(horizontal: 4), + child: FilterChip( + label: Text(category.name), + avatar: category.icon, + selected: selectedCategory.value == index, + showCheckmark: false, + onSelected: (_) { + selectedCategory.value = index; + + listController.animateToItem( + index: index, + scrollController: scrollController, + curve: (_) => Curves.easeInOut, + duration: (_) => + Duration(milliseconds: 300), + alignment: 0, + ); + }, + ), + ), + ) + .toList(), + ), + ), + Expanded( + child: SuperListView.builder( + listController: listController, + controller: scrollController, + itemCount: combined.length, + itemBuilder: (context, index) { + final category = combined[index]; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + DividerText(category.name), + DefaultTextStyle( + style: TextStyle(fontSize: 24), + child: GridView( + gridDelegate: + const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 52, + mainAxisExtent: 52, + ), + children: category.emojis + .map( + (emoji) => IconButton( + onPressed: () {}, + icon: emoji.widget, + ), + ) + .toList(), + ), + ), + ], + ); + }, + ), + ), + ], + ); + }, + ); + }, + ), + ); } diff --git a/lib/widgets/room_chat.dart b/lib/widgets/room_chat.dart index 5b30bf4..528d47e 100644 --- a/lib/widgets/room_chat.dart +++ b/lib/widgets/room_chat.dart @@ -17,6 +17,7 @@ import "package:nexus/models/content/message.dart"; import "package:nexus/models/event.dart"; import "package:nexus/models/relation_type.dart"; import "package:nexus/widgets/composer/composer.dart"; +import "package:nexus/widgets/emoji_picker.dart"; import "package:nexus/widgets/pinned_events_drawer.dart"; import "package:nexus/widgets/renderers/event.dart"; import "package:nexus/widgets/member_list.dart"; @@ -237,11 +238,14 @@ final class const RoomChat({ icon: Icon(Icons.emoji_emotions), onPressed: () { + // TODO: More adaptive Navigator.of(context).pop(); - - // TODO: Push - // onSelection: (emoji) => - // notifier.sendReaction(emoji, event).onError(showError), + showModalBottomSheet( + showDragHandle: true, + isScrollControlled: true, + context: context, + builder: (context) => EmojiPicker(), + ); }, ), ],