import 'dart:async'; 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: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 { final BoardDataController _dataController; late final AFBoardDataController boardDataController; GridFieldCache get fieldCache => _dataController.fieldCache; String get gridId => _dataController.gridId; 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( (event, emit) async { await event.when( initial: () async { _startListening(); await _loadGrid(emit); }, createRow: () async { final result = await _dataController.createRow(); result.fold( (rowPB) { emit(state.copyWith(editingRow: some(rowPB))); }, (err) => Log.error(err), ); }, endEditRow: (rowId) { assert(state.editingRow.isSome()); state.editingRow.fold(() => null, (row) { assert(row.id == rowId); emit(state.copyWith(editingRow: none())); }); }, didReceiveGridUpdate: (GridPB grid) { emit(state.copyWith(grid: Some(grid))); }, didReceiveGroups: (List groups) { emit(state.copyWith(groups: groups)); }, didReceiveRows: (List rowInfos) { emit(state.copyWith(rowInfos: rowInfos)); }, ); }, ); } @override Future close() async { await _dataController.dispose(); return super.close(); } GridRowCache? getRowCache(String blockId) { final GridBlockCache? blockCache = _dataController.blocks[blockId]; return blockCache?.rowCache; } void _startListening() { _dataController.addListener( onGridChanged: (grid) { if (!isClosed) { add(BoardEvent.didReceiveGridUpdate(grid)); } }, onGroupChanged: (groups) { List columns = groups.map((group) { return AFBoardColumnData( id: group.groupId, desc: group.desc, items: _buildRows(group.rows), customData: group, ); }).toList(); boardDataController.addColumns(columns); }, onRowsChanged: (List rowInfos, RowsChangedReason reason) { add(BoardEvent.didReceiveRows(rowInfos)); }, onError: (err) { Log.error(err); }, ); } List _buildRows(List rows) { final items = 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: row); }).toList(); return [...items]; } Future _loadGrid(Emitter 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.endEditRow(String rowId) = _EndEditRow; const factory BoardEvent.didReceiveGroups(List groups) = _DidReceiveGroup; const factory BoardEvent.didReceiveRows(List rowInfos) = _DidReceiveRows; const factory BoardEvent.didReceiveGridUpdate( GridPB grid, ) = _DidReceiveGridUpdate; } @freezed class BoardState with _$BoardState { const factory BoardState({ required String gridId, required Option grid, required List groups, required Option editingRow, required List rowInfos, required GridLoadingState loadingState, }) = _BoardState; factory BoardState.initial(String gridId) => BoardState( rowInfos: [], groups: [], grid: none(), gridId: gridId, editingRow: none(), loadingState: const _Loading(), ); } @freezed class GridLoadingState with _$GridLoadingState { const factory GridLoadingState.loading() = _Loading; const factory GridLoadingState.finish( Either successOrFail) = _Finish; } class GridFieldEquatable extends Equatable { final UnmodifiableListView _fields; const GridFieldEquatable( UnmodifiableListView fields, ) : _fields = fields; @override List get props { if (_fields.isEmpty) { return []; } return [ _fields.length, _fields .map((field) => field.width) .reduce((value, element) => value + element), ]; } UnmodifiableListView get value => UnmodifiableListView(_fields); } class BoardColumnItem extends AFColumnItem { final RowPB row; BoardColumnItem({required this.row}); @override String get id => row.id; } class CreateCardItem extends AFColumnItem { @override String get id => '$CreateCardItem'; }