Skip to content

Commit 39b0fe6

Browse files
authored
Merge pull request #1085 from AppFlowy-IO/fix/popover_bugs
Fix/popover bugs
2 parents ed691be + bda16b1 commit 39b0fe6

File tree

20 files changed

+404
-380
lines changed

20 files changed

+404
-380
lines changed

frontend/app_flowy/lib/plugins/board/presentation/card/board_cell.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class EditableRowNotifier {
2323
EditableRowNotifier({required bool isEditing})
2424
: isEditing = ValueNotifier(isEditing);
2525

26-
void insertCell(
26+
void bindCell(
2727
GridCellIdentifier cellIdentifier,
2828
EditableCellNotifier notifier,
2929
) {
@@ -59,7 +59,7 @@ class EditableRowNotifier {
5959
_cells.values.first.isCellEditing.value = false;
6060
}
6161

62-
void clear() {
62+
void unbind() {
6363
for (final notifier in _cells.values) {
6464
notifier.dispose();
6565
}

frontend/app_flowy/lib/plugins/board/presentation/card/board_number_cell.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_servic
33
import 'package:flowy_infra_ui/style_widget/text.dart';
44
import 'package:flutter/material.dart';
55
import 'package:flutter_bloc/flutter_bloc.dart';
6-
76
import 'define.dart';
87

98
class BoardNumberCell extends StatefulWidget {

frontend/app_flowy/lib/plugins/board/presentation/card/board_select_option_cell.dart

Lines changed: 56 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import 'package:app_flowy/plugins/board/application/card/board_select_option_cel
22
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
33
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/extension.dart';
44
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart';
5+
import 'package:appflowy_popover/popover.dart';
6+
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
57
import 'package:flutter/material.dart';
68
import 'package:flutter_bloc/flutter_bloc.dart';
79

@@ -26,9 +28,11 @@ class BoardSelectOptionCell extends StatefulWidget with EditableCell {
2628

2729
class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
2830
late BoardSelectOptionCellBloc _cellBloc;
31+
late PopoverController _popover;
2932

3033
@override
3134
void initState() {
35+
_popover = PopoverController();
3236
final cellController =
3337
widget.cellControllerBuilder.build() as GridSelectOptionCellController;
3438
_cellBloc = BoardSelectOptionCellBloc(cellController: cellController)
@@ -41,43 +45,60 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
4145
return BlocProvider.value(
4246
value: _cellBloc,
4347
child: BlocBuilder<BoardSelectOptionCellBloc, BoardSelectOptionCellState>(
44-
buildWhen: (previous, current) {
45-
return previous.selectedOptions != current.selectedOptions;
46-
},
47-
builder: (context, state) {
48-
if (state.selectedOptions
49-
.where((element) => element.id == widget.groupId)
50-
.isNotEmpty ||
51-
state.selectedOptions.isEmpty) {
52-
return const SizedBox();
53-
} else {
54-
final children = state.selectedOptions
55-
.map(
56-
(option) => SelectOptionTag.fromOption(
57-
context: context,
58-
option: option,
59-
onSelected: () {
60-
SelectOptionCellEditor.show(
61-
context: context,
62-
cellController: widget.cellControllerBuilder.build()
63-
as GridSelectOptionCellController,
64-
);
65-
},
66-
),
67-
)
68-
.toList();
48+
buildWhen: (previous, current) {
49+
return previous.selectedOptions != current.selectedOptions;
50+
}, builder: (context, state) {
51+
// Returns SizedBox if the content of the cell is empty
52+
if (_isEmpty(state)) return const SizedBox();
6953

70-
return IntrinsicHeight(
71-
child: Padding(
72-
padding: const EdgeInsets.symmetric(vertical: 6),
73-
child: SizedBox.expand(
74-
child: Wrap(spacing: 4, runSpacing: 2, children: children),
75-
),
76-
),
54+
final children = state.selectedOptions.map(
55+
(option) {
56+
final tag = SelectOptionTag.fromOption(
57+
context: context,
58+
option: option,
59+
onSelected: () => _popover.show(),
7760
);
78-
}
79-
},
80-
),
61+
return _wrapPopover(tag);
62+
},
63+
).toList();
64+
65+
return IntrinsicHeight(
66+
child: Padding(
67+
padding: const EdgeInsets.symmetric(vertical: 6),
68+
child: SizedBox.expand(
69+
child: Wrap(spacing: 4, runSpacing: 2, children: children),
70+
),
71+
),
72+
);
73+
}),
74+
);
75+
}
76+
77+
bool _isEmpty(BoardSelectOptionCellState state) {
78+
// The cell should hide if the option id is equal to the groupId.
79+
final isInGroup = state.selectedOptions
80+
.where((element) => element.id == widget.groupId)
81+
.isNotEmpty;
82+
return isInGroup || state.selectedOptions.isEmpty;
83+
}
84+
85+
Widget _wrapPopover(Widget child) {
86+
final constraints = BoxConstraints.loose(Size(
87+
SelectOptionCellEditor.editorPanelWidth,
88+
300,
89+
));
90+
return AppFlowyStylePopover(
91+
controller: _popover,
92+
constraints: constraints,
93+
direction: PopoverDirection.bottomWithLeftAligned,
94+
popupBuilder: (BuildContext context) {
95+
return SelectOptionCellEditor(
96+
cellController: widget.cellControllerBuilder.build()
97+
as GridSelectOptionCellController,
98+
);
99+
},
100+
onClose: () {},
101+
child: child,
81102
);
82103
}
83104

frontend/app_flowy/lib/plugins/board/presentation/card/card.dart

Lines changed: 61 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import 'package:app_flowy/plugins/board/application/card/card_bloc.dart';
22
import 'package:app_flowy/plugins/board/application/card/card_data_controller.dart';
3-
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
43
import 'package:app_flowy/plugins/grid/presentation/widgets/row/row_action_sheet.dart';
54
import 'package:flowy_infra/image.dart';
65
import 'package:flowy_infra/theme.dart';
@@ -64,83 +63,103 @@ class _BoardCardState extends State<BoardCard> {
6463
value: _cardBloc,
6564
child: BlocBuilder<BoardCardBloc, BoardCardState>(
6665
buildWhen: (previous, current) {
66+
// Rebuild when:
67+
// 1.If the lenght of the cells is not the same
68+
// 2.isEditing changed
6769
if (previous.cells.length != current.cells.length ||
6870
previous.isEditing != current.isEditing) {
6971
return true;
7072
}
73+
74+
// 3.Compare the content of the cells. The cells consisits of
75+
// list of [BoardCellEquatable] that extends the [Equatable].
7176
return !listEquals(previous.cells, current.cells);
7277
},
7378
builder: (context, state) {
7479
return BoardCardContainer(
7580
buildAccessoryWhen: () => state.isEditing == false,
7681
accessoryBuilder: (context) {
7782
return [
78-
_CardEditOption(
79-
startEditing: () => rowNotifier.becomeFirstResponder(),
80-
),
83+
_CardEditOption(rowNotifier: rowNotifier),
8184
const _CardMoreOption(),
8285
];
8386
},
84-
onTap: (context) {
85-
widget.openCard(context);
86-
},
87-
child: Column(
88-
mainAxisSize: MainAxisSize.min,
89-
children: _makeCells(
90-
context,
91-
state.cells.map((cell) => cell.identifier).toList(),
92-
),
87+
onTap: (context) => widget.openCard(context),
88+
child: _CellColumn(
89+
groupId: widget.groupId,
90+
rowNotifier: rowNotifier,
91+
cellBuilder: widget.cellBuilder,
92+
cells: state.cells,
9393
),
9494
);
9595
},
9696
),
9797
);
9898
}
9999

100+
@override
101+
Future<void> dispose() async {
102+
rowNotifier.dispose();
103+
_cardBloc.close();
104+
super.dispose();
105+
}
106+
}
107+
108+
class _CellColumn extends StatelessWidget {
109+
final String groupId;
110+
final BoardCellBuilder cellBuilder;
111+
final EditableRowNotifier rowNotifier;
112+
final List<BoardCellEquatable> cells;
113+
const _CellColumn({
114+
required this.groupId,
115+
required this.rowNotifier,
116+
required this.cellBuilder,
117+
required this.cells,
118+
Key? key,
119+
}) : super(key: key);
120+
121+
@override
122+
Widget build(BuildContext context) {
123+
return Column(
124+
mainAxisSize: MainAxisSize.min,
125+
children: _makeCells(context, cells),
126+
);
127+
}
128+
100129
List<Widget> _makeCells(
101130
BuildContext context,
102-
List<GridCellIdentifier> cells,
131+
List<BoardCellEquatable> cells,
103132
) {
104133
final List<Widget> children = [];
105-
rowNotifier.clear();
134+
// Remove all the cell listeners.
135+
rowNotifier.unbind();
136+
106137
cells.asMap().forEach(
107-
(int index, GridCellIdentifier cellId) {
108-
EditableCellNotifier cellNotifier;
138+
(int index, BoardCellEquatable cell) {
139+
final isEditing = index == 0 ? rowNotifier.isEditing.value : false;
140+
final cellNotifier = EditableCellNotifier(isEditing: isEditing);
141+
109142
if (index == 0) {
110143
// Only use the first cell to receive user's input when click the edit
111144
// button
112-
cellNotifier = EditableCellNotifier(
113-
isEditing: rowNotifier.isEditing.value,
114-
);
115-
rowNotifier.insertCell(cellId, cellNotifier);
116-
} else {
117-
cellNotifier = EditableCellNotifier();
145+
rowNotifier.bindCell(cell.identifier, cellNotifier);
118146
}
119147

120-
Widget child = widget.cellBuilder.buildCell(
121-
widget.groupId,
122-
cellId,
123-
cellNotifier,
124-
);
125-
126-
child = Padding(
127-
key: cellId.key(),
148+
final child = Padding(
149+
key: cell.identifier.key(),
128150
padding: const EdgeInsets.only(left: 4, right: 4),
129-
child: child,
151+
child: cellBuilder.buildCell(
152+
groupId,
153+
cell.identifier,
154+
cellNotifier,
155+
),
130156
);
131157

132158
children.add(child);
133159
},
134160
);
135161
return children;
136162
}
137-
138-
@override
139-
Future<void> dispose() async {
140-
rowNotifier.dispose();
141-
_cardBloc.close();
142-
super.dispose();
143-
}
144163
}
145164

146165
class _CardMoreOption extends StatelessWidget with CardAccessory {
@@ -164,9 +183,9 @@ class _CardMoreOption extends StatelessWidget with CardAccessory {
164183
}
165184

166185
class _CardEditOption extends StatelessWidget with CardAccessory {
167-
final VoidCallback startEditing;
186+
final EditableRowNotifier rowNotifier;
168187
const _CardEditOption({
169-
required this.startEditing,
188+
required this.rowNotifier,
170189
Key? key,
171190
}) : super(key: key);
172191

@@ -183,6 +202,6 @@ class _CardEditOption extends StatelessWidget with CardAccessory {
183202

184203
@override
185204
void onTap(BuildContext context) {
186-
startEditing();
205+
rowNotifier.becomeFirstResponder();
187206
}
188207
}

frontend/app_flowy/lib/plugins/board/presentation/card/card_container.dart

Lines changed: 28 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -72,52 +72,46 @@ class CardAccessoryContainer extends StatelessWidget {
7272
Widget build(BuildContext context) {
7373
final theme = context.read<AppTheme>();
7474
final children = accessories.map((accessory) {
75-
final hover = FlowyHover(
76-
style: HoverStyle(
77-
hoverColor: theme.hover,
78-
backgroundColor: theme.surface,
79-
borderRadius: BorderRadius.zero,
80-
),
81-
builder: (_, onHover) => SizedBox(
82-
width: 24,
83-
height: 24,
84-
child: accessory,
85-
),
86-
);
8775
return GestureDetector(
8876
behavior: HitTestBehavior.opaque,
8977
onTap: () => accessory.onTap(context),
90-
child: hover,
78+
child: _wrapHover(theme, accessory),
9179
);
9280
}).toList();
81+
return _wrapDecoration(context, Row(children: children));
82+
}
9383

84+
FlowyHover _wrapHover(AppTheme theme, CardAccessory accessory) {
85+
return FlowyHover(
86+
style: HoverStyle(
87+
hoverColor: theme.hover,
88+
backgroundColor: theme.surface,
89+
borderRadius: BorderRadius.zero,
90+
),
91+
builder: (_, onHover) => SizedBox(
92+
width: 24,
93+
height: 24,
94+
child: accessory,
95+
),
96+
);
97+
}
98+
99+
Widget _wrapDecoration(BuildContext context, Widget child) {
100+
final theme = context.read<AppTheme>();
101+
final borderSide = BorderSide(color: theme.shader6, width: 1.0);
102+
final decoration = BoxDecoration(
103+
color: Colors.transparent,
104+
border: Border.fromBorderSide(borderSide),
105+
borderRadius: const BorderRadius.all(Radius.circular(4)),
106+
);
94107
return Container(
95108
clipBehavior: Clip.hardEdge,
96-
decoration: _makeBoxDecoration(context),
97-
child: Row(children: children),
109+
decoration: decoration,
110+
child: child,
98111
);
99112
}
100113
}
101114

102-
BoxDecoration _makeBoxDecoration(BuildContext context) {
103-
final theme = context.read<AppTheme>();
104-
final borderSide = BorderSide(color: theme.shader6, width: 1.0);
105-
return BoxDecoration(
106-
color: Colors.transparent,
107-
border: Border.fromBorderSide(borderSide),
108-
// boxShadow: const [
109-
// BoxShadow(
110-
// color: Colors.transparent,
111-
// spreadRadius: 0,
112-
// blurRadius: 5,
113-
// offset: Offset.zero,
114-
// )
115-
// ],
116-
117-
borderRadius: const BorderRadius.all(Radius.circular(4)),
118-
);
119-
}
120-
121115
class _CardEnterRegion extends StatelessWidget {
122116
final Widget child;
123117
final List<CardAccessory> accessories;

0 commit comments

Comments
 (0)