15.7 登录页
我们说过Github有多种登录方式,为了简单起见,我们只实现通过用户名和密码登录。在实现登录页时有四点需要注意:
- 可以自动填充上次登录的用户名(如果有)。
- 为了防止密码输入错误,密码框应该有开关可以看明文。
- 用户名或密码字段在调用登录接口前有本地合法性校验(比如不能为空)。
- 登录成功后需更新用户信息。
实现代码如下:
1import '../index.dart';
2
3class LoginRoute extends StatefulWidget {
4
5 _LoginRouteState createState() => _LoginRouteState();
6}
7
8class _LoginRouteState extends State<LoginRoute> {
9 TextEditingController _unameController = TextEditingController();
10 TextEditingController _pwdController = TextEditingController();
11 bool pwdShow = false; //密码是否显示明文
12 GlobalKey _formKey = GlobalKey<FormState>();
13 bool _nameAutoFocus = true;
14
15
16 void initState() {
17 // 自动填充上次登录的用户名,填充后将焦点定位到密码输入框
18 _unameController.text = Global.profile.lastLogin;
19 if (_unameController.text != null) {
20 _nameAutoFocus = false;
21 }
22 super.initState();
23 }
24
25
26 Widget build(BuildContext context) {
27 var gm = GmLocalizations.of(context);
28 return Scaffold(
29 appBar: AppBar(title: Text(gm.login)),
30 body: Padding(
31 padding: const EdgeInsets.all(16.0),
32 child: Form(
33 key: _formKey,
34 autovalidate: true,
35 child: Column(
36 children: <Widget>[
37 TextFormField(
38 autofocus: _nameAutoFocus,
39 controller: _unameController,
40 decoration: InputDecoration(
41 labelText: gm.userName,
42 hintText: gm.userNameOrEmail,
43 prefixIcon: Icon(Icons.person),
44 ),
45 // 校验用户名(不能为空)
46 validator: (v) {
47 return v.trim().isNotEmpty ? null : gm.userNameRequired;
48 }),
49 TextFormField(
50 controller: _pwdController,
51 autofocus: !_nameAutoFocus,
52 decoration: InputDecoration(
53 labelText: gm.password,
54 hintText: gm.password,
55 prefixIcon: Icon(Icons.lock),
56 suffixIcon: IconButton(
57 icon: Icon(
58 pwdShow ? Icons.visibility_off : Icons.visibility),
59 onPressed: () {
60 setState(() {
61 pwdShow = !pwdShow;
62 });
63 },
64 )),
65 obscureText: !pwdShow,
66 //校验密码(不能为空)
67 validator: (v) {
68 return v.trim().isNotEmpty ? null : gm.passwordRequired;
69 },
70 ),
71 Padding(
72 padding: const EdgeInsets.only(top: 25),
73 child: ConstrainedBox(
74 constraints: BoxConstraints.expand(height: 55.0),
75 child: RaisedButton(
76 color: Theme.of(context).primaryColor,
77 onPressed: _onLogin,
78 textColor: Colors.white,
79 child: Text(gm.login),
80 ),
81 ),
82 ),
83 ],
84 ),
85 ),
86 ),
87 );
88 }
89
90 void _onLogin() async {
91 // 提交前,先验证各个表单字段是否合法
92 if ((_formKey.currentState as FormState).validate()) {
93 showLoading(context);
94 User user;
95 try {
96 user = await Git(context).login(_unameController.text, _pwdController.text);
97 // 因为登录页返回后,首页会build,所以我们传false,更新user后不触发更新
98 Provider.of<UserModel>(context, listen: false).user = user;
99 } catch (e) {
100 //登录失败则提示
101 if (e.response?.statusCode == 401) {
102 showToast(GmLocalizations.of(context).userNameOrPasswordWrong);
103 } else {
104 showToast(e.toString());
105 }
106 } finally {
107 // 隐藏loading框
108 Navigator.of(context).pop();
109 }
110 if (user != null) {
111 // 返回
112 Navigator.of(context).pop();
113 }
114 }
115 }
116}代码很简单,关键地方都有注释,不再赘述,下面我们看一下运行效果,如图15-5所示。
