sign_in_screen.dart 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. import 'package:app_flowy/startup/startup.dart';
  2. import 'package:app_flowy/user/application/sign_in_bloc.dart';
  3. import 'package:app_flowy/user/domain/i_auth.dart';
  4. import 'package:app_flowy/user/presentation/widgets/background.dart';
  5. import 'package:flowy_infra/size.dart';
  6. import 'package:flowy_infra/theme.dart';
  7. import 'package:flowy_infra_ui/widget/rounded_button.dart';
  8. import 'package:flowy_infra_ui/widget/rounded_input_field.dart';
  9. import 'package:flowy_infra_ui/widget/spacing.dart';
  10. import 'package:flowy_infra_ui/style_widget/snap_bar.dart';
  11. import 'package:flowy_sdk/protobuf/flowy-user/errors.pb.dart';
  12. import 'package:flowy_sdk/protobuf/flowy-user-infra/protobuf.dart' show UserProfile;
  13. import 'package:flutter/material.dart';
  14. import 'package:flutter_bloc/flutter_bloc.dart';
  15. import 'package:dartz/dartz.dart';
  16. import 'package:flowy_infra/image.dart';
  17. class SignInScreen extends StatelessWidget {
  18. final IAuthRouter router;
  19. const SignInScreen({Key? key, required this.router}) : super(key: key);
  20. @override
  21. Widget build(BuildContext context) {
  22. return BlocProvider(
  23. create: (context) => getIt<SignInBloc>(),
  24. child: BlocListener<SignInBloc, SignInState>(
  25. listener: (context, state) {
  26. state.successOrFail.fold(
  27. () => null,
  28. (result) => _handleSuccessOrFail(result, context),
  29. );
  30. },
  31. child: Scaffold(
  32. body: SignInForm(router: router),
  33. ),
  34. ),
  35. );
  36. }
  37. void _handleSuccessOrFail(Either<UserProfile, UserError> result, BuildContext context) {
  38. result.fold(
  39. (user) => router.pushWelcomeScreen(context, user),
  40. (error) => showSnapBar(context, error.msg),
  41. );
  42. }
  43. }
  44. class SignInForm extends StatelessWidget {
  45. final IAuthRouter router;
  46. const SignInForm({
  47. Key? key,
  48. required this.router,
  49. }) : super(key: key);
  50. @override
  51. Widget build(BuildContext context) {
  52. return Align(
  53. alignment: Alignment.center,
  54. child: AuthFormContainer(
  55. children: [
  56. const FlowyLogoTitle(
  57. title: 'Login to Appflowy',
  58. logoSize: Size(60, 60),
  59. ),
  60. const VSpace(30),
  61. const EmailTextField(),
  62. const PasswordTextField(),
  63. ForgetPasswordButton(router: router),
  64. const VSpace(30),
  65. const LoginButton(),
  66. const VSpace(10),
  67. SignUpPrompt(router: router),
  68. if (context.read<SignInBloc>().state.isSubmitting) ...[
  69. const SizedBox(height: 8),
  70. const LinearProgressIndicator(value: null),
  71. ]
  72. ],
  73. ),
  74. );
  75. }
  76. }
  77. class SignUpPrompt extends StatelessWidget {
  78. const SignUpPrompt({
  79. Key? key,
  80. required this.router,
  81. }) : super(key: key);
  82. final IAuthRouter router;
  83. @override
  84. Widget build(BuildContext context) {
  85. final theme = context.watch<AppTheme>();
  86. return Row(
  87. children: [
  88. Text("Dont't have an account", style: TextStyle(color: theme.shader3, fontSize: 12)),
  89. TextButton(
  90. style: TextButton.styleFrom(
  91. textStyle: const TextStyle(fontSize: 12),
  92. ),
  93. onPressed: () => router.pushSignUpScreen(context),
  94. child: Text(
  95. 'Sign Up',
  96. style: TextStyle(color: theme.main1),
  97. ),
  98. ),
  99. ],
  100. mainAxisAlignment: MainAxisAlignment.center,
  101. );
  102. }
  103. }
  104. class LoginButton extends StatelessWidget {
  105. const LoginButton({
  106. Key? key,
  107. }) : super(key: key);
  108. @override
  109. Widget build(BuildContext context) {
  110. final theme = context.watch<AppTheme>();
  111. return RoundedTextButton(
  112. title: 'Login',
  113. height: 48,
  114. borderRadius: Corners.s10Border,
  115. color: theme.main1,
  116. onPressed: () {
  117. context.read<SignInBloc>().add(const SignInEvent.signedInWithUserEmailAndPassword());
  118. },
  119. );
  120. }
  121. }
  122. class ForgetPasswordButton extends StatelessWidget {
  123. const ForgetPasswordButton({
  124. Key? key,
  125. required this.router,
  126. }) : super(key: key);
  127. final IAuthRouter router;
  128. @override
  129. Widget build(BuildContext context) {
  130. final theme = context.watch<AppTheme>();
  131. return TextButton(
  132. style: TextButton.styleFrom(
  133. textStyle: const TextStyle(fontSize: 12),
  134. ),
  135. onPressed: () => router.pushForgetPasswordScreen(context),
  136. child: Text(
  137. 'Forgot Password?',
  138. style: TextStyle(color: theme.main1),
  139. ),
  140. );
  141. }
  142. }
  143. class PasswordTextField extends StatelessWidget {
  144. const PasswordTextField({
  145. Key? key,
  146. }) : super(key: key);
  147. @override
  148. Widget build(BuildContext context) {
  149. final theme = context.watch<AppTheme>();
  150. return BlocBuilder<SignInBloc, SignInState>(
  151. buildWhen: (previous, current) => previous.passwordError != current.passwordError,
  152. builder: (context, state) {
  153. return RoundedInputField(
  154. obscureText: true,
  155. style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
  156. obscureIcon: svg("home/hide"),
  157. obscureHideIcon: svg("home/show"),
  158. hintText: 'Password',
  159. normalBorderColor: theme.shader4,
  160. highlightBorderColor: theme.red,
  161. cursorColor: theme.main1,
  162. errorText: context.read<SignInBloc>().state.passwordError.fold(() => "", (error) => error),
  163. onChanged: (value) => context.read<SignInBloc>().add(SignInEvent.passwordChanged(value)),
  164. );
  165. },
  166. );
  167. }
  168. }
  169. class EmailTextField extends StatelessWidget {
  170. const EmailTextField({
  171. Key? key,
  172. }) : super(key: key);
  173. @override
  174. Widget build(BuildContext context) {
  175. final theme = context.watch<AppTheme>();
  176. return BlocBuilder<SignInBloc, SignInState>(
  177. buildWhen: (previous, current) => previous.emailError != current.emailError,
  178. builder: (context, state) {
  179. return RoundedInputField(
  180. hintText: 'Email',
  181. style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
  182. normalBorderColor: theme.shader4,
  183. highlightBorderColor: theme.red,
  184. cursorColor: theme.main1,
  185. errorText: context.read<SignInBloc>().state.emailError.fold(() => "", (error) => error),
  186. onChanged: (value) => context.read<SignInBloc>().add(SignInEvent.emailChanged(value)),
  187. );
  188. },
  189. );
  190. }
  191. }