1
0
Fork 0
forked from Nexus/nexus

Compare commits

...
Sign in to create a new pull request.

4 commits

5 changed files with 135 additions and 20 deletions

View file

@ -0,0 +1,88 @@
import "dart:convert";
import "package:emoji_text_field/models/emoji_category.dart";
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter/material.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:http/http.dart";
import "package:nexus/models/emoji.dart";
typedef EmojiTuple = (IMap<String, EmojiCategory>, IMap<String, List<String>>);
class EmojiController extends AsyncNotifier<EmojiTuple> {
@override
Future<EmojiTuple> build() async {
final response = await get(
Uri.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<String, dynamic>>()
.map(Emoji.fromJson)
.toIList();
final categoryMap = entries.fold<IMap<String, IList<String>>>(
const IMap.empty(),
(acc, entry) => acc.update(
entry.category,
(list) => list.add(entry.emoji),
ifAbsent: () => IList([entry.emoji]),
),
);
final keywordMap = entries.fold<IMap<String, IList<String>>>(
const IMap.empty(),
(acc, entry) => acc.add(
entry.emoji,
IList<String>([...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(
Map.fromEntries(
keywordMap.entries.map(
(e) => MapEntry(e.key, e.value.toList(growable: false)),
),
),
);
return (customCategories, customKeywords);
}
static final provider =
AsyncNotifierProvider.autoDispose<EmojiController, EmojiTuple>(
EmojiController.new,
);
}

17
lib/models/emoji.dart Normal file
View file

@ -0,0 +1,17 @@
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:freezed_annotation/freezed_annotation.dart";
part "emoji.freezed.dart";
part "emoji.g.dart";
@freezed
abstract class Emoji with _$Emoji {
const factory Emoji({
required String emoji,
required String category,
required IList<String> aliases,
required String description,
required IList<String> tags,
}) = _Emoji;
factory Emoji.fromJson(Map<String, Object?> json) => _$EmojiFromJson(json);
}

View file

@ -1,8 +1,9 @@
import "package:emoji_text_field/emoji_text_field.dart"; import "package:emoji_text_field/emoji_text_field.dart";
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_hooks/flutter_hooks.dart"; import "package:hooks_riverpod/hooks_riverpod.dart";
import "package:nexus/controllers/emoji_controller.dart";
class EmojiPickerButton extends HookWidget { class EmojiPickerButton extends HookConsumerWidget {
final TextEditingController? controller; final TextEditingController? controller;
final void Function(String emoji)? onSelection; final void Function(String emoji)? onSelection;
final VoidCallback? onPressed; final VoidCallback? onPressed;
@ -16,25 +17,34 @@ class EmojiPickerButton extends HookWidget {
}); });
@override @override
Widget build(_) => IconButton( Widget build(_, WidgetRef ref) => IconButton(
onPressed: () { onPressed: () async {
onPressed?.call(); onPressed?.call();
final controller = this.controller ?? TextEditingController(); final controller = this.controller ?? TextEditingController();
showModalBottomSheet(
context: context, final emojis = await ref.watch(EmojiController.provider.future);
builder: (context) => EmojiKeyboardView( if (context.mounted) {
config: EmojiViewConfig( showModalBottomSheet(
showRecentTab: false, context: context,
backgroundColor: Theme.of(context).colorScheme.surfaceContainer, builder: (context) => EmojiKeyboardView(
height: 600, config: EmojiViewConfig(
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
Future.delayed(Duration.zero, () {
if (context.mounted) Navigator.of(context).pop();
});
onSelection?.call(controller.text);
}),
), ),
textController: controller );
..addListener(() async { }
Navigator.of(context).pop();
onSelection?.call(controller.text);
}),
),
);
}, },
icon: Icon(Icons.emoji_emotions), icon: Icon(Icons.emoji_emotions),
); );

View file

@ -26,7 +26,7 @@ flutter.buildFlutterApplication {
dynamic_system_colors = "sha256-es6rjMK1drkqZBKYUP77yw/q5+0uLwWOEDOXRawy3Dc="; dynamic_system_colors = "sha256-es6rjMK1drkqZBKYUP77yw/q5+0uLwWOEDOXRawy3Dc=";
flutter_chat_ui = "sha256-4fuag7lRH5cMBFD3fUzj2K541JwXLoz8HF/4OMr3uhk="; flutter_chat_ui = "sha256-4fuag7lRH5cMBFD3fUzj2K541JwXLoz8HF/4OMr3uhk=";
flutter_link_previewer = "sha256-4fuag7lRH5cMBFD3fUzj2K541JwXLoz8HF/4OMr3uhk="; flutter_link_previewer = "sha256-4fuag7lRH5cMBFD3fUzj2K541JwXLoz8HF/4OMr3uhk=";
emoji_text_field = "sha256-F0QbIHP3wpKoL6QbJ20Oun0SsOdwnXe84IqsK2ad85w="; emoji_text_field = "sha256-3TOys09EP2GRo6pUBGPXaqBlE39O2Cmwt42Hs1cTDKo=";
}; };
postInstall = '' postInstall = ''

View file

@ -359,7 +359,7 @@ packages:
description: description:
path: "." path: "."
ref: HEAD ref: HEAD
resolved-ref: "0e90703a6e876939be70bd1816c49cf14474de61" resolved-ref: "5f7baaf8a6f059ec3ab8ff0f5d02339b00bf6997"
url: "https://github.com/Henry-Hiles/emoji_text_field" url: "https://github.com/Henry-Hiles/emoji_text_field"
source: git source: git
version: "1.0.0" version: "1.0.0"