Skip to content

Commit 1ba2998

Browse files
committed
feat: implement checklist UI
1 parent b4671c1 commit 1ba2998

File tree

64 files changed

+715
-110
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+715
-110
lines changed

frontend/app_flowy/assets/translations/en.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,9 @@
265265
"panelTitle": "Select an option or create one",
266266
"searchOption": "Search for an option"
267267
},
268+
"checklist": {
269+
"panelTitle": "Search an option or create one"
270+
},
268271
"menuName": "Grid"
269272
},
270273
"document": {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ Widget? _buildHeaderIcon(GroupData customData) {
358358
break;
359359
case FieldType.URL:
360360
break;
361-
case FieldType.CheckList:
361+
case FieldType.Checklist:
362362
break;
363363
}
364364

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ class BoardCellBuilder {
5959
editableNotifier: cellNotifier,
6060
key: key,
6161
);
62-
case FieldType.CheckList:
62+
case FieldType.Checklist:
6363
return BoardChecklistCell(
6464
key: key,
6565
);

frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_controller.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ typedef GridCheckboxCellController = IGridCellController<String, String>;
55
typedef GridNumberCellController = IGridCellController<String, String>;
66
typedef GridSelectOptionCellController
77
= IGridCellController<SelectOptionCellDataPB, String>;
8+
typedef GridChecklistCellController
9+
= IGridCellController<SelectOptionCellDataPB, String>;
810
typedef GridDateCellController
911
= IGridCellController<DateCellDataPB, CalendarData>;
1012
typedef GridURLCellController = IGridCellController<URLCellDataPB, String>;
@@ -81,7 +83,7 @@ class GridCellControllerBuilder {
8183
);
8284
case FieldType.MultiSelect:
8385
case FieldType.SingleSelect:
84-
case FieldType.CheckList:
86+
case FieldType.Checklist:
8587
final cellDataLoader = GridCellDataLoader(
8688
cellId: _cellId,
8789
parser: SelectOptionCellDataParser(),
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import 'package:flowy_sdk/log.dart';
2+
import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
3+
import 'package:flutter_bloc/flutter_bloc.dart';
4+
import 'package:freezed_annotation/freezed_annotation.dart';
5+
import 'dart:async';
6+
import 'cell_service/cell_service.dart';
7+
import 'select_option_service.dart';
8+
part 'checklist_cell_bloc.freezed.dart';
9+
10+
class ChecklistCellBloc extends Bloc<ChecklistCellEvent, ChecklistCellState> {
11+
final GridChecklistCellController cellController;
12+
final SelectOptionFFIService _selectOptionService;
13+
void Function()? _onCellChangedFn;
14+
ChecklistCellBloc({
15+
required this.cellController,
16+
}) : _selectOptionService =
17+
SelectOptionFFIService(cellId: cellController.cellId),
18+
super(ChecklistCellState.initial(cellController)) {
19+
on<ChecklistCellEvent>(
20+
(event, emit) async {
21+
await event.when(
22+
initial: () async {
23+
_startListening();
24+
_loadOptions();
25+
},
26+
didReceiveOptions: (data) {
27+
emit(state.copyWith(
28+
allOptions: data.options,
29+
selectedOptions: data.selectOptions,
30+
percent: data.selectOptions.length.toDouble() /
31+
data.options.length.toDouble(),
32+
));
33+
},
34+
);
35+
},
36+
);
37+
}
38+
39+
@override
40+
Future<void> close() async {
41+
if (_onCellChangedFn != null) {
42+
cellController.removeListener(_onCellChangedFn!);
43+
_onCellChangedFn = null;
44+
}
45+
await cellController.dispose();
46+
return super.close();
47+
}
48+
49+
void _startListening() {
50+
_onCellChangedFn = cellController.startListening(
51+
onCellFieldChanged: () {
52+
_loadOptions();
53+
},
54+
onCellChanged: (_) {},
55+
);
56+
}
57+
58+
void _loadOptions() {
59+
_selectOptionService.getOptionContext().then((result) {
60+
if (isClosed) return;
61+
62+
return result.fold(
63+
(data) => add(ChecklistCellEvent.didReceiveOptions(data)),
64+
(err) => Log.error(err),
65+
);
66+
});
67+
}
68+
}
69+
70+
@freezed
71+
class ChecklistCellEvent with _$ChecklistCellEvent {
72+
const factory ChecklistCellEvent.initial() = _InitialCell;
73+
const factory ChecklistCellEvent.didReceiveOptions(
74+
SelectOptionCellDataPB data) = _DidReceiveCellUpdate;
75+
}
76+
77+
@freezed
78+
class ChecklistCellState with _$ChecklistCellState {
79+
const factory ChecklistCellState({
80+
required List<SelectOptionPB> allOptions,
81+
required List<SelectOptionPB> selectedOptions,
82+
required double percent,
83+
}) = _ChecklistCellState;
84+
85+
factory ChecklistCellState.initial(
86+
GridChecklistCellController cellController) {
87+
return const ChecklistCellState(
88+
allOptions: [],
89+
selectedOptions: [],
90+
percent: 0,
91+
);
92+
}
93+
}
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import 'dart:async';
2+
3+
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
4+
import 'package:dartz/dartz.dart';
5+
import 'package:flowy_sdk/log.dart';
6+
import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
7+
import 'package:flutter_bloc/flutter_bloc.dart';
8+
import 'package:freezed_annotation/freezed_annotation.dart';
9+
10+
import 'select_option_service.dart';
11+
12+
part 'checklist_cell_editor_bloc.freezed.dart';
13+
14+
class ChecklistCellEditorBloc
15+
extends Bloc<ChecklistCellEditorEvent, ChecklistCellEditorState> {
16+
final SelectOptionFFIService _selectOptionService;
17+
final GridChecklistCellController cellController;
18+
Timer? _delayOperation;
19+
20+
ChecklistCellEditorBloc({
21+
required this.cellController,
22+
}) : _selectOptionService =
23+
SelectOptionFFIService(cellId: cellController.cellId),
24+
super(ChecklistCellEditorState.initial(cellController)) {
25+
on<ChecklistCellEditorEvent>(
26+
(event, emit) async {
27+
await event.when(
28+
initial: () async {
29+
_startListening();
30+
_loadOptions();
31+
},
32+
didReceiveOptions: (data) {
33+
emit(state.copyWith(
34+
allOptions: _makeChecklistSelectOptions(data, state.predicate),
35+
percent: _percentFromSelectOptionCellData(data),
36+
));
37+
},
38+
newOption: (optionName) {
39+
_createOption(optionName);
40+
emit(state.copyWith(
41+
predicate: '',
42+
));
43+
},
44+
deleteOption: (option) {
45+
_deleteOption([option]);
46+
},
47+
updateOption: (option) {
48+
_updateOption(option);
49+
},
50+
selectOption: (optionId) {
51+
_selectOptionService.select(optionIds: [optionId]);
52+
},
53+
unSelectOption: (optionId) {
54+
_selectOptionService.unSelect(optionIds: [optionId]);
55+
},
56+
filterOption: (String predicate) {},
57+
);
58+
},
59+
);
60+
}
61+
62+
@override
63+
Future<void> close() async {
64+
_delayOperation?.cancel();
65+
await cellController.dispose();
66+
return super.close();
67+
}
68+
69+
void _createOption(String name) async {
70+
final result = await _selectOptionService.create(
71+
name: name,
72+
isSelected: false,
73+
);
74+
result.fold((l) => {}, (err) => Log.error(err));
75+
}
76+
77+
void _deleteOption(List<SelectOptionPB> options) async {
78+
final result = await _selectOptionService.delete(options: options);
79+
result.fold((l) => null, (err) => Log.error(err));
80+
}
81+
82+
void _updateOption(SelectOptionPB option) async {
83+
final result = await _selectOptionService.update(
84+
option: option,
85+
);
86+
87+
result.fold((l) => null, (err) => Log.error(err));
88+
}
89+
90+
void _loadOptions() {
91+
_selectOptionService.getOptionContext().then((result) {
92+
if (isClosed) return;
93+
94+
return result.fold(
95+
(data) => add(ChecklistCellEditorEvent.didReceiveOptions(data)),
96+
(err) => Log.error(err),
97+
);
98+
});
99+
}
100+
101+
void _startListening() {
102+
cellController.startListening(
103+
onCellChanged: ((data) {
104+
if (!isClosed && data != null) {
105+
add(ChecklistCellEditorEvent.didReceiveOptions(data));
106+
}
107+
}),
108+
onCellFieldChanged: () {
109+
_loadOptions();
110+
},
111+
);
112+
}
113+
}
114+
115+
@freezed
116+
class ChecklistCellEditorEvent with _$ChecklistCellEditorEvent {
117+
const factory ChecklistCellEditorEvent.initial() = _Initial;
118+
const factory ChecklistCellEditorEvent.didReceiveOptions(
119+
SelectOptionCellDataPB data) = _DidReceiveOptions;
120+
const factory ChecklistCellEditorEvent.newOption(String optionName) =
121+
_NewOption;
122+
const factory ChecklistCellEditorEvent.selectOption(String optionId) =
123+
_SelectOption;
124+
const factory ChecklistCellEditorEvent.unSelectOption(String optionId) =
125+
_UnSelectOption;
126+
const factory ChecklistCellEditorEvent.updateOption(SelectOptionPB option) =
127+
_UpdateOption;
128+
const factory ChecklistCellEditorEvent.deleteOption(SelectOptionPB option) =
129+
_DeleteOption;
130+
const factory ChecklistCellEditorEvent.filterOption(String predicate) =
131+
_FilterOption;
132+
}
133+
134+
@freezed
135+
class ChecklistCellEditorState with _$ChecklistCellEditorState {
136+
const factory ChecklistCellEditorState({
137+
required List<ChecklistSelectOption> allOptions,
138+
required Option<String> createOption,
139+
required double percent,
140+
required String predicate,
141+
}) = _ChecklistCellEditorState;
142+
143+
factory ChecklistCellEditorState.initial(
144+
GridSelectOptionCellController context) {
145+
final data = context.getCellData(loadIfNotExist: true);
146+
147+
return ChecklistCellEditorState(
148+
allOptions: _makeChecklistSelectOptions(data, ''),
149+
createOption: none(),
150+
percent: _percentFromSelectOptionCellData(data),
151+
predicate: '',
152+
);
153+
}
154+
}
155+
156+
double _percentFromSelectOptionCellData(SelectOptionCellDataPB? data) {
157+
if (data == null) return 0;
158+
159+
final a = data.selectOptions.length.toDouble();
160+
final b = data.options.length.toDouble();
161+
162+
if (a > b) return 1.0;
163+
164+
return a / b;
165+
}
166+
167+
List<ChecklistSelectOption> _makeChecklistSelectOptions(
168+
SelectOptionCellDataPB? data, String predicate) {
169+
if (data == null) {
170+
return [];
171+
}
172+
173+
final List<ChecklistSelectOption> options = [];
174+
final List<SelectOptionPB> allOptions = List.from(data.options);
175+
if (predicate.isNotEmpty) {
176+
allOptions.retainWhere((element) => element.name.contains(predicate));
177+
}
178+
final selectedOptionIds = data.selectOptions.map((e) => e.id).toList();
179+
180+
for (final option in allOptions) {
181+
options.add(
182+
ChecklistSelectOption(selectedOptionIds.contains(option.id), option),
183+
);
184+
}
185+
186+
return options;
187+
}
188+
189+
class ChecklistSelectOption {
190+
final bool isSelected;
191+
final SelectOptionPB data;
192+
193+
ChecklistSelectOption(this.isSelected, this.data);
194+
}

frontend/app_flowy/lib/plugins/grid/application/cell/select_option_editor_bloc.dart

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,24 @@
11
import 'dart:async';
2-
32
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
43
import 'package:dartz/dartz.dart';
54
import 'package:flowy_sdk/log.dart';
65
import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
76
import 'package:flutter_bloc/flutter_bloc.dart';
87
import 'package:freezed_annotation/freezed_annotation.dart';
9-
108
import 'select_option_service.dart';
119

1210
part 'select_option_editor_bloc.freezed.dart';
1311

1412
class SelectOptionCellEditorBloc
1513
extends Bloc<SelectOptionEditorEvent, SelectOptionEditorState> {
16-
final SelectOptionService _selectOptionService;
14+
final SelectOptionFFIService _selectOptionService;
1715
final GridSelectOptionCellController cellController;
1816
Timer? _delayOperation;
1917

2018
SelectOptionCellEditorBloc({
2119
required this.cellController,
2220
}) : _selectOptionService =
23-
SelectOptionService(cellId: cellController.cellId),
21+
SelectOptionFFIService(cellId: cellController.cellId),
2422
super(SelectOptionEditorState.initial(cellController)) {
2523
on<SelectOptionEditorEvent>(
2624
(event, emit) async {

frontend/app_flowy/lib/plugins/grid/application/cell/select_option_service.dart

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,16 @@ import 'package:app_flowy/plugins/grid/application/field/type_option/type_option
66
import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
77
import 'cell_service/cell_service.dart';
88

9-
class SelectOptionService {
9+
class SelectOptionFFIService {
1010
final GridCellIdentifier cellId;
11-
SelectOptionService({required this.cellId});
11+
SelectOptionFFIService({required this.cellId});
1212

1313
String get gridId => cellId.gridId;
1414
String get fieldId => cellId.fieldInfo.id;
1515
String get rowId => cellId.rowId;
1616

17-
Future<Either<Unit, FlowyError>> create({required String name}) {
17+
Future<Either<Unit, FlowyError>> create(
18+
{required String name, bool isSelected = true}) {
1819
return TypeOptionFFIService(gridId: gridId, fieldId: fieldId)
1920
.newOption(name: name)
2021
.then(
@@ -26,8 +27,13 @@ class SelectOptionService {
2627
..fieldId = fieldId
2728
..rowId = rowId;
2829
final payload = SelectOptionChangesetPB.create()
29-
..insertOptions.add(option)
3030
..cellIdentifier = cellIdentifier;
31+
32+
if (isSelected) {
33+
payload.insertOptions.add(option);
34+
} else {
35+
payload.updateOptions.add(option);
36+
}
3137
return GridEventUpdateSelectOption(payload).send();
3238
},
3339
(r) => right(r),

0 commit comments

Comments
 (0)