feat: doc auto refresh (#5088)

* feat: doc auto refresh

* chore: update editor version
This commit is contained in:
Lucas.Xu 2024-04-08 18:01:20 +08:00 committed by GitHub
parent 79ea485a27
commit 5777830730
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 112 additions and 65 deletions

View File

@ -2,8 +2,8 @@ import 'dart:io';
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/plugins/base/emoji/emoji_text.dart';
import 'package:appflowy/plugins/document/application/doc_listener.dart';
import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
import 'package:appflowy/plugins/document/application/document_listener.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/shared/appflowy_network_image.dart';
import 'package:appflowy/workspace/application/view/prelude.dart';

View File

@ -1,8 +1,6 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database/grid/application/row/row_document_bloc.dart';
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
import 'package:appflowy/plugins/document/application/document_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
import 'package:appflowy/workspace/application/view_info/view_info_bloc.dart';
@ -10,6 +8,7 @@ import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class RowDocument extends StatelessWidget {

View File

@ -1,8 +1,8 @@
// This file is "main.dart"
import 'package:freezed_annotation/freezed_annotation.dart';
part 'doc_awareness_metadata.freezed.dart';
part 'doc_awareness_metadata.g.dart';
part 'document_awareness_metadata.freezed.dart';
part 'document_awareness_metadata.g.dart';
@freezed
class DocumentAwarenessMetadata with _$DocumentAwarenessMetadata {

View File

@ -1,12 +1,12 @@
import 'dart:async';
import 'dart:convert';
import 'package:appflowy/plugins/document/application/doc_awareness_metadata.dart';
import 'package:appflowy/plugins/document/application/doc_collab_adapter.dart';
import 'package:appflowy/plugins/document/application/doc_listener.dart';
import 'package:appflowy/plugins/document/application/doc_service.dart';
import 'package:appflowy/plugins/document/application/doc_sync_state_listener.dart';
import 'package:appflowy/plugins/document/application/document_awareness_metadata.dart';
import 'package:appflowy/plugins/document/application/document_collab_adapter.dart';
import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
import 'package:appflowy/plugins/document/application/document_listener.dart';
import 'package:appflowy/plugins/document/application/document_service.dart';
import 'package:appflowy/plugins/document/application/editor_transaction_adapter.dart';
import 'package:appflowy/plugins/trash/application/trash_service.dart';
import 'package:appflowy/shared/feature_flags.dart';
@ -18,6 +18,7 @@ import 'package:appflowy/util/color_to_hex_string.dart';
import 'package:appflowy/util/debounce.dart';
import 'package:appflowy/util/throttle.dart';
import 'package:appflowy/workspace/application/view/view_listener.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-document/entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-document/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
@ -36,7 +37,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'doc_bloc.freezed.dart';
part 'document_bloc.freezed.dart';
class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
DocumentBloc({
@ -69,6 +70,11 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
final _updateSelectionDebounce = Debounce();
final _syncThrottle = Throttler(duration: const Duration(milliseconds: 500));
// The conflict handle logic is not fully implemented yet
// use the syncTimer to force to reload the document state when the conflict happens.
Timer? _syncTimer;
bool _shouldSync = false;
bool get isLocalMode {
final userProfilePB = state.userProfilePB;
final type = userProfilePB?.authenticator ?? AuthenticatorPB.Local;
@ -82,6 +88,8 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
await _viewListener.stop();
await _transactionSubscription?.cancel();
await _documentService.closeDocument(view: view);
_syncTimer?.cancel();
_syncTimer = null;
state.editorState?.service.keyboardService?.closeKeyboard();
state.editorState?.dispose();
return super.close();
@ -93,6 +101,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
) async {
await event.when(
initial: () async {
_resetSyncTimer();
final result = await _fetchDocumentState();
_onViewChanged();
_onDocumentChanged();
@ -169,6 +178,19 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
);
}
void _resetSyncTimer() {
_syncTimer?.cancel();
_syncTimer = null;
_syncTimer = Timer.periodic(const Duration(seconds: 10), (_) {
if (!_shouldSync) {
return;
}
Log.debug('auto sync document');
// unawaited(_documentCollabAdapter.forceReload());
_shouldSync = false;
});
}
/// Fetch document
Future<FlowyResult<EditorState?, FlowyError>> _fetchDocumentState() async {
final result = await _documentService.openDocument(viewId: view.id);
@ -208,6 +230,10 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
// ignore: invalid_use_of_visible_for_testing_member
emit(state.copyWith(isDocumentEmpty: editorState.document.isEmpty));
}
// reset the sync timer
_shouldSync = true;
_resetSyncTimer();
},
);
@ -268,7 +294,9 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
return;
}
unawaited(_documentCollabAdapter.syncV3(docEvent));
unawaited(_documentCollabAdapter.syncV3(docEvent: docEvent));
_resetSyncTimer();
}
Future<void> _onAwarenessStatesUpdate(
@ -292,6 +320,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
}
void _throttleSyncDoc(DocEventPB docEvent) {
_shouldSync = true;
_syncThrottle.call(() {
_onDocumentStateUpdate(docEvent);
});

View File

@ -1,6 +1,6 @@
import 'dart:convert';
import 'package:appflowy/plugins/document/application/doc_awareness_metadata.dart';
import 'package:appflowy/plugins/document/application/document_awareness_metadata.dart';
import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
import 'package:appflowy/plugins/document/application/prelude.dart';
import 'package:appflowy/shared/list_extension.dart';
@ -68,7 +68,7 @@ class DocumentCollabAdapter {
/// Sync version 3
///
/// Diff the local document with the remote document and apply the changes
Future<void> syncV3(DocEventPB docEvent) async {
Future<void> syncV3({DocEventPB? docEvent}) async {
final result = await _service.getDocument(viewId: docId);
final document = result.fold((s) => s.toDocument(), (f) => null);
if (document == null) {
@ -81,7 +81,8 @@ class DocumentCollabAdapter {
return;
}
prettyPrintJson(ops.map((op) => op.toJson()).toList());
// Use for debugging, DO NOT REMOVE
// prettyPrintJson(ops.map((op) => op.toJson()).toList());
final transaction = editorState.transaction;
for (final op in ops) {
@ -103,6 +104,26 @@ class DocumentCollabAdapter {
// }());
}
Future<void> forceReload() async {
final result = await _service.getDocument(viewId: docId);
final document = result.fold((s) => s.toDocument(), (f) => null);
if (document == null) {
return;
}
final beforeSelection = editorState.selection;
final clear = editorState.transaction;
clear.deleteNodes(editorState.document.root.children);
await editorState.apply(clear, isRemote: true);
final insert = editorState.transaction;
insert.insertNodes([0], document.root.children);
await editorState.apply(insert, isRemote: true);
editorState.selection = beforeSelection;
}
Future<void> _syncUpdated(
BlockEventPayloadPB payload,
Transaction transaction,

View File

@ -1,8 +1,8 @@
import 'dart:async';
import 'dart:convert';
import 'package:appflowy/plugins/document/application/doc_awareness_metadata.dart';
import 'package:appflowy/plugins/document/application/doc_listener.dart';
import 'package:appflowy/plugins/document/application/document_awareness_metadata.dart';
import 'package:appflowy/plugins/document/application/document_listener.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/startup/tasks/device_info_task.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
@ -13,7 +13,7 @@ import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'doc_collaborators_bloc.freezed.dart';
part 'document_collaborators_bloc.freezed.dart';
bool _filterCurrentUser = false;

View File

@ -8,10 +8,11 @@ import 'package:appflowy_result/appflowy_result.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'share_bloc.freezed.dart';
part 'document_share_bloc.freezed.dart';
class DocShareBloc extends Bloc<DocShareEvent, DocShareState> {
DocShareBloc({required this.view}) : super(const DocShareState.initial()) {
class DocumentShareBloc extends Bloc<DocumentShareEvent, DocumentShareState> {
DocumentShareBloc({required this.view})
: super(const DocumentShareState.initial()) {
on<ShareMarkdown>(_onShareMarkdown);
}
@ -19,14 +20,14 @@ class DocShareBloc extends Bloc<DocShareEvent, DocShareState> {
Future<void> _onShareMarkdown(
ShareMarkdown event,
Emitter<DocShareState> emit,
Emitter<DocumentShareState> emit,
) async {
emit(const DocShareState.loading());
emit(const DocumentShareState.loading());
final documentExporter = DocumentExporter(view);
final result = await documentExporter.export(DocumentExportType.markdown);
emit(
DocShareState.finish(
DocumentShareState.finish(
result.fold(
(markdown) =>
FlowyResult.success(_saveMarkdownToPath(markdown, event.path)),
@ -45,17 +46,17 @@ class DocShareBloc extends Bloc<DocShareEvent, DocShareState> {
}
@freezed
class DocShareEvent with _$DocShareEvent {
const factory DocShareEvent.shareMarkdown(String path) = ShareMarkdown;
const factory DocShareEvent.shareText() = ShareText;
const factory DocShareEvent.shareLink() = ShareLink;
class DocumentShareEvent with _$DocumentShareEvent {
const factory DocumentShareEvent.shareMarkdown(String path) = ShareMarkdown;
const factory DocumentShareEvent.shareText() = ShareText;
const factory DocumentShareEvent.shareLink() = ShareLink;
}
@freezed
class DocShareState with _$DocShareState {
const factory DocShareState.initial() = _Initial;
const factory DocShareState.loading() = _Loading;
const factory DocShareState.finish(
class DocumentShareState with _$DocumentShareState {
const factory DocumentShareState.initial() = _Initial;
const factory DocumentShareState.loading() = _Loading;
const factory DocumentShareState.finish(
FlowyResult<ExportDataPB, FlowyError> successOrFail,
) = _Finish;
}

View File

@ -11,7 +11,7 @@ import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'doc_sync_bloc.freezed.dart';
part 'document_sync_bloc.freezed.dart';
class DocumentSyncBloc extends Bloc<DocumentSyncEvent, DocumentSyncBlocState> {
DocumentSyncBloc({

View File

@ -1,8 +1,8 @@
import 'dart:async';
import 'dart:convert';
import 'package:appflowy/plugins/document/application/doc_service.dart';
import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
import 'package:appflowy/plugins/document/application/document_service.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-document/protobuf.dart';
import 'package:appflowy_editor/appflowy_editor.dart'

View File

@ -1,3 +1,3 @@
export 'doc_bloc.dart';
export 'doc_service.dart';
export 'share_bloc.dart';
export 'document_bloc.dart';
export 'document_service.dart';
export 'document_share_bloc.dart';

View File

@ -1,5 +1,5 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
import 'package:appflowy/plugins/document/application/document_bloc.dart';
import 'package:appflowy/plugins/document/presentation/banner.dart';
import 'package:appflowy/plugins/document/presentation/editor_notification.dart';
import 'package:appflowy/plugins/document/presentation/editor_page.dart';

View File

@ -1,4 +1,4 @@
import 'package:appflowy/plugins/document/application/doc_collaborators_bloc.dart';
import 'package:appflowy/plugins/document/application/document_collaborators_bloc.dart';
import 'package:appflowy/plugins/document/presentation/collaborator_avater_stack.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';

View File

@ -1,4 +1,4 @@
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
import 'package:appflowy/plugins/document/application/document_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_configuration.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/align_toolbar_item/custom_text_align_command.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/background_color/theme_background_color.dart';

View File

@ -2,7 +2,7 @@ import 'dart:io';
import 'dart:typed_data';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
import 'package:appflowy/plugins/document/application/document_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_util.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/settings/application_data_storage.dart';

View File

@ -1,6 +1,6 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
import 'package:appflowy/plugins/document/application/document_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/insert_page_command.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/selectable_svg_widget.dart';
import 'package:appflowy/workspace/application/view/view_service.dart';

View File

@ -5,7 +5,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart';
import 'package:appflowy/plugins/base/icon/icon_picker.dart';
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
import 'package:appflowy/plugins/document/application/document_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/custom_image_block_component.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_util.dart';

View File

@ -1,11 +1,9 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart';
import 'package:appflowy/plugins/base/drag_handler.dart';
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/plugins/document/application/document_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart';
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
import 'package:appflowy/user/application/reminder/reminder_extension.dart';
@ -27,6 +25,7 @@ import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:fixnum/fixnum.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:nanoid/non_secure.dart';

View File

@ -1,7 +1,5 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/application/share_bloc.dart';
import 'package:appflowy/plugins/document/application/document_share_bloc.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/util/string_extension.dart';
import 'package:appflowy/workspace/application/view/view_listener.dart';
@ -14,6 +12,7 @@ import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/file_picker/file_picker_service.dart';
import 'package:flowy_infra_ui/widget/rounded_button.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class DocumentShareButton extends StatelessWidget {
@ -27,8 +26,8 @@ class DocumentShareButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => getIt<DocShareBloc>(param1: view),
child: BlocListener<DocShareBloc, DocShareState>(
create: (context) => getIt<DocumentShareBloc>(param1: view),
child: BlocListener<DocumentShareBloc, DocumentShareState>(
listener: (context, state) {
state.mapOrNull(
finish: (state) {
@ -39,7 +38,7 @@ class DocumentShareButton extends StatelessWidget {
},
);
},
child: BlocBuilder<DocShareBloc, DocShareState>(
child: BlocBuilder<DocumentShareBloc, DocumentShareState>(
builder: (context, state) => ConstrainedBox(
constraints: const BoxConstraints.expand(
height: 30,
@ -102,7 +101,7 @@ class ShareActionListState extends State<ShareActionList> {
@override
Widget build(BuildContext context) {
final docShareBloc = context.read<DocShareBloc>();
final docShareBloc = context.read<DocumentShareBloc>();
return PopoverActionList<ShareActionWrapper>(
direction: PopoverDirection.bottomWithCenterAligned,
offset: const Offset(0, 8),
@ -126,7 +125,7 @@ class ShareActionListState extends State<ShareActionList> {
fileName: '${name.toFileName()}.md',
);
if (exportPath != null) {
docShareBloc.add(DocShareEvent.shareMarkdown(exportPath));
docShareBloc.add(DocumentShareEvent.shareMarkdown(exportPath));
}
break;
}

View File

@ -1,8 +1,6 @@
import 'package:flutter/material.dart';
import 'package:appflowy/date/date_service.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
import 'package:appflowy/plugins/document/application/document_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/string_extension.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart';
import 'package:appflowy/plugins/inline_actions/inline_actions_result.dart';
@ -14,6 +12,7 @@ import 'package:appflowy_backend/protobuf/flowy-user/reminder.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:fixnum/fixnum.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:nanoid/nanoid.dart';

View File

@ -1,6 +1,6 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database/application/sync/database_sync_bloc.dart';
import 'package:appflowy/plugins/document/application/doc_sync_bloc.dart';
import 'package:appflowy/plugins/document/application/document_sync_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-document/entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';

View File

@ -189,8 +189,8 @@ void _resolveHomeDeps(GetIt getIt) {
);
// share
getIt.registerFactoryParam<DocShareBloc, ViewPB, void>(
(view, _) => DocShareBloc(view: view),
getIt.registerFactoryParam<DocumentShareBloc, ViewPB, void>(
(view, _) => DocumentShareBloc(view: view),
);
getIt.registerSingleton<NotificationActionBloc>(NotificationActionBloc());

View File

@ -53,8 +53,8 @@ packages:
dependency: "direct main"
description:
path: "."
ref: c8cd407
resolved-ref: c8cd4071e36ca6b1fb6d9ef803abb61e9a743c8b
ref: e83378c
resolved-ref: e83378cc656efbf967649e91a322791e3aa3a378
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
source: git
version: "2.3.3"

View File

@ -169,7 +169,7 @@ dependency_overrides:
appflowy_editor:
git:
url: https://github.com/AppFlowy-IO/appflowy-editor.git
ref: "c8cd407"
ref: "e83378c"
sheet:
git:

View File

@ -1,4 +1,4 @@
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
import 'package:appflowy/plugins/document/application/document_bloc.dart';
import 'package:appflowy/workspace/application/home/home_bloc.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';

View File

@ -1,4 +1,4 @@
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
import 'package:appflowy/plugins/document/application/document_bloc.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';