mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-11-08 06:29:07 +00:00
fix: otp launch review issues (#7730)
* fix: add error text under text field * chore: update translation
This commit is contained in:
parent
2e295e6891
commit
8c4324ee9d
@ -67,13 +67,11 @@ extension CommonOperations on WidgetTester {
|
|||||||
} else {
|
} else {
|
||||||
// cloud version
|
// cloud version
|
||||||
final anonymousButton = find.byType(SignInAnonymousButtonV2);
|
final anonymousButton = find.byType(SignInAnonymousButtonV2);
|
||||||
await tapButton(anonymousButton);
|
await tapButton(anonymousButton, warnIfMissed: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Platform.isWindows) {
|
|
||||||
await pumpAndSettle(const Duration(milliseconds: 200));
|
await pumpAndSettle(const Duration(milliseconds: 200));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> tapContinousAnotherWay() async {
|
Future<void> tapContinousAnotherWay() async {
|
||||||
// local version
|
// local version
|
||||||
|
|||||||
@ -199,7 +199,9 @@ class SignInBloc extends Bloc<SignInEvent, SignInState> {
|
|||||||
|
|
||||||
emit(
|
emit(
|
||||||
result.fold(
|
result.fold(
|
||||||
(userProfile) => state.copyWith(isSubmitting: true),
|
(userProfile) => state.copyWith(
|
||||||
|
isSubmitting: false,
|
||||||
|
),
|
||||||
(error) => _stateFromCode(error),
|
(error) => _stateFromCode(error),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -282,8 +284,9 @@ class SignInBloc extends Bloc<SignInEvent, SignInState> {
|
|||||||
case ErrorCode.UserUnauthorized:
|
case ErrorCode.UserUnauthorized:
|
||||||
final errorMsg = error.msg;
|
final errorMsg = error.msg;
|
||||||
String msg = LocaleKeys.signIn_generalError.tr();
|
String msg = LocaleKeys.signIn_generalError.tr();
|
||||||
if (errorMsg.contains('rate limit')) {
|
if (errorMsg.contains('rate limit') ||
|
||||||
msg = LocaleKeys.signIn_limitRateError.tr();
|
errorMsg.contains('For security purposes')) {
|
||||||
|
msg = LocaleKeys.signIn_tooFrequentVerificationCodeRequest.tr();
|
||||||
} else if (errorMsg.contains('invalid')) {
|
} else if (errorMsg.contains('invalid')) {
|
||||||
msg = LocaleKeys.signIn_tokenHasExpiredOrInvalid.tr();
|
msg = LocaleKeys.signIn_tokenHasExpiredOrInvalid.tr();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,7 +23,6 @@ class DesktopSignInScreen extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = AppFlowyTheme.of(context);
|
final theme = AppFlowyTheme.of(context);
|
||||||
|
|
||||||
const indicatorMinHeight = 4.0;
|
|
||||||
return BlocBuilder<SignInBloc, SignInState>(
|
return BlocBuilder<SignInBloc, SignInState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@ -55,15 +54,6 @@ class DesktopSignInScreen extends StatelessWidget {
|
|||||||
// sign in agreement
|
// sign in agreement
|
||||||
const SignInAgreement(),
|
const SignInAgreement(),
|
||||||
|
|
||||||
// loading status
|
|
||||||
const VSpace(indicatorMinHeight),
|
|
||||||
state.isSubmitting
|
|
||||||
? const LinearProgressIndicator(
|
|
||||||
minHeight: indicatorMinHeight,
|
|
||||||
)
|
|
||||||
: const VSpace(indicatorMinHeight),
|
|
||||||
const VSpace(20),
|
|
||||||
|
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
|
|
||||||
// anonymous sign in and settings
|
// anonymous sign in and settings
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import 'package:appflowy/user/presentation/router.dart';
|
|||||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/desktop_sign_in_screen.dart';
|
import 'package:appflowy/user/presentation/screens/sign_in_screen/desktop_sign_in_screen.dart';
|
||||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/mobile_loading_screen.dart';
|
import 'package:appflowy/user/presentation/screens/sign_in_screen/mobile_loading_screen.dart';
|
||||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/mobile_sign_in_screen.dart';
|
import 'package:appflowy/user/presentation/screens/sign_in_screen/mobile_sign_in_screen.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:universal_platform/universal_platform.dart';
|
import 'package:universal_platform/universal_platform.dart';
|
||||||
@ -37,10 +38,17 @@ class SignInScreen extends StatelessWidget {
|
|||||||
void _showSignInError(BuildContext context, SignInState state) {
|
void _showSignInError(BuildContext context, SignInState state) {
|
||||||
final successOrFail = state.successOrFail;
|
final successOrFail = state.successOrFail;
|
||||||
if (successOrFail != null) {
|
if (successOrFail != null) {
|
||||||
handleUserProfileResult(
|
successOrFail.fold(
|
||||||
successOrFail,
|
(userProfile) {
|
||||||
context,
|
if (userProfile.encryptionType == EncryptionTypePB.Symmetric) {
|
||||||
getIt<AuthRouter>(),
|
getIt<AuthRouter>().pushEncryptionScreen(context, userProfile);
|
||||||
|
} else {
|
||||||
|
getIt<AuthRouter>().goHomeScreen(context, userProfile);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) {
|
||||||
|
handleOpenWorkspaceError(context, error);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,14 +2,12 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
|||||||
import 'package:appflowy/user/application/sign_in_bloc.dart';
|
import 'package:appflowy/user/application/sign_in_bloc.dart';
|
||||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_email.dart';
|
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_email.dart';
|
||||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_magic_link_or_passcode_page.dart';
|
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_magic_link_or_passcode_page.dart';
|
||||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
|
||||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:string_validator/string_validator.dart';
|
import 'package:string_validator/string_validator.dart';
|
||||||
import 'package:universal_platform/universal_platform.dart';
|
|
||||||
|
|
||||||
class ContinueWithEmailAndPassword extends StatefulWidget {
|
class ContinueWithEmailAndPassword extends StatefulWidget {
|
||||||
const ContinueWithEmailAndPassword({super.key});
|
const ContinueWithEmailAndPassword({super.key});
|
||||||
@ -23,6 +21,7 @@ class _ContinueWithEmailAndPasswordState
|
|||||||
extends State<ContinueWithEmailAndPassword> {
|
extends State<ContinueWithEmailAndPassword> {
|
||||||
final controller = TextEditingController();
|
final controller = TextEditingController();
|
||||||
final focusNode = FocusNode();
|
final focusNode = FocusNode();
|
||||||
|
final emailKey = GlobalKey<AFTextFieldState>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@ -36,28 +35,43 @@ class _ContinueWithEmailAndPasswordState
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = AppFlowyTheme.of(context);
|
final theme = AppFlowyTheme.of(context);
|
||||||
|
|
||||||
return Column(
|
return BlocListener<SignInBloc, SignInState>(
|
||||||
|
listener: (context, state) {
|
||||||
|
final successOrFail = state.successOrFail;
|
||||||
|
// only push the continue with magic link or passcode page if the magic link is sent successfully
|
||||||
|
if (successOrFail != null) {
|
||||||
|
successOrFail.fold(
|
||||||
|
(_) => emailKey.currentState?.clearError(),
|
||||||
|
(error) => emailKey.currentState?.syncError(
|
||||||
|
errorText: error.msg,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (successOrFail == null && !state.isSubmitting) {
|
||||||
|
_pushContinueWithMagicLinkOrPasscodePage(
|
||||||
|
context,
|
||||||
|
controller.text,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
AFTextField(
|
||||||
height: UniversalPlatform.isMobile ? 38.0 : 40.0,
|
key: emailKey,
|
||||||
child: AFTextField(
|
|
||||||
controller: controller,
|
controller: controller,
|
||||||
hintText: LocaleKeys.signIn_pleaseInputYourEmail.tr(),
|
hintText: LocaleKeys.signIn_pleaseInputYourEmail.tr(),
|
||||||
radius: 10,
|
radius: 10,
|
||||||
onSubmitted: (value) => _pushContinueWithMagicLinkOrPasscodePage(
|
onSubmitted: (value) => _signInWithEmail(
|
||||||
context,
|
context,
|
||||||
value,
|
value,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
VSpace(theme.spacing.l),
|
VSpace(theme.spacing.l),
|
||||||
ContinueWithEmail(
|
ContinueWithEmail(
|
||||||
onTap: () => _pushContinueWithMagicLinkOrPasscodePage(
|
onTap: () => _signInWithEmail(
|
||||||
context,
|
context,
|
||||||
controller.text,
|
controller.text,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Hide password sign in until we implement the reset password / forgot password
|
|
||||||
// VSpace(theme.spacing.l),
|
// VSpace(theme.spacing.l),
|
||||||
// ContinueWithPassword(
|
// ContinueWithPassword(
|
||||||
// onTap: () => _pushContinueWithPasswordPage(
|
// onTap: () => _pushContinueWithPasswordPage(
|
||||||
@ -66,25 +80,29 @@ class _ContinueWithEmailAndPasswordState
|
|||||||
// ),
|
// ),
|
||||||
// ),
|
// ),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _signInWithEmail(BuildContext context, String email) {
|
||||||
|
if (!isEmail(email)) {
|
||||||
|
emailKey.currentState?.syncError(
|
||||||
|
errorText: LocaleKeys.signIn_invalidEmail.tr(),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context
|
||||||
|
.read<SignInBloc>()
|
||||||
|
.add(SignInEvent.signInWithMagicLink(email: email));
|
||||||
|
}
|
||||||
|
|
||||||
void _pushContinueWithMagicLinkOrPasscodePage(
|
void _pushContinueWithMagicLinkOrPasscodePage(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
String email,
|
String email,
|
||||||
) {
|
) {
|
||||||
if (!isEmail(email)) {
|
|
||||||
showToastNotification(
|
|
||||||
message: LocaleKeys.signIn_invalidEmail.tr(),
|
|
||||||
type: ToastificationType.error,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final signInBloc = context.read<SignInBloc>();
|
final signInBloc = context.read<SignInBloc>();
|
||||||
|
|
||||||
signInBloc.add(SignInEvent.signInWithMagicLink(email: email));
|
|
||||||
|
|
||||||
// push the a continue with magic link or passcode screen
|
// push the a continue with magic link or passcode screen
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
@ -114,7 +132,9 @@ class _ContinueWithEmailAndPasswordState
|
|||||||
// Navigator.push(
|
// Navigator.push(
|
||||||
// context,
|
// context,
|
||||||
// MaterialPageRoute(
|
// MaterialPageRoute(
|
||||||
// builder: (context) => ContinueWithPasswordPage(
|
// builder: (context) => BlocProvider.value(
|
||||||
|
// value: signInBloc,
|
||||||
|
// child: ContinueWithPasswordPage(
|
||||||
// email: email,
|
// email: email,
|
||||||
// backToLogin: () => Navigator.pop(context),
|
// backToLogin: () => Navigator.pop(context),
|
||||||
// onEnterPassword: (password) => signInBloc.add(
|
// onEnterPassword: (password) => signInBloc.add(
|
||||||
@ -128,6 +148,7 @@ class _ContinueWithEmailAndPasswordState
|
|||||||
// },
|
// },
|
||||||
// ),
|
// ),
|
||||||
// ),
|
// ),
|
||||||
|
// ),
|
||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,6 +33,8 @@ class _ContinueWithMagicLinkOrPasscodePageState
|
|||||||
|
|
||||||
ToastificationItem? toastificationItem;
|
ToastificationItem? toastificationItem;
|
||||||
|
|
||||||
|
final inputPasscodeKey = GlobalKey<AFTextFieldState>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
passcodeController.dispose();
|
passcodeController.dispose();
|
||||||
@ -44,10 +46,13 @@ class _ContinueWithMagicLinkOrPasscodePageState
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocListener<SignInBloc, SignInState>(
|
return BlocListener<SignInBloc, SignInState>(
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
if (state.isSubmitting) {
|
final successOrFail = state.successOrFail;
|
||||||
_showLoadingDialog();
|
if (successOrFail != null && successOrFail.isFailure) {
|
||||||
} else {
|
successOrFail.onFailure((error) {
|
||||||
_dismissLoadingDialog();
|
inputPasscodeKey.currentState?.syncError(
|
||||||
|
errorText: LocaleKeys.signIn_tokenHasExpiredOrInvalid.tr(),
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
@ -91,24 +96,39 @@ class _ContinueWithMagicLinkOrPasscodePageState
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
// Enter code manually
|
// Enter code manually
|
||||||
SizedBox(
|
AFTextField(
|
||||||
height: 40,
|
key: inputPasscodeKey,
|
||||||
child: AFTextField(
|
|
||||||
controller: passcodeController,
|
controller: passcodeController,
|
||||||
hintText: LocaleKeys.signIn_enterCode.tr(),
|
hintText: LocaleKeys.signIn_enterCode.tr(),
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
radius: 10,
|
radius: 10,
|
||||||
autoFocus: true,
|
autoFocus: true,
|
||||||
onSubmitted: widget.onEnterPasscode,
|
onSubmitted: (passcode) {
|
||||||
),
|
if (passcode.isEmpty) {
|
||||||
|
inputPasscodeKey.currentState?.syncError(
|
||||||
|
errorText: LocaleKeys.signIn_invalidVerificationCode.tr(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
widget.onEnterPasscode(passcode);
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
// todo: ask designer to provide the spacing
|
// todo: ask designer to provide the spacing
|
||||||
VSpace(12),
|
VSpace(12),
|
||||||
|
|
||||||
// continue to login
|
// continue to login
|
||||||
AFFilledTextButton.primary(
|
AFFilledTextButton.primary(
|
||||||
text: 'Continue to sign in',
|
text: LocaleKeys.signIn_continueToSignIn.tr(),
|
||||||
onTap: () => widget.onEnterPasscode(passcodeController.text),
|
onTap: () {
|
||||||
|
final passcode = passcodeController.text;
|
||||||
|
if (passcode.isEmpty) {
|
||||||
|
inputPasscodeKey.currentState?.syncError(
|
||||||
|
errorText: LocaleKeys.signIn_invalidVerificationCode.tr(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
widget.onEnterPasscode(passcode);
|
||||||
|
}
|
||||||
|
},
|
||||||
size: AFButtonSize.l,
|
size: AFButtonSize.l,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
),
|
),
|
||||||
@ -123,6 +143,7 @@ class _ContinueWithMagicLinkOrPasscodePageState
|
|||||||
text: LocaleKeys.signIn_backToLogin.tr(),
|
text: LocaleKeys.signIn_backToLogin.tr(),
|
||||||
size: AFButtonSize.s,
|
size: AFButtonSize.s,
|
||||||
onTap: widget.backToLogin,
|
onTap: widget.backToLogin,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
textColor: (context, isHovering, disabled) {
|
textColor: (context, isHovering, disabled) {
|
||||||
final theme = AppFlowyTheme.of(context);
|
final theme = AppFlowyTheme.of(context);
|
||||||
if (isHovering) {
|
if (isHovering) {
|
||||||
@ -137,6 +158,7 @@ class _ContinueWithMagicLinkOrPasscodePageState
|
|||||||
List<Widget> _buildLogoTitleAndDescription() {
|
List<Widget> _buildLogoTitleAndDescription() {
|
||||||
final theme = AppFlowyTheme.of(context);
|
final theme = AppFlowyTheme.of(context);
|
||||||
final spacing = VSpace(theme.spacing.xxl);
|
final spacing = VSpace(theme.spacing.xxl);
|
||||||
|
if (!isEnteringPasscode) {
|
||||||
return [
|
return [
|
||||||
// logo
|
// logo
|
||||||
const AFLogo(),
|
const AFLogo(),
|
||||||
@ -153,7 +175,39 @@ class _ContinueWithMagicLinkOrPasscodePageState
|
|||||||
|
|
||||||
// description
|
// description
|
||||||
Text(
|
Text(
|
||||||
LocaleKeys.signIn_temporaryVerificationSent.tr(),
|
LocaleKeys.signIn_temporaryVerificationLinkSent.tr(),
|
||||||
|
style: theme.textStyle.body.standard(
|
||||||
|
color: theme.textColorScheme.primary,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
widget.email,
|
||||||
|
style: theme.textStyle.body.enhanced(
|
||||||
|
color: theme.textColorScheme.primary,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
spacing,
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
// logo
|
||||||
|
const AFLogo(),
|
||||||
|
spacing,
|
||||||
|
|
||||||
|
// title
|
||||||
|
Text(
|
||||||
|
LocaleKeys.signIn_enterCode.tr(),
|
||||||
|
style: theme.textStyle.heading.h3(
|
||||||
|
color: theme.textColorScheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
spacing,
|
||||||
|
|
||||||
|
// description
|
||||||
|
Text(
|
||||||
|
LocaleKeys.signIn_temporaryVerificationCodeSent.tr(),
|
||||||
style: theme.textStyle.body.standard(
|
style: theme.textStyle.body.standard(
|
||||||
color: theme.textColorScheme.primary,
|
color: theme.textColorScheme.primary,
|
||||||
),
|
),
|
||||||
@ -169,19 +223,5 @@ class _ContinueWithMagicLinkOrPasscodePageState
|
|||||||
spacing,
|
spacing,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showLoadingDialog() {
|
|
||||||
_dismissLoadingDialog();
|
|
||||||
|
|
||||||
toastificationItem = showToastNotification(
|
|
||||||
message: LocaleKeys.signIn_signingIn.tr(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _dismissLoadingDialog() {
|
|
||||||
final toastificationItem = this.toastificationItem;
|
|
||||||
if (toastificationItem != null) {
|
|
||||||
toastification.dismiss(toastificationItem);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
|
import 'package:appflowy/user/application/sign_in_bloc.dart';
|
||||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/logo/logo.dart';
|
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/logo/logo.dart';
|
||||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class ContinueWithPasswordPage extends StatefulWidget {
|
class ContinueWithPasswordPage extends StatefulWidget {
|
||||||
const ContinueWithPasswordPage({
|
const ContinueWithPasswordPage({
|
||||||
@ -25,6 +27,7 @@ class ContinueWithPasswordPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _ContinueWithPasswordPageState extends State<ContinueWithPasswordPage> {
|
class _ContinueWithPasswordPageState extends State<ContinueWithPasswordPage> {
|
||||||
final passwordController = TextEditingController();
|
final passwordController = TextEditingController();
|
||||||
|
final inputPasswordKey = GlobalKey<AFTextFieldState>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@ -38,6 +41,16 @@ class _ContinueWithPasswordPageState extends State<ContinueWithPasswordPage> {
|
|||||||
body: Center(
|
body: Center(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 320,
|
width: 320,
|
||||||
|
child: BlocListener<SignInBloc, SignInState>(
|
||||||
|
listener: (context, state) {
|
||||||
|
if (state.passwordError != null) {
|
||||||
|
inputPasswordKey.currentState?.syncError(
|
||||||
|
errorText: 'Incorrect password. Please try again.',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
inputPasswordKey.currentState?.clearError();
|
||||||
|
}
|
||||||
|
},
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
@ -53,6 +66,7 @@ class _ContinueWithPasswordPageState extends State<ContinueWithPasswordPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,25 +114,33 @@ class _ContinueWithPasswordPageState extends State<ContinueWithPasswordPage> {
|
|||||||
return [
|
return [
|
||||||
// Password input
|
// Password input
|
||||||
AFTextField(
|
AFTextField(
|
||||||
|
key: inputPasswordKey,
|
||||||
controller: passwordController,
|
controller: passwordController,
|
||||||
hintText: 'Enter password',
|
hintText: 'Enter password',
|
||||||
autoFocus: true,
|
autoFocus: true,
|
||||||
onSubmitted: widget.onEnterPassword,
|
onSubmitted: widget.onEnterPassword,
|
||||||
),
|
),
|
||||||
// todo: ask designer to provide the spacing
|
// todo: ask designer to provide the spacing
|
||||||
VSpace(12),
|
VSpace(8),
|
||||||
|
|
||||||
// todo: forgot password is not implemented yet
|
|
||||||
// Forgot password button
|
// Forgot password button
|
||||||
// AFGhostTextButton(
|
Align(
|
||||||
// text: 'Forget password?',
|
alignment: Alignment.centerLeft,
|
||||||
// size: AFButtonSize.s,
|
child: AFGhostTextButton(
|
||||||
// onTap: widget.onForgotPassword,
|
text: 'Forget password?',
|
||||||
// textColor: (context, isHovering, disabled) {
|
size: AFButtonSize.s,
|
||||||
// return theme.textColorScheme.theme;
|
padding: EdgeInsets.zero,
|
||||||
// },
|
onTap: widget.onForgotPassword,
|
||||||
// ),
|
textColor: (context, isHovering, disabled) {
|
||||||
VSpace(12),
|
final theme = AppFlowyTheme.of(context);
|
||||||
|
if (isHovering) {
|
||||||
|
return theme.fillColorScheme.themeThickHover;
|
||||||
|
}
|
||||||
|
return theme.textColorScheme.theme;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
VSpace(20),
|
||||||
|
|
||||||
// Continue button
|
// Continue button
|
||||||
AFFilledTextButton.primary(
|
AFFilledTextButton.primary(
|
||||||
@ -137,8 +159,12 @@ class _ContinueWithPasswordPageState extends State<ContinueWithPasswordPage> {
|
|||||||
text: 'Back to Login',
|
text: 'Back to Login',
|
||||||
size: AFButtonSize.s,
|
size: AFButtonSize.s,
|
||||||
onTap: widget.backToLogin,
|
onTap: widget.backToLogin,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
textColor: (context, isHovering, disabled) {
|
textColor: (context, isHovering, disabled) {
|
||||||
final theme = AppFlowyTheme.of(context);
|
final theme = AppFlowyTheme.of(context);
|
||||||
|
if (isHovering) {
|
||||||
|
return theme.fillColorScheme.themeThickHover;
|
||||||
|
}
|
||||||
return theme.textColorScheme.theme;
|
return theme.textColorScheme.theme;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@ -26,8 +26,8 @@ class SignInAgreement extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: isLocalAuthEnabled
|
text: isLocalAuthEnabled
|
||||||
? '${LocaleKeys.web_signInLocalAgreement.tr()} \n'
|
? LocaleKeys.web_signInLocalAgreement.tr()
|
||||||
: '${LocaleKeys.web_signInAgreement.tr()} \n',
|
: LocaleKeys.web_signInAgreement.tr(),
|
||||||
style: textStyle,
|
style: textStyle,
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
|
|||||||
@ -101,6 +101,7 @@ class _DesktopThirdPartySignInState extends State<_DesktopThirdPartySignIn> {
|
|||||||
VSpace(theme.spacing.l),
|
VSpace(theme.spacing.l),
|
||||||
AFGhostTextButton(
|
AFGhostTextButton(
|
||||||
text: 'More options',
|
text: 'More options',
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
textColor: (context, isHovering, disabled) {
|
textColor: (context, isHovering, disabled) {
|
||||||
if (isHovering) {
|
if (isHovering) {
|
||||||
return theme.fillColorScheme.themeThickHover;
|
return theme.fillColorScheme.themeThickHover;
|
||||||
|
|||||||
@ -5,6 +5,11 @@ typedef AFTextFieldValidator = (bool result, String errorText) Function(
|
|||||||
TextEditingController controller,
|
TextEditingController controller,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
abstract class AFTextFieldState extends State<AFTextField> {
|
||||||
|
void syncError({required String errorText}) {}
|
||||||
|
void clearError() {}
|
||||||
|
}
|
||||||
|
|
||||||
class AFTextField extends StatefulWidget {
|
class AFTextField extends StatefulWidget {
|
||||||
const AFTextField({
|
const AFTextField({
|
||||||
super.key,
|
super.key,
|
||||||
@ -17,8 +22,12 @@ class AFTextField extends StatefulWidget {
|
|||||||
this.onChanged,
|
this.onChanged,
|
||||||
this.onSubmitted,
|
this.onSubmitted,
|
||||||
this.autoFocus,
|
this.autoFocus,
|
||||||
|
this.height = 40.0,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// The height of the text field.
|
||||||
|
final double height;
|
||||||
|
|
||||||
/// The hint text to display when the text field is empty.
|
/// The hint text to display when the text field is empty.
|
||||||
final String? hintText;
|
final String? hintText;
|
||||||
|
|
||||||
@ -52,7 +61,7 @@ class AFTextField extends StatefulWidget {
|
|||||||
State<AFTextField> createState() => _AFTextFieldState();
|
State<AFTextField> createState() => _AFTextFieldState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AFTextFieldState extends State<AFTextField> {
|
class _AFTextFieldState extends AFTextFieldState {
|
||||||
late final TextEditingController effectiveController;
|
late final TextEditingController effectiveController;
|
||||||
|
|
||||||
bool hasError = false;
|
bool hasError = false;
|
||||||
@ -146,8 +155,11 @@ class _AFTextFieldState extends State<AFTextField> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
child = SizedBox(height: widget.height, child: child);
|
||||||
|
|
||||||
if (hasError && errorText.isNotEmpty) {
|
if (hasError && errorText.isNotEmpty) {
|
||||||
child = Column(
|
child = Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
child,
|
child,
|
||||||
SizedBox(height: theme.spacing.xs),
|
SizedBox(height: theme.spacing.xs),
|
||||||
@ -174,4 +186,22 @@ class _AFTextFieldState extends State<AFTextField> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void syncError({
|
||||||
|
required String errorText,
|
||||||
|
}) {
|
||||||
|
setState(() {
|
||||||
|
hasError = true;
|
||||||
|
this.errorText = errorText;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void clearError() {
|
||||||
|
setState(() {
|
||||||
|
hasError = false;
|
||||||
|
errorText = '';
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,7 +48,7 @@
|
|||||||
"repeatPasswordEmptyError": "Repeat password can't be empty",
|
"repeatPasswordEmptyError": "Repeat password can't be empty",
|
||||||
"unmatchedPasswordError": "Repeat password is not the same as password",
|
"unmatchedPasswordError": "Repeat password is not the same as password",
|
||||||
"syncPromptMessage": "Syncing the data might take a while. Please don't close this page",
|
"syncPromptMessage": "Syncing the data might take a while. Please don't close this page",
|
||||||
"or": "OR",
|
"or": "or",
|
||||||
"signInWithGoogle": "Continue with Google",
|
"signInWithGoogle": "Continue with Google",
|
||||||
"signInWithGithub": "Continue with GitHub",
|
"signInWithGithub": "Continue with GitHub",
|
||||||
"signInWithDiscord": "Continue with Discord",
|
"signInWithDiscord": "Continue with Discord",
|
||||||
@ -70,15 +70,18 @@
|
|||||||
"generalError": "Something went wrong. Please try again later",
|
"generalError": "Something went wrong. Please try again later",
|
||||||
"limitRateError": "For security reasons, you can only request a magic link every 60 seconds",
|
"limitRateError": "For security reasons, you can only request a magic link every 60 seconds",
|
||||||
"magicLinkSentDescription": "A Magic Link was sent to your email. Click the link to complete your login. The link will expire after 5 minutes.",
|
"magicLinkSentDescription": "A Magic Link was sent to your email. Click the link to complete your login. The link will expire after 5 minutes.",
|
||||||
"tokenHasExpiredOrInvalid": "The token has expired or is invalid. Please try again.",
|
"tokenHasExpiredOrInvalid": "The code has expired or is invalid. Please try again.",
|
||||||
"signingIn": "Signing in...",
|
"signingIn": "Signing in...",
|
||||||
"checkYourEmail": "Check your email",
|
"checkYourEmail": "Check your email",
|
||||||
"temporaryVerificationSent": "A temporary verification link has been sent. Please check your inbox at",
|
"temporaryVerificationLinkSent": "A temporary verification link has been sent.\nPlease check your inbox at",
|
||||||
|
"temporaryVerificationCodeSent": "A temporary verification code has been sent.\nPlease check your inbox at",
|
||||||
"continueToSignIn": "Continue to sign in",
|
"continueToSignIn": "Continue to sign in",
|
||||||
"backToLogin": "Back to login",
|
"backToLogin": "Back to login",
|
||||||
"enterCode": "Enter code",
|
"enterCode": "Enter code",
|
||||||
"enterCodeManually": "Enter code manually",
|
"enterCodeManually": "Enter code manually",
|
||||||
"continueWithEmail": "Continue with email"
|
"continueWithEmail": "Continue with email",
|
||||||
|
"invalidVerificationCode": "Please enter a valid verification code",
|
||||||
|
"tooFrequentVerificationCodeRequest": "You have made too many requests. Please try again later."
|
||||||
},
|
},
|
||||||
"workspace": {
|
"workspace": {
|
||||||
"chooseWorkspace": "Choose your workspace",
|
"chooseWorkspace": "Choose your workspace",
|
||||||
@ -2814,8 +2817,8 @@
|
|||||||
"continueWithApple": "Continue with Apple ",
|
"continueWithApple": "Continue with Apple ",
|
||||||
"moreOptions": "More options",
|
"moreOptions": "More options",
|
||||||
"collapse": "Collapse",
|
"collapse": "Collapse",
|
||||||
"signInAgreement": "By clicking \"Continue\" above, you agreed to AppFlowy's",
|
"signInAgreement": "By clicking \"Continue\" above, you agreed to \nAppFlowy's ",
|
||||||
"signInLocalAgreement": "By clicking \"Get Started\" above, you agreed to AppFlowy's",
|
"signInLocalAgreement": "By clicking \"Get Started\" above, you agreed to \nAppFlowy's ",
|
||||||
"and": "and",
|
"and": "and",
|
||||||
"termOfUse": "Terms",
|
"termOfUse": "Terms",
|
||||||
"privacyPolicy": "Privacy Policy",
|
"privacyPolicy": "Privacy Policy",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user