refactor: use Popover in URL cell

This commit is contained in:
Vincent Chan 2022-09-02 14:25:06 +08:00
parent a43259bddb
commit 6296367efc
7 changed files with 151 additions and 82 deletions

View File

@ -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<StatefulWidget> createState() => _PrimaryCellAccessoryState();
}
class _PrimaryCellAccessoryState extends State<PrimaryCellAccessory>
with GridCellAccessoryState {
@override
Widget build(BuildContext context) {
if (isCellEditing) {
if (widget.isCellEditing) {
return const SizedBox();
} else {
final theme = context.watch<AppTheme>();
@ -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<GridCellAccessory> accessories;
final List<GridCellAccessoryBuilder> 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(

View File

@ -94,7 +94,7 @@ abstract class CellEditable {
ValueNotifier<bool> get onCellEditing;
}
typedef AccessoryBuilder = List<GridCellAccessory> Function(
typedef AccessoryBuilder = List<GridCellAccessoryBuilder> Function(
GridCellAccessoryBuildContext buildContext);
abstract class CellAccessory extends Widget {
@ -125,8 +125,8 @@ abstract class GridCellWidget extends StatefulWidget
final ValueNotifier<bool> onCellEditing = ValueNotifier<bool>(false);
@override
List<GridCellAccessory> Function(GridCellAccessoryBuildContext buildContext)?
get accessoryBuilder => null;
List<GridCellAccessoryBuilder> Function(
GridCellAccessoryBuildContext buildContext)? get accessoryBuilder => null;
@override
final GridCellFocusListener beginFocus = GridCellFocusListener();

View File

@ -80,7 +80,7 @@ class CellContainer extends StatelessWidget {
class _GridCellEnterRegion extends StatelessWidget {
final Widget child;
final List<GridCellAccessory> accessories;
final List<GridCellAccessoryBuilder> accessories;
const _GridCellEnterRegion(
{required this.child, required this.accessories, Key? key})
: super(key: key);

View File

@ -64,6 +64,7 @@ class _DateCellState extends GridCellState<GridDateCell> {
return Popover(
controller: _popover,
offset: const Offset(0, 20),
direction: PopoverDirection.bottomWithLeftAligned,
child: SizedBox.expand(
child: GestureDetector(
behavior: HitTestBehavior.opaque,

View File

@ -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<URLCellEditor> 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<URLCellEditor> {
@ -114,3 +71,25 @@ class _URLCellEditorState extends State<URLCellEditor> {
}
}
}
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,
),
),
),
);
}
}

View File

@ -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<GridURLCell> 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<GridCellAccessory> Function(GridCellAccessoryBuildContext buildContext)
List<GridCellAccessoryBuilder> Function(
GridCellAccessoryBuildContext buildContext)
get accessoryBuilder => (buildContext) {
final List<GridCellAccessory> accessories = [];
final List<GridCellAccessoryBuilder> 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<GridURLCell> {
final _popoverController = PopoverController();
GridURLCellController? _cellContext;
late URLCellBloc _cellBloc;
@override
@ -116,14 +129,28 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
),
);
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<URLCellBloc>().state.url;
await _openUrlOrEdit(url);
child: Align(alignment: Alignment.centerLeft, child: richText),
onTap: () async {
final url = context.read<URLCellBloc>().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<GridURLCell> {
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<GridURLCell> {
}
}
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<StatefulWidget> 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<AppTheme>();
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<StatefulWidget> createState() => _CopyURLAccessoryState();
}
class _CopyURLAccessoryState extends State<_CopyURLAccessory>
with GridCellAccessoryState {
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
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());
}

View File

@ -194,12 +194,17 @@ class RowContent extends StatelessWidget {
Provider.of<RegionStateNotifier>(context, listen: false),
accessoryBuilder: (buildContext) {
final builder = child.accessoryBuilder;
List<GridCellAccessory> accessories = [];
List<GridCellAccessoryBuilder> 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) {