Confirm Password
也有助于
Signup
按钮应启用。
我有两个问题:
1这是个问题。当前,如果我通过
Login
注册
形式
注册
按钮已启用,这是错误的,因为
确认密码
2我觉得有比我所做的更好的方法来实现目标
验证和
按钮验证。我最初尝试为创建一个验证器
确认密码
但这需要时间
二者都
密码和确认密码作为输入,但无法使其正常工作
StreamTransformer
只取一个输入参数。有什么更好的办法?
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:rxdart/rxdart.dart';
void main() => runApp(AuthProvider(child: MaterialApp(home: Auth())));
enum AuthMode { Signup, Login }
class Auth extends StatefulWidget {
@override
_AuthState createState() => _AuthState();
}
class _AuthState extends State<Auth> {
AuthMode authMode = AuthMode.Login;
bool get _isLoginMode => authMode == AuthMode.Login;
TextEditingController confirmPasswordCtrl = TextEditingController();
@override
Widget build(BuildContext context) {
final bloc = AuthProvider.of(context);
return Scaffold(
body: Container(
margin: EdgeInsets.all(20.0),
child: Column(
children: <Widget>[
emailField(bloc),
passwordField(bloc),
confirmPasswordField(bloc),
Container(
margin: EdgeInsets.only(top: 40.0),
),
FlatButton(
child: Text('Switch to ${_isLoginMode ? 'Signup' : 'Login'}'),
onPressed: swithAuthMode,
),
loginOrSignupButton(bloc),
],
),
),
);
}
void swithAuthMode() {
setState(() {
authMode = authMode == AuthMode.Login ? AuthMode.Signup : AuthMode.Login;
});
}
Widget confirmPasswordField(AuthBloc bloc) {
return _isLoginMode
? Container()
: StreamBuilder(
stream: bloc.passwordConfirmed,
builder: (context, snapshot) {
return TextField(
obscureText: true,
onChanged: bloc.changeConfirmPassword,
keyboardType: TextInputType.text,
decoration: InputDecoration(
labelText: 'Confirm Password',
errorText: snapshot.hasData && !snapshot.data ? 'password mismatch' : null,
),
);
},
);
}
Widget emailField(AuthBloc bloc) {
return StreamBuilder(
stream: bloc.email,
builder: (context, snapshot) {
return TextField(
keyboardType: TextInputType.emailAddress,
onChanged: bloc.changeEmail,
decoration: InputDecoration(
hintText: 'your email',
labelText: 'Email',
errorText: snapshot.error,
),
);
},
);
}
Widget loginOrSignupButton(AuthBloc bloc) {
return StreamBuilder(
stream: _isLoginMode ? bloc.submitValid : bloc.signupValid,
builder: (context, snapshot) {
print('hasData: ${snapshot.hasData}, data: ${snapshot.data}');
return RaisedButton(
onPressed: // The problem is, after entering some login details then switching from login to signup, the Signup button is enabled.
!snapshot.hasData || !snapshot.data ? null : () => onSubmitPressed(bloc, context),
color: Colors.blue,
child: Text('${_isLoginMode ? 'Log in' : 'Sign up'}'),
);
},
);
}
void onSubmitPressed(AuthBloc bloc, BuildContext context) async {
var response = await bloc.submit(_isLoginMode);
if (response.success) {
Navigator.pushReplacementNamed(context, '/home');
} else {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Error'),
content: Text(response.message),
actions: <Widget>[
FlatButton(
child: Text('Ok'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
});
}
}
Widget passwordField(AuthBloc bloc) {
return StreamBuilder(
stream: bloc.password,
builder: (_, snapshot) {
return TextField(
obscureText: true,
onChanged: bloc.changePassword,
decoration: InputDecoration(
labelText: 'Password',
errorText: snapshot.error,
hintText: 'at least 6 characters',
),
);
},
);
}
}
class AuthProvider extends InheritedWidget {
final bloc;
AuthProvider({Key key, Widget child}) :
bloc = AuthBloc(), super(key:key, child: child);
@override
bool updateShouldNotify(InheritedWidget oldWidget) => true;
static AuthBloc of(BuildContext context) => (context.inheritFromWidgetOfExactType(AuthProvider) as AuthProvider).bloc;
}
class Repository {
// this will call whatever backend to authenticate users.
Future<AuthResult> signupUser(String email, String password) => null;
Future<AuthResult> loginUser(String email, String password) => null;
}
class AuthBloc extends Object with AuthValidator {
final _emailController = BehaviorSubject<String>();
final _passwordController = BehaviorSubject<String>();
final _confirmPasswordController = BehaviorSubject<String>();
final _signupController = PublishSubject<Map<String, dynamic>>();
final Repository _repository = Repository();
Stream<String> get email => _emailController.stream.transform(validateEmail);
Stream<String> get password =>
_passwordController.stream.transform(validatePassword);
Stream<bool> get submitValid =>
Observable.combineLatest2(email, password, (e, p) => true);
// Is there a better way of doing passwordConfirmed and signupValid?
Stream<bool> get passwordConfirmed =>
Observable.combineLatest2(password, _confirmPasswordController.stream, (p, cp) => p == cp);
Stream<bool> get signupValid =>
Observable.combineLatest2(submitValid, passwordConfirmed, (s, p) => s && p);
// sink
Function(String) get changeEmail => _emailController.sink.add;
Function(String) get changePassword => _passwordController.sink.add;
Function(String) get changeConfirmPassword =>
_confirmPasswordController.sink.add;
Future<AuthResult> submit(bool isLogin) async {
final validEmail = _emailController.value;
final validPassword = _passwordController.value;
if (!isLogin)
return await _repository.signupUser(validEmail, validPassword);
return await _repository.loginUser(validEmail, validPassword);
}
void dispose() {
_emailController.close();
_passwordController.close();
_signupController.close();
_confirmPasswordController.close();
}
}
class AuthResult {
bool success;
String message;
AuthResult(this.success, this.message);
}
// demo validator
class AuthValidator {
final validateEmail = StreamTransformer<String, String>.fromHandlers(
handleData: (email, sink) {
if (email.contains('@')) sink.add(email);
else sink.addError('Email is not valid');
}
);
final validatePassword = StreamTransformer<String, String>.fromHandlers(
handleData: (password, sink) {
if (password.length >= 6) sink.add(password);
else sink.addError('Password must be at least 6 characters');
}
);
}