fix: Emoji picker

This commit is contained in:
Christian Pauly 2021-05-20 13:46:52 +02:00
parent 37c40a2cb5
commit e1bd4e174c
4 changed files with 272 additions and 278 deletions

View File

@ -3,7 +3,6 @@ import 'dart:io';
import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:adaptive_page_layout/adaptive_page_layout.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:famedlysdk/famedlysdk.dart';
import 'package:file_picker_cross/file_picker_cross.dart'; import 'package:file_picker_cross/file_picker_cross.dart';
import 'package:fluffychat/config/app_config.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:image_picker/image_picker.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:scroll_to_index/scroll_to_index.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 'package:url_launcher/url_launcher.dart';
import 'send_file_dialog.dart'; import 'send_file_dialog.dart';
@ -78,6 +76,8 @@ class ChatController extends State<Chat> {
bool get canLoadMore => timeline.events.last.type != EventTypes.RoomCreate; bool get canLoadMore => timeline.events.last.type != EventTypes.RoomCreate;
bool showEmojiPicker = false;
void startCallAction() async { void startCallAction() async {
final url = final url =
'${AppConfig.jitsiInstance}${Uri.encodeComponent(Matrix.of(context).client.generateUniqueTransactionId())}'; '${AppConfig.jitsiInstance}${Uri.encodeComponent(Matrix.of(context).client.generateUniqueTransactionId())}';
@ -478,39 +478,24 @@ class ChatController extends State<Chat> {
void scrollDown() => scrollController.jumpTo(0); void scrollDown() => scrollController.jumpTo(0);
void pickEmojiAction(Iterable<Event> allReactionEvents) async { void onEmojiSelected(category, emoji) {
final emoji = await showModalBottomSheet( setState(() => showEmojiPicker = false);
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') ?? <String>[];
recents.insert(0, emoji.name);
// make sure we remove duplicates
prefs.setStringList('recents', recents.toSet().toList());
});
Navigator.of(innerContext, rootNavigator: false).pop(emoji);
},
),
),
],
),
);
if (emoji == null) return; if (emoji == null) return;
// make sure we don't send the same emoji twice // 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; .any((e) => e.content['m.relates_to']['key'] == emoji.emoji)) return;
return sendEmojiAction(emoji.emoji); return sendEmojiAction(emoji.emoji);
} }
Iterable<Event> _allReactionEvents;
void cancelEmojiPicker() => setState(() => showEmojiPicker = false);
void pickEmojiAction(Iterable<Event> allReactionEvents) async {
_allReactionEvents = allReactionEvents;
setState(() => showEmojiPicker = true);
}
void sendEmojiAction(String emoji) async { void sendEmojiAction(String emoji) async {
await showFutureLoadingDialog( await showFutureLoadingDialog(
context: context, context: context,
@ -522,7 +507,10 @@ class ChatController extends State<Chat> {
setState(() => selectedEvents.clear()); setState(() => selectedEvents.clear());
} }
void clearSelectedEvents() => setState(() => selectedEvents.clear()); void clearSelectedEvents() => setState(() {
selectedEvents.clear();
showEmojiPicker = false;
});
void editSelectedEventAction() { void editSelectedEventAction() {
setState(() { setState(() {

View File

@ -2,6 +2,7 @@ import 'dart:math';
import 'dart:ui'; import 'dart:ui';
import 'package:adaptive_page_layout/adaptive_page_layout.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:famedlysdk/famedlysdk.dart';
import 'package:fluffychat/views/chat.dart'; import 'package:fluffychat/views/chat.dart';
import 'package:fluffychat/views/widgets/avatar.dart'; import 'package:fluffychat/views/widgets/avatar.dart';
@ -415,68 +416,70 @@ class ChatUI extends StatelessWidget {
}, },
), ),
), ),
AnimatedContainer( if (!controller.showEmojiPicker)
duration: Duration(milliseconds: 300), AnimatedContainer(
height: (controller.editEvent == null && duration: Duration(milliseconds: 300),
controller.replyEvent == null && height: (controller.editEvent == null &&
controller.room.canSendDefaultMessages && controller.replyEvent == null &&
controller.selectedEvents.length == 1) controller.room.canSendDefaultMessages &&
? 56 controller.selectedEvents.length == 1)
: 0, ? 56
child: Material( : 0,
color: Theme.of(context).secondaryHeaderColor, child: Material(
child: Builder(builder: (context) { color: Theme.of(context).secondaryHeaderColor,
if (!(controller.editEvent == null && child: Builder(builder: (context) {
controller.replyEvent == null && if (!(controller.editEvent == null &&
controller.selectedEvents.length == 1)) { controller.replyEvent == null &&
return Container(); controller.selectedEvents.length == 1)) {
} return Container();
final emojis = List<String>.from(AppEmojis.emojis); }
final allReactionEvents = controller.selectedEvents.first final emojis = List<String>.from(AppEmojis.emojis);
.aggregatedEvents( final allReactionEvents = controller
controller.timeline, RelationshipTypes.reaction) .selectedEvents.first
?.where((event) => .aggregatedEvents(
event.senderId == event.room.client.userID && controller.timeline, RelationshipTypes.reaction)
event.type == 'm.reaction'); ?.where((event) =>
event.senderId == event.room.client.userID &&
event.type == 'm.reaction');
allReactionEvents.forEach((event) { allReactionEvents.forEach((event) {
try { try {
emojis.remove(event.content['m.relates_to']['key']); emojis.remove(event.content['m.relates_to']['key']);
} catch (_) {} } catch (_) {}
}); });
return ListView.builder( return ListView.builder(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
itemCount: emojis.length + 1, itemCount: emojis.length + 1,
itemBuilder: (c, i) => i == emojis.length itemBuilder: (c, i) => i == emojis.length
? InkWell( ? InkWell(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
onTap: () => controller onTap: () => controller
.pickEmojiAction(allReactionEvents), .pickEmojiAction(allReactionEvents),
child: Container( child: Container(
width: 56, width: 56,
height: 56, height: 56,
alignment: Alignment.center, alignment: Alignment.center,
child: Icon(Icons.add_outlined), child: Icon(Icons.add_outlined),
), ),
) )
: InkWell( : InkWell(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
onTap: () => onTap: () =>
controller.sendEmojiAction(emojis[i]), controller.sendEmojiAction(emojis[i]),
child: Container( child: Container(
width: 56, width: 56,
height: 56, height: 56,
alignment: Alignment.center, alignment: Alignment.center,
child: Text( child: Text(
emojis[i], emojis[i],
style: TextStyle(fontSize: 30), style: TextStyle(fontSize: 30),
),
), ),
), ),
), );
); }),
}), ),
), ),
),
AnimatedContainer( AnimatedContainer(
duration: Duration(milliseconds: 300), duration: Duration(milliseconds: 300),
height: controller.editEvent != null || height: controller.editEvent != null ||
@ -507,204 +510,207 @@ class ChatUI extends StatelessWidget {
height: 1, height: 1,
thickness: 1, thickness: 1,
), ),
controller.room.canSendDefaultMessages && if (controller.room.canSendDefaultMessages &&
controller.room.membership == Membership.join controller.room.membership == Membership.join &&
? Container( !controller.showEmojiPicker)
decoration: BoxDecoration( Container(
color: Theme.of(context).scaffoldBackgroundColor, decoration: BoxDecoration(
), color: Theme.of(context).scaffoldBackgroundColor,
child: Row( ),
crossAxisAlignment: CrossAxisAlignment.end, child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.end,
children: controller.selectMode mainAxisAlignment: MainAxisAlignment.spaceBetween,
? <Widget>[ children: controller.selectMode
Container( ? <Widget>[
height: 56, Container(
child: TextButton( height: 56,
onPressed: controller.forwardEventsAction, child: TextButton(
child: Row( onPressed: controller.forwardEventsAction,
children: <Widget>[ child: Row(
Icon(Icons children: <Widget>[
.keyboard_arrow_left_outlined), Icon(Icons.keyboard_arrow_left_outlined),
Text(L10n.of(context).forward), Text(L10n.of(context).forward),
], ],
),
),
), ),
controller.selectedEvents.length == 1 ),
? controller.selectedEvents.first ),
.getDisplayEvent( controller.selectedEvents.length == 1
controller.timeline) ? controller.selectedEvents.first
.status > .getDisplayEvent(
0 controller.timeline)
? Container( .status >
height: 56, 0
child: TextButton( ? Container(
onPressed: height: 56,
controller.replyAction, child: TextButton(
child: Row( onPressed: controller.replyAction,
children: <Widget>[ child: Row(
Text( children: <Widget>[
L10n.of(context).reply), Text(L10n.of(context).reply),
Icon(Icons Icon(
.keyboard_arrow_right), Icons.keyboard_arrow_right),
], ],
),
),
)
: Container(
height: 56,
child: TextButton(
onPressed:
controller.sendAgainAction,
child: Row(
children: <Widget>[
Text(L10n.of(context)
.tryToSendAgain),
SizedBox(width: 4),
Icon(Icons.send_outlined,
size: 16),
],
),
),
)
: Container(),
]
: <Widget>[
AnimatedContainer(
duration: Duration(milliseconds: 200),
height: 56,
width:
controller.inputText.isEmpty ? 56 : 0,
alignment: Alignment.center,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(),
child: PopupMenuButton<String>(
icon: Icon(Icons.add_outlined),
onSelected: controller
.onAddPopupMenuButtonSelected,
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<String>>[
PopupMenuItem<String>(
value: 'file',
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
child: Icon(
Icons.attachment_outlined),
), ),
title:
Text(L10n.of(context).sendFile),
contentPadding: EdgeInsets.all(0),
), ),
)
: Container(
height: 56,
child: TextButton(
onPressed:
controller.sendAgainAction,
child: Row(
children: <Widget>[
Text(L10n.of(context)
.tryToSendAgain),
SizedBox(width: 4),
Icon(Icons.send_outlined,
size: 16),
],
),
),
)
: Container(),
]
: <Widget>[
AnimatedContainer(
duration: Duration(milliseconds: 200),
height: 56,
width: controller.inputText.isEmpty ? 56 : 0,
alignment: Alignment.center,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(),
child: PopupMenuButton<String>(
icon: Icon(Icons.add_outlined),
onSelected:
controller.onAddPopupMenuButtonSelected,
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<String>>[
PopupMenuItem<String>(
value: 'file',
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
child:
Icon(Icons.attachment_outlined),
), ),
PopupMenuItem<String>( title: Text(L10n.of(context).sendFile),
value: 'image', contentPadding: EdgeInsets.all(0),
child: ListTile( ),
leading: CircleAvatar( ),
backgroundColor: Colors.blue, PopupMenuItem<String>(
foregroundColor: Colors.white, value: 'image',
child: Icon(Icons.image_outlined), child: ListTile(
), leading: CircleAvatar(
title: Text( backgroundColor: Colors.blue,
L10n.of(context).sendImage), foregroundColor: Colors.white,
contentPadding: EdgeInsets.all(0), child: Icon(Icons.image_outlined),
),
), ),
if (PlatformInfos.isMobile) title: Text(L10n.of(context).sendImage),
PopupMenuItem<String>( contentPadding: EdgeInsets.all(0),
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<String>(
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),
),
),
],
), ),
), if (PlatformInfos.isMobile)
Container( PopupMenuItem<String>(
height: 56, value: 'camera',
alignment: Alignment.center, child: ListTile(
child: EncryptionButton(controller.room), leading: CircleAvatar(
), backgroundColor: Colors.purple,
Expanded( foregroundColor: Colors.white,
child: Padding( child:
padding: const EdgeInsets.symmetric( Icon(Icons.camera_alt_outlined),
vertical: 4.0), ),
child: InputBar( title:
room: controller.room, Text(L10n.of(context).openCamera),
minLines: 1, contentPadding: EdgeInsets.all(0),
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)
PopupMenuItem<String>(
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( if (PlatformInfos.isMobile &&
height: 56, controller.inputText.isEmpty)
alignment: Alignment.center, Container(
child: IconButton( height: 56,
tooltip: L10n.of(context).voiceMessage, alignment: Alignment.center,
icon: Icon(Icons.mic_none_outlined), child: IconButton(
onPressed: tooltip: L10n.of(context).voiceMessage,
controller.voiceMessageAction, icon: Icon(Icons.mic_none_outlined),
), onPressed: controller.voiceMessageAction,
), ),
if (!PlatformInfos.isMobile || ),
controller.inputText.isNotEmpty) if (!PlatformInfos.isMobile ||
Container( controller.inputText.isNotEmpty)
height: 56, Container(
alignment: Alignment.center, height: 56,
child: IconButton( alignment: Alignment.center,
icon: Icon(Icons.send_outlined), child: IconButton(
onPressed: controller.send, icon: Icon(Icons.send_outlined),
tooltip: L10n.of(context).send, onPressed: controller.send,
), tooltip: L10n.of(context).send,
), ),
], ),
), ],
) ),
: Container(), ),
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,
),
], ],
), ),
), ),

View File

@ -217,7 +217,7 @@ packages:
name: emoji_picker_flutter name: emoji_picker_flutter
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.3" version: "1.0.5"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:

View File

@ -16,7 +16,7 @@ dependencies:
cupertino_icons: any cupertino_icons: any
desktop_notifications: ^0.4.0 desktop_notifications: ^0.4.0
email_validator: ^2.0.1 email_validator: ^2.0.1
emoji_picker_flutter: ^1.0.3 emoji_picker_flutter: ^1.0.5
famedlysdk: famedlysdk:
git: git:
url: https://gitlab.com/famedly/famedlysdk.git url: https://gitlab.com/famedly/famedlysdk.git