diff --git a/lib/components/dialogs/simple_dialogs.dart b/lib/components/dialogs/simple_dialogs.dart index a0d0041..48e315e 100644 --- a/lib/components/dialogs/simple_dialogs.dart +++ b/lib/components/dialogs/simple_dialogs.dart @@ -107,6 +107,30 @@ class SimpleDialogs { return confirmed; } + Future inform({ + String titleText, + String contentText, + String okText, + }) async { + await showDialog( + context: context, + builder: (c) => AlertDialog( + title: titleText != null ? Text(titleText) : null, + content: contentText != null ? Text(contentText) : null, + actions: [ + FlatButton( + child: Text( + okText ?? L10n.of(context).ok.toUpperCase(), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ), + ); + } + Future tryRequestWithLoadingDialog(Future request, {Function(MatrixException) onAdditionalAuth}) async { showLoadingDialog(context); diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb index 91aa4b8..a905f7c 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -424,6 +424,31 @@ "type": "text", "placeholders": {} }, + "Emote Settings": "Emote Einstellungen", + "@Emote Settings": { + "type": "text", + "placeholders": {} + }, + "Emote shortcode": "Emote kürzel", + "@Emote shortcode": { + "type": "text", + "placeholders": {} + }, + "emoteWarnNeedToPick": "Wähle ein Emote-kürzel und ein Bild!", + "@emoteWarnNeedToPick": { + "type": "text", + "placeholders": {} + }, + "emoteExists": "Emote existiert bereits!", + "@emoteExists": { + "type": "text", + "placeholders": {} + }, + "emoteInvalid": "Ungültiges Emote-kürzel!", + "@emoteInvalid": { + "type": "text", + "placeholders": {} + }, "Empty chat": "Leerer Chat", "@Empty chat": { "type": "text", @@ -768,6 +793,11 @@ "type": "text", "placeholders": {} }, + "No emotes found. 😕": "Keine Emotes gefunden. 😕", + "@No emotes found. 😕": { + "type": "text", + "placeholders": {} + }, "No permission": "Keine Berechtigung", "@No permission": { "type": "text", @@ -790,6 +820,11 @@ "number": {} } }, + "ok": "ok", + "@ok": { + "type": "text", + "placeholders": {} + }, "Oops something went wrong...": "Hoppla! Da ist etwas schief gelaufen ...", "@Oops something went wrong...": { "type": "text", @@ -815,6 +850,11 @@ "type": "text", "placeholders": {} }, + "Pick image": "Wähle Bild", + "@Pick image": { + "type": "text", + "placeholders": {} + }, "play": "Play {fileName}", "@play": { "type": "text", @@ -1327,4 +1367,4 @@ "type": "text", "placeholders": {} } -} +} \ No newline at end of file diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index e3ef8bc..38365e5 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2020-05-09T15:29:08.901368", + "@@last_modified": "2020-05-12T08:42:24.358124", "About": "About", "@About": { "type": "text", @@ -424,6 +424,31 @@ "type": "text", "placeholders": {} }, + "Emote Settings": "Emote Settings", + "@Emote Settings": { + "type": "text", + "placeholders": {} + }, + "Emote shortcode": "Emote shortcode", + "@Emote shortcode": { + "type": "text", + "placeholders": {} + }, + "emoteWarnNeedToPick": "You need to pick an emote shortcode and an image!", + "@emoteWarnNeedToPick": { + "type": "text", + "placeholders": {} + }, + "emoteExists": "Emote already exists!", + "@emoteExists": { + "type": "text", + "placeholders": {} + }, + "emoteInvalid": "Invalid emote shortcode!", + "@emoteInvalid": { + "type": "text", + "placeholders": {} + }, "Empty chat": "Empty chat", "@Empty chat": { "type": "text", @@ -768,6 +793,11 @@ "type": "text", "placeholders": {} }, + "No emotes found. 😕": "No emotes found. 😕", + "@No emotes found. 😕": { + "type": "text", + "placeholders": {} + }, "No permission": "No permission", "@No permission": { "type": "text", @@ -790,6 +820,11 @@ "number": {} } }, + "ok": "ok", + "@ok": { + "type": "text", + "placeholders": {} + }, "Oops something went wrong...": "Oops something went wrong...", "@Oops something went wrong...": { "type": "text", @@ -820,6 +855,11 @@ "type": "text", "placeholders": {} }, + "Pick image": "Pick image", + "@Pick image": { + "type": "text", + "placeholders": {} + }, "play": "Play {fileName}", "@play": { "type": "text", diff --git a/lib/l10n/l10n.dart b/lib/l10n/l10n.dart index 23997ff..3863ad1 100644 --- a/lib/l10n/l10n.dart +++ b/lib/l10n/l10n.dart @@ -298,6 +298,20 @@ class L10n extends MatrixLocalizations { String get editDisplayname => Intl.message("Edit displayname"); + String get emoteSettings => Intl.message('Emote Settings'); + + String get emoteShortcode => Intl.message('Emote shortcode'); + + String get emoteWarnNeedToPick => + Intl.message('You need to pick an emote shortcode and an image!', + name: 'emoteWarnNeedToPick'); + + String get emoteExists => + Intl.message('Emote already exists!', name: 'emoteExists'); + + String get emoteInvalid => + Intl.message('Invalid emote shortcode!', name: 'emoteInvalid'); + String get emptyChat => Intl.message("Empty chat"); String get enableEncryptionWarning => Intl.message( @@ -482,6 +496,8 @@ class L10n extends MatrixLocalizations { String get none => Intl.message("None"); + String get noEmotesFound => Intl.message('No emotes found. 😕'); + String get noPermission => Intl.message("No permission"); String get noRoomsFound => Intl.message("No rooms found..."); @@ -491,6 +507,8 @@ class L10n extends MatrixLocalizations { String numberSelected(String number) => Intl.message("$number selected", name: "numberSelected", args: [number]); + String get ok => Intl.message('ok'); + String get oopsSomethingWentWrong => Intl.message("Oops something went wrong..."); @@ -505,6 +523,8 @@ class L10n extends MatrixLocalizations { String get password => Intl.message("Password"); + String get pickImage => Intl.message('Pick image'); + String play(String fileName) => Intl.message( "Play $fileName", name: "play", diff --git a/lib/l10n/messages_de.dart b/lib/l10n/messages_de.dart index 7d38c7f..8afeea9 100644 --- a/lib/l10n/messages_de.dart +++ b/lib/l10n/messages_de.dart @@ -193,6 +193,8 @@ class MessageLookup extends MessageLookupByLibrary { "Download file" : MessageLookupByLibrary.simpleMessage("Datei herunterladen"), "Edit Jitsi instance" : MessageLookupByLibrary.simpleMessage("Jitsi Instanz ändern"), "Edit displayname" : MessageLookupByLibrary.simpleMessage("Anzeigename ändern"), + "Emote Settings" : MessageLookupByLibrary.simpleMessage("Emote Einstellungen"), + "Emote shortcode" : MessageLookupByLibrary.simpleMessage("Emote kürzel"), "Empty chat" : MessageLookupByLibrary.simpleMessage("Leerer Chat"), "Encryption algorithm" : MessageLookupByLibrary.simpleMessage("Verschlüsselungsalgorithmus"), "Encryption is not enabled" : MessageLookupByLibrary.simpleMessage("Verschlüsselung ist nicht aktiviert"), @@ -241,6 +243,7 @@ class MessageLookup extends MessageLookupByLibrary { "Mute chat" : MessageLookupByLibrary.simpleMessage("Stummschalten"), "New message in FluffyChat" : MessageLookupByLibrary.simpleMessage("Neue Nachricht in FluffyChat"), "New private chat" : MessageLookupByLibrary.simpleMessage("Neuer privater Chat"), + "No emotes found. 😕" : MessageLookupByLibrary.simpleMessage("Keine Emotes gefunden. 😕"), "No permission" : MessageLookupByLibrary.simpleMessage("Keine Berechtigung"), "No rooms found..." : MessageLookupByLibrary.simpleMessage("Keine Räume gefunden ..."), "None" : MessageLookupByLibrary.simpleMessage("Keiner"), @@ -249,6 +252,7 @@ class MessageLookup extends MessageLookupByLibrary { "Open camera" : MessageLookupByLibrary.simpleMessage("Kamera öffnen"), "Participating user devices" : MessageLookupByLibrary.simpleMessage("Teilnehmende Geräte"), "Password" : MessageLookupByLibrary.simpleMessage("Passwort"), + "Pick image" : MessageLookupByLibrary.simpleMessage("Wähle Bild"), "Please be aware that you need Pantalaimon to use end-to-end encryption for now." : MessageLookupByLibrary.simpleMessage("Bitte beachte, dass du Pantalaimon brauchst, um Ende-zu-Ende-Verschlüsselung benutzen zu können."), "Please choose a username" : MessageLookupByLibrary.simpleMessage("Bitte wähle einen Benutzernamen"), "Please enter a matrix identifier" : MessageLookupByLibrary.simpleMessage("Bitte eine Matrix ID eingeben"), @@ -339,6 +343,9 @@ class MessageLookup extends MessageLookupByLibrary { "dateAndTimeOfDay" : m21, "dateWithYear" : m22, "dateWithoutYear" : m23, + "emoteExists" : MessageLookupByLibrary.simpleMessage("Emote existiert bereits!"), + "emoteInvalid" : MessageLookupByLibrary.simpleMessage("Ungültiges Emote-kürzel!"), + "emoteWarnNeedToPick" : MessageLookupByLibrary.simpleMessage("Wähle ein Emote-kürzel und ein Bild!"), "groupWith" : m24, "hasWithdrawnTheInvitationFor" : m25, "inviteContactToGroup" : m26, @@ -352,6 +359,7 @@ class MessageLookup extends MessageLookupByLibrary { "loadCountMoreParticipants" : m33, "logInTo" : m34, "numberSelected" : m35, + "ok" : MessageLookupByLibrary.simpleMessage("ok"), "play" : m36, "redactedAnEvent" : m37, "rejectedTheInvitation" : m38, diff --git a/lib/l10n/messages_messages.dart b/lib/l10n/messages_messages.dart index 18c5b56..082d826 100644 --- a/lib/l10n/messages_messages.dart +++ b/lib/l10n/messages_messages.dart @@ -193,6 +193,8 @@ class MessageLookup extends MessageLookupByLibrary { "Download file" : MessageLookupByLibrary.simpleMessage("Download file"), "Edit Jitsi instance" : MessageLookupByLibrary.simpleMessage("Edit Jitsi instance"), "Edit displayname" : MessageLookupByLibrary.simpleMessage("Edit displayname"), + "Emote Settings" : MessageLookupByLibrary.simpleMessage("Emote Settings"), + "Emote shortcode" : MessageLookupByLibrary.simpleMessage("Emote shortcode"), "Empty chat" : MessageLookupByLibrary.simpleMessage("Empty chat"), "Encryption algorithm" : MessageLookupByLibrary.simpleMessage("Encryption algorithm"), "Encryption is not enabled" : MessageLookupByLibrary.simpleMessage("Encryption is not enabled"), @@ -242,6 +244,7 @@ class MessageLookup extends MessageLookupByLibrary { "Mute chat" : MessageLookupByLibrary.simpleMessage("Mute chat"), "New message in FluffyChat" : MessageLookupByLibrary.simpleMessage("New message in FluffyChat"), "New private chat" : MessageLookupByLibrary.simpleMessage("New private chat"), + "No emotes found. 😕" : MessageLookupByLibrary.simpleMessage("No emotes found. 😕"), "No permission" : MessageLookupByLibrary.simpleMessage("No permission"), "No rooms found..." : MessageLookupByLibrary.simpleMessage("No rooms found..."), "None" : MessageLookupByLibrary.simpleMessage("None"), @@ -251,6 +254,7 @@ class MessageLookup extends MessageLookupByLibrary { "Open camera" : MessageLookupByLibrary.simpleMessage("Open camera"), "Participating user devices" : MessageLookupByLibrary.simpleMessage("Participating user devices"), "Password" : MessageLookupByLibrary.simpleMessage("Password"), + "Pick image" : MessageLookupByLibrary.simpleMessage("Pick image"), "Please be aware that you need Pantalaimon to use end-to-end encryption for now." : MessageLookupByLibrary.simpleMessage("Please be aware that you need Pantalaimon to use end-to-end encryption for now."), "Please choose a username" : MessageLookupByLibrary.simpleMessage("Please choose a username"), "Please enter a matrix identifier" : MessageLookupByLibrary.simpleMessage("Please enter a matrix identifier"), @@ -341,6 +345,9 @@ class MessageLookup extends MessageLookupByLibrary { "dateAndTimeOfDay" : m21, "dateWithYear" : m22, "dateWithoutYear" : m23, + "emoteExists" : MessageLookupByLibrary.simpleMessage("Emote already exists!"), + "emoteInvalid" : MessageLookupByLibrary.simpleMessage("Invalid emote shortcode!"), + "emoteWarnNeedToPick" : MessageLookupByLibrary.simpleMessage("You need to pick an emote shortcode and an image!"), "groupWith" : m24, "hasWithdrawnTheInvitationFor" : m25, "inviteContactToGroup" : m26, @@ -354,6 +361,7 @@ class MessageLookup extends MessageLookupByLibrary { "loadCountMoreParticipants" : m33, "logInTo" : m34, "numberSelected" : m35, + "ok" : MessageLookupByLibrary.simpleMessage("ok"), "play" : m36, "redactedAnEvent" : m37, "rejectedTheInvitation" : m38, diff --git a/lib/views/chat_details.dart b/lib/views/chat_details.dart index f023e34..0c6f295 100644 --- a/lib/views/chat_details.dart +++ b/lib/views/chat_details.dart @@ -16,6 +16,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_styled_toast/flutter_styled_toast.dart'; import 'package:image_picker/image_picker.dart'; import 'package:link_text/link_text.dart'; +import './settings_emotes.dart'; class ChatDetails extends StatefulWidget { final Room room; @@ -274,6 +275,22 @@ class _ChatDetailsState extends State { ? widget.room.canonicalAlias : L10n.of(context).none), ), + ListTile( + leading: CircleAvatar( + backgroundColor: + Theme.of(context).scaffoldBackgroundColor, + foregroundColor: Colors.grey, + child: Icon(Icons.insert_emoticon), + ), + title: Text(L10n.of(context).emoteSettings), + onTap: () async => + await Navigator.of(context).push( + AppRoute.defaultRoute( + context, + EmotesSettingsView(room: widget.room), + ), + ), + ), PopupMenuButton( child: ListTile( leading: CircleAvatar( diff --git a/lib/views/settings.dart b/lib/views/settings.dart index 69305d5..014bdc0 100644 --- a/lib/views/settings.dart +++ b/lib/views/settings.dart @@ -17,6 +17,7 @@ import '../components/content_banner.dart'; import '../components/matrix.dart'; import '../l10n/l10n.dart'; import '../utils/app_route.dart'; +import 'settings_emotes.dart'; class SettingsView extends StatelessWidget { @override @@ -226,11 +227,22 @@ class _SettingsState extends State { activeColor: Theme.of(context).primaryColor, onChanged: (bool newValue) async { Matrix.of(context).renderHtml = newValue; - await client.storeAPI.setItem("chat.fluffy.renderHtml", newValue ? "1" : "0"); + await client.storeAPI + .setItem("chat.fluffy.renderHtml", newValue ? "1" : "0"); setState(() => null); }, ), ), + ListTile( + title: Text(L10n.of(context).emoteSettings), + onTap: () async => await Navigator.of(context).push( + AppRoute.defaultRoute( + context, + EmotesSettingsView(), + ), + ), + trailing: Icon(Icons.insert_emoticon), + ), Divider(thickness: 1), ListTile( title: Text( diff --git a/lib/views/settings_emotes.dart b/lib/views/settings_emotes.dart new file mode 100644 index 0000000..101305f --- /dev/null +++ b/lib/views/settings_emotes.dart @@ -0,0 +1,398 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_advanced_networkimage/provider.dart'; +import 'package:famedlysdk/famedlysdk.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:flutter_styled_toast/flutter_styled_toast.dart'; + +import 'chat_list.dart'; +import '../components/adaptive_page_layout.dart'; +import '../components/matrix.dart'; +import '../components/dialogs/simple_dialogs.dart'; +import '../l10n/l10n.dart'; + +class EmotesSettingsView extends StatelessWidget { + final Room room; + + EmotesSettingsView({this.room}); + + @override + Widget build(BuildContext context) { + return AdaptivePageLayout( + primaryPage: FocusPage.SECOND, + firstScaffold: ChatList(), + secondScaffold: EmotesSettings(room: room), + ); + } +} + +class EmotesSettings extends StatefulWidget { + final Room room; + + EmotesSettings({this.room}); + + @override + _EmotesSettingsState createState() => _EmotesSettingsState(); +} + +class _EmoteEntry { + String emote; + String mxc; + _EmoteEntry({this.emote, this.mxc}); + + String get emoteClean => emote.substring(1, emote.length - 1); +} + +class _EmotesSettingsState extends State { + List<_EmoteEntry> emotes; + bool showSave = false; + TextEditingController newEmoteController = TextEditingController(); + TextEditingController newMxcController = TextEditingController(); + + Future _save(BuildContext context) async { + if (readonly) { + return; + } + debugPrint("Saving...."); + final client = Matrix.of(context).client; + // be sure to preserve any data not in "short" + Map content; + if (widget.room != null) { + content = widget.room.getState('im.ponies.room_emotes')?.content ?? + {}; + } else { + content = client.accountData['im.ponies.user_emotes']?.content ?? + {}; + } + debugPrint(content.toString()); + content['short'] = {}; + for (final emote in emotes) { + content['short'][emote.emote] = emote.mxc; + } + debugPrint(content.toString()); + var path = ''; + if (widget.room != null) { + path = '/client/r0/rooms/${widget.room.id}/state/im.ponies.room_emotes/'; + } else { + path = + '/client/r0/user/${client.userID}/account_data/im.ponies.user_emotes'; + } + debugPrint(path); + await SimpleDialogs(context).tryRequestWithLoadingDialog( + client.jsonRequest( + type: HTTPType.PUT, + action: path, + data: content, + ), + ); + } + + bool get readonly => widget.room == null + ? false + : !(widget.room.canSendEvent('im.ponies.room_emotes')); + + @override + Widget build(BuildContext context) { + Client client = Matrix.of(context).client; + if (emotes == null) { + emotes = <_EmoteEntry>[]; + Map emoteSource; + if (widget.room != null) { + emoteSource = widget.room.getState('im.ponies.room_emotes')?.content; + } else { + emoteSource = client.accountData['im.ponies.user_emotes']?.content; + } + if (emoteSource != null && 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)); + } + }); + } + } + return Scaffold( + appBar: AppBar( + title: Text(L10n.of(context).emoteSettings), + ), + floatingActionButton: showSave + ? FloatingActionButton( + child: Icon(Icons.save, color: Colors.white), + onPressed: () async { + await _save(context); + setState(() { + showSave = false; + }); + }, + backgroundColor: Theme.of(context).primaryColor, + ) + : null, + body: StreamBuilder( + stream: widget.room?.onUpdate?.stream, + builder: (context, snapshot) { + return Column( + children: [ + if (!readonly) + Container( + 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: newEmoteController, + autocorrect: false, + minLines: 1, + maxLines: 1, + decoration: InputDecoration( + hintText: L10n.of(context).emoteShortcode, + prefixText: ': ', + suffixText: ':', + prefixStyle: TextStyle( + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.bold, + ), + suffixStyle: TextStyle( + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.bold, + ), + border: InputBorder.none, + ), + ), + ), + title: _EmoteImagePicker(newMxcController), + trailing: InkWell( + child: Icon( + Icons.add, + color: Colors.green, + size: 32.0, + ), + onTap: () async { + debugPrint("blah"); + if (newEmoteController.text == null || + newEmoteController.text.isEmpty || + newMxcController.text == null || + newMxcController.text.isEmpty) { + await SimpleDialogs(context).inform( + contentText: + L10n.of(context).emoteWarnNeedToPick); + return; + } + final emoteCode = ':${newEmoteController.text}:'; + final mxc = newMxcController.text; + if (emotes.indexWhere((e) => + e.emote == emoteCode && e.mxc != mxc) != + -1) { + await SimpleDialogs(context).inform( + contentText: L10n.of(context).emoteExists); + return; + } + if (!RegExp(r'^:[-\w]+:$').hasMatch(emoteCode)) { + await SimpleDialogs(context).inform( + contentText: L10n.of(context).emoteInvalid); + return; + } + emotes.add(_EmoteEntry(emote: emoteCode, mxc: mxc)); + await _save(context); + setState(() { + newEmoteController.text = ''; + newMxcController.text = ''; + showSave = false; + }); + }, + ), + ), + padding: EdgeInsets.symmetric( + vertical: 8.0, + ), + ), + if (!readonly) + Divider( + height: 2, + thickness: 2, + color: Theme.of(context).primaryColor, + ), + Expanded( + child: 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: emotes.length + 1, + itemBuilder: (BuildContext context, int i) { + if (i >= emotes.length) { + return Container(height: 70); + } + final emote = emotes[i]; + final controller = TextEditingController(); + controller.text = emote.emoteClean; + 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: TextField( + readOnly: readonly, + controller: controller, + autocorrect: false, + minLines: 1, + maxLines: 1, + decoration: InputDecoration( + hintText: L10n.of(context).emoteShortcode, + prefixText: ': ', + suffixText: ':', + prefixStyle: TextStyle( + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.bold, + ), + suffixStyle: TextStyle( + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.bold, + ), + border: InputBorder.none, + ), + onSubmitted: (s) { + final emoteCode = ':${s}:'; + if (emotes.indexWhere((e) => + e.emote == emoteCode && + e.mxc != emote.mxc) != + -1) { + controller.text = emote.emoteClean; + SimpleDialogs(context).inform( + contentText: + L10n.of(context).emoteExists); + return; + } + if (!RegExp(r'^:[-\w]+:$') + .hasMatch(emoteCode)) { + controller.text = emote.emoteClean; + SimpleDialogs(context).inform( + contentText: + L10n.of(context).emoteInvalid); + return; + } + setState(() { + emote.emote = emoteCode; + showSave = true; + }); + }, + ), + ), + title: _EmoteImage(emote.mxc), + trailing: readonly + ? null + : InkWell( + child: Icon( + Icons.delete_forever, + color: Colors.red, + size: 32.0, + ), + onTap: () => setState(() { + emotes.removeWhere( + (e) => e.emote == emote.emote); + showSave = true; + }), + ), + ); + }, + ), + ), + ], + ); + }), + ); + } +} + +class _EmoteImage extends StatelessWidget { + final String 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( + Matrix.of(context).client, + width: size * devicePixelRatio, + height: size * devicePixelRatio, + method: ThumbnailMethod.scale, + ); + return Image( + image: AdvancedNetworkImage( + url, + useDiskCache: !kIsWeb, + ), + fit: BoxFit.contain, + width: size, + height: size, + ); + } +} + +class _EmoteImagePicker extends StatefulWidget { + final TextEditingController controller; + + _EmoteImagePicker(this.controller); + + @override + _EmoteImagePickerState createState() => _EmoteImagePickerState(); +} + +class _EmoteImagePickerState extends State<_EmoteImagePicker> { + @override + Widget build(BuildContext context) { + if (widget.controller.text == null || widget.controller.text.isEmpty) { + return RaisedButton( + color: Theme.of(context).primaryColor, + elevation: 5, + textColor: Colors.white, + child: Text(L10n.of(context).pickImage), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + onPressed: () async { + if (kIsWeb) { + showToast(L10n.of(context).notSupportedInWeb); + return; + } + File file = await ImagePicker.pickImage( + source: ImageSource.gallery, + imageQuality: 50, + maxWidth: 128, + maxHeight: 128); + if (file == null) return; + final uploadResp = + await SimpleDialogs(context).tryRequestWithLoadingDialog( + Matrix.of(context).client.upload( + MatrixFile(bytes: await file.readAsBytes(), path: file.path), + ), + ); + setState(() { + widget.controller.text = uploadResp; + }); + }, + ); + } else { + return _EmoteImage(widget.controller.text); + } + } +}