diff --git a/lib/controllers/emoji.dart b/lib/controllers/emoji.dart deleted file mode 100644 index b0c1bd3..0000000 --- a/lib/controllers/emoji.dart +++ /dev/null @@ -1,84 +0,0 @@ -import "dart:convert"; -import "package:emoji_text_field/models/emoji_category.dart"; -import "package:fast_immutable_collections/fast_immutable_collections.dart"; -import "package:material_ui/material_ui.dart"; -import "package:flutter_riverpod/flutter_riverpod.dart"; -import "package:http/http.dart"; -import "package:nexus/models/emoji.dart"; - -typedef EmojiTuple = (IMap, IMap>); - -class EmojiController extends AsyncNotifier { - @override - Future build() async { - final response = await get( - .https("github.com", "github/gemoji/raw/refs/heads/master/db/emoji.json"), - ); - - if (response.statusCode != 200) { - throw Exception("Failed to load emoji data"); - } - - final data = json.decode(response.body); - - final entries = (data as List) - .cast>() - .map(Emoji.fromJson) - .toIList(); - - final categoryMap = entries.fold>>( - .new(), - (acc, entry) => acc.update( - entry.category, - (list) => list.add(entry.emoji), - ifAbsent: () => .new([entry.emoji]), - ), - ); - - final keywordMap = entries.fold>>( - .new(), - (acc, entry) => acc.add( - entry.emoji, - .new([...entry.tags, ...entry.aliases, entry.description]), - ), - ); - - final customCategories = IMap.fromEntries( - categoryMap.entries.map( - (entry) => MapEntry( - entry.key, - EmojiCategory( - name: entry.key, - icon: switch (entry.key) { - "Smileys & Emotion" => Icons.emoji_emotions, - "People & Body" => Icons.emoji_people, - "Animals & Nature" => Icons.emoji_nature, - "Food & Drink" => Icons.emoji_food_beverage, - "Travel & Places" => Icons.travel_explore, - "Activities" => Icons.sports_soccer, - "Objects" => Icons.emoji_objects, - "Symbols" => Icons.emoji_symbols, - "Flags" => Icons.emoji_flags, - _ => Icons.category, - }, - emojis: entry.value.toList(growable: false), - ), - ), - ), - ); - - final customKeywords = IMap( - .fromEntries( - keywordMap.entries.map( - (e) => .new(e.key, e.value.toList(growable: false)), - ), - ), - ); - - return (customCategories, customKeywords); - } - - static final provider = AsyncNotifierProvider( - EmojiController.new, - ); -} diff --git a/lib/models/emoji.dart b/lib/models/emoji.dart index 149ae18..75faf08 100644 --- a/lib/models/emoji.dart +++ b/lib/models/emoji.dart @@ -1,18 +1,25 @@ 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, - required final String category, + @JsonKey( + fromJson: Emoji.widgetFromJson, + readValue: Emoji.readWidgetValueFromJson, + ) + required final Widget widget, + @JsonKey(name: "emoji") required final String value, 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); + static String readWidgetValueFromJson(Map json, _) => + json["emoji"]; factory Emoji.fromJson(Map json) => _$EmojiFromJson(json); } diff --git a/lib/models/emoji_category.dart b/lib/models/emoji_category.dart new file mode 100644 index 0000000..cc39bc3 --- /dev/null +++ b/lib/models/emoji_category.dart @@ -0,0 +1,12 @@ +import "package:fast_immutable_collections/fast_immutable_collections.dart"; +import "package:freezed_annotation/freezed_annotation.dart"; +import "package:nexus/models/emoji.dart"; +import "package:flutter/widgets.dart"; +part "emoji_category.freezed.dart"; + +@Freezed(toJson: false, fromJson: false) +class EmojiCategory({ + required final Widget icon, + required final String name, + required final IList emojis, +}) with _$EmojiCategory; diff --git a/lib/pages/chat.dart b/lib/pages/chat.dart index c58b7a7..9469c91 100644 --- a/lib/pages/chat.dart +++ b/lib/pages/chat.dart @@ -1,6 +1,5 @@ import "package:material_ui/material_ui.dart"; import "package:hooks_riverpod/hooks_riverpod.dart"; -import "package:nexus/controllers/emoji.dart"; import "package:nexus/controllers/init_complete.dart"; import "package:nexus/controllers/key.dart"; import "package:nexus/widgets/appbar.dart"; @@ -17,8 +16,6 @@ class const ChatPage({super.key}) extends ConsumerWidget { final initComplete = ref.watch(InitCompleteController.provider); final roomId = ref.watch(KeyController.provider(KeyController.roomKey)); - ref.read(EmojiController.provider); - return SafeArea( child: Scaffold( appBar: initComplete ? null : Appbar(), diff --git a/lib/widgets/composer/composer.dart b/lib/widgets/composer/composer.dart index 9a597a4..eacfad8 100644 --- a/lib/widgets/composer/composer.dart +++ b/lib/widgets/composer/composer.dart @@ -2,6 +2,7 @@ import "dart:io"; import "package:fast_immutable_collections/fast_immutable_collections.dart"; import "package:file_selector/file_selector.dart"; +import "package:material_emoji_picker/material_emoji_picker.dart"; import "package:material_ui/material_ui.dart"; import "package:flutter/services.dart"; import "package:flutter_hooks/flutter_hooks.dart"; @@ -15,7 +16,6 @@ import "package:nexus/models/event.dart"; import "package:nexus/models/relation_type.dart"; import "package:nexus/widgets/composer/mention_overlay.dart"; import "package:nexus/widgets/composer/relation_preview.dart"; -import "package:nexus/widgets/emoji_picker_button.dart"; import "package:nexus/main.dart"; class const Composer( @@ -119,10 +119,19 @@ class const Composer( ), ) ? [ - EmojiPickerButton( - context: context, - onSelection: (_) => node?.requestFocus(), - controller: controller.value, + IconButton( + onPressed: () => showModalBottomSheet( + isScrollControlled: true, + context: context, + builder: (context) => EmojiPicker( + onSelection: (value) { + Navigator.of(context).pop(); + controller.value.text += value; + node?.requestFocus(); + }, + ), + ), + icon: Icon(Icons.emoji_emotions), ), PopupMenuButton( tooltip: "Add media", diff --git a/lib/widgets/emoji_picker_button.dart b/lib/widgets/emoji_picker_button.dart deleted file mode 100644 index 1e20557..0000000 --- a/lib/widgets/emoji_picker_button.dart +++ /dev/null @@ -1,46 +0,0 @@ -import "package:emoji_text_field/emoji_text_field.dart"; -import "package:material_ui/material_ui.dart"; -import "package:hooks_riverpod/hooks_riverpod.dart"; -import "package:nexus/controllers/emoji.dart"; - -final class const EmojiPickerButton({ - required final BuildContext context, - final TextEditingController? controller, - final void Function(String emoji)? onSelection, - final VoidCallback? onPressed, - super.key, -}) extends HookConsumerWidget { - @override - Widget build(_, WidgetRef ref) => IconButton( - onPressed: () async { - onPressed?.call(); - final controller = this.controller ?? .new(); - - final emojis = await ref.watch(EmojiController.provider.future); - if (context.mounted) { - showModalBottomSheet( - context: context, - builder: (context) => EmojiKeyboardView( - config: .new( - showRecentTab: false, - customCategories: emojis.$1.unlock, - customKeywords: emojis.$2.unlock, - backgroundColor: Theme.of(context).colorScheme.surfaceContainer, - height: 600, - ), - textController: controller - ..addListener(() { - // Without this, there will sometimes be a debugLocked is not true error sometimes - // It might be preferable to use a microtask instead of a `Future.delayed`. - Future.delayed(.zero, () { - if (context.mounted) Navigator.of(context).pop(); - }); - onSelection?.call(controller.text); - }), - ), - ); - } - }, - icon: Icon(Icons.emoji_emotions), - ); -} diff --git a/lib/widgets/room_chat.dart b/lib/widgets/room_chat.dart index dd6ed25..121428b 100644 --- a/lib/widgets/room_chat.dart +++ b/lib/widgets/room_chat.dart @@ -17,7 +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_button.dart"; +import "package:material_emoji_picker/material_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"; @@ -234,11 +234,26 @@ final class const RoomChat({ icon: Text(emoji), ), ), - EmojiPickerButton( - context: context, - onPressed: Navigator.of(context).pop, - onSelection: (emoji) => - notifier.sendReaction(emoji, event).onError(showError), + IconButton( + icon: Icon(Icons.emoji_emotions), + + onPressed: () { + // TODO: More adaptive, e.g. dialog on desktop + Navigator.of(context).pop(); + showModalBottomSheet( + isScrollControlled: true, + context: context, + builder: (context) => EmojiPicker( + onSelection: (value) { + Navigator.of(context).pop(); + notifier + .sendReaction(value, event) + .onError(showError); + }, + allowFreeText: true, + ), + ); + }, ), ], ), diff --git a/pubspec.lock b/pubspec.lock index 82e2d72..b3102b9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -297,15 +297,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.0.8" - emoji_text_field: - dependency: "direct main" - description: - path: "." - ref: HEAD - resolved-ref: "5f7baaf8a6f059ec3ab8ff0f5d02339b00bf6997" - url: "https://github.com/Henry-Hiles/emoji_text_field" - source: git - version: "1.0.0" encrypt: dependency: transitive description: @@ -903,6 +894,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.13.0" + material_emoji_picker: + dependency: "direct main" + description: + name: material_emoji_picker + sha256: "1547195a419134cf0b2396d119dc873589804f461424917b1b61a0966bfb7e5a" + url: "https://pub.dev" + source: hosted + version: "1.0.0+1" material_ui: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index ab2f727..9f3b2c5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -59,9 +59,6 @@ dependencies: http: 1.6.0 flutter_linkify: 6.0.0 linkify: 5.0.0 - emoji_text_field: - git: - url: https://github.com/Henry-Hiles/emoji_text_field flutter_blurhash: 0.9.1 super_sliver_list: 0.4.1 media_kit: 1.2.6 @@ -83,6 +80,7 @@ dependencies: unifiedpush_storage_shared_preferences: 1.0.0 flutter_local_notifications: 22.3.0 flutter_local_notifications_linux: 8.0.1 + material_emoji_picker: 1.0.0+1 dev_dependencies: build_runner: 2.16.1 diff --git a/scripts/generate.dart b/scripts/generate.dart index b9b8511..d8a8cb1 100644 --- a/scripts/generate.dart +++ b/scripts/generate.dart @@ -7,7 +7,7 @@ import "package:nexus/helpers/extensions/get_xcode_sdk.dart"; void main(List args) async { final repoDir = Directory.fromUri(Platform.script.resolve("../gomuks")); - print("Generating FFI Bindings..."); + print("Generating FFI bindings..."); final libclangPath = Platform.environment["LIBCLANG_PATH"]; FfiGenerator( @@ -40,5 +40,5 @@ void main(List args) async { ), ), ); - print("Done!"); + print("FFI bindings generated!"); }