app_bloc.dart 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  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. viewsChanged: (result) {
  41. result.fold(
  42. (views) => add(AppEvent.didReceiveViewUpdated(views)),
  43. (error) => Log.error(error),
  44. );
  45. },
  46. appUpdated: (app) => add(AppEvent.appDidUpdate(app)),
  47. );
  48. }
  49. Future<void> _renameView(Rename e, Emitter<AppState> emit) async {
  50. final result = await appService.updateApp(appId: app.id, name: e.newName);
  51. result.fold(
  52. (l) => emit(state.copyWith(successOrFailure: left(unit))),
  53. (error) => emit(state.copyWith(successOrFailure: right(error))),
  54. );
  55. }
  56. Future<void> _deleteView(Emitter<AppState> emit) async {
  57. final result = await appService.delete(appId: app.id);
  58. result.fold(
  59. (unit) => emit(state.copyWith(successOrFailure: left(unit))),
  60. (error) => emit(state.copyWith(successOrFailure: right(error))),
  61. );
  62. }
  63. Future<void> _createView(CreateView value, Emitter<AppState> emit) async {
  64. final viewOrFailed = await appService.createView(
  65. appId: app.id,
  66. name: value.name,
  67. desc: value.desc,
  68. dataType: value.dataType,
  69. pluginType: value.pluginType,
  70. );
  71. viewOrFailed.fold(
  72. (view) => emit(state.copyWith(
  73. latestCreatedView: view,
  74. successOrFailure: left(unit),
  75. )),
  76. (error) {
  77. Log.error(error);
  78. emit(state.copyWith(successOrFailure: right(error)));
  79. },
  80. );
  81. }
  82. @override
  83. Future<void> close() async {
  84. await appListener.close();
  85. return super.close();
  86. }
  87. Future<void> _didReceiveViewUpdated(List<View> views, Emitter<AppState> emit) async {
  88. final latestCreatedView = state.latestCreatedView;
  89. AppState newState = state.copyWith(views: views);
  90. if (latestCreatedView != null) {
  91. final index = views.indexWhere((element) => element.id == latestCreatedView.id);
  92. if (index == -1) {
  93. newState = newState.copyWith(latestCreatedView: null);
  94. }
  95. }
  96. emit(newState);
  97. }
  98. Future<void> _loadViews(Emitter<AppState> emit) async {
  99. final viewsOrFailed = await appService.getViews(appId: app.id);
  100. viewsOrFailed.fold(
  101. (views) => emit(state.copyWith(views: views)),
  102. (error) {
  103. Log.error(error);
  104. emit(state.copyWith(successOrFailure: right(error)));
  105. },
  106. );
  107. }
  108. }
  109. @freezed
  110. class AppEvent with _$AppEvent {
  111. const factory AppEvent.initial() = Initial;
  112. const factory AppEvent.createView(
  113. String name,
  114. String desc,
  115. PluginDataType dataType,
  116. PluginType pluginType,
  117. ) = CreateView;
  118. const factory AppEvent.delete() = Delete;
  119. const factory AppEvent.rename(String newName) = Rename;
  120. const factory AppEvent.didReceiveViewUpdated(List<View> views) = ReceiveViews;
  121. const factory AppEvent.appDidUpdate(App app) = AppDidUpdate;
  122. }
  123. @freezed
  124. class AppState with _$AppState {
  125. const factory AppState({
  126. required App app,
  127. required List<View> views,
  128. View? latestCreatedView,
  129. required Either<Unit, FlowyError> successOrFailure,
  130. }) = _AppState;
  131. factory AppState.initial(App app) => AppState(
  132. app: app,
  133. views: [],
  134. successOrFailure: left(unit),
  135. );
  136. }
  137. class AppViewDataNotifier extends ChangeNotifier {
  138. List<View> _views = [];
  139. View? _selectedView;
  140. ExpandableController expandController = ExpandableController(initialExpanded: false);
  141. AppViewDataNotifier() {
  142. _setLatestView(getIt<MenuSharedState>().latestOpenView);
  143. getIt<MenuSharedState>().addLatestViewListener((view) {
  144. _setLatestView(view);
  145. });
  146. }
  147. void _setLatestView(View? view) {
  148. view?.freeze();
  149. _selectedView = view;
  150. _expandIfNeed();
  151. }
  152. View? get selectedView => _selectedView;
  153. set views(List<View> views) {
  154. if (_views != views) {
  155. _views = views;
  156. _expandIfNeed();
  157. notifyListeners();
  158. }
  159. }
  160. void _expandIfNeed() {
  161. if (_selectedView == null) {
  162. return;
  163. }
  164. if (!_views.contains(_selectedView!)) {
  165. return;
  166. }
  167. if (expandController.expanded == false) {
  168. // Workaround: Delay 150 milliseconds to make the smooth animation while expanding
  169. Future.delayed(const Duration(milliseconds: 150), () {
  170. expandController.expanded = true;
  171. });
  172. }
  173. }
  174. UnmodifiableListView<View> get views => UnmodifiableListView(_views);
  175. }