| 
									
										
										
										
											2021-10-20 22:19:01 +08:00
										 |  |  | import 'dart:convert'; | 
					
						
							| 
									
										
										
										
											2023-03-07 09:33:59 +08:00
										 |  |  | import 'package:appflowy/plugins/document/presentation/plugins/cover/cover_node_widget.dart'; | 
					
						
							| 
									
										
										
										
											2023-02-26 16:27:17 +08:00
										 |  |  | import 'package:appflowy/plugins/trash/application/trash_service.dart'; | 
					
						
							|  |  |  | import 'package:appflowy/user/application/user_service.dart'; | 
					
						
							|  |  |  | import 'package:appflowy/workspace/application/view/view_listener.dart'; | 
					
						
							|  |  |  | import 'package:appflowy/plugins/document/application/doc_service.dart'; | 
					
						
							| 
									
										
										
										
											2023-03-07 09:33:59 +08:00
										 |  |  | import 'package:appflowy_backend/protobuf/flowy-document/entities.pb.dart'; | 
					
						
							| 
									
										
										
										
											2023-02-16 10:17:08 +08:00
										 |  |  | import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pbserver.dart'; | 
					
						
							| 
									
										
										
										
											2022-10-22 21:57:44 +08:00
										 |  |  | import 'package:appflowy_editor/appflowy_editor.dart' | 
					
						
							| 
									
										
										
										
											2023-03-07 09:33:59 +08:00
										 |  |  |     show EditorState, Document, Transaction, Node; | 
					
						
							| 
									
										
										
										
											2023-01-08 12:10:53 +08:00
										 |  |  | import 'package:appflowy_backend/protobuf/flowy-folder/trash.pb.dart'; | 
					
						
							|  |  |  | import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; | 
					
						
							|  |  |  | import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; | 
					
						
							|  |  |  | import 'package:appflowy_backend/log.dart'; | 
					
						
							| 
									
										
										
										
											2023-03-07 09:33:59 +08:00
										 |  |  | import 'package:flutter/foundation.dart'; | 
					
						
							| 
									
										
										
										
											2021-09-11 21:30:58 +08:00
										 |  |  | import 'package:flutter_bloc/flutter_bloc.dart'; | 
					
						
							| 
									
										
										
										
											2021-07-24 18:55:13 +08:00
										 |  |  | import 'package:freezed_annotation/freezed_annotation.dart'; | 
					
						
							| 
									
										
										
										
											2021-10-19 13:56:11 +08:00
										 |  |  | import 'package:dartz/dartz.dart'; | 
					
						
							| 
									
										
										
										
											2021-10-20 22:19:01 +08:00
										 |  |  | import 'dart:async'; | 
					
						
							| 
									
										
										
										
											2023-02-26 16:27:17 +08:00
										 |  |  | import 'package:appflowy/util/either_extension.dart'; | 
					
						
							| 
									
										
										
										
											2022-02-28 21:17:08 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-24 18:55:13 +08:00
										 |  |  | part 'doc_bloc.freezed.dart'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-23 22:17:47 +08:00
										 |  |  | class DocumentBloc extends Bloc<DocumentEvent, DocumentState> { | 
					
						
							| 
									
										
										
										
											2022-07-19 14:11:29 +08:00
										 |  |  |   final ViewPB view; | 
					
						
							| 
									
										
										
										
											2022-10-26 10:38:57 +08:00
										 |  |  |   final DocumentService _documentService; | 
					
						
							| 
									
										
										
										
											2022-03-01 21:09:52 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-26 10:38:57 +08:00
										 |  |  |   final ViewListener _listener; | 
					
						
							|  |  |  |   final TrashService _trashService; | 
					
						
							| 
									
										
										
										
											2023-02-24 09:16:51 +08:00
										 |  |  |   EditorState? editorState; | 
					
						
							| 
									
										
										
										
											2021-11-04 12:47:41 +08:00
										 |  |  |   StreamSubscription? _subscription; | 
					
						
							| 
									
										
										
										
											2021-07-24 18:55:13 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-23 22:17:47 +08:00
										 |  |  |   DocumentBloc({ | 
					
						
							| 
									
										
										
										
											2021-10-31 19:48:20 +08:00
										 |  |  |     required this.view, | 
					
						
							| 
									
										
										
										
											2022-10-26 10:38:57 +08:00
										 |  |  |   })  : _documentService = DocumentService(), | 
					
						
							|  |  |  |         _listener = ViewListener(view: view), | 
					
						
							|  |  |  |         _trashService = TrashService(), | 
					
						
							|  |  |  |         super(DocumentState.initial()) { | 
					
						
							| 
									
										
										
										
											2022-02-23 22:17:47 +08:00
										 |  |  |     on<DocumentEvent>((event, emit) async { | 
					
						
							| 
									
										
										
										
											2022-01-04 22:44:03 +08:00
										 |  |  |       await event.map( | 
					
						
							|  |  |  |         initial: (Initial value) async { | 
					
						
							|  |  |  |           await _initial(value, emit); | 
					
						
							| 
									
										
										
										
											2022-10-22 21:57:44 +08:00
										 |  |  |           _listenOnViewChange(); | 
					
						
							| 
									
										
										
										
											2022-01-04 22:44:03 +08:00
										 |  |  |         }, | 
					
						
							|  |  |  |         deleted: (Deleted value) async { | 
					
						
							|  |  |  |           emit(state.copyWith(isDeleted: true)); | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         restore: (Restore value) async { | 
					
						
							|  |  |  |           emit(state.copyWith(isDeleted: false)); | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         deletePermanently: (DeletePermanently value) async { | 
					
						
							| 
									
										
										
										
											2022-10-26 10:38:57 +08:00
										 |  |  |           final result = await _trashService | 
					
						
							| 
									
										
										
										
											2022-08-09 10:35:27 +08:00
										 |  |  |               .deleteViews([Tuple2(view.id, TrashType.TrashView)]); | 
					
						
							| 
									
										
										
										
											2022-03-01 21:09:52 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-09 10:35:27 +08:00
										 |  |  |           final newState = result.fold( | 
					
						
							| 
									
										
										
										
											2023-04-10 15:10:42 +08:00
										 |  |  |             (l) => state.copyWith(forceClose: true), | 
					
						
							|  |  |  |             (r) => state, | 
					
						
							|  |  |  |           ); | 
					
						
							| 
									
										
										
										
											2022-01-04 22:44:03 +08:00
										 |  |  |           emit(newState); | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         restorePage: (RestorePage value) async { | 
					
						
							| 
									
										
										
										
											2022-10-26 10:38:57 +08:00
										 |  |  |           final result = await _trashService.putback(view.id); | 
					
						
							| 
									
										
										
										
											2022-08-09 10:35:27 +08:00
										 |  |  |           final newState = result.fold( | 
					
						
							| 
									
										
										
										
											2023-04-10 15:10:42 +08:00
										 |  |  |             (l) => state.copyWith(isDeleted: false), | 
					
						
							|  |  |  |             (r) => state, | 
					
						
							|  |  |  |           ); | 
					
						
							| 
									
										
										
										
											2022-01-04 22:44:03 +08:00
										 |  |  |           emit(newState); | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2021-07-24 18:55:13 +08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2021-09-11 21:30:58 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-19 13:56:11 +08:00
										 |  |  |   @override | 
					
						
							|  |  |  |   Future<void> close() async { | 
					
						
							| 
									
										
										
										
											2022-10-26 10:38:57 +08:00
										 |  |  |     await _listener.stop(); | 
					
						
							| 
									
										
										
										
											2021-11-03 22:04:45 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-03 23:03:42 +08:00
										 |  |  |     if (_subscription != null) { | 
					
						
							|  |  |  |       await _subscription?.cancel(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-26 10:38:57 +08:00
										 |  |  |     await _documentService.closeDocument(docId: view.id); | 
					
						
							| 
									
										
										
										
											2021-10-19 13:56:11 +08:00
										 |  |  |     return super.close(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-23 22:17:47 +08:00
										 |  |  |   Future<void> _initial(Initial value, Emitter<DocumentState> emit) async { | 
					
						
							| 
									
										
										
										
											2023-02-26 16:27:17 +08:00
										 |  |  |     final userProfile = await UserBackendService.getCurrentUserProfile(); | 
					
						
							| 
									
										
										
										
											2023-02-16 10:17:08 +08:00
										 |  |  |     if (userProfile.isRight()) { | 
					
						
							| 
									
										
										
										
											2023-03-07 09:33:59 +08:00
										 |  |  |       return emit( | 
					
						
							| 
									
										
										
										
											2023-02-16 10:17:08 +08:00
										 |  |  |         state.copyWith( | 
					
						
							| 
									
										
										
										
											2023-02-28 14:34:13 +08:00
										 |  |  |           loadingState: DocumentLoadingState.finish( | 
					
						
							|  |  |  |             right(userProfile.asRight()), | 
					
						
							|  |  |  |           ), | 
					
						
							| 
									
										
										
										
											2023-02-16 10:17:08 +08:00
										 |  |  |         ), | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-10-26 10:38:57 +08:00
										 |  |  |     final result = await _documentService.openDocument(view: view); | 
					
						
							| 
									
										
										
										
											2023-03-07 09:33:59 +08:00
										 |  |  |     return result.fold( | 
					
						
							|  |  |  |       (documentData) async { | 
					
						
							|  |  |  |         await _initEditorState(documentData).whenComplete(() { | 
					
						
							|  |  |  |           emit( | 
					
						
							|  |  |  |             state.copyWith( | 
					
						
							|  |  |  |               loadingState: DocumentLoadingState.finish(left(unit)), | 
					
						
							|  |  |  |               userProfilePB: userProfile.asLeft(), | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2022-10-22 21:57:44 +08:00
										 |  |  |       }, | 
					
						
							| 
									
										
										
										
											2023-03-07 09:33:59 +08:00
										 |  |  |       (err) async { | 
					
						
							| 
									
										
										
										
											2022-10-22 21:57:44 +08:00
										 |  |  |         emit( | 
					
						
							|  |  |  |           state.copyWith( | 
					
						
							|  |  |  |             loadingState: DocumentLoadingState.finish(right(err)), | 
					
						
							|  |  |  |           ), | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   void _listenOnViewChange() { | 
					
						
							| 
									
										
										
										
											2022-10-26 10:38:57 +08:00
										 |  |  |     _listener.start( | 
					
						
							| 
									
										
										
										
											2022-05-05 21:15:01 +08:00
										 |  |  |       onViewDeleted: (result) { | 
					
						
							|  |  |  |         result.fold( | 
					
						
							|  |  |  |           (view) => add(const DocumentEvent.deleted()), | 
					
						
							|  |  |  |           (error) {}, | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       onViewRestored: (result) { | 
					
						
							|  |  |  |         result.fold( | 
					
						
							|  |  |  |           (view) => add(const DocumentEvent.restore()), | 
					
						
							|  |  |  |           (error) {}, | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2021-10-20 22:19:01 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-07 09:33:59 +08:00
										 |  |  |   Future<void> _initEditorState(DocumentDataPB documentData) async { | 
					
						
							|  |  |  |     final document = Document.fromJson(jsonDecode(documentData.content)); | 
					
						
							|  |  |  |     final editorState = EditorState(document: document); | 
					
						
							|  |  |  |     this.editorState = editorState; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // listen on document change
 | 
					
						
							|  |  |  |     _subscription = editorState.transactionStream.listen((transaction) { | 
					
						
							| 
									
										
										
										
											2022-10-22 21:57:44 +08:00
										 |  |  |       final json = jsonEncode(TransactionAdaptor(transaction).toJson()); | 
					
						
							| 
									
										
										
										
											2022-10-26 10:38:57 +08:00
										 |  |  |       _documentService | 
					
						
							|  |  |  |           .applyEdit(docId: view.id, operations: json) | 
					
						
							|  |  |  |           .then((result) { | 
					
						
							| 
									
										
										
										
											2022-10-22 21:57:44 +08:00
										 |  |  |         result.fold( | 
					
						
							|  |  |  |           (l) => null, | 
					
						
							|  |  |  |           (err) => Log.error(err), | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2023-03-07 09:33:59 +08:00
										 |  |  |     // log
 | 
					
						
							|  |  |  |     if (kDebugMode) { | 
					
						
							|  |  |  |       editorState.logConfiguration.handler = (log) { | 
					
						
							|  |  |  |         Log.debug(log); | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     // migration
 | 
					
						
							|  |  |  |     final migration = DocumentMigration(editorState: editorState); | 
					
						
							|  |  |  |     await migration.apply(); | 
					
						
							| 
									
										
										
										
											2021-10-20 22:19:01 +08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2021-07-24 18:55:13 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @freezed | 
					
						
							| 
									
										
										
										
											2022-02-23 22:17:47 +08:00
										 |  |  | class DocumentEvent with _$DocumentEvent { | 
					
						
							|  |  |  |   const factory DocumentEvent.initial() = Initial; | 
					
						
							|  |  |  |   const factory DocumentEvent.deleted() = Deleted; | 
					
						
							|  |  |  |   const factory DocumentEvent.restore() = Restore; | 
					
						
							|  |  |  |   const factory DocumentEvent.restorePage() = RestorePage; | 
					
						
							|  |  |  |   const factory DocumentEvent.deletePermanently() = DeletePermanently; | 
					
						
							| 
									
										
										
										
											2021-07-24 18:55:13 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @freezed | 
					
						
							| 
									
										
										
										
											2022-02-23 22:17:47 +08:00
										 |  |  | class DocumentState with _$DocumentState { | 
					
						
							|  |  |  |   const factory DocumentState({ | 
					
						
							|  |  |  |     required DocumentLoadingState loadingState, | 
					
						
							| 
									
										
										
										
											2021-10-31 17:24:55 +08:00
										 |  |  |     required bool isDeleted, | 
					
						
							| 
									
										
										
										
											2021-10-31 20:27:37 +08:00
										 |  |  |     required bool forceClose, | 
					
						
							| 
									
										
										
										
											2023-02-16 10:17:08 +08:00
										 |  |  |     UserProfilePB? userProfilePB, | 
					
						
							| 
									
										
										
										
											2022-02-23 22:17:47 +08:00
										 |  |  |   }) = _DocumentState; | 
					
						
							| 
									
										
										
										
											2021-10-19 13:56:11 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-23 22:17:47 +08:00
										 |  |  |   factory DocumentState.initial() => const DocumentState( | 
					
						
							|  |  |  |         loadingState: _Loading(), | 
					
						
							| 
									
										
										
										
											2021-10-31 17:24:55 +08:00
										 |  |  |         isDeleted: false, | 
					
						
							| 
									
										
										
										
											2021-10-31 20:27:37 +08:00
										 |  |  |         forceClose: false, | 
					
						
							| 
									
										
										
										
											2023-02-16 10:17:08 +08:00
										 |  |  |         userProfilePB: null, | 
					
						
							| 
									
										
										
										
											2021-10-31 17:24:55 +08:00
										 |  |  |       ); | 
					
						
							| 
									
										
										
										
											2021-10-19 13:56:11 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @freezed | 
					
						
							| 
									
										
										
										
											2022-02-23 22:17:47 +08:00
										 |  |  | class DocumentLoadingState with _$DocumentLoadingState { | 
					
						
							|  |  |  |   const factory DocumentLoadingState.loading() = _Loading; | 
					
						
							| 
									
										
										
										
											2022-08-09 10:35:27 +08:00
										 |  |  |   const factory DocumentLoadingState.finish( | 
					
						
							| 
									
										
										
										
											2023-04-10 15:10:42 +08:00
										 |  |  |     Either<Unit, FlowyError> successOrFail, | 
					
						
							|  |  |  |   ) = _Finish; | 
					
						
							| 
									
										
										
										
											2021-07-24 18:55:13 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2022-10-22 21:57:44 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | /// Uses to erase the different between appflowy editor and the backend
 | 
					
						
							|  |  |  | class TransactionAdaptor { | 
					
						
							|  |  |  |   final Transaction transaction; | 
					
						
							|  |  |  |   TransactionAdaptor(this.transaction); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Map<String, dynamic> toJson() { | 
					
						
							|  |  |  |     final json = <String, dynamic>{}; | 
					
						
							|  |  |  |     if (transaction.operations.isNotEmpty) { | 
					
						
							|  |  |  |       // The backend uses [0,0] as the beginning path, but the editor uses [0].
 | 
					
						
							|  |  |  |       // So it needs to extend the path by inserting `0` at the head for all
 | 
					
						
							|  |  |  |       // operations before passing to the backend.
 | 
					
						
							|  |  |  |       json['operations'] = transaction.operations | 
					
						
							|  |  |  |           .map((e) => e.copyWith(path: [0, ...e.path]).toJson()) | 
					
						
							|  |  |  |           .toList(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (transaction.afterSelection != null) { | 
					
						
							|  |  |  |       final selection = transaction.afterSelection!; | 
					
						
							|  |  |  |       final start = selection.start; | 
					
						
							|  |  |  |       final end = selection.end; | 
					
						
							|  |  |  |       json['after_selection'] = selection | 
					
						
							|  |  |  |           .copyWith( | 
					
						
							|  |  |  |             start: start.copyWith(path: [0, ...start.path]), | 
					
						
							|  |  |  |             end: end.copyWith(path: [0, ...end.path]), | 
					
						
							|  |  |  |           ) | 
					
						
							|  |  |  |           .toJson(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (transaction.beforeSelection != null) { | 
					
						
							|  |  |  |       final selection = transaction.beforeSelection!; | 
					
						
							|  |  |  |       final start = selection.start; | 
					
						
							|  |  |  |       final end = selection.end; | 
					
						
							|  |  |  |       json['before_selection'] = selection | 
					
						
							|  |  |  |           .copyWith( | 
					
						
							|  |  |  |             start: start.copyWith(path: [0, ...start.path]), | 
					
						
							|  |  |  |             end: end.copyWith(path: [0, ...end.path]), | 
					
						
							|  |  |  |           ) | 
					
						
							|  |  |  |           .toJson(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return json; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2023-03-07 09:33:59 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | class DocumentMigration { | 
					
						
							|  |  |  |   const DocumentMigration({ | 
					
						
							|  |  |  |     required this.editorState, | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   final EditorState editorState; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /// Migrate the document to the latest version.
 | 
					
						
							|  |  |  |   Future<void> apply() async { | 
					
						
							|  |  |  |     final transaction = editorState.transaction; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // A temporary solution to migrate the document to the latest version.
 | 
					
						
							|  |  |  |     // Once the editor is stable, we can remove this.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // cover plugin
 | 
					
						
							|  |  |  |     if (editorState.document.nodeAtPath([0])?.type != kCoverType) { | 
					
						
							|  |  |  |       transaction.insertNode( | 
					
						
							|  |  |  |         [0], | 
					
						
							|  |  |  |         Node(type: kCoverType), | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     transaction.afterSelection = null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (transaction.operations.isNotEmpty) { | 
					
						
							|  |  |  |       editorState.apply(transaction); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |