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

fix: scroll service fixes #7

Closed
wants to merge 6 commits into from
Closed
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
1 change: 1 addition & 0 deletions example/ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
Expand Down
2 changes: 1 addition & 1 deletion example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ dependencies:
provider: ^6.0.3
url_launcher: ^6.1.5
path_provider: ^2.0.11
google_fonts: ^3.0.1
google_fonts: ^4.0.4
flutter_localizations:
sdk: flutter
file_picker: ^5.0.1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,11 @@ class DeltaTextInputService extends TextInputService with DeltaTextInputClient {
}
}
}

// @override
// void insertContent(KeyboardInsertedContent content) {
// // TODO: implement insertContent
// }
}

const String _whitespace = ' ';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ class _DesktopScrollServiceState extends State<DesktopScrollService>
implements AppFlowyScrollService {
@override
double get dy => widget.scrollController.position.pixels;

@override
bool get implecet => widget.scrollController.position.allowImplicitScrolling;
@override
double get offset => widget.scrollController.offset;
@override
double? get onePageHeight {
final renderBox = context.findRenderObject() as RenderBox?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ class _MobileScrollServiceState extends State<MobileScrollService>
implements AppFlowyScrollService {
@override
double get dy => widget.scrollController.position.pixels;

@override
bool get implecet => widget.scrollController.position.allowImplicitScrolling;
@override
double get offset => widget.scrollController.offset;
@override
double? get onePageHeight {
final renderBox = context.findRenderObject()?.unwrapOrNull<RenderBox>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ class _ScrollServiceWidgetState extends State<ScrollServiceWidget>

@override
late ScrollController scrollController;

@override
bool get implecet => scrollController.position.allowImplicitScrolling;
@override
double get offset => scrollController.offset;
@override
void initState() {
super.initState();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ class _DesktopSelectionServiceWidgetState
editorState.selectionType != SelectionType.block) {
return;
}

_scrollUpOrDownIfNeeded();
currentSelection.value = selection;

void updateSelection() {
Expand Down Expand Up @@ -190,6 +190,35 @@ class _DesktopSelectionServiceWidgetState
}
}

void _scrollUpOrDownIfNeeded() {
final dy = editorState.service.scrollService?.dy;
final selectNodes = currentSelectedNodes;
final selection = currentSelection.value;
if (dy == null || selection == null || selectNodes.isEmpty) {
return;
}

final rect = selectNodes.last.rect;

final size = MediaQuery.of(context).size.height;
final topLimit = size * 0.3;
final bottomLimit = size * 0.8;

// TODO: It is necessary to calculate the relative speed
// according to the gap and move forward more gently.
if (rect.top >= bottomLimit) {
if (selection.isSingle) {
editorState.service.scrollService?.scrollTo(dy + size * 0.2);
} else if (selection.isBackward) {
editorState.service.scrollService?.scrollTo(dy + 10.0);
}
} else if (rect.bottom <= topLimit) {
if (selection.isForward) {
editorState.service.scrollService?.scrollTo(dy - 10.0);
}
}
}

@override
void clearSelection() {
currentSelectedNodes = [];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:ui';
import 'dart:math' as math;
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/src/flutter/overlay.dart';
import 'package:appflowy_editor/src/service/context_menu/built_in_context_menu_item.dart';
Expand All @@ -7,6 +9,7 @@ import 'package:flutter/material.dart' hide Overlay, OverlayEntry;
import 'package:appflowy_editor/src/render/selection/cursor_widget.dart';
import 'package:appflowy_editor/src/render/selection/selection_widget.dart';
import 'package:appflowy_editor/src/service/selection/selection_gesture.dart';
import 'package:flutter/rendering.dart';
import 'package:provider/provider.dart';

class MobileSelectionServiceWidget extends StatefulWidget {
Expand Down Expand Up @@ -63,11 +66,11 @@ class _MobileSelectionServiceWidgetState

// Need to refresh the selection when the metrics changed.
if (currentSelection.value != null) {
// Debounce.debounce(
// 'didChangeMetrics - update selection ',
// const Duration(milliseconds: 100),
// () => updateSelection(currentSelection.value!),
// );
Debounce.debounce(
'didChangeMetrics - update selection ',
const Duration(milliseconds: 100),
() => updateSelection(currentSelection.value!),
);
}
}

Expand Down Expand Up @@ -159,6 +162,51 @@ class _MobileSelectionServiceWidgetState
});
}

RevealedOffset _getOffsetToRevealCaret(Rect rect) {
if (!editorState.service.scrollService!.implecet) {
return RevealedOffset(
offset: editorState.service.scrollService!.offset,
rect: rect,
);
}

final Size editableSize = editorState.renderBox!.size;
final double additionalOffset;
final Offset unitOffset;

// The caret is vertically centered within the line. Expand the caret's
// height so that it spans the line because we're going to ensure that the
// entire expanded caret is scrolled into view.
final Rect expandedRect = Rect.fromCenter(
center: rect.center,
width: rect.width,
height: math.max(rect.height, 20),
);

additionalOffset = expandedRect.height >= editableSize.height
? editableSize.height / 2 - expandedRect.center.dy
: clampDouble(
0.0,
expandedRect.bottom - editableSize.height,
expandedRect.top,
);
unitOffset = const Offset(0, 1);

// No overscrolling when encountering tall fonts/scripts that extend past
// the ascent.
final double targetOffset = clampDouble(
additionalOffset + rect.top + 200,
editorState.service.scrollService!.minScrollExtent,
editorState.service.scrollService!.maxScrollExtent,
);

final double offsetDelta = targetOffset;
return RevealedOffset(
rect: rect.shift(unitOffset * offsetDelta),
offset: targetOffset,
);
}

@override
void clearSelection() {
currentSelectedNodes = [];
Expand Down Expand Up @@ -401,6 +449,14 @@ class _MobileSelectionServiceWidgetState
final selectable = node.selectable;
final cursorRect = selectable?.getCursorRectInPosition(position);
if (selectable != null && cursorRect != null) {
Log.selection
.debug("reveal:${_getOffsetToRevealCaret(cursorRect).offset}");
editorState.service.scrollService?.scrollController.animateTo(
_getOffsetToRevealCaret(cursorRect).offset,
duration: const Duration(milliseconds: 100),
curve: Curves.easeIn,
);

final cursorArea = OverlayEntry(
builder: (context) => CursorWidget(
key: _cursorKey,
Expand Down
20 changes: 19 additions & 1 deletion lib/src/infra/html_converter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -326,9 +326,19 @@ class HTMLToNodesConverter {

Node _handleImage(html.Element element) {
final src = element.attributes["src"];
final alignment = element.attributes["align"] ?? "center";
final width = element.attributes["width"] != null
? double.parse(element.attributes["width"].toString())
: 200;
final height = element.attributes["height"] != null
? double.parse(element.attributes["height"].toString())
: 100;
final attributes = <String, dynamic>{};
if (src != null) {
attributes["image_src"] = src;
attributes["align"] = alignment;
attributes["width"] = width;
attributes["height"] = height;
}
return Node(type: "image", attributes: attributes, children: LinkedList());
}
Expand Down Expand Up @@ -448,10 +458,18 @@ class NodesToHTMLConverter {
final textNode = node;
final anchor = html.Element.tag(HTMLTag.image);
anchor.attributes["src"] = textNode.attributes["image_src"];
anchor.attributes["align"] = textNode.attributes["align"];

anchor.attributes["width"] = textNode.attributes["width"] != null
? textNode.attributes["width"].toString()
: "200";

anchor.attributes["height"] = textNode.attributes["height"] != null
? textNode.attributes["height"].toString()
: "100";

_result.add(anchor);
}
// TODO: handle image and other blocks
}
if (_stashListContainer != null) {
_result.add(_stashListContainer!);
Expand Down
11 changes: 8 additions & 3 deletions lib/src/render/image/image_node_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,27 @@ import 'package:appflowy_editor/src/render/action_menu/action_menu.dart';
import 'package:appflowy_editor/src/render/action_menu/action_menu_item.dart';
import 'package:appflowy_editor/src/service/render_plugin_service.dart';
import 'package:flutter/material.dart';

import 'image_node_widget.dart';

class ImageNodeBuilder extends NodeWidgetBuilder<Node>
with ActionProvider<Node> {
@override
Widget build(NodeWidgetContext<Node> context) {
final src = context.node.attributes['image_src'];
final align = context.node.attributes['align'];
//some time align attribute is not present from dynamic data so we are adding default value
final align = context.node.attributes['align'] ?? "center";
double? width;
double? height;
if (context.node.attributes.containsKey('width')) {
width = context.node.attributes['width'].toDouble();
}
if (context.node.attributes.containsKey('height')) {
height = context.node.attributes['height'].toDouble();
}
return ImageNodeWidget(
key: context.node.key,
node: context.node,
height: height,
src: src,
width: width,
editable: context.editorState.editable,
Expand Down Expand Up @@ -112,4 +117,4 @@ class ImageNodeBuilder extends NodeWidgetBuilder<Node>
});
context.editorState.apply(transaction);
}
}
}
9 changes: 7 additions & 2 deletions lib/src/render/image/image_node_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ class ImageNodeWidget extends StatefulWidget {
this.width,
required this.alignment,
required this.editable,
this.height,
required this.onResize,
}) : super(key: key);

final Node node;
final String src;
final double? width;
final double? height;
final Alignment alignment;
final bool editable;
final void Function(double width) onResize;
Expand All @@ -33,6 +35,7 @@ class ImageNodeWidgetState extends State<ImageNodeWidget> with SelectableMixin {
final _imageKey = GlobalKey();

double? _imageWidth;
double? _imageHeight;
double _initial = 0;
double _distance = 0;

Expand All @@ -47,6 +50,7 @@ class ImageNodeWidgetState extends State<ImageNodeWidget> with SelectableMixin {
super.initState();

_imageWidth = widget.width;
_imageHeight = widget.height;
_imageStreamListener = ImageStreamListener(
(image, _) {
_imageWidth = _imageKey.currentContext
Expand Down Expand Up @@ -141,6 +145,7 @@ class ImageNodeWidgetState extends State<ImageNodeWidget> with SelectableMixin {
final networkImage = Image.network(
widget.src,
width: _imageWidth == null ? null : _imageWidth! - _distance,
height: _imageHeight,
gaplessPlayback: true,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null ||
Expand Down Expand Up @@ -213,13 +218,13 @@ class ImageNodeWidgetState extends State<ImageNodeWidget> with SelectableMixin {

Widget _buildError(BuildContext context) {
return Container(
height: 100,
height: _imageHeight ?? 100,
width: _imageWidth,
alignment: Alignment.center,
padding: const EdgeInsets.only(top: 8.0, bottom: 8.0),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(4.0)),
border: Border.all(width: 1, color: Colors.grey),
border: Border.all(width: 1, color: Colors.black),
),
child: const Text('Could not load the image'),
);
Expand Down
5 changes: 5 additions & 0 deletions lib/src/service/input_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -344,4 +344,9 @@ class _AppFlowyInputState extends State<AppFlowyInput>
void performSelector(String selectorName) {
// TODO: implement performSelector
}

@override
void insertContent(KeyboardInsertedContent content) {
// TODO: implement insertContent
}
}
2 changes: 2 additions & 0 deletions lib/src/service/scroll_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ abstract class AppFlowyScrollService implements AutoScrollerService {

/// scroll controller
ScrollController get scrollController;
bool get implecet;
double get offset;

/// Scrolls to the specified position.
///
Expand Down