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

TF-3308 Drag and drop text composer #3321

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
76 changes: 74 additions & 2 deletions contact/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -252,10 +252,10 @@ packages:
dependency: transitive
description:
name: device_info_plus
sha256: "1d6e5a61674ba3a68fb048a7c7b4ff4bebfed8d7379dbe8f2b718231be9a7c95"
sha256: "2c35b6d1682b028e42d07b3aee4b98fa62996c10bc12cb651ec856a80d6a761b"
url: "https://pub.dev"
source: hosted
version: "8.1.0"
version: "9.0.2"
device_info_plus_platform_interface:
dependency: transitive
description:
Expand Down Expand Up @@ -655,6 +655,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.4"
irondash_engine_context:
dependency: transitive
description:
name: irondash_engine_context
sha256: "0e803321935ca7af1a88f1391be9edfdb940df800353670bfc694934c7643ff3"
url: "https://pub.dev"
source: hosted
version: "0.4.1"
irondash_message_channel:
dependency: transitive
description:
name: irondash_message_channel
sha256: "500daa1fbe679f7d28a5258df3ff47dab6de352e680dc93c1ca9eae1555d8db5"
url: "https://pub.dev"
source: hosted
version: "0.3.1"
jmap_dart_client:
dependency: "direct main"
description:
Expand Down Expand Up @@ -904,6 +920,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.0.1"
pixel_snap:
dependency: transitive
description:
name: pixel_snap
sha256: "677410ea37b07cd37ecb6d5e6c0d8d7615a7cf3bd92ba406fd1ac57e937d1fb0"
url: "https://pub.dev"
source: hosted
version: "0.1.5"
platform:
dependency: transitive
description:
Expand Down Expand Up @@ -1062,6 +1086,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.10.0"
sprintf:
dependency: transitive
description:
name: sprintf
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
stack_trace:
dependency: transitive
description:
Expand Down Expand Up @@ -1094,6 +1126,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.0"
super_clipboard:
dependency: transitive
description:
name: super_clipboard
sha256: "1031873af66c796177eff073d1453c9f19a75e6f9b308ca0b950928d0a20aedd"
url: "https://pub.dev"
source: hosted
version: "0.7.3"
super_drag_and_drop:
dependency: transitive
description:
name: super_drag_and_drop
sha256: cb884e24f75127ddfba7908808ac265249075511c72584148345b259ca99788c
url: "https://pub.dev"
source: hosted
version: "0.7.0"
super_native_extensions:
dependency: transitive
description:
name: super_native_extensions
sha256: fd658e096e99ed7555c0727a8201dbe3b8afc0059742ac14bdcff115e08f589c
url: "https://pub.dev"
source: hosted
version: "0.7.3"
term_glyph:
dependency: transitive
description:
Expand Down Expand Up @@ -1222,6 +1278,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.0"
uuid:
dependency: transitive
description:
name: uuid
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
url: "https://pub.dev"
source: hosted
version: "4.5.1"
vector_graphics:
dependency: transitive
description:
Expand Down Expand Up @@ -1294,6 +1358,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.1.4"
win32_registry:
dependency: transitive
description:
name: win32_registry
sha256: "1c52f994bdccb77103a6231ad4ea331a244dbcef5d1f37d8462f713143b0bfae"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
xdg_directories:
dependency: transitive
description:
Expand Down
109 changes: 109 additions & 0 deletions core/lib/presentation/views/text/text_drop_zone_web.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import 'dart:async';

import 'package:collection/collection.dart';
import 'package:core/presentation/extensions/list_nullable_extensions.dart';
import 'package:core/utils/app_logger.dart';
import 'package:flutter/material.dart';
import 'package:super_drag_and_drop/super_drag_and_drop.dart';
import 'package:universal_html/html.dart' hide VoidCallback;

typedef OnSuperTextDrop = void Function(String value);

class TextDropZoneWeb extends StatefulWidget {
const TextDropZoneWeb({
super.key,
required this.child,
this.onHover,
this.onLeave,
this.onDrop,
});

final Widget child;
final VoidCallback? onHover;
final VoidCallback? onLeave;
final OnSuperTextDrop? onDrop;

@override
State<TextDropZoneWeb> createState() => _TextDropZoneWebState();
}

class _TextDropZoneWebState extends State<TextDropZoneWeb> {
bool _textIsDragging = false;
StreamSubscription? _dragEnterSubscription;
StreamSubscription? _dragOverSubscription;
StreamSubscription? _dropSubscription;
StreamSubscription? _dragLeaveSubscription;

@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_dragEnterSubscription = window.onDragEnter.listen((event) {
setState(() {
_textIsDragging = event.dataTransfer.types?.validateFilesTransfer != true;
});
});
_dragOverSubscription = window.onDragOver.listen((event) {
setState(() {
_textIsDragging = event.dataTransfer.types?.validateFilesTransfer != true;
});
});
_dropSubscription = window.onDrop.listen((event) {
setState(() {
_textIsDragging = false;
});
});
_dragLeaveSubscription = window.onDragLeave.listen((event) {
setState(() {
_textIsDragging = false;
});
});
});
}

@override
void dispose() {
_dragEnterSubscription?.cancel();
_dragOverSubscription?.cancel();
_dropSubscription?.cancel();
_dragLeaveSubscription?.cancel();
super.dispose();
}

