Browse Source

chore: add reorder bloc test (#1354)

* chore: add reorder bloc test

* chore: add trash test

Co-authored-by: nathan <[email protected]>
Nathan.fooo 2 years ago
parent
commit
67e4a759c7

+ 13 - 11
frontend/app_flowy/lib/plugins/trash/application/trash_bloc.dart

@@ -10,14 +10,16 @@ import 'package:app_flowy/plugins/trash/application/trash_listener.dart';
 part 'trash_bloc.freezed.dart';
 
 class TrashBloc extends Bloc<TrashEvent, TrashState> {
-  final TrashService service;
-  final TrashListener listener;
-  TrashBloc({required this.service, required this.listener})
-      : super(TrashState.init()) {
+  final TrashService _service;
+  final TrashListener _listener;
+  TrashBloc()
+      : _service = TrashService(),
+        _listener = TrashListener(),
+        super(TrashState.init()) {
     on<TrashEvent>((event, emit) async {
       await event.map(initial: (e) async {
-        listener.start(trashUpdated: _listenTrashUpdated);
-        final result = await service.readTrash();
+        _listener.start(trashUpdated: _listenTrashUpdated);
+        final result = await _service.readTrash();
         emit(result.fold(
           (object) => state.copyWith(
               objects: object.items, successOrFailure: left(unit)),
@@ -26,17 +28,17 @@ class TrashBloc extends Bloc<TrashEvent, TrashState> {
       }, didReceiveTrash: (e) async {
         emit(state.copyWith(objects: e.trash));
       }, putback: (e) async {
-        final result = await service.putback(e.trashId);
+        final result = await _service.putback(e.trashId);
         await _handleResult(result, emit);
       }, delete: (e) async {
         final result =
-            await service.deleteViews([Tuple2(e.trash.id, e.trash.ty)]);
+            await _service.deleteViews([Tuple2(e.trash.id, e.trash.ty)]);
         await _handleResult(result, emit);
       }, deleteAll: (e) async {
-        final result = await service.deleteAll();
+        final result = await _service.deleteAll();
         await _handleResult(result, emit);
       }, restoreAll: (e) async {
-        final result = await service.restoreAll();
+        final result = await _service.restoreAll();
         await _handleResult(result, emit);
       });
     });
@@ -63,7 +65,7 @@ class TrashBloc extends Bloc<TrashEvent, TrashState> {
 
   @override
   Future<void> close() async {
-    await listener.close();
+    await _listener.close();
     return super.close();
   }
 }

+ 1 - 12
frontend/app_flowy/lib/startup/deps_resolver.dart

@@ -92,14 +92,6 @@ void _resolveFolderDeps(GetIt getIt) {
     ),
   );
 
-  //Menu
-  getIt.registerFactoryParam<MenuBloc, UserProfilePB, String>(
-    (user, workspaceId) => MenuBloc(
-      workspaceId: workspaceId,
-      listener: getIt<WorkspaceListener>(param1: user, param2: workspaceId),
-    ),
-  );
-
   getIt.registerFactoryParam<MenuUserBloc, UserProfilePB, void>(
     (user, _) => MenuUserBloc(user),
   );
@@ -123,10 +115,7 @@ void _resolveFolderDeps(GetIt getIt) {
   getIt.registerLazySingleton<TrashService>(() => TrashService());
   getIt.registerLazySingleton<TrashListener>(() => TrashListener());
   getIt.registerFactory<TrashBloc>(
-    () => TrashBloc(
-      service: getIt<TrashService>(),
-      listener: getIt<TrashListener>(),
-    ),
+    () => TrashBloc(),
   );
 }
 

+ 1 - 1
frontend/app_flowy/lib/startup/startup.dart

@@ -39,9 +39,9 @@ class FlowyRunner {
 
     // add task
     getIt<AppLauncher>().addTask(InitRustSDKTask());
+    getIt<AppLauncher>().addTask(PluginLoadTask());
 
     if (!env.isTest()) {
-      getIt<AppLauncher>().addTask(PluginLoadTask());
       getIt<AppLauncher>().addTask(InitAppWidgetTask());
       getIt<AppLauncher>().addTask(InitPlatformServiceTask());
     }

+ 20 - 38
frontend/app_flowy/lib/workspace/application/app/app_bloc.dart

@@ -18,11 +18,10 @@ import 'package:dartz/dartz.dart';
 part 'app_bloc.freezed.dart';
 
 class AppBloc extends Bloc<AppEvent, AppState> {
-  final AppPB app;
   final AppService appService;
   final AppListener appListener;
 
-  AppBloc({required this.app})
+  AppBloc({required AppPB app})
       : appService = AppService(),
         appListener = AppListener(appId: app.id),
         super(AppState.initial(app)) {
@@ -34,8 +33,6 @@ class AppBloc extends Bloc<AppEvent, AppState> {
         await _createView(value, emit);
       }, loadViews: (_) async {
         await _loadViews(emit);
-      }, didReceiveViewUpdated: (e) async {
-        await _didReceiveViewUpdated(e.views, emit);
       }, delete: (e) async {
         await _deleteApp(emit);
       }, deleteView: (deletedView) async {
@@ -43,23 +40,26 @@ class AppBloc extends Bloc<AppEvent, AppState> {
       }, rename: (e) async {
         await _renameView(e, emit);
       }, appDidUpdate: (e) async {
-        emit(state.copyWith(app: e.app));
+        final latestCreatedView = state.latestCreatedView;
+        final views = e.app.belongings.items;
+        AppState newState = state.copyWith(
+          views: views,
+          app: e.app,
+        );
+        if (latestCreatedView != null) {
+          final index =
+              views.indexWhere((element) => element.id == latestCreatedView.id);
+          if (index == -1) {
+            newState = newState.copyWith(latestCreatedView: null);
+          }
+        }
+        emit(newState);
       });
     });
   }
 
   void _startListening() {
     appListener.start(
-      onViewsChanged: (result) {
-        result.fold(
-          (views) {
-            if (!isClosed) {
-              add(AppEvent.didReceiveViewUpdated(views));
-            }
-          },
-          (error) => Log.error(error),
-        );
-      },
       onAppUpdated: (app) {
         if (!isClosed) {
           add(AppEvent.appDidUpdate(app));
@@ -69,7 +69,8 @@ class AppBloc extends Bloc<AppEvent, AppState> {
   }
 
   Future<void> _renameView(Rename e, Emitter<AppState> emit) async {
-    final result = await appService.updateApp(appId: app.id, name: e.newName);
+    final result =
+        await appService.updateApp(appId: state.app.id, name: e.newName);
     result.fold(
       (l) => emit(state.copyWith(successOrFailure: left(unit))),
       (error) => emit(state.copyWith(successOrFailure: right(error))),
@@ -78,7 +79,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
 
 // Delete the current app
   Future<void> _deleteApp(Emitter<AppState> emit) async {
-    final result = await appService.delete(appId: app.id);
+    final result = await appService.delete(appId: state.app.id);
     result.fold(
       (unit) => emit(state.copyWith(successOrFailure: left(unit))),
       (error) => emit(state.copyWith(successOrFailure: right(error))),
@@ -95,7 +96,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
 
   Future<void> _createView(CreateView value, Emitter<AppState> emit) async {
     final result = await appService.createView(
-      appId: app.id,
+      appId: state.app.id,
       name: value.name,
       desc: value.desc ?? "",
       dataFormatType: value.pluginBuilder.dataFormatType,
@@ -120,25 +121,8 @@ class AppBloc extends Bloc<AppEvent, AppState> {
     return super.close();
   }
 
-  Future<void> _didReceiveViewUpdated(
-    List<ViewPB> views,
-    Emitter<AppState> emit,
-  ) async {
-    final latestCreatedView = state.latestCreatedView;
-    AppState newState = state.copyWith(views: views);
-    if (latestCreatedView != null) {
-      final index =
-          views.indexWhere((element) => element.id == latestCreatedView.id);
-      if (index == -1) {
-        newState = newState.copyWith(latestCreatedView: null);
-      }
-    }
-
-    emit(newState);
-  }
-
   Future<void> _loadViews(Emitter<AppState> emit) async {
-    final viewsOrFailed = await appService.getViews(appId: app.id);
+    final viewsOrFailed = await appService.getViews(appId: state.app.id);
     viewsOrFailed.fold(
       (views) => emit(state.copyWith(views: views)),
       (error) {
@@ -161,8 +145,6 @@ class AppEvent with _$AppEvent {
   const factory AppEvent.delete() = DeleteApp;
   const factory AppEvent.deleteView(String viewId) = DeleteView;
   const factory AppEvent.rename(String newName) = Rename;
-  const factory AppEvent.didReceiveViewUpdated(List<ViewPB> views) =
-      ReceiveViews;
   const factory AppEvent.appDidUpdate(AppPB app) = AppDidUpdate;
 }
 

+ 8 - 19
frontend/app_flowy/lib/workspace/application/app/app_listener.dart

@@ -11,11 +11,11 @@ import 'package:flowy_sdk/protobuf/flowy-folder/dart_notification.pb.dart';
 import 'package:flowy_sdk/rust_stream.dart';
 
 typedef AppDidUpdateCallback = void Function(AppPB app);
-typedef ViewsDidChangeCallback = void Function(Either<List<ViewPB>, FlowyError> viewsOrFailed);
+typedef ViewsDidChangeCallback = void Function(
+    Either<List<ViewPB>, FlowyError> viewsOrFailed);
 
 class AppListener {
   StreamSubscription<SubscribeObject>? _subscription;
-  ViewsDidChangeCallback? _viewsChanged;
   AppDidUpdateCallback? _updated;
   FolderNotificationParser? _parser;
   String appId;
@@ -24,26 +24,16 @@ class AppListener {
     required this.appId,
   });
 
-  void start({ViewsDidChangeCallback? onViewsChanged, AppDidUpdateCallback? onAppUpdated}) {
-    _viewsChanged = onViewsChanged;
+  void start({AppDidUpdateCallback? onAppUpdated}) {
     _updated = onAppUpdated;
-    _parser = FolderNotificationParser(id: appId, callback: _bservableCallback);
-    _subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable));
+    _parser = FolderNotificationParser(id: appId, callback: _handleCallback);
+    _subscription =
+        RustStreamReceiver.listen((observable) => _parser?.parse(observable));
   }
 
-  void _bservableCallback(FolderNotification ty, Either<Uint8List, FlowyError> result) {
+  void _handleCallback(
+      FolderNotification ty, Either<Uint8List, FlowyError> result) {
     switch (ty) {
-      case FolderNotification.AppViewsChanged:
-        if (_viewsChanged != null) {
-          result.fold(
-            (payload) {
-              final repeatedView = RepeatedViewPB.fromBuffer(payload);
-              _viewsChanged!(left(repeatedView.items));
-            },
-            (error) => _viewsChanged!(right(error)),
-          );
-        }
-        break;
       case FolderNotification.AppUpdated:
         if (_updated != null) {
           result.fold(
@@ -63,7 +53,6 @@ class AppListener {
   Future<void> stop() async {
     _parser = null;
     await _subscription?.cancel();
-    _viewsChanged = null;
     _updated = null;
   }
 }

+ 18 - 9
frontend/app_flowy/lib/workspace/application/menu/menu_bloc.dart

@@ -6,6 +6,8 @@ import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder/app.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-folder/workspace.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
@@ -13,16 +15,23 @@ part 'menu_bloc.freezed.dart';
 
 class MenuBloc extends Bloc<MenuEvent, MenuState> {
   final WorkspaceService _workspaceService;
-  final WorkspaceListener listener;
-  final String workspaceId;
+  final WorkspaceListener _listener;
+  final UserProfilePB user;
+  final WorkspacePB workspace;
 
-  MenuBloc({required this.workspaceId, required this.listener})
-      : _workspaceService = WorkspaceService(workspaceId: workspaceId),
-        super(MenuState.initial()) {
+  MenuBloc({
+    required this.user,
+    required this.workspace,
+  })  : _workspaceService = WorkspaceService(workspaceId: workspace.id),
+        _listener = WorkspaceListener(
+          user: user,
+          workspaceId: workspace.id,
+        ),
+        super(MenuState.initial(workspace)) {
     on<MenuEvent>((event, emit) async {
       await event.map(
         initial: (e) async {
-          listener.start(appsChanged: _handleAppsOrFail);
+          _listener.start(appsChanged: _handleAppsOrFail);
           await _fetchApps(emit);
         },
         openPage: (e) async {
@@ -55,7 +64,7 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> {
 
   @override
   Future<void> close() async {
-    await listener.stop();
+    await _listener.stop();
     return super.close();
   }
 
@@ -110,8 +119,8 @@ class MenuState with _$MenuState {
     required Plugin plugin,
   }) = _MenuState;
 
-  factory MenuState.initial() => MenuState(
-        apps: [],
+  factory MenuState.initial(WorkspacePB workspace) => MenuState(
+        apps: workspace.apps.items,
         successOrFailure: left(unit),
         plugin: makePlugin(pluginType: PluginType.blank),
       );

+ 5 - 4
frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart

@@ -51,8 +51,10 @@ class HomeMenu extends StatelessWidget {
       providers: [
         BlocProvider<MenuBloc>(
           create: (context) {
-            final menuBloc = getIt<MenuBloc>(
-                param1: user, param2: workspaceSetting.workspace.id);
+            final menuBloc = MenuBloc(
+              user: user,
+              workspace: workspaceSetting.workspace,
+            );
             menuBloc.add(const MenuEvent.initial());
             return menuBloc;
           },
@@ -221,8 +223,7 @@ class MenuTopBar extends StatelessWidget {
               const Spacer(),
               Tooltip(
                   richMessage: TextSpan(children: [
-                    TextSpan(
-                        text: "${LocaleKeys.sideBar_closeSidebar.tr()}\n"),
+                    TextSpan(text: "${LocaleKeys.sideBar_closeSidebar.tr()}\n"),
                     TextSpan(
                       text: Platform.isMacOS ? "⌘+\\" : "Ctrl+\\",
                       style: const TextStyle(color: Colors.white60),

+ 61 - 0
frontend/app_flowy/test/bloc_test/menu_test/app_bloc_test.dart

@@ -2,6 +2,7 @@ import 'package:app_flowy/plugins/board/board.dart';
 import 'package:app_flowy/plugins/doc/document.dart';
 import 'package:app_flowy/plugins/grid/grid.dart';
 import 'package:app_flowy/workspace/application/app/app_bloc.dart';
+import 'package:app_flowy/workspace/application/menu/menu_view_section_bloc.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder/app.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
 import 'package:flutter_test/flutter_test.dart';
@@ -125,4 +126,64 @@ void main() {
       },
     );
   });
+
+  group('$AppBloc', () {
+    late AppPB app;
+    setUpAll(() async {
+      app = await test.createTestApp();
+    });
+    blocTest<AppBloc, AppState>(
+      "create documents' order test",
+      build: () => AppBloc(app: app)..add(const AppEvent.initial()),
+      act: (bloc) async {
+        bloc.add(AppEvent.createView("1", DocumentPluginBuilder()));
+        await blocResponseFuture();
+        bloc.add(AppEvent.createView("2", DocumentPluginBuilder()));
+        await blocResponseFuture();
+        bloc.add(AppEvent.createView("3", DocumentPluginBuilder()));
+        await blocResponseFuture();
+      },
+      wait: blocResponseDuration(),
+      verify: (bloc) {
+        assert(bloc.state.views[0].name == '1');
+        assert(bloc.state.views[1].name == '2');
+        assert(bloc.state.views[2].name == '3');
+      },
+    );
+  });
+
+  group('$AppBloc', () {
+    late AppPB app;
+    setUpAll(() async {
+      app = await test.createTestApp();
+    });
+    blocTest<AppBloc, AppState>(
+      "reorder documents",
+      build: () => AppBloc(app: app)..add(const AppEvent.initial()),
+      act: (bloc) async {
+        bloc.add(AppEvent.createView("1", DocumentPluginBuilder()));
+        await blocResponseFuture();
+        bloc.add(AppEvent.createView("2", DocumentPluginBuilder()));
+        await blocResponseFuture();
+        bloc.add(AppEvent.createView("3", DocumentPluginBuilder()));
+        await blocResponseFuture();
+
+        final appViewData = AppViewDataContext(appId: app.id);
+        appViewData.views = bloc.state.views;
+        final viewSectionBloc = ViewSectionBloc(
+          appViewData: appViewData,
+        )..add(const ViewSectionEvent.initial());
+        await blocResponseFuture();
+
+        viewSectionBloc.add(const ViewSectionEvent.moveView(0, 2));
+        await blocResponseFuture();
+      },
+      wait: blocResponseDuration(),
+      verify: (bloc) {
+        assert(bloc.state.views[0].name == '2');
+        assert(bloc.state.views[1].name == '3');
+        assert(bloc.state.views[2].name == '1');
+      },
+    );
+  });
 }

+ 66 - 0
frontend/app_flowy/test/bloc_test/menu_test/menu_bloc_test.dart

@@ -0,0 +1,66 @@
+import 'package:app_flowy/workspace/application/menu/menu_bloc.dart';
+import 'package:bloc_test/bloc_test.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../../util.dart';
+
+void main() {
+  late AppFlowyUnitTest test;
+  setUpAll(() async {
+    test = await AppFlowyUnitTest.ensureInitialized();
+  });
+
+  group('$MenuBloc', () {
+    late MenuBloc menuBloc;
+    setUp(() async {
+      menuBloc = MenuBloc(
+        user: test.userProfile,
+        workspace: test.currentWorkspace,
+      )..add(const MenuEvent.initial());
+
+      await blocResponseFuture();
+    });
+    blocTest<MenuBloc, MenuState>(
+      "assert initial apps is the build-in app",
+      build: () => menuBloc,
+      wait: blocResponseDuration(),
+      verify: (bloc) {
+        assert(bloc.state.apps.length == 1);
+      },
+    );
+    //
+    blocTest<MenuBloc, MenuState>(
+      "create apps",
+      build: () => menuBloc,
+      act: (bloc) async {
+        bloc.add(const MenuEvent.createApp("App 1"));
+        await blocResponseFuture();
+        bloc.add(const MenuEvent.createApp("App 2"));
+        await blocResponseFuture();
+        bloc.add(const MenuEvent.createApp("App 3"));
+      },
+      wait: blocResponseDuration(),
+      verify: (bloc) {
+        // apps[0] is the build-in app
+        assert(bloc.state.apps[1].name == 'App 1');
+        assert(bloc.state.apps[2].name == 'App 2');
+        assert(bloc.state.apps[3].name == 'App 3');
+      },
+    );
+    blocTest<MenuBloc, MenuState>(
+      "reorder apps",
+      build: () => menuBloc,
+      act: (bloc) async {
+        bloc.add(const MenuEvent.moveApp(1, 3));
+      },
+      wait: blocResponseDuration(),
+      verify: (bloc) {
+        assert(bloc.state.apps[1].name == 'App 2');
+        assert(bloc.state.apps[2].name == 'App 3');
+        assert(bloc.state.apps[3].name == 'App 1');
+      },
+    );
+  });
+
+  //
+}

+ 110 - 0
frontend/app_flowy/test/bloc_test/menu_test/trash_bloc_test.dart

@@ -0,0 +1,110 @@
+import 'package:app_flowy/plugins/doc/document.dart';
+import 'package:app_flowy/plugins/trash/application/trash_bloc.dart';
+import 'package:app_flowy/workspace/application/app/app_bloc.dart';
+import 'package:bloc_test/bloc_test.dart';
+import 'package:flowy_sdk/protobuf/flowy-folder/app.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import '../../util.dart';
+
+void main() {
+  late AppFlowyUnitTest test;
+  late AppPB app;
+  late AppBloc appBloc;
+  late List<ViewPB> allViews;
+  setUpAll(() async {
+    test = await AppFlowyUnitTest.ensureInitialized();
+
+    /// Create a new app with three documents
+    app = await test.createTestApp();
+    appBloc = AppBloc(app: app)
+      ..add(const AppEvent.initial())
+      ..add(AppEvent.createView(
+        "Document 1",
+        DocumentPluginBuilder(),
+      ))
+      ..add(AppEvent.createView(
+        "Document 2",
+        DocumentPluginBuilder(),
+      ))
+      ..add(
+        AppEvent.createView(
+          "Document 3",
+          DocumentPluginBuilder(),
+        ),
+      );
+    await blocResponseFuture(millisecond: 200);
+    allViews = [...appBloc.state.app.belongings.items];
+    assert(allViews.length == 3);
+  });
+
+  group('$TrashBloc', () {
+    late TrashBloc trashBloc;
+    late ViewPB deletedView;
+
+    setUpAll(() {});
+
+    setUp(() async {
+      trashBloc = TrashBloc()..add(const TrashEvent.initial());
+      await blocResponseFuture();
+    });
+
+    blocTest<TrashBloc, TrashState>(
+      "delete view",
+      build: () => trashBloc,
+      act: (bloc) async {
+        deletedView = appBloc.state.app.belongings.items[0];
+        appBloc.add(AppEvent.deleteView(deletedView.id));
+      },
+      wait: blocResponseDuration(),
+      verify: (bloc) {
+        assert(appBloc.state.app.belongings.items.length == 2);
+        assert(bloc.state.objects.length == 1);
+        assert(bloc.state.objects.first.id == deletedView.id);
+      },
+    );
+
+    blocTest<TrashBloc, TrashState>(
+      "delete all views",
+      build: () => trashBloc,
+      act: (bloc) async {
+        for (final view in appBloc.state.app.belongings.items) {
+          appBloc.add(AppEvent.deleteView(view.id));
+          await blocResponseFuture();
+        }
+      },
+      wait: blocResponseDuration(),
+      verify: (bloc) {
+        assert(bloc.state.objects[0].id == allViews[0].id);
+        assert(bloc.state.objects[1].id == allViews[1].id);
+        assert(bloc.state.objects[2].id == allViews[2].id);
+      },
+    );
+    blocTest<TrashBloc, TrashState>(
+      "put back",
+      build: () => trashBloc,
+      act: (bloc) async {
+        bloc.add(TrashEvent.putback(allViews[0].id));
+      },
+      wait: blocResponseDuration(),
+      verify: (bloc) {
+        assert(appBloc.state.app.belongings.items.length == 1);
+        assert(bloc.state.objects.length == 2);
+      },
+    );
+    blocTest<TrashBloc, TrashState>(
+      "put back all",
+      build: () => trashBloc,
+      act: (bloc) async {
+        bloc.add(const TrashEvent.restoreAll());
+      },
+      wait: blocResponseDuration(),
+      verify: (bloc) {
+        assert(appBloc.state.app.belongings.items.length == 3);
+        assert(bloc.state.objects.isEmpty);
+      },
+    );
+    //
+  });
+}

+ 4 - 4
frontend/app_flowy/test/util.dart

@@ -113,10 +113,10 @@ class FlowyTestApp implements EntryPoint {
   }
 }
 
-Future<void> blocResponseFuture() {
-  return Future.delayed(const Duration(milliseconds: 100));
+Future<void> blocResponseFuture({int millisecond = 100}) {
+  return Future.delayed(Duration(milliseconds: millisecond));
 }
 
-Duration blocResponseDuration({int millseconds = 100}) {
-  return Duration(milliseconds: millseconds);
+Duration blocResponseDuration({int milliseconds = 100}) {
+  return Duration(milliseconds: milliseconds);
 }

+ 0 - 1
frontend/rust-lib/flowy-folder/src/dart_notification.rs

@@ -12,7 +12,6 @@ pub(crate) enum FolderNotification {
     WorkspaceAppsChanged = 14,
     WorkspaceSetting = 15,
     AppUpdated = 21,
-    AppViewsChanged = 24,
     ViewUpdated = 31,
     ViewDeleted = 32,
     ViewRestored = 33,

+ 9 - 10
frontend/rust-lib/flowy-folder/src/services/view/controller.rs

@@ -1,5 +1,5 @@
 pub use crate::entities::view::ViewDataFormatPB;
-use crate::entities::{DeletedViewPB, ViewInfoPB, ViewLayoutTypePB};
+use crate::entities::{AppPB, DeletedViewPB, ViewInfoPB, ViewLayoutTypePB};
 use crate::manager::{ViewDataProcessor, ViewDataProcessorMap};
 use crate::{
     dart_notification::{send_dart_notification, FolderNotification},
@@ -531,16 +531,15 @@ fn notify_views_changed<'a>(
     trash_controller: Arc<TrashController>,
     transaction: &'a (dyn FolderPersistenceTransaction + 'a),
 ) -> FlowyResult<()> {
-    let items: Vec<ViewPB> = read_belonging_views_on_local(belong_to_id, trash_controller.clone(), transaction)?
-        .into_iter()
-        .map(|view_rev| view_rev.into())
-        .collect();
-    tracing::Span::current().record("view_count", &format!("{}", items.len()).as_str());
-
-    let repeated_view = RepeatedViewPB { items };
-    send_dart_notification(belong_to_id, FolderNotification::AppViewsChanged)
-        .payload(repeated_view)
+    let mut app_rev = transaction.read_app(belong_to_id)?;
+    let trash_ids = trash_controller.read_trash_ids(transaction)?;
+    app_rev.belongings.retain(|view| !trash_ids.contains(&view.id));
+    let app: AppPB = app_rev.into();
+
+    send_dart_notification(belong_to_id, FolderNotification::AppUpdated)
+        .payload(app)
         .send();
+
     Ok(())
 }