我们说过Github有多种登录方式,为了简单起见,我们只实现通过用户名和密码登录。在实现登录页时有四点需要注意:

    1. 可以自动填充上次登录的用户名(如果有)
    2. 为了防止密码输入错误,密码框应该有开关可以看明文
    3. 用户名或密码字段在调用登录接口前有本地合法性校验(比如不能为空)
    4. 登录成功后需更新用户信息

    创建lib/routes/LoginRoute.dart文件,实现代码如下:

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

    代码很简单,关键地方都有注释,不再赘述,下面我们看一下运行效果,如图所示。
    登录页 - 图1