Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bruig: paste image to embed on chat #649

Merged
merged 3 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions bruig/flutterui/bruig/lib/components/attach_file.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ class AttachmentEmbed {

class AttachFileScreen extends StatefulWidget {
final SendMsg _send;
const AttachFileScreen(this._send, {super.key});
final Uint8List? initialFileData;
final String? initialMime;
const AttachFileScreen(this._send, this.initialFileData, this.initialMime,
{super.key});

@override
State<AttachFileScreen> createState() => _AttachFileScreenState();
Expand All @@ -106,6 +109,8 @@ class _AttachFileScreenState extends State<AttachFileScreen> {
super.initState();
_listenForPermissionStatus();
_futureGetPath = _getPath();
fileData = widget.initialFileData;
mime = widget.initialMime ?? "";
}

@override
Expand Down Expand Up @@ -282,14 +287,14 @@ class _AttachFileScreenState extends State<AttachFileScreen> {

@override
Widget build(BuildContext context) {
return selectedAttachmentPath != null
return fileData != null || selectedAttachmentPath != null
? Column(children: [
mime.startsWith('image/')
? Container(
margin: const EdgeInsets.symmetric(vertical: 8.0),
height: 200.0,
child: Image.file(File(selectedAttachmentPath!),
errorBuilder: (BuildContext context, Object error,
child: Image.memory(fileData!, errorBuilder:
(BuildContext context, Object error,
StackTrace? stackTrace) {
return const Center(
child: Text('This image type is not supported'));
Expand Down
97 changes: 95 additions & 2 deletions bruig/flutterui/bruig/lib/components/chat/input.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import 'dart:math';

import 'package:bruig/components/attach_file.dart';
import 'package:bruig/models/uistate.dart';
import 'package:bruig/screens/chats.dart';
import 'package:flutter/material.dart';
import 'package:bruig/components/chat/types.dart';
import 'package:bruig/models/client.dart';
import 'package:bruig/theme_manager.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:super_clipboard/super_clipboard.dart';

class ChatInput extends StatefulWidget {
final SendMsg _send;
Expand All @@ -21,15 +25,74 @@ class ChatInput extends StatefulWidget {
class _ChatInputState extends State<ChatInput> {
final controller = TextEditingController();

final FocusNode node = FocusNode();
List<AttachmentEmbed> embeds = [];
bool isAttaching = false;
Uint8List? initialAttachData;
String? initialAttachMime;

void replaceTextSelection(String s) {
var sel = controller.selection.copyWith();
if (controller.selection.start == -1 && controller.selection.end == -1) {
controller.text = controller.text + s;
} else if (sel.isCollapsed) {
controller.text = controller.text.substring(0, sel.start) +
s +
controller.text.substring(min(controller.text.length, sel.start));
var newPos = sel.baseOffset + s.length;
controller.selection =
sel.copyWith(baseOffset: newPos, extentOffset: newPos);
} else {
controller.text =
controller.text.substring(0, controller.selection.start) +
s +
controller.text.substring(controller.selection.end);
var newPos = sel.baseOffset + s.length;
controller.selection =
sel.copyWith(baseOffset: newPos, extentOffset: newPos);
}
}

Future<void> pasteEvent() async {
final clip = SystemClipboard.instance;
if (clip == null) {
// Clipboard API is not supported on this platform. Use the standard.
replaceTextSelection(Clipboard.kTextPlain);
return;
}
final reader = await clip.read();

/// Binary formats need to be read as streams
if (reader.canProvide(Formats.png)) {
reader.getFile(Formats.png, (file) async {
final stream = await file.readAll();
setState(() {
initialAttachData = stream;
initialAttachMime = "image/png";
isAttaching = true;
});
});
return;
}

// Automatically convert to markdown?
// if (reader.canProvide(Formats.htmlText)) {
// final html = await reader.readValue(Formats.htmlText);
// print("XXXX clip is html $html");
// }

if (reader.canProvide(Formats.plainText)) {
final text = await reader.readValue(Formats.plainText);
replaceTextSelection(text ?? "");
return;
}
}

@override
void initState() {
super.initState();
controller.text = widget.chat.workingMsg;
widget.inputFocusNode.noModEnterKeyHandler = sendMsg;
widget.inputFocusNode.pasteEventHandler = pasteEvent;
}

@override
Expand All @@ -43,14 +106,26 @@ class _ChatInputState extends State<ChatInput> {
baseOffset: workingMsg.length, extentOffset: workingMsg.length);
widget.inputFocusNode.inputFocusNode.requestFocus();
}
oldWidget.inputFocusNode.pasteEventHandler = null;
widget.inputFocusNode.pasteEventHandler = pasteEvent;
}

@override
void dispose() {
widget.inputFocusNode.noModEnterKeyHandler = null;
widget.inputFocusNode.pasteEventHandler = null;
super.dispose();
}

void sendAttachment(String msg) {
setState(() {
isAttaching = false;
initialAttachData = null;
initialAttachMime = null;
});
widget._send(msg);
}

void sendMsg() {
final messageWithoutNewLine = controller.text.trim();
controller.value = const TextEditingValue(
Expand Down Expand Up @@ -82,6 +157,9 @@ class _ChatInputState extends State<ChatInput> {
void cancelAttach() {
setState(() {
isAttaching = false;
initialAttachData = null;
initialAttachMime = null;
widget.inputFocusNode.inputFocusNode.requestFocus();
});
}

Expand All @@ -98,7 +176,8 @@ class _ChatInputState extends State<ChatInput> {
onPressed: cancelAttach,
icon: const Icon(Icons.keyboard_arrow_left_outlined))
]),
AttachFileScreen(widget._send)
AttachFileScreen(
sendAttachment, initialAttachData, initialAttachMime)
])
: Row(children: [
IconButton(
Expand All @@ -117,6 +196,20 @@ class _ChatInputState extends State<ChatInput> {
controller: controller,
minLines: 1,
maxLines: null,
contextMenuBuilder: (BuildContext context,
EditableTextState editableTextState) =>
AdaptiveTextSelectionToolbar.editable(
anchors: editableTextState.contextMenuAnchors,
clipboardStatus: ClipboardStatus.pasteable,
onCopy: null,
onCut: null,
onLiveTextInput: null,
onLookUp: null,
onSearchWeb: null,
onSelectAll: null,
onShare: null,
onPaste: pasteEvent,
),
style: theme.textStyleFor(context, TextSize.medium, null),
keyboardType: TextInputType.multiline,
decoration: InputDecoration(
Expand Down
38 changes: 30 additions & 8 deletions bruig/flutterui/bruig/lib/screens/chats.dart
Original file line number Diff line number Diff line change
Expand Up @@ -199,36 +199,58 @@ class CustomInputFocusNode {
bool shiftRight = false;
bool altLeft = false;
bool altRight = false;
bool metaLeft = false;
bool metaRight = false;
bool get anyMod =>
ctrlLeft || altLeft || shiftLeft || ctrlRight || altRight || shiftRight;
ctrlLeft ||
altLeft ||
shiftLeft ||
ctrlRight ||
altRight ||
shiftRight ||
metaLeft ||
metaRight;

late final FocusNode inputFocusNode;

Function? noModEnterKeyHandler;

// If set, this is called when a paste event ("ctrl+v") is detected.
Function? pasteEventHandler;

CustomInputFocusNode() {
inputFocusNode = FocusNode(onKeyEvent: (node, event) {
if (event.logicalKey.keyId == LogicalKeyboardKey.controlLeft.keyId) {
ctrlLeft = !ctrlLeft;
ctrlLeft = event is KeyDownEvent;
} else if (event.logicalKey.keyId ==
LogicalKeyboardKey.controlRight.keyId) {
ctrlRight = !ctrlRight;
ctrlRight = event is KeyDownEvent;
} else if (event.logicalKey.keyId == LogicalKeyboardKey.altLeft.keyId) {
altLeft = !altLeft;
altLeft = event is KeyDownEvent;
} else if (event.logicalKey.keyId == LogicalKeyboardKey.altRight.keyId) {
altRight = !altRight;
altRight = event is KeyDownEvent;
} else if (event.logicalKey.keyId == LogicalKeyboardKey.shiftLeft.keyId) {
shiftLeft = !shiftLeft;
shiftLeft = event is KeyDownEvent;
} else if (event.logicalKey.keyId == LogicalKeyboardKey.metaLeft.keyId) {
metaLeft = event is KeyDownEvent;
} else if (event.logicalKey.keyId == LogicalKeyboardKey.metaRight.keyId) {
metaRight = event is KeyDownEvent;
} else if (event.logicalKey.keyId ==
LogicalKeyboardKey.shiftRight.keyId) {
shiftRight = !shiftRight;
shiftRight = event is KeyDownEvent;
} else if (event.logicalKey.keyId == LogicalKeyboardKey.enter.keyId) {
// When a special handler is set, call it to bypass standard processing
// of the key and return the 'handled' result.
if (noModEnterKeyHandler != null && !anyMod) {
noModEnterKeyHandler!();
return KeyEventResult.handled;
}
} else if (event.logicalKey.keyId == LogicalKeyboardKey.keyV.keyId &&
event is KeyDownEvent) {
if (pasteEventHandler != null) {
pasteEventHandler!();
return KeyEventResult.handled;
}
}

return KeyEventResult.ignored;
Expand All @@ -239,7 +261,7 @@ class CustomInputFocusNode {
class _ChatsScreenState extends State<ChatsScreen> {
ClientModel get client => widget.client;
AppNotifications get ntfns => widget.ntfns;
CustomInputFocusNode inputFocusNode = CustomInputFocusNode();
final CustomInputFocusNode inputFocusNode = CustomInputFocusNode();
bool hasLNBalance = false;
List<PostListItem> userPostList = [];
Timer? checkLNTimer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@

#include <file_selector_linux/file_selector_plugin.h>
#include <golib_plugin/golib_plugin.h>
#include <irondash_engine_context/irondash_engine_context_plugin.h>
#include <screen_retriever/screen_retriever_plugin.h>
#include <super_native_extensions/super_native_extensions_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
#include <window_manager/window_manager_plugin.h>

Expand All @@ -19,9 +21,15 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) golib_plugin_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "GolibPlugin");
golib_plugin_register_with_registrar(golib_plugin_registrar);
g_autoptr(FlPluginRegistrar) irondash_engine_context_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "IrondashEngineContextPlugin");
irondash_engine_context_plugin_register_with_registrar(irondash_engine_context_registrar);
g_autoptr(FlPluginRegistrar) screen_retriever_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin");
screen_retriever_plugin_register_with_registrar(screen_retriever_registrar);
g_autoptr(FlPluginRegistrar) super_native_extensions_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "SuperNativeExtensionsPlugin");
super_native_extensions_plugin_register_with_registrar(super_native_extensions_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
Expand Down
2 changes: 2 additions & 0 deletions bruig/flutterui/bruig/linux/flutter/generated_plugins.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
list(APPEND FLUTTER_PLUGIN_LIST
file_selector_linux
golib_plugin
irondash_engine_context
screen_retriever
super_native_extensions
url_launcher_linux
window_manager
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,34 @@
import FlutterMacOS
import Foundation

import device_info_plus
import file_selector_macos
import flutter_local_notifications
import golib_plugin
import irondash_engine_context
import mobile_scanner
import package_info_plus
import path_provider_foundation
import screen_retriever
import share_plus
import shared_preferences_foundation
import super_native_extensions
import url_launcher_macos
import window_manager

func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
GolibPlugin.register(with: registry.registrar(forPlugin: "GolibPlugin"))
IrondashEngineContextPlugin.register(with: registry.registrar(forPlugin: "IrondashEngineContextPlugin"))
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SuperNativeExtensionsPlugin.register(with: registry.registrar(forPlugin: "SuperNativeExtensionsPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin"))
}
Loading
Loading