Browse Source

add user appearance setting

appflowy 3 years ago
parent
commit
8449f736e7
40 changed files with 1262 additions and 397 deletions
  1. 1 0
      backend/Cargo.lock
  2. 355 289
      frontend/app_flowy/packages/flowy_sdk/lib/dispatch/code_gen.dart
  3. 1 1
      frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dispatch.dart
  4. 1 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user-data-model/protobuf.dart
  5. 135 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user-data-model/user_setting.pb.dart
  6. 7 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user-data-model/user_setting.pbenum.dart
  7. 32 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user-data-model/user_setting.pbjson.dart
  8. 9 0
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user-data-model/user_setting.pbserver.dart
  9. 2 2
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/event_map.pb.dart
  10. 5 1
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/event_map.pbenum.dart
  11. 4 2
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/event_map.pbjson.dart
  12. 2 2
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/event_map.pbserver.dart
  13. 1 1
      frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/protobuf.dart
  14. 53 53
      frontend/rust-lib/flowy-database/src/kv/kv.rs
  15. 1 1
      frontend/rust-lib/flowy-folder/src/services/persistence/migration.rs
  16. 3 3
      frontend/rust-lib/flowy-folder/src/services/workspace/event_handler.rs
  17. 1 1
      frontend/rust-lib/flowy-net/src/http_server/user.rs
  18. 1 1
      frontend/rust-lib/flowy-net/src/local_server/server.rs
  19. 1 1
      frontend/rust-lib/flowy-sdk/src/deps_resolve/user_deps.rs
  20. 1 1
      frontend/rust-lib/flowy-sdk/src/module.rs
  21. 1 2
      frontend/rust-lib/flowy-test/src/helper.rs
  22. 2 2
      frontend/rust-lib/flowy-user/Flowy.toml
  23. 37 1
      frontend/rust-lib/flowy-user/src/event_map.rs
  24. 34 1
      frontend/rust-lib/flowy-user/src/handlers/user_handler.rs
  25. 1 2
      frontend/rust-lib/flowy-user/src/lib.rs
  26. 32 21
      frontend/rust-lib/flowy-user/src/protobuf/model/event_map.rs
  27. 2 2
      frontend/rust-lib/flowy-user/src/protobuf/model/mod.rs
  28. 2 0
      frontend/rust-lib/flowy-user/src/protobuf/proto/event_map.proto
  29. 1 1
      frontend/rust-lib/flowy-user/src/services/user_session.rs
  30. 1 1
      frontend/rust-lib/flowy-user/tests/event/auth_test.rs
  31. 1 1
      frontend/rust-lib/flowy-user/tests/event/user_profile_test.rs
  32. 1 0
      shared-lib/Cargo.lock
  33. 6 0
      shared-lib/flowy-collaboration/src/client_folder/folder_pad.rs
  34. 2 0
      shared-lib/flowy-derive/src/derive_cache/derive_cache.rs
  35. 1 0
      shared-lib/flowy-user-data-model/Cargo.toml
  36. 2 4
      shared-lib/flowy-user-data-model/src/entities/mod.rs
  37. 32 0
      shared-lib/flowy-user-data-model/src/entities/user_setting.rs
  38. 3 0
      shared-lib/flowy-user-data-model/src/protobuf/model/mod.rs
  39. 475 0
      shared-lib/flowy-user-data-model/src/protobuf/model/user_setting.rs
  40. 10 0
      shared-lib/flowy-user-data-model/src/protobuf/proto/user_setting.proto

+ 1 - 0
backend/Cargo.lock

@@ -1542,6 +1542,7 @@ dependencies = [
  "lazy_static",
  "lazy_static",
  "log",
  "log",
  "protobuf",
  "protobuf",
+ "serde",
  "unicode-segmentation",
  "unicode-segmentation",
  "validator",
  "validator",
 ]
 ]

+ 355 - 289
frontend/app_flowy/packages/flowy_sdk/lib/dispatch/code_gen.dart

@@ -1,503 +1,569 @@
+
+
 /// Auto gen code from rust ast, do not edit
 /// Auto gen code from rust ast, do not edit
 part of 'dispatch.dart';
 part of 'dispatch.dart';
