sign_in_screen.dart 6.3 KB

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