appflowy 3 роки тому
батько
коміт
7ad3e35c16
65 змінених файлів з 2249 додано та 438 видалено
  1. 9 8
      app_flowy/lib/startup/tasks/application_task.dart
  2. 4 9
      app_flowy/lib/user/presentation/welcome_screen.dart
  3. 1 2
      app_flowy/lib/workspace/application/app/app_listen_bloc.dart
  4. 2 7
      app_flowy/lib/workspace/domain/i_app.dart
  5. 0 1
      app_flowy/lib/workspace/domain/i_trash.dart
  6. 2 5
      app_flowy/lib/workspace/infrastructure/i_app_impl.dart
  7. 9 9
      app_flowy/lib/workspace/infrastructure/i_user_impl.dart
  8. 11 23
      app_flowy/lib/workspace/infrastructure/repos/app_repo.dart
  9. 8 37
      app_flowy/lib/workspace/infrastructure/repos/helper.dart
  10. 23 0
      app_flowy/lib/workspace/infrastructure/repos/trash_repo.dart
  11. 4 4
      app_flowy/lib/workspace/infrastructure/repos/view_repo.dart
  12. 6 6
      app_flowy/lib/workspace/infrastructure/repos/workspace_repo.dart
  13. 1 1
      app_flowy/lib/workspace/presentation/home/navigation.dart
  14. 128 0
      app_flowy/lib/workspace/presentation/stack_page/trash/trash_page.dart
  15. 0 0
      app_flowy/lib/workspace/presentation/stack_page/trash/widget/list_body.dart
  16. 1 0
      app_flowy/lib/workspace/presentation/stack_page/trash/widget/list_header.dart
  17. 6 0
      app_flowy/lib/workspace/presentation/stack_page/trash/widget/sizes.dart
  18. 0 47
      app_flowy/lib/workspace/presentation/stack_page/trash_page.dart
  19. 8 7
      app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/list_overlay.dart
  20. 102 0
      app_flowy/packages/flowy_infra_ui/lib/style_widget/button.dart
  21. 5 0
      app_flowy/packages/flowy_infra_ui/lib/style_widget/text.dart
  22. 0 56
      app_flowy/packages/flowy_infra_ui/lib/style_widget/text_button.dart
  23. 36 2
      app_flowy/packages/flowy_sdk/lib/dispatch/code_gen.dart
  24. 4 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbenum.dart
  25. 3 1
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbjson.dart
  26. 17 19
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/observable.pbenum.dart
  27. 6 7
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/observable.pbjson.dart
  28. 2 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/protobuf.dart
  29. 231 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_create.pb.dart
  30. 7 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_create.pbenum.dart
  31. 46 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_create.pbjson.dart
  32. 9 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_create.pbserver.dart
  33. 58 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_delete.pb.dart
  34. 7 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_delete.pbenum.dart
  35. 20 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_delete.pbjson.dart
  36. 9 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_delete.pbserver.dart
  37. 2 1
      rust-lib/flowy-database/migrations/2021-07-09-063045_flowy-user/up.sql
  38. 1 0
      rust-lib/flowy-database/src/schema.rs
  39. 5 1
      rust-lib/flowy-derive/src/derive_cache/derive_cache.rs
  40. 2 0
      rust-lib/flowy-workspace/Cargo.toml
  41. 3 1
      rust-lib/flowy-workspace/src/entities/trash/trash_create.rs
  42. 1 1
      rust-lib/flowy-workspace/src/entities/trash/trash_delete.rs
  43. 2 2
      rust-lib/flowy-workspace/src/entities/view/view_create.rs
  44. 2 0
      rust-lib/flowy-workspace/src/errors.rs
  45. 7 1
      rust-lib/flowy-workspace/src/event.rs
  46. 23 2
      rust-lib/flowy-workspace/src/handlers/trash_handler.rs
  47. 4 1
      rust-lib/flowy-workspace/src/module.rs
  48. 1 2
      rust-lib/flowy-workspace/src/notify/observable.rs
  49. 19 8
      rust-lib/flowy-workspace/src/protobuf/model/event.rs
  50. 6 0
      rust-lib/flowy-workspace/src/protobuf/model/mod.rs
  51. 63 69
      rust-lib/flowy-workspace/src/protobuf/model/observable.rs
  52. 785 0
      rust-lib/flowy-workspace/src/protobuf/model/trash_create.rs
  53. 204 0
      rust-lib/flowy-workspace/src/protobuf/model/trash_delete.rs
  54. 2 0
      rust-lib/flowy-workspace/src/protobuf/proto/event.proto
  55. 2 3
      rust-lib/flowy-workspace/src/protobuf/proto/observable.proto
  56. 17 0
      rust-lib/flowy-workspace/src/protobuf/proto/trash_create.proto
  57. 5 0
      rust-lib/flowy-workspace/src/protobuf/proto/trash_delete.proto
  58. 4 6
      rust-lib/flowy-workspace/src/services/app_controller.rs
  59. 1 3
      rust-lib/flowy-workspace/src/services/server/middleware.rs
  60. 102 6
      rust-lib/flowy-workspace/src/services/trash_can.rs
  61. 79 41
      rust-lib/flowy-workspace/src/services/view_controller.rs
  62. 4 4
      rust-lib/flowy-workspace/src/services/workspace_controller.rs
  63. 10 3
      rust-lib/flowy-workspace/src/sql_tables/trash/trash_sql.rs
  64. 30 2
      rust-lib/flowy-workspace/src/sql_tables/trash/trash_table.rs
  65. 78 30
      rust-lib/flowy-workspace/src/sql_tables/view/view_sql.rs

+ 9 - 8
app_flowy/lib/startup/tasks/application_task.dart

@@ -38,14 +38,15 @@ class ApplicationWidget extends StatelessWidget {
     final theme = AppTheme.fromType(ThemeType.light);
     FlowyOverlayConfig config = FlowyOverlayConfig(barrierColor: Colors.transparent);
     return Provider.value(
-        value: theme,
-        child: MaterialApp(
-          builder: overlayManagerBuilder(config: config),
-          debugShowCheckedModeBanner: false,
-          theme: theme.themeData,
-          navigatorKey: AppGlobals.rootNavKey,
-          home: child,
-        ));
+      value: theme,
+      child: MaterialApp(
+        builder: overlayManagerBuilder(config: config),
+        debugShowCheckedModeBanner: false,
+        theme: theme.themeData,
+        navigatorKey: AppGlobals.rootNavKey,
+        home: child,
+      ),
+    );
   }
 }
 

+ 4 - 9
app_flowy/lib/user/presentation/welcome_screen.dart

@@ -2,7 +2,7 @@ import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/application/workspace/welcome_bloc.dart';
 import 'package:app_flowy/workspace/domain/i_user.dart';
 import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
-import 'package:flowy_infra_ui/style_widget/text_button.dart';
+import 'package:flowy_infra_ui/style_widget/button.dart';
 import 'package:flowy_infra_ui/widget/error_page.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/workspace_create.pb.dart';
 import 'package:flutter/material.dart';
@@ -19,8 +19,7 @@ class WelcomeScreen extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return BlocProvider(
-      create: (_) => getIt<WelcomeBloc>(param1: repo.user)
-        ..add(const WelcomeEvent.initial()),
+      create: (_) => getIt<WelcomeBloc>(param1: repo.user)..add(const WelcomeEvent.initial()),
       child: BlocBuilder<WelcomeBloc, WelcomeState>(
         builder: (context, state) {
           return Scaffold(
@@ -55,9 +54,7 @@ class WelcomeScreen extends StatelessWidget {
         "Create workspace",
         fontSize: 14,
         onPressed: () {
-          context
-              .read<WelcomeBloc>()
-              .add(const WelcomeEvent.createWorkspace("workspace", ""));
+          context.read<WelcomeBloc>().add(const WelcomeEvent.createWorkspace("workspace", ""));
         },
       ),
     );
@@ -88,9 +85,7 @@ class WelcomeScreen extends StatelessWidget {
 class WorkspaceItem extends StatelessWidget {
   final Workspace workspace;
   final void Function(Workspace workspace) onPressed;
-  const WorkspaceItem(
-      {Key? key, required this.workspace, required this.onPressed})
-      : super(key: key);
+  const WorkspaceItem({Key? key, required this.workspace, required this.onPressed}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {

+ 1 - 2
app_flowy/lib/workspace/application/app/app_listen_bloc.dart

@@ -17,8 +17,7 @@ class AppListenBloc extends Bloc<AppListenEvent, AppListenState> {
   ) async* {
     yield* event.map(started: (_) async* {
       listener.start(
-        addViewCallback: (viewsOrFail) => _handleViewsOrFail(viewsOrFail),
-        deleteViewCallback: (viewsOrFail) => _handleViewsOrFail(viewsOrFail),
+        viewsChangeCallback: (viewsOrFail) => _handleViewsOrFail(viewsOrFail),
       );
     }, didReceiveViews: (ViewsReceived value) async* {
       yield value.viewsOrFail.fold(

+ 2 - 7
app_flowy/lib/workspace/domain/i_app.dart

@@ -2,9 +2,7 @@ import 'package:flowy_sdk/protobuf/flowy-workspace/protobuf.dart';
 import 'package:dartz/dartz.dart';
 
 typedef AppUpdatedCallback = void Function(String name, String desc);
-typedef AppCreateViewCallback = void Function(Either<List<View>, WorkspaceError> viewsOrFailed);
-
-typedef AppDeleteViewCallback = void Function(Either<List<View>, WorkspaceError> viewsOrFailed);
+typedef AppViewsChangeCallback = void Function(Either<List<View>, WorkspaceError> viewsOrFailed);
 
 abstract class IApp {
   Future<Either<List<View>, WorkspaceError>> getViews();
@@ -13,10 +11,7 @@ abstract class IApp {
 }
 
 abstract class IAppListenr {
-  void start(
-      {AppCreateViewCallback? addViewCallback,
-      AppDeleteViewCallback? deleteViewCallback,
-      AppUpdatedCallback? updatedCallback});
+  void start({AppViewsChangeCallback? viewsChangeCallback, AppUpdatedCallback? updatedCallback});
 
   Future<void> stop();
 }

+ 0 - 1
app_flowy/lib/workspace/domain/i_trash.dart

@@ -1,7 +1,6 @@
 import 'dart:async';
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
 
 abstract class TrashObject {
   String get id;

+ 2 - 5
app_flowy/lib/workspace/infrastructure/i_app_impl.dart

@@ -40,10 +40,7 @@ class IAppListenerhImpl extends IAppListenr {
   }
 
   @override
-  void start(
-      {AppCreateViewCallback? addViewCallback,
-      AppDeleteViewCallback? deleteViewCallback,
-      AppUpdatedCallback? updatedCallback}) {
-    repo.startListen(createView: addViewCallback, deleteView: deleteViewCallback, update: updatedCallback);
+  void start({AppViewsChangeCallback? viewsChangeCallback, AppUpdatedCallback? updatedCallback}) {
+    repo.startListen(viewsChanged: viewsChangeCallback, update: updatedCallback);
   }
 }

+ 9 - 9
app_flowy/lib/workspace/infrastructure/i_user_impl.dart

@@ -55,8 +55,8 @@ class IUserListenerImpl extends IUserListener {
   AuthChangedCallback? _authChanged;
   UserProfileUpdateCallback? _profileUpdated;
 
-  late WorkspaceObservableParser _workspaceParser;
-  late UserObservableParser _userParser;
+  late WorkspaceNotificationParser _workspaceParser;
+  late UserNotificationParser _userParser;
   late UserProfile _user;
   IUserListenerImpl({
     required UserProfile user,
@@ -66,9 +66,9 @@ class IUserListenerImpl extends IUserListener {
 
   @override
   void start() {
-    _workspaceParser = WorkspaceObservableParser(id: _user.token, callback: _workspaceObservableCallback);
+    _workspaceParser = WorkspaceNotificationParser(id: _user.token, callback: _NotificationCallback);
 
-    _userParser = UserObservableParser(id: _user.token, callback: _userObservableCallback);
+    _userParser = UserNotificationParser(id: _user.token, callback: _userObservableCallback);
 
     _subscription = RustStreamReceiver.listen((observable) {
       _workspaceParser.parse(observable);
@@ -96,11 +96,11 @@ class IUserListenerImpl extends IUserListener {
     _workspacesUpdated = workspacesCallback;
   }
 
-  void _workspaceObservableCallback(WorkspaceObservable ty, Either<Uint8List, WorkspaceError> result) {
+  void _NotificationCallback(Notification ty, Either<Uint8List, WorkspaceError> result) {
     switch (ty) {
-      case WorkspaceObservable.UserCreateWorkspace:
-      case WorkspaceObservable.UserDeleteWorkspace:
-      case WorkspaceObservable.WorkspaceListUpdated:
+      case Notification.UserCreateWorkspace:
+      case Notification.UserDeleteWorkspace:
+      case Notification.WorkspaceListUpdated:
         if (_workspacesUpdated != null) {
           result.fold(
             (payload) {
@@ -111,7 +111,7 @@ class IUserListenerImpl extends IUserListener {
           );
         }
         break;
-      case WorkspaceObservable.UserUnauthorized:
+      case Notification.UserUnauthorized:
         if (_authChanged != null) {
           result.fold(
             (_) {},

+ 11 - 23
app_flowy/lib/workspace/infrastructure/repos/app_repo.dart

@@ -54,48 +54,36 @@ class AppRepository {
 
 class AppListenerRepository {
   StreamSubscription<ObservableSubject>? _subscription;
-  AppCreateViewCallback? _createView;
-  AppDeleteViewCallback? _deleteView;
+  AppViewsChangeCallback? _viewsChanged;
   AppUpdatedCallback? _update;
-  late WorkspaceObservableParser _extractor;
+  late WorkspaceNotificationParser _extractor;
   String appId;
 
   AppListenerRepository({
     required this.appId,
   });
 
-  void startListen({AppCreateViewCallback? createView, AppDeleteViewCallback? deleteView, AppUpdatedCallback? update}) {
-    _createView = createView;
-    _deleteView = deleteView;
+  void startListen({AppViewsChangeCallback? viewsChanged, AppUpdatedCallback? update}) {
+    _viewsChanged = viewsChanged;
     _update = update;
-    _extractor = WorkspaceObservableParser(id: appId, callback: _bservableCallback);
+    _extractor = WorkspaceNotificationParser(id: appId, callback: _bservableCallback);
     _subscription = RustStreamReceiver.listen((observable) => _extractor.parse(observable));
   }
 
-  void _bservableCallback(WorkspaceObservable ty, Either<Uint8List, WorkspaceError> result) {
+  void _bservableCallback(Notification ty, Either<Uint8List, WorkspaceError> result) {
     switch (ty) {
-      case WorkspaceObservable.AppCreateView:
-        if (_createView != null) {
+      case Notification.AppViewsChanged:
+        if (_viewsChanged != null) {
           result.fold(
             (payload) {
               final repeatedView = RepeatedView.fromBuffer(payload);
-              _createView!(left(repeatedView.items));
+              _viewsChanged!(left(repeatedView.items));
             },
-            (error) => _createView!(right(error)),
+            (error) => _viewsChanged!(right(error)),
           );
         }
         break;
-      case WorkspaceObservable.AppDeleteView:
-        if (_deleteView != null) {
-          result.fold(
-            (payload) => _deleteView!(
-              left(RepeatedView.fromBuffer(payload).items),
-            ),
-            (error) => _deleteView!(right(error)),
-          );
-        }
-        break;
-      case WorkspaceObservable.AppUpdated:
+      case Notification.AppUpdated:
         if (_update != null) {
           result.fold(
             (payload) {

+ 8 - 37
app_flowy/lib/workspace/infrastructure/repos/helper.dart

@@ -5,39 +5,10 @@ import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/observable.pb.dart';
 import 'package:dartz/dartz.dart';
 
-// class WorkspaceObservableParser {
-//   String id;
-//   void Function(WorkspaceObservable, Either<Uint8List, WorkspaceError>)
-//       callback;
-
-//   WorkspaceObservableParser({required this.id, required this.callback});
-//   void parse(ObservableSubject subject) {
-//     if (subject.id != id) {
-//       return;
-//     }
-
-//     final ty = WorkspaceObservable.valueOf(subject.ty);
-//     if (ty == null) {
-//       return;
-//     }
-
-//     if (subject.hasPayload()) {
-//       final bytes = Uint8List.fromList(subject.error);
-//       callback(ty, left(bytes));
-//     } else if (subject.hasError()) {
-//       final bytes = Uint8List.fromList(subject.error);
-//       final error = WorkspaceError.fromBuffer(bytes);
-//       callback(ty, right(error));
-//     } else {
-//       // do nothing
-//     }
-//   }
-// }
-
 typedef UserObservableCallback = void Function(UserObservable, Either<Uint8List, UserError>);
 
-class UserObservableParser extends ObservableParser<UserObservable, UserError> {
-  UserObservableParser({required String id, required UserObservableCallback callback})
+class UserNotificationParser extends NotificationParser<UserObservable, UserError> {
+  UserNotificationParser({required String id, required UserObservableCallback callback})
       : super(
           id: id,
           callback: callback,
@@ -46,26 +17,26 @@ class UserObservableParser extends ObservableParser<UserObservable, UserError> {
         );
 }
 
-typedef WorkspaceObservableCallback = void Function(WorkspaceObservable, Either<Uint8List, WorkspaceError>);
+typedef NotificationCallback = void Function(Notification, Either<Uint8List, WorkspaceError>);
 
-class WorkspaceObservableParser extends ObservableParser<WorkspaceObservable, WorkspaceError> {
-  WorkspaceObservableParser({required String id, required WorkspaceObservableCallback callback})
+class WorkspaceNotificationParser extends NotificationParser<Notification, WorkspaceError> {
+  WorkspaceNotificationParser({required String id, required NotificationCallback callback})
       : super(
           id: id,
           callback: callback,
-          tyParser: (ty) => WorkspaceObservable.valueOf(ty),
+          tyParser: (ty) => Notification.valueOf(ty),
           errorParser: (bytes) => WorkspaceError.fromBuffer(bytes),
         );
 }
 
-class ObservableParser<T, E> {
+class NotificationParser<T, E> {
   String id;
   void Function(T, Either<Uint8List, E>) callback;
 
   T? Function(int) tyParser;
   E Function(Uint8List) errorParser;
 
-  ObservableParser({required this.id, required this.callback, required this.errorParser, required this.tyParser});
+  NotificationParser({required this.id, required this.callback, required this.errorParser, required this.tyParser});
   void parse(ObservableSubject subject) {
     if (subject.id != id) {
       return;

+ 23 - 0
app_flowy/lib/workspace/infrastructure/repos/trash_repo.dart

@@ -0,0 +1,23 @@
+import 'dart:async';
+import 'package:dartz/dartz.dart';
+import 'package:flowy_sdk/dispatch/dispatch.dart';
+import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-workspace/trash_create.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-workspace/trash_delete.pb.dart';
+
+class TrashRepo {
+  Future<Either<RepeatedTrash, WorkspaceError>> readTrash() {
+    return WorkspaceEventReadTrash().send();
+  }
+
+  Future<Either<Unit, WorkspaceError>> putback(String trashId) {
+    final id = TrashIdentifier.create()..id = trashId;
+
+    return WorkspaceEventPutbackTrash(id).send();
+  }
+
+  Future<Either<Unit, WorkspaceError>> delete(String trashId) {
+    final id = TrashIdentifier.create()..id = trashId;
+    return WorkspaceEventDeleteTrash(id).send();
+  }
+}

+ 4 - 4
app_flowy/lib/workspace/infrastructure/repos/view_repo.dart

@@ -53,7 +53,7 @@ class ViewRepository {
 class ViewListenerRepository {
   StreamSubscription<ObservableSubject>? _subscription;
   ViewUpdatedCallback? _update;
-  late WorkspaceObservableParser _extractor;
+  late WorkspaceNotificationParser _extractor;
   View view;
 
   ViewListenerRepository({
@@ -64,7 +64,7 @@ class ViewListenerRepository {
     ViewUpdatedCallback? update,
   }) {
     _update = update;
-    _extractor = WorkspaceObservableParser(
+    _extractor = WorkspaceNotificationParser(
       id: view.id,
       callback: (ty, result) {
         _handleObservableType(ty, result);
@@ -74,9 +74,9 @@ class ViewListenerRepository {
     _subscription = RustStreamReceiver.listen((observable) => _extractor.parse(observable));
   }
 
-  void _handleObservableType(WorkspaceObservable ty, Either<Uint8List, WorkspaceError> result) {
+  void _handleObservableType(Notification ty, Either<Uint8List, WorkspaceError> result) {
     switch (ty) {
-      case WorkspaceObservable.ViewUpdated:
+      case Notification.ViewUpdated:
         if (_update != null) {
           result.fold(
             (payload) {

+ 6 - 6
app_flowy/lib/workspace/infrastructure/repos/workspace_repo.dart

@@ -67,7 +67,7 @@ class WorkspaceListenerRepo {
   WorkspaceCreateAppCallback? _createApp;
   WorkspaceDeleteAppCallback? _deleteApp;
   WorkspaceUpdatedCallback? _update;
-  late WorkspaceObservableParser _extractor;
+  late WorkspaceNotificationParser _extractor;
   final UserProfile user;
   final String workspaceId;
 
@@ -85,7 +85,7 @@ class WorkspaceListenerRepo {
     _deleteApp = deleteApp;
     _update = update;
 
-    _extractor = WorkspaceObservableParser(
+    _extractor = WorkspaceNotificationParser(
       id: workspaceId,
       callback: (ty, result) {
         _handleObservableType(ty, result);
@@ -95,9 +95,9 @@ class WorkspaceListenerRepo {
     _subscription = RustStreamReceiver.listen((observable) => _extractor.parse(observable));
   }
 
-  void _handleObservableType(WorkspaceObservable ty, Either<Uint8List, WorkspaceError> result) {
+  void _handleObservableType(Notification ty, Either<Uint8List, WorkspaceError> result) {
     switch (ty) {
-      case WorkspaceObservable.WorkspaceUpdated:
+      case Notification.WorkspaceUpdated:
         if (_update != null) {
           result.fold(
             (payload) {
@@ -108,7 +108,7 @@ class WorkspaceListenerRepo {
           );
         }
         break;
-      case WorkspaceObservable.WorkspaceCreateApp:
+      case Notification.WorkspaceCreateApp:
         if (_createApp != null) {
           result.fold(
             (payload) => _createApp!(
@@ -118,7 +118,7 @@ class WorkspaceListenerRepo {
           );
         }
         break;
-      case WorkspaceObservable.WorkspaceDeleteApp:
+      case Notification.WorkspaceDeleteApp:
         if (_deleteApp != null) {
           result.fold(
             (payload) => _deleteApp!(

+ 1 - 1
app_flowy/lib/workspace/presentation/home/navigation.dart

@@ -1,6 +1,6 @@
 import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
-import 'package:flowy_infra_ui/style_widget/text_button.dart';
+import 'package:flowy_infra_ui/style_widget/button.dart';
 import 'package:flutter/material.dart';
 import 'package:provider/provider.dart';
 import 'package:styled_widget/styled_widget.dart';

+ 128 - 0
app_flowy/lib/workspace/presentation/stack_page/trash/trash_page.dart

@@ -0,0 +1,128 @@
+import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
+import 'package:flowy_infra/image.dart';
+import 'package:flowy_infra/theme.dart';
+import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
+import 'package:flowy_infra_ui/style_widget/text.dart';
+import 'package:flowy_infra_ui/style_widget/button.dart';
+import 'package:flowy_infra_ui/widget/spacing.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import 'package:styled_widget/styled_widget.dart';
+
+class TrashStackContext extends HomeStackContext {
+  @override
+  String get identifier => "TrashStackContext";
+
+  @override
+  List<Object?> get props => ["TrashStackContext"];
+
+  @override
+  Widget get titleWidget => const FlowyText.medium('Trash', fontSize: 12);
+
+  @override
+  HomeStackType get type => HomeStackType.trash;
+
+  @override
+  Widget render() {
+    return const TrashStackPage();
+  }
+
+  @override
+  List<NavigationItem> get navigationItems => [this];
+}
+
+class TrashStackPage extends StatefulWidget {
+  const TrashStackPage({Key? key}) : super(key: key);
+
+  @override
+  State<TrashStackPage> createState() => _TrashStackPageState();
+}
+
+class _TrashStackPageState extends State<TrashStackPage> {
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return SizedBox.expand(
+      child: Column(
+        children: [
+          _renderTopBar(theme),
+          _renderTrashList(context, theme),
+        ],
+        mainAxisAlignment: MainAxisAlignment.start,
+      ).padding(horizontal: 80, vertical: 48),
+    );
+  }
+
+  Widget _renderTopBar(AppTheme theme) {
+    return SizedBox(
+      height: 36,
+      child: Row(
+        children: [
+          const FlowyText.semibold('Trash'),
+          const Spacer(),
+          SizedBox.fromSize(
+            size: const Size(102, 30),
+            child: FlowyButton(
+              text: const FlowyText.medium('Restore all', fontSize: 12),
+              icon: svg('editor/restore'),
+              hoverColor: theme.hover,
+              onTap: () {},
+            ),
+          ),
+          const HSpace(6),
+          SizedBox.fromSize(
+            size: const Size(102, 30),
+            child: FlowyButton(
+              text: const FlowyText.medium('Delete all', fontSize: 12),
+              icon: svg('editor/delete'),
+              hoverColor: theme.hover,
+              onTap: () {},
+            ),
+          )
+        ],
+      ),
+    );
+  }
+
+  Widget _renderTrashList(BuildContext context, AppTheme theme) {
+    return Expanded(
+      child: CustomScrollView(
+        physics: StyledScrollPhysics(),
+        slivers: [
+          _renderListHeader(context),
+          _renderListBody(context),
+        ],
+      ),
+    );
+  }
+
+  Widget _renderListHeader(BuildContext context) {
+    return const SliverAppBar(
+      automaticallyImplyLeading: false,
+      backgroundColor: Colors.green,
+      title: Text('Have a nice day'),
+      floating: true,
+    );
+  }
+
+  Widget _renderListBody(BuildContext context) {
+    return SliverList(
+      delegate: SliverChildBuilderDelegate(
+        (BuildContext context, int index) {
+          return Card(
+            child: Container(
+              color: Colors.blue[100 * (index % 9 + 1)],
+              height: 80,
+              alignment: Alignment.center,
+              child: Text(
+                "Item $index",
+                style: const TextStyle(fontSize: 30),
+              ),
+            ),
+          );
+        },
+        childCount: 3,
+      ),
+    );
+  }
+}

+ 0 - 0
app_flowy/lib/workspace/presentation/stack_page/trash/widget/list_body.dart


+ 1 - 0
app_flowy/lib/workspace/presentation/stack_page/trash/widget/list_header.dart

@@ -0,0 +1 @@
+

+ 6 - 0
app_flowy/lib/workspace/presentation/stack_page/trash/widget/sizes.dart

@@ -0,0 +1,6 @@
+class TrashListSizes {
+  static double scale = 1;
+  static double get fileNameWidth => 320 * scale;
+  static double get lashModifyWidth => 230 * scale;
+  static double get createTimeWidth => 230 * scale;
+}

+ 0 - 47
app_flowy/lib/workspace/presentation/stack_page/trash_page.dart

@@ -1,47 +0,0 @@
-import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
-import 'package:flowy_infra_ui/style_widget/text.dart';
-import 'package:flutter/material.dart';
-
-class TrashStackContext extends HomeStackContext {
-  @override
-  String get identifier => "TrashStackContext";
-
-  @override
-  List<Object?> get props => ["TrashStackContext"];
-
-  @override
-  Widget get titleWidget => const FlowyText.medium('Trash', fontSize: 12);
-
-  @override
-  HomeStackType get type => HomeStackType.trash;
-
-  @override
-  Widget render() {
-    return const TrashStackPage();
-  }
-
-  @override
-  List<NavigationItem> get navigationItems => [this];
-}
-
-class TrashStackPage extends StatefulWidget {
-  const TrashStackPage({Key? key}) : super(key: key);
-
-  @override
-  State<TrashStackPage> createState() => _TrashStackPageState();
-}
-
-class _TrashStackPageState extends State<TrashStackPage> {
-  @override
-  Widget build(BuildContext context) {
-    return SizedBox.expand(
-      child: Container(
-        color: Colors.white,
-        child: Padding(
-          padding: const EdgeInsets.all(10),
-          child: Container(),
-        ),
-      ),
-    );
-  }
-}

+ 8 - 7
app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/list_overlay.dart

@@ -31,13 +31,14 @@ class ListOverlay extends StatelessWidget {
           ],
         ),
         child: Padding(
-            padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 6),
-            child: ListView.builder(
-              shrinkWrap: true,
-              itemBuilder: itemBuilder,
-              itemCount: itemCount,
-              controller: controller,
-            )),
+          padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 6),
+          child: ListView.builder(
+            shrinkWrap: true,
+            itemBuilder: itemBuilder,
+            itemCount: itemCount,
+            controller: controller,
+          ),
+        ),
       ),
     );
   }

+ 102 - 0
app_flowy/packages/flowy_infra_ui/lib/style_widget/button.dart

@@ -0,0 +1,102 @@
+import 'package:flowy_infra/theme.dart';
+import 'package:flowy_infra_ui/style_widget/hover.dart';
+import 'package:flowy_infra_ui/style_widget/text.dart';
+import 'package:flowy_infra_ui/widget/spacing.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+import 'package:provider/provider.dart';
+
+class FlowyButton extends StatelessWidget {
+  final Widget text;
+  final VoidCallback? onTap;
+  final EdgeInsets padding;
+  final Widget? icon;
+  final Color hoverColor;
+  const FlowyButton({
+    Key? key,
+    required this.text,
+    this.onTap,
+    this.padding = const EdgeInsets.symmetric(horizontal: 3, vertical: 2),
+    this.icon,
+    this.hoverColor = Colors.transparent,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return InkWell(
+      onTap: onTap,
+      child: FlowyHover(
+        config: HoverDisplayConfig(borderRadius: BorderRadius.circular(6), hoverColor: hoverColor),
+        builder: (context, onHover) => _render(),
+      ),
+    );
+  }
+
+  Widget _render() {
+    List<Widget> children = List.empty(growable: true);
+
+    if (icon != null) {
+      children.add(SizedBox.fromSize(size: const Size.square(16), child: icon!));
+      children.add(const HSpace(6));
+    }
+
+    children.add(Align(child: text));
+
+    return Padding(
+      padding: padding,
+      child: Row(
+        children: children,
+      ),
+    );
+  }
+}
+
+class FlowyTextButton extends StatelessWidget {
+  final String text;
+  final double fontSize;
+  final VoidCallback? onPressed;
+  final EdgeInsets padding;
+  const FlowyTextButton(this.text,
+      {Key? key,
+      this.onPressed,
+      this.fontSize = 16,
+      this.padding = const EdgeInsets.symmetric(horizontal: 8, vertical: 6)})
+      : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return InkWell(
+      onTap: onPressed,
+      child: FlowyHover(
+        config: HoverDisplayConfig(borderRadius: BorderRadius.circular(6), hoverColor: theme.bg3),
+        builder: (context, onHover) => _render(),
+      ),
+    );
+  }
+
+  Widget _render() {
+    return Padding(
+      padding: padding,
+      child: Align(
+        alignment: Alignment.centerLeft,
+        child: FlowyText(text, fontSize: fontSize),
+      ),
+    );
+  }
+}
+// return TextButton(
+//   style: ButtonStyle(
+//     textStyle: MaterialStateProperty.all(TextStyle(fontSize: fontSize)),
+//     alignment: Alignment.centerLeft,
+//     foregroundColor: MaterialStateProperty.all(Colors.black),
+//     padding: MaterialStateProperty.all<EdgeInsets>(
+//         const EdgeInsets.symmetric(horizontal: 2)),
+//   ),
+//   onPressed: onPressed,
+//   child: Text(
+//     text,
+//     overflow: TextOverflow.ellipsis,
+//     softWrap: false,
+//   ),
+// );

+ 5 - 0
app_flowy/packages/flowy_infra_ui/lib/style_widget/text.dart

@@ -15,6 +15,11 @@ class FlowyText extends StatelessWidget {
     this.fontWeight = FontWeight.w500,
   }) : super(key: key);
 
+  const FlowyText.semibold(this.title, {Key? key, this.fontSize = 16, TextOverflow? overflow})
+      : fontWeight = FontWeight.w600,
+        overflow = overflow ?? TextOverflow.ellipsis,
+        super(key: key);
+
   const FlowyText.medium(this.title, {Key? key, this.fontSize = 16, TextOverflow? overflow})
       : fontWeight = FontWeight.w500,
         overflow = overflow ?? TextOverflow.ellipsis,

+ 0 - 56
app_flowy/packages/flowy_infra_ui/lib/style_widget/text_button.dart

@@ -1,56 +0,0 @@
-import 'package:flowy_infra/theme.dart';
-import 'package:flowy_infra_ui/style_widget/hover.dart';
-import 'package:flowy_infra_ui/style_widget/text.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/widgets.dart';
-import 'package:provider/provider.dart';
-
-class FlowyTextButton extends StatelessWidget {
-  final String text;
-  final double fontSize;
-  final VoidCallback? onPressed;
-  final EdgeInsets padding;
-  const FlowyTextButton(this.text,
-      {Key? key,
-      this.onPressed,
-      this.fontSize = 16,
-      this.padding = const EdgeInsets.symmetric(horizontal: 8, vertical: 6)})
-      : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    final theme = context.watch<AppTheme>();
-    return InkWell(
-      onTap: onPressed,
-      child: FlowyHover(
-        config: HoverDisplayConfig(borderRadius: BorderRadius.circular(6), hoverColor: theme.bg3),
-        builder: (context, onHover) => _render(),
-      ),
-    );
-  }
-
-  Widget _render() {
-    return Padding(
-      padding: padding,
-      child: Align(
-        alignment: Alignment.centerLeft,
-        child: FlowyText(text, fontSize: fontSize),
-      ),
-    );
-  }
-}
-// return TextButton(
-    //   style: ButtonStyle(
-    //     textStyle: MaterialStateProperty.all(TextStyle(fontSize: fontSize)),
-    //     alignment: Alignment.centerLeft,
-    //     foregroundColor: MaterialStateProperty.all(Colors.black),
-    //     padding: MaterialStateProperty.all<EdgeInsets>(
-    //         const EdgeInsets.symmetric(horizontal: 2)),
-    //   ),
-    //   onPressed: onPressed,
-    //   child: Text(
-    //     text,
-    //     overflow: TextOverflow.ellipsis,
-    //     softWrap: false,
-    //   ),
-    // );

+ 36 - 2
app_flowy/packages/flowy_sdk/lib/dispatch/code_gen.dart

@@ -274,17 +274,51 @@ class WorkspaceEventApplyDocDelta {
 class WorkspaceEventReadTrash {
     WorkspaceEventReadTrash();
 
-    Future<Either<RepeatedView, WorkspaceError>> send() {
+    Future<Either<RepeatedTrash, WorkspaceError>> send() {
      final request = FFIRequest.create()
         ..event = WorkspaceEvent.ReadTrash.toString();
 
      return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-        (okBytes) => left(RepeatedView.fromBuffer(okBytes)),
+        (okBytes) => left(RepeatedTrash.fromBuffer(okBytes)),
         (errBytes) => right(WorkspaceError.fromBuffer(errBytes)),
       ));
     }
 }
 
+class WorkspaceEventPutbackTrash {
+     TrashIdentifier request;
+     WorkspaceEventPutbackTrash(this.request);
+
+    Future<Either<Unit, WorkspaceError>> send() {
+    final request = FFIRequest.create()
+          ..event = WorkspaceEvent.PutbackTrash.toString()
+          ..payload = requestToBytes(this.request);
+
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (bytes) => left(unit),
+           (errBytes) => right(WorkspaceError.fromBuffer(errBytes)),
+        ));
+    }
+}
+
+class WorkspaceEventDeleteTrash {
+     TrashIdentifier request;
+     WorkspaceEventDeleteTrash(this.request);
+
+    Future<Either<Unit, WorkspaceError>> send() {
+    final request = FFIRequest.create()
+          ..event = WorkspaceEvent.DeleteTrash.toString()
+          ..payload = requestToBytes(this.request);
+
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (bytes) => left(unit),
+           (errBytes) => right(WorkspaceError.fromBuffer(errBytes)),
+        ));
+    }
+}
+
 class WorkspaceEventInitWorkspace {
     WorkspaceEventInitWorkspace();
 

+ 4 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbenum.dart

@@ -27,6 +27,8 @@ class WorkspaceEvent extends $pb.ProtobufEnum {
   static const WorkspaceEvent OpenView = WorkspaceEvent._(205, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'OpenView');
   static const WorkspaceEvent ApplyDocDelta = WorkspaceEvent._(206, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ApplyDocDelta');
   static const WorkspaceEvent ReadTrash = WorkspaceEvent._(300, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ReadTrash');
+  static const WorkspaceEvent PutbackTrash = WorkspaceEvent._(301, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PutbackTrash');
+  static const WorkspaceEvent DeleteTrash = WorkspaceEvent._(302, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DeleteTrash');
   static const WorkspaceEvent InitWorkspace = WorkspaceEvent._(1000, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'InitWorkspace');
 
   static const $core.List<WorkspaceEvent> values = <WorkspaceEvent> [
@@ -47,6 +49,8 @@ class WorkspaceEvent extends $pb.ProtobufEnum {
     OpenView,
     ApplyDocDelta,
     ReadTrash,
+    PutbackTrash,
+    DeleteTrash,
     InitWorkspace,
   ];
 

+ 3 - 1
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbjson.dart

@@ -29,9 +29,11 @@ const WorkspaceEvent$json = const {
     const {'1': 'OpenView', '2': 205},
     const {'1': 'ApplyDocDelta', '2': 206},
     const {'1': 'ReadTrash', '2': 300},
+    const {'1': 'PutbackTrash', '2': 301},
+    const {'1': 'DeleteTrash', '2': 302},
     const {'1': 'InitWorkspace', '2': 1000},
   ],
 };
 
 /// Descriptor for `WorkspaceEvent`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List workspaceEventDescriptor = $convert.base64Decode('Cg5Xb3Jrc3BhY2VFdmVudBITCg9DcmVhdGVXb3Jrc3BhY2UQABIUChBSZWFkQ3VyV29ya3NwYWNlEAESEgoOUmVhZFdvcmtzcGFjZXMQAhITCg9EZWxldGVXb3Jrc3BhY2UQAxIRCg1PcGVuV29ya3NwYWNlEAQSFQoRUmVhZFdvcmtzcGFjZUFwcHMQBRINCglDcmVhdGVBcHAQZRINCglEZWxldGVBcHAQZhILCgdSZWFkQXBwEGcSDQoJVXBkYXRlQXBwEGgSDwoKQ3JlYXRlVmlldxDJARINCghSZWFkVmlldxDKARIPCgpVcGRhdGVWaWV3EMsBEg8KCkRlbGV0ZVZpZXcQzAESDQoIT3BlblZpZXcQzQESEgoNQXBwbHlEb2NEZWx0YRDOARIOCglSZWFkVHJhc2gQrAISEgoNSW5pdFdvcmtzcGFjZRDoBw==');
+final $typed_data.Uint8List workspaceEventDescriptor = $convert.base64Decode('Cg5Xb3Jrc3BhY2VFdmVudBITCg9DcmVhdGVXb3Jrc3BhY2UQABIUChBSZWFkQ3VyV29ya3NwYWNlEAESEgoOUmVhZFdvcmtzcGFjZXMQAhITCg9EZWxldGVXb3Jrc3BhY2UQAxIRCg1PcGVuV29ya3NwYWNlEAQSFQoRUmVhZFdvcmtzcGFjZUFwcHMQBRINCglDcmVhdGVBcHAQZRINCglEZWxldGVBcHAQZhILCgdSZWFkQXBwEGcSDQoJVXBkYXRlQXBwEGgSDwoKQ3JlYXRlVmlldxDJARINCghSZWFkVmlldxDKARIPCgpVcGRhdGVWaWV3EMsBEg8KCkRlbGV0ZVZpZXcQzAESDQoIT3BlblZpZXcQzQESEgoNQXBwbHlEb2NEZWx0YRDOARIOCglSZWFkVHJhc2gQrAISEQoMUHV0YmFja1RyYXNoEK0CEhAKC0RlbGV0ZVRyYXNoEK4CEhIKDUluaXRXb3Jrc3BhY2UQ6Ac=');

+ 17 - 19
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/observable.pbenum.dart

@@ -9,21 +9,20 @@
 import 'dart:core' as $core;
 import 'package:protobuf/protobuf.dart' as $pb;
 
-class WorkspaceObservable extends $pb.ProtobufEnum {
-  static const WorkspaceObservable Unknown = WorkspaceObservable._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Unknown');
-  static const WorkspaceObservable UserCreateWorkspace = WorkspaceObservable._(10, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserCreateWorkspace');
-  static const WorkspaceObservable UserDeleteWorkspace = WorkspaceObservable._(11, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserDeleteWorkspace');
-  static const WorkspaceObservable WorkspaceUpdated = WorkspaceObservable._(12, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceUpdated');
-  static const WorkspaceObservable WorkspaceCreateApp = WorkspaceObservable._(13, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceCreateApp');
-  static const WorkspaceObservable WorkspaceDeleteApp = WorkspaceObservable._(14, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceDeleteApp');
-  static const WorkspaceObservable WorkspaceListUpdated = WorkspaceObservable._(15, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceListUpdated');
-  static const WorkspaceObservable AppUpdated = WorkspaceObservable._(21, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AppUpdated');
-  static const WorkspaceObservable AppCreateView = WorkspaceObservable._(23, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AppCreateView');
-  static const WorkspaceObservable AppDeleteView = WorkspaceObservable._(24, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AppDeleteView');
-  static const WorkspaceObservable ViewUpdated = WorkspaceObservable._(31, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ViewUpdated');
-  static const WorkspaceObservable UserUnauthorized = WorkspaceObservable._(100, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserUnauthorized');
+class Notification extends $pb.ProtobufEnum {
+  static const Notification Unknown = Notification._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Unknown');
+  static const Notification UserCreateWorkspace = Notification._(10, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserCreateWorkspace');
+  static const Notification UserDeleteWorkspace = Notification._(11, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserDeleteWorkspace');
+  static const Notification WorkspaceUpdated = Notification._(12, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceUpdated');
+  static const Notification WorkspaceCreateApp = Notification._(13, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceCreateApp');
+  static const Notification WorkspaceDeleteApp = Notification._(14, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceDeleteApp');
+  static const Notification WorkspaceListUpdated = Notification._(15, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceListUpdated');
+  static const Notification AppUpdated = Notification._(21, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AppUpdated');
+  static const Notification AppViewsChanged = Notification._(24, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AppViewsChanged');
+  static const Notification ViewUpdated = Notification._(31, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ViewUpdated');
+  static const Notification UserUnauthorized = Notification._(100, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserUnauthorized');
 
-  static const $core.List<WorkspaceObservable> values = <WorkspaceObservable> [
+  static const $core.List<Notification> values = <Notification> [
     Unknown,
     UserCreateWorkspace,
     UserDeleteWorkspace,
@@ -32,15 +31,14 @@ class WorkspaceObservable extends $pb.ProtobufEnum {
     WorkspaceDeleteApp,
     WorkspaceListUpdated,
     AppUpdated,
-    AppCreateView,
-    AppDeleteView,
+    AppViewsChanged,
     ViewUpdated,
     UserUnauthorized,
   ];
 
-  static final $core.Map<$core.int, WorkspaceObservable> _byValue = $pb.ProtobufEnum.initByValue(values);
-  static WorkspaceObservable? valueOf($core.int value) => _byValue[value];
+  static final $core.Map<$core.int, Notification> _byValue = $pb.ProtobufEnum.initByValue(values);
+  static Notification? valueOf($core.int value) => _byValue[value];
 
-  const WorkspaceObservable._($core.int v, $core.String n) : super(v, n);
+  const Notification._($core.int v, $core.String n) : super(v, n);
 }
 

+ 6 - 7
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/observable.pbjson.dart

@@ -8,9 +8,9 @@
 import 'dart:core' as $core;
 import 'dart:convert' as $convert;
 import 'dart:typed_data' as $typed_data;
-@$core.Deprecated('Use workspaceObservableDescriptor instead')
-const WorkspaceObservable$json = const {
-  '1': 'WorkspaceObservable',
+@$core.Deprecated('Use notificationDescriptor instead')
+const Notification$json = const {
+  '1': 'Notification',
   '2': const [
     const {'1': 'Unknown', '2': 0},
     const {'1': 'UserCreateWorkspace', '2': 10},
@@ -20,12 +20,11 @@ const WorkspaceObservable$json = const {
     const {'1': 'WorkspaceDeleteApp', '2': 14},
     const {'1': 'WorkspaceListUpdated', '2': 15},
     const {'1': 'AppUpdated', '2': 21},
-    const {'1': 'AppCreateView', '2': 23},
-    const {'1': 'AppDeleteView', '2': 24},
+    const {'1': 'AppViewsChanged', '2': 24},
     const {'1': 'ViewUpdated', '2': 31},
     const {'1': 'UserUnauthorized', '2': 100},
   ],
 };
 
-/// Descriptor for `WorkspaceObservable`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List workspaceObservableDescriptor = $convert.base64Decode('ChNXb3Jrc3BhY2VPYnNlcnZhYmxlEgsKB1Vua25vd24QABIXChNVc2VyQ3JlYXRlV29ya3NwYWNlEAoSFwoTVXNlckRlbGV0ZVdvcmtzcGFjZRALEhQKEFdvcmtzcGFjZVVwZGF0ZWQQDBIWChJXb3Jrc3BhY2VDcmVhdGVBcHAQDRIWChJXb3Jrc3BhY2VEZWxldGVBcHAQDhIYChRXb3Jrc3BhY2VMaXN0VXBkYXRlZBAPEg4KCkFwcFVwZGF0ZWQQFRIRCg1BcHBDcmVhdGVWaWV3EBcSEQoNQXBwRGVsZXRlVmlldxAYEg8KC1ZpZXdVcGRhdGVkEB8SFAoQVXNlclVuYXV0aG9yaXplZBBk');
+/// Descriptor for `Notification`. Decode as a `google.protobuf.EnumDescriptorProto`.
+final $typed_data.Uint8List notificationDescriptor = $convert.base64Decode('CgxOb3RpZmljYXRpb24SCwoHVW5rbm93bhAAEhcKE1VzZXJDcmVhdGVXb3Jrc3BhY2UQChIXChNVc2VyRGVsZXRlV29ya3NwYWNlEAsSFAoQV29ya3NwYWNlVXBkYXRlZBAMEhYKEldvcmtzcGFjZUNyZWF0ZUFwcBANEhYKEldvcmtzcGFjZURlbGV0ZUFwcBAOEhgKFFdvcmtzcGFjZUxpc3RVcGRhdGVkEA8SDgoKQXBwVXBkYXRlZBAVEhMKD0FwcFZpZXdzQ2hhbmdlZBAYEg8KC1ZpZXdVcGRhdGVkEB8SFAoQVXNlclVuYXV0aG9yaXplZBBk');

+ 2 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/protobuf.dart

@@ -5,6 +5,7 @@ export './app_query.pb.dart';
 export './workspace_delete.pb.dart';
 export './observable.pb.dart';
 export './errors.pb.dart';
+export './trash_delete.pb.dart';
 export './workspace_update.pb.dart';
 export './app_create.pb.dart';
 export './workspace_query.pb.dart';
@@ -14,4 +15,5 @@ export './workspace_user_detail.pb.dart';
 export './workspace_create.pb.dart';
 export './app_update.pb.dart';
 export './view_query.pb.dart';
+export './trash_create.pb.dart';
 export './app_delete.pb.dart';

+ 231 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_create.pb.dart

@@ -0,0 +1,231 @@
+///
+//  Generated code. Do not modify.
+//  source: trash_create.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
+
+import 'dart:core' as $core;
+
+import 'package:fixnum/fixnum.dart' as $fixnum;
+import 'package:protobuf/protobuf.dart' as $pb;
+
+class CreateTrashParams extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'CreateTrashParams', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
+    ..aInt64(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'modifiedTime')
+    ..aInt64(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'createTime')
+    ..hasRequiredFields = false
+  ;
+
+  CreateTrashParams._() : super();
+  factory CreateTrashParams({
+    $core.String? id,
+    $core.String? name,
+    $fixnum.Int64? modifiedTime,
+    $fixnum.Int64? createTime,
+  }) {
+    final _result = create();
+    if (id != null) {
+      _result.id = id;
+    }
+    if (name != null) {
+      _result.name = name;
+    }
+    if (modifiedTime != null) {
+      _result.modifiedTime = modifiedTime;
+    }
+    if (createTime != null) {
+      _result.createTime = createTime;
+    }
+    return _result;
+  }
+  factory CreateTrashParams.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory CreateTrashParams.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  CreateTrashParams clone() => CreateTrashParams()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  CreateTrashParams copyWith(void Function(CreateTrashParams) updates) => super.copyWith((message) => updates(message as CreateTrashParams)) as CreateTrashParams; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static CreateTrashParams create() => CreateTrashParams._();
+  CreateTrashParams createEmptyInstance() => create();
+  static $pb.PbList<CreateTrashParams> createRepeated() => $pb.PbList<CreateTrashParams>();
+  @$core.pragma('dart2js:noInline')
+  static CreateTrashParams getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CreateTrashParams>(create);
+  static CreateTrashParams? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get id => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set id($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearId() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.String get name => $_getSZ(1);
+  @$pb.TagNumber(2)
+  set name($core.String v) { $_setString(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasName() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearName() => clearField(2);
+
+  @$pb.TagNumber(3)
+  $fixnum.Int64 get modifiedTime => $_getI64(2);
+  @$pb.TagNumber(3)
+  set modifiedTime($fixnum.Int64 v) { $_setInt64(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasModifiedTime() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearModifiedTime() => clearField(3);
+
+  @$pb.TagNumber(4)
+  $fixnum.Int64 get createTime => $_getI64(3);
+  @$pb.TagNumber(4)
+  set createTime($fixnum.Int64 v) { $_setInt64(3, v); }
+  @$pb.TagNumber(4)
+  $core.bool hasCreateTime() => $_has(3);
+  @$pb.TagNumber(4)
+  void clearCreateTime() => clearField(4);
+}
+
+class Trash extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Trash', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
+    ..aInt64(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'modifiedTime')
+    ..aInt64(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'createTime')
+    ..hasRequiredFields = false
+  ;
+
+  Trash._() : super();
+  factory Trash({
+    $core.String? id,
+    $core.String? name,
+    $fixnum.Int64? modifiedTime,
+    $fixnum.Int64? createTime,
+  }) {
+    final _result = create();
+    if (id != null) {
+      _result.id = id;
+    }
+    if (name != null) {
+      _result.name = name;
+    }
+    if (modifiedTime != null) {
+      _result.modifiedTime = modifiedTime;
+    }
+    if (createTime != null) {
+      _result.createTime = createTime;
+    }
+    return _result;
+  }
+  factory Trash.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory Trash.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  Trash clone() => Trash()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  Trash copyWith(void Function(Trash) updates) => super.copyWith((message) => updates(message as Trash)) as Trash; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static Trash create() => Trash._();
+  Trash createEmptyInstance() => create();
+  static $pb.PbList<Trash> createRepeated() => $pb.PbList<Trash>();
+  @$core.pragma('dart2js:noInline')
+  static Trash getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Trash>(create);
+  static Trash? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get id => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set id($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearId() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.String get name => $_getSZ(1);
+  @$pb.TagNumber(2)
+  set name($core.String v) { $_setString(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasName() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearName() => clearField(2);
+
+  @$pb.TagNumber(3)
+  $fixnum.Int64 get modifiedTime => $_getI64(2);
+  @$pb.TagNumber(3)
+  set modifiedTime($fixnum.Int64 v) { $_setInt64(2, v); }
+  @$pb.TagNumber(3)
+  $core.bool hasModifiedTime() => $_has(2);
+  @$pb.TagNumber(3)
+  void clearModifiedTime() => clearField(3);
+
+  @$pb.TagNumber(4)
+  $fixnum.Int64 get createTime => $_getI64(3);
+  @$pb.TagNumber(4)
+  set createTime($fixnum.Int64 v) { $_setInt64(3, v); }
+  @$pb.TagNumber(4)
+  $core.bool hasCreateTime() => $_has(3);
+  @$pb.TagNumber(4)
+  void clearCreateTime() => clearField(4);
+}
+
+class RepeatedTrash extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'RepeatedTrash', createEmptyInstance: create)
+    ..pc<Trash>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'items', $pb.PbFieldType.PM, subBuilder: Trash.create)
+    ..hasRequiredFields = false
+  ;
+
+  RepeatedTrash._() : super();
+  factory RepeatedTrash({
+    $core.Iterable<Trash>? items,
+  }) {
+    final _result = create();
+    if (items != null) {
+      _result.items.addAll(items);
+    }
+    return _result;
+  }
+  factory RepeatedTrash.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory RepeatedTrash.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  RepeatedTrash clone() => RepeatedTrash()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  RepeatedTrash copyWith(void Function(RepeatedTrash) updates) => super.copyWith((message) => updates(message as RepeatedTrash)) as RepeatedTrash; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static RepeatedTrash create() => RepeatedTrash._();
+  RepeatedTrash createEmptyInstance() => create();
+  static $pb.PbList<RepeatedTrash> createRepeated() => $pb.PbList<RepeatedTrash>();
+  @$core.pragma('dart2js:noInline')
+  static RepeatedTrash getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<RepeatedTrash>(create);
+  static RepeatedTrash? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.List<Trash> get items => $_getList(0);
+}
+

+ 7 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_create.pbenum.dart

@@ -0,0 +1,7 @@
+///
+//  Generated code. Do not modify.
+//  source: trash_create.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
+

+ 46 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_create.pbjson.dart

@@ -0,0 +1,46 @@
+///
+//  Generated code. Do not modify.
+//  source: trash_create.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
+
+import 'dart:core' as $core;
+import 'dart:convert' as $convert;
+import 'dart:typed_data' as $typed_data;
+@$core.Deprecated('Use createTrashParamsDescriptor instead')
+const CreateTrashParams$json = const {
+  '1': 'CreateTrashParams',
+  '2': const [
+    const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
+    const {'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'},
+    const {'1': 'modified_time', '3': 3, '4': 1, '5': 3, '10': 'modifiedTime'},
+    const {'1': 'create_time', '3': 4, '4': 1, '5': 3, '10': 'createTime'},
+  ],
+};
+
+/// Descriptor for `CreateTrashParams`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List createTrashParamsDescriptor = $convert.base64Decode('ChFDcmVhdGVUcmFzaFBhcmFtcxIOCgJpZBgBIAEoCVICaWQSEgoEbmFtZRgCIAEoCVIEbmFtZRIjCg1tb2RpZmllZF90aW1lGAMgASgDUgxtb2RpZmllZFRpbWUSHwoLY3JlYXRlX3RpbWUYBCABKANSCmNyZWF0ZVRpbWU=');
+@$core.Deprecated('Use trashDescriptor instead')
+const Trash$json = const {
+  '1': 'Trash',
+  '2': const [
+    const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
+    const {'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'},
+    const {'1': 'modified_time', '3': 3, '4': 1, '5': 3, '10': 'modifiedTime'},
+    const {'1': 'create_time', '3': 4, '4': 1, '5': 3, '10': 'createTime'},
+  ],
+};
+
+/// Descriptor for `Trash`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List trashDescriptor = $convert.base64Decode('CgVUcmFzaBIOCgJpZBgBIAEoCVICaWQSEgoEbmFtZRgCIAEoCVIEbmFtZRIjCg1tb2RpZmllZF90aW1lGAMgASgDUgxtb2RpZmllZFRpbWUSHwoLY3JlYXRlX3RpbWUYBCABKANSCmNyZWF0ZVRpbWU=');
+@$core.Deprecated('Use repeatedTrashDescriptor instead')
+const RepeatedTrash$json = const {
+  '1': 'RepeatedTrash',
+  '2': const [
+    const {'1': 'items', '3': 1, '4': 3, '5': 11, '6': '.Trash', '10': 'items'},
+  ],
+};
+
+/// Descriptor for `RepeatedTrash`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List repeatedTrashDescriptor = $convert.base64Decode('Cg1SZXBlYXRlZFRyYXNoEhwKBWl0ZW1zGAEgAygLMgYuVHJhc2hSBWl0ZW1z');

+ 9 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_create.pbserver.dart

@@ -0,0 +1,9 @@
+///
+//  Generated code. Do not modify.
+//  source: trash_create.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
+
+export 'trash_create.pb.dart';
+

+ 58 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_delete.pb.dart

@@ -0,0 +1,58 @@
+///
+//  Generated code. Do not modify.
+//  source: trash_delete.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
+
+import 'dart:core' as $core;
+
+import 'package:protobuf/protobuf.dart' as $pb;
+
+class TrashIdentifier extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'TrashIdentifier', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'id')
+    ..hasRequiredFields = false
+  ;
+
+  TrashIdentifier._() : super();
+  factory TrashIdentifier({
+    $core.String? id,
+  }) {
+    final _result = create();
+    if (id != null) {
+      _result.id = id;
+    }
+    return _result;
+  }
+  factory TrashIdentifier.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory TrashIdentifier.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
+  'Will be removed in next major version')
+  TrashIdentifier clone() => TrashIdentifier()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  TrashIdentifier copyWith(void Function(TrashIdentifier) updates) => super.copyWith((message) => updates(message as TrashIdentifier)) as TrashIdentifier; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static TrashIdentifier create() => TrashIdentifier._();
+  TrashIdentifier createEmptyInstance() => create();
+  static $pb.PbList<TrashIdentifier> createRepeated() => $pb.PbList<TrashIdentifier>();
+  @$core.pragma('dart2js:noInline')
+  static TrashIdentifier getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<TrashIdentifier>(create);
+  static TrashIdentifier? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get id => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set id($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearId() => clearField(1);
+}
+

+ 7 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_delete.pbenum.dart

@@ -0,0 +1,7 @@
+///
+//  Generated code. Do not modify.
+//  source: trash_delete.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
+

+ 20 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_delete.pbjson.dart

@@ -0,0 +1,20 @@
+///
+//  Generated code. Do not modify.
+//  source: trash_delete.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
+
+import 'dart:core' as $core;
+import 'dart:convert' as $convert;
+import 'dart:typed_data' as $typed_data;
+@$core.Deprecated('Use trashIdentifierDescriptor instead')
+const TrashIdentifier$json = const {
+  '1': 'TrashIdentifier',
+  '2': const [
+    const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
+  ],
+};
+
+/// Descriptor for `TrashIdentifier`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List trashIdentifierDescriptor = $convert.base64Decode('Cg9UcmFzaElkZW50aWZpZXISDgoCaWQYASABKAlSAmlk');

+ 9 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/trash_delete.pbserver.dart

@@ -0,0 +1,9 @@
+///
+//  Generated code. Do not modify.
+//  source: trash_delete.proto
+//
+// @dart = 2.12
+// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
+
+export 'trash_delete.pb.dart';
+

+ 2 - 1
rust-lib/flowy-database/migrations/2021-07-09-063045_flowy-user/up.sql

@@ -48,5 +48,6 @@ CREATE TABLE trash_table (
     name TEXT NOT NULL DEFAULT '',
     desc TEXT NOT NULL DEFAULT '',
     modified_time BIGINT NOT NULL DEFAULT 0,
-    create_time BIGINT NOT NULL DEFAULT 0
+    create_time BIGINT NOT NULL DEFAULT 0,
+    source INTEGER NOT NULL DEFAULT 0
 );

+ 1 - 0
rust-lib/flowy-database/src/schema.rs

@@ -40,6 +40,7 @@ table! {
         desc -> Text,
         modified_time -> BigInt,
         create_time -> BigInt,
+        source -> Integer,
     }
 }
 

+ 5 - 1
rust-lib/flowy-derive/src/derive_cache/derive_cache.rs

@@ -38,6 +38,10 @@ pub fn category_from_str(type_str: &str) -> TypeCategory {
         | "QueryWorkspaceRequest"
         | "QueryWorkspaceParams"
         | "CurrentWorkspace"
+        | "TrashIdentifier"
+        | "CreateTrashParams"
+        | "Trash"
+        | "RepeatedTrash"
         | "UpdateViewRequest"
         | "UpdateViewParams"
         | "DeleteViewRequest"
@@ -81,7 +85,7 @@ pub fn category_from_str(type_str: &str) -> TypeCategory {
         "ViewType"
         | "WorkspaceEvent"
         | "ErrorCode"
-        | "WorkspaceObservable"
+        | "Notification"
         | "WsModule"
         | "RevType"
         | "WsDataType"

+ 2 - 0
rust-lib/flowy-workspace/Cargo.toml

@@ -16,6 +16,7 @@ flowy-document = { path = "../flowy-document" }
 flowy-ot = { path = "../flowy-ot" }
 flowy-net = { path = "../flowy-net", features = ["flowy_request"] }
 
+parking_lot = "0.11"
 protobuf = {version = "2.18.0"}
 log = "0.4.14"
 diesel = {version = "1.4.7", features = ["sqlite"]}
@@ -24,6 +25,7 @@ diesel_derives = {version = "1.4.1", features = ["sqlite"]}
 #diesel_derives = { git = "https://github.com/diesel-rs/diesel.git", branch = "master",features = ["sqlite"] }
 
 futures-core = { version = "0.3", default-features = false }
+futures = "0.3.15"
 pin-project = "1.0.0"
 strum = "0.21"
 strum_macros = "0.21"

+ 3 - 1
rust-lib/flowy-workspace/src/entities/trash/trash_create.rs

@@ -1,4 +1,4 @@
-use crate::errors::WorkspaceError;
+use crate::{errors::WorkspaceError, impl_def_and_def_mut};
 use flowy_derive::ProtoBuf;
 use std::convert::TryInto;
 
@@ -37,3 +37,5 @@ pub struct RepeatedTrash {
     #[pb(index = 1)]
     pub items: Vec<Trash>,
 }
+
+impl_def_and_def_mut!(RepeatedTrash, Trash);

+ 1 - 1
rust-lib/flowy-workspace/src/entities/trash/trash_delete.rs

@@ -1,7 +1,7 @@
 use flowy_derive::ProtoBuf;
 
 #[derive(PartialEq, ProtoBuf, Default, Debug, Clone)]
-pub struct DeleteTrashRequest {
+pub struct TrashIdentifier {
     #[pb(index = 1)]
     pub id: String,
 }

+ 2 - 2
rust-lib/flowy-workspace/src/entities/view/view_create.rs

@@ -154,8 +154,8 @@ pub struct RepeatedView {
 
 impl_def_and_def_mut!(RepeatedView, View);
 
-impl std::convert::Into(Trash) for View {
-    fn into(self) -> (Trash) {
+impl std::convert::Into<Trash> for View {
+    fn into(self) -> Trash {
         Trash {
             id: self.id,
             name: self.name,

+ 2 - 0
rust-lib/flowy-workspace/src/errors.rs

@@ -6,6 +6,8 @@ use flowy_document::errors::DocError;
 use flowy_net::errors::ErrorCode as ServerErrorCode;
 use std::{convert::TryInto, fmt, fmt::Debug};
 
+pub type WorkspaceResult<T> = std::result::Result<T, WorkspaceError>;
+
 #[derive(Debug, Default, Clone, ProtoBuf)]
 pub struct WorkspaceError {
     #[pb(index = 1)]

+ 7 - 1
rust-lib/flowy-workspace/src/event.rs

@@ -52,9 +52,15 @@ pub enum WorkspaceEvent {
     #[event(input = "DocDelta", output = "DocDelta")]
     ApplyDocDelta     = 206,
 
-    #[event(output = "DocDelta", output = "RepeatedView")]
+    #[event(output = "RepeatedTrash")]
     ReadTrash         = 300,
 
+    #[event(input = "TrashIdentifier")]
+    PutbackTrash      = 301,
+
+    #[event(input = "TrashIdentifier")]
+    DeleteTrash       = 302,
+
     #[event()]
     InitWorkspace     = 1000,
 }

+ 23 - 2
rust-lib/flowy-workspace/src/handlers/trash_handler.rs

@@ -1,13 +1,34 @@
 use crate::{
-    entities::{trash::RepeatedTrash, view::RepeatedView},
+    entities::{
+        trash::{RepeatedTrash, TrashIdentifier},
+        view::RepeatedView,
+    },
     errors::WorkspaceError,
     services::TrashCan,
 };
 use flowy_dispatch::prelude::{data_result, Data, DataResult, Unit};
 use std::{convert::TryInto, sync::Arc};
 
-#[tracing::instrument(skip(data, controller), err)]
+#[tracing::instrument(skip(controller), err)]
 pub(crate) async fn read_trash_handler(controller: Unit<Arc<TrashCan>>) -> DataResult<RepeatedTrash, WorkspaceError> {
     let repeated_trash = controller.read_trash()?;
     data_result(repeated_trash)
 }
+
+#[tracing::instrument(skip(identifier, controller), err)]
+pub(crate) async fn putback_trash_handler(
+    identifier: Data<TrashIdentifier>,
+    controller: Unit<Arc<TrashCan>>,
+) -> Result<(), WorkspaceError> {
+    let _ = controller.putback(&identifier.id)?;
+    Ok(())
+}
+
+#[tracing::instrument(skip(identifier, controller), err)]
+pub(crate) async fn delete_trash_handler(
+    identifier: Data<TrashIdentifier>,
+    controller: Unit<Arc<TrashCan>>,
+) -> Result<(), WorkspaceError> {
+    let _ = controller.delete_trash(&identifier.id)?;
+    Ok(())
+}

+ 4 - 1
rust-lib/flowy-workspace/src/module.rs

@@ -91,7 +91,10 @@ pub fn create(workspace: Arc<WorkspaceController>) -> Module {
         .event(WorkspaceEvent::OpenView, open_view_handler)
         .event(WorkspaceEvent::ApplyDocDelta, apply_doc_delta_handler);
 
-    module = module.event(WorkspaceEvent::ReadTrash, read_trash_handler);
+    module = module
+        .event(WorkspaceEvent::ReadTrash, read_trash_handler)
+        .event(WorkspaceEvent::PutbackTrash, putback_trash_handler)
+        .event(WorkspaceEvent::DeleteTrash, delete_trash_handler);
 
     module
 }

+ 1 - 2
rust-lib/flowy-workspace/src/notify/observable.rs

@@ -12,8 +12,7 @@ pub(crate) enum Notification {
     WorkspaceDeleteApp   = 14,
     WorkspaceListUpdated = 15,
     AppUpdated           = 21,
-    AppCreateView        = 23,
-    AppDeleteView        = 24,
+    AppViewsChanged      = 24,
     ViewUpdated          = 31,
     UserUnauthorized     = 100,
 }

+ 19 - 8
rust-lib/flowy-workspace/src/protobuf/model/event.rs

@@ -42,6 +42,8 @@ pub enum WorkspaceEvent {
     OpenView = 205,
     ApplyDocDelta = 206,
     ReadTrash = 300,
+    PutbackTrash = 301,
+    DeleteTrash = 302,
     InitWorkspace = 1000,
 }
 
@@ -69,6 +71,8 @@ impl ::protobuf::ProtobufEnum for WorkspaceEvent {
             205 => ::std::option::Option::Some(WorkspaceEvent::OpenView),
             206 => ::std::option::Option::Some(WorkspaceEvent::ApplyDocDelta),
             300 => ::std::option::Option::Some(WorkspaceEvent::ReadTrash),
+            301 => ::std::option::Option::Some(WorkspaceEvent::PutbackTrash),
+            302 => ::std::option::Option::Some(WorkspaceEvent::DeleteTrash),
             1000 => ::std::option::Option::Some(WorkspaceEvent::InitWorkspace),
             _ => ::std::option::Option::None
         }
@@ -93,6 +97,8 @@ impl ::protobuf::ProtobufEnum for WorkspaceEvent {
             WorkspaceEvent::OpenView,
             WorkspaceEvent::ApplyDocDelta,
             WorkspaceEvent::ReadTrash,
+            WorkspaceEvent::PutbackTrash,
+            WorkspaceEvent::DeleteTrash,
             WorkspaceEvent::InitWorkspace,
         ];
         values
@@ -122,7 +128,7 @@ impl ::protobuf::reflect::ProtobufValue for WorkspaceEvent {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x0bevent.proto*\xd1\x02\n\x0eWorkspaceEvent\x12\x13\n\x0fCreateWorksp\
+    \n\x0bevent.proto*\xf6\x02\n\x0eWorkspaceEvent\x12\x13\n\x0fCreateWorksp\
     ace\x10\0\x12\x14\n\x10ReadCurWorkspace\x10\x01\x12\x12\n\x0eReadWorkspa\
     ces\x10\x02\x12\x13\n\x0fDeleteWorkspace\x10\x03\x12\x11\n\rOpenWorkspac\
     e\x10\x04\x12\x15\n\x11ReadWorkspaceApps\x10\x05\x12\r\n\tCreateApp\x10e\
@@ -130,10 +136,11 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \x10h\x12\x0f\n\nCreateView\x10\xc9\x01\x12\r\n\x08ReadView\x10\xca\x01\
     \x12\x0f\n\nUpdateView\x10\xcb\x01\x12\x0f\n\nDeleteView\x10\xcc\x01\x12\
     \r\n\x08OpenView\x10\xcd\x01\x12\x12\n\rApplyDocDelta\x10\xce\x01\x12\
-    \x0e\n\tReadTrash\x10\xac\x02\x12\x12\n\rInitWorkspace\x10\xe8\x07J\x8c\
-    \x06\n\x06\x12\x04\0\0\x15\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\
-    \x05\0\x12\x04\x02\0\x15\x01\n\n\n\x03\x05\0\x01\x12\x03\x02\x05\x13\n\
-    \x0b\n\x04\x05\0\x02\0\x12\x03\x03\x04\x18\n\x0c\n\x05\x05\0\x02\0\x01\
+    \x0e\n\tReadTrash\x10\xac\x02\x12\x11\n\x0cPutbackTrash\x10\xad\x02\x12\
+    \x10\n\x0bDeleteTrash\x10\xae\x02\x12\x12\n\rInitWorkspace\x10\xe8\x07J\
+    \xde\x06\n\x06\x12\x04\0\0\x17\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\
+    \x02\x05\0\x12\x04\x02\0\x17\x01\n\n\n\x03\x05\0\x01\x12\x03\x02\x05\x13\
+    \n\x0b\n\x04\x05\0\x02\0\x12\x03\x03\x04\x18\n\x0c\n\x05\x05\0\x02\0\x01\
     \x12\x03\x03\x04\x13\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x03\x16\x17\n\
     \x0b\n\x04\x05\0\x02\x01\x12\x03\x04\x04\x19\n\x0c\n\x05\x05\0\x02\x01\
     \x01\x12\x03\x04\x04\x14\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x04\x17\
@@ -167,9 +174,13 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \x05\0\x02\x0f\x01\x12\x03\x12\x04\x11\n\x0c\n\x05\x05\0\x02\x0f\x02\x12\
     \x03\x12\x14\x17\n\x0b\n\x04\x05\0\x02\x10\x12\x03\x13\x04\x14\n\x0c\n\
     \x05\x05\0\x02\x10\x01\x12\x03\x13\x04\r\n\x0c\n\x05\x05\0\x02\x10\x02\
-    \x12\x03\x13\x10\x13\n\x0b\n\x04\x05\0\x02\x11\x12\x03\x14\x04\x19\n\x0c\
-    \n\x05\x05\0\x02\x11\x01\x12\x03\x14\x04\x11\n\x0c\n\x05\x05\0\x02\x11\
-    \x02\x12\x03\x14\x14\x18b\x06proto3\
+    \x12\x03\x13\x10\x13\n\x0b\n\x04\x05\0\x02\x11\x12\x03\x14\x04\x17\n\x0c\
+    \n\x05\x05\0\x02\x11\x01\x12\x03\x14\x04\x10\n\x0c\n\x05\x05\0\x02\x11\
+    \x02\x12\x03\x14\x13\x16\n\x0b\n\x04\x05\0\x02\x12\x12\x03\x15\x04\x16\n\
+    \x0c\n\x05\x05\0\x02\x12\x01\x12\x03\x15\x04\x0f\n\x0c\n\x05\x05\0\x02\
+    \x12\x02\x12\x03\x15\x12\x15\n\x0b\n\x04\x05\0\x02\x13\x12\x03\x16\x04\
+    \x19\n\x0c\n\x05\x05\0\x02\x13\x01\x12\x03\x16\x04\x11\n\x0c\n\x05\x05\0\
+    \x02\x13\x02\x12\x03\x16\x14\x18b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 6 - 0
rust-lib/flowy-workspace/src/protobuf/model/mod.rs

@@ -18,6 +18,9 @@ pub use observable::*;
 mod errors; 
 pub use errors::*; 
 
+mod trash_delete; 
+pub use trash_delete::*; 
+
 mod workspace_update; 
 pub use workspace_update::*; 
 
@@ -45,5 +48,8 @@ pub use app_update::*;
 mod view_query; 
 pub use view_query::*; 
 
+mod trash_create; 
+pub use trash_create::*; 
+
 mod app_delete; 
 pub use app_delete::*; 

+ 63 - 69
rust-lib/flowy-workspace/src/protobuf/model/observable.rs

@@ -24,7 +24,7 @@
 // const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_22_1;
 
 #[derive(Clone,PartialEq,Eq,Debug,Hash)]
-pub enum WorkspaceObservable {
+pub enum Notification {
     Unknown = 0,
     UserCreateWorkspace = 10,
     UserDeleteWorkspace = 11,
@@ -33,49 +33,46 @@ pub enum WorkspaceObservable {
     WorkspaceDeleteApp = 14,
     WorkspaceListUpdated = 15,
     AppUpdated = 21,
-    AppCreateView = 23,
-    AppDeleteView = 24,
+    AppViewsChanged = 24,
     ViewUpdated = 31,
     UserUnauthorized = 100,
 }
 
-impl ::protobuf::ProtobufEnum for WorkspaceObservable {
+impl ::protobuf::ProtobufEnum for Notification {
     fn value(&self) -> i32 {
         *self as i32
     }
 
-    fn from_i32(value: i32) -> ::std::option::Option<WorkspaceObservable> {
+    fn from_i32(value: i32) -> ::std::option::Option<Notification> {
         match value {
-            0 => ::std::option::Option::Some(WorkspaceObservable::Unknown),
-            10 => ::std::option::Option::Some(WorkspaceObservable::UserCreateWorkspace),
-            11 => ::std::option::Option::Some(WorkspaceObservable::UserDeleteWorkspace),
-            12 => ::std::option::Option::Some(WorkspaceObservable::WorkspaceUpdated),
-            13 => ::std::option::Option::Some(WorkspaceObservable::WorkspaceCreateApp),
-            14 => ::std::option::Option::Some(WorkspaceObservable::WorkspaceDeleteApp),
-            15 => ::std::option::Option::Some(WorkspaceObservable::WorkspaceListUpdated),
-            21 => ::std::option::Option::Some(WorkspaceObservable::AppUpdated),
-            23 => ::std::option::Option::Some(WorkspaceObservable::AppCreateView),
-            24 => ::std::option::Option::Some(WorkspaceObservable::AppDeleteView),
-            31 => ::std::option::Option::Some(WorkspaceObservable::ViewUpdated),
-            100 => ::std::option::Option::Some(WorkspaceObservable::UserUnauthorized),
+            0 => ::std::option::Option::Some(Notification::Unknown),
+            10 => ::std::option::Option::Some(Notification::UserCreateWorkspace),
+            11 => ::std::option::Option::Some(Notification::UserDeleteWorkspace),
+            12 => ::std::option::Option::Some(Notification::WorkspaceUpdated),
+            13 => ::std::option::Option::Some(Notification::WorkspaceCreateApp),
+            14 => ::std::option::Option::Some(Notification::WorkspaceDeleteApp),
+            15 => ::std::option::Option::Some(Notification::WorkspaceListUpdated),
+            21 => ::std::option::Option::Some(Notification::AppUpdated),
+            24 => ::std::option::Option::Some(Notification::AppViewsChanged),
+            31 => ::std::option::Option::Some(Notification::ViewUpdated),
+            100 => ::std::option::Option::Some(Notification::UserUnauthorized),
             _ => ::std::option::Option::None
         }
     }
 
     fn values() -> &'static [Self] {
-        static values: &'static [WorkspaceObservable] = &[
-            WorkspaceObservable::Unknown,
-            WorkspaceObservable::UserCreateWorkspace,
-            WorkspaceObservable::UserDeleteWorkspace,
-            WorkspaceObservable::WorkspaceUpdated,
-            WorkspaceObservable::WorkspaceCreateApp,
-            WorkspaceObservable::WorkspaceDeleteApp,
-            WorkspaceObservable::WorkspaceListUpdated,
-            WorkspaceObservable::AppUpdated,
-            WorkspaceObservable::AppCreateView,
-            WorkspaceObservable::AppDeleteView,
-            WorkspaceObservable::ViewUpdated,
-            WorkspaceObservable::UserUnauthorized,
+        static values: &'static [Notification] = &[
+            Notification::Unknown,
+            Notification::UserCreateWorkspace,
+            Notification::UserDeleteWorkspace,
+            Notification::WorkspaceUpdated,
+            Notification::WorkspaceCreateApp,
+            Notification::WorkspaceDeleteApp,
+            Notification::WorkspaceListUpdated,
+            Notification::AppUpdated,
+            Notification::AppViewsChanged,
+            Notification::ViewUpdated,
+            Notification::UserUnauthorized,
         ];
         values
     }
@@ -83,61 +80,58 @@ impl ::protobuf::ProtobufEnum for WorkspaceObservable {
     fn enum_descriptor_static() -> &'static ::protobuf::reflect::EnumDescriptor {
         static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::LazyV2::INIT;
         descriptor.get(|| {
-            ::protobuf::reflect::EnumDescriptor::new_pb_name::<WorkspaceObservable>("WorkspaceObservable", file_descriptor_proto())
+            ::protobuf::reflect::EnumDescriptor::new_pb_name::<Notification>("Notification", file_descriptor_proto())
         })
     }
 }
 
-impl ::std::marker::Copy for WorkspaceObservable {
+impl ::std::marker::Copy for Notification {
 }
 
-impl ::std::default::Default for WorkspaceObservable {
+impl ::std::default::Default for Notification {
     fn default() -> Self {
-        WorkspaceObservable::Unknown
+        Notification::Unknown
     }
 }
 
-impl ::protobuf::reflect::ProtobufValue for WorkspaceObservable {
+impl ::protobuf::reflect::ProtobufValue for Notification {
     fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
         ::protobuf::reflect::ReflectValueRef::Enum(::protobuf::ProtobufEnum::descriptor(self))
     }
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x10observable.proto*\x91\x02\n\x13WorkspaceObservable\x12\x0b\n\x07Un\
-    known\x10\0\x12\x17\n\x13UserCreateWorkspace\x10\n\x12\x17\n\x13UserDele\
-    teWorkspace\x10\x0b\x12\x14\n\x10WorkspaceUpdated\x10\x0c\x12\x16\n\x12W\
-    orkspaceCreateApp\x10\r\x12\x16\n\x12WorkspaceDeleteApp\x10\x0e\x12\x18\
-    \n\x14WorkspaceListUpdated\x10\x0f\x12\x0e\n\nAppUpdated\x10\x15\x12\x11\
-    \n\rAppCreateView\x10\x17\x12\x11\n\rAppDeleteView\x10\x18\x12\x0f\n\x0b\
-    ViewUpdated\x10\x1f\x12\x14\n\x10UserUnauthorized\x10dJ\x96\x04\n\x06\
-    \x12\x04\0\0\x0f\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x05\0\x12\
-    \x04\x02\0\x0f\x01\n\n\n\x03\x05\0\x01\x12\x03\x02\x05\x18\n\x0b\n\x04\
-    \x05\0\x02\0\x12\x03\x03\x04\x10\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x03\
-    \x04\x0b\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x03\x0e\x0f\n\x0b\n\x04\x05\
-    \0\x02\x01\x12\x03\x04\x04\x1d\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x04\
-    \x04\x17\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x04\x1a\x1c\n\x0b\n\x04\
-    \x05\0\x02\x02\x12\x03\x05\x04\x1d\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\
-    \x05\x04\x17\n\x0c\n\x05\x05\0\x02\x02\x02\x12\x03\x05\x1a\x1c\n\x0b\n\
-    \x04\x05\0\x02\x03\x12\x03\x06\x04\x1a\n\x0c\n\x05\x05\0\x02\x03\x01\x12\
-    \x03\x06\x04\x14\n\x0c\n\x05\x05\0\x02\x03\x02\x12\x03\x06\x17\x19\n\x0b\
-    \n\x04\x05\0\x02\x04\x12\x03\x07\x04\x1c\n\x0c\n\x05\x05\0\x02\x04\x01\
-    \x12\x03\x07\x04\x16\n\x0c\n\x05\x05\0\x02\x04\x02\x12\x03\x07\x19\x1b\n\
-    \x0b\n\x04\x05\0\x02\x05\x12\x03\x08\x04\x1c\n\x0c\n\x05\x05\0\x02\x05\
-    \x01\x12\x03\x08\x04\x16\n\x0c\n\x05\x05\0\x02\x05\x02\x12\x03\x08\x19\
-    \x1b\n\x0b\n\x04\x05\0\x02\x06\x12\x03\t\x04\x1e\n\x0c\n\x05\x05\0\x02\
-    \x06\x01\x12\x03\t\x04\x18\n\x0c\n\x05\x05\0\x02\x06\x02\x12\x03\t\x1b\
-    \x1d\n\x0b\n\x04\x05\0\x02\x07\x12\x03\n\x04\x14\n\x0c\n\x05\x05\0\x02\
-    \x07\x01\x12\x03\n\x04\x0e\n\x0c\n\x05\x05\0\x02\x07\x02\x12\x03\n\x11\
-    \x13\n\x0b\n\x04\x05\0\x02\x08\x12\x03\x0b\x04\x17\n\x0c\n\x05\x05\0\x02\
-    \x08\x01\x12\x03\x0b\x04\x11\n\x0c\n\x05\x05\0\x02\x08\x02\x12\x03\x0b\
-    \x14\x16\n\x0b\n\x04\x05\0\x02\t\x12\x03\x0c\x04\x17\n\x0c\n\x05\x05\0\
-    \x02\t\x01\x12\x03\x0c\x04\x11\n\x0c\n\x05\x05\0\x02\t\x02\x12\x03\x0c\
-    \x14\x16\n\x0b\n\x04\x05\0\x02\n\x12\x03\r\x04\x15\n\x0c\n\x05\x05\0\x02\
-    \n\x01\x12\x03\r\x04\x0f\n\x0c\n\x05\x05\0\x02\n\x02\x12\x03\r\x12\x14\n\
-    \x0b\n\x04\x05\0\x02\x0b\x12\x03\x0e\x04\x1b\n\x0c\n\x05\x05\0\x02\x0b\
-    \x01\x12\x03\x0e\x04\x14\n\x0c\n\x05\x05\0\x02\x0b\x02\x12\x03\x0e\x17\
-    \x1ab\x06proto3\
+    \n\x10observable.proto*\xf9\x01\n\x0cNotification\x12\x0b\n\x07Unknown\
+    \x10\0\x12\x17\n\x13UserCreateWorkspace\x10\n\x12\x17\n\x13UserDeleteWor\
+    kspace\x10\x0b\x12\x14\n\x10WorkspaceUpdated\x10\x0c\x12\x16\n\x12Worksp\
+    aceCreateApp\x10\r\x12\x16\n\x12WorkspaceDeleteApp\x10\x0e\x12\x18\n\x14\
+    WorkspaceListUpdated\x10\x0f\x12\x0e\n\nAppUpdated\x10\x15\x12\x13\n\x0f\
+    AppViewsChanged\x10\x18\x12\x0f\n\x0bViewUpdated\x10\x1f\x12\x14\n\x10Us\
+    erUnauthorized\x10dJ\xed\x03\n\x06\x12\x04\0\0\x0e\x01\n\x08\n\x01\x0c\
+    \x12\x03\0\0\x12\n\n\n\x02\x05\0\x12\x04\x02\0\x0e\x01\n\n\n\x03\x05\0\
+    \x01\x12\x03\x02\x05\x11\n\x0b\n\x04\x05\0\x02\0\x12\x03\x03\x04\x10\n\
+    \x0c\n\x05\x05\0\x02\0\x01\x12\x03\x03\x04\x0b\n\x0c\n\x05\x05\0\x02\0\
+    \x02\x12\x03\x03\x0e\x0f\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x04\x04\x1d\n\
+    \x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x04\x04\x17\n\x0c\n\x05\x05\0\x02\
+    \x01\x02\x12\x03\x04\x1a\x1c\n\x0b\n\x04\x05\0\x02\x02\x12\x03\x05\x04\
+    \x1d\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\x05\x04\x17\n\x0c\n\x05\x05\0\
+    \x02\x02\x02\x12\x03\x05\x1a\x1c\n\x0b\n\x04\x05\0\x02\x03\x12\x03\x06\
+    \x04\x1a\n\x0c\n\x05\x05\0\x02\x03\x01\x12\x03\x06\x04\x14\n\x0c\n\x05\
+    \x05\0\x02\x03\x02\x12\x03\x06\x17\x19\n\x0b\n\x04\x05\0\x02\x04\x12\x03\
+    \x07\x04\x1c\n\x0c\n\x05\x05\0\x02\x04\x01\x12\x03\x07\x04\x16\n\x0c\n\
+    \x05\x05\0\x02\x04\x02\x12\x03\x07\x19\x1b\n\x0b\n\x04\x05\0\x02\x05\x12\
+    \x03\x08\x04\x1c\n\x0c\n\x05\x05\0\x02\x05\x01\x12\x03\x08\x04\x16\n\x0c\
+    \n\x05\x05\0\x02\x05\x02\x12\x03\x08\x19\x1b\n\x0b\n\x04\x05\0\x02\x06\
+    \x12\x03\t\x04\x1e\n\x0c\n\x05\x05\0\x02\x06\x01\x12\x03\t\x04\x18\n\x0c\
+    \n\x05\x05\0\x02\x06\x02\x12\x03\t\x1b\x1d\n\x0b\n\x04\x05\0\x02\x07\x12\
+    \x03\n\x04\x14\n\x0c\n\x05\x05\0\x02\x07\x01\x12\x03\n\x04\x0e\n\x0c\n\
+    \x05\x05\0\x02\x07\x02\x12\x03\n\x11\x13\n\x0b\n\x04\x05\0\x02\x08\x12\
+    \x03\x0b\x04\x19\n\x0c\n\x05\x05\0\x02\x08\x01\x12\x03\x0b\x04\x13\n\x0c\
+    \n\x05\x05\0\x02\x08\x02\x12\x03\x0b\x16\x18\n\x0b\n\x04\x05\0\x02\t\x12\
+    \x03\x0c\x04\x15\n\x0c\n\x05\x05\0\x02\t\x01\x12\x03\x0c\x04\x0f\n\x0c\n\
+    \x05\x05\0\x02\t\x02\x12\x03\x0c\x12\x14\n\x0b\n\x04\x05\0\x02\n\x12\x03\
+    \r\x04\x1b\n\x0c\n\x05\x05\0\x02\n\x01\x12\x03\r\x04\x14\n\x0c\n\x05\x05\
+    \0\x02\n\x02\x12\x03\r\x17\x1ab\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 785 - 0
rust-lib/flowy-workspace/src/protobuf/model/trash_create.rs

@@ -0,0 +1,785 @@
+// This file is generated by rust-protobuf 2.22.1. Do not edit
+// @generated
+
+// https://github.com/rust-lang/rust-clippy/issues/702
+#![allow(unknown_lints)]
+#![allow(clippy::all)]
+
+#![allow(unused_attributes)]
+#![cfg_attr(rustfmt, rustfmt::skip)]
+
+#![allow(box_pointers)]
+#![allow(dead_code)]
+#![allow(missing_docs)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(non_upper_case_globals)]
+#![allow(trivial_casts)]
+#![allow(unused_imports)]
+#![allow(unused_results)]
+//! Generated file from `trash_create.proto`
+
+/// Generated files are compatible only with the same version
+/// of protobuf runtime.
+// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_22_1;
+
+#[derive(PartialEq,Clone,Default)]
+pub struct CreateTrashParams {
+    // message fields
+    pub id: ::std::string::String,
+    pub name: ::std::string::String,
+    pub modified_time: i64,
+    pub create_time: i64,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a CreateTrashParams {
+    fn default() -> &'a CreateTrashParams {
+        <CreateTrashParams as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl CreateTrashParams {
+    pub fn new() -> CreateTrashParams {
+        ::std::default::Default::default()
+    }
+
+    // string id = 1;
+
+
+    pub fn get_id(&self) -> &str {
+        &self.id
+    }
+    pub fn clear_id(&mut self) {
+        self.id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_id(&mut self, v: ::std::string::String) {
+        self.id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_id(&mut self) -> &mut ::std::string::String {
+        &mut self.id
+    }
+
+    // Take field
+    pub fn take_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.id, ::std::string::String::new())
+    }
+
+    // string name = 2;
+
+
+    pub fn get_name(&self) -> &str {
+        &self.name
+    }
+    pub fn clear_name(&mut self) {
+        self.name.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_name(&mut self, v: ::std::string::String) {
+        self.name = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_name(&mut self) -> &mut ::std::string::String {
+        &mut self.name
+    }
+
+    // Take field
+    pub fn take_name(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.name, ::std::string::String::new())
+    }
+
+    // int64 modified_time = 3;
+
+
+    pub fn get_modified_time(&self) -> i64 {
+        self.modified_time
+    }
+    pub fn clear_modified_time(&mut self) {
+        self.modified_time = 0;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_modified_time(&mut self, v: i64) {
+        self.modified_time = v;
+    }
+
+    // int64 create_time = 4;
+
+
+    pub fn get_create_time(&self) -> i64 {
+        self.create_time
+    }
+    pub fn clear_create_time(&mut self) {
+        self.create_time = 0;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_create_time(&mut self, v: i64) {
+        self.create_time = v;
+    }
+}
+
+impl ::protobuf::Message for CreateTrashParams {
+    fn is_initialized(&self) -> bool {
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.id)?;
+                },
+                2 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.name)?;
+                },
+                3 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_int64()?;
+                    self.modified_time = tmp;
+                },
+                4 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_int64()?;
+                    self.create_time = tmp;
+                },
+                _ => {
+                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
+                },
+            };
+        }
+        ::std::result::Result::Ok(())
+    }
+
+    // Compute sizes of nested messages
+    #[allow(unused_variables)]
+    fn compute_size(&self) -> u32 {
+        let mut my_size = 0;
+        if !self.id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.id);
+        }
+        if !self.name.is_empty() {
+            my_size += ::protobuf::rt::string_size(2, &self.name);
+        }
+        if self.modified_time != 0 {
+            my_size += ::protobuf::rt::value_size(3, self.modified_time, ::protobuf::wire_format::WireTypeVarint);
+        }
+        if self.create_time != 0 {
+            my_size += ::protobuf::rt::value_size(4, self.create_time, ::protobuf::wire_format::WireTypeVarint);
+        }
+        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
+        self.cached_size.set(my_size);
+        my_size
+    }
+
+    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        if !self.id.is_empty() {
+            os.write_string(1, &self.id)?;
+        }
+        if !self.name.is_empty() {
+            os.write_string(2, &self.name)?;
+        }
+        if self.modified_time != 0 {
+            os.write_int64(3, self.modified_time)?;
+        }
+        if self.create_time != 0 {
+            os.write_int64(4, self.create_time)?;
+        }
+        os.write_unknown_fields(self.get_unknown_fields())?;
+        ::std::result::Result::Ok(())
+    }
+
+    fn get_cached_size(&self) -> u32 {
+        self.cached_size.get()
+    }
+
+    fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
+        &self.unknown_fields
+    }
+
+    fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
+        &mut self.unknown_fields
+    }
+
+    fn as_any(&self) -> &dyn (::std::any::Any) {
+        self as &dyn (::std::any::Any)
+    }
+    fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
+        self as &mut dyn (::std::any::Any)
+    }
+    fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
+        self
+    }
+
+    fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
+        Self::descriptor_static()
+    }
+
+    fn new() -> CreateTrashParams {
+        CreateTrashParams::new()
+    }
+
+    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            let mut fields = ::std::vec::Vec::new();
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "id",
+                |m: &CreateTrashParams| { &m.id },
+                |m: &mut CreateTrashParams| { &mut m.id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "name",
+                |m: &CreateTrashParams| { &m.name },
+                |m: &mut CreateTrashParams| { &mut m.name },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeInt64>(
+                "modified_time",
+                |m: &CreateTrashParams| { &m.modified_time },
+                |m: &mut CreateTrashParams| { &mut m.modified_time },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeInt64>(
+                "create_time",
+                |m: &CreateTrashParams| { &m.create_time },
+                |m: &mut CreateTrashParams| { &mut m.create_time },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<CreateTrashParams>(
+                "CreateTrashParams",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static CreateTrashParams {
+        static instance: ::protobuf::rt::LazyV2<CreateTrashParams> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(CreateTrashParams::new)
+    }
+}
+
+impl ::protobuf::Clear for CreateTrashParams {
+    fn clear(&mut self) {
+        self.id.clear();
+        self.name.clear();
+        self.modified_time = 0;
+        self.create_time = 0;
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for CreateTrashParams {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for CreateTrashParams {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+#[derive(PartialEq,Clone,Default)]
+pub struct Trash {
+    // message fields
+    pub id: ::std::string::String,
+    pub name: ::std::string::String,
+    pub modified_time: i64,
+    pub create_time: i64,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a Trash {
+    fn default() -> &'a Trash {
+        <Trash as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl Trash {
+    pub fn new() -> Trash {
+        ::std::default::Default::default()
+    }
+
+    // string id = 1;
+
+
+    pub fn get_id(&self) -> &str {
+        &self.id
+    }
+    pub fn clear_id(&mut self) {
+        self.id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_id(&mut self, v: ::std::string::String) {
+        self.id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_id(&mut self) -> &mut ::std::string::String {
+        &mut self.id
+    }
+
+    // Take field
+    pub fn take_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.id, ::std::string::String::new())
+    }
+
+    // string name = 2;
+
+
+    pub fn get_name(&self) -> &str {
+        &self.name
+    }
+    pub fn clear_name(&mut self) {
+        self.name.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_name(&mut self, v: ::std::string::String) {
+        self.name = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_name(&mut self) -> &mut ::std::string::String {
+        &mut self.name
+    }
+
+    // Take field
+    pub fn take_name(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.name, ::std::string::String::new())
+    }
+
+    // int64 modified_time = 3;
+
+
+    pub fn get_modified_time(&self) -> i64 {
+        self.modified_time
+    }
+    pub fn clear_modified_time(&mut self) {
+        self.modified_time = 0;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_modified_time(&mut self, v: i64) {
+        self.modified_time = v;
+    }
+
+    // int64 create_time = 4;
+
+
+    pub fn get_create_time(&self) -> i64 {
+        self.create_time
+    }
+    pub fn clear_create_time(&mut self) {
+        self.create_time = 0;
+    }
+
+    // Param is passed by value, moved
+    pub fn set_create_time(&mut self, v: i64) {
+        self.create_time = v;
+    }
+}
+
+impl ::protobuf::Message for Trash {
+    fn is_initialized(&self) -> bool {
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.id)?;
+                },
+                2 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.name)?;
+                },
+                3 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_int64()?;
+                    self.modified_time = tmp;
+                },
+                4 => {
+                    if wire_type != ::protobuf::wire_format::WireTypeVarint {
+                        return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
+                    }
+                    let tmp = is.read_int64()?;
+                    self.create_time = tmp;
+                },
+                _ => {
+                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
+                },
+            };
+        }
+        ::std::result::Result::Ok(())
+    }
+
+    // Compute sizes of nested messages
+    #[allow(unused_variables)]
+    fn compute_size(&self) -> u32 {
+        let mut my_size = 0;
+        if !self.id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.id);
+        }
+        if !self.name.is_empty() {
+            my_size += ::protobuf::rt::string_size(2, &self.name);
+        }
+        if self.modified_time != 0 {
+            my_size += ::protobuf::rt::value_size(3, self.modified_time, ::protobuf::wire_format::WireTypeVarint);
+        }
+        if self.create_time != 0 {
+            my_size += ::protobuf::rt::value_size(4, self.create_time, ::protobuf::wire_format::WireTypeVarint);
+        }
+        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
+        self.cached_size.set(my_size);
+        my_size
+    }
+
+    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        if !self.id.is_empty() {
+            os.write_string(1, &self.id)?;
+        }
+        if !self.name.is_empty() {
+            os.write_string(2, &self.name)?;
+        }
+        if self.modified_time != 0 {
+            os.write_int64(3, self.modified_time)?;
+        }
+        if self.create_time != 0 {
+            os.write_int64(4, self.create_time)?;
+        }
+        os.write_unknown_fields(self.get_unknown_fields())?;
+        ::std::result::Result::Ok(())
+    }
+
+    fn get_cached_size(&self) -> u32 {
+        self.cached_size.get()
+    }
+
+    fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
+        &self.unknown_fields
+    }
+
+    fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
+        &mut self.unknown_fields
+    }
+
+    fn as_any(&self) -> &dyn (::std::any::Any) {
+        self as &dyn (::std::any::Any)
+    }
+    fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
+        self as &mut dyn (::std::any::Any)
+    }
+    fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
+        self
+    }
+
+    fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
+        Self::descriptor_static()
+    }
+
+    fn new() -> Trash {
+        Trash::new()
+    }
+
+    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            let mut fields = ::std::vec::Vec::new();
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "id",
+                |m: &Trash| { &m.id },
+                |m: &mut Trash| { &mut m.id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "name",
+                |m: &Trash| { &m.name },
+                |m: &mut Trash| { &mut m.name },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeInt64>(
+                "modified_time",
+                |m: &Trash| { &m.modified_time },
+                |m: &mut Trash| { &mut m.modified_time },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeInt64>(
+                "create_time",
+                |m: &Trash| { &m.create_time },
+                |m: &mut Trash| { &mut m.create_time },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<Trash>(
+                "Trash",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static Trash {
+        static instance: ::protobuf::rt::LazyV2<Trash> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(Trash::new)
+    }
+}
+
+impl ::protobuf::Clear for Trash {
+    fn clear(&mut self) {
+        self.id.clear();
+        self.name.clear();
+        self.modified_time = 0;
+        self.create_time = 0;
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for Trash {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for Trash {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+#[derive(PartialEq,Clone,Default)]
+pub struct RepeatedTrash {
+    // message fields
+    pub items: ::protobuf::RepeatedField<Trash>,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a RepeatedTrash {
+    fn default() -> &'a RepeatedTrash {
+        <RepeatedTrash as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl RepeatedTrash {
+    pub fn new() -> RepeatedTrash {
+        ::std::default::Default::default()
+    }
+
+    // repeated .Trash items = 1;
+
+
+    pub fn get_items(&self) -> &[Trash] {
+        &self.items
+    }
+    pub fn clear_items(&mut self) {
+        self.items.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_items(&mut self, v: ::protobuf::RepeatedField<Trash>) {
+        self.items = v;
+    }
+
+    // Mutable pointer to the field.
+    pub fn mut_items(&mut self) -> &mut ::protobuf::RepeatedField<Trash> {
+        &mut self.items
+    }
+
+    // Take field
+    pub fn take_items(&mut self) -> ::protobuf::RepeatedField<Trash> {
+        ::std::mem::replace(&mut self.items, ::protobuf::RepeatedField::new())
+    }
+}
+
+impl ::protobuf::Message for RepeatedTrash {
+    fn is_initialized(&self) -> bool {
+        for v in &self.items {
+            if !v.is_initialized() {
+                return false;
+            }
+        };
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_repeated_message_into(wire_type, is, &mut self.items)?;
+                },
+                _ => {
+                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
+                },
+            };
+        }
+        ::std::result::Result::Ok(())
+    }
+
+    // Compute sizes of nested messages
+    #[allow(unused_variables)]
+    fn compute_size(&self) -> u32 {
+        let mut my_size = 0;
+        for value in &self.items {
+            let len = value.compute_size();
+            my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
+        };
+        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
+        self.cached_size.set(my_size);
+        my_size
+    }
+
+    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        for v in &self.items {
+            os.write_tag(1, ::protobuf::wire_format::WireTypeLengthDelimited)?;
+            os.write_raw_varint32(v.get_cached_size())?;
+            v.write_to_with_cached_sizes(os)?;
+        };
+        os.write_unknown_fields(self.get_unknown_fields())?;
+        ::std::result::Result::Ok(())
+    }
+
+    fn get_cached_size(&self) -> u32 {
+        self.cached_size.get()
+    }
+
+    fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
+        &self.unknown_fields
+    }
+
+    fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
+        &mut self.unknown_fields
+    }
+
+    fn as_any(&self) -> &dyn (::std::any::Any) {
+        self as &dyn (::std::any::Any)
+    }
+    fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
+        self as &mut dyn (::std::any::Any)
+    }
+    fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
+        self
+    }
+
+    fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
+        Self::descriptor_static()
+    }
+
+    fn new() -> RepeatedTrash {
+        RepeatedTrash::new()
+    }
+
+    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            let mut fields = ::std::vec::Vec::new();
+            fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<Trash>>(
+                "items",
+                |m: &RepeatedTrash| { &m.items },
+                |m: &mut RepeatedTrash| { &mut m.items },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<RepeatedTrash>(
+                "RepeatedTrash",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static RepeatedTrash {
+        static instance: ::protobuf::rt::LazyV2<RepeatedTrash> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(RepeatedTrash::new)
+    }
+}
+
+impl ::protobuf::Clear for RepeatedTrash {
+    fn clear(&mut self) {
+        self.items.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for RepeatedTrash {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for RepeatedTrash {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+static file_descriptor_proto_data: &'static [u8] = b"\
+    \n\x12trash_create.proto\"}\n\x11CreateTrashParams\x12\x0e\n\x02id\x18\
+    \x01\x20\x01(\tR\x02id\x12\x12\n\x04name\x18\x02\x20\x01(\tR\x04name\x12\
+    #\n\rmodified_time\x18\x03\x20\x01(\x03R\x0cmodifiedTime\x12\x1f\n\x0bcr\
+    eate_time\x18\x04\x20\x01(\x03R\ncreateTime\"q\n\x05Trash\x12\x0e\n\x02i\
+    d\x18\x01\x20\x01(\tR\x02id\x12\x12\n\x04name\x18\x02\x20\x01(\tR\x04nam\
+    e\x12#\n\rmodified_time\x18\x03\x20\x01(\x03R\x0cmodifiedTime\x12\x1f\n\
+    \x0bcreate_time\x18\x04\x20\x01(\x03R\ncreateTime\"-\n\rRepeatedTrash\
+    \x12\x1c\n\x05items\x18\x01\x20\x03(\x0b2\x06.TrashR\x05itemsJ\xd7\x04\n\
+    \x06\x12\x04\0\0\x10\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\
+    \x12\x04\x02\0\x07\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x19\n\x0b\n\
+    \x04\x04\0\x02\0\x12\x03\x03\x04\x12\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\
+    \x03\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\x0b\r\n\x0c\n\x05\x04\
+    \0\x02\0\x03\x12\x03\x03\x10\x11\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x04\
+    \x04\x14\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x04\x04\n\n\x0c\n\x05\x04\
+    \0\x02\x01\x01\x12\x03\x04\x0b\x0f\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\
+    \x04\x12\x13\n\x0b\n\x04\x04\0\x02\x02\x12\x03\x05\x04\x1c\n\x0c\n\x05\
+    \x04\0\x02\x02\x05\x12\x03\x05\x04\t\n\x0c\n\x05\x04\0\x02\x02\x01\x12\
+    \x03\x05\n\x17\n\x0c\n\x05\x04\0\x02\x02\x03\x12\x03\x05\x1a\x1b\n\x0b\n\
+    \x04\x04\0\x02\x03\x12\x03\x06\x04\x1a\n\x0c\n\x05\x04\0\x02\x03\x05\x12\
+    \x03\x06\x04\t\n\x0c\n\x05\x04\0\x02\x03\x01\x12\x03\x06\n\x15\n\x0c\n\
+    \x05\x04\0\x02\x03\x03\x12\x03\x06\x18\x19\n\n\n\x02\x04\x01\x12\x04\x08\
+    \0\r\x01\n\n\n\x03\x04\x01\x01\x12\x03\x08\x08\r\n\x0b\n\x04\x04\x01\x02\
+    \0\x12\x03\t\x04\x12\n\x0c\n\x05\x04\x01\x02\0\x05\x12\x03\t\x04\n\n\x0c\
+    \n\x05\x04\x01\x02\0\x01\x12\x03\t\x0b\r\n\x0c\n\x05\x04\x01\x02\0\x03\
+    \x12\x03\t\x10\x11\n\x0b\n\x04\x04\x01\x02\x01\x12\x03\n\x04\x14\n\x0c\n\
+    \x05\x04\x01\x02\x01\x05\x12\x03\n\x04\n\n\x0c\n\x05\x04\x01\x02\x01\x01\
+    \x12\x03\n\x0b\x0f\n\x0c\n\x05\x04\x01\x02\x01\x03\x12\x03\n\x12\x13\n\
+    \x0b\n\x04\x04\x01\x02\x02\x12\x03\x0b\x04\x1c\n\x0c\n\x05\x04\x01\x02\
+    \x02\x05\x12\x03\x0b\x04\t\n\x0c\n\x05\x04\x01\x02\x02\x01\x12\x03\x0b\n\
+    \x17\n\x0c\n\x05\x04\x01\x02\x02\x03\x12\x03\x0b\x1a\x1b\n\x0b\n\x04\x04\
+    \x01\x02\x03\x12\x03\x0c\x04\x1a\n\x0c\n\x05\x04\x01\x02\x03\x05\x12\x03\
+    \x0c\x04\t\n\x0c\n\x05\x04\x01\x02\x03\x01\x12\x03\x0c\n\x15\n\x0c\n\x05\
+    \x04\x01\x02\x03\x03\x12\x03\x0c\x18\x19\n\n\n\x02\x04\x02\x12\x04\x0e\0\
+    \x10\x01\n\n\n\x03\x04\x02\x01\x12\x03\x0e\x08\x15\n\x0b\n\x04\x04\x02\
+    \x02\0\x12\x03\x0f\x04\x1d\n\x0c\n\x05\x04\x02\x02\0\x04\x12\x03\x0f\x04\
+    \x0c\n\x0c\n\x05\x04\x02\x02\0\x06\x12\x03\x0f\r\x12\n\x0c\n\x05\x04\x02\
+    \x02\0\x01\x12\x03\x0f\x13\x18\n\x0c\n\x05\x04\x02\x02\0\x03\x12\x03\x0f\
+    \x1b\x1cb\x06proto3\
+";
+
+static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
+
+fn parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto {
+    ::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap()
+}
+
+pub fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto {
+    file_descriptor_proto_lazy.get(|| {
+        parse_descriptor_proto()
+    })
+}

+ 204 - 0
rust-lib/flowy-workspace/src/protobuf/model/trash_delete.rs

@@ -0,0 +1,204 @@
+// This file is generated by rust-protobuf 2.22.1. Do not edit
+// @generated
+
+// https://github.com/rust-lang/rust-clippy/issues/702
+#![allow(unknown_lints)]
+#![allow(clippy::all)]
+
+#![allow(unused_attributes)]
+#![cfg_attr(rustfmt, rustfmt::skip)]
+
+#![allow(box_pointers)]
+#![allow(dead_code)]
+#![allow(missing_docs)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(non_upper_case_globals)]
+#![allow(trivial_casts)]
+#![allow(unused_imports)]
+#![allow(unused_results)]
+//! Generated file from `trash_delete.proto`
+
+/// Generated files are compatible only with the same version
+/// of protobuf runtime.
+// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_22_1;
+
+#[derive(PartialEq,Clone,Default)]
+pub struct TrashIdentifier {
+    // message fields
+    pub id: ::std::string::String,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a TrashIdentifier {
+    fn default() -> &'a TrashIdentifier {
+        <TrashIdentifier as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl TrashIdentifier {
+    pub fn new() -> TrashIdentifier {
+        ::std::default::Default::default()
+    }
+
+    // string id = 1;
+
+
+    pub fn get_id(&self) -> &str {
+        &self.id
+    }
+    pub fn clear_id(&mut self) {
+        self.id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_id(&mut self, v: ::std::string::String) {
+        self.id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_id(&mut self) -> &mut ::std::string::String {
+        &mut self.id
+    }
+
+    // Take field
+    pub fn take_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.id, ::std::string::String::new())
+    }
+}
+
+impl ::protobuf::Message for TrashIdentifier {
+    fn is_initialized(&self) -> bool {
+        true
+    }
+
+    fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        while !is.eof()? {
+            let (field_number, wire_type) = is.read_tag_unpack()?;
+            match field_number {
+                1 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.id)?;
+                },
+                _ => {
+                    ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
+                },
+            };
+        }
+        ::std::result::Result::Ok(())
+    }
+
+    // Compute sizes of nested messages
+    #[allow(unused_variables)]
+    fn compute_size(&self) -> u32 {
+        let mut my_size = 0;
+        if !self.id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.id);
+        }
+        my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
+        self.cached_size.set(my_size);
+        my_size
+    }
+
+    fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
+        if !self.id.is_empty() {
+            os.write_string(1, &self.id)?;
+        }
+        os.write_unknown_fields(self.get_unknown_fields())?;
+        ::std::result::Result::Ok(())
+    }
+
+    fn get_cached_size(&self) -> u32 {
+        self.cached_size.get()
+    }
+
+    fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
+        &self.unknown_fields
+    }
+
+    fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
+        &mut self.unknown_fields
+    }
+
+    fn as_any(&self) -> &dyn (::std::any::Any) {
+        self as &dyn (::std::any::Any)
+    }
+    fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
+        self as &mut dyn (::std::any::Any)
+    }
+    fn into_any(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
+        self
+    }
+
+    fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
+        Self::descriptor_static()
+    }
+
+    fn new() -> TrashIdentifier {
+        TrashIdentifier::new()
+    }
+
+    fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
+        static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
+        descriptor.get(|| {
+            let mut fields = ::std::vec::Vec::new();
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "id",
+                |m: &TrashIdentifier| { &m.id },
+                |m: &mut TrashIdentifier| { &mut m.id },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<TrashIdentifier>(
+                "TrashIdentifier",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static TrashIdentifier {
+        static instance: ::protobuf::rt::LazyV2<TrashIdentifier> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(TrashIdentifier::new)
+    }
+}
+
+impl ::protobuf::Clear for TrashIdentifier {
+    fn clear(&mut self) {
+        self.id.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for TrashIdentifier {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for TrashIdentifier {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+static file_descriptor_proto_data: &'static [u8] = b"\
+    \n\x12trash_delete.proto\"!\n\x0fTrashIdentifier\x12\x0e\n\x02id\x18\x01\
+    \x20\x01(\tR\x02idJa\n\x06\x12\x04\0\0\x04\x01\n\x08\n\x01\x0c\x12\x03\0\
+    \0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x04\x01\n\n\n\x03\x04\0\x01\x12\x03\
+    \x02\x08\x17\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x12\n\x0c\n\x05\x04\
+    \0\x02\0\x05\x12\x03\x03\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\
+    \x0b\r\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x10\x11b\x06proto3\
+";
+
+static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
+
+fn parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto {
+    ::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap()
+}
+
+pub fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto {
+    file_descriptor_proto_lazy.get(|| {
+        parse_descriptor_proto()
+    })
+}

+ 2 - 0
rust-lib/flowy-workspace/src/protobuf/proto/event.proto

@@ -18,5 +18,7 @@ enum WorkspaceEvent {
     OpenView = 205;
     ApplyDocDelta = 206;
     ReadTrash = 300;
+    PutbackTrash = 301;
+    DeleteTrash = 302;
     InitWorkspace = 1000;
 }

+ 2 - 3
rust-lib/flowy-workspace/src/protobuf/proto/observable.proto

@@ -1,6 +1,6 @@
 syntax = "proto3";
 
-enum WorkspaceObservable {
+enum Notification {
     Unknown = 0;
     UserCreateWorkspace = 10;
     UserDeleteWorkspace = 11;
@@ -9,8 +9,7 @@ enum WorkspaceObservable {
     WorkspaceDeleteApp = 14;
     WorkspaceListUpdated = 15;
     AppUpdated = 21;
-    AppCreateView = 23;
-    AppDeleteView = 24;
+    AppViewsChanged = 24;
     ViewUpdated = 31;
     UserUnauthorized = 100;
 }

+ 17 - 0
rust-lib/flowy-workspace/src/protobuf/proto/trash_create.proto

@@ -0,0 +1,17 @@
+syntax = "proto3";
+
+message CreateTrashParams {
+    string id = 1;
+    string name = 2;
+    int64 modified_time = 3;
+    int64 create_time = 4;
+}
+message Trash {
+    string id = 1;
+    string name = 2;
+    int64 modified_time = 3;
+    int64 create_time = 4;
+}
+message RepeatedTrash {
+    repeated Trash items = 1;
+}

+ 5 - 0
rust-lib/flowy-workspace/src/protobuf/proto/trash_delete.proto

@@ -0,0 +1,5 @@
+syntax = "proto3";
+
+message TrashIdentifier {
+    string id = 1;
+}

+ 4 - 6
rust-lib/flowy-workspace/src/services/app_controller.rs

@@ -36,7 +36,7 @@ impl AppController {
         conn.immediate_transaction::<_, WorkspaceError, _>(|| {
             let _ = self.save_app(app.clone(), &*conn)?;
             let apps = self.read_local_apps(&app.workspace_id, &*conn)?;
-            dart_notify(&app.workspace_id, WorkspaceObservable::WorkspaceCreateApp)
+            dart_notify(&app.workspace_id, Notification::WorkspaceCreateApp)
                 .payload(apps)
                 .send();
             Ok(())
@@ -59,13 +59,13 @@ impl AppController {
         Ok(app_table.into())
     }
 
-    #[tracing::instrument(level = "debug", skip(self), fields(dart_notify)  err)]
+    #[tracing::instrument(level = "debug", skip(self), err)]
     pub(crate) async fn delete_app(&self, app_id: &str) -> Result<(), WorkspaceError> {
         let conn = &*self.database.db_connection()?;
         conn.immediate_transaction::<_, WorkspaceError, _>(|| {
             let app = self.sql.delete_app(app_id, &*conn)?;
             let apps = self.read_local_apps(&app.workspace_id, &*conn)?;
-            dart_notify(&app.workspace_id, WorkspaceObservable::WorkspaceDeleteApp)
+            dart_notify(&app.workspace_id, Notification::WorkspaceDeleteApp)
                 .payload(apps)
                 .send();
             Ok(())
@@ -88,9 +88,7 @@ impl AppController {
         conn.immediate_transaction::<_, WorkspaceError, _>(|| {
             let _ = self.sql.update_app(changeset, conn)?;
             let app: App = self.sql.read_app(&app_id, None, conn)?.into();
-            dart_notify(&app_id, WorkspaceObservable::AppUpdated)
-                .payload(app)
-                .send();
+            dart_notify(&app_id, Notification::AppUpdated).payload(app).send();
             Ok(())
         })?;
 

+ 1 - 3
rust-lib/flowy-workspace/src/services/server/middleware.rs

@@ -21,9 +21,7 @@ impl ResponseMiddleware for WorkspaceMiddleware {
                     None => {},
                     Some(token) => {
                         let error = WorkspaceError::new(ErrorCode::UserUnauthorized, "");
-                        dart_notify(token, WorkspaceObservable::UserUnauthorized)
-                            .error(error)
-                            .send()
+                        dart_notify(token, Notification::UserUnauthorized).error(error).send()
                     },
                 }
             }

+ 102 - 6
rust-lib/flowy-workspace/src/services/trash_can.rs

@@ -1,19 +1,115 @@
 use crate::{
     entities::trash::{RepeatedTrash, Trash},
-    errors::WorkspaceError,
+    errors::{WorkspaceError, WorkspaceResult},
     module::WorkspaceDatabase,
+    sql_tables::trash::{TrashSource, TrashTable, TrashTableSql},
 };
-use std::sync::Arc;
+use flowy_database::SqliteConnection;
+use parking_lot::RwLock;
+use std::{collections::HashSet, sync::Arc};
+use tokio::sync::broadcast;
+
+#[derive(Clone, PartialEq, Eq)]
+pub enum TrashEvent {
+    Putback(TrashSource, String),
+    Delete(TrashSource, String),
+}
+
+impl TrashEvent {
+    pub fn select(self, s: TrashSource) -> Option<TrashEvent> {
+        match &self {
+            TrashEvent::Putback(source, id) => {
+                if source == &s {
+                    return Some(self);
+                }
+            },
+            TrashEvent::Delete(source, id) => {
+                if source == &s {
+                    return Some(self);
+                }
+            },
+        }
+        None
+    }
+
+    fn split(self) -> (TrashSource, String) {
+        match self {
+            TrashEvent::Putback(source, id) => (source, id),
+            TrashEvent::Delete(source, id) => (source, id),
+        }
+    }
+}
 
 pub struct TrashCan {
     database: Arc<dyn WorkspaceDatabase>,
+    notify: broadcast::Sender<TrashEvent>,
 }
 
 impl TrashCan {
-    pub fn new(database: Arc<dyn WorkspaceDatabase>) -> Self { Self { database } }
-    pub fn read_trash(&self) -> Result<RepeatedTrash, WorkspaceError> { Ok(RepeatedTrash { items: vec![] }) }
+    pub fn new(database: Arc<dyn WorkspaceDatabase>) -> Self {
+        let (tx, _) = broadcast::channel(10);
+
+        Self { database, notify: tx }
+    }
+    pub fn read_trash(&self) -> Result<RepeatedTrash, WorkspaceError> {
+        let conn = self.database.db_connection()?;
+        let repeated_trash = TrashTableSql::read_all(&*conn)?;
+        Ok(repeated_trash)
+    }
+
+    #[tracing::instrument(level = "debug", skip(self), fields(putback)  err)]
+    pub fn putback(&self, trash_id: &str) -> WorkspaceResult<()> {
+        let conn = self.database.db_connection()?;
+        // Opti: transaction
+        let trash_table = TrashTableSql::read(trash_id, &*conn)?;
+        let _ = TrashTableSql::delete_trash(trash_id, &*conn)?;
+        tracing::Span::current().record(
+            "putback",
+            &format!("{:?}: {}", &trash_table.source, trash_table.id).as_str(),
+        );
+
+        self.notify
+            .send(TrashEvent::Putback(trash_table.source, trash_table.id));
+
+        Ok(())
+    }
+
+    #[tracing::instrument(level = "debug", skip(self)  err)]
+    pub fn delete_trash(&self, trash_id: &str) -> WorkspaceResult<()> {
+        let conn = self.database.db_connection()?;
+        let trash_table = TrashTableSql::read(trash_id, &*conn)?;
+        let _ = TrashTableSql::delete_trash(trash_id, &*conn)?;
+
+        self.notify.send(TrashEvent::Delete(trash_table.source, trash_table.id));
+        Ok(())
+    }
+
+    pub fn subscribe(&self) -> broadcast::Receiver<TrashEvent> { self.notify.subscribe() }
+
+    #[tracing::instrument(level = "debug", skip(self, trash, source, conn), fields(add_trash)  err)]
+    pub fn add<T: Into<Trash>>(
+        &self,
+        trash: T,
+        source: TrashSource,
+        conn: &SqliteConnection,
+    ) -> Result<(), WorkspaceError> {
+        let trash = trash.into();
+        let trash_table = TrashTable {
+            id: trash.id,
+            name: trash.name,
+            desc: "".to_owned(),
+            modified_time: trash.modified_time,
+            create_time: trash.create_time,
+            source,
+        };
 
-    pub fn add<T: Into<Trash>>(&self, trash: T) { let trash = trash.into(); }
+        tracing::Span::current().record(
+            "add_trash",
+            &format!("{:?}: {}", &trash_table.source, trash_table.id).as_str(),
+        );
 
-    pub fn remove(&self, trash_id: &str) {}
+        let trash_id = trash_table.id.clone();
+        let _ = TrashTableSql::create_trash(trash_table, &*conn)?;
+        Ok(())
+    }
 }

+ 79 - 41
rust-lib/flowy-workspace/src/services/view_controller.rs

@@ -14,19 +14,23 @@ use crate::{
     },
     errors::internal_error,
     module::WorkspaceUser,
-    notify::WorkspaceObservable,
-    services::TrashCan,
+    notify::Notification,
+    services::{TrashCan, TrashEvent},
+    sql_tables::trash::TrashSource,
 };
 use flowy_database::SqliteConnection;
 use flowy_document::{
     entities::doc::{CreateDocParams, DocDelta, QueryDocParams},
     module::FlowyDocument,
 };
+
+use crate::errors::WorkspaceResult;
+use futures::{future, FutureExt, StreamExt, TryStreamExt};
 use std::sync::Arc;
+use tokio::sync::broadcast::error::RecvError;
 
 pub(crate) struct ViewController {
     user: Arc<dyn WorkspaceUser>,
-    sql: Arc<ViewTableSql>,
     server: Server,
     database: Arc<dyn WorkspaceDatabase>,
     trash_can: Arc<TrashCan>,
@@ -41,10 +45,8 @@ impl ViewController {
         trash_can: Arc<TrashCan>,
         document: Arc<FlowyDocument>,
     ) -> Self {
-        let sql = Arc::new(ViewTableSql {});
         Self {
             user,
-            sql,
             server,
             database,
             trash_can,
@@ -54,10 +56,11 @@ impl ViewController {
 
     pub(crate) fn init(&self) -> Result<(), WorkspaceError> {
         let _ = self.document.init()?;
+        self.listen_trash_can_event();
         Ok(())
     }
 
-    #[tracing::instrument(level = "debug", skip(self, params), fields(dart_notify)  err)]
+    #[tracing::instrument(level = "debug", skip(self, params), err)]
     pub(crate) async fn create_view(&self, params: CreateViewParams) -> Result<View, WorkspaceError> {
         let view = self.create_view_on_server(params.clone()).await?;
         let conn = &*self.database.db_connection()?;
@@ -66,8 +69,8 @@ impl ViewController {
             let _ = self.save_view(view.clone(), conn)?;
             self.document.create(CreateDocParams::new(&view.id, params.data))?;
 
-            let repeated_view = self.sql.read_views(&view.belong_to_id, Some(false), conn)?;
-            dart_notify(&view.belong_to_id, WorkspaceObservable::AppCreateView)
+            let repeated_view = ViewTableSql::read_views(&view.belong_to_id, conn)?;
+            dart_notify(&view.belong_to_id, Notification::AppViewsChanged)
                 .payload(repeated_view)
                 .send();
             Ok(())
@@ -78,13 +81,13 @@ impl ViewController {
 
     pub(crate) fn save_view(&self, view: View, conn: &SqliteConnection) -> Result<(), WorkspaceError> {
         let view_table = ViewTable::new(view);
-        let _ = self.sql.create_view(view_table, conn)?;
+        let _ = ViewTableSql::create_view(view_table, conn)?;
         Ok(())
     }
 
     pub(crate) async fn read_view(&self, params: QueryViewParams) -> Result<View, WorkspaceError> {
         let conn = self.database.db_connection()?;
-        let view_table = self.sql.read_view(&params.view_id, Some(params.is_trash), &*conn)?;
+        let view_table = ViewTableSql::read_view(&params.view_id, &*conn)?;
         let view: View = view_table.into();
         let _ = self.read_view_on_server(params);
         Ok(view)
@@ -96,18 +99,18 @@ impl ViewController {
         Ok(edit_context.delta().await.map_err(internal_error)?)
     }
 
-    #[tracing::instrument(level = "debug", skip(self, params), fields(dart_notify)  err)]
+    #[tracing::instrument(level = "debug", skip(self, params), err)]
     pub(crate) async fn delete_view(&self, params: DeleteViewParams) -> Result<(), WorkspaceError> {
         let conn = &*self.database.db_connection()?;
         let _ = self.delete_view_on_server(&params.view_id);
 
         conn.immediate_transaction::<_, WorkspaceError, _>(|| {
-            let view_table = self.sql.delete_view(&params.view_id, conn)?;
+            let view_table = ViewTableSql::delete_view(&params.view_id, conn)?;
             let _ = self.document.delete(params.into())?;
 
-            let repeated_view = self.sql.read_views(&view_table.belong_to_id, Some(false), conn)?;
+            let repeated_view = ViewTableSql::read_views(&view_table.belong_to_id, conn)?;
 
-            dart_notify(&view_table.belong_to_id, WorkspaceObservable::AppDeleteView)
+            dart_notify(&view_table.belong_to_id, Notification::AppViewsChanged)
                 .payload(repeated_view)
                 .send();
             Ok(())
@@ -121,51 +124,32 @@ impl ViewController {
     pub(crate) async fn read_views_belong_to(&self, belong_to_id: &str) -> Result<RepeatedView, WorkspaceError> {
         // TODO: read from server
         let conn = self.database.db_connection()?;
-        let repeated_view = self.sql.read_views(belong_to_id, Some(false), &*conn)?;
+        let repeated_view = ViewTableSql::read_views(belong_to_id, &*conn)?;
         Ok(repeated_view)
     }
 
-    #[tracing::instrument(level = "debug", skip(self, params), fields(dart_notify)  err)]
+    #[tracing::instrument(level = "debug", skip(self, params), err)]
     pub(crate) async fn update_view(&self, params: UpdateViewParams) -> Result<View, WorkspaceError> {
         let conn = &*self.database.db_connection()?;
         let changeset = ViewTableChangeset::new(params.clone());
         let view_id = changeset.id.clone();
 
         let updated_view = conn.immediate_transaction::<_, WorkspaceError, _>(|| {
-            let _ = self.sql.update_view(changeset, conn)?;
-            let view: View = self.sql.read_view(&view_id, None, conn)?.into();
-            let _ = self.add_to_trash_if_need(view.clone(), params.is_trash.clone())?;
-
+            let _ = ViewTableSql::update_view(changeset, conn)?;
+            let view: View = ViewTableSql::read_view(&view_id, conn)?.into();
             match params.is_trash {
                 None => {
-                    dart_notify(&view_id, WorkspaceObservable::ViewUpdated)
+                    dart_notify(&view_id, Notification::ViewUpdated)
                         .payload(view.clone())
                         .send();
                 },
                 Some(is_trash) => {
-                    let repeated_view = self.sql.read_views(&view.belong_to_id, Some(false), conn)?;
-                    dart_notify(&view.belong_to_id, WorkspaceObservable::AppDeleteView)
-                        .payload(repeated_view)
-                        .send();
-
-                    match is_trash {
-                        true => self.trash_can.add(view.clone()),
-                        false => self.trash_can.remove(&view.id),
+                    if is_trash {
+                        self.trash_can.add(view.clone(), TrashSource::View, conn);
                     }
+                    let _ = notify_view_num_did_change(&view.belong_to_id, conn)?;
                 },
             }
-
-            if params.is_trash.is_some() {
-                let repeated_view = self.sql.read_views(&view.belong_to_id, Some(false), conn)?;
-                dart_notify(&view.belong_to_id, WorkspaceObservable::AppDeleteView)
-                    .payload(repeated_view)
-                    .send();
-            } else {
-                dart_notify(&view_id, WorkspaceObservable::ViewUpdated)
-                    .payload(view.clone())
-                    .send();
-            }
-
             Ok(view)
         })?;
 
@@ -237,4 +221,58 @@ impl ViewController {
         });
         Ok(())
     }
+
+    fn listen_trash_can_event(&self) {
+        let mut rx = self.trash_can.subscribe();
+        let database = self.database.clone();
+        let _ = tokio::spawn(async move {
+            loop {
+                let mut stream = Box::pin(rx.recv().into_stream().filter_map(|result| async move {
+                    match result {
+                        Ok(event) => event.select(TrashSource::View),
+                        Err(_) => None,
+                    }
+                }));
+                let event: Option<TrashEvent> = stream.next().await;
+                match event {
+                    Some(event) => handle_trash_event(database.clone(), event),
+                    None => {},
+                }
+            }
+        });
+    }
+}
+
+fn notify_view_num_did_change(belong_to_id: &str, conn: &SqliteConnection) -> WorkspaceResult<()> {
+    let repeated_view = ViewTableSql::read_views(belong_to_id, conn)?;
+    dart_notify(belong_to_id, Notification::AppViewsChanged)
+        .payload(repeated_view)
+        .send();
+    Ok(())
+}
+
+fn handle_trash_event(database: Arc<dyn WorkspaceDatabase>, event: TrashEvent) {
+    let result = || {
+        let conn = &*database.db_connection()?;
+        match event {
+            TrashEvent::Putback(_, pub_back_id) => {
+                let _ = conn.immediate_transaction::<_, WorkspaceError, _>(|| {
+                    let view_table = ViewTableSql::read_view(&pub_back_id, conn)?;
+                    notify_view_num_did_change(&view_table.belong_to_id, conn)
+                })?;
+            },
+            TrashEvent::Delete(_, delete_id) => {
+                let _ = conn.immediate_transaction::<_, WorkspaceError, _>(|| {
+                    let view_table = ViewTableSql::delete_view(&delete_id, conn)?;
+                    notify_view_num_did_change(&view_table.belong_to_id, conn)
+                })?;
+            },
+        }
+        Ok::<(), WorkspaceError>(())
+    };
+
+    match result() {
+        Ok(_) => {},
+        Err(e) => log::error!("{:?}", e),
+    }
 }

+ 4 - 4
rust-lib/flowy-workspace/src/services/workspace_controller.rs

@@ -69,7 +69,7 @@ impl WorkspaceController {
         conn.immediate_transaction::<_, WorkspaceError, _>(|| {
             self.workspace_sql.create_workspace(workspace_table, conn)?;
             let repeated_workspace = self.read_local_workspaces(None, &user_id, conn)?;
-            dart_notify(&token, WorkspaceObservable::UserCreateWorkspace)
+            dart_notify(&token, Notification::UserCreateWorkspace)
                 .payload(repeated_workspace)
                 .send();
 
@@ -88,7 +88,7 @@ impl WorkspaceController {
             let _ = self.workspace_sql.update_workspace(changeset, conn)?;
             let user_id = self.user.user_id()?;
             let workspace = self.read_local_workspace(workspace_id.clone(), &user_id, conn)?;
-            dart_notify(&workspace_id, WorkspaceObservable::WorkspaceUpdated)
+            dart_notify(&workspace_id, Notification::WorkspaceUpdated)
                 .payload(workspace)
                 .send();
 
@@ -108,7 +108,7 @@ impl WorkspaceController {
         conn.immediate_transaction::<_, WorkspaceError, _>(|| {
             let _ = self.workspace_sql.delete_workspace(workspace_id, conn)?;
             let repeated_workspace = self.read_local_workspaces(None, &user_id, conn)?;
-            dart_notify(&token, WorkspaceObservable::UserDeleteWorkspace)
+            dart_notify(&token, Notification::UserDeleteWorkspace)
                 .payload(repeated_workspace)
                 .send();
 
@@ -297,7 +297,7 @@ impl WorkspaceController {
                 Ok(())
             })?;
 
-            dart_notify(&token, WorkspaceObservable::WorkspaceListUpdated)
+            dart_notify(&token, Notification::WorkspaceListUpdated)
                 .payload(workspaces)
                 .send();
             Result::<(), WorkspaceError>::Ok(())

+ 10 - 3
rust-lib/flowy-workspace/src/sql_tables/trash/trash_sql.rs

@@ -10,18 +10,25 @@ use flowy_database::{
 pub struct TrashTableSql {}
 
 impl TrashTableSql {
-    pub(crate) fn create_trash(&self, trash_table: TrashTable, conn: &SqliteConnection) -> Result<(), WorkspaceError> {
+    pub(crate) fn create_trash(trash_table: TrashTable, conn: &SqliteConnection) -> Result<(), WorkspaceError> {
         diesel_insert_table!(trash_table, &trash_table, conn);
         Ok(())
     }
 
-    pub(crate) fn read_trash(&self, conn: &SqliteConnection) -> Result<RepeatedTrash, WorkspaceError> {
+    pub(crate) fn read_all(conn: &SqliteConnection) -> Result<RepeatedTrash, WorkspaceError> {
         let trash_tables = dsl::trash_table.load::<TrashTable>(conn)?;
         let items = trash_tables.into_iter().map(|t| t.into()).collect::<Vec<Trash>>();
         Ok(RepeatedTrash { items })
     }
 
-    pub(crate) fn delete_trash(&self, trash_id: &str, conn: &SqliteConnection) -> Result<(), WorkspaceError> {
+    pub(crate) fn read(trash_id: &str, conn: &SqliteConnection) -> Result<TrashTable, WorkspaceError> {
+        let trash_table = dsl::trash_table
+            .filter(trash_table::id.eq(trash_id))
+            .first::<TrashTable>(conn)?;
+        Ok(trash_table)
+    }
+
+    pub(crate) fn delete_trash(trash_id: &str, conn: &SqliteConnection) -> Result<(), WorkspaceError> {
         diesel_delete_table!(trash_table, trash_id, conn);
         Ok(())
     }

+ 30 - 2
rust-lib/flowy-workspace/src/sql_tables/trash/trash_table.rs

@@ -1,5 +1,5 @@
 use crate::entities::trash::Trash;
-use diesel::sql_types::Binary;
+use diesel::sql_types::{Binary, Integer};
 use flowy_database::schema::trash_table;
 
 #[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)]
@@ -10,8 +10,8 @@ pub(crate) struct TrashTable {
     pub desc: String,
     pub modified_time: i64,
     pub create_time: i64,
+    pub source: TrashSource,
 }
-
 impl std::convert::Into<Trash> for TrashTable {
     fn into(self) -> Trash {
         Trash {
@@ -22,3 +22,31 @@ impl std::convert::Into<Trash> for TrashTable {
         }
     }
 }
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)]
+#[repr(i32)]
+#[sql_type = "Integer"]
+pub enum TrashSource {
+    Unknown = 0,
+    View    = 1,
+}
+
+impl std::default::Default for TrashSource {
+    fn default() -> Self { TrashSource::Unknown }
+}
+
+impl std::convert::From<i32> for TrashSource {
+    fn from(value: i32) -> Self {
+        match value {
+            0 => TrashSource::Unknown,
+            1 => TrashSource::View,
+            _o => TrashSource::Unknown,
+        }
+    }
+}
+
+impl TrashSource {
+    pub fn value(&self) -> i32 { *self as i32 }
+}
+
+impl_sql_integer_expression!(TrashSource);

+ 78 - 30
rust-lib/flowy-workspace/src/sql_tables/view/view_sql.rs

@@ -5,14 +5,14 @@ use crate::{
 };
 use flowy_database::{
     prelude::*,
-    schema::{view_table, view_table::dsl},
+    schema::{trash_table, trash_table::dsl::id as trash_id, view_table, view_table::dsl},
     SqliteConnection,
 };
 
 pub struct ViewTableSql {}
 
 impl ViewTableSql {
-    pub(crate) fn create_view(&self, view_table: ViewTable, conn: &SqliteConnection) -> Result<(), WorkspaceError> {
+    pub(crate) fn create_view(view_table: ViewTable, conn: &SqliteConnection) -> Result<(), WorkspaceError> {
         match diesel_record_count!(view_table, &view_table.id, conn) {
             0 => diesel_insert_table!(view_table, &view_table, conn),
             _ => {
@@ -23,35 +23,44 @@ impl ViewTableSql {
         Ok(())
     }
 
-    pub(crate) fn read_view(
-        &self,
-        view_id: &str,
-        is_trash: Option<bool>,
-        conn: &SqliteConnection,
-    ) -> Result<ViewTable, WorkspaceError> {
+    pub(crate) fn read_view(view_id: &str, conn: &SqliteConnection) -> Result<ViewTable, WorkspaceError> {
         // https://docs.diesel.rs/diesel/query_builder/struct.UpdateStatement.html
-        let mut filter = dsl::view_table.filter(view_table::id.eq(view_id)).into_boxed();
-        if let Some(is_trash) = is_trash {
-            filter = filter.filter(view_table::is_trash.eq(is_trash));
+        // let mut filter =
+        // dsl::view_table.filter(view_table::id.eq(view_id)).into_boxed();
+        // if let Some(is_trash) = is_trash {
+        //     filter = filter.filter(view_table::is_trash.eq(is_trash));
+        // }
+        // let repeated_view = filter.first::<ViewTable>(conn)?;
+        let view_table = dsl::view_table
+            .filter(view_table::id.eq(view_id))
+            .first::<ViewTable>(conn)?;
+
+        let repeated_trash: Vec<String> = trash_table::dsl::trash_table
+            .select((trash_table::dsl::id))
+            .load(conn)?;
+
+        if repeated_trash.contains(&view_table.id) {
+            return Err(WorkspaceError::not_found());
         }
-        let view_table = filter.first::<ViewTable>(conn)?;
+
         Ok(view_table)
     }
 
     // belong_to_id will be the app_id or view_id.
-    pub(crate) fn read_views(
-        &self,
-        belong_to_id: &str,
-        is_trash: Option<bool>,
-        conn: &SqliteConnection,
-    ) -> Result<RepeatedView, WorkspaceError> {
-        let mut filter = dsl::view_table
+    pub(crate) fn read_views(belong_to_id: &str, conn: &SqliteConnection) -> Result<RepeatedView, WorkspaceError> {
+        let mut view_tables = dsl::view_table
             .filter(view_table::belong_to_id.eq(belong_to_id))
-            .into_boxed();
-        if let Some(is_trash) = is_trash {
-            filter = filter.filter(view_table::is_trash.eq(is_trash));
-        }
-        let view_tables = filter.load::<ViewTable>(conn)?;
+            .into_boxed()
+            .load::<ViewTable>(conn)?;
+
+        let repeated_trash: Vec<String> = trash_table::dsl::trash_table
+            .select((trash_table::dsl::id))
+            .load(conn)?;
+
+        view_tables = view_tables
+            .into_iter()
+            .filter(|table| !repeated_trash.contains(&table.id))
+            .collect::<Vec<ViewTable>>();
 
         let views = view_tables
             .into_iter()
@@ -61,16 +70,12 @@ impl ViewTableSql {
         Ok(RepeatedView { items: views })
     }
 
-    pub(crate) fn update_view(
-        &self,
-        changeset: ViewTableChangeset,
-        conn: &SqliteConnection,
-    ) -> Result<(), WorkspaceError> {
+    pub(crate) fn update_view(changeset: ViewTableChangeset, conn: &SqliteConnection) -> Result<(), WorkspaceError> {
         diesel_update_table!(view_table, changeset, conn);
         Ok(())
     }
 
-    pub(crate) fn delete_view(&self, view_id: &str, conn: &SqliteConnection) -> Result<ViewTable, WorkspaceError> {
+    pub(crate) fn delete_view(view_id: &str, conn: &SqliteConnection) -> Result<ViewTable, WorkspaceError> {
         let view_table = dsl::view_table
             .filter(view_table::id.eq(view_id))
             .first::<ViewTable>(conn)?;
@@ -78,3 +83,46 @@ impl ViewTableSql {
         Ok(view_table)
     }
 }
+
+// pub(crate) fn read_views(
+//     belong_to_id: &str,
+//     is_trash: Option<bool>,
+//     conn: &SqliteConnection,
+// ) -> Result<RepeatedView, WorkspaceError> {
+//     let views = dsl::view_table
+//         .inner_join(trash_table::dsl::trash_table.on(trash_id.ne(view_table::
+// id)))         .filter(view_table::belong_to_id.eq(belong_to_id))
+//         .select((
+//             view_table::id,
+//             view_table::belong_to_id,
+//             view_table::name,
+//             view_table::desc,
+//             view_table::modified_time,
+//             view_table::create_time,
+//             view_table::thumbnail,
+//             view_table::view_type,
+//             view_table::version,
+//         ))
+//         .load(conn)?
+//         .into_iter()
+//         .map(
+//             |(id, belong_to_id, name, desc, create_time, modified_time,
+// thumbnail, view_type, version)| {                 ViewTable {
+//                     id,
+//                     belong_to_id,
+//                     name,
+//                     desc,
+//                     modified_time,
+//                     create_time,
+//                     thumbnail,
+//                     view_type,
+//                     version,
+//                     is_trash: false,
+//                 }
+//                 .into()
+//             },
+//         )
+//         .collect::<Vec<View>>();
+//
+//     Ok(RepeatedView { items: views })
+// }