import 'package:appflowy/core/config/kv.dart'; import 'package:appflowy/core/config/kv_keys.dart'; import 'package:appflowy/core/frameless_window.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/application/historical_user_bloc.dart'; import 'package:appflowy/user/application/sign_in_bloc.dart'; import 'package:appflowy/user/presentation/router.dart'; import 'package:appflowy/user/presentation/widgets/background.dart'; import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/size.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/widget/rounded_button.dart'; import 'package:flowy_infra_ui/widget/rounded_input_field.dart'; import 'package:flowy_infra_ui/style_widget/snap_bar.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:dartz/dartz.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; class SignInScreen extends StatelessWidget { const SignInScreen({ super.key, required this.router, }); final AuthRouter router; @override Widget build(BuildContext context) { return BlocProvider( create: (context) => getIt(), child: BlocConsumer( listener: (context, state) { state.successOrFail.fold( () => null, (result) => _handleSuccessOrFail(result, context), ); }, builder: (_, __) => Scaffold( appBar: const PreferredSize( preferredSize: Size(double.infinity, 60), child: MoveWindowDetector(), ), body: SignInForm(router: router), ), ), ); } void _handleSuccessOrFail( Either result, BuildContext context, ) { result.fold( (user) { if (user.encryptionType == EncryptionTypePB.Symmetric) { router.pushEncryptionScreen(context, user); } else { router.pushHomeScreen(context, user); } }, (error) => showSnapBar(context, error.msg), ); } } class SignInForm extends StatelessWidget { const SignInForm({ super.key, required this.router, }); final AuthRouter router; @override Widget build(BuildContext context) { final isSubmitting = context.read().state.isSubmitting; const indicatorMinHeight = 4.0; return Align( alignment: Alignment.center, child: AuthFormContainer( children: [ // Email. FlowyLogoTitle( title: LocaleKeys.signIn_loginTitle.tr(), logoSize: const Size(60, 60), ), const VSpace(30), // Email and password. don't support yet. /* ...[ const EmailTextField(), const VSpace(5), const PasswordTextField(), const VSpace(20), const LoginButton(), const VSpace(10), const VSpace(10), SignUpPrompt(router: router), ], */ const SignInAsGuestButton(), // third-party sign in. const VSpace(20), const OrContinueWith(), const VSpace(10), const ThirdPartySignInButtons(), const VSpace(20), // loading status ...isSubmitting ? [ const VSpace(indicatorMinHeight), const LinearProgressIndicator( value: null, minHeight: indicatorMinHeight, ), ] : [ const VSpace(indicatorMinHeight * 2.0) ], // add the same space when there's no loading status. // ConstrainedBox( // constraints: const BoxConstraints(maxHeight: 140), // child: HistoricalUserList( // didOpenUser: () async { // await FlowyRunner.run( // FlowyApp(), // integrationEnv(), // ); // }, // ), // ), const VSpace(20), ], ), ); } } class SignUpPrompt extends StatelessWidget { const SignUpPrompt({ Key? key, required this.router, }) : super(key: key); final AuthRouter router; @override Widget build(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ FlowyText.medium( LocaleKeys.signIn_dontHaveAnAccount.tr(), color: Theme.of(context).hintColor, ), TextButton( style: TextButton.styleFrom( textStyle: Theme.of(context).textTheme.bodyMedium, ), onPressed: () => router.pushSignUpScreen(context), child: Text( LocaleKeys.signUp_buttonText.tr(), style: TextStyle(color: Theme.of(context).colorScheme.primary), ), ), ForgetPasswordButton(router: router), ], ); } } class LoginButton extends StatelessWidget { const LoginButton({ Key? key, }) : super(key: key); @override Widget build(BuildContext context) { return RoundedTextButton( title: LocaleKeys.signIn_loginButtonText.tr(), height: 48, borderRadius: Corners.s10Border, onPressed: () => context .read() .add(const SignInEvent.signedInWithUserEmailAndPassword()), ); } } class SignInAsGuestButton extends StatelessWidget { const SignInAsGuestButton({ Key? key, }) : super(key: key); @override Widget build(BuildContext context) { return BlocProvider( create: (context) => HistoricalUserBloc() ..add( const HistoricalUserEvent.initial(), ), child: BlocListener( listenWhen: (previous, current) => previous.openedHistoricalUser != current.openedHistoricalUser, listener: (context, state) async { await runAppFlowy(); }, child: BlocBuilder( builder: (context, state) { if (state.historicalUsers.isEmpty) { return RoundedTextButton( title: LocaleKeys.signIn_loginAsGuestButtonText.tr(), height: 48, borderRadius: Corners.s6Border, onPressed: () { getIt().set(KVKeys.loginType, 'local'); context .read() .add(const SignInEvent.signedInAsGuest()); }, ); } else { return RoundedTextButton( title: LocaleKeys.signIn_continueAnonymousUser.tr(), height: 48, borderRadius: Corners.s6Border, onPressed: () { final bloc = context.read(); final user = bloc.state.historicalUsers.first; bloc.add(HistoricalUserEvent.openHistoricalUser(user)); }, ); } }, ), ), ); } } class ForgetPasswordButton extends StatelessWidget { const ForgetPasswordButton({ Key? key, required this.router, }) : super(key: key); final AuthRouter router; @override Widget build(BuildContext context) { return TextButton( style: TextButton.styleFrom( textStyle: Theme.of(context).textTheme.bodyMedium, ), onPressed: () { throw UnimplementedError(); }, child: Text( LocaleKeys.signIn_forgotPassword.tr(), style: TextStyle(color: Theme.of(context).colorScheme.primary), ), ); } } class PasswordTextField extends StatelessWidget { const PasswordTextField({ Key? key, }) : super(key: key); @override Widget build(BuildContext context) { return BlocBuilder( buildWhen: (previous, current) => previous.passwordError != current.passwordError, builder: (context, state) { return RoundedInputField( obscureText: true, obscureIcon: const FlowySvg(FlowySvgs.hide_m), obscureHideIcon: const FlowySvg(FlowySvgs.show_m), hintText: LocaleKeys.signIn_passwordHint.tr(), errorText: context .read() .state .passwordError .fold(() => "", (error) => error), onChanged: (value) => context .read() .add(SignInEvent.passwordChanged(value)), ); }, ); } } class EmailTextField extends StatelessWidget { const EmailTextField({ Key? key, }) : super(key: key); @override Widget build(BuildContext context) { return BlocBuilder( buildWhen: (previous, current) => previous.emailError != current.emailError, builder: (context, state) { return RoundedInputField( hintText: LocaleKeys.signIn_emailHint.tr(), errorText: context .read() .state .emailError .fold(() => "", (error) => error), onChanged: (value) => context.read().add(SignInEvent.emailChanged(value)), ); }, ); } } class OrContinueWith extends StatelessWidget { const OrContinueWith({super.key}); @override Widget build(BuildContext context) { return const Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Flexible( child: Divider( color: Colors.white, height: 10, ), ), FlowyText.regular(' Or continue with '), Flexible( child: Divider( color: Colors.white, height: 10, ), ), ], ); } } class ThirdPartySignInButton extends StatelessWidget { const ThirdPartySignInButton({ Key? key, required this.icon, required this.onPressed, }) : super(key: key); final FlowySvgData icon; final VoidCallback onPressed; @override Widget build(BuildContext context) { return FlowyIconButton( height: 48, width: 48, iconPadding: const EdgeInsets.all(8.0), radius: Corners.s10Border, onPressed: onPressed, icon: FlowySvg( icon, blendMode: null, ), ); } } class ThirdPartySignInButtons extends StatelessWidget { final MainAxisAlignment mainAxisAlignment; const ThirdPartySignInButtons({ this.mainAxisAlignment = MainAxisAlignment.center, super.key, }); @override Widget build(BuildContext context) { return Row( mainAxisAlignment: mainAxisAlignment, children: [ ThirdPartySignInButton( icon: FlowySvgs.google_mark_xl, onPressed: () { getIt().set(KVKeys.loginType, 'supabase'); context.read().add( const SignInEvent.signedInWithOAuth('google'), ); }, ), // const SizedBox(width: 20), // ThirdPartySignInButton( // icon: 'login/github-mark', // onPressed: () { // getIt().set(KVKeys.loginType, 'supabase'); // context // .read() // .add(const SignInEvent.signedInWithOAuth('github')); // }, // ), // const SizedBox(width: 20), // ThirdPartySignInButton( // icon: 'login/discord-mark', // onPressed: () { // getIt().set(KVKeys.loginType, 'supabase'); // context // .read() // .add(const SignInEvent.signedInWithOAuth('discord')); // }, // ), ], ); } }