diff --git a/lib/pages/settings_emotes.dart b/lib/pages/settings_emotes.dart index 62152343..bc8b0149 100644 --- a/lib/pages/settings_emotes.dart +++ b/lib/pages/settings_emotes.dart @@ -24,8 +24,6 @@ class EmoteEntry { String emote; String mxc; EmoteEntry({this.emote, this.mxc}); - - String get emoteClean => emote.substring(1, emote.length - 1); } class EmotesSettingsController extends State { @@ -39,54 +37,57 @@ class EmotesSettingsController extends State { TextEditingController newEmoteController = TextEditingController(); TextEditingController newMxcController = TextEditingController(); + ImagePackContent _getPack(BuildContext context) { + final client = Matrix.of(context).client; + final event = (room != null + ? room.getState('im.ponies.room_emotes', stateKey ?? '') + : client.accountData['im.ponies.user_emotes']) ?? + BasicEvent.fromJson({ + 'type': 'm.dummy', + 'content': {}, + }); + // make sure we work on a *copy* of the event + return BasicEvent.fromJson(event.toJson()).parsedImagePackContent; + } + Future _save(BuildContext context) async { if (readonly) { return; } final client = Matrix.of(context).client; - // be sure to preserve any data not in "short" - Map content; - if (room != null) { - content = - room.getState('im.ponies.room_emotes', stateKey ?? '')?.content ?? - {}; - } else { - content = client.accountData['im.ponies.user_emotes']?.content ?? - {}; - } - if (!(content['emoticons'] is Map)) { - content['emoticons'] = {}; - } + final pack = _getPack(context); // add / update changed emotes final allowedShortcodes = {}; for (final emote in emotes) { allowedShortcodes.add(emote.emote); - if (!(content['emoticons'][emote.emote] is Map)) { - content['emoticons'][emote.emote] = {}; + if (pack.images.containsKey(emote.emote)) { + pack.images[emote.emote].url = Uri.parse(emote.mxc); + } else { + pack.images[emote.emote] = + ImagePackImageContent.fromJson({ + 'url': emote.mxc, + }); } - content['emoticons'][emote.emote]['url'] = emote.mxc; } // remove emotes no more needed // we make the iterator .toList() here so that we don't get into trouble modifying the very // thing we are iterating over - for (final shortcode in content['emoticons'].keys.toList()) { + for (final shortcode in pack.images.keys.toList()) { if (!allowedShortcodes.contains(shortcode)) { - content['emoticons'].remove(shortcode); + pack.images.remove(shortcode); } } - // remove the old "short" key - content.remove('short'); if (room != null) { await showFutureLoadingDialog( context: context, future: () => client.setRoomStateWithKey( - room.id, 'im.ponies.room_emotes', content, stateKey ?? ''), + room.id, 'im.ponies.room_emotes', pack.toJson(), stateKey ?? ''), ); } else { await showFutureLoadingDialog( context: context, future: () => client.setAccountData( - client.userID, 'im.ponies.user_emotes', content), + client.userID, 'im.ponies.user_emotes', pack.toJson()), ); } } @@ -126,14 +127,13 @@ class EmotesSettingsController extends State { }); void submitEmoteAction( - String s, + String emoteCode, EmoteEntry emote, TextEditingController controller, ) { - final emoteCode = ':$s:'; if (emotes.indexWhere((e) => e.emote == emoteCode && e.mxc != emote.mxc) != -1) { - controller.text = emote.emoteClean; + controller.text = emote.emote; showOkAlertDialog( useRootNavigator: false, context: context, @@ -142,8 +142,8 @@ class EmotesSettingsController extends State { ); return; } - if (!RegExp(r'^:[-\w]+:$').hasMatch(emoteCode)) { - controller.text = emote.emoteClean; + if (!RegExp(r'^[-\w]+$').hasMatch(emoteCode)) { + controller.text = emote.emote; showOkAlertDialog( useRootNavigator: false, context: context, @@ -190,7 +190,7 @@ class EmotesSettingsController extends State { ); return; } - final emoteCode = ':${newEmoteController.text}:'; + final emoteCode = '${newEmoteController.text}'; final mxc = newMxcController.text; if (emotes.indexWhere((e) => e.emote == emoteCode && e.mxc != mxc) != -1) { await showOkAlertDialog( @@ -201,7 +201,7 @@ class EmotesSettingsController extends State { ); return; } - if (!RegExp(r'^:[-\w]+:$').hasMatch(emoteCode)) { + if (!RegExp(r'^[-\w]+$').hasMatch(emoteCode)) { await showOkAlertDialog( useRootNavigator: false, context: context, @@ -262,35 +262,10 @@ class EmotesSettingsController extends State { Widget build(BuildContext context) { if (emotes == null) { emotes = []; - Map emoteSource; - if (room != null) { - emoteSource = - room.getState('im.ponies.room_emotes', stateKey ?? '')?.content; - } else { - emoteSource = Matrix.of(context) - .client - .accountData['im.ponies.user_emotes'] - ?.content; - } - if (emoteSource != null) { - if (emoteSource['emoticons'] is Map) { - emoteSource['emoticons'].forEach((key, value) { - if (key is String && - value is Map && - value['url'] is String && - value['url'].startsWith('mxc://')) { - emotes.add(EmoteEntry(emote: key, mxc: value['url'])); - } - }); - } else if (emoteSource['short'] is Map) { - emoteSource['short'].forEach((key, value) { - if (key is String && - value is String && - value.startsWith('mxc://')) { - emotes.add(EmoteEntry(emote: key, mxc: value)); - } - }); - } + final pack = _getPack(context); + for (final entry in pack.images.entries) { + emotes + .add(EmoteEntry(emote: entry.key, mxc: entry.value.url.toString())); } } return EmotesSettingsView(this); diff --git a/lib/pages/views/settings_emotes_view.dart b/lib/pages/views/settings_emotes_view.dart index d67fdb8d..b00fdd69 100644 --- a/lib/pages/views/settings_emotes_view.dart +++ b/lib/pages/views/settings_emotes_view.dart @@ -1,8 +1,10 @@ +import 'package:fluffychat/utils/platform_infos.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/widgets/layouts/max_width_body.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import '../../widgets/matrix.dart'; import '../settings_emotes.dart'; @@ -117,7 +119,9 @@ class EmotesSettingsView extends StatelessWidget { final emote = controller.emotes[i]; final textEditingController = TextEditingController(); - textEditingController.text = emote.emoteClean; + textEditingController.text = emote.emote; + final useShortCuts = (PlatformInfos.isWeb || + PlatformInfos.isDesktop); return ListTile( leading: Container( width: 180.0, @@ -129,35 +133,60 @@ class EmotesSettingsView extends StatelessWidget { color: Theme.of(context).secondaryHeaderColor, ), - child: TextField( - readOnly: controller.readonly, - controller: textEditingController, - autocorrect: false, - minLines: 1, - maxLines: 1, - decoration: InputDecoration( - hintText: L10n.of(context).emoteShortcode, - prefixText: ': ', - suffixText: ':', - prefixStyle: TextStyle( - color: Theme.of(context) - .colorScheme - .secondary, - fontWeight: FontWeight.bold, + child: Shortcuts( + shortcuts: !useShortCuts + ? {} + : { + LogicalKeySet( + LogicalKeyboardKey.enter): + SubmitLineIntent(), + }, + child: Actions( + actions: !useShortCuts + ? {} + : { + SubmitLineIntent: + CallbackAction(onInvoke: (i) { + controller.submitEmoteAction( + textEditingController.text, + emote, + textEditingController, + ); + return null; + }), + }, + child: TextField( + readOnly: controller.readonly, + controller: textEditingController, + autocorrect: false, + minLines: 1, + maxLines: 1, + decoration: InputDecoration( + hintText: + L10n.of(context).emoteShortcode, + prefixText: ': ', + suffixText: ':', + prefixStyle: TextStyle( + color: Theme.of(context) + .colorScheme + .secondary, + fontWeight: FontWeight.bold, + ), + suffixStyle: TextStyle( + color: Theme.of(context) + .colorScheme + .secondary, + fontWeight: FontWeight.bold, + ), + border: InputBorder.none, + ), + onSubmitted: (s) => + controller.submitEmoteAction( + s, + emote, + textEditingController, + ), ), - suffixStyle: TextStyle( - color: Theme.of(context) - .colorScheme - .secondary, - fontWeight: FontWeight.bold, - ), - border: InputBorder.none, - ), - onSubmitted: (s) => - controller.submitEmoteAction( - s, - emote, - textEditingController, ), ), ), @@ -232,3 +261,5 @@ class _EmoteImagePickerState extends State<_EmoteImagePicker> { } } } + +class SubmitLineIntent extends Intent {} diff --git a/lib/widgets/input_bar.dart b/lib/widgets/input_bar.dart index 6c2f76d4..1c9650fd 100644 --- a/lib/widgets/input_bar.dart +++ b/lib/widgets/input_bar.dart @@ -63,16 +63,18 @@ class InputBar extends StatelessWidget { if (emojiMatch != null) { final packSearch = emojiMatch[1]; final emoteSearch = emojiMatch[2].toLowerCase(); - final emotePacks = room.emotePacks; + final emotePacks = room.getImagePacks(ImagePackUsage.emoticon); if (packSearch == null || packSearch.isEmpty) { for (final pack in emotePacks.entries) { - for (final emote in pack.value.entries) { + for (final emote in pack.value.images.entries) { if (emote.key.toLowerCase().contains(emoteSearch)) { ret.add({ 'type': 'emote', 'name': emote.key, 'pack': pack.key, - 'mxc': emote.value, + 'pack_avatar_url': pack.value.pack.avatarUrl?.toString(), + 'pack_display_name': pack.value.pack.displayName ?? pack.key, + 'mxc': emote.value.url.toString(), }); } if (ret.length > maxResults) { @@ -84,13 +86,17 @@ class InputBar extends StatelessWidget { } } } else if (emotePacks[packSearch] != null) { - for (final emote in emotePacks[packSearch].entries) { + for (final emote in emotePacks[packSearch].images.entries) { if (emote.key.toLowerCase().contains(emoteSearch)) { ret.add({ 'type': 'emote', 'name': emote.key, 'pack': packSearch, - 'mxc': emote.value, + 'pack_avatar_url': + emotePacks[packSearch].pack.avatarUrl?.toString(), + 'pack_display_name': + emotePacks[packSearch].pack.displayName ?? packSearch, + 'mxc': emote.value.url.toString(), }); } if (ret.length > maxResults) { @@ -239,8 +245,15 @@ class InputBar extends StatelessWidget { child: Align( alignment: Alignment.centerRight, child: Opacity( - opacity: 0.5, - child: Text(suggestion['pack']), + opacity: suggestion['pack_avatar_url'] != null ? 0.8 : 0.5, + child: suggestion['pack_avatar_url'] != null + ? Avatar( + Uri.parse(suggestion['pack_avatar_url']), + suggestion['pack_display_name'], + size: size * 0.9, + client: client, + ) + : Text(suggestion['pack_display_name']), ), ), ), @@ -289,12 +302,12 @@ class InputBar extends StatelessWidget { var isUnique = true; final insertEmote = suggestion['name']; final insertPack = suggestion['pack']; - final emotePacks = room.emotePacks; + final emotePacks = room.getImagePacks(ImagePackUsage.emoticon); for (final pack in emotePacks.entries) { if (pack.key == insertPack) { continue; } - for (final emote in pack.value.entries) { + for (final emote in pack.value.images.entries) { if (emote.key == insertEmote) { isUnique = false; break; @@ -304,10 +317,7 @@ class InputBar extends StatelessWidget { break; } } - insertText = (isUnique - ? insertEmote - : ':$insertPack~${insertEmote.substring(1)}') + - ' '; + insertText = ':${isUnique ? '' : insertPack + '~'}$insertEmote: '; startText = replaceText.replaceAllMapped( RegExp(r'(\s|^)(:(?:[-\w]+~)?[-\w]+)$'), (Match m) => '${m[1]}$insertText', diff --git a/pubspec.lock b/pubspec.lock index 7bd70dcd..01809022 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -647,14 +647,14 @@ packages: name: matrix url: "https://pub.dartlang.org" source: hosted - version: "0.1.7" + version: "0.1.8" matrix_api_lite: dependency: transitive description: name: matrix_api_lite url: "https://pub.dartlang.org" source: hosted - version: "0.3.3" + version: "0.3.5" matrix_link_text: dependency: transitive description: @@ -1110,6 +1110,13 @@ packages: description: flutter source: sdk version: "0.0.99" + slugify: + dependency: transitive + description: + name: slugify + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" source_map_stack_trace: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index be4705c8..fd1d126c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -48,7 +48,7 @@ dependencies: intl: any localstorage: ^4.0.0+1 lottie: ^1.1.0 - matrix: ^0.1.7 + matrix: ^0.1.8 native_imaging: git: url: https://gitlab.com/famedly/libraries/native_imaging.git