Watch
1
0
Fork
You've already forked nexus
0
forked from Nexus/nexus

WIP: New Emoji Picker

This commit is contained in:
istalri 2026-08-01 00:58:57 +02:00
commit 941e1c0e69
7 changed files with 543 additions and 140 deletions

View file

@ -1,17 +1,47 @@
import "dart:convert";
import "package:emoji_text_field/models/emoji_category.dart";
import "package:collection/collection.dart";
import "package:fast_immutable_collections/fast_immutable_collections.dart";
import "package:flutter/foundation.dart";
import "package:flutter/material.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:http/http.dart";
import "package:nexus/models/emoji.dart";
import "package:nexus/models/gemoji.dart";
typedef EmojiTuple = (IMap<String, EmojiCategory>, IMap<String, List<String>>);
class EmoteSection {
final String category;
final String label;
final IconData icon;
final List<EmoteItem> items;
final bool isRecent;
class EmojiController extends AsyncNotifier<EmojiTuple> {
const EmoteSection({
required this.category,
required this.label,
required this.icon,
required this.items,
this.isRecent = false,
});
}
class EmoteItem {
final String id;
final String display;
final List<String> keywords;
final int useCounter;
final Image? customImage;
const EmoteItem({
required this.id,
required this.display,
required this.keywords,
required this.useCounter,
this.customImage,
});
}
class EmojiController extends AsyncNotifier<IList<EmoteSection>> {
@override
Future<EmojiTuple> build() async {
Future<IList<EmoteSection>> build() async {
final response = await get(
.https("github.com", "github/gemoji/raw/refs/heads/master/db/emoji.json"),
);
@ -20,69 +50,72 @@ class EmojiController extends AsyncNotifier<EmojiTuple> {
throw Exception("Failed to load emoji data");
}
return compute(_parseEmojiJson, response.body);
return compute(parseEmojiJson, response.body);
}
static EmojiTuple _parseEmojiJson(String body) {
static IList<EmoteSection> parseEmojiJson(String body) {
final data = json.decode(body) as List;
final entries = data
final allEntries = data
.cast<Map<String, dynamic>>()
.map(Emoji.fromJson)
.map(Gemoji.fromJson)
.toIList();
final categoryMap = entries.fold<IMap<String, IList<String>>>(
.new(),
(acc, entry) => acc.update(
entry.category,
(list) => list.add(entry.emoji),
ifAbsent: () => .new([entry.emoji]),
),
);
final groupedByCategory = groupBy(allEntries, (g) => g.category);
final keywordMap = entries.fold<IMap<String, IList<String>>>(
final result = groupedByCategory.entries.fold<IList<EmoteSection>>(
.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),
),
(resultList, entry) => resultList.add(
EmoteSection(
category: entry.key,
label: entry.key,
icon: getIconForCategory(entry.key),
items: entry.value
.map(
(gemoji) => EmoteItem(
id: gemoji.emoji,
display: gemoji.emoji,
keywords: [
...gemoji.tags,
...gemoji.aliases,
...gemoji.description.split(" "),
],
useCounter: 0,
),
)
.toList(),
isRecent: false,
),
),
);
final customKeywords = IMap(
.fromEntries(
keywordMap.entries.map(
(e) => .new(e.key, e.value.toList(growable: false)),
),
),
);
return (customCategories, customKeywords);
return result;
}
static final provider = AsyncNotifierProvider<EmojiController, EmojiTuple>(
EmojiController.new,
);
static final provider =
AsyncNotifierProvider<EmojiController, IList<EmoteSection>>(
EmojiController.new,
);
static IconData getIconForCategory(String category) {
switch (category.toLowerCase()) {
case "smileys & emotion":
return Icons.sentiment_very_satisfied;
case "people & body":
return Icons.face;
case "food & drink":
return Icons.restaurant;
case "travel & places":
return Icons.flag;
case "activities":
return Icons.sports_soccer;
case "objects":
return Icons.lightbulb;
case "symbols":
return Icons.circle;
case "flags":
return Icons.flag;
default:
return Icons.emoji_emotions_outlined;
}
}
}