app_bloc.dart 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. import 'dart:collection';
  2. import 'package:app_flowy/startup/plugin/plugin.dart';
  3. import 'package:app_flowy/startup/startup.dart';
  4. import 'package:app_flowy/workspace/application/app/app_listener.dart';
  5. import 'package:app_flowy/workspace/application/app/app_service.dart';
  6. import 'package:app_flowy/workspace/presentation/home/menu/menu.dart';
  7. import 'package:expandable/expandable.dart';
  8. import 'package:flowy_sdk/log.dart';
  9. import 'package:flowy_sdk/protobuf/flowy-folder/app.pb.dart';
  10. import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
  11. import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
  12. import 'package:flutter/foundation.dart';
  13. import 'package:freezed_annotation/freezed_annotation.dart';
  14. import 'package:flutter_bloc/flutter_bloc.dart';
  15. import 'package:dartz/dartz.dart';
  16. part 'app_bloc.freezed.dart';
  17. class AppBloc extends Bloc<AppEvent, AppState> {
  18. final AppPB app;
  19. final AppService appService;
  20. final AppListener appListener;
  21. AppBloc(
  22. {required this.app, required this.appService, required this.appListener})
  23. : super(AppState.initial(app)) {
  24. on<AppEvent>((event, emit) async {
  25. await event.map(initial: (e) async {
  26. _startListening();
  27. await _loadViews(emit);
  28. }, createView: (CreateView value) async {
  29. await _createView(value, emit);
  30. }, didReceiveViewUpdated: (e) async {
  31. await _didReceiveViewUpdated(e.views, emit);
  32. }, delete: (e) async {
  33. await _deleteView(emit);
  34. }, rename: (e) async {
  35. await _renameView(e, emit);
  36. }, appDidUpdate: (e) async {
  37. emit(state.copyWith(app: e.app));
  38. });
  39. });
  40. }
  41. void _startListening() {
  42. appListener.start(
  43. onViewsChanged: (result) {
  44. result.fold(
  45. (views) {
  46. if (!isClosed) {
  47. add(AppEvent.didReceiveViewUpdated(views));
  48. }
  49. },
  50. (error) => Log.error(error),
  51. );
  52. },
  53. onAppUpdated: (app) {
  54. if (!isClosed) {
  55. add(AppEvent.appDidUpdate(app));
  56. }
  57. },
  58. );
  59. }
  60. Future<void> _renameView(Rename e, Emitter<AppState> emit) async {
  61. final result = await appService.updateApp(appId: app.id, name: e.newName);
  62. result.fold(
  63. (l) => emit(state.copyWith(successOrFailure: left(unit))),
  64. (error) => emit(state.copyWith(successOrFailure: right(error))),
  65. );
  66. }
  67. Future<void> _deleteView(Emitter<AppState> emit) async {
  68. final result = await appService.delete(appId: app.id);
  69. result.fold(
  70. (unit) => emit(state.copyWith(successOrFailure: left(unit))),
  71. (error) => emit(state.copyWith(successOrFailure: right(error))),
  72. );
  73. }
  74. Future<void> _createView(CreateView value, Emitter<AppState> emit) async {
  75. final viewOrFailed = await appService.createView(
  76. appId: app.id,
  77. name: value.name,
  78. desc: value.desc,
  79. dataType: value.dataType,
  80. pluginType: value.pluginType,
  81. );
  82. viewOrFailed.fold(
  83. (view) => emit(state.copyWith(
  84. latestCreatedView: view,
  85. successOrFailure: left(unit),
  86. )),
  87. (error) {
  88. Log.error(error);
  89. emit(state.copyWith(successOrFailure: right(error)));
  90. },
  91. );
  92. }
  93. @override
  94. Future<void> close() async {
  95. await appListener.stop();
  96. return super.close();
  97. }
  98. Future<void> _didReceiveViewUpdated(
  99. List<ViewPB> views, Emitter<AppState> emit) async {
  100. final latestCreatedView = state.latestCreatedView;
  101. AppState newState = state.copyWith(views: views);
  102. if (latestCreatedView != null) {
  103. final index =
  104. views.indexWhere((element) => element.id == latestCreatedView.id);
  105. if (index == -1) {
  106. newState = newState.copyWith(latestCreatedView: null);
  107. }
  108. }
  109. emit(newState);
  110. }
  111. Future<void> _loadViews(Emitter<AppState> emit) async {
  112. final viewsOrFailed = await appService.getViews(appId: app.id);
  113. viewsOrFailed.fold(
  114. (views) => emit(state.copyWith(views: views)),
  115. (error) {
  116. Log.error(error);
  117. emit(state.copyWith(successOrFailure: right(error)));
  118. },
  119. );
  120. }
  121. }
  122. @freezed
  123. class AppEvent with _$AppEvent {
  124. const factory AppEvent.initial() = Initial;
  125. const factory AppEvent.createView(
  126. String name,
  127. String desc,
  128. PluginDataType dataType,
  129. PluginType pluginType,
  130. ) = CreateView;
  131. const factory AppEvent.delete() = Delete;
  132. const factory AppEvent.rename(String newName) = Rename;
  133. const factory AppEvent.didReceiveViewUpdated(List<ViewPB> views) =
  134. ReceiveViews;
  135. const factory AppEvent.appDidUpdate(AppPB app) = AppDidUpdate;
  136. }
  137. @freezed
  138. class AppState with _$AppState {
  139. const factory AppState({
  140. required AppPB app,
  141. required List<ViewPB> views,
  142. ViewPB? latestCreatedView,
  143. required Either<Unit, FlowyError> successOrFailure,
  144. }) = _AppState;
  145. factory AppState.initial(AppPB app) => AppState(
  146. app: app,
  147. views: [],
  148. successOrFailure: left(unit),
  149. );
  150. }
  151. class AppViewDataContext extends ChangeNotifier {
  152. final String appId;
  153. final ValueNotifier<List<ViewPB>> _viewsNotifier = ValueNotifier([]);
  154. final ValueNotifier<ViewPB?> _selectedViewNotifier = ValueNotifier(null);
  155. VoidCallback? _menuSharedStateListener;
  156. ExpandableController expandController =
  157. ExpandableController(initialExpanded: false);
  158. AppViewDataContext({required this.appId}) {
  159. _setLatestView(getIt<MenuSharedState>().latestOpenView);
  160. _menuSharedStateListener =
  161. getIt<MenuSharedState>().addLatestViewListener((view) {
  162. _setLatestView(view);
  163. });
  164. }
  165. VoidCallback addSelectedViewChangeListener(void Function(ViewPB?) callback) {
  166. listener() {
  167. callback(_selectedViewNotifier.value);
  168. }
  169. _selectedViewNotifier.addListener(listener);
  170. return listener;
  171. }
  172. void removeSelectedViewListener(VoidCallback listener) {
  173. _selectedViewNotifier.removeListener(listener);
  174. }
  175. void _setLatestView(ViewPB? view) {
  176. view?.freeze();
  177. if (_selectedViewNotifier.value != view) {
  178. _selectedViewNotifier.value = view;
  179. _expandIfNeed();
  180. notifyListeners();
  181. }
  182. }
  183. ViewPB? get selectedView => _selectedViewNotifier.value;
  184. set views(List<ViewPB> views) {
  185. if (_viewsNotifier.value != views) {
  186. _viewsNotifier.value = views;
  187. _expandIfNeed();
  188. notifyListeners();
  189. }
  190. }
  191. UnmodifiableListView<ViewPB> get views =>
  192. UnmodifiableListView(_viewsNotifier.value);
  193. VoidCallback addViewsChangeListener(
  194. void Function(UnmodifiableListView<ViewPB>) callback) {
  195. listener() {
  196. callback(views);
  197. }
  198. _viewsNotifier.addListener(listener);
  199. return listener;
  200. }
  201. void removeViewsListener(VoidCallback listener) {
  202. _viewsNotifier.removeListener(listener);
  203. }
  204. void _expandIfNeed() {
  205. if (_selectedViewNotifier.value == null) {
  206. return;
  207. }
  208. if (!_viewsNotifier.value.contains(_selectedViewNotifier.value)) {
  209. return;
  210. }
  211. if (expandController.expanded == false) {
  212. // Workaround: Delay 150 milliseconds to make the smooth animation while expanding
  213. Future.delayed(const Duration(milliseconds: 150), () {
  214. expandController.expanded = true;
  215. });
  216. }
  217. }
  218. @override
  219. void dispose() {
  220. if (_menuSharedStateListener != null) {
  221. getIt<MenuSharedState>()
  222. .removeLatestViewListener(_menuSharedStateListener!);
  223. }
  224. super.dispose();
  225. }
  226. }