Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(neon_framework): Implement a custom emoji picker #2623

Merged
merged 3 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,6 @@
[submodule "external/nextcloud-tables"]
path = external/nextcloud-tables
url = https://github.com/nextcloud/tables
[submodule "external/emoji-metadata"]
path = external/emoji-metadata
url = https://github.com/googlefonts/emoji-metadata
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"**/CHANGELOG.md",
"external",
"packages/dynamite/example/lib",
"packages/neon_framework/lib/src/utils/emojis.dart",
"packages/neon_framework/example/web/sqflite_sw.js",
"packages/neon_framework/example/web/sqlite3.wasm",
"packages/neon_lints/lib",
Expand Down
1 change: 1 addition & 0 deletions external/emoji-metadata
Submodule emoji-metadata added at 3544a2
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
#include "generated_plugin_registrant.h"

#include <dynamic_color/dynamic_color_plugin.h>
#include <emoji_picker_flutter/emoji_picker_flutter_plugin.h>
#include <file_selector_linux/file_selector_plugin.h>
#include <screen_retriever_linux/screen_retriever_linux_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
Expand All @@ -17,9 +16,6 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) dynamic_color_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin");
dynamic_color_plugin_register_with_registrar(dynamic_color_registrar);
g_autoptr(FlPluginRegistrar) emoji_picker_flutter_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "EmojiPickerFlutterPlugin");
emoji_picker_flutter_plugin_register_with_registrar(emoji_picker_flutter_registrar);
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

