Bladeren bron

test sign in

appflowy 3 jaren geleden
bovenliggende
commit
7d342df1f1

+ 3 - 0
.idea/appflowy_client.iml

@@ -78,6 +78,9 @@
       <excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/example/.pub" />
       <excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/example/.dart_tool" />
       <excludeFolder url="file://$MODULE_DIR$/app_flowy/macos/Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/example/build" />
+      <excludeFolder url="file://$MODULE_DIR$/app_flowy/packages/flowy_infra/.dart_tool" />
+      <excludeFolder url="file://$MODULE_DIR$/app_flowy/packages/flowy_infra/build" />
+      <excludeFolder url="file://$MODULE_DIR$/app_flowy/packages/flowy_infra/.pub" />
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />

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

@@ -1,5 +1,6 @@
 import 'package:app_flowy/startup/launch.dart';
 import 'package:app_flowy/startup/startup.dart';
+import 'package:app_flowy/user/infrastructure/interface_impl.dart';
 import 'package:app_flowy/welcome/infrastructure/interface_impl.dart';
 import 'package:flowy_sdk/flowy_sdk.dart';
 import 'package:get_it/get_it.dart';
@@ -13,5 +14,6 @@ Future<void> initGetIt(
   getIt.registerLazySingleton<FlowySDK>(() => const FlowySDK());
   getIt.registerLazySingleton<AppLauncher>(() => AppLauncher(env, getIt));
 
-  await Welcome.dependencyResolved(getIt);
+  await WelcomeDepsResolver.resolve(getIt);
+  await UserDepsResolver.resolve(getIt);
 }

+ 46 - 0
app_flowy/lib/user/application/sign_in/sign_in_bloc.dart

@@ -0,0 +1,46 @@
+import 'package:app_flowy/user/domain/interface.dart';
+import 'package:dartz/dartz.dart';
+import 'package:flowy_sdk/protobuf/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/user_detail.pb.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+// ignore: import_of_legacy_library_into_null_safe
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+part 'sign_in_event.dart';
+part 'sign_in_state.dart';
+part 'sign_in_bloc.freezed.dart';
+
+class SignInBloc extends Bloc<SignInEvent, SignInState> {
+  final IAuth authImpl;
+  SignInBloc(this.authImpl) : super(SignInState.initial());
+
+  @override
+  Stream<SignInState> mapEventToState(
+    SignInEvent event,
+  ) async* {
+    yield* event.map(
+      signedInWithUserEmailAndPassword: (e) async* {
+        yield* _performActionOnSignIn(
+          state,
+        );
+      },
+      emailChanged: (EmailChanged value) async* {
+        yield state.copyWith(email: value.email, signInFailure: none());
+      },
+      passwordChanged: (PasswordChanged value) async* {
+        yield state.copyWith(password: value.password, signInFailure: none());
+      },
+    );
+  }
+
+  Stream<SignInState> _performActionOnSignIn(SignInState state) async* {
+    yield state.copyWith(isSubmitting: true);
+
+    final result = await authImpl.signIn(state.email, state.password);
+    yield result.fold(
+      (userDetail) => state.copyWith(
+          isSubmitting: false, signInFailure: some(left(userDetail))),
+      (s) => state.copyWith(isSubmitting: false, signInFailure: some(right(s))),
+    );
+  }
+}

+ 642 - 0
app_flowy/lib/user/application/sign_in/sign_in_bloc.freezed.dart

