Просмотр исходного кода

add splash screen and config sign up

appflowy 3 лет назад
Родитель
Сommit
024ab85864
34 измененных файлов с 741 добавлено и 601 удалено
  1. 2 2
      app_flowy/lib/main.dart
  2. 2 1
      app_flowy/lib/startup/tasks/rust_sdk_init_task.dart
  3. 59 17
      app_flowy/lib/user/application/sign_up_bloc.dart
  4. 207 1
      app_flowy/lib/user/application/sign_up_bloc.freezed.dart
  5. 3 3
      app_flowy/lib/user/domain/i_auth.dart
  6. 8 12
      app_flowy/lib/user/infrastructure/i_auth_impl.dart
  7. 6 4
      app_flowy/lib/user/presentation/sign_in_screen.dart
  8. 56 31
      app_flowy/lib/user/presentation/sign_up_screen.dart
  9. 11 11
      app_flowy/lib/welcome/application/splash_bloc.dart
  10. 52 52
      app_flowy/lib/welcome/application/splash_bloc.freezed.dart
  11. 5 3
      app_flowy/lib/welcome/domain/i_welcome.dart
  12. 3 4
      app_flowy/lib/welcome/infrastructure/deps_resolver.dart
  13. 27 32
      app_flowy/lib/welcome/infrastructure/i_welcome_impl.dart
  14. 81 0
      app_flowy/lib/welcome/presentation/splash_screen.dart
  15. 80 52
      app_flowy/lib/welcome/presentation/welcome_screen.dart
  16. 0 236
      app_flowy/lib/workspace/presentation/workspace/workspace_select_screen.dart
  17. 1 1
      app_flowy/packages/flowy_infra/lib/flowy_logger.dart
  18. 12 7
      app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart
  19. 2 0
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/observable.pbenum.dart
  20. 2 1
      app_flowy/packages/flowy_sdk/lib/protobuf/flowy-workspace/observable.pbjson.dart
  21. 1 0
      backend/tests/api/helper.rs
  22. 1 1
      rust-lib/dart-ffi/src/lib.rs
  23. 2 2
      rust-lib/flowy-user/src/entities/auth.rs
  24. 1 1
      rust-lib/flowy-user/src/entities/user_profile.rs
  25. 54 54
      rust-lib/flowy-user/src/protobuf/model/user_profile.rs
  26. 1 0
      rust-lib/flowy-user/src/protobuf/proto/user_profile.proto
  27. 0 12
      rust-lib/flowy-user/src/services/helper.rs
  28. 0 1
      rust-lib/flowy-user/src/services/mod.rs
  29. 7 5
      rust-lib/flowy-user/src/services/user/user_session.rs
  30. 13 18
      rust-lib/flowy-workspace/src/observable/observable.rs
  31. 32 27
      rust-lib/flowy-workspace/src/protobuf/model/observable.rs
  32. 1 0
      rust-lib/flowy-workspace/src/protobuf/proto/observable.proto
  33. 2 5
      rust-lib/flowy-workspace/src/services/helper.rs
  34. 7 5
      rust-lib/flowy-workspace/src/services/workspace_controller.rs

+ 2 - 2
app_flowy/lib/main.dart

@@ -1,11 +1,11 @@
 import 'package:app_flowy/startup/startup.dart';
-import 'package:app_flowy/welcome/presentation/welcome_screen.dart';
+import 'package:app_flowy/welcome/presentation/splash_screen.dart';
 import 'package:flutter/material.dart';
 
 class FlowyAppFactory implements AppFactory {
   @override
   Widget create() {
-    return const WelcomeScreen();
+    return const SplashScreen();
   }
 }
 

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

@@ -42,7 +42,8 @@ class ApplicationBlocObserver extends BlocObserver {
   @override
   // ignore: unnecessary_overrides
   void onTransition(Bloc bloc, Transition transition) {
-    Log.debug(transition);
+    Log.debug(
+        "[current]: ${transition.currentState} \n[next]: ${transition.nextState}");
     super.onTransition(bloc, transition);
   }
 

+ 59 - 17
app_flowy/lib/user/application/sign_up_bloc.dart

@@ -14,28 +14,65 @@ class SignUpBloc extends Bloc<SignUpEvent, SignUpState> {
   Stream<SignUpState> mapEventToState(
     SignUpEvent event,
   ) async* {
-    yield* event.map(
-      signUpWithUserEmailAndPassword: (e) async* {
-        yield* _performActionOnSignUp(
-          state,
-        );
-      },
-      emailChanged: (EmailChanged value) async* {
-        yield state.copyWith(email: value.email, successOrFail: none());
-      },
-      passwordChanged: (PasswordChanged value) async* {
-        yield state.copyWith(password: value.password, successOrFail: none());
-      },
-    );
+    yield* event.map(signUpWithUserEmailAndPassword: (e) async* {
+      yield* _performActionOnSignUp();
+    }, emailChanged: (EmailChanged value) async* {
+      yield state.copyWith(email: value.email, successOrFail: none());
+    }, passwordChanged: (PasswordChanged value) async* {
+      yield state.copyWith(password: value.password, successOrFail: none());
+    }, repeatPasswordChanged: (RepeatPasswordChanged value) async* {
+      yield state.copyWith(
+          repeatedPassword: value.password, successOrFail: none());
+    });
   }
 
-  Stream<SignUpState> _performActionOnSignUp(SignUpState state) async* {
-    yield state.copyWith(isSubmitting: true);
+  Stream<SignUpState> _performActionOnSignUp() async* {
+    yield state.copyWith(
+      isSubmitting: true,
+    );
+
+    final password = state.password;
+    final repeatedPassword = state.repeatedPassword;
+    if (password == null) {
+      yield state.copyWith(
+        isSubmitting: false,
+        passwordError: some("Password can't be empty"),
+      );
+      return;
+    }
+
+    if (repeatedPassword == null) {
+      yield state.copyWith(
+        isSubmitting: false,
+        repeatPasswordError: some("Repeat password can't be empty"),
+      );
+      return;
+    }
 
-    final result = await authImpl.signIn(state.email, state.password);
+    if (password != repeatedPassword) {
+      yield state.copyWith(
+        isSubmitting: false,
+        repeatPasswordError:
+            some("Repeat password is not the same as password"),
+      );
+      return;
+    }
+
+    yield state.copyWith(
+      passwordError: none(),
+      repeatPasswordError: none(),
+    );
+
+    final result =
+        await authImpl.signUp(state.email, state.password, state.email);
     yield result.fold(
       (userProfile) => state.copyWith(
-          isSubmitting: false, successOrFail: some(left(userProfile))),
+        isSubmitting: false,
+        successOrFail: some(left(userProfile)),
+        emailError: none(),
+        passwordError: none(),
+        repeatPasswordError: none(),
+      ),
       (error) => stateFromCode(error),
     );
   }
@@ -65,6 +102,8 @@ abstract class SignUpEvent with _$SignUpEvent {
       SignUpWithUserEmailAndPassword;
   const factory SignUpEvent.emailChanged(String email) = EmailChanged;
   const factory SignUpEvent.passwordChanged(String password) = PasswordChanged;
+  const factory SignUpEvent.repeatPasswordChanged(String password) =
+      RepeatPasswordChanged;
 }
 
 @freezed
@@ -72,8 +111,10 @@ abstract class SignUpState with _$SignUpState {
   const factory SignUpState({
     String? email,
     String? password,
+    String? repeatedPassword,
     required bool isSubmitting,
     required Option<String> passwordError,
+    required Option<String> repeatPasswordError,
     required Option<String> emailError,
     required Option<Either<UserProfile, UserError>> successOrFail,
   }) = _SignUpState;
@@ -81,6 +122,7 @@ abstract class SignUpState with _$SignUpState {
   factory SignUpState.initial() => SignUpState(
         isSubmitting: false,
         passwordError: none(),
+        repeatPasswordError: none(),
         emailError: none(),
         successOrFail: none(),
       );

+ 207 - 1
app_flowy/lib/user/application/sign_up_bloc.freezed.dart

@@ -31,6 +31,12 @@ class _$SignUpEventTearOff {
       password,
     );
   }
+
+  RepeatPasswordChanged repeatPasswordChanged(String password) {
+    return RepeatPasswordChanged(
+      password,
+    );
+  }
 }
 
 /// @nodoc
@@ -43,6 +49,7 @@ mixin _$SignUpEvent {
     required TResult Function() signUpWithUserEmailAndPassword,
     required TResult Function(String email) emailChanged,
     required TResult Function(String password) passwordChanged,
+    required TResult Function(String password) repeatPasswordChanged,
   }) =>
       throw _privateConstructorUsedError;
   @optionalTypeArgs
@@ -50,6 +57,7 @@ mixin _$SignUpEvent {
     TResult Function()? signUpWithUserEmailAndPassword,
     TResult Function(String email)? emailChanged,
     TResult Function(String password)? passwordChanged,
+    TResult Function(String password)? repeatPasswordChanged,
     required TResult orElse(),
   }) =>
       throw _privateConstructorUsedError;
@@ -59,6 +67,8 @@ mixin _$SignUpEvent {
         signUpWithUserEmailAndPassword,
     required TResult Function(EmailChanged value) emailChanged,
     required TResult Function(PasswordChanged value) passwordChanged,
+    required TResult Function(RepeatPasswordChanged value)
+        repeatPasswordChanged,
   }) =>
       throw _privateConstructorUsedError;
   @optionalTypeArgs
@@ -67,6 +77,7 @@ mixin _$SignUpEvent {
         signUpWithUserEmailAndPassword,
     TResult Function(EmailChanged value)? emailChanged,
     TResult Function(PasswordChanged value)? passwordChanged,
+    TResult Function(RepeatPasswordChanged value)? repeatPasswordChanged,
     required TResult orElse(),
   }) =>
       throw _privateConstructorUsedError;
@@ -135,6 +146,7 @@ class _$SignUpWithUserEmailAndPassword
     required TResult Function() signUpWithUserEmailAndPassword,
     required TResult Function(String email) emailChanged,
     required TResult Function(String password) passwordChanged,
+    required TResult Function(String password) repeatPasswordChanged,
   }) {
     return signUpWithUserEmailAndPassword();
   }
@@ -145,6 +157,7 @@ class _$SignUpWithUserEmailAndPassword
     TResult Function()? signUpWithUserEmailAndPassword,
     TResult Function(String email)? emailChanged,
     TResult Function(String password)? passwordChanged,
+    TResult Function(String password)? repeatPasswordChanged,
     required TResult orElse(),
   }) {
     if (signUpWithUserEmailAndPassword != null) {
@@ -160,6 +173,8 @@ class _$SignUpWithUserEmailAndPassword
         signUpWithUserEmailAndPassword,
     required TResult Function(EmailChanged value) emailChanged,
     required TResult Function(PasswordChanged value) passwordChanged,
+    required TResult Function(RepeatPasswordChanged value)
+        repeatPasswordChanged,
   }) {
     return signUpWithUserEmailAndPassword(this);
   }
@@ -171,6 +186,7 @@ class _$SignUpWithUserEmailAndPassword
         signUpWithUserEmailAndPassword,
     TResult Function(EmailChanged value)? emailChanged,
     TResult Function(PasswordChanged value)? passwordChanged,