list(APPEND FLUTTER_PLUGIN_LIST
dynamic_color
emoji_picker_flutter
file_selector_linux
screen_retriever_linux
url_launcher_linux
Expand Down
8 changes: 0 additions & 8 deletions packages/neon_framework/example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -338,14 +338,6 @@ packages:
relative: true
source: path
version: "0.5.0+1"
emoji_picker_flutter:
dependency: transitive
description:
name: emoji_picker_flutter
sha256: "08567e6f914d36c32091a96cf2f51d2558c47aa2bd47a590dc4f50e42e0965f6"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
equatable:
dependency: transitive
description:
Expand Down
225 changes: 225 additions & 0 deletions packages/neon_framework/generate_emojis.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
import 'dart:convert';

import 'package:code_builder/code_builder.dart';
import 'package:collection/collection.dart';
import 'package:dart_style/dart_style.dart';
import 'package:string_normalizer/string_normalizer.dart';
import 'package:universal_io/io.dart';

/// Android 14 only supports Unicode 15.0, so we use that for now, even though newer versions are available:
/// https://developer.android.com/guide/topics/resources/internationalization#versioning-nougat
const unicodeVersion = '15_0';

void main() {
final formatter = DartFormatter(pageWidth: 120);
final emitter = DartEmitter(
orderDirectives: true,
useNullSafetySyntax: true,
);

final inputFile = File('../../external/emoji-metadata/emoji_${unicodeVersion}_ordering.json');
final input = json.decode(inputFile.readAsStringSync());

final library = Library((b) {
b.docs.add('// GENERATED CODE - DO NOT MODIFY BY HAND');

b.body.add(
Class(
(b) => b
..name = 'Emoji'
..docs.add('/// Holds the metadata of a Unicode emoji.')
..constructors.add(
Constructor(
(b) => b
..constant = true
..docs.add('/// Creates a new [Emoji].')
..optionalParameters.addAll([
Parameter(
(b) => b
..name = 'base'
..named = true
..toThis = true
..required = true,
),
Parameter(
(b) => b
..name = 'alternates'
..named = true
..toThis = true
..required = true,
),
Parameter(
(b) => b
..name = 'emoticons'
..named = true
..toThis = true
..required = true,
),
Parameter(
(b) => b
..name = 'shortcodes'
..named = true
..toThis = true
..required = true,
),
Parameter(
(b) => b
..name = 'animated'
..named = true
..toThis = true
..required = true,
),
]),
),
)
..fields.addAll([
Field(
(b) => b
..name = 'base'
..type = refer('String')
..modifier = FieldModifier.final$
..docs.add('/// The base emoji symbol.'),
),
Field(
(b) => b
..name = 'alternates'
..type = refer('List<String>')
..modifier = FieldModifier.final$
..docs.add('/// The associated alternate emoji symbols.'),
),
Field(
(b) => b
..name = 'emoticons'
..type = refer('List<String>')
..modifier = FieldModifier.final$
..docs.add('/// The associated emoticons.'),
),
Field(
(b) => b
..name = 'shortcodes'
..type = refer('List<String>')
..modifier = FieldModifier.final$
..docs.add('/// The associated short codes.'),
),
Field(
(b) => b
..name = 'animated'
..type = refer('bool')
..modifier = FieldModifier.final$
..docs.add('/// Whether the emoji can be animated.'),
),
]),
),
);

final names = <String, String>{};
const groupIds = <String, String>{
'Smileys and emotions': 'smileysAndEmotions',
'People': 'people',
'Animals and nature': 'animalsAndNature',
'Food and drink': 'foodAndDrink',
'Travel and places': 'travelAndPlaces',
'Activities and events': 'activitiesAndEvents',
'Objects': 'objects',
'Symbols': 'symbols',
'Flags': 'flags',
};
final emojiGroups = <String, List<String>>{};

for (final group in (input as List).map((t) => t as Map<String, dynamic>)) {
final groupName = group['group'] as String;
final groupId = groupIds[groupName]!;

for (final emoji in (group['emoji'] as List).map((t) => t as Map<String, dynamic>)) {
final base = String.fromCharCodes((emoji['base'] as List).cast<int>());
final alternates = (emoji['alternates'] as List)
.map((alternate) => String.fromCharCodes((alternate as List).cast<int>()))
.toList();
final emoticons = (emoji['emoticons'] as List).cast<String>();
final shortcodes = (emoji['shortcodes'] as List).cast<String>();
final animated = emoji['animated'] as bool;

var name = '';
for (final shortcode in shortcodes) {
name = shortcode.substring(1, shortcode.length - 1).toLowerCase();
name = name
.split('-')
.map(
(t) => switch (t) {
'1' => 'one',
'2' => 'two',
'3' => 'three',
'4' => 'four',
'5' => 'five',
'6' => 'six',
'7' => 'seven',
'8' => 'eight',
'9' => 'nine',
_ => t,
},
)
.mapIndexed((i, t) => i == 0 ? t : '${t.substring(0, 1).toUpperCase()}${t.substring(1)}')
.join();
name = StringNormalizer.normalize(name);
name = name.replaceAll(RegExp('[^a-z]', caseSensitive: false), '');
if (name.isNotEmpty) {
break;
}
}

if (name == 'new') {
name = r'$new';
}

emojiGroups[groupId] ??= [];
emojiGroups[groupId]!.add(name);

// Some emojis are in multiple groups
if (names.containsKey(name)) {
if (names[name] != base) {
throw Exception('Conflicting name $name for different emojis: $base ${names[name]}');
}
continue;
}
names[name] = base;

b.body.add(
Code('''
/// The $base emoji.
const $name = Emoji(
base: '$base',
alternates: [${alternates.isNotEmpty ? "'${alternates.join("', '")}'," : ''}],
emoticons: [${emoticons.isNotEmpty ? "'${emoticons.join("', '").replaceAll("'", r"\'").replaceAll(r'$', r'\$')}'," : ''}],
shortcodes: ['${shortcodes.join("', '")}',],
animated: $animated,
);

'''),
);
}
}

for (final groupId in emojiGroups.keys) {
final emojis = emojiGroups[groupId]!;

b.body.add(
Code('''
/// The "${groupIds.entries.firstWhere((e) => e.value == groupId).key}" emojis.
const ${groupId}Group = [${emojis.join(', ')},];

'''),
);
}

b.body.add(
Code('''
/// All emojis.
const all = [${emojiGroups.values.expand((t) => [...t]).join(', ')},];

'''),
);
});

final output = library.accept(emitter).toString();
File('lib/src/utils/emojis.dart').writeAsStringSync(formatter.format(output));
}
11 changes: 10 additions & 1 deletion packages/neon_framework/lib/l10n/en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -294,5 +294,14 @@
"userStatusClearAtThisWeek": "This week",
"userStatusActionClear": "Clear status",
"userStatusStatusMessage": "Status message",
"userStatusOnlineStatus": "Online status"
"userStatusOnlineStatus": "Online status",
"emojisCategorySmileysAndEmotions": "Smileys and emotions",
"emojisCategoryPeople": "People",
"emojisCategoryAnimalsAndNature": "Animals and nature",
"emojisCategoryFoodAndDrink": "Food and drink",
"emojisCategoryTravelAndPlaces": "Travel and places",
"emojisCategoryActivitiesAndEvents": "Activities and events",
"emojisCategoryObjects": "Objects",
"emojisCategorySymbols": "Symbols",
"emojisCategoryFlags": "Flags"
}
54 changes: 54 additions & 0 deletions packages/neon_framework/lib/l10n/localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,60 @@ abstract class NeonLocalizations {
/// In en, this message translates to:
/// **'Online status'**
String get userStatusOnlineStatus;

/// No description provided for @emojisCategorySmileysAndEmotions.
///
/// In en, this message translates to:
/// **'Smileys and emotions'**
String get emojisCategorySmileysAndEmotions;

/// No description provided for @emojisCategoryPeople.
///
/// In en, this message translates to:
/// **'People'**
String get emojisCategoryPeople;

/// No description provided for @emojisCategoryAnimalsAndNature.
///
/// In en, this message translates to:
/// **'Animals and nature'**
String get emojisCategoryAnimalsAndNature;

/// No description provided for @emojisCategoryFoodAndDrink.
///
/// In en, this message translates to:
/// **'Food and drink'**
String get emojisCategoryFoodAndDrink;

/// No description provided for @emojisCategoryTravelAndPlaces.
///
/// In en, this message translates to:
/// **'Travel and places'**
String get emojisCategoryTravelAndPlaces;

/// No description provided for @emojisCategoryActivitiesAndEvents.
///
/// In en, this message translates to:
/// **'Activities and events'**
String get emojisCategoryActivitiesAndEvents;

/// No description provided for @emojisCategoryObjects.
///
/// In en, this message translates to:
/// **'Objects'**
String get emojisCategoryObjects;

/// No description provided for @emojisCategorySymbols.
///
/// In en, this message translates to:
/// **'Symbols'**
String get emojisCategorySymbols;

/// No description provided for @emojisCategoryFlags.
///
/// In en, this message translates to:
/// **'Flags'**
String get emojisCategoryFlags;
}

class _NeonLocalizationsDelegate extends LocalizationsDelegate<NeonLocalizations> {
Expand Down
27 changes: 27 additions & 0 deletions packages/neon_framework/lib/l10n/localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -507,4 +507,31 @@ class NeonLocalizationsEn extends NeonLocalizations {

@override
String get userStatusOnlineStatus => 'Online status';

@override
String get emojisCategorySmileysAndEmotions => 'Smileys and emotions';

@override
String get emojisCategoryPeople => 'People';

@override
String get emojisCategoryAnimalsAndNature => 'Animals and nature';

@override
String get emojisCategoryFoodAndDrink => 'Food and drink';

@override
String get emojisCategoryTravelAndPlaces => 'Travel and places';

@override
String get emojisCategoryActivitiesAndEvents => 'Activities and events';

@override
String get emojisCategoryObjects => 'Objects';

@override
String get emojisCategorySymbols => 'Symbols';

@override
String get emojisCategoryFlags => 'Flags';
}
Loading