app_bloc.dart 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  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. layout: value.layout,
  82. );
  83. viewOrFailed.fold(
  84. (view) => emit(state.copyWith(
  85. latestCreatedView: view,
  86. successOrFailure: left(unit),
  87. )),
  88. (error) {
  89. Log.error(error);
  90. emit(state.copyWith(successOrFailure: right(error)));
  91. },
  92. );
  93. }
  94. @override
  95. Future<void> close() async {
  96. await appListener.stop();
  97. return super.close();
  98. }
  99. Future<void> _didReceiveViewUpdated(
  100. List<ViewPB> views, Emitter<AppState> emit) async {
  101. final latestCreatedView = state.latestCreatedView;
  102. AppState newState = state.copyWith(views: views);
  103. if (latestCreatedView != null) {
  104. final index =
  105. views.indexWhere((element) => element.id == latestCreatedView.id);
  106. if (index == -1) {
  107. newState = newState.copyWith(latestCreatedView: null);
  108. }
  109. }
  110. emit(newState);
  111. }
  112. Future<void> _loadViews(Emitter<AppState> emit) async {
  113. final viewsOrFailed = await appService.getViews(appId: app.id);
  114. viewsOrFailed.fold(
  115. (views) => emit(state.copyWith(views: views)),
  116. (error) {
  117. Log.error(error);
  118. emit(state.copyWith(successOrFailure: right(error)));
  119. },
  120. );
  121. }
  122. }
  123. @freezed
  124. class AppEvent with _$AppEvent {
  125. const factory AppEvent.initial() = Initial;
  126. const factory AppEvent.createView(
  127. String name,
  128. String desc,
  129. ViewDataTypePB dataType,
  130. ViewLayoutTypePB layout,
  131. PluginType pluginType,
  132. ) = CreateView;
  133. const factory AppEvent.delete() = Delete;
  134. const factory AppEvent.rename(String newName) = Rename;
  135. const factory AppEvent.didReceiveViewUpdated(List<ViewPB> views) =
  136. ReceiveViews;
  137. const factory AppEvent.appDidUpdate(AppPB app) = AppDidUpdate;
  138. }
  139. @freezed
  140. class AppState with _$AppState {
  141. const factory AppState({
  142. required AppPB app,
  143. required List<ViewPB> views,
  144. ViewPB? latestCreatedView,
  145. required Either<Unit, FlowyError> successOrFailure,
  146. }) = _AppState;
  147. factory AppState.initial(AppPB app) => AppState(
  148. app: app,
  149. views: [],
  150. successOrFailure: left(unit),
  151. );
  152. }
  153. class AppViewDataContext extends ChangeNotifier {
  154. final String appId;
  155. final ValueNotifier<List<ViewPB>> _viewsNotifier = ValueNotifier([]);
  156. final ValueNotifier<ViewPB?> _selectedViewNotifier = ValueNotifier(null);
  157. VoidCallback? _menuSharedStateListener;
  158. ExpandableController expandController =
  159. ExpandableController(initialExpanded: false);
  160. AppViewDataContext({required this.appId}) {
  161. _setLatestView(getIt<MenuSharedState>().latestOpenView);
  162. _menuSharedStateListener =
  163. getIt<MenuSharedState>().addLatestViewListener((view) {
  164. _setLatestView(view);
  165. });
  166. }
  167. VoidCallback addSelectedViewChangeListener(void Function(ViewPB?) callback) {
  168. listener() {
  169. callback(_selectedViewNotifier.value);
  170. }
  171. _selectedViewNotifier.addListener(listener);
  172. return listener;
  173. }
  174. void removeSelectedViewListener(VoidCallback listener) {
  175. _selectedViewNotifier.removeListener(listener);
  176. }
  177. void _setLatestView(ViewPB? view) {
  178. view?.freeze();
  179. if (_selectedViewNotifier.value != view) {
  180. _selectedViewNotifier.value = view;
  181. _expandIfNeed();
  182. notifyListeners();
  183. }
  184. }
  185. ViewPB? get selectedView => _selectedViewNotifier.value;
  186. set views(List<ViewPB> views) {
  187. if (_viewsNotifier.value != views) {
  188. _viewsNotifier.value = views;
  189. _expandIfNeed();
  190. notifyListeners();
  191. }
  192. }
  193. UnmodifiableListView<ViewPB> get views =>
  194. UnmodifiableListView(_viewsNotifier.value);
  195. VoidCallback addViewsChangeListener(
  196. void Function(UnmodifiableListView<ViewPB>) callback) {
  197. listener() {
  198. callback(views);
  199. }
  200. _viewsNotifier.addListener(listener);
  201. return listener;
  202. }
  203. void removeViewsListener(VoidCallback listener) {
  204. _viewsNotifier.removeListener(listener);
  205. }
  206. void _expandIfNeed() {
  207. if (_selectedViewNotifier.value == null) {
  208. return;
  209. }
  210. if (!_viewsNotifier.value.contains(_selectedViewNotifier.value)) {
  211. return;
  212. }
  213. if (expandController.expanded == false) {
  214. // Workaround: Delay 150 milliseconds to make the smooth animation while expanding
  215. Future.delayed(const Duration(milliseconds: 150), () {
  216. expandController.expanded = true;
  217. });
  218. }
  219. }
  220. @override
  221. void dispose() {
  222. if (_menuSharedStateListener != null) {
  223. getIt<MenuSharedState>()
  224. .removeLatestViewListener(_menuSharedStateListener!);
  225. }
  226. super.dispose();
  227. }
  228. }