-
 class FolderEventCreateWorkspace {
 class FolderEventCreateWorkspace {
-  CreateWorkspaceRequest request;
-  FolderEventCreateWorkspace(this.request);
+     CreateWorkspaceRequest request;
+     FolderEventCreateWorkspace(this.request);
 
 
-  Future<Either<Workspace, FlowyError>> send() {
+    Future<Either<Workspace, FlowyError>> send() {
     final request = FFIRequest.create()
     final request = FFIRequest.create()
-      ..event = FolderEvent.CreateWorkspace.toString()
-      ..payload = requestToBytes(this.request);
+          ..event = FolderEvent.CreateWorkspace.toString()
+          ..payload = requestToBytes(this.request);
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (okBytes) => left(Workspace.fromBuffer(okBytes)),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (okBytes) => left(Workspace.fromBuffer(okBytes)),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
         ));
-  }
+    }
 }
 }
 
 
 class FolderEventReadCurWorkspace {
 class FolderEventReadCurWorkspace {
-  FolderEventReadCurWorkspace();
+    FolderEventReadCurWorkspace();
 
 
-  Future<Either<CurrentWorkspaceSetting, FlowyError>> send() {
-    final request = FFIRequest.create()..event = FolderEvent.ReadCurWorkspace.toString();
+    Future<Either<CurrentWorkspaceSetting, FlowyError>> send() {
+     final request = FFIRequest.create()
+        ..event = FolderEvent.ReadCurWorkspace.toString();
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (okBytes) => left(CurrentWorkspaceSetting.fromBuffer(okBytes)),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
-        ));
-  }
+     return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
+        (okBytes) => left(CurrentWorkspaceSetting.fromBuffer(okBytes)),
+        (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+      ));
+    }
 }
 }
 
 
 class FolderEventReadWorkspaces {
 class FolderEventReadWorkspaces {
-  QueryWorkspaceRequest request;
-  FolderEventReadWorkspaces(this.request);
+     QueryWorkspaceRequest request;
+     FolderEventReadWorkspaces(this.request);
 
 
-  Future<Either<RepeatedWorkspace, FlowyError>> send() {
+    Future<Either<RepeatedWorkspace, FlowyError>> send() {
     final request = FFIRequest.create()
     final request = FFIRequest.create()
-      ..event = FolderEvent.ReadWorkspaces.toString()
-      ..payload = requestToBytes(this.request);
+          ..event = FolderEvent.ReadWorkspaces.toString()
+          ..payload = requestToBytes(this.request);
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (okBytes) => left(RepeatedWorkspace.fromBuffer(okBytes)),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (okBytes) => left(RepeatedWorkspace.fromBuffer(okBytes)),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
         ));
-  }
+    }
 }
 }
 
 
 class FolderEventDeleteWorkspace {
 class FolderEventDeleteWorkspace {
-  QueryWorkspaceRequest request;
-  FolderEventDeleteWorkspace(this.request);
+     QueryWorkspaceRequest request;
+     FolderEventDeleteWorkspace(this.request);
 
 
-  Future<Either<Unit, FlowyError>> send() {
+    Future<Either<Unit, FlowyError>> send() {
     final request = FFIRequest.create()
     final request = FFIRequest.create()
-      ..event = FolderEvent.DeleteWorkspace.toString()
-      ..payload = requestToBytes(this.request);
+          ..event = FolderEvent.DeleteWorkspace.toString()
+          ..payload = requestToBytes(this.request);
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (bytes) => left(unit),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (bytes) => left(unit),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
         ));
-  }
+    }
 }
 }
 
 
 class FolderEventOpenWorkspace {
 class FolderEventOpenWorkspace {
-  QueryWorkspaceRequest request;
-  FolderEventOpenWorkspace(this.request);
+     QueryWorkspaceRequest request;
+     FolderEventOpenWorkspace(this.request);
 
 
-  Future<Either<Workspace, FlowyError>> send() {
+    Future<Either<Workspace, FlowyError>> send() {
     final request = FFIRequest.create()
     final request = FFIRequest.create()
-      ..event = FolderEvent.OpenWorkspace.toString()
-      ..payload = requestToBytes(this.request);
+          ..event = FolderEvent.OpenWorkspace.toString()
+          ..payload = requestToBytes(this.request);
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (okBytes) => left(Workspace.fromBuffer(okBytes)),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (okBytes) => left(Workspace.fromBuffer(okBytes)),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
         ));
-  }
+    }
 }
 }
 
 
 class FolderEventReadWorkspaceApps {
 class FolderEventReadWorkspaceApps {
-  QueryWorkspaceRequest request;
-  FolderEventReadWorkspaceApps(this.request);
+     QueryWorkspaceRequest request;
+     FolderEventReadWorkspaceApps(this.request);
 
 
-  Future<Either<RepeatedApp, FlowyError>> send() {
+    Future<Either<RepeatedApp, FlowyError>> send() {
     final request = FFIRequest.create()
     final request = FFIRequest.create()
-      ..event = FolderEvent.ReadWorkspaceApps.toString()
-      ..payload = requestToBytes(this.request);
+          ..event = FolderEvent.ReadWorkspaceApps.toString()
+          ..payload = requestToBytes(this.request);
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (okBytes) => left(RepeatedApp.fromBuffer(okBytes)),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (okBytes) => left(RepeatedApp.fromBuffer(okBytes)),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
         ));
-  }
+    }
 }
 }
 
 
 class FolderEventCreateApp {
 class FolderEventCreateApp {
-  CreateAppRequest request;
-  FolderEventCreateApp(this.request);
+     CreateAppRequest request;
+     FolderEventCreateApp(this.request);
 
 
-  Future<Either<App, FlowyError>> send() {
+    Future<Either<App, FlowyError>> send() {
     final request = FFIRequest.create()
     final request = FFIRequest.create()
-      ..event = FolderEvent.CreateApp.toString()
-      ..payload = requestToBytes(this.request);
+          ..event = FolderEvent.CreateApp.toString()
+          ..payload = requestToBytes(this.request);
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (okBytes) => left(App.fromBuffer(okBytes)),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (okBytes) => left(App.fromBuffer(okBytes)),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
         ));
-  }
+    }
 }
 }
 
 
 class FolderEventDeleteApp {
 class FolderEventDeleteApp {
-  QueryAppRequest request;
-  FolderEventDeleteApp(this.request);
+     QueryAppRequest request;
+     FolderEventDeleteApp(this.request);
 
 
-  Future<Either<Unit, FlowyError>> send() {
+    Future<Either<Unit, FlowyError>> send() {
     final request = FFIRequest.create()
     final request = FFIRequest.create()
-      ..event = FolderEvent.DeleteApp.toString()
-      ..payload = requestToBytes(this.request);
+          ..event = FolderEvent.DeleteApp.toString()
+          ..payload = requestToBytes(this.request);
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (bytes) => left(unit),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (bytes) => left(unit),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
         ));
-  }
+    }
 }
 }
 
 
 class FolderEventReadApp {
 class FolderEventReadApp {
-  QueryAppRequest request;
-  FolderEventReadApp(this.request);
+     QueryAppRequest request;
+     FolderEventReadApp(this.request);
 
 
-  Future<Either<App, FlowyError>> send() {
+    Future<Either<App, FlowyError>> send() {
     final request = FFIRequest.create()
     final request = FFIRequest.create()
-      ..event = FolderEvent.ReadApp.toString()
-      ..payload = requestToBytes(this.request);
+          ..event = FolderEvent.ReadApp.toString()
+          ..payload = requestToBytes(this.request);
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (okBytes) => left(App.fromBuffer(okBytes)),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (okBytes) => left(App.fromBuffer(okBytes)),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
         ));
-  }
+    }
 }
 }
 
 
 class FolderEventUpdateApp {
 class FolderEventUpdateApp {
-  UpdateAppRequest request;
-  FolderEventUpdateApp(this.request);
+     UpdateAppRequest request;
+     FolderEventUpdateApp(this.request);
 
 
-  Future<Either<Unit, FlowyError>> send() {
+    Future<Either<Unit, FlowyError>> send() {
     final request = FFIRequest.create()
     final request = FFIRequest.create()
-      ..event = FolderEvent.UpdateApp.toString()
-      ..payload = requestToBytes(this.request);
+          ..event = FolderEvent.UpdateApp.toString()
+          ..payload = requestToBytes(this.request);
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (bytes) => left(unit),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (bytes) => left(unit),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
         ));
-  }
+    }
 }
 }
 
 
 class FolderEventCreateView {
 class FolderEventCreateView {
-  CreateViewRequest request;
-  FolderEventCreateView(this.request);
+     CreateViewRequest request;
+     FolderEventCreateView(this.request);
 
 
-  Future<Either<View, FlowyError>> send() {
+    Future<Either<View, FlowyError>> send() {
     final request = FFIRequest.create()
     final request = FFIRequest.create()
-      ..event = FolderEvent.CreateView.toString()
-      ..payload = requestToBytes(this.request);
+          ..event = FolderEvent.CreateView.toString()
+          ..payload = requestToBytes(this.request);
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (okBytes) => left(View.fromBuffer(okBytes)),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (okBytes) => left(View.fromBuffer(okBytes)),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
         ));
-  }
+    }
 }
 }
 
 
 class FolderEventReadView {
 class FolderEventReadView {
-  QueryViewRequest request;
-  FolderEventReadView(this.request);
+     QueryViewRequest request;
+     FolderEventReadView(this.request);
 
 
-  Future<Either<View, FlowyError>> send() {
+    Future<Either<View, FlowyError>> send() {
     final request = FFIRequest.create()
     final request = FFIRequest.create()
-      ..event = FolderEvent.ReadView.toString()
-      ..payload = requestToBytes(this.request);
+          ..event = FolderEvent.ReadView.toString()
+          ..payload = requestToBytes(this.request);
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (okBytes) => left(View.fromBuffer(okBytes)),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (okBytes) => left(View.fromBuffer(okBytes)),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
         ));
-  }
+    }
 }
 }
 
 
 class FolderEventUpdateView {
 class FolderEventUpdateView {
-  UpdateViewRequest request;
-  FolderEventUpdateView(this.request);
+     UpdateViewRequest request;
+     FolderEventUpdateView(this.request);
 
 
-  Future<Either<View, FlowyError>> send() {
+    Future<Either<View, FlowyError>> send() {
     final request = FFIRequest.create()
     final request = FFIRequest.create()
-      ..event = FolderEvent.UpdateView.toString()
-      ..payload = requestToBytes(this.request);
+          ..event = FolderEvent.UpdateView.toString()
+          ..payload = requestToBytes(this.request);
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (okBytes) => left(View.fromBuffer(okBytes)),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (okBytes) => left(View.fromBuffer(okBytes)),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
         ));
-  }
+    }
 }
 }
 
 
 class FolderEventDeleteView {
 class FolderEventDeleteView {
-  QueryViewRequest request;
-  FolderEventDeleteView(this.request);
+     QueryViewRequest request;
+     FolderEventDeleteView(this.request);
 
 
-  Future<Either<Unit, FlowyError>> send() {
+    Future<Either<Unit, FlowyError>> send() {
     final request = FFIRequest.create()
     final request = FFIRequest.create()
-      ..event = FolderEvent.DeleteView.toString()
-      ..payload = requestToBytes(this.request);
+          ..event = FolderEvent.DeleteView.toString()
+          ..payload = requestToBytes(this.request);
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (bytes) => left(unit),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (bytes) => left(unit),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
         ));
-  }
+    }
 }
 }
 
 
 class FolderEventDuplicateView {
 class FolderEventDuplicateView {
-  QueryViewRequest request;
-  FolderEventDuplicateView(this.request);
+     QueryViewRequest request;
+     FolderEventDuplicateView(this.request);
 
 
-  Future<Either<Unit, FlowyError>> send() {
+    Future<Either<Unit, FlowyError>> send() {
     final request = FFIRequest.create()
     final request = FFIRequest.create()
-      ..event = FolderEvent.DuplicateView.toString()
-      ..payload = requestToBytes(this.request);
+          ..event = FolderEvent.DuplicateView.toString()
+          ..payload = requestToBytes(this.request);
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (bytes) => left(unit),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (bytes) => left(unit),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
         ));
-  }
+    }
 }
 }
 
 
 class FolderEventCopyLink {
 class FolderEventCopyLink {
-  FolderEventCopyLink();
+    FolderEventCopyLink();
 
 
-  Future<Either<Unit, FlowyError>> send() {
-    final request = FFIRequest.create()..event = FolderEvent.CopyLink.toString();
+    Future<Either<Unit, FlowyError>> send() {
+     final request = FFIRequest.create()
+        ..event = FolderEvent.CopyLink.toString();
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (bytes) => left(unit),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
-        ));
-  }
+     return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
+        (bytes) => left(unit),
+        (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+      ));
+    }
 }
 }
 
 
 class FolderEventOpenDocument {
 class FolderEventOpenDocument {
-  QueryViewRequest request;
-  FolderEventOpenDocument(this.request);
+     QueryViewRequest request;
+     FolderEventOpenDocument(this.request);
 
 
-  Future<Either<DocumentDelta, FlowyError>> send() {
+    Future<Either<DocumentDelta, FlowyError>> send() {
     final request = FFIRequest.create()
     final request = FFIRequest.create()
-      ..event = FolderEvent.OpenDocument.toString()
-      ..payload = requestToBytes(this.request);
+          ..event = FolderEvent.OpenDocument.toString()
+          ..payload = requestToBytes(this.request);
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (okBytes) => left(DocumentDelta.fromBuffer(okBytes)),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (okBytes) => left(DocumentDelta.fromBuffer(okBytes)),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
         ));
-  }
+    }
 }
 }
 
 
 class FolderEventCloseView {
 class FolderEventCloseView {
-  QueryViewRequest request;
-  FolderEventCloseView(this.request);
+     QueryViewRequest request;
+     FolderEventCloseView(this.request);
 
 
-  Future<Either<Unit, FlowyError>> send() {
+    Future<Either<Unit, FlowyError>> send() {
     final request = FFIRequest.create()
     final request = FFIRequest.create()
-      ..event = FolderEvent.CloseView.toString()
-      ..payload = requestToBytes(this.request);
+          ..event = FolderEvent.CloseView.toString()
+          ..payload = requestToBytes(this.request);
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (bytes) => left(unit),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (bytes) => left(unit),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
         ));
-  }
+    }
 }
 }
 
 
 class FolderEventReadTrash {
 class FolderEventReadTrash {
-  FolderEventReadTrash();
+    FolderEventReadTrash();
 
 
-  Future<Either<RepeatedTrash, FlowyError>> send() {
-    final request = FFIRequest.create()..event = FolderEvent.ReadTrash.toString();
+    Future<Either<RepeatedTrash, FlowyError>> send() {
+     final request = FFIRequest.create()
+        ..event = FolderEvent.ReadTrash.toString();
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (okBytes) => left(RepeatedTrash.fromBuffer(okBytes)),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
-        ));
-  }
+     return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
+        (okBytes) => left(RepeatedTrash.fromBuffer(okBytes)),
+        (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+      ));
+    }
 }
 }
 
 
 class FolderEventPutbackTrash {
 class FolderEventPutbackTrash {
-  TrashId request;
-  FolderEventPutbackTrash(this.request);
+     TrashId request;
+     FolderEventPutbackTrash(this.request);
 
 
-  Future<Either<Unit, FlowyError>> send() {
+    Future<Either<Unit, FlowyError>> send() {
     final request = FFIRequest.create()
     final request = FFIRequest.create()
-      ..event = FolderEvent.PutbackTrash.toString()
-      ..payload = requestToBytes(this.request);
+          ..event = FolderEvent.PutbackTrash.toString()
+          ..payload = requestToBytes(this.request);
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (bytes) => left(unit),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (bytes) => left(unit),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
         ));
-  }
+    }
 }
 }
 
 
 class FolderEventDeleteTrash {
 class FolderEventDeleteTrash {
-  RepeatedTrashId request;
-  FolderEventDeleteTrash(this.request);
+     RepeatedTrashId request;
+     FolderEventDeleteTrash(this.request);
 
 
-  Future<Either<Unit, FlowyError>> send() {
+    Future<Either<Unit, FlowyError>> send() {
     final request = FFIRequest.create()
     final request = FFIRequest.create()
-      ..event = FolderEvent.DeleteTrash.toString()
-      ..payload = requestToBytes(this.request);
+          ..event = FolderEvent.DeleteTrash.toString()
+          ..payload = requestToBytes(this.request);
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (bytes) => left(unit),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (bytes) => left(unit),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
         ));
-  }
+    }
 }
 }
 
 
 class FolderEventRestoreAllTrash {
 class FolderEventRestoreAllTrash {
-  FolderEventRestoreAllTrash();
+    FolderEventRestoreAllTrash();
 
 
-  Future<Either<Unit, FlowyError>> send() {
-    final request = FFIRequest.create()..event = FolderEvent.RestoreAllTrash.toString();
+    Future<Either<Unit, FlowyError>> send() {
+     final request = FFIRequest.create()
+        ..event = FolderEvent.RestoreAllTrash.toString();
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (bytes) => left(unit),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
-        ));
-  }
+     return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
+        (bytes) => left(unit),
+        (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+      ));
+    }
 }
 }
 
 
 class FolderEventDeleteAllTrash {
 class FolderEventDeleteAllTrash {
-  FolderEventDeleteAllTrash();
+    FolderEventDeleteAllTrash();
 
 
-  Future<Either<Unit, FlowyError>> send() {
-    final request = FFIRequest.create()..event = FolderEvent.DeleteAllTrash.toString();
+    Future<Either<Unit, FlowyError>> send() {
+     final request = FFIRequest.create()
+        ..event = FolderEvent.DeleteAllTrash.toString();
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (bytes) => left(unit),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
-        ));
-  }
+     return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
+        (bytes) => left(unit),
+        (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+      ));
+    }
 }
 }
 
 
 class FolderEventApplyDocDelta {
 class FolderEventApplyDocDelta {
-  DocumentDelta request;
-  FolderEventApplyDocDelta(this.request);
+     DocumentDelta request;
+     FolderEventApplyDocDelta(this.request);
 
 
-  Future<Either<DocumentDelta, FlowyError>> send() {
-    FFIRequest request = FFIRequest.create()
-      ..event = FolderEvent.ApplyDocDelta.toString()
-      ..payload = requestToBytes(this.request);
+    Future<Either<DocumentDelta, FlowyError>> send() {
+    final request = FFIRequest.create()
+          ..event = FolderEvent.ApplyDocDelta.toString()
+          ..payload = requestToBytes(this.request);
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (okBytes) => left(DocumentDelta.fromBuffer(okBytes)),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (okBytes) => left(DocumentDelta.fromBuffer(okBytes)),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
         ));
-  }
+    }
 }
 }
 
 
 class FolderEventExportDocument {
 class FolderEventExportDocument {
-  ExportRequest request;
-  FolderEventExportDocument(this.request);
+     ExportRequest request;
+     FolderEventExportDocument(this.request);
 
 
-  Future<Either<ExportData, FlowyError>> send() {
+    Future<Either<ExportData, FlowyError>> send() {
     final request = FFIRequest.create()
     final request = FFIRequest.create()
-      ..event = FolderEvent.ExportDocument.toString()
-      ..payload = requestToBytes(this.request);
+          ..event = FolderEvent.ExportDocument.toString()
+          ..payload = requestToBytes(this.request);
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (okBytes) => left(ExportData.fromBuffer(okBytes)),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (okBytes) => left(ExportData.fromBuffer(okBytes)),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
         ));
-  }
+    }
 }
 }
 
 
 class NetworkEventUpdateNetworkType {
 class NetworkEventUpdateNetworkType {
-  NetworkState request;
-  NetworkEventUpdateNetworkType(this.request);
+     NetworkState request;
+     NetworkEventUpdateNetworkType(this.request);
 
 
-  Future<Either<Unit, FlowyError>> send() {
+    Future<Either<Unit, FlowyError>> send() {
     final request = FFIRequest.create()
     final request = FFIRequest.create()
-      ..event = NetworkEvent.UpdateNetworkType.toString()
-      ..payload = requestToBytes(this.request);
+          ..event = NetworkEvent.UpdateNetworkType.toString()
+          ..payload = requestToBytes(this.request);
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (bytes) => left(unit),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (bytes) => left(unit),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
         ));
-  }
+    }
 }
 }
 
 
 class UserEventInitUser {
 class UserEventInitUser {
-  UserEventInitUser();
+    UserEventInitUser();
 
 
-  Future<Either<Unit, FlowyError>> send() {
-    final request = FFIRequest.create()..event = UserEvent.InitUser.toString();
+    Future<Either<Unit, FlowyError>> send() {
+     final request = FFIRequest.create()
+        ..event = UserEvent.InitUser.toString();
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (bytes) => left(unit),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
-        ));
-  }
+     return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
+        (bytes) => left(unit),
+        (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+      ));
+    }
 }
 }
 
 
 class UserEventSignIn {
 class UserEventSignIn {
-  SignInRequest request;
-  UserEventSignIn(this.request);
+     SignInRequest request;
+     UserEventSignIn(this.request);
 
 
-  Future<Either<UserProfile, FlowyError>> send() {
+    Future<Either<UserProfile, FlowyError>> send() {
     final request = FFIRequest.create()
     final request = FFIRequest.create()
-      ..event = UserEvent.SignIn.toString()
-      ..payload = requestToBytes(this.request);
+          ..event = UserEvent.SignIn.toString()
+          ..payload = requestToBytes(this.request);
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (okBytes) => left(UserProfile.fromBuffer(okBytes)),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (okBytes) => left(UserProfile.fromBuffer(okBytes)),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
         ));
-  }
+    }
 }
 }
 
 
 class UserEventSignUp {
 class UserEventSignUp {
-  SignUpRequest request;
-  UserEventSignUp(this.request);
+     SignUpRequest request;
+     UserEventSignUp(this.request);
 
 
-  Future<Either<UserProfile, FlowyError>> send() {
+    Future<Either<UserProfile, FlowyError>> send() {
     final request = FFIRequest.create()
     final request = FFIRequest.create()
-      ..event = UserEvent.SignUp.toString()
-      ..payload = requestToBytes(this.request);
+          ..event = UserEvent.SignUp.toString()
+          ..payload = requestToBytes(this.request);
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (okBytes) => left(UserProfile.fromBuffer(okBytes)),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (okBytes) => left(UserProfile.fromBuffer(okBytes)),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
         ));
-  }
+    }
 }
 }
 
 
 class UserEventSignOut {
 class UserEventSignOut {
-  UserEventSignOut();
+    UserEventSignOut();
 
 
-  Future<Either<Unit, FlowyError>> send() {
-    final request = FFIRequest.create()..event = UserEvent.SignOut.toString();
+    Future<Either<Unit, FlowyError>> send() {
+     final request = FFIRequest.create()
+        ..event = UserEvent.SignOut.toString();
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (bytes) => left(unit),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
-        ));
-  }
+     return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
+        (bytes) => left(unit),
+        (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+      ));
+    }
 }
 }
 
 
 class UserEventUpdateUser {
 class UserEventUpdateUser {
-  UpdateUserRequest request;
-  UserEventUpdateUser(this.request);
+     UpdateUserRequest request;
+     UserEventUpdateUser(this.request);
 
 
-  Future<Either<Unit, FlowyError>> send() {
+    Future<Either<Unit, FlowyError>> send() {
     final request = FFIRequest.create()
     final request = FFIRequest.create()
-      ..event = UserEvent.UpdateUser.toString()
-      ..payload = requestToBytes(this.request);
+          ..event = UserEvent.UpdateUser.toString()
+          ..payload = requestToBytes(this.request);
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (bytes) => left(unit),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (bytes) => left(unit),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
         ));
-  }
+    }
 }
 }
 
 
 class UserEventGetUserProfile {
 class UserEventGetUserProfile {
-  UserEventGetUserProfile();
+    UserEventGetUserProfile();
 
 
-  Future<Either<UserProfile, FlowyError>> send() {
-    final request = FFIRequest.create()..event = UserEvent.GetUserProfile.toString();
+    Future<Either<UserProfile, FlowyError>> send() {
+     final request = FFIRequest.create()
+        ..event = UserEvent.GetUserProfile.toString();
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (okBytes) => left(UserProfile.fromBuffer(okBytes)),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
-        ));
-  }
+     return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
+        (okBytes) => left(UserProfile.fromBuffer(okBytes)),
+        (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+      ));
+    }
 }
 }
 
 
 class UserEventCheckUser {
 class UserEventCheckUser {
-  UserEventCheckUser();
+    UserEventCheckUser();
+
+    Future<Either<UserProfile, FlowyError>> send() {
+     final request = FFIRequest.create()
+        ..event = UserEvent.CheckUser.toString();
+
+     return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
+        (okBytes) => left(UserProfile.fromBuffer(okBytes)),
+        (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+      ));
+    }
+}
+
+class UserEventUpdateAppearanceSetting {
+     AppearanceSettings request;
+     UserEventUpdateAppearanceSetting(this.request);
 
 
-  Future<Either<UserProfile, FlowyError>> send() {
-    final request = FFIRequest.create()..event = UserEvent.CheckUser.toString();
+    Future<Either<Unit, FlowyError>> send() {
+    final request = FFIRequest.create()
+          ..event = UserEvent.UpdateAppearanceSetting.toString()
+          ..payload = requestToBytes(this.request);
 
 
-    return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
-          (okBytes) => left(UserProfile.fromBuffer(okBytes)),
-          (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+    return Dispatch.asyncRequest(request)
+        .then((bytesResult) => bytesResult.fold(
+           (bytes) => left(unit),
+           (errBytes) => right(FlowyError.fromBuffer(errBytes)),
         ));
         ));
-  }
+    }
 }
 }