+    TResult Function(RepeatPasswordChanged value)? repeatPasswordChanged,
     required TResult orElse(),
   }) {
     if (signUpWithUserEmailAndPassword != null) {
@@ -252,6 +268,7 @@ class _$EmailChanged implements EmailChanged {
     required TResult Function() signUpWithUserEmailAndPassword,
     required TResult Function(String email) emailChanged,
     required TResult Function(String password) passwordChanged,
+    required TResult Function(String password) repeatPasswordChanged,
   }) {
     return emailChanged(email);
   }
@@ -262,6 +279,7 @@ class _$EmailChanged implements EmailChanged {
     TResult Function()? signUpWithUserEmailAndPassword,
     TResult Function(String email)? emailChanged,
     TResult Function(String password)? passwordChanged,
+    TResult Function(String password)? repeatPasswordChanged,
     required TResult orElse(),
   }) {
     if (emailChanged != null) {
@@ -277,6 +295,8 @@ class _$EmailChanged implements EmailChanged {
         signUpWithUserEmailAndPassword,
     required TResult Function(EmailChanged value) emailChanged,
     required TResult Function(PasswordChanged value) passwordChanged,
+    required TResult Function(RepeatPasswordChanged value)
+        repeatPasswordChanged,
   }) {
     return emailChanged(this);
   }
@@ -288,6 +308,7 @@ class _$EmailChanged implements EmailChanged {
         signUpWithUserEmailAndPassword,
     TResult Function(EmailChanged value)? emailChanged,
     TResult Function(PasswordChanged value)? passwordChanged,
+    TResult Function(RepeatPasswordChanged value)? repeatPasswordChanged,
     required TResult orElse(),
   }) {
     if (emailChanged != null) {
@@ -375,6 +396,7 @@ class _$PasswordChanged implements PasswordChanged {
     required TResult Function() signUpWithUserEmailAndPassword,
     required TResult Function(String email) emailChanged,
     required TResult Function(String password) passwordChanged,
+    required TResult Function(String password) repeatPasswordChanged,
   }) {
     return passwordChanged(password);
   }
@@ -385,6 +407,7 @@ class _$PasswordChanged implements PasswordChanged {
     TResult Function()? signUpWithUserEmailAndPassword,
     TResult Function(String email)? emailChanged,
     TResult Function(String password)? passwordChanged,
+    TResult Function(String password)? repeatPasswordChanged,
     required TResult orElse(),
   }) {
     if (passwordChanged != null) {
@@ -400,6 +423,8 @@ class _$PasswordChanged implements PasswordChanged {
         signUpWithUserEmailAndPassword,
     required TResult Function(EmailChanged value) emailChanged,
     required TResult Function(PasswordChanged value) passwordChanged,
+    required TResult Function(RepeatPasswordChanged value)
+        repeatPasswordChanged,
   }) {
     return passwordChanged(this);
   }
@@ -411,6 +436,7 @@ class _$PasswordChanged implements PasswordChanged {
         signUpWithUserEmailAndPassword,
     TResult Function(EmailChanged value)? emailChanged,
     TResult Function(PasswordChanged value)? passwordChanged,
+    TResult Function(RepeatPasswordChanged value)? repeatPasswordChanged,
     required TResult orElse(),
   }) {
     if (passwordChanged != null) {
@@ -429,6 +455,136 @@ abstract class PasswordChanged implements SignUpEvent {
       throw _privateConstructorUsedError;
 }
 
+/// @nodoc
+abstract class $RepeatPasswordChangedCopyWith<$Res> {
+  factory $RepeatPasswordChangedCopyWith(RepeatPasswordChanged value,
+          $Res Function(RepeatPasswordChanged) then) =
+      _$RepeatPasswordChangedCopyWithImpl<$Res>;
+  $Res call({String password});
+}
+
+/// @nodoc
+class _$RepeatPasswordChangedCopyWithImpl<$Res>
+    extends _$SignUpEventCopyWithImpl<$Res>
+    implements $RepeatPasswordChangedCopyWith<$Res> {
+  _$RepeatPasswordChangedCopyWithImpl(
+      RepeatPasswordChanged _value, $Res Function(RepeatPasswordChanged) _then)
+      : super(_value, (v) => _then(v as RepeatPasswordChanged));
+
+  @override
+  RepeatPasswordChanged get _value => super._value as RepeatPasswordChanged;
+
+  @override
+  $Res call({
+    Object? password = freezed,
+  }) {
+    return _then(RepeatPasswordChanged(
+      password == freezed
+          ? _value.password
+          : password // ignore: cast_nullable_to_non_nullable
+              as String,
+    ));
+  }
+}
+
+/// @nodoc
+
+class _$RepeatPasswordChanged implements RepeatPasswordChanged {
+  const _$RepeatPasswordChanged(this.password);
+
+  @override
+  final String password;
+
+  @override
+  String toString() {
+    return 'SignUpEvent.repeatPasswordChanged(password: $password)';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) ||
+        (other is RepeatPasswordChanged &&
+            (identical(other.password, password) ||
+                const DeepCollectionEquality()
+                    .equals(other.password, password)));
+  }
+
+  @override
+  int get hashCode =>
+      runtimeType.hashCode ^ const DeepCollectionEquality().hash(password);
+
+  @JsonKey(ignore: true)
+  @override
+  $RepeatPasswordChangedCopyWith<RepeatPasswordChanged> get copyWith =>
+      _$RepeatPasswordChangedCopyWithImpl<RepeatPasswordChanged>(
+          this, _$identity);
+
+  @override
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function() signUpWithUserEmailAndPassword,
+    required TResult Function(String email) emailChanged,
+    required TResult Function(String password) passwordChanged,
+    required TResult Function(String password) repeatPasswordChanged,
+  }) {
+    return repeatPasswordChanged(password);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function()? signUpWithUserEmailAndPassword,
+    TResult Function(String email)? emailChanged,
+    TResult Function(String password)? passwordChanged,
+    TResult Function(String password)? repeatPasswordChanged,
+    required TResult orElse(),
+  }) {
+    if (repeatPasswordChanged != null) {
+      return repeatPasswordChanged(password);
+    }
+    return orElse();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(SignUpWithUserEmailAndPassword value)
+        signUpWithUserEmailAndPassword,
+    required TResult Function(EmailChanged value) emailChanged,
+    required TResult Function(PasswordChanged value) passwordChanged,
+    required TResult Function(RepeatPasswordChanged value)
+        repeatPasswordChanged,
+  }) {
+    return repeatPasswordChanged(this);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(SignUpWithUserEmailAndPassword value)?
+        signUpWithUserEmailAndPassword,
+    TResult Function(EmailChanged value)? emailChanged,
+    TResult Function(PasswordChanged value)? passwordChanged,
+    TResult Function(RepeatPasswordChanged value)? repeatPasswordChanged,
+    required TResult orElse(),
+  }) {
+    if (repeatPasswordChanged != null) {
+      return repeatPasswordChanged(this);
+    }
+    return orElse();
+  }
+}
+
+abstract class RepeatPasswordChanged implements SignUpEvent {
+  const factory RepeatPasswordChanged(String password) =
+      _$RepeatPasswordChanged;
+
+  String get password => throw _privateConstructorUsedError;
+  @JsonKey(ignore: true)
+  $RepeatPasswordChangedCopyWith<RepeatPasswordChanged> get copyWith =>
+      throw _privateConstructorUsedError;
+}
+
 /// @nodoc
 class _$SignUpStateTearOff {
   const _$SignUpStateTearOff();
@@ -436,15 +592,19 @@ class _$SignUpStateTearOff {
   _SignUpState call(
       {String? email,
       String? password,
+      String? repeatedPassword,
       required bool isSubmitting,
       required Option<String> passwordError,
+      required Option<String> repeatPasswordError,
       required Option<String> emailError,
       required Option<Either<UserProfile, UserError>> successOrFail}) {
     return _SignUpState(
       email: email,
       password: password,
+      repeatedPassword: repeatedPassword,
       isSubmitting: isSubmitting,
       passwordError: passwordError,
+      repeatPasswordError: repeatPasswordError,
       emailError: emailError,
       successOrFail: successOrFail,
     );
@@ -458,8 +618,10 @@ const $SignUpState = _$SignUpStateTearOff();
 mixin _$SignUpState {
   String? get email => throw _privateConstructorUsedError;
   String? get password => throw _privateConstructorUsedError;
+  String? get repeatedPassword => throw _privateConstructorUsedError;
   bool get isSubmitting => throw _privateConstructorUsedError;
   Option<String> get passwordError => throw _privateConstructorUsedError;
+  Option<String> get repeatPasswordError => throw _privateConstructorUsedError;
   Option<String> get emailError => throw _privateConstructorUsedError;
   Option<Either<UserProfile, UserError>> get successOrFail =>
       throw _privateConstructorUsedError;
@@ -477,8 +639,10 @@ abstract class $SignUpStateCopyWith<$Res> {
   $Res call(
       {String? email,
       String? password,
+      String? repeatedPassword,
       bool isSubmitting,
       Option<String> passwordError,
+      Option<String> repeatPasswordError,
       Option<String> emailError,
       Option<Either<UserProfile, UserError>> successOrFail});
 }
@@ -495,8 +659,10 @@ class _$SignUpStateCopyWithImpl<$Res> implements $SignUpStateCopyWith<$Res> {
   $Res call({
     Object? email = freezed,
     Object? password = freezed,
+    Object? repeatedPassword = freezed,
     Object? isSubmitting = freezed,
     Object? passwordError = freezed,
+    Object? repeatPasswordError = freezed,
     Object? emailError = freezed,
     Object? successOrFail = freezed,
   }) {
@@ -509,6 +675,10 @@ class _$SignUpStateCopyWithImpl<$Res> implements $SignUpStateCopyWith<$Res> {
           ? _value.password
           : password // ignore: cast_nullable_to_non_nullable
               as String?,
+      repeatedPassword: repeatedPassword == freezed
+          ? _value.repeatedPassword
+          : repeatedPassword // ignore: cast_nullable_to_non_nullable
+              as String?,
       isSubmitting: isSubmitting == freezed
           ? _value.isSubmitting
           : isSubmitting // ignore: cast_nullable_to_non_nullable
@@ -517,6 +687,10 @@ class _$SignUpStateCopyWithImpl<$Res> implements $SignUpStateCopyWith<$Res> {
           ? _value.passwordError
           : passwordError // ignore: cast_nullable_to_non_nullable
               as Option<String>,
+      repeatPasswordError: repeatPasswordError == freezed
+          ? _value.repeatPasswordError
+          : repeatPasswordError // ignore: cast_nullable_to_non_nullable
+              as Option<String>,
       emailError: emailError == freezed
           ? _value.emailError
           : emailError // ignore: cast_nullable_to_non_nullable
@@ -539,8 +713,10 @@ abstract class _$SignUpStateCopyWith<$Res>
   $Res call(
       {String? email,
       String? password,
+      String? repeatedPassword,
       bool isSubmitting,
       Option<String> passwordError,
+      Option<String> repeatPasswordError,
       Option<String> emailError,
       Option<Either<UserProfile, UserError>> successOrFail});
 }
@@ -559,8 +735,10 @@ class __$SignUpStateCopyWithImpl<$Res> extends _$SignUpStateCopyWithImpl<$Res>
   $Res call({
     Object? email = freezed,
     Object? password = freezed,
+    Object? repeatedPassword = freezed,
     Object? isSubmitting = freezed,
     Object? passwordError = freezed,
+    Object? repeatPasswordError = freezed,
     Object? emailError = freezed,
     Object? successOrFail = freezed,
   }) {
@@ -573,6 +751,10 @@ class __$SignUpStateCopyWithImpl<$Res> extends _$SignUpStateCopyWithImpl<$Res>
           ? _value.password
           : password // ignore: cast_nullable_to_non_nullable
               as String?,
+      repeatedPassword: repeatedPassword == freezed
+          ? _value.repeatedPassword
+          : repeatedPassword // ignore: cast_nullable_to_non_nullable
+              as String?,
       isSubmitting: isSubmitting == freezed
           ? _value.isSubmitting
           : isSubmitting // ignore: cast_nullable_to_non_nullable
@@ -581,6 +763,10 @@ class __$SignUpStateCopyWithImpl<$Res> extends _$SignUpStateCopyWithImpl<$Res>
           ? _value.passwordError
           : passwordError // ignore: cast_nullable_to_non_nullable
               as Option<String>,
+      repeatPasswordError: repeatPasswordError == freezed
+          ? _value.repeatPasswordError
+          : repeatPasswordError // ignore: cast_nullable_to_non_nullable
+              as Option<String>,
       emailError: emailError == freezed
           ? _value.emailError
           : emailError // ignore: cast_nullable_to_non_nullable
@@ -599,8 +785,10 @@ class _$_SignUpState implements _SignUpState {
   const _$_SignUpState(
       {this.email,
       this.password,
+      this.repeatedPassword,
       required this.isSubmitting,
       required this.passwordError,
+      required this.repeatPasswordError,
       required this.emailError,
       required this.successOrFail});
 
@@ -609,17 +797,21 @@ class _$_SignUpState implements _SignUpState {
   @override
   final String? password;
   @override
+  final String? repeatedPassword;
+  @override
   final bool isSubmitting;
   @override
   final Option<String> passwordError;
   @override
+  final Option<String> repeatPasswordError;
+  @override
   final Option<String> emailError;
   @override
   final Option<Either<UserProfile, UserError>> successOrFail;
 
   @override
   String toString() {
-    return 'SignUpState(email: $email, password: $password, isSubmitting: $isSubmitting, passwordError: $passwordError, emailError: $emailError, successOrFail: $successOrFail)';
+    return 'SignUpState(email: $email, password: $password, repeatedPassword: $repeatedPassword, isSubmitting: $isSubmitting, passwordError: $passwordError, repeatPasswordError: $repeatPasswordError, emailError: $emailError, successOrFail: $successOrFail)';
   }
 
   @override
@@ -631,12 +823,18 @@ class _$_SignUpState implements _SignUpState {
             (identical(other.password, password) ||
                 const DeepCollectionEquality()
                     .equals(other.password, password)) &&
+            (identical(other.repeatedPassword, repeatedPassword) ||
+                const DeepCollectionEquality()
+                    .equals(other.repeatedPassword, repeatedPassword)) &&
             (identical(other.isSubmitting, isSubmitting) ||
                 const DeepCollectionEquality()
                     .equals(other.isSubmitting, isSubmitting)) &&
             (identical(other.passwordError, passwordError) ||
                 const DeepCollectionEquality()
                     .equals(other.passwordError, passwordError)) &&
+            (identical(other.repeatPasswordError, repeatPasswordError) ||
+                const DeepCollectionEquality()
+                    .equals(other.repeatPasswordError, repeatPasswordError)) &&
             (identical(other.emailError, emailError) ||
                 const DeepCollectionEquality()
                     .equals(other.emailError, emailError)) &&
@@ -650,8 +848,10 @@ class _$_SignUpState implements _SignUpState {
       runtimeType.hashCode ^
       const DeepCollectionEquality().hash(email) ^
       const DeepCollectionEquality().hash(password) ^
+      const DeepCollectionEquality().hash(repeatedPassword) ^
       const DeepCollectionEquality().hash(isSubmitting) ^
       const DeepCollectionEquality().hash(passwordError) ^
+      const DeepCollectionEquality().hash(repeatPasswordError) ^
       const DeepCollectionEquality().hash(emailError) ^
       const DeepCollectionEquality().hash(successOrFail);
 
@@ -665,8 +865,10 @@ abstract class _SignUpState implements SignUpState {
   const factory _SignUpState(
           {String? email,
           String? password,
+          String? repeatedPassword,
           required bool isSubmitting,
           required Option<String> passwordError,
+          required Option<String> repeatPasswordError,
           required Option<String> emailError,
           required Option<Either<UserProfile, UserError>> successOrFail}) =
       _$_SignUpState;
@@ -676,10 +878,14 @@ abstract class _SignUpState implements SignUpState {
   @override
   String? get password => throw _privateConstructorUsedError;
   @override
+  String? get repeatedPassword => throw _privateConstructorUsedError;
+  @override
   bool get isSubmitting => throw _privateConstructorUsedError;
   @override
   Option<String> get passwordError => throw _privateConstructorUsedError;
   @override
+  Option<String> get repeatPasswordError => throw _privateConstructorUsedError;
+  @override
   Option<String> get emailError => throw _privateConstructorUsedError;
   @override
   Option<Either<UserProfile, UserError>> get successOrFail =>

+ 3 - 3
app_flowy/lib/user/domain/i_auth.dart

@@ -12,7 +12,7 @@ abstract class IAuth {
 }
 
 abstract class IAuthRouter {
-  void showWorkspaceSelectScreen(BuildContext context, UserProfile user);
-  void showSignUpScreen(BuildContext context);
-  void showForgetPasswordScreen(BuildContext context);
+  void pushWelcomeScreen(BuildContext context, UserProfile userProfile);
+  void pushSignUpScreen(BuildContext context);
+  void pushForgetPasswordScreen(BuildContext context);
 }

+ 8 - 12
app_flowy/lib/user/infrastructure/i_auth_impl.dart

@@ -1,4 +1,6 @@
-import 'package:app_flowy/user/presentation/sign_up/sign_up_screen.dart';
+import 'package:app_flowy/startup/startup.dart';
+import 'package:app_flowy/user/presentation/sign_up_screen.dart';
+import 'package:app_flowy/welcome/domain/i_welcome.dart';
 import 'package:app_flowy/workspace/infrastructure/repos/user_repo.dart';
 import 'package:app_flowy/workspace/presentation/workspace/workspace_select_screen.dart';
 import 'package:dartz/dartz.dart';
@@ -34,26 +36,20 @@ class AuthImpl extends IAuth {
 
 class AuthRouterImpl extends IAuthRouter {
   @override
-  void showForgetPasswordScreen(BuildContext context) {
+  void pushForgetPasswordScreen(BuildContext context) {
     // TODO: implement showForgetPasswordScreen
   }
 
   @override
-  void showWorkspaceSelectScreen(BuildContext context, UserProfile user) {
-    Navigator.of(context).push(
-      PageRoutes.fade(
-        () => WorkspaceSelectScreen(
-          repo: UserRepo(user: user),
-        ),
-      ),
-    );
+  void pushWelcomeScreen(BuildContext context, UserProfile userProfile) {
+    getIt<IWelcomeRoute>().pushWelcomeScreen(context, userProfile);
   }
 
   @override
-  void showSignUpScreen(BuildContext context) {
+  void pushSignUpScreen(BuildContext context) {
     Navigator.of(context).push(
       PageRoutes.fade(
-        () => const SignUpScreen(),
+        () => SignUpScreen(router: getIt<IAuthRouter>()),
       ),
     );
   }

+ 6 - 4
app_flowy/lib/user/presentation/sign_in_screen.dart

@@ -1,7 +1,7 @@
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/user/application/sign_in_bloc.dart';
 import 'package:app_flowy/user/domain/i_auth.dart';
-import 'package:app_flowy/user/presentation/sign_in/widgets/background.dart';
+import 'package:app_flowy/user/presentation/widgets/background.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/widget/rounded_button.dart';
 import 'package:flowy_infra_ui/widget/rounded_input_field.dart';
@@ -38,7 +38,7 @@ class SignInScreen extends StatelessWidget {
   void _handleSuccessOrFail(
       Either<UserProfile, UserError> result, BuildContext context) {
     result.fold(
-      (user) => router.showWorkspaceSelectScreen(context, user),
+      (user) => router.pushWelcomeScreen(context, user),
       (error) => _showErrorMessage(context, error.msg),
     );
   }
@@ -106,7 +106,7 @@ class SignUpPrompt extends StatelessWidget {
           style: TextButton.styleFrom(
             textStyle: const TextStyle(fontSize: 12),
           ),
-          onPressed: () => router.showSignUpScreen(context),
+          onPressed: () => router.pushSignUpScreen(context),
           child: Text(
             'Sign Up',
             style: TextStyle(color: theme.main1),
@@ -155,7 +155,7 @@ class ForgetPasswordButton extends StatelessWidget {
       style: TextButton.styleFrom(
         textStyle: const TextStyle(fontSize: 12),
       ),
-      onPressed: () => router.showForgetPasswordScreen(context),
+      onPressed: () => router.pushForgetPasswordScreen(context),
       child: Text(
         'Forgot Password?',
         style: TextStyle(color: theme.main1),
@@ -178,6 +178,7 @@ class PasswordTextField extends StatelessWidget {
       builder: (context, state) {
         return RoundedInputField(
           obscureText: true,
+          fontSize: 14,
           obscureIcon: svgWidgetWithName("home/Hide.svg"),
           obscureHideIcon: svgWidgetWithName("home/Show.svg"),
           hintText: 'Password',
@@ -211,6 +212,7 @@ class EmailTextField extends StatelessWidget {
       builder: (context, state) {
         return RoundedInputField(
           hintText: 'Email',
+          fontSize: 14,
           normalBorderColor: theme.shader4,
           highlightBorderColor: theme.red,
           errorText: context

+ 56 - 31
app_flowy/lib/user/presentation/sign_up_screen.dart

@@ -1,5 +1,6 @@
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/user/application/sign_up_bloc.dart';
+import 'package:app_flowy/user/domain/i_auth.dart';
 import 'package:app_flowy/user/presentation/widgets/background.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/widget/rounded_button.dart';
@@ -13,7 +14,8 @@ import 'package:dartz/dartz.dart';
 import 'package:flowy_infra/image.dart';
 
 class SignUpScreen extends StatelessWidget {
-  const SignUpScreen({Key? key}) : super(key: key);
+  final IAuthRouter router;
+  const SignUpScreen({Key? key, required this.router}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
@@ -22,31 +24,23 @@ class SignUpScreen extends StatelessWidget {
       child: BlocListener<SignUpBloc, SignUpState>(
         listener: (context, state) {
           state.successOrFail.fold(
-            () => null,
-            (result) => _handleSuccessOrFail(result, context),
+            () => {},
+            (result) => _handleSuccessOrFail(context, result),
           );
         },
-        child: const Scaffold(
-          body: SignUpForm(),
-        ),
+        child: const Scaffold(body: SignUpForm()),
       ),
     );
   }
 
   void _handleSuccessOrFail(
-      Either<UserProfile, UserError> result, BuildContext context) {
+      BuildContext context, Either<UserProfile, UserError> result) {
     result.fold(
-      (user) => {
-        // router.showWorkspaceSelectScreen(context, user)
-      },
-      (error) => _showErrorMessage(context, error.msg),
-    );
-  }
-
-  void _showErrorMessage(BuildContext context, String msg) {
-    ScaffoldMessenger.of(context).showSnackBar(
-      SnackBar(
-        content: Text(msg),
+      (user) => router.pushWelcomeScreen(context, user),
+      (error) => ScaffoldMessenger.of(context).showSnackBar(
+        SnackBar(
+          content: Text(error.msg),
+        ),
       ),
     );
   }
@@ -70,7 +64,7 @@ class SignUpForm extends StatelessWidget {
           const VSpace(30),
           const EmailTextField(),
           const PasswordTextField(),
-          const PasswordTextField(hintText: "Repeate password"),
+          const RepeatPasswordTextField(),
           const VSpace(30),
           const SignUpButton(),
           const VSpace(10),
@@ -95,17 +89,14 @@ class SignUpPrompt extends StatelessWidget {
     final theme = context.watch<AppTheme>();
     return Row(
       children: [
-        Text("Already have an account",
-            style: TextStyle(color: theme.shader3, fontSize: 12)),
+        Text(
+          "Already have an account?",
+          style: TextStyle(color: theme.shader3, fontSize: 12),
+        ),
         TextButton(
-          style: TextButton.styleFrom(
-            textStyle: const TextStyle(fontSize: 12),
-          ),
+          style: TextButton.styleFrom(textStyle: const TextStyle(fontSize: 12)),
           onPressed: () => Navigator.pop(context),
-          child: Text(
-            'Sign In',
-            style: TextStyle(color: theme.main1),
-          ),
+          child: Text('Sign In', style: TextStyle(color: theme.main1)),
         ),
       ],
       mainAxisAlignment: MainAxisAlignment.center,
@@ -136,10 +127,8 @@ class SignUpButton extends StatelessWidget {
 }
 
 class PasswordTextField extends StatelessWidget {
-  final String hintText;
   const PasswordTextField({
     Key? key,
-    this.hintText = "Password",
   }) : super(key: key);
 
   @override
@@ -155,7 +144,7 @@ class PasswordTextField extends StatelessWidget {
           obscureHideIcon: svgWidgetWithName("home/Show.svg"),
           fontSize: 14,
           fontWeight: FontWeight.w500,
-          hintText: hintText,
+          hintText: "Password",
           normalBorderColor: theme.shader4,
           highlightBorderColor: theme.red,
           errorText: context
@@ -172,6 +161,41 @@ class PasswordTextField extends StatelessWidget {
   }
 }
 
+class RepeatPasswordTextField extends StatelessWidget {
+  const RepeatPasswordTextField({
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final theme = context.watch<AppTheme>();
+    return BlocBuilder<SignUpBloc, SignUpState>(
+      buildWhen: (previous, current) =>
+          previous.repeatPasswordError != current.repeatPasswordError,
+      builder: (context, state) {
+        return RoundedInputField(
+          obscureText: true,
+          obscureIcon: svgWidgetWithName("home/Hide.svg"),
+          obscureHideIcon: svgWidgetWithName("home/Show.svg"),
+          fontSize: 14,
+          fontWeight: FontWeight.w500,
+          hintText: "Repeate password",
+          normalBorderColor: theme.shader4,
+          highlightBorderColor: theme.red,
+          errorText: context
+              .read<SignUpBloc>()
+              .state
+              .repeatPasswordError
+              .fold(() => "", (error) => error),
+          onChanged: (value) => context
+              .read<SignUpBloc>()
+              .add(SignUpEvent.repeatPasswordChanged(value)),
+        );
+      },
+    );
+  }
+}
+
 class EmailTextField extends StatelessWidget {
   const EmailTextField({
     Key? key,
@@ -186,6 +210,7 @@ class EmailTextField extends StatelessWidget {
       builder: (context, state) {
         return RoundedInputField(
           hintText: 'Email',
+          fontSize: 14,
           normalBorderColor: theme.shader4,
           highlightBorderColor: theme.red,
           errorText: context

+ 11 - 11
app_flowy/lib/welcome/application/welcome_bloc.dart → app_flowy/lib/welcome/application/splash_bloc.dart

@@ -3,14 +3,14 @@ import 'package:app_flowy/welcome/domain/i_welcome.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 
-part 'welcome_bloc.freezed.dart';
+part 'splash_bloc.freezed.dart';
 
-class WelcomeBloc extends Bloc<WelcomeEvent, WelcomeState> {
-  final IWelcomeAuth authImpl;
-  WelcomeBloc(this.authImpl) : super(WelcomeState.initial());
+class SplashBloc extends Bloc<SplashEvent, SplashState> {
+  final ISplashAuth authImpl;
+  SplashBloc(this.authImpl) : super(SplashState.initial());
 
   @override
-  Stream<WelcomeState> mapEventToState(WelcomeEvent event) async* {
+  Stream<SplashState> mapEventToState(SplashEvent event) async* {
     yield* event.map(
       getUser: (val) async* {
         final authState = await authImpl.currentUserProfile();
@@ -21,17 +21,17 @@ class WelcomeBloc extends Bloc<WelcomeEvent, WelcomeState> {
 }
 
 @freezed
-abstract class WelcomeEvent with _$WelcomeEvent {
-  const factory WelcomeEvent.getUser() = _GetUser;
+abstract class SplashEvent with _$SplashEvent {
+  const factory SplashEvent.getUser() = _GetUser;
 }
 
 @freezed
-abstract class WelcomeState implements _$WelcomeState {
-  const factory WelcomeState({
+abstract class SplashState implements _$SplashState {
+  const factory SplashState({
     required AuthState auth,
-  }) = _WelcomeState;
+  }) = _SplashState;
 
-  factory WelcomeState.initial() => const WelcomeState(
+  factory SplashState.initial() => const SplashState(
         auth: AuthState.initial(),
       );
 }

+ 52 - 52
app_flowy/lib/welcome/application/welcome_bloc.freezed.dart → app_flowy/lib/welcome/application/splash_bloc.freezed.dart

@@ -1,7 +1,7 @@
 // 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, invalid_annotation_target
 
-part of 'welcome_bloc.dart';
+part of 'splash_bloc.dart';
 
 // **************************************************************************
 // FreezedGenerator
@@ -13,8 +13,8 @@ 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 _$WelcomeEventTearOff {
-  const _$WelcomeEventTearOff();
+class _$SplashEventTearOff {
+  const _$SplashEventTearOff();
 
   _GetUser getUser() {
     return const _GetUser();
@@ -22,10 +22,10 @@ class _$WelcomeEventTearOff {
 }
 
 /// @nodoc
-const $WelcomeEvent = _$WelcomeEventTearOff();
+const $SplashEvent = _$SplashEventTearOff();
 
 /// @nodoc
-mixin _$WelcomeEvent {
+mixin _$SplashEvent {
   @optionalTypeArgs
   TResult when<TResult extends Object?>({
     required TResult Function() getUser,
@@ -51,19 +51,19 @@ mixin _$WelcomeEvent {
 }
 
 /// @nodoc
-abstract class $WelcomeEventCopyWith<$Res> {
-  factory $WelcomeEventCopyWith(
-          WelcomeEvent value, $Res Function(WelcomeEvent) then) =
-      _$WelcomeEventCopyWithImpl<$Res>;
+abstract class $SplashEventCopyWith<$Res> {
+  factory $SplashEventCopyWith(
+          SplashEvent value, $Res Function(SplashEvent) then) =
+      _$SplashEventCopyWithImpl<$Res>;
 }
 
 /// @nodoc
-class _$WelcomeEventCopyWithImpl<$Res> implements $WelcomeEventCopyWith<$Res> {
-  _$WelcomeEventCopyWithImpl(this._value, this._then);
+class _$SplashEventCopyWithImpl<$Res> implements $SplashEventCopyWith<$Res> {
+  _$SplashEventCopyWithImpl(this._value, this._then);
 
-  final WelcomeEvent _value;
+  final SplashEvent _value;
   // ignore: unused_field
-  final $Res Function(WelcomeEvent) _then;
+  final $Res Function(SplashEvent) _then;
 }
 
 /// @nodoc
@@ -73,7 +73,7 @@ abstract class _$GetUserCopyWith<$Res> {
 }
 
 /// @nodoc
-class __$GetUserCopyWithImpl<$Res> extends _$WelcomeEventCopyWithImpl<$Res>
+class __$GetUserCopyWithImpl<$Res> extends _$SplashEventCopyWithImpl<$Res>
     implements _$GetUserCopyWith<$Res> {
   __$GetUserCopyWithImpl(_GetUser _value, $Res Function(_GetUser) _then)
       : super(_value, (v) => _then(v as _GetUser));
@@ -89,7 +89,7 @@ class _$_GetUser implements _GetUser {
 
   @override
   String toString() {
-    return 'WelcomeEvent.getUser()';
+    return 'SplashEvent.getUser()';
   }
 
   @override
@@ -141,50 +141,50 @@ class _$_GetUser implements _GetUser {
   }
 }
 
-abstract class _GetUser implements WelcomeEvent {
+abstract class _GetUser implements SplashEvent {
   const factory _GetUser() = _$_GetUser;
 }
 
 /// @nodoc
-class _$WelcomeStateTearOff {
-  const _$WelcomeStateTearOff();
+class _$SplashStateTearOff {
+  const _$SplashStateTearOff();
 
-  _WelcomeState call({required AuthState auth}) {
-    return _WelcomeState(
+  _SplashState call({required AuthState auth}) {
+    return _SplashState(
       auth: auth,
     );
   }
 }
 
 /// @nodoc
-const $WelcomeState = _$WelcomeStateTearOff();
+const $SplashState = _$SplashStateTearOff();
 
 /// @nodoc
-mixin _$WelcomeState {
+mixin _$SplashState {
   AuthState get auth => throw _privateConstructorUsedError;
 
   @JsonKey(ignore: true)
-  $WelcomeStateCopyWith<WelcomeState> get copyWith =>
+  $SplashStateCopyWith<SplashState> get copyWith =>
       throw _privateConstructorUsedError;
 }
 
 /// @nodoc
-abstract class $WelcomeStateCopyWith<$Res> {
-  factory $WelcomeStateCopyWith(
-          WelcomeState value, $Res Function(WelcomeState) then) =
-      _$WelcomeStateCopyWithImpl<$Res>;
+abstract class $SplashStateCopyWith<$Res> {
+  factory $SplashStateCopyWith(
+          SplashState value, $Res Function(SplashState) then) =
+      _$SplashStateCopyWithImpl<$Res>;
   $Res call({AuthState auth});
 
   $AuthStateCopyWith<$Res> get auth;
 }
 
 /// @nodoc
-class _$WelcomeStateCopyWithImpl<$Res> implements $WelcomeStateCopyWith<$Res> {
-  _$WelcomeStateCopyWithImpl(this._value, this._then);
+class _$SplashStateCopyWithImpl<$Res> implements $SplashStateCopyWith<$Res> {
+  _$SplashStateCopyWithImpl(this._value, this._then);
 
-  final WelcomeState _value;
+  final SplashState _value;
   // ignore: unused_field
-  final $Res Function(WelcomeState) _then;
+  final $Res Function(SplashState) _then;
 
   @override
   $Res call({
@@ -207,11 +207,11 @@ class _$WelcomeStateCopyWithImpl<$Res> implements $WelcomeStateCopyWith<$Res> {
 }
 
 /// @nodoc
-abstract class _$WelcomeStateCopyWith<$Res>
-    implements $WelcomeStateCopyWith<$Res> {
-  factory _$WelcomeStateCopyWith(
-          _WelcomeState value, $Res Function(_WelcomeState) then) =
-      __$WelcomeStateCopyWithImpl<$Res>;
+abstract class _$SplashStateCopyWith<$Res>
+    implements $SplashStateCopyWith<$Res> {
+  factory _$SplashStateCopyWith(
+          _SplashState value, $Res Function(_SplashState) then) =
+      __$SplashStateCopyWithImpl<$Res>;
   @override
   $Res call({AuthState auth});
 
@@ -220,20 +220,20 @@ abstract class _$WelcomeStateCopyWith<$Res>
 }
 
 /// @nodoc
-class __$WelcomeStateCopyWithImpl<$Res> extends _$WelcomeStateCopyWithImpl<$Res>
-    implements _$WelcomeStateCopyWith<$Res> {
-  __$WelcomeStateCopyWithImpl(
-      _WelcomeState _value, $Res Function(_WelcomeState) _then)
-      : super(_value, (v) => _then(v as _WelcomeState));
+class __$SplashStateCopyWithImpl<$Res> extends _$SplashStateCopyWithImpl<$Res>
+    implements _$SplashStateCopyWith<$Res> {
+  __$SplashStateCopyWithImpl(
+      _SplashState _value, $Res Function(_SplashState) _then)
+      : super(_value, (v) => _then(v as _SplashState));
 
   @override
-  _WelcomeState get _value => super._value as _WelcomeState;
+  _SplashState get _value => super._value as _SplashState;
 
   @override
   $Res call({
     Object? auth = freezed,
   }) {
-    return _then(_WelcomeState(
+    return _then(_SplashState(
       auth: auth == freezed
           ? _value.auth
           : auth // ignore: cast_nullable_to_non_nullable
@@ -244,21 +244,21 @@ class __$WelcomeStateCopyWithImpl<$Res> extends _$WelcomeStateCopyWithImpl<$Res>
 
 /// @nodoc
 
-class _$_WelcomeState implements _WelcomeState {
-  const _$_WelcomeState({required this.auth});
+class _$_SplashState implements _SplashState {
+  const _$_SplashState({required this.auth});
 
   @override
   final AuthState auth;
 
   @override
   String toString() {
-    return 'WelcomeState(auth: $auth)';
+    return 'SplashState(auth: $auth)';
   }
 
   @override
   bool operator ==(dynamic other) {
     return identical(this, other) ||
-        (other is _WelcomeState &&
+        (other is _SplashState &&
             (identical(other.auth, auth) ||
                 const DeepCollectionEquality().equals(other.auth, auth)));
   }
@@ -269,17 +269,17 @@ class _$_WelcomeState implements _WelcomeState {
 
   @JsonKey(ignore: true)
   @override
-  _$WelcomeStateCopyWith<_WelcomeState> get copyWith =>
-      __$WelcomeStateCopyWithImpl<_WelcomeState>(this, _$identity);
+  _$SplashStateCopyWith<_SplashState> get copyWith =>
+      __$SplashStateCopyWithImpl<_SplashState>(this, _$identity);
 }
 
-abstract class _WelcomeState implements WelcomeState {
-  const factory _WelcomeState({required AuthState auth}) = _$_WelcomeState;
+abstract class _SplashState implements SplashState {
+  const factory _SplashState({required AuthState auth}) = _$_SplashState;
 
   @override
   AuthState get auth => throw _privateConstructorUsedError;
   @override
   @JsonKey(ignore: true)
-  _$WelcomeStateCopyWith<_WelcomeState> get copyWith =>
+  _$SplashStateCopyWith<_SplashState> get copyWith =>
       throw _privateConstructorUsedError;
 }

+ 5 - 3
app_flowy/lib/welcome/domain/i_welcome.dart

@@ -3,11 +3,13 @@ import 'package:flutter/widgets.dart';
 
 import 'auth_state.dart';
 
-abstract class IWelcomeAuth {
+abstract class ISplashAuth {
   Future<AuthState> currentUserProfile();
 }
 
 abstract class IWelcomeRoute {
-  Widget pushSignInScreen();
-  Future<void> pushHomeScreen(BuildContext context, UserProfile profile);
+  void pushSignInScreen(BuildContext context);
+  Future<void> pushWelcomeScreen(BuildContext context, UserProfile profile);
+  void pushHomeScreen(
+      BuildContext context, UserProfile profile, String workspaceId);
 }

+ 3 - 4
app_flowy/lib/welcome/infrastructure/deps_resolver.dart

@@ -1,5 +1,5 @@
 import 'package:app_flowy/workspace/application/edit_pannel/edit_pannel_bloc.dart';
-import 'package:app_flowy/welcome/application/welcome_bloc.dart';
+import 'package:app_flowy/welcome/application/splash_bloc.dart';
 import 'package:app_flowy/welcome/infrastructure/i_welcome_impl.dart';
 import 'package:app_flowy/workspace/application/home/home_bloc.dart';
 import 'package:app_flowy/workspace/application/home/home_watcher_bloc.dart';
@@ -7,13 +7,12 @@ import 'package:get_it/get_it.dart';
 
 class WelcomeDepsResolver {
   static Future<void> resolve(GetIt getIt) async {
-    getIt.registerFactory<IWelcomeAuth>(() => WelcomeAuthImpl());
+    getIt.registerFactory<ISplashAuth>(() => WelcomeAuthImpl());
     getIt.registerFactory<IWelcomeRoute>(() => WelcomeRoute());
     getIt.registerFactory<HomeBloc>(() => HomeBloc());
     getIt.registerFactory<HomeWatcherBloc>(() => HomeWatcherBloc());
     getIt.registerFactory<EditPannelBloc>(() => EditPannelBloc());
 
-    getIt
-        .registerFactory<WelcomeBloc>(() => WelcomeBloc(getIt<IWelcomeAuth>()));
+    getIt.registerFactory<SplashBloc>(() => SplashBloc(getIt<ISplashAuth>()));
   }
 }

+ 27 - 32
app_flowy/lib/welcome/infrastructure/i_welcome_impl.dart

@@ -1,29 +1,28 @@
 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_screen.dart';
 import 'package:app_flowy/welcome/domain/auth_state.dart';
 import 'package:app_flowy/welcome/domain/i_welcome.dart';
 import 'package:app_flowy/workspace/infrastructure/repos/user_repo.dart';
 import 'package:app_flowy/workspace/presentation/home/home_screen.dart';
-import 'package:app_flowy/workspace/presentation/workspace/workspace_select_screen.dart';
+import 'package:app_flowy/welcome/presentation/welcome_screen.dart';
 import 'package:flowy_infra/time/duration.dart';
 import 'package:flowy_infra_ui/widget/route/animation.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
 import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart';
-import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart' as workspace;
 import 'package:flutter/material.dart';
 import 'package:flutter/widgets.dart';
 
 export 'package:app_flowy/welcome/domain/i_welcome.dart';
 
-class WelcomeAuthImpl implements IWelcomeAuth {
+class WelcomeAuthImpl implements ISplashAuth {
   @override
   Future<AuthState> currentUserProfile() {
     final result = UserEventGetUserProfile().send();
     return result.then((result) {
       return result.fold(
-        (UserProfile) {
-          return AuthState.authenticated(UserProfile);
+        (userProfile) {
+          return AuthState.authenticated(userProfile);
         },
         (userError) {
           return AuthState.unauthenticated(userError);
@@ -35,39 +34,35 @@ class WelcomeAuthImpl implements IWelcomeAuth {
 
 class WelcomeRoute implements IWelcomeRoute {
   @override
-  Future<void> pushHomeScreen(BuildContext context, UserProfile user) async {
+  Future<void> pushWelcomeScreen(BuildContext context, UserProfile user) async {
     final repo = UserRepo(user: user);
-    return WorkspaceEventReadCurWorkspace().send().then(
-      (result) {
-        return result.fold(
-          (workspace) =>
-              _pushToScreen(context, HomeScreen(repo.user, workspace.id)),
-          (error) async {
-            assert(error.code == workspace.ErrorCode.CurrentWorkspaceNotFound);
-            final screen = WorkspaceSelectScreen(repo: repo);
-            final workspaceId = await Navigator.of(context).push(
-              PageRoutes.fade(
-                () => screen,
-                RouteDurations.slow.inMilliseconds * .001,
-              ),
-            );
-
-            _pushToScreen(context, HomeScreen(repo.user, workspaceId));
-          },
-        );
-      },
+    final screen = WelcomeScreen(repo: repo);
+    final workspaceId = await Navigator.of(context).push(
+      PageRoutes.fade(
+        () => screen,
+        RouteDurations.slow.inMilliseconds * .001,
+      ),
     );
+
+    pushHomeScreen(context, repo.user, workspaceId);
   }
 
   @override
-  Widget pushSignInScreen() {
-    return SignInScreen(router: getIt<IAuthRouter>());
+  void pushHomeScreen(
+      BuildContext context, UserProfile userProfile, String workspaceId) {
+    Navigator.push(
+      context,
+      PageRoutes.fade(() => HomeScreen(userProfile, workspaceId),
+          RouteDurations.slow.inMilliseconds * .001),
+    );
   }
 
-  void _pushToScreen(BuildContext context, Widget screen) {
+  @override
+  void pushSignInScreen(BuildContext context) {
     Navigator.push(
-        context,
-        PageRoutes.fade(
-            () => screen, RouteDurations.slow.inMilliseconds * .001));
+      context,
+      PageRoutes.fade(() => SignInScreen(router: getIt<IAuthRouter>()),
+          RouteDurations.slow.inMilliseconds * .001),
+    );
   }
 }

+ 81 - 0
app_flowy/lib/welcome/presentation/splash_screen.dart

@@ -0,0 +1,81 @@
+import 'package:app_flowy/welcome/domain/i_welcome.dart';
+import 'package:app_flowy/welcome/domain/auth_state.dart';
+import 'package:app_flowy/startup/startup.dart';
+import 'package:app_flowy/welcome/application/splash_bloc.dart';
+import 'package:flowy_infra/flowy_logger.dart';
+import 'package:flowy_sdk/dispatch/dispatch.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart' as workspace;
+
+class SplashScreen extends StatelessWidget {
+  const SplashScreen({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocProvider(
+      create: (context) {
+        return getIt<SplashBloc>()..add(const SplashEvent.getUser());
+      },
+      child: Scaffold(
+        body: BlocListener<SplashBloc, SplashState>(
+          listener: (context, state) {
+            state.auth.map(
+              authenticated: (r) => _handleAuthenticated(context, r),
+              unauthenticated: (r) => _handleUnauthenticated(context, r),
+              initial: (r) => {},
+            );
+          },
+          child: const Body(),
+        ),
+      ),
+    );
+  }
+
+  void _handleAuthenticated(BuildContext context, Authenticated result) {
+    final userProfile = result.userProfile;
+    WorkspaceEventReadCurWorkspace().send().then(
+      (result) {
+        return result.fold(
+          (workspace) => getIt<IWelcomeRoute>()
+              .pushHomeScreen(context, userProfile, workspace.id),
+          (error) async {
+            assert(error.code == workspace.ErrorCode.CurrentWorkspaceNotFound);
+            getIt<IWelcomeRoute>().pushWelcomeScreen(context, userProfile);
+          },
+        );
+      },
+    );
+  }
+
+  void _handleUnauthenticated(BuildContext context, Unauthenticated result) {
+    Log.error(result.error);
+    getIt<IWelcomeRoute>().pushSignInScreen(context);
+  }
+}
+
+class Body extends StatelessWidget {
+  const Body({Key? key}) : super(key: key);
+  @override
+  Widget build(BuildContext context) {
+    var size = MediaQuery.of(context).size;
+
+    return Container(
+      alignment: Alignment.center,
+      child: SingleChildScrollView(
+        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')),
+            const CircularProgressIndicator.adaptive(),
+          ],
+        ),
+      ),
+    );
+  }
+}

+ 80 - 52
app_flowy/lib/welcome/presentation/welcome_screen.dart

@@ -1,77 +1,105 @@
-import 'package:app_flowy/welcome/domain/i_welcome.dart';
-import 'package:app_flowy/welcome/domain/auth_state.dart';
-import 'package:app_flowy/startup/startup.dart';
-import 'package:app_flowy/welcome/application/welcome_bloc.dart';
-import 'package:flowy_infra_ui/widget/route/animation.dart';
-import 'package:flowy_infra/flowy_logger.dart';
+import 'package:app_flowy/workspace/application/workspace/workspace_list_bloc.dart';
+import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
+import 'package:flowy_infra_ui/style_widget/text_button.dart';
+import 'package:flowy_infra_ui/widget/error_page.dart';
+import 'package:flowy_sdk/protobuf/flowy-workspace/workspace_create.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:flowy_infra/time/prelude.dart';
+import 'package:app_flowy/workspace/infrastructure/repos/user_repo.dart';
 
 class WelcomeScreen extends StatelessWidget {
-  const WelcomeScreen({Key? key}) : super(key: key);
+  final UserRepo repo;
+  const WelcomeScreen({
+    Key? key,
+    required this.repo,
+  }) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
     return BlocProvider(
-      create: (context) {
-        return getIt<WelcomeBloc>()..add(const WelcomeEvent.getUser());
-      },
-      child: Scaffold(
-        body: BlocListener<WelcomeBloc, WelcomeState>(
-          listener: (context, state) {
-            state.auth.map(
-              authenticated: (r) => _handleAuthenticated(context, r),
-              unauthenticated: (r) => _handleUnauthenticated(context, r),
-              initial: (r) => {},
-            );
-          },
-          child: const Body(),
-        ),
+      create: (_) =>
+          WorkspaceListBloc(repo)..add(const WorkspaceListEvent.initial()),
+      child: BlocBuilder<WorkspaceListBloc, WorkspaceListState>(
+        builder: (context, state) {
+          return Scaffold(
+            body: Padding(
+              padding: const EdgeInsets.all(60.0),
+              child: Column(
+                children: [
+                  _renderBody(state),
+                  _renderCreateButton(context),
+                ],
+              ),
+            ),
+          );
+        },
       ),
     );
   }
 
-  void _pushToScreen(BuildContext context, Widget screen) {
-    /// Let the splash view sit for a bit. Mainly for aesthetics and to ensure a smooth intro animation.
-    Navigator.push(
-        context,
-        PageRoutes.fade(
-            () => screen, RouteDurations.slow.inMilliseconds * .001));
+  Widget _renderBody(WorkspaceListState state) {
+    final body = state.successOrFailure.fold(
+      (_) => _renderList(state.workspaces),
+      (error) => FlowyErrorPage(error.toString()),
+    );
+    return body;
   }
 
-  void _handleAuthenticated(BuildContext context, Authenticated result) {
-    getIt<IWelcomeRoute>().pushHomeScreen(context, result.userProfile);
+  Widget _renderCreateButton(BuildContext context) {
+    return SizedBox(
+      width: 200,
+      height: 40,
+      child: FlowyTextButton(
+        "Create workspace",
+        fontSize: 14,
+        onPressed: () {
+          context
+              .read<WorkspaceListBloc>()
+              .add(const WorkspaceListEvent.createWorkspace("workspace", ""));
+        },
+      ),
+    );
   }
 
-  void _handleUnauthenticated(BuildContext context, Unauthenticated result) {
-    Log.error(result.error);
+  Widget _renderList(List<Workspace> workspaces) {
+    return Expanded(
+      child: StyledListView(
+        itemBuilder: (BuildContext context, int index) {
+          final workspace = workspaces[index];
+          return WorkspaceItem(
+            workspace: workspace,
+            onPressed: (workspace) => _handleOnPress(context, workspace),
+          );
+        },
+        itemCount: workspaces.length,
+      ),
+    );
+  }
+
+  void _handleOnPress(BuildContext context, Workspace workspace) {
+    context
+        .read<WorkspaceListBloc>()
+        .add(WorkspaceListEvent.openWorkspace(workspace));
 
-    _pushToScreen(context, getIt<IWelcomeRoute>().pushSignInScreen());
+    Navigator.of(context).pop(workspace.id);
   }
 }
 
-class Body extends StatelessWidget {
-  const Body({Key? key}) : super(key: key);
+class WorkspaceItem extends StatelessWidget {
+  final Workspace workspace;
+  final void Function(Workspace workspace) onPressed;
+  const WorkspaceItem(
+      {Key? key, required this.workspace, required this.onPressed})
+      : super(key: key);
+
   @override
   Widget build(BuildContext context) {
-    var size = MediaQuery.of(context).size;
-
-    return Container(
-      alignment: Alignment.center,
-      child: SingleChildScrollView(
-        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')),
-            const CircularProgressIndicator.adaptive(),
-          ],
-        ),
+    return SizedBox(
+      height: 46,
+      child: FlowyTextButton(
+        workspace.name,
+        fontSize: 14,
+        onPressed: () => onPressed(workspace),
       ),
     );
   }

+ 0 - 236
app_flowy/lib/workspace/presentation/workspace/workspace_select_screen.dart

@@ -1,236 +0,0 @@
-import 'package:app_flowy/workspace/application/workspace/workspace_list_bloc.dart';
-import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
-import 'package:flowy_infra_ui/style_widget/text_button.dart';
-import 'package:flowy_infra_ui/widget/error_page.dart';
-import 'package:flowy_sdk/protobuf/flowy-workspace/workspace_create.pb.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-
-import 'package:app_flowy/workspace/infrastructure/repos/user_repo.dart';
-
-class WorkspaceSelectScreen extends StatelessWidget {
-  final UserRepo repo;
-  const WorkspaceSelectScreen({
-    Key? key,
-    required this.repo,
-  }) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    return BlocProvider(
-      create: (_) =>
-          WorkspaceListBloc(repo)..add(const WorkspaceListEvent.initial()),
-      child: BlocBuilder<WorkspaceListBloc, WorkspaceListState>(
-        builder: (context, state) {
-          return Scaffold(
-            body: Padding(
-              padding: const EdgeInsets.all(60.0),
-              child: Column(
-                children: [
-                  _renderBody(state),
-                  _renderCreateButton(context),
-                ],
-              ),
-            ),
-          );
-        },
-      ),
-    );
-  }
-
-  Widget _renderBody(WorkspaceListState state) {
-    final body = state.successOrFailure.fold(
-      (_) => _renderList(state.workspaces),
-      (error) => FlowyErrorPage(error.toString()),
-    );
-    return body;
-  }
-
-  Widget _renderCreateButton(BuildContext context) {
-    return SizedBox(
-      width: 200,
-      height: 40,
-      child: FlowyTextButton(
-        "Create workspace",
-        fontSize: 14,
-        onPressed: () {
-          context
-              .read<WorkspaceListBloc>()
-              .add(const WorkspaceListEvent.createWorkspace("workspace", ""));
-        },
-      ),
-    );
-  }
-
-  Widget _renderList(List<Workspace> workspaces) {
-    return Expanded(
-      child: StyledListView(
-        itemBuilder: (BuildContext context, int index) {
-          final workspace = workspaces[index];
-          return WorkspaceItem(
-            workspace: workspace,
-            onPressed: (workspace) => _handleOnPress(context, workspace),
-          );
-        },
-        itemCount: workspaces.length,
-      ),
-    );
-  }
-
-  void _handleOnPress(BuildContext context, Workspace workspace) {
-    context
-        .read<WorkspaceListBloc>()
-        .add(WorkspaceListEvent.openWorkspace(workspace));
-
-    Navigator.of(context).pop(workspace.id);
-  }
-}
-
-class WorkspaceItem extends StatelessWidget {
-  final Workspace workspace;
-  final void Function(Workspace workspace) onPressed;
-  const WorkspaceItem(
-      {Key? key, required this.workspace, required this.onPressed})
-      : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    return SizedBox(
-      height: 46,
-      child: FlowyTextButton(
-        workspace.name,
-        fontSize: 14,
-        onPressed: () => onPressed(workspace),
-      ),
-    );
-  }
-}
-
-
-// Bloc and Provider
-
-// *************Provider***************
-// class WorkspaceListNotifier with ChangeNotifier {
-//   UserRepo repo;
-//   List<Workspace> workspaces = [];
-//   WorkspaceListNotifier({
-//     required this.repo,
-//   }) {
-//     fetch();
-//   }
-
-//   void fetch() {
-//     repo.fetchWorkspaces().then((result) {
-//       result.fold((workspaces) {
-//         this.workspaces = workspaces;
-//         notifyListeners();
-//       }, (error) {
-//         Log.error(error);
-//       });
-//     });
-//   }
-// }
-
-// class WorkspaceSelectScreen extends StatelessWidget {
-//   final UserProfile user;
-//   const WorkspaceSelectScreen({
-//     Key? key,
-//     required this.user,
-//   }) : super(key: key);
-
-//   @override
-//   Widget build(BuildContext context) {
-//     return MultiProvider(
-//         providers: [
-//           ChangeNotifierProvider(
-//             create: (_) => WorkspaceListNotifier(repo: UserRepo(user: user)),
-//           )
-//         ],
-//         child: Consumer<WorkspaceListNotifier>(builder: (ctx, notifier, child) {
-//           return StyledListView(
-//             itemBuilder: (BuildContext context, int index) {
-//               final workspace = notifier.workspaces[index];
-//               return WorkspaceItem(workspace);
-//             },
-//             itemCount: notifier.workspaces.length,
-//           );
-//         }));
-//   }
-// }
-
-// *************Bloc***************
-//
-// class WorkspaceListBloc extends Bloc<WorkspaceListEvent, WorkspaceListState> {
-//   UserRepo repo;
-//   WorkspaceListBloc(this.repo) : super(WorkspaceListState.initial());
-
-//   @override
-//   Stream<WorkspaceListState> mapEventToState(
-//     WorkspaceListEvent event,
-//   ) async* {
-//     yield* event.map(
-//       initial: (e) async* {
-//         yield* _fetchWorkspaces();
-//       },
-//     );
-//   }
-
-//   Stream<WorkspaceListState> _fetchWorkspaces() async* {
-//     final workspacesOrFailed = await repo.fetchWorkspaces();
-
-//     yield workspacesOrFailed.fold(
-//         (workspaces) => state.copyWith(
-//             workspaces: workspaces, successOrFailure: left(unit)),
-//         (error) => state.copyWith(successOrFailure: right(error)));
-//   }
-// }
-
-// @freezed
-// abstract class WorkspaceListEvent with _$WorkspaceListEvent {
-//   const factory WorkspaceListEvent.initial() = Initial;
-// }
-
-// @freezed
-// abstract class WorkspaceListState implements _$WorkspaceListState {
-//   const factory WorkspaceListState({
-//     required bool isLoading,
-//     required List<Workspace> workspaces,
-//     required Either<Unit, WorkspaceError> successOrFailure,
-//   }) = _WorkspaceListState;
-
-//   factory WorkspaceListState.initial() => WorkspaceListState(
-//         isLoading: false,
-//         workspaces: List.empty(),
-//         successOrFailure: left(unit),
-//       );
-// }
-//
-// class WorkspaceSelectScreen extends StatelessWidget {
-//   final UserProfile user;
-//   const WorkspaceSelectScreen({
-//     Key? key,
-//     required this.user,
-//   }) : super(key: key);
-
-//   @override
-//   Widget build(BuildContext context) {
-//     return BlocProvider(
-//       create: (_) => WorkspaceListBloc(UserRepo(user: user))
-//         ..add(const WorkspaceListEvent.initial()),
-//       child: BlocBuilder<WorkspaceListBloc, WorkspaceListState>(
-//         builder: (context, state) {
-//           return state.successOrFailure.fold(
-//             (_) => StyledListView(
-//               itemBuilder: (BuildContext context, int index) {
-//                 final workspace = state.workspaces[index];
-//                 return WorkspaceItem(workspace);
-//               },
-//               itemCount: state.workspaces.length,
-//             ),
-//             (error) => Container(),
-//           );
-//         },
-//       ),
-//     );
-//   }
-// }

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

@@ -8,7 +8,7 @@ class Log {
   Log() {
     _logger = Logger(
       printer: PrettyPrinter(
-          methodCount: 2, // number of method calls to be displayed
+          methodCount: 0, // number of method calls to be displayed
           errorMethodCount:
               8, // number of method calls if stacktrace is provided
           lineLength: 120, // width of the output

+ 12 - 7
app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart

@@ -85,13 +85,18 @@ class _RoundedInputFieldState extends State<RoundedInputField> {
     ];
 
     if (widget.errorText.isNotEmpty) {
-      children.add(Text(
-        widget.errorText,
-        style: TextStyle(
-            color: widget.highlightBorderColor,
-            fontWeight: widget.fontWeight,
-            fontSize: widget.fontSize),
-      ));
+      children.add(
+        Align(
+          alignment: Alignment.centerLeft,
+          child: Text(
+            widget.errorText,
+            style: TextStyle(
+                color: widget.highlightBorderColor,
+                fontWeight: widget.fontWeight,
+                fontSize: widget.fontSize),
+          ),
+        ),
+      );
     }
 
     return AnimatedSize(

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

@@ -16,6 +16,7 @@ class WorkspaceObservable extends $pb.ProtobufEnum {
   static const WorkspaceObservable WorkspaceUpdated = WorkspaceObservable._(12, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceUpdated');
   static const WorkspaceObservable WorkspaceCreateApp = WorkspaceObservable._(13, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceCreateApp');
   static const WorkspaceObservable WorkspaceDeleteApp = WorkspaceObservable._(14, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceDeleteApp');
+  static const WorkspaceObservable WorkspaceListUpdated = WorkspaceObservable._(15, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'WorkspaceListUpdated');
   static const WorkspaceObservable AppUpdated = WorkspaceObservable._(21, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AppUpdated');
   static const WorkspaceObservable AppCreateView = WorkspaceObservable._(23, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AppCreateView');
   static const WorkspaceObservable AppDeleteView = WorkspaceObservable._(24, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AppDeleteView');
@@ -28,6 +29,7 @@ class WorkspaceObservable extends $pb.ProtobufEnum {
     WorkspaceUpdated,
     WorkspaceCreateApp,
     WorkspaceDeleteApp,
+    WorkspaceListUpdated,
     AppUpdated,
     AppCreateView,
     AppDeleteView,

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

@@ -18,6 +18,7 @@ const WorkspaceObservable$json = const {
     const {'1': 'WorkspaceUpdated', '2': 12},
     const {'1': 'WorkspaceCreateApp', '2': 13},
     const {'1': 'WorkspaceDeleteApp', '2': 14},
+    const {'1': 'WorkspaceListUpdated', '2': 15},
     const {'1': 'AppUpdated', '2': 21},
     const {'1': 'AppCreateView', '2': 23},
     const {'1': 'AppDeleteView', '2': 24},
@@ -26,4 +27,4 @@ const WorkspaceObservable$json = const {
 };
 
 /// Descriptor for `WorkspaceObservable`. Decode as a `google.protobuf.EnumDescriptorProto`.
-final $typed_data.Uint8List workspaceObservableDescriptor = $convert.base64Decode('ChNXb3Jrc3BhY2VPYnNlcnZhYmxlEgsKB1Vua25vd24QABIXChNVc2VyQ3JlYXRlV29ya3NwYWNlEAoSFwoTVXNlckRlbGV0ZVdvcmtzcGFjZRALEhQKEFdvcmtzcGFjZVVwZGF0ZWQQDBIWChJXb3Jrc3BhY2VDcmVhdGVBcHAQDRIWChJXb3Jrc3BhY2VEZWxldGVBcHAQDhIOCgpBcHBVcGRhdGVkEBUSEQoNQXBwQ3JlYXRlVmlldxAXEhEKDUFwcERlbGV0ZVZpZXcQGBIPCgtWaWV3VXBkYXRlZBAf');
+final $typed_data.Uint8List workspaceObservableDescriptor = $convert.base64Decode('ChNXb3Jrc3BhY2VPYnNlcnZhYmxlEgsKB1Vua25vd24QABIXChNVc2VyQ3JlYXRlV29ya3NwYWNlEAoSFwoTVXNlckRlbGV0ZVdvcmtzcGFjZRALEhQKEFdvcmtzcGFjZVVwZGF0ZWQQDBIWChJXb3Jrc3BhY2VDcmVhdGVBcHAQDRIWChJXb3Jrc3BhY2VEZWxldGVBcHAQDhIYChRXb3Jrc3BhY2VMaXN0VXBkYXRlZBAPEg4KCkFwcFVwZGF0ZWQQFRIRCg1BcHBDcmVhdGVWaWV3EBcSEQoNQXBwRGVsZXRlVmlldxAYEg8KC1ZpZXdVcGRhdGVkEB8=');

+ 1 - 0
backend/tests/api/helper.rs

@@ -182,6 +182,7 @@ async fn configure_database(config: &DatabaseSettings) -> PgPool {
 }
 
 async fn drop_test_database(database_name: String) {
+    // https://stackoverflow.com/questions/36502401/postgres-drop-database-error-pq-cannot-drop-the-currently-open-database?rq=1
     let configuration = {
         let mut c = get_configuration().expect("Failed to read configuration.");
         c.database.database_name = "flowy".to_owned();

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

@@ -24,7 +24,7 @@ pub extern "C" fn init_sdk(path: *mut c_char) -> i64 {
     let c_str: &CStr = unsafe { CStr::from_ptr(path) };
     let path: &str = c_str.to_str().unwrap();
 
-    let config = FlowySDKConfig::new(path).log_filter("info");
+    let config = FlowySDKConfig::new(path).log_filter("debug");
     *FLOWY_SDK.write() = Some(Arc::new(FlowySDK::new(config)));
 
     return 1;

+ 2 - 2
rust-lib/flowy-user/src/entities/auth.rs

@@ -11,7 +11,7 @@ pub struct SignInRequest {
     pub password: String,
 }
 
-#[derive(Default, ProtoBuf)]
+#[derive(Default, ProtoBuf, Debug)]
 pub struct SignInParams {
     #[pb(index = 1)]
     pub email: String,
@@ -76,7 +76,7 @@ impl TryInto<SignUpParams> for SignUpRequest {
     }
 }
 
-#[derive(ProtoBuf, Default)]
+#[derive(ProtoBuf, Default, Debug)]
 pub struct SignUpParams {
     #[pb(index = 1)]
     pub email: String,

+ 1 - 1
rust-lib/flowy-user/src/entities/user_profile.rs

@@ -85,7 +85,7 @@ impl UpdateUserRequest {
     }
 }
 
-#[derive(ProtoBuf, Default, Clone)]
+#[derive(ProtoBuf, Default, Clone, Debug)]
 pub struct UpdateUserParams {
     // TODO: remove user id
     #[pb(index = 1)]

+ 54 - 54
rust-lib/flowy-user/src/protobuf/model/user_profile.rs

@@ -1298,60 +1298,60 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     \x05email\x12\x1c\n\x08password\x18\x04\x20\x01(\tH\x02R\x08passwordB\r\
     \n\x0bone_of_nameB\x0e\n\x0cone_of_emailB\x11\n\x0fone_of_password*1\n\n\
     UserStatus\x12\x0b\n\x07Unknown\x10\0\x12\t\n\x05Login\x10\x01\x12\x0b\n\
-    \x07Expired\x10\x02J\xbb\x08\n\x06\x12\x04\0\0\x19\x01\n\x08\n\x01\x0c\
-    \x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x01\0\x03\x01\n\n\n\x03\x04\0\
-    \x01\x12\x03\x01\x08\x11\n\x0b\n\x04\x04\0\x02\0\x12\x03\x02\x04\x15\n\
-    \x0c\n\x05\x04\0\x02\0\x05\x12\x03\x02\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\
-    \x12\x03\x02\x0b\x10\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x02\x13\x14\n\n\
-    \n\x02\x04\x01\x12\x04\x04\0\x08\x01\n\n\n\x03\x04\x01\x01\x12\x03\x04\
-    \x08\x13\n\x0b\n\x04\x04\x01\x02\0\x12\x03\x05\x04\x12\n\x0c\n\x05\x04\
-    \x01\x02\0\x05\x12\x03\x05\x04\n\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\
-    \x05\x0b\r\n\x0c\n\x05\x04\x01\x02\0\x03\x12\x03\x05\x10\x11\n\x0b\n\x04\
-    \x04\x01\x02\x01\x12\x03\x06\x04\x15\n\x0c\n\x05\x04\x01\x02\x01\x05\x12\
-    \x03\x06\x04\n\n\x0c\n\x05\x04\x01\x02\x01\x01\x12\x03\x06\x0b\x10\n\x0c\
-    \n\x05\x04\x01\x02\x01\x03\x12\x03\x06\x13\x14\n\x0b\n\x04\x04\x01\x02\
-    \x02\x12\x03\x07\x04\x14\n\x0c\n\x05\x04\x01\x02\x02\x05\x12\x03\x07\x04\
-    \n\n\x0c\n\x05\x04\x01\x02\x02\x01\x12\x03\x07\x0b\x0f\n\x0c\n\x05\x04\
-    \x01\x02\x02\x03\x12\x03\x07\x12\x13\n\n\n\x02\x04\x02\x12\x04\t\0\x0e\
-    \x01\n\n\n\x03\x04\x02\x01\x12\x03\t\x08\x19\n\x0b\n\x04\x04\x02\x02\0\
-    \x12\x03\n\x04\x12\n\x0c\n\x05\x04\x02\x02\0\x05\x12\x03\n\x04\n\n\x0c\n\
-    \x05\x04\x02\x02\0\x01\x12\x03\n\x0b\r\n\x0c\n\x05\x04\x02\x02\0\x03\x12\
-    \x03\n\x10\x11\n\x0b\n\x04\x04\x02\x08\0\x12\x03\x0b\x04*\n\x0c\n\x05\
-    \x04\x02\x08\0\x01\x12\x03\x0b\n\x15\n\x0b\n\x04\x04\x02\x02\x01\x12\x03\
-    \x0b\x18(\n\x0c\n\x05\x04\x02\x02\x01\x05\x12\x03\x0b\x18\x1e\n\x0c\n\
-    \x05\x04\x02\x02\x01\x01\x12\x03\x0b\x1f#\n\x0c\n\x05\x04\x02\x02\x01\
-    \x03\x12\x03\x0b&'\n\x0b\n\x04\x04\x02\x08\x01\x12\x03\x0c\x04,\n\x0c\n\
-    \x05\x04\x02\x08\x01\x01\x12\x03\x0c\n\x16\n\x0b\n\x04\x04\x02\x02\x02\
-    \x12\x03\x0c\x19*\n\x0c\n\x05\x04\x02\x02\x02\x05\x12\x03\x0c\x19\x1f\n\
-    \x0c\n\x05\x04\x02\x02\x02\x01\x12\x03\x0c\x20%\n\x0c\n\x05\x04\x02\x02\
-    \x02\x03\x12\x03\x0c()\n\x0b\n\x04\x04\x02\x08\x02\x12\x03\r\x042\n\x0c\
-    \n\x05\x04\x02\x08\x02\x01\x12\x03\r\n\x19\n\x0b\n\x04\x04\x02\x02\x03\
-    \x12\x03\r\x1c0\n\x0c\n\x05\x04\x02\x02\x03\x05\x12\x03\r\x1c\"\n\x0c\n\
-    \x05\x04\x02\x02\x03\x01\x12\x03\r#+\n\x0c\n\x05\x04\x02\x02\x03\x03\x12\
-    \x03\r./\n\n\n\x02\x04\x03\x12\x04\x0f\0\x14\x01\n\n\n\x03\x04\x03\x01\
-    \x12\x03\x0f\x08\x18\n\x0b\n\x04\x04\x03\x02\0\x12\x03\x10\x04\x12\n\x0c\
-    \n\x05\x04\x03\x02\0\x05\x12\x03\x10\x04\n\n\x0c\n\x05\x04\x03\x02\0\x01\
-    \x12\x03\x10\x0b\r\n\x0c\n\x05\x04\x03\x02\0\x03\x12\x03\x10\x10\x11\n\
-    \x0b\n\x04\x04\x03\x08\0\x12\x03\x11\x04*\n\x0c\n\x05\x04\x03\x08\0\x01\
-    \x12\x03\x11\n\x15\n\x0b\n\x04\x04\x03\x02\x01\x12\x03\x11\x18(\n\x0c\n\
-    \x05\x04\x03\x02\x01\x05\x12\x03\x11\x18\x1e\n\x0c\n\x05\x04\x03\x02\x01\
-    \x01\x12\x03\x11\x1f#\n\x0c\n\x05\x04\x03\x02\x01\x03\x12\x03\x11&'\n\
-    \x0b\n\x04\x04\x03\x08\x01\x12\x03\x12\x04,\n\x0c\n\x05\x04\x03\x08\x01\
-    \x01\x12\x03\x12\n\x16\n\x0b\n\x04\x04\x03\x02\x02\x12\x03\x12\x19*\n\
-    \x0c\n\x05\x04\x03\x02\x02\x05\x12\x03\x12\x19\x1f\n\x0c\n\x05\x04\x03\
-    \x02\x02\x01\x12\x03\x12\x20%\n\x0c\n\x05\x04\x03\x02\x02\x03\x12\x03\
-    \x12()\n\x0b\n\x04\x04\x03\x08\x02\x12\x03\x13\x042\n\x0c\n\x05\x04\x03\
-    \x08\x02\x01\x12\x03\x13\n\x19\n\x0b\n\x04\x04\x03\x02\x03\x12\x03\x13\
-    \x1c0\n\x0c\n\x05\x04\x03\x02\x03\x05\x12\x03\x13\x1c\"\n\x0c\n\x05\x04\
-    \x03\x02\x03\x01\x12\x03\x13#+\n\x0c\n\x05\x04\x03\x02\x03\x03\x12\x03\
-    \x13./\n\n\n\x02\x05\0\x12\x04\x15\0\x19\x01\n\n\n\x03\x05\0\x01\x12\x03\
-    \x15\x05\x0f\n\x0b\n\x04\x05\0\x02\0\x12\x03\x16\x04\x10\n\x0c\n\x05\x05\
-    \0\x02\0\x01\x12\x03\x16\x04\x0b\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x16\
-    \x0e\x0f\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x17\x04\x0e\n\x0c\n\x05\x05\0\
-    \x02\x01\x01\x12\x03\x17\x04\t\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x17\
-    \x0c\r\n\x0b\n\x04\x05\0\x02\x02\x12\x03\x18\x04\x10\n\x0c\n\x05\x05\0\
-    \x02\x02\x01\x12\x03\x18\x04\x0b\n\x0c\n\x05\x05\0\x02\x02\x02\x12\x03\
-    \x18\x0e\x0fb\x06proto3\
+    \x07Expired\x10\x02J\xbb\x08\n\x06\x12\x04\0\0\x1a\x01\n\x08\n\x01\x0c\
+    \x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x04\x01\n\n\n\x03\x04\0\
+    \x01\x12\x03\x02\x08\x11\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\x15\n\
+    \x0c\n\x05\x04\0\x02\0\x05\x12\x03\x03\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\
+    \x12\x03\x03\x0b\x10\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x13\x14\n\n\
+    \n\x02\x04\x01\x12\x04\x05\0\t\x01\n\n\n\x03\x04\x01\x01\x12\x03\x05\x08\
+    \x13\n\x0b\n\x04\x04\x01\x02\0\x12\x03\x06\x04\x12\n\x0c\n\x05\x04\x01\
+    \x02\0\x05\x12\x03\x06\x04\n\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\x06\
+    \x0b\r\n\x0c\n\x05\x04\x01\x02\0\x03\x12\x03\x06\x10\x11\n\x0b\n\x04\x04\
+    \x01\x02\x01\x12\x03\x07\x04\x15\n\x0c\n\x05\x04\x01\x02\x01\x05\x12\x03\
+    \x07\x04\n\n\x0c\n\x05\x04\x01\x02\x01\x01\x12\x03\x07\x0b\x10\n\x0c\n\
+    \x05\x04\x01\x02\x01\x03\x12\x03\x07\x13\x14\n\x0b\n\x04\x04\x01\x02\x02\
+    \x12\x03\x08\x04\x14\n\x0c\n\x05\x04\x01\x02\x02\x05\x12\x03\x08\x04\n\n\
+    \x0c\n\x05\x04\x01\x02\x02\x01\x12\x03\x08\x0b\x0f\n\x0c\n\x05\x04\x01\
+    \x02\x02\x03\x12\x03\x08\x12\x13\n\n\n\x02\x04\x02\x12\x04\n\0\x0f\x01\n\
+    \n\n\x03\x04\x02\x01\x12\x03\n\x08\x19\n\x0b\n\x04\x04\x02\x02\0\x12\x03\
+    \x0b\x04\x12\n\x0c\n\x05\x04\x02\x02\0\x05\x12\x03\x0b\x04\n\n\x0c\n\x05\
+    \x04\x02\x02\0\x01\x12\x03\x0b\x0b\r\n\x0c\n\x05\x04\x02\x02\0\x03\x12\
+    \x03\x0b\x10\x11\n\x0b\n\x04\x04\x02\x08\0\x12\x03\x0c\x04*\n\x0c\n\x05\
+    \x04\x02\x08\0\x01\x12\x03\x0c\n\x15\n\x0b\n\x04\x04\x02\x02\x01\x12\x03\
+    \x0c\x18(\n\x0c\n\x05\x04\x02\x02\x01\x05\x12\x03\x0c\x18\x1e\n\x0c\n\
+    \x05\x04\x02\x02\x01\x01\x12\x03\x0c\x1f#\n\x0c\n\x05\x04\x02\x02\x01\
+    \x03\x12\x03\x0c&'\n\x0b\n\x04\x04\x02\x08\x01\x12\x03\r\x04,\n\x0c\n\
+    \x05\x04\x02\x08\x01\x01\x12\x03\r\n\x16\n\x0b\n\x04\x04\x02\x02\x02\x12\
+    \x03\r\x19*\n\x0c\n\x05\x04\x02\x02\x02\x05\x12\x03\r\x19\x1f\n\x0c\n\
+    \x05\x04\x02\x02\x02\x01\x12\x03\r\x20%\n\x0c\n\x05\x04\x02\x02\x02\x03\
+    \x12\x03\r()\n\x0b\n\x04\x04\x02\x08\x02\x12\x03\x0e\x042\n\x0c\n\x05\
+    \x04\x02\x08\x02\x01\x12\x03\x0e\n\x19\n\x0b\n\x04\x04\x02\x02\x03\x12\
+    \x03\x0e\x1c0\n\x0c\n\x05\x04\x02\x02\x03\x05\x12\x03\x0e\x1c\"\n\x0c\n\
+    \x05\x04\x02\x02\x03\x01\x12\x03\x0e#+\n\x0c\n\x05\x04\x02\x02\x03\x03\
+    \x12\x03\x0e./\n\n\n\x02\x04\x03\x12\x04\x10\0\x15\x01\n\n\n\x03\x04\x03\
+    \x01\x12\x03\x10\x08\x18\n\x0b\n\x04\x04\x03\x02\0\x12\x03\x11\x04\x12\n\
+    \x0c\n\x05\x04\x03\x02\0\x05\x12\x03\x11\x04\n\n\x0c\n\x05\x04\x03\x02\0\
+    \x01\x12\x03\x11\x0b\r\n\x0c\n\x05\x04\x03\x02\0\x03\x12\x03\x11\x10\x11\
+    \n\x0b\n\x04\x04\x03\x08\0\x12\x03\x12\x04*\n\x0c\n\x05\x04\x03\x08\0\
+    \x01\x12\x03\x12\n\x15\n\x0b\n\x04\x04\x03\x02\x01\x12\x03\x12\x18(\n\
+    \x0c\n\x05\x04\x03\x02\x01\x05\x12\x03\x12\x18\x1e\n\x0c\n\x05\x04\x03\
+    \x02\x01\x01\x12\x03\x12\x1f#\n\x0c\n\x05\x04\x03\x02\x01\x03\x12\x03\
+    \x12&'\n\x0b\n\x04\x04\x03\x08\x01\x12\x03\x13\x04,\n\x0c\n\x05\x04\x03\
+    \x08\x01\x01\x12\x03\x13\n\x16\n\x0b\n\x04\x04\x03\x02\x02\x12\x03\x13\
+    \x19*\n\x0c\n\x05\x04\x03\x02\x02\x05\x12\x03\x13\x19\x1f\n\x0c\n\x05\
+    \x04\x03\x02\x02\x01\x12\x03\x13\x20%\n\x0c\n\x05\x04\x03\x02\x02\x03\
+    \x12\x03\x13()\n\x0b\n\x04\x04\x03\x08\x02\x12\x03\x14\x042\n\x0c\n\x05\
+    \x04\x03\x08\x02\x01\x12\x03\x14\n\x19\n\x0b\n\x04\x04\x03\x02\x03\x12\
+    \x03\x14\x1c0\n\x0c\n\x05\x04\x03\x02\x03\x05\x12\x03\x14\x1c\"\n\x0c\n\
+    \x05\x04\x03\x02\x03\x01\x12\x03\x14#+\n\x0c\n\x05\x04\x03\x02\x03\x03\
+    \x12\x03\x14./\n\n\n\x02\x05\0\x12\x04\x16\0\x1a\x01\n\n\n\x03\x05\0\x01\
+    \x12\x03\x16\x05\x0f\n\x0b\n\x04\x05\0\x02\0\x12\x03\x17\x04\x10\n\x0c\n\
+    \x05\x05\0\x02\0\x01\x12\x03\x17\x04\x0b\n\x0c\n\x05\x05\0\x02\0\x02\x12\
+    \x03\x17\x0e\x0f\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x18\x04\x0e\n\x0c\n\
+    \x05\x05\0\x02\x01\x01\x12\x03\x18\x04\t\n\x0c\n\x05\x05\0\x02\x01\x02\
+    \x12\x03\x18\x0c\r\n\x0b\n\x04\x05\0\x02\x02\x12\x03\x19\x04\x10\n\x0c\n\
+    \x05\x05\0\x02\x02\x01\x12\x03\x19\x04\x0b\n\x0c\n\x05\x05\0\x02\x02\x02\
+    \x12\x03\x19\x0e\x0fb\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 1 - 0
rust-lib/flowy-user/src/protobuf/proto/user_profile.proto

@@ -1,4 +1,5 @@
 syntax = "proto3";
+
 message UserToken {
     string token = 1;
 }

+ 0 - 12
rust-lib/flowy-user/src/services/helper.rs

@@ -1,12 +0,0 @@
-use std::future::Future;
-
-pub async fn spawn<F>(f: F)
-where
-    F: Future + Send + 'static,
-    F::Output: Send + 'static,
-{
-    match tokio::spawn(f).await {
-        Ok(_) => {},
-        Err(e) => log::error!("{:?}", e),
-    }
-}

+ 0 - 1
rust-lib/flowy-user/src/services/mod.rs

@@ -1,3 +1,2 @@
-mod helper;
 mod server;
 pub mod user;

+ 7 - 5
rust-lib/flowy-user/src/services/user/user_session.rs

@@ -18,8 +18,6 @@ use flowy_infra::kv::KV;
 use flowy_sqlite::ConnectionPool;
 use parking_lot::RwLock;
 use serde::{Deserialize, Serialize};
-
-use crate::services::helper::spawn;
 use std::sync::Arc;
 
 pub struct UserSessionConfig {
@@ -70,6 +68,7 @@ impl UserSession {
         self.database.get_pool(&user_id)
     }
 
+    #[tracing::instrument(level = "debug", skip(self))]
     pub async fn sign_in(&self, params: SignInParams) -> Result<UserProfile, UserError> {
         if self.is_login(&params.email) {
             self.user_profile().await
@@ -83,6 +82,7 @@ impl UserSession {
         }
     }
 
+    #[tracing::instrument(level = "debug", skip(self))]
     pub async fn sign_up(&self, params: SignUpParams) -> Result<UserProfile, UserError> {
         if self.is_login(&params.email) {
             self.user_profile().await
@@ -96,6 +96,7 @@ impl UserSession {
         }
     }
 
+    #[tracing::instrument(level = "debug", skip(self))]
     pub async fn sign_out(&self) -> Result<(), UserError> {
         let session = self.get_session()?;
         let _ = diesel::delete(dsl::user_table.filter(dsl::id.eq(&session.user_id))).execute(&*(self.db_conn()?))?;
@@ -106,6 +107,7 @@ impl UserSession {
         Ok(())
     }
 
+    #[tracing::instrument(level = "debug", skip(self))]
     pub async fn update_user(&self, params: UpdateUserParams) -> Result<(), UserError> {
         let session = self.get_session()?;
         let changeset = UserTableChangeset::new(params.clone());
@@ -140,7 +142,7 @@ impl UserSession {
     async fn read_user_profile_on_server(&self, token: &str) -> Result<(), UserError> {
         let server = self.server.clone();
         let token = token.to_owned();
-        let _ = spawn(async move {
+        let _ = tokio::spawn(async move {
             match server.get_user(&token).await {
                 Ok(profile) => {
                     //
@@ -158,7 +160,7 @@ impl UserSession {
     async fn update_user_on_server(&self, token: &str, params: UpdateUserParams) -> Result<(), UserError> {
         let server = self.server.clone();
         let token = token.to_owned();
-        let _ = spawn(async move {
+        let _ = tokio::spawn(async move {
             match server.update_user(&token, params).await {
                 Ok(_) => {},
                 Err(e) => {
@@ -174,7 +176,7 @@ impl UserSession {
     async fn sign_out_on_server(&self, token: &str) -> Result<(), UserError> {
         let server = self.server.clone();
         let token = token.to_owned();
-        let _ = spawn(async move {
+        let _ = tokio::spawn(async move {
             match server.sign_out(&token).await {
                 Ok(_) => {},
                 Err(e) => log::error!("Sign out failed: {:?}", e),

+ 13 - 18
rust-lib/flowy-workspace/src/observable/observable.rs

@@ -7,20 +7,21 @@ const OBSERVABLE_CATEGORY: &'static str = "Workspace";
 
 #[derive(ProtoBuf_Enum, Debug)]
 pub(crate) enum WorkspaceObservable {
-    Unknown             = 0,
+    Unknown              = 0,
 
-    UserCreateWorkspace = 10,
-    UserDeleteWorkspace = 11,
+    UserCreateWorkspace  = 10,
+    UserDeleteWorkspace  = 11,
 
-    WorkspaceUpdated    = 12,
-    WorkspaceCreateApp  = 13,
-    WorkspaceDeleteApp  = 14,
+    WorkspaceUpdated     = 12,
+    WorkspaceCreateApp   = 13,
+    WorkspaceDeleteApp   = 14,
+    WorkspaceListUpdated = 15,
 
-    AppUpdated          = 21,
-    AppCreateView       = 23,
-    AppDeleteView       = 24,
+    AppUpdated           = 21,
+    AppCreateView        = 23,
+    AppDeleteView        = 24,
 
-    ViewUpdated         = 31,
+    ViewUpdated          = 31,
 }
 
 impl std::default::Default for WorkspaceObservable {
@@ -58,11 +59,7 @@ impl ObservableSender {
     }
 
     pub(crate) fn send(self) {
-        log::trace!(
-            "Workspace observable id: {}, ty: {:?}",
-            self.subject_id,
-            self.ty
-        );
+        log::trace!("Workspace observable id: {}, ty: {:?}", self.subject_id, self.ty);
 
         let subject_payload = match self.payload {
             None => None,
@@ -82,9 +79,7 @@ impl ObservableSender {
     }
 }
 
-pub(crate) fn send_observable(id: &str, ty: WorkspaceObservable) {
-    ObservableSender::new(id, ty).send();
-}
+pub(crate) fn send_observable(id: &str, ty: WorkspaceObservable) { ObservableSender::new(id, ty).send(); }
 
 #[allow(dead_code)]
 pub(crate) fn send_observable_with_payload<T>(id: &str, ty: WorkspaceObservable, payload: T)

+ 32 - 27
rust-lib/flowy-workspace/src/protobuf/model/observable.rs

@@ -31,6 +31,7 @@ pub enum WorkspaceObservable {
     WorkspaceUpdated = 12,
     WorkspaceCreateApp = 13,
     WorkspaceDeleteApp = 14,
+    WorkspaceListUpdated = 15,
     AppUpdated = 21,
     AppCreateView = 23,
     AppDeleteView = 24,
@@ -50,6 +51,7 @@ impl ::protobuf::ProtobufEnum for WorkspaceObservable {
             12 => ::std::option::Option::Some(WorkspaceObservable::WorkspaceUpdated),
             13 => ::std::option::Option::Some(WorkspaceObservable::WorkspaceCreateApp),
             14 => ::std::option::Option::Some(WorkspaceObservable::WorkspaceDeleteApp),
+            15 => ::std::option::Option::Some(WorkspaceObservable::WorkspaceListUpdated),
             21 => ::std::option::Option::Some(WorkspaceObservable::AppUpdated),
             23 => ::std::option::Option::Some(WorkspaceObservable::AppCreateView),
             24 => ::std::option::Option::Some(WorkspaceObservable::AppDeleteView),
@@ -66,6 +68,7 @@ impl ::protobuf::ProtobufEnum for WorkspaceObservable {
             WorkspaceObservable::WorkspaceUpdated,
             WorkspaceObservable::WorkspaceCreateApp,
             WorkspaceObservable::WorkspaceDeleteApp,
+            WorkspaceObservable::WorkspaceListUpdated,
             WorkspaceObservable::AppUpdated,
             WorkspaceObservable::AppCreateView,
             WorkspaceObservable::AppDeleteView,
@@ -98,35 +101,37 @@ impl ::protobuf::reflect::ProtobufValue for WorkspaceObservable {
 }
 
 static file_descriptor_proto_data: &'static [u8] = b"\
-    \n\x10observable.proto*\xe1\x01\n\x13WorkspaceObservable\x12\x0b\n\x07Un\
+    \n\x10observable.proto*\xfb\x01\n\x13WorkspaceObservable\x12\x0b\n\x07Un\
     known\x10\0\x12\x17\n\x13UserCreateWorkspace\x10\n\x12\x17\n\x13UserDele\
     teWorkspace\x10\x0b\x12\x14\n\x10WorkspaceUpdated\x10\x0c\x12\x16\n\x12W\
-    orkspaceCreateApp\x10\r\x12\x16\n\x12WorkspaceDeleteApp\x10\x0e\x12\x0e\
-    \n\nAppUpdated\x10\x15\x12\x11\n\rAppCreateView\x10\x17\x12\x11\n\rAppDe\
-    leteView\x10\x18\x12\x0f\n\x0bViewUpdated\x10\x1fJ\xc4\x03\n\x06\x12\x04\
-    \0\0\r\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x05\0\x12\x04\x02\0\
-    \r\x01\n\n\n\x03\x05\0\x01\x12\x03\x02\x05\x18\n\x0b\n\x04\x05\0\x02\0\
-    \x12\x03\x03\x04\x10\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\x03\x04\x0b\n\
-    \x0c\n\x05\x05\0\x02\0\x02\x12\x03\x03\x0e\x0f\n\x0b\n\x04\x05\0\x02\x01\
-    \x12\x03\x04\x04\x1d\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x04\x04\x17\n\
-    \x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x04\x1a\x1c\n\x0b\n\x04\x05\0\x02\
-    \x02\x12\x03\x05\x04\x1d\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\x05\x04\
-    \x17\n\x0c\n\x05\x05\0\x02\x02\x02\x12\x03\x05\x1a\x1c\n\x0b\n\x04\x05\0\
-    \x02\x03\x12\x03\x06\x04\x1a\n\x0c\n\x05\x05\0\x02\x03\x01\x12\x03\x06\
-    \x04\x14\n\x0c\n\x05\x05\0\x02\x03\x02\x12\x03\x06\x17\x19\n\x0b\n\x04\
-    \x05\0\x02\x04\x12\x03\x07\x04\x1c\n\x0c\n\x05\x05\0\x02\x04\x01\x12\x03\
-    \x07\x04\x16\n\x0c\n\x05\x05\0\x02\x04\x02\x12\x03\x07\x19\x1b\n\x0b\n\
-    \x04\x05\0\x02\x05\x12\x03\x08\x04\x1c\n\x0c\n\x05\x05\0\x02\x05\x01\x12\
-    \x03\x08\x04\x16\n\x0c\n\x05\x05\0\x02\x05\x02\x12\x03\x08\x19\x1b\n\x0b\
-    \n\x04\x05\0\x02\x06\x12\x03\t\x04\x14\n\x0c\n\x05\x05\0\x02\x06\x01\x12\
-    \x03\t\x04\x0e\n\x0c\n\x05\x05\0\x02\x06\x02\x12\x03\t\x11\x13\n\x0b\n\
-    \x04\x05\0\x02\x07\x12\x03\n\x04\x17\n\x0c\n\x05\x05\0\x02\x07\x01\x12\
-    \x03\n\x04\x11\n\x0c\n\x05\x05\0\x02\x07\x02\x12\x03\n\x14\x16\n\x0b\n\
-    \x04\x05\0\x02\x08\x12\x03\x0b\x04\x17\n\x0c\n\x05\x05\0\x02\x08\x01\x12\
-    \x03\x0b\x04\x11\n\x0c\n\x05\x05\0\x02\x08\x02\x12\x03\x0b\x14\x16\n\x0b\
-    \n\x04\x05\0\x02\t\x12\x03\x0c\x04\x15\n\x0c\n\x05\x05\0\x02\t\x01\x12\
-    \x03\x0c\x04\x0f\n\x0c\n\x05\x05\0\x02\t\x02\x12\x03\x0c\x12\x14b\x06pro\
-    to3\
+    orkspaceCreateApp\x10\r\x12\x16\n\x12WorkspaceDeleteApp\x10\x0e\x12\x18\
+    \n\x14WorkspaceListUpdated\x10\x0f\x12\x0e\n\nAppUpdated\x10\x15\x12\x11\
+    \n\rAppCreateView\x10\x17\x12\x11\n\rAppDeleteView\x10\x18\x12\x0f\n\x0b\
+    ViewUpdated\x10\x1fJ\xed\x03\n\x06\x12\x04\0\0\x0e\x01\n\x08\n\x01\x0c\
+    \x12\x03\0\0\x12\n\n\n\x02\x05\0\x12\x04\x02\0\x0e\x01\n\n\n\x03\x05\0\
+    \x01\x12\x03\x02\x05\x18\n\x0b\n\x04\x05\0\x02\0\x12\x03\x03\x04\x10\n\
+    \x0c\n\x05\x05\0\x02\0\x01\x12\x03\x03\x04\x0b\n\x0c\n\x05\x05\0\x02\0\
+    \x02\x12\x03\x03\x0e\x0f\n\x0b\n\x04\x05\0\x02\x01\x12\x03\x04\x04\x1d\n\
+    \x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x04\x04\x17\n\x0c\n\x05\x05\0\x02\
+    \x01\x02\x12\x03\x04\x1a\x1c\n\x0b\n\x04\x05\0\x02\x02\x12\x03\x05\x04\
+    \x1d\n\x0c\n\x05\x05\0\x02\x02\x01\x12\x03\x05\x04\x17\n\x0c\n\x05\x05\0\
+    \x02\x02\x02\x12\x03\x05\x1a\x1c\n\x0b\n\x04\x05\0\x02\x03\x12\x03\x06\
+    \x04\x1a\n\x0c\n\x05\x05\0\x02\x03\x01\x12\x03\x06\x04\x14\n\x0c\n\x05\
+    \x05\0\x02\x03\x02\x12\x03\x06\x17\x19\n\x0b\n\x04\x05\0\x02\x04\x12\x03\
+    \x07\x04\x1c\n\x0c\n\x05\x05\0\x02\x04\x01\x12\x03\x07\x04\x16\n\x0c\n\
+    \x05\x05\0\x02\x04\x02\x12\x03\x07\x19\x1b\n\x0b\n\x04\x05\0\x02\x05\x12\
+    \x03\x08\x04\x1c\n\x0c\n\x05\x05\0\x02\x05\x01\x12\x03\x08\x04\x16\n\x0c\
+    \n\x05\x05\0\x02\x05\x02\x12\x03\x08\x19\x1b\n\x0b\n\x04\x05\0\x02\x06\
+    \x12\x03\t\x04\x1e\n\x0c\n\x05\x05\0\x02\x06\x01\x12\x03\t\x04\x18\n\x0c\
+    \n\x05\x05\0\x02\x06\x02\x12\x03\t\x1b\x1d\n\x0b\n\x04\x05\0\x02\x07\x12\
+    \x03\n\x04\x14\n\x0c\n\x05\x05\0\x02\x07\x01\x12\x03\n\x04\x0e\n\x0c\n\
+    \x05\x05\0\x02\x07\x02\x12\x03\n\x11\x13\n\x0b\n\x04\x05\0\x02\x08\x12\
+    \x03\x0b\x04\x17\n\x0c\n\x05\x05\0\x02\x08\x01\x12\x03\x0b\x04\x11\n\x0c\
+    \n\x05\x05\0\x02\x08\x02\x12\x03\x0b\x14\x16\n\x0b\n\x04\x05\0\x02\t\x12\
+    \x03\x0c\x04\x17\n\x0c\n\x05\x05\0\x02\t\x01\x12\x03\x0c\x04\x11\n\x0c\n\
+    \x05\x05\0\x02\t\x02\x12\x03\x0c\x14\x16\n\x0b\n\x04\x05\0\x02\n\x12\x03\
+    \r\x04\x15\n\x0c\n\x05\x05\0\x02\n\x01\x12\x03\r\x04\x0f\n\x0c\n\x05\x05\
+    \0\x02\n\x02\x12\x03\r\x12\x14b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 1 - 0
rust-lib/flowy-workspace/src/protobuf/proto/observable.proto

@@ -7,6 +7,7 @@ enum WorkspaceObservable {
     WorkspaceUpdated = 12;
     WorkspaceCreateApp = 13;
     WorkspaceDeleteApp = 14;
+    WorkspaceListUpdated = 15;
     AppUpdated = 21;
     AppCreateView = 23;
     AppDeleteView = 24;

+ 2 - 5
rust-lib/flowy-workspace/src/services/helper.rs

@@ -1,10 +1,7 @@
-pub async fn spawn<F>(f: F)
+pub fn spawn<F>(f: F)
 where
     F: std::future::Future + Send + 'static,
     F::Output: Send + 'static,
 {
-    match tokio::spawn(f).await {
-        Ok(_) => {},
-        Err(e) => log::error!("{:?}", e),
-    }
+    let _ = tokio::spawn(f);
 }

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

@@ -82,7 +82,7 @@ impl WorkspaceController {
 
     pub(crate) async fn read_workspaces(&self, params: QueryWorkspaceParams) -> Result<RepeatedWorkspace, WorkspaceError> {
         let user_id = self.user.user_id()?;
-        let workspace_tables = self.read_workspace_table(params.workspace_id.clone(), user_id)?;
+        let workspace_tables = self.read_workspace_table(params.workspace_id.clone(), user_id.clone())?;
         let mut workspaces = vec![];
         for table in workspace_tables {
             let apps = self.read_apps(&table.id).await?;
@@ -91,7 +91,7 @@ impl WorkspaceController {
             workspaces.push(workspace);
         }
 
-        let _ = self.read_workspaces_on_server(params).await?;
+        let _ = self.read_workspaces_on_server(user_id, params).await?;
         Ok(RepeatedWorkspace { items: workspaces })
     }
 
@@ -183,12 +183,13 @@ impl WorkspaceController {
     }
 
     #[tracing::instrument(skip(self), err)]
-    async fn read_workspaces_on_server(&self, params: QueryWorkspaceParams) -> Result<(), WorkspaceError> {
+    async fn read_workspaces_on_server(&self, user_id: String, params: QueryWorkspaceParams) -> Result<(), WorkspaceError> {
         let (token, server) = self.token_with_server()?;
         spawn(async move {
             match server.read_workspace(&token, params).await {
-                Ok(_workspaces) => {
-                    // TODO: notify
+                Ok(workspaces) => {
+                    log::debug!("Workspace list: {:?}", workspaces);
+                    send_observable(&user_id, WorkspaceObservable::UserCreateWorkspace);
                 },
                 Err(e) => {
                     // TODO: retry?
@@ -196,6 +197,7 @@ impl WorkspaceController {
                 },
             }
         });
+
         Ok(())
     }
 }