diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 64242b49..eb6b2df3 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -1885,6 +1885,11 @@ "type": "text", "placeholders": {} }, + "sendSticker": "Send sticker", + "@sendSticker": { + "type": "text", + "placeholders": {} + }, "sendOriginal": "Send original", "@sendOriginal": { "type": "text", diff --git a/lib/pages/chat.dart b/lib/pages/chat.dart index b3c34169..9484d007 100644 --- a/lib/pages/chat.dart +++ b/lib/pages/chat.dart @@ -28,6 +28,7 @@ import 'package:vrouter/vrouter.dart'; import '../utils/localized_exception_extension.dart'; import 'send_file_dialog.dart'; +import 'sticker_picker_dialog.dart'; import '../utils/matrix_sdk_extensions.dart/filtered_timeline_extension.dart'; import '../utils/matrix_sdk_extensions.dart/matrix_file_extension.dart'; @@ -308,6 +309,28 @@ class ChatController extends State { ); } + void sendStickerAction() async { + final sticker = await showModalBottomSheet( + context: context, + useRootNavigator: false, + builder: (c) => StickerPickerDialog(room: room), + ); + if (sticker == null) return; + final eventContent = { + 'body': sticker.body, + if (sticker.info != null) 'info': sticker.info, + 'url': sticker.url.toString(), + }; + // send the sticker + await showFutureLoadingDialog( + context: context, + future: () => room.sendEvent( + eventContent, + type: EventTypes.Sticker, + ), + ); + } + void voiceMessageAction() async { if (await Permission.microphone.isGranted != true) { final status = await Permission.microphone.request(); @@ -645,12 +668,16 @@ class ChatController extends State { void onAddPopupMenuButtonSelected(String choice) { if (choice == 'file') { sendFileAction(); - } else if (choice == 'image') { + } + if (choice == 'image') { sendImageAction(); } if (choice == 'camera') { openCameraAction(); } + if (choice == 'sticker') { + sendStickerAction(); + } if (choice == 'voice') { voiceMessageAction(); } diff --git a/lib/pages/sticker_picker_dialog.dart b/lib/pages/sticker_picker_dialog.dart new file mode 100644 index 00000000..3c6e79de --- /dev/null +++ b/lib/pages/sticker_picker_dialog.dart @@ -0,0 +1,133 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:matrix/matrix.dart'; + +import '../widgets/event_content/image_bubble.dart'; +import '../widgets/avatar.dart'; +import '../widgets/default_app_bar_search_field.dart'; + +class StickerPickerDialog extends StatefulWidget { + final Room room; + + const StickerPickerDialog({this.room, Key key}) : super(key: key); + + @override + StickerPickerDialogState createState() => StickerPickerDialogState(); +} + +class StickerPickerDialogState extends State { + String searchFilter; + + @override + Widget build(BuildContext context) { + final stickerPacks = widget.room.getImagePacks(ImagePackUsage.sticker); + final packSlugs = stickerPacks.keys.toList(); + + final _packBuilder = (BuildContext context, int packIndex) { + final pack = stickerPacks[packSlugs[packIndex]]; + final filteredImagePackImageEntried = pack.images.entries.toList(); + if (searchFilter?.isNotEmpty ?? false) { + filteredImagePackImageEntried.removeWhere((e) => + !(e.key.toLowerCase().contains(searchFilter.toLowerCase()) || + (e.value.body + ?.toLowerCase() + ?.contains(searchFilter.toLowerCase()) ?? + false))); + } + final imageKeys = + filteredImagePackImageEntried.map((e) => e.key).toList(); + if (imageKeys.isEmpty) { + return Container(); + } + final packName = pack.pack.displayName ?? packSlugs[packIndex]; + return Column( + children: [ + if (packIndex != 0) SizedBox(height: 20), + if (packName != 'user') + ListTile( + leading: Avatar( + pack.pack.avatarUrl, + packName, + client: widget.room.client, + ), + title: Text(packName), + ), + SizedBox(height: 6), + GridView.builder( + itemCount: imageKeys.length, + gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 100), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemBuilder: (BuildContext context, int imageIndex) { + final image = pack.images[imageKeys[imageIndex]]; + final fakeEvent = Event.fromJson({ + 'type': EventTypes.Sticker, + 'content': { + 'url': image.url.toString(), + 'info': image.info, + }, + 'event_id': 'fake_event', + }, widget.room); + return InkWell( + key: ValueKey(image.url.toString()), + onTap: () { + // copy the image + final imageCopy = + ImagePackImageContent.fromJson(image.toJson().copy()); + // set the body, if it doesn't exist, to the key + imageCopy.body ??= imageKeys[imageIndex]; + Navigator.of(context, rootNavigator: false) + .pop(imageCopy); + }, + child: AbsorbPointer( + absorbing: true, + child: ImageBubble( + fakeEvent, + tapToView: false, + fit: BoxFit.contain, + width: 100, + height: 100, + ), + ), + ); + }, + ), + ], + ); + }; + + return Scaffold( + body: Container( + width: double.maxFinite, + child: CustomScrollView( + slivers: [ + SliverAppBar( + floating: true, + pinned: true, + automaticallyImplyLeading: false, + titleSpacing: 0, + backgroundColor: Theme.of(context).dialogBackgroundColor, + leading: IconButton( + icon: Icon(Icons.close), + onPressed: Navigator.of(context, rootNavigator: false).pop, + ), + title: DefaultAppBarSearchField( + autofocus: false, + hintText: L10n.of(context).search, + suffix: Icon(Icons.search_outlined), + onChanged: (s) => setState(() => searchFilter = s), + ), + ), + SliverList( + delegate: SliverChildBuilderDelegate( + _packBuilder, + childCount: packSlugs.length, + )), + ], + ), + ), + ); + } +} diff --git a/lib/pages/views/chat_view.dart b/lib/pages/views/chat_view.dart index eb6c3cfe..bcc2b3f1 100644 --- a/lib/pages/views/chat_view.dart +++ b/lib/pages/views/chat_view.dart @@ -626,6 +626,20 @@ class ChatView extends StatelessWidget { contentPadding: EdgeInsets.all(0), ), ), + PopupMenuItem( + value: 'sticker', + child: ListTile( + leading: CircleAvatar( + backgroundColor: Colors.orange, + foregroundColor: Colors.white, + child: Icon(Icons + .emoji_emotions_outlined), + ), + title: Text( + L10n.of(context).sendSticker), + contentPadding: EdgeInsets.all(0), + ), + ), if (PlatformInfos.isMobile) PopupMenuItem( value: 'voice',