diff --git a/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart b/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart index e69de29bb2..6d5eeace4c 100644 --- a/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart +++ b/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart @@ -0,0 +1,161 @@ +import 'dart:async'; +import 'package:app_flowy/plugins/grid/application/block/block_cache.dart'; +import 'package:app_flowy/plugins/grid/application/grid_data_controller.dart'; +import 'package:app_flowy/plugins/grid/application/row/row_cache.dart'; +import 'package:dartz/dartz.dart'; +import 'package:equatable/equatable.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'; + +part 'board_bloc.freezed.dart'; + +class BoardBloc extends Bloc { + final GridDataController dataController; + + BoardBloc({required ViewPB view}) + : dataController = GridDataController(view: view), + super(BoardState.initial(view.id)) { + on( + (event, emit) async { + await event.when( + initial: () async { + _startListening(); + await _loadGrid(emit); + }, + createRow: () { + dataController.createRow(); + }, + didReceiveGridUpdate: (grid) { + emit(state.copyWith(grid: Some(grid))); + }, + didReceiveFieldUpdate: (fields) { + emit(state.copyWith( + fields: GridFieldEquatable(fields), + )); + }, + didReceiveRowUpdate: (newRowInfos, reason) { + emit(state.copyWith( + rowInfos: newRowInfos, + reason: reason, + )); + }, + ); + }, + ); + } + + @override + Future 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)); + } + }, + onRowsChanged: (rowInfos, reason) { + if (!isClosed) { + add(BoardEvent.didReceiveRowUpdate(rowInfos, reason)); + } + }, + onFieldsChanged: (fields) { + if (!isClosed) { + add(BoardEvent.didReceiveFieldUpdate(fields)); + } + }, + ); + } + + 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.didReceiveRowUpdate( + List rows, + GridRowChangeReason listState, + ) = _DidReceiveRowUpdate; + const factory BoardEvent.didReceiveFieldUpdate( + UnmodifiableListView fields, + ) = _DidReceiveFieldUpdate; + + const factory BoardEvent.didReceiveGridUpdate( + GridPB grid, + ) = _DidReceiveGridUpdate; +} + +@freezed +class BoardState with _$BoardState { + const factory BoardState({ + required String gridId, + required Option grid, + required GridFieldEquatable fields, + required List rowInfos, + required GridLoadingState loadingState, + required GridRowChangeReason reason, + }) = _BoardState; + + factory BoardState.initial(String gridId) => BoardState( + fields: GridFieldEquatable(UnmodifiableListView([])), + rowInfos: [], + grid: none(), + gridId: gridId, + loadingState: const _Loading(), + reason: const InitialListState(), + ); +} + +@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); +} diff --git a/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart b/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart index 953587852a..78d93b8e30 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart @@ -1,8 +1,47 @@ // 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'; + +class BoardPage2 extends StatelessWidget { + final ViewPB view; + const BoardPage2({required this.view, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => BoardBloc(view: view), + child: BlocBuilder( + builder: (context, state) { + return state.loadingState.map( + loading: (_) => + const Center(child: CircularProgressIndicator.adaptive()), + finish: (result) { + return result.successOrFail.fold( + (_) => const BoardContent(), + (err) => FlowyErrorPage(err.toString()), + ); + }, + ); + }, + ), + ); + } +} + +class BoardContent extends StatelessWidget { + const BoardContent({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container(); + } +} class BoardPage extends StatefulWidget { final ViewPB _view;