mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: load field typeOption
This commit is contained in:
parent
25eb5bf1b0
commit
0d6c04ae81
@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'package:app_flowy/plugins/grid/application/block/block_cache.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/grid_data_controller.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
|
||||
import 'package:appflowy_board/appflowy_board.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
@ -14,11 +15,32 @@ import 'dart:collection';
|
||||
part 'board_bloc.freezed.dart';
|
||||
|
||||
class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
final GridDataController dataController;
|
||||
final GridDataController _gridDataController;
|
||||
late final BoardDataController boardDataController;
|
||||
|
||||
BoardBloc({required ViewPB view})
|
||||
: dataController = GridDataController(view: view),
|
||||
: _gridDataController = GridDataController(view: view),
|
||||
super(BoardState.initial(view.id)) {
|
||||
boardDataController = BoardDataController(
|
||||
onMoveColumn: (
|
||||
fromIndex,
|
||||
toIndex,
|
||||
) {},
|
||||
onMoveColumnItem: (
|
||||
columnId,
|
||||
fromIndex,
|
||||
toIndex,
|
||||
) {},
|
||||
onMoveColumnItemToColumn: (
|
||||
fromColumnId,
|
||||
fromIndex,
|
||||
toColumnId,
|
||||
toIndex,
|
||||
) {},
|
||||
);
|
||||
|
||||
// boardDataController.addColumns(_buildColumns());
|
||||
|
||||
on<BoardEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
@ -27,21 +49,19 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
await _loadGrid(emit);
|
||||
},
|
||||
createRow: () {
|
||||
dataController.createRow();
|
||||
_gridDataController.createRow();
|
||||
},
|
||||
didReceiveGridUpdate: (grid) {
|
||||
didReceiveGridUpdate: (GridPB grid) {
|
||||
emit(state.copyWith(grid: Some(grid)));
|
||||
},
|
||||
didReceiveFieldUpdate: (fields) {
|
||||
emit(state.copyWith(
|
||||
fields: GridFieldEquatable(fields),
|
||||
));
|
||||
didReceiveFieldUpdate: (UnmodifiableListView<GridFieldPB> fields) {
|
||||
emit(state.copyWith(fields: GridFieldEquatable(fields)));
|
||||
},
|
||||
didReceiveRowUpdate: (newRowInfos, reason) {
|
||||
emit(state.copyWith(
|
||||
rowInfos: newRowInfos,
|
||||
reason: reason,
|
||||
));
|
||||
didReceiveRowUpdate: (
|
||||
List<GridRowInfo> newRowInfos,
|
||||
GridRowChangeReason reason,
|
||||
) {
|
||||
emit(state.copyWith(rowInfos: newRowInfos, reason: reason));
|
||||
},
|
||||
);
|
||||
},
|
||||
@ -50,17 +70,17 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await dataController.dispose();
|
||||
await _gridDataController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
GridRowCache? getRowCache(String blockId, String rowId) {
|
||||
final GridBlockCache? blockCache = dataController.blocks[blockId];
|
||||
final GridBlockCache? blockCache = _gridDataController.blocks[blockId];
|
||||
return blockCache?.rowCache;
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
dataController.addListener(
|
||||
_gridDataController.addListener(
|
||||
onGridChanged: (grid) {
|
||||
if (!isClosed) {
|
||||
add(BoardEvent.didReceiveGridUpdate(grid));
|
||||
@ -73,14 +93,43 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
},
|
||||
onFieldsChanged: (fields) {
|
||||
if (!isClosed) {
|
||||
_buildColumns(fields);
|
||||
add(BoardEvent.didReceiveFieldUpdate(fields));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _buildColumns(UnmodifiableListView<GridFieldPB> fields) {
|
||||
List<BoardColumnData> columns = [];
|
||||
|
||||
for (final field in fields) {
|
||||
if (field.fieldType == FieldType.SingleSelect) {
|
||||
// return BoardColumnData(customData: field, id: field.id, desc: "1");
|
||||
}
|
||||
}
|
||||
|
||||
boardDataController.addColumns(columns);
|
||||
|
||||
// final column1 = BoardColumnData(id: "To Do", items: [
|
||||
// TextItem("Card 1"),
|
||||
// TextItem("Card 2"),
|
||||
// RichTextItem(title: "Card 3", subtitle: 'Aug 1, 2020 4:05 PM'),
|
||||
// TextItem("Card 4"),
|
||||
// ]);
|
||||
// final column2 = BoardColumnData(id: "In Progress", items: [
|
||||
// RichTextItem(title: "Card 5", subtitle: 'Aug 1, 2020 4:05 PM'),
|
||||
// TextItem("Card 6"),
|
||||
// ]);
|
||||
|
||||
// final column3 = BoardColumnData(id: "Done", items: []);
|
||||
// boardDataController.addColumn(column1);
|
||||
// boardDataController.addColumn(column2);
|
||||
// boardDataController.addColumn(column3);
|
||||
}
|
||||
|
||||
Future<void> _loadGrid(Emitter<BoardState> emit) async {
|
||||
final result = await dataController.loadData();
|
||||
final result = await _gridDataController.loadData();
|
||||
result.fold(
|
||||
(grid) => emit(
|
||||
state.copyWith(loadingState: GridLoadingState.finish(left(unit))),
|
||||
@ -159,3 +208,22 @@ class GridFieldEquatable extends Equatable {
|
||||
|
||||
UnmodifiableListView<GridFieldPB> get value => UnmodifiableListView(_fields);
|
||||
}
|
||||
|
||||
class TextItem extends ColumnItem {
|
||||
final String s;
|
||||
|
||||
TextItem(this.s);
|
||||
|
||||
@override
|
||||
String get id => s;
|
||||
}
|
||||
|
||||
class RichTextItem extends ColumnItem {
|
||||
final String title;
|
||||
final String subtitle;
|
||||
|
||||
RichTextItem({required this.title, required this.subtitle});
|
||||
|
||||
@override
|
||||
String get id => title;
|
||||
}
|
||||
|
@ -3,19 +3,20 @@
|
||||
import 'package:appflowy_board/appflowy_board.dart';
|
||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../application/board_bloc.dart';
|
||||
|
||||
class BoardPage2 extends StatelessWidget {
|
||||
class BoardPage extends StatelessWidget {
|
||||
final ViewPB view;
|
||||
const BoardPage2({required this.view, Key? key}) : super(key: key);
|
||||
const BoardPage({required this.view, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => BoardBloc(view: view),
|
||||
create: (context) =>
|
||||
BoardBloc(view: view)..add(const BoardEvent.initial()),
|
||||
child: BlocBuilder<BoardBloc, BoardState>(
|
||||
builder: (context, state) {
|
||||
return state.loadingState.map(
|
||||
@ -23,7 +24,7 @@ class BoardPage2 extends StatelessWidget {
|
||||
const Center(child: CircularProgressIndicator.adaptive()),
|
||||
finish: (result) {
|
||||
return result.successOrFail.fold(
|
||||
(_) => const BoardContent(),
|
||||
(_) => BoardContent(),
|
||||
(err) => FlowyErrorPage(err.toString()),
|
||||
);
|
||||
},
|
||||
@ -35,88 +36,24 @@ class BoardPage2 extends StatelessWidget {
|
||||
}
|
||||
|
||||
class BoardContent extends StatelessWidget {
|
||||
const BoardContent({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
|
||||
class BoardPage extends StatefulWidget {
|
||||
final ViewPB _view;
|
||||
|
||||
const BoardPage({required ViewPB view, Key? key})
|
||||
: _view = view,
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
State<BoardPage> createState() => _BoardPageState();
|
||||
}
|
||||
|
||||
class _BoardPageState extends State<BoardPage> {
|
||||
final BoardDataController boardDataController = BoardDataController(
|
||||
onMoveColumn: (fromIndex, toIndex) {
|
||||
debugPrint('Move column from $fromIndex to $toIndex');
|
||||
},
|
||||
onMoveColumnItem: (columnId, fromIndex, toIndex) {
|
||||
debugPrint('Move $columnId:$fromIndex to $columnId:$toIndex');
|
||||
},
|
||||
onMoveColumnItemToColumn: (fromColumnId, fromIndex, toColumnId, toIndex) {
|
||||
debugPrint('Move $fromColumnId:$fromIndex to $toColumnId:$toIndex');
|
||||
},
|
||||
);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final column1 = BoardColumnData(id: "To Do", items: [
|
||||
TextItem("Card 1"),
|
||||
TextItem("Card 2"),
|
||||
RichTextItem(title: "Card 3", subtitle: 'Aug 1, 2020 4:05 PM'),
|
||||
TextItem("Card 4"),
|
||||
]);
|
||||
final column2 = BoardColumnData(id: "In Progress", items: [
|
||||
RichTextItem(title: "Card 5", subtitle: 'Aug 1, 2020 4:05 PM'),
|
||||
TextItem("Card 6"),
|
||||
]);
|
||||
|
||||
final column3 = BoardColumnData(id: "Done", items: []);
|
||||
|
||||
boardDataController.addColumn(column1);
|
||||
boardDataController.addColumn(column2);
|
||||
boardDataController.addColumn(column3);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final config = BoardConfig(
|
||||
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
|
||||
);
|
||||
|
||||
BoardContent({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<BoardBloc, BoardState>(
|
||||
builder: (context, state) {
|
||||
return Container(
|
||||
color: Colors.white,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
||||
child: Board(
|
||||
dataController: boardDataController,
|
||||
footBuilder: (context, columnData) {
|
||||
return AppFlowyColumnFooter(
|
||||
icon: const Icon(Icons.add, size: 20),
|
||||
title: const Text('New'),
|
||||
height: 50,
|
||||
margin: config.columnItemPadding,
|
||||
);
|
||||
},
|
||||
headerBuilder: (context, columnData) {
|
||||
return AppFlowyColumnHeader(
|
||||
icon: const Icon(Icons.lightbulb_circle),
|
||||
title: Text(columnData.id),
|
||||
addIcon: const Icon(Icons.add, size: 20),
|
||||
moreIcon: const Icon(Icons.more_horiz, size: 20),
|
||||
height: 50,
|
||||
margin: config.columnItemPadding,
|
||||
);
|
||||
},
|
||||
dataController: context.read<BoardBloc>().boardDataController,
|
||||
headerBuilder: _buildHeader,
|
||||
footBuilder: _buildFooter,
|
||||
cardBuilder: (context, item) {
|
||||
return AppFlowyColumnItemCard(
|
||||
key: ObjectKey(item),
|
||||
@ -130,6 +67,28 @@ class _BoardPageState extends State<BoardPage> {
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader(BuildContext context, BoardColumnData columnData) {
|
||||
return AppFlowyColumnHeader(
|
||||
icon: const Icon(Icons.lightbulb_circle),
|
||||
title: Text(columnData.desc),
|
||||
addIcon: const Icon(Icons.add, size: 20),
|
||||
moreIcon: const Icon(Icons.more_horiz, size: 20),
|
||||
height: 50,
|
||||
margin: config.columnItemPadding,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFooter(BuildContext context, BoardColumnData columnData) {
|
||||
return AppFlowyColumnFooter(
|
||||
icon: const Icon(Icons.add, size: 20),
|
||||
title: const Text('New'),
|
||||
height: 50,
|
||||
margin: config.columnItemPadding,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCard(ColumnItem item) {
|
||||
@ -171,25 +130,6 @@ class _BoardPageState extends State<BoardPage> {
|
||||
}
|
||||
}
|
||||
|
||||
class TextItem extends ColumnItem {
|
||||
final String s;
|
||||
|
||||
TextItem(this.s);
|
||||
|
||||
@override
|
||||
String get id => s;
|
||||
}
|
||||
|
||||
class RichTextItem extends ColumnItem {
|
||||
final String title;
|
||||
final String subtitle;
|
||||
|
||||
RichTextItem({required this.title, required this.subtitle});
|
||||
|
||||
@override
|
||||
String get id => title;
|
||||
}
|
||||
|
||||
extension HexColor on Color {
|
||||
static Color fromHex(String hexString) {
|
||||
final buffer = StringBuffer();
|
||||
|
@ -0,0 +1,192 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:app_flowy/plugins/grid/application/field/grid_listener.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../row/row_cache.dart';
|
||||
|
||||
class FieldsNotifier extends ChangeNotifier {
|
||||
List<GridFieldPB> _fields = [];
|
||||
|
||||
set fields(List<GridFieldPB> fields) {
|
||||
_fields = fields;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
List<GridFieldPB> get fields => _fields;
|
||||
}
|
||||
|
||||
typedef FieldChangesetCallback = void Function(GridFieldChangesetPB);
|
||||
typedef FieldsCallback = void Function(List<GridFieldPB>);
|
||||
|
||||
class GridFieldCache {
|
||||
final String gridId;
|
||||
final GridFieldsListener _fieldListener;
|
||||
FieldsNotifier? _fieldNotifier = FieldsNotifier();
|
||||
final Map<FieldsCallback, VoidCallback> _fieldsCallbackMap = {};
|
||||
final Map<FieldChangesetCallback, FieldChangesetCallback>
|
||||
_changesetCallbackMap = {};
|
||||
|
||||
GridFieldCache({required this.gridId})
|
||||
: _fieldListener = GridFieldsListener(gridId: gridId) {
|
||||
_fieldListener.start(onFieldsChanged: (result) {
|
||||
result.fold(
|
||||
(changeset) {
|
||||
_deleteFields(changeset.deletedFields);
|
||||
_insertFields(changeset.insertedFields);
|
||||
_updateFields(changeset.updatedFields);
|
||||
for (final listener in _changesetCallbackMap.values) {
|
||||
listener(changeset);
|
||||
}
|
||||
},
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
await _fieldListener.stop();
|
||||
_fieldNotifier?.dispose();
|
||||
_fieldNotifier = null;
|
||||
}
|
||||
|
||||
UnmodifiableListView<GridFieldPB> get unmodifiableFields =>
|
||||
UnmodifiableListView(_fieldNotifier?.fields ?? []);
|
||||
|
||||
List<GridFieldPB> get fields => [..._fieldNotifier?.fields ?? []];
|
||||
|
||||
set fields(List<GridFieldPB> fields) {
|
||||
_fieldNotifier?.fields = [...fields];
|
||||
}
|
||||
|
||||
void addListener({
|
||||
FieldsCallback? onFields,
|
||||
FieldChangesetCallback? onChangeset,
|
||||
bool Function()? listenWhen,
|
||||
}) {
|
||||
if (onChangeset != null) {
|
||||
fn(c) {
|
||||
if (listenWhen != null && listenWhen() == false) {
|
||||
return;
|
||||
}
|
||||
onChangeset(c);
|
||||
}
|
||||
|
||||
_changesetCallbackMap[onChangeset] = fn;
|
||||
}
|
||||
|
||||
if (onFields != null) {
|
||||
fn() {
|
||||
if (listenWhen != null && listenWhen() == false) {
|
||||
return;
|
||||
}
|
||||
onFields(fields);
|
||||
}
|
||||
|
||||
_fieldsCallbackMap[onFields] = fn;
|
||||
_fieldNotifier?.addListener(fn);
|
||||
}
|
||||
}
|
||||
|
||||
void removeListener({
|
||||
FieldsCallback? onFieldsListener,
|
||||
FieldChangesetCallback? onChangesetListener,
|
||||
}) {
|
||||
if (onFieldsListener != null) {
|
||||
final fn = _fieldsCallbackMap.remove(onFieldsListener);
|
||||
if (fn != null) {
|
||||
_fieldNotifier?.removeListener(fn);
|
||||
}
|
||||
}
|
||||
|
||||
if (onChangesetListener != null) {
|
||||
_changesetCallbackMap.remove(onChangesetListener);
|
||||
}
|
||||
}
|
||||
|
||||
void _deleteFields(List<GridFieldIdPB> deletedFields) {
|
||||
if (deletedFields.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final List<GridFieldPB> newFields = fields;
|
||||
final Map<String, GridFieldIdPB> deletedFieldMap = {
|
||||
for (var fieldOrder in deletedFields) fieldOrder.fieldId: fieldOrder
|
||||
};
|
||||
|
||||
newFields.retainWhere((field) => (deletedFieldMap[field.id] == null));
|
||||
_fieldNotifier?.fields = newFields;
|
||||
}
|
||||
|
||||
void _insertFields(List<IndexFieldPB> insertedFields) {
|
||||
if (insertedFields.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final List<GridFieldPB> newFields = fields;
|
||||
for (final indexField in insertedFields) {
|
||||
if (newFields.length > indexField.index) {
|
||||
newFields.insert(indexField.index, indexField.field_1);
|
||||
} else {
|
||||
newFields.add(indexField.field_1);
|
||||
}
|
||||
}
|
||||
_fieldNotifier?.fields = newFields;
|
||||
}
|
||||
|
||||
void _updateFields(List<GridFieldPB> updatedFields) {
|
||||
if (updatedFields.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final List<GridFieldPB> newFields = fields;
|
||||
for (final updatedField in updatedFields) {
|
||||
final index =
|
||||
newFields.indexWhere((field) => field.id == updatedField.id);
|
||||
if (index != -1) {
|
||||
newFields.removeAt(index);
|
||||
newFields.insert(index, updatedField);
|
||||
}
|
||||
}
|
||||
_fieldNotifier?.fields = newFields;
|
||||
}
|
||||
}
|
||||
|
||||
class GridRowFieldNotifierImpl extends IGridRowFieldNotifier {
|
||||
final GridFieldCache _cache;
|
||||
FieldChangesetCallback? _onChangesetFn;
|
||||
FieldsCallback? _onFieldFn;
|
||||
GridRowFieldNotifierImpl(GridFieldCache cache) : _cache = cache;
|
||||
|
||||
@override
|
||||
UnmodifiableListView<GridFieldPB> get fields => _cache.unmodifiableFields;
|
||||
|
||||
@override
|
||||
void onRowFieldsChanged(VoidCallback callback) {
|
||||
_onFieldFn = (_) => callback();
|
||||
_cache.addListener(onFields: _onFieldFn);
|
||||
}
|
||||
|
||||
@override
|
||||
void onRowFieldChanged(void Function(GridFieldPB) callback) {
|
||||
_onChangesetFn = (GridFieldChangesetPB changeset) {
|
||||
for (final updatedField in changeset.updatedFields) {
|
||||
callback(updatedField);
|
||||
}
|
||||
};
|
||||
|
||||
_cache.addListener(onChangeset: _onChangesetFn);
|
||||
}
|
||||
|
||||
@override
|
||||
void onRowDispose() {
|
||||
if (_onFieldFn != null) {
|
||||
_cache.removeListener(onFieldsListener: _onFieldFn!);
|
||||
_onFieldFn = null;
|
||||
}
|
||||
|
||||
if (_onChangesetFn != null) {
|
||||
_cache.removeListener(onChangesetListener: _onChangesetFn!);
|
||||
_onChangesetFn = null;
|
||||
}
|
||||
}
|
||||
}
|
@ -13,7 +13,8 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
||||
required String gridId,
|
||||
required String fieldName,
|
||||
required IFieldTypeOptionLoader loader,
|
||||
}) : dataController = TypeOptionDataController(gridId: gridId, loader: loader),
|
||||
}) : dataController =
|
||||
TypeOptionDataController(gridId: gridId, loader: loader),
|
||||
super(FieldEditorState.initial(gridId, fieldName)) {
|
||||
on<FieldEditorEvent>(
|
||||
(event, emit) async {
|
||||
@ -24,7 +25,7 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
||||
add(FieldEditorEvent.didReceiveFieldChanged(field));
|
||||
}
|
||||
});
|
||||
await dataController.loadData();
|
||||
await dataController.loadTypeOptionData();
|
||||
},
|
||||
updateName: (name) {
|
||||
dataController.fieldName = name;
|
||||
@ -48,7 +49,8 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
||||
class FieldEditorEvent with _$FieldEditorEvent {
|
||||
const factory FieldEditorEvent.initial() = _InitialField;
|
||||
const factory FieldEditorEvent.updateName(String name) = _UpdateName;
|
||||
const factory FieldEditorEvent.didReceiveFieldChanged(GridFieldPB field) = _DidReceiveFieldChanged;
|
||||
const factory FieldEditorEvent.didReceiveFieldChanged(GridFieldPB field) =
|
||||
_DidReceiveFieldChanged;
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
@ -146,7 +146,8 @@ abstract class IFieldTypeOptionLoader {
|
||||
String get gridId;
|
||||
Future<Either<FieldTypeOptionDataPB, FlowyError>> load();
|
||||
|
||||
Future<Either<FieldTypeOptionDataPB, FlowyError>> switchToField(String fieldId, FieldType fieldType) {
|
||||
Future<Either<FieldTypeOptionDataPB, FlowyError>> switchToField(
|
||||
String fieldId, FieldType fieldType) {
|
||||
final payload = EditFieldPayloadPB.create()
|
||||
..gridId = gridId
|
||||
..fieldId = fieldId
|
||||
@ -206,7 +207,7 @@ class TypeOptionDataController {
|
||||
required IFieldTypeOptionLoader loader,
|
||||
}) : _loader = loader;
|
||||
|
||||
Future<Either<Unit, FlowyError>> loadData() async {
|
||||
Future<Either<Unit, FlowyError>> loadTypeOptionData() async {
|
||||
final result = await _loader.load();
|
||||
return result.fold(
|
||||
(data) {
|
||||
@ -238,7 +239,8 @@ class TypeOptionDataController {
|
||||
_updateData(newTypeOptionData: typeOptionData);
|
||||
}
|
||||
|
||||
void _updateData({String? newName, GridFieldPB? newField, List<int>? newTypeOptionData}) {
|
||||
void _updateData(
|
||||
{String? newName, GridFieldPB? newField, List<int>? newTypeOptionData}) {
|
||||
_data = _data.rebuild((rebuildData) {
|
||||
if (newName != null) {
|
||||
rebuildData.field_2 = rebuildData.field_2.rebuild((rebuildField) {
|
||||
|
@ -13,13 +13,13 @@ class MultiSelectTypeOptionContext
|
||||
final TypeOptionService service;
|
||||
|
||||
MultiSelectTypeOptionContext({
|
||||
required MultiSelectTypeOptionWidgetDataParser dataBuilder,
|
||||
required MultiSelectTypeOptionWidgetDataParser dataParser,
|
||||
required TypeOptionDataController dataController,
|
||||
}) : service = TypeOptionService(
|
||||
gridId: dataController.gridId,
|
||||
fieldId: dataController.field.id,
|
||||
),
|
||||
super(dataParser: dataBuilder, dataController: dataController);
|
||||
super(dataParser: dataParser, dataController: dataController);
|
||||
|
||||
@override
|
||||
List<SelectOptionPB> Function(SelectOptionPB) get deleteOption {
|
||||
|
@ -14,12 +14,12 @@ class SingleSelectTypeOptionContext
|
||||
|
||||
SingleSelectTypeOptionContext({
|
||||
required SingleSelectTypeOptionWidgetDataParser dataBuilder,
|
||||
required TypeOptionDataController fieldContext,
|
||||
required TypeOptionDataController dataController,
|
||||
}) : service = TypeOptionService(
|
||||
gridId: fieldContext.gridId,
|
||||
fieldId: fieldContext.field.id,
|
||||
gridId: dataController.gridId,
|
||||
fieldId: dataController.field.id,
|
||||
),
|
||||
super(dataParser: dataBuilder, dataController: fieldContext);
|
||||
super(dataParser: dataBuilder, dataController: dataController);
|
||||
|
||||
@override
|
||||
List<SelectOptionPB> Function(SelectOptionPB) get deleteOption {
|
||||
|
@ -0,0 +1,329 @@
|
||||
import 'dart:collection';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
part 'row_cache.freezed.dart';
|
||||
|
||||
typedef RowUpdateCallback = void Function();
|
||||
|
||||
abstract class IGridRowFieldNotifier {
|
||||
UnmodifiableListView<GridFieldPB> get fields;
|
||||
void onRowFieldsChanged(VoidCallback callback);
|
||||
void onRowFieldChanged(void Function(GridFieldPB) callback);
|
||||
void onRowDispose();
|
||||
}
|
||||
|
||||
/// Cache the rows in memory
|
||||
/// Insert / delete / update row
|
||||
///
|
||||
/// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid for more information.
|
||||
|
||||
class GridRowCache {
|
||||
final String gridId;
|
||||
final GridBlockPB block;
|
||||
|
||||
/// _rows containers the current block's rows
|
||||
/// Use List to reverse the order of the GridRow.
|
||||
List<GridRowInfo> _rowInfos = [];
|
||||
|
||||
/// Use Map for faster access the raw row data.
|
||||
final HashMap<String, GridRowPB> _rowByRowId;
|
||||
|
||||
final GridCellCache _cellCache;
|
||||
final IGridRowFieldNotifier _fieldNotifier;
|
||||
final _GridRowChangesetNotifier _rowChangeReasonNotifier;
|
||||
|
||||
UnmodifiableListView<GridRowInfo> get rows => UnmodifiableListView(_rowInfos);
|
||||
GridCellCache get cellCache => _cellCache;
|
||||
|
||||
GridRowCache({
|
||||
required this.gridId,
|
||||
required this.block,
|
||||
required IGridRowFieldNotifier notifier,
|
||||
}) : _cellCache = GridCellCache(gridId: gridId),
|
||||
_rowByRowId = HashMap(),
|
||||
_rowChangeReasonNotifier = _GridRowChangesetNotifier(),
|
||||
_fieldNotifier = notifier {
|
||||
//
|
||||
notifier.onRowFieldsChanged(() => _rowChangeReasonNotifier
|
||||
.receive(const GridRowChangeReason.fieldDidChange()));
|
||||
notifier.onRowFieldChanged((field) => _cellCache.remove(field.id));
|
||||
_rowInfos = block.rows
|
||||
.map((rowInfo) => buildGridRow(rowInfo.id, rowInfo.height.toDouble()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
_fieldNotifier.onRowDispose();
|
||||
_rowChangeReasonNotifier.dispose();
|
||||
await _cellCache.dispose();
|
||||
}
|
||||
|
||||
void applyChangesets(List<GridBlockChangesetPB> changesets) {
|
||||
for (final changeset in changesets) {
|
||||
_deleteRows(changeset.deletedRows);
|
||||
_insertRows(changeset.insertedRows);
|
||||
_updateRows(changeset.updatedRows);
|
||||
_hideRows(changeset.hideRows);
|
||||
_showRows(changeset.visibleRows);
|
||||
}
|
||||
}
|
||||
|
||||
void _deleteRows(List<String> deletedRows) {
|
||||
if (deletedRows.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<GridRowInfo> newRows = [];
|
||||
final DeletedIndexs deletedIndex = [];
|
||||
final Map<String, String> deletedRowByRowId = {
|
||||
for (var rowId in deletedRows) rowId: rowId
|
||||
};
|
||||
|
||||
_rowInfos.asMap().forEach((index, row) {
|
||||
if (deletedRowByRowId[row.id] == null) {
|
||||
newRows.add(row);
|
||||
} else {
|
||||
_rowByRowId.remove(row.id);
|
||||
deletedIndex.add(DeletedIndex(index: index, row: row));
|
||||
}
|
||||
});
|
||||
_rowInfos = newRows;
|
||||
_rowChangeReasonNotifier.receive(GridRowChangeReason.delete(deletedIndex));
|
||||
}
|
||||
|
||||
void _insertRows(List<InsertedRowPB> insertRows) {
|
||||
if (insertRows.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
InsertedIndexs insertIndexs = [];
|
||||
for (final insertRow in insertRows) {
|
||||
final insertIndex = InsertedIndex(
|
||||
index: insertRow.index,
|
||||
rowId: insertRow.rowId,
|
||||
);
|
||||
insertIndexs.add(insertIndex);
|
||||
_rowInfos.insert(insertRow.index,
|
||||
(buildGridRow(insertRow.rowId, insertRow.height.toDouble())));
|
||||
}
|
||||
|
||||
_rowChangeReasonNotifier.receive(GridRowChangeReason.insert(insertIndexs));
|
||||
}
|
||||
|
||||
void _updateRows(List<UpdatedRowPB> updatedRows) {
|
||||
if (updatedRows.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final UpdatedIndexs updatedIndexs = UpdatedIndexs();
|
||||
for (final updatedRow in updatedRows) {
|
||||
final rowId = updatedRow.rowId;
|
||||
final index = _rowInfos.indexWhere((row) => row.id == rowId);
|
||||
if (index != -1) {
|
||||
_rowByRowId[rowId] = updatedRow.row;
|
||||
|
||||
_rowInfos.removeAt(index);
|
||||
_rowInfos.insert(
|
||||
index, buildGridRow(rowId, updatedRow.row.height.toDouble()));
|
||||
updatedIndexs[rowId] = UpdatedIndex(index: index, rowId: rowId);
|
||||
}
|
||||
}
|
||||
|
||||
_rowChangeReasonNotifier.receive(GridRowChangeReason.update(updatedIndexs));
|
||||
}
|
||||
|
||||
void _hideRows(List<String> hideRows) {}
|
||||
|
||||
void _showRows(List<String> visibleRows) {}
|
||||
|
||||
void onRowsChanged(
|
||||
void Function(GridRowChangeReason) onRowChanged,
|
||||
) {
|
||||
_rowChangeReasonNotifier.addListener(() {
|
||||
onRowChanged(_rowChangeReasonNotifier.reason);
|
||||
});
|
||||
}
|
||||
|
||||
RowUpdateCallback addListener({
|
||||
required String rowId,
|
||||
void Function(GridCellMap, GridRowChangeReason)? onCellUpdated,
|
||||
bool Function()? listenWhen,
|
||||
}) {
|
||||
listenerHandler() async {
|
||||
if (listenWhen != null && listenWhen() == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
notifyUpdate() {
|
||||
if (onCellUpdated != null) {
|
||||
final row = _rowByRowId[rowId];
|
||||
if (row != null) {
|
||||
final GridCellMap cellDataMap = _makeGridCells(rowId, row);
|
||||
onCellUpdated(cellDataMap, _rowChangeReasonNotifier.reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_rowChangeReasonNotifier.reason.whenOrNull(
|
||||
update: (indexs) {
|
||||
if (indexs[rowId] != null) notifyUpdate();
|
||||
},
|
||||
fieldDidChange: () => notifyUpdate(),
|
||||
);
|
||||
}
|
||||
|
||||
_rowChangeReasonNotifier.addListener(listenerHandler);
|
||||
return listenerHandler;
|
||||
}
|
||||
|
||||
void removeRowListener(VoidCallback callback) {
|
||||
_rowChangeReasonNotifier.removeListener(callback);
|
||||
}
|
||||
|
||||
GridCellMap loadGridCells(String rowId) {
|
||||
final GridRowPB? data = _rowByRowId[rowId];
|
||||
if (data == null) {
|
||||
_loadRow(rowId);
|
||||
}
|
||||
return _makeGridCells(rowId, data);
|
||||
}
|
||||
|
||||
Future<void> _loadRow(String rowId) async {
|
||||
final payload = GridRowIdPB.create()
|
||||
..gridId = gridId
|
||||
..blockId = block.id
|
||||
..rowId = rowId;
|
||||
|
||||
final result = await GridEventGetRow(payload).send();
|
||||
result.fold(
|
||||
(optionRow) => _refreshRow(optionRow),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
}
|
||||
|
||||
GridCellMap _makeGridCells(String rowId, GridRowPB? row) {
|
||||
var cellDataMap = GridCellMap.new();
|
||||
for (final field in _fieldNotifier.fields) {
|
||||
if (field.visibility) {
|
||||
cellDataMap[field.id] = GridCellIdentifier(
|
||||
rowId: rowId,
|
||||
gridId: gridId,
|
||||
field: field,
|
||||
);
|
||||
}
|
||||
}
|
||||
return cellDataMap;
|
||||
}
|
||||
|
||||
void _refreshRow(OptionalRowPB optionRow) {
|
||||
if (!optionRow.hasRow()) {
|
||||
return;
|
||||
}
|
||||
final updatedRow = optionRow.row;
|
||||
updatedRow.freeze();
|
||||
|
||||
_rowByRowId[updatedRow.id] = updatedRow;
|
||||
final index =
|
||||
_rowInfos.indexWhere((gridRow) => gridRow.id == updatedRow.id);
|
||||
if (index != -1) {
|
||||
// update the corresponding row in _rows if they are not the same
|
||||
if (_rowInfos[index].rawRow != updatedRow) {
|
||||
final row = _rowInfos.removeAt(index).copyWith(rawRow: updatedRow);
|
||||
_rowInfos.insert(index, row);
|
||||
|
||||
// Calculate the update index
|
||||
final UpdatedIndexs updatedIndexs = UpdatedIndexs();
|
||||
updatedIndexs[row.id] = UpdatedIndex(index: index, rowId: row.id);
|
||||
|
||||
//
|
||||
_rowChangeReasonNotifier
|
||||
.receive(GridRowChangeReason.update(updatedIndexs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GridRowInfo buildGridRow(String rowId, double rowHeight) {
|
||||
return GridRowInfo(
|
||||
gridId: gridId,
|
||||
blockId: block.id,
|
||||
fields: _fieldNotifier.fields,
|
||||
id: rowId,
|
||||
height: rowHeight,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _GridRowChangesetNotifier extends ChangeNotifier {
|
||||
GridRowChangeReason reason = const InitialListState();
|
||||
|
||||
_GridRowChangesetNotifier();
|
||||
|
||||
void receive(GridRowChangeReason newReason) {
|
||||
reason = newReason;
|
||||
reason.map(
|
||||
insert: (_) => notifyListeners(),
|
||||
delete: (_) => notifyListeners(),
|
||||
update: (_) => notifyListeners(),
|
||||
fieldDidChange: (_) => notifyListeners(),
|
||||
initial: (_) {},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridRowInfo with _$GridRowInfo {
|
||||
const factory GridRowInfo({
|
||||
required String gridId,
|
||||
required String blockId,
|
||||
required String id,
|
||||
required UnmodifiableListView<GridFieldPB> fields,
|
||||
required double height,
|
||||
GridRowPB? rawRow,
|
||||
}) = _GridRowInfo;
|
||||
}
|
||||
|
||||
typedef InsertedIndexs = List<InsertedIndex>;
|
||||
typedef DeletedIndexs = List<DeletedIndex>;
|
||||
typedef UpdatedIndexs = LinkedHashMap<String, UpdatedIndex>;
|
||||
|
||||
@freezed
|
||||
class GridRowChangeReason with _$GridRowChangeReason {
|
||||
const factory GridRowChangeReason.insert(InsertedIndexs items) = _Insert;
|
||||
const factory GridRowChangeReason.delete(DeletedIndexs items) = _Delete;
|
||||
const factory GridRowChangeReason.update(UpdatedIndexs indexs) = _Update;
|
||||
const factory GridRowChangeReason.fieldDidChange() = _FieldDidChange;
|
||||
const factory GridRowChangeReason.initial() = InitialListState;
|
||||
}
|
||||
|
||||
class InsertedIndex {
|
||||
final int index;
|
||||
final String rowId;
|
||||
InsertedIndex({
|
||||
required this.index,
|
||||
required this.rowId,
|
||||
});
|
||||
}
|
||||
|
||||
class DeletedIndex {
|
||||
final int index;
|
||||
final GridRowInfo row;
|
||||
DeletedIndex({
|
||||
required this.index,
|
||||
required this.row,
|
||||
});
|
||||
}
|
||||
|
||||
class UpdatedIndex {
|
||||
final int index;
|
||||
final String rowId;
|
||||
UpdatedIndex({
|
||||
required this.index,
|
||||
required this.rowId,
|
||||
});
|
||||
}
|
@ -65,7 +65,7 @@ TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder(
|
||||
);
|
||||
case FieldType.SingleSelect:
|
||||
final context = SingleSelectTypeOptionContext(
|
||||
fieldContext: dataController,
|
||||
dataController: dataController,
|
||||
dataBuilder: SingleSelectTypeOptionWidgetDataParser(),
|
||||
);
|
||||
return SingleSelectTypeOptionWidgetBuilder(
|
||||
@ -75,7 +75,7 @@ TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder(
|
||||
case FieldType.MultiSelect:
|
||||
final context = MultiSelectTypeOptionContext(
|
||||
dataController: dataController,
|
||||
dataBuilder: MultiSelectTypeOptionWidgetDataParser(),
|
||||
dataParser: MultiSelectTypeOptionWidgetDataParser(),
|
||||
);
|
||||
return MultiSelectTypeOptionWidgetBuilder(
|
||||
context,
|
||||
|
@ -20,6 +20,12 @@ class Log {
|
||||
}
|
||||
}
|
||||
|
||||
static void warn(String? message) {
|
||||
if (enableLog) {
|
||||
debugPrint('🐛[Warn]=> $message');
|
||||
}
|
||||
}
|
||||
|
||||
static void trace(String? message) {
|
||||
if (enableLog) {
|
||||
// debugPrint('❗️[Trace]=> $message');
|
||||
|
@ -56,25 +56,26 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
|
||||
|
||||
/// Move the item from [fromIndex] to [toIndex]. It will do nothing if the
|
||||
/// [fromIndex] equal to the [toIndex].
|
||||
void move(int fromIndex, int toIndex) {
|
||||
bool move(int fromIndex, int toIndex) {
|
||||
assert(fromIndex >= 0);
|
||||
assert(toIndex >= 0);
|
||||
|
||||
if (fromIndex == toIndex) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
Log.debug(
|
||||
'[$BoardColumnDataController] $columnData move item from $fromIndex to $toIndex');
|
||||
final item = columnData._items.removeAt(fromIndex);
|
||||
columnData._items.insert(toIndex, item);
|
||||
notifyListeners();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Insert an item to [index] and notify the listen if the value of [notify]
|
||||
/// is true.
|
||||
///
|
||||
/// The default value of [notify] is true.
|
||||
void insert(int index, ColumnItem item, {bool notify = true}) {
|
||||
bool insert(int index, ColumnItem item, {bool notify = true}) {
|
||||
assert(index >= 0);
|
||||
Log.debug(
|
||||
'[$BoardColumnDataController] $columnData insert $item at $index');
|
||||
@ -85,9 +86,14 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
|
||||
columnData._items.add(item);
|
||||
}
|
||||
|
||||
if (notify) {
|
||||
notifyListeners();
|
||||
if (notify) notifyListeners();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool add(ColumnItem item, {bool notify = true}) {
|
||||
columnData._items.add(item);
|
||||
if (notify) notifyListeners();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Replace the item at index with the [newItem].
|
||||
@ -107,14 +113,18 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
|
||||
}
|
||||
|
||||
/// [BoardColumnData] represents the data of each Column of the Board.
|
||||
class BoardColumnData extends ReoderFlexItem with EquatableMixin {
|
||||
class BoardColumnData<CustomData> extends ReoderFlexItem with EquatableMixin {
|
||||
@override
|
||||
final String id;
|
||||
final String desc;
|
||||
final List<ColumnItem> _items;
|
||||
final CustomData? customData;
|
||||
|
||||
BoardColumnData({
|
||||
this.customData,
|
||||
required this.id,
|
||||
required List<ColumnItem> items,
|
||||
this.desc = "",
|
||||
List<ColumnItem> items = const [],
|
||||
}) : _items = items;
|
||||
|
||||
/// Returns the readonly List<ColumnItem>
|
||||
|
@ -44,32 +44,84 @@ class BoardDataController extends ChangeNotifier
|
||||
this.onMoveColumnItemToColumn,
|
||||
});
|
||||
|
||||
void addColumn(BoardColumnData columnData) {
|
||||
void addColumn(BoardColumnData columnData, {bool notify = true}) {
|
||||
if (_columnControllers[columnData.id] != null) return;
|
||||
|
||||
final controller = BoardColumnDataController(columnData: columnData);
|
||||
_columnDatas.add(columnData);
|
||||
_columnControllers[columnData.id] = controller;
|
||||
if (notify) notifyListeners();
|
||||
}
|
||||
|
||||
void addColumns(List<BoardColumnData> columns, {bool notify = true}) {
|
||||
for (final column in columns) {
|
||||
addColumn(column, notify: false);
|
||||
}
|
||||
|
||||
if (columns.isNotEmpty && notify) notifyListeners();
|
||||
}
|
||||
|
||||
void removeColumn(String columnId, {bool notify = true}) {
|
||||
final index = _columnDatas.indexWhere((column) => column.id == columnId);
|
||||
if (index == -1) {
|
||||
Log.warn(
|
||||
'Try to remove Column:[$columnId] failed. Column:[$columnId] not exist');
|
||||
}
|
||||
|
||||
if (index != -1) {
|
||||
_columnDatas.removeAt(index);
|
||||
_columnControllers.remove(columnId);
|
||||
|
||||
if (notify) notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
void removeColumns(List<String> columnIds, {bool notify = true}) {
|
||||
for (final columnId in columnIds) {
|
||||
removeColumn(columnId, notify: false);
|
||||
}
|
||||
|
||||
if (columnIds.isNotEmpty && notify) notifyListeners();
|
||||
}
|
||||
|
||||
BoardColumnDataController columnController(String columnId) {
|
||||
return _columnControllers[columnId]!;
|
||||
}
|
||||
|
||||
void moveColumn(int fromIndex, int toIndex) {
|
||||
BoardColumnDataController? getColumnController(String columnId) {
|
||||
final columnController = _columnControllers[columnId];
|
||||
if (columnController == null) {
|
||||
Log.warn('Column:[$columnId] \'s controller is not exist');
|
||||
}
|
||||
|
||||
return columnController;
|
||||
}
|
||||
|
||||
void moveColumn(int fromIndex, int toIndex, {bool notify = true}) {
|
||||
final columnData = _columnDatas.removeAt(fromIndex);
|
||||
_columnDatas.insert(toIndex, columnData);
|
||||
onMoveColumn?.call(fromIndex, toIndex);
|
||||
notifyListeners();
|
||||
if (notify) notifyListeners();
|
||||
}
|
||||
|
||||
void moveColumnItem(String columnId, int fromIndex, int toIndex) {
|
||||
final columnController = _columnControllers[columnId];
|
||||
assert(columnController != null);
|
||||
if (columnController != null) {
|
||||
columnController.move(fromIndex, toIndex);
|
||||
if (getColumnController(columnId)?.move(fromIndex, toIndex) ?? false) {
|
||||
onMoveColumnItem?.call(columnId, fromIndex, toIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void addColumnItem(String columnId, ColumnItem item) {
|
||||
getColumnController(columnId)?.add(item);
|
||||
}
|
||||
|
||||
void insertColumnItem(String columnId, int index, ColumnItem item) {
|
||||
getColumnController(columnId)?.insert(index, item);
|
||||
}
|
||||
|
||||
void removeColumnItem(String columnId, String itemId) {
|
||||
getColumnController(columnId)?.removeWhere((item) => item.id == itemId);
|
||||
}
|
||||
|
||||
@override
|
||||
@protected
|
||||
void swapColumnItem(
|
||||
|
Loading…
Reference in New Issue
Block a user