mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: improve grid focus and hover event handling (#1735)
* chore: improve primary cell accessory behavior * fix: focus border disappearing * chore: port to GridCellState * chore: fix typo * chore: connect popover controller * chore: final
This commit is contained in:
parent
347245aaa1
commit
243a781b6c
@ -1,6 +1,6 @@
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/checklist_cell_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/checklist_cell/checklist_cell.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/checklist_cell/checklist_progress_bar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
@ -29,7 +29,10 @@ class _BoardChecklistCellState extends State<BoardChecklistCell> {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: const ChecklistProgressBar(),
|
||||
child: BlocBuilder<ChecklistCellBloc, ChecklistCellState>(
|
||||
builder: (context, state) =>
|
||||
ChecklistProgressBar(percent: state.percent),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -67,18 +67,14 @@ class _PrimaryCellAccessoryState extends State<PrimaryCellAccessory>
|
||||
with GridCellAccessoryState {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.isCellEditing) {
|
||||
return const SizedBox();
|
||||
} else {
|
||||
return Tooltip(
|
||||
message: LocaleKeys.tooltip_openAsPage.tr(),
|
||||
textStyle: AFThemeExtension.of(context).caption.textColor(Colors.white),
|
||||
child: svgWidget(
|
||||
"grid/expander",
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
);
|
||||
}
|
||||
return Tooltip(
|
||||
message: LocaleKeys.tooltip_openAsPage.tr(),
|
||||
textStyle: AFThemeExtension.of(context).caption.textColor(Colors.white),
|
||||
child: svgWidget(
|
||||
"grid/expander",
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -12,20 +12,23 @@ class CellContainer extends StatelessWidget {
|
||||
final GridCellWidget child;
|
||||
final AccessoryBuilder? accessoryBuilder;
|
||||
final double width;
|
||||
final RegionStateNotifier rowStateNotifier;
|
||||
final bool isPrimary;
|
||||
final CellContainerNotifier cellContainerNotifier;
|
||||
|
||||
const CellContainer({
|
||||
Key? key,
|
||||
required this.child,
|
||||
required this.width,
|
||||
required this.rowStateNotifier,
|
||||
required this.isPrimary,
|
||||
required this.cellContainerNotifier,
|
||||
this.accessoryBuilder,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider<_CellContainerNotifier>(
|
||||
create: (_) => _CellContainerNotifier(child),
|
||||
child: Selector<_CellContainerNotifier, bool>(
|
||||
return ChangeNotifierProvider.value(
|
||||
value: cellContainerNotifier,
|
||||
child: Selector<CellContainerNotifier, bool>(
|
||||
selector: (context, notifier) => notifier.isFocus,
|
||||
builder: (privderContext, isFocus, _) {
|
||||
Widget container = Center(child: GridCellShortcuts(child: child));
|
||||
@ -41,6 +44,7 @@ class CellContainer extends StatelessWidget {
|
||||
if (accessories.isNotEmpty) {
|
||||
container = _GridCellEnterRegion(
|
||||
accessories: accessories,
|
||||
isPrimary: isPrimary,
|
||||
child: container,
|
||||
);
|
||||
}
|
||||
@ -81,17 +85,23 @@ class CellContainer extends StatelessWidget {
|
||||
class _GridCellEnterRegion extends StatelessWidget {
|
||||
final Widget child;
|
||||
final List<GridCellAccessoryBuilder> accessories;
|
||||
const _GridCellEnterRegion(
|
||||
{required this.child, required this.accessories, Key? key})
|
||||
: super(key: key);
|
||||
final bool isPrimary;
|
||||
const _GridCellEnterRegion({
|
||||
required this.child,
|
||||
required this.accessories,
|
||||
required this.isPrimary,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Selector2<RegionStateNotifier, _CellContainerNotifier, bool>(
|
||||
selector: (context, regionNotifier, cellNotifier) => !cellNotifier.isFocus && (cellNotifier.onEnter),
|
||||
builder: (context, onEnter, _) {
|
||||
return Selector2<RegionStateNotifier, CellContainerNotifier, bool>(
|
||||
selector: (context, regionNotifier, cellNotifier) =>
|
||||
!cellNotifier.isFocus &&
|
||||
(cellNotifier.onEnter || regionNotifier.onEnter && isPrimary),
|
||||
builder: (context, showAccessory, _) {
|
||||
List<Widget> children = [child];
|
||||
if (onEnter) {
|
||||
if (showAccessory) {
|
||||
children.add(
|
||||
CellAccessoryContainer(accessories: accessories).positioned(
|
||||
right: GridSize.cellContentInsets.right,
|
||||
@ -102,10 +112,10 @@ class _GridCellEnterRegion extends StatelessWidget {
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
onEnter: (p) =>
|
||||
Provider.of<_CellContainerNotifier>(context, listen: false)
|
||||
Provider.of<CellContainerNotifier>(context, listen: false)
|
||||
.onEnter = true,
|
||||
onExit: (p) =>
|
||||
Provider.of<_CellContainerNotifier>(context, listen: false)
|
||||
Provider.of<CellContainerNotifier>(context, listen: false)
|
||||
.onEnter = false,
|
||||
child: Stack(
|
||||
alignment: AlignmentDirectional.center,
|
||||
@ -118,13 +128,13 @@ class _GridCellEnterRegion extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _CellContainerNotifier extends ChangeNotifier {
|
||||
class CellContainerNotifier extends ChangeNotifier {
|
||||
final CellEditable cellEditable;
|
||||
VoidCallback? _onCellFocusListener;
|
||||
bool _isFocus = false;
|
||||
bool _onEnter = false;
|
||||
|
||||
_CellContainerNotifier(this.cellEditable) {
|
||||
CellContainerNotifier(this.cellEditable) {
|
||||
_onCellFocusListener = () => isFocus = cellEditable.onCellFocus.value;
|
||||
cellEditable.onCellFocus.addListener(_onCellFocusListener!);
|
||||
}
|
||||
|
@ -5,9 +5,10 @@ import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../cell_builder.dart';
|
||||
import 'checklist_cell_editor.dart';
|
||||
import 'checklist_prograss_bar.dart';
|
||||
import 'checklist_progress_bar.dart';
|
||||
|
||||
class GridChecklistCell extends GridCellWidget {
|
||||
final GridCellControllerBuilder cellControllerBuilder;
|
||||
@ -15,12 +16,12 @@ class GridChecklistCell extends GridCellWidget {
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
GridChecklistCellState createState() => GridChecklistCellState();
|
||||
GridCellState<GridChecklistCell> createState() => GridChecklistCellState();
|
||||
}
|
||||
|
||||
class GridChecklistCellState extends State<GridChecklistCell> {
|
||||
late PopoverController _popover;
|
||||
class GridChecklistCellState extends GridCellState<GridChecklistCell> {
|
||||
late ChecklistCellBloc _cellBloc;
|
||||
late final PopoverController _popover;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -36,52 +37,32 @@ class GridChecklistCellState extends State<GridChecklistCell> {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: Stack(
|
||||
alignment: AlignmentDirectional.center,
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
_wrapPopover(const ChecklistProgressBar()),
|
||||
InkWell(onTap: () => _popover.show()),
|
||||
],
|
||||
child: AppFlowyPopover(
|
||||
controller: _popover,
|
||||
constraints: BoxConstraints.loose(const Size(260, 400)),
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
triggerActions: PopoverTriggerFlags.none,
|
||||
popupBuilder: (BuildContext context) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
widget.onCellEditing.value = true;
|
||||
});
|
||||
return GridChecklistCellEditor(
|
||||
cellController: widget.cellControllerBuilder.build()
|
||||
as GridChecklistCellController,
|
||||
);
|
||||
},
|
||||
onClose: () => widget.onCellEditing.value = false,
|
||||
child: Padding(
|
||||
padding: GridSize.cellContentInsets,
|
||||
child: BlocBuilder<ChecklistCellBloc, ChecklistCellState>(
|
||||
builder: (context, state) =>
|
||||
ChecklistProgressBar(percent: state.percent),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _wrapPopover(Widget child) {
|
||||
return AppFlowyPopover(
|
||||
controller: _popover,
|
||||
constraints: BoxConstraints.loose(const Size(260, 400)),
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
triggerActions: PopoverTriggerFlags.none,
|
||||
popupBuilder: (BuildContext context) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
widget.onCellEditing.value = true;
|
||||
});
|
||||
return GridChecklistCellEditor(
|
||||
cellController: widget.cellControllerBuilder.build()
|
||||
as GridChecklistCellController,
|
||||
);
|
||||
},
|
||||
onClose: () => widget.onCellEditing.value = false,
|
||||
child: Padding(
|
||||
padding: GridSize.cellContentInsets,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ChecklistProgressBar extends StatelessWidget {
|
||||
const ChecklistProgressBar({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<ChecklistCellBloc, ChecklistCellState>(
|
||||
builder: (context, state) {
|
||||
return ChecklistPrograssBar(
|
||||
percent: state.percent,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
void requestBeginFocus() => _popover.show();
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/checklist_cell_editor_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/layout/sizes.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/checklist_cell/checklist_prograss_bar.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/checklist_cell/checklist_progress_bar.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/header/type_option/select_option_editor.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
@ -48,7 +48,7 @@ class _GridChecklistCellEditorState extends State<GridChecklistCellEditor> {
|
||||
child: BlocBuilder<ChecklistCellEditorBloc, ChecklistCellEditorState>(
|
||||
builder: (context, state) {
|
||||
final List<Widget> slivers = [
|
||||
const SliverChecklistPrograssBar(),
|
||||
const SliverChecklistProgressBar(),
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: GridSize.typeOptionContentInsets,
|
||||
|
@ -8,9 +8,9 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:percent_indicator/percent_indicator.dart';
|
||||
|
||||
class ChecklistPrograssBar extends StatelessWidget {
|
||||
class ChecklistProgressBar extends StatelessWidget {
|
||||
final double percent;
|
||||
const ChecklistPrograssBar({required this.percent, Key? key})
|
||||
const ChecklistProgressBar({required this.percent, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
@ -26,21 +26,21 @@ class ChecklistPrograssBar extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class SliverChecklistPrograssBar extends StatelessWidget {
|
||||
const SliverChecklistPrograssBar({Key? key}) : super(key: key);
|
||||
class SliverChecklistProgressBar extends StatelessWidget {
|
||||
const SliverChecklistProgressBar({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SliverPersistentHeader(
|
||||
pinned: true,
|
||||
delegate: _SliverChecklistPrograssBarDelegate(),
|
||||
delegate: _SliverChecklistProgressBarDelegate(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SliverChecklistPrograssBarDelegate
|
||||
class _SliverChecklistProgressBarDelegate
|
||||
extends SliverPersistentHeaderDelegate {
|
||||
_SliverChecklistPrograssBarDelegate();
|
||||
_SliverChecklistProgressBarDelegate();
|
||||
|
||||
double fixHeight = 60;
|
||||
|
||||
@ -71,7 +71,7 @@ class _SliverChecklistPrograssBarDelegate
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 6.0),
|
||||
child: ChecklistPrograssBar(percent: state.percent),
|
||||
child: ChecklistProgressBar(percent: state.percent),
|
||||
),
|
||||
],
|
||||
),
|
@ -39,11 +39,12 @@ class GridSingleSelectCell extends GridCellWidget {
|
||||
}
|
||||
|
||||
@override
|
||||
State<GridSingleSelectCell> createState() => _SingleSelectCellState();
|
||||
GridCellState<GridSingleSelectCell> createState() => _SingleSelectCellState();
|
||||
}
|
||||
|
||||
class _SingleSelectCellState extends State<GridSingleSelectCell> {
|
||||
class _SingleSelectCellState extends GridCellState<GridSingleSelectCell> {
|
||||
late SelectOptionCellBloc _cellBloc;
|
||||
late final PopoverController _popover;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -51,6 +52,7 @@ class _SingleSelectCellState extends State<GridSingleSelectCell> {
|
||||
widget.cellControllerBuilder.build() as GridSelectOptionCellController;
|
||||
_cellBloc = getIt<SelectOptionCellBloc>(param1: cellController)
|
||||
..add(const SelectOptionCellEvent.initial());
|
||||
_popover = PopoverController();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@ -61,10 +63,12 @@ class _SingleSelectCellState extends State<GridSingleSelectCell> {
|
||||
child: BlocBuilder<SelectOptionCellBloc, SelectOptionCellState>(
|
||||
builder: (context, state) {
|
||||
return SelectOptionWrap(
|
||||
selectOptions: state.selectedOptions,
|
||||
cellStyle: widget.cellStyle,
|
||||
onFocus: (value) => widget.onCellEditing.value = value,
|
||||
cellControllerBuilder: widget.cellControllerBuilder);
|
||||
selectOptions: state.selectedOptions,
|
||||
cellStyle: widget.cellStyle,
|
||||
onCellEditing: widget.onCellEditing,
|
||||
popoverController: _popover,
|
||||
cellControllerBuilder: widget.cellControllerBuilder,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
@ -75,6 +79,9 @@ class _SingleSelectCellState extends State<GridSingleSelectCell> {
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void requestBeginFocus() => _popover.show();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
@ -95,11 +102,12 @@ class GridMultiSelectCell extends GridCellWidget {
|
||||
}
|
||||
|
||||
@override
|
||||
State<GridMultiSelectCell> createState() => _MultiSelectCellState();
|
||||
GridCellState<GridMultiSelectCell> createState() => _MultiSelectCellState();
|
||||
}
|
||||
|
||||
class _MultiSelectCellState extends State<GridMultiSelectCell> {
|
||||
class _MultiSelectCellState extends GridCellState<GridMultiSelectCell> {
|
||||
late SelectOptionCellBloc _cellBloc;
|
||||
late final PopoverController _popover;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -107,6 +115,7 @@ class _MultiSelectCellState extends State<GridMultiSelectCell> {
|
||||
widget.cellControllerBuilder.build() as GridSelectOptionCellController;
|
||||
_cellBloc = getIt<SelectOptionCellBloc>(param1: cellController)
|
||||
..add(const SelectOptionCellEvent.initial());
|
||||
_popover = PopoverController();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@ -119,7 +128,8 @@ class _MultiSelectCellState extends State<GridMultiSelectCell> {
|
||||
return SelectOptionWrap(
|
||||
selectOptions: state.selectedOptions,
|
||||
cellStyle: widget.cellStyle,
|
||||
onFocus: (value) => widget.onCellEditing.value = value,
|
||||
onCellEditing: widget.onCellEditing,
|
||||
popoverController: _popover,
|
||||
cellControllerBuilder: widget.cellControllerBuilder,
|
||||
);
|
||||
},
|
||||
@ -132,17 +142,23 @@ class _MultiSelectCellState extends State<GridMultiSelectCell> {
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void requestBeginFocus() => _popover.show();
|
||||
}
|
||||
|
||||
class SelectOptionWrap extends StatefulWidget {
|
||||
final List<SelectOptionPB> selectOptions;
|
||||
final void Function(bool)? onFocus;
|
||||
final SelectOptionCellStyle? cellStyle;
|
||||
final GridCellControllerBuilder cellControllerBuilder;
|
||||
final PopoverController popoverController;
|
||||
final ValueNotifier onCellEditing;
|
||||
|
||||
const SelectOptionWrap({
|
||||
required this.selectOptions,
|
||||
required this.cellControllerBuilder,
|
||||
this.onFocus,
|
||||
required this.onCellEditing,
|
||||
required this.popoverController,
|
||||
this.cellStyle,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
@ -152,48 +168,29 @@ class SelectOptionWrap extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _SelectOptionWrapState extends State<SelectOptionWrap> {
|
||||
late PopoverController _popover;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_popover = PopoverController();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget child = _buildOptions(context);
|
||||
|
||||
return Stack(
|
||||
alignment: AlignmentDirectional.center,
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
_wrapPopover(child),
|
||||
InkWell(onTap: () => _popover.show()),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _wrapPopover(Widget child) {
|
||||
final constraints = BoxConstraints.loose(Size(
|
||||
SelectOptionCellEditor.editorPanelWidth,
|
||||
300,
|
||||
));
|
||||
return AppFlowyPopover(
|
||||
controller: _popover,
|
||||
controller: widget.popoverController,
|
||||
constraints: constraints,
|
||||
margin: EdgeInsets.zero,
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
popupBuilder: (BuildContext context) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
widget.onFocus?.call(true);
|
||||
widget.onCellEditing.value = true;
|
||||
});
|
||||
return SelectOptionCellEditor(
|
||||
cellController: widget.cellControllerBuilder.build()
|
||||
as GridSelectOptionCellController,
|
||||
);
|
||||
},
|
||||
onClose: () => widget.onFocus?.call(false),
|
||||
onClose: () => widget.onCellEditing.value = false,
|
||||
child: Padding(
|
||||
padding: GridSize.cellContentInsets,
|
||||
child: child,
|
||||
|
@ -225,8 +225,8 @@ class RowContent extends StatelessWidget {
|
||||
|
||||
return CellContainer(
|
||||
width: cellId.fieldInfo.width.toDouble(),
|
||||
rowStateNotifier:
|
||||
Provider.of<RegionStateNotifier>(context, listen: false),
|
||||
isPrimary: cellId.fieldInfo.isPrimary,
|
||||
cellContainerNotifier: CellContainerNotifier(child),
|
||||
accessoryBuilder: (buildContext) {
|
||||
final builder = child.accessoryBuilder;
|
||||
List<GridCellAccessoryBuilder> accessories = [];
|
||||
|
Loading…
Reference in New Issue
Block a user