mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: card ui
This commit is contained in:
@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:app_flowy/plugins/grid/application/block/block_cache.dart';
|
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/row/row_cache.dart';
|
import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
|
||||||
import 'package:appflowy_board/appflowy_board.dart';
|
import 'package:appflowy_board/appflowy_board.dart';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
@ -20,6 +21,9 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
final BoardDataController _dataController;
|
final BoardDataController _dataController;
|
||||||
late final AFBoardDataController boardDataController;
|
late final AFBoardDataController boardDataController;
|
||||||
|
|
||||||
|
GridFieldCache get fieldCache => _dataController.fieldCache;
|
||||||
|
String get gridId => _dataController.gridId;
|
||||||
|
|
||||||
BoardBloc({required ViewPB view})
|
BoardBloc({required ViewPB view})
|
||||||
: _dataController = BoardDataController(view: view),
|
: _dataController = BoardDataController(view: view),
|
||||||
super(BoardState.initial(view.id)) {
|
super(BoardState.initial(view.id)) {
|
||||||
@ -57,6 +61,9 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
didReceiveGroups: (List<GroupPB> groups) {
|
didReceiveGroups: (List<GroupPB> groups) {
|
||||||
emit(state.copyWith(groups: groups));
|
emit(state.copyWith(groups: groups));
|
||||||
},
|
},
|
||||||
|
didReceiveRows: (List<RowInfo> rowInfos) {
|
||||||
|
emit(state.copyWith(rowInfos: rowInfos));
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -68,7 +75,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
return super.close();
|
return super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
GridRowCache? getRowCache(String blockId, String rowId) {
|
GridRowCache? getRowCache(String blockId) {
|
||||||
final GridBlockCache? blockCache = _dataController.blocks[blockId];
|
final GridBlockCache? blockCache = _dataController.blocks[blockId];
|
||||||
return blockCache?.rowCache;
|
return blockCache?.rowCache;
|
||||||
}
|
}
|
||||||
@ -92,6 +99,9 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
|
|
||||||
boardDataController.addColumns(columns);
|
boardDataController.addColumns(columns);
|
||||||
},
|
},
|
||||||
|
onRowsChanged: (List<RowInfo> rowInfos, RowChangeReason reason) {
|
||||||
|
add(BoardEvent.didReceiveRows(rowInfos));
|
||||||
|
},
|
||||||
onError: (err) {
|
onError: (err) {
|
||||||
Log.error(err);
|
Log.error(err);
|
||||||
},
|
},
|
||||||
@ -100,15 +110,15 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
|
|
||||||
List<BoardColumnItem> _buildRows(List<RowPB> rows) {
|
List<BoardColumnItem> _buildRows(List<RowPB> rows) {
|
||||||
return rows.map((row) {
|
return rows.map((row) {
|
||||||
final rowInfo = RowInfo(
|
// final rowInfo = RowInfo(
|
||||||
gridId: _dataController.gridId,
|
// gridId: _dataController.gridId,
|
||||||
blockId: row.blockId,
|
// blockId: row.blockId,
|
||||||
id: row.id,
|
// id: row.id,
|
||||||
fields: _dataController.fieldCache.unmodifiableFields,
|
// fields: _dataController.fieldCache.unmodifiableFields,
|
||||||
height: row.height.toDouble(),
|
// height: row.height.toDouble(),
|
||||||
rawRow: row,
|
// rawRow: row,
|
||||||
);
|
// );
|
||||||
return BoardColumnItem(row: rowInfo);
|
return BoardColumnItem(row: row);
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,6 +141,8 @@ class BoardEvent with _$BoardEvent {
|
|||||||
const factory BoardEvent.createRow() = _CreateRow;
|
const factory BoardEvent.createRow() = _CreateRow;
|
||||||
const factory BoardEvent.didReceiveGroups(List<GroupPB> groups) =
|
const factory BoardEvent.didReceiveGroups(List<GroupPB> groups) =
|
||||||
_DidReceiveGroup;
|
_DidReceiveGroup;
|
||||||
|
const factory BoardEvent.didReceiveRows(List<RowInfo> rowInfos) =
|
||||||
|
_DidReceiveRows;
|
||||||
const factory BoardEvent.didReceiveGridUpdate(
|
const factory BoardEvent.didReceiveGridUpdate(
|
||||||
GridPB grid,
|
GridPB grid,
|
||||||
) = _DidReceiveGridUpdate;
|
) = _DidReceiveGridUpdate;
|
||||||
@ -186,7 +198,7 @@ class GridFieldEquatable extends Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class BoardColumnItem extends AFColumnItem {
|
class BoardColumnItem extends AFColumnItem {
|
||||||
final RowInfo row;
|
final RowPB row;
|
||||||
|
|
||||||
BoardColumnItem({required this.row});
|
BoardColumnItem({required this.row});
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@ import 'dart:collection';
|
|||||||
import 'package:app_flowy/plugins/grid/application/block/block_cache.dart';
|
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/field/field_cache.dart';
|
||||||
import 'package:app_flowy/plugins/grid/application/grid_service.dart';
|
import 'package:app_flowy/plugins/grid/application/grid_service.dart';
|
||||||
|
import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
|
||||||
|
import 'package:flowy_sdk/log.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.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-folder/view.pb.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
@ -12,6 +14,10 @@ import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
|
|||||||
typedef OnFieldsChanged = void Function(UnmodifiableListView<FieldPB>);
|
typedef OnFieldsChanged = void Function(UnmodifiableListView<FieldPB>);
|
||||||
typedef OnGridChanged = void Function(GridPB);
|
typedef OnGridChanged = void Function(GridPB);
|
||||||
typedef OnGroupChanged = void Function(List<GroupPB>);
|
typedef OnGroupChanged = void Function(List<GroupPB>);
|
||||||
|
typedef OnRowsChanged = void Function(
|
||||||
|
List<RowInfo> rowInfos,
|
||||||
|
RowChangeReason,
|
||||||
|
);
|
||||||
typedef OnError = void Function(FlowyError);
|
typedef OnError = void Function(FlowyError);
|
||||||
|
|
||||||
class BoardDataController {
|
class BoardDataController {
|
||||||
@ -21,17 +27,25 @@ class BoardDataController {
|
|||||||
|
|
||||||
// key: the block id
|
// key: the block id
|
||||||
final LinkedHashMap<String, GridBlockCache> _blocks;
|
final LinkedHashMap<String, GridBlockCache> _blocks;
|
||||||
UnmodifiableMapView<String, GridBlockCache> get blocks =>
|
LinkedHashMap<String, GridBlockCache> get blocks => _blocks;
|
||||||
UnmodifiableMapView(_blocks);
|
|
||||||
|
|
||||||
OnFieldsChanged? _onFieldsChanged;
|
OnFieldsChanged? _onFieldsChanged;
|
||||||
OnGridChanged? _onGridChanged;
|
OnGridChanged? _onGridChanged;
|
||||||
OnGroupChanged? _onGroupChanged;
|
OnGroupChanged? _onGroupChanged;
|
||||||
|
OnRowsChanged? _onRowsChanged;
|
||||||
OnError? _onError;
|
OnError? _onError;
|
||||||
|
|
||||||
|
List<RowInfo> get rowInfos {
|
||||||
|
final List<RowInfo> rows = [];
|
||||||
|
for (var block in _blocks.values) {
|
||||||
|
rows.addAll(block.rows);
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
BoardDataController({required ViewPB view})
|
BoardDataController({required ViewPB view})
|
||||||
: gridId = view.id,
|
: gridId = view.id,
|
||||||
_blocks = LinkedHashMap.identity(),
|
_blocks = LinkedHashMap.new(),
|
||||||
_gridFFIService = GridService(gridId: view.id),
|
_gridFFIService = GridService(gridId: view.id),
|
||||||
fieldCache = GridFieldCache(gridId: view.id);
|
fieldCache = GridFieldCache(gridId: view.id);
|
||||||
|
|
||||||
@ -39,11 +53,13 @@ class BoardDataController {
|
|||||||
OnGridChanged? onGridChanged,
|
OnGridChanged? onGridChanged,
|
||||||
OnFieldsChanged? onFieldsChanged,
|
OnFieldsChanged? onFieldsChanged,
|
||||||
OnGroupChanged? onGroupChanged,
|
OnGroupChanged? onGroupChanged,
|
||||||
|
OnRowsChanged? onRowsChanged,
|
||||||
OnError? onError,
|
OnError? onError,
|
||||||
}) {
|
}) {
|
||||||
_onGridChanged = onGridChanged;
|
_onGridChanged = onGridChanged;
|
||||||
_onFieldsChanged = onFieldsChanged;
|
_onFieldsChanged = onFieldsChanged;
|
||||||
_onGroupChanged = onGroupChanged;
|
_onGroupChanged = onGroupChanged;
|
||||||
|
_onRowsChanged = onRowsChanged;
|
||||||
_onError = onError;
|
_onError = onError;
|
||||||
|
|
||||||
fieldCache.addListener(onFields: (fields) {
|
fieldCache.addListener(onFields: (fields) {
|
||||||
@ -57,6 +73,7 @@ class BoardDataController {
|
|||||||
() => result.fold(
|
() => result.fold(
|
||||||
(grid) async {
|
(grid) async {
|
||||||
_onGridChanged?.call(grid);
|
_onGridChanged?.call(grid);
|
||||||
|
_initialBlocks(grid.blocks);
|
||||||
return await _loadFields(grid).then((result) {
|
return await _loadFields(grid).then((result) {
|
||||||
return result.fold(
|
return result.fold(
|
||||||
(l) {
|
(l) {
|
||||||
@ -85,6 +102,29 @@ class BoardDataController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _initialBlocks(List<BlockPB> blocks) {
|
||||||
|
for (final block in blocks) {
|
||||||
|
if (_blocks[block.id] != null) {
|
||||||
|
Log.warn("Initial duplicate block's cache: ${block.id}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final cache = GridBlockCache(
|
||||||
|
gridId: gridId,
|
||||||
|
block: block,
|
||||||
|
fieldCache: fieldCache,
|
||||||
|
);
|
||||||
|
|
||||||
|
cache.addListener(
|
||||||
|
onChangeReason: (reason) {
|
||||||
|
_onRowsChanged?.call(rowInfos, reason);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
_blocks[block.id] = cache;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<Either<Unit, FlowyError>> _loadFields(GridPB grid) async {
|
Future<Either<Unit, FlowyError>> _loadFields(GridPB grid) async {
|
||||||
final result = await _gridFFIService.getFields(fieldIds: grid.fields);
|
final result = await _gridFFIService.getFields(fieldIds: grid.fields);
|
||||||
return Future(
|
return Future(
|
||||||
|
@ -0,0 +1,111 @@
|
|||||||
|
import 'dart:collection';
|
||||||
|
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||||
|
import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
|
||||||
|
import 'package:app_flowy/plugins/grid/application/row/row_service.dart';
|
||||||
|
import 'package:equatable/equatable.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:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'card_data_controller.dart';
|
||||||
|
|
||||||
|
part 'card_bloc.freezed.dart';
|
||||||
|
|
||||||
|
class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
|
||||||
|
final RowFFIService _rowService;
|
||||||
|
final CardDataController _dataController;
|
||||||
|
|
||||||
|
BoardCardBloc({
|
||||||
|
required String gridId,
|
||||||
|
required CardDataController dataController,
|
||||||
|
}) : _rowService = RowFFIService(
|
||||||
|
gridId: gridId,
|
||||||
|
blockId: dataController.rowPB.blockId,
|
||||||
|
rowId: dataController.rowPB.id,
|
||||||
|
),
|
||||||
|
_dataController = dataController,
|
||||||
|
super(BoardCardState.initial(
|
||||||
|
dataController.rowPB, dataController.loadData())) {
|
||||||
|
on<BoardCardEvent>(
|
||||||
|
(event, emit) async {
|
||||||
|
await event.map(
|
||||||
|
initial: (_InitialRow value) async {
|
||||||
|
await _startListening();
|
||||||
|
},
|
||||||
|
createRow: (_CreateRow value) {
|
||||||
|
_rowService.createRow();
|
||||||
|
},
|
||||||
|
didReceiveCells: (_DidReceiveCells value) async {
|
||||||
|
final cells = value.gridCellMap.values
|
||||||
|
.map((e) => GridCellEquatable(e.field))
|
||||||
|
.toList();
|
||||||
|
emit(state.copyWith(
|
||||||
|
gridCellMap: value.gridCellMap,
|
||||||
|
cells: UnmodifiableListView(cells),
|
||||||
|
changeReason: value.reason,
|
||||||
|
));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() async {
|
||||||
|
_dataController.dispose();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _startListening() async {
|
||||||
|
_dataController.addListener(
|
||||||
|
onRowChanged: (cells, reason) {
|
||||||
|
if (!isClosed) {
|
||||||
|
add(BoardCardEvent.didReceiveCells(cells, reason));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class BoardCardEvent with _$BoardCardEvent {
|
||||||
|
const factory BoardCardEvent.initial() = _InitialRow;
|
||||||
|
const factory BoardCardEvent.createRow() = _CreateRow;
|
||||||
|
const factory BoardCardEvent.didReceiveCells(
|
||||||
|
GridCellMap gridCellMap, RowChangeReason reason) = _DidReceiveCells;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class BoardCardState with _$BoardCardState {
|
||||||
|
const factory BoardCardState({
|
||||||
|
required RowPB rowPB,
|
||||||
|
required GridCellMap gridCellMap,
|
||||||
|
required UnmodifiableListView<GridCellEquatable> cells,
|
||||||
|
RowChangeReason? changeReason,
|
||||||
|
}) = _BoardCardState;
|
||||||
|
|
||||||
|
factory BoardCardState.initial(RowPB rowPB, GridCellMap cellDataMap) =>
|
||||||
|
BoardCardState(
|
||||||
|
rowPB: rowPB,
|
||||||
|
gridCellMap: cellDataMap,
|
||||||
|
cells: UnmodifiableListView(
|
||||||
|
cellDataMap.values.map((e) => GridCellEquatable(e.field)).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class GridCellEquatable extends Equatable {
|
||||||
|
final FieldPB _field;
|
||||||
|
|
||||||
|
const GridCellEquatable(FieldPB field) : _field = field;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
_field.id,
|
||||||
|
_field.fieldType,
|
||||||
|
_field.visibility,
|
||||||
|
_field.width,
|
||||||
|
];
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
import 'package:app_flowy/plugins/board/presentation/card/card_cell_builder.dart';
|
||||||
|
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||||
|
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_field_notifier.dart';
|
||||||
|
import 'package:app_flowy/plugins/grid/application/field/field_cache.dart';
|
||||||
|
import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
|
||||||
|
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
typedef OnCardChanged = void Function(GridCellMap, RowChangeReason);
|
||||||
|
|
||||||
|
class CardDataController extends BoardCellBuilderDelegate {
|
||||||
|
final RowPB rowPB;
|
||||||
|
final GridFieldCache _fieldCache;
|
||||||
|
final GridRowCache _rowCache;
|
||||||
|
final List<VoidCallback> _onCardChangedListeners = [];
|
||||||
|
|
||||||
|
CardDataController({
|
||||||
|
required this.rowPB,
|
||||||
|
required GridFieldCache fieldCache,
|
||||||
|
required GridRowCache rowCache,
|
||||||
|
}) : _fieldCache = fieldCache,
|
||||||
|
_rowCache = rowCache;
|
||||||
|
|
||||||
|
GridCellMap loadData() {
|
||||||
|
return _rowCache.loadGridCells(rowPB.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addListener({OnCardChanged? onRowChanged}) {
|
||||||
|
_onCardChangedListeners.add(_rowCache.addListener(
|
||||||
|
rowId: rowPB.id,
|
||||||
|
onCellUpdated: onRowChanged,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
for (final fn in _onCardChangedListeners) {
|
||||||
|
_rowCache.removeRowListener(fn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
GridCellFieldNotifier buildFieldNotifier() {
|
||||||
|
return GridCellFieldNotifier(
|
||||||
|
notifier: GridCellFieldNotifierImpl(_fieldCache));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
GridCellCache get cellCache => _rowCache.cellCache;
|
||||||
|
}
|
@ -31,7 +31,7 @@ class BoardPluginBuilder implements PluginBuilder {
|
|||||||
|
|
||||||
class BoardPluginConfig implements PluginConfig {
|
class BoardPluginConfig implements PluginConfig {
|
||||||
@override
|
@override
|
||||||
bool get creatable => false;
|
bool get creatable => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BoardPlugin extends Plugin {
|
class BoardPlugin extends Plugin {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
// ignore_for_file: unused_field
|
// ignore_for_file: unused_field
|
||||||
|
|
||||||
|
import 'package:app_flowy/plugins/board/application/card/card_data_controller.dart';
|
||||||
import 'package:appflowy_board/appflowy_board.dart';
|
import 'package:appflowy_board/appflowy_board.dart';
|
||||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||||
@ -7,6 +8,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import '../application/board_bloc.dart';
|
import '../application/board_bloc.dart';
|
||||||
import 'card/card.dart';
|
import 'card/card.dart';
|
||||||
|
import 'card/card_cell_builder.dart';
|
||||||
|
|
||||||
class BoardPage extends StatelessWidget {
|
class BoardPage extends StatelessWidget {
|
||||||
final ViewPB view;
|
final ViewPB view;
|
||||||
@ -51,6 +53,7 @@ class BoardContent extends StatelessWidget {
|
|||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
||||||
child: AFBoard(
|
child: AFBoard(
|
||||||
|
key: UniqueKey(),
|
||||||
dataController: context.read<BoardBloc>().boardDataController,
|
dataController: context.read<BoardBloc>().boardDataController,
|
||||||
headerBuilder: _buildHeader,
|
headerBuilder: _buildHeader,
|
||||||
footBuilder: _buildFooter,
|
footBuilder: _buildFooter,
|
||||||
@ -87,10 +90,29 @@ class BoardContent extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCard(BuildContext context, AFColumnItem item) {
|
Widget _buildCard(BuildContext context, AFColumnItem item) {
|
||||||
final rowInfo = (item as BoardColumnItem).row;
|
final rowPB = (item as BoardColumnItem).row;
|
||||||
|
final rowCache = context.read<BoardBloc>().getRowCache(rowPB.blockId);
|
||||||
|
|
||||||
|
/// Return placeholder widget if the rowCache is null.
|
||||||
|
if (rowCache == null) return SizedBox(key: ObjectKey(item));
|
||||||
|
|
||||||
|
final fieldCache = context.read<BoardBloc>().fieldCache;
|
||||||
|
final gridId = context.read<BoardBloc>().gridId;
|
||||||
|
final cardController = CardDataController(
|
||||||
|
fieldCache: fieldCache,
|
||||||
|
rowCache: rowCache,
|
||||||
|
rowPB: rowPB,
|
||||||
|
);
|
||||||
|
|
||||||
|
final cellBuilder = BoardCellBuilder(cardController);
|
||||||
|
|
||||||
return AppFlowyColumnItemCard(
|
return AppFlowyColumnItemCard(
|
||||||
key: ObjectKey(item),
|
key: ObjectKey(item),
|
||||||
child: BoardCard(rowInfo: rowInfo),
|
child: BoardCard(
|
||||||
|
cellBuilder: cellBuilder,
|
||||||
|
dataController: cardController,
|
||||||
|
gridId: gridId,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class BoardCheckboxCell extends StatefulWidget {
|
||||||
|
final GridCellControllerBuilder cellControllerBuilder;
|
||||||
|
|
||||||
|
const BoardCheckboxCell({
|
||||||
|
required this.cellControllerBuilder,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BoardCheckboxCell> createState() => _BoardCheckboxCellState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BoardCheckboxCellState extends State<BoardCheckboxCell> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class BoardDateCell extends StatefulWidget {
|
||||||
|
final GridCellControllerBuilder cellControllerBuilder;
|
||||||
|
|
||||||
|
const BoardDateCell({
|
||||||
|
required this.cellControllerBuilder,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BoardDateCell> createState() => _BoardDateCellState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BoardDateCellState extends State<BoardDateCell> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class BoardNumberCell extends StatefulWidget {
|
||||||
|
final GridCellControllerBuilder cellControllerBuilder;
|
||||||
|
|
||||||
|
const BoardNumberCell({
|
||||||
|
required this.cellControllerBuilder,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BoardNumberCell> createState() => _BoardNumberCellState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BoardNumberCellState extends State<BoardNumberCell> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class BoardUrlCell extends StatefulWidget {
|
||||||
|
final GridCellControllerBuilder cellControllerBuilder;
|
||||||
|
|
||||||
|
const BoardUrlCell({
|
||||||
|
required this.cellControllerBuilder,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BoardUrlCell> createState() => _BoardUrlCellState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BoardUrlCellState extends State<BoardUrlCell> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,63 @@
|
|||||||
import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
|
import 'package:app_flowy/plugins/board/application/card/card_bloc.dart';
|
||||||
|
import 'package:app_flowy/plugins/board/application/card/card_data_controller.dart';
|
||||||
|
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class BoardCard extends StatelessWidget {
|
import 'card_cell_builder.dart';
|
||||||
final RowInfo rowInfo;
|
|
||||||
|
|
||||||
const BoardCard({required this.rowInfo, Key? key}) : super(key: key);
|
class BoardCard extends StatefulWidget {
|
||||||
|
final String gridId;
|
||||||
|
final CardDataController dataController;
|
||||||
|
final BoardCellBuilder cellBuilder;
|
||||||
|
|
||||||
|
const BoardCard({
|
||||||
|
required this.gridId,
|
||||||
|
required this.dataController,
|
||||||
|
required this.cellBuilder,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BoardCard> createState() => _BoardCardState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BoardCardState extends State<BoardCard> {
|
||||||
|
late BoardCardBloc _cardBloc;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_cardBloc = BoardCardBloc(
|
||||||
|
gridId: widget.gridId,
|
||||||
|
dataController: widget.dataController,
|
||||||
|
);
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return const SizedBox(height: 20, child: Text('1234'));
|
return BlocProvider.value(
|
||||||
|
value: _cardBloc,
|
||||||
|
child: BlocBuilder<BoardCardBloc, BoardCardState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return SizedBox(
|
||||||
|
height: 100,
|
||||||
|
child: Column(
|
||||||
|
children: _makeCells(context, state.gridCellMap),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _makeCells(BuildContext context, GridCellMap cellMap) {
|
||||||
|
return cellMap.values.map(
|
||||||
|
(cellId) {
|
||||||
|
final child = widget.cellBuilder.buildCell(cellId);
|
||||||
|
|
||||||
|
return SizedBox(height: 39, child: child);
|
||||||
|
},
|
||||||
|
).toList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||||
|
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'board_checkbox_cell.dart';
|
||||||
|
import 'board_date_cell.dart';
|
||||||
|
import 'board_number_cell.dart';
|
||||||
|
import 'board_select_option_cell.dart';
|
||||||
|
import 'board_text_cell.dart';
|
||||||
|
import 'board_url_cell.dart';
|
||||||
|
|
||||||
|
abstract class BoardCellBuilderDelegate
|
||||||
|
extends GridCellControllerBuilderDelegate {
|
||||||
|
GridCellCache get cellCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
class BoardCellBuilder {
|
||||||
|
final BoardCellBuilderDelegate delegate;
|
||||||
|
|
||||||
|
BoardCellBuilder(this.delegate);
|
||||||
|
|
||||||
|
Widget buildCell(GridCellIdentifier cellId) {
|
||||||
|
final cellControllerBuilder = GridCellControllerBuilder(
|
||||||
|
delegate: delegate,
|
||||||
|
cellId: cellId,
|
||||||
|
cellCache: delegate.cellCache,
|
||||||
|
);
|
||||||
|
|
||||||
|
final key = cellId.key();
|
||||||
|
switch (cellId.fieldType) {
|
||||||
|
case FieldType.Checkbox:
|
||||||
|
return BoardCheckboxCell(
|
||||||
|
cellControllerBuilder: cellControllerBuilder,
|
||||||
|
key: key,
|
||||||
|
);
|
||||||
|
case FieldType.DateTime:
|
||||||
|
return BoardDateCell(
|
||||||
|
cellControllerBuilder: cellControllerBuilder,
|
||||||
|
key: key,
|
||||||
|
);
|
||||||
|
case FieldType.SingleSelect:
|
||||||
|
return BoardSelectOptionCell(
|
||||||
|
cellControllerBuilder: cellControllerBuilder,
|
||||||
|
key: key,
|
||||||
|
);
|
||||||
|
case FieldType.MultiSelect:
|
||||||
|
return BoardSelectOptionCell(
|
||||||
|
cellControllerBuilder: cellControllerBuilder,
|
||||||
|
key: key,
|
||||||
|
);
|
||||||
|
case FieldType.Number:
|
||||||
|
return BoardNumberCell(
|
||||||
|
cellControllerBuilder: cellControllerBuilder,
|
||||||
|
key: key,
|
||||||
|
);
|
||||||
|
case FieldType.RichText:
|
||||||
|
return BoardTextCell(
|
||||||
|
cellControllerBuilder: cellControllerBuilder,
|
||||||
|
key: key,
|
||||||
|
);
|
||||||
|
case FieldType.URL:
|
||||||
|
return BoardUrlCell(
|
||||||
|
cellControllerBuilder: cellControllerBuilder,
|
||||||
|
key: key,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw UnimplementedError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -46,7 +46,7 @@ class GridDataController {
|
|||||||
|
|
||||||
GridDataController({required ViewPB view})
|
GridDataController({required ViewPB view})
|
||||||
: gridId = view.id,
|
: gridId = view.id,
|
||||||
_blocks = LinkedHashMap.identity(),
|
_blocks = LinkedHashMap.new(),
|
||||||
_gridFFIService = GridService(gridId: view.id),
|
_gridFFIService = GridService(gridId: view.id),
|
||||||
fieldCache = GridFieldCache(gridId: view.id);
|
fieldCache = GridFieldCache(gridId: view.id);
|
||||||
|
|
||||||
|
@ -12,10 +12,10 @@ part 'row_action_sheet_bloc.freezed.dart';
|
|||||||
|
|
||||||
class RowActionSheetBloc
|
class RowActionSheetBloc
|
||||||
extends Bloc<RowActionSheetEvent, RowActionSheetState> {
|
extends Bloc<RowActionSheetEvent, RowActionSheetState> {
|
||||||
final RowService _rowService;
|
final RowFFIService _rowService;
|
||||||
|
|
||||||
RowActionSheetBloc({required RowInfo rowData})
|
RowActionSheetBloc({required RowInfo rowData})
|
||||||
: _rowService = RowService(
|
: _rowService = RowFFIService(
|
||||||
gridId: rowData.gridId,
|
gridId: rowData.gridId,
|
||||||
blockId: rowData.blockId,
|
blockId: rowData.blockId,
|
||||||
rowId: rowData.id,
|
rowId: rowData.id,
|
||||||
|
@ -12,13 +12,13 @@ import 'row_service.dart';
|
|||||||
part 'row_bloc.freezed.dart';
|
part 'row_bloc.freezed.dart';
|
||||||
|
|
||||||
class RowBloc extends Bloc<RowEvent, RowState> {
|
class RowBloc extends Bloc<RowEvent, RowState> {
|
||||||
final RowService _rowService;
|
final RowFFIService _rowService;
|
||||||
final GridRowDataController _dataController;
|
final GridRowDataController _dataController;
|
||||||
|
|
||||||
RowBloc({
|
RowBloc({
|
||||||
required RowInfo rowInfo,
|
required RowInfo rowInfo,
|
||||||
required GridRowDataController dataController,
|
required GridRowDataController dataController,
|
||||||
}) : _rowService = RowService(
|
}) : _rowService = RowFFIService(
|
||||||
gridId: rowInfo.gridId,
|
gridId: rowInfo.gridId,
|
||||||
blockId: rowInfo.blockId,
|
blockId: rowInfo.blockId,
|
||||||
rowId: rowInfo.id,
|
rowId: rowInfo.id,
|
||||||
@ -35,13 +35,12 @@ class RowBloc extends Bloc<RowEvent, RowState> {
|
|||||||
_rowService.createRow();
|
_rowService.createRow();
|
||||||
},
|
},
|
||||||
didReceiveCells: (_DidReceiveCells value) async {
|
didReceiveCells: (_DidReceiveCells value) async {
|
||||||
final fields = value.gridCellMap.values
|
final cells = value.gridCellMap.values
|
||||||
.map((e) => GridCellEquatable(e.field))
|
.map((e) => GridCellEquatable(e.field))
|
||||||
.toList();
|
.toList();
|
||||||
final snapshots = UnmodifiableListView(fields);
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
gridCellMap: value.gridCellMap,
|
gridCellMap: value.gridCellMap,
|
||||||
snapshots: snapshots,
|
cells: UnmodifiableListView(cells),
|
||||||
changeReason: value.reason,
|
changeReason: value.reason,
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
@ -80,7 +79,7 @@ class RowState with _$RowState {
|
|||||||
const factory RowState({
|
const factory RowState({
|
||||||
required RowInfo rowInfo,
|
required RowInfo rowInfo,
|
||||||
required GridCellMap gridCellMap,
|
required GridCellMap gridCellMap,
|
||||||
required UnmodifiableListView<GridCellEquatable> snapshots,
|
required UnmodifiableListView<GridCellEquatable> cells,
|
||||||
RowChangeReason? changeReason,
|
RowChangeReason? changeReason,
|
||||||
}) = _RowState;
|
}) = _RowState;
|
||||||
|
|
||||||
@ -88,8 +87,9 @@ class RowState with _$RowState {
|
|||||||
RowState(
|
RowState(
|
||||||
rowInfo: rowInfo,
|
rowInfo: rowInfo,
|
||||||
gridCellMap: cellDataMap,
|
gridCellMap: cellDataMap,
|
||||||
snapshots: UnmodifiableListView(
|
cells: UnmodifiableListView(
|
||||||
cellDataMap.values.map((e) => GridCellEquatable(e.field)).toList()),
|
cellDataMap.values.map((e) => GridCellEquatable(e.field)).toList(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,10 +13,6 @@ class GridRowDataController extends GridCellBuilderDelegate {
|
|||||||
final GridFieldCache _fieldCache;
|
final GridFieldCache _fieldCache;
|
||||||
final GridRowCache _rowCache;
|
final GridRowCache _rowCache;
|
||||||
|
|
||||||
GridFieldCache get fieldCache => _fieldCache;
|
|
||||||
|
|
||||||
GridRowCache get rowCache => _rowCache;
|
|
||||||
|
|
||||||
GridRowDataController({
|
GridRowDataController({
|
||||||
required this.rowInfo,
|
required this.rowInfo,
|
||||||
required GridFieldCache fieldCache,
|
required GridFieldCache fieldCache,
|
||||||
@ -49,5 +45,5 @@ class GridRowDataController extends GridCellBuilderDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
GridCellCache get cellCache => rowCache.cellCache;
|
GridCellCache get cellCache => _rowCache.cellCache;
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,12 @@ import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
|
|||||||
import 'package:flowy_sdk/protobuf/flowy-grid/grid_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:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
|
||||||
|
|
||||||
class RowService {
|
class RowFFIService {
|
||||||
final String gridId;
|
final String gridId;
|
||||||
final String blockId;
|
final String blockId;
|
||||||
final String rowId;
|
final String rowId;
|
||||||
|
|
||||||
RowService(
|
RowFFIService(
|
||||||
{required this.gridId, required this.blockId, required this.rowId});
|
{required this.gridId, required this.blockId, required this.rowId});
|
||||||
|
|
||||||
Future<Either<RowPB, FlowyError>> createRow() {
|
Future<Either<RowPB, FlowyError>> createRow() {
|
||||||
|
@ -164,7 +164,7 @@ class RowContent extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<RowBloc, RowState>(
|
return BlocBuilder<RowBloc, RowState>(
|
||||||
buildWhen: (previous, current) =>
|
buildWhen: (previous, current) =>
|
||||||
!listEquals(previous.snapshots, current.snapshots),
|
!listEquals(previous.cells, current.cells),
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return IntrinsicHeight(
|
return IntrinsicHeight(
|
||||||
child: Row(
|
child: Row(
|
||||||
|
@ -53,7 +53,7 @@ class MenuUser extends StatelessWidget {
|
|||||||
borderRadius: Corners.s5Border,
|
borderRadius: Corners.s5Border,
|
||||||
child: CircleAvatar(
|
child: CircleAvatar(
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
child: svgWidget('emoji/$iconUrl'),
|
child: Container(),
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -150,7 +150,7 @@ impl FolderTest {
|
|||||||
// assert_eq!(json, expected_json);
|
// assert_eq!(json, expected_json);
|
||||||
// }
|
// }
|
||||||
FolderScript::AssertWorkspace(workspace) => {
|
FolderScript::AssertWorkspace(workspace) => {
|
||||||
assert_eq!(self.workspace, workspace);
|
assert_eq!(self.workspace, workspace, "Workspace not equal");
|
||||||
}
|
}
|
||||||
FolderScript::ReadWorkspace(workspace_id) => {
|
FolderScript::ReadWorkspace(workspace_id) => {
|
||||||
let workspace = read_workspace(sdk, workspace_id).await.pop().unwrap();
|
let workspace = read_workspace(sdk, workspace_id).await.pop().unwrap();
|
||||||
@ -166,7 +166,7 @@ impl FolderTest {
|
|||||||
// assert_eq!(json, expected_json);
|
// assert_eq!(json, expected_json);
|
||||||
// }
|
// }
|
||||||
FolderScript::AssertApp(app) => {
|
FolderScript::AssertApp(app) => {
|
||||||
assert_eq!(self.app, app);
|
assert_eq!(self.app, app, "App not equal");
|
||||||
}
|
}
|
||||||
FolderScript::ReadApp(app_id) => {
|
FolderScript::ReadApp(app_id) => {
|
||||||
let app = read_app(sdk, &app_id).await;
|
let app = read_app(sdk, &app_id).await;
|
||||||
@ -184,7 +184,7 @@ impl FolderTest {
|
|||||||
self.view = view;
|
self.view = view;
|
||||||
}
|
}
|
||||||
FolderScript::AssertView(view) => {
|
FolderScript::AssertView(view) => {
|
||||||
assert_eq!(self.view, view);
|
assert_eq!(self.view, view, "View not equal");
|
||||||
}
|
}
|
||||||
FolderScript::ReadView(view_id) => {
|
FolderScript::ReadView(view_id) => {
|
||||||
let view = read_view(sdk, &view_id).await;
|
let view = read_view(sdk, &view_id).await;
|
||||||
@ -215,7 +215,7 @@ impl FolderTest {
|
|||||||
}
|
}
|
||||||
FolderScript::AssertRevisionState { rev_id, state } => {
|
FolderScript::AssertRevisionState { rev_id, state } => {
|
||||||
let record = cache.get(rev_id).await.unwrap();
|
let record = cache.get(rev_id).await.unwrap();
|
||||||
assert_eq!(record.state, state);
|
assert_eq!(record.state, state, "Revision state is not match");
|
||||||
if let RevisionState::Ack = state {
|
if let RevisionState::Ack = state {
|
||||||
// There is a defer action that writes the revisions to disk, so we wait here.
|
// There is a defer action that writes the revisions to disk, so we wait here.
|
||||||
// Make sure everything is written.
|
// Make sure everything is written.
|
||||||
@ -235,7 +235,7 @@ impl FolderTest {
|
|||||||
.unwrap_or_else(|| panic!("Expected Next revision is {}, but receive None", rev_id.unwrap()));
|
.unwrap_or_else(|| panic!("Expected Next revision is {}, but receive None", rev_id.unwrap()));
|
||||||
let mut notify = rev_manager.ack_notify();
|
let mut notify = rev_manager.ack_notify();
|
||||||
let _ = notify.recv().await;
|
let _ = notify.recv().await;
|
||||||
assert_eq!(next_revision.rev_id, rev_id.unwrap());
|
assert_eq!(next_revision.rev_id, rev_id.unwrap(), "Revision id not match");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user