From e1bd4e174c245b86e3d1b43b211b8923f1dd339d Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Thu, 20 May 2021 13:46:52 +0200 Subject: [PATCH] fix: Emoji picker --- lib/views/chat.dart | 48 ++-- lib/views/ui/chat_ui.dart | 498 +++++++++++++++++++------------------- pubspec.lock | 2 +- pubspec.yaml | 2 +- 4 files changed, 272 insertions(+), 278 deletions(-) diff --git a/lib/views/chat.dart b/lib/views/chat.dart index 5431d22f..c472afd5 100644 --- a/lib/views/chat.dart +++ b/lib/views/chat.dart @@ -3,7 +3,6 @@ import 'dart:io'; import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:adaptive_page_layout/adaptive_page_layout.dart'; -import 'package:emoji_picker_flutter/emoji_picker_flutter.dart'; import 'package:famedlysdk/famedlysdk.dart'; import 'package:file_picker_cross/file_picker_cross.dart'; import 'package:fluffychat/config/app_config.dart'; @@ -23,7 +22,6 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:image_picker/image_picker.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:scroll_to_index/scroll_to_index.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:url_launcher/url_launcher.dart'; import 'send_file_dialog.dart'; @@ -78,6 +76,8 @@ class ChatController extends State { bool get canLoadMore => timeline.events.last.type != EventTypes.RoomCreate; + bool showEmojiPicker = false; + void startCallAction() async { final url = '${AppConfig.jitsiInstance}${Uri.encodeComponent(Matrix.of(context).client.generateUniqueTransactionId())}'; @@ -478,39 +478,24 @@ class ChatController extends State { void scrollDown() => scrollController.jumpTo(0); - void pickEmojiAction(Iterable allReactionEvents) async { - final emoji = await showModalBottomSheet( - context: context, - backgroundColor: Colors.transparent, - builder: (innerContext) => Column( - children: [ - Spacer(), - Material( - color: Theme.of(context).scaffoldBackgroundColor, - child: EmojiPicker( - onEmojiSelected: (category, emoji) { - // recent emojis don't work, so we sadly have to re-implement them - // https://github.com/JeffG05/emoji_picker/issues/31 - SharedPreferences.getInstance().then((prefs) { - final recents = prefs.getStringList('recents') ?? []; - recents.insert(0, emoji.name); - // make sure we remove duplicates - prefs.setStringList('recents', recents.toSet().toList()); - }); - Navigator.of(innerContext, rootNavigator: false).pop(emoji); - }, - ), - ), - ], - ), - ); + void onEmojiSelected(category, emoji) { + setState(() => showEmojiPicker = false); if (emoji == null) return; // make sure we don't send the same emoji twice - if (allReactionEvents + if (_allReactionEvents .any((e) => e.content['m.relates_to']['key'] == emoji.emoji)) return; return sendEmojiAction(emoji.emoji); } + Iterable _allReactionEvents; + + void cancelEmojiPicker() => setState(() => showEmojiPicker = false); + + void pickEmojiAction(Iterable allReactionEvents) async { + _allReactionEvents = allReactionEvents; + setState(() => showEmojiPicker = true); + } + void sendEmojiAction(String emoji) async { await showFutureLoadingDialog( context: context, @@ -522,7 +507,10 @@ class ChatController extends State { setState(() => selectedEvents.clear()); } - void clearSelectedEvents() => setState(() => selectedEvents.clear()); + void clearSelectedEvents() => setState(() { + selectedEvents.clear(); + showEmojiPicker = false; + }); void editSelectedEventAction() { setState(() { diff --git a/lib/views/ui/chat_ui.dart b/lib/views/ui/chat_ui.dart index b09a5b7e..9b45ff9c 100644 --- a/lib/views/ui/chat_ui.dart +++ b/lib/views/ui/chat_ui.dart @@ -2,6 +2,7 @@ import 'dart:math'; import 'dart:ui'; import 'package:adaptive_page_layout/adaptive_page_layout.dart'; +import 'package:emoji_picker_flutter/emoji_picker_flutter.dart'; import 'package:famedlysdk/famedlysdk.dart'; import 'package:fluffychat/views/chat.dart'; import 'package:fluffychat/views/widgets/avatar.dart'; @@ -415,68 +416,70 @@ class ChatUI extends StatelessWidget { }, ), ), - AnimatedContainer( - duration: Duration(milliseconds: 300), - height: (controller.editEvent == null && - controller.replyEvent == null && - controller.room.canSendDefaultMessages && - controller.selectedEvents.length == 1) - ? 56 - : 0, - child: Material( - color: Theme.of(context).secondaryHeaderColor, - child: Builder(builder: (context) { - if (!(controller.editEvent == null && - controller.replyEvent == null && - controller.selectedEvents.length == 1)) { - return Container(); - } - final emojis = List.from(AppEmojis.emojis); - final allReactionEvents = controller.selectedEvents.first - .aggregatedEvents( - controller.timeline, RelationshipTypes.reaction) - ?.where((event) => - event.senderId == event.room.client.userID && - event.type == 'm.reaction'); + if (!controller.showEmojiPicker) + AnimatedContainer( + duration: Duration(milliseconds: 300), + height: (controller.editEvent == null && + controller.replyEvent == null && + controller.room.canSendDefaultMessages && + controller.selectedEvents.length == 1) + ? 56 + : 0, + child: Material( + color: Theme.of(context).secondaryHeaderColor, + child: Builder(builder: (context) { + if (!(controller.editEvent == null && + controller.replyEvent == null && + controller.selectedEvents.length == 1)) { + return Container(); + } + final emojis = List.from(AppEmojis.emojis); + final allReactionEvents = controller + .selectedEvents.first + .aggregatedEvents( + controller.timeline, RelationshipTypes.reaction) + ?.where((event) => + event.senderId == event.room.client.userID && + event.type == 'm.reaction'); - allReactionEvents.forEach((event) { - try { - emojis.remove(event.content['m.relates_to']['key']); - } catch (_) {} - }); - return ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: emojis.length + 1, - itemBuilder: (c, i) => i == emojis.length - ? InkWell( - borderRadius: BorderRadius.circular(8), - onTap: () => controller - .pickEmojiAction(allReactionEvents), - child: Container( - width: 56, - height: 56, - alignment: Alignment.center, - child: Icon(Icons.add_outlined), - ), - ) - : InkWell( - borderRadius: BorderRadius.circular(8), - onTap: () => - controller.sendEmojiAction(emojis[i]), - child: Container( - width: 56, - height: 56, - alignment: Alignment.center, - child: Text( - emojis[i], - style: TextStyle(fontSize: 30), + allReactionEvents.forEach((event) { + try { + emojis.remove(event.content['m.relates_to']['key']); + } catch (_) {} + }); + return ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: emojis.length + 1, + itemBuilder: (c, i) => i == emojis.length + ? InkWell( + borderRadius: BorderRadius.circular(8), + onTap: () => controller + .pickEmojiAction(allReactionEvents), + child: Container( + width: 56, + height: 56, + alignment: Alignment.center, + child: Icon(Icons.add_outlined), + ), + ) + : InkWell( + borderRadius: BorderRadius.circular(8), + onTap: () => + controller.sendEmojiAction(emojis[i]), + child: Container( + width: 56, + height: 56, + alignment: Alignment.center, + child: Text( + emojis[i], + style: TextStyle(fontSize: 30), + ), ), ), - ), - ); - }), + ); + }), + ), ), - ), AnimatedContainer( duration: Duration(milliseconds: 300), height: controller.editEvent != null || @@ -507,204 +510,207 @@ class ChatUI extends StatelessWidget { height: 1, thickness: 1, ), - controller.room.canSendDefaultMessages && - controller.room.membership == Membership.join - ? Container( - decoration: BoxDecoration( - color: Theme.of(context).scaffoldBackgroundColor, - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: controller.selectMode - ? [ - Container( - height: 56, - child: TextButton( - onPressed: controller.forwardEventsAction, - child: Row( - children: [ - Icon(Icons - .keyboard_arrow_left_outlined), - Text(L10n.of(context).forward), - ], - ), - ), + if (controller.room.canSendDefaultMessages && + controller.room.membership == Membership.join && + !controller.showEmojiPicker) + Container( + decoration: BoxDecoration( + color: Theme.of(context).scaffoldBackgroundColor, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: controller.selectMode + ? [ + Container( + height: 56, + child: TextButton( + onPressed: controller.forwardEventsAction, + child: Row( + children: [ + Icon(Icons.keyboard_arrow_left_outlined), + Text(L10n.of(context).forward), + ], ), - controller.selectedEvents.length == 1 - ? controller.selectedEvents.first - .getDisplayEvent( - controller.timeline) - .status > - 0 - ? Container( - height: 56, - child: TextButton( - onPressed: - controller.replyAction, - child: Row( - children: [ - Text( - L10n.of(context).reply), - Icon(Icons - .keyboard_arrow_right), - ], - ), - ), - ) - : Container( - height: 56, - child: TextButton( - onPressed: - controller.sendAgainAction, - child: Row( - children: [ - Text(L10n.of(context) - .tryToSendAgain), - SizedBox(width: 4), - Icon(Icons.send_outlined, - size: 16), - ], - ), - ), - ) - : Container(), - ] - : [ - AnimatedContainer( - duration: Duration(milliseconds: 200), - height: 56, - width: - controller.inputText.isEmpty ? 56 : 0, - alignment: Alignment.center, - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration(), - child: PopupMenuButton( - icon: Icon(Icons.add_outlined), - onSelected: controller - .onAddPopupMenuButtonSelected, - itemBuilder: (BuildContext context) => - >[ - PopupMenuItem( - value: 'file', - child: ListTile( - leading: CircleAvatar( - backgroundColor: Colors.green, - foregroundColor: Colors.white, - child: Icon( - Icons.attachment_outlined), + ), + ), + controller.selectedEvents.length == 1 + ? controller.selectedEvents.first + .getDisplayEvent( + controller.timeline) + .status > + 0 + ? Container( + height: 56, + child: TextButton( + onPressed: controller.replyAction, + child: Row( + children: [ + Text(L10n.of(context).reply), + Icon( + Icons.keyboard_arrow_right), + ], ), - title: - Text(L10n.of(context).sendFile), - contentPadding: EdgeInsets.all(0), ), + ) + : Container( + height: 56, + child: TextButton( + onPressed: + controller.sendAgainAction, + child: Row( + children: [ + Text(L10n.of(context) + .tryToSendAgain), + SizedBox(width: 4), + Icon(Icons.send_outlined, + size: 16), + ], + ), + ), + ) + : Container(), + ] + : [ + AnimatedContainer( + duration: Duration(milliseconds: 200), + height: 56, + width: controller.inputText.isEmpty ? 56 : 0, + alignment: Alignment.center, + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration(), + child: PopupMenuButton( + icon: Icon(Icons.add_outlined), + onSelected: + controller.onAddPopupMenuButtonSelected, + itemBuilder: (BuildContext context) => + >[ + PopupMenuItem( + value: 'file', + child: ListTile( + leading: CircleAvatar( + backgroundColor: Colors.green, + foregroundColor: Colors.white, + child: + Icon(Icons.attachment_outlined), ), - PopupMenuItem( - value: 'image', - child: ListTile( - leading: CircleAvatar( - backgroundColor: Colors.blue, - foregroundColor: Colors.white, - child: Icon(Icons.image_outlined), - ), - title: Text( - L10n.of(context).sendImage), - contentPadding: EdgeInsets.all(0), - ), + title: Text(L10n.of(context).sendFile), + contentPadding: EdgeInsets.all(0), + ), + ), + PopupMenuItem( + value: 'image', + child: ListTile( + leading: CircleAvatar( + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + child: Icon(Icons.image_outlined), ), - if (PlatformInfos.isMobile) - PopupMenuItem( - value: 'camera', - child: ListTile( - leading: CircleAvatar( - backgroundColor: Colors.purple, - foregroundColor: Colors.white, - child: Icon( - Icons.camera_alt_outlined), - ), - title: Text( - L10n.of(context).openCamera), - contentPadding: EdgeInsets.all(0), - ), - ), - if (PlatformInfos.isMobile) - PopupMenuItem( - value: 'voice', - child: ListTile( - leading: CircleAvatar( - backgroundColor: Colors.red, - foregroundColor: Colors.white, - child: Icon( - Icons.mic_none_outlined), - ), - title: Text(L10n.of(context) - .voiceMessage), - contentPadding: EdgeInsets.all(0), - ), - ), - ], + title: Text(L10n.of(context).sendImage), + contentPadding: EdgeInsets.all(0), + ), ), - ), - Container( - height: 56, - alignment: Alignment.center, - child: EncryptionButton(controller.room), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 4.0), - child: InputBar( - room: controller.room, - minLines: 1, - maxLines: kIsWeb ? 1 : 8, - autofocus: !PlatformInfos.isMobile, - keyboardType: !PlatformInfos.isMobile - ? TextInputType.text - : TextInputType.multiline, - onSubmitted: - controller.onInputBarSubmitted, - focusNode: controller.inputFocus, - controller: controller.sendController, - decoration: InputDecoration( - hintText: - L10n.of(context).writeAMessage, - hintMaxLines: 1, - border: InputBorder.none, - enabledBorder: InputBorder.none, - filled: false, + if (PlatformInfos.isMobile) + PopupMenuItem( + value: 'camera', + child: ListTile( + leading: CircleAvatar( + backgroundColor: Colors.purple, + foregroundColor: Colors.white, + child: + Icon(Icons.camera_alt_outlined), + ), + title: + Text(L10n.of(context).openCamera), + contentPadding: EdgeInsets.all(0), ), - onChanged: controller.onInputBarChanged, ), + if (PlatformInfos.isMobile) + PopupMenuItem( + value: 'voice', + child: ListTile( + leading: CircleAvatar( + backgroundColor: Colors.red, + foregroundColor: Colors.white, + child: + Icon(Icons.mic_none_outlined), + ), + title: Text( + L10n.of(context).voiceMessage), + contentPadding: EdgeInsets.all(0), + ), + ), + ], + ), + ), + Container( + height: 56, + alignment: Alignment.center, + child: EncryptionButton(controller.room), + ), + Expanded( + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 4.0), + child: InputBar( + room: controller.room, + minLines: 1, + maxLines: kIsWeb ? 1 : 8, + autofocus: !PlatformInfos.isMobile, + keyboardType: !PlatformInfos.isMobile + ? TextInputType.text + : TextInputType.multiline, + onSubmitted: controller.onInputBarSubmitted, + focusNode: controller.inputFocus, + controller: controller.sendController, + decoration: InputDecoration( + hintText: L10n.of(context).writeAMessage, + hintMaxLines: 1, + border: InputBorder.none, + enabledBorder: InputBorder.none, + filled: false, ), + onChanged: controller.onInputBarChanged, ), - if (PlatformInfos.isMobile && - controller.inputText.isEmpty) - Container( - height: 56, - alignment: Alignment.center, - child: IconButton( - tooltip: L10n.of(context).voiceMessage, - icon: Icon(Icons.mic_none_outlined), - onPressed: - controller.voiceMessageAction, - ), - ), - if (!PlatformInfos.isMobile || - controller.inputText.isNotEmpty) - Container( - height: 56, - alignment: Alignment.center, - child: IconButton( - icon: Icon(Icons.send_outlined), - onPressed: controller.send, - tooltip: L10n.of(context).send, - ), - ), - ], - ), - ) - : Container(), + ), + ), + if (PlatformInfos.isMobile && + controller.inputText.isEmpty) + Container( + height: 56, + alignment: Alignment.center, + child: IconButton( + tooltip: L10n.of(context).voiceMessage, + icon: Icon(Icons.mic_none_outlined), + onPressed: controller.voiceMessageAction, + ), + ), + if (!PlatformInfos.isMobile || + controller.inputText.isNotEmpty) + Container( + height: 56, + alignment: Alignment.center, + child: IconButton( + icon: Icon(Icons.send_outlined), + onPressed: controller.send, + tooltip: L10n.of(context).send, + ), + ), + ], + ), + ), + AnimatedContainer( + duration: Duration(milliseconds: 300), + height: controller.showEmojiPicker + ? MediaQuery.of(context).size.height / 2 + : 0, + child: controller.showEmojiPicker + ? EmojiPicker( + onEmojiSelected: controller.onEmojiSelected, + onBackspacePressed: controller.cancelEmojiPicker, + ) + : null, + ), ], ), ), diff --git a/pubspec.lock b/pubspec.lock index b6582234..74481982 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -217,7 +217,7 @@ packages: name: emoji_picker_flutter url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "1.0.5" fake_async: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index e7dd03f6..b0fed425 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ dependencies: cupertino_icons: any desktop_notifications: ^0.4.0 email_validator: ^2.0.1 - emoji_picker_flutter: ^1.0.3 + emoji_picker_flutter: ^1.0.5 famedlysdk: git: url: https://gitlab.com/famedly/famedlysdk.git