diff --git a/lib/main_app.dart b/lib/main_app.dart index 4e687d4fe..afe353b2a 100644 --- a/lib/main_app.dart +++ b/lib/main_app.dart @@ -413,6 +413,7 @@ class InvoiceNinjaAppState extends State { */ theme: state.prefState.enableDarkMode ? ThemeData( + useMaterial3: false, tooltipTheme: TooltipThemeData( waitDuration: Duration(milliseconds: 500), ), @@ -438,6 +439,7 @@ class InvoiceNinjaAppState extends State { BottomAppBarTheme(color: const Color(0xFF1B1C1E)), ) : ThemeData( + useMaterial3: false, tooltipTheme: TooltipThemeData( waitDuration: Duration(milliseconds: 500), ), diff --git a/lib/redux/app/app_state.dart b/lib/redux/app/app_state.dart index 160868dd8..a90e90883 100644 --- a/lib/redux/app/app_state.dart +++ b/lib/redux/app/app_state.dart @@ -162,7 +162,7 @@ abstract class AppState implements Built { } final companies = - list.where((CompanyEntity company) => (company.id).isNotEmpty).toList(); + list.where((CompanyEntity company) => company.id.isNotEmpty).toList(); return companies; } diff --git a/lib/redux/dashboard/dashboard_sidebar_selectors.dart b/lib/redux/dashboard/dashboard_sidebar_selectors.dart index 59de11a0e..f6fb7d8cd 100644 --- a/lib/redux/dashboard/dashboard_sidebar_selectors.dart +++ b/lib/redux/dashboard/dashboard_sidebar_selectors.dart @@ -191,7 +191,7 @@ List _runningTasks({ } }); - tasks.sort((taskA, taskB) => (taskB.updatedAt).compareTo(taskA.updatedAt)); + tasks.sort((taskA, taskB) => taskB.updatedAt.compareTo(taskA.updatedAt)); return tasks; } @@ -219,7 +219,7 @@ List _recentTasks({ } }); - tasks.sort((taskA, taskB) => (taskB.updatedAt).compareTo(taskA.updatedAt)); + tasks.sort((taskA, taskB) => taskB.updatedAt.compareTo(taskA.updatedAt)); return tasks; } diff --git a/lib/redux/recurring_expense/recurring_expense_actions.dart b/lib/redux/recurring_expense/recurring_expense_actions.dart index d172f8ad8..6d4b94edc 100644 --- a/lib/redux/recurring_expense/recurring_expense_actions.dart +++ b/lib/redux/recurring_expense/recurring_expense_actions.dart @@ -388,10 +388,9 @@ void handleRecurringExpenseAction(BuildContext? context, break; case EntityAction.start: store.dispatch(StartRecurringExpensesRequest( - completer: snackBarCompleter( - (recurringExpense.lastSentDate).isEmpty - ? localization!.startedRecurringInvoice - : localization!.resumedRecurringInvoice), + completer: snackBarCompleter(recurringExpense.lastSentDate.isEmpty + ? localization!.startedRecurringInvoice + : localization!.resumedRecurringInvoice), expenseIds: recurringExpenseIds, )); break; diff --git a/lib/redux/recurring_invoice/recurring_invoice_actions.dart b/lib/redux/recurring_invoice/recurring_invoice_actions.dart index ce1bc7700..969b78525 100644 --- a/lib/redux/recurring_invoice/recurring_invoice_actions.dart +++ b/lib/redux/recurring_invoice/recurring_invoice_actions.dart @@ -629,10 +629,9 @@ void handleRecurringInvoiceAction(BuildContext? context, break; case EntityAction.start: store.dispatch(StartRecurringInvoicesRequest( - completer: snackBarCompleter( - (recurringInvoice.lastSentDate).isEmpty - ? localization!.startedRecurringInvoice - : localization!.resumedRecurringInvoice), + completer: snackBarCompleter(recurringInvoice.lastSentDate.isEmpty + ? localization!.startedRecurringInvoice + : localization!.resumedRecurringInvoice), invoiceIds: recurringInvoiceIds, )); break; diff --git a/lib/redux/vendor/vendor_actions.dart b/lib/redux/vendor/vendor_actions.dart index 8adb3cc52..b0b865fe7 100644 --- a/lib/redux/vendor/vendor_actions.dart +++ b/lib/redux/vendor/vendor_actions.dart @@ -300,7 +300,7 @@ void handleVendorAction( break; case EntityAction.vendorPortal: final contact = vendor.contacts.firstWhere((contact) { - return (contact.link).isNotEmpty; + return contact.link.isNotEmpty; }, orElse: null); launchUrl(Uri.parse(contact.silentLink)); break; diff --git a/lib/ui/app/dialogs/multiselect_dialog.dart b/lib/ui/app/dialogs/multiselect_dialog.dart index 5c5ab58e9..ed101a0c5 100644 --- a/lib/ui/app/dialogs/multiselect_dialog.dart +++ b/lib/ui/app/dialogs/multiselect_dialog.dart @@ -74,7 +74,7 @@ class MultiSelectListState extends State { void initState() { super.initState(); selected = - (widget.selected).isNotEmpty ? widget.selected : widget.defaultSelected; + widget.selected.isNotEmpty ? widget.selected : widget.defaultSelected; _controller = ScrollController(); } diff --git a/lib/ui/app/document_grid.dart b/lib/ui/app/document_grid.dart index 2acd58969..a404e9c3d 100644 --- a/lib/ui/app/document_grid.dart +++ b/lib/ui/app/document_grid.dart @@ -459,7 +459,7 @@ class DocumentPreview extends StatelessWidget { key: ValueKey(document.preview), imageUrl: '${cleanApiUrl(state.credentials.url)}/documents/${document.hash}', - imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet, + //imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet, httpHeaders: {'X-API-TOKEN': state.credentials.token}, placeholder: (context, url) => Container( height: height, diff --git a/lib/ui/app/invoice/invoice_email_view.dart b/lib/ui/app/invoice/invoice_email_view.dart index 25c9dc49f..29caf0d52 100644 --- a/lib/ui/app/invoice/invoice_email_view.dart +++ b/lib/ui/app/invoice/invoice_email_view.dart @@ -87,7 +87,7 @@ class _InvoiceEmailViewState extends State selectedTemplate = EmailTemplate.reminder3; else if ((invoice.reminder1Sent ?? '').isNotEmpty) selectedTemplate = EmailTemplate.reminder2; - else if ((invoice.lastSentDate).isNotEmpty) + else if (invoice.lastSentDate.isNotEmpty) selectedTemplate = EmailTemplate.reminder1; else selectedTemplate = EmailTemplate.invoice; diff --git a/lib/ui/app/lists/activity_list_tile.dart b/lib/ui/app/lists/activity_list_tile.dart index 3c90f481e..6bb74fcb1 100644 --- a/lib/ui/app/lists/activity_list_tile.dart +++ b/lib/ui/app/lists/activity_list_tile.dart @@ -139,7 +139,7 @@ class ActivityListTile extends StatelessWidget { subtitle: Row( children: [ Flexible( - child: Text(((activity.notes).isNotEmpty + child: Text((activity.notes.isNotEmpty ? localization.lookup(activity.notes).trim() + '\n' : '') + formatDate( diff --git a/lib/ui/client/view/client_view_overview.dart b/lib/ui/client/view/client_view_overview.dart index 3bcc2a556..a08a907e8 100644 --- a/lib/ui/client/view/client_view_overview.dart +++ b/lib/ui/client/view/client_view_overview.dart @@ -165,7 +165,7 @@ class ClientOverview extends StatelessWidget { ), ), ListDivider(), - if ((client.privateNotes).isNotEmpty) ...[ + if (client.privateNotes.isNotEmpty) ...[ IconMessage(client.privateNotes, iconData: Icons.lock), ListDivider() ], diff --git a/lib/ui/invoice/edit/invoice_item_selector.dart b/lib/ui/invoice/edit/invoice_item_selector.dart index b11146081..93f1d6183 100644 --- a/lib/ui/invoice/edit/invoice_item_selector.dart +++ b/lib/ui/invoice/edit/invoice_item_selector.dart @@ -129,7 +129,7 @@ class _InvoiceItemSelectorState extends State if (selected != null) { _filterClientId = (selected as BelongsToClient).clientId; - } else if ((widget.clientId).isEmpty) { + } else if (widget.clientId.isEmpty) { _filterClientId = null; } } diff --git a/lib/ui/purchase_order/purchase_order_list_item.dart b/lib/ui/purchase_order/purchase_order_list_item.dart index 1127b7c44..c5693e127 100644 --- a/lib/ui/purchase_order/purchase_order_list_item.dart +++ b/lib/ui/purchase_order/purchase_order_list_item.dart @@ -217,7 +217,7 @@ class PurchaseOrderListItem extends StatelessWidget { children: [ Expanded( child: filterMatch == null - ? Text((((purchaseOrder.number).isEmpty + ? Text(((purchaseOrder.number.isEmpty ? localization!.pending : purchaseOrder.number) + ' • ' + diff --git a/lib/ui/purchase_order/purchase_order_presenter.dart b/lib/ui/purchase_order/purchase_order_presenter.dart index d3355e5c2..7b3b9ce54 100644 --- a/lib/ui/purchase_order/purchase_order_presenter.dart +++ b/lib/ui/purchase_order/purchase_order_presenter.dart @@ -73,7 +73,7 @@ class PurchaseOrderPresenter extends EntityPresenter { case PurchaseOrderFields.status: return EntityStatusChip(entity: purchaseOrder, showState: true); case PurchaseOrderFields.number: - return Text((purchaseOrder.number).isEmpty + return Text(purchaseOrder.number.isEmpty ? localization!.pending : purchaseOrder.number); case PurchaseOrderFields.client: diff --git a/lib/ui/quote/quote_list_item.dart b/lib/ui/quote/quote_list_item.dart index 97a6435ee..65010d23f 100644 --- a/lib/ui/quote/quote_list_item.dart +++ b/lib/ui/quote/quote_list_item.dart @@ -113,7 +113,7 @@ class QuoteListItem extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - (quote.number).isEmpty + quote.number.isEmpty ? localization!.pending : quote.number, style: textStyle, diff --git a/lib/ui/recurring_invoice/recurring_invoice_list_item.dart b/lib/ui/recurring_invoice/recurring_invoice_list_item.dart index d79c17e3a..0e03e7fa2 100644 --- a/lib/ui/recurring_invoice/recurring_invoice_list_item.dart +++ b/lib/ui/recurring_invoice/recurring_invoice_list_item.dart @@ -122,7 +122,7 @@ class RecurringInvoiceListItem extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - (invoice.number).isEmpty + invoice.number.isEmpty ? localization.pending : invoice.number, style: textStyle, diff --git a/lib/utils/markdown.dart b/lib/utils/markdown.dart index 0c942c618..32ff4c1dc 100644 --- a/lib/utils/markdown.dart +++ b/lib/utils/markdown.dart @@ -1,3 +1,4 @@ +/* // DELETE THIS FILE ONCE SUPER EDITOR IS UPDATED // Note: using the standard function crashes with h1 tags @@ -588,3 +589,4 @@ class _EmptyParagraphSyntax extends md.BlockSyntax { return md.Element('p', []); } } +*/ \ No newline at end of file diff --git a/lib/utils/super_editor/super_editor.dart b/lib/utils/super_editor/super_editor.dart index c4f0f17a1..51b834663 100644 --- a/lib/utils/super_editor/super_editor.dart +++ b/lib/utils/super_editor/super_editor.dart @@ -1,8 +1,11 @@ +//import 'package:example/logging.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:invoiceninja_flutter/utils/markdown.dart'; -import 'package:invoiceninja_flutter/utils/super_editor/toolbar.dart'; import 'package:super_editor/super_editor.dart'; +import 'package:super_editor_markdown/super_editor_markdown.dart'; + +import 'package:invoiceninja_flutter/utils/super_editor/toolbar.dart'; +//import '_toolbar.dart'; /// Example of a rich text editor. /// @@ -19,39 +22,46 @@ class ExampleEditor extends StatefulWidget { final Function(String)? onChanged; @override - _ExampleEditorState createState() => _ExampleEditorState(); + State createState() => _ExampleEditorState(); } class _ExampleEditorState extends State { + final GlobalKey _viewportKey = GlobalKey(); final GlobalKey _docLayoutKey = GlobalKey(); - late Document _doc; - DocumentEditor? _docEditor; - DocumentComposer? _composer; + late MutableDocument _doc; + final _docChangeSignal = SignalNotifier(); + late MutableDocumentComposer _composer; + late Editor _docEditor; late CommonEditorOperations _docOps; - FocusNode? _editorFocusNode; + late FocusNode _editorFocusNode; - ScrollController? _scrollController; + late ScrollController _scrollController; + + final SelectionLayerLinks _selectionLayerLinks = SelectionLayerLinks(); final _darkBackground = const Color(0xFF222222); final _lightBackground = Colors.white; - //Brightness _brightness = Brightness.light; + final _brightness = ValueNotifier(Brightness.light); SuperEditorDebugVisualsConfig? _debugConfig; - OverlayEntry? _textFormatBarOverlayEntry; + final _textFormatBarOverlayController = OverlayPortalController(); final _textSelectionAnchor = ValueNotifier(null); - OverlayEntry? _imageFormatBarOverlayEntry; + final _imageFormatBarOverlayController = OverlayPortalController(); final _imageSelectionAnchor = ValueNotifier(null); - final _overlayController = MagnifierAndToolbarController(); + // 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(); - // Fix for

tags cutting off text var markdown = widget.value; markdown = markdown.replaceAll('

', '\n'); @@ -60,64 +70,38 @@ class _ExampleEditorState extends State { markdown = markdown.replaceAll('

', ''); markdown = markdown.replaceAll('', ''); + // _doc = createInitialDocument()..addListener(_onDocumentChange); _doc = deserializeMarkdownToDocument(markdown) - ..addListener(_hideOrShowToolbar) - ..addListener(_onChanged); - - _docEditor = DocumentEditor(document: _doc as MutableDocument); - _composer = DocumentComposer(); - _composer!.selectionNotifier.addListener(_hideOrShowToolbar); + ..addListener(_onDocumentChange); + _composer = MutableDocumentComposer(); + _composer.selectionNotifier.addListener(_hideOrShowToolbar); + _docEditor = + createDefaultDocumentEditor(document: _doc, composer: _composer); _docOps = CommonEditorOperations( - editor: _docEditor!, - composer: _composer!, + editor: _docEditor, + document: _doc, + composer: _composer, documentLayoutResolver: () => _docLayoutKey.currentState as DocumentLayout, ); _editorFocusNode = FocusNode(); _scrollController = ScrollController()..addListener(_hideOrShowToolbar); - } - @override - void didUpdateWidget(ExampleEditor oldWidget) { - super.didUpdateWidget(oldWidget); - - if (widget.value != oldWidget.value) { - _setValue(widget.value); - } - } - - void _setValue(String value) { - _doc.removeListener(_hideOrShowToolbar); - _doc.removeListener(_onChanged); - _doc = deserializeMarkdownToDocument(value) - ..addListener(_hideOrShowToolbar) - ..addListener(_onChanged); - _docEditor = DocumentEditor(document: _doc as MutableDocument); - _editorFocusNode = FocusNode(); + _iosControlsController = SuperEditorIosControlsController(); } @override void dispose() { - if (_textFormatBarOverlayEntry != null) { - _textFormatBarOverlayEntry!.remove(); - } - - _doc.removeListener(_hideOrShowToolbar); - _doc.removeListener(_onChanged); - _scrollController!.removeListener(_hideOrShowToolbar); - _composer!.removeListener(_hideOrShowToolbar); - - _scrollController!.dispose(); - _editorFocusNode!.dispose(); - _composer!.dispose(); + _iosControlsController.dispose(); + _scrollController.dispose(); + _editorFocusNode.dispose(); + _composer.dispose(); super.dispose(); } - void _onChanged() { - if (widget.onChanged != null) { - final value = serializeDocumentToMarkdown(_docEditor!.document); - widget.onChanged!(value); - } + void _onDocumentChange(_) { + _hideOrShowToolbar(); + _docChangeSignal.notifyListeners(); } void _hideOrShowToolbar() { @@ -127,7 +111,7 @@ class _ExampleEditorState extends State { return; } - final selection = _composer!.selection; + final selection = _composer.selection; if (selection == null) { // Nothing is selected. We don't want to show a toolbar // in this case. @@ -155,6 +139,7 @@ class _ExampleEditorState extends State { final selectedNode = _doc.getNodeById(selection.extent.nodeId); if (selectedNode is ImageNode) { + //appLog.fine("Showing image toolbar"); // Show the editor's toolbar for image sizing. _showImageToolbar(); _hideEditorToolbar(); @@ -178,36 +163,16 @@ class _ExampleEditorState extends State { } void _showEditorToolbar() { - if (_textFormatBarOverlayEntry == null) { - // Create an overlay entry to build the editor toolbar. - // TODO: add an overlay to the Editor widget to avoid using the - // application overlay - _textFormatBarOverlayEntry ??= OverlayEntry(builder: (context) { - return EditorToolbar( - anchor: _textSelectionAnchor, - editorFocusNode: _editorFocusNode, - editor: _docEditor, - composer: _composer, - closeToolbar: _hideEditorToolbar, - ); - }); - - // Display the toolbar in the application overlay. - final overlay = Overlay.of(context); - overlay.insert(_textFormatBarOverlayEntry!); - } + _textFormatBarOverlayController.show(); // Schedule a callback after this frame to locate the selection // bounds on the screen and display the toolbar near the selected // text. + // TODO: switch this to use a Leader and Follower WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - if (_textFormatBarOverlayEntry == null) { - return; - } - final docBoundingBox = (_docLayoutKey.currentState as DocumentLayout) .getRectForSelection( - _composer!.selection!.base, _composer!.selection!.extent)!; + _composer.selection!.base, _composer.selection!.extent)!; final docBox = _docLayoutKey.currentContext!.findRenderObject() as RenderBox; final overlayBoundingBox = Rect.fromPoints( @@ -224,22 +189,14 @@ class _ExampleEditorState extends State { // the bar doesn't momentarily "flash" at its old anchor position. _textSelectionAnchor.value = null; - if (_textFormatBarOverlayEntry != null) { - // Remove the toolbar overlay and null-out the entry. - // We null out the entry because we can't query whether - // or not the entry exists in the overlay, so in our - // case, null implies the entry is not in the overlay, - // and non-null implies the entry is in the overlay. - _textFormatBarOverlayEntry!.remove(); - _textFormatBarOverlayEntry = null; + _textFormatBarOverlayController.hide(); - // Ensure that focus returns to the editor. - // - // I tried explicitly unfocus()'ing the URL textfield - // in the toolbar but it didn't return focus to the - // editor. I'm not sure why. - _editorFocusNode!.requestFocus(); - } + // Ensure that focus returns to the editor. + // + // I tried explicitly unfocus()'ing the URL textfield + // in the toolbar but it didn't return focus to the + // editor. I'm not sure why. + _editorFocusNode.requestFocus(); } DocumentGestureMode get _gestureMode { @@ -267,63 +224,41 @@ class _ExampleEditorState extends State { case TargetPlatform.macOS: case TargetPlatform.windows: return TextInputSource.ime; - // return DocumentInputSource.keyboard; } } 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(); void _showImageToolbar() { - if (_imageFormatBarOverlayEntry == null) { - // Create an overlay entry to build the image toolbar. - _imageFormatBarOverlayEntry ??= OverlayEntry(builder: (context) { - return ImageFormatToolbar( - anchor: _imageSelectionAnchor, - composer: _composer, - setWidth: (nodeId, width) { - final node = _doc.getNodeById(nodeId)!; - final currentStyles = - SingleColumnLayoutComponentStyles.fromMetadata(node); - SingleColumnLayoutComponentStyles( - width: width, - padding: currentStyles.padding, - ).applyTo(node); - }, - closeToolbar: _hideImageToolbar, - ); - }); - - // Display the toolbar in the application overlay. - final overlay = Overlay.of(context); - overlay.insert(_imageFormatBarOverlayEntry!); - } - // Schedule a callback after this frame to locate the selection // bounds on the screen and display the toolbar near the selected // text. + // TODO: switch to a Leader and Follower for this WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - if (_imageFormatBarOverlayEntry == null) { - return; - } - final docBoundingBox = (_docLayoutKey.currentState as DocumentLayout) .getRectForSelection( - _composer!.selection!.base, _composer!.selection!.extent)!; + _composer.selection!.base, _composer.selection!.extent)!; final docBox = _docLayoutKey.currentContext!.findRenderObject() as RenderBox; final overlayBoundingBox = Rect.fromPoints( @@ -333,6 +268,8 @@ class _ExampleEditorState extends State { _imageSelectionAnchor.value = overlayBoundingBox.center; }); + + _imageFormatBarOverlayController.show(); } void _hideImageToolbar() { @@ -340,56 +277,74 @@ class _ExampleEditorState extends State { // it doesn't momentarily "flash" at its old anchor position. _imageSelectionAnchor.value = null; - if (_imageFormatBarOverlayEntry != null) { - // Remove the image toolbar overlay and null-out the entry. - // We null out the entry because we can't query whether - // or not the entry exists in the overlay, so in our - // case, null implies the entry is not in the overlay, - // and non-null implies the entry is in the overlay. - _imageFormatBarOverlayEntry!.remove(); - _imageFormatBarOverlayEntry = null; + _imageFormatBarOverlayController.hide(); - // Ensure that focus returns to the editor. - _editorFocusNode!.requestFocus(); - } + // Ensure that focus returns to the editor. + _editorFocusNode.requestFocus(); } @override Widget build(BuildContext context) { - return Theme( - data: ThemeData(brightness: Brightness.light), - child: Builder(builder: (themedContext) { - // This builder captures the new theme - return Stack( - children: [ - Column( - children: [ - Expanded( - child: _buildEditor(themedContext), - ), - if (_isMobile) _buildMountedToolbar(), - ], - ), - /* - Align( - alignment: Alignment.bottomRight, - child: _buildCornerFabs(), - ), - */ - ], + return ValueListenableBuilder( + valueListenable: _brightness, + builder: (context, brightness, child) { + return Theme( + data: ThemeData(brightness: brightness), + child: child!, ); - }), + }, + child: Builder( + // This builder captures the new theme + builder: (themedContext) { + return OverlayPortal( + controller: _textFormatBarOverlayController, + overlayChildBuilder: _buildFloatingToolbar, + child: OverlayPortal( + controller: _imageFormatBarOverlayController, + overlayChildBuilder: _buildImageToolbar, + child: Stack( + children: [ + Column( + children: [ + Expanded( + child: _buildEditor(themedContext), + ), + if (_isMobile) // + _buildMountedToolbar(), + ], + ), + Align( + alignment: Alignment.bottomRight, + child: ListenableBuilder( + listenable: _composer.selectionNotifier, + builder: (context, child) { + return Padding( + padding: EdgeInsets.only( + bottom: _isMobile && _composer.selection != null + ? 48 + : 0), + child: child, + ); + }, + child: _buildCornerFabs(), + ), + ), + ], + ), + ), + ); + }, + ), ); } - /* Widget _buildCornerFabs() { return Padding( padding: const EdgeInsets.only(right: 16, bottom: 16), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, - children: [ + children: [ _buildDebugVisualsToggle(), const SizedBox(height: 16), _buildLightAndDarkModeToggle(), @@ -397,21 +352,21 @@ class _ExampleEditorState extends State { ), ); } - */ - /* Widget _buildDebugVisualsToggle() { return FloatingActionButton( - backgroundColor: - _brightness == Brightness.light ? _darkBackground : _lightBackground, - foregroundColor: - _brightness == Brightness.light ? _lightBackground : _darkBackground, + backgroundColor: _brightness.value == Brightness.light + ? _darkBackground + : _lightBackground, + foregroundColor: _brightness.value == Brightness.light + ? _lightBackground + : _darkBackground, elevation: 5, onPressed: () { setState(() { _debugConfig = _debugConfig != null ? null - : SuperEditorDebugVisualsConfig( + : const SuperEditorDebugVisualsConfig( showFocus: true, showImeConnection: true, ); @@ -421,23 +376,23 @@ class _ExampleEditorState extends State { Icons.bug_report, ), ); - } + } Widget _buildLightAndDarkModeToggle() { return FloatingActionButton( - backgroundColor: - _brightness == Brightness.light ? _darkBackground : _lightBackground, - foregroundColor: - _brightness == Brightness.light ? _lightBackground : _darkBackground, + backgroundColor: _brightness.value == Brightness.light + ? _darkBackground + : _lightBackground, + foregroundColor: _brightness.value == Brightness.light + ? _lightBackground + : _darkBackground, elevation: 5, onPressed: () { - setState(() { - _brightness = _brightness == Brightness.light - ? Brightness.dark - : Brightness.light; - }); + _brightness.value = _brightness.value == Brightness.light + ? Brightness.dark + : Brightness.light; }, - child: _brightness == Brightness.light + child: _brightness.value == Brightness.light ? const Icon( Icons.dark_mode, ) @@ -446,7 +401,6 @@ class _ExampleEditorState extends State { ), ); } - */ Widget _buildEditor(BuildContext context) { final isLight = Theme.of(context).brightness == Brightness.light; @@ -455,77 +409,134 @@ class _ExampleEditorState extends State { color: isLight ? _lightBackground : _darkBackground, child: SuperEditorDebugVisuals( config: _debugConfig ?? const SuperEditorDebugVisualsConfig(), - child: SuperEditor( - editor: _docEditor!, - composer: _composer, - focusNode: _editorFocusNode, - scrollController: _scrollController, - documentLayoutKey: _docLayoutKey, - documentOverlayBuilders: [ - DefaultCaretOverlayBuilder( - CaretStyle() - .copyWith(color: isLight ? Colors.black : Colors.redAccent), - ), - ], - selectionStyle: isLight - ? defaultSelectionStyle - : SelectionStyles( - selectionColor: Colors.red.withOpacity(0.3), + child: KeyedSubtree( + key: _viewportKey, + 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), ), - stylesheet: defaultStylesheet.copyWith( - addRulesAfter: [ - if (!isLight) ..._darkModeStyles, - taskStyles, - ], + if (defaultTargetPlatform == TargetPlatform.iOS) ...[ + SuperEditorAndroidToolbarFocalPointDocumentLayerBuilder(), + SuperEditorAndroidHandlesDocumentLayerBuilder(), + ], + if (defaultTargetPlatform == TargetPlatform.android) ...[ + SuperEditorAndroidToolbarFocalPointDocumentLayerBuilder(), + SuperEditorAndroidHandlesDocumentLayerBuilder(), + ], + ], + 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: (_) => AndroidTextEditingFloatingToolbar( + ), + ), + ); + } + + 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, ), - iOSToolbarBuilder: (_) => IOSTextEditingFloatingToolbar( - onCutPressed: _cut, - onCopyPressed: _copy, - onPastePressed: _paste, - focalPoint: _overlayController.toolbarTopAnchor!, - ), - overlayController: _overlayController, - ), - ), + ); + }, ); } Widget _buildMountedToolbar() { return MultiListenableBuilder( listenables: { - _doc, - _composer!.selectionNotifier, + _docChangeSignal, + _composer.selectionNotifier, }, builder: (_) { - final selection = _composer!.selection; + final selection = _composer.selection; if (selection == null) { return const SizedBox(); } return KeyboardEditingToolbar( + editor: _docEditor, document: _doc, - composer: _composer!, + composer: _composer, commonOps: _docOps, ); }, ); } + + Widget _buildFloatingToolbar(BuildContext context) { + return EditorToolbar( + editorViewportKey: _viewportKey, + anchor: _selectionLayerLinks.expandedSelectionBoundsLink, + editorFocusNode: _editorFocusNode, + editor: _docEditor, + document: _doc, + composer: _composer, + closeToolbar: _hideEditorToolbar, + ); + } + + Widget _buildImageToolbar(BuildContext context) { + return ImageFormatToolbar( + anchor: _imageSelectionAnchor, + composer: _composer, + setWidth: (nodeId, width) { + print('Applying width $width to node $nodeId'); + final node = _doc.getNodeById(nodeId)!; + final currentStyles = + SingleColumnLayoutComponentStyles.fromMetadata(node); + SingleColumnLayoutComponentStyles( + width: width, + padding: currentStyles.padding, + ).applyTo(node); + + // TODO: schedule a presentation reflow so that the image changes size immediately (https://github.com/superlistapp/super_editor/issues/1529) + // Right now, nothing happens when pressing the button, unless we force a + // rebuild/reflow. + }, + closeToolbar: _hideImageToolbar, + ); + } } // Makes text light, for use during dark mode styling. @@ -533,7 +544,7 @@ final _darkModeStyles = [ StyleRule( BlockSelector.all, (doc, docNode) { - return { + return { 'textStyle': const TextStyle( color: Color(0xFFCCCCCC), ), @@ -543,7 +554,7 @@ final _darkModeStyles = [ StyleRule( const BlockSelector('header1'), (doc, docNode) { - return { + return { 'textStyle': const TextStyle( color: Color(0xFF888888), ), @@ -553,7 +564,7 @@ final _darkModeStyles = [ StyleRule( const BlockSelector('header2'), (doc, docNode) { - return { + return { 'textStyle': const TextStyle( color: Color(0xFF888888), ), diff --git a/lib/utils/super_editor/toolbar.dart b/lib/utils/super_editor/toolbar.dart index 7939993bf..de50da096 100644 --- a/lib/utils/super_editor/toolbar.dart +++ b/lib/utils/super_editor/toolbar.dart @@ -1,6 +1,10 @@ import 'dart:math'; +//import 'package:example/logging.dart'; import 'package:flutter/material.dart'; +//import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:follow_the_leader/follow_the_leader.dart'; +import 'package:overlord/follow_the_leader.dart'; import 'package:super_editor/super_editor.dart'; /// Small toolbar that is intended to display near some selected @@ -13,33 +17,40 @@ import 'package:super_editor/super_editor.dart'; class EditorToolbar extends StatefulWidget { const EditorToolbar({ Key? key, - required this.anchor, + required this.editorViewportKey, required this.editorFocusNode, required this.editor, + required this.document, required this.composer, + required this.anchor, required this.closeToolbar, }) : super(key: key); - /// [EditorToolbar] displays itself horizontally centered and - /// slightly above the given [anchor] value. + /// [GlobalKey] that should be attached to a widget that wraps the viewport + /// area, which keeps the toolbar from appearing outside of the editor area. + final GlobalKey editorViewportKey; + + /// A [LeaderLink] that should be attached to the boundary of the toolbar + /// focal area, such as wrapped around the user's selection area. /// - /// [anchor] is a [ValueNotifier] so that [EditorToolbar] can - /// reposition itself as the [Offset] value changes. - final ValueNotifier anchor; + /// The toolbar is positioned relative to this anchor link. + final LeaderLink anchor; /// The [FocusNode] attached to the editor to which this toolbar applies. - final FocusNode? editorFocusNode; + final FocusNode editorFocusNode; /// The [editor] is used to alter document content, such as /// when the user selects a different block format for a /// text blob, e.g., paragraph, header, blockquote, or /// to apply styles to text. - final DocumentEditor? editor; + final Editor? editor; + + final Document document; /// The [composer] provides access to the user's current /// selection within the document, which dictates the /// content that is altered by the toolbar's options. - final DocumentComposer? composer; + final DocumentComposer composer; /// Delegate that instructs the owner of this [EditorToolbar] /// to close the toolbar, such as after submitting a URL @@ -47,52 +58,73 @@ class EditorToolbar extends StatefulWidget { final VoidCallback closeToolbar; @override - _EditorToolbarState createState() => _EditorToolbarState(); + State createState() => _EditorToolbarState(); } class _EditorToolbarState extends State { + late final FollowerAligner _toolbarAligner; + late FollowerBoundary _screenBoundary; + bool _showUrlField = false; - FocusNode? _urlFocusNode; - AttributedTextEditingController? _urlController; + late FocusNode _popoverFocusNode; + late FocusNode _urlFocusNode; + ImeAttributedTextEditingController? _urlController; @override void initState() { super.initState(); + + _toolbarAligner = CupertinoPopoverToolbarAligner(widget.editorViewportKey); + + _popoverFocusNode = FocusNode(); + _urlFocusNode = FocusNode(); - _urlController = SingleLineAttributedTextEditingController(_applyLink); + _urlController = ImeAttributedTextEditingController( + controller: SingleLineAttributedTextEditingController(_applyLink)) // + ..onPerformActionPressed = _onPerformAction + ..text = AttributedText('https://'); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + _screenBoundary = WidgetFollowerBoundary( + boundaryKey: widget.editorViewportKey, + devicePixelRatio: MediaQuery.devicePixelRatioOf(context), + ); } @override void dispose() { - _urlFocusNode!.dispose(); + _urlFocusNode.dispose(); _urlController!.dispose(); + _popoverFocusNode.dispose(); super.dispose(); } - /* /// Returns true if the currently selected text node is capable of being /// transformed into a different type text node, returns false if /// multiple nodes are selected, no node is selected, or the selected /// node is not a standard text block. bool _isConvertibleNode() { - final selection = widget.composer.selection; + final selection = widget.composer.selection!; if (selection.base.nodeId != selection.extent.nodeId) { return false; } - final selectedNode = - widget.editor.document.getNodeById(selection.extent.nodeId); + final selectedNode = widget.document.getNodeById(selection.extent.nodeId); return selectedNode is ParagraphNode || selectedNode is ListItemNode; } - + /// Returns the block type of the currently selected text node. /// /// Throws an exception if the currently selected node is not a text node. _TextType _getCurrentTextType() { - final selectedNode = widget.editor.document - .getNodeById(widget.composer.selection.extent.nodeId); + final selectedNode = + widget.document.getNodeById(widget.composer.selection!.extent.nodeId); if (selectedNode is ParagraphNode) { - final dynamic type = selectedNode.getMetadataValue('blockType'); + final type = selectedNode.getMetadataValue('blockType'); if (type == header1Attribution) { return _TextType.header1; @@ -118,10 +150,10 @@ class _EditorToolbarState extends State { /// /// Throws an exception if the currently selected node is not a text node. TextAlign _getCurrentTextAlignment() { - final selectedNode = widget.editor.document - .getNodeById(widget.composer.selection.extent.nodeId); + final selectedNode = + widget.document.getNodeById(widget.composer.selection!.extent.nodeId); if (selectedNode is ParagraphNode) { - final dynamic align = selectedNode.getMetadataValue('textAlign'); + final align = selectedNode.getMetadataValue('textAlign'); switch (align) { case 'left': return TextAlign.left; @@ -143,13 +175,12 @@ class _EditorToolbarState extends State { /// Returns true if a single text node is selected and that text node /// is capable of respecting alignment, returns false otherwise. bool _isTextAlignable() { - final selection = widget.composer.selection; + final selection = widget.composer.selection!; if (selection.base.nodeId != selection.extent.nodeId) { return false; } - final selectedNode = - widget.editor.document.getNodeById(selection.extent.nodeId); + final selectedNode = widget.document.getNodeById(selection.extent.nodeId); return selectedNode is ParagraphNode; } @@ -158,7 +189,7 @@ class _EditorToolbarState extends State { /// /// For example: convert a paragraph to a blockquote, or a header /// to a list item. - void _convertTextToNewType(_TextType newType) { + void _convertTextToNewType(_TextType? newType) { final existingTextType = _getCurrentTextType(); if (existingTextType == newType) { @@ -167,53 +198,53 @@ class _EditorToolbarState extends State { } if (_isListItem(existingTextType) && _isListItem(newType)) { - widget.editor.executeCommand( - ChangeListItemTypeCommand( - nodeId: widget.composer.selection.extent.nodeId, + widget.editor!.execute([ + ChangeListItemTypeRequest( + nodeId: widget.composer.selection!.extent.nodeId, newType: newType == _TextType.orderedListItem ? ListItemType.ordered : ListItemType.unordered, ), - ); + ]); } else if (_isListItem(existingTextType) && !_isListItem(newType)) { - widget.editor.executeCommand( - ConvertListItemToParagraphCommand( - nodeId: widget.composer.selection.extent.nodeId, - paragraphMetadata: { + widget.editor!.execute([ + ConvertListItemToParagraphRequest( + nodeId: widget.composer.selection!.extent.nodeId, + paragraphMetadata: { 'blockType': _getBlockTypeAttribution(newType), }, ), - ); + ]); } else if (!_isListItem(existingTextType) && _isListItem(newType)) { - widget.editor.executeCommand( - ConvertParagraphToListItemCommand( - nodeId: widget.composer.selection.extent.nodeId, + widget.editor!.execute([ + ConvertParagraphToListItemRequest( + nodeId: widget.composer.selection!.extent.nodeId, type: newType == _TextType.orderedListItem ? ListItemType.ordered : ListItemType.unordered, ), - ); + ]); } else { // Apply a new block type to an existing paragraph node. - final existingNode = widget.editor.document - .getNodeById(widget.composer.selection.extent.nodeId) - as ParagraphNode; - existingNode.putMetadataValue( - 'blockType', _getBlockTypeAttribution(newType)); + widget.editor!.execute([ + ChangeParagraphBlockTypeRequest( + nodeId: widget.composer.selection!.extent.nodeId, + blockType: _getBlockTypeAttribution(newType), + ), + ]); } } - /// Returns true if the given [_TextType] represents an /// ordered or unordered list item, returns false otherwise. - bool _isListItem(_TextType type) { + bool _isListItem(_TextType? type) { return type == _TextType.orderedListItem || type == _TextType.unorderedListItem; } /// Returns the text [Attribution] associated with the given /// [_TextType], e.g., [_TextType.header1] -> [header1Attribution]. - Attribution _getBlockTypeAttribution(_TextType newType) { + Attribution? _getBlockTypeAttribution(_TextType? newType) { switch (newType) { case _TextType.header1: return header1Attribution; @@ -228,36 +259,35 @@ class _EditorToolbarState extends State { return null; } } - */ /// Toggles bold styling for the current selected text. void _toggleBold() { - widget.editor!.executeCommand( - ToggleTextAttributionsCommand( - documentSelection: widget.composer!.selection!, + widget.editor!.execute([ + ToggleTextAttributionsRequest( + documentRange: widget.composer.selection!, attributions: {boldAttribution}, ), - ); + ]); } /// Toggles italic styling for the current selected text. void _toggleItalics() { - widget.editor!.executeCommand( - ToggleTextAttributionsCommand( - documentSelection: widget.composer!.selection!, + widget.editor!.execute([ + ToggleTextAttributionsRequest( + documentRange: widget.composer.selection!, attributions: {italicsAttribution}, ), - ); + ]); } /// Toggles strikethrough styling for the current selected text. void _toggleStrikethrough() { - widget.editor!.executeCommand( - ToggleTextAttributionsCommand( - documentSelection: widget.composer!.selection!, + widget.editor!.execute([ + ToggleTextAttributionsRequest( + documentRange: widget.composer.selection!, attributions: {strikethroughAttribution}, ), - ); + ]); } /// Returns true if the current text selection includes part @@ -276,16 +306,15 @@ class _EditorToolbarState extends State { /// Returns any link-based [AttributionSpan]s that appear partially /// or wholly within the current text selection. Set _getSelectedLinkSpans() { - final selection = widget.composer!.selection!; + final selection = widget.composer.selection!; final baseOffset = (selection.base.nodePosition as TextPosition).offset; final extentOffset = (selection.extent.nodePosition as TextPosition).offset; final selectionStart = min(baseOffset, extentOffset); final selectionEnd = max(baseOffset, extentOffset); - final selectionRange = - SpanRange(start: selectionStart, end: selectionEnd - 1); + final selectionRange = SpanRange(selectionStart, selectionEnd - 1); - final textNode = widget.editor!.document - .getNodeById(selection.extent.nodeId) as TextNode; + final textNode = + widget.document.getNodeById(selection.extent.nodeId) as TextNode; final text = textNode.text; final overlappingLinkAttributions = text.getAttributionSpansInRange( @@ -300,16 +329,15 @@ class _EditorToolbarState extends State { /// Takes appropriate action when the toolbar's link button is /// pressed. void _onLinkPressed() { - final selection = widget.composer!.selection!; + final selection = widget.composer.selection!; final baseOffset = (selection.base.nodePosition as TextPosition).offset; final extentOffset = (selection.extent.nodePosition as TextPosition).offset; final selectionStart = min(baseOffset, extentOffset); final selectionEnd = max(baseOffset, extentOffset); - final selectionRange = - SpanRange(start: selectionStart, end: selectionEnd - 1); + final selectionRange = SpanRange(selectionStart, selectionEnd - 1); - final textNode = widget.editor!.document - .getNodeById(selection.extent.nodeId) as TextNode; + final textNode = + widget.document.getNodeById(selection.extent.nodeId) as TextNode; final text = textNode.text; final overlappingLinkAttributions = text.getAttributionSpansInRange( @@ -341,15 +369,14 @@ class _EditorToolbarState extends State { // the entire link attribution. text.removeAttribution( overlappingLinkSpan.attribution, - SpanRange( - start: overlappingLinkSpan.start, end: overlappingLinkSpan.end), + SpanRange(overlappingLinkSpan.start, overlappingLinkSpan.end), ); } } else { // There are no other links in the selection. Show the URL text field. setState(() { _showUrlField = true; - _urlFocusNode!.requestFocus(); + _urlFocusNode.requestFocus(); }); } } @@ -359,7 +386,7 @@ class _EditorToolbarState extends State { void _applyLink() { final url = _urlController!.text.text; - final selection = widget.composer!.selection!; + final selection = widget.composer.selection!; final baseOffset = (selection.base.nodePosition as TextPosition).offset; final extentOffset = (selection.extent.nodePosition as TextPosition).offset; final selectionStart = min(baseOffset, extentOffset); @@ -367,8 +394,8 @@ class _EditorToolbarState extends State { final selectionRange = TextRange(start: selectionStart, end: selectionEnd - 1); - final textNode = widget.editor!.document - .getNodeById(selection.extent.nodeId) as TextNode; + final textNode = + widget.document.getNodeById(selection.extent.nodeId) as TextNode; final text = textNode.text; final trimmedRange = _trimTextRangeWhitespace(text, selectionRange); @@ -383,8 +410,8 @@ class _EditorToolbarState extends State { _urlController!.clear(); setState(() { _showUrlField = false; - _urlFocusNode! - .unfocus(disposition: UnfocusDisposition.previouslyFocusedChild); + _urlFocusNode.unfocus( + disposition: UnfocusDisposition.previouslyFocusedChild); widget.closeToolbar(); }); } @@ -403,17 +430,16 @@ class _EditorToolbarState extends State { endOffset -= 1; } - return SpanRange(start: startOffset, end: endOffset); + return SpanRange(startOffset, endOffset); } - /* /// Changes the alignment of the current selected text node /// to reflect [newAlignment]. - void _changeAlignment(TextAlign newAlignment) { + void _changeAlignment(TextAlign? newAlignment) { if (newAlignment == null) { return; } - String newAlignmentValue; + String? newAlignmentValue; switch (newAlignment) { case TextAlign.left: case TextAlign.start: @@ -431,8 +457,8 @@ class _EditorToolbarState extends State { break; } - final selectedNode = widget.editor.document - .getNodeById(widget.composer.selection.extent.nodeId) as ParagraphNode; + final selectedNode = widget.document + .getNodeById(widget.composer.selection!.extent.nodeId) as ParagraphNode; selectedNode.putMetadataValue('textAlign', newAlignmentValue); } @@ -441,190 +467,199 @@ class _EditorToolbarState extends State { String _getTextTypeName(_TextType textType) { switch (textType) { case _TextType.header1: + // return AppLocalizations.of(context)!.labelHeader1; return 'Header 1'; case _TextType.header2: + // return AppLocalizations.of(context)!.labelHeader2; return 'Header 2'; case _TextType.header3: + // return AppLocalizations.of(context)!.labelHeader3; return 'Header 3'; case _TextType.paragraph: + // return AppLocalizations.of(context)!.labelParagraph; return 'Paragraph'; case _TextType.blockquote: + // return AppLocalizations.of(context)!.labelBlockquote; return 'Blockquote'; case _TextType.orderedListItem: + // return AppLocalizations.of(context)!.labelOrderedListItem; return 'Ordered List Item'; case _TextType.unorderedListItem: + // return AppLocalizations.of(context)!.labelUnorderedListItem; return 'Unordered List Item'; } - - return ''; } - */ + + void _onPerformAction(TextInputAction action) { + if (action == TextInputAction.done) { + _applyLink(); + } + } @override Widget build(BuildContext context) { - return Stack( + return BuildInOrder( children: [ - // Conditionally display the URL text field below - // the standard toolbar. - if (_showUrlField) - Positioned( - left: widget.anchor.value!.dx, - top: widget.anchor.value!.dy, - child: FractionalTranslation( - translation: const Offset(-0.5, 0.0), - child: _buildUrlField(), - ), - ), - _PositionedToolbar( - anchor: widget.anchor, - composer: widget.composer, - child: ValueListenableBuilder( - valueListenable: widget.composer!.selectionNotifier, - builder: (context, selection, child) { - if (selection == null) { - return const SizedBox(); - } - if (selection.extent.nodePosition is! TextPosition) { - // The user selected non-text content. This toolbar is probably - // about to disappear. Until then, build nothing, because the - // toolbar needs to inspect selected text to build correctly. - return const SizedBox(); - } - - return _buildToolbar(); - }, + FollowerFadeOutBeyondBoundary( + link: widget.anchor, + boundary: _screenBoundary, + child: Follower.withAligner( + link: widget.anchor, + aligner: _toolbarAligner, + boundary: _screenBoundary, + showWhenUnlinked: false, + child: _buildToolbars(), ), ), ], ); } - Widget _buildToolbar() { - return Material( - shape: const StadiumBorder(), - elevation: 5, - clipBehavior: Clip.hardEdge, - child: SizedBox( - height: 40, - child: Row( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - /* - // https://github.com/superlistapp/super_editor/issues/689 - // https://github.com/flutter/flutter/issues/106923 - // Only allow the user to select a new type of text node if - // the currently selected node can be converted. - if (_isConvertibleNode()) ...[ - Tooltip( - message: 'Text Block Type', - child: DropdownButton<_TextType>( - value: _getCurrentTextType(), - items: _TextType.values - .map((textType) => DropdownMenuItem<_TextType>( - value: textType, - child: Padding( - padding: const EdgeInsets.only(left: 16.0), - child: Text(_getTextTypeName(textType)), - ), - )) - .toList(), - icon: const Icon(Icons.arrow_drop_down), - style: TextStyle( - color: Theme.of(context).brightness == Brightness.dark - ? Colors.white - : Colors.black, - fontSize: 12, - ), - underline: const SizedBox(), - elevation: 0, - itemHeight: 48, - onChanged: _convertTextToNewType, - ), - ), - _buildVerticalDivider(), - ], - */ - Center( - child: IconButton( - onPressed: _toggleBold, - icon: const Icon(Icons.format_bold), - splashRadius: 16, - tooltip: 'Bold', - ), - ), - Center( - child: IconButton( - onPressed: _toggleItalics, - icon: const Icon(Icons.format_italic), - splashRadius: 16, - tooltip: 'Italics', - ), - ), - Center( - child: IconButton( - onPressed: _toggleStrikethrough, - icon: const Icon(Icons.strikethrough_s), - splashRadius: 16, - tooltip: 'Strikethrough', - ), - ), - Center( - child: IconButton( - onPressed: _areMultipleLinksSelected() ? null : _onLinkPressed, - icon: const Icon(Icons.link), - color: _isSingleLinkSelected() - ? const Color(0xFF007AFF) - : IconTheme.of(context).color, - splashRadius: 16, - tooltip: 'Link', - ), - ), - // Only display alignment controls if the currently selected text - // node respects alignment. List items, for example, do not. - /* - if (_isTextAlignable()) ...[ - _buildVerticalDivider(), - Tooltip( - message: 'Text Alignment', - child: DropdownButton( - value: _getCurrentTextAlignment(), - items: [ - TextAlign.left, - TextAlign.center, - TextAlign.right, - TextAlign.justify - ] - .map((textAlign) => DropdownMenuItem( - value: textAlign, - child: Padding( - padding: const EdgeInsets.only(left: 8.0), - child: Icon(_buildTextAlignIcon(textAlign)), - ), - )) - .toList(), - icon: const Icon(Icons.arrow_drop_down), - style: const TextStyle( - color: Colors.black, - fontSize: 12, - ), - underline: const SizedBox(), - elevation: 0, - itemHeight: 48, - onChanged: _changeAlignment, - ), - ), - ], - _buildVerticalDivider(), - Center( - child: IconButton( - onPressed: () {}, - icon: const Icon(Icons.more_vert), - splashRadius: 16, - tooltip: 'More Options', - ), - ), - */ + Widget _buildToolbars() { + return SuperEditorPopover( + popoverFocusNode: _popoverFocusNode, + editorFocusNode: widget.editorFocusNode, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildToolbar(), + if (_showUrlField) ...[ + const SizedBox(height: 8), + _buildUrlField(), ], + ], + ), + ); + } + + Widget _buildToolbar() { + return IntrinsicWidth( + child: Material( + shape: const StadiumBorder(), + elevation: 5, + clipBehavior: Clip.hardEdge, + child: SizedBox( + height: 40, + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Only allow the user to select a new type of text node if + // the currently selected node can be converted. + if (_isConvertibleNode()) ...[ + Tooltip( + //message: AppLocalizations.of(context)!.labelTextBlockType, + message: 'Block Type', + child: DropdownButton<_TextType>( + value: _getCurrentTextType(), + items: _TextType.values + .map((textType) => DropdownMenuItem<_TextType>( + value: textType, + child: Padding( + padding: const EdgeInsets.only(left: 16.0), + child: Text(_getTextTypeName(textType)), + ), + )) + .toList(), + icon: const Icon(Icons.arrow_drop_down), + style: const TextStyle( + color: Colors.black, + fontSize: 12, + ), + underline: const SizedBox(), + elevation: 0, + itemHeight: 48, + onChanged: _convertTextToNewType, + ), + ), + _buildVerticalDivider(), + ], + Center( + child: IconButton( + onPressed: _toggleBold, + icon: const Icon(Icons.format_bold), + splashRadius: 16, + //tooltip: AppLocalizations.of(context)!.Bold, + tooltip: 'Bold', + ), + ), + Center( + child: IconButton( + onPressed: _toggleItalics, + icon: const Icon(Icons.format_italic), + splashRadius: 16, + //tooltip: AppLocalizations.of(context)!.labelItalics, + tooltip: 'Italics', + ), + ), + Center( + child: IconButton( + onPressed: _toggleStrikethrough, + icon: const Icon(Icons.strikethrough_s), + splashRadius: 16, + //tooltip: AppLocalizations.of(context)!.labelStrikethrough, + tooltip: 'Strikethrough', + ), + ), + Center( + child: IconButton( + onPressed: + _areMultipleLinksSelected() ? null : _onLinkPressed, + icon: const Icon(Icons.link), + color: _isSingleLinkSelected() + ? const Color(0xFF007AFF) + : IconTheme.of(context).color, + splashRadius: 16, + //tooltip: AppLocalizations.of(context)!.labelLink, + tooltip: 'Link', + ), + ), + // Only display alignment controls if the currently selected text + // node respects alignment. List items, for example, do not. + if (_isTextAlignable()) ...[ + _buildVerticalDivider(), + Tooltip( + //message: AppLocalizations.of(context)!.labelTextAlignment, + message: 'Text Alignment', + child: DropdownButton( + value: _getCurrentTextAlignment(), + items: [ + TextAlign.left, + TextAlign.center, + TextAlign.right, + TextAlign.justify + ] + .map((textAlign) => DropdownMenuItem( + value: textAlign, + child: Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Icon(_buildTextAlignIcon(textAlign)), + ), + )) + .toList(), + icon: const Icon(Icons.arrow_drop_down), + style: const TextStyle( + color: Colors.black, + fontSize: 12, + ), + underline: const SizedBox(), + elevation: 0, + itemHeight: 48, + onChanged: _changeAlignment, + ), + ), + ], + _buildVerticalDivider(), + Center( + child: IconButton( + onPressed: () {}, + icon: const Icon(Icons.more_vert), + splashRadius: 16, + //tooltip: AppLocalizations.of(context)!.labelMoreOptions, + tooltip: 'More Options', + ), + ), + ], + ), ), ), ); @@ -642,9 +677,9 @@ class _EditorToolbarState extends State { child: Row( children: [ Expanded( - child: FocusWithCustomParent( + child: Focus( focusNode: _urlFocusNode, - parentFocusNode: widget.editorFocusNode, + parentNode: _popoverFocusNode, // We use a SuperTextField instead of a TextField because TextField // automatically re-parents its FocusNode, which causes #609. Flutter // #106923 tracks the TextField issue. @@ -656,19 +691,17 @@ class _EditorToolbarState extends State { inputSource: TextInputSource.ime, hintBehavior: HintBehavior.displayHintUntilTextEntered, hintBuilder: (context) { - return Text( - 'Enter a url...', - style: const TextStyle( + return const Text( + 'enter a url...', + style: TextStyle( color: Colors.grey, fontSize: 16, ), ); }, textStyleBuilder: (_) { - return TextStyle( - color: Theme.of(context).brightness == Brightness.dark - ? Colors.white - : Colors.black, + return const TextStyle( + color: Colors.black, fontSize: 16, ); }, @@ -682,7 +715,7 @@ class _EditorToolbarState extends State { padding: EdgeInsets.zero, onPressed: () { setState(() { - _urlFocusNode!.unfocus(); + _urlFocusNode.unfocus(); _showUrlField = false; _urlController!.clear(); }); @@ -694,7 +727,6 @@ class _EditorToolbarState extends State { ); } - /* Widget _buildVerticalDivider() { return Container( width: 1, @@ -702,7 +734,6 @@ class _EditorToolbarState extends State { ); } - IconData _buildTextAlignIcon(TextAlign align) { switch (align) { case TextAlign.left: @@ -716,13 +747,9 @@ class _EditorToolbarState extends State { case TextAlign.justify: return Icons.format_align_justify; } - - return null; } - */ } -/* enum _TextType { header1, header2, @@ -732,7 +759,6 @@ enum _TextType { orderedListItem, unorderedListItem, } -*/ /// Small toolbar that is intended to display over an image and /// offer controls to expand or contract the size of the image. @@ -760,7 +786,7 @@ class ImageFormatToolbar extends StatefulWidget { /// The [composer] provides access to the user's current /// selection within the document, which dictates the /// content that is altered by the toolbar's options. - final DocumentComposer? composer; + final DocumentComposer composer; /// Callback that should update the width of the component with /// the given [nodeId] to match the given [width]. @@ -771,16 +797,16 @@ class ImageFormatToolbar extends StatefulWidget { final VoidCallback closeToolbar; @override - _ImageFormatToolbarState createState() => _ImageFormatToolbarState(); + State createState() => _ImageFormatToolbarState(); } class _ImageFormatToolbarState extends State { void _makeImageConfined() { - widget.setWidth(widget.composer!.selection!.extent.nodeId, null); + widget.setWidth(widget.composer.selection!.extent.nodeId, null); } void _makeImageFullBleed() { - widget.setWidth(widget.composer!.selection!.extent.nodeId, double.infinity); + widget.setWidth(widget.composer.selection!.extent.nodeId, double.infinity); } @override @@ -789,8 +815,9 @@ class _ImageFormatToolbarState extends State { anchor: widget.anchor, composer: widget.composer, child: ValueListenableBuilder( - valueListenable: widget.composer!.selectionNotifier, + valueListenable: widget.composer.selectionNotifier, builder: (context, selection, child) { + //appLog.fine("Building image toolbar. Selection: $selection"); if (selection == null) { return const SizedBox(); } @@ -825,6 +852,7 @@ class _ImageFormatToolbarState extends State { onPressed: _makeImageConfined, icon: const Icon(Icons.photo_size_select_large), splashRadius: 16, + //tooltip: AppLocalizations.of(context)!.labelBold, tooltip: 'Bold', ), ), @@ -833,6 +861,7 @@ class _ImageFormatToolbarState extends State { onPressed: _makeImageFullBleed, icon: const Icon(Icons.photo_size_select_actual), splashRadius: 16, + //tooltip: AppLocalizations.of(context)!.labelItalics, tooltip: 'Italics', ), ), @@ -853,7 +882,7 @@ class _PositionedToolbar extends StatelessWidget { }) : super(key: key); final ValueNotifier anchor; - final DocumentComposer? composer; + final DocumentComposer composer; final Widget child; @override @@ -861,12 +890,16 @@ class _PositionedToolbar extends StatelessWidget { return ValueListenableBuilder( valueListenable: anchor, builder: (context, offset, _) { - if (offset == null || composer!.selection == null) { + //appLog.fine( + // "(Re)Building _PositionedToolbar widget due to anchor change"); + if (offset == null || composer.selection == null) { + //appLog.fine("Anchor is null. Building an empty box."); // When no anchor position is available, or the user hasn't // selected any text, show nothing. return const SizedBox(); } + //appLog.fine("Anchor is non-null: $offset, child: $child"); return SizedBox.expand( child: Stack( children: [ diff --git a/pubspec.lock b/pubspec.lock index 624115ec5..94910228d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: archive - sha256: "7e0d52067d05f2e0324268097ba723b71cb41ac8a6a2b24d1edf9c536b987b03" + sha256: "7b875fd4a20b165a3084bd2d210439b22ebc653f21cea4842729c0c30c82596b" url: "https://pub.dev" source: hosted - version: "3.4.6" + version: "3.4.9" args: dependency: transitive description: @@ -42,12 +42,13 @@ packages: source: hosted version: "2.11.0" attributed_text: - dependency: transitive + dependency: "direct overridden" description: - name: attributed_text - sha256: e43495051b63e6cdbe96aa62123974074cca109d9c56f74ce2ffaec8060e044e - url: "https://pub.dev" - source: hosted + path: attributed_text + ref: stable + resolved-ref: c040c690f1dc0f7c482d8ece02622c200cb8e1b1 + url: "https://github.com/superlistapp/super_editor" + source: git version: "0.2.2" barcode: dependency: transitive @@ -102,10 +103,10 @@ packages: dependency: transitive description: name: build_daemon - sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65" + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.0.1" build_resolvers: dependency: transitive description: @@ -118,10 +119,10 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "10c6bcdbf9d049a0b666702cf1cee4ddfdc38f02a19d35ae392863b47519848b" + sha256: "67d591d602906ef9201caf93452495ad1812bea2074f04e25dbd7c133785821b" url: "https://pub.dev" source: hosted - version: "2.4.6" + version: "2.4.7" build_runner_core: dependency: transitive description: @@ -142,26 +143,42 @@ packages: dependency: "direct main" description: name: built_value - sha256: a8de5955205b4d1dbbbc267daddf2178bd737e4bab8987c04a500478c9651e74 + sha256: "69acb7007eb2a31dc901512bfe0f7b767168be34cb734835d54c070bfa74c1b2" url: "https://pub.dev" source: hosted - version: "8.6.3" + version: "8.8.0" built_value_generator: dependency: "direct dev" description: name: built_value_generator - sha256: a7a20bd4a943316c46c6e89b1a5631a2dace50ca6742b73d0b719fd243a7da00 + sha256: "78680d78a6cab222fc5725ffa21065a05bba951452ae84d08b3c0e150fd3f9f6" url: "https://pub.dev" source: hosted - version: "8.6.3" + version: "8.8.0" cached_network_image: dependency: "direct main" description: name: cached_network_image - sha256: ebab9f6c55a7aa8d62cdfe0877f8b5ce806c1956b0a244ba39fcadeb0e1aad85 + sha256: f98972704692ba679db144261172a8e20feb145636c617af0eb4022132a6797f + url: "https://pub.dev" + source: hosted + version: "3.3.0" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "56aa42a7a01e3c9db8456d9f3f999931f1e05535b5a424271e9a38cabf066613" url: "https://pub.dev" source: hosted version: "3.0.0" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "759b9a9f8f6ccbb66c185df805fac107f05730b1dab9c64626d1008cca532257" + url: "https://pub.dev" + source: hosted + version: "1.1.0" characters: dependency: transitive description: @@ -222,18 +239,18 @@ packages: dependency: transitive description: name: code_builder - sha256: "1be9be30396d7e4c0db42c35ea6ccd7cc6a1e19916b5dc64d6ac216b5544d677" + sha256: b2151ce26a06171005b379ecff6e08d34c470180ffe16b8e14b6d52be292b55f url: "https://pub.dev" source: hosted - version: "4.7.0" + version: "4.8.0" collection: dependency: "direct main" description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.18.0" console: dependency: transitive description: @@ -262,18 +279,18 @@ packages: dependency: transitive description: name: coverage - sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097" + sha256: "595a29b55ce82d53398e1bcc2cba525d7bd7c59faeb2d2540e9d42c390cfeeeb" url: "https://pub.dev" source: hosted - version: "1.6.3" + version: "1.6.4" cross_file: dependency: transitive description: name: cross_file - sha256: "445db18de832dba8d851e287aff8ccf169bed30d2e94243cb54c7d2f1ed2142c" + sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e url: "https://pub.dev" source: hosted - version: "0.3.3+6" + version: "0.3.3+8" crypto: dependency: transitive description: @@ -310,10 +327,10 @@ packages: dependency: "direct main" description: name: device_info_plus - sha256: "7035152271ff67b072a211152846e9f1259cf1be41e34cd3e0b5463d2d6b8419" + sha256: "0042cb3b2a76413ea5f8a2b40cec2a33e01d0c937e91f0f7c211fde4f7739ba6" url: "https://pub.dev" source: hosted - version: "9.1.0" + version: "9.1.1" device_info_plus_platform_interface: dependency: transitive description: @@ -423,14 +440,6 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_blurhash: - dependency: transitive - description: - name: flutter_blurhash - sha256: "05001537bd3fac7644fa6558b09ec8c0a3f2eba78c0765f88912882b1331a5c6" - url: "https://pub.dev" - source: hosted - version: "0.7.0" flutter_cache_manager: dependency: transitive description: @@ -469,10 +478,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: f185ac890306b5779ecbd611f52502d8d4d63d27703ef73161ca0407e815f02c + sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da url: "https://pub.dev" source: hosted - version: "2.0.16" + version: "2.0.17" flutter_redux: dependency: "direct main" description: @@ -485,10 +494,10 @@ packages: dependency: "direct main" description: name: flutter_slidable - sha256: cc4231579e3eae41ae166660df717f4bad1359c87f4a4322ad8ba1befeb3d2be + sha256: "19ed4813003a6ff4e9c6bcce37e792a2a358919d7603b2b31ff200229191e44c" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.0.1" flutter_staggered_grid_view: dependency: "direct main" description: @@ -527,10 +536,10 @@ packages: dependency: transitive description: name: follow_the_leader - sha256: "40112c4fa8fdc9e60c3350f09e5b9c86e1205f4f8427feee15ef8da4c60c635c" + sha256: "71f4bfca904974a98d21558bbf7489e1262da6ac46de912791fe84cf6516b9ae" url: "https://pub.dev" source: hosted - version: "0.0.4+5" + version: "0.0.4+7" frontend_server_client: dependency: transitive description: @@ -580,18 +589,18 @@ packages: dependency: transitive description: name: google_sign_in_ios - sha256: "974944859f9cd40eb8a15b3fe8efb2d47fb7e99438f763f61a1ccd28d74ff4ce" + sha256: "81495441405c138e3c638f5097bebaa0db644567b3976e08944cfb8926ff2e6d" url: "https://pub.dev" source: hosted - version: "5.6.4" + version: "5.6.5" google_sign_in_platform_interface: dependency: transitive description: name: google_sign_in_platform_interface - sha256: "35ceee5f0eadc1c07b0b4af7553246e315c901facbb7d3dadf734ba2693ceec4" + sha256: e10eaaa30a0cb03af12dd324fb2e630ac7e9d854d0530f7a87a4d825031f9a4a url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.3" google_sign_in_web: dependency: transitive description: @@ -692,10 +701,10 @@ packages: dependency: transitive description: name: image_picker_android - sha256: "0c7b83bbe2980c8a8e36e974f055e11e51675784e13a4762889feed0f3937ff2" + sha256: d6a6e78821086b0b737009b09363018309bbc6de3fd88cc5c26bc2bb44a4957f url: "https://pub.dev" source: hosted - version: "0.8.8+1" + version: "0.8.8+2" image_picker_for_web: dependency: transitive description: @@ -708,10 +717,10 @@ packages: dependency: transitive description: name: image_picker_ios - sha256: c5538cacefacac733c724be7484377923b476216ad1ead35a0d2eadcdc0fc497 + sha256: "76ec722aeea419d03aa915c2c96bf5b47214b053899088c9abb4086ceecf97a7" url: "https://pub.dev" source: hosted - version: "0.8.8+2" + version: "0.8.8+4" image_picker_linux: dependency: transitive description: @@ -764,10 +773,10 @@ packages: dependency: transitive description: name: in_app_purchase_android - sha256: "63997b855f10799a1022939bbf02e3f59b6f400f4deee858f46fd528df5f5fab" + sha256: c4b84caa4e2c7ffebda444c5033fd8423cc3a45a6e1066929bbbcd4daf665db5 url: "https://pub.dev" source: hosted - version: "0.3.0+13" + version: "0.3.0+15" in_app_purchase_platform_interface: dependency: transitive description: @@ -780,26 +789,26 @@ packages: dependency: transitive description: name: in_app_purchase_storekit - sha256: "88afd256c7605d431f0ce29d0161f9554851f90ecb92ceb9e18196c4e7858d52" + sha256: "29526f5ce85bd908b4cacdadb2e8ef299bccbb516b90d2881805343f868502ab" url: "https://pub.dev" source: hosted - version: "0.3.6+7" + version: "0.3.7" in_app_review: dependency: "direct main" description: name: in_app_review - sha256: "16328b8202d36522322b95804ae5d975577aa9f584d634985849ba1099645850" + sha256: "41ec6f30427ab09eb6ae1c85c4a2a624a145fc5d726f023de4d97170ec9e5466" url: "https://pub.dev" source: hosted - version: "2.0.6" + version: "2.0.8" in_app_review_platform_interface: dependency: transitive description: name: in_app_review_platform_interface - sha256: b12ec9aaf6b34d3a72aa95895eb252b381896246bdad4ef378d444affe8410ef + sha256: fed2c755f2125caa9ae10495a3c163aa7fab5af3585a9c62ef4a6920c5b45f10 url: "https://pub.dev" source: hosted - version: "2.0.4" + version: "2.0.5" intl: dependency: "direct main" description: @@ -844,10 +853,10 @@ packages: dependency: transitive description: name: linkify - sha256: bdfbdafec6cdc9cd0ebb333a868cafc046714ad508e48be8095208c54691d959 + sha256: "4139ea77f4651ab9c315b577da2dd108d9aa0bd84b5d03d33323f1970c645832" url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "5.0.0" local_auth: dependency: "direct main" description: @@ -860,18 +869,18 @@ packages: dependency: transitive description: name: local_auth_android - sha256: "9ad0b1ffa6f04f4d91e38c2d4c5046583e23f4cae8345776a994e8670df57fb1" + sha256: df4ccb3193525b8a60c78a5ca7bf188a47705bcf77bcc837a6b2cf6da64ae0e2 url: "https://pub.dev" source: hosted - version: "1.0.34" + version: "1.0.35" local_auth_ios: dependency: transitive description: name: local_auth_ios - sha256: "26a8d1ad0b4ef6f861d29921be8383000fda952e323a5b6752cf82ca9cf9a7a9" + sha256: "8293faf72ef0ac4710f209edd03916c2d4c1eeab0483bdcf9b2e659c2f7d737b" url: "https://pub.dev" source: hosted - version: "1.1.4" + version: "1.1.5" local_auth_platform_interface: dependency: transitive description: @@ -940,10 +949,10 @@ packages: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" mime: dependency: transitive description: @@ -964,10 +973,10 @@ packages: dependency: "direct dev" description: name: msix - sha256: "6e76e2491d5c809d784ce2b68e6c3426097fb5c68e61fe121c8c3341ab89bf46" + sha256: "519b183d15dc9f9c594f247e2d2339d855cf0eaacc30e19b128e14f3ecc62047" url: "https://pub.dev" source: hosted - version: "3.16.4" + version: "3.16.7" navigation_builder: dependency: "direct overridden" description: @@ -988,10 +997,10 @@ packages: dependency: transitive description: name: octo_image - sha256: "107f3ed1330006a3bea63615e81cf637433f5135a52466c7caa0e7152bca9143" + sha256: "45b40f99622f11901238e18d48f5f12ea36426d8eced9f4cbf58479c7aa2430d" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "2.0.0" overflow_view: dependency: "direct main" description: @@ -1028,10 +1037,10 @@ packages: dependency: transitive description: name: package_info_plus - sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017" + sha256: "88bc797f44a94814f2213db1c9bd5badebafdfb8290ca9f78d4b9ee2a3db4d79" url: "https://pub.dev" source: hosted - version: "4.2.0" + version: "5.0.1" package_info_plus_platform_interface: dependency: transitive description: @@ -1068,10 +1077,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "6b8b19bd80da4f11ce91b2d1fb931f3006911477cec227cce23d3253d80df3f1" + sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.1" path_provider_foundation: dependency: transitive description: @@ -1108,58 +1117,66 @@ packages: dependency: transitive description: name: pdf - sha256: "9f75fc7f5580ea5e635b5724de58fb27f684c9ad03ed46fdc1aac768e4557315" + sha256: "93cbb2c06de9bab91844550f19896b2373e7a5ce25173995e7e5ec5e1741429d" url: "https://pub.dev" source: hosted - version: "3.10.4" + version: "3.10.7" permission_handler: dependency: "direct main" description: name: permission_handler - sha256: "284a66179cabdf942f838543e10413246f06424d960c92ba95c84439154fcac8" + sha256: "860c6b871c94c78e202dc69546d4d8fd84bd59faeb36f8fb9888668a53ff4f78" url: "https://pub.dev" source: hosted - version: "11.0.1" + version: "11.1.0" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: f9fddd3b46109bd69ff3f9efa5006d2d309b7aec0f3c1c5637a60a2d5659e76e + sha256: "2f1bec180ee2f5665c22faada971a8f024761f632e93ddc23310487df52dcfa6" url: "https://pub.dev" source: hosted - version: "11.1.0" + version: "12.0.1" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5" + sha256: "1a816084338ada8d574b1cb48390e6e8b19305d5120fe3a37c98825bacc78306" url: "https://pub.dev" source: hosted - version: "9.1.4" + version: "9.2.0" + permission_handler_html: + dependency: transitive + description: + name: permission_handler_html + sha256: "11b762a8c123dced6461933a88ea1edbbe036078c3f9f41b08886e678e7864df" + url: "https://pub.dev" + source: hosted + version: "0.1.0+2" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - sha256: "6760eb5ef34589224771010805bea6054ad28453906936f843a8cc4d3a55c4a4" + sha256: d87349312f7eaf6ce0adaf668daf700ac5b06af84338bd8b8574dfbd93ffe1a1 url: "https://pub.dev" source: hosted - version: "3.12.0" + version: "4.0.2" permission_handler_windows: dependency: transitive description: name: permission_handler_windows - sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098 + sha256: "1e8640c1e39121128da6b816d236e714d2cf17fac5a105dd6acdd3403a628004" url: "https://pub.dev" source: hosted - version: "0.1.3" + version: "0.2.0" petitparser: dependency: transitive description: name: petitparser - sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "6.0.2" pigeon: dependency: transitive description: @@ -1188,26 +1205,26 @@ packages: dependency: transitive description: name: platform - sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102 url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.2" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d + sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 url: "https://pub.dev" source: hosted - version: "2.1.6" + version: "2.1.7" pointer_interceptor: dependency: "direct main" description: name: pointer_interceptor - sha256: "7626e034489820fd599380d2bb4d3f4a0a5e3529370b62bfce53ab736b91adb2" + sha256: adf7a637f97c077041d36801b43be08559fd4322d2127b3f20bb7be1b9eebc22 url: "https://pub.dev" source: hosted - version: "0.9.3+6" + version: "0.9.3+7" pointycastle: dependency: transitive description: @@ -1228,10 +1245,10 @@ packages: dependency: "direct main" description: name: printing - sha256: e7c383dca95ee7b88c02dc1c66638628d3dcdc2fb2cc47e7a595facd47e46b56 + sha256: ad39a42a5f83125952457dfd94f395c8cf0eb1f7759583dadb769be5c7f99d24 url: "https://pub.dev" source: hosted - version: "5.11.0" + version: "5.11.1" process: dependency: transitive description: @@ -1325,34 +1342,34 @@ packages: dependency: transitive description: name: sentry - sha256: "9cfd325611ab54b57d5e26957466823f05bea9d6cfcc8d48f11817b8bcedf0d1" + sha256: e7ded42974bac5f69e4ca4ddc57d30499dd79381838f24b7e8fd9aa4139e7b79 url: "https://pub.dev" source: hosted - version: "7.12.0" + version: "7.13.2" sentry_flutter: dependency: "direct main" description: name: sentry_flutter - sha256: "0cd7d622cb63c94fd1b2f87ab508e158b950bd281e2a80f327ebf73bb217eaf3" + sha256: d6f55ec7a1f681784165021f749007712a72ff57eadf91e963331b6ae326f089 url: "https://pub.dev" source: hosted - version: "7.12.0" + version: "7.13.2" share_plus: dependency: "direct main" description: name: share_plus - sha256: "2dafa6c1f8d8ee67b0e0587881947b78baab4671b7c08792cf91279e7ac14192" + sha256: f74fc3f1cbd99f39760182e176802f693fa0ec9625c045561cfad54681ea93dd url: "https://pub.dev" source: hosted - version: "7.2.0" + version: "7.2.1" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - sha256: "357412af4178d8e11d14f41723f80f12caea54cf0d5cd29af9dcdab85d58aea7" + sha256: df08bc3a07d01f5ea47b45d03ffcba1fa9cd5370fb44b3f38c70e42cced0f956 url: "https://pub.dev" source: hosted - version: "3.3.0" + version: "3.3.1" shared_preferences: dependency: "direct main" description: @@ -1397,10 +1414,10 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf + sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" shared_preferences_windows: dependency: transitive description: @@ -1522,18 +1539,18 @@ packages: dependency: transitive description: name: sqflite_common - sha256: "1b92f368f44b0dee2425bb861cfa17b6f6cf3961f762ff6f941d20b33355660a" + sha256: bb4738f15b23352822f4c42a531677e5c6f522e079461fd240ead29d8d8a54a6 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.5.0+2" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" states_rebuilder: dependency: "direct main" description: @@ -1546,10 +1563,10 @@ packages: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" stream_transform: dependency: transitive description: @@ -1567,29 +1584,32 @@ packages: source: hosted version: "1.2.0" super_editor: - dependency: "direct main" + dependency: "direct overridden" description: - name: super_editor - sha256: "2d5acf95449f53eec1c7d0788530b3a667758224a1e4aa4164bd5d6c0d159bab" - url: "https://pub.dev" - source: hosted + path: super_editor + ref: stable + resolved-ref: c040c690f1dc0f7c482d8ece02622c200cb8e1b1 + url: "https://github.com/superlistapp/super_editor" + source: git version: "0.2.6" super_editor_markdown: - dependency: "direct main" + dependency: "direct overridden" description: - name: super_editor_markdown - sha256: "2515d0183ee21aa22d577e95e80b1e4bc1b9ce0f651127d40844be7cb58b5330" - url: "https://pub.dev" - source: hosted + path: super_editor_markdown + ref: stable + resolved-ref: c040c690f1dc0f7c482d8ece02622c200cb8e1b1 + url: "https://github.com/superlistapp/super_editor" + source: git version: "0.1.5" super_text_layout: - dependency: transitive + dependency: "direct overridden" description: - name: super_text_layout - sha256: "2f2a8b36553f775c390924f079b5a8ba6c717b0885f44d80a9602bfa182b6f9f" - url: "https://pub.dev" - source: hosted - version: "0.1.7" + path: super_text_layout + ref: stable + resolved-ref: c040c690f1dc0f7c482d8ece02622c200cb8e1b1 + url: "https://github.com/superlistapp/super_editor" + source: git + version: "0.1.8" sync_http: dependency: transitive description: @@ -1618,34 +1638,34 @@ packages: dependency: "direct dev" description: name: test - sha256: "13b41f318e2a5751c3169137103b60c584297353d4b1761b66029bae6411fe46" + sha256: a1f7595805820fcc05e5c52e3a231aedd0b72972cb333e8c738a8b1239448b6f url: "https://pub.dev" source: hosted - version: "1.24.3" + version: "1.24.9" test_api: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.1" test_core: dependency: transitive description: name: test_core - sha256: "99806e9e6d95c7b059b7a0fc08f07fc53fabe54a829497f0d9676299f1e8637e" + sha256: a757b14fc47507060a162cc2530d9a4a2f92f5100a952c7443b5cad5ef5b106a url: "https://pub.dev" source: hosted - version: "0.5.3" + version: "0.5.9" timeago: dependency: "direct main" description: name: timeago - sha256: "4addcda362e51f23cf7ae2357fccd053f29d59b4ddd17fb07fc3e7febb47a456" + sha256: c44b80cbc6b44627c00d76960f2af571f6f50e5dbedef4d9215d455e4335165b url: "https://pub.dev" source: hosted - version: "3.5.0" + version: "3.6.0" timing: dependency: transitive description: @@ -1682,66 +1702,66 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "47e208a6711459d813ba18af120d9663c20bdf6985d6ad39fe165d2538378d27" + sha256: b1c9e98774adf8820c96fbc7ae3601231d324a7d5ebd8babe27b6dfac91357ba url: "https://pub.dev" source: hosted - version: "6.1.14" + version: "6.2.1" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: b04af59516ab45762b2ca6da40fa830d72d0f6045cd97744450b73493fa76330 + sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "6.2.0" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "7c65021d5dee51813d652357bc65b8dd4a6177082a9966bc8ba6ee477baa795f" + sha256: bba3373219b7abb6b5e0d071b0fe66dfbe005d07517a68e38d4fc3638f35c6d3 url: "https://pub.dev" source: hosted - version: "6.1.5" + version: "6.2.1" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: b651aad005e0cb06a01dbd84b428a301916dc75f0e7ea6165f80057fee2d8e8e + sha256: "9f2d390e096fdbe1e6e6256f97851e51afc2d9c423d3432f1d6a02a8a9a8b9fd" url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.1.0" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: b55486791f666e62e0e8ff825e58a023fd6b1f71c49926483f1128d3bbd8fe88 + sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 url: "https://pub.dev" source: hosted - version: "3.0.7" + version: "3.1.0" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: "95465b39f83bfe95fcb9d174829d6476216f2d548b79c38ab2506e0458787618" + sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50" url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.2.0" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "2942294a500b4fa0b918685aff406773ba0a4cd34b7f42198742a94083020ce5" + sha256: "138bd45b3a456dcfafc46d1a146787424f8d2edfbf2809c9324361e58f851cf7" url: "https://pub.dev" source: hosted - version: "2.0.20" + version: "2.2.1" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "95fef3129dc7cfaba2bc3d5ba2e16063bb561fc6d78e63eee16162bc70029069" + sha256: "7754a1ad30ee896b265f8d14078b0513a4dba28d358eabb9d5f339886f4a1adc" url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.1.0" uuid: dependency: transitive description: @@ -1778,10 +1798,10 @@ packages: dependency: transitive description: name: vm_service - sha256: c620a6f783fa22436da68e42db7ebbf18b8c44b9a46ab911f666ff09ffd9153f + sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583 url: "https://pub.dev" source: hosted - version: "11.7.1" + version: "11.10.0" vs_scrollbar: dependency: transitive description: @@ -1802,10 +1822,10 @@ packages: dependency: transitive description: name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 url: "https://pub.dev" source: hosted - version: "0.1.4-beta" + version: "0.3.0" web_socket_channel: dependency: transitive description: @@ -1834,34 +1854,34 @@ packages: dependency: "direct main" description: name: webview_flutter - sha256: c1ab9b81090705c6069197d9fdc1625e587b52b8d70cdde2339d177ad0dbb98e + sha256: "42393b4492e629aa3a88618530a4a00de8bb46e50e7b3993fedbfdc5352f0dbf" url: "https://pub.dev" source: hosted - version: "4.4.1" + version: "4.4.2" webview_flutter_android: dependency: transitive description: name: webview_flutter_android - sha256: b0cd33dd7d3dd8e5f664e11a19e17ba12c352647269921a3b568406b001f1dff + sha256: "8326ee235f87605a2bfc444a4abc897f4abc78d83f054ba7d3d1074ce82b4fbf" url: "https://pub.dev" source: hosted - version: "3.12.0" + version: "3.12.1" webview_flutter_platform_interface: dependency: transitive description: name: webview_flutter_platform_interface - sha256: "6d9213c65f1060116757a7c473247c60f3f7f332cac33dc417c9e362a9a13e4f" + sha256: "68e86162aa8fc646ae859e1585995c096c95fc2476881fa0c4a8d10f56013a5a" url: "https://pub.dev" source: hosted - version: "2.6.0" + version: "2.8.0" webview_flutter_wkwebview: dependency: transitive description: name: webview_flutter_wkwebview - sha256: "30b9af6bdd457b44c08748b9190d23208b5165357cc2eb57914fee1366c42974" + sha256: accdaaa49a2aca2dc3c3230907988954cdd23fed0a19525d6c9789d380f4dc76 url: "https://pub.dev" source: hosted - version: "3.9.1" + version: "3.9.4" widget_kit_plugin: dependency: "direct main" description: @@ -1874,10 +1894,10 @@ packages: dependency: transitive description: name: win32 - sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" + sha256: b0f37db61ba2f2e9b7a78a1caece0052564d1bc70668156cf3a29d676fe4e574 url: "https://pub.dev" source: hosted - version: "5.0.9" + version: "5.1.1" win32_registry: dependency: transitive description: @@ -1906,10 +1926,10 @@ packages: dependency: transitive description: name: xml - sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.5.0" yaml: dependency: transitive description: @@ -1919,5 +1939,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.1.0 <4.0.0" - flutter: ">=3.13.0" + dart: ">=3.2.0 <4.0.0" + flutter: ">=3.16.0" diff --git a/pubspec.yaml b/pubspec.yaml index 7bafb445e..e65877d30 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,7 +39,8 @@ dependencies: built_value: ^8.1.2 built_collection: ^5.1.0 memoize: ^3.0.0 - cached_network_image: 3.0.0 # imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet, + #cached_network_image: 3.0.0 # imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet, + cached_network_image: ^3.3.0 url_launcher: ^6.0.20 share_plus: ^7.1.0 intl: 0.17.0 @@ -72,13 +73,14 @@ dependencies: contacts_service: ^0.6.3 diacritic: ^0.1.3 states_rebuilder: ^6.2.0 - super_editor: ^0.2.6 - super_editor_markdown: ^0.1.5 + #super_editor: ^0.2.6 + #super_editor_markdown: ^0.1.5 #markdown: ^5.0.0 # REMOVE THIS - #super_editor: + #super_editor_markdown: # git: # url: https://github.com/superlistapp/super_editor.git - # path: super_editor + # path: super_editor_markdown + # ref: stable html2md: ^1.2.6 printing: ^5.11.0 image_cropper: ^4.0.1 @@ -101,6 +103,26 @@ dependency_overrides: intl: any navigation_builder: ^0.0.3 states_rebuilder: ^6.3.0 + super_editor: + git: + url: https://github.com/superlistapp/super_editor + path: super_editor + ref: stable + super_editor_markdown: + git: + url: https://github.com/superlistapp/super_editor + path: super_editor_markdown + ref: stable + super_text_layout: + git: + url: https://github.com/superlistapp/super_editor + path: super_text_layout + ref: stable + attributed_text: + git: + url: https://github.com/superlistapp/super_editor + path: attributed_text + ref: stable dev_dependencies: flutter_driver: diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt index 930d2071a..903f4899d 100644 --- a/windows/flutter/CMakeLists.txt +++ b/windows/flutter/CMakeLists.txt @@ -10,6 +10,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") @@ -92,7 +97,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS