doc_bloc.dart 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  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/document/presentation/editor_plugins/migration/editor_migration.dart';
  4. import 'package:appflowy/plugins/trash/application/trash_service.dart';
  5. import 'package:appflowy/user/application/user_service.dart';
  6. import 'package:appflowy/util/json_print.dart';
  7. import 'package:appflowy/workspace/application/view/view_listener.dart';
  8. import 'package:appflowy/workspace/application/doc/doc_listener.dart';
  9. import 'package:appflowy/plugins/document/application/doc_service.dart';
  10. import 'package:appflowy_backend/log.dart';
  11. import 'package:appflowy_backend/protobuf/flowy-document2/protobuf.dart';
  12. import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pbserver.dart';
  13. import 'package:appflowy_editor/appflowy_editor.dart'
  14. show EditorState, LogLevel;
  15. import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
  16. import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
  17. import 'package:flutter/foundation.dart';
  18. import 'package:flutter/services.dart';
  19. import 'package:flutter_bloc/flutter_bloc.dart';
  20. import 'package:freezed_annotation/freezed_annotation.dart';
  21. import 'package:dartz/dartz.dart';
  22. import 'dart:async';
  23. part 'doc_bloc.freezed.dart';
  24. class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
  25. DocumentBloc({
  26. required this.view,
  27. }) : _documentListener = DocumentListener(id: view.id),
  28. _viewListener = ViewListener(view: view),
  29. _documentService = DocumentService(),
  30. _trashService = TrashService(),
  31. super(DocumentState.initial()) {
  32. _transactionAdapter = TransactionAdapter(
  33. documentId: view.id,
  34. documentService: _documentService,
  35. );
  36. on<DocumentEvent>(_onDocumentEvent);
  37. }
  38. final ViewPB view;
  39. final DocumentListener _documentListener;
  40. final ViewListener _viewListener;
  41. final DocumentService _documentService;
  42. final TrashService _trashService;
  43. late final TransactionAdapter _transactionAdapter;
  44. EditorState? editorState;
  45. StreamSubscription? _subscription;
  46. @override
  47. Future<void> close() async {
  48. await _viewListener.stop();
  49. await _subscription?.cancel();
  50. await _documentService.closeDocument(view: view);
  51. editorState?.cancelSubscription();
  52. return super.close();
  53. }
  54. Future<void> _onDocumentEvent(
  55. DocumentEvent event,
  56. Emitter<DocumentState> emit,
  57. ) async {
  58. await event.map(
  59. initial: (Initial value) async {
  60. final state = await _fetchDocumentState();
  61. await _subscribe(state);
  62. emit(state);
  63. },
  64. deleted: (Deleted value) async {
  65. emit(state.copyWith(isDeleted: true));
  66. },
  67. restore: (Restore value) async {
  68. emit(state.copyWith(isDeleted: false));
  69. },
  70. deletePermanently: (DeletePermanently value) async {
  71. final result = await _trashService.deleteViews([view.id]);
  72. emit(state.copyWith(forceClose: result.swap().isLeft()));
  73. },
  74. restorePage: (RestorePage value) async {
  75. final result = await _trashService.putback(view.id);
  76. emit(state.copyWith(isDeleted: result.swap().isRight()));
  77. },
  78. );
  79. }
  80. Future<void> _subscribe(DocumentState state) async {
  81. _onViewChanged();
  82. _onDocumentChanged();
  83. // create the editor state
  84. await state.loadingState.whenOrNull(
  85. finish: (data) async => data.map((r) {
  86. _initAppFlowyEditorState(r);
  87. }),
  88. );
  89. }
  90. /// subscribe to the view(document page) change
  91. void _onViewChanged() {
  92. _viewListener.start(
  93. onViewDeleted: (r) =>
  94. r.swap().map((r) => add(const DocumentEvent.deleted())),
  95. onViewRestored: (r) =>
  96. r.swap().map((r) => add(const DocumentEvent.restore())),
  97. );
  98. }
  99. /// subscribe to the document content change
  100. void _onDocumentChanged() {
  101. _documentListener.start(
  102. didReceiveUpdate: (docEvent) {
  103. // todo: integrate the document change to the editor
  104. // prettyPrintJson(docEvent.toProto3Json());
  105. },
  106. );
  107. }
  108. /// Fetch document
  109. Future<DocumentState> _fetchDocumentState() async {
  110. final result = await UserBackendService.getCurrentUserProfile().then(
  111. (value) async => value.andThen(
  112. // open the document
  113. await _documentService.openDocument(view: view),
  114. ),
  115. );
  116. return state.copyWith(
  117. loadingState: DocumentLoadingState.finish(result),
  118. );
  119. }
  120. Future<void> _initAppFlowyEditorState(DocumentDataPB data) async {
  121. if (kDebugMode) {
  122. prettyPrintJson(data.toProto3Json());
  123. }
  124. final document = data.toDocument();
  125. if (document == null) {
  126. assert(false, 'document is null');
  127. return;
  128. }
  129. // test: read from asset
  130. final readme = await rootBundle.loadString('assets/template/readme.json');
  131. final readmeDocument = EditorMigration.migrateDocument(readme);
  132. final editorState = EditorState(document: readmeDocument);
  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.deleted() = Deleted;
  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. }