sign_up_screen.dart 6.9 KB

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