sign_up_screen.dart 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. import 'package:appflowy/startup/startup.dart';
  2. import 'package:appflowy/user/application/sign_up_bloc.dart';
  3. import 'package:appflowy/user/presentation/router.dart';
  4. import 'package:appflowy/user/presentation/widgets/background.dart';
  5. import 'package:easy_localization/easy_localization.dart';
  6. import 'package:flowy_infra_ui/style_widget/text.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:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
  11. import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
  12. show UserProfilePB;
  13. import 'package:flowy_infra_ui/style_widget/snap_bar.dart';
  14. import 'package:flutter/material.dart';
  15. import 'package:flutter_bloc/flutter_bloc.dart';
  16. import 'package:dartz/dartz.dart';
  17. import 'package:flowy_infra/image.dart';
  18. import 'package:appflowy/generated/locale_keys.g.dart';
  19. class SignUpScreen extends StatelessWidget {
  20. final AuthRouter router;
  21. const SignUpScreen({Key? key, required this.router}) : super(key: key);
  22. @override
  23. Widget build(BuildContext context) {
  24. return BlocProvider(
  25. create: (context) => getIt<SignUpBloc>(),
  26. child: BlocListener<SignUpBloc, SignUpState>(
  27. listener: (context, state) {
  28. state.successOrFail.fold(
  29. () => {},
  30. (result) => _handleSuccessOrFail(context, result),
  31. );
  32. },
  33. child: const Scaffold(body: SignUpForm()),
  34. ),
  35. );
  36. }
  37. void _handleSuccessOrFail(
  38. BuildContext context, Either<UserProfilePB, FlowyError> result) {
  39. result.fold(
  40. (user) => router.pushWelcomeScreen(context, user),
  41. (error) => showSnapBar(context, error.msg),
  42. );
  43. }
  44. }
  45. class SignUpForm extends StatelessWidget {
  46. const SignUpForm({
  47. Key? key,
  48. }) : super(key: key);
  49. @override
  50. Widget build(BuildContext context) {
  51. return Align(
  52. alignment: Alignment.center,
  53. child: AuthFormContainer(
  54. children: [
  55. FlowyLogoTitle(
  56. title: LocaleKeys.signUp_title.tr(),
  57. logoSize: const Size(60, 60),
  58. ),
  59. const VSpace(30),
  60. const EmailTextField(),
  61. const PasswordTextField(),
  62. const RepeatPasswordTextField(),
  63. const VSpace(30),
  64. const SignUpButton(),
  65. const VSpace(10),
  66. const SignUpPrompt(),
  67. if (context.read<SignUpBloc>().state.isSubmitting) ...[
  68. const SizedBox(height: 8),
  69. const LinearProgressIndicator(value: null),
  70. ]
  71. ],
  72. ),
  73. );
  74. }
  75. }
  76. class SignUpPrompt extends StatelessWidget {
  77. const SignUpPrompt({
  78. Key? key,
  79. }) : super(key: key);
  80. @override
  81. Widget build(BuildContext context) {
  82. return Row(
  83. mainAxisAlignment: MainAxisAlignment.center,
  84. children: [
  85. FlowyText.medium(
  86. LocaleKeys.signUp_alreadyHaveAnAccount.tr(),
  87. color: Theme.of(context).hintColor,
  88. ),
  89. TextButton(
  90. style: TextButton.styleFrom(
  91. textStyle: Theme.of(context).textTheme.bodyMedium),
  92. onPressed: () => Navigator.pop(context),
  93. child: FlowyText.medium(
  94. LocaleKeys.signIn_buttonText.tr(),
  95. color: Theme.of(context).colorScheme.primary,
  96. ),
  97. ),
  98. ],
  99. );
  100. }
  101. }
  102. class SignUpButton extends StatelessWidget {
  103. const SignUpButton({
  104. Key? key,
  105. }) : super(key: key);
  106. @override
  107. Widget build(BuildContext context) {
  108. return RoundedTextButton(
  109. title: LocaleKeys.signUp_getStartedText.tr(),
  110. height: 48,
  111. onPressed: () {
  112. context
  113. .read<SignUpBloc>()
  114. .add(const SignUpEvent.signUpWithUserEmailAndPassword());
  115. },
  116. );
  117. }
  118. }
  119. class PasswordTextField extends StatelessWidget {
  120. const PasswordTextField({
  121. Key? key,
  122. }) : super(key: key);
  123. @override
  124. Widget build(BuildContext context) {
  125. return BlocBuilder<SignUpBloc, SignUpState>(
  126. buildWhen: (previous, current) =>
  127. previous.passwordError != current.passwordError,
  128. builder: (context, state) {
  129. return RoundedInputField(
  130. obscureText: true,
  131. obscureIcon: svgWidget("home/hide"),
  132. obscureHideIcon: svgWidget("home/show"),
  133. hintText: LocaleKeys.signUp_passwordHint.tr(),
  134. normalBorderColor: Theme.of(context).colorScheme.outline,
  135. errorBorderColor: Theme.of(context).colorScheme.error,
  136. cursorColor: Theme.of(context).colorScheme.primary,
  137. errorText: context
  138. .read<SignUpBloc>()
  139. .state
  140. .passwordError
  141. .fold(() => "", (error) => error),
  142. onChanged: (value) => context
  143. .read<SignUpBloc>()
  144. .add(SignUpEvent.passwordChanged(value)),
  145. );
  146. },
  147. );
  148. }
  149. }
  150. class RepeatPasswordTextField extends StatelessWidget {
  151. const RepeatPasswordTextField({
  152. Key? key,
  153. }) : super(key: key);
  154. @override
  155. Widget build(BuildContext context) {
  156. return BlocBuilder<SignUpBloc, SignUpState>(
  157. buildWhen: (previous, current) =>
  158. previous.repeatPasswordError != current.repeatPasswordError,
  159. builder: (context, state) {
  160. return RoundedInputField(
  161. obscureText: true,
  162. obscureIcon: svgWidget("home/hide"),
  163. obscureHideIcon: svgWidget("home/show"),
  164. hintText: LocaleKeys.signUp_repeatPasswordHint.tr(),
  165. normalBorderColor: Theme.of(context).colorScheme.outline,
  166. errorBorderColor: Theme.of(context).colorScheme.error,
  167. cursorColor: Theme.of(context).colorScheme.primary,
  168. errorText: context
  169. .read<SignUpBloc>()
  170. .state
  171. .repeatPasswordError
  172. .fold(() => "", (error) => error),
  173. onChanged: (value) => context
  174. .read<SignUpBloc>()
  175. .add(SignUpEvent.repeatPasswordChanged(value)),
  176. );
  177. },
  178. );
  179. }
  180. }
  181. class EmailTextField extends StatelessWidget {
  182. const EmailTextField({
  183. Key? key,
  184. }) : super(key: key);
  185. @override
  186. Widget build(BuildContext context) {
  187. return BlocBuilder<SignUpBloc, SignUpState>(
  188. buildWhen: (previous, current) =>
  189. previous.emailError != current.emailError,
  190. builder: (context, state) {
  191. return RoundedInputField(
  192. hintText: LocaleKeys.signUp_emailHint.tr(),
  193. style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
  194. normalBorderColor: Theme.of(context).colorScheme.outline,
  195. errorBorderColor: Theme.of(context).colorScheme.error,
  196. cursorColor: Theme.of(context).colorScheme.primary,
  197. errorText: context
  198. .read<SignUpBloc>()
  199. .state
  200. .emailError
  201. .fold(() => "", (error) => error),
  202. onChanged: (value) =>
  203. context.read<SignUpBloc>().add(SignUpEvent.emailChanged(value)),
  204. );
  205. },
  206. );
  207. }
  208. }