Skip to content

Commit

Permalink
Update SuperEditor and SuperReader layer system to provide app-level …
Browse files Browse the repository at this point in the history
…composition (iOS)(Relates to #424, #893, #1166)(Resolves #1508) (#1470)
  • Loading branch information
matthew-carroll authored and web-flow committed Oct 23, 2023
1 parent d10f528 commit ef09ab4
Show file tree
Hide file tree
Showing 62 changed files with 4,436 additions and 1,931 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:io';

import 'package:example/demos/editor_configs/keyboard_overlay_clipper.dart';
import 'package:flutter/material.dart';
import 'package:follow_the_leader/follow_the_leader.dart';
import 'package:super_editor/super_editor.dart';

/// Mobile iOS document editing demo.
Expand All @@ -21,10 +22,15 @@ class _MobileEditingIOSDemoState extends State<MobileEditingIOSDemo> {
late MutableDocumentComposer _composer;
late Editor _docEditor;
late CommonEditorOperations _docOps;
late MagnifierAndToolbarController _overlayController;

FocusNode? _editorFocusNode;

final _selectionLayerLinks = SelectionLayerLinks();

// TODO: get rid of overlay controller once Android is refactored to use a control scope (as follow up to: https://github.com/superlistapp/super_editor/pull/1470)
late MagnifierAndToolbarController _overlayController;
late final SuperEditorIosControlsController _iosEditorControlsController;

@override
void initState() {
super.initState();
Expand All @@ -38,11 +44,19 @@ class _MobileEditingIOSDemoState extends State<MobileEditingIOSDemo> {
documentLayoutResolver: () => _docLayoutKey.currentState as DocumentLayout,
);
_editorFocusNode = FocusNode();

// TODO: get rid of the overlay controller
_overlayController = MagnifierAndToolbarController();
_iosEditorControlsController = SuperEditorIosControlsController(
toolbarBuilder: _buildIosToolbar,
magnifierBuilder: _buildIosMagnifier,
);
}

@override
void dispose() {
_iosEditorControlsController.dispose();

_editorFocusNode!.dispose();
_composer.dispose();
_doc.removeListener(_onDocumentChange);
Expand All @@ -53,17 +67,23 @@ class _MobileEditingIOSDemoState extends State<MobileEditingIOSDemo> {

void _cut() {
_docOps.cut();
// TODO: get rid of overlay controller once Android is refactored to use a control scope (as follow up to: https://github.com/superlistapp/super_editor/pull/1470)
_overlayController.hideToolbar();
_iosEditorControlsController.hideToolbar();
}

void _copy() {
_docOps.copy();
// TODO: get rid of overlay controller once Android is refactored to use a control scope (as follow up to: https://github.com/superlistapp/super_editor/pull/1470)
_overlayController.hideToolbar();
_iosEditorControlsController.hideToolbar();
}

void _paste() {
_docOps.paste();
// TODO: get rid of overlay controller once Android is refactored to use a control scope (as follow up to: https://github.com/superlistapp/super_editor/pull/1470)
_overlayController.hideToolbar();
_iosEditorControlsController.hideToolbar();
}

@override
Expand All @@ -72,25 +92,23 @@ class _MobileEditingIOSDemoState extends State<MobileEditingIOSDemo> {
child: Column(
children: [
Expanded(
child: SuperEditor(
focusNode: _editorFocusNode,
documentLayoutKey: _docLayoutKey,
editor: _docEditor,
document: _doc,
composer: _composer,
overlayController: _overlayController,
gestureMode: DocumentGestureMode.iOS,
inputSource: TextInputSource.ime,
iOSToolbarBuilder: (_) => IOSTextEditingFloatingToolbar(
onCutPressed: _cut,
onCopyPressed: _copy,
onPastePressed: _paste,
focalPoint: _overlayController.toolbarTopAnchor!,
),
stylesheet: defaultStylesheet.copyWith(
documentPadding: const EdgeInsets.all(16),
child: SuperEditorIosControlsScope(
controller: _iosEditorControlsController,
child: SuperEditor(
focusNode: _editorFocusNode,
documentLayoutKey: _docLayoutKey,
editor: _docEditor,
document: _doc,
composer: _composer,
gestureMode: DocumentGestureMode.iOS,
inputSource: TextInputSource.ime,
selectionLayerLinks: _selectionLayerLinks,
stylesheet: defaultStylesheet.copyWith(
documentPadding: const EdgeInsets.all(16),
),
overlayController: _overlayController,
createOverlayControlsClipper: (_) => const KeyboardToolbarClipper(),
),
createOverlayControlsClipper: (_) => const KeyboardToolbarClipper(),
),
),
MultiListenableBuilder(
Expand All @@ -105,6 +123,26 @@ class _MobileEditingIOSDemoState extends State<MobileEditingIOSDemo> {
);
}

Widget _buildIosToolbar(BuildContext context, Key mobileToolbarKey, LeaderLink focalPoint) {
return IOSTextEditingFloatingToolbar(
key: mobileToolbarKey,
focalPoint: focalPoint,
onCutPressed: _cut,
onCopyPressed: _copy,
onPastePressed: _paste,
);
}

Widget _buildIosMagnifier(BuildContext context, Key magnifierKey, LeaderLink focalPoint) {
return Center(
child: IOSFollowingMagnifier.roundedRectangle(
magnifierKey: magnifierKey,
leaderLink: focalPoint,
offsetFromFocalPoint: const Offset(0, -72),
),
);
}

Widget _buildMountedToolbar() {
final selection = _composer.selection;

Expand Down
132 changes: 74 additions & 58 deletions super_editor/example/lib/demos/example_editor/example_editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,12 @@ class _ExampleEditorState extends State<ExampleEditor> {
final _imageFormatBarOverlayController = OverlayPortalController();
final _imageSelectionAnchor = ValueNotifier<Offset?>(null);

// TODO: get rid of overlay controller once Android is refactored to use a control scope (as follow up to: https://github.com/superlistapp/super_editor/pull/1470)
final _overlayController = MagnifierAndToolbarController() //
..screenPadding = const EdgeInsets.all(20.0);

late final SuperEditorIosControlsController _iosControlsController;

@override
void initState() {
super.initState();
Expand All @@ -61,10 +64,13 @@ class _ExampleEditorState extends State<ExampleEditor> {
);
_editorFocusNode = FocusNode();
_scrollController = ScrollController()..addListener(_hideOrShowToolbar);

_iosControlsController = SuperEditorIosControlsController();
}

@override
void dispose() {
_iosControlsController.dispose();
_scrollController.dispose();
_editorFocusNode.dispose();
_composer.dispose();
Expand Down Expand Up @@ -199,17 +205,23 @@ class _ExampleEditorState extends State<ExampleEditor> {

void _cut() {
_docOps.cut();
// TODO: get rid of overlay controller once Android is refactored to use a control scope (as follow up to: https://github.com/superlistapp/super_editor/pull/1470)
_overlayController.hideToolbar();
_iosControlsController.hideToolbar();
}

void _copy() {
_docOps.copy();
// TODO: get rid of overlay controller once Android is refactored to use a control scope (as follow up to: https://github.com/superlistapp/super_editor/pull/1470)
_overlayController.hideToolbar();
_iosControlsController.hideToolbar();
}

void _paste() {
_docOps.paste();
// TODO: get rid of overlay controller once Android is refactored to use a control scope (as follow up to: https://github.com/superlistapp/super_editor/pull/1470)
_overlayController.hideToolbar();
_iosControlsController.hideToolbar();
}

void _selectAll() => _docOps.selectAll();
Expand Down Expand Up @@ -277,7 +289,16 @@ class _ExampleEditorState extends State<ExampleEditor> {
),
Align(
alignment: Alignment.bottomRight,
child: _buildCornerFabs(),
child: ListenableBuilder(
listenable: _composer.selectionNotifier,
builder: (context, child) {
return Padding(
padding: EdgeInsets.only(bottom: _isMobile && _composer.selection != null ? 48 : 0),
child: child,
);
},
child: _buildCornerFabs(),
),
),
],
),
Expand Down Expand Up @@ -351,72 +372,67 @@ class _ExampleEditorState extends State<ExampleEditor> {
config: _debugConfig ?? const SuperEditorDebugVisualsConfig(),
child: KeyedSubtree(
key: _viewportKey,
child: SuperEditor(
editor: _docEditor,
document: _doc,
composer: _composer,
focusNode: _editorFocusNode,
scrollController: _scrollController,
documentLayoutKey: _docLayoutKey,
documentOverlayBuilders: [
DefaultCaretOverlayBuilder(
caretStyle: const CaretStyle().copyWith(color: isLight ? Colors.black : Colors.redAccent),
child: SuperEditorIosControlsScope(
controller: _iosControlsController,
child: SuperEditor(
editor: _docEditor,
document: _doc,
composer: _composer,
focusNode: _editorFocusNode,
scrollController: _scrollController,
documentLayoutKey: _docLayoutKey,
documentOverlayBuilders: [
DefaultCaretOverlayBuilder(
caretStyle: const CaretStyle().copyWith(color: isLight ? Colors.black : Colors.redAccent),
),
SuperEditorIosToolbarFocalPointDocumentLayerBuilder(),
SuperEditorIosHandlesDocumentLayerBuilder(),
],
selectionLayerLinks: _selectionLayerLinks,
selectionStyle: isLight
? defaultSelectionStyle
: SelectionStyles(
selectionColor: Colors.red.withOpacity(0.3),
),
stylesheet: defaultStylesheet.copyWith(
addRulesAfter: [
if (!isLight) ..._darkModeStyles,
taskStyles,
],
),
],
selectionLayerLinks: _selectionLayerLinks,
selectionStyle: isLight
? defaultSelectionStyle
: SelectionStyles(
selectionColor: Colors.red.withOpacity(0.3),
),
stylesheet: defaultStylesheet.copyWith(
addRulesAfter: [
if (!isLight) ..._darkModeStyles,
taskStyles,
componentBuilders: [
TaskComponentBuilder(_docEditor),
...defaultComponentBuilders,
],
gestureMode: _gestureMode,
inputSource: _inputSource,
keyboardActions: _inputSource == TextInputSource.ime ? defaultImeKeyboardActions : defaultKeyboardActions,
androidToolbarBuilder: (_) => _buildAndroidFloatingToolbar(),
overlayController: _overlayController,
),
componentBuilders: [
TaskComponentBuilder(_docEditor),
...defaultComponentBuilders,
],
gestureMode: _gestureMode,
inputSource: _inputSource,
keyboardActions: _inputSource == TextInputSource.ime ? defaultImeKeyboardActions : defaultKeyboardActions,
androidToolbarBuilder: (_) => ListenableBuilder(
listenable: _brightness,
builder: (context, _) {
return Theme(
data: ThemeData(brightness: _brightness.value),
child: AndroidTextEditingFloatingToolbar(
onCutPressed: _cut,
onCopyPressed: _copy,
onPastePressed: _paste,
onSelectAllPressed: _selectAll,
),
);
},
),
iOSToolbarBuilder: (_) => ListenableBuilder(
listenable: _brightness,
builder: (context, _) {
return Theme(
data: ThemeData(brightness: _brightness.value),
child: IOSTextEditingFloatingToolbar(
onCutPressed: _cut,
onCopyPressed: _copy,
onPastePressed: _paste,
focalPoint: _overlayController.toolbarTopAnchor!,
),
);
},
),
overlayController: _overlayController,
),
),
),
);
}

Widget _buildAndroidFloatingToolbar() {
return ListenableBuilder(
listenable: _brightness,
builder: (context, _) {
return Theme(
data: ThemeData(brightness: _brightness.value),
child: AndroidTextEditingFloatingToolbar(
onCutPressed: _cut,
onCopyPressed: _copy,
onPastePressed: _paste,
onSelectAllPressed: _selectAll,
),
);
},
);
}

Widget _buildMountedToolbar() {
return MultiListenableBuilder(
listenables: <Listenable>{
Expand Down
Loading

0 comments on commit ef09ab4

Please sign in to comment.