mirror of
https://gitlab.com/famedly/fluffychat.git
synced 2024-11-16 17:09:31 +01:00
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:
parent
0eba2ae859
commit
30ce5c7f57
@ -2711,7 +2711,6 @@
|
|||||||
"markAsRead": "Mark as read",
|
"markAsRead": "Mark as read",
|
||||||
"reportUser": "Report user",
|
"reportUser": "Report user",
|
||||||
"dismiss": "Dismiss",
|
"dismiss": "Dismiss",
|
||||||
"markAsRead": "Mark as read",
|
|
||||||
"matrixWidgets": "Matrix Widgets",
|
"matrixWidgets": "Matrix Widgets",
|
||||||
"integrationsNotImplemented": "Editing widgets and integrations is not possible yet.",
|
"integrationsNotImplemented": "Editing widgets and integrations is not possible yet.",
|
||||||
"editIntegrations": "Edit widgets and integrations",
|
"editIntegrations": "Edit widgets and integrations",
|
||||||
@ -2725,5 +2724,6 @@
|
|||||||
},
|
},
|
||||||
"pinMessage": "Pin to room",
|
"pinMessage": "Pin to room",
|
||||||
"pinnedEventsError": "Error loading pinned messages",
|
"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"
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,7 @@ To run code after the widget was created first we use the WidgetBindings in the
|
|||||||
```dart
|
```dart
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance!.addPostFrameCallback((_) {
|
||||||
// Do something when build is finished
|
// Do something when build is finished
|
||||||
});
|
});
|
||||||
super.initState();
|
super.initState();
|
||||||
|
@ -8,6 +8,7 @@ import 'package:flutter/services.dart';
|
|||||||
|
|
||||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||||
import 'package:desktop_drop/desktop_drop.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:file_picker_cross/file_picker_cross.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||||
@ -125,6 +126,8 @@ class ChatController extends State<Chat> {
|
|||||||
|
|
||||||
bool showEmojiPicker = false;
|
bool showEmojiPicker = false;
|
||||||
|
|
||||||
|
EmojiPickerType emojiPickerType = EmojiPickerType.keyboard;
|
||||||
|
|
||||||
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())}';
|
||||||
@ -178,6 +181,8 @@ class ChatController extends State<Chat> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
scrollController.addListener(_updateScrollController);
|
scrollController.addListener(_updateScrollController);
|
||||||
|
|
||||||
|
inputFocus.addListener(_inputFocusListener);
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,6 +244,7 @@ class ChatController extends State<Chat> {
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
timeline?.cancelSubscriptions();
|
timeline?.cancelSubscriptions();
|
||||||
timeline = null;
|
timeline = null;
|
||||||
|
inputFocus.removeListener(_inputFocusListener);
|
||||||
super.dispose();
|
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 {
|
void sendLocationAction() async {
|
||||||
await showDialog(
|
await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@ -668,7 +688,18 @@ class ChatController extends State<Chat> {
|
|||||||
|
|
||||||
void scrollDown() => scrollController.jumpTo(0);
|
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);
|
setState(() => showEmojiPicker = false);
|
||||||
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
|
||||||
@ -677,12 +708,41 @@ class ChatController extends State<Chat> {
|
|||||||
return sendEmojiAction(emoji.emoji);
|
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;
|
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;
|
_allReactionEvents = allReactionEvents;
|
||||||
|
emojiPickerType = EmojiPickerType.reaction;
|
||||||
setState(() => showEmojiPicker = true);
|
setState(() => showEmojiPicker = true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -902,3 +962,5 @@ class ChatController extends State<Chat> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => ChatView(this);
|
Widget build(BuildContext context) => ChatView(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum EmojiPickerType { reaction, keyboard }
|
||||||
|
@ -18,7 +18,7 @@ class ChatEmojiPicker extends StatelessWidget {
|
|||||||
child: controller.showEmojiPicker
|
child: controller.showEmojiPicker
|
||||||
? EmojiPicker(
|
? EmojiPicker(
|
||||||
onEmojiSelected: controller.onEmojiSelected,
|
onEmojiSelected: controller.onEmojiSelected,
|
||||||
onBackspacePressed: controller.cancelEmojiPicker,
|
onBackspacePressed: controller.emojiPickerBackspace,
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:animations/animations.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
import 'package:matrix/matrix.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/avatar.dart';
|
||||||
import 'package:fluffychat/widgets/matrix.dart';
|
import 'package:fluffychat/widgets/matrix.dart';
|
||||||
import 'chat.dart';
|
import 'chat.dart';
|
||||||
import 'encryption_button.dart';
|
|
||||||
import 'input_bar.dart';
|
import 'input_bar.dart';
|
||||||
|
|
||||||
class ChatInputRow extends StatelessWidget {
|
class ChatInputRow extends StatelessWidget {
|
||||||
final ChatController controller;
|
final ChatController controller;
|
||||||
|
|
||||||
const ChatInputRow(this.controller, {Key? key}) : super(key: key);
|
const ChatInputRow(this.controller, {Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (controller.showEmojiPicker) return Container();
|
if (controller.showEmojiPicker &&
|
||||||
|
controller.emojiPickerType == EmojiPickerType.reaction) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
return Row(
|
return Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
@ -176,7 +180,31 @@ class ChatInputRow extends StatelessWidget {
|
|||||||
Container(
|
Container(
|
||||||
height: 56,
|
height: 56,
|
||||||
alignment: Alignment.center,
|
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 &&
|
if (controller.matrix!.isMultiAccount &&
|
||||||
controller.matrix!.hasComplexBundles &&
|
controller.matrix!.hasComplexBundles &&
|
||||||
|
@ -12,6 +12,7 @@ import 'package:fluffychat/config/app_config.dart';
|
|||||||
import 'package:fluffychat/config/themes.dart';
|
import 'package:fluffychat/config/themes.dart';
|
||||||
import 'package:fluffychat/pages/chat/chat.dart';
|
import 'package:fluffychat/pages/chat/chat.dart';
|
||||||
import 'package:fluffychat/pages/chat/chat_app_bar_title.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/pinned_events.dart';
|
||||||
import 'package:fluffychat/pages/chat/reactions_picker.dart';
|
import 'package:fluffychat/pages/chat/reactions_picker.dart';
|
||||||
import 'package:fluffychat/pages/chat/reply_display.dart';
|
import 'package:fluffychat/pages/chat/reply_display.dart';
|
||||||
@ -119,6 +120,7 @@ class ChatView extends StatelessWidget {
|
|||||||
icon: const Icon(Icons.widgets),
|
icon: const Icon(Icons.widgets),
|
||||||
tooltip: L10n.of(context)!.matrixWidgets,
|
tooltip: L10n.of(context)!.matrixWidgets,
|
||||||
),
|
),
|
||||||
|
EncryptionButton(controller.room!),
|
||||||
ChatSettingsPopupMenu(controller.room!, !controller.room!.isDirectChat),
|
ChatSettingsPopupMenu(controller.room!, !controller.room!.isDirectChat),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,8 @@ class ReactionsPicker extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
child: const Icon(Icons.add_outlined),
|
child: const Icon(Icons.add_outlined),
|
||||||
),
|
),
|
||||||
onTap: () => controller.pickEmojiAction(allReactionEvents))
|
onTap: () =>
|
||||||
|
controller.pickEmojiReactionAction(allReactionEvents))
|
||||||
]);
|
]);
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
@ -94,6 +94,11 @@ class FlutterFluffyBoxDatabase extends FluffyBoxDatabase {
|
|||||||
directory = Directory.current;
|
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;
|
path = directory.path;
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
|
@ -9,6 +9,9 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
)
|
)
|
||||||
|
|
||||||
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
)
|
||||||
|
|
||||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||||
|
|
||||||
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
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 $<TARGET_FILE:${plugin}_plugin>)
|
||||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
||||||
endforeach(plugin)
|
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)
|
||||||
|
Loading…
Reference in New Issue
Block a user