app_bloc.dart 7.9 KB

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