mirror of
				https://gitlab.com/famedly/fluffychat.git
				synced 2025-11-03 22:07:23 +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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user