diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_accessory.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_accessory.dart index 8a88316473..341d4095b0 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_accessory.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_accessory.dart @@ -20,14 +20,36 @@ class GridCellAccessoryBuildContext { }); } -abstract class GridCellAccessory implements Widget { +class GridCellAccessoryBuilder { + final GlobalKey _key = GlobalKey(); + + final Widget Function(Key key) _builder; + + GridCellAccessoryBuilder({required Widget Function(Key key) builder}) + : _builder = builder; + + Widget build() => _builder(_key); + + void onTap() { + (_key.currentState as GridCellAccessoryState).onTap(); + } + + bool enable() { + if (_key.currentState == null) { + return true; + } + return (_key.currentState as GridCellAccessoryState).enable(); + } +} + +abstract class GridCellAccessoryState { void onTap(); // The accessory will be hidden if enable() return false; bool enable() => true; } -class PrimaryCellAccessory extends StatelessWidget with GridCellAccessory { +class PrimaryCellAccessory extends StatefulWidget { final VoidCallback onTapCallback; final bool isCellEditing; const PrimaryCellAccessory({ @@ -36,9 +58,15 @@ class PrimaryCellAccessory extends StatelessWidget with GridCellAccessory { Key? key, }) : super(key: key); + @override + State createState() => _PrimaryCellAccessoryState(); +} + +class _PrimaryCellAccessoryState extends State + with GridCellAccessoryState { @override Widget build(BuildContext context) { - if (isCellEditing) { + if (widget.isCellEditing) { return const SizedBox(); } else { final theme = context.watch(); @@ -53,10 +81,10 @@ class PrimaryCellAccessory extends StatelessWidget with GridCellAccessory { } @override - void onTap() => onTapCallback(); + void onTap() => widget.onTapCallback(); @override - bool enable() => !isCellEditing; + bool enable() => !widget.isCellEditing; } class AccessoryHover extends StatefulWidget { @@ -170,7 +198,7 @@ class _Background extends StatelessWidget { } class CellAccessoryContainer extends StatelessWidget { - final List accessories; + final List accessories; const CellAccessoryContainer({required this.accessories, Key? key}) : super(key: key); @@ -186,7 +214,7 @@ class CellAccessoryContainer extends StatelessWidget { width: 26, height: 26, padding: const EdgeInsets.all(3), - child: accessory, + child: accessory.build(), ), ); return GestureDetector( diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_builder.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_builder.dart index 0a7c3a48a7..1f885c3821 100755 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_builder.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_builder.dart @@ -94,7 +94,7 @@ abstract class CellEditable { ValueNotifier get onCellEditing; } -typedef AccessoryBuilder = List Function( +typedef AccessoryBuilder = List Function( GridCellAccessoryBuildContext buildContext); abstract class CellAccessory extends Widget { @@ -125,8 +125,8 @@ abstract class GridCellWidget extends StatefulWidget final ValueNotifier onCellEditing = ValueNotifier(false); @override - List Function(GridCellAccessoryBuildContext buildContext)? - get accessoryBuilder => null; + List Function( + GridCellAccessoryBuildContext buildContext)? get accessoryBuilder => null; @override final GridCellFocusListener beginFocus = GridCellFocusListener(); diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_container.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_container.dart index eea58775dd..bbe22ffe8f 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_container.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_container.dart @@ -80,7 +80,7 @@ class CellContainer extends StatelessWidget { class _GridCellEnterRegion extends StatelessWidget { final Widget child; - final List accessories; + final List accessories; const _GridCellEnterRegion( {required this.child, required this.accessories, Key? key}) : super(key: key); diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_cell.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_cell.dart index fbdc2ec9ab..2b7ef9b6ab 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_cell.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_cell.dart @@ -64,6 +64,7 @@ class _DateCellState extends GridCellState { return Popover( controller: _popover, offset: const Offset(0, 20), + direction: PopoverDirection.bottomWithLeftAligned, child: SizedBox.expand( child: GestureDetector( behavior: HitTestBehavior.opaque, diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/url_cell/cell_editor.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/url_cell/cell_editor.dart index b9e0f1ef48..e390104187 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/url_cell/cell_editor.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/url_cell/cell_editor.dart @@ -6,56 +6,13 @@ import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; -class URLCellEditor extends StatefulWidget with FlowyOverlayDelegate { +class URLCellEditor extends StatefulWidget { final GridURLCellController cellController; - final VoidCallback completed; - const URLCellEditor( - {required this.cellController, required this.completed, Key? key}) + const URLCellEditor({required this.cellController, Key? key}) : super(key: key); @override State createState() => _URLCellEditorState(); - - static void show( - BuildContext context, - GridURLCellController cellContext, - VoidCallback completed, - ) { - FlowyOverlay.of(context).remove(identifier()); - final editor = URLCellEditor( - cellController: cellContext, - completed: completed, - ); - - // - FlowyOverlay.of(context).insertWithAnchor( - widget: OverlayContainer( - constraints: BoxConstraints.loose(const Size(300, 160)), - child: SizedBox( - width: 200, - child: Padding(padding: const EdgeInsets.all(6), child: editor), - ), - ), - identifier: URLCellEditor.identifier(), - anchorContext: context, - anchorDirection: AnchorDirection.bottomWithCenterAligned, - delegate: editor, - ); - } - - static String identifier() { - return (URLCellEditor).toString(); - } - - @override - bool asBarrier() { - return true; - } - - @override - void didRemove() { - completed(); - } } class _URLCellEditorState extends State { @@ -114,3 +71,25 @@ class _URLCellEditorState extends State { } } } + +class URLEditorPopover extends StatelessWidget { + final GridURLCellController cellController; + const URLEditorPopover({required this.cellController, Key? key}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return OverlayContainer( + constraints: BoxConstraints.loose(const Size(300, 160)), + child: SizedBox( + width: 200, + child: Padding( + padding: const EdgeInsets.all(6), + child: URLCellEditor( + cellController: cellController, + ), + ), + ), + ); + } +} diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/url_cell/url_cell.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/url_cell/url_cell.dart index 102595e166..27e7d4433c 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/url_cell/url_cell.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/url_cell/url_cell.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:app_flowy/plugins/grid/application/cell/url_cell_bloc.dart'; import 'package:app_flowy/workspace/presentation/home/toast.dart'; +import 'package:appflowy_popover/popover.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/theme.dart'; @@ -48,27 +49,37 @@ class GridURLCell extends GridCellWidget { @override GridCellState createState() => _GridURLCellState(); - GridCellAccessory accessoryFromType( + GridCellAccessoryBuilder accessoryFromType( GridURLCellAccessoryType ty, GridCellAccessoryBuildContext buildContext) { switch (ty) { case GridURLCellAccessoryType.edit: final cellController = cellControllerBuilder.build() as GridURLCellController; - return _EditURLAccessory( + return GridCellAccessoryBuilder( + builder: (Key key) => _EditURLAccessory( + key: key, cellContext: cellController, - anchorContext: buildContext.anchorContext); + anchorContext: buildContext.anchorContext, + ), + ); case GridURLCellAccessoryType.copyURL: final cellContext = cellControllerBuilder.build() as GridURLCellController; - return _CopyURLAccessory(cellContext: cellContext); + return GridCellAccessoryBuilder( + builder: (Key key) => _CopyURLAccessory( + key: key, + cellContext: cellContext, + ), + ); } } @override - List Function(GridCellAccessoryBuildContext buildContext) + List Function( + GridCellAccessoryBuildContext buildContext) get accessoryBuilder => (buildContext) { - final List accessories = []; + final List accessories = []; if (cellStyle != null) { accessories.addAll(cellStyle!.accessoryTypes.map((ty) { return accessoryFromType(ty, buildContext); @@ -86,6 +97,8 @@ class GridURLCell extends GridCellWidget { } class _GridURLCellState extends GridCellState { + final _popoverController = PopoverController(); + GridURLCellController? _cellContext; late URLCellBloc _cellBloc; @override @@ -116,14 +129,28 @@ class _GridURLCellState extends GridCellState { ), ); - return SizedBox.expand( + return Popover( + controller: _popoverController, + direction: PopoverDirection.bottomWithLeftAligned, + offset: const Offset(0, 20), + child: SizedBox.expand( child: GestureDetector( - child: Align(alignment: Alignment.centerLeft, child: richText), - onTap: () async { - final url = context.read().state.url; - await _openUrlOrEdit(url); + child: Align(alignment: Alignment.centerLeft, child: richText), + onTap: () async { + final url = context.read().state.url; + await _openUrlOrEdit(url); + }, + ), + ), + popupBuilder: (BuildContext popoverContext) { + return URLEditorPopover( + cellController: _cellContext!, + ); }, - )); + onClose: () { + widget.onCellEditing.value = false; + }, + ); }, ), ); @@ -140,12 +167,10 @@ class _GridURLCellState extends GridCellState { if (url.isNotEmpty && await canLaunchUrl(uri)) { await launchUrl(uri); } else { - final cellContext = + _cellContext = widget.cellControllerBuilder.build() as GridURLCellController; widget.onCellEditing.value = true; - URLCellEditor.show(context, cellContext, () { - widget.onCellEditing.value = false; - }); + _popoverController.show(); } } @@ -163,7 +188,7 @@ class _GridURLCellState extends GridCellState { } } -class _EditURLAccessory extends StatelessWidget with GridCellAccessory { +class _EditURLAccessory extends StatefulWidget { final GridURLCellController cellContext; final BuildContext anchorContext; const _EditURLAccessory({ @@ -172,24 +197,55 @@ class _EditURLAccessory extends StatelessWidget with GridCellAccessory { Key? key, }) : super(key: key); + @override + State createState() => _EditURLAccessoryState(); +} + +class _EditURLAccessoryState extends State<_EditURLAccessory> + with GridCellAccessoryState { + late PopoverController _popoverController; + + @override + void initState() { + _popoverController = PopoverController(); + super.initState(); + } + @override Widget build(BuildContext context) { final theme = context.watch(); - return svgWidget("editor/edit", color: theme.iconColor); + return Popover( + controller: _popoverController, + direction: PopoverDirection.bottomWithLeftAligned, + triggerActions: PopoverTriggerActionFlags.click, + offset: const Offset(0, 20), + child: svgWidget("editor/edit", color: theme.iconColor), + popupBuilder: (BuildContext popoverContext) { + return URLEditorPopover( + cellController: widget.cellContext.clone(), + ); + }, + ); } @override void onTap() { - URLCellEditor.show(anchorContext, cellContext, () {}); + _popoverController.show(); } } -class _CopyURLAccessory extends StatelessWidget with GridCellAccessory { +class _CopyURLAccessory extends StatefulWidget { final GridURLCellController cellContext; const _CopyURLAccessory({required this.cellContext, Key? key}) : super(key: key); @override + State createState() => _CopyURLAccessoryState(); +} + +class _CopyURLAccessoryState extends State<_CopyURLAccessory> + with GridCellAccessoryState { + @override Widget build(BuildContext context) { final theme = context.watch(); return svgWidget("editor/copy", color: theme.iconColor); @@ -198,7 +254,7 @@ class _CopyURLAccessory extends StatelessWidget with GridCellAccessory { @override void onTap() { final content = - cellContext.getCellData(loadIfNotExist: false)?.content ?? ""; + widget.cellContext.getCellData(loadIfNotExist: false)?.content ?? ""; Clipboard.setData(ClipboardData(text: content)); showMessageToast(LocaleKeys.grid_row_copyProperty.tr()); } diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/grid_row.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/grid_row.dart index 55ec7b9832..74e48db8b1 100755 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/grid_row.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/grid_row.dart @@ -194,12 +194,17 @@ class RowContent extends StatelessWidget { Provider.of(context, listen: false), accessoryBuilder: (buildContext) { final builder = child.accessoryBuilder; - List accessories = []; + List accessories = []; if (cellId.field.isPrimary) { - accessories.add(PrimaryCellAccessory( - onTapCallback: onExpand, - isCellEditing: buildContext.isCellEditing, - )); + accessories.add( + GridCellAccessoryBuilder( + builder: (key) => PrimaryCellAccessory( + key: key, + onTapCallback: onExpand, + isCellEditing: buildContext.isCellEditing, + ), + ), + ); } if (builder != null) {