diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 2a56f0379..0c8f45c98 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -3452,5 +3452,8 @@ } }, "thread": "Thread", - "backToMainChat": "Back to main chat" + "backToMainChat": "Back to main chat", + "saveChanges": "Save changes", + "add": "Add", + "newSticker": "New sticker" } diff --git a/lib/pages/settings_emotes/settings_emotes.dart b/lib/pages/settings_emotes/settings_emotes.dart index 56a96ffc8..f3afd598d 100644 --- a/lib/pages/settings_emotes/settings_emotes.dart +++ b/lib/pages/settings_emotes/settings_emotes.dart @@ -38,6 +38,7 @@ class EmotesSettingsController extends State { bool showSave = false; TextEditingController newImageCodeController = TextEditingController(); + ValueNotifier newImageController = ValueNotifier(null); @@ -69,25 +70,25 @@ class EmotesSettingsController extends State { return; } final client = Matrix.of(context).client; - if (room != null) { - await showFutureLoadingDialog( - context: context, - future: () => client.setRoomStateWithKey( - room!.id, - 'im.ponies.room_emotes', - stateKey ?? '', - pack!.toJson(), - ), - ); - } else { - await showFutureLoadingDialog( - context: context, - future: () => client.setAccountData( - client.userID!, - 'im.ponies.user_emotes', - pack!.toJson(), - ), - ); + final result = await showFutureLoadingDialog( + context: context, + future: () => room != null + ? client.setRoomStateWithKey( + room!.id, + 'im.ponies.room_emotes', + stateKey ?? '', + pack!.toJson(), + ) + : client.setAccountData( + client.userID!, + 'im.ponies.user_emotes', + pack!.toJson(), + ), + ); + if (!result.isError) { + setState(() { + showSave = false; + }); } } @@ -172,6 +173,13 @@ class EmotesSettingsController extends State { bool get readonly => room == null ? false : !(room!.canSendEvent('im.ponies.room_emotes')); + void resetAction() { + setState(() { + _pack = _getPack(); + showSave = false; + }); + } + void saveAction() async { await save(context); setState(() { @@ -227,24 +235,25 @@ class EmotesSettingsController extends State { ); final pickedFile = result.firstOrNull; if (pickedFile == null) return; + var file = MatrixImageFile( bytes: await pickedFile.readAsBytes(), name: pickedFile.name, ); - try { - file = (await file.generateThumbnail( - nativeImplementations: ClientManager.nativeImplementations, - ))!; - } catch (e, s) { - Logs().w('Unable to create thumbnail', e, s); - } + final uploadResp = await showFutureLoadingDialog( context: context, - future: () => Matrix.of(context).client.uploadContent( - file.bytes, - filename: file.name, - contentType: file.mimeType, - ), + future: () async { + file = await file.generateThumbnail( + nativeImplementations: ClientManager.nativeImplementations, + ) ?? + file; + return Matrix.of(context).client.uploadContent( + file.bytes, + filename: file.name, + contentType: file.mimeType, + ); + }, ); if (uploadResp.error == null) { setState(() { @@ -266,6 +275,9 @@ class EmotesSettingsController extends State { 'url': uploadResp.result.toString(), 'info': info, }); + if (newImageCodeController.text.isEmpty) { + newImageCodeController.text = pickedFile.name.split('.').first; + } }); } } diff --git a/lib/pages/settings_emotes/settings_emotes_view.dart b/lib/pages/settings_emotes/settings_emotes_view.dart index 3134cba8d..0163aa32a 100644 --- a/lib/pages/settings_emotes/settings_emotes_view.dart +++ b/lib/pages/settings_emotes/settings_emotes_view.dart @@ -25,91 +25,92 @@ class EmotesSettingsView extends StatelessWidget { final imageKeys = controller.pack!.images.keys.toList(); return Scaffold( appBar: AppBar( - leading: const Center(child: BackButton()), - title: Text(L10n.of(context).customEmojisAndStickers), + automaticallyImplyLeading: !controller.showSave, + title: controller.showSave + ? TextButton( + onPressed: controller.resetAction, + child: Text(L10n.of(context).cancel), + ) + : Text(L10n.of(context).customEmojisAndStickers), actions: [ - PopupMenuButton( - useRootNavigator: true, - onSelected: (value) { - switch (value) { - case PopupMenuEmojiActions.export: - controller.exportAsZip(); - break; - case PopupMenuEmojiActions.import: - controller.importEmojiZip(); - break; - } - }, - enabled: !controller.readonly, - itemBuilder: (context) => [ - PopupMenuItem( - value: PopupMenuEmojiActions.import, - child: Text(L10n.of(context).importFromZipFile), + if (controller.showSave) + ElevatedButton( + onPressed: () => controller.save(context), + style: ElevatedButton.styleFrom( + backgroundColor: theme.colorScheme.primary, + foregroundColor: theme.colorScheme.onPrimary, ), - PopupMenuItem( - value: PopupMenuEmojiActions.export, - child: Text(L10n.of(context).exportEmotePack), - ), - ], - ), + child: Text(L10n.of(context).saveChanges), + ) + else + PopupMenuButton( + useRootNavigator: true, + onSelected: (value) { + switch (value) { + case PopupMenuEmojiActions.export: + controller.exportAsZip(); + break; + case PopupMenuEmojiActions.import: + controller.importEmojiZip(); + break; + } + }, + enabled: !controller.readonly, + itemBuilder: (context) => [ + PopupMenuItem( + value: PopupMenuEmojiActions.import, + child: Text(L10n.of(context).importFromZipFile), + ), + PopupMenuItem( + value: PopupMenuEmojiActions.export, + child: Text(L10n.of(context).exportEmotePack), + ), + ], + ), ], ), - floatingActionButton: controller.showSave - ? FloatingActionButton( - onPressed: controller.saveAction, - child: const Icon(Icons.save_outlined, color: Colors.white), - ) - : null, body: MaxWidthBody( child: Column( mainAxisSize: MainAxisSize.min, children: [ if (!controller.readonly) - Container( + Padding( padding: const EdgeInsets.symmetric( vertical: 8.0, ), child: ListTile( - leading: Container( - width: 180.0, - height: 38, - padding: const EdgeInsets.symmetric(horizontal: 8), - decoration: BoxDecoration( - borderRadius: const BorderRadius.all(Radius.circular(10)), - color: theme.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.colorScheme.secondary, - fontWeight: FontWeight.bold, - ), - suffixStyle: TextStyle( - color: theme.colorScheme.secondary, - fontWeight: FontWeight.bold, - ), - border: InputBorder.none, + 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, ), ), - title: _ImagePicker( + leading: _ImagePicker( + readOnly: controller.showSave, controller: controller.newImageController, onPressed: controller.imagePickerAction, ), - trailing: InkWell( - onTap: controller.addImageAction, - child: const Icon( - Icons.add_outlined, - color: Colors.green, - size: 32.0, - ), + trailing: TextButton( + onPressed: controller.showSave || + controller.newImageController.value == null + ? null + : controller.addImageAction, + child: Text(L10n.of(context).add), ), ), ), @@ -148,80 +149,65 @@ class EmotesSettingsView extends StatelessWidget { final useShortCuts = (PlatformInfos.isWeb || PlatformInfos.isDesktop); return ListTile( - leading: Container( - width: 180.0, - height: 38, - padding: const EdgeInsets.symmetric(horizontal: 8), - decoration: BoxDecoration( - borderRadius: - const BorderRadius.all(Radius.circular(10)), - color: theme.secondaryHeaderColor, - ), - child: Shortcuts( - shortcuts: !useShortCuts + title: Shortcuts( + shortcuts: !useShortCuts + ? {} + : { + LogicalKeySet(LogicalKeyboardKey.enter): + SubmitLineIntent(), + }, + child: Actions( + actions: !useShortCuts ? {} : { - LogicalKeySet(LogicalKeyboardKey.enter): - SubmitLineIntent(), + SubmitLineIntent: CallbackAction( + onInvoke: (i) { + controller.submitImageAction( + imageCode, + textEditingController.text, + image, + textEditingController, + ); + return null; + }, + ), }, - 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.colorScheme.secondary, - fontWeight: FontWeight.bold, + child: TextField( + readOnly: controller.readonly, + controller: textEditingController, + autocorrect: false, + minLines: 1, + maxLines: 1, + maxLength: 128, + decoration: InputDecoration( + hintText: L10n.of(context).emoteShortcode, + prefixText: ': ', + suffixText: ':', + counter: const SizedBox.shrink(), + filled: false, + enabledBorder: const OutlineInputBorder( + borderSide: BorderSide( + color: Colors.transparent, ), - suffixStyle: TextStyle( - color: theme.colorScheme.secondary, - fontWeight: FontWeight.bold, - ), - border: InputBorder.none, - ), - onSubmitted: (s) => - controller.submitImageAction( - imageCode, - s, - image, - textEditingController, ), ), + onSubmitted: (s) => controller.submitImageAction( + imageCode, + s, + image, + textEditingController, + ), ), ), ), - title: _EmoteImage(image.url), + leading: _EmoteImage(image.url), trailing: controller.readonly ? null - : InkWell( - onTap: () => + : IconButton( + tooltip: L10n.of(context).delete, + onPressed: () => controller.removeImageAction(imageCode), - child: const Icon( - Icons.delete_outlined, - color: Colors.red, - size: 32.0, - ), + icon: const Icon(Icons.delete_outlined), ), ); }, @@ -256,10 +242,15 @@ class _EmoteImage extends StatelessWidget { class _ImagePicker extends StatefulWidget { final ValueNotifier controller; + final bool readOnly; final void Function(ValueNotifier) onPressed; - const _ImagePicker({required this.controller, required this.onPressed}); + const _ImagePicker({ + required this.controller, + this.readOnly = false, + required this.onPressed, + }); @override _ImagePickerState createState() => _ImagePickerState(); @@ -269,9 +260,11 @@ class _ImagePickerState extends State<_ImagePicker> { @override Widget build(BuildContext context) { if (widget.controller.value == null) { - return ElevatedButton( - onPressed: () => widget.onPressed(widget.controller), - child: Text(L10n.of(context).pickImage), + 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); diff --git a/pubspec.lock b/pubspec.lock index 965667782..b55ddbb7b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1096,10 +1096,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.16.0" mgrs_dart: dependency: transitive description: @@ -1821,26 +1821,26 @@ packages: dependency: transitive description: name: test - sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" + sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb" url: "https://pub.dev" source: hosted - version: "1.26.3" + version: "1.26.2" test_api: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.6" test_core: dependency: transitive description: name: test_core - sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" + sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a" url: "https://pub.dev" source: hosted - version: "0.6.12" + version: "0.6.11" timezone: dependency: transitive description: