Skip to content

Commit 9518e16

Browse files
committed
chore: config cell accessory
1 parent 9c5081b commit 9518e16

File tree

12 files changed

+284
-200
lines changed

12 files changed

+284
-200
lines changed
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import 'package:flowy_infra_ui/style_widget/hover.dart';
2+
import 'package:flutter/widgets.dart';
3+
import 'package:flowy_infra/theme.dart';
4+
import 'package:flutter/material.dart';
5+
import 'package:provider/provider.dart';
6+
import 'package:flowy_infra/size.dart';
7+
import 'package:styled_widget/styled_widget.dart';
8+
9+
abstract class GridCellAccessory implements Widget {
10+
void onTap(BuildContext context);
11+
}
12+
13+
abstract class AccessoryHoverChild extends Widget {
14+
const AccessoryHoverChild({Key? key}) : super(key: key);
15+
16+
// The hover will show if the onFocus's value is true
17+
ValueNotifier<bool>? get isFocus;
18+
19+
List<GridCellAccessory> accessories();
20+
}
21+
22+
class AccessoryHover extends StatefulWidget {
23+
final AccessoryHoverChild child;
24+
final EdgeInsets contentPadding;
25+
const AccessoryHover({
26+
required this.child,
27+
this.contentPadding = EdgeInsets.zero,
28+
Key? key,
29+
}) : super(key: key);
30+
31+
@override
32+
State<AccessoryHover> createState() => _AccessoryHoverState();
33+
}
34+
35+
class _AccessoryHoverState extends State<AccessoryHover> {
36+
late AccessoryHoverState _hoverState;
37+
VoidCallback? _listenerFn;
38+
39+
@override
40+
void initState() {
41+
_hoverState = AccessoryHoverState();
42+
_listenerFn = () => _hoverState.isFocus = widget.child.isFocus?.value ?? false;
43+
widget.child.isFocus?.addListener(_listenerFn!);
44+
45+
super.initState();
46+
}
47+
48+
@override
49+
void dispose() {
50+
_hoverState.dispose();
51+
52+
if (_listenerFn != null) {
53+
widget.child.isFocus?.removeListener(_listenerFn!);
54+
_listenerFn = null;
55+
}
56+
super.dispose();
57+
}
58+
59+
@override
60+
Widget build(BuildContext context) {
61+
List<Widget> children = [
62+
const _Background(),
63+
Padding(padding: widget.contentPadding, child: widget.child),
64+
];
65+
final accessories = widget.child.accessories();
66+
if (accessories.isNotEmpty) {
67+
children.add(
68+
Padding(
69+
padding: const EdgeInsets.only(right: 6),
70+
child: AccessoryContainer(accessories: accessories),
71+
).positioned(right: 0),
72+
);
73+
}
74+
75+
return ChangeNotifierProvider.value(
76+
value: _hoverState,
77+
child: MouseRegion(
78+
cursor: SystemMouseCursors.click,
79+
opaque: false,
80+
onEnter: (p) => setState(() => _hoverState.onHover = true),
81+
onExit: (p) => setState(() => _hoverState.onHover = false),
82+
child: Stack(
83+
fit: StackFit.loose,
84+
alignment: AlignmentDirectional.center,
85+
children: children,
86+
),
87+
),
88+
);
89+
}
90+
}
91+
92+
class AccessoryHoverState extends ChangeNotifier {
93+
bool _onHover = false;
94+
bool _isFocus = false;
95+
96+
set onHover(bool value) {
97+
if (_onHover != value) {
98+
_onHover = value;
99+
notifyListeners();
100+
}
101+
}
102+
103+
bool get onHover => _onHover;
104+
105+
set isFocus(bool value) {
106+
if (_isFocus != value) {
107+
_isFocus = value;
108+
notifyListeners();
109+
}
110+
}
111+
112+
bool get isFocus => _isFocus;
113+
}
114+
115+
class _Background extends StatelessWidget {
116+
const _Background({Key? key}) : super(key: key);
117+
118+
@override
119+
Widget build(BuildContext context) {
120+
final theme = context.watch<AppTheme>();
121+
return Consumer<AccessoryHoverState>(
122+
builder: (context, state, child) {
123+
if (state.onHover || state.isFocus) {
124+
return FlowyHoverContainer(
125+
style: HoverStyle(borderRadius: Corners.s6Border, hoverColor: theme.shader6),
126+
);
127+
} else {
128+
return const SizedBox();
129+
}
130+
},
131+
);
132+
}
133+
}
134+
135+
class AccessoryContainer extends StatelessWidget {
136+
final List<GridCellAccessory> accessories;
137+
const AccessoryContainer({required this.accessories, Key? key}) : super(key: key);
138+
139+
@override
140+
Widget build(BuildContext context) {
141+
final theme = context.watch<AppTheme>();
142+
final children = accessories.map((accessory) {
143+
final hover = FlowyHover(
144+
style: HoverStyle(hoverColor: theme.bg3, backgroundColor: theme.surface),
145+
builder: (_, onHover) => Container(
146+
width: 26,
147+
height: 26,
148+
padding: const EdgeInsets.all(3),
149+
child: accessory,
150+
),
151+
);
152+
return GestureDetector(
153+
child: hover,
154+
behavior: HitTestBehavior.opaque,
155+
onTap: () => accessory.onTap(context),
156+
);
157+
}).toList();
158+
159+
return Wrap(children: children, spacing: 6);
160+
}
161+
}

frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart

Lines changed: 25 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
2-
import 'package:flowy_infra_ui/style_widget/hover.dart';
32
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show FieldType;
43
import 'package:flutter/widgets.dart';
54
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart';
@@ -8,6 +7,7 @@ import 'package:flutter/material.dart';
87
import 'package:provider/provider.dart';
98
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
109
import 'package:styled_widget/styled_widget.dart';
10+
import 'cell_accessory.dart';
1111
import 'checkbox_cell.dart';
1212
import 'date_cell/date_cell.dart';
1313
import 'number_cell.dart';
@@ -48,22 +48,20 @@ class BlankCell extends StatelessWidget {
4848
}
4949
}
5050

51-
abstract class GridCellExpander implements Widget {
52-
void onExpand(BuildContext context);
53-
}
54-
55-
abstract class GridCellWidget implements FlowyHoverWidget {
51+
abstract class GridCellWidget implements AccessoryHoverChild, CellContainerFocustable {
5652
@override
57-
final ValueNotifier<bool> onFocus = ValueNotifier<bool>(false);
53+
final ValueNotifier<bool> isFocus = ValueNotifier<bool>(false);
5854

59-
final GridCellBeginFocusFocus beginFocus = GridCellBeginFocusFocus();
60-
61-
GridCellExpander? buildExpander() {
62-
return null;
55+
@override
56+
List<GridCellAccessory> accessories() {
57+
return List.empty();
6358
}
59+
60+
@override
61+
final GridCellRequestBeginFocus requestBeginFocus = GridCellRequestBeginFocus();
6462
}
6563

66-
class GridCellBeginFocusFocus extends ChangeNotifier {
64+
class GridCellRequestBeginFocus extends ChangeNotifier {
6765
VoidCallback? _listener;
6866

6967
void setListener(VoidCallback listener) {
@@ -130,17 +128,22 @@ class CellStateNotifier extends ChangeNotifier {
130128
bool get onEnter => _onEnter;
131129
}
132130

131+
abstract class CellContainerFocustable {
132+
// Listen on the requestBeginFocus if the
133+
GridCellRequestBeginFocus get requestBeginFocus;
134+
}
135+
133136
class CellContainer extends StatelessWidget {
134137
final GridCellWidget child;
135-
final GridCellExpander? expander;
138+
final List<GridCellAccessory> accessories;
136139
final double width;
137140
final RegionStateNotifier rowStateNotifier;
138141
const CellContainer({
139142
Key? key,
140143
required this.child,
141144
required this.width,
142145
required this.rowStateNotifier,
143-
this.expander,
146+
this.accessories = const [],
144147
}) : super(key: key);
145148

146149
@override
@@ -152,17 +155,17 @@ class CellContainer extends StatelessWidget {
152155
selector: (context, notifier) => notifier.isFocus,
153156
builder: (context, isFocus, _) {
154157
Widget container = Center(child: child);
155-
child.onFocus.addListener(() {
156-
Provider.of<CellStateNotifier>(context, listen: false).isFocus = child.onFocus.value;
158+
child.isFocus.addListener(() {
159+
Provider.of<CellStateNotifier>(context, listen: false).isFocus = child.isFocus.value;
157160
});
158161

159-
if (expander != null) {
160-
container = CellEnterRegion(child: container, expander: expander!);
162+
if (accessories.isNotEmpty) {
163+
container = CellEnterRegion(child: container, accessories: accessories);
161164
}
162165

163166
return GestureDetector(
164167
behavior: HitTestBehavior.translucent,
165-
onTap: () => child.beginFocus.notify(),
168+
onTap: () => child.requestBeginFocus.notify(),
166169
child: Container(
167170
constraints: BoxConstraints(maxWidth: width, minHeight: 46),
168171
decoration: _makeBoxDecoration(context, isFocus),
@@ -189,33 +192,17 @@ class CellContainer extends StatelessWidget {
189192

190193
class CellEnterRegion extends StatelessWidget {
191194
final Widget child;
192-
final GridCellExpander expander;
193-
const CellEnterRegion({required this.child, required this.expander, Key? key}) : super(key: key);
195+
final List<GridCellAccessory> accessories;
196+
const CellEnterRegion({required this.child, required this.accessories, Key? key}) : super(key: key);
194197

195198
@override
196199
Widget build(BuildContext context) {
197-
final theme = context.watch<AppTheme>();
198-
199200
return Selector<CellStateNotifier, bool>(
200201
selector: (context, notifier) => notifier.onEnter,
201202
builder: (context, onEnter, _) {
202203
List<Widget> children = [child];
203204
if (onEnter) {
204-
final hover = FlowyHover(
205-
style: HoverStyle(hoverColor: theme.bg3, backgroundColor: theme.surface),
206-
builder: (_, onHover) => Container(
207-
width: 26,
208-
height: 26,
209-
padding: const EdgeInsets.all(3),
210-
child: expander,
211-
),
212-
);
213-
214-
children.add(GestureDetector(
215-
child: hover,
216-
behavior: HitTestBehavior.opaque,
217-
onTap: () => expander.onExpand(context),
218-
).positioned(right: 0));
205+
children.add(AccessoryContainer(accessories: accessories).positioned(right: 0));
219206
}
220207

221208
return MouseRegion(
@@ -225,7 +212,6 @@ class CellEnterRegion extends StatelessWidget {
225212
child: Stack(
226213
alignment: AlignmentDirectional.center,
227214
fit: StackFit.expand,
228-
// alignment: AlignmentDirectional.centerEnd,
229215
children: children,
230216
),
231217
);

frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/checkbox_cell.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,13 @@ class _CheckboxCellState extends State<CheckboxCell> {
5757

5858
@override
5959
Future<void> dispose() async {
60-
widget.beginFocus.removeAllListener();
60+
widget.requestBeginFocus.removeAllListener();
6161
_cellBloc.close();
6262
super.dispose();
6363
}
6464

6565
void _handleRequestFocus() {
66-
widget.beginFocus.setListener(() {
66+
widget.requestBeginFocus.setListener(() {
6767
_cellBloc.add(const CheckboxCellEvent.select());
6868
});
6969
}

frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/date_cell/date_cell.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ class _DateCellState extends State<DateCell> {
7676

7777
void _showCalendar(BuildContext context) {
7878
final bloc = context.read<DateCellBloc>();
79-
widget.onFocus.value = true;
80-
final calendar = DateCellEditor(onDismissed: () => widget.onFocus.value = false);
79+
widget.isFocus.value = true;
80+
final calendar = DateCellEditor(onDismissed: () => widget.isFocus.value = false);
8181
calendar.show(
8282
context,
8383
cellContext: bloc.cellContext.clone(),

frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/number_cell.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class _NumberCellState extends State<NumberCell> {
6565

6666
@override
6767
Future<void> dispose() async {
68-
widget.beginFocus.removeAllListener();
68+
widget.requestBeginFocus.removeAllListener();
6969
_delayOperation?.cancel();
7070
_cellBloc.close();
7171
_focusNode.removeAllListener();
@@ -91,15 +91,15 @@ class _NumberCellState extends State<NumberCell> {
9191
}
9292

9393
void _listenOnFocusNodeChanged() {
94-
widget.onFocus.value = _focusNode.hasFocus;
94+
widget.isFocus.value = _focusNode.hasFocus;
9595
_focusNode.setListener(() {
96-
widget.onFocus.value = _focusNode.hasFocus;
96+
widget.isFocus.value = _focusNode.hasFocus;
9797
focusChanged();
9898
});
9999
}
100100

101101
void _handleCellRequestFocus(BuildContext context) {
102-
widget.beginFocus.setListener(() {
102+
widget.requestBeginFocus.setListener(() {
103103
if (_focusNode.hasFocus == false && _focusNode.canRequestFocus) {
104104
FocusScope.of(context).requestFocus(_focusNode);
105105
}

frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/select_option_cell/select_option_cell.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
5959
return _SelectOptionCell(
6060
selectOptions: state.selectedOptions,
6161
cellStyle: widget.cellStyle,
62-
onFocus: (value) => widget.onFocus.value = value,
62+
onFocus: (value) => widget.isFocus.value = value,
6363
cellContextBuilder: widget.cellContextBuilder);
6464
},
6565
),
@@ -113,7 +113,7 @@ class _MultiSelectCellState extends State<MultiSelectCell> {
113113
return _SelectOptionCell(
114114
selectOptions: state.selectedOptions,
115115
cellStyle: widget.cellStyle,
116-
onFocus: (value) => widget.onFocus.value = value,
116+
onFocus: (value) => widget.isFocus.value = value,
117117
cellContextBuilder: widget.cellContextBuilder);
118118
},
119119
),

0 commit comments

Comments
 (0)