@@ -0,0 +1,642 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides
+
+part of 'sign_in_bloc.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+T _$identity<T>(T value) => value;
+
+final _privateConstructorUsedError = UnsupportedError(
+    'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
+
+/// @nodoc
+class _$SignInEventTearOff {
+  const _$SignInEventTearOff();
+
+  SignedInWithUserEmailAndPassword signedInWithUserEmailAndPassword() {
+    return const SignedInWithUserEmailAndPassword();
+  }
+
+  EmailChanged emailChanged(String email) {
+    return EmailChanged(
+      email,
+    );
+  }
+
+  PasswordChanged passwordChanged(String password) {
+    return PasswordChanged(
+      password,
+    );
+  }
+}
+
+/// @nodoc
+const $SignInEvent = _$SignInEventTearOff();
+
+/// @nodoc
+mixin _$SignInEvent {
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function() signedInWithUserEmailAndPassword,
+    required TResult Function(String email) emailChanged,
+    required TResult Function(String password) passwordChanged,
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function()? signedInWithUserEmailAndPassword,
+    TResult Function(String email)? emailChanged,
+    TResult Function(String password)? passwordChanged,
+    required TResult orElse(),
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(SignedInWithUserEmailAndPassword value)
+        signedInWithUserEmailAndPassword,
+    required TResult Function(EmailChanged value) emailChanged,
+    required TResult Function(PasswordChanged value) passwordChanged,
+  }) =>
+      throw _privateConstructorUsedError;
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(SignedInWithUserEmailAndPassword value)?
+        signedInWithUserEmailAndPassword,
+    TResult Function(EmailChanged value)? emailChanged,
+    TResult Function(PasswordChanged value)? passwordChanged,
+    required TResult orElse(),
+  }) =>
+      throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $SignInEventCopyWith<$Res> {
+  factory $SignInEventCopyWith(
+          SignInEvent value, $Res Function(SignInEvent) then) =
+      _$SignInEventCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class _$SignInEventCopyWithImpl<$Res> implements $SignInEventCopyWith<$Res> {
+  _$SignInEventCopyWithImpl(this._value, this._then);
+
+  final SignInEvent _value;
+  // ignore: unused_field
+  final $Res Function(SignInEvent) _then;
+}
+
+/// @nodoc
+abstract class $SignedInWithUserEmailAndPasswordCopyWith<$Res> {
+  factory $SignedInWithUserEmailAndPasswordCopyWith(
+          SignedInWithUserEmailAndPassword value,
+          $Res Function(SignedInWithUserEmailAndPassword) then) =
+      _$SignedInWithUserEmailAndPasswordCopyWithImpl<$Res>;
+}
+
+/// @nodoc
+class _$SignedInWithUserEmailAndPasswordCopyWithImpl<$Res>
+    extends _$SignInEventCopyWithImpl<$Res>
+    implements $SignedInWithUserEmailAndPasswordCopyWith<$Res> {
+  _$SignedInWithUserEmailAndPasswordCopyWithImpl(
+      SignedInWithUserEmailAndPassword _value,
+      $Res Function(SignedInWithUserEmailAndPassword) _then)
+      : super(_value, (v) => _then(v as SignedInWithUserEmailAndPassword));
+
+  @override
+  SignedInWithUserEmailAndPassword get _value =>
+      super._value as SignedInWithUserEmailAndPassword;
+}
+
+/// @nodoc
+
+class _$SignedInWithUserEmailAndPassword
+    implements SignedInWithUserEmailAndPassword {
+  const _$SignedInWithUserEmailAndPassword();
+
+  @override
+  String toString() {
+    return 'SignInEvent.signedInWithUserEmailAndPassword()';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) ||
+        (other is SignedInWithUserEmailAndPassword);
+  }
+
+  @override
+  int get hashCode => runtimeType.hashCode;
+
+  @override
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function() signedInWithUserEmailAndPassword,
+    required TResult Function(String email) emailChanged,
+    required TResult Function(String password) passwordChanged,
+  }) {
+    return signedInWithUserEmailAndPassword();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function()? signedInWithUserEmailAndPassword,
+    TResult Function(String email)? emailChanged,
+    TResult Function(String password)? passwordChanged,
+    required TResult orElse(),
+  }) {
+    if (signedInWithUserEmailAndPassword != null) {
+      return signedInWithUserEmailAndPassword();
+    }
+    return orElse();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(SignedInWithUserEmailAndPassword value)
+        signedInWithUserEmailAndPassword,
+    required TResult Function(EmailChanged value) emailChanged,
+    required TResult Function(PasswordChanged value) passwordChanged,
+  }) {
+    return signedInWithUserEmailAndPassword(this);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(SignedInWithUserEmailAndPassword value)?
+        signedInWithUserEmailAndPassword,
+    TResult Function(EmailChanged value)? emailChanged,
+    TResult Function(PasswordChanged value)? passwordChanged,
+    required TResult orElse(),
+  }) {
+    if (signedInWithUserEmailAndPassword != null) {
+      return signedInWithUserEmailAndPassword(this);
+    }
+    return orElse();
+  }
+}
+
+abstract class SignedInWithUserEmailAndPassword implements SignInEvent {
+  const factory SignedInWithUserEmailAndPassword() =
+      _$SignedInWithUserEmailAndPassword;
+}
+
+/// @nodoc
+abstract class $EmailChangedCopyWith<$Res> {
+  factory $EmailChangedCopyWith(
+          EmailChanged value, $Res Function(EmailChanged) then) =
+      _$EmailChangedCopyWithImpl<$Res>;
+  $Res call({String email});
+}
+
+/// @nodoc
+class _$EmailChangedCopyWithImpl<$Res> extends _$SignInEventCopyWithImpl<$Res>
+    implements $EmailChangedCopyWith<$Res> {
+  _$EmailChangedCopyWithImpl(
+      EmailChanged _value, $Res Function(EmailChanged) _then)
+      : super(_value, (v) => _then(v as EmailChanged));
+
+  @override
+  EmailChanged get _value => super._value as EmailChanged;
+
+  @override
+  $Res call({
+    Object? email = freezed,
+  }) {
+    return _then(EmailChanged(
+      email == freezed
+          ? _value.email
+          : email // ignore: cast_nullable_to_non_nullable
+              as String,
+    ));
+  }
+}
+
+/// @nodoc
+
+class _$EmailChanged implements EmailChanged {
+  const _$EmailChanged(this.email);
+
+  @override
+  final String email;
+
+  @override
+  String toString() {
+    return 'SignInEvent.emailChanged(email: $email)';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) ||
+        (other is EmailChanged &&
+            (identical(other.email, email) ||
+                const DeepCollectionEquality().equals(other.email, email)));
+  }
+
+  @override
+  int get hashCode =>
+      runtimeType.hashCode ^ const DeepCollectionEquality().hash(email);
+
+  @JsonKey(ignore: true)
+  @override
+  $EmailChangedCopyWith<EmailChanged> get copyWith =>
+      _$EmailChangedCopyWithImpl<EmailChanged>(this, _$identity);
+
+  @override
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function() signedInWithUserEmailAndPassword,
+    required TResult Function(String email) emailChanged,
+    required TResult Function(String password) passwordChanged,
+  }) {
+    return emailChanged(email);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function()? signedInWithUserEmailAndPassword,
+    TResult Function(String email)? emailChanged,
+    TResult Function(String password)? passwordChanged,
+    required TResult orElse(),
+  }) {
+    if (emailChanged != null) {
+      return emailChanged(email);
+    }
+    return orElse();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(SignedInWithUserEmailAndPassword value)
+        signedInWithUserEmailAndPassword,
+    required TResult Function(EmailChanged value) emailChanged,
+    required TResult Function(PasswordChanged value) passwordChanged,
+  }) {
+    return emailChanged(this);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(SignedInWithUserEmailAndPassword value)?
+        signedInWithUserEmailAndPassword,
+    TResult Function(EmailChanged value)? emailChanged,
+    TResult Function(PasswordChanged value)? passwordChanged,
+    required TResult orElse(),
+  }) {
+    if (emailChanged != null) {
+      return emailChanged(this);
+    }
+    return orElse();
+  }
+}
+
+abstract class EmailChanged implements SignInEvent {
+  const factory EmailChanged(String email) = _$EmailChanged;
+
+  String get email => throw _privateConstructorUsedError;
+  @JsonKey(ignore: true)
+  $EmailChangedCopyWith<EmailChanged> get copyWith =>
+      throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $PasswordChangedCopyWith<$Res> {
+  factory $PasswordChangedCopyWith(
+          PasswordChanged value, $Res Function(PasswordChanged) then) =
+      _$PasswordChangedCopyWithImpl<$Res>;
+  $Res call({String password});
+}
+
+/// @nodoc
+class _$PasswordChangedCopyWithImpl<$Res>
+    extends _$SignInEventCopyWithImpl<$Res>
+    implements $PasswordChangedCopyWith<$Res> {
+  _$PasswordChangedCopyWithImpl(
+      PasswordChanged _value, $Res Function(PasswordChanged) _then)
+      : super(_value, (v) => _then(v as PasswordChanged));
+
+  @override
+  PasswordChanged get _value => super._value as PasswordChanged;
+
+  @override
+  $Res call({
+    Object? password = freezed,
+  }) {
+    return _then(PasswordChanged(
+      password == freezed
+          ? _value.password
+          : password // ignore: cast_nullable_to_non_nullable
+              as String,
+    ));
+  }
+}
+
+/// @nodoc
+
+class _$PasswordChanged implements PasswordChanged {
+  const _$PasswordChanged(this.password);
+
+  @override
+  final String password;
+
+  @override
+  String toString() {
+    return 'SignInEvent.passwordChanged(password: $password)';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) ||
+        (other is PasswordChanged &&
+            (identical(other.password, password) ||
+                const DeepCollectionEquality()
+                    .equals(other.password, password)));
+  }
+
+  @override
+  int get hashCode =>
+      runtimeType.hashCode ^ const DeepCollectionEquality().hash(password);
+
+  @JsonKey(ignore: true)
+  @override
+  $PasswordChangedCopyWith<PasswordChanged> get copyWith =>
+      _$PasswordChangedCopyWithImpl<PasswordChanged>(this, _$identity);
+
+  @override
+  @optionalTypeArgs
+  TResult when<TResult extends Object?>({
+    required TResult Function() signedInWithUserEmailAndPassword,
+    required TResult Function(String email) emailChanged,
+    required TResult Function(String password) passwordChanged,
+  }) {
+    return passwordChanged(password);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeWhen<TResult extends Object?>({
+    TResult Function()? signedInWithUserEmailAndPassword,
+    TResult Function(String email)? emailChanged,
+    TResult Function(String password)? passwordChanged,
+    required TResult orElse(),
+  }) {
+    if (passwordChanged != null) {
+      return passwordChanged(password);
+    }
+    return orElse();
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult map<TResult extends Object?>({
+    required TResult Function(SignedInWithUserEmailAndPassword value)
+        signedInWithUserEmailAndPassword,
+    required TResult Function(EmailChanged value) emailChanged,
+    required TResult Function(PasswordChanged value) passwordChanged,
+  }) {
+    return passwordChanged(this);
+  }
+
+  @override
+  @optionalTypeArgs
+  TResult maybeMap<TResult extends Object?>({
+    TResult Function(SignedInWithUserEmailAndPassword value)?
+        signedInWithUserEmailAndPassword,
+    TResult Function(EmailChanged value)? emailChanged,
+    TResult Function(PasswordChanged value)? passwordChanged,
+    required TResult orElse(),
+  }) {
+    if (passwordChanged != null) {
+      return passwordChanged(this);
+    }
+    return orElse();
+  }
+}
+
+abstract class PasswordChanged implements SignInEvent {
+  const factory PasswordChanged(String password) = _$PasswordChanged;
+
+  String get password => throw _privateConstructorUsedError;
+  @JsonKey(ignore: true)
+  $PasswordChangedCopyWith<PasswordChanged> get copyWith =>
+      throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+class _$SignInStateTearOff {
+  const _$SignInStateTearOff();
+
+  _SignInState call(
+      {String? email,
+      String? password,
+      required bool isSubmitting,
+      required Option<Either<UserDetail, UserError>> signInFailure}) {
+    return _SignInState(
+      email: email,
+      password: password,
+      isSubmitting: isSubmitting,
+      signInFailure: signInFailure,
+    );
+  }
+}
+
+/// @nodoc
+const $SignInState = _$SignInStateTearOff();
+
+/// @nodoc
+mixin _$SignInState {
+  String? get email => throw _privateConstructorUsedError;
+  String? get password => throw _privateConstructorUsedError;
+  bool get isSubmitting => throw _privateConstructorUsedError;
+  Option<Either<UserDetail, UserError>> get signInFailure =>
+      throw _privateConstructorUsedError;
+
+  @JsonKey(ignore: true)
+  $SignInStateCopyWith<SignInState> get copyWith =>
+      throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $SignInStateCopyWith<$Res> {
+  factory $SignInStateCopyWith(
+          SignInState value, $Res Function(SignInState) then) =
+      _$SignInStateCopyWithImpl<$Res>;
+  $Res call(
+      {String? email,
+      String? password,
+      bool isSubmitting,
+      Option<Either<UserDetail, UserError>> signInFailure});
+}
+
+/// @nodoc
+class _$SignInStateCopyWithImpl<$Res> implements $SignInStateCopyWith<$Res> {
+  _$SignInStateCopyWithImpl(this._value, this._then);
+
+  final SignInState _value;
+  // ignore: unused_field
+  final $Res Function(SignInState) _then;
+
+  @override
+  $Res call({
+    Object? email = freezed,
+    Object? password = freezed,
+    Object? isSubmitting = freezed,
+    Object? signInFailure = freezed,
+  }) {
+    return _then(_value.copyWith(
+      email: email == freezed
+          ? _value.email
+          : email // ignore: cast_nullable_to_non_nullable
+              as String?,
+      password: password == freezed
+          ? _value.password
+          : password // ignore: cast_nullable_to_non_nullable
+              as String?,
+      isSubmitting: isSubmitting == freezed
+          ? _value.isSubmitting
+          : isSubmitting // ignore: cast_nullable_to_non_nullable
+              as bool,
+      signInFailure: signInFailure == freezed
+          ? _value.signInFailure
+          : signInFailure // ignore: cast_nullable_to_non_nullable
+              as Option<Either<UserDetail, UserError>>,
+    ));
+  }
+}
+
+/// @nodoc
+abstract class _$SignInStateCopyWith<$Res>
+    implements $SignInStateCopyWith<$Res> {
+  factory _$SignInStateCopyWith(
+          _SignInState value, $Res Function(_SignInState) then) =
+      __$SignInStateCopyWithImpl<$Res>;
+  @override
+  $Res call(
+      {String? email,
+      String? password,
+      bool isSubmitting,
+      Option<Either<UserDetail, UserError>> signInFailure});
+}
+
+/// @nodoc
+class __$SignInStateCopyWithImpl<$Res> extends _$SignInStateCopyWithImpl<$Res>
+    implements _$SignInStateCopyWith<$Res> {
+  __$SignInStateCopyWithImpl(
+      _SignInState _value, $Res Function(_SignInState) _then)
+      : super(_value, (v) => _then(v as _SignInState));
+
+  @override
+  _SignInState get _value => super._value as _SignInState;
+
+  @override
+  $Res call({
+    Object? email = freezed,
+    Object? password = freezed,
+    Object? isSubmitting = freezed,
+    Object? signInFailure = freezed,
+  }) {
+    return _then(_SignInState(
+      email: email == freezed
+          ? _value.email
+          : email // ignore: cast_nullable_to_non_nullable
+              as String?,
+      password: password == freezed
+          ? _value.password
+          : password // ignore: cast_nullable_to_non_nullable
+              as String?,
+      isSubmitting: isSubmitting == freezed
+          ? _value.isSubmitting
+          : isSubmitting // ignore: cast_nullable_to_non_nullable
+              as bool,
+      signInFailure: signInFailure == freezed
+          ? _value.signInFailure
+          : signInFailure // ignore: cast_nullable_to_non_nullable
+              as Option<Either<UserDetail, UserError>>,
+    ));
+  }
+}
+
+/// @nodoc
+
+class _$_SignInState implements _SignInState {
+  const _$_SignInState(
+      {this.email,
+      this.password,
+      required this.isSubmitting,
+      required this.signInFailure});
+
+  @override
+  final String? email;
+  @override
+  final String? password;
+  @override
+  final bool isSubmitting;
+  @override
+  final Option<Either<UserDetail, UserError>> signInFailure;
+
+  @override
+  String toString() {
+    return 'SignInState(email: $email, password: $password, isSubmitting: $isSubmitting, signInFailure: $signInFailure)';
+  }
+
+  @override
+  bool operator ==(dynamic other) {
+    return identical(this, other) ||
+        (other is _SignInState &&
+            (identical(other.email, email) ||
+                const DeepCollectionEquality().equals(other.email, email)) &&
+            (identical(other.password, password) ||
+                const DeepCollectionEquality()
+                    .equals(other.password, password)) &&
+            (identical(other.isSubmitting, isSubmitting) ||
+                const DeepCollectionEquality()
+                    .equals(other.isSubmitting, isSubmitting)) &&
+            (identical(other.signInFailure, signInFailure) ||
+                const DeepCollectionEquality()
+                    .equals(other.signInFailure, signInFailure)));
+  }
+
+  @override
+  int get hashCode =>
+      runtimeType.hashCode ^
+      const DeepCollectionEquality().hash(email) ^
+      const DeepCollectionEquality().hash(password) ^
+      const DeepCollectionEquality().hash(isSubmitting) ^
+      const DeepCollectionEquality().hash(signInFailure);
+
+  @JsonKey(ignore: true)
+  @override
+  _$SignInStateCopyWith<_SignInState> get copyWith =>
+      __$SignInStateCopyWithImpl<_SignInState>(this, _$identity);
+}
+
+abstract class _SignInState implements SignInState {
+  const factory _SignInState(
+          {String? email,
+          String? password,
+          required bool isSubmitting,
+          required Option<Either<UserDetail, UserError>> signInFailure}) =
+      _$_SignInState;
+
+  @override
+  String? get email => throw _privateConstructorUsedError;
+  @override
+  String? get password => throw _privateConstructorUsedError;
+  @override
+  bool get isSubmitting => throw _privateConstructorUsedError;
+  @override
+  Option<Either<UserDetail, UserError>> get signInFailure =>
+      throw _privateConstructorUsedError;
+  @override
+  @JsonKey(ignore: true)
+  _$SignInStateCopyWith<_SignInState> get copyWith =>
+      throw _privateConstructorUsedError;
+}

+ 10 - 0
app_flowy/lib/user/application/sign_in/sign_in_event.dart

@@ -0,0 +1,10 @@
+part of 'sign_in_bloc.dart';
+
+@freezed
+abstract class SignInEvent with _$SignInEvent {
+  const factory SignInEvent.signedInWithUserEmailAndPassword() =
+      SignedInWithUserEmailAndPassword;
+
+  const factory SignInEvent.emailChanged(String email) = EmailChanged;
+  const factory SignInEvent.passwordChanged(String password) = PasswordChanged;
+}

+ 16 - 0
app_flowy/lib/user/application/sign_in/sign_in_state.dart

@@ -0,0 +1,16 @@
+part of 'sign_in_bloc.dart';
+
+@freezed
+abstract class SignInState with _$SignInState {
+  const factory SignInState({
+    String? email,
+    String? password,
+    required bool isSubmitting,
+    required Option<Either<UserDetail, UserError>> signInFailure,
+  }) = _SignInState;
+
+  factory SignInState.initial() => SignInState(
+        isSubmitting: false,
+        signInFailure: none(),
+      );
+}

+ 9 - 0
app_flowy/lib/user/domain/interface.dart

@@ -0,0 +1,9 @@
+import 'package:flowy_sdk/protobuf/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/user_detail.pb.dart';
+import 'package:dartz/dartz.dart';
+
+abstract class IAuth {
+  Future<Either<UserDetail, UserError>> signIn(String? email, String? password);
+  Future<Either<UserDetail, UserError>> signUp(
+      String? name, String? password, String? email);
+}

+ 30 - 0
app_flowy/lib/user/infrastructure/auth_repo.dart

@@ -0,0 +1,30 @@
+import 'package:dartz/dartz.dart';
+import 'package:flowy_sdk/dispatch/dispatch.dart';
+import 'package:flowy_sdk/protobuf/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/sign_in.pb.dart';
+import 'package:flowy_sdk/protobuf/sign_up.pb.dart';
+import 'package:flowy_sdk/protobuf/user_detail.pb.dart';
+
+class AuthRepository {
+  Future<Either<UserDetail, UserError>> signIn(
+      {required String? email, required String? password}) {
+    //
+    final request = SignInRequest.create()
+      ..email = email ?? ''
+      ..password = password ?? '';
+
+    return UserEventSignIn(request).send();
+  }
+
+  Future<Either<UserDetail, UserError>> signUp(
+      {required String? name,
+      required String? password,
+      required String? email}) {
+    final request = SignUpRequest.create()
+      ..email = email ?? ''
+      ..name = name ?? ''
+      ..password = password ?? '';
+
+    return UserEventSignUp(request).send();
+  }
+}

+ 39 - 0
app_flowy/lib/user/infrastructure/interface_impl.dart

@@ -0,0 +1,39 @@
+import 'package:app_flowy/user/application/sign_in/sign_in_bloc.dart';
+import 'package:dartz/dartz.dart';
+import 'package:flowy_sdk/protobuf/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/user_detail.pb.dart';
+import 'package:get_it/get_it.dart';
+
+import 'package:app_flowy/user/domain/interface.dart';
+import 'package:app_flowy/user/infrastructure/auth_repo.dart';
+
+class UserDepsResolver {
+  static Future<void> resolve(GetIt getIt) async {
+    getIt.registerLazySingleton<AuthRepository>(() => AuthRepository());
+
+    //Interface implementation
+    getIt.registerFactory<IAuth>(() => AuthImpl(repo: getIt<AuthRepository>()));
+
+    //Bloc
+    getIt.registerFactory<SignInBloc>(() => SignInBloc(getIt<IAuth>()));
+  }
+}
+
+class AuthImpl extends IAuth {
+  AuthRepository repo;
+  AuthImpl({
+    required this.repo,
+  });
+
+  @override
+  Future<Either<UserDetail, UserError>> signIn(
+      String? email, String? password) {
+    return repo.signIn(email: email, password: password);
+  }
+
+  @override
+  Future<Either<UserDetail, UserError>> signUp(
+      String? name, String? password, String? email) {
+    return repo.signUp(name: name, password: password, email: email);
+  }
+}

+ 19 - 0
app_flowy/lib/user/presentation/sign_in/sign_in_screen.dart

@@ -0,0 +1,19 @@
+import 'package:app_flowy/startup/startup.dart';
+import 'package:app_flowy/user/application/sign_in/sign_in_bloc.dart';
+import 'package:app_flowy/user/presentation/sign_in/widgets/body.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+class SignInScreen extends StatelessWidget {
+  const SignInScreen({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocProvider(
+      create: (context) => getIt<SignInBloc>(),
+      child: const Scaffold(
+        body: Body(),
+      ),
+    );
+  }
+}

+ 29 - 0
app_flowy/lib/user/presentation/sign_in/widgets/background.dart

@@ -0,0 +1,29 @@
+import 'package:flutter/material.dart';
+
+class SignInBackground extends StatelessWidget {
+  final Widget child;
+  const SignInBackground({
+    Key? key,
+    required this.child,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    var size = MediaQuery.of(context).size;
+    return SizedBox(
+        height: size.height,
+        width: double.infinity,
+        child: Stack(
+          alignment: Alignment.center,
+          children: [
+            Image(
+                fit: BoxFit.cover,
+                width: size.width,
+                height: size.height,
+                image: const AssetImage(
+                    'assets/images/appflowy_launch_splash.jpg')),
+            child,
+          ],
+        ));
+  }
+}

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

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

+ 4 - 7
app_flowy/lib/welcome/infrastructure/interface_impl.dart

@@ -3,6 +3,7 @@ import 'package:app_flowy/home/application/home_bloc.dart';
 import 'package:app_flowy/home/application/menu/menu_bloc.dart';
 import 'package:app_flowy/home/application/watcher/home_watcher_bloc.dart';
 import 'package:app_flowy/home/presentation/home_screen.dart';
+import 'package:app_flowy/user/presentation/sign_in/sign_in_screen.dart';
 import 'package:app_flowy/welcome/application/welcome_bloc.dart';
 import 'package:app_flowy/welcome/domain/auth_state.dart';
 import 'package:app_flowy/welcome/domain/interface.dart';
@@ -12,8 +13,8 @@ import 'package:flutter/material.dart';
 import 'package:flutter/widgets.dart';
 import 'package:get_it/get_it.dart';
 
-class Welcome {
-  static Future<void> dependencyResolved(GetIt getIt) async {
+class WelcomeDepsResolver {
+  static Future<void> resolve(GetIt getIt) async {
     getIt.registerFactory<IWelcomeAuth>(() => WelcomeAuthImpl());
     getIt.registerFactory<IWelcomeRoute>(() => WelcomeRoute());
     getIt.registerFactory<HomeBloc>(() => HomeBloc());
@@ -52,10 +53,6 @@ class WelcomeRoute implements IWelcomeRoute {
 
   @override
   Widget pushSignInScreen() {
-    return Container(
-      width: 100,
-      height: 100,
-      color: Colors.red,
-    );
+    return const SignInScreen();
   }
 }

+ 8 - 4
rust-lib/flowy-sdk/src/lib.rs

@@ -3,21 +3,25 @@ pub use module::*;
 
 use flowy_dispatch::prelude::*;
 use module::build_modules;
+use std::sync::atomic::{AtomicBool, Ordering};
 
+static INIT_LOG: AtomicBool = AtomicBool::new(false);
 pub struct FlowySDK {}
 
 impl FlowySDK {
-    pub fn init_log(directory: &str) { flowy_log::init_log("flowy", directory, "Debug").unwrap(); }
+    pub fn init_log(directory: &str) {
+        if !INIT_LOG.load(Ordering::SeqCst) {
+            INIT_LOG.store(true, Ordering::SeqCst);
+            flowy_log::init_log("flowy", directory, "Debug").unwrap();
+        }
+    }
 
     pub fn init(path: &str) {
         tracing::info!("🔥 Root path: {}", path);
-
         flowy_infra::kv::KVStore::init(path);
-
         let config = ModuleConfig {
             root: path.to_string(),
         };
-
         EventDispatch::construct(|| build_modules(config));
     }
 }