mirror of
				https://gitlab.com/famedly/fluffychat.git
				synced 2025-11-04 06:17:26 +01:00 
			
		
		
		
	chore: cleanup image pack editor to attach metadata and prepare to make it more general
This commit is contained in:
		
							parent
							
								
									ed231da6a1
								
							
						
					
					
						commit
						5a44b3b3d1
					
				@ -2,16 +2,15 @@ import 'package:adaptive_dialog/adaptive_dialog.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:matrix/matrix.dart';
 | 
			
		||||
import 'package:file_picker_cross/file_picker_cross.dart';
 | 
			
		||||
import 'package:fluffychat/utils/platform_infos.dart';
 | 
			
		||||
import 'package:flutter/foundation.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
 | 
			
		||||
import 'package:vrouter/vrouter.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
 | 
			
		||||
import 'package:image_picker/image_picker.dart';
 | 
			
		||||
import 'views/settings_emotes_view.dart';
 | 
			
		||||
import '../widgets/matrix.dart';
 | 
			
		||||
import '../utils/resize_image.dart';
 | 
			
		||||
 | 
			
		||||
class EmotesSettings extends StatefulWidget {
 | 
			
		||||
  EmotesSettings({Key key}) : super(key: key);
 | 
			
		||||
@ -20,24 +19,18 @@ class EmotesSettings extends StatefulWidget {
 | 
			
		||||
  EmotesSettingsController createState() => EmotesSettingsController();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class EmoteEntry {
 | 
			
		||||
  String emote;
 | 
			
		||||
  String mxc;
 | 
			
		||||
  EmoteEntry({this.emote, this.mxc});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class EmotesSettingsController extends State<EmotesSettings> {
 | 
			
		||||
  String get roomId => VRouter.of(context).pathParameters['roomid'];
 | 
			
		||||
  Room get room =>
 | 
			
		||||
      roomId != null ? Matrix.of(context).client.getRoomById(roomId) : null;
 | 
			
		||||
  String get stateKey => VRouter.of(context).pathParameters['state_key'];
 | 
			
		||||
 | 
			
		||||
  List<EmoteEntry> emotes;
 | 
			
		||||
  bool showSave = false;
 | 
			
		||||
  TextEditingController newEmoteController = TextEditingController();
 | 
			
		||||
  TextEditingController newMxcController = TextEditingController();
 | 
			
		||||
  TextEditingController newImageCodeController = TextEditingController();
 | 
			
		||||
  ValueNotifier<ImagePackImageContent> newImageController =
 | 
			
		||||
      ValueNotifier<ImagePackImageContent>(null);
 | 
			
		||||
 | 
			
		||||
  ImagePackContent _getPack(BuildContext context) {
 | 
			
		||||
  ImagePackContent _getPack() {
 | 
			
		||||
    final client = Matrix.of(context).client;
 | 
			
		||||
    final event = (room != null
 | 
			
		||||
            ? room.getState('im.ponies.room_emotes', stateKey ?? '')
 | 
			
		||||
@ -50,33 +43,20 @@ class EmotesSettingsController extends State<EmotesSettings> {
 | 
			
		||||
    return BasicEvent.fromJson(event.toJson()).parsedImagePackContent;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ImagePackContent _pack;
 | 
			
		||||
  ImagePackContent get pack {
 | 
			
		||||
    if (_pack != null) {
 | 
			
		||||
      return _pack;
 | 
			
		||||
    }
 | 
			
		||||
    _pack = _getPack();
 | 
			
		||||
    return _pack;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> _save(BuildContext context) async {
 | 
			
		||||
    if (readonly) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    final client = Matrix.of(context).client;
 | 
			
		||||
    final pack = _getPack(context);
 | 
			
		||||
    // add / update changed emotes
 | 
			
		||||
    final allowedShortcodes = <String>{};
 | 
			
		||||
    for (final emote in emotes) {
 | 
			
		||||
      allowedShortcodes.add(emote.emote);
 | 
			
		||||
      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,
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // 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 pack.images.keys.toList()) {
 | 
			
		||||
      if (!allowedShortcodes.contains(shortcode)) {
 | 
			
		||||
        pack.images.remove(shortcode);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (room != null) {
 | 
			
		||||
      await showFutureLoadingDialog(
 | 
			
		||||
        context: context,
 | 
			
		||||
@ -121,19 +101,19 @@ class EmotesSettingsController extends State<EmotesSettings> {
 | 
			
		||||
    setState(() => null);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void removeEmoteAction(EmoteEntry emote) => setState(() {
 | 
			
		||||
        emotes.removeWhere((e) => e.emote == emote.emote);
 | 
			
		||||
  void removeImageAction(String oldImageCode) => setState(() {
 | 
			
		||||
        pack.images.remove(oldImageCode);
 | 
			
		||||
        showSave = true;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
  void submitEmoteAction(
 | 
			
		||||
    String emoteCode,
 | 
			
		||||
    EmoteEntry emote,
 | 
			
		||||
  void submitImageAction(
 | 
			
		||||
    String oldImageCode,
 | 
			
		||||
    String imageCode,
 | 
			
		||||
    ImagePackImageContent image,
 | 
			
		||||
    TextEditingController controller,
 | 
			
		||||
  ) {
 | 
			
		||||
    if (emotes.indexWhere((e) => e.emote == emoteCode && e.mxc != emote.mxc) !=
 | 
			
		||||
        -1) {
 | 
			
		||||
      controller.text = emote.emote;
 | 
			
		||||
    if (pack.images.keys.any((k) => k == imageCode && k != oldImageCode)) {
 | 
			
		||||
      controller.text = oldImageCode;
 | 
			
		||||
      showOkAlertDialog(
 | 
			
		||||
        useRootNavigator: false,
 | 
			
		||||
        context: context,
 | 
			
		||||
@ -142,8 +122,8 @@ class EmotesSettingsController extends State<EmotesSettings> {
 | 
			
		||||
      );
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (!RegExp(r'^[-\w]+$').hasMatch(emoteCode)) {
 | 
			
		||||
      controller.text = emote.emote;
 | 
			
		||||
    if (!RegExp(r'^[-\w]+$').hasMatch(imageCode)) {
 | 
			
		||||
      controller.text = oldImageCode;
 | 
			
		||||
      showOkAlertDialog(
 | 
			
		||||
        useRootNavigator: false,
 | 
			
		||||
        context: context,
 | 
			
		||||
@ -153,7 +133,8 @@ class EmotesSettingsController extends State<EmotesSettings> {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    setState(() {
 | 
			
		||||
      emote.emote = emoteCode;
 | 
			
		||||
      pack.images[imageCode] = image;
 | 
			
		||||
      pack.images.remove(oldImageCode);
 | 
			
		||||
      showSave = true;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
@ -177,11 +158,10 @@ class EmotesSettingsController extends State<EmotesSettings> {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void addEmoteAction() async {
 | 
			
		||||
    if (newEmoteController.text == null ||
 | 
			
		||||
        newEmoteController.text.isEmpty ||
 | 
			
		||||
        newMxcController.text == null ||
 | 
			
		||||
        newMxcController.text.isEmpty) {
 | 
			
		||||
  void addImageAction() async {
 | 
			
		||||
    if (newImageCodeController.text == null ||
 | 
			
		||||
        newImageCodeController.text.isEmpty ||
 | 
			
		||||
        newImageController.value == null) {
 | 
			
		||||
      await showOkAlertDialog(
 | 
			
		||||
        useRootNavigator: false,
 | 
			
		||||
        context: context,
 | 
			
		||||
@ -190,9 +170,8 @@ class EmotesSettingsController extends State<EmotesSettings> {
 | 
			
		||||
      );
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    final emoteCode = '${newEmoteController.text}';
 | 
			
		||||
    final mxc = newMxcController.text;
 | 
			
		||||
    if (emotes.indexWhere((e) => e.emote == emoteCode && e.mxc != mxc) != -1) {
 | 
			
		||||
    final imageCode = newImageCodeController.text;
 | 
			
		||||
    if (pack.images.containsKey(imageCode)) {
 | 
			
		||||
      await showOkAlertDialog(
 | 
			
		||||
        useRootNavigator: false,
 | 
			
		||||
        context: context,
 | 
			
		||||
@ -201,7 +180,7 @@ class EmotesSettingsController extends State<EmotesSettings> {
 | 
			
		||||
      );
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (!RegExp(r'^[-\w]+$').hasMatch(emoteCode)) {
 | 
			
		||||
    if (!RegExp(r'^[-\w]+$').hasMatch(imageCode)) {
 | 
			
		||||
      await showOkAlertDialog(
 | 
			
		||||
        useRootNavigator: false,
 | 
			
		||||
        context: context,
 | 
			
		||||
@ -210,41 +189,28 @@ class EmotesSettingsController extends State<EmotesSettings> {
 | 
			
		||||
      );
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    emotes.add(EmoteEntry(emote: emoteCode, mxc: mxc));
 | 
			
		||||
    pack.images[imageCode] = newImageController.value;
 | 
			
		||||
    await _save(context);
 | 
			
		||||
    setState(() {
 | 
			
		||||
      newEmoteController.text = '';
 | 
			
		||||
      newMxcController.text = '';
 | 
			
		||||
      newImageCodeController.text = '';
 | 
			
		||||
      newImageController.value = null;
 | 
			
		||||
      showSave = false;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void emoteImagePickerAction(TextEditingController controller) async {
 | 
			
		||||
    if (kIsWeb) {
 | 
			
		||||
      ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
          SnackBar(content: Text(L10n.of(context).notSupportedInWeb)));
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    MatrixFile file;
 | 
			
		||||
    if (PlatformInfos.isMobile) {
 | 
			
		||||
      final result = await ImagePicker().getImage(
 | 
			
		||||
          source: ImageSource.gallery,
 | 
			
		||||
          imageQuality: 50,
 | 
			
		||||
          maxWidth: 1600,
 | 
			
		||||
          maxHeight: 1600);
 | 
			
		||||
      if (result == null) return;
 | 
			
		||||
      file = MatrixFile(
 | 
			
		||||
        bytes: await result.readAsBytes(),
 | 
			
		||||
        name: result.path,
 | 
			
		||||
      );
 | 
			
		||||
    } else {
 | 
			
		||||
      final result =
 | 
			
		||||
          await FilePickerCross.importFromStorage(type: FileTypeCross.image);
 | 
			
		||||
      if (result == null) return;
 | 
			
		||||
      file = MatrixFile(
 | 
			
		||||
        bytes: result.toUint8List(),
 | 
			
		||||
        name: result.fileName,
 | 
			
		||||
      );
 | 
			
		||||
  void imagePickerAction(
 | 
			
		||||
      ValueNotifier<ImagePackImageContent> controller) async {
 | 
			
		||||
    final result =
 | 
			
		||||
        await FilePickerCross.importFromStorage(type: FileTypeCross.image);
 | 
			
		||||
    if (result == null) return;
 | 
			
		||||
    var file = MatrixImageFile(
 | 
			
		||||
      bytes: result.toUint8List(),
 | 
			
		||||
      name: result.fileName,
 | 
			
		||||
    );
 | 
			
		||||
    try {
 | 
			
		||||
      file = await resizeImage(file, max: 1600);
 | 
			
		||||
    } catch (_) {
 | 
			
		||||
      // do nothing
 | 
			
		||||
    }
 | 
			
		||||
    final uploadResp = await showFutureLoadingDialog(
 | 
			
		||||
      context: context,
 | 
			
		||||
@ -253,21 +219,30 @@ class EmotesSettingsController extends State<EmotesSettings> {
 | 
			
		||||
    );
 | 
			
		||||
    if (uploadResp.error == null) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        controller.text = uploadResp.result;
 | 
			
		||||
        final info = <String, dynamic>{
 | 
			
		||||
          ...file.info,
 | 
			
		||||
        };
 | 
			
		||||
        // normalize width / height to 256, required for stickers
 | 
			
		||||
        if (info['w'] is int && info['h'] is int) {
 | 
			
		||||
          final ratio = info['w'] / info['h'];
 | 
			
		||||
          if (info['w'] > info['h']) {
 | 
			
		||||
            info['w'] = 256;
 | 
			
		||||
            info['h'] = (256.0 / ratio).round();
 | 
			
		||||
          } else {
 | 
			
		||||
            info['h'] = 256;
 | 
			
		||||
            info['w'] = (ratio * 256.0).round();
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        controller.value = ImagePackImageContent.fromJson(<String, dynamic>{
 | 
			
		||||
          'url': uploadResp.result,
 | 
			
		||||
          'info': info,
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    if (emotes == null) {
 | 
			
		||||
      emotes = <EmoteEntry>[];
 | 
			
		||||
      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);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -17,6 +17,7 @@ class EmotesSettingsView extends StatelessWidget {
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final client = Matrix.of(context).client;
 | 
			
		||||
    final imageKeys = controller.pack.images.keys.toList();
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        leading: BackButton(),
 | 
			
		||||
@ -29,200 +30,195 @@ class EmotesSettingsView extends StatelessWidget {
 | 
			
		||||
            )
 | 
			
		||||
          : null,
 | 
			
		||||
      body: MaxWidthBody(
 | 
			
		||||
        child: StreamBuilder(
 | 
			
		||||
            stream: controller.room?.onUpdate?.stream,
 | 
			
		||||
            builder: (context, snapshot) {
 | 
			
		||||
              return Column(
 | 
			
		||||
                children: <Widget>[
 | 
			
		||||
                  if (!controller.readonly)
 | 
			
		||||
                    Container(
 | 
			
		||||
                      padding: EdgeInsets.symmetric(
 | 
			
		||||
                        vertical: 8.0,
 | 
			
		||||
        child: Column(
 | 
			
		||||
          children: <Widget>[
 | 
			
		||||
            if (!controller.readonly)
 | 
			
		||||
              Container(
 | 
			
		||||
                padding: EdgeInsets.symmetric(
 | 
			
		||||
                  vertical: 8.0,
 | 
			
		||||
                ),
 | 
			
		||||
                child: ListTile(
 | 
			
		||||
                  leading: Container(
 | 
			
		||||
                    width: 180.0,
 | 
			
		||||
                    height: 38,
 | 
			
		||||
                    padding: EdgeInsets.symmetric(horizontal: 8),
 | 
			
		||||
                    decoration: BoxDecoration(
 | 
			
		||||
                      borderRadius: BorderRadius.all(Radius.circular(10)),
 | 
			
		||||
                      color: Theme.of(context).secondaryHeaderColor,
 | 
			
		||||
                    ),
 | 
			
		||||
                    child: TextField(
 | 
			
		||||
                      controller: controller.newImageCodeController,
 | 
			
		||||
                      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,
 | 
			
		||||
                      ),
 | 
			
		||||
                      child: ListTile(
 | 
			
		||||
                        leading: Container(
 | 
			
		||||
                          width: 180.0,
 | 
			
		||||
                          height: 38,
 | 
			
		||||
                          padding: EdgeInsets.symmetric(horizontal: 8),
 | 
			
		||||
                          decoration: BoxDecoration(
 | 
			
		||||
                            borderRadius: BorderRadius.all(Radius.circular(10)),
 | 
			
		||||
                            color: Theme.of(context).secondaryHeaderColor,
 | 
			
		||||
                          ),
 | 
			
		||||
                          child: TextField(
 | 
			
		||||
                            controller: controller.newEmoteController,
 | 
			
		||||
                            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,
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                  title: _ImagePicker(
 | 
			
		||||
                    controller: controller.newImageController,
 | 
			
		||||
                    onPressed: controller.imagePickerAction,
 | 
			
		||||
                  ),
 | 
			
		||||
                  trailing: InkWell(
 | 
			
		||||
                    onTap: controller.addImageAction,
 | 
			
		||||
                    child: Icon(
 | 
			
		||||
                      Icons.add_outlined,
 | 
			
		||||
                      color: Colors.green,
 | 
			
		||||
                      size: 32.0,
 | 
			
		||||
                    ),
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
            if (controller.room != null)
 | 
			
		||||
              ListTile(
 | 
			
		||||
                title: Text(L10n.of(context).enableEmotesGlobally),
 | 
			
		||||
                trailing: Switch(
 | 
			
		||||
                  value: controller.isGloballyActive(client),
 | 
			
		||||
                  onChanged: controller.setIsGloballyActive,
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
            if (!controller.readonly || controller.room != null)
 | 
			
		||||
              Divider(
 | 
			
		||||
                height: 2,
 | 
			
		||||
                thickness: 2,
 | 
			
		||||
                color: Theme.of(context).primaryColor,
 | 
			
		||||
              ),
 | 
			
		||||
            Expanded(
 | 
			
		||||
              child: imageKeys.isEmpty
 | 
			
		||||
                  ? Center(
 | 
			
		||||
                      child: Padding(
 | 
			
		||||
                        padding: EdgeInsets.all(16),
 | 
			
		||||
                        child: Text(
 | 
			
		||||
                          L10n.of(context).noEmotesFound,
 | 
			
		||||
                          style: TextStyle(fontSize: 20),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
                    )
 | 
			
		||||
                  : ListView.separated(
 | 
			
		||||
                      separatorBuilder: (BuildContext context, int i) =>
 | 
			
		||||
                          Container(),
 | 
			
		||||
                      itemCount: imageKeys.length + 1,
 | 
			
		||||
                      itemBuilder: (BuildContext context, int i) {
 | 
			
		||||
                        if (i >= imageKeys.length) {
 | 
			
		||||
                          return Container(height: 70);
 | 
			
		||||
                        }
 | 
			
		||||
                        final imageCode = imageKeys[i];
 | 
			
		||||
                        final image = controller.pack.images[imageCode];
 | 
			
		||||
                        final textEditingController = TextEditingController();
 | 
			
		||||
                        textEditingController.text = imageCode;
 | 
			
		||||
                        final useShortCuts =
 | 
			
		||||
                            (PlatformInfos.isWeb || PlatformInfos.isDesktop);
 | 
			
		||||
                        return ListTile(
 | 
			
		||||
                          leading: Container(
 | 
			
		||||
                            width: 180.0,
 | 
			
		||||
                            height: 38,
 | 
			
		||||
                            padding: EdgeInsets.symmetric(horizontal: 8),
 | 
			
		||||
                            decoration: BoxDecoration(
 | 
			
		||||
                              borderRadius:
 | 
			
		||||
                                  BorderRadius.all(Radius.circular(10)),
 | 
			
		||||
                              color: Theme.of(context).secondaryHeaderColor,
 | 
			
		||||
                            ),
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                        title: _EmoteImagePicker(
 | 
			
		||||
                          controller: controller.newMxcController,
 | 
			
		||||
                          onPressed: controller.emoteImagePickerAction,
 | 
			
		||||
                        ),
 | 
			
		||||
                        trailing: InkWell(
 | 
			
		||||
                          onTap: controller.addEmoteAction,
 | 
			
		||||
                          child: Icon(
 | 
			
		||||
                            Icons.add_outlined,
 | 
			
		||||
                            color: Colors.green,
 | 
			
		||||
                            size: 32.0,
 | 
			
		||||
                          ),
 | 
			
		||||
                        ),
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                  if (controller.room != null)
 | 
			
		||||
                    ListTile(
 | 
			
		||||
                      title: Text(L10n.of(context).enableEmotesGlobally),
 | 
			
		||||
                      trailing: Switch(
 | 
			
		||||
                        value: controller.isGloballyActive(client),
 | 
			
		||||
                        onChanged: controller.setIsGloballyActive,
 | 
			
		||||
                      ),
 | 
			
		||||
                    ),
 | 
			
		||||
                  if (!controller.readonly || controller.room != null)
 | 
			
		||||
                    Divider(
 | 
			
		||||
                      height: 2,
 | 
			
		||||
                      thickness: 2,
 | 
			
		||||
                      color: Theme.of(context).primaryColor,
 | 
			
		||||
                    ),
 | 
			
		||||
                  Expanded(
 | 
			
		||||
                    child: controller.emotes.isEmpty
 | 
			
		||||
                        ? Center(
 | 
			
		||||
                            child: Padding(
 | 
			
		||||
                              padding: EdgeInsets.all(16),
 | 
			
		||||
                              child: Text(
 | 
			
		||||
                                L10n.of(context).noEmotesFound,
 | 
			
		||||
                                style: TextStyle(fontSize: 20),
 | 
			
		||||
                              ),
 | 
			
		||||
                            ),
 | 
			
		||||
                          )
 | 
			
		||||
                        : ListView.separated(
 | 
			
		||||
                            separatorBuilder: (BuildContext context, int i) =>
 | 
			
		||||
                                Container(),
 | 
			
		||||
                            itemCount: controller.emotes.length + 1,
 | 
			
		||||
                            itemBuilder: (BuildContext context, int i) {
 | 
			
		||||
                              if (i >= controller.emotes.length) {
 | 
			
		||||
                                return Container(height: 70);
 | 
			
		||||
                              }
 | 
			
		||||
                              final emote = controller.emotes[i];
 | 
			
		||||
                              final textEditingController =
 | 
			
		||||
                                  TextEditingController();
 | 
			
		||||
                              textEditingController.text = emote.emote;
 | 
			
		||||
                              final useShortCuts = (PlatformInfos.isWeb ||
 | 
			
		||||
                                  PlatformInfos.isDesktop);
 | 
			
		||||
                              return ListTile(
 | 
			
		||||
                                leading: Container(
 | 
			
		||||
                                  width: 180.0,
 | 
			
		||||
                                  height: 38,
 | 
			
		||||
                                  padding: EdgeInsets.symmetric(horizontal: 8),
 | 
			
		||||
                                  decoration: BoxDecoration(
 | 
			
		||||
                                    borderRadius:
 | 
			
		||||
                                        BorderRadius.all(Radius.circular(10)),
 | 
			
		||||
                                    color:
 | 
			
		||||
                                        Theme.of(context).secondaryHeaderColor,
 | 
			
		||||
                                  ),
 | 
			
		||||
                                  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,
 | 
			
		||||
                                        ),
 | 
			
		||||
                                      ),
 | 
			
		||||
                            child: Shortcuts(
 | 
			
		||||
                              shortcuts: !useShortCuts
 | 
			
		||||
                                  ? {}
 | 
			
		||||
                                  : {
 | 
			
		||||
                                      LogicalKeySet(LogicalKeyboardKey.enter):
 | 
			
		||||
                                          SubmitLineIntent(),
 | 
			
		||||
                                    },
 | 
			
		||||
                              child: Actions(
 | 
			
		||||
                                actions: !useShortCuts
 | 
			
		||||
                                    ? {}
 | 
			
		||||
                                    : {
 | 
			
		||||
                                        SubmitLineIntent:
 | 
			
		||||
                                            CallbackAction(onInvoke: (i) {
 | 
			
		||||
                                          controller.submitImageAction(
 | 
			
		||||
                                            imageCode,
 | 
			
		||||
                                            textEditingController.text,
 | 
			
		||||
                                            image,
 | 
			
		||||
                                            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.submitImageAction(
 | 
			
		||||
                                    imageCode,
 | 
			
		||||
                                    s,
 | 
			
		||||
                                    image,
 | 
			
		||||
                                    textEditingController,
 | 
			
		||||
                                  ),
 | 
			
		||||
                                ),
 | 
			
		||||
                                title: _EmoteImage(emote.mxc),
 | 
			
		||||
                                trailing: controller.readonly
 | 
			
		||||
                                    ? null
 | 
			
		||||
                                    : InkWell(
 | 
			
		||||
                                        onTap: () =>
 | 
			
		||||
                                            controller.removeEmoteAction(emote),
 | 
			
		||||
                                        child: Icon(
 | 
			
		||||
                                          Icons.delete_forever_outlined,
 | 
			
		||||
                                          color: Colors.red,
 | 
			
		||||
                                          size: 32.0,
 | 
			
		||||
                                        ),
 | 
			
		||||
                                      ),
 | 
			
		||||
                              );
 | 
			
		||||
                            },
 | 
			
		||||
                              ),
 | 
			
		||||
                            ),
 | 
			
		||||
                          ),
 | 
			
		||||
                  ),
 | 
			
		||||
                ],
 | 
			
		||||
              );
 | 
			
		||||
            }),
 | 
			
		||||
                          title: _EmoteImage(image.url),
 | 
			
		||||
                          trailing: controller.readonly
 | 
			
		||||
                              ? null
 | 
			
		||||
                              : InkWell(
 | 
			
		||||
                                  onTap: () =>
 | 
			
		||||
                                      controller.removeImageAction(imageCode),
 | 
			
		||||
                                  child: Icon(
 | 
			
		||||
                                    Icons.delete_forever_outlined,
 | 
			
		||||
                                    color: Colors.red,
 | 
			
		||||
                                    size: 32.0,
 | 
			
		||||
                                  ),
 | 
			
		||||
                                ),
 | 
			
		||||
                        );
 | 
			
		||||
                      },
 | 
			
		||||
                    ),
 | 
			
		||||
            ),
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _EmoteImage extends StatelessWidget {
 | 
			
		||||
  final String mxc;
 | 
			
		||||
  final Uri mxc;
 | 
			
		||||
  _EmoteImage(this.mxc);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final size = 38.0;
 | 
			
		||||
    final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
 | 
			
		||||
    final url = Uri.parse(mxc)?.getThumbnail(
 | 
			
		||||
    final url = mxc?.getThumbnail(
 | 
			
		||||
      Matrix.of(context).client,
 | 
			
		||||
      width: size * devicePixelRatio,
 | 
			
		||||
      height: size * devicePixelRatio,
 | 
			
		||||
@ -237,27 +233,27 @@ class _EmoteImage extends StatelessWidget {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _EmoteImagePicker extends StatefulWidget {
 | 
			
		||||
  final TextEditingController controller;
 | 
			
		||||
class _ImagePicker extends StatefulWidget {
 | 
			
		||||
  final ValueNotifier<ImagePackImageContent> controller;
 | 
			
		||||
 | 
			
		||||
  final void Function(TextEditingController) onPressed;
 | 
			
		||||
  final void Function(ValueNotifier<ImagePackImageContent>) onPressed;
 | 
			
		||||
 | 
			
		||||
  _EmoteImagePicker({@required this.controller, @required this.onPressed});
 | 
			
		||||
  _ImagePicker({@required this.controller, @required this.onPressed});
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  _EmoteImagePickerState createState() => _EmoteImagePickerState();
 | 
			
		||||
  _ImagePickerState createState() => _ImagePickerState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _EmoteImagePickerState extends State<_EmoteImagePicker> {
 | 
			
		||||
class _ImagePickerState extends State<_ImagePicker> {
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    if (widget.controller.text == null || widget.controller.text.isEmpty) {
 | 
			
		||||
    if (widget.controller.value == null) {
 | 
			
		||||
      return ElevatedButton(
 | 
			
		||||
        onPressed: () => widget.onPressed(widget.controller),
 | 
			
		||||
        child: Text(L10n.of(context).pickImage),
 | 
			
		||||
      );
 | 
			
		||||
    } else {
 | 
			
		||||
      return _EmoteImage(widget.controller.text);
 | 
			
		||||
      return _EmoteImage(widget.controller.value.url);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,16 @@ Future<MatrixImageFile> resizeImage(MatrixImageFile file,
 | 
			
		||||
  // freeze up the UI a bit
 | 
			
		||||
 | 
			
		||||
  // we can't do width / height fetching in a separate isolate, as that may use the UI stuff
 | 
			
		||||
  await native.init();
 | 
			
		||||
 | 
			
		||||
  // somehow doing native.init twice fixes it for linux desktop?
 | 
			
		||||
  // TODO: once native imaging is on sound null safety the errors are consistent and
 | 
			
		||||
  // then we can properly handle this instead
 | 
			
		||||
  // https://gitlab.com/famedly/company/frontend/libraries/native_imaging/-/issues/5
 | 
			
		||||
  try {
 | 
			
		||||
    await native.init();
 | 
			
		||||
  } catch (_) {
 | 
			
		||||
    await native.init();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _IsolateArgs args;
 | 
			
		||||
  try {
 | 
			
		||||
@ -47,9 +56,14 @@ Future<MatrixImageFile> resizeImage(MatrixImageFile file,
 | 
			
		||||
    mimeType: 'image/jpeg',
 | 
			
		||||
    width: res.width,
 | 
			
		||||
    height: res.height,
 | 
			
		||||
    blurhash: res.blurhash,
 | 
			
		||||
  );
 | 
			
		||||
  // only return the thumbnail if the size actually decreased
 | 
			
		||||
  return thumbnail.size >= file.size ? file : thumbnail;
 | 
			
		||||
  return thumbnail.size >= file.size ||
 | 
			
		||||
          thumbnail.width >= file.width ||
 | 
			
		||||
          thumbnail.height >= file.height
 | 
			
		||||
      ? file
 | 
			
		||||
      : thumbnail;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _IsolateArgs {
 | 
			
		||||
@ -70,7 +84,12 @@ class _IsolateResponse {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Future<_IsolateResponse> _isolateFunction(_IsolateArgs args) async {
 | 
			
		||||
  await native.init();
 | 
			
		||||
  // Hack for desktop, see above why
 | 
			
		||||
  try {
 | 
			
		||||
    await native.init();
 | 
			
		||||
  } catch (_) {
 | 
			
		||||
    await native.init();
 | 
			
		||||
  }
 | 
			
		||||
  var nativeImg = native.Image();
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user