feat: checklist improve (#2653)

* feat: improve checklist

* feat: reimplement checklist

* test: fix
This commit is contained in:
Nathan.fooo
2023-05-30 09:41:33 +08:00
committed by GitHub
parent dc73df4203
commit 107662dceb
54 changed files with 897 additions and 501 deletions

View File

@ -1,3 +1,4 @@
import 'package:appflowy_backend/protobuf/flowy-database2/checklist_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart';
@ -11,8 +12,7 @@ typedef CheckboxCellController = CellController<String, String>;
typedef NumberCellController = CellController<String, String>;
typedef SelectOptionCellController
= CellController<SelectOptionCellDataPB, String>;
typedef ChecklistCellController
= CellController<SelectOptionCellDataPB, String>;
typedef ChecklistCellController = CellController<ChecklistCellDataPB, String>;
typedef DateCellController = CellController<DateCellDataPB, DateCellData>;
typedef URLCellController = CellController<URLCellDataPB, String>;
@ -79,7 +79,6 @@ class CellControllerBuilder {
);
case FieldType.MultiSelect:
case FieldType.SingleSelect:
case FieldType.Checklist:
final cellDataLoader = CellDataLoader(
cellId: _cellId,
parser: SelectOptionCellDataParser(),
@ -93,6 +92,19 @@ class CellControllerBuilder {
cellDataPersistence: TextCellDataPersistence(cellId: _cellId),
);
case FieldType.Checklist:
final cellDataLoader = CellDataLoader(
cellId: _cellId,
parser: ChecklistCellDataParser(),
reloadOnFieldChanged: true,
);
return ChecklistCellController(
cellId: _cellId,
cellCache: _cellCache,
cellDataLoader: cellDataLoader,
cellDataPersistence: TextCellDataPersistence(cellId: _cellId),
);
case FieldType.URL:
final cellDataLoader = CellDataLoader(
cellId: _cellId,

View File

@ -84,6 +84,16 @@ class SelectOptionCellDataParser
}
}
class ChecklistCellDataParser implements CellDataParser<ChecklistCellDataPB> {
@override
ChecklistCellDataPB? parserData(List<int> data) {
if (data.isEmpty) {
return null;
}
return ChecklistCellDataPB.fromBuffer(data);
}
}
class URLCellDataParser implements CellDataParser<URLCellDataPB> {
@override
URLCellDataPB? parserData(List<int> data) {

View File

@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:collection';
import 'package:appflowy_backend/protobuf/flowy-database2/checklist_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/url_entities.pb.dart';

View File

@ -0,0 +1,71 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/cell_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/checklist_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:dartz/dartz.dart';
class ChecklistCellBackendService {
final CellIdentifier cellId;
ChecklistCellBackendService({required this.cellId});
Future<Either<Unit, FlowyError>> create({
required String name,
}) {
final payload = ChecklistCellDataChangesetPB.create()
..viewId = cellId.viewId
..fieldId = cellId.fieldInfo.id
..rowId = cellId.rowId
..insertOptions.add(name);
return DatabaseEventUpdateChecklistCell(payload).send();
}
Future<Either<Unit, FlowyError>> delete({
required List<String> optionIds,
}) {
final payload = ChecklistCellDataChangesetPB.create()
..viewId = cellId.viewId
..fieldId = cellId.fieldInfo.id
..rowId = cellId.rowId
..deleteOptionIds.addAll(optionIds);
return DatabaseEventUpdateChecklistCell(payload).send();
}
Future<Either<Unit, FlowyError>> select({
required String optionId,
}) {
final payload = ChecklistCellDataChangesetPB.create()
..viewId = cellId.viewId
..fieldId = cellId.fieldInfo.id
..rowId = cellId.rowId
..selectedOptionIds.add(optionId);
return DatabaseEventUpdateChecklistCell(payload).send();
}
Future<Either<Unit, FlowyError>> update({
required SelectOptionPB option,
}) {
final payload = ChecklistCellDataChangesetPB.create()
..viewId = cellId.viewId
..fieldId = cellId.fieldInfo.id
..rowId = cellId.rowId
..updateOptions.add(option);
return DatabaseEventUpdateChecklistCell(payload).send();
}
Future<Either<ChecklistCellDataPB, FlowyError>> getCellData() {
final payload = CellIdPB.create()
..fieldId = cellId.fieldInfo.id
..viewId = cellId.viewId
..rowId = cellId.rowId
..rowId = cellId.rowId;
return DatabaseEventGetChecklistCellData(payload).send();
}
}

View File

@ -7,9 +7,9 @@ import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/cell_entities.pb.dart';
class SelectOptionBackendService {
class SelectOptionCellBackendService {
final CellIdentifier cellId;
SelectOptionBackendService({required this.cellId});
SelectOptionCellBackendService({required this.cellId});
String get viewId => cellId.viewId;
String get fieldId => cellId.fieldInfo.id;

View File

@ -17,7 +17,7 @@ import 'field_type_option_editor.dart';
class FieldEditor extends StatefulWidget {
final String viewId;
final String fieldName;
final bool isGroupField;
final bool isGroupingField;
final Function(String)? onDeleted;
final IFieldTypeOptionLoader typeOptionLoader;
@ -25,7 +25,7 @@ class FieldEditor extends StatefulWidget {
required this.viewId,
this.fieldName = "",
required this.typeOptionLoader,
this.isGroupField = false,
this.isGroupingField = false,
this.onDeleted,
Key? key,
}) : super(key: key);
@ -61,7 +61,7 @@ class _FieldEditorState extends State<FieldEditor> {
create: (context) => FieldEditorBloc(
viewId: widget.viewId,
fieldName: widget.fieldName,
isGroupField: widget.isGroupField,
isGroupField: widget.isGroupingField,
loader: widget.typeOptionLoader,
)..add(const FieldEditorEvent.initial()),
child: ListView.builder(

View File

@ -226,7 +226,7 @@ class _SelectOptionColorCell extends StatelessWidget {
dimension: 16,
child: Container(
decoration: BoxDecoration(
color: color.make(context),
color: color.toColor(context),
shape: BoxShape.circle,
),
),

View File

@ -1,22 +1,22 @@
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/select_option_service.dart';
import 'package:appflowy/plugins/database_view/application/cell/checklist_cell_service.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/checklist_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import 'checklist_cell_editor_bloc.dart';
part 'checklist_cell_bloc.freezed.dart';
class ChecklistCardCellBloc
extends Bloc<ChecklistCellEvent, ChecklistCellState> {
final ChecklistCellController cellController;
final SelectOptionBackendService _selectOptionSvc;
final ChecklistCellBackendService _checklistCellSvc;
void Function()? _onCellChangedFn;
ChecklistCardCellBloc({
required this.cellController,
}) : _selectOptionSvc =
SelectOptionBackendService(cellId: cellController.cellId),
}) : _checklistCellSvc =
ChecklistCellBackendService(cellId: cellController.cellId),
super(ChecklistCellState.initial(cellController)) {
on<ChecklistCellEvent>(
(event, emit) async {
@ -29,8 +29,8 @@ class ChecklistCardCellBloc
emit(
state.copyWith(
allOptions: data.options,
selectedOptions: data.selectOptions,
percent: percentFromSelectOptionCellData(data),
selectedOptions: data.selectedOptions,
percent: data.percentage,
),
);
},
@ -63,7 +63,7 @@ class ChecklistCardCellBloc
}
void _loadOptions() {
_selectOptionSvc.getCellData().then((result) {
_checklistCellSvc.getCellData().then((result) {
if (isClosed) return;
return result.fold(
@ -78,7 +78,7 @@ class ChecklistCardCellBloc
class ChecklistCellEvent with _$ChecklistCellEvent {
const factory ChecklistCellEvent.initial() = _InitialCell;
const factory ChecklistCellEvent.didReceiveOptions(
SelectOptionCellDataPB data,
ChecklistCellDataPB data,
) = _DidReceiveCellUpdate;
}

View File

@ -48,33 +48,33 @@ class _GridChecklistCellEditorState extends State<GridChecklistCellEditor> {
final List<Widget> slivers = [
const SliverChecklistProgressBar(),
SliverToBoxAdapter(
child: Padding(
padding: GridSize.typeOptionContentInsets,
child: ListView.separated(
controller: ScrollController(),
shrinkWrap: true,
itemCount: state.allOptions.length,
itemBuilder: (BuildContext context, int index) {
return _ChecklistOptionCell(
option: state.allOptions[index],
popoverMutex: popoverMutex,
);
},
separatorBuilder: (BuildContext context, int index) {
return VSpace(GridSize.typeOptionSeparatorHeight);
},
),
child: ListView.separated(
controller: ScrollController(),
shrinkWrap: true,
itemCount: state.allOptions.length,
itemBuilder: (BuildContext context, int index) {
return _ChecklistOptionCell(
option: state.allOptions[index],
popoverMutex: popoverMutex,
);
},
separatorBuilder: (BuildContext context, int index) {
return VSpace(GridSize.typeOptionSeparatorHeight);
},
),
),
];
return ScrollConfiguration(
behavior: const ScrollBehavior().copyWith(scrollbars: false),
child: CustomScrollView(
shrinkWrap: true,
slivers: slivers,
controller: ScrollController(),
physics: StyledScrollPhysics(),
return Padding(
padding: const EdgeInsets.all(8.0),
child: ScrollConfiguration(
behavior: const ScrollBehavior().copyWith(scrollbars: false),
child: CustomScrollView(
shrinkWrap: true,
slivers: slivers,
controller: ScrollController(),
physics: StyledScrollPhysics(),
),
),
);
},

View File

@ -1,7 +1,8 @@
import 'dart:async';
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/select_option_service.dart';
import 'package:appflowy/plugins/database_view/application/cell/checklist_cell_service.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/checklist_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart';
import 'package:dartz/dartz.dart';
import 'package:appflowy_backend/log.dart';
@ -12,13 +13,13 @@ part 'checklist_cell_editor_bloc.freezed.dart';
class ChecklistCellEditorBloc
extends Bloc<ChecklistCellEditorEvent, ChecklistCellEditorState> {
final SelectOptionBackendService _selectOptionService;
final ChecklistCellBackendService _checklistCellService;
final ChecklistCellController cellController;
ChecklistCellEditorBloc({
required this.cellController,
}) : _selectOptionService =
SelectOptionBackendService(cellId: cellController.cellId),
}) : _checklistCellService =
ChecklistCellBackendService(cellId: cellController.cellId),
super(ChecklistCellEditorState.initial(cellController)) {
on<ChecklistCellEditorEvent>(
(event, emit) async {
@ -31,7 +32,7 @@ class ChecklistCellEditorBloc
emit(
state.copyWith(
allOptions: _makeChecklistSelectOptions(data, state.predicate),
percent: percentFromSelectOptionCellData(data),
percent: data.percentage,
),
);
},
@ -50,11 +51,7 @@ class ChecklistCellEditorBloc
_updateOption(option);
},
selectOption: (option) async {
if (option.isSelected) {
await _selectOptionService.unSelect(optionIds: [option.data.id]);
} else {
await _selectOptionService.select(optionIds: [option.data.id]);
}
await _checklistCellService.select(optionId: option.data.id);
},
filterOption: (String predicate) {},
);
@ -69,20 +66,19 @@ class ChecklistCellEditorBloc
}
void _createOption(String name) async {
final result = await _selectOptionService.create(
name: name,
isSelected: false,
);
final result = await _checklistCellService.create(name: name);
result.fold((l) => {}, (err) => Log.error(err));
}
void _deleteOption(List<SelectOptionPB> options) async {
final result = await _selectOptionService.delete(options: options);
final result = await _checklistCellService.delete(
optionIds: options.map((e) => e.id).toList(),
);
result.fold((l) => null, (err) => Log.error(err));
}
void _updateOption(SelectOptionPB option) async {
final result = await _selectOptionService.update(
final result = await _checklistCellService.update(
option: option,
);
@ -90,7 +86,7 @@ class ChecklistCellEditorBloc
}
void _loadOptions() {
_selectOptionService.getCellData().then((result) {
_checklistCellService.getCellData().then((result) {
if (isClosed) return;
return result.fold(
@ -118,7 +114,7 @@ class ChecklistCellEditorBloc
class ChecklistCellEditorEvent with _$ChecklistCellEditorEvent {
const factory ChecklistCellEditorEvent.initial() = _Initial;
const factory ChecklistCellEditorEvent.didReceiveOptions(
SelectOptionCellDataPB data,
ChecklistCellDataPB data,
) = _DidReceiveOptions;
const factory ChecklistCellEditorEvent.newOption(String optionName) =
_NewOption;
@ -142,34 +138,20 @@ class ChecklistCellEditorState with _$ChecklistCellEditorState {
required String predicate,
}) = _ChecklistCellEditorState;
factory ChecklistCellEditorState.initial(SelectOptionCellController context) {
factory ChecklistCellEditorState.initial(ChecklistCellController context) {
final data = context.getCellData(loadIfNotExist: true);
return ChecklistCellEditorState(
allOptions: _makeChecklistSelectOptions(data, ''),
createOption: none(),
percent: percentFromSelectOptionCellData(data),
percent: data?.percentage ?? 0,
predicate: '',
);
}
}
double percentFromSelectOptionCellData(SelectOptionCellDataPB? data) {
if (data == null) return 0;
final b = data.options.length.toDouble();
if (b == 0) {
return 0;
}
final a = data.selectOptions.length.toDouble();
if (a > b) return 1.0;
return a / b;
}
List<ChecklistSelectOption> _makeChecklistSelectOptions(
SelectOptionCellDataPB? data,
ChecklistCellDataPB? data,
String predicate,
) {
if (data == null) {
@ -181,7 +163,7 @@ List<ChecklistSelectOption> _makeChecklistSelectOptions(
if (predicate.isNotEmpty) {
allOptions.retainWhere((element) => element.name.contains(predicate));
}
final selectedOptionIds = data.selectOptions.map((e) => e.id).toList();
final selectedOptionIds = data.selectedOptions.map((e) => e.id).toList();
for (final option in allOptions) {
options.add(

View File

@ -1,6 +1,8 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor_bloc.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -19,7 +21,9 @@ class ChecklistProgressBar extends StatelessWidget {
lineHeight: 10.0,
percent: percent,
padding: EdgeInsets.zero,
progressColor: Theme.of(context).colorScheme.primary,
progressColor: percent < 1.0
? SelectOptionColorPB.Blue.toColor(context)
: SelectOptionColorPB.Green.toColor(context),
backgroundColor: AFThemeExtension.of(context).progressBarBGColor,
barRadius: const Radius.circular(5),
);

View File

@ -10,7 +10,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
extension SelectOptionColorExtension on SelectOptionColorPB {
Color make(BuildContext context) {
Color toColor(BuildContext context) {
switch (this) {
case SelectOptionColorPB.Purple:
return AFThemeExtension.of(context).tint1;
@ -82,7 +82,7 @@ class SelectOptionTag extends StatelessWidget {
}) {
return SelectOptionTag(
name: option.name,
color: option.color.make(context),
color: option.color.toColor(context),
onSelected: onSelected,
onRemove: onRemove,
);

View File

@ -5,19 +5,19 @@ import 'package:dartz/dartz.dart';
import 'package:appflowy_backend/log.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'select_option_service.dart';
import '../../../../application/cell/select_option_cell_service.dart';
part 'select_option_editor_bloc.freezed.dart';
class SelectOptionCellEditorBloc
extends Bloc<SelectOptionEditorEvent, SelectOptionEditorState> {
final SelectOptionBackendService _selectOptionService;
final SelectOptionCellBackendService _selectOptionService;
final SelectOptionCellController cellController;
SelectOptionCellEditorBloc({
required this.cellController,
}) : _selectOptionService =
SelectOptionBackendService(cellId: cellController.cellId),
SelectOptionCellBackendService(cellId: cellController.cellId),
super(SelectOptionEditorState.initial(cellController)) {
on<SelectOptionEditorEvent>(
(event, emit) async {

View File

@ -309,7 +309,7 @@ class _PropertyCellState extends State<_PropertyCell> {
return FieldEditor(
viewId: widget.cellId.viewId,
fieldName: widget.cellId.fieldInfo.field.name,
isGroupField: widget.cellId.fieldInfo.isGroupField,
isGroupingField: widget.cellId.fieldInfo.isGroupField,
typeOptionLoader: FieldTypeOptionLoader(
viewId: widget.cellId.viewId,
field: widget.cellId.fieldInfo.field,

View File

@ -73,7 +73,8 @@ class ColorOptionAction extends PopoverActionCell {
final transaction = editorState.transaction;
for (final node in nodes) {
transaction.updateNode(node, {
blockComponentBackgroundColor: color.make(context).toHex(),
blockComponentBackgroundColor:
color.toColor(context).toHex(),
});
}
editorState.apply(transaction);
@ -91,7 +92,7 @@ class ColorOptionAction extends PopoverActionCell {
return null;
}
for (final value in SelectOptionColorPB.values) {
if (value.make(context).toHex() == hexColor) {
if (value.toColor(context).toHex() == hexColor) {
return value;
}
}