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