+
+class UserEventGetAppearanceSetting {
+    UserEventGetAppearanceSetting();
+
+    Future<Either<AppearanceSettings, FlowyError>> send() {
+     final request = FFIRequest.create()
+        ..event = UserEvent.GetAppearanceSetting.toString();
+
+     return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
+        (okBytes) => left(AppearanceSettings.fromBuffer(okBytes)),
+        (errBytes) => right(FlowyError.fromBuffer(errBytes)),
+      ));
+    }
+}
+

+ 1 - 1
frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dispatch.dart

@@ -7,7 +7,7 @@ import 'package:flowy_sdk/protobuf/flowy-collaboration/document_info.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-net/event.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-net/event.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-net/network_state.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-net/network_state.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-user/event.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-user/event_map.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder/event.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder/event.pb.dart';
 import 'package:isolates/isolates.dart';
 import 'package:isolates/isolates.dart';
 import 'package:isolates/ports.dart';
 import 'package:isolates/ports.dart';

+ 1 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user-data-model/protobuf.dart

@@ -2,3 +2,4 @@
 export './errors.pb.dart';
 export './errors.pb.dart';
 export './user_profile.pb.dart';
 export './user_profile.pb.dart';
 export './auth.pb.dart';
 export './auth.pb.dart';
+export './user_setting.pb.dart';

+ 135 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user-data-model/user_setting.pb.dart

