mirror of
https://gitlab.com/famedly/fluffychat.git
synced 2024-12-25 15:02:33 +01:00
Merge branch 'soru/emotes-and-stickers' into 'main'
chore: Update image pack file format to match that of the emote msc See merge request famedly/fluffychat!433
This commit is contained in:
commit
41b6a14211
@ -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<EmotesSettings> {
|
||||
@ -39,54 +37,57 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
||||
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(<String, dynamic>{
|
||||
'type': 'm.dummy',
|
||||
'content': <String, dynamic>{},
|
||||
});
|
||||
// make sure we work on a *copy* of the event
|
||||
return BasicEvent.fromJson(event.toJson()).parsedImagePackContent;
|
||||
}
|
||||
|
||||
Future<void> _save(BuildContext context) async {
|
||||
if (readonly) {
|
||||
return;
|
||||
}
|
||||
final client = Matrix.of(context).client;
|
||||
// be sure to preserve any data not in "short"
|
||||
Map<String, dynamic> content;
|
||||
if (room != null) {
|
||||
content =
|
||||
room.getState('im.ponies.room_emotes', stateKey ?? '')?.content ??
|
||||
<String, dynamic>{};
|
||||
} else {
|
||||
content = client.accountData['im.ponies.user_emotes']?.content ??
|
||||
<String, dynamic>{};
|
||||
}
|
||||
if (!(content['emoticons'] is Map)) {
|
||||
content['emoticons'] = <String, dynamic>{};
|
||||
}
|
||||
final pack = _getPack(context);
|
||||
// add / update changed emotes
|
||||
final allowedShortcodes = <String>{};
|
||||
for (final emote in emotes) {
|
||||
allowedShortcodes.add(emote.emote);
|
||||
if (!(content['emoticons'][emote.emote] is Map)) {
|
||||
content['emoticons'][emote.emote] = <String, dynamic>{};
|
||||
if (pack.images.containsKey(emote.emote)) {
|
||||
pack.images[emote.emote].url = Uri.parse(emote.mxc);
|
||||
} else {
|
||||
pack.images[emote.emote] =
|
||||
ImagePackImageContent.fromJson(<String, dynamic>{
|
||||
'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<EmotesSettings> {
|
||||
});
|
||||
|
||||
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<EmotesSettings> {
|
||||
);
|
||||
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<EmotesSettings> {
|
||||
);
|
||||
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<EmotesSettings> {
|
||||
);
|
||||
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<EmotesSettings> {
|
||||
Widget build(BuildContext context) {
|
||||
if (emotes == null) {
|
||||
emotes = <EmoteEntry>[];
|
||||
Map<String, dynamic> 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);
|
||||
|
@ -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 {}
|
||||
|
@ -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',
|
||||
|
11
pubspec.lock
11
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:
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user