doc_bloc.dart 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
  2. import 'package:appflowy/plugins/document/application/editor_transaction_adapter.dart';
  3. import 'package:appflowy/plugins/trash/application/trash_service.dart';
  4. import 'package:appflowy/user/application/user_service.dart';
  5. import 'package:appflowy/util/json_print.dart';
  6. import 'package:appflowy/workspace/application/view/view_listener.dart';
  7. import 'package:appflowy/workspace/application/doc/doc_listener.dart';
  8. import 'package:appflowy/plugins/document/application/doc_service.dart';
  9. import 'package:appflowy_backend/protobuf/flowy-document2/protobuf.dart';
  10. import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pbserver.dart';
  11. import 'package:appflowy_editor/appflowy_editor.dart'
  12. show EditorState, LogLevel;
  13. import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
  14. import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
  15. import 'package:flutter/foundation.dart';
  16. import 'package:flutter_bloc/flutter_bloc.dart';
  17. import 'package:freezed_annotation/freezed_annotation.dart';
  18. import 'package:dartz/dartz.dart';
  19. import 'dart:async';
  20. part 'doc_bloc.freezed.dart';
  21. class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
  22. DocumentBloc({
  23. required this.view,
  24. }) : _documentListener = DocumentListener(id: view.id),
  25. _viewListener = ViewListener(viewId: view.id),
  26. _documentService = DocumentService(),
  27. _trashService = TrashService(),
  28. super(DocumentState.initial()) {
  29. _transactionAdapter = TransactionAdapter(
  30. documentId: view.id,
  31. documentService: _documentService,
  32. );
  33. on<DocumentEvent>(_onDocumentEvent);
  34. }
  35. final ViewPB view;
  36. final DocumentListener _documentListener;
  37. final ViewListener _viewListener;
  38. final DocumentService _documentService;
  39. final TrashService _trashService;
  40. late final TransactionAdapter _transactionAdapter;
  41. EditorState? editorState;
  42. StreamSubscription? _subscription;
  43. @override
  44. Future<void> close() async {
  45. await _viewListener.stop();
  46. await _subscription?.cancel();
  47. await _documentService.closeDocument(view: view);
  48. editorState?.cancelSubscription();
  49. return super.close();
  50. }
  51. Future<void> _onDocumentEvent(
  52. DocumentEvent event,
  53. Emitter<DocumentState> emit,
  54. ) async {
  55. await event.map(
  56. initial: (Initial value) async {
  57. final state = await _fetchDocumentState();
  58. await _subscribe(state);
  59. emit(state);
  60. },
  61. moveToTrash: (MoveToTrash value) async {
  62. emit(state.copyWith(isDeleted: true));
  63. },
  64. restore: (Restore value) async {
  65. emit(state.copyWith(isDeleted: false));
  66. },
  67. deletePermanently: (DeletePermanently value) async {
  68. final result = await _trashService.deleteViews([view.id]);
  69. final forceClose = result.fold((l) => true, (r) => false);
  70. emit(state.copyWith(forceClose: forceClose));
  71. },
  72. restorePage: (RestorePage value) async {
  73. final result = await _trashService.putback(view.id);
  74. final isDeleted = result.fold((l) => false, (r) => true);
  75. emit(state.copyWith(isDeleted: isDeleted));
  76. },
  77. );
  78. }
  79. Future<void> _subscribe(DocumentState state) async {
  80. _onViewChanged();
  81. _onDocumentChanged();
  82. // create the editor state
  83. await state.loadingState.whenOrNull(
  84. finish: (data) async => data.map((r) {
  85. _initAppFlowyEditorState(r);
  86. }),
  87. );
  88. }
  89. /// subscribe to the view(document page) change
  90. void _onViewChanged() {
  91. _viewListener.start(
  92. onViewMoveToTrash: (r) {
  93. r.swap().map((r) => add(const DocumentEvent.moveToTrash()));
  94. },
  95. onViewDeleted: (r) {
  96. r.swap().map((r) => add(const DocumentEvent.moveToTrash()));
  97. },
  98. onViewRestored: (r) =>
  99. r.swap().map((r) => add(const DocumentEvent.restore())),
  100. );
  101. }
  102. /// subscribe to the document content change
  103. void _onDocumentChanged() {
  104. _documentListener.start(
  105. didReceiveUpdate: (docEvent) {
  106. // todo: integrate the document change to the editor
  107. // prettyPrintJson(docEvent.toProto3Json());
  108. },
  109. );
  110. }
  111. /// Fetch document
  112. Future<DocumentState> _fetchDocumentState() async {
  113. final result = await UserBackendService.getCurrentUserProfile().then(
  114. (value) async => value.andThen(
  115. // open the document
  116. await _documentService.openDocument(view: view),
  117. ),
  118. );
  119. return state.copyWith(
  120. loadingState: DocumentLoadingState.finish(result),
  121. );
  122. }
  123. Future<void> _initAppFlowyEditorState(DocumentDataPB data) async {
  124. if (kDebugMode) {
  125. prettyPrintJson(data.toProto3Json());
  126. }
  127. final document = data.toDocument();
  128. if (document == null) {
  129. assert(false, 'document is null');
  130. return;
  131. }
  132. final editorState = EditorState(document: document);
  133. this.editorState = editorState;
  134. // subscribe to the document change from the editor
  135. _subscription = editorState.transactionStream.listen((transaction) async {
  136. await _transactionAdapter.apply(transaction, editorState);
  137. });
  138. // output the log from the editor when debug mode
  139. if (kDebugMode) {
  140. editorState.logConfiguration
  141. ..level = LogLevel.all
  142. ..handler = (log) {
  143. // Log.debug(log);
  144. };
  145. }
  146. }
  147. }
  148. @freezed
  149. class DocumentEvent with _$DocumentEvent {
  150. const factory DocumentEvent.initial() = Initial;
  151. const factory DocumentEvent.moveToTrash() = MoveToTrash;
  152. const factory DocumentEvent.restore() = Restore;
  153. const factory DocumentEvent.restorePage() = RestorePage;
  154. const factory DocumentEvent.deletePermanently() = DeletePermanently;
  155. }
  156. @freezed
  157. class DocumentState with _$DocumentState {
  158. const factory DocumentState({
  159. required DocumentLoadingState loadingState,
  160. required bool isDeleted,
  161. required bool forceClose,
  162. UserProfilePB? userProfilePB,
  163. }) = _DocumentState;
  164. factory DocumentState.initial() => const DocumentState(
  165. loadingState: _Loading(),
  166. isDeleted: false,
  167. forceClose: false,
  168. userProfilePB: null,
  169. );
  170. }
  171. @freezed
  172. class DocumentLoadingState with _$DocumentLoadingState {
  173. const factory DocumentLoadingState.loading() = _Loading;
  174. const factory DocumentLoadingState.finish(
  175. Either<FlowyError, DocumentDataPB> successOrFail,
  176. ) = _Finish;
  177. }