@@ -0,0 +1,135 @@
+///
+//  Generated code. Do not modify.
+//  source: user_setting.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 UserPreferences extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'UserPreferences', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'userId')
+    ..aOM<AppearanceSettings>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'appearanceSetting', subBuilder: AppearanceSettings.create)
+    ..hasRequiredFields = false
+  ;
+
+  UserPreferences._() : super();
+  factory UserPreferences({
+    $core.String? userId,
+    AppearanceSettings? appearanceSetting,
+  }) {
+    final _result = create();
+    if (userId != null) {
+      _result.userId = userId;
+    }
+    if (appearanceSetting != null) {
+      _result.appearanceSetting = appearanceSetting;
+    }
+    return _result;
+  }
+  factory UserPreferences.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory UserPreferences.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')
+  UserPreferences clone() => UserPreferences()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  UserPreferences copyWith(void Function(UserPreferences) updates) => super.copyWith((message) => updates(message as UserPreferences)) as UserPreferences; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static UserPreferences create() => UserPreferences._();
+  UserPreferences createEmptyInstance() => create();
+  static $pb.PbList<UserPreferences> createRepeated() => $pb.PbList<UserPreferences>();
+  @$core.pragma('dart2js:noInline')
+  static UserPreferences getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<UserPreferences>(create);
+  static UserPreferences? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get userId => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set userId($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasUserId() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearUserId() => clearField(1);
+
+  @$pb.TagNumber(2)
+  AppearanceSettings get appearanceSetting => $_getN(1);
+  @$pb.TagNumber(2)
+  set appearanceSetting(AppearanceSettings v) { setField(2, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasAppearanceSetting() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearAppearanceSetting() => clearField(2);
+  @$pb.TagNumber(2)
+  AppearanceSettings ensureAppearanceSetting() => $_ensure(1);
+}
+
+class AppearanceSettings extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'AppearanceSettings', createEmptyInstance: create)
+    ..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'theme')
+    ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'language')
+    ..hasRequiredFields = false
+  ;
+
+  AppearanceSettings._() : super();
+  factory AppearanceSettings({
+    $core.String? theme,
+    $core.String? language,
+  }) {
+    final _result = create();
+    if (theme != null) {
+      _result.theme = theme;
+    }
+    if (language != null) {
+      _result.language = language;
+    }
+    return _result;
+  }
+  factory AppearanceSettings.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory AppearanceSettings.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')
+  AppearanceSettings clone() => AppearanceSettings()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  AppearanceSettings copyWith(void Function(AppearanceSettings) updates) => super.copyWith((message) => updates(message as AppearanceSettings)) as AppearanceSettings; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static AppearanceSettings create() => AppearanceSettings._();
+  AppearanceSettings createEmptyInstance() => create();
+  static $pb.PbList<AppearanceSettings> createRepeated() => $pb.PbList<AppearanceSettings>();
+  @$core.pragma('dart2js:noInline')
+  static AppearanceSettings getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<AppearanceSettings>(create);
+  static AppearanceSettings? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.String get theme => $_getSZ(0);
+  @$pb.TagNumber(1)
+  set theme($core.String v) { $_setString(0, v); }
+  @$pb.TagNumber(1)
+  $core.bool hasTheme() => $_has(0);
+  @$pb.TagNumber(1)
+  void clearTheme() => clearField(1);
+
+  @$pb.TagNumber(2)
+  $core.String get language => $_getSZ(1);
+  @$pb.TagNumber(2)
+  set language($core.String v) { $_setString(1, v); }
+  @$pb.TagNumber(2)
+  $core.bool hasLanguage() => $_has(1);
+  @$pb.TagNumber(2)
+  void clearLanguage() => clearField(2);
+}
+

+ 7 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user-data-model/user_setting.pbenum.dart

@@ -0,0 +1,7 @@
+///
+//  Generated code. Do not modify.
+//  source: user_setting.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
+

+ 32 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user-data-model/user_setting.pbjson.dart

@@ -0,0 +1,32 @@
+///
+//  Generated code. Do not modify.
+//  source: user_setting.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 userPreferencesDescriptor instead')
+const UserPreferences$json = const {
+  '1': 'UserPreferences',
+  '2': const [
+    const {'1': 'user_id', '3': 1, '4': 1, '5': 9, '10': 'userId'},
+    const {'1': 'appearance_setting', '3': 2, '4': 1, '5': 11, '6': '.AppearanceSettings', '10': 'appearanceSetting'},
+  ],
+};
+
+/// Descriptor for `UserPreferences`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List userPreferencesDescriptor = $convert.base64Decode('Cg9Vc2VyUHJlZmVyZW5jZXMSFwoHdXNlcl9pZBgBIAEoCVIGdXNlcklkEkIKEmFwcGVhcmFuY2Vfc2V0dGluZxgCIAEoCzITLkFwcGVhcmFuY2VTZXR0aW5nc1IRYXBwZWFyYW5jZVNldHRpbmc=');
+@$core.Deprecated('Use appearanceSettingsDescriptor instead')
+const AppearanceSettings$json = const {
+  '1': 'AppearanceSettings',
+  '2': const [
+    const {'1': 'theme', '3': 1, '4': 1, '5': 9, '10': 'theme'},
+    const {'1': 'language', '3': 2, '4': 1, '5': 9, '10': 'language'},
+  ],
+};
+
+/// Descriptor for `AppearanceSettings`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List appearanceSettingsDescriptor = $convert.base64Decode('ChJBcHBlYXJhbmNlU2V0dGluZ3MSFAoFdGhlbWUYASABKAlSBXRoZW1lEhoKCGxhbmd1YWdlGAIgASgJUghsYW5ndWFnZQ==');

+ 9 - 0
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user-data-model/user_setting.pbserver.dart

@@ -0,0 +1,9 @@
+///
+//  Generated code. Do not modify.
+//  source: user_setting.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 'user_setting.pb.dart';
+

+ 2 - 2
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/event.pb.dart → frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/event_map.pb.dart

@@ -1,11 +1,11 @@
 ///
 ///
 //  Generated code. Do not modify.
 //  Generated code. Do not modify.
-//  source: event.proto
+//  source: event_map.proto
 //
 //
 // @dart = 2.12
 // @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
 // 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 'dart:core' as $core;
 
 
-export 'event.pbenum.dart';
+export 'event_map.pbenum.dart';
 
 

+ 5 - 1
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/event.pbenum.dart → frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/event_map.pbenum.dart

@@ -1,6 +1,6 @@
 ///
 ///
 //  Generated code. Do not modify.
 //  Generated code. Do not modify.
-//  source: event.proto
+//  source: event_map.proto
 //
 //
 // @dart = 2.12
 // @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
 // 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
@@ -17,6 +17,8 @@ class UserEvent extends $pb.ProtobufEnum {
   static const UserEvent UpdateUser = UserEvent._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateUser');
   static const UserEvent UpdateUser = UserEvent._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateUser');
   static const UserEvent GetUserProfile = UserEvent._(5, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetUserProfile');
   static const UserEvent GetUserProfile = UserEvent._(5, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetUserProfile');
   static const UserEvent CheckUser = UserEvent._(6, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CheckUser');
   static const UserEvent CheckUser = UserEvent._(6, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CheckUser');
+  static const UserEvent UpdateAppearanceSetting = UserEvent._(7, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UpdateAppearanceSetting');
+  static const UserEvent GetAppearanceSetting = UserEvent._(8, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetAppearanceSetting');
 
 
   static const $core.List<UserEvent> values = <UserEvent> [
   static const $core.List<UserEvent> values = <UserEvent> [
     InitUser,
     InitUser,
@@ -26,6 +28,8 @@ class UserEvent extends $pb.ProtobufEnum {
     UpdateUser,
     UpdateUser,
     GetUserProfile,
     GetUserProfile,
     CheckUser,
     CheckUser,
+    UpdateAppearanceSetting,
+    GetAppearanceSetting,
   ];
   ];
 
 
   static final $core.Map<$core.int, UserEvent> _byValue = $pb.ProtobufEnum.initByValue(values);
   static final $core.Map<$core.int, UserEvent> _byValue = $pb.ProtobufEnum.initByValue(values);

+ 4 - 2
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/event.pbjson.dart → frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/event_map.pbjson.dart

@@ -1,6 +1,6 @@
 ///
 ///
 //  Generated code. Do not modify.
 //  Generated code. Do not modify.
-//  source: event.proto
+//  source: event_map.proto
 //
 //
 // @dart = 2.12
 // @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
 // 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
@@ -19,8 +19,10 @@ const UserEvent$json = const {
     const {'1': 'UpdateUser', '2': 4},
     const {'1': 'UpdateUser', '2': 4},
     const {'1': 'GetUserProfile', '2': 5},
     const {'1': 'GetUserProfile', '2': 5},
     const {'1': 'CheckUser', '2': 6},
     const {'1': 'CheckUser', '2': 6},
+    const {'1': 'UpdateAppearanceSetting', '2': 7},
+    const {'1': 'GetAppearanceSetting', '2': 8},
   ],
   ],
 };
 };
 
 
 /// Descriptor for `UserEvent`. Decode as a `google.protobuf.EnumDescriptorProto`.
 /// Descriptor for `UserEvent`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List userEventDescriptor = $convert.base64Decode('CglVc2VyRXZlbnQSDAoISW5pdFVzZXIQABIKCgZTaWduSW4QARIKCgZTaWduVXAQAhILCgdTaWduT3V0EAMSDgoKVXBkYXRlVXNlchAEEhIKDkdldFVzZXJQcm9maWxlEAUSDQoJQ2hlY2tVc2VyEAY=');
+final $typed_data.Uint8List userEventDescriptor = $convert.base64Decode('CglVc2VyRXZlbnQSDAoISW5pdFVzZXIQABIKCgZTaWduSW4QARIKCgZTaWduVXAQAhILCgdTaWduT3V0EAMSDgoKVXBkYXRlVXNlchAEEhIKDkdldFVzZXJQcm9maWxlEAUSDQoJQ2hlY2tVc2VyEAYSGwoXVXBkYXRlQXBwZWFyYW5jZVNldHRpbmcQBxIYChRHZXRBcHBlYXJhbmNlU2V0dGluZxAI');

+ 2 - 2
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/event.pbserver.dart → frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/event_map.pbserver.dart

@@ -1,9 +1,9 @@
 ///
 ///
 //  Generated code. Do not modify.
 //  Generated code. Do not modify.
-//  source: event.proto
+//  source: event_map.proto
 //
 //
 // @dart = 2.12
 // @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
 // 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 'event.pb.dart';
+export 'event_map.pb.dart';
 
 

+ 1 - 1
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/protobuf.dart

@@ -1,3 +1,3 @@
 // Auto-generated, do not edit 
 // Auto-generated, do not edit 
 export './dart_notification.pb.dart';
 export './dart_notification.pb.dart';
-export './event.pb.dart';
+export './event_map.pb.dart';

+ 53 - 53
frontend/rust-lib/flowy-database/src/kv/kv.rs

@@ -5,6 +5,36 @@ use lazy_static::lazy_static;
 use lib_sqlite::{DBConnection, Database, PoolConfig};
 use lib_sqlite::{DBConnection, Database, PoolConfig};
 use std::{collections::HashMap, path::Path, sync::RwLock};
 use std::{collections::HashMap, path::Path, sync::RwLock};
 
 
+macro_rules! impl_get_func {
+    (
+        $func_name:ident,
+        $get_method:ident=>$target:ident
+    ) => {
+        #[allow(dead_code)]
+        pub fn $func_name(k: &str) -> Option<$target> {
+            match KV::get(k) {
+                Ok(item) => item.$get_method,
+                Err(_) => None,
+            }
+        }
+    };
+}
+
+macro_rules! impl_set_func {
+    ($func_name:ident,$set_method:ident,$key_type:ident) => {
+        #[allow(dead_code)]
+        pub fn $func_name(key: &str, value: $key_type) {
+            let mut item = KeyValue::new(key);
+            item.$set_method = Some(value);
+            match KV::set(item) {
+                Ok(_) => {}
+                Err(e) => {
+                    log::error!("{:?}", e)
+                }
+            };
+        }
+    };
+}
 const DB_NAME: &str = "kv.db";
 const DB_NAME: &str = "kv.db";
 lazy_static! {
 lazy_static! {
     static ref KV_HOLDER: RwLock<KV> = RwLock::new(KV::new());
     static ref KV_HOLDER: RwLock<KV> = RwLock::new(KV::new());
@@ -86,6 +116,27 @@ impl KV {
 
 
         Ok(())
         Ok(())
     }
     }
+
+    pub fn get_bool(key: &str) -> bool {
+        match KV::get(key) {
+            Ok(item) => item.bool_value.unwrap_or(false),
+            Err(_) => false,
+        }
+    }
+
+    impl_set_func!(set_str, str_value, String);
+
+    impl_set_func!(set_bool, bool_value, bool);
+
+    impl_set_func!(set_int, int_value, i64);
+
+    impl_set_func!(set_float, float_value, f64);
+
+    impl_get_func!(get_str,str_value=>String);
+
+    impl_get_func!(get_int,int_value=>i64);
+
+    impl_get_func!(get_float,float_value=>f64);
 }
 }
 
 
 fn read_cache(key: &str) -> Option<KeyValue> {
 fn read_cache(key: &str) -> Option<KeyValue> {
@@ -107,57 +158,6 @@ fn update_cache(value: KeyValue) {
     };
     };
 }
 }
 
 
-macro_rules! impl_get_func {
-    (
-        $func_name:ident,
-        $get_method:ident=>$target:ident
-    ) => {
-        impl KV {
-            #[allow(dead_code)]
-            pub fn $func_name(k: &str) -> Option<$target> {
-                match KV::get(k) {
-                    Ok(item) => item.$get_method,
-                    Err(_) => None,
-                }
-            }
-        }
-    };
-}
-
-macro_rules! impl_set_func {
-    ($func_name:ident,$set_method:ident,$key_type:ident) => {
-        impl KV {
-            #[allow(dead_code)]
-            pub fn $func_name(key: &str, value: $key_type) {
-                let mut item = KeyValue::new(key);
-                item.$set_method = Some(value);
-                match KV::set(item) {
-                    Ok(_) => {}
-                    Err(e) => {
-                        log::error!("{:?}", e)
-                    }
-                };
-            }
-        }
-    };
-}
-
-impl_set_func!(set_str, str_value, String);
-
-impl_set_func!(set_bool, bool_value, bool);
-
-impl_set_func!(set_int, int_value, i64);
-
-impl_set_func!(set_float, float_value, f64);
-
-impl_get_func!(get_str,str_value=>String);
-
-impl_get_func!(get_int,int_value=>i64);
-
-impl_get_func!(get_float,float_value=>f64);
-
-impl_get_func!(get_bool,bool_value=>bool);
-
 fn get_connection() -> Result<DBConnection, String> {
 fn get_connection() -> Result<DBConnection, String> {
     match KV_HOLDER.read() {
     match KV_HOLDER.read() {
         Ok(store) => {
         Ok(store) => {
@@ -216,8 +216,8 @@ mod tests {
         assert_eq!(KV::get_str("2"), None);
         assert_eq!(KV::get_str("2"), None);
 
 
         KV::set_bool("1", true);
         KV::set_bool("1", true);
-        assert!(KV::get_bool("1").unwrap());
+        assert!(KV::get_bool("1"));
 
 
-        assert_eq!(KV::get_bool("2"), None);
+        assert!(!KV::get_bool("2"));
     }
     }
 }
 }

+ 1 - 1
frontend/rust-lib/flowy-folder/src/services/persistence/migration.rs

@@ -29,7 +29,7 @@ impl FolderMigration {
 
 
     pub fn run_v1_migration(&self) -> FlowyResult<Option<FolderPad>> {
     pub fn run_v1_migration(&self) -> FlowyResult<Option<FolderPad>> {
         let key = md5(format!("{}{}", self.user_id, V1_MIGRATION));
         let key = md5(format!("{}{}", self.user_id, V1_MIGRATION));
-        if KV::get_bool(&key).unwrap_or(false) {
+        if KV::get_bool(&key) {
             return Ok(None);
             return Ok(None);
         }
         }
         tracing::trace!("Run folder version 1 migrations");
         tracing::trace!("Run folder version 1 migrations");

+ 3 - 3
frontend/rust-lib/flowy-folder/src/services/workspace/event_handler.rs

@@ -107,12 +107,12 @@ fn read_workspaces_on_server(
         let workspaces = server.read_workspace(&token, params).await?;
         let workspaces = server.read_workspace(&token, params).await?;
         let _ = persistence
         let _ = persistence
             .begin_transaction(|transaction| {
             .begin_transaction(|transaction| {
-                tracing::debug!("Save {} workspace", workspaces.len());
+                tracing::trace!("Save {} workspace", workspaces.len());
                 for workspace in &workspaces.items {
                 for workspace in &workspaces.items {
                     let m_workspace = workspace.clone();
                     let m_workspace = workspace.clone();
                     let apps = m_workspace.apps.clone().into_inner();
                     let apps = m_workspace.apps.clone().into_inner();
                     let _ = transaction.create_workspace(&user_id, m_workspace)?;
                     let _ = transaction.create_workspace(&user_id, m_workspace)?;
-                    tracing::debug!("Save {} apps", apps.len());
+                    tracing::trace!("Save {} apps", apps.len());
                     for app in apps {
                     for app in apps {
                         let views = app.belongings.clone().into_inner();
                         let views = app.belongings.clone().into_inner();
                         match transaction.create_app(app) {
                         match transaction.create_app(app) {
@@ -120,7 +120,7 @@ fn read_workspaces_on_server(
                             Err(e) => log::error!("create app failed: {:?}", e),
                             Err(e) => log::error!("create app failed: {:?}", e),
                         }
                         }
 
 
-                        tracing::debug!("Save {} views", views.len());
+                        tracing::trace!("Save {} views", views.len());
                         for view in views {
                         for view in views {
                             match transaction.create_view(view) {
                             match transaction.create_view(view) {
                                 Ok(_) => {}
                                 Ok(_) => {}

+ 1 - 1
frontend/rust-lib/flowy-net/src/http_server/user.rs

@@ -1,6 +1,6 @@
 use backend_service::{configuration::*, errors::ServerError, request::HttpRequestBuilder};
 use backend_service::{configuration::*, errors::ServerError, request::HttpRequestBuilder};
 use flowy_error::FlowyError;
 use flowy_error::FlowyError;
-use flowy_user::module::UserCloudService;
+use flowy_user::event_map::UserCloudService;
 use flowy_user_data_model::entities::{
 use flowy_user_data_model::entities::{
     SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserParams, UserProfile,
     SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserParams, UserProfile,
 };
 };

+ 1 - 1
frontend/rust-lib/flowy-net/src/local_server/server.rs

@@ -255,7 +255,7 @@ use flowy_folder_data_model::entities::{
     view::{CreateViewParams, RepeatedView, RepeatedViewId, UpdateViewParams, View, ViewId},
     view::{CreateViewParams, RepeatedView, RepeatedViewId, UpdateViewParams, View, ViewId},
     workspace::{CreateWorkspaceParams, RepeatedWorkspace, UpdateWorkspaceParams, Workspace, WorkspaceId},
     workspace::{CreateWorkspaceParams, RepeatedWorkspace, UpdateWorkspaceParams, Workspace, WorkspaceId},
 };
 };
-use flowy_user::module::UserCloudService;
+use flowy_user::event_map::UserCloudService;
 use flowy_user_data_model::entities::{
 use flowy_user_data_model::entities::{
     SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserParams, UserProfile,
     SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserParams, UserProfile,
 };
 };

+ 1 - 1
frontend/rust-lib/flowy-sdk/src/deps_resolve/user_deps.rs

@@ -1,6 +1,6 @@
 use backend_service::configuration::ClientServerConfiguration;
 use backend_service::configuration::ClientServerConfiguration;
 use flowy_net::{http_server::user::UserHttpCloudService, local_server::LocalServer};
 use flowy_net::{http_server::user::UserHttpCloudService, local_server::LocalServer};
-use flowy_user::module::UserCloudService;
+use flowy_user::event_map::UserCloudService;
 
 
 use std::sync::Arc;
 use std::sync::Arc;
 
 

+ 1 - 1
frontend/rust-lib/flowy-sdk/src/module.rs

@@ -16,7 +16,7 @@ pub fn mk_modules(
 }
 }
 
 
 fn mk_user_module(user_session: Arc<UserSession>) -> Module {
 fn mk_user_module(user_session: Arc<UserSession>) -> Module {
-    flowy_user::module::create(user_session)
+    flowy_user::event_map::create(user_session)
 }
 }
 
 
 fn mk_folder_module(core: Arc<FolderManager>) -> Module {
 fn mk_folder_module(core: Arc<FolderManager>) -> Module {

+ 1 - 2
frontend/rust-lib/flowy-test/src/helper.rs

@@ -1,5 +1,4 @@
 use crate::prelude::*;
 use crate::prelude::*;
-
 use flowy_folder::{
 use flowy_folder::{
     entities::{
     entities::{
         app::*,
         app::*,
@@ -11,7 +10,7 @@ use flowy_folder::{
 use flowy_user::{
 use flowy_user::{
     entities::{SignInRequest, SignUpRequest, UserProfile},
     entities::{SignInRequest, SignUpRequest, UserProfile},
     errors::FlowyError,
     errors::FlowyError,
-    event::UserEvent::{InitUser, SignIn, SignOut, SignUp},
+    event_map::UserEvent::{InitUser, SignIn, SignOut, SignUp},
 };
 };
 use lib_dispatch::prelude::{EventDispatcher, ModuleRequest, ToBytes};
 use lib_dispatch::prelude::{EventDispatcher, ModuleRequest, ToBytes};
 use lib_infra::uuid_string;
 use lib_infra::uuid_string;

+ 2 - 2
frontend/rust-lib/flowy-user/Flowy.toml

@@ -1,3 +1,3 @@
 
 
-proto_crates = ["src/event.rs", "src/dart_notification.rs"]
-event_files = ["src/event.rs"]
+proto_crates = ["src/event_map.rs", "src/dart_notification.rs"]
+event_files = ["src/event_map.rs"]

+ 37 - 1
frontend/rust-lib/flowy-user/src/module.rs → frontend/rust-lib/flowy-user/src/event_map.rs

@@ -1,4 +1,4 @@
-use crate::{errors::FlowyError, event::UserEvent, handlers::*, services::UserSession};
+use crate::{errors::FlowyError, handlers::*, services::UserSession};
 use flowy_user_data_model::entities::{
 use flowy_user_data_model::entities::{
     SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserParams, UserProfile,
     SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserParams, UserProfile,
 };
 };
@@ -17,6 +17,8 @@ pub fn create(user_session: Arc<UserSession>) -> Module {
         .event(UserEvent::SignOut, sign_out)
         .event(UserEvent::SignOut, sign_out)
         .event(UserEvent::UpdateUser, update_user_handler)
         .event(UserEvent::UpdateUser, update_user_handler)
         .event(UserEvent::CheckUser, check_user_handler)
         .event(UserEvent::CheckUser, check_user_handler)
+        .event(UserEvent::UpdateAppearanceSetting, update_appearance_setting)
+        .event(UserEvent::GetAppearanceSetting, get_appearance_setting)
 }
 }
 
 
 pub trait UserCloudService: Send + Sync {
 pub trait UserCloudService: Send + Sync {
@@ -27,3 +29,37 @@ pub trait UserCloudService: Send + Sync {
     fn get_user(&self, token: &str) -> FutureResult<UserProfile, FlowyError>;
     fn get_user(&self, token: &str) -> FutureResult<UserProfile, FlowyError>;
     fn ws_addr(&self) -> String;
     fn ws_addr(&self) -> String;
 }
 }
+
+use flowy_derive::{Flowy_Event, ProtoBuf_Enum};
+use strum_macros::Display;
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)]
+#[event_err = "FlowyError"]
+pub enum UserEvent {
+    #[event()]
+    InitUser = 0,
+
+    #[event(input = "SignInRequest", output = "UserProfile")]
+    SignIn = 1,
+
+    #[event(input = "SignUpRequest", output = "UserProfile")]
+    SignUp = 2,
+
+    #[event(passthrough)]
+    SignOut = 3,
+
+    #[event(input = "UpdateUserRequest")]
+    UpdateUser = 4,
+
+    #[event(output = "UserProfile")]
+    GetUserProfile = 5,
+
+    #[event(output = "UserProfile")]
+    CheckUser = 6,
+
+    #[event(input = "AppearanceSettings")]
+    UpdateAppearanceSetting = 7,
+
+    #[event(output = "AppearanceSettings")]
+    GetAppearanceSetting = 8,
+}

+ 34 - 1
frontend/rust-lib/flowy-user/src/handlers/user_handler.rs

@@ -1,5 +1,9 @@
 use crate::{errors::FlowyError, services::UserSession};
 use crate::{errors::FlowyError, services::UserSession};
-use flowy_user_data_model::entities::*;
+use flowy_database::kv::KV;
+use flowy_user_data_model::entities::{
+    AppearanceSettings, UpdateUserParams, UpdateUserRequest, UserProfile, APPEARANCE_DEFAULT_LANGUAGE,
+    APPEARANCE_DEFAULT_THEME,
+};
 use lib_dispatch::prelude::*;
 use lib_dispatch::prelude::*;
 use std::{convert::TryInto, sync::Arc};
 use std::{convert::TryInto, sync::Arc};
 
 
@@ -36,3 +40,32 @@ pub async fn update_user_handler(
     session.update_user(params).await?;
     session.update_user(params).await?;
     Ok(())
     Ok(())
 }
 }
+
+const APPEARANCE_SETTING_CACHE_KEY: &str = "appearance_settings";
+
+#[tracing::instrument(skip(data), err)]
+pub async fn update_appearance_setting(data: Data<AppearanceSettings>) -> Result<(), FlowyError> {
+    let mut setting = data.into_inner();
+    if setting.theme.is_empty() {
+        setting.theme = APPEARANCE_DEFAULT_THEME.to_string();
+    }
+
+    if setting.language.is_empty() {
+        setting.theme = APPEARANCE_DEFAULT_LANGUAGE.to_string();
+    }
+
+    let s = serde_json::to_string(&setting)?;
+    KV::set_str(APPEARANCE_SETTING_CACHE_KEY, s);
+    Ok(())
+}
+
+#[tracing::instrument(err)]
+pub async fn get_appearance_setting() -> DataResult<AppearanceSettings, FlowyError> {
+    match KV::get_str(APPEARANCE_SETTING_CACHE_KEY) {
+        None => data_result(AppearanceSettings::default()),
+        Some(s) => {
+            let setting: AppearanceSettings = serde_json::from_str(&s)?;
+            data_result(setting)
+        }
+    }
+}

+ 1 - 2
frontend/rust-lib/flowy-user/src/lib.rs

@@ -1,7 +1,6 @@
 mod dart_notification;
 mod dart_notification;
-pub mod event;
+pub mod event_map;
 mod handlers;
 mod handlers;
-pub mod module;
 pub mod protobuf;
 pub mod protobuf;
 pub mod services;
 pub mod services;
 // mod sql_tables;
 // mod sql_tables;

+ 32 - 21
frontend/rust-lib/flowy-user/src/protobuf/model/event.rs → frontend/rust-lib/flowy-user/src/protobuf/model/event_map.rs

@@ -17,7 +17,7 @@
 #![allow(trivial_casts)]
 #![allow(trivial_casts)]
 #![allow(unused_imports)]
 #![allow(unused_imports)]
 #![allow(unused_results)]
 #![allow(unused_results)]
-//! Generated file from `event.proto`
+//! Generated file from `event_map.proto`
 
 
 /// Generated files are compatible only with the same version
 /// Generated files are compatible only with the same version
 /// of protobuf runtime.
 /// of protobuf runtime.
@@ -32,6 +32,8 @@ pub enum UserEvent {
     UpdateUser = 4,
     UpdateUser = 4,
     GetUserProfile = 5,
     GetUserProfile = 5,
     CheckUser = 6,
     CheckUser = 6,
+    UpdateAppearanceSetting = 7,
+    GetAppearanceSetting = 8,
 }
 }
 
 
 impl ::protobuf::ProtobufEnum for UserEvent {
 impl ::protobuf::ProtobufEnum for UserEvent {
@@ -48,6 +50,8 @@ impl ::protobuf::ProtobufEnum for UserEvent {
             4 => ::std::option::Option::Some(UserEvent::UpdateUser),
             4 => ::std::option::Option::Some(UserEvent::UpdateUser),
             5 => ::std::option::Option::Some(UserEvent::GetUserProfile),
             5 => ::std::option::Option::Some(UserEvent::GetUserProfile),
             6 => ::std::option::Option::Some(UserEvent::CheckUser),
             6 => ::std::option::Option::Some(UserEvent::CheckUser),
+            7 => ::std::option::Option::Some(UserEvent::UpdateAppearanceSetting),
+            8 => ::std::option::Option::Some(UserEvent::GetAppearanceSetting),
             _ => ::std::option::Option::None
             _ => ::std::option::Option::None
         }
         }
     }
     }
@@ -61,6 +65,8 @@ impl ::protobuf::ProtobufEnum for UserEvent {
             UserEvent::UpdateUser,
             UserEvent::UpdateUser,
             UserEvent::GetUserProfile,
             UserEvent::GetUserProfile,
             UserEvent::CheckUser,
             UserEvent::CheckUser,
+            UserEvent::UpdateAppearanceSetting,
+            UserEvent::GetAppearanceSetting,
         ];
         ];
         values
         values
     }
     }
@@ -89,26 +95,31 @@ impl ::protobuf::reflect::ProtobufValue for UserEvent {
 }
 }
 
 
 static file_descriptor_proto_data: &'static [u8] = b"\
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x0bevent.proto*q\n\tUserEvent\x12\x0c\n\x08InitUser\x10\0\x12\n\n\x06\
-    SignIn\x10\x01\x12\n\n\x06SignUp\x10\x02\x12\x0b\n\x07SignOut\x10\x03\
-    \x12\x0e\n\nUpdateUser\x10\x04\x12\x12\n\x0eGetUserProfile\x10\x05\x12\r\
-    \n\tCheckUser\x10\x06J\xc9\x02\n\x06\x12\x04\0\0\n\x01\n\x08\n\x01\x0c\
-    \x12\x03\0\0\x12\n\n\n\x02\x05\0\x12\x04\x02\0\n\x01\n\n\n\x03\x05\0\x01\
-    \x12\x03\x02\x05\x0e\n\x0b\n\x04\x05\0\x02\0\x12\x03\x03\x04\x11\n\x0c\n\
-    \x05\x05\0\x02\0\x01\x12\x03\x03\x04\x0c\n\x0c\n\x05\x05\0\x02\0\x02\x12\
-    \x03\x03\x0f\x10\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x04\x04\x0f\n\x0c\n\
-    \x05\x05\0\x02\x01\x01\x12\x03\x04\x04\n\n\x0c\n\x05\x05\0\x02\x01\x02\
-    \x12\x03\x04\r\x0e\n\x0b\n\x04\x05\0\x02\x02\x12\x03\x05\x04\x0f\n\x0c\n\
-    \x05\x05\0\x02\x02\x01\x12\x03\x05\x04\n\n\x0c\n\x05\x05\0\x02\x02\x02\
-    \x12\x03\x05\r\x0e\n\x0b\n\x04\x05\0\x02\x03\x12\x03\x06\x04\x10\n\x0c\n\
-    \x05\x05\0\x02\x03\x01\x12\x03\x06\x04\x0b\n\x0c\n\x05\x05\0\x02\x03\x02\
-    \x12\x03\x06\x0e\x0f\n\x0b\n\x04\x05\0\x02\x04\x12\x03\x07\x04\x13\n\x0c\
-    \n\x05\x05\0\x02\x04\x01\x12\x03\x07\x04\x0e\n\x0c\n\x05\x05\0\x02\x04\
-    \x02\x12\x03\x07\x11\x12\n\x0b\n\x04\x05\0\x02\x05\x12\x03\x08\x04\x17\n\
-    \x0c\n\x05\x05\0\x02\x05\x01\x12\x03\x08\x04\x12\n\x0c\n\x05\x05\0\x02\
-    \x05\x02\x12\x03\x08\x15\x16\n\x0b\n\x04\x05\0\x02\x06\x12\x03\t\x04\x12\
-    \n\x0c\n\x05\x05\0\x02\x06\x01\x12\x03\t\x04\r\n\x0c\n\x05\x05\0\x02\x06\
-    \x02\x12\x03\t\x10\x11b\x06proto3\
+    \n\x0fevent_map.proto*\xa8\x01\n\tUserEvent\x12\x0c\n\x08InitUser\x10\0\
+    \x12\n\n\x06SignIn\x10\x01\x12\n\n\x06SignUp\x10\x02\x12\x0b\n\x07SignOu\
+    t\x10\x03\x12\x0e\n\nUpdateUser\x10\x04\x12\x12\n\x0eGetUserProfile\x10\
+    \x05\x12\r\n\tCheckUser\x10\x06\x12\x1b\n\x17UpdateAppearanceSetting\x10\
+    \x07\x12\x18\n\x14GetAppearanceSetting\x10\x08J\x9b\x03\n\x06\x12\x04\0\
+    \0\x0c\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x05\0\x12\x04\x02\0\
+    \x0c\x01\n\n\n\x03\x05\0\x01\x12\x03\x02\x05\x0e\n\x0b\n\x04\x05\0\x02\0\
+    \x12\x03\x03\x04\x11\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x03\x04\x0c\n\
+    \x0c\n\x05\x05\0\x02\0\x02\x12\x03\x03\x0f\x10\n\x0b\n\x04\x05\0\x02\x01\
+    \x12\x03\x04\x04\x0f\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x04\x04\n\n\
+    \x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x04\r\x0e\n\x0b\n\x04\x05\0\x02\x02\
+    \x12\x03\x05\x04\x0f\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\x05\x04\n\n\
+    \x0c\n\x05\x05\0\x02\x02\x02\x12\x03\x05\r\x0e\n\x0b\n\x04\x05\0\x02\x03\
+    \x12\x03\x06\x04\x10\n\x0c\n\x05\x05\0\x02\x03\x01\x12\x03\x06\x04\x0b\n\
+    \x0c\n\x05\x05\0\x02\x03\x02\x12\x03\x06\x0e\x0f\n\x0b\n\x04\x05\0\x02\
+    \x04\x12\x03\x07\x04\x13\n\x0c\n\x05\x05\0\x02\x04\x01\x12\x03\x07\x04\
+    \x0e\n\x0c\n\x05\x05\0\x02\x04\x02\x12\x03\x07\x11\x12\n\x0b\n\x04\x05\0\
+    \x02\x05\x12\x03\x08\x04\x17\n\x0c\n\x05\x05\0\x02\x05\x01\x12\x03\x08\
+    \x04\x12\n\x0c\n\x05\x05\0\x02\x05\x02\x12\x03\x08\x15\x16\n\x0b\n\x04\
+    \x05\0\x02\x06\x12\x03\t\x04\x12\n\x0c\n\x05\x05\0\x02\x06\x01\x12\x03\t\
+    \x04\r\n\x0c\n\x05\x05\0\x02\x06\x02\x12\x03\t\x10\x11\n\x0b\n\x04\x05\0\
+    \x02\x07\x12\x03\n\x04\x20\n\x0c\n\x05\x05\0\x02\x07\x01\x12\x03\n\x04\
+    \x1b\n\x0c\n\x05\x05\0\x02\x07\x02\x12\x03\n\x1e\x1f\n\x0b\n\x04\x05\0\
+    \x02\x08\x12\x03\x0b\x04\x1d\n\x0c\n\x05\x05\0\x02\x08\x01\x12\x03\x0b\
+    \x04\x18\n\x0c\n\x05\x05\0\x02\x08\x02\x12\x03\x0b\x1b\x1cb\x06proto3\
 ";
 ";
 
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 2 - 2
frontend/rust-lib/flowy-user/src/protobuf/model/mod.rs

@@ -4,5 +4,5 @@
 mod dart_notification;
 mod dart_notification;
 pub use dart_notification::*;
 pub use dart_notification::*;
 
 
-mod event;
-pub use event::*;
+mod event_map;
+pub use event_map::*;

+ 2 - 0
frontend/rust-lib/flowy-user/src/protobuf/proto/event.proto → frontend/rust-lib/flowy-user/src/protobuf/proto/event_map.proto

@@ -8,4 +8,6 @@ enum UserEvent {
     UpdateUser = 4;
     UpdateUser = 4;
     GetUserProfile = 5;
     GetUserProfile = 5;
     CheckUser = 6;
     CheckUser = 6;
+    UpdateAppearanceSetting = 7;
+    GetAppearanceSetting = 8;
 }
 }

+ 1 - 1
frontend/rust-lib/flowy-user/src/services/user_session.rs

@@ -1,7 +1,7 @@
 use crate::{
 use crate::{
     dart_notification::*,
     dart_notification::*,
     errors::{ErrorCode, FlowyError},
     errors::{ErrorCode, FlowyError},
-    module::UserCloudService,
+    event_map::UserCloudService,
     services::{
     services::{
         database::{UserDB, UserTable, UserTableChangeset},
         database::{UserDB, UserTable, UserTableChangeset},
         notifier::UserNotifier,
         notifier::UserNotifier,

+ 1 - 1
frontend/rust-lib/flowy-user/tests/event/auth_test.rs

@@ -1,6 +1,6 @@
 use crate::helper::*;
 use crate::helper::*;
 use flowy_test::{event_builder::UserModuleEventBuilder, FlowySDKTest};
 use flowy_test::{event_builder::UserModuleEventBuilder, FlowySDKTest};
-use flowy_user::{errors::ErrorCode, event::UserEvent::*};
+use flowy_user::{errors::ErrorCode, event_map::UserEvent::*};
 use flowy_user_data_model::entities::{SignInRequest, SignUpRequest, UserProfile};
 use flowy_user_data_model::entities::{SignInRequest, SignUpRequest, UserProfile};
 
 
 #[tokio::test]
 #[tokio::test]

+ 1 - 1
frontend/rust-lib/flowy-user/tests/event/user_profile_test.rs

@@ -1,6 +1,6 @@
 use crate::helper::*;
 use crate::helper::*;
 use flowy_test::{event_builder::UserModuleEventBuilder, FlowySDKTest};
 use flowy_test::{event_builder::UserModuleEventBuilder, FlowySDKTest};
-use flowy_user::{errors::ErrorCode, event::UserEvent::*};
+use flowy_user::{errors::ErrorCode, event_map::UserEvent::*};
 use flowy_user_data_model::entities::{UpdateUserRequest, UserProfile};
 use flowy_user_data_model::entities::{UpdateUserRequest, UserProfile};
 use lib_infra::uuid_string;
 use lib_infra::uuid_string;
 use serial_test::*;
 use serial_test::*;

+ 1 - 0
shared-lib/Cargo.lock

@@ -705,6 +705,7 @@ dependencies = [
  "protobuf",
  "protobuf",
  "quickcheck",
  "quickcheck",
  "quickcheck_macros",
  "quickcheck_macros",
+ "serde",
  "serial_test",
  "serial_test",
  "unicode-segmentation",
  "unicode-segmentation",
  "validator",
  "validator",

+ 6 - 0
shared-lib/flowy-collaboration/src/client_folder/folder_pad.rs

@@ -80,6 +80,7 @@ impl FolderPad {
         self.workspaces.is_empty() && self.trash.is_empty()
         self.workspaces.is_empty() && self.trash.is_empty()
     }
     }
 
 
+    #[tracing::instrument(level = "trace", skip(self, workspace), fields(workspace_name=%workspace.name), err)]
     pub fn create_workspace(&mut self, workspace: Workspace) -> CollaborateResult<Option<FolderChange>> {
     pub fn create_workspace(&mut self, workspace: Workspace) -> CollaborateResult<Option<FolderChange>> {
         let workspace = Arc::new(workspace);
         let workspace = Arc::new(workspace);
         if self.workspaces.contains(&workspace) {
         if self.workspaces.contains(&workspace) {
@@ -132,6 +133,7 @@ impl FolderPad {
         }
         }
     }
     }
 
 
+    #[tracing::instrument(level = "trace", skip(self), err)]
     pub fn delete_workspace(&mut self, workspace_id: &str) -> CollaborateResult<Option<FolderChange>> {
     pub fn delete_workspace(&mut self, workspace_id: &str) -> CollaborateResult<Option<FolderChange>> {
         self.modify_workspaces(|workspaces| {
         self.modify_workspaces(|workspaces| {
             workspaces.retain(|w| w.id != workspace_id);
             workspaces.retain(|w| w.id != workspace_id);
@@ -139,6 +141,7 @@ impl FolderPad {
         })
         })
     }
     }
 
 
+    #[tracing::instrument(level = "trace", skip(self), fields(app_name=%app.name), err)]
     pub fn create_app(&mut self, app: App) -> CollaborateResult<Option<FolderChange>> {
     pub fn create_app(&mut self, app: App) -> CollaborateResult<Option<FolderChange>> {
         let workspace_id = app.workspace_id.clone();
         let workspace_id = app.workspace_id.clone();
         self.with_workspace(&workspace_id, move |workspace| {
         self.with_workspace(&workspace_id, move |workspace| {
@@ -178,6 +181,7 @@ impl FolderPad {
         })
         })
     }
     }
 
 
+    #[tracing::instrument(level = "trace", skip(self), err)]
     pub fn delete_app(&mut self, app_id: &str) -> CollaborateResult<Option<FolderChange>> {
     pub fn delete_app(&mut self, app_id: &str) -> CollaborateResult<Option<FolderChange>> {
         let app = self.read_app(app_id)?;
         let app = self.read_app(app_id)?;
         self.with_workspace(&app.workspace_id, |workspace| {
         self.with_workspace(&app.workspace_id, |workspace| {
@@ -186,6 +190,7 @@ impl FolderPad {
         })
         })
     }
     }
 
 
+    #[tracing::instrument(level = "trace", skip(self), fields(view_name=%view.name), err)]
     pub fn create_view(&mut self, view: View) -> CollaborateResult<Option<FolderChange>> {
     pub fn create_view(&mut self, view: View) -> CollaborateResult<Option<FolderChange>> {
         let app_id = view.belong_to_id.clone();
         let app_id = view.belong_to_id.clone();
         self.with_app(&app_id, move |app| {
         self.with_app(&app_id, move |app| {
@@ -242,6 +247,7 @@ impl FolderPad {
         })
         })
     }
     }
 
 
+    #[tracing::instrument(level = "trace", skip(self), err)]
     pub fn delete_view(&mut self, view_id: &str) -> CollaborateResult<Option<FolderChange>> {
     pub fn delete_view(&mut self, view_id: &str) -> CollaborateResult<Option<FolderChange>> {
         let view = self.read_view(view_id)?;
         let view = self.read_view(view_id)?;
         self.with_app(&view.belong_to_id, |app| {
         self.with_app(&view.belong_to_id, |app| {

+ 2 - 0
shared-lib/flowy-derive/src/derive_cache/derive_cache.rs

@@ -31,6 +31,8 @@ pub fn category_from_str(type_str: &str) -> TypeCategory {
         | "UserProfile"
         | "UserProfile"
         | "UpdateUserRequest"
         | "UpdateUserRequest"
         | "UpdateUserParams"
         | "UpdateUserParams"
+        | "UserPreferences"
+        | "AppearanceSettings"
         | "ClientRevisionWSData"
         | "ClientRevisionWSData"
         | "ServerRevisionWSData"
         | "ServerRevisionWSData"
         | "NewDocumentUser"
         | "NewDocumentUser"

+ 1 - 0
shared-lib/flowy-user-data-model/Cargo.toml

@@ -12,6 +12,7 @@ protobuf = {version = "2.18.0"}
 bytes = "1.0"
 bytes = "1.0"
 unicode-segmentation = "1.8"
 unicode-segmentation = "1.8"
 derive_more = {version = "0.99", features = ["display"]}
 derive_more = {version = "0.99", features = ["display"]}
+serde = { version = "1.0", features = ["derive"] }
 validator = "0.12.0"
 validator = "0.12.0"
 log = "0.4.14"
 log = "0.4.14"
 fancy-regex = "0.5.0"
 fancy-regex = "0.5.0"

+ 2 - 4
shared-lib/flowy-user-data-model/src/entities/mod.rs

@@ -1,9 +1,7 @@
 pub use auth::*;
 pub use auth::*;
 pub use user_profile::*;
 pub use user_profile::*;
+pub use user_setting::*;
 
 
 pub mod auth;
 pub mod auth;
 mod user_profile;
 mod user_profile;
-
-pub mod prelude {
-    pub use crate::entities::{auth::*, user_profile::*};
-}
+mod user_setting;

+ 32 - 0
shared-lib/flowy-user-data-model/src/entities/user_setting.rs

@@ -0,0 +1,32 @@
+use flowy_derive::ProtoBuf;
+use serde::{Deserialize, Serialize};
+
+#[derive(ProtoBuf, Default, Debug, Clone)]
+pub struct UserPreferences {
+    #[pb(index = 1)]
+    user_id: String,
+
+    #[pb(index = 2)]
+    appearance_setting: AppearanceSettings,
+}
+
+#[derive(ProtoBuf, Serialize, Deserialize, Debug, Clone)]
+pub struct AppearanceSettings {
+    #[pb(index = 1)]
+    pub theme: String,
+
+    #[pb(index = 2)]
+    pub language: String,
+}
+
+pub const APPEARANCE_DEFAULT_THEME: &str = "light";
+pub const APPEARANCE_DEFAULT_LANGUAGE: &str = "en";
+
+impl std::default::Default for AppearanceSettings {
+    fn default() -> Self {
+        AppearanceSettings {
+            theme: APPEARANCE_DEFAULT_THEME.to_owned(),
+            language: APPEARANCE_DEFAULT_LANGUAGE.to_owned(),
+        }
+    }
+}

+ 3 - 0
shared-lib/flowy-user-data-model/src/protobuf/model/mod.rs

@@ -9,3 +9,6 @@ pub use user_profile::*;
 
 
 mod auth;
 mod auth;
 pub use auth::*;
 pub use auth::*;
+
+mod user_setting;
+pub use user_setting::*;

+ 475 - 0
shared-lib/flowy-user-data-model/src/protobuf/model/user_setting.rs

@@ -0,0 +1,475 @@
+// 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 `user_setting.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 UserPreferences {
+    // message fields
+    pub user_id: ::std::string::String,
+    pub appearance_setting: ::protobuf::SingularPtrField<AppearanceSettings>,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a UserPreferences {
+    fn default() -> &'a UserPreferences {
+        <UserPreferences as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl UserPreferences {
+    pub fn new() -> UserPreferences {
+        ::std::default::Default::default()
+    }
+
+    // string user_id = 1;
+
+
+    pub fn get_user_id(&self) -> &str {
+        &self.user_id
+    }
+    pub fn clear_user_id(&mut self) {
+        self.user_id.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_user_id(&mut self, v: ::std::string::String) {
+        self.user_id = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_user_id(&mut self) -> &mut ::std::string::String {
+        &mut self.user_id
+    }
+
+    // Take field
+    pub fn take_user_id(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.user_id, ::std::string::String::new())
+    }
+
+    // .AppearanceSettings appearance_setting = 2;
+
+
+    pub fn get_appearance_setting(&self) -> &AppearanceSettings {
+        self.appearance_setting.as_ref().unwrap_or_else(|| <AppearanceSettings as ::protobuf::Message>::default_instance())
+    }
+    pub fn clear_appearance_setting(&mut self) {
+        self.appearance_setting.clear();
+    }
+
+    pub fn has_appearance_setting(&self) -> bool {
+        self.appearance_setting.is_some()
+    }
+
+    // Param is passed by value, moved
+    pub fn set_appearance_setting(&mut self, v: AppearanceSettings) {
+        self.appearance_setting = ::protobuf::SingularPtrField::some(v);
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_appearance_setting(&mut self) -> &mut AppearanceSettings {
+        if self.appearance_setting.is_none() {
+            self.appearance_setting.set_default();
+        }
+        self.appearance_setting.as_mut().unwrap()
+    }
+
+    // Take field
+    pub fn take_appearance_setting(&mut self) -> AppearanceSettings {
+        self.appearance_setting.take().unwrap_or_else(|| AppearanceSettings::new())
+    }
+}
+
+impl ::protobuf::Message for UserPreferences {
+    fn is_initialized(&self) -> bool {
+        for v in &self.appearance_setting {
+            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_singular_proto3_string_into(wire_type, is, &mut self.user_id)?;
+                },
+                2 => {
+                    ::protobuf::rt::read_singular_message_into(wire_type, is, &mut self.appearance_setting)?;
+                },
+                _ => {
+                    ::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.user_id.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.user_id);
+        }
+        if let Some(ref v) = self.appearance_setting.as_ref() {
+            let len = v.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<()> {
+        if !self.user_id.is_empty() {
+            os.write_string(1, &self.user_id)?;
+        }
+        if let Some(ref v) = self.appearance_setting.as_ref() {
+            os.write_tag(2, ::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() -> UserPreferences {
+        UserPreferences::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>(
+                "user_id",
+                |m: &UserPreferences| { &m.user_id },
+                |m: &mut UserPreferences| { &mut m.user_id },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_singular_ptr_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<AppearanceSettings>>(
+                "appearance_setting",
+                |m: &UserPreferences| { &m.appearance_setting },
+                |m: &mut UserPreferences| { &mut m.appearance_setting },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<UserPreferences>(
+                "UserPreferences",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static UserPreferences {
+        static instance: ::protobuf::rt::LazyV2<UserPreferences> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(UserPreferences::new)
+    }
+}
+
+impl ::protobuf::Clear for UserPreferences {
+    fn clear(&mut self) {
+        self.user_id.clear();
+        self.appearance_setting.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for UserPreferences {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for UserPreferences {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+#[derive(PartialEq,Clone,Default)]
+pub struct AppearanceSettings {
+    // message fields
+    pub theme: ::std::string::String,
+    pub language: ::std::string::String,
+    // special fields
+    pub unknown_fields: ::protobuf::UnknownFields,
+    pub cached_size: ::protobuf::CachedSize,
+}
+
+impl<'a> ::std::default::Default for &'a AppearanceSettings {
+    fn default() -> &'a AppearanceSettings {
+        <AppearanceSettings as ::protobuf::Message>::default_instance()
+    }
+}
+
+impl AppearanceSettings {
+    pub fn new() -> AppearanceSettings {
+        ::std::default::Default::default()
+    }
+
+    // string theme = 1;
+
+
+    pub fn get_theme(&self) -> &str {
+        &self.theme
+    }
+    pub fn clear_theme(&mut self) {
+        self.theme.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_theme(&mut self, v: ::std::string::String) {
+        self.theme = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_theme(&mut self) -> &mut ::std::string::String {
+        &mut self.theme
+    }
+
+    // Take field
+    pub fn take_theme(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.theme, ::std::string::String::new())
+    }
+
+    // string language = 2;
+
+
+    pub fn get_language(&self) -> &str {
+        &self.language
+    }
+    pub fn clear_language(&mut self) {
+        self.language.clear();
+    }
+
+    // Param is passed by value, moved
+    pub fn set_language(&mut self, v: ::std::string::String) {
+        self.language = v;
+    }
+
+    // Mutable pointer to the field.
+    // If field is not initialized, it is initialized with default value first.
+    pub fn mut_language(&mut self) -> &mut ::std::string::String {
+        &mut self.language
+    }
+
+    // Take field
+    pub fn take_language(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.language, ::std::string::String::new())
+    }
+}
+
+impl ::protobuf::Message for AppearanceSettings {
+    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.theme)?;
+                },
+                2 => {
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.language)?;
+                },
+                _ => {
+                    ::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.theme.is_empty() {
+            my_size += ::protobuf::rt::string_size(1, &self.theme);
+        }
+        if !self.language.is_empty() {
+            my_size += ::protobuf::rt::string_size(2, &self.language);
+        }
+        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.theme.is_empty() {
+            os.write_string(1, &self.theme)?;
+        }
+        if !self.language.is_empty() {
+            os.write_string(2, &self.language)?;
+        }
+        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() -> AppearanceSettings {
+        AppearanceSettings::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>(
+                "theme",
+                |m: &AppearanceSettings| { &m.theme },
+                |m: &mut AppearanceSettings| { &mut m.theme },
+            ));
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
+                "language",
+                |m: &AppearanceSettings| { &m.language },
+                |m: &mut AppearanceSettings| { &mut m.language },
+            ));
+            ::protobuf::reflect::MessageDescriptor::new_pb_name::<AppearanceSettings>(
+                "AppearanceSettings",
+                fields,
+                file_descriptor_proto()
+            )
+        })
+    }
+
+    fn default_instance() -> &'static AppearanceSettings {
+        static instance: ::protobuf::rt::LazyV2<AppearanceSettings> = ::protobuf::rt::LazyV2::INIT;
+        instance.get(AppearanceSettings::new)
+    }
+}
+
+impl ::protobuf::Clear for AppearanceSettings {
+    fn clear(&mut self) {
+        self.theme.clear();
+        self.language.clear();
+        self.unknown_fields.clear();
+    }
+}
+
+impl ::std::fmt::Debug for AppearanceSettings {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+        ::protobuf::text_format::fmt(self, f)
+    }
+}
+
+impl ::protobuf::reflect::ProtobufValue for AppearanceSettings {
+    fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
+        ::protobuf::reflect::ReflectValueRef::Message(self)
+    }
+}
+
+static file_descriptor_proto_data: &'static [u8] = b"\
+    \n\x12user_setting.proto\"n\n\x0fUserPreferences\x12\x17\n\x07user_id\
+    \x18\x01\x20\x01(\tR\x06userId\x12B\n\x12appearance_setting\x18\x02\x20\
+    \x01(\x0b2\x13.AppearanceSettingsR\x11appearanceSetting\"F\n\x12Appearan\
+    ceSettings\x12\x14\n\x05theme\x18\x01\x20\x01(\tR\x05theme\x12\x1a\n\x08\
+    language\x18\x02\x20\x01(\tR\x08languageJ\x9e\x02\n\x06\x12\x04\0\0\t\
+    \x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x05\
+    \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\x17\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\x12\n\x0c\n\x05\x04\0\x02\0\x03\x12\
+    \x03\x03\x15\x16\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x04.\n\x0c\n\x05\
+    \x04\0\x02\x01\x06\x12\x03\x04\x04\x16\n\x0c\n\x05\x04\0\x02\x01\x01\x12\
+    \x03\x04\x17)\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x04,-\n\n\n\x02\x04\
+    \x01\x12\x04\x06\0\t\x01\n\n\n\x03\x04\x01\x01\x12\x03\x06\x08\x1a\n\x0b\
+    \n\x04\x04\x01\x02\0\x12\x03\x07\x04\x15\n\x0c\n\x05\x04\x01\x02\0\x05\
+    \x12\x03\x07\x04\n\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\x07\x0b\x10\n\
+    \x0c\n\x05\x04\x01\x02\0\x03\x12\x03\x07\x13\x14\n\x0b\n\x04\x04\x01\x02\
+    \x01\x12\x03\x08\x04\x18\n\x0c\n\x05\x04\x01\x02\x01\x05\x12\x03\x08\x04\
+    \n\n\x0c\n\x05\x04\x01\x02\x01\x01\x12\x03\x08\x0b\x13\n\x0c\n\x05\x04\
+    \x01\x02\x01\x03\x12\x03\x08\x16\x17b\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()
+    })
+}

+ 10 - 0
shared-lib/flowy-user-data-model/src/protobuf/proto/user_setting.proto

@@ -0,0 +1,10 @@
+syntax = "proto3";
+
+message UserPreferences {
+    string user_id = 1;
+    AppearanceSettings appearance_setting = 2;
+}
+message AppearanceSettings {
+    string theme = 1;
+    string language = 2;
+}