feat: implement an emoji keyboard

- add button to show emoji keyboard
- change database directory for debug builds

Signed-off-by: TheOneWithTheBraid <the-one@with-the-braid.cf>
This commit is contained in:
TheOneWithTheBraid 2022-02-14 13:49:46 +01:00
parent 0eba2ae859
commit 30ce5c7f57
9 changed files with 117 additions and 11 deletions

View File

@ -2711,7 +2711,6 @@
"markAsRead": "Mark as read",
"reportUser": "Report user",
"dismiss": "Dismiss",
"markAsRead": "Mark as read",
"matrixWidgets": "Matrix Widgets",
"integrationsNotImplemented": "Editing widgets and integrations is not possible yet.",
"editIntegrations": "Edit widgets and integrations",
@ -2725,5 +2724,6 @@
},
"pinMessage": "Pin to room",
"pinnedEventsError": "Error loading pinned messages",
"confirmEventUnpin": "Are you sure to permanently unpin the event?"
"confirmEventUnpin": "Are you sure to permanently unpin the event?",
"emojis": "Emojis"
}

View File

@ -122,7 +122,7 @@ To run code after the widget was created first we use the WidgetBindings in the
```dart
@override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) {
WidgetsBinding.instance!.addPostFrameCallback((_) {
// Do something when build is finished
});
super.initState();

View File

@ -8,6 +8,7 @@ import 'package:flutter/services.dart';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:desktop_drop/desktop_drop.dart';
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
import 'package:file_picker_cross/file_picker_cross.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
@ -125,6 +126,8 @@ class ChatController extends State<Chat> {
bool showEmojiPicker = false;
EmojiPickerType emojiPickerType = EmojiPickerType.keyboard;
void startCallAction() async {
final url =
'${AppConfig.jitsiInstance}${Uri.encodeComponent(Matrix.of(context).client.generateUniqueTransactionId())}';
@ -178,6 +181,8 @@ class ChatController extends State<Chat> {
@override
void initState() {
scrollController.addListener(_updateScrollController);
inputFocus.addListener(_inputFocusListener);
super.initState();
}
@ -239,6 +244,7 @@ class ChatController extends State<Chat> {
void dispose() {
timeline?.cancelSubscriptions();
timeline = null;
inputFocus.removeListener(_inputFocusListener);
super.dispose();
}
@ -426,6 +432,20 @@ class ChatController extends State<Chat> {
});
}
void emojiPickerAction() {
emojiPickerType = EmojiPickerType.keyboard;
setState(() => showEmojiPicker = !showEmojiPicker);
_inputFocusListener();
}
void _inputFocusListener() {
if (showEmojiPicker) {
inputFocus.unfocus();
} else {
inputFocus.requestFocus();
}
}
void sendLocationAction() async {
await showDialog(
context: context,
@ -668,7 +688,18 @@ class ChatController extends State<Chat> {
void scrollDown() => scrollController.jumpTo(0);
void onEmojiSelected(_, emoji) {
void onEmojiSelected(_, Emoji? emoji) {
switch (emojiPickerType) {
case EmojiPickerType.reaction:
senEmojiReaction(emoji);
break;
case EmojiPickerType.keyboard:
typeEmoji(emoji);
break;
}
}
void senEmojiReaction(Emoji? emoji) {
setState(() => showEmojiPicker = false);
if (emoji == null) return;
// make sure we don't send the same emoji twice
@ -677,12 +708,41 @@ class ChatController extends State<Chat> {
return sendEmojiAction(emoji.emoji);
}
void typeEmoji(Emoji? emoji) {
if (emoji == null) return;
final text = sendController.text;
final selection = sendController.selection;
final newText = sendController.text.isEmpty
? emoji.emoji
: text.replaceRange(selection.start, selection.end, emoji.emoji);
sendController.value = TextEditingValue(
text: newText,
selection: TextSelection.collapsed(
// don't forget an UTF-8 combined emoji might have a length > 1
offset: selection.baseOffset + emoji.emoji.length,
),
);
}
late Iterable<Event> _allReactionEvents;
void cancelEmojiPicker() => setState(() => showEmojiPicker = false);
void emojiPickerBackspace() {
switch (emojiPickerType) {
case EmojiPickerType.reaction:
setState(() => showEmojiPicker = false);
break;
case EmojiPickerType.keyboard:
sendController
..text = sendController.text.characters.skipLast(1).toString()
..selection = TextSelection.fromPosition(
TextPosition(offset: sendController.text.length));
break;
}
}
void pickEmojiAction(Iterable<Event> allReactionEvents) async {
void pickEmojiReactionAction(Iterable<Event> allReactionEvents) async {
_allReactionEvents = allReactionEvents;
emojiPickerType = EmojiPickerType.reaction;
setState(() => showEmojiPicker = true);
}
@ -902,3 +962,5 @@ class ChatController extends State<Chat> {
@override
Widget build(BuildContext context) => ChatView(this);
}
enum EmojiPickerType { reaction, keyboard }

View File

@ -18,7 +18,7 @@ class ChatEmojiPicker extends StatelessWidget {
child: controller.showEmojiPicker
? EmojiPicker(
onEmojiSelected: controller.onEmojiSelected,
onBackspacePressed: controller.cancelEmojiPicker,
onBackspacePressed: controller.emojiPickerBackspace,
)
: null,
);

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:animations/animations.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';
@ -8,16 +9,19 @@ import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'chat.dart';
import 'encryption_button.dart';
import 'input_bar.dart';
class ChatInputRow extends StatelessWidget {
final ChatController controller;
const ChatInputRow(this.controller, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
if (controller.showEmojiPicker) return Container();
if (controller.showEmojiPicker &&
controller.emojiPickerType == EmojiPickerType.reaction) {
return Container();
}
return Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -176,7 +180,31 @@ class ChatInputRow extends StatelessWidget {
Container(
height: 56,
alignment: Alignment.center,
child: EncryptionButton(controller.room!),
child: IconButton(
tooltip: L10n.of(context)!.emojis,
icon: PageTransitionSwitcher(
transitionBuilder: (
Widget child,
Animation<double> primaryAnimation,
Animation<double> secondaryAnimation,
) {
return SharedAxisTransition(
animation: primaryAnimation,
secondaryAnimation: secondaryAnimation,
transitionType: SharedAxisTransitionType.scaled,
child: child,
fillColor: Colors.transparent,
);
},
child: Icon(
controller.showEmojiPicker
? Icons.keyboard
: Icons.emoji_emotions_outlined,
key: ValueKey(controller.showEmojiPicker),
),
),
onPressed: controller.emojiPickerAction,
),
),
if (controller.matrix!.isMultiAccount &&
controller.matrix!.hasComplexBundles &&

View File

@ -12,6 +12,7 @@ import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pages/chat/chat_app_bar_title.dart';
import 'package:fluffychat/pages/chat/encryption_button.dart';
import 'package:fluffychat/pages/chat/pinned_events.dart';
import 'package:fluffychat/pages/chat/reactions_picker.dart';
import 'package:fluffychat/pages/chat/reply_display.dart';
@ -119,6 +120,7 @@ class ChatView extends StatelessWidget {
icon: const Icon(Icons.widgets),
tooltip: L10n.of(context)!.matrixWidgets,
),
EncryptionButton(controller.room!),
ChatSettingsPopupMenu(controller.room!, !controller.room!.isDirectChat),
];
}

View File

@ -77,7 +77,8 @@ class ReactionsPicker extends StatelessWidget {
),
child: const Icon(Icons.add_outlined),
),
onTap: () => controller.pickEmojiAction(allReactionEvents))
onTap: () =>
controller.pickEmojiReactionAction(allReactionEvents))
]);
}),
),

View File

@ -94,6 +94,11 @@ class FlutterFluffyBoxDatabase extends FluffyBoxDatabase {
directory = Directory.current;
}
}
// do not destroy your stable FluffyChat in debug mode
if (kDebugMode) {
directory = Directory(directory.uri.resolve('debug').toFilePath());
directory.create(recursive: true);
}
path = directory.path;
}
return path;

View File

@ -9,6 +9,9 @@ list(APPEND FLUTTER_PLUGIN_LIST
url_launcher_windows
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
@ -17,3 +20,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST})
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)