diff --git a/frontend/appflowy_flutter/lib/user/application/user_listener.dart b/frontend/appflowy_flutter/lib/user/application/user_listener.dart index 2fbacf3b6b..9de85f1c6d 100644 --- a/frontend/appflowy_flutter/lib/user/application/user_listener.dart +++ b/frontend/appflowy_flutter/lib/user/application/user_listener.dart @@ -14,6 +14,9 @@ import 'package:appflowy_backend/rust_stream.dart'; import 'package:appflowy_result/appflowy_result.dart'; import 'package:flowy_infra/notifier.dart'; +typedef DidUserWorkspaceUpdateCallback = void Function( + RepeatedUserWorkspacePB workspaces, +); typedef UserProfileNotifyValue = FlowyResult; typedef AuthNotifyValue = FlowyResult; @@ -27,14 +30,20 @@ class UserListener { UserNotificationParser? _userParser; StreamSubscription? _subscription; PublishNotifier? _profileNotifier = PublishNotifier(); + DidUserWorkspaceUpdateCallback? didUpdateUserWorkspaces; void start({ void Function(UserProfileNotifyValue)? onProfileUpdated, + void Function(RepeatedUserWorkspacePB)? didUpdateUserWorkspaces, }) { if (onProfileUpdated != null) { _profileNotifier?.addPublishListener(onProfileUpdated); } + if (didUpdateUserWorkspaces != null) { + this.didUpdateUserWorkspaces = didUpdateUserWorkspaces; + } + _userParser = UserNotificationParser( id: _userProfile.id.toString(), callback: _userNotificationCallback, @@ -63,6 +72,14 @@ class UserListener { (error) => _profileNotifier?.value = FlowyResult.failure(error), ); break; + case user.UserNotification.DidUpdateUserWorkspaces: + result.map( + (r) { + final value = RepeatedUserWorkspacePB.fromBuffer(r); + didUpdateUserWorkspaces?.call(value); + }, + ); + break; default: break; } @@ -108,6 +125,7 @@ class UserWorkspaceListener { _settingChangedNotifier?.value = FlowyResult.failure(error), ); break; + default: break; } diff --git a/frontend/appflowy_flutter/lib/workspace/application/user/user_workspace_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/user/user_workspace_bloc.dart index 8d0ecbf4b1..1eec72fd21 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/user/user_workspace_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/user/user_workspace_bloc.dart @@ -3,6 +3,7 @@ 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_listener.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'; @@ -22,11 +23,18 @@ class UserWorkspaceBloc extends Bloc { UserWorkspaceBloc({ required this.userProfile, }) : _userService = UserBackendService(userId: userProfile.id), + _listener = UserListener(userProfile: userProfile), super(UserWorkspaceState.initial()) { on( (event, emit) async { await event.when( initial: () async { + _listener + ..didUpdateUserWorkspaces = (workspaces) { + add(UserWorkspaceEvent.updateWorkspaces(workspaces)); + } + ..start(); + final result = await _fetchWorkspaces(); final isCollabWorkspaceOn = userProfile.authenticator != AuthenticatorPB.Local && @@ -246,13 +254,30 @@ class UserWorkspaceBloc extends Bloc { ), ); }, + updateWorkspaces: (workspaces) async { + if (!const DeepCollectionEquality() + .equals(workspaces.items, state.workspaces)) { + emit( + state.copyWith( + workspaces: workspaces.items, + ), + ); + } + }, ); }, ); } + @override + Future close() { + _listener.stop(); + return super.close(); + } + final UserProfilePB userProfile; final UserBackendService _userService; + final UserListener _listener; Future< ( @@ -308,6 +333,9 @@ class UserWorkspaceEvent with _$UserWorkspaceEvent { ) = _UpdateWorkspaceIcon; const factory UserWorkspaceEvent.leaveWorkspace(String workspaceId) = LeaveWorkspace; + const factory UserWorkspaceEvent.updateWorkspaces( + RepeatedUserWorkspacePB workspaces, + ) = UpdateWorkspaces; } enum UserWorkspaceActionType { diff --git a/frontend/rust-lib/flowy-user/src/user_manager/manager_user_workspace.rs b/frontend/rust-lib/flowy-user/src/user_manager/manager_user_workspace.rs index 64bec8ebd3..a96fb5c7a6 100644 --- a/frontend/rust-lib/flowy-user/src/user_manager/manager_user_workspace.rs +++ b/frontend/rust-lib/flowy-user/src/user_manager/manager_user_workspace.rs @@ -332,14 +332,24 @@ pub fn save_user_workspaces( ) -> FlowyResult<()> { let user_workspaces = user_workspaces .iter() - .flat_map(|user_workspace| UserWorkspaceTable::try_from((uid, user_workspace)).ok()) - .collect::>(); + .map(|user_workspace| UserWorkspaceTable::try_from((uid, user_workspace))) + .collect::, _>>()?; conn.immediate_transaction(|conn| { - for user_workspace in user_workspaces { - if let Err(err) = diesel::update( + let existing_ids = user_workspace_table::dsl::user_workspace_table + .select(user_workspace_table::id) + .load::(conn)?; + let new_ids: Vec = user_workspaces.iter().map(|w| w.id.clone()).collect(); + let ids_to_delete: Vec = existing_ids + .into_iter() + .filter(|id| !new_ids.contains(id)) + .collect(); + + // insert or update the user workspaces + for user_workspace in &user_workspaces { + let affected_rows = diesel::update( user_workspace_table::dsl::user_workspace_table - .filter(user_workspace_table::id.eq(user_workspace.id.clone())), + .filter(user_workspace_table::id.eq(&user_workspace.id)), ) .set(( user_workspace_table::name.eq(&user_workspace.name), @@ -347,18 +357,24 @@ pub fn save_user_workspaces( user_workspace_table::database_storage_id.eq(&user_workspace.database_storage_id), user_workspace_table::icon.eq(&user_workspace.icon), )) - .execute(conn) - .and_then(|rows| { - if rows == 0 { - let _ = diesel::insert_into(user_workspace_table::table) - .values(user_workspace) - .execute(conn)?; - } - Ok(()) - }) { - tracing::error!("Error saving user workspace: {:?}", err); + .execute(conn)?; + + if affected_rows == 0 { + diesel::insert_into(user_workspace_table::table) + .values(user_workspace) + .execute(conn)?; } } + + // delete the user workspaces that are not in the new list + if !ids_to_delete.is_empty() { + diesel::delete( + user_workspace_table::dsl::user_workspace_table + .filter(user_workspace_table::id.eq_any(ids_to_delete)), + ) + .execute(conn)?; + } + Ok::<(), FlowyError>(()) }) }