app_bloc.dart 7.1 KB

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