feat: collab cursor/selection (#4983)

* feat: support collab selection

* feat: collab cusro/selection

* chore: add metadata field

* feat: support displaying user name above cursor

* fix: emit error

* feat: support displaying collaborators

* feat: sync collaborator

* fix: collab doc issues

* chore: update deps

* feat: refactor device id

* chore: enable share button

* chore: update collab a816214

* fix: clippy lint

* chore: use extension type instead class function

* feat: add clear recent views button in debug mode

* chore: support clear recent views

* feat: support saving the last opened workspace

* chore: update collab
This commit is contained in:
Lucas.Xu
2024-03-28 17:46:31 +08:00
committed by GitHub
parent bf98a627b9
commit 60acf8c889
50 changed files with 1050 additions and 163 deletions

View File

@ -1,54 +0,0 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:appflowy/core/notification/document_notification.dart';
import 'package:appflowy_backend/protobuf/flowy-document/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-notification/subject.pb.dart';
import 'package:appflowy_backend/rust_stream.dart';
import 'package:appflowy_result/appflowy_result.dart';
class DocumentListener {
DocumentListener({
required this.id,
});
final String id;
StreamSubscription<SubscribeObject>? _subscription;
DocumentNotificationParser? _parser;
Function(DocEventPB docEvent)? didReceiveUpdate;
void start({
Function(DocEventPB docEvent)? didReceiveUpdate,
}) {
this.didReceiveUpdate = didReceiveUpdate;
_parser = DocumentNotificationParser(
id: id,
callback: _callback,
);
_subscription = RustStreamReceiver.listen(
(observable) => _parser?.parse(observable),
);
}
void _callback(
DocumentNotification ty,
FlowyResult<Uint8List, FlowyError> result,
) {
switch (ty) {
case DocumentNotification.DidReceiveUpdate:
result.map((r) => didReceiveUpdate?.call(DocEventPB.fromBuffer(r)));
break;
default:
break;
}
}
Future<void> stop() async {
await _subscription?.cancel();
_subscription = null;
}
}

View File

@ -1,57 +0,0 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:appflowy/core/notification/document_notification.dart';
import 'package:appflowy_backend/protobuf/flowy-document/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-notification/subject.pb.dart';
import 'package:appflowy_backend/rust_stream.dart';
import 'package:appflowy_result/appflowy_result.dart';
class DocumentSyncStateListener {
DocumentSyncStateListener({
required this.id,
});
final String id;
StreamSubscription<SubscribeObject>? _subscription;
DocumentNotificationParser? _parser;
Function(DocumentSyncStatePB syncState)? didReceiveSyncState;
void start({
Function(DocumentSyncStatePB syncState)? didReceiveSyncState,
}) {
this.didReceiveSyncState = didReceiveSyncState;
_parser = DocumentNotificationParser(
id: id,
callback: _callback,
);
_subscription = RustStreamReceiver.listen(
(observable) => _parser?.parse(observable),
);
}
void _callback(
DocumentNotification ty,
FlowyResult<Uint8List, FlowyError> result,
) {
switch (ty) {
case DocumentNotification.DidUpdateDocumentSyncState:
result.map(
(r) {
final value = DocumentSyncStatePB.fromBuffer(r);
didReceiveSyncState?.call(value);
},
);
break;
default:
break;
}
}
Future<void> stop() async {
await _subscription?.cancel();
_subscription = null;
}
}

View File

@ -1,5 +1,8 @@
import 'package:appflowy/core/config/kv.dart';
import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/shared/feature_flags.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/user_service.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-error/code.pbenum.dart';
@ -135,6 +138,12 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
),
(e) => state.currentWorkspace,
);
result.onSuccess((_) async {
await getIt<KeyValueStorage>().set(
KVKeys.lastOpenedWorkspaceId,
workspaceId,
);
});
emit(
state.copyWith(
currentWorkspace: currentWorkspace,
@ -220,11 +229,21 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
Future<(UserWorkspacePB currentWorkspace, List<UserWorkspacePB> workspaces)?>
_fetchWorkspaces() async {
try {
final lastOpenedWorkspaceId = await getIt<KeyValueStorage>().get(
KVKeys.lastOpenedWorkspaceId,
);
final currentWorkspace =
await _userService.getCurrentWorkspace().getOrThrow();
final workspaces = await _userService.getWorkspaces().getOrThrow();
final currentWorkspaceInList =
UserWorkspacePB currentWorkspaceInList =
workspaces.firstWhere((e) => e.workspaceId == currentWorkspace.id);
if (lastOpenedWorkspaceId != null) {
final lastOpenedWorkspace = workspaces
.firstWhereOrNull((e) => e.workspaceId == lastOpenedWorkspaceId);
if (lastOpenedWorkspace != null) {
currentWorkspaceInList = lastOpenedWorkspace;
}
}
return (currentWorkspaceInList, workspaces);
} catch (e) {
Log.error('fetch workspace error: $e');