@override
Widget build(BuildContext context) {
if (!_textIsDragging) return widget.child;

return DropRegion(
formats: const [Formats.plainText],
onDropOver: (dropOverEvent) {
final dragItem = dropOverEvent.session.items.firstOrNull;
if (dragItem == null || !dragItem.canProvide(Formats.plainText)) {
return DropOperation.none;
}

widget.onHover?.call();

return DropOperation.copy;
},
onDropLeave: (_) => widget.onLeave?.call(),
onPerformDrop: (performDropEvent) async {
final item = performDropEvent.session.items.firstOrNull;
if (item == null) return;

item.dataReader?.getValue<String>(
Formats.plainText,
(value) {
log('TextDropZoneWeb::onPerformDrop:value = $value');
if (value == null) return;
widget.onDrop?.call(value);
},
onError: (error) {
logError('TextDropZoneWeb::onPerformDrop:error: $error');
}
);
},
child: widget.child,
);
}
}
94 changes: 51 additions & 43 deletions core/lib/presentation/views/text/text_field_builder.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/views/text/text_drop_zone_web.dart';
import 'package:core/utils/direction_utils.dart';
import 'package:flutter/material.dart';
import 'package:languagetool_textfield/languagetool_textfield.dart';
Expand Down Expand Up @@ -27,6 +28,7 @@ class TextFieldBuilder extends StatefulWidget {
final bool readOnly;
final MouseCursor? mouseCursor;
final LanguageToolController? languageToolController;
final bool dropTextEnabled;

const TextFieldBuilder({
super.key,
Expand All @@ -52,6 +54,7 @@ class TextFieldBuilder extends StatefulWidget {
this.onTapOutside,
this.onTextChange,
this.onTextSubmitted,
this.dropTextEnabled = false,
});

@override
Expand Down Expand Up @@ -85,8 +88,10 @@ class _TextFieldBuilderState extends State<TextFieldBuilder> {

@override
Widget build(BuildContext context) {
Widget child;

if (_languageToolController != null) {
return LanguageToolTextField(
child = LanguageToolTextField(
key: widget.key,
controller: _languageToolController!,
cursorColor: widget.cursorColor,
Expand All @@ -104,55 +109,46 @@ class _TextFieldBuilderState extends State<TextFieldBuilder> {
textDirection: _textDirection,
readOnly: widget.readOnly,
mouseCursor: widget.mouseCursor,
onTextChange: (value) {
widget.onTextChange?.call(value);
if (value.isNotEmpty) {
final directionByText = DirectionUtils.getDirectionByEndsText(value);
if (directionByText != _textDirection) {
setState(() {
_textDirection = directionByText;
});
}
}
},
onTextChange: _onChanged,
onTextSubmitted: widget.onTextSubmitted,
onTap: widget.onTap,
onTapOutside: widget.onTapOutside,
);
} else {
child = TextField(
key: widget.key,
controller: _controller,
cursorColor: widget.cursorColor,
autocorrect: widget.autocorrect,
textInputAction: widget.textInputAction,
decoration: widget.decoration,
maxLines: widget.maxLines,
minLines: widget.minLines,
keyboardAppearance: widget.keyboardAppearance,
style: widget.textStyle,
obscureText: widget.obscureText,
keyboardType: widget.keyboardType,
autofocus: widget.autoFocus,
focusNode: widget.focusNode,
textDirection: _textDirection,
readOnly: widget.readOnly,
mouseCursor: widget.mouseCursor,
onChanged: _onChanged,
onSubmitted: widget.onTextSubmitted,
onTap: widget.onTap,
onTapOutside: widget.onTapOutside,
);
}

return TextField(
key: widget.key,
controller: _controller,
cursorColor: widget.cursorColor,
autocorrect: widget.autocorrect,
textInputAction: widget.textInputAction,
decoration: widget.decoration,
maxLines: widget.maxLines,
minLines: widget.minLines,
keyboardAppearance: widget.keyboardAppearance,
style: widget.textStyle,
obscureText: widget.obscureText,
keyboardType: widget.keyboardType,
autofocus: widget.autoFocus,
focusNode: widget.focusNode,
textDirection: _textDirection,
readOnly: widget.readOnly,
mouseCursor: widget.mouseCursor,
onChanged: (value) {
widget.onTextChange?.call(value);
if (value.isNotEmpty) {
final directionByText = DirectionUtils.getDirectionByEndsText(value);
if (directionByText != _textDirection) {
setState(() {
_textDirection = directionByText;
});
}
}
if (!widget.dropTextEnabled) return child;

return TextDropZoneWeb(
onDrop: (value) {
(_languageToolController ?? _controller)?.text += value;
widget.focusNode?.requestFocus();
_onChanged(value);
},
onSubmitted: widget.onTextSubmitted,
onTap: widget.onTap,
onTapOutside: widget.onTapOutside,
child: child,
);
}

Expand All @@ -166,4 +162,16 @@ class _TextFieldBuilderState extends State<TextFieldBuilder> {
}
super.dispose();
}

void _onChanged(String value) {
widget.onTextChange?.call(value);
if (value.isNotEmpty) {
final directionByText = DirectionUtils.getDirectionByEndsText(value);
if (directionByText != _textDirection) {
setState(() {
_textDirection = directionByText;
});
}
}
}
}
Loading
Loading