import 'dart:async'; import 'package:dartz/dartz.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid-data-model/protobuf.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'cell/cell_service/cell_service.dart'; import 'grid_service.dart'; import 'row/row_service.dart'; part 'grid_bloc.freezed.dart'; class GridBloc extends Bloc { final GridService _gridService; final GridFieldCache fieldCache; late final GridRowCache rowCache; late final GridCellCache cellCache; GridBloc({required View view}) : _gridService = GridService(gridId: view.id), fieldCache = GridFieldCache(gridId: view.id), super(GridState.initial(view.id)) { rowCache = GridRowCache( gridId: view.id, fieldDelegate: GridRowCacheDelegateImpl(fieldCache), ); cellCache = GridCellCache( gridId: view.id, fieldDelegate: GridCellCacheDelegateImpl(fieldCache), ); on( (event, emit) async { await event.map( initial: (InitialGrid value) async { _startListening(); await _loadGrid(emit); }, createRow: (_CreateRow value) { _gridService.createRow(); }, didReceiveRowUpdate: (_DidReceiveRowUpdate value) { emit(state.copyWith(rows: value.rows, listState: value.listState)); }, didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) { emit(state.copyWith(rows: rowCache.clonedRows, fields: value.fields)); }, ); }, ); } @override Future close() async { await _gridService.closeGrid(); await cellCache.dispose(); await rowCache.dispose(); await fieldCache.dispose(); return super.close(); } void _startListening() { fieldCache.addListener( listenWhen: () => !isClosed, onChanged: (fields) => add(GridEvent.didReceiveFieldUpdate(fields)), ); rowCache.addListener( listenWhen: () => !isClosed, onChanged: (rows, listState) => add(GridEvent.didReceiveRowUpdate(rowCache.clonedRows, listState)), ); } Future _loadGrid(Emitter emit) async { final result = await _gridService.loadGrid(); return Future( () => result.fold( (grid) async => await _loadFields(grid, emit), (err) => emit(state.copyWith(loadingState: GridLoadingState.finish(right(err)))), ), ); } Future _loadFields(Grid grid, Emitter emit) async { final result = await _gridService.getFields(fieldOrders: grid.fieldOrders); return Future( () => result.fold( (fields) { fieldCache.fields = fields.items; rowCache.resetRows(grid.blockOrders); emit(state.copyWith( grid: Some(grid), fields: fieldCache.clonedFields, rows: rowCache.clonedRows, loadingState: GridLoadingState.finish(left(unit)), )); }, (err) => emit(state.copyWith(loadingState: GridLoadingState.finish(right(err)))), ), ); } } @freezed class GridEvent with _$GridEvent { const factory GridEvent.initial() = InitialGrid; const factory GridEvent.createRow() = _CreateRow; const factory GridEvent.didReceiveRowUpdate(List rows, GridRowChangeReason listState) = _DidReceiveRowUpdate; const factory GridEvent.didReceiveFieldUpdate(List fields) = _DidReceiveFieldUpdate; } @freezed class GridState with _$GridState { const factory GridState({ required String gridId, required Option grid, required List fields, required List rows, required GridLoadingState loadingState, required GridRowChangeReason listState, }) = _GridState; factory GridState.initial(String gridId) => GridState( fields: [], rows: [], grid: none(), gridId: gridId, loadingState: const _Loading(), listState: const InitialListState(), ); } @freezed class GridLoadingState with _$GridLoadingState { const factory GridLoadingState.loading() = _Loading; const factory GridLoadingState.finish(Either successOrFail) = _Finish; }