fix: add error text under text field

This commit is contained in:
Lucas.Xu 2025-04-11 16:43:13 +08:00
parent 2e295e6891
commit caf8ff974f
4 changed files with 102 additions and 54 deletions

View file

@ -2,6 +2,8 @@ import 'package:appflowy/generated/locale_keys.g.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_magic_link_or_passcode_page.dart';
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_password.dart';
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_password_page.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy_ui/appflowy_ui.dart';
import 'package:easy_localization/easy_localization.dart';
@ -57,14 +59,13 @@ class _ContinueWithEmailAndPasswordState
controller.text,
),
),
// Hide password sign in until we implement the reset password / forgot password
// VSpace(theme.spacing.l),
// ContinueWithPassword(
// onTap: () => _pushContinueWithPasswordPage(
// context,
// controller.text,
// ),
// ),
VSpace(theme.spacing.l),
ContinueWithPassword(
onTap: () => _pushContinueWithPasswordPage(
context,
controller.text,
),
),
],
);
}
@ -106,28 +107,31 @@ class _ContinueWithEmailAndPasswordState
);
}
// void _pushContinueWithPasswordPage(
// BuildContext context,
// String email,
// ) {
// final signInBloc = context.read<SignInBloc>();
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) => ContinueWithPasswordPage(
// email: email,
// backToLogin: () => Navigator.pop(context),
// onEnterPassword: (password) => signInBloc.add(
// SignInEvent.signInWithEmailAndPassword(
// email: email,
// password: password,
// ),
// ),
// onForgotPassword: () {
// // todo: implement forgot password
// },
// ),
// ),
// );
// }
void _pushContinueWithPasswordPage(
BuildContext context,
String email,
) {
final signInBloc = context.read<SignInBloc>();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BlocProvider.value(
value: signInBloc,
child: ContinueWithPasswordPage(
email: email,
backToLogin: () => Navigator.pop(context),
onEnterPassword: (password) => signInBloc.add(
SignInEvent.signInWithEmailAndPassword(
email: email,
password: password,
),
),
onForgotPassword: () {
// todo: implement forgot password
},
),
),
),
);
}
}

View file

@ -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_ui/appflowy_ui.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class ContinueWithPasswordPage extends StatefulWidget {
const ContinueWithPasswordPage({
@ -25,6 +27,7 @@ class ContinueWithPasswordPage extends StatefulWidget {
class _ContinueWithPasswordPageState extends State<ContinueWithPasswordPage> {
final passwordController = TextEditingController();
final inputPasswordKey = GlobalKey<AFTextFieldState>();
@override
void dispose() {
@ -38,18 +41,30 @@ class _ContinueWithPasswordPageState extends State<ContinueWithPasswordPage> {
body: Center(
child: SizedBox(
width: 320,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Logo and title
..._buildLogoAndTitle(),
child: BlocListener<SignInBloc, SignInState>(
listener: (context, state) {
if (state.passwordError != null) {
inputPasswordKey.currentState?.syncError(
hasError: true,
errorText: 'Incorrect password. Please try again.',
);
} else {
inputPasswordKey.currentState?.syncError();
}
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Logo and title
..._buildLogoAndTitle(),
// Password input and buttons
..._buildPasswordSection(),
// Password input and buttons
..._buildPasswordSection(),
// Back to login
..._buildBackToLogin(),
],
// Back to login
..._buildBackToLogin(),
],
),
),
),
),
@ -100,25 +115,33 @@ class _ContinueWithPasswordPageState extends State<ContinueWithPasswordPage> {
return [
// Password input
AFTextField(
key: inputPasswordKey,
controller: passwordController,
hintText: 'Enter password',
autoFocus: true,
onSubmitted: widget.onEnterPassword,
),
// todo: ask designer to provide the spacing
VSpace(12),
VSpace(8),
// todo: forgot password is not implemented yet
// Forgot password button
// AFGhostTextButton(
// text: 'Forget password?',
// size: AFButtonSize.s,
// onTap: widget.onForgotPassword,
// textColor: (context, isHovering, disabled) {
// return theme.textColorScheme.theme;
// },
// ),
VSpace(12),
Align(
alignment: Alignment.centerLeft,
child: AFGhostTextButton(
text: 'Forget password?',
size: AFButtonSize.s,
padding: EdgeInsets.zero,
onTap: widget.onForgotPassword,
textColor: (context, isHovering, disabled) {
final theme = AppFlowyTheme.of(context);
if (isHovering) {
return theme.fillColorScheme.themeThickHover;
}
return theme.textColorScheme.theme;
},
),
),
VSpace(20),
// Continue button
AFFilledTextButton.primary(
@ -137,8 +160,12 @@ class _ContinueWithPasswordPageState extends State<ContinueWithPasswordPage> {
text: 'Back to Login',
size: AFButtonSize.s,
onTap: widget.backToLogin,
padding: EdgeInsets.zero,
textColor: (context, isHovering, disabled) {
final theme = AppFlowyTheme.of(context);
if (isHovering) {
return theme.fillColorScheme.themeThickHover;
}
return theme.textColorScheme.theme;
},
),

View file

@ -101,6 +101,7 @@ class _DesktopThirdPartySignInState extends State<_DesktopThirdPartySignIn> {
VSpace(theme.spacing.l),
AFGhostTextButton(
text: 'More options',
padding: EdgeInsets.zero,
textColor: (context, isHovering, disabled) {
if (isHovering) {
return theme.fillColorScheme.themeThickHover;

View file

@ -5,6 +5,10 @@ typedef AFTextFieldValidator = (bool result, String errorText) Function(
TextEditingController controller,
);
abstract class AFTextFieldState extends State<AFTextField> {
void syncError({bool hasError = false, String errorText = ''}) {}
}
class AFTextField extends StatefulWidget {
const AFTextField({
super.key,
@ -52,7 +56,7 @@ class AFTextField extends StatefulWidget {
State<AFTextField> createState() => _AFTextFieldState();
}
class _AFTextFieldState extends State<AFTextField> {
class _AFTextFieldState extends AFTextFieldState {
late final TextEditingController effectiveController;
bool hasError = false;
@ -148,6 +152,7 @@ class _AFTextFieldState extends State<AFTextField> {
if (hasError && errorText.isNotEmpty) {
child = Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
child,
SizedBox(height: theme.spacing.xs),
@ -174,4 +179,15 @@ class _AFTextFieldState extends State<AFTextField> {
});
}
}
@override
void syncError({
bool hasError = false,
String errorText = '',
}) {
setState(() {
this.hasError = hasError;
this.errorText = errorText;
});
}
}