Merge pull request #820 from AppFlowy-IO/feat/appflowy_board

Feat/appflowy board
This commit is contained in:
Nathan.fooo 2022-08-12 17:11:49 +08:00 committed by GitHub
commit 435d1b8354
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
193 changed files with 4014 additions and 2441 deletions

View File

@ -44,7 +44,7 @@
"type": "dart",
"preLaunchTask": "AF: Clean + Rebuild All",
"env": {
"RUST_LOG": "info"
"RUST_LOG": "trace"
},
"cwd": "${workspaceRoot}/app_flowy"
},

View File

@ -0,0 +1,195 @@
import 'dart:async';
import 'package:app_flowy/plugins/grid/application/block/block_cache.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/log.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:collection';
import 'board_data_controller.dart';
part 'board_bloc.freezed.dart';
class BoardBloc extends Bloc<BoardEvent, BoardState> {
final BoardDataController _dataController;
late final AFBoardDataController boardDataController;
BoardBloc({required ViewPB view})
: _dataController = BoardDataController(view: view),
super(BoardState.initial(view.id)) {
boardDataController = AFBoardDataController(
onMoveColumn: (
fromIndex,
toIndex,
) {},
onMoveColumnItem: (
columnId,
fromIndex,
toIndex,
) {},
onMoveColumnItemToColumn: (
fromColumnId,
fromIndex,
toColumnId,
toIndex,
) {},
);
on<BoardEvent>(
(event, emit) async {
await event.when(
initial: () async {
_startListening();
await _loadGrid(emit);
},
createRow: () {
_dataController.createRow();
},
didReceiveGridUpdate: (GridPB grid) {
emit(state.copyWith(grid: Some(grid)));
},
didReceiveGroups: (List<GroupPB> groups) {
emit(state.copyWith(groups: groups));
},
);
},
);
}
@override
Future<void> close() async {
await _dataController.dispose();
return super.close();
}
GridRowCache? getRowCache(String blockId, String rowId) {
final GridBlockCache? blockCache = _dataController.blocks[blockId];
return blockCache?.rowCache;
}
void _startListening() {
_dataController.addListener(
onGridChanged: (grid) {
if (!isClosed) {
add(BoardEvent.didReceiveGridUpdate(grid));
}
},
onGroupChanged: (groups) {
List<AFBoardColumnData> columns = groups.map((group) {
return AFBoardColumnData(
id: group.groupId,
desc: group.desc,
items: _buildRows(group.rows),
customData: group,
);
}).toList();
boardDataController.addColumns(columns);
},
onError: (err) {
Log.error(err);
},
);
}
List<BoardColumnItem> _buildRows(List<RowPB> rows) {
return rows.map((row) {
final rowInfo = RowInfo(
gridId: _dataController.gridId,
blockId: row.blockId,
id: row.id,
fields: _dataController.fieldCache.unmodifiableFields,
height: row.height.toDouble(),
rawRow: row,
);
return BoardColumnItem(row: rowInfo);
}).toList();
}
Future<void> _loadGrid(Emitter<BoardState> emit) async {
final result = await _dataController.loadData();
result.fold(
(grid) => emit(
state.copyWith(loadingState: GridLoadingState.finish(left(unit))),
),
(err) => emit(
state.copyWith(loadingState: GridLoadingState.finish(right(err))),
),
);
}
}
@freezed
class BoardEvent with _$BoardEvent {
const factory BoardEvent.initial() = InitialGrid;
const factory BoardEvent.createRow() = _CreateRow;
const factory BoardEvent.didReceiveGroups(List<GroupPB> groups) =
_DidReceiveGroup;
const factory BoardEvent.didReceiveGridUpdate(
GridPB grid,
) = _DidReceiveGridUpdate;
}
@freezed
class BoardState with _$BoardState {
const factory BoardState({
required String gridId,
required Option<GridPB> grid,
required List<GroupPB> groups,
required List<RowInfo> rowInfos,
required GridLoadingState loadingState,
}) = _BoardState;
factory BoardState.initial(String gridId) => BoardState(
rowInfos: [],
groups: [],
grid: none(),
gridId: gridId,
loadingState: const _Loading(),
);
}
@freezed
class GridLoadingState with _$GridLoadingState {
const factory GridLoadingState.loading() = _Loading;
const factory GridLoadingState.finish(
Either<Unit, FlowyError> successOrFail) = _Finish;
}
class GridFieldEquatable extends Equatable {
final UnmodifiableListView<FieldPB> _fields;
const GridFieldEquatable(
UnmodifiableListView<FieldPB> fields,
) : _fields = fields;
@override
List<Object?> get props {
if (_fields.isEmpty) {
return [];
}
return [
_fields.length,
_fields
.map((field) => field.width)
.reduce((value, element) => value + element),
];
}
UnmodifiableListView<FieldPB> get value => UnmodifiableListView(_fields);
}
class BoardColumnItem extends AFColumnItem {
final RowInfo row;
BoardColumnItem({required this.row});
@override
String get id => row.id;
}

View File

@ -0,0 +1,113 @@
import 'dart:collection';
import 'package:app_flowy/plugins/grid/application/block/block_cache.dart';
import 'package:app_flowy/plugins/grid/application/field/field_cache.dart';
import 'package:app_flowy/plugins/grid/application/grid_service.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
import 'dart:async';
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
typedef OnFieldsChanged = void Function(UnmodifiableListView<FieldPB>);
typedef OnGridChanged = void Function(GridPB);
typedef OnGroupChanged = void Function(List<GroupPB>);
typedef OnError = void Function(FlowyError);
class BoardDataController {
final String gridId;
final GridService _gridFFIService;
final GridFieldCache fieldCache;
// key: the block id
final LinkedHashMap<String, GridBlockCache> _blocks;
UnmodifiableMapView<String, GridBlockCache> get blocks =>
UnmodifiableMapView(_blocks);
OnFieldsChanged? _onFieldsChanged;
OnGridChanged? _onGridChanged;
OnGroupChanged? _onGroupChanged;
OnError? _onError;
BoardDataController({required ViewPB view})
: gridId = view.id,
_blocks = LinkedHashMap.identity(),
_gridFFIService = GridService(gridId: view.id),
fieldCache = GridFieldCache(gridId: view.id);
void addListener({
OnGridChanged? onGridChanged,
OnFieldsChanged? onFieldsChanged,
OnGroupChanged? onGroupChanged,
OnError? onError,
}) {
_onGridChanged = onGridChanged;
_onFieldsChanged = onFieldsChanged;
_onGroupChanged = onGroupChanged;
_onError = onError;
fieldCache.addListener(onFields: (fields) {
_onFieldsChanged?.call(UnmodifiableListView(fields));
});
}
Future<Either<Unit, FlowyError>> loadData() async {
final result = await _gridFFIService.loadGrid();
return Future(
() => result.fold(
(grid) async {
_onGridChanged?.call(grid);
return await _loadFields(grid).then((result) {
return result.fold(
(l) {
_loadGroups();
return left(l);
},
(err) => right(err),
);
});
},
(err) => right(err),
),
);
}
void createRow() {
_gridFFIService.createRow();
}
Future<void> dispose() async {
await _gridFFIService.closeGrid();
await fieldCache.dispose();
for (final blockCache in _blocks.values) {
blockCache.dispose();
}
}
Future<Either<Unit, FlowyError>> _loadFields(GridPB grid) async {
final result = await _gridFFIService.getFields(fieldIds: grid.fields);
return Future(
() => result.fold(
(fields) {
fieldCache.fields = fields.items;
_onFieldsChanged?.call(UnmodifiableListView(fieldCache.fields));
return left(unit);
},
(err) => right(err),
),
);
}
Future<void> _loadGroups() async {
final result = await _gridFFIService.loadGroups();
return Future(
() => result.fold(
(groups) {
_onGroupChanged?.call(groups.items);
},
(err) => _onError?.call(err),
),
);
}
}

View File

@ -0,0 +1,76 @@
import 'dart:async';
import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
part 'board_select_option_cell_bloc.freezed.dart';
class BoardSelectOptionCellBloc
extends Bloc<BoardSelectOptionCellEvent, BoardSelectOptionCellState> {
final GridSelectOptionCellController cellController;
void Function()? _onCellChangedFn;
BoardSelectOptionCellBloc({
required this.cellController,
}) : super(BoardSelectOptionCellState.initial(cellController)) {
on<BoardSelectOptionCellEvent>(
(event, emit) async {
await event.when(
initial: () async {
_startListening();
},
didReceiveOptions: (List<SelectOptionPB> selectedOptions) {
emit(state.copyWith(selectedOptions: selectedOptions));
},
);
},
);
}
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellController.dispose();
return super.close();
}
void _startListening() {
_onCellChangedFn = cellController.startListening(
onCellChanged: ((selectOptionContext) {
if (!isClosed) {
add(BoardSelectOptionCellEvent.didReceiveOptions(
selectOptionContext?.selectOptions ?? [],
));
}
}),
);
}
}
@freezed
class BoardSelectOptionCellEvent with _$BoardSelectOptionCellEvent {
const factory BoardSelectOptionCellEvent.initial() = _InitialCell;
const factory BoardSelectOptionCellEvent.didReceiveOptions(
List<SelectOptionPB> selectedOptions,
) = _DidReceiveOptions;
}
@freezed
class BoardSelectOptionCellState with _$BoardSelectOptionCellState {
const factory BoardSelectOptionCellState({
required List<SelectOptionPB> selectedOptions,
}) = _BoardSelectOptionCellState;
factory BoardSelectOptionCellState.initial(
GridSelectOptionCellController context) {
final data = context.getCellData();
return BoardSelectOptionCellState(
selectedOptions: data?.selectOptions ?? [],
);
}
}

View File

@ -0,0 +1,66 @@
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
part 'board_text_cell_bloc.freezed.dart';
class BoardTextCellBloc extends Bloc<BoardTextCellEvent, BoardTextCellState> {
final GridCellController cellController;
void Function()? _onCellChangedFn;
BoardTextCellBloc({
required this.cellController,
}) : super(BoardTextCellState.initial(cellController)) {
on<BoardTextCellEvent>(
(event, emit) async {
await event.when(
initial: () async {
_startListening();
},
didReceiveCellUpdate: (content) {
emit(state.copyWith(content: content));
},
);
},
);
}
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellController.dispose();
return super.close();
}
void _startListening() {
_onCellChangedFn = cellController.startListening(
onCellChanged: ((cellContent) {
if (!isClosed) {
add(BoardTextCellEvent.didReceiveCellUpdate(cellContent ?? ""));
}
}),
);
}
}
@freezed
class BoardTextCellEvent with _$BoardTextCellEvent {
const factory BoardTextCellEvent.initial() = _InitialCell;
const factory BoardTextCellEvent.didReceiveCellUpdate(String cellContent) =
_DidReceiveCellUpdate;
}
@freezed
class BoardTextCellState with _$BoardTextCellState {
const factory BoardTextCellState({
required String content,
}) = _BoardTextCellState;
factory BoardTextCellState.initial(GridCellController context) =>
BoardTextCellState(
content: context.getCellData() ?? "",
);
}

View File

@ -0,0 +1,20 @@
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
class BoardGroupService {
final String gridId;
FieldPB? groupField;
BoardGroupService(this.gridId);
void setGroupField(FieldPB field) {
groupField = field;
}
}
abstract class CanBeGroupField {
String get groupContent;
}
// class SingleSelectGroup extends CanBeGroupField {
// final SingleSelectTypeOptionContext typeOptionContext;
// }

View File

@ -23,12 +23,15 @@ class BoardPluginBuilder implements PluginBuilder {
PluginType get pluginType => DefaultPlugin.board.type();
@override
ViewDataType get dataType => ViewDataType.Grid;
ViewDataTypePB get dataType => ViewDataTypePB.Database;
@override
SubViewDataTypePB get subDataType => SubViewDataTypePB.Board;
}
class BoardPluginConfig implements PluginConfig {
@override
bool get creatable => true;
bool get creatable => false;
}
class BoardPlugin extends Plugin {

View File

@ -1,154 +1,98 @@
// ignore_for_file: unused_field
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:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../application/board_bloc.dart';
import 'card/card.dart';
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();
}
class BoardPage extends StatelessWidget {
final ViewPB view;
BoardPage({required this.view, Key? key}) : super(key: ValueKey(view.id));
@override
Widget build(BuildContext context) {
final config = BoardConfig(
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
);
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,
);
},
cardBuilder: (context, item) {
return AppFlowyColumnItemCard(
key: ObjectKey(item),
child: _buildCard(item),
);
},
columnConstraints: const BoxConstraints.tightFor(width: 240),
config: BoardConfig(
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
),
),
return BlocProvider(
create: (context) =>
BoardBloc(view: view)..add(const BoardEvent.initial()),
child: BlocBuilder<BoardBloc, BoardState>(
builder: (context, state) {
return state.loadingState.map(
loading: (_) =>
const Center(child: CircularProgressIndicator.adaptive()),
finish: (result) {
return result.successOrFail.fold(
(_) => BoardContent(),
(err) => FlowyErrorPage(err.toString()),
);
},
);
},
),
);
}
}
Widget _buildCard(ColumnItem item) {
if (item is TextItem) {
return Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Text(item.s),
),
);
}
class BoardContent extends StatelessWidget {
final config = AFBoardConfig(
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
);
if (item is RichTextItem) {
return Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.title,
style: const TextStyle(fontSize: 14),
textAlign: TextAlign.left,
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: AFBoard(
dataController: context.read<BoardBloc>().boardDataController,
headerBuilder: _buildHeader,
footBuilder: _buildFooter,
cardBuilder: _buildCard,
columnConstraints: const BoxConstraints.tightFor(width: 240),
config: AFBoardConfig(
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
),
const SizedBox(height: 10),
Text(
item.subtitle,
style: const TextStyle(fontSize: 12, color: Colors.grey),
)
],
),
),
),
);
}
throw UnimplementedError();
);
},
);
}
}
class TextItem extends ColumnItem {
final String s;
Widget _buildHeader(BuildContext context, AFBoardColumnData 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,
);
}
TextItem(this.s);
Widget _buildFooter(BuildContext context, AFBoardColumnData columnData) {
return AppFlowyColumnFooter(
icon: const Icon(Icons.add, size: 20),
title: const Text('New'),
height: 50,
margin: config.columnItemPadding,
);
}
@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;
Widget _buildCard(BuildContext context, AFColumnItem item) {
final rowInfo = (item as BoardColumnItem).row;
return AppFlowyColumnItemCard(
key: ObjectKey(item),
child: BoardCard(rowInfo: rowInfo),
);
}
}
extension HexColor on Color {

View File

@ -0,0 +1,51 @@
import 'package:app_flowy/plugins/board/application/card/board_select_option_cell_bloc.dart';
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_cell.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class BoardSelectOptionCell extends StatefulWidget {
final GridCellControllerBuilder cellControllerBuilder;
const BoardSelectOptionCell({
required this.cellControllerBuilder,
Key? key,
}) : super(key: key);
@override
State<BoardSelectOptionCell> createState() => _BoardSelectOptionCellState();
}
class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
late BoardSelectOptionCellBloc _cellBloc;
@override
void initState() {
final cellController =
widget.cellControllerBuilder.build() as GridSelectOptionCellController;
_cellBloc = BoardSelectOptionCellBloc(cellController: cellController)
..add(const BoardSelectOptionCellEvent.initial());
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocProvider.value(
value: _cellBloc,
child: BlocBuilder<BoardSelectOptionCellBloc, BoardSelectOptionCellState>(
builder: (context, state) {
return SelectOptionWrap(
selectOptions: state.selectedOptions,
cellControllerBuilder: widget.cellControllerBuilder,
);
},
),
);
}
@override
Future<void> dispose() async {
_cellBloc.close();
super.dispose();
}
}

View File

@ -0,0 +1,49 @@
import 'package:app_flowy/plugins/board/application/card/board_text_cell_bloc.dart';
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class BoardTextCell extends StatefulWidget {
final GridCellControllerBuilder cellControllerBuilder;
const BoardTextCell({required this.cellControllerBuilder, Key? key})
: super(key: key);
@override
State<BoardTextCell> createState() => _BoardTextCellState();
}
class _BoardTextCellState extends State<BoardTextCell> {
late BoardTextCellBloc _cellBloc;
@override
void initState() {
final cellController =
widget.cellControllerBuilder.build() as GridCellController;
_cellBloc = BoardTextCellBloc(cellController: cellController)
..add(const BoardTextCellEvent.initial());
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocProvider.value(
value: _cellBloc,
child: BlocBuilder<BoardTextCellBloc, BoardTextCellState>(
builder: (context, state) {
return SizedBox(
height: 30,
child: FlowyText.medium(state.content),
);
},
),
);
}
@override
Future<void> dispose() async {
_cellBloc.close();
super.dispose();
}
}

View File

