fix: show newChecklistTask on hover over entire list item in row detail page (#4622)

This commit is contained in:
Richard Shiue 2024-02-07 23:20:26 +08:00 committed by GitHub
parent 3129fa6cc1
commit 88e9d63a13
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 53 additions and 75 deletions

View File

@ -10,7 +10,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart';
import '../editable_cell_skeleton/checklist.dart'; import '../editable_cell_skeleton/checklist.dart';
@ -113,40 +113,34 @@ class _ChecklistItemsState extends State<ChecklistItems> {
), ),
const VSpace(4), const VSpace(4),
...children, ...children,
const ChecklistItemControl(), ChecklistItemControl(cellNotifer: widget.cellContainerNotifier),
], ],
), ),
); );
} }
} }
class ChecklistItemControl extends StatefulWidget { class ChecklistItemControl extends StatelessWidget {
const ChecklistItemControl({super.key}); const ChecklistItemControl({super.key, required this.cellNotifer});
@override final CellContainerNotifier cellNotifer;
State<ChecklistItemControl> createState() => _ChecklistItemControlState();
}
class _ChecklistItemControlState extends State<ChecklistItemControl> {
bool _isHover = false;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MouseRegion( return ChangeNotifierProvider.value(
onHover: (_) => setState(() => _isHover = true), value: cellNotifer,
onExit: (_) => setState(() => _isHover = false), child: Consumer<CellContainerNotifier>(
child: GestureDetector( builder: (buildContext, notifier, _) => GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
onTap: () => context onTap: () => context
.read<ChecklistCellBloc>() .read<ChecklistCellBloc>()
.add(const ChecklistCellEvent.createNewTask("")), .add(const ChecklistCellEvent.createNewTask("")),
child: Padding( child: Container(
padding: const EdgeInsets.fromLTRB(8.0, 4.0, 8.0, 0), margin: const EdgeInsets.fromLTRB(8.0, 4.0, 8.0, 0),
child: SizedBox(
height: 12, height: 12,
child: AnimatedSwitcher( child: AnimatedSwitcher(
duration: const Duration(milliseconds: 150), duration: const Duration(milliseconds: 150),
child: _isHover child: notifier.isHover
? FlowyTooltip( ? FlowyTooltip(
message: LocaleKeys.grid_checklist_addNew.tr(), message: LocaleKeys.grid_checklist_addNew.tr(),
child: Row( child: Row(

View File

@ -190,11 +190,9 @@ class EditableCellBuilder {
} }
abstract class CellEditable { abstract class CellEditable {
RequestFocusListener get requestFocus; SingleListenerChangeNotifier get requestFocus;
CellContainerNotifier get cellContainerNotifier; CellContainerNotifier get cellContainerNotifier;
// ValueNotifier<bool> get onCellEditing;
} }
typedef AccessoryBuilder = List<GridCellAccessoryBuilder> Function( typedef AccessoryBuilder = List<GridCellAccessoryBuilder> Function(
@ -204,9 +202,6 @@ typedef AccessoryBuilder = List<GridCellAccessoryBuilder> Function(
abstract class CellAccessory extends Widget { abstract class CellAccessory extends Widget {
const CellAccessory({super.key}); const CellAccessory({super.key});
// The hover will show if the isHover's value is true
ValueNotifier<bool>? get onAccessoryHover;
AccessoryBuilder? get accessoryBuilder; AccessoryBuilder? get accessoryBuilder;
} }
@ -217,20 +212,11 @@ abstract class EditableCellWidget extends StatefulWidget
@override @override
final CellContainerNotifier cellContainerNotifier = CellContainerNotifier(); final CellContainerNotifier cellContainerNotifier = CellContainerNotifier();
// When the cell is focused, we assume that the accessory also be hovered.
@override @override
ValueNotifier<bool> get onAccessoryHover => ValueNotifier(false); AccessoryBuilder? get accessoryBuilder => null;
// @override
// final ValueNotifier<bool> onCellEditing = ValueNotifier<bool>(false);
@override @override
List<GridCellAccessoryBuilder> Function( final requestFocus = SingleListenerChangeNotifier();
GridCellAccessoryBuildContext buildContext,
)? get accessoryBuilder => null;
@override
final RequestFocusListener requestFocus = RequestFocusListener();
@override @override
final Map<CellKeyboardKey, CellKeyboardAction> shortcutHandlers = {}; final Map<CellKeyboardKey, CellKeyboardAction> shortcutHandlers = {};
@ -240,28 +226,25 @@ abstract class GridCellState<T extends EditableCellWidget> extends State<T> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
widget.requestFocus.addListener(onRequestFocus);
widget.requestFocus.setListener(requestBeginFocus);
} }
@override @override
void didUpdateWidget(covariant T oldWidget) { void didUpdateWidget(covariant T oldWidget) {
if (oldWidget != this) { if (oldWidget != this) {
widget.requestFocus.setListener(requestBeginFocus); widget.requestFocus.addListener(onRequestFocus);
} }
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
} }
@override @override
void dispose() { void dispose() {
widget.onAccessoryHover.dispose();
widget.requestFocus.removeAllListener();
widget.requestFocus.dispose(); widget.requestFocus.dispose();
super.dispose(); super.dispose();
} }
/// Subclass can override this method to request focus. /// Subclass can override this method to request focus.
void requestBeginFocus(); void onRequestFocus();
String? onCopy() => null; String? onCopy() => null;
} }
@ -287,7 +270,7 @@ abstract class GridEditableTextCell<T extends EditableCellWidget>
} }
@override @override
void requestBeginFocus() { void onRequestFocus() {
if (!focusNode.hasFocus && focusNode.canRequestFocus) { if (!focusNode.hasFocus && focusNode.canRequestFocus) {
FocusScope.of(context).requestFocus(focusNode); FocusScope.of(context).requestFocus(focusNode);
} }
@ -304,28 +287,25 @@ abstract class GridEditableTextCell<T extends EditableCellWidget>
Future<void> focusChanged() async {} Future<void> focusChanged() async {}
} }
class RequestFocusListener extends ChangeNotifier { class SingleListenerChangeNotifier extends ChangeNotifier {
VoidCallback? _listener; VoidCallback? _listener;
void setListener(VoidCallback listener) { @override
void addListener(VoidCallback listener) {
if (_listener != null) { if (_listener != null) {
removeListener(_listener!); removeListener(_listener!);
} }
_listener = listener; _listener = listener;
addListener(listener); super.addListener(listener);
} }
void removeAllListener() { @override
if (_listener != null) { void dispose() {
removeListener(_listener!); _listener = null;
_listener = null; super.dispose();
}
} }
void notify() { void notify() => notifyListeners();
notifyListeners();
}
} }
class SingleListenerFocusNode extends FocusNode { class SingleListenerFocusNode extends FocusNode {
@ -374,7 +354,7 @@ class EditableCellSkinMap {
FieldType.Checklist => checklistSkin != null, FieldType.Checklist => checklistSkin != null,
FieldType.CreatedTime || FieldType.CreatedTime ||
FieldType.LastEditedTime => FieldType.LastEditedTime =>
throw timestampSkin != null, timestampSkin != null,
FieldType.DateTime => dateSkin != null, FieldType.DateTime => dateSkin != null,
FieldType.MultiSelect || FieldType.MultiSelect ||
FieldType.SingleSelect => FieldType.SingleSelect =>

View File

@ -80,9 +80,7 @@ class _CheckboxCellState extends GridCellState<EditableCheckboxCell> {
} }
@override @override
void requestBeginFocus() { void onRequestFocus() => cellBloc.add(const CheckboxCellEvent.select());
cellBloc.add(const CheckboxCellEvent.select());
}
@override @override
String? onCopy() { String? onCopy() {

View File

@ -85,7 +85,7 @@ class GridChecklistCellState extends GridCellState<EditableChecklistCell> {
} }
@override @override
void requestBeginFocus() { void onRequestFocus() {
if (widget.skin is DesktopGridChecklistCellSkin) { if (widget.skin is DesktopGridChecklistCellSkin) {
_popover.show(); _popover.show();
} }

View File

@ -84,7 +84,7 @@ class _DateCellState extends GridCellState<EditableDateCell> {
} }
@override @override
void requestBeginFocus() { void onRequestFocus() {
_popover.show(); _popover.show();
widget.cellContainerNotifier.isFocus = true; widget.cellContainerNotifier.isFocus = true;
} }

View File

@ -96,7 +96,7 @@ class _NumberCellState extends GridEditableTextCell<EditableNumberCell> {
SingleListenerFocusNode focusNode = SingleListenerFocusNode(); SingleListenerFocusNode focusNode = SingleListenerFocusNode();
@override @override
void requestBeginFocus() { void onRequestFocus() {
focusNode.requestFocus(); focusNode.requestFocus();
} }

View File

@ -92,5 +92,5 @@ class _SelectOptionCellState extends GridCellState<EditableSelectOptionCell> {
} }
@override @override
void requestBeginFocus() => _popover.show(); void onRequestFocus() => _popover.show();
} }

View File

@ -97,7 +97,7 @@ class _TextCellState extends GridEditableTextCell<EditableTextCell> {
SingleListenerFocusNode focusNode = SingleListenerFocusNode(); SingleListenerFocusNode focusNode = SingleListenerFocusNode();
@override @override
void requestBeginFocus() { void onRequestFocus() {
focusNode.requestFocus(); focusNode.requestFocus();
} }

View File

@ -83,7 +83,7 @@ class _TimestampCellState extends GridCellState<EditableTimestampCell> {
} }
@override @override
void requestBeginFocus() { void onRequestFocus() {
widget.cellContainerNotifier.isFocus = true; widget.cellContainerNotifier.isFocus = true;
} }

View File

@ -98,7 +98,7 @@ class _GridCellEnterRegion extends StatelessWidget {
return Selector2<RegionStateNotifier, CellContainerNotifier, bool>( return Selector2<RegionStateNotifier, CellContainerNotifier, bool>(
selector: (context, regionNotifier, cellNotifier) => selector: (context, regionNotifier, cellNotifier) =>
!cellNotifier.isFocus && !cellNotifier.isFocus &&
(cellNotifier.onEnter || regionNotifier.onEnter && isPrimary), (cellNotifier.isHover || regionNotifier.onEnter && isPrimary),
builder: (context, showAccessory, _) { builder: (context, showAccessory, _) {
final List<Widget> children = [child]; final List<Widget> children = [child];
@ -113,9 +113,9 @@ class _GridCellEnterRegion extends StatelessWidget {
return MouseRegion( return MouseRegion(
cursor: SystemMouseCursors.click, cursor: SystemMouseCursors.click,
onEnter: (p) => onEnter: (p) =>
CellContainerNotifier.of(context, listen: false).onEnter = true, CellContainerNotifier.of(context, listen: false).isHover = true,
onExit: (p) => onExit: (p) =>
CellContainerNotifier.of(context, listen: false).onEnter = false, CellContainerNotifier.of(context, listen: false).isHover = false,
child: Stack( child: Stack(
alignment: AlignmentDirectional.center, alignment: AlignmentDirectional.center,
fit: StackFit.expand, fit: StackFit.expand,
@ -138,7 +138,7 @@ class CellContainerNotifier extends ChangeNotifier {
} }
} }
set onEnter(bool value) { set isHover(bool value) {
if (_onEnter != value) { if (_onEnter != value) {
_onEnter = value; _onEnter = value;
notifyListeners(); notifyListeners();
@ -147,7 +147,7 @@ class CellContainerNotifier extends ChangeNotifier {
bool get isFocus => _isFocus; bool get isFocus => _isFocus;
bool get onEnter => _onEnter; bool get isHover => _onEnter;
static CellContainerNotifier of(BuildContext context, {bool listen = true}) { static CellContainerNotifier of(BuildContext context, {bool listen = true}) {
return Provider.of<CellContainerNotifier>(context, listen: listen); return Provider.of<CellContainerNotifier>(context, listen: listen);

View File

@ -189,8 +189,14 @@ class _PropertyCellState extends State<_PropertyCell> {
margin: const EdgeInsets.only(bottom: 8), margin: const EdgeInsets.only(bottom: 8),
constraints: const BoxConstraints(minHeight: 30), constraints: const BoxConstraints(minHeight: 30),
child: MouseRegion( child: MouseRegion(
onEnter: (event) => _isFieldHover.value = true, onEnter: (event) {
onExit: (event) => _isFieldHover.value = false, _isFieldHover.value = true;
cell.cellContainerNotifier.isHover = true;
},
onExit: (event) {
_isFieldHover.value = false;
cell.cellContainerNotifier.isHover = false;
},
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [