forked from Nexus/nexus
WIP: New Emoji Picker
This commit is contained in:
parent
49b94a2e34
commit
941e1c0e69
7 changed files with 543 additions and 140 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue