| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 | import 'dart:collection';import 'package:app_flowy/plugin/plugin.dart';import 'package:app_flowy/startup/startup.dart';import 'package:app_flowy/workspace/application/app/app_listener.dart';import 'package:app_flowy/workspace/application/app/app_service.dart';import 'package:app_flowy/workspace/presentation/home/menu/menu.dart';import 'package:expandable/expandable.dart';import 'package:flowy_sdk/log.dart';import 'package:flowy_sdk/protobuf/flowy-folder/app.pb.dart';import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';import 'package:flutter/foundation.dart';import 'package:freezed_annotation/freezed_annotation.dart';import 'package:flutter_bloc/flutter_bloc.dart';import 'package:dartz/dartz.dart';part 'app_bloc.freezed.dart';class AppBloc extends Bloc<AppEvent, AppState> {  final AppPB app;  final AppService appService;  final AppListener appListener;  AppBloc({required this.app, required this.appService, required this.appListener}) : super(AppState.initial(app)) {    on<AppEvent>((event, emit) async {      await event.map(initial: (e) async {        _startListening();        await _loadViews(emit);      }, createView: (CreateView value) async {        await _createView(value, emit);      }, didReceiveViewUpdated: (e) async {        await _didReceiveViewUpdated(e.views, emit);      }, delete: (e) async {        await _deleteView(emit);      }, rename: (e) async {        await _renameView(e, emit);      }, appDidUpdate: (e) async {        emit(state.copyWith(app: e.app));      });    });  }  void _startListening() {    appListener.start(      onViewsChanged: (result) {        result.fold(          (views) {            if (!isClosed) {              add(AppEvent.didReceiveViewUpdated(views));            }          },          (error) => Log.error(error),        );      },      onAppUpdated: (app) {        if (!isClosed) {          add(AppEvent.appDidUpdate(app));        }      },    );  }  Future<void> _renameView(Rename e, Emitter<AppState> emit) async {    final result = await appService.updateApp(appId: app.id, name: e.newName);    result.fold(      (l) => emit(state.copyWith(successOrFailure: left(unit))),      (error) => emit(state.copyWith(successOrFailure: right(error))),    );  }  Future<void> _deleteView(Emitter<AppState> emit) async {    final result = await appService.delete(appId: app.id);    result.fold(      (unit) => emit(state.copyWith(successOrFailure: left(unit))),      (error) => emit(state.copyWith(successOrFailure: right(error))),    );  }  Future<void> _createView(CreateView value, Emitter<AppState> emit) async {    final viewOrFailed = await appService.createView(      appId: app.id,      name: value.name,      desc: value.desc,      dataType: value.dataType,      pluginType: value.pluginType,    );    viewOrFailed.fold(      (view) => emit(state.copyWith(        latestCreatedView: view,        successOrFailure: left(unit),      )),      (error) {        Log.error(error);        emit(state.copyWith(successOrFailure: right(error)));      },    );  }  @override  Future<void> close() async {    await appListener.stop();    return super.close();  }  Future<void> _didReceiveViewUpdated(List<ViewPB> views, Emitter<AppState> emit) async {    final latestCreatedView = state.latestCreatedView;    AppState newState = state.copyWith(views: views);    if (latestCreatedView != null) {      final index = views.indexWhere((element) => element.id == latestCreatedView.id);      if (index == -1) {        newState = newState.copyWith(latestCreatedView: null);      }    }    emit(newState);  }  Future<void> _loadViews(Emitter<AppState> emit) async {    final viewsOrFailed = await appService.getViews(appId: app.id);    viewsOrFailed.fold(      (views) => emit(state.copyWith(views: views)),      (error) {        Log.error(error);        emit(state.copyWith(successOrFailure: right(error)));      },    );  }}@freezedclass AppEvent with _$AppEvent {  const factory AppEvent.initial() = Initial;  const factory AppEvent.createView(    String name,    String desc,    PluginDataType dataType,    PluginType pluginType,  ) = CreateView;  const factory AppEvent.delete() = Delete;  const factory AppEvent.rename(String newName) = Rename;  const factory AppEvent.didReceiveViewUpdated(List<ViewPB> views) = ReceiveViews;  const factory AppEvent.appDidUpdate(AppPB app) = AppDidUpdate;}@freezedclass AppState with _$AppState {  const factory AppState({    required AppPB app,    required List<ViewPB> views,    ViewPB? latestCreatedView,    required Either<Unit, FlowyError> successOrFailure,  }) = _AppState;  factory AppState.initial(AppPB app) => AppState(        app: app,        views: [],        successOrFailure: left(unit),      );}class AppViewDataContext extends ChangeNotifier {  final String appId;  final ValueNotifier<List<ViewPB>> _viewsNotifier = ValueNotifier([]);  final ValueNotifier<ViewPB?> _selectedViewNotifier = ValueNotifier(null);  VoidCallback? _menuSharedStateListener;  ExpandableController expandController = ExpandableController(initialExpanded: false);  AppViewDataContext({required this.appId}) {    _setLatestView(getIt<MenuSharedState>().latestOpenView);    _menuSharedStateListener = getIt<MenuSharedState>().addLatestViewListener((view) {      _setLatestView(view);    });  }  VoidCallback addSelectedViewChangeListener(void Function(ViewPB?) callback) {    listener() {      callback(_selectedViewNotifier.value);    }    _selectedViewNotifier.addListener(listener);    return listener;  }  void removeSelectedViewListener(VoidCallback listener) {    _selectedViewNotifier.removeListener(listener);  }  void _setLatestView(ViewPB? view) {    view?.freeze();    if (_selectedViewNotifier.value != view) {      _selectedViewNotifier.value = view;      _expandIfNeed();      notifyListeners();    }  }  ViewPB? get selectedView => _selectedViewNotifier.value;  set views(List<ViewPB> views) {    if (_viewsNotifier.value != views) {      _viewsNotifier.value = views;      _expandIfNeed();      notifyListeners();    }  }  UnmodifiableListView<ViewPB> get views => UnmodifiableListView(_viewsNotifier.value);  VoidCallback addViewsChangeListener(void Function(UnmodifiableListView<ViewPB>) callback) {    listener() {      callback(views);    }    _viewsNotifier.addListener(listener);    return listener;  }  void removeViewsListener(VoidCallback listener) {    _viewsNotifier.removeListener(listener);  }  void _expandIfNeed() {    if (_selectedViewNotifier.value == null) {      return;    }    if (!_viewsNotifier.value.contains(_selectedViewNotifier.value)) {      return;    }    if (expandController.expanded == false) {      // Workaround: Delay 150 milliseconds to make the smooth animation while expanding      Future.delayed(const Duration(milliseconds: 150), () {        expandController.expanded = true;      });    }  }  @override  void dispose() {    if (_menuSharedStateListener != null) {      getIt<MenuSharedState>().removeLatestViewListener(_menuSharedStateListener!);    }    super.dispose();  }}
 |