use now-separate emoji picker package
This commit is contained in:
parent
ddc83c4a4b
commit
3b2a0c4c88
9 changed files with 18 additions and 23729 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -1,39 +0,0 @@
|
||||||
import "dart:convert";
|
|
||||||
|
|
||||||
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:nexus/models/emoji.dart";
|
|
||||||
import "package:nexus/models/emoji_category.dart";
|
|
||||||
|
|
||||||
class EmojiController extends AsyncNotifier<IList<EmojiCategory>> {
|
|
||||||
@override
|
|
||||||
Future<IList<EmojiCategory>> build() async {
|
|
||||||
final emojiJson = await rootBundle.loadString("assets/bundled/emoji.json");
|
|
||||||
final decoded = (jsonDecode(emojiJson) as List<dynamic>)
|
|
||||||
.cast<Map<String, Object?>>();
|
|
||||||
|
|
||||||
final entries = decoded.map(
|
|
||||||
(json) =>
|
|
||||||
(category: json["category"]! as String, emoji: Emoji.fromJson(json)),
|
|
||||||
);
|
|
||||||
|
|
||||||
final grouped = groupBy(entries, (e) => e.category);
|
|
||||||
|
|
||||||
return .new(
|
|
||||||
grouped.entries.map(
|
|
||||||
(entry) => .new(
|
|
||||||
icon: entry.value.first.emoji.widget,
|
|
||||||
name: entry.key,
|
|
||||||
emojis: .new(entry.value.map((e) => e.emoji)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static final provider =
|
|
||||||
AsyncNotifierProvider<EmojiController, IList<EmojiCategory>>(
|
|
||||||
EmojiController.new,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import "package:material_ui/material_ui.dart";
|
import "package:material_ui/material_ui.dart";
|
||||||
import "package:hooks_riverpod/hooks_riverpod.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/init_complete.dart";
|
||||||
import "package:nexus/controllers/key.dart";
|
import "package:nexus/controllers/key.dart";
|
||||||
import "package:nexus/widgets/appbar.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 initComplete = ref.watch(InitCompleteController.provider);
|
||||||
final roomId = ref.watch(KeyController.provider(KeyController.roomKey));
|
final roomId = ref.watch(KeyController.provider(KeyController.roomKey));
|
||||||
|
|
||||||
ref.read(EmojiController.provider);
|
|
||||||
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: initComplete ? null : Appbar(),
|
appBar: initComplete ? null : Appbar(),
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import "dart:io";
|
||||||
|
|
||||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:file_selector/file_selector.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:material_ui/material_ui.dart";
|
||||||
import "package:flutter/services.dart";
|
import "package:flutter/services.dart";
|
||||||
import "package:flutter_hooks/flutter_hooks.dart";
|
import "package:flutter_hooks/flutter_hooks.dart";
|
||||||
|
|
@ -16,7 +17,6 @@ import "package:nexus/models/relation_type.dart";
|
||||||
import "package:nexus/widgets/composer/mention_overlay.dart";
|
import "package:nexus/widgets/composer/mention_overlay.dart";
|
||||||
import "package:nexus/widgets/composer/relation_preview.dart";
|
import "package:nexus/widgets/composer/relation_preview.dart";
|
||||||
import "package:nexus/main.dart";
|
import "package:nexus/main.dart";
|
||||||
import "package:nexus/widgets/emoji_picker.dart";
|
|
||||||
|
|
||||||
class const Composer(
|
class const Composer(
|
||||||
final String roomId, {
|
final String roomId, {
|
||||||
|
|
@ -125,6 +125,7 @@ class const Composer(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => EmojiPicker(
|
builder: (context) => EmojiPicker(
|
||||||
onSelection: (value) {
|
onSelection: (value) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
controller.value.text += value;
|
controller.value.text += value;
|
||||||
node?.requestFocus();
|
node?.requestFocus();
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,187 +0,0 @@
|
||||||
import "dart:async";
|
|
||||||
|
|
||||||
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<EmojiCategory> prependCategories = const IList.empty(),
|
|
||||||
final IList<EmojiCategory> appendCategories = const IList.empty(),
|
|
||||||
required final FutureOr<void> Function(String value) onSelection,
|
|
||||||
final bool allowFreeText = false,
|
|
||||||
super.key,
|
|
||||||
}) extends HookConsumerWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
final selectedCategory = useState<int?>(null);
|
|
||||||
final search = useState("");
|
|
||||||
|
|
||||||
final scrollController = useScrollController();
|
|
||||||
final listController = useMemoized(ListController.new);
|
|
||||||
|
|
||||||
final categoryScrollController = useScrollController();
|
|
||||||
final categoryListController = 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;
|
|
||||||
categoryListController.animateToItem(
|
|
||||||
index: firstVisible,
|
|
||||||
scrollController: categoryScrollController,
|
|
||||||
alignment: 0,
|
|
||||||
duration: (_) => Duration(milliseconds: 300),
|
|
||||||
curve: (_) => Curves.easeInOut,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
listController.addListener(onChange);
|
|
||||||
return () => listController.removeListener(onChange);
|
|
||||||
}, [listController]);
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
padding: .all(16),
|
|
||||||
constraints: .loose(.new(500, 700)),
|
|
||||||
child: ref
|
|
||||||
.watch(EmojiController.provider)
|
|
||||||
.betterWhen(
|
|
||||||
data: (categories) {
|
|
||||||
final combined = useMemoized(
|
|
||||||
() => prependCategories
|
|
||||||
.addAll(categories)
|
|
||||||
.addAll(appendCategories),
|
|
||||||
);
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
spacing: 4,
|
|
||||||
children: [
|
|
||||||
SearchBar(
|
|
||||||
hintText: "Search emoji",
|
|
||||||
leading: Icon(Icons.search),
|
|
||||||
onChanged: (value) => search.value = value,
|
|
||||||
trailing: [
|
|
||||||
if (allowFreeText)
|
|
||||||
IconButton(
|
|
||||||
onPressed: search.value.isEmpty
|
|
||||||
? null
|
|
||||||
: () async {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
|
|
||||||
await onSelection(search.value);
|
|
||||||
},
|
|
||||||
icon: Icon(Icons.send),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
height: 48,
|
|
||||||
child: SuperListView(
|
|
||||||
scrollDirection: .horizontal,
|
|
||||||
controller: categoryScrollController,
|
|
||||||
listController: categoryListController,
|
|
||||||
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];
|
|
||||||
final emojis = search.value.isEmpty
|
|
||||||
? category.emojis
|
|
||||||
: category.emojis
|
|
||||||
.where(
|
|
||||||
(emoji) =>
|
|
||||||
emoji.aliases.join().contains(
|
|
||||||
search.value,
|
|
||||||
) ||
|
|
||||||
emoji.description.contains(
|
|
||||||
search.value,
|
|
||||||
) ||
|
|
||||||
emoji.tags.join().contains(
|
|
||||||
search.value,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toIList();
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
DividerText(category.name),
|
|
||||||
GridView.builder(
|
|
||||||
gridDelegate:
|
|
||||||
const SliverGridDelegateWithMaxCrossAxisExtent(
|
|
||||||
maxCrossAxisExtent: 56,
|
|
||||||
mainAxisExtent: 56,
|
|
||||||
),
|
|
||||||
itemCount: emojis.length,
|
|
||||||
shrinkWrap: true,
|
|
||||||
physics: NeverScrollableScrollPhysics(),
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final emoji = emojis[index];
|
|
||||||
|
|
||||||
return IconButton(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
onPressed: () async {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
await onSelection(emoji.value);
|
|
||||||
},
|
|
||||||
icon: SizedBox.square(
|
|
||||||
dimension: 36,
|
|
||||||
child: FittedBox(child: emoji.widget),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -17,7 +17,7 @@ import "package:nexus/models/content/message.dart";
|
||||||
import "package:nexus/models/event.dart";
|
import "package:nexus/models/event.dart";
|
||||||
import "package:nexus/models/relation_type.dart";
|
import "package:nexus/models/relation_type.dart";
|
||||||
import "package:nexus/widgets/composer/composer.dart";
|
import "package:nexus/widgets/composer/composer.dart";
|
||||||
import "package:nexus/widgets/emoji_picker.dart";
|
import "package:material_emoji_picker/material_emoji_picker.dart";
|
||||||
import "package:nexus/widgets/pinned_events_drawer.dart";
|
import "package:nexus/widgets/pinned_events_drawer.dart";
|
||||||
import "package:nexus/widgets/renderers/event.dart";
|
import "package:nexus/widgets/renderers/event.dart";
|
||||||
import "package:nexus/widgets/member_list.dart";
|
import "package:nexus/widgets/member_list.dart";
|
||||||
|
|
@ -244,9 +244,12 @@ final class const RoomChat({
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => EmojiPicker(
|
builder: (context) => EmojiPicker(
|
||||||
onSelection: (value) => notifier
|
onSelection: (value) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
notifier
|
||||||
.sendReaction(value, event)
|
.sendReaction(value, event)
|
||||||
.onError(showError),
|
.onError(showError);
|
||||||
|
},
|
||||||
allowFreeText: true,
|
allowFreeText: true,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -894,6 +894,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.13.0"
|
version: "0.13.0"
|
||||||
|
material_emoji_picker:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: material_emoji_picker
|
||||||
|
sha256: b67d9c8d08f64d145402aa3528082a71fb58132c6c6480025a19f4a5d705ab8e
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
material_ui:
|
material_ui:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@ dependencies:
|
||||||
unifiedpush_storage_shared_preferences: 1.0.0
|
unifiedpush_storage_shared_preferences: 1.0.0
|
||||||
flutter_local_notifications: 22.3.0
|
flutter_local_notifications: 22.3.0
|
||||||
flutter_local_notifications_linux: 8.0.1
|
flutter_local_notifications_linux: 8.0.1
|
||||||
|
material_emoji_picker: 1.0.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
build_runner: 2.16.1
|
build_runner: 2.16.1
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,10 @@
|
||||||
import "dart:io";
|
import "dart:io";
|
||||||
|
|
||||||
import "package:ffigen/ffigen.dart";
|
import "package:ffigen/ffigen.dart";
|
||||||
import "package:http/http.dart";
|
|
||||||
import "package:path/path.dart";
|
import "package:path/path.dart";
|
||||||
import "package:nexus/helpers/extensions/get_xcode_sdk.dart";
|
import "package:nexus/helpers/extensions/get_xcode_sdk.dart";
|
||||||
|
|
||||||
void main(List<String> args) async {
|
void main(List<String> args) async {
|
||||||
print("Downloading emoji list...");
|
|
||||||
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
await File.fromUri(Platform.script.resolve("../assets/bundled/emoji.json"))
|
|
||||||
.writeAsString(response.body);
|
|
||||||
|
|
||||||
print("Emoji list written!");
|
|
||||||
|
|
||||||
final repoDir = Directory.fromUri(Platform.script.resolve("../gomuks"));
|
final repoDir = Directory.fromUri(Platform.script.resolve("../gomuks"));
|
||||||
|
|
||||||
print("Generating FFI bindings...");
|
print("Generating FFI bindings...");
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue