Browse Source

merge master

Jaylen Bian 3 years ago
parent
commit
cc17529977
100 changed files with 2643 additions and 1023 deletions
  1. 1 1
      Makefile.toml
  2. 2 1
      app_flowy/.vscode/launch.json
  3. 17 6
      app_flowy/.vscode/tasks.json
  4. 1 1
      app_flowy/analysis_options.yaml
  5. BIN
      app_flowy/assets/images/app_flowy_logo.jpg
  6. BIN
      app_flowy/assets/images/avatar.jpg
  7. BIN
      app_flowy/assets/images/file_icon.jpg
  8. 1 1
      app_flowy/lib/startup/deps_inject/prelude.dart
  9. 1 7
      app_flowy/lib/startup/tasks/rust_sdk_init_task.dart
  10. 28 7
      app_flowy/lib/user/application/sign_in/sign_in_bloc.dart
  11. 71 21
      app_flowy/lib/user/application/sign_in/sign_in_bloc.freezed.dart
  12. 0 0
      app_flowy/lib/user/application/sign_up/sign_up_event.dart
  13. 0 1
      app_flowy/lib/user/application/sign_up/sign_up_state.dart
  14. 7 0
      app_flowy/lib/user/domain/i_auth.dart
  15. 2 1
      app_flowy/lib/user/infrastructure/deps_resolver.dart
  16. 20 0
      app_flowy/lib/user/infrastructure/i_auth_impl.dart
  17. 202 4
      app_flowy/lib/user/presentation/sign_in/sign_in_screen.dart
  18. 47 19
      app_flowy/lib/user/presentation/sign_in/widgets/background.dart
  19. 0 125
      app_flowy/lib/user/presentation/sign_in/widgets/body.dart
  20. 1 1
      app_flowy/lib/welcome/application/welcome_bloc.dart
  21. 1 1
      app_flowy/lib/welcome/domain/i_welcome.dart
  22. 4 2
      app_flowy/lib/welcome/infrastructure/i_welcome_impl.dart
  23. 0 2
      app_flowy/lib/workspace/application/doc/doc_bloc.dart
  24. 0 134
      app_flowy/lib/workspace/application/doc/doc_bloc.freezed.dart
  25. 1 3
      app_flowy/lib/workspace/application/menu/menu_bloc.dart
  26. 60 0
      app_flowy/lib/workspace/application/menu/menu_user_bloc.dart
  27. 432 0
      app_flowy/lib/workspace/application/menu/menu_user_bloc.freezed.dart
  28. 18 0
      app_flowy/lib/workspace/domain/i_user.dart
  29. 16 0
      app_flowy/lib/workspace/domain/image.dart
  30. 12 8
      app_flowy/lib/workspace/domain/page_stack/page_stack.dart
  31. 1 1
      app_flowy/lib/workspace/domain/page_stack/page_stack_bloc.dart
  32. 24 9
      app_flowy/lib/workspace/infrastructure/deps_resolver.dart
  33. 1 2
      app_flowy/lib/workspace/infrastructure/i_app_impl.dart
  34. 22 3
      app_flowy/lib/workspace/infrastructure/i_doc_impl.dart
  35. 36 0
      app_flowy/lib/workspace/infrastructure/i_user_impl.dart
  36. 36 0
      app_flowy/lib/workspace/infrastructure/repos/user_repo.dart
  37. 8 7
      app_flowy/lib/workspace/infrastructure/repos/workspace_repo.dart
  38. 85 63
      app_flowy/lib/workspace/presentation/app/app_widget.dart
  39. 10 12
      app_flowy/lib/workspace/presentation/app/view_list.dart
  40. 17 1
      app_flowy/lib/workspace/presentation/doc/doc_page.dart
  41. 14 7
      app_flowy/lib/workspace/presentation/doc/editor_widget.dart
  42. 1 1
      app_flowy/lib/workspace/presentation/home/home_layout.dart
  43. 5 4
      app_flowy/lib/workspace/presentation/home/home_screen.dart
  44. 51 16
      app_flowy/lib/workspace/presentation/view/view_widget.dart
  45. 12 12
      app_flowy/lib/workspace/presentation/widgets/blank_page.dart
  46. 66 24
      app_flowy/lib/workspace/presentation/widgets/home_top_bar.dart
  47. 0 29
      app_flowy/lib/workspace/presentation/widgets/menu/app_list.dart
  48. 65 0
      app_flowy/lib/workspace/presentation/widgets/menu/create_app_dialog.dart
  49. 68 147
      app_flowy/lib/workspace/presentation/widgets/menu/menu.dart
  50. 40 0
      app_flowy/lib/workspace/presentation/widgets/menu/menu_list.dart
  51. 50 0
      app_flowy/lib/workspace/presentation/widgets/menu/menu_new_app.dart
  52. 0 4
      app_flowy/lib/workspace/presentation/widgets/menu/menu_size.dart
  53. 37 0
      app_flowy/lib/workspace/presentation/widgets/menu/menu_top_bar.dart
  54. 66 0
      app_flowy/lib/workspace/presentation/widgets/menu/menu_user.dart
  55. 0 1
      app_flowy/lib/workspace/presentation/widgets/menu/prelude.dart
  56. 1 1
      app_flowy/macos/Podfile.lock
  57. 30 11
      app_flowy/packages/flowy_editor/lib/src/service/controller.dart
  58. 136 64
      app_flowy/packages/flowy_editor/lib/src/widget/raw_editor.dart
  59. 1 1
      app_flowy/packages/flowy_infra/lib/size.dart
  60. 1 1
      app_flowy/packages/flowy_infra/lib/theme.dart
  61. 1 0
      app_flowy/packages/flowy_infra/lib/time/duration.dart
  62. 8 1
      app_flowy/packages/flowy_infra_ui/example/pubspec.lock
  63. 57 0
      app_flowy/packages/flowy_infra_ui/lib/style_widget/styled_hover.dart
  64. 55 0
      app_flowy/packages/flowy_infra_ui/lib/style_widget/styled_icon_button.dart
  65. 107 0
      app_flowy/packages/flowy_infra_ui/lib/style_widget/styled_navigation_list.dart
  66. 34 0
      app_flowy/packages/flowy_infra_ui/lib/style_widget/styled_progress_indicator.dart
  67. 23 0
      app_flowy/packages/flowy_infra_ui/lib/style_widget/styled_text.dart
  68. 54 0
      app_flowy/packages/flowy_infra_ui/lib/style_widget/styled_text_button.dart
  69. 6 6
      app_flowy/packages/flowy_infra_ui/lib/widget/mouse_hover_builder.dart
  70. 69 12
      app_flowy/packages/flowy_infra_ui/lib/widget/rounded_button.dart
  71. 76 15
      app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart
  72. 14 3
      app_flowy/packages/flowy_infra_ui/lib/widget/text_field_container.dart
  73. 12 5
      app_flowy/packages/flowy_infra_ui/pubspec.lock
  74. 1 0
      app_flowy/packages/flowy_infra_ui/pubspec.yaml
  75. 14 0
      app_flowy/packages/flowy_sdk/lib/dispatch/code_gen.dart
  76. 4 4
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/errors.pb.dart
  77. 22 22
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/errors.pbenum.dart
  78. 7 7
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/errors.pbjson.dart
  79. 4 4
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/errors.pb.dart
  80. 19 19
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/errors.pbenum.dart
  81. 7 7
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/errors.pbjson.dart
  82. 2 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbenum.dart
  83. 2 1
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/event.pbjson.dart
  84. 13 5
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/view_create.pbenum.dart
  85. 41 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_create.pb.dart
  86. 10 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_create.pbjson.dart
  87. 131 124
      app_flowy/pubspec.lock
  88. 1 1
      rust-lib/dart-ffi/src/lib.rs
  89. 1 1
      rust-lib/dart-ffi/src/model/ffi_response.rs
  90. 3 2
      rust-lib/flowy-derive/src/derive_cache/derive_cache.rs
  91. 1 1
      rust-lib/flowy-dispatch/src/errors/errors.rs
  92. 0 1
      rust-lib/flowy-dispatch/src/response/builder.rs
  93. 3 2
      rust-lib/flowy-editor/src/handlers/doc_handler.rs
  94. 1 4
      rust-lib/flowy-editor/src/module.rs
  95. 1 1
      rust-lib/flowy-editor/src/services/doc_controller.rs
  96. 1 1
      rust-lib/flowy-editor/src/services/file_manager/file.rs
  97. 5 2
      rust-lib/flowy-editor/src/services/file_manager/manager.rs
  98. 1 1
      rust-lib/flowy-editor/src/sql_tables/doc/doc_sql.rs
  99. 1 2
      rust-lib/flowy-editor/src/sql_tables/doc/doc_table.rs
  100. 2 2
      rust-lib/flowy-editor/src/sql_tables/doc/mod.rs

+ 1 - 1
Makefile.toml

@@ -10,7 +10,7 @@ CARGO_MAKE_CRATE_NAME = "dart-ffi"
 DEV = true
 DEV = true
 LIB_OUT_DIR = "debug"
 LIB_OUT_DIR = "debug"
 RELEASE = false
 RELEASE = false
-TARGET_OS = "unknown"
+#TARGET_OS = "unknown"
 
 
 [tasks.setup-crate-type]
 [tasks.setup-crate-type]
 private = true
 private = true

+ 2 - 1
app_flowy/.vscode/launch.json

@@ -9,7 +9,8 @@
             "request": "launch",
             "request": "launch",
             "program": "${workspaceRoot}/lib/main.dart",
             "program": "${workspaceRoot}/lib/main.dart",
             "type": "dart",
             "type": "dart",
-            // "preLaunchTask": "build rust sdk"
+            "preLaunchTask": "build_flowy_sdk",
+            "cwd": "${workspaceRoot}"
         },
         },
         {
         {
             "name": "app_flowy (profile mode)",
             "name": "app_flowy (profile mode)",

+ 17 - 6
app_flowy/.vscode/tasks.json

@@ -1,8 +1,6 @@
 {
 {
 	"version": "2.0.0",
 	"version": "2.0.0",
-	"tasks": [
-		{
-			// https://code.visualstudio.com/docs/editor/tasks
+	// https://code.visualstudio.com/docs/editor/tasks
 			//https://gist.github.com/deadalusai/9e13e36d61ec7fb72148
 			//https://gist.github.com/deadalusai/9e13e36d61ec7fb72148
 			
 			
 			// ${workspaceRoot}: the root folder of the team
 			// ${workspaceRoot}: the root folder of the team
@@ -11,9 +9,22 @@
 			// ${fileDirname}: the current opened file's dirname
 			// ${fileDirname}: the current opened file's dirname
 			// ${fileExtname}: the current opened file's extension
 			// ${fileExtname}: the current opened file's extension
 			// ${cwd}: the current working directory of the spawned process
 			// ${cwd}: the current working directory of the spawned process
-
+	"tasks": [
+		{
+			"type": "shell",
+			"command": "sh ./scripts/build_sdk.sh",
+			"group": "build",
+			"options": {
+				"cwd": "${workspaceFolder}/../"
+			  },
+			// "problemMatcher": [
+            //     "$rustc"
+            // ],
+			"label": "build_flowy_sdk"
+		},
+		{
 			"type": "shell",
 			"type": "shell",
-			"command": "sh ${workspaceFolder}/../scripts/build_sdk.sh",
+			"command": "sh ./scripts/code_gen.sh",
 			"group": "build",
 			"group": "build",
 			"options": {
 			"options": {
 				"cwd": "${workspaceFolder}/../"
 				"cwd": "${workspaceFolder}/../"
@@ -21,7 +32,7 @@
 			"problemMatcher": [
 			"problemMatcher": [
                 "$rustc"
                 "$rustc"
             ],
             ],
-			"label": "build rust sdk"
+			"label": "generate events"
 		}
 		}
 	]
 	]
 }
 }

+ 1 - 1
app_flowy/analysis_options.yaml

@@ -15,7 +15,7 @@ analyzer:
     - "**/*.g.dart"
     - "**/*.g.dart"
     - "**/*.freezed.dart"
     - "**/*.freezed.dart"
     - "packages/flowy_editor/**"
     - "packages/flowy_editor/**"
-    - "packages/flowy_infra_ui/**"
+    # - "packages/flowy_infra_ui/**"
     
     
 linter:
 linter:
   # The lint rules applied to this project can be customized in the
   # The lint rules applied to this project can be customized in the

BIN
app_flowy/assets/images/app_flowy_logo.jpg


BIN
app_flowy/assets/images/avatar.jpg


BIN
app_flowy/assets/images/file_icon.jpg


+ 1 - 1
app_flowy/lib/startup/deps_inject/prelude.dart

@@ -15,7 +15,7 @@ Future<void> initGetIt(
   getIt.registerLazySingleton<FlowySDK>(() => const FlowySDK());
   getIt.registerLazySingleton<FlowySDK>(() => const FlowySDK());
   getIt.registerLazySingleton<AppLauncher>(() => AppLauncher(env, getIt));
   getIt.registerLazySingleton<AppLauncher>(() => AppLauncher(env, getIt));
 
 
-  await WelcomeDepsResolver.resolve(getIt);
   await UserDepsResolver.resolve(getIt);
   await UserDepsResolver.resolve(getIt);
+  await WelcomeDepsResolver.resolve(getIt);
   await HomeDepsResolver.resolve(getIt);
   await HomeDepsResolver.resolve(getIt);
 }
 }

+ 1 - 7
app_flowy/lib/startup/tasks/rust_sdk_init_task.dart

@@ -40,7 +40,7 @@ class ApplicationBlocObserver extends BlocObserver {
   @override
   @override
   // ignore: unnecessary_overrides
   // ignore: unnecessary_overrides
   void onTransition(Bloc bloc, Transition transition) {
   void onTransition(Bloc bloc, Transition transition) {
-    // Log.debug(transition);
+    Log.debug(transition);
     super.onTransition(bloc, transition);
     super.onTransition(bloc, transition);
   }
   }
 
 
@@ -50,9 +50,3 @@ class ApplicationBlocObserver extends BlocObserver {
     super.onError(bloc, error, stackTrace);
     super.onError(bloc, error, stackTrace);
   }
   }
 }
 }
-
-class EngineBlocConfig {
-  static void setup() {
-    Bloc.observer = ApplicationBlocObserver();
-  }
-}

+ 28 - 7
app_flowy/lib/user/application/sign_in/sign_in_bloc.dart

@@ -22,10 +22,10 @@ class SignInBloc extends Bloc<SignInEvent, SignInState> {
         );
         );
       },
       },
       emailChanged: (EmailChanged value) async* {
       emailChanged: (EmailChanged value) async* {
-        yield state.copyWith(email: value.email, signInFailure: none());
+        yield state.copyWith(email: value.email, successOrFail: none());
       },
       },
       passwordChanged: (PasswordChanged value) async* {
       passwordChanged: (PasswordChanged value) async* {
-        yield state.copyWith(password: value.password, signInFailure: none());
+        yield state.copyWith(password: value.password, successOrFail: none());
       },
       },
     );
     );
   }
   }
@@ -36,17 +36,34 @@ class SignInBloc extends Bloc<SignInEvent, SignInState> {
     final result = await authImpl.signIn(state.email, state.password);
     final result = await authImpl.signIn(state.email, state.password);
     yield result.fold(
     yield result.fold(
       (userDetail) => state.copyWith(
       (userDetail) => state.copyWith(
-          isSubmitting: false, signInFailure: some(left(userDetail))),
-      (s) => state.copyWith(isSubmitting: false, signInFailure: some(right(s))),
+          isSubmitting: false, successOrFail: some(left(userDetail))),
+      (error) => stateFromCode(error),
     );
     );
   }
   }
+
+  SignInState stateFromCode(UserError error) {
+    switch (error.code) {
+      case UserErrCode.EmailInvalid:
+        return state.copyWith(
+            isSubmitting: false,
+            emailError: some(error.msg),
+            passwordError: none());
+      case UserErrCode.PasswordInvalid:
+        return state.copyWith(
+            isSubmitting: false,
+            passwordError: some(error.msg),
+            emailError: none());
+      default:
+        return state.copyWith(
+            isSubmitting: false, successOrFail: some(right(error)));
+    }
+  }
 }
 }
 
 
 @freezed
 @freezed
 abstract class SignInEvent with _$SignInEvent {
 abstract class SignInEvent with _$SignInEvent {
   const factory SignInEvent.signedInWithUserEmailAndPassword() =
   const factory SignInEvent.signedInWithUserEmailAndPassword() =
       SignedInWithUserEmailAndPassword;
       SignedInWithUserEmailAndPassword;
-
   const factory SignInEvent.emailChanged(String email) = EmailChanged;
   const factory SignInEvent.emailChanged(String email) = EmailChanged;
   const factory SignInEvent.passwordChanged(String password) = PasswordChanged;
   const factory SignInEvent.passwordChanged(String password) = PasswordChanged;
 }
 }
@@ -57,11 +74,15 @@ abstract class SignInState with _$SignInState {
     String? email,
     String? email,
     String? password,
     String? password,
     required bool isSubmitting,
     required bool isSubmitting,
-    required Option<Either<UserDetail, UserError>> signInFailure,
+    required Option<String> passwordError,
+    required Option<String> emailError,
+    required Option<Either<UserDetail, UserError>> successOrFail,
   }) = _SignInState;
   }) = _SignInState;
 
 
   factory SignInState.initial() => SignInState(
   factory SignInState.initial() => SignInState(
         isSubmitting: false,
         isSubmitting: false,
-        signInFailure: none(),
+        passwordError: none(),
+        emailError: none(),
+        successOrFail: none(),
       );
       );
 }
 }

+ 71 - 21
app_flowy/lib/user/application/sign_in/sign_in_bloc.freezed.dart

@@ -438,12 +438,16 @@ class _$SignInStateTearOff {
       {String? email,
       {String? email,
       String? password,
       String? password,
       required bool isSubmitting,
       required bool isSubmitting,
-      required Option<Either<UserDetail, UserError>> signInFailure}) {
+      required Option<String> passwordError,
+      required Option<String> emailError,
+      required Option<Either<UserDetail, UserError>> successOrFail}) {
     return _SignInState(
     return _SignInState(
       email: email,
       email: email,
       password: password,
       password: password,
       isSubmitting: isSubmitting,
       isSubmitting: isSubmitting,
-      signInFailure: signInFailure,
+      passwordError: passwordError,
+      emailError: emailError,
+      successOrFail: successOrFail,
     );
     );
   }
   }
 }
 }
@@ -456,7 +460,9 @@ mixin _$SignInState {
   String? get email => throw _privateConstructorUsedError;
   String? get email => throw _privateConstructorUsedError;
   String? get password => throw _privateConstructorUsedError;
   String? get password => throw _privateConstructorUsedError;
   bool get isSubmitting => throw _privateConstructorUsedError;
   bool get isSubmitting => throw _privateConstructorUsedError;
-  Option<Either<UserDetail, UserError>> get signInFailure =>
+  Option<String> get passwordError => throw _privateConstructorUsedError;
+  Option<String> get emailError => throw _privateConstructorUsedError;
+  Option<Either<UserDetail, UserError>> get successOrFail =>
       throw _privateConstructorUsedError;
       throw _privateConstructorUsedError;
 
 
   @JsonKey(ignore: true)
   @JsonKey(ignore: true)
@@ -473,7 +479,9 @@ abstract class $SignInStateCopyWith<$Res> {
       {String? email,
       {String? email,
       String? password,
       String? password,
       bool isSubmitting,
       bool isSubmitting,
-      Option<Either<UserDetail, UserError>> signInFailure});
+      Option<String> passwordError,
+      Option<String> emailError,
+      Option<Either<UserDetail, UserError>> successOrFail});
 }
 }
 
 
 /// @nodoc
 /// @nodoc
@@ -489,7 +497,9 @@ class _$SignInStateCopyWithImpl<$Res> implements $SignInStateCopyWith<$Res> {
     Object? email = freezed,
     Object? email = freezed,
     Object? password = freezed,
     Object? password = freezed,
     Object? isSubmitting = freezed,
     Object? isSubmitting = freezed,
-    Object? signInFailure = freezed,
+    Object? passwordError = freezed,
+    Object? emailError = freezed,
+    Object? successOrFail = freezed,
   }) {
   }) {
     return _then(_value.copyWith(
     return _then(_value.copyWith(
       email: email == freezed
       email: email == freezed
@@ -504,9 +514,17 @@ class _$SignInStateCopyWithImpl<$Res> implements $SignInStateCopyWith<$Res> {
           ? _value.isSubmitting
           ? _value.isSubmitting
           : isSubmitting // ignore: cast_nullable_to_non_nullable
           : isSubmitting // ignore: cast_nullable_to_non_nullable
               as bool,
               as bool,
-      signInFailure: signInFailure == freezed
-          ? _value.signInFailure
-          : signInFailure // ignore: cast_nullable_to_non_nullable
+      passwordError: passwordError == freezed
+          ? _value.passwordError
+          : passwordError // ignore: cast_nullable_to_non_nullable
+              as Option<String>,
+      emailError: emailError == freezed
+          ? _value.emailError
+          : emailError // ignore: cast_nullable_to_non_nullable
+              as Option<String>,
+      successOrFail: successOrFail == freezed
+          ? _value.successOrFail
+          : successOrFail // ignore: cast_nullable_to_non_nullable
               as Option<Either<UserDetail, UserError>>,
               as Option<Either<UserDetail, UserError>>,
     ));
     ));
   }
   }
@@ -523,7 +541,9 @@ abstract class _$SignInStateCopyWith<$Res>
       {String? email,
       {String? email,
       String? password,
       String? password,
       bool isSubmitting,
       bool isSubmitting,
-      Option<Either<UserDetail, UserError>> signInFailure});
+      Option<String> passwordError,
+      Option<String> emailError,
+      Option<Either<UserDetail, UserError>> successOrFail});
 }
 }
 
 
 /// @nodoc
 /// @nodoc
@@ -541,7 +561,9 @@ class __$SignInStateCopyWithImpl<$Res> extends _$SignInStateCopyWithImpl<$Res>
     Object? email = freezed,
     Object? email = freezed,
     Object? password = freezed,
     Object? password = freezed,
     Object? isSubmitting = freezed,
     Object? isSubmitting = freezed,
-    Object? signInFailure = freezed,
+    Object? passwordError = freezed,
+    Object? emailError = freezed,
+    Object? successOrFail = freezed,
   }) {
   }) {
     return _then(_SignInState(
     return _then(_SignInState(
       email: email == freezed
       email: email == freezed
@@ -556,9 +578,17 @@ class __$SignInStateCopyWithImpl<$Res> extends _$SignInStateCopyWithImpl<$Res>
           ? _value.isSubmitting
           ? _value.isSubmitting
           : isSubmitting // ignore: cast_nullable_to_non_nullable
           : isSubmitting // ignore: cast_nullable_to_non_nullable
               as bool,
               as bool,
-      signInFailure: signInFailure == freezed
-          ? _value.signInFailure
-          : signInFailure // ignore: cast_nullable_to_non_nullable
+      passwordError: passwordError == freezed
+          ? _value.passwordError
+          : passwordError // ignore: cast_nullable_to_non_nullable
+              as Option<String>,
+      emailError: emailError == freezed
+          ? _value.emailError
+          : emailError // ignore: cast_nullable_to_non_nullable
+              as Option<String>,
+      successOrFail: successOrFail == freezed
+          ? _value.successOrFail
+          : successOrFail // ignore: cast_nullable_to_non_nullable
               as Option<Either<UserDetail, UserError>>,
               as Option<Either<UserDetail, UserError>>,
     ));
     ));
   }
   }
@@ -571,7 +601,9 @@ class _$_SignInState implements _SignInState {
       {this.email,
       {this.email,
       this.password,
       this.password,
       required this.isSubmitting,
       required this.isSubmitting,
-      required this.signInFailure});
+      required this.passwordError,
+      required this.emailError,
+      required this.successOrFail});
 
 
   @override
   @override
   final String? email;
   final String? email;
@@ -580,11 +612,15 @@ class _$_SignInState implements _SignInState {
   @override
   @override
   final bool isSubmitting;
   final bool isSubmitting;
   @override
   @override
-  final Option<Either<UserDetail, UserError>> signInFailure;
+  final Option<String> passwordError;
+  @override
+  final Option<String> emailError;
+  @override
+  final Option<Either<UserDetail, UserError>> successOrFail;
 
 
   @override
   @override
   String toString() {
   String toString() {
-    return 'SignInState(email: $email, password: $password, isSubmitting: $isSubmitting, signInFailure: $signInFailure)';
+    return 'SignInState(email: $email, password: $password, isSubmitting: $isSubmitting, passwordError: $passwordError, emailError: $emailError, successOrFail: $successOrFail)';
   }
   }
 
 
   @override
   @override
@@ -599,9 +635,15 @@ class _$_SignInState implements _SignInState {
             (identical(other.isSubmitting, isSubmitting) ||
             (identical(other.isSubmitting, isSubmitting) ||
                 const DeepCollectionEquality()
                 const DeepCollectionEquality()
                     .equals(other.isSubmitting, isSubmitting)) &&
                     .equals(other.isSubmitting, isSubmitting)) &&
-            (identical(other.signInFailure, signInFailure) ||
+            (identical(other.passwordError, passwordError) ||
+                const DeepCollectionEquality()
+                    .equals(other.passwordError, passwordError)) &&
+            (identical(other.emailError, emailError) ||
                 const DeepCollectionEquality()
                 const DeepCollectionEquality()
-                    .equals(other.signInFailure, signInFailure)));
+                    .equals(other.emailError, emailError)) &&
+            (identical(other.successOrFail, successOrFail) ||
+                const DeepCollectionEquality()
+                    .equals(other.successOrFail, successOrFail)));
   }
   }
 
 
   @override
   @override
@@ -610,7 +652,9 @@ class _$_SignInState implements _SignInState {
       const DeepCollectionEquality().hash(email) ^
       const DeepCollectionEquality().hash(email) ^
       const DeepCollectionEquality().hash(password) ^
       const DeepCollectionEquality().hash(password) ^
       const DeepCollectionEquality().hash(isSubmitting) ^
       const DeepCollectionEquality().hash(isSubmitting) ^
-      const DeepCollectionEquality().hash(signInFailure);
+      const DeepCollectionEquality().hash(passwordError) ^
+      const DeepCollectionEquality().hash(emailError) ^
+      const DeepCollectionEquality().hash(successOrFail);
 
 
   @JsonKey(ignore: true)
   @JsonKey(ignore: true)
   @override
   @override
@@ -623,7 +667,9 @@ abstract class _SignInState implements SignInState {
           {String? email,
           {String? email,
           String? password,
           String? password,
           required bool isSubmitting,
           required bool isSubmitting,
-          required Option<Either<UserDetail, UserError>> signInFailure}) =
+          required Option<String> passwordError,
+          required Option<String> emailError,
+          required Option<Either<UserDetail, UserError>> successOrFail}) =
       _$_SignInState;
       _$_SignInState;
 
 
   @override
   @override
@@ -633,7 +679,11 @@ abstract class _SignInState implements SignInState {
   @override
   @override
   bool get isSubmitting => throw _privateConstructorUsedError;
   bool get isSubmitting => throw _privateConstructorUsedError;
   @override
   @override
-  Option<Either<UserDetail, UserError>> get signInFailure =>
+  Option<String> get passwordError => throw _privateConstructorUsedError;
+  @override
+  Option<String> get emailError => throw _privateConstructorUsedError;
+  @override
+  Option<Either<UserDetail, UserError>> get successOrFail =>
       throw _privateConstructorUsedError;
       throw _privateConstructorUsedError;
   @override
   @override
   @JsonKey(ignore: true)
   @JsonKey(ignore: true)

+ 0 - 0
app_flowy/lib/user/application/sign_up/sign_up_event.dart


+ 0 - 1
app_flowy/lib/user/application/sign_up/sign_up_state.dart

@@ -1 +0,0 @@
-

+ 7 - 0
app_flowy/lib/user/domain/i_auth.dart

@@ -1,5 +1,6 @@
 import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart';
 import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart';
 import 'package:dartz/dartz.dart';
 import 'package:dartz/dartz.dart';
+import 'package:flutter/material.dart';
 
 
 abstract class IAuth {
 abstract class IAuth {
   Future<Either<UserDetail, UserError>> signIn(String? email, String? password);
   Future<Either<UserDetail, UserError>> signIn(String? email, String? password);
@@ -8,3 +9,9 @@ abstract class IAuth {
 
 
   Future<Either<Unit, UserError>> signOut();
   Future<Either<Unit, UserError>> signOut();
 }
 }
+
+abstract class IAuthRouter {
+  void showHomeScreen(BuildContext context, UserDetail user);
+  void showSignUpScreen(BuildContext context);
+  void showForgetPasswordScreen(BuildContext context);
+}

+ 2 - 1
app_flowy/lib/user/infrastructure/deps_resolver.dart

@@ -6,10 +6,11 @@ import 'package:get_it/get_it.dart';
 
 
 class UserDepsResolver {
 class UserDepsResolver {
   static Future<void> resolve(GetIt getIt) async {
   static Future<void> resolve(GetIt getIt) async {
-    getIt.registerLazySingleton<AuthRepository>(() => AuthRepository());
+    getIt.registerFactory<AuthRepository>(() => AuthRepository());
 
 
     //Interface implementation
     //Interface implementation
     getIt.registerFactory<IAuth>(() => AuthImpl(repo: getIt<AuthRepository>()));
     getIt.registerFactory<IAuth>(() => AuthImpl(repo: getIt<AuthRepository>()));
+    getIt.registerFactory<IAuthRouter>(() => AuthRouterImpl());
 
 
     //Bloc
     //Bloc
     getIt.registerFactory<SignInBloc>(() => SignInBloc(getIt<IAuth>()));
     getIt.registerFactory<SignInBloc>(() => SignInBloc(getIt<IAuth>()));

+ 20 - 0
app_flowy/lib/user/infrastructure/i_auth_impl.dart

@@ -1,7 +1,10 @@
+import 'package:app_flowy/workspace/presentation/home/home_screen.dart';
 import 'package:dartz/dartz.dart';
 import 'package:dartz/dartz.dart';
+import 'package:flowy_infra_ui/widget/route/animation.dart';
 import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart';
 import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart';
 import 'package:app_flowy/user/domain/i_auth.dart';
 import 'package:app_flowy/user/domain/i_auth.dart';
 import 'package:app_flowy/user/infrastructure/repos/auth_repo.dart';
 import 'package:app_flowy/user/infrastructure/repos/auth_repo.dart';
+import 'package:flutter/material.dart';
 
 
 class AuthImpl extends IAuth {
 class AuthImpl extends IAuth {
   AuthRepository repo;
   AuthRepository repo;
@@ -26,3 +29,20 @@ class AuthImpl extends IAuth {
     return repo.signOut();
     return repo.signOut();
   }
   }
 }
 }
+
+class AuthRouterImpl extends IAuthRouter {
+  @override
+  void showForgetPasswordScreen(BuildContext context) {
+    // TODO: implement showForgetPasswordScreen
+  }
+
+  @override
+  void showHomeScreen(BuildContext context, UserDetail user) {
+    Navigator.of(context).push(PageRoutes.fade(() => HomeScreen(user)));
+  }
+
+  @override
+  void showSignUpScreen(BuildContext context) {
+    // TODO: implement showSignUpScreen
+  }
+}

+ 202 - 4
app_flowy/lib/user/presentation/sign_in/sign_in_screen.dart

@@ -1,19 +1,217 @@
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/user/application/sign_in/sign_in_bloc.dart';
 import 'package:app_flowy/user/application/sign_in/sign_in_bloc.dart';
-import 'package:app_flowy/user/presentation/sign_in/widgets/body.dart';
+import 'package:app_flowy/user/domain/i_auth.dart';
+import 'package:app_flowy/user/presentation/sign_in/widgets/background.dart';
+import 'package:flowy_infra_ui/widget/rounded_button.dart';
+import 'package:flowy_infra_ui/widget/rounded_input_field.dart';
+import 'package:flowy_infra_ui/widget/spacing.dart';
+import 'package:flowy_sdk/protobuf/flowy-user/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-user/user_detail.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:dartz/dartz.dart';
 
 
 class SignInScreen extends StatelessWidget {
 class SignInScreen extends StatelessWidget {
-  const SignInScreen({Key? key}) : super(key: key);
+  final IAuthRouter router;
+  const SignInScreen({Key? key, required this.router}) : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     return BlocProvider(
     return BlocProvider(
       create: (context) => getIt<SignInBloc>(),
       create: (context) => getIt<SignInBloc>(),
-      child: const Scaffold(
-        body: Body(),
+      child: BlocListener<SignInBloc, SignInState>(
+        listener: (context, state) {
+          state.successOrFail.fold(
+            () => null,
+            (result) => _handleSuccessOrFail(result, context),
+          );
+        },
+        child: Scaffold(
+          body: SignInForm(router: router),
+        ),
       ),
       ),
     );
     );
   }
   }
+
+  void _handleSuccessOrFail(
+      Either<UserDetail, UserError> result, BuildContext context) {
+    result.fold(
+      (user) => router.showHomeScreen(context, user),
+      (error) => _showErrorMessage(context, error.msg),
+    );
+  }
+
+  void _showErrorMessage(BuildContext context, String msg) {
+    ScaffoldMessenger.of(context).showSnackBar(
+      SnackBar(
+        content: Text(msg),
+      ),
+    );
+  }
+}
+
+class SignInForm extends StatelessWidget {
+  final IAuthRouter router;
+  const SignInForm({
+    Key? key,
+    required this.router,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Align(
+      alignment: Alignment.center,
+      child: SignInFormContainer(
+        children: [
+          const SignInTitle(
+            title: 'Login to Appflowy',
+            logoSize: Size(60, 60),
+          ),
+          const VSpace(30),
+          const EmailTextField(),
+          const PasswordTextField(),
+          ForgetPasswordButton(router: router),
+          const LoginButton(),
+          const VSpace(10),
+          SignUpPrompt(router: router),
+          if (context.read<SignInBloc>().state.isSubmitting) ...[
+            const SizedBox(height: 8),
+            const LinearProgressIndicator(value: null),
+          ]
+        ],
+      ),
+    );
+  }
+}
+
+class SignUpPrompt extends StatelessWidget {
+  const SignUpPrompt({
+    Key? key,
+    required this.router,
+  }) : super(key: key);
+
+  final IAuthRouter router;
+
+  @override
+  Widget build(BuildContext context) {
+    return Row(
+      children: [
+        const Text("Dont't have an account",
+            style: TextStyle(color: Colors.blueGrey, fontSize: 12)),
+        TextButton(
+          style: TextButton.styleFrom(
+            textStyle: const TextStyle(fontSize: 12),
+          ),
+          onPressed: () => router.showSignUpScreen(context),
+          child: const Text(
+            'Sign Up',
+            style: TextStyle(color: Colors.lightBlue),
+          ),
+        ),
+      ],
+      mainAxisAlignment: MainAxisAlignment.center,
+    );
+  }
+}
+
+class LoginButton extends StatelessWidget {
+  const LoginButton({
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return RoundedTextButton(
+      title: 'Login',
+      height: 45,
+      borderRadius: BorderRadius.circular(10),
+      color: Colors.lightBlue,
+      press: () {
+        context
+            .read<SignInBloc>()
+            .add(const SignInEvent.signedInWithUserEmailAndPassword());
+      },
+    );
+  }
+}
+
+class ForgetPasswordButton extends StatelessWidget {
+  const ForgetPasswordButton({
+    Key? key,
+    required this.router,
+  }) : super(key: key);
+
+  final IAuthRouter router;
+
+  @override
+  Widget build(BuildContext context) {
+    return TextButton(
+      style: TextButton.styleFrom(
+        textStyle: const TextStyle(fontSize: 12),
+      ),
+      onPressed: () => router.showForgetPasswordScreen(context),
+      child: const Text(
+        'Forgot Password?',
+        style: TextStyle(color: Colors.lightBlue),
+      ),
+    );
+  }
+}
+
+class PasswordTextField extends StatelessWidget {
+  const PasswordTextField({
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<SignInBloc, SignInState>(
+      buildWhen: (previous, current) =>
+          previous.passwordError != current.passwordError,
+      builder: (context, state) {
+        return RoundedInputField(
+          obscureText: true,
+          hintText: 'password',
+          normalBorderColor: Colors.green,
+          highlightBorderColor: Colors.red,
+          errorText: context
+              .read<SignInBloc>()
+              .state
+              .passwordError
+              .fold(() => "", (error) => error),
+          onChanged: (value) => context
+              .read<SignInBloc>()
+              .add(SignInEvent.passwordChanged(value)),
+        );
+      },
+    );
+  }
+}
+
+class EmailTextField extends StatelessWidget {
+  const EmailTextField({
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<SignInBloc, SignInState>(
+      buildWhen: (previous, current) =>
+          previous.emailError != current.emailError,
+      builder: (context, state) {
+        return RoundedInputField(
+          hintText: 'email',
+          normalBorderColor: Colors.green,
+          highlightBorderColor: Colors.red,
+          errorText: context
+              .read<SignInBloc>()
+              .state
+              .emailError
+              .fold(() => "", (error) => error),
+          onChanged: (value) =>
+              context.read<SignInBloc>().add(SignInEvent.emailChanged(value)),
+        );
+      },
+    );
+  }
 }
 }

+ 47 - 19
app_flowy/lib/user/presentation/sign_in/widgets/background.dart

@@ -1,29 +1,57 @@
+import 'package:flowy_infra_ui/widget/spacing.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 
 
-class SignInBackground extends StatelessWidget {
-  final Widget child;
-  const SignInBackground({
+class SignInFormContainer extends StatelessWidget {
+  final List<Widget> children;
+  const SignInFormContainer({
     Key? key,
     Key? key,
-    required this.child,
+    required this.children,
   }) : super(key: key);
   }) : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
-    var size = MediaQuery.of(context).size;
+    final size = MediaQuery.of(context).size;
     return SizedBox(
     return SizedBox(
-        height: size.height,
-        width: double.infinity,
-        child: Stack(
-          alignment: Alignment.center,
-          children: [
-            Image(
-                fit: BoxFit.cover,
-                width: size.width,
-                height: size.height,
-                image: const AssetImage(
-                    'assets/images/appflowy_launch_splash.jpg')),
-            child,
-          ],
-        ));
+      width: size.width * 0.3,
+      child: Column(
+        mainAxisAlignment: MainAxisAlignment.center,
+        children: children,
+      ),
+    );
+  }
+}
+
+class SignInTitle extends StatelessWidget {
+  final String title;
+  final Size logoSize;
+  const SignInTitle({
+    Key? key,
+    required this.title,
+    required this.logoSize,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return SizedBox(
+      child: Column(
+        mainAxisAlignment: MainAxisAlignment.center,
+        children: [
+          Image(
+              fit: BoxFit.cover,
+              width: logoSize.width,
+              height: logoSize.height,
+              image: const AssetImage('assets/images/app_flowy_logo.jpg')),
+          const VSpace(30),
+          Text(
+            title,
+            style: const TextStyle(
+              color: Colors.black,
+              fontWeight: FontWeight.bold,
+              fontSize: 20,
+            ),
+          )
+        ],
+      ),
+    );
   }
   }
 }
 }

+ 0 - 125
app_flowy/lib/user/presentation/sign_in/widgets/body.dart

@@ -1,125 +0,0 @@
-import 'package:app_flowy/startup/startup.dart';
-import 'package:app_flowy/user/application/sign_in/sign_in_bloc.dart';
-import 'package:app_flowy/user/presentation/sign_in/widgets/background.dart';
-import 'package:app_flowy/workspace/presentation/home/home_screen.dart';
-import 'package:dartz/dartz.dart';
-import 'package:flowy_infra_ui/widget/rounded_button.dart';
-import 'package:flowy_infra_ui/widget/rounded_input_field.dart';
-import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-
-class Body extends StatelessWidget {
-  const Body({Key? key}) : super(key: key);
-  @override
-  Widget build(BuildContext context) {
-    return BlocProvider(
-      create: (context) => getIt<SignInBloc>(),
-      child: const SignInBackground(
-        child: SignInForm(),
-      ),
-    );
-  }
-}
-
-class SignInForm extends StatelessWidget {
-  const SignInForm({
-    Key? key,
-  }) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    return BlocConsumer<SignInBloc, SignInState>(
-      listenWhen: (p, c) => p != c,
-      listener: (context, state) {
-        state.signInFailure.fold(
-          () {},
-          (result) => _handleStateErrors(result, context),
-        );
-      },
-      builder: (context, state) {
-        return SignInFormBackground(
-          children: [
-            const SizedBox(height: 30),
-            RoundedInputField(
-              icon: Icons.person,
-              hintText: 'email',
-              onChanged: (value) => context
-                  .read<SignInBloc>()
-                  .add(SignInEvent.emailChanged(value)),
-            ),
-            RoundedInputField(
-              icon: Icons.lock,
-              obscureText: true,
-              hintText: 'password',
-              onChanged: (value) => context
-                  .read<SignInBloc>()
-                  .add(SignInEvent.passwordChanged(value)),
-            ),
-            RoundedButton(
-              title: 'LOGIN',
-              press: () {
-                context
-                    .read<SignInBloc>()
-                    .add(const SignInEvent.signedInWithUserEmailAndPassword());
-              },
-            ),
-            if (state.isSubmitting) ...[
-              const SizedBox(height: 8),
-              const LinearProgressIndicator(value: null),
-            ]
-          ],
-        );
-      },
-    );
-  }
-
-  void _handleStateErrors(
-      Either<UserDetail, UserError> some, BuildContext context) {
-    some.fold(
-      (userDetail) => showHomeScreen(context, userDetail),
-      (result) => _showErrorMessage(context, result.msg),
-    );
-  }
-
-  void _showErrorMessage(BuildContext context, String msg) {
-    ScaffoldMessenger.of(context).showSnackBar(
-      SnackBar(
-        content: Text(msg),
-      ),
-    );
-  }
-
-  void showHomeScreen(BuildContext context, UserDetail userDetail) {
-    Navigator.pushReplacement(
-      context,
-      MaterialPageRoute(
-        builder: (context) {
-          return HomeScreen(userDetail);
-        },
-      ),
-    );
-  }
-}
-
-class SignInFormBackground extends StatelessWidget {
-  final List<Widget> children;
-  const SignInFormBackground({
-    Key? key,
-    required this.children,
-  }) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    final size = MediaQuery.of(context).size;
-
-    return Container(
-      width: size.width * 0.4,
-      alignment: Alignment.center,
-      child: SingleChildScrollView(
-        child: Column(
-            mainAxisAlignment: MainAxisAlignment.center, children: children),
-      ),
-    );
-  }
-}

+ 1 - 1
app_flowy/lib/welcome/application/welcome_bloc.dart

@@ -13,7 +13,7 @@ class WelcomeBloc extends Bloc<WelcomeEvent, WelcomeState> {
   Stream<WelcomeState> mapEventToState(WelcomeEvent event) async* {
   Stream<WelcomeState> mapEventToState(WelcomeEvent event) async* {
     yield* event.map(
     yield* event.map(
       getUser: (val) async* {
       getUser: (val) async* {
-        final authState = await authImpl.currentUserState();
+        final authState = await authImpl.currentUserDetail();
         yield state.copyWith(auth: authState);
         yield state.copyWith(auth: authState);
       },
       },
     );
     );

+ 1 - 1
app_flowy/lib/welcome/domain/i_welcome.dart

@@ -4,7 +4,7 @@ import 'package:flutter/widgets.dart';
 import 'auth_state.dart';
 import 'auth_state.dart';
 
 
 abstract class IWelcomeAuth {
 abstract class IWelcomeAuth {
-  Future<AuthState> currentUserState();
+  Future<AuthState> currentUserDetail();
 }
 }
 
 
 abstract class IWelcomeRoute {
 abstract class IWelcomeRoute {

+ 4 - 2
app_flowy/lib/welcome/infrastructure/i_welcome_impl.dart

@@ -1,3 +1,5 @@
+import 'package:app_flowy/startup/startup.dart';
+import 'package:app_flowy/user/domain/i_auth.dart';
 import 'package:app_flowy/user/presentation/sign_in/sign_in_screen.dart';
 import 'package:app_flowy/user/presentation/sign_in/sign_in_screen.dart';
 import 'package:app_flowy/welcome/domain/auth_state.dart';
 import 'package:app_flowy/welcome/domain/auth_state.dart';
 import 'package:app_flowy/welcome/domain/i_welcome.dart';
 import 'package:app_flowy/welcome/domain/i_welcome.dart';
@@ -11,7 +13,7 @@ export 'package:app_flowy/welcome/domain/i_welcome.dart';
 
 
 class WelcomeAuthImpl implements IWelcomeAuth {
 class WelcomeAuthImpl implements IWelcomeAuth {
   @override
   @override
-  Future<AuthState> currentUserState() {
+  Future<AuthState> currentUserDetail() {
     final result = UserEventGetStatus().send();
     final result = UserEventGetStatus().send();
     return result.then((result) {
     return result.then((result) {
       return result.fold(
       return result.fold(
@@ -34,6 +36,6 @@ class WelcomeRoute implements IWelcomeRoute {
 
 
   @override
   @override
   Widget pushSignInScreen() {
   Widget pushSignInScreen() {
-    return const SignInScreen();
+    return SignInScreen(router: getIt<IAuthRouter>());
   }
   }
 }
 }

+ 0 - 2
app_flowy/lib/workspace/application/doc/doc_bloc.dart

@@ -14,7 +14,6 @@ class DocBloc extends Bloc<DocEvent, DocState> {
   Stream<DocState> mapEventToState(DocEvent event) async* {
   Stream<DocState> mapEventToState(DocEvent event) async* {
     yield* event.map(
     yield* event.map(
       initial: (e) async* {},
       initial: (e) async* {},
-      save: (Save value) async* {},
       close: (Close value) async* {},
       close: (Close value) async* {},
     );
     );
   }
   }
@@ -23,7 +22,6 @@ class DocBloc extends Bloc<DocEvent, DocState> {
 @freezed
 @freezed
 abstract class DocEvent with _$DocEvent {
 abstract class DocEvent with _$DocEvent {
   const factory DocEvent.initial() = Initial;
   const factory DocEvent.initial() = Initial;
-  const factory DocEvent.save(String jsonStr) = Save;
   const factory DocEvent.close() = Close;
   const factory DocEvent.close() = Close;
 }
 }
 
 

+ 0 - 134
app_flowy/lib/workspace/application/doc/doc_bloc.freezed.dart

@@ -20,12 +20,6 @@ class _$DocEventTearOff {
     return const Initial();
     return const Initial();
   }
   }
 
 
-  Save save(String jsonStr) {
-    return Save(
-      jsonStr,
-    );
-  }
-
   Close close() {
   Close close() {
     return const Close();
     return const Close();
   }
   }
@@ -39,14 +33,12 @@ mixin _$DocEvent {
   @optionalTypeArgs
   @optionalTypeArgs
   TResult when<TResult extends Object?>({
   TResult when<TResult extends Object?>({
     required TResult Function() initial,
     required TResult Function() initial,
-    required TResult Function(String jsonStr) save,
     required TResult Function() close,
     required TResult Function() close,
   }) =>
   }) =>
       throw _privateConstructorUsedError;
       throw _privateConstructorUsedError;
   @optionalTypeArgs
   @optionalTypeArgs
   TResult maybeWhen<TResult extends Object?>({
   TResult maybeWhen<TResult extends Object?>({
     TResult Function()? initial,
     TResult Function()? initial,
-    TResult Function(String jsonStr)? save,
     TResult Function()? close,
     TResult Function()? close,
     required TResult orElse(),
     required TResult orElse(),
   }) =>
   }) =>
@@ -54,14 +46,12 @@ mixin _$DocEvent {
   @optionalTypeArgs
   @optionalTypeArgs
   TResult map<TResult extends Object?>({
   TResult map<TResult extends Object?>({
     required TResult Function(Initial value) initial,
     required TResult Function(Initial value) initial,
-    required TResult Function(Save value) save,
     required TResult Function(Close value) close,
     required TResult Function(Close value) close,
   }) =>
   }) =>
       throw _privateConstructorUsedError;
       throw _privateConstructorUsedError;
   @optionalTypeArgs
   @optionalTypeArgs
   TResult maybeMap<TResult extends Object?>({
   TResult maybeMap<TResult extends Object?>({
     TResult Function(Initial value)? initial,
     TResult Function(Initial value)? initial,
-    TResult Function(Save value)? save,
     TResult Function(Close value)? close,
     TResult Function(Close value)? close,
     required TResult orElse(),
     required TResult orElse(),
   }) =>
   }) =>
@@ -121,7 +111,6 @@ class _$Initial implements Initial {
   @optionalTypeArgs
   @optionalTypeArgs
   TResult when<TResult extends Object?>({
   TResult when<TResult extends Object?>({
     required TResult Function() initial,
     required TResult Function() initial,
-    required TResult Function(String jsonStr) save,
     required TResult Function() close,
     required TResult Function() close,
   }) {
   }) {
     return initial();
     return initial();
@@ -131,7 +120,6 @@ class _$Initial implements Initial {
   @optionalTypeArgs
   @optionalTypeArgs
   TResult maybeWhen<TResult extends Object?>({
   TResult maybeWhen<TResult extends Object?>({
     TResult Function()? initial,
     TResult Function()? initial,
-    TResult Function(String jsonStr)? save,
     TResult Function()? close,
     TResult Function()? close,
     required TResult orElse(),
     required TResult orElse(),
   }) {
   }) {
@@ -145,7 +133,6 @@ class _$Initial implements Initial {
   @optionalTypeArgs
   @optionalTypeArgs
   TResult map<TResult extends Object?>({
   TResult map<TResult extends Object?>({
     required TResult Function(Initial value) initial,
     required TResult Function(Initial value) initial,
-    required TResult Function(Save value) save,
     required TResult Function(Close value) close,
     required TResult Function(Close value) close,
   }) {
   }) {
     return initial(this);
     return initial(this);
@@ -155,7 +142,6 @@ class _$Initial implements Initial {
   @optionalTypeArgs
   @optionalTypeArgs
   TResult maybeMap<TResult extends Object?>({
   TResult maybeMap<TResult extends Object?>({
     TResult Function(Initial value)? initial,
     TResult Function(Initial value)? initial,
-    TResult Function(Save value)? save,
     TResult Function(Close value)? close,
     TResult Function(Close value)? close,
     required TResult orElse(),
     required TResult orElse(),
   }) {
   }) {
@@ -170,122 +156,6 @@ abstract class Initial implements DocEvent {
   const factory Initial() = _$Initial;
   const factory Initial() = _$Initial;
 }
 }
 
 
-/// @nodoc
-abstract class $SaveCopyWith<$Res> {
-  factory $SaveCopyWith(Save value, $Res Function(Save) then) =
-      _$SaveCopyWithImpl<$Res>;
-  $Res call({String jsonStr});
-}
-
-/// @nodoc
-class _$SaveCopyWithImpl<$Res> extends _$DocEventCopyWithImpl<$Res>
-    implements $SaveCopyWith<$Res> {
-  _$SaveCopyWithImpl(Save _value, $Res Function(Save) _then)
-      : super(_value, (v) => _then(v as Save));
-
-  @override
-  Save get _value => super._value as Save;
-
-  @override
-  $Res call({
-    Object? jsonStr = freezed,
-  }) {
-    return _then(Save(
-      jsonStr == freezed
-          ? _value.jsonStr
-          : jsonStr // ignore: cast_nullable_to_non_nullable
-              as String,
-    ));
-  }
-}
-
-/// @nodoc
-
-class _$Save implements Save {
-  const _$Save(this.jsonStr);
-
-  @override
-  final String jsonStr;
-
-  @override
-  String toString() {
-    return 'DocEvent.save(jsonStr: $jsonStr)';
-  }
-
-  @override
-  bool operator ==(dynamic other) {
-    return identical(this, other) ||
-        (other is Save &&
-            (identical(other.jsonStr, jsonStr) ||
-                const DeepCollectionEquality().equals(other.jsonStr, jsonStr)));
-  }
-
-  @override
-  int get hashCode =>
-      runtimeType.hashCode ^ const DeepCollectionEquality().hash(jsonStr);
-
-  @JsonKey(ignore: true)
-  @override
-  $SaveCopyWith<Save> get copyWith =>
-      _$SaveCopyWithImpl<Save>(this, _$identity);
-
-  @override
-  @optionalTypeArgs
-  TResult when<TResult extends Object?>({
-    required TResult Function() initial,
-    required TResult Function(String jsonStr) save,
-    required TResult Function() close,
-  }) {
-    return save(jsonStr);
-  }
-
-  @override
-  @optionalTypeArgs
-  TResult maybeWhen<TResult extends Object?>({
-    TResult Function()? initial,
-    TResult Function(String jsonStr)? save,
-    TResult Function()? close,
-    required TResult orElse(),
-  }) {
-    if (save != null) {
-      return save(jsonStr);
-    }
-    return orElse();
-  }
-
-  @override
-  @optionalTypeArgs
-  TResult map<TResult extends Object?>({
-    required TResult Function(Initial value) initial,
-    required TResult Function(Save value) save,
-    required TResult Function(Close value) close,
-  }) {
-    return save(this);
-  }
-
-  @override
-  @optionalTypeArgs
-  TResult maybeMap<TResult extends Object?>({
-    TResult Function(Initial value)? initial,
-    TResult Function(Save value)? save,
-    TResult Function(Close value)? close,
-    required TResult orElse(),
-  }) {
-    if (save != null) {
-      return save(this);
-    }
-    return orElse();
-  }
-}
-
-abstract class Save implements DocEvent {
-  const factory Save(String jsonStr) = _$Save;
-
-  String get jsonStr => throw _privateConstructorUsedError;
-  @JsonKey(ignore: true)
-  $SaveCopyWith<Save> get copyWith => throw _privateConstructorUsedError;
-}
-
 /// @nodoc
 /// @nodoc
 abstract class $CloseCopyWith<$Res> {
 abstract class $CloseCopyWith<$Res> {
   factory $CloseCopyWith(Close value, $Res Function(Close) then) =
   factory $CloseCopyWith(Close value, $Res Function(Close) then) =
@@ -324,7 +194,6 @@ class _$Close implements Close {
   @optionalTypeArgs
   @optionalTypeArgs
   TResult when<TResult extends Object?>({
   TResult when<TResult extends Object?>({
     required TResult Function() initial,
     required TResult Function() initial,
-    required TResult Function(String jsonStr) save,
     required TResult Function() close,
     required TResult Function() close,
   }) {
   }) {
     return close();
     return close();
@@ -334,7 +203,6 @@ class _$Close implements Close {
   @optionalTypeArgs
   @optionalTypeArgs
   TResult maybeWhen<TResult extends Object?>({
   TResult maybeWhen<TResult extends Object?>({
     TResult Function()? initial,
     TResult Function()? initial,
-    TResult Function(String jsonStr)? save,
     TResult Function()? close,
     TResult Function()? close,
     required TResult orElse(),
     required TResult orElse(),
   }) {
   }) {
@@ -348,7 +216,6 @@ class _$Close implements Close {
   @optionalTypeArgs
   @optionalTypeArgs
   TResult map<TResult extends Object?>({
   TResult map<TResult extends Object?>({
     required TResult Function(Initial value) initial,
     required TResult Function(Initial value) initial,
-    required TResult Function(Save value) save,
     required TResult Function(Close value) close,
     required TResult Function(Close value) close,
   }) {
   }) {
     return close(this);
     return close(this);
@@ -358,7 +225,6 @@ class _$Close implements Close {
   @optionalTypeArgs
   @optionalTypeArgs
   TResult maybeMap<TResult extends Object?>({
   TResult maybeMap<TResult extends Object?>({
     TResult Function(Initial value)? initial,
     TResult Function(Initial value)? initial,
-    TResult Function(Save value)? save,
     TResult Function(Close value)? close,
     TResult Function(Close value)? close,
     required TResult orElse(),
     required TResult orElse(),
   }) {
   }) {

+ 1 - 3
app_flowy/lib/workspace/application/menu/menu_bloc.dart

@@ -40,9 +40,7 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> {
   }
   }
 
 
   Stream<MenuState> _performActionOnCreateApp(CreateApp event) async* {
   Stream<MenuState> _performActionOnCreateApp(CreateApp event) async* {
-    await iWorkspaceImpl
-        .createApp(name: event.name, desc: event.desc)
-        .then((result) async* {
+    iWorkspaceImpl.createApp(name: event.name, desc: event.desc).then((result) {
       result.fold(
       result.fold(
         (app) => {},
         (app) => {},
         (error) async* {
         (error) async* {

+ 60 - 0
app_flowy/lib/workspace/application/menu/menu_user_bloc.dart

@@ -0,0 +1,60 @@
+import 'package:app_flowy/workspace/domain/i_user.dart';
+import 'package:flowy_sdk/protobuf/flowy-user/user_detail.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-workspace/workspace_create.pb.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'package:dartz/dartz.dart';
+
+part 'menu_user_bloc.freezed.dart';
+
+class MenuUserBloc extends Bloc<MenuUserEvent, MenuUserState> {
+  final IUser iUserImpl;
+
+  MenuUserBloc(this.iUserImpl) : super(MenuUserState.initial(iUserImpl.user));
+
+  @override
+  Stream<MenuUserState> mapEventToState(MenuUserEvent event) async* {
+    yield* event.map(
+      initial: (_) async* {
+        // fetch workspaces
+        iUserImpl.fetchWorkspaces().then((result) {
+          result.fold(
+            (workspaces) async* {
+              yield state.copyWith(workspaces: some(workspaces));
+            },
+            (error) async* {
+              yield state.copyWith(successOrFailure: right(error.msg));
+            },
+          );
+        });
+      },
+      fetchWorkspaces: (_FetchWorkspaces value) async* {},
+    );
+  }
+
+  @override
+  Future<void> close() async {
+    super.close();
+  }
+}
+
+@freezed
+class MenuUserEvent with _$MenuUserEvent {
+  const factory MenuUserEvent.initial() = _Initial;
+  const factory MenuUserEvent.fetchWorkspaces() = _FetchWorkspaces;
+}
+
+@freezed
+class MenuUserState with _$MenuUserState {
+  const factory MenuUserState({
+    required UserDetail user,
+    required Option<List<Workspace>> workspaces,
+    required Either<Unit, String> successOrFailure,
+  }) = _MenuUserState;
+
+  factory MenuUserState.initial(UserDetail user) => MenuUserState(
+        user: user,
+        workspaces: none(),
+        successOrFailure: left(unit),
+      );
+}

+ 432 - 0
app_flowy/lib/workspace/application/menu/menu_user_bloc.freezed.dart

@@ -0,0 +1,432 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides
+
+part of 'menu_user_bloc.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+T _$identity<T>(T value) => value;
+
+final _privateConstructorUsedError = UnsupportedError(
+    'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
+
+/// @nodoc
+class _$MenuUserEventTearOff {
+  const _$MenuUserEventTearOff();
+
+  _Initial initial() {
+    return const _Initial();
+  }
+
+  _FetchWorkspaces fetchWorkspaces() {
+    return const _FetchWorkspaces();
+  }
+}
+
+/// @nodoc
+const $MenuUserEvent = _$MenuUserEventTearOff();
+
+/// @nodoc
+mixin _$MenuUserEvent {
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function() initial,
+    required TResult Function() fetchWorkspaces,
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function()? initial,
+    TResult Function()? fetchWorkspaces,
+    required TResult orElse(),
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(_Initial value) initial,
+    required TResult Function(_FetchWorkspaces value) fetchWorkspaces,
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(_Initial value)? initial,
+    TResult Function(_FetchWorkspaces value)? fetchWorkspaces,
+    required TResult orElse(),
+  }) =>
+      throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $MenuUserEventCopyWith<$Res> {
+  factory $MenuUserEventCopyWith(
+          MenuUserEvent value, $Res Function(MenuUserEvent) then) =
+      _$MenuUserEventCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class _$MenuUserEventCopyWithImpl<$Res>
+    implements $MenuUserEventCopyWith<$Res> {
+  _$MenuUserEventCopyWithImpl(this._value, this._then);
+
+  final MenuUserEvent _value;
+  // ignore: unused_field
+  final $Res Function(MenuUserEvent) _then;
+}
+
+/// @nodoc
+abstract class _$InitialCopyWith<$Res> {
+  factory _$InitialCopyWith(_Initial value, $Res Function(_Initial) then) =
+      __$InitialCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class __$InitialCopyWithImpl<$Res> extends _$MenuUserEventCopyWithImpl<$Res>
+    implements _$InitialCopyWith<$Res> {
+  __$InitialCopyWithImpl(_Initial _value, $Res Function(_Initial) _then)
+      : super(_value, (v) => _then(v as _Initial));
+
+  @override
+  _Initial get _value => super._value as _Initial;
+}
+
+/// @nodoc
+
+class _$_Initial implements _Initial {
+  const _$_Initial();
+
+  @override
+  String toString() {
+    return 'MenuUserEvent.initial()';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) || (other is _Initial);
+  }
+
+  @override
+  int get hashCode => runtimeType.hashCode;
+
+  @override
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function() initial,
+    required TResult Function() fetchWorkspaces,
+  }) {
+    return initial();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function()? initial,
+    TResult Function()? fetchWorkspaces,
+    required TResult orElse(),
+  }) {
+    if (initial != null) {
+      return initial();
+    }
+    return orElse();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(_Initial value) initial,
+    required TResult Function(_FetchWorkspaces value) fetchWorkspaces,
+  }) {
+    return initial(this);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(_Initial value)? initial,
+    TResult Function(_FetchWorkspaces value)? fetchWorkspaces,
+    required TResult orElse(),
+  }) {
+    if (initial != null) {
+      return initial(this);
+    }
+    return orElse();
+  }
+}
+
+abstract class _Initial implements MenuUserEvent {
+  const factory _Initial() = _$_Initial;
+}
+
+/// @nodoc
+abstract class _$FetchWorkspacesCopyWith<$Res> {
+  factory _$FetchWorkspacesCopyWith(
+          _FetchWorkspaces value, $Res Function(_FetchWorkspaces) then) =
+      __$FetchWorkspacesCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class __$FetchWorkspacesCopyWithImpl<$Res>
+    extends _$MenuUserEventCopyWithImpl<$Res>
+    implements _$FetchWorkspacesCopyWith<$Res> {
+  __$FetchWorkspacesCopyWithImpl(
+      _FetchWorkspaces _value, $Res Function(_FetchWorkspaces) _then)
+      : super(_value, (v) => _then(v as _FetchWorkspaces));
+
+  @override
+  _FetchWorkspaces get _value => super._value as _FetchWorkspaces;
+}
+
+/// @nodoc
+
+class _$_FetchWorkspaces implements _FetchWorkspaces {
+  const _$_FetchWorkspaces();
+
+  @override
+  String toString() {
+    return 'MenuUserEvent.fetchWorkspaces()';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) || (other is _FetchWorkspaces);
+  }
+
+  @override
+  int get hashCode => runtimeType.hashCode;
+
+  @override
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function() initial,
+    required TResult Function() fetchWorkspaces,
+  }) {
+    return fetchWorkspaces();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function()? initial,
+    TResult Function()? fetchWorkspaces,
+    required TResult orElse(),
+  }) {
+    if (fetchWorkspaces != null) {
+      return fetchWorkspaces();
+    }
+    return orElse();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(_Initial value) initial,
+    required TResult Function(_FetchWorkspaces value) fetchWorkspaces,
+  }) {
+    return fetchWorkspaces(this);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(_Initial value)? initial,
+    TResult Function(_FetchWorkspaces value)? fetchWorkspaces,
+    required TResult orElse(),
+  }) {
+    if (fetchWorkspaces != null) {
+      return fetchWorkspaces(this);
+    }
+    return orElse();
+  }
+}
+
+abstract class _FetchWorkspaces implements MenuUserEvent {
+  const factory _FetchWorkspaces() = _$_FetchWorkspaces;
+}
+
+/// @nodoc
+class _$MenuUserStateTearOff {
+  const _$MenuUserStateTearOff();
+
+  _MenuUserState call(
+      {required UserDetail user,
+      required Option<List<Workspace>> workspaces,
+      required Either<Unit, String> successOrFailure}) {
+    return _MenuUserState(
+      user: user,
+      workspaces: workspaces,
+      successOrFailure: successOrFailure,
+    );
+  }
+}
+
+/// @nodoc
+const $MenuUserState = _$MenuUserStateTearOff();
+
+/// @nodoc
+mixin _$MenuUserState {
+  UserDetail get user => throw _privateConstructorUsedError;
+  Option<List<Workspace>> get workspaces => throw _privateConstructorUsedError;
+  Either<Unit, String> get successOrFailure =>
+      throw _privateConstructorUsedError;
+
+  @JsonKey(ignore: true)
+  $MenuUserStateCopyWith<MenuUserState> get copyWith =>
+      throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $MenuUserStateCopyWith<$Res> {
+  factory $MenuUserStateCopyWith(
+          MenuUserState value, $Res Function(MenuUserState) then) =
+      _$MenuUserStateCopyWithImpl<$Res>;
+  $Res call(
+      {UserDetail user,
+      Option<List<Workspace>> workspaces,
+      Either<Unit, String> successOrFailure});
+}
+
+/// @nodoc
+class _$MenuUserStateCopyWithImpl<$Res>
+    implements $MenuUserStateCopyWith<$Res> {
+  _$MenuUserStateCopyWithImpl(this._value, this._then);
+
+  final MenuUserState _value;
+  // ignore: unused_field
+  final $Res Function(MenuUserState) _then;
+
+  @override
+  $Res call({
+    Object? user = freezed,
+    Object? workspaces = freezed,
+    Object? successOrFailure = freezed,
+  }) {
+    return _then(_value.copyWith(
+      user: user == freezed
+          ? _value.user
+          : user // ignore: cast_nullable_to_non_nullable
+              as UserDetail,
+      workspaces: workspaces == freezed
+          ? _value.workspaces
+          : workspaces // ignore: cast_nullable_to_non_nullable
+              as Option<List<Workspace>>,
+      successOrFailure: successOrFailure == freezed
+          ? _value.successOrFailure
+          : successOrFailure // ignore: cast_nullable_to_non_nullable
+              as Either<Unit, String>,
+    ));
+  }
+}
+
+/// @nodoc
+abstract class _$MenuUserStateCopyWith<$Res>
+    implements $MenuUserStateCopyWith<$Res> {
+  factory _$MenuUserStateCopyWith(
+          _MenuUserState value, $Res Function(_MenuUserState) then) =
+      __$MenuUserStateCopyWithImpl<$Res>;
+  @override
+  $Res call(
+      {UserDetail user,
+      Option<List<Workspace>> workspaces,
+      Either<Unit, String> successOrFailure});
+}
+
+/// @nodoc
+class __$MenuUserStateCopyWithImpl<$Res>
+    extends _$MenuUserStateCopyWithImpl<$Res>
+    implements _$MenuUserStateCopyWith<$Res> {
+  __$MenuUserStateCopyWithImpl(
+      _MenuUserState _value, $Res Function(_MenuUserState) _then)
+      : super(_value, (v) => _then(v as _MenuUserState));
+
+  @override
+  _MenuUserState get _value => super._value as _MenuUserState;
+
+  @override
+  $Res call({
+    Object? user = freezed,
+    Object? workspaces = freezed,
+    Object? successOrFailure = freezed,
+  }) {
+    return _then(_MenuUserState(
+      user: user == freezed
+          ? _value.user
+          : user // ignore: cast_nullable_to_non_nullable
+              as UserDetail,
+      workspaces: workspaces == freezed
+          ? _value.workspaces
+          : workspaces // ignore: cast_nullable_to_non_nullable
+              as Option<List<Workspace>>,
+      successOrFailure: successOrFailure == freezed
+          ? _value.successOrFailure
+          : successOrFailure // ignore: cast_nullable_to_non_nullable
+              as Either<Unit, String>,
+    ));
+  }
+}
+
+/// @nodoc
+
+class _$_MenuUserState implements _MenuUserState {
+  const _$_MenuUserState(
+      {required this.user,
+      required this.workspaces,
+      required this.successOrFailure});
+
+  @override
+  final UserDetail user;
+  @override
+  final Option<List<Workspace>> workspaces;
+  @override
+  final Either<Unit, String> successOrFailure;
+
+  @override
+  String toString() {
+    return 'MenuUserState(user: $user, workspaces: $workspaces, successOrFailure: $successOrFailure)';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) ||
+        (other is _MenuUserState &&
+            (identical(other.user, user) ||
+                const DeepCollectionEquality().equals(other.user, user)) &&
+            (identical(other.workspaces, workspaces) ||
+                const DeepCollectionEquality()
+                    .equals(other.workspaces, workspaces)) &&
+            (identical(other.successOrFailure, successOrFailure) ||
+                const DeepCollectionEquality()
+                    .equals(other.successOrFailure, successOrFailure)));
+  }
+
+  @override
+  int get hashCode =>
+      runtimeType.hashCode ^
+      const DeepCollectionEquality().hash(user) ^
+      const DeepCollectionEquality().hash(workspaces) ^
+      const DeepCollectionEquality().hash(successOrFailure);
+
+  @JsonKey(ignore: true)
+  @override
+  _$MenuUserStateCopyWith<_MenuUserState> get copyWith =>
+      __$MenuUserStateCopyWithImpl<_MenuUserState>(this, _$identity);
+}
+
+abstract class _MenuUserState implements MenuUserState {
+  const factory _MenuUserState(
+      {required UserDetail user,
+      required Option<List<Workspace>> workspaces,
+      required Either<Unit, String> successOrFailure}) = _$_MenuUserState;
+
+  @override
+  UserDetail get user => throw _privateConstructorUsedError;
+  @override
+  Option<List<Workspace>> get workspaces => throw _privateConstructorUsedError;
+  @override
+  Either<Unit, String> get successOrFailure =>
+      throw _privateConstructorUsedError;
+  @override
+  @JsonKey(ignore: true)
+  _$MenuUserStateCopyWith<_MenuUserState> get copyWith =>
+      throw _privateConstructorUsedError;
+}

+ 18 - 0
app_flowy/lib/workspace/domain/i_user.dart

@@ -0,0 +1,18 @@
+import 'package:dartz/dartz.dart';
+import 'package:flowy_sdk/protobuf/flowy-user/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-user/user_detail.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-workspace/workspace_create.pb.dart';
+
+export 'package:flowy_sdk/protobuf/flowy-workspace/workspace_create.pb.dart';
+export 'package:flowy_sdk/protobuf/flowy-user/errors.pb.dart';
+export 'package:flowy_sdk/protobuf/flowy-user/user_detail.pb.dart';
+export 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
+
+abstract class IUser {
+  UserDetail get user;
+  Future<Either<UserDetail, UserError>> fetchUserDetail(String userId);
+  Future<Either<List<Workspace>, WorkspaceError>> fetchWorkspaces();
+  Future<Either<Unit, WorkspaceError>> deleteWorkspace(String workspaceId);
+  Future<Either<Unit, UserError>> signOut();
+}

+ 16 - 0
app_flowy/lib/workspace/domain/image.dart

@@ -0,0 +1,16 @@
+import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
+import 'package:flutter/material.dart';
+
+AssetImage assetImageForViewType(ViewType type) {
+  final imageName = imageNameForViewType(type);
+  return AssetImage('assets/images/$imageName');
+}
+
+String imageNameForViewType(ViewType type) {
+  switch (type) {
+    case ViewType.Doc:
+      return "file_icon.jpg";
+    default:
+      return "file_icon.jpg";
+  }
+}

+ 12 - 8
app_flowy/lib/workspace/domain/page_stack/page_stack.dart

@@ -23,7 +23,8 @@ class HomePageStack {
   }
   }
 
 
   void setStackView(HomeStackView? stackView) {
   void setStackView(HomeStackView? stackView) {
-    _bloc.add(PageStackEvent.setStackView(stackView ?? const BlankStackView()));
+    _bloc.add(PageStackEvent.setStackView(
+        stackView ?? const AnnouncementStackView()));
   }
   }
 
 
   Widget stackTopBar() {
   Widget stackTopBar() {
@@ -32,7 +33,7 @@ class HomePageStack {
       child: BlocBuilder<PageStackBloc, PageStackState>(
       child: BlocBuilder<PageStackBloc, PageStackState>(
         builder: (context, state) {
         builder: (context, state) {
           return HomeTopBar(
           return HomeTopBar(
-            title: state.stackView.title,
+            view: state.stackView,
           );
           );
         },
         },
       ),
       ),
@@ -61,14 +62,17 @@ List<Widget> _buildStackWidget(HomeStackView stackView) {
     if (viewType == stackView.type) {
     if (viewType == stackView.type) {
       switch (stackView.type) {
       switch (stackView.type) {
         case ViewType.Blank:
         case ViewType.Blank:
-          return BlankPage(stackView: stackView as BlankStackView);
+          return AnnouncementPage(
+              stackView: stackView as AnnouncementStackView);
         case ViewType.Doc:
         case ViewType.Doc:
-          return DocPage(stackView: stackView as DocPageStackView);
+          final docView = stackView as DocPageStackView;
+          return DocPage(key: ValueKey(docView.view.id), stackView: docView);
         default:
         default:
-          return BlankPage(stackView: stackView as BlankStackView);
+          return AnnouncementPage(
+              stackView: stackView as AnnouncementStackView);
       }
       }
     } else {
     } else {
-      return const BlankPage(stackView: BlankStackView());
+      return const AnnouncementPage(stackView: AnnouncementStackView());
     }
     }
   }).toList();
   }).toList();
 }
 }
@@ -76,11 +80,11 @@ List<Widget> _buildStackWidget(HomeStackView stackView) {
 HomeStackView stackViewFromView(View view) {
 HomeStackView stackViewFromView(View view) {
   switch (view.viewType) {
   switch (view.viewType) {
     case ViewType.Blank:
     case ViewType.Blank:
-      return const BlankStackView();
+      return const AnnouncementStackView();
     case ViewType.Doc:
     case ViewType.Doc:
       return DocPageStackView(view);
       return DocPageStackView(view);
     default:
     default:
-      return const BlankStackView();
+      return const AnnouncementStackView();
   }
   }
 }
 }
 
 

+ 1 - 1
app_flowy/lib/workspace/domain/page_stack/page_stack_bloc.dart

@@ -30,6 +30,6 @@ abstract class PageStackState implements _$PageStackState {
   }) = _PageStackState;
   }) = _PageStackState;
 
 
   factory PageStackState.initial() => const PageStackState(
   factory PageStackState.initial() => const PageStackState(
-        stackView: BlankStackView(),
+        stackView: AnnouncementStackView(),
       );
       );
 }
 }

+ 24 - 9
app_flowy/lib/workspace/infrastructure/deps_resolver.dart

@@ -2,6 +2,7 @@ import 'package:app_flowy/workspace/application/app/app_bloc.dart';
 import 'package:app_flowy/workspace/application/app/app_watch_bloc.dart';
 import 'package:app_flowy/workspace/application/app/app_watch_bloc.dart';
 import 'package:app_flowy/workspace/application/doc/doc_bloc.dart';
 import 'package:app_flowy/workspace/application/doc/doc_bloc.dart';
 import 'package:app_flowy/workspace/application/menu/menu_bloc.dart';
 import 'package:app_flowy/workspace/application/menu/menu_bloc.dart';
+import 'package:app_flowy/workspace/application/menu/menu_user_bloc.dart';
 import 'package:app_flowy/workspace/application/menu/menu_watch.dart';
 import 'package:app_flowy/workspace/application/menu/menu_watch.dart';
 import 'package:app_flowy/workspace/application/view/doc_watch_bloc.dart';
 import 'package:app_flowy/workspace/application/view/doc_watch_bloc.dart';
 import 'package:app_flowy/workspace/application/view/view_bloc.dart';
 import 'package:app_flowy/workspace/application/view/view_bloc.dart';
@@ -15,8 +16,11 @@ import 'package:app_flowy/workspace/infrastructure/repos/app_repo.dart';
 import 'package:app_flowy/workspace/infrastructure/repos/doc_repo.dart';
 import 'package:app_flowy/workspace/infrastructure/repos/doc_repo.dart';
 import 'package:app_flowy/workspace/infrastructure/repos/view_repo.dart';
 import 'package:app_flowy/workspace/infrastructure/repos/view_repo.dart';
 import 'package:app_flowy/workspace/infrastructure/repos/workspace_repo.dart';
 import 'package:app_flowy/workspace/infrastructure/repos/workspace_repo.dart';
+import 'package:flowy_editor/flowy_editor.dart';
+import 'package:flowy_sdk/protobuf/flowy-user/user_detail.pb.dart';
 import 'package:get_it/get_it.dart';
 import 'package:get_it/get_it.dart';
 
 
+import 'i_user_impl.dart';
 import 'i_view_impl.dart';
 import 'i_view_impl.dart';
 
 
 class HomeDepsResolver {
 class HomeDepsResolver {
@@ -31,10 +35,10 @@ class HomeDepsResolver {
         (appId, _) => IAppWatchImpl(repo: AppWatchRepository(appId: appId)));
         (appId, _) => IAppWatchImpl(repo: AppWatchRepository(appId: appId)));
 
 
     //workspace
     //workspace
-    getIt.registerFactoryParam<IWorkspace, String, void>((workspaceId, _) =>
-        IWorkspaceImpl(repo: WorkspaceRepo(workspaceId: workspaceId)));
-    getIt.registerFactoryParam<IWorkspaceWatch, String, void>((workspacId, _) =>
-        IWorkspaceWatchImpl(repo: WorkspaceWatchRepo(workspaceId: workspacId)));
+    getIt.registerFactoryParam<IWorkspace, UserDetail, void>(
+        (user, _) => IWorkspaceImpl(repo: WorkspaceRepo(user: user)));
+    getIt.registerFactoryParam<IWorkspaceWatch, UserDetail, void>(
+        (user, _) => IWorkspaceWatchImpl(repo: WorkspaceWatchRepo(user: user)));
 
 
     // View
     // View
     getIt.registerFactoryParam<IView, String, void>(
     getIt.registerFactoryParam<IView, String, void>(
@@ -46,12 +50,20 @@ class HomeDepsResolver {
     getIt.registerFactoryParam<IDoc, String, void>(
     getIt.registerFactoryParam<IDoc, String, void>(
         (docId, _) => IDocImpl(repo: DocRepository(docId: docId)));
         (docId, _) => IDocImpl(repo: DocRepository(docId: docId)));
 
 
-    //Bloc
-    getIt.registerFactoryParam<MenuBloc, String, void>(
-        (workspaceId, _) => MenuBloc(getIt<IWorkspace>(param1: workspaceId)));
-    getIt.registerFactoryParam<MenuWatchBloc, String, void>((workspaceId, _) =>
-        MenuWatchBloc(getIt<IWorkspaceWatch>(param1: workspaceId)));
+    // User
+    getIt.registerFactoryParam<IUser, UserDetail, void>(
+        (user, _) => IUserImpl(repo: UserRepo(user: user)));
 
 
+    //Menu Bloc
+    getIt.registerFactoryParam<MenuBloc, UserDetail, void>(
+        (user, _) => MenuBloc(getIt<IWorkspace>(param1: user)));
+    getIt.registerFactoryParam<MenuWatchBloc, UserDetail, void>(
+        (user, _) => MenuWatchBloc(getIt<IWorkspaceWatch>(param1: user)));
+
+    getIt.registerFactoryParam<MenuUserBloc, UserDetail, void>(
+        (user, _) => MenuUserBloc(getIt<IUser>(param1: user)));
+
+    //
     getIt.registerFactoryParam<AppBloc, String, void>(
     getIt.registerFactoryParam<AppBloc, String, void>(
         (appId, _) => AppBloc(getIt<IApp>(param1: appId)));
         (appId, _) => AppBloc(getIt<IApp>(param1: appId)));
     getIt.registerFactoryParam<AppWatchBloc, String, void>(
     getIt.registerFactoryParam<AppWatchBloc, String, void>(
@@ -66,6 +78,9 @@ class HomeDepsResolver {
     getIt.registerFactoryParam<DocBloc, String, void>(
     getIt.registerFactoryParam<DocBloc, String, void>(
         (docId, _) => DocBloc(getIt<IDoc>(param1: docId)));
         (docId, _) => DocBloc(getIt<IDoc>(param1: docId)));
 
 
+    // editor
+    getIt.registerFactoryParam<EditorPersistence, String, void>(
+        (docId, _) => EditorPersistenceImpl(repo: DocRepository(docId: docId)));
     // getIt.registerFactoryParam<ViewBloc, String, void>(
     // getIt.registerFactoryParam<ViewBloc, String, void>(
     //     (viewId, _) => ViewBloc(iViewImpl: getIt<IView>(param1: viewId)));
     //     (viewId, _) => ViewBloc(iViewImpl: getIt<IView>(param1: viewId)));
   }
   }

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

@@ -35,8 +35,7 @@ class IAppImpl extends IApp {
         final result = await docRepo.createDoc(
         final result = await docRepo.createDoc(
             name: view.name, desc: "", text: "[{\"insert\":\"\\n\"}]");
             name: view.name, desc: "", text: "[{\"insert\":\"\\n\"}]");
         return result.fold((l) => left(view), (r) {
         return result.fold((l) => left(view), (r) {
-          return right(
-              WorkspaceError(code: WorkspaceErrorCode.Unknown, msg: r.msg));
+          return right(WorkspaceError(code: WsErrCode.Unknown, msg: r.msg));
         });
         });
       default:
       default:
         return left(view);
         return left(view);

+ 22 - 3
app_flowy/lib/workspace/infrastructure/i_doc_impl.dart

@@ -1,10 +1,11 @@
 import 'dart:convert';
 import 'dart:convert';
 
 
-import 'package:app_flowy/workspace/domain/i_doc.dart';
-import 'package:app_flowy/workspace/infrastructure/repos/doc_repo.dart';
+import 'package:dartz/dartz.dart';
 import 'package:flowy_editor/flowy_editor.dart';
 import 'package:flowy_editor/flowy_editor.dart';
 import 'package:flowy_sdk/protobuf/flowy-editor/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-editor/errors.pb.dart';
-import 'package:dartz/dartz.dart';
+
+import 'package:app_flowy/workspace/domain/i_doc.dart';
+import 'package:app_flowy/workspace/infrastructure/repos/doc_repo.dart';
 
 
 class IDocImpl extends IDoc {
 class IDocImpl extends IDoc {
   DocRepository repo;
   DocRepository repo;
@@ -49,3 +50,21 @@ class IDocImpl extends IDoc {
     return document;
     return document;
   }
   }
 }
 }
+
+class EditorPersistenceImpl extends EditorPersistence {
+  DocRepository repo;
+  EditorPersistenceImpl({
+    required this.repo,
+  });
+
+  @override
+  Future<bool> save(List<dynamic> jsonList) async {
+    final json = jsonEncode(jsonList);
+    return repo.updateDoc(text: json).then((result) {
+      return result.fold(
+        (l) => true,
+        (r) => false,
+      );
+    });
+  }
+}

+ 36 - 0
app_flowy/lib/workspace/infrastructure/i_user_impl.dart

@@ -0,0 +1,36 @@
+import 'package:dartz/dartz.dart';
+
+import 'package:app_flowy/workspace/domain/i_user.dart';
+import 'package:app_flowy/workspace/infrastructure/repos/user_repo.dart';
+export 'package:app_flowy/workspace/domain/i_user.dart';
+export 'package:app_flowy/workspace/infrastructure/repos/user_repo.dart';
+
+class IUserImpl extends IUser {
+  UserRepo repo;
+  IUserImpl({
+    required this.repo,
+  });
+
+  @override
+  Future<Either<Unit, WorkspaceError>> deleteWorkspace(String workspaceId) {
+    return repo.deleteWorkspace(workspaceId: workspaceId);
+  }
+
+  @override
+  Future<Either<UserDetail, UserError>> fetchUserDetail(String userId) {
+    return repo.fetchUserDetail(userId: userId);
+  }
+
+  @override
+  Future<Either<Unit, UserError>> signOut() {
+    return repo.signOut();
+  }
+
+  @override
+  UserDetail get user => repo.user;
+
+  @override
+  Future<Either<List<Workspace>, WorkspaceError>> fetchWorkspaces() {
+    return repo.fetchWorkspaces();
+  }
+}

+ 36 - 0
app_flowy/lib/workspace/infrastructure/repos/user_repo.dart

@@ -0,0 +1,36 @@
+import 'package:dartz/dartz.dart';
+import 'package:flowy_sdk/dispatch/dispatch.dart';
+import 'package:flowy_sdk/protobuf/flowy-user/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-user/user_detail.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-workspace/workspace_create.pb.dart';
+
+class UserRepo {
+  final UserDetail user;
+  UserRepo({
+    required this.user,
+  });
+
+  Future<Either<UserDetail, UserError>> fetchUserDetail(
+      {required String userId}) {
+    return UserEventGetStatus().send();
+  }
+
+  Future<Either<Unit, WorkspaceError>> deleteWorkspace(
+      {required String workspaceId}) {
+    throw UnimplementedError();
+  }
+
+  Future<Either<Unit, UserError>> signOut() {
+    return UserEventSignOut().send();
+  }
+
+  Future<Either<List<Workspace>, WorkspaceError>> fetchWorkspaces() {
+    return WorkspaceEventReadAllWorkspace().send().then((result) {
+      return result.fold(
+        (workspaces) => left(workspaces.items),
+        (r) => right(r),
+      );
+    });
+  }
+}

+ 8 - 7
app_flowy/lib/workspace/infrastructure/repos/workspace_repo.dart

@@ -5,6 +5,7 @@ import 'package:dartz/dartz.dart';
 import 'package:flowy_infra/flowy_logger.dart';
 import 'package:flowy_infra/flowy_logger.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
 import 'package:flowy_sdk/protobuf/flowy-observable/subject.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-observable/subject.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-user/user_detail.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/app_create.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/app_create.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/observable.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/observable.pb.dart';
@@ -13,9 +14,9 @@ import 'package:flowy_sdk/protobuf/flowy-workspace/workspace_query.pb.dart';
 import 'package:flowy_sdk/rust_stream.dart';
 import 'package:flowy_sdk/rust_stream.dart';
 
 
 class WorkspaceRepo {
 class WorkspaceRepo {
-  String workspaceId;
+  UserDetail user;
   WorkspaceRepo({
   WorkspaceRepo({
-    required this.workspaceId,
+    required this.user,
   });
   });
 
 
   Future<Either<App, WorkspaceError>> createApp(String appName, String desc) {
   Future<Either<App, WorkspaceError>> createApp(String appName, String desc) {
@@ -38,7 +39,7 @@ class WorkspaceRepo {
   Future<Either<Workspace, WorkspaceError>> getWorkspace(
   Future<Either<Workspace, WorkspaceError>> getWorkspace(
       {bool readApps = false}) {
       {bool readApps = false}) {
     final request = QueryWorkspaceRequest.create()
     final request = QueryWorkspaceRequest.create()
-      ..workspaceId = workspaceId
+      ..workspaceId = user.workspace
       ..readApps = readApps;
       ..readApps = readApps;
 
 
     return WorkspaceEventGetWorkspace(request).send().then((result) {
     return WorkspaceEventGetWorkspace(request).send().then((result) {
@@ -54,13 +55,13 @@ class WorkspaceWatchRepo {
   StreamSubscription<ObservableSubject>? _subscription;
   StreamSubscription<ObservableSubject>? _subscription;
   WorkspaceAddAppCallback? _addAppCallback;
   WorkspaceAddAppCallback? _addAppCallback;
   WorkspaceUpdatedCallback? _updatedCallback;
   WorkspaceUpdatedCallback? _updatedCallback;
-  final String workspaceId;
+  final UserDetail user;
   late WorkspaceRepo _repo;
   late WorkspaceRepo _repo;
 
 
   WorkspaceWatchRepo({
   WorkspaceWatchRepo({
-    required this.workspaceId,
+    required this.user,
   }) {
   }) {
-    _repo = WorkspaceRepo(workspaceId: workspaceId);
+    _repo = WorkspaceRepo(user: user);
   }
   }
 
 
   void startWatching(
   void startWatching(
@@ -70,7 +71,7 @@ class WorkspaceWatchRepo {
     _updatedCallback = updatedCallback;
     _updatedCallback = updatedCallback;
 
 
     _subscription = RustStreamReceiver.listen((observable) {
     _subscription = RustStreamReceiver.listen((observable) {
-      if (observable.subjectId != workspaceId) {
+      if (observable.subjectId != user.workspace) {
         return;
         return;
       }
       }
 
 

+ 85 - 63
app_flowy/lib/workspace/presentation/app/app_widget.dart

@@ -1,20 +1,32 @@
 import 'package:app_flowy/workspace/application/app/app_bloc.dart';
 import 'package:app_flowy/workspace/application/app/app_bloc.dart';
 import 'package:app_flowy/workspace/application/app/app_watch_bloc.dart';
 import 'package:app_flowy/workspace/application/app/app_watch_bloc.dart';
 import 'package:app_flowy/workspace/presentation/app/view_list.dart';
 import 'package:app_flowy/workspace/presentation/app/view_list.dart';
-import 'package:app_flowy/workspace/presentation/widgets/menu/menu_size.dart';
+import 'package:app_flowy/workspace/presentation/widgets/menu/menu_list.dart';
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/startup/startup.dart';
 import 'package:expandable/expandable.dart';
 import 'package:expandable/expandable.dart';
-import 'package:flowy_infra/size.dart';
 import 'package:flowy_infra_ui/widget/error_page.dart';
 import 'package:flowy_infra_ui/widget/error_page.dart';
+import 'package:flowy_infra_ui/widget/spacing.dart';
+import 'package:flowy_infra_ui/style_widget/styled_text_button.dart';
+import 'package:flowy_infra_ui/style_widget/styled_icon_button.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/app_create.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/app_create.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:dartz/dartz.dart';
 import 'package:dartz/dartz.dart';
 
 
-class AppWidget extends StatelessWidget {
+class AppWidgetSize {
+  static double expandedIconSize = 24;
+  static double expandedIconRightSpace = 8;
+
+  static double scale = 1;
+
+  static double get expandedPadding =>
+      expandedIconSize * scale + expandedIconRightSpace;
+}
+
+class AppWidget extends MenuItem {
   final App app;
   final App app;
-  const AppWidget(this.app, {Key? key}) : super(key: key);
+  AppWidget(this.app, {Key? key}) : super(key: ValueKey(app.id));
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
@@ -35,11 +47,9 @@ class AppWidget extends StatelessWidget {
         builder: (context, state) {
         builder: (context, state) {
           final child = state.map(
           final child = state.map(
             initial: (_) => BlocBuilder<AppBloc, AppState>(
             initial: (_) => BlocBuilder<AppBloc, AppState>(
-              builder: (context, state) {
-                return ViewList(state.views);
-              },
+              builder: (context, state) => _renderViewList(state.views),
             ),
             ),
-            loadViews: (s) => ViewList(some(s.views)),
+            loadViews: (s) => _renderViewList(some(s.views)),
             loadFail: (s) => FlowyErrorPage(s.error.toString()),
             loadFail: (s) => FlowyErrorPage(s.error.toString()),
           );
           );
 
 
@@ -54,31 +64,36 @@ class AppWidget extends StatelessWidget {
       child: ScrollOnExpand(
       child: ScrollOnExpand(
         scrollOnExpand: true,
         scrollOnExpand: true,
         scrollOnCollapse: false,
         scrollOnCollapse: false,
-        child: Card(
-          clipBehavior: Clip.antiAlias,
-          child: Column(
-            children: <Widget>[
-              ExpandablePanel(
-                theme: const ExpandableThemeData(
-                  headerAlignment: ExpandablePanelHeaderAlignment.center,
-                  tapBodyToExpand: false,
-                  tapBodyToCollapse: false,
-                  iconPadding: EdgeInsets.zero,
-                  hasIcon: false,
-                ),
-                header: AppHeader(app),
-                expanded: Padding(
-                  padding: EdgeInsets.only(left: Sizes.iconMed),
-                  child: child,
-                ),
-                collapsed: const SizedBox(),
+        child: Column(
+          children: <Widget>[
+            ExpandablePanel(
+              theme: const ExpandableThemeData(
+                headerAlignment: ExpandablePanelHeaderAlignment.center,
+                tapBodyToExpand: false,
+                tapBodyToCollapse: false,
+                tapHeaderToExpand: false,
+                iconPadding: EdgeInsets.zero,
+                hasIcon: false,
               ),
               ),
-            ],
-          ),
+              header: AppHeader(app),
+              expanded: child,
+              collapsed: const SizedBox(),
+            ),
+          ],
         ),
         ),
       ),
       ),
     );
     );
   }
   }
+
+  Widget _renderViewList(Option<List<View>> views) {
+    return Padding(
+      padding: const EdgeInsets.symmetric(vertical: 8),
+      child: ViewList(views),
+    );
+  }
+
+  @override
+  MenuItemType get type => MenuItemType.app;
 }
 }
 
 
 class AppHeader extends StatelessWidget {
 class AppHeader extends StatelessWidget {
@@ -90,46 +105,53 @@ class AppHeader extends StatelessWidget {
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
-    return Container(
-      color: Colors.white,
-      child: Padding(
-        padding: EdgeInsets.symmetric(vertical: Insets.m),
-        child: Row(
-          mainAxisAlignment: MainAxisAlignment.center,
-          crossAxisAlignment: CrossAxisAlignment.center,
-          children: [
-            ExpandableIcon(
-              theme: ExpandableThemeData(
-                expandIcon: Icons.arrow_right,
-                collapseIcon: Icons.arrow_drop_down,
-                iconColor: Colors.black,
-                iconSize: HomeMenuSize.collapseIconSize,
-                iconPadding: EdgeInsets.zero,
-                hasIcon: false,
-              ),
-            ),
-            Expanded(
-              child: Text(app.name),
+    return Row(
+      mainAxisAlignment: MainAxisAlignment.center,
+      crossAxisAlignment: CrossAxisAlignment.center,
+      children: [
+        InkWell(
+          onTap: () {
+            ExpandableController.of(context,
+                    rebuildOnChange: false, required: true)
+                ?.toggle();
+          },
+          child: ExpandableIcon(
+            theme: ExpandableThemeData(
+              expandIcon: Icons.arrow_drop_up,
+              collapseIcon: Icons.arrow_drop_down,
+              iconColor: Colors.black,
+              iconSize: AppWidgetSize.expandedIconSize,
+              iconPadding: EdgeInsets.zero,
+              hasIcon: false,
             ),
             ),
-            SizedBox(
-              height: HomeMenuSize.createViewButtonSize,
-              child: createViewPopupMenu(context),
-            ),
-          ],
+          ),
         ),
         ),
-      ),
+        HSpace(AppWidgetSize.expandedIconRightSpace),
+        Expanded(
+          child: StyledTextButton(
+            app.name,
+            onPressed: () {
+              debugPrint('show app document');
+            },
+          ),
+        ),
+        StyledIconButton(
+          icon: const Icon(Icons.add),
+          onPressed: () {
+            debugPrint('add view');
+          },
+        ),
+      ],
     );
     );
   }
   }
 
 
-  Widget createViewPopupMenu(BuildContext context) {
-    return PopupMenuButton(
-        iconSize: 24,
-        tooltip: 'create new view',
-        icon: const Icon(Icons.add),
-        padding: EdgeInsets.zero,
-        onSelected: (viewType) => _createView(viewType as ViewType, context),
-        itemBuilder: (context) => menuItemBuilder());
-  }
+  // return PopupMenuButton(
+  //     iconSize: 20,
+  //     tooltip: 'create new view',
+  //     icon: const Icon(Icons.add),
+  //     padding: EdgeInsets.zero,
+  //     onSelected: (viewType) => _createView(viewType as ViewType, context),
+  //     itemBuilder: (context) => menuItemBuilder());
 
 
   List<PopupMenuEntry> menuItemBuilder() {
   List<PopupMenuEntry> menuItemBuilder() {
     return ViewType.values
     return ViewType.values

+ 10 - 12
app_flowy/lib/workspace/presentation/app/view_list.dart

@@ -1,11 +1,9 @@
 import 'package:app_flowy/workspace/presentation/view/view_widget.dart';
 import 'package:app_flowy/workspace/presentation/view/view_widget.dart';
 import 'package:flowy_infra/flowy_logger.dart';
 import 'package:flowy_infra/flowy_logger.dart';
-import 'package:flowy_infra/size.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:dartz/dartz.dart';
 import 'package:dartz/dartz.dart';
-import 'package:styled_widget/styled_widget.dart';
 
 
 class ViewList extends StatelessWidget {
 class ViewList extends StatelessWidget {
   final Option<List<View>> views;
   final Option<List<View>> views;
@@ -15,24 +13,24 @@ class ViewList extends StatelessWidget {
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     Log.info('ViewList build');
     Log.info('ViewList build');
     return views.fold(
     return views.fold(
-      () => const SizedBox(
-        height: 10,
-      ),
+      () => const SizedBox(),
       (views) {
       (views) {
         return Column(
         return Column(
-          children: buildViewWidgets(views),
-        ).padding(vertical: Insets.sm);
+          children: _renderViews(views),
+        );
       },
       },
     );
     );
   }
   }
 
 
-  List<ViewWidget> buildViewWidgets(List<View> views) {
+  List<Widget> _renderViews(List<View> views) {
     var targetViews = views.map((view) {
     var targetViews = views.map((view) {
-      return ViewWidget(
-        icon: const Icon(Icons.file_copy),
-        view: view,
+      return Padding(
+        padding: const EdgeInsets.symmetric(vertical: 2),
+        child: ViewWidget(
+          view: view,
+        ),
       );
       );
-    }).toList(growable: true);
+    }).toList(growable: false);
     return targetViews;
     return targetViews;
   }
   }
 
 

+ 17 - 1
app_flowy/lib/workspace/presentation/doc/doc_page.dart

@@ -6,6 +6,7 @@ import 'package:flowy_infra_ui/widget/error_page.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:flowy_infra_ui/style_widget/styled_progress_indicator.dart';
 
 
 class DocPage extends HomeStackWidget {
 class DocPage extends HomeStackWidget {
   const DocPage({Key? key, required DocPageStackView stackView})
   const DocPage({Key? key, required DocPageStackView stackView})
@@ -29,13 +30,28 @@ class _DocPageState extends State<DocPage> {
           BlocBuilder<DocWatchBloc, DocWatchState>(builder: (context, state) {
           BlocBuilder<DocWatchBloc, DocWatchState>(builder: (context, state) {
         assert(widget.stackView is DocPageStackView);
         assert(widget.stackView is DocPageStackView);
         return state.map(
         return state.map(
-          loading: (_) => const CircularProgressIndicator.adaptive(),
+          loading: (_) => const StyledProgressIndicator(),
           loadDoc: (s) => EditorWdiget(doc: s.doc),
           loadDoc: (s) => EditorWdiget(doc: s.doc),
           loadFail: (s) => FlowyErrorPage(s.error.toString()),
           loadFail: (s) => FlowyErrorPage(s.error.toString()),
         );
         );
       }),
       }),
     );
     );
   }
   }
+
+  @override
+  void dispose() {
+    super.dispose();
+  }
+
+  @override
+  void deactivate() {
+    super.deactivate();
+  }
+
+  @override
+  void didUpdateWidget(covariant DocPage oldWidget) {
+    super.didUpdateWidget(oldWidget);
+  }
 }
 }
 
 
 class DocPageStackView extends HomeStackView {
 class DocPageStackView extends HomeStackView {

+ 14 - 7
app_flowy/lib/workspace/presentation/doc/editor_widget.dart

@@ -16,6 +16,7 @@ class EditorWdiget extends StatelessWidget {
     controller = EditorController(
     controller = EditorController(
       document: doc.data,
       document: doc.data,
       selection: const TextSelection.collapsed(offset: 0),
       selection: const TextSelection.collapsed(offset: 0),
+      persistence: getIt<EditorPersistence>(param1: doc.info.id),
     );
     );
   }
   }
 
 
@@ -23,12 +24,16 @@ class EditorWdiget extends StatelessWidget {
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     return BlocProvider(
     return BlocProvider(
       create: (context) => getIt<DocBloc>(param1: doc.info.id),
       create: (context) => getIt<DocBloc>(param1: doc.info.id),
-      child: Column(
-        mainAxisAlignment: MainAxisAlignment.spaceBetween,
-        children: [
-          _renderEditor(controller),
-          _renderToolbar(controller),
-        ],
+      child: BlocBuilder<DocBloc, DocState>(
+        builder: (ctx, state) {
+          return Column(
+            mainAxisAlignment: MainAxisAlignment.spaceBetween,
+            children: [
+              _renderEditor(controller),
+              _renderToolbar(controller),
+            ],
+          );
+        },
       ),
       ),
     );
     );
   }
   }
@@ -45,7 +50,9 @@ class EditorWdiget extends StatelessWidget {
       scrollBottomInset: 0,
       scrollBottomInset: 0,
       scrollController: ScrollController(),
       scrollController: ScrollController(),
     );
     );
-    return Expanded(child: editor);
+    return Expanded(
+      child: Padding(padding: const EdgeInsets.all(10), child: editor),
+    );
   }
   }
 
 
   Widget _renderToolbar(EditorController controller) {
   Widget _renderToolbar(EditorController controller) {

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

@@ -23,7 +23,7 @@ class HomeLayout {
 
 
     showEditPannel = homeBlocState.editContext.isSome();
     showEditPannel = homeBlocState.editContext.isSome();
 
 
-    menuWidth = Sizes.sideBarSm;
+    menuWidth = Sizes.sideBarMed;
     if (context.widthPx >= PageBreaks.desktop) {
     if (context.widthPx >= PageBreaks.desktop) {
       menuWidth = Sizes.sideBarLg;
       menuWidth = Sizes.sideBarLg;
     }
     }

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

@@ -14,8 +14,8 @@ import 'home_layout.dart';
 
 
 class HomeScreen extends StatelessWidget {
 class HomeScreen extends StatelessWidget {
   static GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
   static GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
-  final UserDetail userDetail;
-  const HomeScreen(this.userDetail, {Key? key}) : super(key: key);
+  final UserDetail user;
+  const HomeScreen(this.user, {Key? key}) : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
@@ -31,7 +31,8 @@ class HomeScreen extends StatelessWidget {
           buildWhen: (previous, current) => previous != current,
           buildWhen: (previous, current) => previous != current,
           builder: (context, state) {
           builder: (context, state) {
             return StyledContainer(
             return StyledContainer(
-              Theme.of(context).colorScheme.background,
+              Theme.of(context).colorScheme.surface,
+              // Colors.white,
               child: _buildBody(
               child: _buildBody(
                   state, context.read<HomeBloc>().state.forceCollapse),
                   state, context.read<HomeBloc>().state.forceCollapse),
             );
             );
@@ -74,7 +75,7 @@ class HomeScreen extends StatelessWidget {
       isCollapseChanged: (isCollapse) {
       isCollapseChanged: (isCollapse) {
         homeBloc.add(HomeEvent.forceCollapse(isCollapse));
         homeBloc.add(HomeEvent.forceCollapse(isCollapse));
       },
       },
-      workspaceId: userDetail.workspace,
+      user: user,
     );
     );
     homeMenu = RepaintBoundary(child: homeMenu);
     homeMenu = RepaintBoundary(child: homeMenu);
     homeMenu = FocusTraversalGroup(child: homeMenu);
     homeMenu = FocusTraversalGroup(child: homeMenu);

+ 51 - 16
app_flowy/lib/workspace/presentation/view/view_widget.dart

@@ -1,32 +1,67 @@
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/startup/startup.dart';
+import 'package:app_flowy/workspace/domain/image.dart';
 import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
 import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
+import 'package:app_flowy/workspace/presentation/app/app_widget.dart';
+import 'package:flowy_infra_ui/widget/spacing.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
+import 'package:flowy_infra_ui/style_widget/styled_icon_button.dart';
+import 'package:flowy_infra_ui/style_widget/styled_hover.dart';
 
 
 class ViewWidget extends StatelessWidget {
 class ViewWidget extends StatelessWidget {
   final View view;
   final View view;
-  final Widget icon;
-  const ViewWidget({Key? key, required this.view, required this.icon})
-      : super(key: key);
+  const ViewWidget({Key? key, required this.view}) : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
-    return InkWell(onTap: _openView(context), child: buildContent());
+    return InkWell(
+      onTap: _openView(context),
+      child: StyledHover(
+        color: Colors.grey.shade300,
+        borderRadius: BorderRadius.circular(8),
+        builder: (context, onHover) => _render(context, onHover),
+      ),
+    );
   }
   }
 
 
-  Row buildContent() {
-    return Row(
-      children: [
-        icon,
-        const SizedBox(
-          width: 4,
+  Widget _render(BuildContext context, bool onHover) {
+    const double width = 20;
+    List<Widget> children = [
+      Image(
+          fit: BoxFit.cover,
+          width: width,
+          height: width,
+          image: assetImageForViewType(view.viewType)),
+      const HSpace(6),
+      Text(
+        view.name,
+        textAlign: TextAlign.start,
+        style: const TextStyle(fontSize: 15),
+      ),
+    ];
+
+    if (onHover) {
+      children.add(const Spacer());
+
+      children.add(Align(
+        alignment: Alignment.center,
+        child: StyledMore(
+          width: width,
+          onPressed: () {},
         ),
         ),
-        Text(
-          view.name,
-          textAlign: TextAlign.start,
-          style: const TextStyle(fontSize: 15),
-        )
-      ],
+      ));
+    }
+
+    final padding = EdgeInsets.only(
+      left: AppWidgetSize.expandedPadding,
+      top: 5,
+      bottom: 5,
+      right: 5,
+    );
+
+    return Padding(
+      padding: padding,
+      child: Row(children: children),
     );
     );
   }
   }
 
 

+ 12 - 12
app_flowy/lib/workspace/presentation/widgets/blank_page.dart

@@ -2,30 +2,30 @@ import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 
 
-class BlankStackView extends HomeStackView {
-  const BlankStackView() : super(type: ViewType.Blank, title: 'Blank');
+class AnnouncementStackView extends HomeStackView {
+  const AnnouncementStackView() : super(type: ViewType.Blank, title: 'Blank');
 
 
   @override
   @override
   List<Object> get props => [];
   List<Object> get props => [];
 }
 }
 
 
-class BlankPage extends HomeStackWidget {
-  const BlankPage({Key? key, required BlankStackView stackView})
+class AnnouncementPage extends HomeStackWidget {
+  const AnnouncementPage({Key? key, required AnnouncementStackView stackView})
       : super(key: key, stackView: stackView);
       : super(key: key, stackView: stackView);
 
 
   @override
   @override
-  State<StatefulWidget> createState() => _BlankPageState();
+  State<StatefulWidget> createState() => _AnnouncementPage();
 }
 }
 
 
-class _BlankPageState extends State<BlankPage> {
+class _AnnouncementPage extends State<AnnouncementPage> {
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
-    return Container(
-      color: Theme.of(context).colorScheme.primary,
-      child: const Center(
-        child: Text(
-          'Hello AppFlowy',
-          style: TextStyle(fontSize: 60),
+    return SizedBox.expand(
+      child: Container(
+        color: Theme.of(context).colorScheme.surface,
+        child: Padding(
+          padding: const EdgeInsets.all(10),
+          child: Container(),
         ),
         ),
       ),
       ),
     );
     );

+ 66 - 24
app_flowy/lib/workspace/presentation/widgets/home_top_bar.dart

@@ -1,49 +1,91 @@
+import 'package:app_flowy/workspace/domain/image.dart';
+import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
 import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
 import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
+import 'package:flowy_infra_ui/widget/rounded_button.dart';
+import 'package:flowy_infra_ui/widget/spacing.dart';
+import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pbenum.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
+import 'package:flowy_infra_ui/style_widget/styled_icon_button.dart';
+import 'package:flowy_infra_ui/style_widget/styled_text.dart';
 
 
 class HomeTopBar extends StatelessWidget {
 class HomeTopBar extends StatelessWidget {
-  final String title;
-  const HomeTopBar({Key? key, required this.title}) : super(key: key);
+  final HomeStackView view;
+  const HomeTopBar({Key? key, required this.view}) : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
-    return Container(
-      padding: EdgeInsets.symmetric(horizontal: HomeInsets.topBarTitlePadding),
+    return SizedBox(
       height: HomeSizes.topBarHeight,
       height: HomeSizes.topBarHeight,
-      child: Column(
-        crossAxisAlignment: CrossAxisAlignment.center,
-        children: [
-          HomeTitle(title: title),
-        ],
+      child: Container(
+        decoration: BoxDecoration(
+          border: Border(
+            bottom: BorderSide(width: 0.5, color: Colors.grey.shade300),
+          ),
+        ),
+        child: Padding(
+          padding:
+              EdgeInsets.symmetric(horizontal: HomeInsets.topBarTitlePadding),
+          child: Row(
+            crossAxisAlignment: CrossAxisAlignment.center,
+            children: [
+              HomeTitle(title: view.title, type: view.type),
+              const Spacer(),
+              _renderShareButton(),
+              _renderMoreButton(),
+            ],
+          ),
+        ),
       ),
       ),
     );
     );
   }
   }
+
+  Widget _renderShareButton() {
+    return RoundedTextButton(
+      title: 'Share',
+      height: 30,
+      width: 60,
+      fontSize: 12,
+      borderRadius: BorderRadius.circular(6),
+      color: Colors.lightBlue,
+      press: () {
+        debugPrint('share page');
+      },
+    );
+  }
+
+  Widget _renderMoreButton() {
+    return StyledMore(
+      width: 24,
+      onPressed: () {
+        debugPrint('show more');
+      },
+    );
+  }
 }
 }
 
 
 class HomeTitle extends StatelessWidget {
 class HomeTitle extends StatelessWidget {
   final String title;
   final String title;
-  final _editingController = TextEditingController(
-    text: '',
-  );
+  final ViewType type;
 
 
-  HomeTitle({
+  const HomeTitle({
     Key? key,
     Key? key,
     required this.title,
     required this.title,
+    required this.type,
   }) : super(key: key);
   }) : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
-    _editingController.text = title;
-
-    return Expanded(
-      child: TextField(
-        controller: _editingController,
-        textAlign: TextAlign.left,
-        style: const TextStyle(fontSize: 28.0),
-        decoration: const InputDecoration(
-          hintText: 'Name the view',
-          border: UnderlineInputBorder(borderSide: BorderSide.none),
-        ),
+    return Flexible(
+      child: Row(
+        children: [
+          Image(
+              fit: BoxFit.scaleDown,
+              width: 15,
+              height: 15,
+              image: assetImageForViewType(type)),
+          const HSpace(6),
+          StyledText(title, fontSize: 16),
+        ],
       ),
       ),
     );
     );
   }
   }

+ 0 - 29
app_flowy/lib/workspace/presentation/widgets/menu/app_list.dart

@@ -1,29 +0,0 @@
-import 'package:app_flowy/workspace/presentation/app/app_widget.dart';
-import 'package:expandable/expandable.dart';
-import 'package:flowy_sdk/protobuf/flowy-workspace/app_create.pb.dart';
-import 'package:flutter/material.dart';
-import 'package:dartz/dartz.dart';
-
-class AppList extends StatelessWidget {
-  final Option<List<App>> apps;
-  const AppList({required this.apps, Key? key}) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    return apps.fold(() {
-      return const Expanded(child: Text('You have no apps, create one?'));
-    }, (apps) {
-      return ExpandableTheme(
-          data: const ExpandableThemeData(
-            iconColor: Colors.blue,
-            useInkWell: true,
-          ),
-          child: Expanded(
-            child: ListView(
-              physics: const BouncingScrollPhysics(),
-              children: apps.map((app) => AppWidget(app)).toList(),
-            ),
-          ));
-    });
-  }
-}

+ 65 - 0
app_flowy/lib/workspace/presentation/widgets/menu/create_app_dialog.dart

@@ -0,0 +1,65 @@
+import 'package:app_flowy/startup/tasks/application_task.dart';
+import 'package:flowy_infra/size.dart';
+import 'package:flowy_infra/text_style.dart';
+import 'package:flowy_infra/theme.dart';
+import 'package:flowy_infra_ui/style_widget/styled_text_input.dart';
+import 'package:flowy_infra_ui/widget/buttons/ok_cancel_button.dart';
+import 'package:flowy_infra_ui/widget/dialog/dialog_context.dart';
+import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
+import 'package:flowy_infra_ui/widget/spacing.dart';
+import 'package:flutter/material.dart';
+// ignore: implementation_imports
+import 'package:provider/src/provider.dart';
+import 'package:textstyle_extensions/textstyle_extensions.dart';
+
+// ignore: must_be_immutable
+class CreateAppDialogContext extends DialogContext {
+  String appName;
+  final Function(String)? confirm;
+
+  CreateAppDialogContext({this.appName = "", this.confirm})
+      : super(identifier: 'CreateAppDialogContext');
+
+  @override
+  Widget buildWiget(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return StyledDialog(
+      child: Column(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        children: <Widget>[
+          ...[
+            Text('Create App'.toUpperCase(),
+                style: TextStyles.T1.textColor(theme.bg1)),
+            VSpace(Insets.sm * 1.5),
+            // Container(color: theme.greyWeak.withOpacity(.35), height: 1),
+            VSpace(Insets.m * 1.5),
+          ],
+          StyledFormTextInput(
+            hintText: "App name",
+            onChanged: (text) {
+              appName = text;
+            },
+          ),
+          SizedBox(height: Insets.l),
+          OkCancelButton(
+            onOkPressed: () {
+              if (confirm != null) {
+                confirm!(appName);
+                AppGlobals.nav.pop();
+              }
+            },
+            onCancelPressed: () {
+              AppGlobals.nav.pop();
+            },
+          )
+        ],
+      ),
+    );
+  }
+
+  @override
+  List<Object> get props => [identifier];
+
+  @override
+  bool get barrierDismissable => false;
+}

+ 68 - 147
app_flowy/lib/workspace/presentation/widgets/menu/menu.dart

@@ -1,34 +1,34 @@
-import 'package:app_flowy/workspace/application/menu/menu_bloc.dart';
-import 'package:app_flowy/workspace/application/menu/menu_watch.dart';
-import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
-import 'package:app_flowy/startup/startup.dart';
-import 'package:app_flowy/startup/tasks/application_task.dart';
-import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
+import 'package:app_flowy/workspace/presentation/widgets/menu/menu_new_app.dart';
+import 'package:app_flowy/workspace/presentation/widgets/menu/menu_top_bar.dart';
 import 'package:dartz/dartz.dart';
 import 'package:dartz/dartz.dart';
 import 'package:flowy_infra/size.dart';
 import 'package:flowy_infra/size.dart';
-import 'package:flowy_infra/text_style.dart';
-import 'package:flowy_infra/theme.dart';
-import 'package:flowy_infra_ui/style_widget/styled_text_input.dart';
-import 'package:flowy_infra_ui/widget/buttons/ok_cancel_button.dart';
-import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
 import 'package:flowy_infra_ui/widget/error_page.dart';
 import 'package:flowy_infra_ui/widget/error_page.dart';
-import 'package:flowy_infra_ui/widget/spacing.dart';
+import 'package:flowy_sdk/protobuf/flowy-user/user_detail.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-workspace/app_create.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:styled_widget/styled_widget.dart';
 import 'package:styled_widget/styled_widget.dart';
-import 'package:textstyle_extensions/textstyle_extensions.dart';
-import 'app_list.dart';
+
+import 'package:app_flowy/startup/startup.dart';
+import 'package:app_flowy/workspace/application/menu/menu_bloc.dart';
+import 'package:app_flowy/workspace/application/menu/menu_watch.dart';
+import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
+import 'package:app_flowy/workspace/presentation/app/app_widget.dart';
+import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
+import 'package:app_flowy/workspace/presentation/widgets/menu/menu_user.dart';
+
+import 'menu_list.dart';
 
 
 class HomeMenu extends StatelessWidget {
 class HomeMenu extends StatelessWidget {
   final Function(HomeStackView?) pageContextChanged;
   final Function(HomeStackView?) pageContextChanged;
   final Function(bool) isCollapseChanged;
   final Function(bool) isCollapseChanged;
-  final String workspaceId;
+  final UserDetail user;
 
 
   const HomeMenu(
   const HomeMenu(
       {Key? key,
       {Key? key,
       required this.pageContextChanged,
       required this.pageContextChanged,
       required this.isCollapseChanged,
       required this.isCollapseChanged,
-      required this.workspaceId})
+      required this.user})
       : super(key: key);
       : super(key: key);
 
 
   @override
   @override
@@ -36,10 +36,10 @@ class HomeMenu extends StatelessWidget {
     return MultiBlocProvider(
     return MultiBlocProvider(
       providers: [
       providers: [
         BlocProvider<MenuBloc>(
         BlocProvider<MenuBloc>(
-            create: (context) => getIt<MenuBloc>(param1: workspaceId)
-              ..add(const MenuEvent.initial())),
+            create: (context) =>
+                getIt<MenuBloc>(param1: user)..add(const MenuEvent.initial())),
         BlocProvider(
         BlocProvider(
-            create: (context) => getIt<MenuWatchBloc>(param1: workspaceId)
+            create: (context) => getIt<MenuWatchBloc>(param1: user)
               ..add(const MenuWatchEvent.started())),
               ..add(const MenuWatchEvent.started())),
       ],
       ],
       child: MultiBlocListener(
       child: MultiBlocListener(
@@ -61,161 +61,82 @@ class HomeMenu extends StatelessWidget {
   }
   }
 
 
   Widget _renderBody(BuildContext context) {
   Widget _renderBody(BuildContext context) {
+    // nested cloumn: https://siddharthmolleti.com/flutter-box-constraints-nested-column-s-row-s-3dfacada7361
     return Container(
     return Container(
-      color: Theme.of(context).colorScheme.primaryVariant,
+      color: Theme.of(context).colorScheme.background,
       child: Column(
       child: Column(
         mainAxisAlignment: MainAxisAlignment.start,
         mainAxisAlignment: MainAxisAlignment.start,
         children: [
         children: [
-          const MenuTopBar(),
-          _renderAppList(context),
-          _renderNewButton(context),
+          Expanded(
+            child: Column(
+              mainAxisAlignment: MainAxisAlignment.start,
+              children: [
+                _renderTopBar(context),
+                _renderMenuList(context),
+              ],
+            ).padding(horizontal: Insets.l),
+          ),
+          _renderNewAppButton(context),
         ],
         ],
-      ).padding(horizontal: Insets.sm),
-    );
-  }
-
-  Widget _renderAppList(BuildContext context) {
-    return BlocBuilder<MenuWatchBloc, MenuWatchState>(
-      builder: (context, state) => state.map(
-        initial: (_) => BlocBuilder<MenuBloc, MenuState>(
-          builder: (context, state) {
-            return AppList(apps: state.apps);
-          },
-        ),
-        loadApps: (s) => AppList(apps: some(s.apps)),
-        loadFail: (s) => FlowyErrorPage(s.error.toString()),
       ),
       ),
     );
     );
   }
   }
 
 
-  Widget _renderNewButton(BuildContext context) {
-    return NewAppButton(
-      createAppCallback: (appName) =>
-          context.read<MenuBloc>().add(MenuEvent.createApp(appName, desc: "")),
-    );
-  }
-}
-
-class MenuTopBar extends StatelessWidget {
-  const MenuTopBar({Key? key}) : super(key: key);
-  @override
-  Widget build(BuildContext context) {
-    return BlocBuilder<MenuBloc, MenuState>(
+  Widget _renderMenuList(BuildContext context) {
+    return BlocBuilder<MenuWatchBloc, MenuWatchState>(
       builder: (context, state) {
       builder: (context, state) {
-        return SizedBox(
-          height: HomeSizes.menuTopBarHeight,
-          child: Row(
-            children: [
-              const Text(
-                'AppFlowy',
-                style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
-              ).constrained(minWidth: 100),
-              const Spacer(),
-              IconButton(
-                icon: const Icon(Icons.arrow_left),
-                onPressed: () =>
-                    context.read<MenuBloc>().add(const MenuEvent.collapse()),
-              ),
-            ],
+        return state.map(
+          initial: (_) => MenuList(
+            menuItems: menuItemsWithApps(context.read<MenuBloc>().state.apps),
+          ),
+          loadApps: (s) => MenuList(
+            menuItems: menuItemsWithApps(some(s.apps)),
           ),
           ),
+          loadFail: (s) => FlowyErrorPage(s.error.toString()),
         );
         );
       },
       },
     );
     );
   }
   }
-}
-
-class NewAppButton extends StatelessWidget {
-  final Function(String)? createAppCallback;
 
 
-  const NewAppButton({this.createAppCallback, Key? key}) : super(key: key);
-  @override
-  Widget build(BuildContext context) {
-    return SizedBox(
-      height: HomeSizes.menuAddButtonHeight,
-      child: Row(
-        mainAxisAlignment: MainAxisAlignment.start,
-        children: [
-          const Icon(Icons.add),
-          const SizedBox(
-            width: 10,
-          ),
-          TextButton(
-            onPressed: () async => await _showCreateAppDialog(context),
-            child: _buttonTitle(),
-          )
-        ],
-      ),
+  Widget _renderNewAppButton(BuildContext context) {
+    return NewAppButton(
+      press: (appName) =>
+          context.read<MenuBloc>().add(MenuEvent.createApp(appName, desc: "")),
     );
     );
   }
   }
 
 
-  Widget _buttonTitle() {
-    return const Text('New App',
-        style: TextStyle(
-          color: Colors.black,
-          fontWeight: FontWeight.bold,
-          fontSize: 20,
-        ));
+  Widget _renderTopBar(BuildContext context) {
+    return SizedBox(
+      height: HomeSizes.menuTopBarHeight,
+      child: const MenuTopBar(),
+    );
   }
   }
 
 
-  Future<void> _showCreateAppDialog(BuildContext context) async {
-    await Dialogs.showWithContext(CreateAppDialogContext(
-      confirm: (appName) {
-        if (appName.isNotEmpty && createAppCallback != null) {
-          createAppCallback!(appName);
-        }
-      },
-    ), context);
+  List<MenuItem> menuItemsWithApps(Option<List<App>> someApps) {
+    return MenuItemBuilder().withUser(user).withApps(someApps).build();
   }
   }
 }
 }
 
 
-//ignore: must_be_immutable
-class CreateAppDialogContext extends DialogContext {
-  String appName;
-  final Function(String)? confirm;
+class MenuItemBuilder {
+  List<MenuItem> items = [];
 
 
-  CreateAppDialogContext({this.appName = "", this.confirm})
-      : super(identifier: 'CreateAppDialogContext');
+  MenuItemBuilder();
 
 
-  @override
-  Widget buildWiget(BuildContext context) {
-    final theme = context.watch<AppTheme>();
-    return StyledDialog(
-      child: Column(
-        crossAxisAlignment: CrossAxisAlignment.start,
-        children: <Widget>[
-          ...[
-            Text('Create App'.toUpperCase(),
-                style: TextStyles.T1.textColor(theme.accent1Darker)),
-            VSpace(Insets.sm * 1.5),
-            // Container(color: theme.greyWeak.withOpacity(.35), height: 1),
-            VSpace(Insets.m * 1.5),
-          ],
-          StyledFormTextInput(
-            hintText: "App name",
-            onChanged: (text) {
-              appName = text;
-            },
-          ),
-          SizedBox(height: Insets.l),
-          OkCancelButton(
-            onOkPressed: () {
-              if (confirm != null) {
-                confirm!(appName);
-                AppGlobals.nav.pop();
-              }
-            },
-            onCancelPressed: () {
-              AppGlobals.nav.pop();
-            },
-          )
-        ],
-      ),
-    );
+  MenuItemBuilder withUser(UserDetail user) {
+    items.add(MenuUser(user));
+    return this;
   }
   }
 
 
-  @override
-  List<Object> get props => [identifier];
+  MenuItemBuilder withApps(Option<List<App>> someApps) {
+    List<MenuItem> appWidgets = someApps.foldRight(
+      [],
+      (apps, _) => apps.map((app) => AppWidget(app)).toList(),
+    );
+    items.addAll(appWidgets);
+    return this;
+  }
 
 
-  @override
-  bool get barrierDismissable => false;
+  List<MenuItem> build() {
+    return items;
+  }
 }
 }

+ 40 - 0
app_flowy/lib/workspace/presentation/widgets/menu/menu_list.dart

@@ -0,0 +1,40 @@
+import 'package:expandable/expandable.dart';
+import 'package:flowy_infra_ui/widget/spacing.dart';
+import 'package:flowy_infra/time/duration.dart';
+import 'package:flutter/material.dart';
+
+enum MenuItemType {
+  userProfile,
+  dashboard,
+  favorites,
+  app,
+}
+
+abstract class MenuItem extends StatelessWidget {
+  const MenuItem({Key? key}) : super(key: key);
+
+  MenuItemType get type;
+}
+
+class MenuList extends StatelessWidget {
+  final List<MenuItem> menuItems;
+  const MenuList({required this.menuItems, Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return ExpandableTheme(
+      data: ExpandableThemeData(
+          useInkWell: true, animationDuration: Durations.medium),
+      child: Expanded(
+        child: ListView.separated(
+          itemCount: menuItems.length,
+          separatorBuilder: (context, index) => const VSpace(10),
+          physics: const BouncingScrollPhysics(),
+          itemBuilder: (BuildContext context, int index) {
+            return menuItems[index];
+          },
+        ),
+      ),
+    );
+  }
+}

+ 50 - 0
app_flowy/lib/workspace/presentation/widgets/menu/menu_new_app.dart

@@ -0,0 +1,50 @@
+import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
+import 'package:app_flowy/workspace/presentation/widgets/menu/create_app_dialog.dart';
+import 'package:flowy_infra/size.dart';
+import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
+import 'package:flutter/material.dart';
+import 'package:styled_widget/styled_widget.dart';
+
+class NewAppButton extends StatelessWidget {
+  final Function(String)? press;
+
+  const NewAppButton({this.press, Key? key}) : super(key: key);
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      decoration: BoxDecoration(
+        border: Border(
+          top: BorderSide(width: 1, color: Colors.grey.shade300),
+        ),
+      ),
+      height: HomeSizes.menuAddButtonHeight,
+      child: Row(
+        mainAxisAlignment: MainAxisAlignment.start,
+        children: [
+          const Icon(Icons.add_circle_rounded, size: 30),
+          TextButton(
+            onPressed: () async => await _showCreateAppDialog(context),
+            child: const Text(
+              'New App',
+              style: TextStyle(
+                color: Colors.black,
+                fontWeight: FontWeight.bold,
+                fontSize: 20,
+              ),
+            ),
+          )
+        ],
+      ).padding(horizontal: Insets.l),
+    );
+  }
+
+  Future<void> _showCreateAppDialog(BuildContext context) async {
+    await Dialogs.showWithContext(CreateAppDialogContext(
+      confirm: (appName) {
+        if (appName.isNotEmpty && press != null) {
+          press!(appName);
+        }
+      },
+    ), context);
+  }
+}

+ 0 - 4
app_flowy/lib/workspace/presentation/widgets/menu/menu_size.dart

@@ -1,4 +0,0 @@
-class HomeMenuSize {
-  static double get createViewButtonSize => 30;
-  static double get collapseIconSize => 24;
-}

+ 37 - 0
app_flowy/lib/workspace/presentation/widgets/menu/menu_top_bar.dart

@@ -0,0 +1,37 @@
+import 'package:app_flowy/workspace/application/menu/menu_bloc.dart';
+import 'package:flowy_infra_ui/widget/spacing.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+class MenuTopBar extends StatelessWidget {
+  const MenuTopBar({Key? key}) : super(key: key);
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<MenuBloc, MenuState>(
+      builder: (context, state) {
+        return Row(
+          children: [
+            const Image(
+                fit: BoxFit.cover,
+                width: 25,
+                height: 25,
+                image: AssetImage('assets/images/app_flowy_logo.jpg')),
+            const HSpace(8),
+            const Text(
+              'AppFlowy',
+              style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
+            ),
+            const Spacer(),
+            IconButton(
+              icon: const Icon(Icons.arrow_left),
+              alignment: Alignment.centerRight,
+              padding: EdgeInsets.zero,
+              onPressed: () =>
+                  context.read<MenuBloc>().add(const MenuEvent.collapse()),
+            ),
+          ],
+        );
+      },
+    );
+  }
+}

+ 66 - 0
app_flowy/lib/workspace/presentation/widgets/menu/menu_user.dart

@@ -0,0 +1,66 @@
+import 'package:app_flowy/startup/startup.dart';
+import 'package:app_flowy/workspace/application/menu/menu_user_bloc.dart';
+import 'package:app_flowy/workspace/presentation/widgets/menu/menu_list.dart';
+import 'package:flowy_infra_ui/widget/spacing.dart';
+import 'package:flowy_sdk/protobuf/flowy-user/user_detail.pb.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:flowy_infra_ui/style_widget/styled_text.dart';
+import 'package:flowy_infra_ui/style_widget/styled_icon_button.dart';
+
+class MenuUser extends MenuItem {
+  final UserDetail user;
+  MenuUser(this.user, {Key? key}) : super(key: ValueKey(user.id));
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocProvider<MenuUserBloc>(
+      create: (context) =>
+          getIt<MenuUserBloc>(param1: user)..add(const MenuUserEvent.initial()),
+      child: BlocBuilder<MenuUserBloc, MenuUserState>(
+        builder: (context, state) => Row(children: [
+          _renderAvatar(context),
+          const HSpace(10),
+          _renderUserName(context),
+          const HSpace(10),
+          _renderDropButton(context),
+        ]),
+      ),
+    );
+  }
+
+  Widget _renderAvatar(BuildContext context) {
+    return SizedBox(
+      width: 30,
+      height: 30,
+      child: ClipRRect(
+        borderRadius: BorderRadius.circular(10),
+        child: const Image(image: AssetImage('assets/images/avatar.jpg')),
+      ),
+    );
+  }
+
+  Widget _renderUserName(BuildContext context) {
+    String name = context.read<MenuUserBloc>().state.user.name;
+    if (name.isEmpty) {
+      name = context.read<MenuUserBloc>().state.user.email;
+    }
+    return Flexible(
+      child: StyledText(name, fontSize: 18),
+    );
+  }
+
+  Widget _renderDropButton(BuildContext context) {
+    return StyledIconButton(
+      width: 30,
+      iconRatio: 0.8,
+      icon: const Icon(Icons.arrow_drop_down),
+      onPressed: () {
+        debugPrint('show user profile');
+      },
+    );
+  }
+
+  @override
+  MenuItemType get type => MenuItemType.userProfile;
+}

+ 0 - 1
app_flowy/lib/workspace/presentation/widgets/menu/prelude.dart

@@ -1,2 +1 @@
 export 'menu.dart';
 export 'menu.dart';
-export 'menu_size.dart';

+ 1 - 1
app_flowy/macos/Podfile.lock

@@ -49,4 +49,4 @@ SPEC CHECKSUMS:
 
 
 PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c
 PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c
 
 
-COCOAPODS: 1.9.3
+COCOAPODS: 1.10.1

+ 30 - 11
app_flowy/packages/flowy_editor/lib/src/service/controller.dart

@@ -10,10 +10,16 @@ import '../model/document/document.dart';
 import '../model/document/style.dart';
 import '../model/document/style.dart';
 import '../model/document/node/embed.dart';
 import '../model/document/node/embed.dart';
 
 
+abstract class EditorPersistence {
+  Future<bool> save(List<dynamic> jsonList);
+}
+
 class EditorController extends ChangeNotifier {
 class EditorController extends ChangeNotifier {
+  final EditorPersistence? persistence;
   EditorController({
   EditorController({
     required this.document,
     required this.document,
     required this.selection,
     required this.selection,
+    this.persistence,
   });
   });
 
 
   factory EditorController.basic() {
   factory EditorController.basic() {
@@ -38,7 +44,8 @@ class EditorController extends ChangeNotifier {
       );
       );
 
 
   Style getSelectionStyle() =>
   Style getSelectionStyle() =>
-      document.collectStyle(selection.start, selection.end - selection.start)..mergeAll(toggledStyle);
+      document.collectStyle(selection.start, selection.end - selection.start)
+        ..mergeAll(toggledStyle);
 
 
   bool get hasUndo => document.hasUndo;
   bool get hasUndo => document.hasUndo;
 
 
@@ -58,10 +65,10 @@ class EditorController extends ChangeNotifier {
     }
     }
   }
   }
 
 
-  Future<bool> save() async {
-    document.toDelta().toJson();
-    // TODO: vedon - Save document to database
-    return true;
+  void save() {
+    if (persistence != null) {
+      persistence!.save(document.toDelta().toJson());
+    }
   }
   }
 
 
   @override
   @override
@@ -80,7 +87,9 @@ class EditorController extends ChangeNotifier {
   }
   }
 
 
   void formatText(int index, int length, Attribute? attribute) {
   void formatText(int index, int length, Attribute? attribute) {
-    if (length == 0 && attribute!.isInline && attribute.key != Attribute.link.key) {
+    if (length == 0 &&
+        attribute!.isInline &&
+        attribute.key != Attribute.link.key) {
       toggledStyle = toggledStyle.put(attribute);
       toggledStyle = toggledStyle.put(attribute);
     }
     }
 
 
@@ -95,16 +104,25 @@ class EditorController extends ChangeNotifier {
     notifyListeners();
     notifyListeners();
   }
   }
 
 
-  void replaceText(int index, int length, Object? data, TextSelection? textSelection) {
+  void replaceText(
+      int index, int length, Object? data, TextSelection? textSelection) {
     assert(data is String || data is Embeddable);
     assert(data is String || data is Embeddable);
 
 
     Delta? delta;
     Delta? delta;
     if (length > 0 || data is! String || data.isNotEmpty) {
     if (length > 0 || data is! String || data.isNotEmpty) {
       delta = document.replace(index, length, data);
       delta = document.replace(index, length, data);
-      var shouldRetainDelta = toggledStyle.isNotEmpty && delta.isNotEmpty && delta.length <= 2 && delta.last.isInsert;
-      if (shouldRetainDelta && toggledStyle.isNotEmpty && delta.length == 2 && delta.last.data == '\n') {
+      print(delta);
+      var shouldRetainDelta = toggledStyle.isNotEmpty &&
+          delta.isNotEmpty &&
+          delta.length <= 2 &&
+          delta.last.isInsert;
+      if (shouldRetainDelta &&
+          toggledStyle.isNotEmpty &&
+          delta.length == 2 &&
+          delta.last.data == '\n') {
         // if all attributes are inline, shouldRetainDelta should be false
         // if all attributes are inline, shouldRetainDelta should be false
-        final anyAttributeNotInline = toggledStyle.values.any((attr) => !attr.isInline);
+        final anyAttributeNotInline =
+            toggledStyle.values.any((attr) => !attr.isInline);
         shouldRetainDelta &= anyAttributeNotInline;
         shouldRetainDelta &= anyAttributeNotInline;
       }
       }
       if (shouldRetainDelta) {
       if (shouldRetainDelta) {
@@ -146,7 +164,8 @@ class EditorController extends ChangeNotifier {
 
 
     textSelection = selection.copyWith(
     textSelection = selection.copyWith(
       baseOffset: delta.transformPosition(selection.baseOffset, force: false),
       baseOffset: delta.transformPosition(selection.baseOffset, force: false),
-      extentOffset: delta.transformPosition(selection.extentOffset, force: false),
+      extentOffset:
+          delta.transformPosition(selection.extentOffset, force: false),
     );
     );
     if (selection != textSelection) {
     if (selection != textSelection) {
       _updateSelection(textSelection, source);
       _updateSelection(textSelection, source);

+ 136 - 64
app_flowy/packages/flowy_editor/lib/src/widget/raw_editor.dart

@@ -60,7 +60,8 @@ class RawEditor extends StatefulWidget {
     this.embedBuilder,
     this.embedBuilder,
   )   : assert(maxHeight == null || maxHeight > 0, 'maxHeight cannot be null'),
   )   : assert(maxHeight == null || maxHeight > 0, 'maxHeight cannot be null'),
         assert(minHeight == null || minHeight >= 0, 'minHeight cannot be null'),
         assert(minHeight == null || minHeight >= 0, 'minHeight cannot be null'),
-        assert(maxHeight == null || minHeight == null || maxHeight >= minHeight),
+        assert(
+            maxHeight == null || minHeight == null || maxHeight >= minHeight),
         showCursor = showCursor ?? true,
         showCursor = showCursor ?? true,
         super(key: key);
         super(key: key);
 
 
@@ -111,7 +112,10 @@ abstract class EditorState extends State<RawEditor> {
 }
 }
 
 
 class _RawEditorState extends EditorState
 class _RawEditorState extends EditorState
-    with AutomaticKeepAliveClientMixin<RawEditor>, WidgetsBindingObserver, TickerProviderStateMixin<RawEditor>
+    with
+        AutomaticKeepAliveClientMixin<RawEditor>,
+        WidgetsBindingObserver,
+        TickerProviderStateMixin<RawEditor>
     implements TextSelectionDelegate, TextInputClient {
     implements TextSelectionDelegate, TextInputClient {
   final GlobalKey _editorKey = GlobalKey();
   final GlobalKey _editorKey = GlobalKey();
   final List<TextEditingValue> _sentRemoteValues = [];
   final List<TextEditingValue> _sentRemoteValues = [];
@@ -129,7 +133,8 @@ class _RawEditorState extends EditorState
   bool _didAutoFocus = false;
   bool _didAutoFocus = false;
   bool _keyboardVisible = false;
   bool _keyboardVisible = false;
   DefaultStyles? _styles;
   DefaultStyles? _styles;
-  final ClipboardStatusNotifier? _clipboardStatus = kIsWeb ? null : ClipboardStatusNotifier();
+  final ClipboardStatusNotifier? _clipboardStatus =
+      kIsWeb ? null : ClipboardStatusNotifier();
   final LayerLink _toolbarLayerLink = LayerLink();
   final LayerLink _toolbarLayerLink = LayerLink();
   final LayerLink _startHandleLayerLink = LayerLink();
   final LayerLink _startHandleLayerLink = LayerLink();
   final LayerLink _endHandleLayerLink = LayerLink();
   final LayerLink _endHandleLayerLink = LayerLink();
@@ -177,57 +182,78 @@ class _RawEditorState extends EditorState
         downKey = key == LogicalKeyboardKey.arrowDown;
         downKey = key == LogicalKeyboardKey.arrowDown;
 
 
     if ((rightKey || leftKey) && !(rightKey && leftKey)) {
     if ((rightKey || leftKey) && !(rightKey && leftKey)) {
-      newSelection =
-          _jumpToBeginOrEndOfWord(newSelection, wordModifier, leftKey, rightKey, plainText, lineModifier, shift);
+      newSelection = _jumpToBeginOrEndOfWord(newSelection, wordModifier,
+          leftKey, rightKey, plainText, lineModifier, shift);
     }
     }
 
 
     if (downKey || upKey) {
     if (downKey || upKey) {
-      newSelection = _handleMovingCursorVertically(upKey, downKey, shift, selection, newSelection, plainText);
+      newSelection = _handleMovingCursorVertically(
+          upKey, downKey, shift, selection, newSelection, plainText);
     }
     }
 
 
     if (!shift) {
     if (!shift) {
-      newSelection = _placeCollapsedSelection(selection, newSelection, leftKey, rightKey);
+      newSelection =
+          _placeCollapsedSelection(selection, newSelection, leftKey, rightKey);
     }
     }
 
 
     widget.controller.updateSelection(newSelection, ChangeSource.LOCAL);
     widget.controller.updateSelection(newSelection, ChangeSource.LOCAL);
   }
   }
 
 
-  TextSelection _placeCollapsedSelection(
-      TextSelection selection, TextSelection newSelection, bool leftKey, bool rightKey) {
+  TextSelection _placeCollapsedSelection(TextSelection selection,
+      TextSelection newSelection, bool leftKey, bool rightKey) {
     var newOffset = newSelection.extentOffset;
     var newOffset = newSelection.extentOffset;
     if (!selection.isCollapsed) {
     if (!selection.isCollapsed) {
       if (leftKey) {
       if (leftKey) {
-        newOffset =
-            newSelection.baseOffset < newSelection.extentOffset ? newSelection.baseOffset : newSelection.extentOffset;
+        newOffset = newSelection.baseOffset < newSelection.extentOffset
+            ? newSelection.baseOffset
+            : newSelection.extentOffset;
       } else if (rightKey) {
       } else if (rightKey) {
-        newOffset =
-            newSelection.baseOffset > newSelection.extentOffset ? newSelection.baseOffset : newSelection.extentOffset;
+        newOffset = newSelection.baseOffset > newSelection.extentOffset
+            ? newSelection.baseOffset
+            : newSelection.extentOffset;
       }
       }
     }
     }
     return TextSelection.fromPosition(TextPosition(offset: newOffset));
     return TextSelection.fromPosition(TextPosition(offset: newOffset));
   }
   }
 
 
   TextSelection _handleMovingCursorVertically(
   TextSelection _handleMovingCursorVertically(
-      bool upKey, bool downKey, bool shift, TextSelection selection, TextSelection newSelection, String plainText) {
-    final originPosition = TextPosition(offset: upKey ? selection.baseOffset : selection.extentOffset);
+      bool upKey,
+      bool downKey,
+      bool shift,
+      TextSelection selection,
+      TextSelection newSelection,
+      String plainText) {
+    final originPosition = TextPosition(
+        offset: upKey ? selection.baseOffset : selection.extentOffset);
 
 
     final child = getRenderEditor()!.childAtPosition(originPosition);
     final child = getRenderEditor()!.childAtPosition(originPosition);
-    final localPosition = TextPosition(offset: originPosition.offset - child.container.documentOffset);
+    final localPosition = TextPosition(
+        offset: originPosition.offset - child.container.documentOffset);
 
 
-    var position = upKey ? child.getPositionAbove(localPosition) : child.getPositionBelow(localPosition);
+    var position = upKey
+        ? child.getPositionAbove(localPosition)
+        : child.getPositionBelow(localPosition);
 
 
     if (position == null) {
     if (position == null) {
-      final sibling = upKey ? getRenderEditor()!.childBefore(child) : getRenderEditor()!.childAfter(child);
+      final sibling = upKey
+          ? getRenderEditor()!.childBefore(child)
+          : getRenderEditor()!.childAfter(child);
       if (sibling == null) {
       if (sibling == null) {
         position = TextPosition(offset: upKey ? 0 : plainText.length - 1);
         position = TextPosition(offset: upKey ? 0 : plainText.length - 1);
       } else {
       } else {
-        final finalOffset = Offset(child.getOffsetForCaret(localPosition).dx,
-            sibling.getOffsetForCaret(TextPosition(offset: upKey ? sibling.container.length - 1 : 0)).dy);
+        final finalOffset = Offset(
+            child.getOffsetForCaret(localPosition).dx,
+            sibling
+                .getOffsetForCaret(TextPosition(
+                    offset: upKey ? sibling.container.length - 1 : 0))
+                .dy);
         final siblingPosition = sibling.getPositionForOffset(finalOffset);
         final siblingPosition = sibling.getPositionForOffset(finalOffset);
-        position = TextPosition(offset: sibling.container.documentOffset + siblingPosition.offset);
+        position = TextPosition(
+            offset: sibling.container.documentOffset + siblingPosition.offset);
       }
       }
     } else {
     } else {
-      position = TextPosition(offset: child.container.documentOffset + position.offset);
+      position = TextPosition(
+          offset: child.container.documentOffset + position.offset);
     }
     }
 
 
     if (position.offset == newSelection.extentOffset) {
     if (position.offset == newSelection.extentOffset) {
@@ -250,33 +276,47 @@ class _RawEditorState extends EditorState
     return newSelection;
     return newSelection;
   }
   }
 
 
-  TextSelection _jumpToBeginOrEndOfWord(TextSelection newSelection, bool wordModifier, bool leftKey, bool rightKey,
-      String plainText, bool lineModifier, bool shift) {
+  TextSelection _jumpToBeginOrEndOfWord(
+      TextSelection newSelection,
+      bool wordModifier,
+      bool leftKey,
+      bool rightKey,
+      String plainText,
+      bool lineModifier,
+      bool shift) {
     if (wordModifier) {
     if (wordModifier) {
       if (leftKey) {
       if (leftKey) {
         final textSelection = getRenderEditor()!.selectWordAtPosition(
         final textSelection = getRenderEditor()!.selectWordAtPosition(
-            TextPosition(offset: _previousCharacter(newSelection.extentOffset, plainText, false)));
+            TextPosition(
+                offset: _previousCharacter(
+                    newSelection.extentOffset, plainText, false)));
         return newSelection.copyWith(extentOffset: textSelection.baseOffset);
         return newSelection.copyWith(extentOffset: textSelection.baseOffset);
       }
       }
-      final textSelection = getRenderEditor()!
-          .selectWordAtPosition(TextPosition(offset: _nextCharacter(newSelection.extentOffset, plainText, false)));
+      final textSelection = getRenderEditor()!.selectWordAtPosition(
+          TextPosition(
+              offset:
+                  _nextCharacter(newSelection.extentOffset, plainText, false)));
       return newSelection.copyWith(extentOffset: textSelection.extentOffset);
       return newSelection.copyWith(extentOffset: textSelection.extentOffset);
     } else if (lineModifier) {
     } else if (lineModifier) {
       if (leftKey) {
       if (leftKey) {
         final textSelection = getRenderEditor()!.selectLineAtPosition(
         final textSelection = getRenderEditor()!.selectLineAtPosition(
-            TextPosition(offset: _previousCharacter(newSelection.extentOffset, plainText, false)));
+            TextPosition(
+                offset: _previousCharacter(
+                    newSelection.extentOffset, plainText, false)));
         return newSelection.copyWith(extentOffset: textSelection.baseOffset);
         return newSelection.copyWith(extentOffset: textSelection.baseOffset);
       }
       }
       final startPoint = newSelection.extentOffset;
       final startPoint = newSelection.extentOffset;
       if (startPoint < plainText.length) {
       if (startPoint < plainText.length) {
-        final textSelection = getRenderEditor()!.selectLineAtPosition(TextPosition(offset: startPoint));
+        final textSelection = getRenderEditor()!
+            .selectLineAtPosition(TextPosition(offset: startPoint));
         return newSelection.copyWith(extentOffset: textSelection.extentOffset);
         return newSelection.copyWith(extentOffset: textSelection.extentOffset);
       }
       }
       return newSelection;
       return newSelection;
     }
     }
 
 
     if (rightKey && newSelection.extentOffset < plainText.length) {
     if (rightKey && newSelection.extentOffset < plainText.length) {
-      final nextExtent = _nextCharacter(newSelection.extentOffset, plainText, true);
+      final nextExtent =
+          _nextCharacter(newSelection.extentOffset, plainText, true);
       final distance = nextExtent - newSelection.extentOffset;
       final distance = nextExtent - newSelection.extentOffset;
       newSelection = newSelection.copyWith(extentOffset: nextExtent);
       newSelection = newSelection.copyWith(extentOffset: nextExtent);
       if (shift) {
       if (shift) {
@@ -286,7 +326,8 @@ class _RawEditorState extends EditorState
     }
     }
 
 
     if (leftKey && newSelection.extentOffset > 0) {
     if (leftKey && newSelection.extentOffset > 0) {
-      final previousExtent = _previousCharacter(newSelection.extentOffset, plainText, true);
+      final previousExtent =
+          _previousCharacter(newSelection.extentOffset, plainText, true);
       final distance = newSelection.extentOffset - previousExtent;
       final distance = newSelection.extentOffset - previousExtent;
       newSelection = newSelection.copyWith(extentOffset: previousExtent);
       newSelection = newSelection.copyWith(extentOffset: previousExtent);
       if (shift) {
       if (shift) {
@@ -326,7 +367,9 @@ class _RawEditorState extends EditorState
     var count = 0;
     var count = 0;
     int? lastNonWhitespace;
     int? lastNonWhitespace;
     for (final currentString in string.characters) {
     for (final currentString in string.characters) {
-      if (!includeWhitespace && !WHITE_SPACE.contains(currentString.characters.first.toString().codeUnitAt(0))) {
+      if (!includeWhitespace &&
+          !WHITE_SPACE.contains(
+              currentString.characters.first.toString().codeUnitAt(0))) {
         lastNonWhitespace = count;
         lastNonWhitespace = count;
       }
       }
       if (count + currentString.length >= index) {
       if (count + currentString.length >= index) {
@@ -337,7 +380,8 @@ class _RawEditorState extends EditorState
     return 0;
     return 0;
   }
   }
 
 
-  bool get hasConnection => _textInputConnection != null && _textInputConnection!.attached;
+  bool get hasConnection =>
+      _textInputConnection != null && _textInputConnection!.attached;
 
 
   void openConnectionIfNeeded() {
   void openConnectionIfNeeded() {
     if (!shouldCreateInputConnection) {
     if (!shouldCreateInputConnection) {
@@ -388,7 +432,8 @@ class _RawEditorState extends EditorState
       return;
       return;
     }
     }
 
 
-    final shouldRemember = textEditingValue.text != _lastKnownRemoteTextEditingValue!.text;
+    final shouldRemember =
+        textEditingValue.text != _lastKnownRemoteTextEditingValue!.text;
     _lastKnownRemoteTextEditingValue = actualValue;
     _lastKnownRemoteTextEditingValue = actualValue;
     _textInputConnection!.setEditingState(actualValue);
     _textInputConnection!.setEditingState(actualValue);
     if (shouldRemember) {
     if (shouldRemember) {
@@ -397,7 +442,8 @@ class _RawEditorState extends EditorState
   }
   }
 
 
   @override
   @override
-  TextEditingValue? get currentTextEditingValue => _lastKnownRemoteTextEditingValue;
+  TextEditingValue? get currentTextEditingValue =>
+      _lastKnownRemoteTextEditingValue;
 
 
   @override
   @override
   AutofillScope? get currentAutofillScope => null;
   AutofillScope? get currentAutofillScope => null;
@@ -429,7 +475,8 @@ class _RawEditorState extends EditorState
     final text = value.text;
     final text = value.text;
     final cursorPosition = value.selection.extentOffset;
     final cursorPosition = value.selection.extentOffset;
     final diff = getDiff(oldText, text, cursorPosition);
     final diff = getDiff(oldText, text, cursorPosition);
-    widget.controller.replaceText(diff.start, diff.deleted.length, diff.inserted, value.selection);
+    widget.controller.replaceText(
+        diff.start, diff.deleted.length, diff.inserted, value.selection);
   }
   }
 
 
   @override
   @override
@@ -479,8 +526,11 @@ class _RawEditorState extends EditorState
     super.build(context);
     super.build(context);
 
 
     var _doc = widget.controller.document;
     var _doc = widget.controller.document;
-    if (_doc.isEmpty() && !widget.focusNode.hasFocus && widget.placeholder != null) {
-      _doc = Document.fromJson(jsonDecode('[{"attributes":{"placeholder":true},"insert":"${widget.placeholder}\\n"}]'));
+    if (_doc.isEmpty() &&
+        !widget.focusNode.hasFocus &&
+        widget.placeholder != null) {
+      _doc = Document.fromJson(jsonDecode(
+          '[{"attributes":{"placeholder":true},"insert":"${widget.placeholder}\\n"}]'));
     }
     }
 
 
     Widget child = CompositedTransformTarget(
     Widget child = CompositedTransformTarget(
@@ -503,7 +553,8 @@ class _RawEditorState extends EditorState
     );
     );
 
 
     if (widget.scrollable) {
     if (widget.scrollable) {
-      final baselinePadding = EdgeInsets.only(top: _styles!.paragraph!.verticalSpacing.item1);
+      final baselinePadding =
+          EdgeInsets.only(top: _styles!.paragraph!.verticalSpacing.item1);
       child = BaselineProxy(
       child = BaselineProxy(
         textStyle: _styles!.paragraph!.style,
         textStyle: _styles!.paragraph!.style,
         padding: baselinePadding,
         padding: baselinePadding,
@@ -534,7 +585,8 @@ class _RawEditorState extends EditorState
     );
     );
   }
   }
 
 
-  void _handleSelectionChanged(TextSelection selection, SelectionChangedCause cause) {
+  void _handleSelectionChanged(
+      TextSelection selection, SelectionChangedCause cause) {
     widget.controller.updateSelection(selection, ChangeSource.LOCAL);
     widget.controller.updateSelection(selection, ChangeSource.LOCAL);
 
 
     _selectionOverlay?.handlesVisible = _shouldShowSelectionHandles();
     _selectionOverlay?.handlesVisible = _shouldShowSelectionHandles();
@@ -563,7 +615,9 @@ class _RawEditorState extends EditorState
             _styles,
             _styles,
             widget.enableInteractiveSelection,
             widget.enableInteractiveSelection,
             _hasFocus,
             _hasFocus,
-            attrs.containsKey(Attribute.codeBlock.key) ? const EdgeInsets.all(16) : null,
+            attrs.containsKey(Attribute.codeBlock.key)
+                ? const EdgeInsets.all(16)
+                : null,
             widget.embedBuilder,
             widget.embedBuilder,
             _cursorController,
             _cursorController,
             indentLevelCounts);
             indentLevelCounts);
@@ -575,7 +629,8 @@ class _RawEditorState extends EditorState
     return result;
     return result;
   }
   }
 
 
-  EditableTextLine _getEditableTextLineFromNode(Line node, BuildContext context) {
+  EditableTextLine _getEditableTextLineFromNode(
+      Line node, BuildContext context) {
     final textLine = TextLine(
     final textLine = TextLine(
       line: node,
       line: node,
       textDirection: _textDirection,
       textDirection: _textDirection,
@@ -598,7 +653,8 @@ class _RawEditorState extends EditorState
     return editableTextLine;
     return editableTextLine;
   }
   }
 
 
-  Tuple2<double, double> _getVerticalSpacingForLine(Line line, DefaultStyles? defaultStyles) {
+  Tuple2<double, double> _getVerticalSpacingForLine(
+      Line line, DefaultStyles? defaultStyles) {
     final attrs = line.style.attributes;
     final attrs = line.style.attributes;
     if (attrs.containsKey(Attribute.header.key)) {
     if (attrs.containsKey(Attribute.header.key)) {
       final int? level = attrs[Attribute.header.key]!.value;
       final int? level = attrs[Attribute.header.key]!.value;
@@ -623,7 +679,8 @@ class _RawEditorState extends EditorState
     return defaultStyles!.paragraph!.verticalSpacing;
     return defaultStyles!.paragraph!.verticalSpacing;
   }
   }
 
 
-  Tuple2<double, double> _getVerticalSpacingForBlock(Block node, DefaultStyles? defaultStyles) {
+  Tuple2<double, double> _getVerticalSpacingForBlock(
+      Block node, DefaultStyles? defaultStyles) {
     final attrs = node.style.attributes;
     final attrs = node.style.attributes;
     if (attrs.containsKey(Attribute.quoteBlock.key)) {
     if (attrs.containsKey(Attribute.quoteBlock.key)) {
       return defaultStyles!.quote!.verticalSpacing;
       return defaultStyles!.quote!.verticalSpacing;
@@ -666,7 +723,8 @@ class _RawEditorState extends EditorState
     } else {
     } else {
       _keyboardVisibilityController = KeyboardVisibilityController();
       _keyboardVisibilityController = KeyboardVisibilityController();
       _keyboardVisible = _keyboardVisibilityController!.isVisible;
       _keyboardVisible = _keyboardVisibilityController!.isVisible;
-      _keyboardVisibilitySubscription = _keyboardVisibilityController?.onChange.listen((visible) {
+      _keyboardVisibilitySubscription =
+          _keyboardVisibilityController?.onChange.listen((visible) {
         _keyboardVisible = visible;
         _keyboardVisible = visible;
         if (visible) {
         if (visible) {
           _onChangeTextEditingValue();
           _onChangeTextEditingValue();
@@ -689,7 +747,9 @@ class _RawEditorState extends EditorState
     super.didChangeDependencies();
     super.didChangeDependencies();
     final parentStyles = EditorStyles.getStyles(context, true);
     final parentStyles = EditorStyles.getStyles(context, true);
     final defaultStyles = DefaultStyles.getInstance(context);
     final defaultStyles = DefaultStyles.getInstance(context);
-    _styles = (parentStyles != null) ? defaultStyles.merge(parentStyles) : defaultStyles;
+    _styles = (parentStyles != null)
+        ? defaultStyles.merge(parentStyles)
+        : defaultStyles;
 
 
     if (widget.customStyles != null) {
     if (widget.customStyles != null) {
       _styles = _styles!.merge(widget.customStyles!);
       _styles = _styles!.merge(widget.customStyles!);
@@ -749,7 +809,8 @@ class _RawEditorState extends EditorState
   }
   }
 
 
   bool _shouldShowSelectionHandles() {
   bool _shouldShowSelectionHandles() {
-    return widget.showSelectionHandles && !widget.controller.selection.isCollapsed;
+    return widget.showSelectionHandles &&
+        !widget.controller.selection.isCollapsed;
   }
   }
 
 
   void handleDelete(bool forward) {
   void handleDelete(bool forward) {
@@ -760,7 +821,8 @@ class _RawEditorState extends EditorState
     var textAfter = selection.textAfter(plainText);
     var textAfter = selection.textAfter(plainText);
     if (selection.isCollapsed) {
     if (selection.isCollapsed) {
       if (!forward && textBefore.isNotEmpty) {
       if (!forward && textBefore.isNotEmpty) {
-        final characterBoundary = _previousCharacter(textBefore.length, textBefore, true);
+        final characterBoundary =
+            _previousCharacter(textBefore.length, textBefore, true);
         textBefore = textBefore.substring(0, characterBoundary);
         textBefore = textBefore.substring(0, characterBoundary);
         cursorPosition = characterBoundary;
         cursorPosition = characterBoundary;
       }
       }
@@ -784,15 +846,13 @@ class _RawEditorState extends EditorState
     final selection = widget.controller.selection;
     final selection = widget.controller.selection;
     final plainText = textEditingValue.text;
     final plainText = textEditingValue.text;
     if (shortcut == InputShortcut.SAVE) {
     if (shortcut == InputShortcut.SAVE) {
-      bool saved = await widget.controller.save();
-      if (!saved) {
-        log('Unabled to save document.');
-      }
+      widget.controller.save();
       return;
       return;
     }
     }
     if (shortcut == InputShortcut.COPY) {
     if (shortcut == InputShortcut.COPY) {
       if (!selection.isCollapsed) {
       if (!selection.isCollapsed) {
-        await Clipboard.setData(ClipboardData(text: selection.textInside(plainText)));
+        await Clipboard.setData(
+            ClipboardData(text: selection.textInside(plainText)));
       }
       }
       return;
       return;
     }
     }
@@ -809,7 +869,8 @@ class _RawEditorState extends EditorState
         );
         );
 
 
         textEditingValue = TextEditingValue(
         textEditingValue = TextEditingValue(
-          text: selection.textBefore(plainText) + selection.textAfter(plainText),
+          text:
+              selection.textBefore(plainText) + selection.textAfter(plainText),
           selection: TextSelection.collapsed(offset: selection.start),
           selection: TextSelection.collapsed(offset: selection.start),
         );
         );
       }
       }
@@ -827,7 +888,8 @@ class _RawEditorState extends EditorState
       }
       }
       return;
       return;
     }
     }
-    if (shortcut == InputShortcut.SELECT_ALL && widget.enableInteractiveSelection) {
+    if (shortcut == InputShortcut.SELECT_ALL &&
+        widget.enableInteractiveSelection) {
       widget.controller.updateSelection(
       widget.controller.updateSelection(
           selection.copyWith(
           selection.copyWith(
             baseOffset: 0,
             baseOffset: 0,
@@ -881,14 +943,16 @@ class _RawEditorState extends EditorState
   void _onChangeTextEditingValue() {
   void _onChangeTextEditingValue() {
     _showCaretOnScreen();
     _showCaretOnScreen();
     updateRemoteValueIfNeeded();
     updateRemoteValueIfNeeded();
-    _cursorController.startOrStopCursorTimerIfNeeded(_hasFocus, widget.controller.selection);
+    _cursorController.startOrStopCursorTimerIfNeeded(
+        _hasFocus, widget.controller.selection);
     if (hasConnection) {
     if (hasConnection) {
       _cursorController
       _cursorController
         ..stopCursorTimer(resetCharTicks: false)
         ..stopCursorTimer(resetCharTicks: false)
         ..startCursorTimer();
         ..startCursorTimer();
     }
     }
 
 
-    SchedulerBinding.instance!.addPostFrameCallback((_) => _updateOrDisposeSelectionOverlayIfNeeded());
+    SchedulerBinding.instance!.addPostFrameCallback(
+        (_) => _updateOrDisposeSelectionOverlayIfNeeded());
     if (mounted) {
     if (mounted) {
       setState(() {
       setState(() {
         // Use widget.controller.value in build()
         // Use widget.controller.value in build()
@@ -931,7 +995,8 @@ class _RawEditorState extends EditorState
 
 
   void _handleFocusChanged() {
   void _handleFocusChanged() {
     openOrCloseConnection();
     openOrCloseConnection();
-    _cursorController.startOrStopCursorTimerIfNeeded(_hasFocus, widget.controller.selection);
+    _cursorController.startOrStopCursorTimerIfNeeded(
+        _hasFocus, widget.controller.selection);
     _updateOrDisposeSelectionOverlayIfNeeded();
     _updateOrDisposeSelectionOverlayIfNeeded();
     if (_hasFocus) {
     if (_hasFocus) {
       WidgetsBinding.instance!.addObserver(this);
       WidgetsBinding.instance!.addObserver(this);
@@ -962,7 +1027,8 @@ class _RawEditorState extends EditorState
       _showCaretOnScreenScheduled = false;
       _showCaretOnScreenScheduled = false;
 
 
       final viewport = RenderAbstractViewport.of(getRenderEditor())!;
       final viewport = RenderAbstractViewport.of(getRenderEditor())!;
-      final editorOffset = getRenderEditor()!.localToGlobal(const Offset(0, 0), ancestor: viewport);
+      final editorOffset = getRenderEditor()!
+          .localToGlobal(const Offset(0, 0), ancestor: viewport);
       final offsetInViewport = _scrollController!.offset + editorOffset.dy;
       final offsetInViewport = _scrollController!.offset + editorOffset.dy;
 
 
       final offset = getRenderEditor()!.getOffsetToRevealCursor(
       final offset = getRenderEditor()!.getOffsetToRevealCursor(
@@ -1045,7 +1111,8 @@ class _RawEditorState extends EditorState
       final value = textEditingValue;
       final value = textEditingValue;
       final data = await Clipboard.getData(Clipboard.kTextPlain);
       final data = await Clipboard.getData(Clipboard.kTextPlain);
       if (data != null) {
       if (data != null) {
-        final length = textEditingValue.selection.end - textEditingValue.selection.start;
+        final length =
+            textEditingValue.selection.end - textEditingValue.selection.start;
         widget.controller.replaceText(
         widget.controller.replaceText(
           value.selection.start,
           value.selection.start,
           length,
           length,
@@ -1054,7 +1121,9 @@ class _RawEditorState extends EditorState
         );
         );
         // move cursor to the end of pasted text selection
         // move cursor to the end of pasted text selection
         widget.controller.updateSelection(
         widget.controller.updateSelection(
-            TextSelection.collapsed(offset: value.selection.start + data.text!.length), ChangeSource.LOCAL);
+            TextSelection.collapsed(
+                offset: value.selection.start + data.text!.length),
+            ChangeSource.LOCAL);
       }
       }
     }
     }
   }
   }
@@ -1064,7 +1133,8 @@ class _RawEditorState extends EditorState
     if (data == null) {
     if (data == null) {
       return false;
       return false;
     }
     }
-    return textEditingValue.text.length - value.text.length == data.text!.length;
+    return textEditingValue.text.length - value.text.length ==
+        data.text!.length;
   }
   }
 
 
   @override
   @override
@@ -1097,7 +1167,8 @@ class _RawEditorState extends EditorState
   }
   }
 
 
   @override
   @override
-  void userUpdateTextEditingValue(TextEditingValue value, SelectionChangedCause cause) {
+  void userUpdateTextEditingValue(
+      TextEditingValue value, SelectionChangedCause cause) {
     // TODO: implement userUpdateTextEditingValue
     // TODO: implement userUpdateTextEditingValue
   }
   }
 }
 }
@@ -1147,7 +1218,8 @@ class _Editor extends MultiChildRenderObjectWidget {
   }
   }
 
 
   @override
   @override
-  void updateRenderObject(BuildContext context, covariant RenderEditor renderObject) {
+  void updateRenderObject(
+      BuildContext context, covariant RenderEditor renderObject) {
     renderObject
     renderObject
       ..document = document
       ..document = document
       ..container = document.root
       ..container = document.root

+ 1 - 1
app_flowy/packages/flowy_infra/lib/size.dart

@@ -54,7 +54,7 @@ class Sizes {
 
 
   static double get sideBarSm => 200 * hitScale;
   static double get sideBarSm => 200 * hitScale;
 
 
-  static double get sideBarMed => 220 * hitScale;
+  static double get sideBarMed => 240 * hitScale;
 
 
   static double get sideBarLg => 290 * hitScale;
   static double get sideBarLg => 290 * hitScale;
 }
 }

+ 1 - 1
app_flowy/packages/flowy_infra/lib/theme.dart

@@ -39,7 +39,7 @@ class AppTheme {
     switch (t) {
     switch (t) {
       case ThemeType.light:
       case ThemeType.light:
         return AppTheme(isDark: false)
         return AppTheme(isDark: false)
-          ..bg1 = const Color(0xfff1f7f0)
+          ..bg1 = const Color.fromARGB(255, 247, 248, 252)
           ..bg2 = const Color(0xffc1dcbc)
           ..bg2 = const Color(0xffc1dcbc)
           ..surface = Colors.white
           ..surface = Colors.white
           ..accent1 = const Color(0xff00a086)
           ..accent1 = const Color(0xff00a086)

+ 1 - 0
app_flowy/packages/flowy_infra/lib/time/duration.dart

@@ -13,4 +13,5 @@ class Durations {
 
 
 class RouteDurations {
 class RouteDurations {
   static Duration get slow => .7.seconds;
   static Duration get slow => .7.seconds;
+  static Duration get medium => .35.seconds;
 }
 }

+ 8 - 1
app_flowy/packages/flowy_infra_ui/example/pubspec.lock

@@ -156,6 +156,13 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "1.0.1"
     version: "1.0.1"
+  loading_indicator:
+    dependency: transitive
+    description:
+      name: loading_indicator
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.0.1"
   logger:
   logger:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -296,4 +303,4 @@ packages:
     version: "2.1.0"
     version: "2.1.0"
 sdks:
 sdks:
   dart: ">=2.12.0 <3.0.0"
   dart: ">=2.12.0 <3.0.0"
-  flutter: ">=1.20.0"
+  flutter: ">=2.0.0"

+ 57 - 0
app_flowy/packages/flowy_infra_ui/lib/style_widget/styled_hover.dart

@@ -0,0 +1,57 @@
+import 'package:flutter/material.dart';
+// ignore: unused_import
+import 'package:flowy_infra/time/duration.dart';
+
+typedef HoverBuilder = Widget Function(BuildContext context, bool onHover);
+
+class StyledHover extends StatefulWidget {
+  final Color color;
+  final Color borderColor;
+  final double borderWidth;
+  final BorderRadius borderRadius;
+  final HoverBuilder builder;
+
+  const StyledHover({
+    Key? key,
+    required this.color,
+    this.borderColor = Colors.transparent,
+    this.borderWidth = 0,
+    this.borderRadius = BorderRadius.zero,
+    required this.builder,
+  }) : super(key: key);
+
+  @override
+  State<StyledHover> createState() => _StyledHoverState();
+}
+
+class _StyledHoverState extends State<StyledHover> {
+  bool _onHover = false;
+
+  @override
+  Widget build(BuildContext context) {
+    final hoverColor =
+        _onHover ? widget.color : Theme.of(context).colorScheme.background;
+
+    final hoverBorder = Border.all(
+      color: widget.borderColor,
+      width: widget.borderWidth,
+    );
+
+    return MouseRegion(
+      cursor: SystemMouseCursors.click,
+      onEnter: (p) => setOnHover(true),
+      onExit: (p) => setOnHover(false),
+      child: Container(
+        decoration: BoxDecoration(
+          border: hoverBorder,
+          color: hoverColor,
+          borderRadius: widget.borderRadius,
+        ),
+        // duration: .1.seconds,
+        child: widget.builder(context, _onHover),
+      ),
+    );
+  }
+
+  void setOnHover(bool value) => setState(() => _onHover = value);
+}

+ 55 - 0
app_flowy/packages/flowy_infra_ui/lib/style_widget/styled_icon_button.dart

@@ -0,0 +1,55 @@
+import 'package:flutter/material.dart';
+
+class StyledIconButton extends StatelessWidget {
+  final double width;
+  final double? height;
+  final double iconRatio;
+  final Icon icon;
+  final VoidCallback? onPressed;
+
+  const StyledIconButton({
+    Key? key,
+    this.height,
+    this.onPressed,
+    this.width = 30,
+    required this.icon,
+    this.iconRatio = 0.5,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return SizedBox(
+      width: width,
+      height: height ?? width,
+      child: IconButton(
+        icon: icon,
+        padding: EdgeInsets.zero,
+        iconSize: width * iconRatio,
+        alignment: Alignment.center,
+        onPressed: onPressed,
+      ),
+    );
+  }
+}
+
+class StyledMore extends StatelessWidget {
+  final double width;
+  final double? height;
+  final VoidCallback? onPressed;
+
+  const StyledMore({
+    Key? key,
+    this.height,
+    this.onPressed,
+    required this.width,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return StyledIconButton(
+      width: width,
+      height: height,
+      icon: const Icon(Icons.more_vert),
+    );
+  }
+}

+ 107 - 0
app_flowy/packages/flowy_infra_ui/lib/style_widget/styled_navigation_list.dart

@@ -0,0 +1,107 @@
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+typedef NaviAction = void Function(String);
+
+abstract class NaviItem {
+  String get identifier;
+  NaviAction get action;
+}
+
+class StyledNavigationController extends ChangeNotifier {
+  List<NaviItem> naviItems;
+  StyledNavigationController({this.naviItems = const []});
+}
+
+class StyledNavigationList extends StatelessWidget {
+  const StyledNavigationList({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return MultiProvider(
+      providers: [
+        ChangeNotifierProvider(create: (_) => StyledNavigationController()),
+      ],
+      child: Consumer(builder: (ctx, StyledNavigationController ctrl, child) {
+        return Row(
+          children: _buildNaviItemWidget(ctrl.naviItems),
+        );
+      }),
+    );
+  }
+
+  List<Widget> _buildNaviItemWidget(List<NaviItem> items) {
+    if (items.isEmpty) {
+      return [];
+    }
+
+    List<NaviItem> newItems = _selectNaviItem(items);
+    Widget last = NaviItemWidget(newItems.removeLast());
+
+    List<Widget> widgets = newItems
+        .map(
+          (item) => NaviItemDivider(
+            child: NaviItemWidget(item),
+          ),
+        )
+        .toList();
+
+    widgets.add(last);
+
+    return widgets;
+  }
+
+  List<NaviItem> _selectNaviItem(List<NaviItem> items) {
+    final length = items.length;
+    if (length > 4) {
+      final ellipsisItems = items.getRange(1, length - 2).toList();
+      return [
+        items[0],
+        EllipsisNaviItem(items: ellipsisItems),
+        items[length - 2],
+        items[length - 1]
+      ];
+    } else {
+      return items;
+    }
+  }
+}
+
+class NaviItemWidget extends StatelessWidget {
+  final NaviItem item;
+  const NaviItemWidget(this.item, {Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      child: null,
+    );
+  }
+}
+
+class NaviItemDivider extends StatelessWidget {
+  final Widget child;
+  const NaviItemDivider({Key? key, required this.child}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Row(
+      children: [child, const Text('/')],
+    );
+  }
+}
+
+class EllipsisNaviItem extends NaviItem {
+  final List<NaviItem> items;
+  EllipsisNaviItem({
+    required this.items,
+  });
+
+  @override
+  // TODO: implement action
+  NaviAction get action => throw UnimplementedError();
+
+  @override
+  // TODO: implement identifier
+  String get identifier => throw UnimplementedError();
+}

+ 34 - 0
app_flowy/packages/flowy_infra_ui/lib/style_widget/styled_progress_indicator.dart

@@ -0,0 +1,34 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+import 'package:loading_indicator/loading_indicator.dart';
+
+List<Color> _kDefaultRainbowColors = const [
+  Colors.red,
+  Colors.orange,
+  Colors.yellow,
+  Colors.green,
+  Colors.blue,
+  Colors.indigo,
+  Colors.purple,
+];
+
+// CircularProgressIndicator()
+class StyledProgressIndicator extends StatelessWidget {
+  const StyledProgressIndicator({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return SizedBox.expand(
+      child: Center(
+        child: SizedBox(
+          width: 60,
+          child: LoadingIndicator(
+            indicatorType: Indicator.pacman,
+            colors: _kDefaultRainbowColors,
+            strokeWidth: 4.0,
+          ),
+        ),
+      ),
+    );
+  }
+}

+ 23 - 0
app_flowy/packages/flowy_infra_ui/lib/style_widget/styled_text.dart

@@ -0,0 +1,23 @@
+import 'package:flutter/widgets.dart';
+
+class StyledText extends StatelessWidget {
+  final String title;
+  final TextOverflow overflow;
+  final double fontSize;
+  const StyledText(
+    this.title, {
+    Key? key,
+    this.overflow = TextOverflow.ellipsis,
+    this.fontSize = 16,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return Text(
+      title,
+      overflow: overflow,
+      softWrap: false,
+      style: TextStyle(fontSize: fontSize),
+    );
+  }
+}

+ 54 - 0
app_flowy/packages/flowy_infra_ui/lib/style_widget/styled_text_button.dart

@@ -0,0 +1,54 @@
+import 'package:flowy_infra_ui/style_widget/styled_hover.dart';
+import 'package:flowy_infra_ui/style_widget/styled_text.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+
+class StyledTextButton extends StatelessWidget {
+  final String text;
+  final double fontSize;
+  final VoidCallback? onPressed;
+  final EdgeInsets padding;
+  const StyledTextButton(this.text,
+      {Key? key,
+      this.onPressed,
+      this.fontSize = 16,
+      this.padding = const EdgeInsets.symmetric(horizontal: 8, vertical: 6)})
+      : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return InkWell(
+      onTap: onPressed,
+      child: StyledHover(
+        color: Colors.grey.shade300,
+        borderRadius: BorderRadius.circular(8),
+        builder: (context, onHover) => _render(),
+      ),
+    );
+  }
+
+  Widget _render() {
+    return Padding(
+      padding: padding,
+      child: Align(
+        alignment: Alignment.centerLeft,
+        child: StyledText(text, fontSize: fontSize),
+      ),
+    );
+  }
+}
+// return TextButton(
+    //   style: ButtonStyle(
+    //     textStyle: MaterialStateProperty.all(TextStyle(fontSize: fontSize)),
+    //     alignment: Alignment.centerLeft,
+    //     foregroundColor: MaterialStateProperty.all(Colors.black),
+    //     padding: MaterialStateProperty.all<EdgeInsets>(
+    //         const EdgeInsets.symmetric(horizontal: 2)),
+    //   ),
+    //   onPressed: onPressed,
+    //   child: Text(
+    //     text,
+    //     overflow: TextOverflow.ellipsis,
+    //     softWrap: false,
+    //   ),
+    // );

+ 6 - 6
app_flowy/packages/flowy_infra_ui/lib/widget/mouse_hover_builder.dart

@@ -1,7 +1,7 @@
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/rendering.dart';
 import 'package:flutter/rendering.dart';
 
 
-typedef HoverBuilder = Widget Function(BuildContext context, bool isHovering);
+typedef HoverBuilder = Widget Function(BuildContext context, bool onHover);
 
 
 class MouseHoverBuilder extends StatefulWidget {
 class MouseHoverBuilder extends StatefulWidget {
   final bool isClickable;
   final bool isClickable;
@@ -15,17 +15,17 @@ class MouseHoverBuilder extends StatefulWidget {
 }
 }
 
 
 class _MouseHoverBuilderState extends State<MouseHoverBuilder> {
 class _MouseHoverBuilderState extends State<MouseHoverBuilder> {
-  bool isOver = false;
+  bool _onHover = false;
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     return MouseRegion(
     return MouseRegion(
       cursor: widget.isClickable ? SystemMouseCursors.click : SystemMouseCursors.basic,
       cursor: widget.isClickable ? SystemMouseCursors.click : SystemMouseCursors.basic,
-      onEnter: (p) => setOver(true),
-      onExit: (p) => setOver(false),
-      child: widget.builder(context, isOver),
+      onEnter: (p) => setOnHover(true),
+      onExit: (p) => setOnHover(false),
+      child: widget.builder(context, _onHover),
     );
     );
   }
   }
 
 
-  void setOver(bool value) => setState(() => isOver = value);
+  void setOnHover(bool value) => setState(() => _onHover = value);
 }
 }

+ 69 - 12
app_flowy/packages/flowy_infra_ui/lib/widget/rounded_button.dart

@@ -1,32 +1,89 @@
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 
 
-class RoundedButton extends StatelessWidget {
+class RoundedTextButton extends StatelessWidget {
   final VoidCallback? press;
   final VoidCallback? press;
   final String? title;
   final String? title;
-  final Size? size;
+  final double? width;
+  final double? height;
+  final BorderRadius borderRadius;
+  final Color borderColor;
+  final Color color;
+  final Color textColor;
+  final double fontSize;
 
 
-  const RoundedButton({
+  const RoundedTextButton({
     Key? key,
     Key? key,
     this.press,
     this.press,
     this.title,
     this.title,
-    this.size,
+    this.width,
+    this.height,
+    this.borderRadius = BorderRadius.zero,
+    this.borderColor = Colors.transparent,
+    this.color = Colors.transparent,
+    this.textColor = Colors.white,
+    this.fontSize = 16,
   }) : super(key: key);
   }) : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     return ConstrainedBox(
     return ConstrainedBox(
       constraints: BoxConstraints(
       constraints: BoxConstraints(
-        minWidth: 100,
-        maxWidth: size?.width ?? double.infinity,
-        minHeight: 50,
-        maxHeight: size?.height ?? double.infinity,
+        minWidth: 10,
+        maxWidth: width ?? double.infinity,
+        minHeight: 10,
+        maxHeight: height ?? 60,
       ),
       ),
       child: Container(
       child: Container(
-        margin: const EdgeInsets.symmetric(vertical: 10),
-        child: TextButton(
-          child: Text(title ?? ''),
-          onPressed: press,
+        decoration: BoxDecoration(
+          border: Border.all(color: borderColor),
+          borderRadius: borderRadius,
+          color: color,
         ),
         ),
+        child: SizedBox.expand(
+          child: TextButton(
+            child: Text(
+              title ?? '',
+              style: TextStyle(color: textColor, fontSize: fontSize),
+            ),
+            onPressed: press,
+          ),
+        ),
+      ),
+    );
+  }
+}
+
+class RoundedImageButton extends StatelessWidget {
+  final VoidCallback? press;
+  final double size;
+  final BorderRadius borderRadius;
+  final Color borderColor;
+  final Color color;
+  final Widget child;
+
+  const RoundedImageButton({
+    Key? key,
+    this.press,
+    required this.size,
+    this.borderRadius = BorderRadius.zero,
+    this.borderColor = Colors.transparent,
+    this.color = Colors.transparent,
+    required this.child,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return SizedBox(
+      width: size,
+      height: size,
+      child: TextButton(
+        onPressed: press,
+        style: ButtonStyle(
+            shape: MaterialStateProperty.all<RoundedRectangleBorder>(
+                RoundedRectangleBorder(
+          borderRadius: borderRadius,
+        ))),
+        child: child,
       ),
       ),
     );
     );
   }
   }

+ 76 - 15
app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart

@@ -1,35 +1,96 @@
+import 'package:flowy_infra_ui/widget/rounded_button.dart';
 import 'package:flowy_infra_ui/widget/text_field_container.dart';
 import 'package:flowy_infra_ui/widget/text_field_container.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
+import 'package:flowy_infra/time/duration.dart';
 
 
-class RoundedInputField extends StatelessWidget {
+// ignore: must_be_immutable
+class RoundedInputField extends StatefulWidget {
   final String? hintText;
   final String? hintText;
   final IconData? icon;
   final IconData? icon;
   final bool obscureText;
   final bool obscureText;
+  final Color normalBorderColor;
+  final Color highlightBorderColor;
+  final String errorText;
   final ValueChanged<String>? onChanged;
   final ValueChanged<String>? onChanged;
+  late bool enableObscure;
 
 
-  const RoundedInputField({
+  RoundedInputField({
     Key? key,
     Key? key,
     this.hintText,
     this.hintText,
-    this.icon = Icons.person,
+    this.icon,
     this.obscureText = false,
     this.obscureText = false,
     this.onChanged,
     this.onChanged,
-  }) : super(key: key);
+    this.normalBorderColor = Colors.transparent,
+    this.highlightBorderColor = Colors.transparent,
+    this.errorText = "",
+  }) : super(key: key) {
+    enableObscure = obscureText;
+  }
+
+  @override
+  State<RoundedInputField> createState() => _RoundedInputFieldState();
+}
 
 
+class _RoundedInputFieldState extends State<RoundedInputField> {
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
-    return TextFieldContainer(
+    final Icon? newIcon = widget.icon == null
+        ? null
+        : Icon(
+            widget.icon!,
+            color: const Color(0xFF6F35A5),
+          );
+
+    var borderColor = widget.normalBorderColor;
+    if (widget.errorText.isNotEmpty) {
+      borderColor = widget.highlightBorderColor;
+    }
+
+    List<Widget> children = [
+      TextFieldContainer(
+        borderRadius: BorderRadius.circular(10),
+        borderColor: borderColor,
         child: TextFormField(
         child: TextFormField(
-      onChanged: onChanged,
-      cursorColor: const Color(0xFF6F35A5),
-      obscureText: obscureText,
-      decoration: InputDecoration(
-        icon: Icon(
-          icon,
-          color: const Color(0xFF6F35A5),
+          onChanged: widget.onChanged,
+          cursorColor: const Color(0xFF6F35A5),
+          obscureText: widget.enableObscure,
+          decoration: InputDecoration(
+            icon: newIcon,
+            hintText: widget.hintText,
+            border: InputBorder.none,
+            suffixIcon: suffixIcon(),
+          ),
         ),
         ),
-        hintText: hintText,
-        border: InputBorder.none,
       ),
       ),
-    ));
+    ];
+
+    if (widget.errorText.isNotEmpty) {
+      children.add(Text(
+        widget.errorText,
+        style: TextStyle(color: widget.highlightBorderColor),
+      ));
+    }
+
+    return AnimatedSize(
+      duration: .4.seconds,
+      curve: Curves.easeInOut,
+      child: Column(
+        children: children,
+      ),
+    );
+  }
+
+  Widget? suffixIcon() {
+    if (widget.obscureText == false) {
+      return null;
+    }
+    return RoundedImageButton(
+      size: 20,
+      press: () {
+        widget.enableObscure = !widget.enableObscure;
+        setState(() {});
+      },
+      child: const Icon(Icons.password, size: 15),
+    );
   }
   }
 }
 }

+ 14 - 3
app_flowy/packages/flowy_infra_ui/lib/widget/text_field_container.dart

@@ -3,21 +3,32 @@ import 'package:flutter/material.dart';
 
 
 class TextFieldContainer extends StatelessWidget {
 class TextFieldContainer extends StatelessWidget {
   final Widget child;
   final Widget child;
+  final BorderRadius borderRadius;
+  final Color borderColor;
+  final double? height;
+  final double? width;
   const TextFieldContainer({
   const TextFieldContainer({
     Key? key,
     Key? key,
     required this.child,
     required this.child,
+    this.borderRadius = BorderRadius.zero,
+    this.borderColor = Colors.white,
+    this.height,
+    this.width,
   }) : super(key: key);
   }) : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     return Container(
     return Container(
       margin: const EdgeInsets.symmetric(vertical: 10),
       margin: const EdgeInsets.symmetric(vertical: 10),
-      padding: const EdgeInsets.symmetric(horizontal: 20),
+      padding: const EdgeInsets.symmetric(horizontal: 15),
+      height: height,
+      width: width,
       decoration: BoxDecoration(
       decoration: BoxDecoration(
+        border: Border.all(color: borderColor),
         color: Colors.white,
         color: Colors.white,
-        borderRadius: BorderRadius.circular(30),
+        borderRadius: borderRadius,
       ),
       ),
-      child: child,
+      child: Align(alignment: Alignment.center, child: child),
     );
     );
   }
   }
 
 

+ 12 - 5
app_flowy/packages/flowy_infra_ui/pubspec.lock

@@ -14,7 +14,7 @@ packages:
       name: async
       name: async
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
-    version: "2.7.0"
+    version: "2.6.1"
   boolean_selector:
   boolean_selector:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -35,7 +35,7 @@ packages:
       name: charcode
       name: charcode
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
-    version: "1.3.1"
+    version: "1.2.0"
   clock:
   clock:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -142,6 +142,13 @@ packages:
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
     version: "1.0.1"
     version: "1.0.1"
+  loading_indicator:
+    dependency: "direct main"
+    description:
+      name: loading_indicator
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.0.1"
   logger:
   logger:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -162,7 +169,7 @@ packages:
       name: meta
       name: meta
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
-    version: "1.7.0"
+    version: "1.3.0"
   nested:
   nested:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -244,7 +251,7 @@ packages:
       name: test_api
       name: test_api
       url: "https://pub.dartlang.org"
       url: "https://pub.dartlang.org"
     source: hosted
     source: hosted
-    version: "0.4.1"
+    version: "0.3.0"
   textstyle_extensions:
   textstyle_extensions:
     dependency: "direct main"
     dependency: "direct main"
     description:
     description:
@@ -282,4 +289,4 @@ packages:
     version: "2.1.0"
     version: "2.1.0"
 sdks:
 sdks:
   dart: ">=2.12.0 <3.0.0"
   dart: ">=2.12.0 <3.0.0"
-  flutter: ">=1.20.0"
+  flutter: ">=2.0.0"

+ 1 - 0
app_flowy/packages/flowy_infra_ui/pubspec.yaml

@@ -19,6 +19,7 @@ dependencies:
   styled_widget: '>=0.3.1'
   styled_widget: '>=0.3.1'
   equatable: '>=2.0.2'
   equatable: '>=2.0.2'
   animations: ^2.0.0
   animations: ^2.0.0
+  loading_indicator: ^3.0.1
 
 
   # Federated Platform Interface
   # Federated Platform Interface
   flowy_infra_ui_platform_interface:
   flowy_infra_ui_platform_interface:

+ 14 - 0
app_flowy/packages/flowy_sdk/lib/dispatch/code_gen.dart

@@ -118,6 +118,20 @@ class WorkspaceEventGetWorkspace {
     }
     }
 }
 }
 
 
+class WorkspaceEventReadAllWorkspace {
+    WorkspaceEventReadAllWorkspace();
+
+    Future<Either<Workspaces, WorkspaceError>> send() {
+     final request = FFIRequest.create()
+        ..event = WorkspaceEvent.ReadAllWorkspace.toString();
+
+     return Dispatch.asyncRequest(request).then((bytesResult) => bytesResult.fold(
+        (okBytes) => left(Workspaces.fromBuffer(okBytes)),
+        (errBytes) => right(WorkspaceError.fromBuffer(errBytes)),
+      ));
+    }
+}
+
 class WorkspaceEventCreateApp {
 class WorkspaceEventCreateApp {
      CreateAppRequest request;
      CreateAppRequest request;
      WorkspaceEventCreateApp(this.request);
      WorkspaceEventCreateApp(this.request);

+ 4 - 4
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/errors.pb.dart

@@ -15,14 +15,14 @@ export 'errors.pbenum.dart';
 
 
 class UserError extends $pb.GeneratedMessage {
 class UserError extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'UserError', createEmptyInstance: create)
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'UserError', createEmptyInstance: create)
-    ..e<UserErrorCode>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'code', $pb.PbFieldType.OE, defaultOrMaker: UserErrorCode.Unknown, valueOf: UserErrorCode.valueOf, enumValues: UserErrorCode.values)
+    ..e<UserErrCode>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'code', $pb.PbFieldType.OE, defaultOrMaker: UserErrCode.Unknown, valueOf: UserErrCode.valueOf, enumValues: UserErrCode.values)
     ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'msg')
     ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'msg')
     ..hasRequiredFields = false
     ..hasRequiredFields = false
   ;
   ;
 
 
   UserError._() : super();
   UserError._() : super();
   factory UserError({
   factory UserError({
-    UserErrorCode? code,
+    UserErrCode? code,
     $core.String? msg,
     $core.String? msg,
   }) {
   }) {
     final _result = create();
     final _result = create();
@@ -56,9 +56,9 @@ class UserError extends $pb.GeneratedMessage {
   static UserError? _defaultInstance;
   static UserError? _defaultInstance;
 
 
   @$pb.TagNumber(1)
   @$pb.TagNumber(1)
-  UserErrorCode get code => $_getN(0);
+  UserErrCode get code => $_getN(0);
   @$pb.TagNumber(1)
   @$pb.TagNumber(1)
-  set code(UserErrorCode v) { setField(1, v); }
+  set code(UserErrCode v) { setField(1, v); }
   @$pb.TagNumber(1)
   @$pb.TagNumber(1)
   $core.bool hasCode() => $_has(0);
   $core.bool hasCode() => $_has(0);
   @$pb.TagNumber(1)
   @$pb.TagNumber(1)

+ 22 - 22
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/errors.pbenum.dart

@@ -9,26 +9,26 @@
 import 'dart:core' as $core;
 import 'dart:core' as $core;
 import 'package:protobuf/protobuf.dart' as $pb;
 import 'package:protobuf/protobuf.dart' as $pb;
 
 
-class UserErrorCode extends $pb.ProtobufEnum {
-  static const UserErrorCode Unknown = UserErrorCode._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Unknown');
-  static const UserErrorCode UserDatabaseInitFailed = UserErrorCode._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserDatabaseInitFailed');
-  static const UserErrorCode UserDatabaseWriteLocked = UserErrorCode._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserDatabaseWriteLocked');
-  static const UserErrorCode UserDatabaseReadLocked = UserErrorCode._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserDatabaseReadLocked');
-  static const UserErrorCode UserDatabaseDidNotMatch = UserErrorCode._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserDatabaseDidNotMatch');
-  static const UserErrorCode UserDatabaseInternalError = UserErrorCode._(5, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserDatabaseInternalError');
-  static const UserErrorCode SqlInternalError = UserErrorCode._(6, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'SqlInternalError');
-  static const UserErrorCode UserNotLoginYet = UserErrorCode._(10, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserNotLoginYet');
-  static const UserErrorCode ReadCurrentIdFailed = UserErrorCode._(11, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ReadCurrentIdFailed');
-  static const UserErrorCode WriteCurrentIdFailed = UserErrorCode._(12, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WriteCurrentIdFailed');
-  static const UserErrorCode EmailInvalid = UserErrorCode._(20, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'EmailInvalid');
-  static const UserErrorCode PasswordInvalid = UserErrorCode._(21, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PasswordInvalid');
-  static const UserErrorCode UserNameInvalid = UserErrorCode._(22, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserNameInvalid');
-  static const UserErrorCode UserWorkspaceInvalid = UserErrorCode._(23, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserWorkspaceInvalid');
-  static const UserErrorCode UserIdInvalid = UserErrorCode._(24, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserIdInvalid');
-  static const UserErrorCode CreateDefaultWorkspaceFailed = UserErrorCode._(25, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateDefaultWorkspaceFailed');
-  static const UserErrorCode DefaultWorkspaceAlreadyExist = UserErrorCode._(26, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DefaultWorkspaceAlreadyExist');
+class UserErrCode extends $pb.ProtobufEnum {
+  static const UserErrCode Unknown = UserErrCode._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Unknown');
+  static const UserErrCode UserDatabaseInitFailed = UserErrCode._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserDatabaseInitFailed');
+  static const UserErrCode UserDatabaseWriteLocked = UserErrCode._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserDatabaseWriteLocked');
+  static const UserErrCode UserDatabaseReadLocked = UserErrCode._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserDatabaseReadLocked');
+  static const UserErrCode UserDatabaseDidNotMatch = UserErrCode._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserDatabaseDidNotMatch');
+  static const UserErrCode UserDatabaseInternalError = UserErrCode._(5, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserDatabaseInternalError');
+  static const UserErrCode SqlInternalError = UserErrCode._(6, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'SqlInternalError');
+  static const UserErrCode UserNotLoginYet = UserErrCode._(10, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserNotLoginYet');
+  static const UserErrCode ReadCurrentIdFailed = UserErrCode._(11, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ReadCurrentIdFailed');
+  static const UserErrCode WriteCurrentIdFailed = UserErrCode._(12, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WriteCurrentIdFailed');
+  static const UserErrCode EmailInvalid = UserErrCode._(20, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'EmailInvalid');
+  static const UserErrCode PasswordInvalid = UserErrCode._(21, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PasswordInvalid');
+  static const UserErrCode UserNameInvalid = UserErrCode._(22, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserNameInvalid');
+  static const UserErrCode UserWorkspaceInvalid = UserErrCode._(23, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserWorkspaceInvalid');
+  static const UserErrCode UserIdInvalid = UserErrCode._(24, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserIdInvalid');
+  static const UserErrCode CreateDefaultWorkspaceFailed = UserErrCode._(25, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateDefaultWorkspaceFailed');
+  static const UserErrCode DefaultWorkspaceAlreadyExist = UserErrCode._(26, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DefaultWorkspaceAlreadyExist');
 
 
-  static const $core.List<UserErrorCode> values = <UserErrorCode> [
+  static const $core.List<UserErrCode> values = <UserErrCode> [
     Unknown,
     Unknown,
     UserDatabaseInitFailed,
     UserDatabaseInitFailed,
     UserDatabaseWriteLocked,
     UserDatabaseWriteLocked,
@@ -48,9 +48,9 @@ class UserErrorCode extends $pb.ProtobufEnum {
     DefaultWorkspaceAlreadyExist,
     DefaultWorkspaceAlreadyExist,
   ];
   ];
 
 
-  static final $core.Map<$core.int, UserErrorCode> _byValue = $pb.ProtobufEnum.initByValue(values);
-  static UserErrorCode? valueOf($core.int value) => _byValue[value];
+  static final $core.Map<$core.int, UserErrCode> _byValue = $pb.ProtobufEnum.initByValue(values);
+  static UserErrCode? valueOf($core.int value) => _byValue[value];
 
 
-  const UserErrorCode._($core.int v, $core.String n) : super(v, n);
+  const UserErrCode._($core.int v, $core.String n) : super(v, n);
 }
 }
 
 

+ 7 - 7
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-user/errors.pbjson.dart

@@ -8,9 +8,9 @@
 import 'dart:core' as $core;
 import 'dart:core' as $core;
 import 'dart:convert' as $convert;
 import 'dart:convert' as $convert;
 import 'dart:typed_data' as $typed_data;
 import 'dart:typed_data' as $typed_data;
-@$core.Deprecated('Use userErrorCodeDescriptor instead')
-const UserErrorCode$json = const {
-  '1': 'UserErrorCode',
+@$core.Deprecated('Use userErrCodeDescriptor instead')
+const UserErrCode$json = const {
+  '1': 'UserErrCode',
   '2': const [
   '2': const [
     const {'1': 'Unknown', '2': 0},
     const {'1': 'Unknown', '2': 0},
     const {'1': 'UserDatabaseInitFailed', '2': 1},
     const {'1': 'UserDatabaseInitFailed', '2': 1},
@@ -32,16 +32,16 @@ const UserErrorCode$json = const {
   ],
   ],
 };
 };
 
 
-/// Descriptor for `UserErrorCode`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List userErrorCodeDescriptor = $convert.base64Decode('Cg1Vc2VyRXJyb3JDb2RlEgsKB1Vua25vd24QABIaChZVc2VyRGF0YWJhc2VJbml0RmFpbGVkEAESGwoXVXNlckRhdGFiYXNlV3JpdGVMb2NrZWQQAhIaChZVc2VyRGF0YWJhc2VSZWFkTG9ja2VkEAMSGwoXVXNlckRhdGFiYXNlRGlkTm90TWF0Y2gQBBIdChlVc2VyRGF0YWJhc2VJbnRlcm5hbEVycm9yEAUSFAoQU3FsSW50ZXJuYWxFcnJvchAGEhMKD1VzZXJOb3RMb2dpbllldBAKEhcKE1JlYWRDdXJyZW50SWRGYWlsZWQQCxIYChRXcml0ZUN1cnJlbnRJZEZhaWxlZBAMEhAKDEVtYWlsSW52YWxpZBAUEhMKD1Bhc3N3b3JkSW52YWxpZBAVEhMKD1VzZXJOYW1lSW52YWxpZBAWEhgKFFVzZXJXb3Jrc3BhY2VJbnZhbGlkEBcSEQoNVXNlcklkSW52YWxpZBAYEiAKHENyZWF0ZURlZmF1bHRXb3Jrc3BhY2VGYWlsZWQQGRIgChxEZWZhdWx0V29ya3NwYWNlQWxyZWFkeUV4aXN0EBo=');
+/// Descriptor for `UserErrCode`. Decode as a `google.protobuf.EnumDescriptorProto`.
+final $typed_data.Uint8List userErrCodeDescriptor = $convert.base64Decode('CgtVc2VyRXJyQ29kZRILCgdVbmtub3duEAASGgoWVXNlckRhdGFiYXNlSW5pdEZhaWxlZBABEhsKF1VzZXJEYXRhYmFzZVdyaXRlTG9ja2VkEAISGgoWVXNlckRhdGFiYXNlUmVhZExvY2tlZBADEhsKF1VzZXJEYXRhYmFzZURpZE5vdE1hdGNoEAQSHQoZVXNlckRhdGFiYXNlSW50ZXJuYWxFcnJvchAFEhQKEFNxbEludGVybmFsRXJyb3IQBhITCg9Vc2VyTm90TG9naW5ZZXQQChIXChNSZWFkQ3VycmVudElkRmFpbGVkEAsSGAoUV3JpdGVDdXJyZW50SWRGYWlsZWQQDBIQCgxFbWFpbEludmFsaWQQFBITCg9QYXNzd29yZEludmFsaWQQFRITCg9Vc2VyTmFtZUludmFsaWQQFhIYChRVc2VyV29ya3NwYWNlSW52YWxpZBAXEhEKDVVzZXJJZEludmFsaWQQGBIgChxDcmVhdGVEZWZhdWx0V29ya3NwYWNlRmFpbGVkEBkSIAocRGVmYXVsdFdvcmtzcGFjZUFscmVhZHlFeGlzdBAa');
 @$core.Deprecated('Use userErrorDescriptor instead')
 @$core.Deprecated('Use userErrorDescriptor instead')
 const UserError$json = const {
 const UserError$json = const {
   '1': 'UserError',
   '1': 'UserError',
   '2': const [
   '2': const [
-    const {'1': 'code', '3': 1, '4': 1, '5': 14, '6': '.UserErrorCode', '10': 'code'},
+    const {'1': 'code', '3': 1, '4': 1, '5': 14, '6': '.UserErrCode', '10': 'code'},
     const {'1': 'msg', '3': 2, '4': 1, '5': 9, '10': 'msg'},
     const {'1': 'msg', '3': 2, '4': 1, '5': 9, '10': 'msg'},
   ],
   ],
 };
 };
 
 
 /// Descriptor for `UserError`. Decode as a `google.protobuf.DescriptorProto`.
 /// Descriptor for `UserError`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List userErrorDescriptor = $convert.base64Decode('CglVc2VyRXJyb3ISIgoEY29kZRgBIAEoDjIOLlVzZXJFcnJvckNvZGVSBGNvZGUSEAoDbXNnGAIgASgJUgNtc2c=');
+final $typed_data.Uint8List userErrorDescriptor = $convert.base64Decode('CglVc2VyRXJyb3ISIAoEY29kZRgBIAEoDjIMLlVzZXJFcnJDb2RlUgRjb2RlEhAKA21zZxgCIAEoCVIDbXNn');

+ 4 - 4
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/errors.pb.dart

@@ -15,14 +15,14 @@ export 'errors.pbenum.dart';
 
 
 class WorkspaceError extends $pb.GeneratedMessage {
 class WorkspaceError extends $pb.GeneratedMessage {
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'WorkspaceError', createEmptyInstance: create)
   static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'WorkspaceError', createEmptyInstance: create)
-    ..e<WorkspaceErrorCode>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'code', $pb.PbFieldType.OE, defaultOrMaker: WorkspaceErrorCode.Unknown, valueOf: WorkspaceErrorCode.valueOf, enumValues: WorkspaceErrorCode.values)
+    ..e<WsErrCode>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'code', $pb.PbFieldType.OE, defaultOrMaker: WsErrCode.Unknown, valueOf: WsErrCode.valueOf, enumValues: WsErrCode.values)
     ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'msg')
     ..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'msg')
     ..hasRequiredFields = false
     ..hasRequiredFields = false
   ;
   ;
 
 
   WorkspaceError._() : super();
   WorkspaceError._() : super();
   factory WorkspaceError({
   factory WorkspaceError({
-    WorkspaceErrorCode? code,
+    WsErrCode? code,
     $core.String? msg,
     $core.String? msg,
   }) {
   }) {
     final _result = create();
     final _result = create();
@@ -56,9 +56,9 @@ class WorkspaceError extends $pb.GeneratedMessage {
   static WorkspaceError? _defaultInstance;
   static WorkspaceError? _defaultInstance;
 
 
   @$pb.TagNumber(1)
   @$pb.TagNumber(1)
-  WorkspaceErrorCode get code => $_getN(0);
+  WsErrCode get code => $_getN(0);
   @$pb.TagNumber(1)
   @$pb.TagNumber(1)
-  set code(WorkspaceErrorCode v) { setField(1, v); }
+  set code(WsErrCode v) { setField(1, v); }
   @$pb.TagNumber(1)
   @$pb.TagNumber(1)
   $core.bool hasCode() => $_has(0);
   $core.bool hasCode() => $_has(0);
   @$pb.TagNumber(1)
   @$pb.TagNumber(1)

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

@@ -9,23 +9,23 @@
 import 'dart:core' as $core;
 import 'dart:core' as $core;
 import 'package:protobuf/protobuf.dart' as $pb;
 import 'package:protobuf/protobuf.dart' as $pb;
 
 
-class WorkspaceErrorCode extends $pb.ProtobufEnum {
-  static const WorkspaceErrorCode Unknown = WorkspaceErrorCode._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Unknown');
-  static const WorkspaceErrorCode WorkspaceNameInvalid = WorkspaceErrorCode._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceNameInvalid');
-  static const WorkspaceErrorCode WorkspaceIdInvalid = WorkspaceErrorCode._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceIdInvalid');
-  static const WorkspaceErrorCode AppColorStyleInvalid = WorkspaceErrorCode._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AppColorStyleInvalid');
-  static const WorkspaceErrorCode AppIdInvalid = WorkspaceErrorCode._(10, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AppIdInvalid');
-  static const WorkspaceErrorCode AppNameInvalid = WorkspaceErrorCode._(11, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AppNameInvalid');
-  static const WorkspaceErrorCode ViewNameInvalid = WorkspaceErrorCode._(20, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ViewNameInvalid');
-  static const WorkspaceErrorCode ViewThumbnailInvalid = WorkspaceErrorCode._(21, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ViewThumbnailInvalid');
-  static const WorkspaceErrorCode ViewIdInvalid = WorkspaceErrorCode._(22, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ViewIdInvalid');
-  static const WorkspaceErrorCode ViewDescInvalid = WorkspaceErrorCode._(23, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ViewDescInvalid');
-  static const WorkspaceErrorCode DatabaseConnectionFail = WorkspaceErrorCode._(100, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DatabaseConnectionFail');
-  static const WorkspaceErrorCode WorkspaceDatabaseError = WorkspaceErrorCode._(101, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceDatabaseError');
-  static const WorkspaceErrorCode UserInternalError = WorkspaceErrorCode._(102, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserInternalError');
-  static const WorkspaceErrorCode UserNotLoginYet = WorkspaceErrorCode._(103, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserNotLoginYet');
+class WsErrCode extends $pb.ProtobufEnum {
+  static const WsErrCode Unknown = WsErrCode._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Unknown');
+  static const WsErrCode WorkspaceNameInvalid = WsErrCode._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceNameInvalid');
+  static const WsErrCode WorkspaceIdInvalid = WsErrCode._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceIdInvalid');
+  static const WsErrCode AppColorStyleInvalid = WsErrCode._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AppColorStyleInvalid');
+  static const WsErrCode AppIdInvalid = WsErrCode._(10, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AppIdInvalid');
+  static const WsErrCode AppNameInvalid = WsErrCode._(11, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AppNameInvalid');
+  static const WsErrCode ViewNameInvalid = WsErrCode._(20, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ViewNameInvalid');
+  static const WsErrCode ViewThumbnailInvalid = WsErrCode._(21, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ViewThumbnailInvalid');
+  static const WsErrCode ViewIdInvalid = WsErrCode._(22, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ViewIdInvalid');
+  static const WsErrCode ViewDescInvalid = WsErrCode._(23, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ViewDescInvalid');
+  static const WsErrCode DatabaseConnectionFail = WsErrCode._(100, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DatabaseConnectionFail');
+  static const WsErrCode WorkspaceDatabaseError = WsErrCode._(101, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceDatabaseError');
+  static const WsErrCode UserInternalError = WsErrCode._(102, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserInternalError');
+  static const WsErrCode UserNotLoginYet = WsErrCode._(103, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UserNotLoginYet');
 
 
-  static const $core.List<WorkspaceErrorCode> values = <WorkspaceErrorCode> [
+  static const $core.List<WsErrCode> values = <WsErrCode> [
     Unknown,
     Unknown,
     WorkspaceNameInvalid,
     WorkspaceNameInvalid,
     WorkspaceIdInvalid,
     WorkspaceIdInvalid,
@@ -42,9 +42,9 @@ class WorkspaceErrorCode extends $pb.ProtobufEnum {
     UserNotLoginYet,
     UserNotLoginYet,
   ];
   ];
 
 
-  static final $core.Map<$core.int, WorkspaceErrorCode> _byValue = $pb.ProtobufEnum.initByValue(values);
-  static WorkspaceErrorCode? valueOf($core.int value) => _byValue[value];
+  static final $core.Map<$core.int, WsErrCode> _byValue = $pb.ProtobufEnum.initByValue(values);
+  static WsErrCode? valueOf($core.int value) => _byValue[value];
 
 
-  const WorkspaceErrorCode._($core.int v, $core.String n) : super(v, n);
+  const WsErrCode._($core.int v, $core.String n) : super(v, n);
 }
 }
 
 

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

@@ -8,9 +8,9 @@
 import 'dart:core' as $core;
 import 'dart:core' as $core;
 import 'dart:convert' as $convert;
 import 'dart:convert' as $convert;
 import 'dart:typed_data' as $typed_data;
 import 'dart:typed_data' as $typed_data;
-@$core.Deprecated('Use workspaceErrorCodeDescriptor instead')
-const WorkspaceErrorCode$json = const {
-  '1': 'WorkspaceErrorCode',
+@$core.Deprecated('Use wsErrCodeDescriptor instead')
+const WsErrCode$json = const {
+  '1': 'WsErrCode',
   '2': const [
   '2': const [
     const {'1': 'Unknown', '2': 0},
     const {'1': 'Unknown', '2': 0},
     const {'1': 'WorkspaceNameInvalid', '2': 1},
     const {'1': 'WorkspaceNameInvalid', '2': 1},
@@ -29,16 +29,16 @@ const WorkspaceErrorCode$json = const {
   ],
   ],
 };
 };
 
 
-/// Descriptor for `WorkspaceErrorCode`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List workspaceErrorCodeDescriptor = $convert.base64Decode('ChJXb3Jrc3BhY2VFcnJvckNvZGUSCwoHVW5rbm93bhAAEhgKFFdvcmtzcGFjZU5hbWVJbnZhbGlkEAESFgoSV29ya3NwYWNlSWRJbnZhbGlkEAISGAoUQXBwQ29sb3JTdHlsZUludmFsaWQQAxIQCgxBcHBJZEludmFsaWQQChISCg5BcHBOYW1lSW52YWxpZBALEhMKD1ZpZXdOYW1lSW52YWxpZBAUEhgKFFZpZXdUaHVtYm5haWxJbnZhbGlkEBUSEQoNVmlld0lkSW52YWxpZBAWEhMKD1ZpZXdEZXNjSW52YWxpZBAXEhoKFkRhdGFiYXNlQ29ubmVjdGlvbkZhaWwQZBIaChZXb3Jrc3BhY2VEYXRhYmFzZUVycm9yEGUSFQoRVXNlckludGVybmFsRXJyb3IQZhITCg9Vc2VyTm90TG9naW5ZZXQQZw==');
+/// Descriptor for `WsErrCode`. Decode as a `google.protobuf.EnumDescriptorProto`.
+final $typed_data.Uint8List wsErrCodeDescriptor = $convert.base64Decode('CglXc0VyckNvZGUSCwoHVW5rbm93bhAAEhgKFFdvcmtzcGFjZU5hbWVJbnZhbGlkEAESFgoSV29ya3NwYWNlSWRJbnZhbGlkEAISGAoUQXBwQ29sb3JTdHlsZUludmFsaWQQAxIQCgxBcHBJZEludmFsaWQQChISCg5BcHBOYW1lSW52YWxpZBALEhMKD1ZpZXdOYW1lSW52YWxpZBAUEhgKFFZpZXdUaHVtYm5haWxJbnZhbGlkEBUSEQoNVmlld0lkSW52YWxpZBAWEhMKD1ZpZXdEZXNjSW52YWxpZBAXEhoKFkRhdGFiYXNlQ29ubmVjdGlvbkZhaWwQZBIaChZXb3Jrc3BhY2VEYXRhYmFzZUVycm9yEGUSFQoRVXNlckludGVybmFsRXJyb3IQZhITCg9Vc2VyTm90TG9naW5ZZXQQZw==');
 @$core.Deprecated('Use workspaceErrorDescriptor instead')
 @$core.Deprecated('Use workspaceErrorDescriptor instead')
 const WorkspaceError$json = const {
 const WorkspaceError$json = const {
   '1': 'WorkspaceError',
   '1': 'WorkspaceError',
   '2': const [
   '2': const [
-    const {'1': 'code', '3': 1, '4': 1, '5': 14, '6': '.WorkspaceErrorCode', '10': 'code'},
+    const {'1': 'code', '3': 1, '4': 1, '5': 14, '6': '.WsErrCode', '10': 'code'},
     const {'1': 'msg', '3': 2, '4': 1, '5': 9, '10': 'msg'},
     const {'1': 'msg', '3': 2, '4': 1, '5': 9, '10': 'msg'},
   ],
   ],
 };
 };
 
 
 /// Descriptor for `WorkspaceError`. Decode as a `google.protobuf.DescriptorProto`.
 /// Descriptor for `WorkspaceError`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List workspaceErrorDescriptor = $convert.base64Decode('Cg5Xb3Jrc3BhY2VFcnJvchInCgRjb2RlGAEgASgOMhMuV29ya3NwYWNlRXJyb3JDb2RlUgRjb2RlEhAKA21zZxgCIAEoCVIDbXNn');
+final $typed_data.Uint8List workspaceErrorDescriptor = $convert.base64Decode('Cg5Xb3Jrc3BhY2VFcnJvchIeCgRjb2RlGAEgASgOMgouV3NFcnJDb2RlUgRjb2RlEhAKA21zZxgCIAEoCVIDbXNn');

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

@@ -13,6 +13,7 @@ class WorkspaceEvent extends $pb.ProtobufEnum {
   static const WorkspaceEvent CreateWorkspace = WorkspaceEvent._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateWorkspace');
   static const WorkspaceEvent CreateWorkspace = WorkspaceEvent._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateWorkspace');
   static const WorkspaceEvent GetCurWorkspace = WorkspaceEvent._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetCurWorkspace');
   static const WorkspaceEvent GetCurWorkspace = WorkspaceEvent._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetCurWorkspace');
   static const WorkspaceEvent GetWorkspace = WorkspaceEvent._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetWorkspace');
   static const WorkspaceEvent GetWorkspace = WorkspaceEvent._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetWorkspace');
+  static const WorkspaceEvent ReadAllWorkspace = WorkspaceEvent._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ReadAllWorkspace');
   static const WorkspaceEvent CreateApp = WorkspaceEvent._(101, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateApp');
   static const WorkspaceEvent CreateApp = WorkspaceEvent._(101, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateApp');
   static const WorkspaceEvent GetApp = WorkspaceEvent._(102, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetApp');
   static const WorkspaceEvent GetApp = WorkspaceEvent._(102, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetApp');
   static const WorkspaceEvent CreateView = WorkspaceEvent._(201, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateView');
   static const WorkspaceEvent CreateView = WorkspaceEvent._(201, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CreateView');
@@ -23,6 +24,7 @@ class WorkspaceEvent extends $pb.ProtobufEnum {
     CreateWorkspace,
     CreateWorkspace,
     GetCurWorkspace,
     GetCurWorkspace,
     GetWorkspace,
     GetWorkspace,
+    ReadAllWorkspace,
     CreateApp,
     CreateApp,
     GetApp,
     GetApp,
     CreateView,
     CreateView,

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

@@ -15,6 +15,7 @@ const WorkspaceEvent$json = const {
     const {'1': 'CreateWorkspace', '2': 0},
     const {'1': 'CreateWorkspace', '2': 0},
     const {'1': 'GetCurWorkspace', '2': 1},
     const {'1': 'GetCurWorkspace', '2': 1},
     const {'1': 'GetWorkspace', '2': 2},
     const {'1': 'GetWorkspace', '2': 2},
+    const {'1': 'ReadAllWorkspace', '2': 3},
     const {'1': 'CreateApp', '2': 101},
     const {'1': 'CreateApp', '2': 101},
     const {'1': 'GetApp', '2': 102},
     const {'1': 'GetApp', '2': 102},
     const {'1': 'CreateView', '2': 201},
     const {'1': 'CreateView', '2': 201},
@@ -24,4 +25,4 @@ const WorkspaceEvent$json = const {
 };
 };
 
 
 /// Descriptor for `WorkspaceEvent`. Decode as a `google.protobuf.EnumDescriptorProto`.
 /// Descriptor for `WorkspaceEvent`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List workspaceEventDescriptor = $convert.base64Decode('Cg5Xb3Jrc3BhY2VFdmVudBITCg9DcmVhdGVXb3Jrc3BhY2UQABITCg9HZXRDdXJXb3Jrc3BhY2UQARIQCgxHZXRXb3Jrc3BhY2UQAhINCglDcmVhdGVBcHAQZRIKCgZHZXRBcHAQZhIPCgpDcmVhdGVWaWV3EMkBEg0KCFJlYWRWaWV3EMoBEg8KClVwZGF0ZVZpZXcQywE=');
+final $typed_data.Uint8List workspaceEventDescriptor = $convert.base64Decode('Cg5Xb3Jrc3BhY2VFdmVudBITCg9DcmVhdGVXb3Jrc3BhY2UQABITCg9HZXRDdXJXb3Jrc3BhY2UQARIQCgxHZXRXb3Jrc3BhY2UQAhIUChBSZWFkQWxsV29ya3NwYWNlEAMSDQoJQ3JlYXRlQXBwEGUSCgoGR2V0QXBwEGYSDwoKQ3JlYXRlVmlldxDJARINCghSZWFkVmlldxDKARIPCgpVcGRhdGVWaWV3EMsB');

+ 13 - 5
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/view_create.pbenum.dart

@@ -10,17 +10,25 @@ import 'dart:core' as $core;
 import 'package:protobuf/protobuf.dart' as $pb;
 import 'package:protobuf/protobuf.dart' as $pb;
 
 
 class ViewType extends $pb.ProtobufEnum {
 class ViewType extends $pb.ProtobufEnum {
-  static const ViewType Blank = ViewType._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Blank');
-  static const ViewType Doc = ViewType._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Doc');
+  static const ViewType Blank = ViewType._(
+      0,
+      const $core.bool.fromEnvironment('protobuf.omit_enum_names')
+          ? ''
+          : 'Blank');
+  static const ViewType Doc = ViewType._(
+      1,
+      const $core.bool.fromEnvironment('protobuf.omit_enum_names')
+          ? ''
+          : 'Doc');
 
 
-  static const $core.List<ViewType> values = <ViewType> [
+  static const $core.List<ViewType> values = <ViewType>[
     Blank,
     Blank,
     Doc,
     Doc,
   ];
   ];
 
 
-  static final $core.Map<$core.int, ViewType> _byValue = $pb.ProtobufEnum.initByValue(values);
+  static final $core.Map<$core.int, ViewType> _byValue =
+      $pb.ProtobufEnum.initByValue(values);
   static ViewType? valueOf($core.int value) => _byValue[value];
   static ViewType? valueOf($core.int value) => _byValue[value];
 
 
   const ViewType._($core.int v, $core.String n) : super(v, n);
   const ViewType._($core.int v, $core.String n) : super(v, n);
 }
 }
-

+ 41 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_create.pb.dart

@@ -163,3 +163,44 @@ class Workspace extends $pb.GeneratedMessage {
   $0.RepeatedApp ensureApps() => $_ensure(3);
   $0.RepeatedApp ensureApps() => $_ensure(3);
 }
 }
 
 
+class Workspaces extends $pb.GeneratedMessage {
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Workspaces', createEmptyInstance: create)
+    ..pc<Workspace>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'items', $pb.PbFieldType.PM, subBuilder: Workspace.create)
+    ..hasRequiredFields = false
+  ;
+
+  Workspaces._() : super();
+  factory Workspaces({
+    $core.Iterable<Workspace>? items,
+  }) {
+    final _result = create();
+    if (items != null) {
+      _result.items.addAll(items);
+    }
+    return _result;
+  }
+  factory Workspaces.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
+  factory Workspaces.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')
+  Workspaces clone() => Workspaces()..mergeFromMessage(this);
+  @$core.Deprecated(
+  'Using this can add significant overhead to your binary. '
+  'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
+  'Will be removed in next major version')
+  Workspaces copyWith(void Function(Workspaces) updates) => super.copyWith((message) => updates(message as Workspaces)) as Workspaces; // ignore: deprecated_member_use
+  $pb.BuilderInfo get info_ => _i;
+  @$core.pragma('dart2js:noInline')
+  static Workspaces create() => Workspaces._();
+  Workspaces createEmptyInstance() => create();
+  static $pb.PbList<Workspaces> createRepeated() => $pb.PbList<Workspaces>();
+  @$core.pragma('dart2js:noInline')
+  static Workspaces getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Workspaces>(create);
+  static Workspaces? _defaultInstance;
+
+  @$pb.TagNumber(1)
+  $core.List<Workspace> get items => $_getList(0);
+}
+

+ 10 - 0
app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/workspace_create.pbjson.dart

@@ -32,3 +32,13 @@ const Workspace$json = const {
 
 
 /// Descriptor for `Workspace`. Decode as a `google.protobuf.DescriptorProto`.
 /// Descriptor for `Workspace`. Decode as a `google.protobuf.DescriptorProto`.
 final $typed_data.Uint8List workspaceDescriptor = $convert.base64Decode('CglXb3Jrc3BhY2USDgoCaWQYASABKAlSAmlkEhIKBG5hbWUYAiABKAlSBG5hbWUSEgoEZGVzYxgDIAEoCVIEZGVzYxIgCgRhcHBzGAQgASgLMgwuUmVwZWF0ZWRBcHBSBGFwcHM=');
 final $typed_data.Uint8List workspaceDescriptor = $convert.base64Decode('CglXb3Jrc3BhY2USDgoCaWQYASABKAlSAmlkEhIKBG5hbWUYAiABKAlSBG5hbWUSEgoEZGVzYxgDIAEoCVIEZGVzYxIgCgRhcHBzGAQgASgLMgwuUmVwZWF0ZWRBcHBSBGFwcHM=');
+@$core.Deprecated('Use workspacesDescriptor instead')
+const Workspaces$json = const {
+  '1': 'Workspaces',
+  '2': const [
+    const {'1': 'items', '3': 1, '4': 3, '5': 11, '6': '.Workspace', '10': 'items'},
+  ],
+};
+
+/// Descriptor for `Workspaces`. Decode as a `google.protobuf.DescriptorProto`.
+final $typed_data.Uint8List workspacesDescriptor = $convert.base64Decode('CgpXb3Jrc3BhY2VzEiAKBWl0ZW1zGAEgAygLMgouV29ya3NwYWNlUgVpdGVtcw==');

+ 131 - 124
app_flowy/pubspec.lock

@@ -5,231 +5,231 @@ packages:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: _fe_analyzer_shared
       name: _fe_analyzer_shared
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
-    version: "22.0.0"
+    version: "23.0.0"
   analyzer:
   analyzer:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: analyzer
       name: analyzer
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
-    version: "1.7.1"
+    version: "2.0.0"
   animations:
   animations:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: animations
       name: animations
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.1"
     version: "2.0.1"
   args:
   args:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: args
       name: args
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
-    version: "2.1.1"
+    version: "2.2.0"
   async:
   async:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: async
       name: async
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
-    version: "2.6.1"
+    version: "2.7.0"
   bloc:
   bloc:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: bloc
       name: bloc
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "7.0.0"
     version: "7.0.0"
   boolean_selector:
   boolean_selector:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: boolean_selector
       name: boolean_selector
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.1.0"
     version: "2.1.0"
   build:
   build:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: build
       name: build
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.3"
     version: "2.0.3"
   build_config:
   build_config:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: build_config
       name: build_config
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.0.0"
     version: "1.0.0"
   build_daemon:
   build_daemon:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: build_daemon
       name: build_daemon
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "3.0.0"
     version: "3.0.0"
   build_resolvers:
   build_resolvers:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: build_resolvers
       name: build_resolvers
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.4"
     version: "2.0.4"
   build_runner:
   build_runner:
     dependency: "direct dev"
     dependency: "direct dev"
     description:
     description:
       name: build_runner
       name: build_runner
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.6"
     version: "2.0.6"
   build_runner_core:
   build_runner_core:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: build_runner_core
       name: build_runner_core
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "7.0.1"
     version: "7.0.1"
   built_collection:
   built_collection:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: built_collection
       name: built_collection
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "5.1.0"
     version: "5.1.0"
   built_value:
   built_value:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: built_value
       name: built_value
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "8.1.1"
     version: "8.1.1"
   characters:
   characters:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: characters
       name: characters
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.1.0"
     version: "1.1.0"
   charcode:
   charcode:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: charcode
       name: charcode
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
-    version: "1.2.0"
+    version: "1.3.1"
   checked_yaml:
   checked_yaml:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: checked_yaml
       name: checked_yaml
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.1"
     version: "2.0.1"
   cli_util:
   cli_util:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: cli_util
       name: cli_util
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "0.3.3"
     version: "0.3.3"
   clock:
   clock:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: clock
       name: clock
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.1.0"
     version: "1.1.0"
   code_builder:
   code_builder:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: code_builder
       name: code_builder
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
-    version: "4.0.0"
+    version: "4.1.0"
   collection:
   collection:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: collection
       name: collection
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.15.0"
     version: "1.15.0"
   convert:
   convert:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: convert
       name: convert
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "3.0.1"
     version: "3.0.1"
   crypto:
   crypto:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: crypto
       name: crypto
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "3.0.1"
     version: "3.0.1"
   cupertino_icons:
   cupertino_icons:
     dependency: "direct main"
     dependency: "direct main"
     description:
     description:
       name: cupertino_icons
       name: cupertino_icons
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.0.3"
     version: "1.0.3"
   dart_style:
   dart_style:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: dart_style
       name: dart_style
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
-    version: "2.0.2"
+    version: "2.0.3"
   dartz:
   dartz:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: dartz
       name: dartz
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "0.10.0-nullsafety.2"
     version: "0.10.0-nullsafety.2"
   equatable:
   equatable:
     dependency: "direct main"
     dependency: "direct main"
     description:
     description:
       name: equatable
       name: equatable
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.3"
     version: "2.0.3"
   expandable:
   expandable:
     dependency: "direct main"
     dependency: "direct main"
     description:
     description:
       name: expandable
       name: expandable
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "5.0.1"
     version: "5.0.1"
   fake_async:
   fake_async:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: fake_async
       name: fake_async
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.2.0"
     version: "1.2.0"
   ffi:
   ffi:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: ffi
       name: ffi
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.1.2"
     version: "1.1.2"
   file:
   file:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: file
       name: file
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "6.1.2"
     version: "6.1.2"
   fixnum:
   fixnum:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: fixnum
       name: fixnum
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.0.0"
     version: "1.0.0"
   flowy_editor:
   flowy_editor:
@@ -283,42 +283,42 @@ packages:
     dependency: "direct main"
     dependency: "direct main"
     description:
     description:
       name: flutter_bloc
       name: flutter_bloc
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "7.0.1"
     version: "7.0.1"
   flutter_colorpicker:
   flutter_colorpicker:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: flutter_colorpicker
       name: flutter_colorpicker
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "0.5.0"
     version: "0.5.0"
   flutter_keyboard_visibility:
   flutter_keyboard_visibility:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: flutter_keyboard_visibility
       name: flutter_keyboard_visibility
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
-    version: "5.0.2"
+    version: "5.0.3"
   flutter_keyboard_visibility_platform_interface:
   flutter_keyboard_visibility_platform_interface:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: flutter_keyboard_visibility_platform_interface
       name: flutter_keyboard_visibility_platform_interface
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.0"
     version: "2.0.0"
   flutter_keyboard_visibility_web:
   flutter_keyboard_visibility_web:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: flutter_keyboard_visibility_web
       name: flutter_keyboard_visibility_web
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.0"
     version: "2.0.0"
   flutter_lints:
   flutter_lints:
     dependency: "direct dev"
     dependency: "direct dev"
     description:
     description:
       name: flutter_lints
       name: flutter_lints
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.0.4"
     version: "1.0.4"
   flutter_test:
   flutter_test:
@@ -335,196 +335,203 @@ packages:
     dependency: "direct dev"
     dependency: "direct dev"
     description:
     description:
       name: freezed
       name: freezed
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
-    version: "0.14.2"
+    version: "0.14.3"
   freezed_annotation:
   freezed_annotation:
     dependency: "direct main"
     dependency: "direct main"
     description:
     description:
       name: freezed_annotation
       name: freezed_annotation
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
-    version: "0.14.2"
+    version: "0.14.3"
   frontend_server_client:
   frontend_server_client:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: frontend_server_client
       name: frontend_server_client
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.1.0"
     version: "2.1.0"
   get_it:
   get_it:
     dependency: "direct main"
     dependency: "direct main"
     description:
     description:
       name: get_it
       name: get_it
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "7.2.0"
     version: "7.2.0"
   glob:
   glob:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: glob
       name: glob
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.1"
     version: "2.0.1"
   graphs:
   graphs:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: graphs
       name: graphs
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.0"
     version: "2.0.0"
   http_multi_server:
   http_multi_server:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: http_multi_server
       name: http_multi_server
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "3.0.1"
     version: "3.0.1"
   http_parser:
   http_parser:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: http_parser
       name: http_parser
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "4.0.0"
     version: "4.0.0"
   io:
   io:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: io
       name: io
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.0.3"
     version: "1.0.3"
   isolates:
   isolates:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: isolates
       name: isolates
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "3.0.3+8"
     version: "3.0.3+8"
   js:
   js:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: js
       name: js
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "0.6.3"
     version: "0.6.3"
   json_annotation:
   json_annotation:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: json_annotation
       name: json_annotation
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
-    version: "4.0.1"
+    version: "4.1.0"
   lint:
   lint:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: lint
       name: lint
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.5.3"
     version: "1.5.3"
   lints:
   lints:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: lints
       name: lints
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.0.1"
     version: "1.0.1"
+  loading_indicator:
+    dependency: transitive
+    description:
+      name: loading_indicator
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "3.0.1"
   logger:
   logger:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: logger
       name: logger
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.0.0"
     version: "1.0.0"
   logging:
   logging:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: logging
       name: logging
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.0.1"
     version: "1.0.1"
   matcher:
   matcher:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: matcher
       name: matcher
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "0.12.10"
     version: "0.12.10"
   meta:
   meta:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: meta
       name: meta
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
-    version: "1.3.0"
+    version: "1.7.0"
   mime:
   mime:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: mime
       name: mime
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.0.0"
     version: "1.0.0"
   nested:
   nested:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: nested
       name: nested
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.0.0"
     version: "1.0.0"
   package_config:
   package_config:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: package_config
       name: package_config
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.0"
     version: "2.0.0"
   path:
   path:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: path
       name: path
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.8.0"
     version: "1.8.0"
   path_provider:
   path_provider:
     dependency: "direct main"
     dependency: "direct main"
     description:
     description:
       name: path_provider
       name: path_provider
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.2"
     version: "2.0.2"
   path_provider_linux:
   path_provider_linux:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: path_provider_linux
       name: path_provider_linux
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.0"
     version: "2.0.0"
   path_provider_macos:
   path_provider_macos:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: path_provider_macos
       name: path_provider_macos
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.0"
     version: "2.0.0"
   path_provider_platform_interface:
   path_provider_platform_interface:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: path_provider_platform_interface
       name: path_provider_platform_interface
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.1"
     version: "2.0.1"
   path_provider_windows:
   path_provider_windows:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: path_provider_windows
       name: path_provider_windows
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.1"
     version: "2.0.1"
   pedantic:
   pedantic:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: pedantic
       name: pedantic
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.11.1"
     version: "1.11.1"
   photo_view:
   photo_view:
@@ -540,91 +547,91 @@ packages:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: platform
       name: platform
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "3.0.0"
     version: "3.0.0"
   plugin_platform_interface:
   plugin_platform_interface:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: plugin_platform_interface
       name: plugin_platform_interface
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.1"
     version: "2.0.1"
   pool:
   pool:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: pool
       name: pool
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.5.0"
     version: "1.5.0"
   process:
   process:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: process
       name: process
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
-    version: "4.2.1"
+    version: "4.2.3"
   protobuf:
   protobuf:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: protobuf
       name: protobuf
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.0"
     version: "2.0.0"
   provider:
   provider:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: provider
       name: provider
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "5.0.0"
     version: "5.0.0"
   pub_semver:
   pub_semver:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: pub_semver
       name: pub_semver
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.0"
     version: "2.0.0"
   pubspec_parse:
   pubspec_parse:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: pubspec_parse
       name: pubspec_parse
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.0.0"
     version: "1.0.0"
   quiver:
   quiver:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: quiver
       name: quiver
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "3.0.1"
     version: "3.0.1"
   quiver_hashcode:
   quiver_hashcode:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: quiver_hashcode
       name: quiver_hashcode
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.0"
     version: "2.0.0"
   shelf:
   shelf:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: shelf
       name: shelf
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.2.0"
     version: "1.2.0"
   shelf_web_socket:
   shelf_web_socket:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: shelf_web_socket
       name: shelf_web_socket
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.0.1"
     version: "1.0.1"
   sized_context:
   sized_context:
     dependency: "direct main"
     dependency: "direct main"
     description:
     description:
       name: sized_context
       name: sized_context
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.0.0+1"
     version: "1.0.0+1"
   sky_engine:
   sky_engine:
@@ -636,189 +643,189 @@ packages:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: source_gen
       name: source_gen
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
-    version: "1.0.3"
+    version: "1.0.5"
   source_span:
   source_span:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: source_span
       name: source_span
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.8.1"
     version: "1.8.1"
   stack_trace:
   stack_trace:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: stack_trace
       name: stack_trace
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.10.0"
     version: "1.10.0"
   stream_channel:
   stream_channel:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: stream_channel
       name: stream_channel
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.1.0"
     version: "2.1.0"
   stream_transform:
   stream_transform:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: stream_transform
       name: stream_transform
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.0"
     version: "2.0.0"
   string_scanner:
   string_scanner:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: string_scanner
       name: string_scanner
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.1.0"
     version: "1.1.0"
   string_validator:
   string_validator:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: string_validator
       name: string_validator
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "0.3.0"
     version: "0.3.0"
   styled_widget:
   styled_widget:
     dependency: "direct main"
     dependency: "direct main"
     description:
     description:
       name: styled_widget
       name: styled_widget
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "0.3.1+2"
     version: "0.3.1+2"
   term_glyph:
   term_glyph:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: term_glyph
       name: term_glyph
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.2.0"
     version: "1.2.0"
   test_api:
   test_api:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: test_api
       name: test_api
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
-    version: "0.3.0"
+    version: "0.4.1"
   textstyle_extensions:
   textstyle_extensions:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: textstyle_extensions
       name: textstyle_extensions
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.0-nullsafety"
     version: "2.0.0-nullsafety"
   time:
   time:
     dependency: "direct main"
     dependency: "direct main"
     description:
     description:
       name: time
       name: time
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.0"
     version: "2.0.0"
   timing:
   timing:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: timing
       name: timing
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.0.0"
     version: "1.0.0"
   tuple:
   tuple:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: tuple
       name: tuple
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.0"
     version: "2.0.0"
   typed_data:
   typed_data:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: typed_data
       name: typed_data
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.3.0"
     version: "1.3.0"
   universal_platform:
   universal_platform:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: universal_platform
       name: universal_platform
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.0.0+1"
     version: "1.0.0+1"
   url_launcher:
   url_launcher:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: url_launcher
       name: url_launcher
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "6.0.9"
     version: "6.0.9"
   url_launcher_linux:
   url_launcher_linux:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: url_launcher_linux
       name: url_launcher_linux
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.0"
     version: "2.0.0"
   url_launcher_macos:
   url_launcher_macos:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: url_launcher_macos
       name: url_launcher_macos
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.0"
     version: "2.0.0"
   url_launcher_platform_interface:
   url_launcher_platform_interface:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: url_launcher_platform_interface
       name: url_launcher_platform_interface
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.4"
     version: "2.0.4"
   url_launcher_web:
   url_launcher_web:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: url_launcher_web
       name: url_launcher_web
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.1"
     version: "2.0.1"
   url_launcher_windows:
   url_launcher_windows:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: url_launcher_windows
       name: url_launcher_windows
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.0.0"
     version: "2.0.0"
   uuid:
   uuid:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: uuid
       name: uuid
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "3.0.4"
     version: "3.0.4"
   vector_math:
   vector_math:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: vector_math
       name: vector_math
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.1.0"
     version: "2.1.0"
   watcher:
   watcher:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: watcher
       name: watcher
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "1.0.0"
     version: "1.0.0"
   web_socket_channel:
   web_socket_channel:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: web_socket_channel
       name: web_socket_channel
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.1.0"
     version: "2.1.0"
   win32:
   win32:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: win32
       name: win32
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "2.2.5"
     version: "2.2.5"
   window_size:
   window_size:
@@ -834,14 +841,14 @@ packages:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: xdg_directories
       name: xdg_directories
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "0.2.0"
     version: "0.2.0"
   yaml:
   yaml:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: yaml
       name: yaml
-      url: "https://pub.dartlang.org"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     source: hosted
     version: "3.1.0"
     version: "3.1.0"
 sdks:
 sdks:

+ 1 - 1
rust-lib/dart-ffi/src/lib.rs

@@ -9,7 +9,7 @@ use crate::{
 };
 };
 use flowy_dispatch::prelude::*;
 use flowy_dispatch::prelude::*;
 use flowy_sdk::*;
 use flowy_sdk::*;
-use lazy_static::lazy_static;
+
 use std::{ffi::CStr, os::raw::c_char};
 use std::{ffi::CStr, os::raw::c_char};
 
 
 #[no_mangle]
 #[no_mangle]

+ 1 - 1
rust-lib/dart-ffi/src/model/ffi_response.rs

@@ -1,5 +1,5 @@
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
-use flowy_dispatch::prelude::{DispatchError, EventResponse, Payload, StatusCode};
+use flowy_dispatch::prelude::{EventResponse, Payload, StatusCode};
 
 
 #[derive(ProtoBuf_Enum, Clone, Copy)]
 #[derive(ProtoBuf_Enum, Clone, Copy)]
 pub enum FFIStatusCode {
 pub enum FFIStatusCode {

+ 3 - 2
rust-lib/flowy-derive/src/derive_cache/derive_cache.rs

@@ -33,6 +33,7 @@ pub fn category_from_str(type_str: &str) -> TypeCategory {
         | "UpdateWorkspaceRequest"
         | "UpdateWorkspaceRequest"
         | "CreateWorkspaceRequest"
         | "CreateWorkspaceRequest"
         | "Workspace"
         | "Workspace"
+        | "Workspaces"
         | "QueryWorkspaceRequest"
         | "QueryWorkspaceRequest"
         | "CurrentWorkspace"
         | "CurrentWorkspace"
         | "UpdateViewRequest"
         | "UpdateViewRequest"
@@ -56,12 +57,12 @@ pub fn category_from_str(type_str: &str) -> TypeCategory {
         | "EditorErrorCode"
         | "EditorErrorCode"
         | "ViewType"
         | "ViewType"
         | "WorkspaceEvent"
         | "WorkspaceEvent"
-        | "WorkspaceErrorCode"
+        | "WsErrCode"
         | "WorkspaceObservable"
         | "WorkspaceObservable"
         | "FFIStatusCode"
         | "FFIStatusCode"
         | "UserStatus"
         | "UserStatus"
         | "UserEvent"
         | "UserEvent"
-        | "UserErrorCode"
+        | "UserErrCode"
         => TypeCategory::Enum,
         => TypeCategory::Enum,
 
 
         "Option" => TypeCategory::Opt,
         "Option" => TypeCategory::Opt,

+ 1 - 1
rust-lib/flowy-dispatch/src/errors/errors.rs

@@ -1,7 +1,7 @@
 use crate::{
 use crate::{
     byte_trait::FromBytes,
     byte_trait::FromBytes,
     request::EventRequest,
     request::EventRequest,
-    response::{EventResponse, ResponseBuilder, StatusCode},
+    response::{EventResponse, ResponseBuilder},
 };
 };
 use dyn_clone::DynClone;
 use dyn_clone::DynClone;
 use serde::{Serialize, Serializer};
 use serde::{Serialize, Serializer};

+ 0 - 1
rust-lib/flowy-dispatch/src/response/builder.rs

@@ -1,5 +1,4 @@
 use crate::{
 use crate::{
-    errors::DispatchError,
     request::Payload,
     request::Payload,
     response::{EventResponse, StatusCode},
     response::{EventResponse, StatusCode},
 };
 };

+ 3 - 2
rust-lib/flowy-editor/src/handlers/doc_handler.rs

@@ -4,7 +4,7 @@ use crate::{
     services::{doc_controller::DocController, file_manager::FileManager},
     services::{doc_controller::DocController, file_manager::FileManager},
 };
 };
 use flowy_dispatch::prelude::*;
 use flowy_dispatch::prelude::*;
-use std::{convert::TryInto, path::Path, sync::Arc};
+use std::{convert::TryInto, path::Path};
 use tokio::sync::RwLock;
 use tokio::sync::RwLock;
 
 
 #[tracing::instrument(name = "create_doc", skip(data, controller, manager))]
 #[tracing::instrument(name = "create_doc", skip(data, controller, manager))]
@@ -67,7 +67,8 @@ pub async fn update_doc(
         manager
         manager
             .write()
             .write()
             .await
             .await
-            .save(Path::new(&doc_desc.path), &s, params.id.clone());
+            .save(Path::new(&doc_desc.path), &s, params.id.clone())
+            .unwrap();
     }
     }
 
 
     if params.name.is_some() || params.desc.is_some() {
     if params.name.is_some() || params.desc.is_some() {

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

@@ -2,10 +2,7 @@ use crate::{
     errors::EditorError,
     errors::EditorError,
     event::EditorEvent,
     event::EditorEvent,
     handlers::*,
     handlers::*,
-    services::{
-        doc_controller::DocController,
-        file_manager::{create_dir_if_not_exist, FileManager},
-    },
+    services::{doc_controller::DocController, file_manager::FileManager},
 };
 };
 use flowy_database::DBConnection;
 use flowy_database::DBConnection;
 use flowy_dispatch::prelude::*;
 use flowy_dispatch::prelude::*;

+ 1 - 1
rust-lib/flowy-editor/src/services/doc_controller.rs

@@ -1,5 +1,5 @@
 use crate::{
 use crate::{
-    entities::doc::{CreateDocParams, DocData, DocInfo, QueryDocParams, UpdateDocParams},
+    entities::doc::{CreateDocParams, DocInfo, UpdateDocParams},
     errors::EditorError,
     errors::EditorError,
     module::EditorDatabase,
     module::EditorDatabase,
     sql_tables::doc::{DocTable, DocTableChangeset, DocTableSql},
     sql_tables::doc::{DocTable, DocTableChangeset, DocTableSql},

+ 1 - 1
rust-lib/flowy-editor/src/services/file_manager/file.rs

@@ -6,7 +6,6 @@ use std::{
     io::{Read, Write},
     io::{Read, Write},
     path::{Path, PathBuf},
     path::{Path, PathBuf},
     str,
     str,
-    sync::atomic::{AtomicUsize, Ordering},
     time::SystemTime,
     time::SystemTime,
 };
 };
 
 
@@ -117,6 +116,7 @@ pub(crate) fn try_decode(
     }
     }
 }
 }
 
 
+#[allow(dead_code)]
 pub(crate) fn create_dir_if_not_exist(dir: &str) -> Result<(), io::Error> {
 pub(crate) fn create_dir_if_not_exist(dir: &str) -> Result<(), io::Error> {
     let _ = fs::create_dir_all(dir)?;
     let _ = fs::create_dir_all(dir)?;
     Ok(())
     Ok(())

+ 5 - 2
rust-lib/flowy-editor/src/services/file_manager/manager.rs

@@ -1,9 +1,8 @@
 use crate::{module::EditorUser, services::file_manager::*};
 use crate::{module::EditorUser, services::file_manager::*};
 use std::{
 use std::{
     collections::HashMap,
     collections::HashMap,
-    io,
     path::{Path, PathBuf},
     path::{Path, PathBuf},
-    sync::{Arc, PoisonError, RwLock, RwLockReadGuard},
+    sync::Arc,
 };
 };
 
 
 pub struct FileManager {
 pub struct FileManager {
@@ -49,6 +48,7 @@ impl FileManager {
         }
         }
     }
     }
 
 
+    #[allow(dead_code)]
     pub(crate) fn close<T>(&mut self, id: T)
     pub(crate) fn close<T>(&mut self, id: T)
     where
     where
         T: Into<FileId>,
         T: Into<FileId>,
@@ -71,12 +71,15 @@ impl FileManager {
         Ok(path)
         Ok(path)
     }
     }
 
 
+    #[allow(dead_code)]
     pub(crate) fn get_info(&self, id: &FileId) -> Option<&FileInfo> { self.file_info.get(id) }
     pub(crate) fn get_info(&self, id: &FileId) -> Option<&FileInfo> { self.file_info.get(id) }
 
 
+    #[allow(dead_code)]
     pub(crate) fn get_file_id(&self, path: &Path) -> Option<FileId> {
     pub(crate) fn get_file_id(&self, path: &Path) -> Option<FileId> {
         self.open_files.get(path).cloned()
         self.open_files.get(path).cloned()
     }
     }
 
 
+    #[allow(dead_code)]
     pub fn check_file(&mut self, path: &Path, id: &FileId) -> bool {
     pub fn check_file(&mut self, path: &Path, id: &FileId) -> bool {
         if let Some(info) = self.file_info.get_mut(&id) {
         if let Some(info) = self.file_info.get_mut(&id) {
             let modified_time = get_modified_time(path);
             let modified_time = get_modified_time(path);

+ 1 - 1
rust-lib/flowy-editor/src/sql_tables/doc/doc_sql.rs

@@ -36,5 +36,5 @@ impl DocTableSql {
         Ok(doc_table)
         Ok(doc_table)
     }
     }
 
 
-    pub(crate) fn delete_doc(&self, view_id: &str) -> Result<(), EditorError> { unimplemented!() }
+    pub(crate) fn delete_doc(&self, _view_id: &str) -> Result<(), EditorError> { unimplemented!() }
 }
 }

+ 1 - 2
rust-lib/flowy-editor/src/sql_tables/doc/doc_table.rs

@@ -1,7 +1,6 @@
 use crate::entities::doc::{CreateDocParams, DocInfo, UpdateDocParams};
 use crate::entities::doc::{CreateDocParams, DocInfo, UpdateDocParams};
 use flowy_database::schema::doc_table;
 use flowy_database::schema::doc_table;
-use flowy_infra::{timestamp, uuid};
-use std::convert::TryInto;
+use flowy_infra::timestamp;
 
 
 #[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)]
 #[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)]
 #[table_name = "doc_table"]
 #[table_name = "doc_table"]

+ 2 - 2
rust-lib/flowy-editor/src/sql_tables/doc/mod.rs

@@ -1,5 +1,5 @@
 mod doc_sql;
 mod doc_sql;
 mod doc_table;
 mod doc_table;
 
 
-pub use doc_sql::*;
-pub use doc_table::*;
+pub(crate) use doc_sql::*;
+pub(crate) use doc_table::*;

Some files were not shown because too many files changed in this diff