import 'package:flutter/cupertino.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart'; import 'package:zhiying_comm/pages/login_page/invite/login_invite_page.dart'; import 'package:zhiying_comm/pages/login_page/model/login_model.dart'; import 'package:zhiying_comm/zhiying_comm.dart'; import 'bloc/bloc.dart'; import 'bloc/login_account_repository.dart'; import 'login_account_sk.dart'; import 'widget/slide_verify_widget.dart'; import 'package:zhiying_comm/util/empty_util.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'widget/vcode_widget.dart'; /// /// 账号登陆(手机验证码,密码) /// class LoginAccountPage extends StatelessWidget { final Map model; const LoginAccountPage(this.model, {Key key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( resizeToAvoidBottomInset: false, backgroundColor: HexColor.fromHex('#FFFFFF'), body: BlocProvider( create: (_) => LoginAccountBloc(repository: LoginAccountRepository())..add(LoginAccountInitEvent()), child: LoginAccountPageContianer(), ), ); } } /// 啦啦啦 class LoginAccountPageContianer extends StatefulWidget { @override _LoginAccountPageContianerState createState() => _LoginAccountPageContianerState(); } /// /// 主体逻辑 /// class _LoginAccountPageContianerState extends State implements OnClickListener { TextEditingController _phoneEdController; TextEditingController _vcodeEdController; TextEditingController _passEdController; FocusNode _phoneFN; FocusNode _passFN; FocusNode _vcodeFN; /// 跳转到邀请码页面 void _openInvitePage() { print('跳转到邀请码页面'); Navigator.push(context, CupertinoPageRoute( // builder: (_) => PageFactory.create('login_invite', null) builder: (_) => LoginInvitePage() )); } /// 登陆成功页面 void _openLoginSuccessPage() { Navigator.pushAndRemoveUntil( context, MaterialPageRoute(builder: (BuildContext context) => PageFactory.create('homePage', null)), (Route route) => false, ); } /// 返回上一页 void _openPop() { if (Navigator.canPop(context)) { Navigator.pop(context); } } /// 登陆 void _submitOnClick() { print('登陆'); if (_checkParam(true)) { if (_useVcode) { BlocProvider.of(context).add(LoginAccountTypeVcodeEvent(mobile: _phoneEdController?.text?.toString()?.trim() ?? '', captcha: _vcodeEdController?.text?.toString()?.trim() ?? '')); } else { BlocProvider.of(context).add(LoginAccountTypePasswordEvent(username: _phoneEdController?.text?.toString()?.trim() ?? '', password: _passEdController?.text?.toString()?.trim() ?? '')); } } } /// 切换登陆方式 void _changeLoginTypeOnClick() { print('切换登陆'); _passFN?.unfocus(); _vcodeFN?.unfocus(); _phoneFN?.unfocus(); setState(() { _useVcode = !_useVcode; }); // 清除缓存 if (_useVcode) { _passEdController?.clear(); } else { _vcodeEdController?.clear(); } } /// 同意协议 void _agreeOnClick() { print('同意协议'); setState(() { _acceptAgreement = !_acceptAgreement; }); _checkParam(false); } /// 打开协议 void _openAgreement(String url) { if (!EmptyUtil.isEmpty(url)) { print('打开协议$url'); } } /// 输入框监听 void _onChange(string) { print('$string'); _checkParam(false); } /// 校验登陆参数 bool _checkParam(bool needToast) { // 验证码 if (_useVcode) { String phone = _phoneEdController?.text?.toString()?.trim() ?? null; String vcode = _vcodeEdController?.text?.toString()?.trim() ?? null; if (EmptyUtil.isEmpty(phone)) { if (needToast) Fluttertoast.showToast(msg: '手机号不能为空!'); return false; } if (phone.length != 11) { if (needToast) Fluttertoast.showToast(msg: '手机号格式有误!'); return false; } if (EmptyUtil.isEmpty(vcode)) { if (needToast) Fluttertoast.showToast(msg: '验证码不能为空!'); return false; } if (vcode.length < 4) { if (needToast) Fluttertoast.showToast(msg: '验证码号格式有误!'); return false; } } else { String phone = _phoneEdController?.text?.toString()?.trim() ?? null; String pass = _passEdController?.text?.toString()?.trim() ?? null; if (EmptyUtil.isEmpty(phone)) { if (needToast) Fluttertoast.showToast(msg: '手机号不能为空!'); return false; } if (phone.length != 11) { if (needToast) Fluttertoast.showToast(msg: '手机号格式有误!'); return false; } if (EmptyUtil.isEmpty(pass)) { if (needToast) Fluttertoast.showToast(msg: '验证码不能为空!'); return false; } if (pass.length < 4) { if (needToast) Fluttertoast.showToast(msg: '验证码号格式有误!'); return false; } } if (!_acceptAgreement) { if (needToast) Fluttertoast.showToast(msg: '请同意用户协议与隐私政策'); return false; } setState(() { _canSubmit = true; }); return true; } /// 检测手机号是否合法 bool _checkPhoneNumParam(bool needToast) { String phone = _phoneEdController?.text?.toString()?.trim() ?? null; if (EmptyUtil.isEmpty(phone)) { if (needToast) Fluttertoast.showToast(msg: '手机号不能为空!'); return false; } if (phone.length != 11) { if (needToast) Fluttertoast.showToast(msg: '手机号格式有误!'); return false; } return true; } /// 是否使用验证码登陆 默认使用 bool _useVcode = true; /// 是否可以登陆 bool _canSubmit = false; /// 是否同意协议 bool _acceptAgreement = true; /// 是否显示第三方验证码 bool _showOtherVcode = false; @override void initState() { _phoneEdController = TextEditingController(); _passEdController = TextEditingController(); _vcodeEdController = TextEditingController(); _vcodeFN = FocusNode(); _passFN = FocusNode(); _phoneFN = FocusNode(); super.initState(); } @override void dispose() { _phoneEdController?.dispose(); _passEdController?.dispose(); _vcodeEdController?.dispose(); _phoneFN?.unfocus(); _passFN?.unfocus(); _vcodeFN?.unfocus(); _phoneFN?.dispose(); _passFN?.dispose(); _vcodeFN?.dispose(); super.dispose(); } @override bool onVcodeClick() { /// 获取验证码 if (_checkPhoneNumParam(true)) { BlocProvider.of(context).add(LoginAccountGetVcodeEvent(mobile: _phoneEdController?.text?.toString()?.trim() ?? '')); return true; } return false; } @override Widget build(BuildContext context) { return BlocConsumer( listener: (context, state) { if (state is LoginAccountTypeVcodeLoginSuccessState) {} }, buildWhen: (prev, current) { // 验证码登陆失败 if (current is LoginAccountTypeVcodeLoginErrorState) { Fluttertoast.showToast(msg: '登陆失败'); return false; } // 验证码登陆成功 if (current is LoginAccountTypeVcodeLoginSuccessState) { /// 缓存登陆数据 Provider.of(context, listen: false)?.setUserInfo(current.model); if (current?.model?.registerInviteCodeEnable != '1') { Fluttertoast.showToast(msg: '登陆成功~'); /// 打开也买 _openLoginSuccessPage(); } else { /// 打开邀请页面 _openInvitePage(); } return false; } // 获取验证码成功 if (current is LoginAccountGetVcodeSuccessState) { Fluttertoast.showToast(msg: '验证码下发成功'); return false; } // 获取验证码失败 if (current is LoginAccountGetVcodeErrorState) { Fluttertoast.showToast(msg: '验证码获取失败~'); return false; } return true; }, builder: (context, state) { print('state = $state'); if (state is LoginAccountLoadedState) { return _getMainWidget(state.model); } // 返回骨架屏 return LoginAccountSkeleton(); }, ); } /// 主页面 Widget _getMainWidget(LoginModel model) { print(model); return Column( children: [ /// appBar _getAppBarWidget(model), /// title Padding(padding: const EdgeInsets.only(left: 27.5, right: 27.5, top: 40), child: _getTitleWidget(model)), /// 手机输入框 Padding(padding: const EdgeInsets.only(left: 27.5, right: 27.5, top: 30), child: _getPhoneWidget(model)), /// 验证码 Visibility(visible: _useVcode, child: Padding(padding: const EdgeInsets.only(left: 27.5, right: 27.5, top: 15), child: _getVcodeWidget(model))), /// 密码 Visibility(visible: !_useVcode, child: Padding(padding: const EdgeInsets.only(left: 27.5, right: 27.5, top: 15), child: _getPassInputWidget(model))), /// 第三方验证码 Visibility(visible: _showOtherVcode, child: Padding(padding: const EdgeInsets.only(left: 27.5, right: 27.5, top: 15), child: _getOtherVcodeInputWidget(model))), /// 切换登陆方式tip Padding(padding: const EdgeInsets.only(top: 15), child: _getChangeTipWidget(model)), /// 按钮 Padding(padding: const EdgeInsets.only(left: 27.5, right: 27.5, top: 30), child: _getSubmiBtnWidget(model)), /// 协议 Padding(padding: const EdgeInsets.only(top: 15), child: _getProtoclWidget(model)), /// 底部提示tip Visibility(visible: _useVcode, child: Expanded(child: Align(alignment: Alignment.bottomCenter, child: _getBottomTipWidget(model)))) ], ); } /// appBar Widget _getAppBarWidget(LoginModel model) { return AppBar( backgroundColor: HexColor.fromHex('#FFFFFF'), elevation: 0, title: Text( model?.mobile?.appBarTitle ?? '登录', style: TextStyle(color: HexColor.fromHex(model?.mobile?.appBarTitleColor ?? '#333333')), ), centerTitle: true, leading: IconButton( icon: Icon( Icons.arrow_back_ios, size: 22, color: HexColor.fromHex('#333333'), ), onPressed: () => _openPop(), ), ); } /// title Widget _getTitleWidget(LoginModel model) { return Align( alignment: Alignment.centerLeft, child: Text( model?.mobile?.title ?? '您好,欢迎登录', style: TextStyle(color: HexColor.fromHex(model?.mobile?.titleColor ?? '#333333'), fontSize: 25), )); } /// 手机输入框 Widget _getPhoneWidget(LoginModel model) { return _getCustomInputWidget( hint: model?.mobile?.inputMobileHintText ?? '请输入您的手机号', controller: _phoneEdController, focusNode: _phoneFN, onChanged: _onChange, hintColor: model?.mobile?.inputHintColor ?? '#999999', bgColor: model?.mobile?.inputBgColor ?? '#F7F7F7', textColor: model?.mobile?.inputTextColor ?? '#333333', iconUrl: model?.mobile?.inputMobileIcon ?? ''); } /// 验证码输入框 Widget _getVcodeWidget(LoginModel model) { return Container( height: 42, child: Stack( children: [ _getCustomInputWidget( controller: _vcodeEdController, focusNode: _vcodeFN, onChanged: _onChange, hintColor: model?.mobile?.inputHintColor ?? '#999999', hint: model?.mobile?.inputVcodeHintText ?? '请输入您的验证码', bgColor: model?.mobile?.inputBgColor ?? '#F7F7F7', textColor: model?.mobile?.inputTextColor ?? '#333333', iconUrl: model?.mobile?.inputVcodeIcon ?? ''), Align(alignment: Alignment.centerRight, child: _getVcodeButtonWidget(model)), ], ), ); } /// 验证码按钮 Widget _getVcodeButtonWidget(LoginModel model) { return VcodeWidget( onCallBack: this, awaitTime: int.parse(model?.mobile?.vcodeTime ?? '60'), btnAwaitText: '秒', btnText: '获取验证码', btnTextColor: model?.mobile?.btnVcodeTextColor ?? '#FFFFFF', color: model?.mobile?.btnVcodeBgColor ?? '#FF4343', disabledColor: model?.mobile?.btnVcodeBanBgColor ?? '#DDDDDD', disabledTextColor: model?.mobile?.btnVcodeBanTextColor ?? '#FFFFFF'); } /// 第三方验证码输入框 Widget _getOtherVcodeInputWidget(var model) { return Container( width: 240, height: 42, alignment: Alignment.centerLeft, child: SlideVerifyWidget( width: 240, ), // child: Row( // children: [ // // 输入框 // Expanded( // child: _getCustomInputWidget(hint: '请输入右方验证码', hintColor: '#999999', textColor: '#333333', bgColor: '#F7F7F7', iconUrl: null, ) // ), // // 第三方验证码 // Container( // margin: const EdgeInsets.only(left: 5), // width: 100, // height: double.infinity, // decoration: BoxDecoration( // borderRadius: BorderRadius.circular(8), // color: Colors.red // ), // ), // // ], // ), ); } /// 密码输入框 Widget _getPassInputWidget(LoginModel model) { return Container( height: 42, child: _getCustomInputWidget( controller: _passEdController, focusNode: _passFN, onChanged: _onChange, hint: model?.mobile?.inputPassHintText ?? '请输入您的密码', iconUrl: model?.mobile?.inputPassIcon ?? '', hintColor: model?.mobile?.inputHintColor ?? '#999999', textColor: model?.mobile?.inputTextColor ?? '#333333', bgColor: model?.mobile?.inputBgColor ?? '#F7F7F7'), ); } /// 切换登陆方式tip Widget _getChangeTipWidget(LoginModel model) { return GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => _changeLoginTypeOnClick(), child: Text( _useVcode ? model?.mobile?.textUsePassTip ?? '使用密码登录' : model?.mobile?.textUseVcodeTip ?? '使用验证码登陆', style: TextStyle(fontSize: 12, color: HexColor.fromHex(_useVcode ? model?.mobile?.textUsePassTipColor ?? '#999999' : model?.mobile?.textUseVcodeTipColor ?? '#999999')), ), ); } /// 登陆按钮 Widget _getSubmiBtnWidget(LoginModel model) { return Material( child: Container( height: 52, width: double.infinity, color: Colors.white, child: RaisedButton( child: Text( model?.mobile?.btnLoginText ?? '立即登录', style: TextStyle(fontSize: 15), ), textColor: HexColor.fromHex(model?.mobile?.btnLoginTextColor ?? '#FFFFFF'), color: HexColor.fromHex(model?.mobile?.btnLoginBgColor ?? '#FF3939'), disabledColor: HexColor.fromHex(model?.mobile?.btnLoginBanBgColor ?? '#F5F5F5'), disabledTextColor: HexColor.fromHex(model?.mobile?.btnLoginBanTextColor ?? '#999999'), elevation: 5, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(52 / 2)), onPressed: _canSubmit ? _submitOnClick : null, ), ), ); } /// 协议 Widget _getProtoclWidget(LoginModel model) { // return Text('同意《嗨如意用户协议》 及《营私政策》', style: TextStyle(fontSize: 11, color: HexColor.fromHex('#C0C0C0'))); return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ /// 图标 GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => _agreeOnClick(), child: Padding( padding: const EdgeInsets.all(8.0), child: CachedNetworkImage( imageUrl: _acceptAgreement ? model?.mobile?.protocolSelectIcon ?? '' : model?.mobile?.protocolUnselectIcon ?? '', width: 12, ))), /// 协议文字 RichText( text: TextSpan( text: '', children: model.mobile.protocol.map((item) { return TextSpan( text: item?.text, style: TextStyle(color: HexColor.fromHex(item?.textColor), fontSize: 10), recognizer: TapGestureRecognizer() ..onTap = () { _openAgreement(item.url); }); }).toList()), ) ], ); } /// 底部提示tip Widget _getBottomTipWidget(LoginModel model) { return Padding( padding: const EdgeInsets.only(bottom: 25), child: Text( model?.mobile?.textBottomTip ?? '未注册过的手机将自动注册', style: TextStyle(fontSize: 11, color: HexColor.fromHex(model?.mobile?.textBottomTipColor ?? '#999999')), ), ); } /// 自定义输入框 Widget _getCustomInputWidget( {String hint, String hintColor, String bgColor, String textColor, String iconUrl, TextEditingController controller, ValueChanged onChanged, FocusNode focusNode}) { var border = OutlineInputBorder(borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: HexColor.fromHex(bgColor), width: 0)); return Container( height: 42, padding: const EdgeInsets.symmetric(horizontal: 15), decoration: BoxDecoration( color: HexColor.fromHex(bgColor), borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ CachedNetworkImage( imageUrl: iconUrl ?? '', width: 10, ), Expanded( child: TextField( controller: controller, focusNode: focusNode, onChanged: onChanged, expands: false, style: TextStyle(color: HexColor.fromHex(textColor)), maxLines: 1, keyboardType: TextInputType.number, decoration: InputDecoration( contentPadding: EdgeInsets.only(top: 30, left: 7.5), hintText: hint, hintStyle: TextStyle(fontSize: 13, color: HexColor.fromHex(hintColor)), hintMaxLines: 1, filled: true, fillColor: Colors.transparent, border: border, focusedBorder: border, enabledBorder: border, disabledBorder: border, errorBorder: border, focusedErrorBorder: border, ), ), ), ], ), ); } }