app_bloc.dart 7.2 KB

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