diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 0c8f45c98..55f108571 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -3454,6 +3454,5 @@ "thread": "Thread", "backToMainChat": "Back to main chat", "saveChanges": "Save changes", - "add": "Add", - "newSticker": "New sticker" + "createSticker": "Create sticker or emoji" } diff --git a/lib/pages/settings_emotes/settings_emotes.dart b/lib/pages/settings_emotes/settings_emotes.dart index f3afd598d..173c6b310 100644 --- a/lib/pages/settings_emotes/settings_emotes.dart +++ b/lib/pages/settings_emotes/settings_emotes.dart @@ -3,7 +3,6 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:collection/collection.dart'; import 'package:go_router/go_router.dart'; import 'package:http/http.dart' hide Client; import 'package:matrix/matrix.dart'; @@ -37,10 +36,6 @@ class EmotesSettingsController extends State { String? get stateKey => GoRouterState.of(context).pathParameters['state_key']; bool showSave = false; - TextEditingController newImageCodeController = TextEditingController(); - - ValueNotifier newImageController = - ValueNotifier(null); ImagePackContent _getPack() { final client = Matrix.of(context).client; @@ -135,6 +130,7 @@ class EmotesSettingsController extends State { ImagePackImageContent image, TextEditingController controller, ) { + controller.text = controller.text.trim().replaceAll(' ', '-'); if (pack!.images.keys.any((k) => k == imageCode && k != oldImageCode)) { controller.text = oldImageCode; showOkAlertDialog( @@ -187,99 +183,63 @@ class EmotesSettingsController extends State { }); } - void addImageAction() async { - if (newImageCodeController.text.isEmpty || - newImageController.value == null) { - await showOkAlertDialog( - useRootNavigator: false, - context: context, - title: L10n.of(context).emoteWarnNeedToPick, - okLabel: L10n.of(context).ok, - ); - return; - } - final imageCode = newImageCodeController.text; - if (pack!.images.containsKey(imageCode)) { - await showOkAlertDialog( - useRootNavigator: false, - context: context, - title: L10n.of(context).emoteExists, - okLabel: L10n.of(context).ok, - ); - return; - } - if (!RegExp(r'^[-\w]+$').hasMatch(imageCode)) { - await showOkAlertDialog( - useRootNavigator: false, - context: context, - title: L10n.of(context).emoteInvalid, - okLabel: L10n.of(context).ok, - ); - return; - } - pack!.images[imageCode] = newImageController.value!; - await save(context); - setState(() { - newImageCodeController.text = ''; - newImageController.value = null; - showSave = false; - }); - } - - void imagePickerAction( - ValueNotifier controller, - ) async { - final result = await selectFiles( + void createStickers() async { + final pickedFiles = await selectFiles( context, type: FileSelectorType.images, + allowMultiple: true, ); - final pickedFile = result.firstOrNull; - if (pickedFile == null) return; + if (pickedFiles.isEmpty) return; + if (!mounted) return; - var file = MatrixImageFile( - bytes: await pickedFile.readAsBytes(), - name: pickedFile.name, - ); - - final uploadResp = await showFutureLoadingDialog( + await showFutureLoadingDialog( context: context, - future: () async { - file = await file.generateThumbnail( - nativeImplementations: ClientManager.nativeImplementations, - ) ?? - file; - return Matrix.of(context).client.uploadContent( - file.bytes, - filename: file.name, - contentType: file.mimeType, - ); + futureWithProgress: (setProgress) async { + for (final (i, pickedFile) in pickedFiles.indexed) { + setProgress(i / pickedFiles.length); + var file = MatrixImageFile( + bytes: await pickedFile.readAsBytes(), + name: pickedFile.name, + ); + file = await file.generateThumbnail( + nativeImplementations: ClientManager.nativeImplementations, + ) ?? + file; + final uri = await Matrix.of(context).client.uploadContent( + file.bytes, + filename: file.name, + contentType: file.mimeType, + ); + + setState(() { + final info = { + ...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(); + } + } + final imageCode = pickedFile.name.split('.').first; + pack!.images[imageCode] = + ImagePackImageContent.fromJson({ + 'url': uri.toString(), + 'info': info, + }); + }); + } }, ); - if (uploadResp.error == null) { - setState(() { - final info = { - ...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({ - 'url': uploadResp.result.toString(), - 'info': info, - }); - if (newImageCodeController.text.isEmpty) { - newImageCodeController.text = pickedFile.name.split('.').first; - } - }); - } + + setState(() { + showSave = true; + }); } @override diff --git a/lib/pages/settings_emotes/settings_emotes_view.dart b/lib/pages/settings_emotes/settings_emotes_view.dart index 0163aa32a..238ac4cea 100644 --- a/lib/pages/settings_emotes/settings_emotes_view.dart +++ b/lib/pages/settings_emotes/settings_emotes_view.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:matrix/matrix.dart'; - import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/layouts/max_width_body.dart'; @@ -72,46 +70,15 @@ class EmotesSettingsView extends StatelessWidget { body: MaxWidthBody( child: Column( mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (!controller.readonly) Padding( - padding: const EdgeInsets.symmetric( - vertical: 8.0, - ), - child: ListTile( - title: TextField( - controller: controller.newImageCodeController, - autocorrect: false, - minLines: 1, - maxLines: 1, - readOnly: controller.showSave, - decoration: InputDecoration( - hintText: L10n.of(context).newSticker, - prefixText: ': ', - suffixText: ':', - prefixStyle: TextStyle( - color: theme.colorScheme.secondary, - fontWeight: FontWeight.bold, - ), - suffixStyle: TextStyle( - color: theme.colorScheme.secondary, - fontWeight: FontWeight.bold, - ), - border: InputBorder.none, - ), - ), - leading: _ImagePicker( - readOnly: controller.showSave, - controller: controller.newImageController, - onPressed: controller.imagePickerAction, - ), - trailing: TextButton( - onPressed: controller.showSave || - controller.newImageController.value == null - ? null - : controller.addImageAction, - child: Text(L10n.of(context).add), - ), + padding: const EdgeInsets.all(16.0), + child: ElevatedButton.icon( + onPressed: controller.createStickers, + icon: const Icon(Icons.upload_outlined), + label: Text(L10n.of(context).createSticker), ), ), if (controller.room != null) @@ -240,36 +207,4 @@ class _EmoteImage extends StatelessWidget { } } -class _ImagePicker extends StatefulWidget { - final ValueNotifier controller; - final bool readOnly; - - final void Function(ValueNotifier) onPressed; - - const _ImagePicker({ - required this.controller, - this.readOnly = false, - required this.onPressed, - }); - - @override - _ImagePickerState createState() => _ImagePickerState(); -} - -class _ImagePickerState extends State<_ImagePicker> { - @override - Widget build(BuildContext context) { - if (widget.controller.value == null) { - return IconButton( - tooltip: L10n.of(context).select, - onPressed: - widget.readOnly ? null : () => widget.onPressed(widget.controller), - icon: const Icon(Icons.upload_outlined), - ); - } else { - return _EmoteImage(widget.controller.value!.url); - } - } -} - class SubmitLineIntent extends Intent {}