@ -0,0 +1,13 @@
import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
import 'package:flutter/material.dart';
class BoardCard extends StatelessWidget {
final RowInfo rowInfo;
const BoardCard({required this.rowInfo, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const SizedBox(height: 20, child: Text('1234'));
}
}

View File

@ -1,4 +1,4 @@
library docuemnt_plugin;
library document_plugin;
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:app_flowy/startup/plugin/plugin.dart';
@ -42,10 +42,10 @@ class DocumentPluginBuilder extends PluginBuilder {
String get menuName => LocaleKeys.document_menuName.tr();
@override
PluginType get pluginType => DefaultPlugin.quill.type();
PluginType get pluginType => DefaultPlugin.editor.type();
@override
ViewDataType get dataType => ViewDataType.TextBlock;
ViewDataTypePB get dataType => ViewDataTypePB.TextBlock;
}
class DocumentPlugin implements Plugin {

View File

@ -1,19 +1,19 @@
import 'dart:async';
import 'package:app_flowy/plugins/grid/application/grid_service.dart';
import 'package:app_flowy/plugins/grid/application/row/row_service.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
import '../field/field_cache.dart';
import '../row/row_cache.dart';
import 'block_listener.dart';
/// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid for more information
class GridBlockCache {
final String gridId;
final GridBlockPB block;
final BlockPB block;
late GridRowCache _rowCache;
late GridBlockListener _listener;
List<GridRowInfo> get rows => _rowCache.rows;
List<RowInfo> get rows => _rowCache.rows;
GridRowCache get rowCache => _rowCache;
GridBlockCache({
@ -24,7 +24,7 @@ class GridBlockCache {
_rowCache = GridRowCache(
gridId: gridId,
block: block,
notifier: GridRowCacheFieldNotifierImpl(fieldCache),
notifier: GridRowFieldNotifierImpl(fieldCache),
);
_listener = GridBlockListener(blockId: block.id);
@ -42,7 +42,7 @@ class GridBlockCache {
}
void addListener({
required void Function(GridRowChangeReason) onChangeReason,
required void Function(RowChangeReason) onChangeReason,
bool Function()? listenWhen,
}) {
_rowCache.onRowsChanged((reason) {

View File

@ -3,20 +3,22 @@ import 'package:flutter/foundation.dart';
import 'cell_service.dart';
abstract class IGridFieldChangedNotifier {
void onFieldChanged(void Function(GridFieldPB) callback);
void dispose();
abstract class IGridCellFieldNotifier {
void onCellFieldChanged(void Function(FieldPB) callback);
void onCellDispose();
}
/// GridPB's cell helper wrapper that enables each cell will get notified when the corresponding field was changed.
/// You Register an onFieldChanged callback to listen to the cell changes, and unregister if you don't want to listen.
class GridCellFieldNotifier {
final IGridCellFieldNotifier notifier;
/// fieldId: {objectId: callback}
final Map<String, Map<String, List<VoidCallback>>> _fieldListenerByFieldId =
{};
GridCellFieldNotifier({required IGridFieldChangedNotifier notifier}) {
notifier.onFieldChanged(
GridCellFieldNotifier({required this.notifier}) {
notifier.onCellFieldChanged(
(field) {
final map = _fieldListenerByFieldId[field.id];
if (map != null) {
@ -56,6 +58,7 @@ class GridCellFieldNotifier {
}
Future<void> dispose() async {
notifier.onCellDispose();
_fieldListenerByFieldId.clear();
}
}

View File

@ -1,7 +1,5 @@
import 'dart:async';
import 'dart:collection';
import 'package:app_flowy/plugins/grid/application/grid_service.dart';
import 'package:dartz/dartz.dart';
import 'package:equatable/equatable.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart';
@ -18,7 +16,8 @@ import 'package:app_flowy/plugins/grid/application/cell/cell_listener.dart';
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
import 'dart:convert' show utf8;
import '../../field/type_option/type_option_service.dart';
import '../../field/field_cache.dart';
import '../../field/type_option/type_option_context.dart';
import 'cell_field_notifier.dart';
part 'cell_service.freezed.dart';
part 'cell_data_loader.dart';
@ -61,7 +60,7 @@ class GridCellIdentifier with _$GridCellIdentifier {
const factory GridCellIdentifier({
required String gridId,
required String rowId,
required GridFieldPB field,
required FieldPB field,
}) = _GridCellIdentifier;
// ignore: unused_element

View File

@ -7,23 +7,24 @@ typedef GridDateCellController
= IGridCellController<DateCellDataPB, CalendarData>;
typedef GridURLCellController = IGridCellController<URLCellDataPB, String>;
abstract class GridCellControllerBuilderDelegate {
GridCellFieldNotifier buildFieldNotifier();
}
class GridCellControllerBuilder {
final GridCellIdentifier _cellId;
final GridCellCache _cellCache;
final GridFieldCache _fieldCache;
final GridCellControllerBuilderDelegate delegate;
GridCellControllerBuilder({
required this.delegate,
required GridCellIdentifier cellId,
required GridCellCache cellCache,
required GridFieldCache fieldCache,
}) : _cellCache = cellCache,
_fieldCache = fieldCache,
_cellId = cellId;
IGridCellController build() {
final cellFieldNotifier = GridCellFieldNotifier(
notifier: _GridFieldChangedNotifierImpl(_fieldCache));
final cellFieldNotifier = delegate.buildFieldNotifier();
switch (_cellId.fieldType) {
case FieldType.Checkbox:
final cellDataLoader = GridCellDataLoader(
@ -165,7 +166,7 @@ class IGridCellController<T, D> extends Equatable {
String get fieldId => cellId.field.id;
GridFieldPB get field => cellId.field;
FieldPB get field => cellId.field;
FieldType get fieldType => cellId.field.fieldType;
@ -295,6 +296,7 @@ class IGridCellController<T, D> extends Equatable {
if (_onFieldChangedFn != null) {
_fieldNotifier.unregister(_cacheKey, _onFieldChangedFn!);
_fieldNotifier.dispose();
_onFieldChangedFn = null;
}
}
@ -304,14 +306,14 @@ class IGridCellController<T, D> extends Equatable {
[_cellsCache.get(_cacheKey) ?? "", cellId.rowId + cellId.field.id];
}
class _GridFieldChangedNotifierImpl extends IGridFieldChangedNotifier {
class GridCellFieldNotifierImpl extends IGridCellFieldNotifier {
final GridFieldCache _cache;
FieldChangesetCallback? _onChangesetFn;
_GridFieldChangedNotifierImpl(GridFieldCache cache) : _cache = cache;
GridCellFieldNotifierImpl(GridFieldCache cache) : _cache = cache;
@override
void dispose() {
void onCellDispose() {
if (_onChangesetFn != null) {
_cache.removeListener(onChangesetListener: _onChangesetFn!);
_onChangesetFn = null;
@ -319,8 +321,8 @@ class _GridFieldChangedNotifierImpl extends IGridFieldChangedNotifier {
}
@override
void onFieldChanged(void Function(GridFieldPB p1) callback) {
_onChangesetFn = (GridFieldChangesetPB changeset) {
void onCellFieldChanged(void Function(FieldPB p1) callback) {
_onChangesetFn = (FieldChangesetPB changeset) {
for (final updatedField in changeset.updatedFields) {
callback(updatedField);
}

View File

@ -6,13 +6,13 @@ import 'cell_service/cell_service.dart';
part 'checkbox_cell_bloc.freezed.dart';
class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
final GridCellController cellContext;
final GridCellController cellController;
void Function()? _onCellChangedFn;
CheckboxCellBloc({
required CellService service,
required this.cellContext,
}) : super(CheckboxCellState.initial(cellContext)) {
required this.cellController,
}) : super(CheckboxCellState.initial(cellController)) {
on<CheckboxCellEvent>(
(event, emit) async {
await event.when(
@ -33,16 +33,17 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellContext.removeListener(_onCellChangedFn!);
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellContext.dispose();
cellController.dispose();
return super.close();
}
void _startListening() {
_onCellChangedFn = cellContext.startListening(onCellChanged: ((cellData) {
_onCellChangedFn =
cellController.startListening(onCellChanged: ((cellData) {
if (!isClosed) {
add(CheckboxCellEvent.didReceiveCellUpdate(cellData));
}
@ -50,7 +51,7 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
}
void _updateCellData() {
cellContext.saveCellData(!state.isSelected ? "Yes" : "No");
cellController.saveCellData(!state.isSelected ? "Yes" : "No");
}
}
@ -58,7 +59,8 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
class CheckboxCellEvent with _$CheckboxCellEvent {
const factory CheckboxCellEvent.initial() = _Initial;
const factory CheckboxCellEvent.select() = _Selected;
const factory CheckboxCellEvent.didReceiveCellUpdate(String? cellData) = _DidReceiveCellUpdate;
const factory CheckboxCellEvent.didReceiveCellUpdate(String? cellData) =
_DidReceiveCellUpdate;
}
@freezed

View File

@ -18,14 +18,14 @@ import 'package:fixnum/fixnum.dart' as $fixnum;
part 'date_cal_bloc.freezed.dart';
class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
final GridDateCellController cellContext;
final GridDateCellController cellController;
void Function()? _onCellChangedFn;
DateCalBloc({
required DateTypeOption dateTypeOption,
required DateTypeOptionPB dateTypeOptionPB,
required DateCellDataPB? cellData,
required this.cellContext,
}) : super(DateCalState.initial(dateTypeOption, cellData)) {
required this.cellController,
}) : super(DateCalState.initial(dateTypeOptionPB, cellData)) {
on<DateCalEvent>(
(event, emit) async {
await event.when(
@ -102,7 +102,7 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
}
}
cellContext.saveCellData(newCalData, resultCallback: (result) {
cellController.saveCellData(newCalData, resultCallback: (result) {
result.fold(
() => updateCalData(Some(newCalData), none()),
(err) {
@ -120,7 +120,7 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
String timeFormatPrompt(FlowyError error) {
String msg = LocaleKeys.grid_field_invalidTimeFormat.tr() + ". ";
switch (state.dateTypeOption.timeFormat) {
switch (state.dateTypeOptionPB.timeFormat) {
case TimeFormat.TwelveHour:
msg = msg + "e.g. 01: 00 AM";
break;
@ -136,15 +136,15 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellContext.removeListener(_onCellChangedFn!);
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellContext.dispose();
cellController.dispose();
return super.close();
}
void _startListening() {
_onCellChangedFn = cellContext.startListening(
_onCellChangedFn = cellController.startListening(
onCellChanged: ((cell) {
if (!isClosed) {
add(DateCalEvent.didReceiveCellUpdate(cell));
@ -159,8 +159,8 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
TimeFormat? timeFormat,
bool? includeTime,
}) async {
state.dateTypeOption.freeze();
final newDateTypeOption = state.dateTypeOption.rebuild((typeOption) {
state.dateTypeOptionPB.freeze();
final newDateTypeOption = state.dateTypeOptionPB.rebuild((typeOption) {
if (dateFormat != null) {
typeOption.dateFormat = dateFormat;
}
@ -175,14 +175,14 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
});
final result = await FieldService.updateFieldTypeOption(
gridId: cellContext.gridId,
fieldId: cellContext.field.id,
gridId: cellController.gridId,
fieldId: cellController.field.id,
typeOptionData: newDateTypeOption.writeToBuffer(),
);
result.fold(
(l) => emit(state.copyWith(
dateTypeOption: newDateTypeOption,
dateTypeOptionPB: newDateTypeOption,
timeHintText: _timeHintText(newDateTypeOption))),
(err) => Log.error(err),
);
@ -210,7 +210,7 @@ class DateCalEvent with _$DateCalEvent {
@freezed
class DateCalState with _$DateCalState {
const factory DateCalState({
required DateTypeOption dateTypeOption,
required DateTypeOptionPB dateTypeOptionPB,
required CalendarFormat format,
required DateTime focusedDay,
required Option<String> timeFormatError,
@ -220,24 +220,24 @@ class DateCalState with _$DateCalState {
}) = _DateCalState;
factory DateCalState.initial(
DateTypeOption dateTypeOption,
DateTypeOptionPB dateTypeOptionPB,
DateCellDataPB? cellData,
) {
Option<CalendarData> calData = calDataFromCellData(cellData);
final time = calData.foldRight("", (dateData, previous) => dateData.time);
return DateCalState(
dateTypeOption: dateTypeOption,
dateTypeOptionPB: dateTypeOptionPB,
format: CalendarFormat.month,
focusedDay: DateTime.now(),
time: time,
calData: calData,
timeFormatError: none(),
timeHintText: _timeHintText(dateTypeOption),
timeHintText: _timeHintText(dateTypeOptionPB),
);
}
}
String _timeHintText(DateTypeOption typeOption) {
String _timeHintText(DateTypeOptionPB typeOption) {
switch (typeOption.timeFormat) {
case TimeFormat.TwelveHour:
return LocaleKeys.document_date_timeHintTextInTwelveHour.tr();

View File

@ -7,18 +7,21 @@ import 'cell_service/cell_service.dart';
part 'date_cell_bloc.freezed.dart';
class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
final GridDateCellController cellContext;
final GridDateCellController cellController;
void Function()? _onCellChangedFn;
DateCellBloc({required this.cellContext}) : super(DateCellState.initial(cellContext)) {
DateCellBloc({required this.cellController})
: super(DateCellState.initial(cellController)) {
on<DateCellEvent>(
(event, emit) async {
event.when(
initial: () => _startListening(),
didReceiveCellUpdate: (DateCellDataPB? cellData) {
emit(state.copyWith(data: cellData, dateStr: _dateStrFromCellData(cellData)));
emit(state.copyWith(
data: cellData, dateStr: _dateStrFromCellData(cellData)));
},
didReceiveFieldUpdate: (GridFieldPB value) => emit(state.copyWith(field: value)),
didReceiveFieldUpdate: (FieldPB value) =>
emit(state.copyWith(field: value)),
);
},
);
@ -27,15 +30,15 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellContext.removeListener(_onCellChangedFn!);
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellContext.dispose();
cellController.dispose();
return super.close();
}
void _startListening() {
_onCellChangedFn = cellContext.startListening(
_onCellChangedFn = cellController.startListening(
onCellChanged: ((data) {
if (!isClosed) {
add(DateCellEvent.didReceiveCellUpdate(data));
@ -48,8 +51,10 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
@freezed
class DateCellEvent with _$DateCellEvent {
const factory DateCellEvent.initial() = _InitialCell;
const factory DateCellEvent.didReceiveCellUpdate(DateCellDataPB? data) = _DidReceiveCellUpdate;
const factory DateCellEvent.didReceiveFieldUpdate(GridFieldPB field) = _DidReceiveFieldUpdate;
const factory DateCellEvent.didReceiveCellUpdate(DateCellDataPB? data) =
_DidReceiveCellUpdate;
const factory DateCellEvent.didReceiveFieldUpdate(FieldPB field) =
_DidReceiveFieldUpdate;
}
@freezed
@ -57,7 +62,7 @@ class DateCellState with _$DateCellState {
const factory DateCellState({
required DateCellDataPB? data,
required String dateStr,
required GridFieldPB field,
required FieldPB field,
}) = _DateCellState;
factory DateCellState.initial(GridDateCellController context) {

View File

@ -8,12 +8,12 @@ import 'cell_service/cell_service.dart';
part 'number_cell_bloc.freezed.dart';
class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
final GridCellController cellContext;
final GridCellController cellController;
void Function()? _onCellChangedFn;
NumberCellBloc({
required this.cellContext,
}) : super(NumberCellState.initial(cellContext)) {
required this.cellController,
}) : super(NumberCellState.initial(cellController)) {
on<NumberCellEvent>(
(event, emit) async {
event.when(
@ -24,11 +24,13 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
emit(state.copyWith(content: content));
},
updateCell: (text) {
cellContext.saveCellData(text, resultCallback: (result) {
cellController.saveCellData(text, resultCallback: (result) {
result.fold(
() => null,
(err) {
if (!isClosed) add(NumberCellEvent.didReceiveCellUpdate(right(err)));
if (!isClosed) {
add(NumberCellEvent.didReceiveCellUpdate(right(err)));
}
},
);
});
@ -41,15 +43,15 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellContext.removeListener(_onCellChangedFn!);
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellContext.dispose();
cellController.dispose();
return super.close();
}
void _startListening() {
_onCellChangedFn = cellContext.startListening(
_onCellChangedFn = cellController.startListening(
onCellChanged: ((cellContent) {
if (!isClosed) {
add(NumberCellEvent.didReceiveCellUpdate(left(cellContent ?? "")));
@ -63,7 +65,8 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
class NumberCellEvent with _$NumberCellEvent {
const factory NumberCellEvent.initial() = _Initial;
const factory NumberCellEvent.updateCell(String text) = _UpdateCell;
const factory NumberCellEvent.didReceiveCellUpdate(Either<String, FlowyError> cellContent) = _DidReceiveCellUpdate;
const factory NumberCellEvent.didReceiveCellUpdate(
Either<String, FlowyError> cellContent) = _DidReceiveCellUpdate;
}
@freezed

View File

@ -8,12 +8,12 @@ part 'select_option_cell_bloc.freezed.dart';
class SelectOptionCellBloc
extends Bloc<SelectOptionCellEvent, SelectOptionCellState> {
final GridSelectOptionCellController cellContext;
final GridSelectOptionCellController cellController;
void Function()? _onCellChangedFn;
SelectOptionCellBloc({
required this.cellContext,
}) : super(SelectOptionCellState.initial(cellContext)) {
required this.cellController,
}) : super(SelectOptionCellState.initial(cellController)) {
on<SelectOptionCellEvent>(
(event, emit) async {
await event.map(
@ -33,15 +33,15 @@ class SelectOptionCellBloc
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellContext.removeListener(_onCellChangedFn!);
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellContext.dispose();
cellController.dispose();
return super.close();
}
void _startListening() {
_onCellChangedFn = cellContext.startListening(
_onCellChangedFn = cellController.startListening(
onCellChanged: ((selectOptionContext) {
if (!isClosed) {
add(SelectOptionCellEvent.didReceiveOptions(

View File

@ -15,7 +15,7 @@ class SelectOptionService {
String get rowId => cellId.rowId;
Future<Either<Unit, FlowyError>> create({required String name}) {
return TypeOptionService(gridId: gridId, fieldId: fieldId)
return TypeOptionFFIService(gridId: gridId, fieldId: fieldId)
.newOption(name: name)
.then(
(result) {

View File

@ -6,11 +6,11 @@ import 'cell_service/cell_service.dart';
part 'text_cell_bloc.freezed.dart';
class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
final GridCellController cellContext;
final GridCellController cellController;
void Function()? _onCellChangedFn;
TextCellBloc({
required this.cellContext,
}) : super(TextCellState.initial(cellContext)) {
required this.cellController,
}) : super(TextCellState.initial(cellController)) {
on<TextCellEvent>(
(event, emit) async {
await event.when(
@ -18,7 +18,7 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
_startListening();
},
updateText: (text) {
cellContext.saveCellData(text);
cellController.saveCellData(text);
emit(state.copyWith(content: text));
},
didReceiveCellUpdate: (content) {
@ -32,15 +32,15 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellContext.removeListener(_onCellChangedFn!);
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellContext.dispose();
cellController.dispose();
return super.close();
}
void _startListening() {
_onCellChangedFn = cellContext.startListening(
_onCellChangedFn = cellController.startListening(
onCellChanged: ((cellContent) {
if (!isClosed) {
add(TextCellEvent.didReceiveCellUpdate(cellContent ?? ""));
@ -53,7 +53,8 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
@freezed
class TextCellEvent with _$TextCellEvent {
const factory TextCellEvent.initial() = _InitialCell;
const factory TextCellEvent.didReceiveCellUpdate(String cellContent) = _DidReceiveCellUpdate;
const factory TextCellEvent.didReceiveCellUpdate(String cellContent) =
_DidReceiveCellUpdate;
const factory TextCellEvent.updateText(String text) = _UpdateText;
}

View File

@ -7,11 +7,11 @@ import 'cell_service/cell_service.dart';
part 'url_cell_bloc.freezed.dart';
class URLCellBloc extends Bloc<URLCellEvent, URLCellState> {
final GridURLCellController cellContext;
final GridURLCellController cellController;
void Function()? _onCellChangedFn;
URLCellBloc({
required this.cellContext,
}) : super(URLCellState.initial(cellContext)) {
required this.cellController,
}) : super(URLCellState.initial(cellController)) {
on<URLCellEvent>(
(event, emit) async {
event.when(
@ -25,7 +25,7 @@ class URLCellBloc extends Bloc<URLCellEvent, URLCellState> {
));
},
updateURL: (String url) {
cellContext.saveCellData(url, deduplicate: true);
cellController.saveCellData(url, deduplicate: true);
},
);
},
@ -35,15 +35,15 @@ class URLCellBloc extends Bloc<URLCellEvent, URLCellState> {
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellContext.removeListener(_onCellChangedFn!);
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellContext.dispose();
cellController.dispose();
return super.close();
}
void _startListening() {
_onCellChangedFn = cellContext.startListening(
_onCellChangedFn = cellController.startListening(
onCellChanged: ((cellData) {
if (!isClosed) {
add(URLCellEvent.didReceiveCellUpdate(cellData));
@ -57,7 +57,8 @@ class URLCellBloc extends Bloc<URLCellEvent, URLCellState> {
class URLCellEvent with _$URLCellEvent {
const factory URLCellEvent.initial() = _InitialCell;
const factory URLCellEvent.updateURL(String url) = _UpdateURL;
const factory URLCellEvent.didReceiveCellUpdate(URLCellDataPB? cell) = _DidReceiveCellUpdate;
const factory URLCellEvent.didReceiveCellUpdate(URLCellDataPB? cell) =
_DidReceiveCellUpdate;
}
@freezed

View File

@ -7,11 +7,11 @@ import 'cell_service/cell_service.dart';
part 'url_cell_editor_bloc.freezed.dart';
class URLCellEditorBloc extends Bloc<URLCellEditorEvent, URLCellEditorState> {
final GridURLCellController cellContext;
final GridURLCellController cellController;
void Function()? _onCellChangedFn;
URLCellEditorBloc({
required this.cellContext,
}) : super(URLCellEditorState.initial(cellContext)) {
required this.cellController,
}) : super(URLCellEditorState.initial(cellController)) {
on<URLCellEditorEvent>(
(event, emit) async {
event.when(
@ -19,7 +19,7 @@ class URLCellEditorBloc extends Bloc<URLCellEditorEvent, URLCellEditorState> {
_startListening();
},
updateText: (text) {
cellContext.saveCellData(text, deduplicate: true);
cellController.saveCellData(text, deduplicate: true);
emit(state.copyWith(content: text));
},
didReceiveCellUpdate: (cellData) {
@ -33,15 +33,15 @@ class URLCellEditorBloc extends Bloc<URLCellEditorEvent, URLCellEditorState> {
@override
Future<void> close() async {
if (_onCellChangedFn != null) {
cellContext.removeListener(_onCellChangedFn!);
cellController.removeListener(_onCellChangedFn!);
_onCellChangedFn = null;
}
cellContext.dispose();
cellController.dispose();
return super.close();
}
void _startListening() {
_onCellChangedFn = cellContext.startListening(
_onCellChangedFn = cellController.startListening(
onCellChanged: ((cellData) {
if (!isClosed) {
add(URLCellEditorEvent.didReceiveCellUpdate(cellData));
@ -54,7 +54,8 @@ class URLCellEditorBloc extends Bloc<URLCellEditorEvent, URLCellEditorState> {
@freezed
class URLCellEditorEvent with _$URLCellEditorEvent {
const factory URLCellEditorEvent.initial() = _InitialCell;
const factory URLCellEditorEvent.didReceiveCellUpdate(URLCellDataPB? cell) = _DidReceiveCellUpdate;
const factory URLCellEditorEvent.didReceiveCellUpdate(URLCellDataPB? cell) =
_DidReceiveCellUpdate;
const factory URLCellEditorEvent.updateText(String text) = _UpdateText;
}

View File

@ -7,11 +7,13 @@ import 'field_service.dart';
part 'field_action_sheet_bloc.freezed.dart';
class FieldActionSheetBloc extends Bloc<FieldActionSheetEvent, FieldActionSheetState> {
class FieldActionSheetBloc
extends Bloc<FieldActionSheetEvent, FieldActionSheetState> {
final FieldService fieldService;
FieldActionSheetBloc({required GridFieldPB field, required this.fieldService})
: super(FieldActionSheetState.initial(FieldTypeOptionDataPB.create()..field_2 = field)) {
FieldActionSheetBloc({required FieldPB field, required this.fieldService})
: super(FieldActionSheetState.initial(
FieldTypeOptionDataPB.create()..field_2 = field)) {
on<FieldActionSheetEvent>(
(event, emit) async {
await event.map(
@ -57,7 +59,8 @@ class FieldActionSheetBloc extends Bloc<FieldActionSheetEvent, FieldActionSheetS
@freezed
class FieldActionSheetEvent with _$FieldActionSheetEvent {
const factory FieldActionSheetEvent.updateFieldName(String name) = _UpdateFieldName;
const factory FieldActionSheetEvent.updateFieldName(String name) =
_UpdateFieldName;
const factory FieldActionSheetEvent.hideField() = _HideField;
const factory FieldActionSheetEvent.duplicateField() = _DuplicateField;
const factory FieldActionSheetEvent.deleteField() = _DeleteField;
@ -72,7 +75,8 @@ class FieldActionSheetState with _$FieldActionSheetState {
required String fieldName,
}) = _FieldActionSheetState;
factory FieldActionSheetState.initial(FieldTypeOptionDataPB data) => FieldActionSheetState(
factory FieldActionSheetState.initial(FieldTypeOptionDataPB data) =>
FieldActionSheetState(
fieldTypeOptionData: data,
errorText: '',
fieldName: data.field_2.name,

View File

@ -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<FieldPB> _fields = [];
set fields(List<FieldPB> fields) {
_fields = fields;
notifyListeners();
}
List<FieldPB> get fields => _fields;
}
typedef FieldChangesetCallback = void Function(FieldChangesetPB);
typedef FieldsCallback = void Function(List<FieldPB>);
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<FieldPB> get unmodifiableFields =>
UnmodifiableListView(_fieldNotifier?.fields ?? []);
List<FieldPB> get fields => [..._fieldNotifier?.fields ?? []];
set fields(List<FieldPB> 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<FieldIdPB> deletedFields) {
if (deletedFields.isEmpty) {
return;
}
final List<FieldPB> newFields = fields;
final Map<String, FieldIdPB> 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<FieldPB> 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<FieldPB> updatedFields) {
if (updatedFields.isEmpty) {
return;
}
final List<FieldPB> 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<FieldPB> get fields => _cache.unmodifiableFields;
@override
void onRowFieldsChanged(VoidCallback callback) {
_onFieldFn = (_) => callback();
_cache.addListener(onFields: _onFieldFn);
}
@override
void onRowFieldChanged(void Function(FieldPB) callback) {
_onChangesetFn = (FieldChangesetPB 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;
}
}
}

View File

@ -63,7 +63,7 @@ class FieldCellBloc extends Bloc<FieldCellEvent, FieldCellState> {
@freezed
class FieldCellEvent with _$FieldCellEvent {
const factory FieldCellEvent.initial() = _InitialCell;
const factory FieldCellEvent.didReceiveFieldUpdate(GridFieldPB field) =
const factory FieldCellEvent.didReceiveFieldUpdate(FieldPB field) =
_DidReceiveFieldUpdate;
const factory FieldCellEvent.startUpdateWidth(double offset) =
_StartUpdateWidth;
@ -74,7 +74,7 @@ class FieldCellEvent with _$FieldCellEvent {
class FieldCellState with _$FieldCellState {
const factory FieldCellState({
required String gridId,
required GridFieldPB field,
required FieldPB field,
required double width,
}) = _FieldCellState;

View File

@ -1,9 +1,12 @@
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import 'field_service.dart';
import 'package:dartz/dartz.dart';
import 'type_option/type_option_context.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'type_option/type_option_data_controller.dart';
part 'field_editor_bloc.freezed.dart';
class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
@ -13,7 +16,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,13 +28,13 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
add(FieldEditorEvent.didReceiveFieldChanged(field));
}
});
await dataController.loadData();
await dataController.loadTypeOptionData();
},
updateName: (name) {
dataController.fieldName = name;
emit(state.copyWith(name: name));
},
didReceiveFieldChanged: (GridFieldPB field) {
didReceiveFieldChanged: (FieldPB field) {
emit(state.copyWith(field: Some(field)));
},
);
@ -48,7 +52,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(FieldPB field) =
_DidReceiveFieldChanged;
}
@freezed
@ -57,7 +62,7 @@ class FieldEditorState with _$FieldEditorState {
required String gridId,
required String errorText,
required String name,
required Option<GridFieldPB> field,
required Option<FieldPB> field,
}) = _FieldEditorState;
factory FieldEditorState.initial(

View File

@ -7,16 +7,18 @@ import 'dart:async';
import 'dart:typed_data';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
typedef UpdateFieldNotifiedValue = Either<GridFieldPB, FlowyError>;
typedef UpdateFieldNotifiedValue = Either<FieldPB, FlowyError>;
class SingleFieldListener {
final String fieldId;
PublishNotifier<UpdateFieldNotifiedValue>? _updateFieldNotifier = PublishNotifier();
PublishNotifier<UpdateFieldNotifiedValue>? _updateFieldNotifier =
PublishNotifier();
GridNotificationListener? _listener;
SingleFieldListener({required this.fieldId});
void start({required void Function(UpdateFieldNotifiedValue) onFieldChanged}) {
void start(
{required void Function(UpdateFieldNotifiedValue) onFieldChanged}) {
_updateFieldNotifier?.addPublishListener(onFieldChanged);
_listener = GridNotificationListener(
objectId: fieldId,
@ -31,7 +33,8 @@ class SingleFieldListener {
switch (ty) {
case GridNotification.DidUpdateField:
result.fold(
(payload) => _updateFieldNotifier?.value = left(GridFieldPB.fromBuffer(payload)),
(payload) =>
_updateFieldNotifier?.value = left(FieldPB.fromBuffer(payload)),
(error) => _updateFieldNotifier?.value = right(error),
);
break;

View File

@ -1,13 +1,10 @@
import 'package:dartz/dartz.dart';
import 'package:flowy_infra/notifier.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:protobuf/protobuf.dart';
part 'field_service.freezed.dart';
/// FieldService consists of lots of event functions. We define the events in the backend(Rust),
@ -73,7 +70,7 @@ class FieldService {
// Create the field if it does not exist. Otherwise, update the field.
static Future<Either<Unit, FlowyError>> insertField({
required String gridId,
required GridFieldPB field,
required FieldPB field,
List<int>? typeOptionData,
String? startFieldId,
}) {
@ -121,7 +118,7 @@ class FieldService {
Future<Either<FieldTypeOptionDataPB, FlowyError>> getFieldTypeOptionData({
required FieldType fieldType,
}) {
final payload = GridFieldTypeOptionIdPB.create()
final payload = FieldTypeOptionIdPB.create()
..gridId = gridId
..fieldId = fieldId
..fieldType = fieldType;
@ -138,158 +135,6 @@ class FieldService {
class GridFieldCellContext with _$GridFieldCellContext {
const factory GridFieldCellContext({
required String gridId,
required GridFieldPB field,
required FieldPB field,
}) = _GridFieldCellContext;
}
abstract class IFieldTypeOptionLoader {
String get gridId;
Future<Either<FieldTypeOptionDataPB, FlowyError>> load();
Future<Either<FieldTypeOptionDataPB, FlowyError>> switchToField(String fieldId, FieldType fieldType) {
final payload = EditFieldPayloadPB.create()
..gridId = gridId
..fieldId = fieldId
..fieldType = fieldType;
return GridEventSwitchToField(payload).send();
}
}
class NewFieldTypeOptionLoader extends IFieldTypeOptionLoader {
@override
final String gridId;
NewFieldTypeOptionLoader({
required this.gridId,
});
@override
Future<Either<FieldTypeOptionDataPB, FlowyError>> load() {
final payload = CreateFieldPayloadPB.create()
..gridId = gridId
..fieldType = FieldType.RichText;
return GridEventCreateFieldTypeOption(payload).send();
}
}
class FieldTypeOptionLoader extends IFieldTypeOptionLoader {
@override
final String gridId;
final GridFieldPB field;
FieldTypeOptionLoader({
required this.gridId,
required this.field,
});
@override
Future<Either<FieldTypeOptionDataPB, FlowyError>> load() {
final payload = GridFieldTypeOptionIdPB.create()
..gridId = gridId
..fieldId = field.id
..fieldType = field.fieldType;
return GridEventGetFieldTypeOption(payload).send();
}
}
class TypeOptionDataController {
final String gridId;
final IFieldTypeOptionLoader _loader;
late FieldTypeOptionDataPB _data;
final PublishNotifier<GridFieldPB> _fieldNotifier = PublishNotifier();
TypeOptionDataController({
required this.gridId,
required IFieldTypeOptionLoader loader,
}) : _loader = loader;
Future<Either<Unit, FlowyError>> loadData() async {
final result = await _loader.load();
return result.fold(
(data) {
data.freeze();
_data = data;
_fieldNotifier.value = data.field_2;
return left(unit);
},
(err) {
Log.error(err);
return right(err);
},
);
}
GridFieldPB get field => _data.field_2;
set field(GridFieldPB field) {
_updateData(newField: field);
}
List<int> get typeOptionData => _data.typeOptionData;
set fieldName(String name) {
_updateData(newName: name);
}
set typeOptionData(List<int> typeOptionData) {
_updateData(newTypeOptionData: typeOptionData);
}
void _updateData({String? newName, GridFieldPB? newField, List<int>? newTypeOptionData}) {
_data = _data.rebuild((rebuildData) {
if (newName != null) {
rebuildData.field_2 = rebuildData.field_2.rebuild((rebuildField) {
rebuildField.name = newName;
});
}
if (newField != null) {
rebuildData.field_2 = newField;
}
if (newTypeOptionData != null) {
rebuildData.typeOptionData = newTypeOptionData;
}
});
_fieldNotifier.value = _data.field_2;
FieldService.insertField(
gridId: gridId,
field: field,
typeOptionData: typeOptionData,
);
}
Future<void> switchToField(FieldType newFieldType) {
return _loader.switchToField(field.id, newFieldType).then((result) {
return result.fold(
(fieldTypeOptionData) {
_updateData(
newField: fieldTypeOptionData.field_2,
newTypeOptionData: fieldTypeOptionData.typeOptionData,
);
},
(err) {
Log.error(err);
},
);
});
}
void Function() addFieldListener(void Function(GridFieldPB) callback) {
listener() {
callback(field);
}
_fieldNotifier.addListener(listener);
return listener;
}
void removeFieldListener(void Function() listener) {
_fieldNotifier.removeListener(listener);
}
}

View File

@ -3,11 +3,11 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import 'field_service.dart';
import 'type_option/type_option_data_controller.dart';
part 'field_type_option_edit_bloc.freezed.dart';
class FieldTypeOptionEditBloc extends Bloc<FieldTypeOptionEditEvent, FieldTypeOptionEditState> {
class FieldTypeOptionEditBloc
extends Bloc<FieldTypeOptionEditEvent, FieldTypeOptionEditState> {
final TypeOptionDataController _dataController;
void Function()? _fieldListenFn;
@ -42,16 +42,19 @@ class FieldTypeOptionEditBloc extends Bloc<FieldTypeOptionEditEvent, FieldTypeOp
@freezed
class FieldTypeOptionEditEvent with _$FieldTypeOptionEditEvent {
const factory FieldTypeOptionEditEvent.initial() = _Initial;
const factory FieldTypeOptionEditEvent.didReceiveFieldUpdated(GridFieldPB field) = _DidReceiveFieldUpdated;
const factory FieldTypeOptionEditEvent.didReceiveFieldUpdated(FieldPB field) =
_DidReceiveFieldUpdated;
}
@freezed
class FieldTypeOptionEditState with _$FieldTypeOptionEditState {
const factory FieldTypeOptionEditState({
required GridFieldPB field,
required FieldPB field,
}) = _FieldTypeOptionEditState;
factory FieldTypeOptionEditState.initial(TypeOptionDataController fieldContext) => FieldTypeOptionEditState(
factory FieldTypeOptionEditState.initial(
TypeOptionDataController fieldContext) =>
FieldTypeOptionEditState(
field: fieldContext.field,
);
}

View File

@ -7,15 +7,17 @@ import 'dart:async';
import 'dart:typed_data';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
typedef UpdateFieldNotifiedValue = Either<GridFieldChangesetPB, FlowyError>;
typedef UpdateFieldNotifiedValue = Either<FieldChangesetPB, FlowyError>;
class GridFieldsListener {
final String gridId;
PublishNotifier<UpdateFieldNotifiedValue>? updateFieldsNotifier = PublishNotifier();
PublishNotifier<UpdateFieldNotifiedValue>? updateFieldsNotifier =
PublishNotifier();
GridNotificationListener? _listener;
GridFieldsListener({required this.gridId});
void start({required void Function(UpdateFieldNotifiedValue) onFieldsChanged}) {
void start(
{required void Function(UpdateFieldNotifiedValue) onFieldsChanged}) {
updateFieldsNotifier?.addPublishListener(onFieldsChanged);
_listener = GridNotificationListener(
objectId: gridId,
@ -27,7 +29,8 @@ class GridFieldsListener {
switch (ty) {
case GridNotification.DidUpdateGridField:
result.fold(
(payload) => updateFieldsNotifier?.value = left(GridFieldChangesetPB.fromBuffer(payload)),
(payload) => updateFieldsNotifier?.value =
left(FieldChangesetPB.fromBuffer(payload)),
(error) => updateFieldsNotifier?.value = right(error),
);
break;

View File

@ -1,21 +1,13 @@
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_service.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import 'package:protobuf/protobuf.dart';
import 'type_option_context.dart';
part 'date_bloc.freezed.dart';
typedef DateTypeOptionContext = TypeOptionWidgetContext<DateTypeOption>;
class DateTypeOptionDataParser extends TypeOptionDataParser<DateTypeOption> {
@override
DateTypeOption fromBuffer(List<int> buffer) {
return DateTypeOption.fromBuffer(buffer);
}
}
class DateTypeOptionBloc
extends Bloc<DateTypeOptionEvent, DateTypeOptionState> {
DateTypeOptionBloc({required DateTypeOptionContext typeOptionContext})
@ -40,7 +32,7 @@ class DateTypeOptionBloc
);
}
DateTypeOption _updateTypeOption({
DateTypeOptionPB _updateTypeOption({
DateFormat? dateFormat,
TimeFormat? timeFormat,
bool? includeTime,
@ -80,9 +72,9 @@ class DateTypeOptionEvent with _$DateTypeOptionEvent {
@freezed
class DateTypeOptionState with _$DateTypeOptionState {
const factory DateTypeOptionState({
required DateTypeOption typeOption,
required DateTypeOptionPB typeOption,
}) = _DateTypeOptionState;
factory DateTypeOptionState.initial(DateTypeOption typeOption) =>
factory DateTypeOptionState.initial(DateTypeOptionPB typeOption) =>
DateTypeOptionState(typeOption: typeOption);
}

View File

@ -1,25 +1,32 @@
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/multi_select_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
import 'dart:async';
import 'package:protobuf/protobuf.dart';
import 'select_option_type_option_bloc.dart';
import 'type_option_context.dart';
import 'type_option_service.dart';
import 'package:protobuf/protobuf.dart';
class MultiSelectTypeOptionContext
extends TypeOptionWidgetContext<MultiSelectTypeOption>
with SelectOptionTypeOptionAction {
final TypeOptionService service;
class MultiSelectAction with ISelectOptionAction {
final String gridId;
final String fieldId;
final TypeOptionFFIService service;
final MultiSelectTypeOptionContext typeOptionContext;
MultiSelectTypeOptionContext({
required MultiSelectTypeOptionWidgetDataParser dataBuilder,
required TypeOptionDataController dataController,
}) : service = TypeOptionService(
gridId: dataController.gridId,
fieldId: dataController.field.id,
),
super(dataParser: dataBuilder, dataController: dataController);
MultiSelectAction({
required this.gridId,
required this.fieldId,
required this.typeOptionContext,
}) : service = TypeOptionFFIService(
gridId: gridId,
fieldId: fieldId,
);
MultiSelectTypeOptionPB get typeOption => typeOptionContext.typeOption;
set typeOption(MultiSelectTypeOptionPB newTypeOption) {
typeOptionContext.typeOption = newTypeOption;
}
@override
List<SelectOptionPB> Function(SelectOptionPB) get deleteOption {
@ -59,7 +66,7 @@ class MultiSelectTypeOptionContext
}
@override
List<SelectOptionPB> Function(SelectOptionPB) get udpateOption {
List<SelectOptionPB> Function(SelectOptionPB) get updateOption {
return (SelectOptionPB option) {
typeOption.freeze();
typeOption = typeOption.rebuild((typeOption) {
@ -73,11 +80,3 @@ class MultiSelectTypeOptionContext
};
}
}
class MultiSelectTypeOptionWidgetDataParser
extends TypeOptionDataParser<MultiSelectTypeOption> {
@override
MultiSelectTypeOption fromBuffer(List<int> buffer) {
return MultiSelectTypeOption.fromBuffer(buffer);
}
}

View File

@ -1,23 +1,13 @@
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_service.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/format.pbenum.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import 'package:protobuf/protobuf.dart';
import 'type_option_context.dart';
part 'number_bloc.freezed.dart';
typedef NumberTypeOptionContext = TypeOptionWidgetContext<NumberTypeOption>;
class NumberTypeOptionWidgetDataParser
extends TypeOptionDataParser<NumberTypeOption> {
@override
NumberTypeOption fromBuffer(List<int> buffer) {
return NumberTypeOption.fromBuffer(buffer);
}
}
class NumberTypeOptionBloc
extends Bloc<NumberTypeOptionEvent, NumberTypeOptionState> {
NumberTypeOptionBloc({required NumberTypeOptionContext typeOptionContext})
@ -33,7 +23,7 @@ class NumberTypeOptionBloc
);
}
NumberTypeOption _updateNumberFormat(NumberFormat format) {
NumberTypeOptionPB _updateNumberFormat(NumberFormat format) {
state.typeOption.freeze();
return state.typeOption.rebuild((typeOption) {
typeOption.format = format;
@ -55,10 +45,10 @@ class NumberTypeOptionEvent with _$NumberTypeOptionEvent {
@freezed
class NumberTypeOptionState with _$NumberTypeOptionState {
const factory NumberTypeOptionState({
required NumberTypeOption typeOption,
required NumberTypeOptionPB typeOption,
}) = _NumberTypeOptionState;
factory NumberTypeOptionState.initial(NumberTypeOption typeOption) =>
factory NumberTypeOptionState.initial(NumberTypeOptionPB typeOption) =>
NumberTypeOptionState(
typeOption: typeOption,
);

View File

@ -5,16 +5,17 @@ import 'dart:async';
import 'package:dartz/dartz.dart';
part 'select_option_type_option_bloc.freezed.dart';
abstract class SelectOptionTypeOptionAction {
abstract class ISelectOptionAction {
Future<List<SelectOptionPB>> Function(String) get insertOption;
List<SelectOptionPB> Function(SelectOptionPB) get deleteOption;
List<SelectOptionPB> Function(SelectOptionPB) get udpateOption;
List<SelectOptionPB> Function(SelectOptionPB) get updateOption;
}
class SelectOptionTypeOptionBloc extends Bloc<SelectOptionTypeOptionEvent, SelectOptionTypeOptionState> {
final SelectOptionTypeOptionAction typeOptionAction;
class SelectOptionTypeOptionBloc
extends Bloc<SelectOptionTypeOptionEvent, SelectOptionTypeOptionState> {
final ISelectOptionAction typeOptionAction;
SelectOptionTypeOptionBloc({
required List<SelectOptionPB> options,
@ -24,7 +25,8 @@ class SelectOptionTypeOptionBloc extends Bloc<SelectOptionTypeOptionEvent, Selec
(event, emit) async {
await event.when(
createOption: (optionName) async {
final List<SelectOptionPB> options = await typeOptionAction.insertOption(optionName);
final List<SelectOptionPB> options =
await typeOptionAction.insertOption(optionName);
emit(state.copyWith(options: options));
},
addingOption: () {
@ -34,11 +36,13 @@ class SelectOptionTypeOptionBloc extends Bloc<SelectOptionTypeOptionEvent, Selec
emit(state.copyWith(isEditingOption: false, newOptionName: none()));
},
updateOption: (option) {
final List<SelectOptionPB> options = typeOptionAction.udpateOption(option);
final List<SelectOptionPB> options =
typeOptionAction.updateOption(option);
emit(state.copyWith(options: options));
},
deleteOption: (option) {
final List<SelectOptionPB> options = typeOptionAction.deleteOption(option);
final List<SelectOptionPB> options =
typeOptionAction.deleteOption(option);
emit(state.copyWith(options: options));
},
);
@ -54,11 +58,15 @@ class SelectOptionTypeOptionBloc extends Bloc<SelectOptionTypeOptionEvent, Selec
@freezed
class SelectOptionTypeOptionEvent with _$SelectOptionTypeOptionEvent {
const factory SelectOptionTypeOptionEvent.createOption(String optionName) = _CreateOption;
const factory SelectOptionTypeOptionEvent.createOption(String optionName) =
_CreateOption;
const factory SelectOptionTypeOptionEvent.addingOption() = _AddingOption;
const factory SelectOptionTypeOptionEvent.endAddingOption() = _EndAddingOption;
const factory SelectOptionTypeOptionEvent.updateOption(SelectOptionPB option) = _UpdateOption;
const factory SelectOptionTypeOptionEvent.deleteOption(SelectOptionPB option) = _DeleteOption;
const factory SelectOptionTypeOptionEvent.endAddingOption() =
_EndAddingOption;
const factory SelectOptionTypeOptionEvent.updateOption(
SelectOptionPB option) = _UpdateOption;
const factory SelectOptionTypeOptionEvent.deleteOption(
SelectOptionPB option) = _DeleteOption;
}
@freezed
@ -67,9 +75,10 @@ class SelectOptionTypeOptionState with _$SelectOptionTypeOptionState {
required List<SelectOptionPB> options,
required bool isEditingOption,
required Option<String> newOptionName,
}) = _SelectOptionTyepOptionState;
}) = _SelectOptionTypeOptionState;
factory SelectOptionTypeOptionState.initial(List<SelectOptionPB> options) => SelectOptionTypeOptionState(
factory SelectOptionTypeOptionState.initial(List<SelectOptionPB> options) =>
SelectOptionTypeOptionState(
options: options,
isEditingOption: false,
newOptionName: none(),

View File

@ -1,25 +1,29 @@
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/single_select_type_option.pb.dart';
import 'dart:async';
import 'package:protobuf/protobuf.dart';
import 'select_option_type_option_bloc.dart';
import 'type_option_context.dart';
import 'type_option_service.dart';
class SingleSelectTypeOptionContext
extends TypeOptionWidgetContext<SingleSelectTypeOptionPB>
with SelectOptionTypeOptionAction {
final TypeOptionService service;
class SingleSelectAction with ISelectOptionAction {
final String gridId;
final String fieldId;
final SingleSelectTypeOptionContext typeOptionContext;
final TypeOptionFFIService service;
SingleSelectTypeOptionContext({
required SingleSelectTypeOptionWidgetDataParser dataBuilder,
required TypeOptionDataController fieldContext,
}) : service = TypeOptionService(
gridId: fieldContext.gridId,
fieldId: fieldContext.field.id,
),
super(dataParser: dataBuilder, dataController: fieldContext);
SingleSelectAction({
required this.gridId,
required this.fieldId,
required this.typeOptionContext,
}) : service = TypeOptionFFIService(gridId: gridId, fieldId: fieldId);
SingleSelectTypeOptionPB get typeOption => typeOptionContext.typeOption;
set typeOption(SingleSelectTypeOptionPB newTypeOption) {
typeOptionContext.typeOption = newTypeOption;
}
@override
List<SelectOptionPB> Function(SelectOptionPB) get deleteOption {
@ -59,7 +63,7 @@ class SingleSelectTypeOptionContext
}
@override
List<SelectOptionPB> Function(SelectOptionPB) get udpateOption {
List<SelectOptionPB> Function(SelectOptionPB) get updateOption {
return (SelectOptionPB option) {
typeOption.freeze();
typeOption = typeOption.rebuild((typeOption) {
@ -73,11 +77,3 @@ class SingleSelectTypeOptionContext
};
}
}
class SingleSelectTypeOptionWidgetDataParser
extends TypeOptionDataParser<SingleSelectTypeOptionPB> {
@override
SingleSelectTypeOptionPB fromBuffer(List<int> buffer) {
return SingleSelectTypeOptionPB.fromBuffer(buffer);
}
}

View File

@ -0,0 +1,195 @@
import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/multi_select_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/single_select_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/text_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart';
import 'package:protobuf/protobuf.dart';
import 'type_option_data_controller.dart';
abstract class TypeOptionDataParser<T> {
T fromBuffer(List<int> buffer);
}
// Number
typedef NumberTypeOptionContext = TypeOptionContext<NumberTypeOptionPB>;
class NumberTypeOptionWidgetDataParser
extends TypeOptionDataParser<NumberTypeOptionPB> {
@override
NumberTypeOptionPB fromBuffer(List<int> buffer) {
return NumberTypeOptionPB.fromBuffer(buffer);
}
}
// RichText
typedef RichTextTypeOptionContext = TypeOptionContext<RichTextTypeOptionPB>;
class RichTextTypeOptionWidgetDataParser
extends TypeOptionDataParser<RichTextTypeOptionPB> {
@override
RichTextTypeOptionPB fromBuffer(List<int> buffer) {
return RichTextTypeOptionPB.fromBuffer(buffer);
}
}
// Checkbox
typedef CheckboxTypeOptionContext = TypeOptionContext<CheckboxTypeOptionPB>;
class CheckboxTypeOptionWidgetDataParser
extends TypeOptionDataParser<CheckboxTypeOptionPB> {
@override
CheckboxTypeOptionPB fromBuffer(List<int> buffer) {
return CheckboxTypeOptionPB.fromBuffer(buffer);
}
}
// URL
typedef URLTypeOptionContext = TypeOptionContext<URLTypeOptionPB>;
class URLTypeOptionWidgetDataParser
extends TypeOptionDataParser<URLTypeOptionPB> {
@override
URLTypeOptionPB fromBuffer(List<int> buffer) {
return URLTypeOptionPB.fromBuffer(buffer);
}
}
// Date
typedef DateTypeOptionContext = TypeOptionContext<DateTypeOptionPB>;
class DateTypeOptionDataParser extends TypeOptionDataParser<DateTypeOptionPB> {
@override
DateTypeOptionPB fromBuffer(List<int> buffer) {
return DateTypeOptionPB.fromBuffer(buffer);
}
}
// SingleSelect
typedef SingleSelectTypeOptionContext
= TypeOptionContext<SingleSelectTypeOptionPB>;
class SingleSelectTypeOptionWidgetDataParser
extends TypeOptionDataParser<SingleSelectTypeOptionPB> {
@override
SingleSelectTypeOptionPB fromBuffer(List<int> buffer) {
return SingleSelectTypeOptionPB.fromBuffer(buffer);
}
}
// Multi-select
typedef MultiSelectTypeOptionContext
= TypeOptionContext<MultiSelectTypeOptionPB>;
class MultiSelectTypeOptionWidgetDataParser
extends TypeOptionDataParser<MultiSelectTypeOptionPB> {
@override
MultiSelectTypeOptionPB fromBuffer(List<int> buffer) {
return MultiSelectTypeOptionPB.fromBuffer(buffer);
}
}
class TypeOptionContext<T extends GeneratedMessage> {
T? _typeOptionObject;
final TypeOptionDataParser<T> dataParser;
final TypeOptionDataController _dataController;
TypeOptionContext({
required this.dataParser,
required TypeOptionDataController dataController,
}) : _dataController = dataController;
String get gridId => _dataController.gridId;
String get fieldId => _dataController.field.id;
Future<void> loadTypeOptionData({
required void Function(T) onCompleted,
required void Function(FlowyError) onError,
}) async {
await _dataController.loadTypeOptionData().then((result) {
result.fold((l) => null, (err) => onError(err));
});
onCompleted(typeOption);
}
T get typeOption {
if (_typeOptionObject != null) {
return _typeOptionObject!;
}
final T object = _dataController.getTypeOption(dataParser);
_typeOptionObject = object;
return object;
}
set typeOption(T typeOption) {
_dataController.typeOptionData = typeOption.writeToBuffer();
_typeOptionObject = typeOption;
}
}
abstract class TypeOptionFieldDelegate {
void onFieldChanged(void Function(String) callback);
void dispose();
}
abstract class IFieldTypeOptionLoader {
String get gridId;
Future<Either<FieldTypeOptionDataPB, FlowyError>> load();
Future<Either<FieldTypeOptionDataPB, FlowyError>> switchToField(
String fieldId, FieldType fieldType) {
final payload = EditFieldPayloadPB.create()
..gridId = gridId
..fieldId = fieldId
..fieldType = fieldType;
return GridEventSwitchToField(payload).send();
}
}
class NewFieldTypeOptionLoader extends IFieldTypeOptionLoader {
@override
final String gridId;
NewFieldTypeOptionLoader({
required this.gridId,
});
@override
Future<Either<FieldTypeOptionDataPB, FlowyError>> load() {
final payload = CreateFieldPayloadPB.create()
..gridId = gridId
..fieldType = FieldType.RichText;
return GridEventCreateFieldTypeOption(payload).send();
}
}
class FieldTypeOptionLoader extends IFieldTypeOptionLoader {
@override
final String gridId;
final FieldPB field;
FieldTypeOptionLoader({
required this.gridId,
required this.field,
});
@override
Future<Either<FieldTypeOptionDataPB, FlowyError>> load() {
final payload = FieldTypeOptionIdPB.create()
..gridId = gridId
..fieldId = field.id
..fieldType = field.fieldType;
return GridEventGetFieldTypeOption(payload).send();
}
}

View File

@ -0,0 +1,123 @@
import 'package:flowy_infra/notifier.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
import 'package:dartz/dartz.dart';
import 'package:protobuf/protobuf.dart';
import 'package:flowy_sdk/log.dart';
import 'type_option_context.dart';
class TypeOptionDataController {
final String gridId;
final IFieldTypeOptionLoader loader;
late FieldTypeOptionDataPB _data;
final PublishNotifier<FieldPB> _fieldNotifier = PublishNotifier();
TypeOptionDataController({
required this.gridId,
required this.loader,
FieldPB? field,
}) {
if (field != null) {
_data = FieldTypeOptionDataPB.create()
..gridId = gridId
..field_2 = field;
}
}
Future<Either<Unit, FlowyError>> loadTypeOptionData() async {
final result = await loader.load();
return result.fold(
(data) {
data.freeze();
_data = data;
_fieldNotifier.value = data.field_2;
return left(unit);
},
(err) {
Log.error(err);
return right(err);
},
);
}
FieldPB get field {
return _data.field_2;
}
set field(FieldPB field) {
_updateData(newField: field);
}
T getTypeOption<T>(TypeOptionDataParser<T> parser) {
return parser.fromBuffer(_data.typeOptionData);
}
set fieldName(String name) {
_updateData(newName: name);
}
set typeOptionData(List<int> typeOptionData) {
_updateData(newTypeOptionData: typeOptionData);
}
void _updateData({
String? newName,
FieldPB? newField,
List<int>? newTypeOptionData,
}) {
_data = _data.rebuild((rebuildData) {
if (newName != null) {
rebuildData.field_2 = rebuildData.field_2.rebuild((rebuildField) {
rebuildField.name = newName;
});
}
if (newField != null) {
rebuildData.field_2 = newField;
}
if (newTypeOptionData != null) {
rebuildData.typeOptionData = newTypeOptionData;
}
});
_fieldNotifier.value = _data.field_2;
FieldService.insertField(
gridId: gridId,
field: field,
typeOptionData: _data.typeOptionData,
);
}
Future<void> switchToField(FieldType newFieldType) {
return loader.switchToField(field.id, newFieldType).then((result) {
return result.fold(
(fieldTypeOptionData) {
_updateData(
newField: fieldTypeOptionData.field_2,
newTypeOptionData: fieldTypeOptionData.typeOptionData,
);
},
(err) {
Log.error(err);
},
);
});
}
void Function() addFieldListener(void Function(FieldPB) callback) {
listener() {
callback(field);
}
_fieldNotifier.addListener(listener);
return listener;
}
void removeFieldListener(void Function() listener) {
_fieldNotifier.removeListener(listener);
}
}

View File

@ -1,19 +1,14 @@
import 'dart:typed_data';
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/cell_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
import 'package:protobuf/protobuf.dart';
class TypeOptionService {
class TypeOptionFFIService {
final String gridId;
final String fieldId;
TypeOptionService({
TypeOptionFFIService({
required this.gridId,
required this.fieldId,
});
@ -29,79 +24,3 @@ class TypeOptionService {
return GridEventNewSelectOption(payload).send();
}
}
abstract class TypeOptionDataParser<T> {
T fromBuffer(List<int> buffer);
}
class TypeOptionWidgetContext<T extends GeneratedMessage> {
T? _typeOptionObject;
final TypeOptionDataController _dataController;
final TypeOptionDataParser<T> dataParser;
TypeOptionWidgetContext({
required this.dataParser,
required TypeOptionDataController dataController,
}) : _dataController = dataController;
String get gridId => _dataController.gridId;
GridFieldPB get field => _dataController.field;
T get typeOption {
if (_typeOptionObject != null) {
return _typeOptionObject!;
}
final T object = dataParser.fromBuffer(_dataController.typeOptionData);
_typeOptionObject = object;
return object;
}
set typeOption(T typeOption) {
_dataController.typeOptionData = typeOption.writeToBuffer();
_typeOptionObject = typeOption;
}
}
abstract class TypeOptionFieldDelegate {
void onFieldChanged(void Function(String) callback);
void dispose();
}
class TypeOptionContext2<T> {
final String gridId;
final GridFieldPB field;
final FieldService _fieldService;
T? _data;
final TypeOptionDataParser dataBuilder;
TypeOptionContext2({
required this.gridId,
required this.field,
required this.dataBuilder,
Uint8List? data,
}) : _fieldService = FieldService(gridId: gridId, fieldId: field.id) {
if (data != null) {
_data = dataBuilder.fromBuffer(data);
}
}
Future<Either<T, FlowyError>> typeOptionData() {
if (_data != null) {
return Future(() => left(_data!));
}
return _fieldService
.getFieldTypeOptionData(fieldType: field.fieldType)
.then((result) {
return result.fold(
(data) {
_data = dataBuilder.fromBuffer(data.typeOptionData);
return left(_data!);
},
(err) => right(err),
);
});
}
}

View File

@ -8,7 +8,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'block/block_cache.dart';
import 'grid_data_controller.dart';
import 'row/row_service.dart';
import 'row/row_cache.dart';
import 'dart:collection';
part 'grid_bloc.freezed.dart';
@ -97,11 +97,11 @@ class GridEvent with _$GridEvent {
const factory GridEvent.initial() = InitialGrid;
const factory GridEvent.createRow() = _CreateRow;
const factory GridEvent.didReceiveRowUpdate(
List<GridRowInfo> rows,
GridRowChangeReason listState,
List<RowInfo> rows,
RowChangeReason listState,
) = _DidReceiveRowUpdate;
const factory GridEvent.didReceiveFieldUpdate(
UnmodifiableListView<GridFieldPB> fields,
UnmodifiableListView<FieldPB> fields,
) = _DidReceiveFieldUpdate;
const factory GridEvent.didReceiveGridUpdate(
@ -115,9 +115,9 @@ class GridState with _$GridState {
required String gridId,
required Option<GridPB> grid,
required GridFieldEquatable fields,
required List<GridRowInfo> rowInfos,
required List<RowInfo> rowInfos,
required GridLoadingState loadingState,
required GridRowChangeReason reason,
required RowChangeReason reason,
}) = _GridState;
factory GridState.initial(String gridId) => GridState(
@ -138,9 +138,9 @@ class GridLoadingState with _$GridLoadingState {
}
class GridFieldEquatable extends Equatable {
final UnmodifiableListView<GridFieldPB> _fields;
final UnmodifiableListView<FieldPB> _fields;
const GridFieldEquatable(
UnmodifiableListView<GridFieldPB> fields,
UnmodifiableListView<FieldPB> fields,
) : _fields = fields;
@override
@ -157,5 +157,5 @@ class GridFieldEquatable extends Equatable {
];
}
UnmodifiableListView<GridFieldPB> get value => UnmodifiableListView(_fields);
UnmodifiableListView<FieldPB> get value => UnmodifiableListView(_fields);
}

View File

@ -9,16 +9,18 @@ import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
import 'dart:async';
import 'package:dartz/dartz.dart';
import 'block/block_cache.dart';
import 'field/field_cache.dart';
import 'prelude.dart';
import 'row/row_cache.dart';
typedef OnFieldsChanged = void Function(UnmodifiableListView<GridFieldPB>);
typedef OnFieldsChanged = void Function(UnmodifiableListView<FieldPB>);
typedef OnGridChanged = void Function(GridPB);
typedef OnRowsChanged = void Function(
List<GridRowInfo> rowInfos,
GridRowChangeReason,
List<RowInfo> rowInfos,
RowChangeReason,
);
typedef ListenONRowChangedCondition = bool Function();
typedef ListenOnRowChangedCondition = bool Function();
class GridDataController {
final String gridId;
@ -34,8 +36,8 @@ class GridDataController {
OnFieldsChanged? _onFieldsChanged;
OnGridChanged? _onGridChanged;
List<GridRowInfo> get rowInfos {
final List<GridRowInfo> rows = [];
List<RowInfo> get rowInfos {
final List<RowInfo> rows = [];
for (var block in _blocks.values) {
rows.addAll(block.rows);
}
@ -89,7 +91,7 @@ class GridDataController {
}
}
void _initialBlocks(List<GridBlockPB> blocks) {
void _initialBlocks(List<BlockPB> blocks) {
for (final block in blocks) {
if (_blocks[block.id] != null) {
Log.warn("Initial duplicate block's cache: ${block.id}");

View File

@ -4,7 +4,8 @@ import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import 'grid_service.dart';
import 'field/field_cache.dart';
part 'grid_header_bloc.freezed.dart';
@ -35,7 +36,7 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
Future<void> _moveField(
_MoveField value, Emitter<GridHeaderState> emit) async {
final fields = List<GridFieldPB>.from(state.fields);
final fields = List<FieldPB>.from(state.fields);
fields.insert(value.toIndex, fields.removeAt(value.fromIndex));
emit(state.copyWith(fields: fields));
@ -63,19 +64,19 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
@freezed
class GridHeaderEvent with _$GridHeaderEvent {
const factory GridHeaderEvent.initial() = _InitialHeader;
const factory GridHeaderEvent.didReceiveFieldUpdate(
List<GridFieldPB> fields) = _DidReceiveFieldUpdate;
const factory GridHeaderEvent.didReceiveFieldUpdate(List<FieldPB> fields) =
_DidReceiveFieldUpdate;
const factory GridHeaderEvent.moveField(
GridFieldPB field, int fromIndex, int toIndex) = _MoveField;
FieldPB field, int fromIndex, int toIndex) = _MoveField;
}
@freezed
class GridHeaderState with _$GridHeaderState {
const factory GridHeaderState({required List<GridFieldPB> fields}) =
const factory GridHeaderState({required List<FieldPB> fields}) =
_GridHeaderState;
factory GridHeaderState.initial(List<GridFieldPB> fields) {
// final List<GridFieldPB> newFields = List.from(fields);
factory GridHeaderState.initial(List<FieldPB> fields) {
// final List<FieldPB> newFields = List.from(fields);
// newFields.retainWhere((field) => field.visibility);
return GridHeaderState(fields: fields);
}

View File

@ -1,17 +1,12 @@
import 'dart:collection';
import 'package:app_flowy/plugins/grid/application/field/grid_listener.dart';
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.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/grid_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/group.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
import 'package:flutter/foundation.dart';
import 'row/row_service.dart';
class GridService {
final String gridId;
@ -26,18 +21,17 @@ class GridService {
return GridEventGetGrid(payload).send();
}
Future<Either<GridRowPB, FlowyError>> createRow(
{Option<String>? startRowId}) {
Future<Either<RowPB, FlowyError>> createRow({Option<String>? startRowId}) {
CreateRowPayloadPB payload = CreateRowPayloadPB.create()..gridId = gridId;
startRowId?.fold(() => null, (id) => payload.startRowId = id);
return GridEventCreateRow(payload).send();
}
Future<Either<RepeatedGridFieldPB, FlowyError>> getFields(
{required List<GridFieldIdPB> fieldIds}) {
Future<Either<RepeatedFieldPB, FlowyError>> getFields(
{required List<FieldIdPB> fieldIds}) {
final payload = QueryFieldPayloadPB.create()
..gridId = gridId
..fieldIds = RepeatedGridFieldIdPB(items: fieldIds);
..fieldIds = RepeatedFieldIdPB(items: fieldIds);
return GridEventGetFields(payload).send();
}
@ -45,188 +39,9 @@ class GridService {
final request = ViewIdPB(value: gridId);
return FolderEventCloseView(request).send();
}
}
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 GridRowCacheFieldNotifierImpl extends GridRowCacheFieldNotifier {
final GridFieldCache _cache;
FieldChangesetCallback? _onChangesetFn;
FieldsCallback? _onFieldFn;
GridRowCacheFieldNotifierImpl(GridFieldCache cache) : _cache = cache;
@override
UnmodifiableListView<GridFieldPB> get fields => _cache.unmodifiableFields;
@override
void onFieldsChanged(VoidCallback callback) {
_onFieldFn = (_) => callback();
_cache.addListener(onFields: _onFieldFn);
}
@override
void onFieldChanged(void Function(GridFieldPB) callback) {
_onChangesetFn = (GridFieldChangesetPB changeset) {
for (final updatedField in changeset.updatedFields) {
callback(updatedField);
}
};
_cache.addListener(onChangeset: _onChangesetFn);
}
@override
void dispose() {
if (_onFieldFn != null) {
_cache.removeListener(onFieldsListener: _onFieldFn!);
_onFieldFn = null;
}
if (_onChangesetFn != null) {
_cache.removeListener(onChangesetListener: _onChangesetFn!);
_onChangesetFn = null;
}
Future<Either<RepeatedGridGroupPB, FlowyError>> loadGroups() {
final payload = GridIdPB(value: gridId);
return GridEventGetGroup(payload).send();
}
}

View File

@ -4,13 +4,13 @@ export 'row/row_service.dart';
export 'grid_service.dart';
export 'grid_header_bloc.dart';
// GridFieldPB
// FieldPB
export 'field/field_service.dart';
export 'field/field_action_sheet_bloc.dart';
export 'field/field_editor_bloc.dart';
export 'field/field_type_option_edit_bloc.dart';
// GridFieldPB Type Option
// FieldPB Type Option
export 'field/type_option/date_bloc.dart';
export 'field/type_option/number_bloc.dart';
export 'field/type_option/single_select_type_option.dart';

View File

@ -6,13 +6,15 @@ import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import 'package:dartz/dartz.dart';
import 'row_cache.dart';
part 'row_action_sheet_bloc.freezed.dart';
class RowActionSheetBloc
extends Bloc<RowActionSheetEvent, RowActionSheetState> {
final RowService _rowService;
RowActionSheetBloc({required GridRowInfo rowData})
RowActionSheetBloc({required RowInfo rowData})
: _rowService = RowService(
gridId: rowData.gridId,
blockId: rowData.blockId,
@ -54,11 +56,10 @@ class RowActionSheetEvent with _$RowActionSheetEvent {
@freezed
class RowActionSheetState with _$RowActionSheetState {
const factory RowActionSheetState({
required GridRowInfo rowData,
required RowInfo rowData,
}) = _RowActionSheetState;
factory RowActionSheetState.initial(GridRowInfo rowData) =>
RowActionSheetState(
factory RowActionSheetState.initial(RowInfo rowData) => RowActionSheetState(
rowData: rowData,
);
}

View File

@ -5,6 +5,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import 'row_cache.dart';
import 'row_data_controller.dart';
import 'row_service.dart';
@ -15,7 +16,7 @@ class RowBloc extends Bloc<RowEvent, RowState> {
final GridRowDataController _dataController;
RowBloc({
required GridRowInfo rowInfo,
required RowInfo rowInfo,
required GridRowDataController dataController,
}) : _rowService = RowService(
gridId: rowInfo.gridId,
@ -71,19 +72,19 @@ class RowEvent with _$RowEvent {
const factory RowEvent.initial() = _InitialRow;
const factory RowEvent.createRow() = _CreateRow;
const factory RowEvent.didReceiveCells(
GridCellMap gridCellMap, GridRowChangeReason reason) = _DidReceiveCells;
GridCellMap gridCellMap, RowChangeReason reason) = _DidReceiveCells;
}
@freezed
class RowState with _$RowState {
const factory RowState({
required GridRowInfo rowInfo,
required RowInfo rowInfo,
required GridCellMap gridCellMap,
required UnmodifiableListView<GridCellEquatable> snapshots,
GridRowChangeReason? changeReason,
RowChangeReason? changeReason,
}) = _RowState;
factory RowState.initial(GridRowInfo rowInfo, GridCellMap cellDataMap) =>
factory RowState.initial(RowInfo rowInfo, GridCellMap cellDataMap) =>
RowState(
rowInfo: rowInfo,
gridCellMap: cellDataMap,
@ -93,9 +94,9 @@ class RowState with _$RowState {
}
class GridCellEquatable extends Equatable {
final GridFieldPB _field;
final FieldPB _field;
const GridCellEquatable(GridFieldPB field) : _field = field;
const GridCellEquatable(FieldPB field) : _field = field;
@override
List<Object?> get props => [

View File

@ -0,0 +1,328 @@
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<FieldPB> get fields;
void onRowFieldsChanged(VoidCallback callback);
void onRowFieldChanged(void Function(FieldPB) 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 BlockPB block;
/// _rows containers the current block's rows
/// Use List to reverse the order of the GridRow.
List<RowInfo> _rowInfos = [];
/// Use Map for faster access the raw row data.
final HashMap<String, RowPB> _rowByRowId;
final GridCellCache _cellCache;
final IGridRowFieldNotifier _fieldNotifier;
final _RowChangesetNotifier _rowChangeReasonNotifier;
UnmodifiableListView<RowInfo> 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 = _RowChangesetNotifier(),
_fieldNotifier = notifier {
//
notifier.onRowFieldsChanged(() => _rowChangeReasonNotifier
.receive(const RowChangeReason.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<RowInfo> 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(RowChangeReason.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(RowChangeReason.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(RowChangeReason.update(updatedIndexs));
}
void _hideRows(List<String> hideRows) {}
void _showRows(List<String> visibleRows) {}
void onRowsChanged(
void Function(RowChangeReason) onRowChanged,
) {
_rowChangeReasonNotifier.addListener(() {
onRowChanged(_rowChangeReasonNotifier.reason);
});
}
RowUpdateCallback addListener({
required String rowId,
void Function(GridCellMap, RowChangeReason)? 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 RowPB? data = _rowByRowId[rowId];
if (data == null) {
_loadRow(rowId);
}
return _makeGridCells(rowId, data);
}
Future<void> _loadRow(String rowId) async {
final payload = RowIdPB.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, RowPB? 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(RowChangeReason.update(updatedIndexs));
}
}
}
RowInfo buildGridRow(String rowId, double rowHeight) {
return RowInfo(
gridId: gridId,
blockId: block.id,
fields: _fieldNotifier.fields,
id: rowId,
height: rowHeight,
);
}
}
class _RowChangesetNotifier extends ChangeNotifier {
RowChangeReason reason = const InitialListState();
_RowChangesetNotifier();
void receive(RowChangeReason newReason) {
reason = newReason;
reason.map(
insert: (_) => notifyListeners(),
delete: (_) => notifyListeners(),
update: (_) => notifyListeners(),
fieldDidChange: (_) => notifyListeners(),
initial: (_) {},
);
}
}
@freezed
class RowInfo with _$RowInfo {
const factory RowInfo({
required String gridId,
required String blockId,
required String id,
required UnmodifiableListView<FieldPB> fields,
required double height,
RowPB? rawRow,
}) = _RowInfo;
}
typedef InsertedIndexs = List<InsertedIndex>;
typedef DeletedIndexs = List<DeletedIndex>;
typedef UpdatedIndexs = LinkedHashMap<String, UpdatedIndex>;
@freezed
class RowChangeReason with _$RowChangeReason {
const factory RowChangeReason.insert(InsertedIndexs items) = _Insert;
const factory RowChangeReason.delete(DeletedIndexs items) = _Delete;
const factory RowChangeReason.update(UpdatedIndexs indexs) = _Update;
const factory RowChangeReason.fieldDidChange() = _FieldDidChange;
const factory RowChangeReason.initial() = InitialListState;
}
class InsertedIndex {
final int index;
final String rowId;
InsertedIndex({
required this.index,
required this.rowId,
});
}
class DeletedIndex {
final int index;
final RowInfo row;
DeletedIndex({
required this.index,
required this.row,
});
}
class UpdatedIndex {
final int index;
final String rowId;
UpdatedIndex({
required this.index,
required this.rowId,
});
}

View File

@ -1,13 +1,15 @@
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_field_notifier.dart';
import 'package:flutter/material.dart';
import '../../presentation/widgets/cell/cell_builder.dart';
import '../cell/cell_service/cell_service.dart';
import '../grid_service.dart';
import 'row_service.dart';
import '../field/field_cache.dart';
import 'row_cache.dart';
typedef OnRowChanged = void Function(GridCellMap, GridRowChangeReason);
typedef OnRowChanged = void Function(GridCellMap, RowChangeReason);
class GridRowDataController {
final String rowId;
VoidCallback? _onRowChangedListener;
class GridRowDataController extends GridCellBuilderDelegate {
final RowInfo rowInfo;
final List<VoidCallback> _onRowChangedListeners = [];
final GridFieldCache _fieldCache;
final GridRowCache _rowCache;
@ -16,26 +18,36 @@ class GridRowDataController {
GridRowCache get rowCache => _rowCache;
GridRowDataController({
required this.rowId,
required this.rowInfo,
required GridFieldCache fieldCache,
required GridRowCache rowCache,
}) : _fieldCache = fieldCache,
_rowCache = rowCache;
GridCellMap loadData() {
return _rowCache.loadGridCells(rowId);
return _rowCache.loadGridCells(rowInfo.id);
}
void addListener({OnRowChanged? onRowChanged}) {
_onRowChangedListener = _rowCache.addListener(
rowId: rowId,
_onRowChangedListeners.add(_rowCache.addListener(
rowId: rowInfo.id,
onCellUpdated: onRowChanged,
);
));
}
void dispose() {
if (_onRowChangedListener != null) {
_rowCache.removeRowListener(_onRowChangedListener!);
for (final fn in _onRowChangedListeners) {
_rowCache.removeRowListener(fn);
}
}
// GridCellBuilderDelegate implementation
@override
GridCellFieldNotifier buildFieldNotifier() {
return GridCellFieldNotifier(
notifier: GridCellFieldNotifierImpl(_fieldCache));
}
@override
GridCellCache get cellCache => rowCache.cellCache;
}

View File

@ -2,26 +2,24 @@ import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_servic
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import 'row_service.dart';
import 'row_data_controller.dart';
part 'row_detail_bloc.freezed.dart';
class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
final GridRowInfo rowInfo;
final GridRowCache _rowCache;
void Function()? _rowListenFn;
final GridRowDataController dataController;
RowDetailBloc({
required this.rowInfo,
required GridRowCache rowCache,
}) : _rowCache = rowCache,
super(RowDetailState.initial()) {
required this.dataController,
}) : super(RowDetailState.initial()) {
on<RowDetailEvent>(
(event, emit) async {
await event.map(
initial: (_Initial value) async {
await _startListening();
_loadCellData();
final cells = dataController.loadData();
if (!isClosed) {
add(RowDetailEvent.didReceiveCellDatas(cells.values.toList()));
}
},
didReceiveCellDatas: (_DidReceiveCellDatas value) {
emit(state.copyWith(gridCells: value.gridCells));
@ -33,27 +31,19 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
@override
Future<void> close() async {
if (_rowListenFn != null) {
_rowCache.removeRowListener(_rowListenFn!);
}
dataController.dispose();
return super.close();
}
Future<void> _startListening() async {
_rowListenFn = _rowCache.addListener(
rowId: rowInfo.id,
onCellUpdated: (cellDatas, reason) =>
add(RowDetailEvent.didReceiveCellDatas(cellDatas.values.toList())),
listenWhen: () => !isClosed,
dataController.addListener(
onRowChanged: (cells, reason) {
if (!isClosed) {
add(RowDetailEvent.didReceiveCellDatas(cells.values.toList()));
}
},
);
}
Future<void> _loadCellData() async {
final cellDataMap = _rowCache.loadGridCells(rowInfo.id);
if (!isClosed) {
add(RowDetailEvent.didReceiveCellDatas(cellDataMap.values.toList()));
}
}
}
@freezed

View File

@ -8,12 +8,13 @@ import 'dart:typed_data';
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
typedef UpdateRowNotifiedValue = Either<GridRowPB, FlowyError>;
typedef UpdateFieldNotifiedValue = Either<List<GridFieldPB>, FlowyError>;
typedef UpdateRowNotifiedValue = Either<RowPB, FlowyError>;
typedef UpdateFieldNotifiedValue = Either<List<FieldPB>, FlowyError>;
class RowListener {
final String rowId;
PublishNotifier<UpdateRowNotifiedValue>? updateRowNotifier = PublishNotifier();
PublishNotifier<UpdateRowNotifiedValue>? updateRowNotifier =
PublishNotifier();
GridNotificationListener? _listener;
RowListener({required this.rowId});
@ -26,7 +27,8 @@ class RowListener {
switch (ty) {
case GridNotification.DidUpdateRow:
result.fold(
(payload) => updateRowNotifier?.value = left(GridRowPB.fromBuffer(payload)),
(payload) =>
updateRowNotifier?.value = left(RowPB.fromBuffer(payload)),
(error) => updateRowNotifier?.value = right(error),
);
break;

View File

@ -1,283 +1,9 @@
import 'dart:collection';
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.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/grid_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_service.freezed.dart';
typedef RowUpdateCallback = void Function();
abstract class GridRowCacheFieldNotifier {
UnmodifiableListView<GridFieldPB> get fields;
void onFieldsChanged(VoidCallback callback);
void onFieldChanged(void Function(GridFieldPB) callback);
void dispose();
}
/// 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 GridRowCacheFieldNotifier _fieldNotifier;
final _GridRowChangesetNotifier _rowChangeReasonNotifier;
UnmodifiableListView<GridRowInfo> get rows => UnmodifiableListView(_rowInfos);
GridCellCache get cellCache => _cellCache;
GridRowCache({
required this.gridId,
required this.block,
required GridRowCacheFieldNotifier notifier,
}) : _cellCache = GridCellCache(gridId: gridId),
_rowByRowId = HashMap(),
_rowChangeReasonNotifier = _GridRowChangesetNotifier(),
_fieldNotifier = notifier {
//
notifier.onFieldsChanged(() => _rowChangeReasonNotifier
.receive(const GridRowChangeReason.fieldDidChange()));
notifier.onFieldChanged((field) => _cellCache.remove(field.id));
_rowInfos = block.rows
.map((rowInfo) => buildGridRow(rowInfo.id, rowInfo.height.toDouble()))
.toList();
}
Future<void> dispose() async {
_fieldNotifier.dispose();
_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: (_) {},
);
}
}
class RowService {
final String gridId;
@ -287,7 +13,7 @@ class RowService {
RowService(
{required this.gridId, required this.blockId, required this.rowId});
Future<Either<GridRowPB, FlowyError>> createRow() {
Future<Either<RowPB, FlowyError>> createRow() {
CreateRowPayloadPB payload = CreateRowPayloadPB.create()
..gridId = gridId
..startRowId = rowId;
@ -308,7 +34,7 @@ class RowService {
}
Future<Either<OptionalRowPB, FlowyError>> getRow() {
final payload = GridRowIdPB.create()
final payload = RowIdPB.create()
..gridId = gridId
..blockId = blockId
..rowId = rowId;
@ -317,7 +43,7 @@ class RowService {
}
Future<Either<Unit, FlowyError>> deleteRow() {
final payload = GridRowIdPB.create()
final payload = RowIdPB.create()
..gridId = gridId
..blockId = blockId
..rowId = rowId;
@ -326,7 +52,7 @@ class RowService {
}
Future<Either<Unit, FlowyError>> duplicateRow() {
final payload = GridRowIdPB.create()
final payload = RowIdPB.create()
..gridId = gridId
..blockId = blockId
..rowId = rowId;
@ -334,55 +60,3 @@ class RowService {
return GridEventDuplicateRow(payload).send();
}
}
@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,
});
}

View File

@ -1,16 +1,17 @@
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
import 'package:app_flowy/plugins/grid/application/grid_service.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import '../field/field_cache.dart';
part 'property_bloc.freezed.dart';
class GridPropertyBloc extends Bloc<GridPropertyEvent, GridPropertyState> {
final GridFieldCache _fieldCache;
Function(List<GridFieldPB>)? _onFieldsFn;
Function(List<FieldPB>)? _onFieldsFn;
GridPropertyBloc({required String gridId, required GridFieldCache fieldCache})
: _fieldCache = fieldCache,
@ -66,8 +67,8 @@ class GridPropertyEvent with _$GridPropertyEvent {
const factory GridPropertyEvent.initial() = _Initial;
const factory GridPropertyEvent.setFieldVisibility(
String fieldId, bool visibility) = _SetFieldVisibility;
const factory GridPropertyEvent.didReceiveFieldUpdate(
List<GridFieldPB> fields) = _DidReceiveFieldUpdate;
const factory GridPropertyEvent.didReceiveFieldUpdate(List<FieldPB> fields) =
_DidReceiveFieldUpdate;
const factory GridPropertyEvent.moveField(int fromIndex, int toIndex) =
_MoveField;
}
@ -76,10 +77,10 @@ class GridPropertyEvent with _$GridPropertyEvent {
class GridPropertyState with _$GridPropertyState {
const factory GridPropertyState({
required String gridId,
required List<GridFieldPB> fields,
required List<FieldPB> fields,
}) = _GridPropertyState;
factory GridPropertyState.initial(String gridId, List<GridFieldPB> fields) =>
factory GridPropertyState.initial(String gridId, List<FieldPB> fields) =>
GridPropertyState(
gridId: gridId,
fields: fields,

View File

@ -25,7 +25,10 @@ class GridPluginBuilder implements PluginBuilder {
PluginType get pluginType => DefaultPlugin.grid.type();
@override
ViewDataType get dataType => ViewDataType.Grid;
ViewDataTypePB get dataType => ViewDataTypePB.Database;
@override
SubViewDataTypePB? get subDataType => SubViewDataTypePB.Grid;
}
class GridPluginConfig implements PluginConfig {

View File

@ -1,7 +1,7 @@
import 'package:app_flowy/plugins/grid/application/field/field_cache.dart';
import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/plugins/grid/application/grid_bloc.dart';
import 'package:app_flowy/plugins/grid/application/row/row_service.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart';
@ -12,12 +12,15 @@ import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter/material.dart';
import 'package:linked_scroll_controller/linked_scroll_controller.dart';
import '../application/row/row_cache.dart';
import 'controller/grid_scroll.dart';
import 'layout/layout.dart';
import 'layout/sizes.dart';
import 'widgets/cell/cell_builder.dart';
import 'widgets/row/grid_row.dart';
import 'widgets/footer/grid_footer.dart';
import 'widgets/header/grid_header.dart';
import 'widgets/row/row_detail.dart';
import 'widgets/shortcuts.dart';
import 'widgets/toolbar/grid_toolbar.dart';
@ -222,7 +225,7 @@ class _GridRowsState extends State<_GridRows> {
initialItemCount: context.read<GridBloc>().state.rowInfos.length,
itemBuilder:
(BuildContext context, int index, Animation<double> animation) {
final GridRowInfo rowInfo =
final RowInfo rowInfo =
context.read<GridBloc>().state.rowInfos[index];
return _renderRow(context, rowInfo, animation);
},
@ -233,31 +236,59 @@ class _GridRowsState extends State<_GridRows> {
Widget _renderRow(
BuildContext context,
GridRowInfo rowInfo,
RowInfo rowInfo,
Animation<double> animation,
) {
final rowCache =
context.read<GridBloc>().getRowCache(rowInfo.blockId, rowInfo.id);
final fieldCache = context.read<GridBloc>().dataController.fieldCache;
if (rowCache != null) {
final dataController = GridRowDataController(
rowId: rowInfo.id,
fieldCache: fieldCache,
rowCache: rowCache,
);
/// Return placeholder widget if the rowCache is null.
if (rowCache == null) return const SizedBox();
return SizeTransition(
sizeFactor: animation,
child: GridRowWidget(
rowData: rowInfo,
dataController: dataController,
key: ValueKey(rowInfo.id),
),
);
} else {
return const SizedBox();
}
final fieldCache = context.read<GridBloc>().dataController.fieldCache;
final dataController = GridRowDataController(
rowInfo: rowInfo,
fieldCache: fieldCache,
rowCache: rowCache,
);
return SizeTransition(
sizeFactor: animation,
child: GridRowWidget(
rowInfo: rowInfo,
dataController: dataController,
cellBuilder: GridCellBuilder(delegate: dataController),
openDetailPage: (context, cellBuilder) {
_openRowDetailPage(
context,
rowInfo,
fieldCache,
rowCache,
cellBuilder,
);
},
key: ValueKey(rowInfo.id),
),
);
}
void _openRowDetailPage(
BuildContext context,
RowInfo rowInfo,
GridFieldCache fieldCache,
GridRowCache rowCache,
GridCellBuilder cellBuilder,
) {
final dataController = GridRowDataController(
rowInfo: rowInfo,
fieldCache: fieldCache,
rowCache: rowCache,
);
RowDetailPage(
cellBuilder: cellBuilder,
dataController: dataController,
).show(context);
}
}

View File

@ -2,11 +2,15 @@ import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'sizes.dart';
class GridLayout {
static double headerWidth(List<GridFieldPB> fields) {
static double headerWidth(List<FieldPB> fields) {
if (fields.isEmpty) return 0;
final fieldsWidth = fields.map((field) => field.width.toDouble()).reduce((value, element) => value + element);
final fieldsWidth = fields
.map((field) => field.width.toDouble())
.reduce((value, element) => value + element);
return fieldsWidth + GridSize.leadingHeaderPadding + GridSize.trailHeaderPadding;
return fieldsWidth +
GridSize.leadingHeaderPadding +
GridSize.trailHeaderPadding;
}
}

View File

@ -1,5 +1,4 @@
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
import 'package:app_flowy/plugins/grid/application/grid_service.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
@ -13,23 +12,26 @@ import 'select_option_cell/select_option_cell.dart';
import 'text_cell.dart';
import 'url_cell/url_cell.dart';
abstract class GridCellBuilderDelegate
extends GridCellControllerBuilderDelegate {
GridCellCache get cellCache;
}
class GridCellBuilder {
final GridCellCache cellCache;
final GridFieldCache fieldCache;
final GridCellBuilderDelegate delegate;
GridCellBuilder({
required this.cellCache,
required this.fieldCache,
required this.delegate,
});
GridCellWidget build(GridCellIdentifier cell, {GridCellStyle? style}) {
GridCellWidget build(GridCellIdentifier cellId, {GridCellStyle? style}) {
final cellControllerBuilder = GridCellControllerBuilder(
cellId: cell,
cellCache: cellCache,
fieldCache: fieldCache,
cellId: cellId,
cellCache: delegate.cellCache,
delegate: delegate,
);
final key = cell.key();
switch (cell.fieldType) {
final key = cellId.key();
switch (cellId.fieldType) {
case FieldType.Checkbox:
return GridCheckboxCell(
cellControllerBuilder: cellControllerBuilder,

View File

@ -22,8 +22,8 @@ class _CheckboxCellState extends GridCellState<GridCheckboxCell> {
@override
void initState() {
final cellContext = widget.cellControllerBuilder.build();
_cellBloc = getIt<CheckboxCellBloc>(param1: cellContext)
final cellController = widget.cellControllerBuilder.build();
_cellBloc = getIt<CheckboxCellBloc>(param1: cellController)
..add(const CheckboxCellEvent.initial());
super.initState();
}

View File

@ -43,8 +43,8 @@ class _DateCellState extends GridCellState<GridDateCell> {
@override
void initState() {
final cellContext = widget.cellControllerBuilder.build();
_cellBloc = getIt<DateCellBloc>(param1: cellContext)
final cellController = widget.cellControllerBuilder.build();
_cellBloc = getIt<DateCellBloc>(param1: cellController)
..add(const DateCellEvent.initial());
super.initState();
}
@ -84,7 +84,7 @@ class _DateCellState extends GridCellState<GridDateCell> {
DateCellEditor(onDismissed: () => widget.onCellEditing.value = false);
calendar.show(
context,
cellController: bloc.cellContext.clone(),
cellController: bloc.cellController.clone(),
);
}

View File

@ -1,5 +1,6 @@
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:app_flowy/plugins/grid/application/cell/date_cal_bloc.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
@ -39,10 +40,10 @@ class DateCellEditor with FlowyOverlayDelegate {
final result =
await cellController.getFieldTypeOption(DateTypeOptionDataParser());
result.fold(
(dateTypeOption) {
(dateTypeOptionPB) {
final calendar = _CellCalendarWidget(
cellContext: cellController,
dateTypeOption: dateTypeOption,
dateTypeOptionPB: dateTypeOptionPB,
);
FlowyOverlay.of(context).insertWithAnchor(
@ -78,11 +79,11 @@ class DateCellEditor with FlowyOverlayDelegate {
class _CellCalendarWidget extends StatelessWidget {
final GridDateCellController cellContext;
final DateTypeOption dateTypeOption;
final DateTypeOptionPB dateTypeOptionPB;
const _CellCalendarWidget({
required this.cellContext,
required this.dateTypeOption,
required this.dateTypeOptionPB,
Key? key,
}) : super(key: key);
@ -92,9 +93,9 @@ class _CellCalendarWidget extends StatelessWidget {
return BlocProvider(
create: (context) {
return DateCalBloc(
dateTypeOption: dateTypeOption,
dateTypeOptionPB: dateTypeOptionPB,
cellData: cellContext.getCellData(),
cellContext: cellContext,
cellController: cellContext,
)..add(const DateCalEvent.initial());
},
child: BlocBuilder<DateCalBloc, DateCalState>(
@ -196,7 +197,7 @@ class _IncludeTimeButton extends StatelessWidget {
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return BlocSelector<DateCalBloc, DateCalState, bool>(
selector: (state) => state.dateTypeOption.includeTime,
selector: (state) => state.dateTypeOptionPB.includeTime,
builder: (context, includeTime) {
return SizedBox(
height: 50,
@ -243,7 +244,7 @@ class _TimeTextFieldState extends State<_TimeTextField> {
void initState() {
_focusNode = FocusNode();
_controller = TextEditingController(text: widget.bloc.state.time);
if (widget.bloc.state.dateTypeOption.includeTime) {
if (widget.bloc.state.dateTypeOptionPB.includeTime) {
_focusNode.addListener(() {
if (mounted) {
_CalDateTimeSetting.hide(context);
@ -264,7 +265,7 @@ class _TimeTextFieldState extends State<_TimeTextField> {
},
listenWhen: (p, c) => p.time != c.time,
builder: (context, state) {
if (state.dateTypeOption.includeTime) {
if (state.dateTypeOptionPB.includeTime) {
return Padding(
padding: kMargin,
child: RoundedInputField(
@ -306,23 +307,24 @@ class _DateTypeOptionButton extends StatelessWidget {
final title = LocaleKeys.grid_field_dateFormat.tr() +
" &" +
LocaleKeys.grid_field_timeFormat.tr();
return BlocSelector<DateCalBloc, DateCalState, DateTypeOption>(
selector: (state) => state.dateTypeOption,
builder: (context, dateTypeOption) {
return BlocSelector<DateCalBloc, DateCalState, DateTypeOptionPB>(
selector: (state) => state.dateTypeOptionPB,
builder: (context, dateTypeOptionPB) {
return FlowyButton(
text: FlowyText.medium(title, fontSize: 12),
hoverColor: theme.hover,
margin: kMargin,
onTap: () => _showTimeSetting(dateTypeOption, context),
onTap: () => _showTimeSetting(dateTypeOptionPB, context),
rightIcon: svgWidget("grid/more", color: theme.iconColor),
);
},
);
}
void _showTimeSetting(DateTypeOption dateTypeOption, BuildContext context) {
void _showTimeSetting(
DateTypeOptionPB dateTypeOptionPB, BuildContext context) {
final setting = _CalDateTimeSetting(
dateTypeOption: dateTypeOption,
dateTypeOptionPB: dateTypeOptionPB,
onEvent: (event) => context.read<DateCalBloc>().add(event),
);
setting.show(context);
@ -330,10 +332,10 @@ class _DateTypeOptionButton extends StatelessWidget {
}
class _CalDateTimeSetting extends StatefulWidget {
final DateTypeOption dateTypeOption;
final DateTypeOptionPB dateTypeOptionPB;
final Function(DateCalEvent) onEvent;
const _CalDateTimeSetting(
{required this.dateTypeOption, required this.onEvent, Key? key})
{required this.dateTypeOptionPB, required this.onEvent, Key? key})
: super(key: key);
@override
@ -370,17 +372,17 @@ class _CalDateTimeSettingState extends State<_CalDateTimeSetting> {
List<Widget> children = [
DateFormatButton(onTap: () {
final list = DateFormatList(
selectedFormat: widget.dateTypeOption.dateFormat,
selectedFormat: widget.dateTypeOptionPB.dateFormat,
onSelected: (format) =>
widget.onEvent(DateCalEvent.setDateFormat(format)),
);
_showOverlay(context, list);
}),
TimeFormatButton(
timeFormat: widget.dateTypeOption.timeFormat,
timeFormat: widget.dateTypeOptionPB.timeFormat,
onTap: () {
final list = TimeFormatList(
selectedFormat: widget.dateTypeOption.timeFormat,
selectedFormat: widget.dateTypeOptionPB.timeFormat,
onSelected: (format) =>
widget.onEvent(DateCalEvent.setTimeFormat(format)),
);

View File

@ -25,8 +25,8 @@ class _NumberCellState extends GridFocusNodeCellState<GridNumberCell> {
@override
void initState() {
final cellContext = widget.cellControllerBuilder.build();
_cellBloc = getIt<NumberCellBloc>(param1: cellContext)
final cellController = widget.cellControllerBuilder.build();
_cellBloc = getIt<NumberCellBloc>(param1: cellController)
..add(const NumberCellEvent.initial());
_controller =
TextEditingController(text: contentFromState(_cellBloc.state));

View File

@ -46,9 +46,9 @@ class _SingleSelectCellState extends State<GridSingleSelectCell> {
@override
void initState() {
final cellContext =
final cellController =
widget.cellControllerBuilder.build() as GridSelectOptionCellController;
_cellBloc = getIt<SelectOptionCellBloc>(param1: cellContext)
_cellBloc = getIt<SelectOptionCellBloc>(param1: cellController)
..add(const SelectOptionCellEvent.initial());
super.initState();
}
@ -59,7 +59,7 @@ class _SingleSelectCellState extends State<GridSingleSelectCell> {
value: _cellBloc,
child: BlocBuilder<SelectOptionCellBloc, SelectOptionCellState>(
builder: (context, state) {
return _SelectOptionCell(
return SelectOptionWrap(
selectOptions: state.selectedOptions,
cellStyle: widget.cellStyle,
onFocus: (value) => widget.onCellEditing.value = value,
@ -102,9 +102,9 @@ class _MultiSelectCellState extends State<GridMultiSelectCell> {
@override
void initState() {
final cellContext =
final cellController =
widget.cellControllerBuilder.build() as GridSelectOptionCellController;
_cellBloc = getIt<SelectOptionCellBloc>(param1: cellContext)
_cellBloc = getIt<SelectOptionCellBloc>(param1: cellController)
..add(const SelectOptionCellEvent.initial());
super.initState();
}
@ -115,11 +115,12 @@ class _MultiSelectCellState extends State<GridMultiSelectCell> {
value: _cellBloc,
child: BlocBuilder<SelectOptionCellBloc, SelectOptionCellState>(
builder: (context, state) {
return _SelectOptionCell(
selectOptions: state.selectedOptions,
cellStyle: widget.cellStyle,
onFocus: (value) => widget.onCellEditing.value = value,
cellControllerBuilder: widget.cellControllerBuilder);
return SelectOptionWrap(
selectOptions: state.selectedOptions,
cellStyle: widget.cellStyle,
onFocus: (value) => widget.onCellEditing.value = value,
cellControllerBuilder: widget.cellControllerBuilder,
);
},
),
);
@ -132,16 +133,16 @@ class _MultiSelectCellState extends State<GridMultiSelectCell> {
}
}
class _SelectOptionCell extends StatelessWidget {
class SelectOptionWrap extends StatelessWidget {
final List<SelectOptionPB> selectOptions;
final void Function(bool) onFocus;
final void Function(bool)? onFocus;
final SelectOptionCellStyle? cellStyle;
final GridCellControllerBuilder cellControllerBuilder;
const _SelectOptionCell({
const SelectOptionWrap({
required this.selectOptions,
required this.onFocus,
required this.cellStyle,
required this.cellControllerBuilder,
this.onFocus,
this.cellStyle,
Key? key,
}) : super(key: key);
@ -177,11 +178,11 @@ class _SelectOptionCell extends StatelessWidget {
child,
InkWell(
onTap: () {
onFocus(true);
onFocus?.call(true);
final cellContext =
cellControllerBuilder.build() as GridSelectOptionCellController;
SelectOptionCellEditor.show(
context, cellContext, () => onFocus(false));
context, cellContext, () => onFocus?.call(false));
},
),
],

View File

@ -39,8 +39,8 @@ class _GridTextCellState extends GridFocusNodeCellState<GridTextCell> {
@override
void initState() {
final cellContext = widget.cellControllerBuilder.build();
_cellBloc = getIt<TextCellBloc>(param1: cellContext);
final cellController = widget.cellControllerBuilder.build();
_cellBloc = getIt<TextCellBloc>(param1: cellController);
_cellBloc.add(const TextCellEvent.initial());
_controller = TextEditingController(text: _cellBloc.state.content);
super.initState();

View File

@ -64,7 +64,7 @@ class _URLCellEditorState extends State<URLCellEditor> {
@override
void initState() {
_cellBloc = URLCellEditorBloc(cellContext: widget.cellController);
_cellBloc = URLCellEditorBloc(cellController: widget.cellController);
_cellBloc.add(const URLCellEditorEvent.initial());
_controller = TextEditingController(text: _cellBloc.state.content);

View File

@ -52,10 +52,10 @@ class GridURLCell extends GridCellWidget {
GridURLCellAccessoryType ty, GridCellAccessoryBuildContext buildContext) {
switch (ty) {
case GridURLCellAccessoryType.edit:
final cellContext =
final cellController =
cellControllerBuilder.build() as GridURLCellController;
return _EditURLAccessory(
cellContext: cellContext,
cellContext: cellController,
anchorContext: buildContext.anchorContext);
case GridURLCellAccessoryType.copyURL:
@ -92,7 +92,7 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
void initState() {
final cellContext =
widget.cellControllerBuilder.build() as GridURLCellController;
_cellBloc = URLCellBloc(cellContext: cellContext);
_cellBloc = URLCellBloc(cellController: cellContext);
_cellBloc.add(const URLCellEvent.initial());
super.initState();
}

View File

@ -1,5 +1,6 @@
import 'package:app_flowy/plugins/grid/application/field/field_cell_bloc.dart';
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
@ -143,7 +144,7 @@ class _DragToExpandLine extends StatelessWidget {
class FieldCellButton extends StatelessWidget {
final VoidCallback onTap;
final GridFieldPB field;
final FieldPB field;
const FieldCellButton({
required this.field,
required this.onTap,

View File

@ -1,5 +1,5 @@
import 'package:app_flowy/plugins/grid/application/field/field_editor_bloc.dart';
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';

View File

@ -1,4 +1,5 @@
import 'dart:typed_data';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_data_controller.dart';
import 'package:dartz/dartz.dart' show Either;
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
@ -15,7 +16,7 @@ import 'field_type_extension.dart';
import 'field_type_list.dart';
import 'type_option/builder.dart';
typedef UpdateFieldCallback = void Function(GridFieldPB, Uint8List);
typedef UpdateFieldCallback = void Function(FieldPB, Uint8List);
typedef SwitchToFieldCallback
= Future<Either<FieldTypeOptionDataPB, FlowyError>> Function(
String fieldId,
@ -63,7 +64,7 @@ class _FieldTypeOptionEditorState extends State<FieldTypeOptionEditor> {
);
}
Widget _switchFieldTypeButton(BuildContext context, GridFieldPB field) {
Widget _switchFieldTypeButton(BuildContext context, FieldPB field) {
final theme = context.watch<AppTheme>();
return SizedBox(
height: GridSize.typeOptionItemHeight,
@ -94,8 +95,8 @@ class _FieldTypeOptionEditorState extends State<FieldTypeOptionEditor> {
return makeTypeOptionWidget(
context: context,
dataController: widget.dataController,
overlayDelegate: overlayDelegate,
dataController: widget.dataController,
);
}

View File

@ -1,3 +1,5 @@
import 'package:app_flowy/plugins/grid/application/field/field_cache.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/plugins/grid/application/prelude.dart';
import 'package:flowy_infra/image.dart';
@ -168,7 +170,7 @@ class CreateFieldButton extends StatelessWidget {
class SliverHeaderDelegateImplementation
extends SliverPersistentHeaderDelegate {
final String gridId;
final List<GridFieldPB> fields;
final List<FieldPB> fields;
SliverHeaderDelegateImplementation(
{required this.gridId, required this.fields});

View File

@ -1,7 +1,15 @@
import 'dart:typed_data';
import 'package:app_flowy/plugins/grid/application/field/type_option/multi_select_type_option.dart';
import 'package:app_flowy/plugins/grid/application/prelude.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_data_controller.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/multi_select_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/single_select_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/text_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart';
import 'package:protobuf/protobuf.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flutter/material.dart';
import 'checkbox.dart';
@ -39,70 +47,147 @@ Widget? makeTypeOptionWidget({
required TypeOptionDataController dataController,
required TypeOptionOverlayDelegate overlayDelegate,
}) {
final builder = makeTypeOptionWidgetBuilder(dataController, overlayDelegate);
final builder = makeTypeOptionWidgetBuilder(
dataController: dataController,
overlayDelegate: overlayDelegate,
);
return builder.build(context);
}
TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder(
TypeOptionDataController dataController,
TypeOptionOverlayDelegate overlayDelegate,
) {
TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder({
required TypeOptionDataController dataController,
required TypeOptionOverlayDelegate overlayDelegate,
}) {
final gridId = dataController.gridId;
final fieldType = dataController.field.fieldType;
switch (dataController.field.fieldType) {
case FieldType.Checkbox:
final context = CheckboxTypeOptionContext(
dataController: dataController,
dataParser: CheckboxTypeOptionWidgetDataParser(),
return CheckboxTypeOptionWidgetBuilder(
makeTypeOptionContextWithDataController<CheckboxTypeOptionPB>(
gridId: gridId,
fieldType: fieldType,
dataController: dataController,
),
);
return CheckboxTypeOptionWidgetBuilder(context);
case FieldType.DateTime:
final context = DateTypeOptionContext(
dataController: dataController,
dataParser: DateTypeOptionDataParser(),
);
return DateTypeOptionWidgetBuilder(
context,
makeTypeOptionContextWithDataController<DateTypeOptionPB>(
gridId: gridId,
fieldType: fieldType,
dataController: dataController,
),
overlayDelegate,
);
case FieldType.SingleSelect:
final context = SingleSelectTypeOptionContext(
fieldContext: dataController,
dataBuilder: SingleSelectTypeOptionWidgetDataParser(),
);
return SingleSelectTypeOptionWidgetBuilder(
context,
makeTypeOptionContextWithDataController<SingleSelectTypeOptionPB>(
gridId: gridId,
fieldType: fieldType,
dataController: dataController,
),
overlayDelegate,
);
case FieldType.MultiSelect:
final context = MultiSelectTypeOptionContext(
dataController: dataController,
dataBuilder: MultiSelectTypeOptionWidgetDataParser(),
);
return MultiSelectTypeOptionWidgetBuilder(
context,
makeTypeOptionContextWithDataController<MultiSelectTypeOptionPB>(
gridId: gridId,
fieldType: fieldType,
dataController: dataController,
),
overlayDelegate,
);
case FieldType.Number:
final context = NumberTypeOptionContext(
dataController: dataController,
dataParser: NumberTypeOptionWidgetDataParser(),
);
return NumberTypeOptionWidgetBuilder(
context,
makeTypeOptionContextWithDataController<NumberTypeOptionPB>(
gridId: gridId,
fieldType: fieldType,
dataController: dataController,
),
overlayDelegate,
);
case FieldType.RichText:
final context = RichTextTypeOptionContext(
dataController: dataController,
dataParser: RichTextTypeOptionWidgetDataParser(),
return RichTextTypeOptionWidgetBuilder(
makeTypeOptionContextWithDataController<RichTextTypeOptionPB>(
gridId: gridId,
fieldType: fieldType,
dataController: dataController,
),
);
return RichTextTypeOptionWidgetBuilder(context);
case FieldType.URL:
final context = URLTypeOptionContext(
dataController: dataController,
dataParser: URLTypeOptionWidgetDataParser(),
return URLTypeOptionWidgetBuilder(
makeTypeOptionContextWithDataController<URLTypeOptionPB>(
gridId: gridId,
fieldType: fieldType,
dataController: dataController,
),
);
return URLTypeOptionWidgetBuilder(context);
}
throw UnimplementedError;
}
TypeOptionContext<T> makeTypeOptionContext<T extends GeneratedMessage>({
required String gridId,
required FieldPB field,
}) {
final loader = FieldTypeOptionLoader(gridId: gridId, field: field);
final dataController = TypeOptionDataController(
gridId: gridId,
loader: loader,
field: field,
);
return makeTypeOptionContextWithDataController(
gridId: gridId,
fieldType: field.fieldType,
dataController: dataController,
);
}
TypeOptionContext<T>
makeTypeOptionContextWithDataController<T extends GeneratedMessage>({
required String gridId,
required FieldType fieldType,
required TypeOptionDataController dataController,
}) {
switch (fieldType) {
case FieldType.Checkbox:
return CheckboxTypeOptionContext(
dataController: dataController,
dataParser: CheckboxTypeOptionWidgetDataParser(),
) as TypeOptionContext<T>;
case FieldType.DateTime:
return DateTypeOptionContext(
dataController: dataController,
dataParser: DateTypeOptionDataParser(),
) as TypeOptionContext<T>;
case FieldType.SingleSelect:
return SingleSelectTypeOptionContext(
dataController: dataController,
dataParser: SingleSelectTypeOptionWidgetDataParser(),
) as TypeOptionContext<T>;
case FieldType.MultiSelect:
return MultiSelectTypeOptionContext(
dataController: dataController,
dataParser: MultiSelectTypeOptionWidgetDataParser(),
) as TypeOptionContext<T>;
case FieldType.Number:
return NumberTypeOptionContext(
dataController: dataController,
dataParser: NumberTypeOptionWidgetDataParser(),
) as TypeOptionContext<T>;
case FieldType.RichText:
return RichTextTypeOptionContext(
dataController: dataController,
dataParser: RichTextTypeOptionWidgetDataParser(),
) as TypeOptionContext<T>;
case FieldType.URL:
return URLTypeOptionContext(
dataController: dataController,
dataParser: URLTypeOptionWidgetDataParser(),
) as TypeOptionContext<T>;
}
throw UnimplementedError;
}

View File

@ -1,18 +1,7 @@
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_service.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pb.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:flutter/material.dart';
import 'builder.dart';
typedef CheckboxTypeOptionContext = TypeOptionWidgetContext<CheckboxTypeOption>;
class CheckboxTypeOptionWidgetDataParser
extends TypeOptionDataParser<CheckboxTypeOption> {
@override
CheckboxTypeOption fromBuffer(List<int> buffer) {
return CheckboxTypeOption.fromBuffer(buffer);
}
}
class CheckboxTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
CheckboxTypeOptionWidgetBuilder(CheckboxTypeOptionContext typeOptionContext);

View File

@ -1,4 +1,5 @@
import 'package:app_flowy/plugins/grid/application/field/type_option/date_bloc.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:easy_localization/easy_localization.dart' hide DateFormat;
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:flowy_infra/image.dart';

View File

@ -1,4 +1,5 @@
import 'package:app_flowy/plugins/grid/application/field/type_option/multi_select_type_option.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:flutter/material.dart';
import '../field_type_option_editor.dart';
@ -12,7 +13,11 @@ class MultiSelectTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
MultiSelectTypeOptionContext typeOptionContext,
TypeOptionOverlayDelegate overlayDelegate,
) : _widget = MultiSelectTypeOptionWidget(
typeOptionContext: typeOptionContext,
selectOptionAction: MultiSelectAction(
fieldId: typeOptionContext.fieldId,
gridId: typeOptionContext.gridId,
typeOptionContext: typeOptionContext,
),
overlayDelegate: overlayDelegate,
);
@ -21,11 +26,11 @@ class MultiSelectTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
}
class MultiSelectTypeOptionWidget extends TypeOptionWidget {
final MultiSelectTypeOptionContext typeOptionContext;
final MultiSelectAction selectOptionAction;
final TypeOptionOverlayDelegate overlayDelegate;
const MultiSelectTypeOptionWidget({
required this.typeOptionContext,
required this.selectOptionAction,
required this.overlayDelegate,
Key? key,
}) : super(key: key);
@ -33,10 +38,10 @@ class MultiSelectTypeOptionWidget extends TypeOptionWidget {
@override
Widget build(BuildContext context) {
return SelectOptionTypeOptionWidget(
options: typeOptionContext.typeOption.options,
options: selectOptionAction.typeOption.options,
beginEdit: () => overlayDelegate.hideOverlay(context),
overlayDelegate: overlayDelegate,
typeOptionAction: typeOptionContext,
typeOptionAction: selectOptionAction,
// key: ValueKey(state.typeOption.hashCode),
);
}

View File

@ -1,5 +1,6 @@
import 'package:app_flowy/plugins/grid/application/field/type_option/number_bloc.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/number_format_bloc.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';

View File

@ -1,18 +1,7 @@
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_service.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/text_type_option.pb.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:flutter/material.dart';
import 'builder.dart';
typedef RichTextTypeOptionContext = TypeOptionWidgetContext<RichTextTypeOption>;
class RichTextTypeOptionWidgetDataParser
extends TypeOptionDataParser<RichTextTypeOption> {
@override
RichTextTypeOption fromBuffer(List<int> buffer) {
return RichTextTypeOption.fromBuffer(buffer);
}
}
class RichTextTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
RichTextTypeOptionWidgetBuilder(RichTextTypeOptionContext typeOptionContext);

View File

@ -20,7 +20,7 @@ class SelectOptionTypeOptionWidget extends StatelessWidget {
final List<SelectOptionPB> options;
final VoidCallback beginEdit;
final TypeOptionOverlayDelegate overlayDelegate;
final SelectOptionTypeOptionAction typeOptionAction;
final ISelectOptionAction typeOptionAction;
const SelectOptionTypeOptionWidget({
required this.options,
@ -34,7 +34,9 @@ class SelectOptionTypeOptionWidget extends StatelessWidget {
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => SelectOptionTypeOptionBloc(
options: options, typeOptionAction: typeOptionAction),
options: options,
typeOptionAction: typeOptionAction,
),
child:
BlocBuilder<SelectOptionTypeOptionBloc, SelectOptionTypeOptionState>(
builder: (context, state) {

View File

@ -1,4 +1,5 @@
import 'package:app_flowy/plugins/grid/application/field/type_option/single_select_type_option.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:flutter/material.dart';
import '../field_type_option_editor.dart';
import 'builder.dart';
@ -8,10 +9,14 @@ class SingleSelectTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
final SingleSelectTypeOptionWidget _widget;
SingleSelectTypeOptionWidgetBuilder(
SingleSelectTypeOptionContext typeOptionContext,
SingleSelectTypeOptionContext singleSelectTypeOption,
TypeOptionOverlayDelegate overlayDelegate,
) : _widget = SingleSelectTypeOptionWidget(
typeOptionContext: typeOptionContext,
selectOptionAction: SingleSelectAction(
fieldId: singleSelectTypeOption.fieldId,
gridId: singleSelectTypeOption.gridId,
typeOptionContext: singleSelectTypeOption,
),
overlayDelegate: overlayDelegate,
);
@ -20,11 +25,11 @@ class SingleSelectTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
}
class SingleSelectTypeOptionWidget extends TypeOptionWidget {
final SingleSelectTypeOptionContext typeOptionContext;
final SingleSelectAction selectOptionAction;
final TypeOptionOverlayDelegate overlayDelegate;
const SingleSelectTypeOptionWidget({
required this.typeOptionContext,
required this.selectOptionAction,
required this.overlayDelegate,
Key? key,
}) : super(key: key);
@ -32,10 +37,10 @@ class SingleSelectTypeOptionWidget extends TypeOptionWidget {
@override
Widget build(BuildContext context) {
return SelectOptionTypeOptionWidget(
options: typeOptionContext.typeOption.options,
options: selectOptionAction.typeOption.options,
beginEdit: () => overlayDelegate.hideOverlay(context),
overlayDelegate: overlayDelegate,
typeOptionAction: typeOptionContext,
typeOptionAction: selectOptionAction,
// key: ValueKey(state.typeOption.hashCode),
);
}

View File

@ -1,18 +1,7 @@
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_service.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:flutter/material.dart';
import 'builder.dart';
typedef URLTypeOptionContext = TypeOptionWidgetContext<URLTypeOption>;
class URLTypeOptionWidgetDataParser
extends TypeOptionDataParser<URLTypeOption> {
@override
URLTypeOption fromBuffer(List<int> buffer) {
return URLTypeOption.fromBuffer(buffer);
}
}
class URLTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
URLTypeOptionWidgetBuilder(URLTypeOptionContext typeOptionContext);

View File

@ -1,4 +1,5 @@
import 'package:app_flowy/plugins/grid/application/prelude.dart';
import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
@ -13,22 +14,20 @@ import '../cell/cell_accessory.dart';
import '../cell/cell_container.dart';
import '../cell/prelude.dart';
import 'row_action_sheet.dart';
import 'row_detail.dart';
class GridRowWidget extends StatefulWidget {
final GridRowInfo rowData;
final RowInfo rowInfo;
final GridRowDataController dataController;
final GridCellBuilder cellBuilder;
final void Function(BuildContext, GridCellBuilder) openDetailPage;
GridRowWidget({
required this.rowData,
const GridRowWidget({
required this.rowInfo,
required this.dataController,
required this.cellBuilder,
required this.openDetailPage,
Key? key,
}) : cellBuilder = GridCellBuilder(
cellCache: dataController.rowCache.cellCache,
fieldCache: dataController.fieldCache,
),
super(key: key);
}) : super(key: key);
@override
State<GridRowWidget> createState() => _GridRowWidgetState();
@ -40,7 +39,7 @@ class _GridRowWidgetState extends State<GridRowWidget> {
@override
void initState() {
_rowBloc = RowBloc(
rowInfo: widget.rowData,
rowInfo: widget.rowInfo,
dataController: widget.dataController,
);
_rowBloc.add(const RowEvent.initial());
@ -55,17 +54,20 @@ class _GridRowWidgetState extends State<GridRowWidget> {
child: BlocBuilder<RowBloc, RowState>(
buildWhen: (p, c) => p.rowInfo.height != c.rowInfo.height,
builder: (context, state) {
return Row(
children: [
const _RowLeading(),
Expanded(
child: _RowCells(
final children = [
const _RowLeading(),
Expanded(
child: RowContent(
builder: widget.cellBuilder,
onExpand: () => _expandRow(context),
)),
const _RowTrailing(),
],
);
onExpand: () => widget.openDetailPage(
context,
widget.cellBuilder,
),
),
),
const _RowTrailing(),
];
return Row(children: children);
},
),
),
@ -77,15 +79,6 @@ class _GridRowWidgetState extends State<GridRowWidget> {
_rowBloc.close();
super.dispose();
}
void _expandRow(BuildContext context) {
final page = RowDetailPage(
rowInfo: widget.rowData,
rowCache: widget.dataController.rowCache,
cellBuilder: widget.cellBuilder,
);
page.show(context);
}
}
class _RowLeading extends StatelessWidget {
@ -158,10 +151,10 @@ class _DeleteRowButton extends StatelessWidget {
}
}
class _RowCells extends StatelessWidget {
class RowContent extends StatelessWidget {
final VoidCallback onExpand;
final GridCellBuilder builder;
const _RowCells({
const RowContent({
required this.builder,
required this.onExpand,
Key? key,

View File

@ -1,5 +1,4 @@
import 'package:app_flowy/plugins/grid/application/row/row_action_sheet_bloc.dart';
import 'package:app_flowy/plugins/grid/application/row/row_service.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:flowy_infra/image.dart';
@ -12,10 +11,11 @@ import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../application/row/row_cache.dart';
import '../../layout/sizes.dart';
class GridRowActionSheet extends StatelessWidget {
final GridRowInfo rowData;
final RowInfo rowData;
const GridRowActionSheet({required this.rowData, Key? key}) : super(key: key);
@override

View File

@ -1,7 +1,7 @@
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
import 'package:app_flowy/plugins/grid/application/row/row_detail_bloc.dart';
import 'package:app_flowy/plugins/grid/application/row/row_service.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -21,13 +21,11 @@ import '../header/field_cell.dart';
import '../header/field_editor.dart';
class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate {
final GridRowInfo rowInfo;
final GridRowCache rowCache;
final GridRowDataController dataController;
final GridCellBuilder cellBuilder;
const RowDetailPage({
required this.rowInfo,
required this.rowCache,
required this.dataController,
required this.cellBuilder,
Key? key,
}) : super(key: key);
@ -63,8 +61,7 @@ class _RowDetailPageState extends State<RowDetailPage> {
return BlocProvider(
create: (context) {
final bloc = RowDetailBloc(
rowInfo: widget.rowInfo,
rowCache: widget.rowCache,
dataController: widget.dataController,
);
bloc.add(const RowDetailEvent.initial());
return bloc;

View File

@ -1,6 +1,5 @@
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
import 'package:app_flowy/plugins/grid/application/grid_service.dart';
import 'package:app_flowy/plugins/grid/application/setting/property_bloc.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/header/field_type_extension.dart';
import 'package:flowy_infra/image.dart';
@ -15,6 +14,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:styled_widget/styled_widget.dart';
import '../../../application/field/field_cache.dart';
import '../../layout/sizes.dart';
import '../header/field_editor.dart';
@ -78,7 +78,7 @@ class GridPropertyList extends StatelessWidget with FlowyOverlayDelegate {
}
class _GridPropertyCell extends StatelessWidget {
final GridFieldPB field;
final FieldPB field;
final String gridId;
const _GridPropertyCell({required this.gridId, required this.field, Key? key})
: super(key: key);

View File

@ -1,4 +1,3 @@
import 'package:app_flowy/plugins/grid/application/grid_service.dart';
import 'package:app_flowy/plugins/grid/application/setting/setting_bloc.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
@ -12,6 +11,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
import '../../../application/field/field_cache.dart';
import '../../layout/sizes.dart';
import 'grid_property.dart';

View File

@ -1,4 +1,3 @@
import 'package:app_flowy/plugins/grid/application/grid_service.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/extension.dart';
@ -6,6 +5,7 @@ import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../application/field/field_cache.dart';
import '../../layout/sizes.dart';
import 'grid_setting.dart';

View File

@ -21,6 +21,8 @@ import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:get_it/get_it.dart';
import '../plugins/grid/application/field/field_cache.dart';
class DependencyResolver {
static Future<void> resolve(GetIt getIt) async {
_resolveUserDeps(getIt);
@ -168,33 +170,33 @@ void _resolveGridDeps(GetIt getIt) {
getIt.registerFactoryParam<TextCellBloc, GridCellController, void>(
(context, _) => TextCellBloc(
cellContext: context,
cellController: context,
),
);
getIt.registerFactoryParam<SelectOptionCellBloc,
GridSelectOptionCellController, void>(
(context, _) => SelectOptionCellBloc(
cellContext: context,
cellController: context,
),
);
getIt.registerFactoryParam<NumberCellBloc, GridCellController, void>(
(context, _) => NumberCellBloc(
cellContext: context,
cellController: context,
),
);
getIt.registerFactoryParam<DateCellBloc, GridDateCellController, void>(
(context, _) => DateCellBloc(
cellContext: context,
cellController: context,
),
);
getIt.registerFactoryParam<CheckboxCellBloc, GridCellController, void>(
(cellData, _) => CheckboxCellBloc(
service: CellService(),
cellContext: cellData,
cellController: cellData,
),
);

View File

@ -10,7 +10,7 @@ import 'package:flutter/widgets.dart';
export "./src/sandbox.dart";
enum DefaultPlugin {
quill,
editor,
blank,
trash,
grid,
@ -20,7 +20,7 @@ enum DefaultPlugin {
extension FlowyDefaultPluginExt on DefaultPlugin {
int type() {
switch (this) {
case DefaultPlugin.quill:
case DefaultPlugin.editor:
return 0;
case DefaultPlugin.blank:
return 1;
@ -35,7 +35,6 @@ extension FlowyDefaultPluginExt on DefaultPlugin {
}
typedef PluginType = int;
typedef PluginDataType = ViewDataType;
typedef PluginId = String;
abstract class Plugin {
@ -55,7 +54,9 @@ abstract class PluginBuilder {
PluginType get pluginType;
ViewDataType get dataType => ViewDataType.TextBlock;
ViewDataTypePB get dataType => ViewDataTypePB.TextBlock;
SubViewDataTypePB? get subDataType => null;
}
abstract class PluginConfig {

View File

@ -86,6 +86,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
desc: value.desc,
dataType: value.dataType,
pluginType: value.pluginType,
subDataType: value.subDataType,
);
viewOrFailed.fold(
(view) => emit(state.copyWith(
@ -138,7 +139,8 @@ class AppEvent with _$AppEvent {
const factory AppEvent.createView(
String name,
String desc,
PluginDataType dataType,
ViewDataTypePB dataType,
SubViewDataTypePB? subDataType,
PluginType pluginType,
) = CreateView;
const factory AppEvent.delete() = Delete;

View File

@ -24,16 +24,21 @@ class AppService {
required String appId,
required String name,
required String desc,
required PluginDataType dataType,
required ViewDataTypePB dataType,
required PluginType pluginType,
SubViewDataTypePB? subDataType,
}) {
final payload = CreateViewPayloadPB.create()
var payload = CreateViewPayloadPB.create()
..belongToId = appId
..name = name
..desc = desc
..dataType = dataType
..pluginType = pluginType;
if (subDataType != null) {
payload.subDataType = subDataType;
}
return FolderEventCreateView(payload).send();
}

View File

@ -49,7 +49,9 @@ class MenuAppHeader extends StatelessWidget {
height: MenuAppSizes.headerHeight,
child: InkWell(
onTap: () {
ExpandableController.of(context, rebuildOnChange: false, required: true)?.toggle();
ExpandableController.of(context,
rebuildOnChange: false, required: true)
?.toggle();
},
child: ExpandableIcon(
theme: ExpandableThemeData(
@ -68,18 +70,23 @@ class MenuAppHeader extends StatelessWidget {
Widget _renderTitle(BuildContext context, AppTheme theme) {
return Expanded(
child: BlocListener<AppBloc, AppState>(
listenWhen: (p, c) => (p.latestCreatedView == null && c.latestCreatedView != null),
listenWhen: (p, c) =>
(p.latestCreatedView == null && c.latestCreatedView != null),
listener: (context, state) {
final expandableController = ExpandableController.of(context, rebuildOnChange: false, required: true)!;
final expandableController = ExpandableController.of(context,
rebuildOnChange: false, required: true)!;
if (!expandableController.expanded) {
expandableController.toggle();
}
},
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => ExpandableController.of(context, rebuildOnChange: false, required: true)?.toggle(),
onTap: () => ExpandableController.of(context,
rebuildOnChange: false, required: true)
?.toggle(),
onSecondaryTap: () {
final actionList = AppDisclosureActionSheet(onSelected: (action) => _handleAction(context, action));
final actionList = AppDisclosureActionSheet(
onSelected: (action) => _handleAction(context, action));
actionList.show(
context,
anchorDirection: AnchorDirection.bottomWithCenterAligned,
@ -107,6 +114,7 @@ class MenuAppHeader extends StatelessWidget {
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
"",
pluginBuilder.dataType,
pluginBuilder.subDataType,
pluginBuilder.pluginType,
));
},

View File

@ -1632,7 +1632,7 @@ final Map<String, String> activities = Map.fromIterables([
'Flying Disc',
'Bowling',
'Cricket Game',
'GridFieldPB Hockey',
'FieldPB Hockey',
'Ice Hockey',
'Lacrosse',
'Ping Pong',

View File

@ -9,7 +9,7 @@ class MultiBoardListExample extends StatefulWidget {
}
class _MultiBoardListExampleState extends State<MultiBoardListExample> {
final BoardDataController boardDataController = BoardDataController(
final AFBoardDataController boardDataController = AFBoardDataController(
onMoveColumn: (fromIndex, toIndex) {
debugPrint('Move column from $fromIndex to $toIndex');
},
@ -23,18 +23,18 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
@override
void initState() {
final column1 = BoardColumnData(id: "To Do", items: [
final column1 = AFBoardColumnData(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: [
final column2 = AFBoardColumnData(id: "In Progress", items: [
RichTextItem(title: "Card 5", subtitle: 'Aug 1, 2020 4:05 PM'),
TextItem("Card 6"),
]);
final column3 = BoardColumnData(id: "Done", items: []);
final column3 = AFBoardColumnData(id: "Done", items: []);
boardDataController.addColumn(column1);
boardDataController.addColumn(column2);
@ -45,14 +45,14 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
@override
Widget build(BuildContext context) {
final config = BoardConfig(
final config = AFBoardConfig(
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
);
return Container(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
child: Board(
child: AFBoard(
dataController: boardDataController,
footBuilder: (context, columnData) {
return AppFlowyColumnFooter(
@ -79,7 +79,7 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
);
},
columnConstraints: const BoxConstraints.tightFor(width: 240),
config: BoardConfig(
config: AFBoardConfig(
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
),
),
@ -87,7 +87,7 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
);
}
Widget _buildCard(ColumnItem item) {
Widget _buildCard(AFColumnItem item) {
if (item is TextItem) {
return Align(
alignment: Alignment.centerLeft,
@ -126,7 +126,7 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
}
}
class TextItem extends ColumnItem {
class TextItem extends AFColumnItem {
final String s;
TextItem(this.s);
@ -135,7 +135,7 @@ class TextItem extends ColumnItem {
String get id => s;
}
class RichTextItem extends ColumnItem {
class RichTextItem extends AFColumnItem {
final String title;
final String subtitle;

View File

@ -9,11 +9,11 @@ class SingleBoardListExample extends StatefulWidget {
}
class _SingleBoardListExampleState extends State<SingleBoardListExample> {
final BoardDataController boardData = BoardDataController();
final AFBoardDataController boardData = AFBoardDataController();
@override
void initState() {
final column = BoardColumnData(id: "1", items: [
final column = AFBoardColumnData(id: "1", items: [
TextItem("a"),
TextItem("b"),
TextItem("c"),
@ -26,7 +26,7 @@ class _SingleBoardListExampleState extends State<SingleBoardListExample> {
@override
Widget build(BuildContext context) {
return Board(
return AFBoard(
dataController: boardData,
cardBuilder: (context, item) {
return _RowWidget(item: item as TextItem, key: ObjectKey(item));
@ -50,7 +50,7 @@ class _RowWidget extends StatelessWidget {
}
}
class TextItem extends ColumnItem {
class TextItem extends AFColumnItem {
final String s;
TextItem(this.s);

View File

@ -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');

View File

@ -8,13 +8,13 @@ import 'reorder_flex/reorder_flex.dart';
import 'reorder_phantom/phantom_controller.dart';
import '../rendering/board_overlay.dart';
class BoardConfig {
class AFBoardConfig {
final double cornerRadius;
final EdgeInsets columnPadding;
final EdgeInsets columnItemPadding;
final Color columnBackgroundColor;
const BoardConfig({
const AFBoardConfig({
this.cornerRadius = 6.0,
this.columnPadding = const EdgeInsets.symmetric(horizontal: 8),
this.columnItemPadding = const EdgeInsets.symmetric(horizontal: 10),
@ -22,7 +22,7 @@ class BoardConfig {
});
}
class Board extends StatelessWidget {
class AFBoard extends StatelessWidget {
/// The direction to use as the main axis.
final Axis direction = Axis.vertical;
@ -30,32 +30,32 @@ class Board extends StatelessWidget {
final Widget? background;
///
final BoardColumnCardBuilder cardBuilder;
final AFBoardColumnCardBuilder cardBuilder;
///
final BoardColumnHeaderBuilder? headerBuilder;
final AFBoardColumnHeaderBuilder? headerBuilder;
///
final BoardColumnFooterBuilder? footBuilder;
final AFBoardColumnFooterBuilder? footBuilder;
///
final BoardDataController dataController;
final AFBoardDataController dataController;
final BoxConstraints columnConstraints;
///
final BoardPhantomController phantomController;
final BoardConfig config;
final AFBoardConfig config;
Board({
AFBoard({
required this.dataController,
required this.cardBuilder,
this.background,
this.footBuilder,
this.headerBuilder,
this.columnConstraints = const BoxConstraints(maxWidth: 200),
this.config = const BoardConfig(),
this.config = const AFBoardConfig(),
Key? key,
}) : phantomController = BoardPhantomController(delegate: dataController),
super(key: key);
@ -64,7 +64,7 @@ class Board extends StatelessWidget {
Widget build(BuildContext context) {
return ChangeNotifierProvider.value(
value: dataController,
child: Consumer<BoardDataController>(
child: Consumer<AFBoardDataController>(
builder: (context, notifier, child) {
return BoardContent(
config: config,
@ -89,20 +89,20 @@ class BoardContent extends StatefulWidget {
final OnDragStarted? onDragStarted;
final OnReorder onReorder;
final OnDragEnded? onDragEnded;
final BoardDataController dataController;
final AFBoardDataController dataController;
final Widget? background;
final BoardConfig config;
final AFBoardConfig config;
final ReorderFlexConfig reorderFlexConfig;
final BoxConstraints columnConstraints;
///
final BoardColumnCardBuilder cardBuilder;
final AFBoardColumnCardBuilder cardBuilder;
///
final BoardColumnHeaderBuilder? headerBuilder;
final AFBoardColumnHeaderBuilder? headerBuilder;
///
final BoardColumnFooterBuilder? footBuilder;
final AFBoardColumnFooterBuilder? footBuilder;
final OverlapDragTargetDelegate delegate;
@ -206,7 +206,7 @@ class _BoardContentState extends State<BoardContent> {
builder: (context, value, child) {
return ConstrainedBox(
constraints: widget.columnConstraints,
child: BoardColumnWidget(
child: AFBoardColumnWidget(
margin: _marginFromIndex(columnIndex),
itemMargin: widget.config.columnItemPadding,
headerBuilder: widget.headerBuilder,
@ -246,9 +246,9 @@ class _BoardContentState extends State<BoardContent> {
}
}
class _BoardColumnDataSourceImpl extends BoardColumnDataDataSource {
class _BoardColumnDataSourceImpl extends AFBoardColumnDataDataSource {
String columnId;
final BoardDataController dataController;
final AFBoardDataController dataController;
_BoardColumnDataSourceImpl({
required this.columnId,
@ -256,7 +256,7 @@ class _BoardColumnDataSourceImpl extends BoardColumnDataDataSource {
});
@override
BoardColumnData get columnData =>
AFBoardColumnData get columnData =>
dataController.columnController(columnId).columnData;
@override

View File

@ -22,23 +22,23 @@ typedef OnColumnDeleted = void Function(String listId, int deletedIndex);
typedef OnColumnInserted = void Function(String listId, int insertedIndex);
typedef BoardColumnCardBuilder = Widget Function(
typedef AFBoardColumnCardBuilder = Widget Function(
BuildContext context,
ColumnItem item,
AFColumnItem item,
);
typedef BoardColumnHeaderBuilder = Widget Function(
typedef AFBoardColumnHeaderBuilder = Widget Function(
BuildContext context,
BoardColumnData columnData,
AFBoardColumnData columnData,
);
typedef BoardColumnFooterBuilder = Widget Function(
typedef AFBoardColumnFooterBuilder = Widget Function(
BuildContext context,
BoardColumnData columnData,
AFBoardColumnData columnData,
);
abstract class BoardColumnDataDataSource extends ReoderFlextDataSource {
BoardColumnData get columnData;
abstract class AFBoardColumnDataDataSource extends ReoderFlextDataSource {
AFBoardColumnData get columnData;
List<String> get acceptedColumnIds;
@ -46,10 +46,10 @@ abstract class BoardColumnDataDataSource extends ReoderFlextDataSource {
String get identifier => columnData.id;
@override
UnmodifiableListView<ColumnItem> get items => columnData.items;
UnmodifiableListView<AFColumnItem> get items => columnData.items;
void debugPrint() {
String msg = '[$BoardColumnDataDataSource] $columnData data: ';
String msg = '[$AFBoardColumnDataDataSource] $columnData data: ';
for (var element in items) {
msg = '$msg$element,';
}
@ -58,10 +58,10 @@ abstract class BoardColumnDataDataSource extends ReoderFlextDataSource {
}
}
/// [BoardColumnWidget] represents the column of the Board.
/// [AFBoardColumnWidget] represents the column of the Board.
///
class BoardColumnWidget extends StatefulWidget {
final BoardColumnDataDataSource dataSource;
class AFBoardColumnWidget extends StatefulWidget {
final AFBoardColumnDataDataSource dataSource;
final ScrollController? scrollController;
final ReorderFlexConfig config;
@ -73,11 +73,11 @@ class BoardColumnWidget extends StatefulWidget {
String get columnId => dataSource.columnData.id;
final BoardColumnCardBuilder cardBuilder;
final AFBoardColumnCardBuilder cardBuilder;
final BoardColumnHeaderBuilder? headerBuilder;
final AFBoardColumnHeaderBuilder? headerBuilder;
final BoardColumnFooterBuilder? footBuilder;
final AFBoardColumnFooterBuilder? footBuilder;
final EdgeInsets margin;
@ -87,7 +87,7 @@ class BoardColumnWidget extends StatefulWidget {
final Color backgroundColor;
const BoardColumnWidget({
const AFBoardColumnWidget({
Key? key,
this.headerBuilder,
this.footBuilder,
@ -106,12 +106,12 @@ class BoardColumnWidget extends StatefulWidget {
super(key: key);
@override
State<BoardColumnWidget> createState() => _BoardColumnWidgetState();
State<AFBoardColumnWidget> createState() => _AFBoardColumnWidgetState();
}
class _BoardColumnWidgetState extends State<BoardColumnWidget> {
class _AFBoardColumnWidgetState extends State<AFBoardColumnWidget> {
final GlobalKey _columnOverlayKey =
GlobalKey(debugLabel: '$BoardColumnWidget overlay key');
GlobalKey(debugLabel: '$AFBoardColumnWidget overlay key');
late BoardOverlayEntry _overlayEntry;
@ -194,7 +194,7 @@ class _BoardColumnWidgetState extends State<BoardColumnWidget> {
);
}
Widget _buildWidget(BuildContext context, ColumnItem item) {
Widget _buildWidget(BuildContext context, AFColumnItem item) {
if (item is PhantomColumnItem) {
return PassthroughPhantomWidget(
key: UniqueKey(),

View File

@ -5,14 +5,14 @@ import 'package:flutter/material.dart';
import '../../utils/log.dart';
import '../reorder_flex/reorder_flex.dart';
abstract class ColumnItem extends ReoderFlexItem {
abstract class AFColumnItem extends ReoderFlexItem {
bool get isPhantom => false;
@override
String toString() => id;
}
/// [BoardColumnDataController] is used to handle the [BoardColumnData].
/// [BoardColumnDataController] is used to handle the [AFBoardColumnData].
/// * Remove an item by calling [removeAt] method.
/// * Move item to another position by calling [move] method.
/// * Insert item to index by calling [insert] method
@ -21,7 +21,7 @@ abstract class ColumnItem extends ReoderFlexItem {
/// All there operations will notify listeners by default.
///
class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
final BoardColumnData columnData;
final AFBoardColumnData columnData;
BoardColumnDataController({
required this.columnData,
@ -31,7 +31,7 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
List<Object?> get props => columnData.props;
/// Returns the readonly List<ColumnItem>
UnmodifiableListView<ColumnItem> get items =>
UnmodifiableListView<AFColumnItem> get items =>
UnmodifiableListView(columnData.items);
/// Remove the item at [index].
@ -39,7 +39,7 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
/// * [notify] the default value of [notify] is true, it will notify the
/// listener. Set to [false] if you do not want to notify the listeners.
///
ColumnItem removeAt(int index, {bool notify = true}) {
AFColumnItem removeAt(int index, {bool notify = true}) {
assert(index >= 0);
Log.debug('[$BoardColumnDataController] $columnData remove item at $index');
@ -50,31 +50,32 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
return item;
}
int removeWhere(bool Function(ColumnItem) condition) {
int removeWhere(bool Function(AFColumnItem) condition) {
return items.indexWhere(condition);
}
/// 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, AFColumnItem item, {bool notify = true}) {
assert(index >= 0);
Log.debug(
'[$BoardColumnDataController] $columnData insert $item at $index');
@ -85,13 +86,18 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
columnData._items.add(item);
}
if (notify) {
notifyListeners();
}
if (notify) notifyListeners();
return true;
}
bool add(AFColumnItem item, {bool notify = true}) {
columnData._items.add(item);
if (notify) notifyListeners();
return true;
}
/// Replace the item at index with the [newItem].
void replace(int index, ColumnItem newItem) {
void replace(int index, AFColumnItem newItem) {
if (columnData._items.isEmpty) {
columnData._items.add(newItem);
Log.debug('[$BoardColumnDataController] $columnData add $newItem');
@ -106,19 +112,23 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin {
}
}
/// [BoardColumnData] represents the data of each Column of the Board.
class BoardColumnData extends ReoderFlexItem with EquatableMixin {
/// [AFBoardColumnData] represents the data of each Column of the Board.
class AFBoardColumnData<CustomData> extends ReoderFlexItem with EquatableMixin {
@override
final String id;
final List<ColumnItem> _items;
final String desc;
final List<AFColumnItem> _items;
final CustomData? customData;
BoardColumnData({
AFBoardColumnData({
this.customData,
required this.id,
required List<ColumnItem> items,
this.desc = "",
List<AFColumnItem> items = const [],
}) : _items = items;
/// Returns the readonly List<ColumnItem>
UnmodifiableListView<ColumnItem> get items => UnmodifiableListView(_items);
UnmodifiableListView<AFColumnItem> get items => UnmodifiableListView(_items);
@override
List<Object?> get props => [id, ..._items];

View File

@ -23,14 +23,14 @@ typedef OnMoveColumnItemToColumn = void Function(
int toIndex,
);
class BoardDataController extends ChangeNotifier
class AFBoardDataController extends ChangeNotifier
with EquatableMixin, BoardPhantomControllerDelegate, ReoderFlextDataSource {
final List<BoardColumnData> _columnDatas = [];
final List<AFBoardColumnData> _columnDatas = [];
final OnMoveColumn? onMoveColumn;
final OnMoveColumnItem? onMoveColumnItem;
final OnMoveColumnItemToColumn? onMoveColumnItemToColumn;
List<BoardColumnData> get columnDatas => _columnDatas;
List<AFBoardColumnData> get columnDatas => _columnDatas;
List<String> get columnIds =>
_columnDatas.map((columnData) => columnData.id).toList();
@ -38,38 +38,90 @@ class BoardDataController extends ChangeNotifier
final LinkedHashMap<String, BoardColumnDataController> _columnControllers =
LinkedHashMap();
BoardDataController({
AFBoardDataController({
this.onMoveColumn,
this.onMoveColumnItem,
this.onMoveColumnItemToColumn,
});
void addColumn(BoardColumnData columnData) {
void addColumn(AFBoardColumnData 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<AFBoardColumnData> 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, AFColumnItem item) {
getColumnController(columnId)?.add(item);
}
void insertColumnItem(String columnId, int index, AFColumnItem item) {
getColumnController(columnId)?.insert(index, item);
}
void removeColumnItem(String columnId, String itemId) {
getColumnController(columnId)?.removeWhere((item) => item.id == itemId);
}
@override
@protected
void swapColumnItem(
@ -106,7 +158,7 @@ class BoardDataController extends ChangeNotifier
}
@override
String get identifier => '$BoardDataController';
String get identifier => '$AFBoardDataController';
@override
UnmodifiableListView<ReoderFlexItem> get items =>
@ -123,7 +175,7 @@ class BoardDataController extends ChangeNotifier
columnController.removeAt(index);
Log.debug(
'[$BoardDataController] Column:[$columnId] remove phantom, current count: ${columnController.items.length}');
'[$AFBoardDataController] Column:[$columnId] remove phantom, current count: ${columnController.items.length}');
}
return isExist;
}

View File

@ -253,7 +253,7 @@ class PhantomRecord {
}
}
class PhantomColumnItem extends ColumnItem {
class PhantomColumnItem extends AFColumnItem {
final PassthroughPhantomContext phantomContext;
PhantomColumnItem(PassthroughPhantomContext insertedPhantom)
@ -290,7 +290,7 @@ class PassthroughPhantomContext extends FakeDragTargetEventTrigger
Widget? get draggingWidget => dragTargetData.draggingWidget;
ColumnItem get itemData => dragTargetData.reorderFlexItem as ColumnItem;
AFColumnItem get itemData => dragTargetData.reorderFlexItem as AFColumnItem;
@override
VoidCallback? onInserted;

Some files were not shown because too many files have changed in this diff Show More