基础库
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 

593 行
19 KiB

  1. import 'package:flutter/cupertino.dart';
  2. import 'package:flutter/gestures.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:cached_network_image/cached_network_image.dart';
  5. import 'package:flutter_bloc/flutter_bloc.dart';
  6. import 'package:provider/provider.dart';
  7. import 'package:zhiying_comm/pages/login_page/invite/login_invite_page.dart';
  8. import 'package:zhiying_comm/pages/login_page/model/login_model.dart';
  9. import 'package:zhiying_comm/zhiying_comm.dart';
  10. import 'bloc/bloc.dart';
  11. import 'bloc/login_account_repository.dart';
  12. import 'login_account_sk.dart';
  13. import 'widget/slide_verify_widget.dart';
  14. import 'package:zhiying_comm/util/empty_util.dart';
  15. import 'package:fluttertoast/fluttertoast.dart';
  16. import 'widget/vcode_widget.dart';
  17. ///
  18. /// 账号登陆(手机验证码,密码)
  19. ///
  20. class LoginAccountPage extends StatelessWidget {
  21. final Map<String, dynamic> model;
  22. const LoginAccountPage(this.model, {Key key}) : super(key: key);
  23. @override
  24. Widget build(BuildContext context) {
  25. return Scaffold(
  26. resizeToAvoidBottomInset: false,
  27. backgroundColor: HexColor.fromHex('#FFFFFF'),
  28. body: BlocProvider<LoginAccountBloc>(
  29. create: (_) => LoginAccountBloc(repository: LoginAccountRepository())..add(LoginAccountInitEvent()),
  30. child: LoginAccountPageContianer(),
  31. ),
  32. );
  33. }
  34. }
  35. /// 啦啦啦
  36. class LoginAccountPageContianer extends StatefulWidget {
  37. @override
  38. _LoginAccountPageContianerState createState() => _LoginAccountPageContianerState();
  39. }
  40. ///
  41. /// 主体逻辑
  42. ///
  43. class _LoginAccountPageContianerState extends State<LoginAccountPageContianer> implements OnClickListener {
  44. TextEditingController _phoneEdController;
  45. TextEditingController _vcodeEdController;
  46. TextEditingController _passEdController;
  47. FocusNode _phoneFN;
  48. FocusNode _passFN;
  49. FocusNode _vcodeFN;
  50. /// 跳转到邀请码页面
  51. void _openInvitePage() {
  52. print('跳转到邀请码页面');
  53. Navigator.push(context, CupertinoPageRoute(
  54. // builder: (_) => PageFactory.create('login_invite', null)
  55. builder: (_) => LoginInvitePage()
  56. ));
  57. }
  58. /// 登陆成功页面
  59. void _openLoginSuccessPage() {
  60. Navigator.pushAndRemoveUntil(
  61. context,
  62. MaterialPageRoute(builder: (BuildContext context) => PageFactory.create('homePage', null)),
  63. (Route<dynamic> route) => false,
  64. );
  65. }
  66. /// 返回上一页
  67. void _openPop() {
  68. if (Navigator.canPop(context)) {
  69. Navigator.pop(context);
  70. }
  71. }
  72. /// 登陆
  73. void _submitOnClick() {
  74. print('登陆');
  75. if (_checkParam(true)) {
  76. if (_useVcode) {
  77. BlocProvider.of<LoginAccountBloc>(context).add(LoginAccountTypeVcodeEvent(mobile: _phoneEdController?.text?.toString()?.trim() ?? '', captcha: _vcodeEdController?.text?.toString()?.trim() ?? ''));
  78. } else {
  79. BlocProvider.of<LoginAccountBloc>(context).add(LoginAccountTypePasswordEvent(username: _phoneEdController?.text?.toString()?.trim() ?? '', password: _passEdController?.text?.toString()?.trim() ?? ''));
  80. }
  81. }
  82. }
  83. /// 切换登陆方式
  84. void _changeLoginTypeOnClick() {
  85. print('切换登陆');
  86. _passFN?.unfocus();
  87. _vcodeFN?.unfocus();
  88. _phoneFN?.unfocus();
  89. setState(() {
  90. _useVcode = !_useVcode;
  91. });
  92. // 清除缓存
  93. if (_useVcode) {
  94. _passEdController?.clear();
  95. } else {
  96. _vcodeEdController?.clear();
  97. }
  98. }
  99. /// 同意协议
  100. void _agreeOnClick() {
  101. print('同意协议');
  102. setState(() {
  103. _acceptAgreement = !_acceptAgreement;
  104. });
  105. _checkParam(false);
  106. }
  107. /// 打开协议
  108. void _openAgreement(String url) {
  109. if (!EmptyUtil.isEmpty(url)) {
  110. print('打开协议$url');
  111. }
  112. }
  113. /// 输入框监听
  114. void _onChange(string) {
  115. print('$string');
  116. _checkParam(false);
  117. }
  118. /// 校验登陆参数
  119. bool _checkParam(bool needToast) {
  120. // 验证码
  121. if (_useVcode) {
  122. String phone = _phoneEdController?.text?.toString()?.trim() ?? null;
  123. String vcode = _vcodeEdController?.text?.toString()?.trim() ?? null;
  124. if (EmptyUtil.isEmpty(phone)) {
  125. if (needToast) Fluttertoast.showToast(msg: '手机号不能为空!');
  126. return false;
  127. }
  128. if (phone.length != 11) {
  129. if (needToast) Fluttertoast.showToast(msg: '手机号格式有误!');
  130. return false;
  131. }
  132. if (EmptyUtil.isEmpty(vcode)) {
  133. if (needToast) Fluttertoast.showToast(msg: '验证码不能为空!');
  134. return false;
  135. }
  136. if (vcode.length < 4) {
  137. if (needToast) Fluttertoast.showToast(msg: '验证码号格式有误!');
  138. return false;
  139. }
  140. } else {
  141. String phone = _phoneEdController?.text?.toString()?.trim() ?? null;
  142. String pass = _passEdController?.text?.toString()?.trim() ?? null;
  143. if (EmptyUtil.isEmpty(phone)) {
  144. if (needToast) Fluttertoast.showToast(msg: '手机号不能为空!');
  145. return false;
  146. }
  147. if (phone.length != 11) {
  148. if (needToast) Fluttertoast.showToast(msg: '手机号格式有误!');
  149. return false;
  150. }
  151. if (EmptyUtil.isEmpty(pass)) {
  152. if (needToast) Fluttertoast.showToast(msg: '验证码不能为空!');
  153. return false;
  154. }
  155. if (pass.length < 4) {
  156. if (needToast) Fluttertoast.showToast(msg: '验证码号格式有误!');
  157. return false;
  158. }
  159. }
  160. if (!_acceptAgreement) {
  161. if (needToast) Fluttertoast.showToast(msg: '请同意用户协议与隐私政策');
  162. return false;
  163. }
  164. setState(() {
  165. _canSubmit = true;
  166. });
  167. return true;
  168. }
  169. /// 检测手机号是否合法
  170. bool _checkPhoneNumParam(bool needToast) {
  171. String phone = _phoneEdController?.text?.toString()?.trim() ?? null;
  172. if (EmptyUtil.isEmpty(phone)) {
  173. if (needToast) Fluttertoast.showToast(msg: '手机号不能为空!');
  174. return false;
  175. }
  176. if (phone.length != 11) {
  177. if (needToast) Fluttertoast.showToast(msg: '手机号格式有误!');
  178. return false;
  179. }
  180. return true;
  181. }
  182. /// 是否使用验证码登陆 默认使用
  183. bool _useVcode = true;
  184. /// 是否可以登陆
  185. bool _canSubmit = false;
  186. /// 是否同意协议
  187. bool _acceptAgreement = true;
  188. /// 是否显示第三方验证码
  189. bool _showOtherVcode = false;
  190. @override
  191. void initState() {
  192. _phoneEdController = TextEditingController();
  193. _passEdController = TextEditingController();
  194. _vcodeEdController = TextEditingController();
  195. _vcodeFN = FocusNode();
  196. _passFN = FocusNode();
  197. _phoneFN = FocusNode();
  198. super.initState();
  199. }
  200. @override
  201. void dispose() {
  202. _phoneEdController?.dispose();
  203. _passEdController?.dispose();
  204. _vcodeEdController?.dispose();
  205. _phoneFN?.unfocus();
  206. _passFN?.unfocus();
  207. _vcodeFN?.unfocus();
  208. _phoneFN?.dispose();
  209. _passFN?.dispose();
  210. _vcodeFN?.dispose();
  211. super.dispose();
  212. }
  213. @override
  214. bool onVcodeClick() {
  215. /// 获取验证码
  216. if (_checkPhoneNumParam(true)) {
  217. BlocProvider.of<LoginAccountBloc>(context).add(LoginAccountGetVcodeEvent(mobile: _phoneEdController?.text?.toString()?.trim() ?? ''));
  218. return true;
  219. }
  220. return false;
  221. }
  222. @override
  223. Widget build(BuildContext context) {
  224. return BlocConsumer<LoginAccountBloc, LoginAccountState>(
  225. listener: (context, state) {
  226. if (state is LoginAccountTypeVcodeLoginSuccessState) {}
  227. },
  228. buildWhen: (prev, current) {
  229. // 验证码登陆失败
  230. if (current is LoginAccountTypeVcodeLoginErrorState) {
  231. Fluttertoast.showToast(msg: '登陆失败');
  232. return false;
  233. }
  234. // 验证码登陆成功
  235. if (current is LoginAccountTypeVcodeLoginSuccessState) {
  236. /// 缓存登陆数据
  237. Provider.of<UserInfoNotifier>(context, listen: false)?.setUserInfo(current.model);
  238. if (current?.model?.registerInviteCodeEnable != '1') {
  239. Fluttertoast.showToast(msg: '登陆成功~');
  240. /// 打开也买
  241. _openLoginSuccessPage();
  242. } else {
  243. /// 打开邀请页面
  244. _openInvitePage();
  245. }
  246. return false;
  247. }
  248. // 获取验证码成功
  249. if (current is LoginAccountGetVcodeSuccessState) {
  250. Fluttertoast.showToast(msg: '验证码下发成功');
  251. return false;
  252. }
  253. // 获取验证码失败
  254. if (current is LoginAccountGetVcodeErrorState) {
  255. Fluttertoast.showToast(msg: '验证码获取失败~');
  256. return false;
  257. }
  258. return true;
  259. },
  260. builder: (context, state) {
  261. print('state = $state');
  262. if (state is LoginAccountLoadedState) {
  263. return _getMainWidget(state.model);
  264. }
  265. // 返回骨架屏
  266. return LoginAccountSkeleton();
  267. },
  268. );
  269. }
  270. /// 主页面
  271. Widget _getMainWidget(LoginModel model) {
  272. print(model);
  273. return Column(
  274. children: <Widget>[
  275. /// appBar
  276. _getAppBarWidget(model),
  277. /// title
  278. Padding(padding: const EdgeInsets.only(left: 27.5, right: 27.5, top: 40), child: _getTitleWidget(model)),
  279. /// 手机输入框
  280. Padding(padding: const EdgeInsets.only(left: 27.5, right: 27.5, top: 30), child: _getPhoneWidget(model)),
  281. /// 验证码
  282. Visibility(visible: _useVcode, child: Padding(padding: const EdgeInsets.only(left: 27.5, right: 27.5, top: 15), child: _getVcodeWidget(model))),
  283. /// 密码
  284. Visibility(visible: !_useVcode, child: Padding(padding: const EdgeInsets.only(left: 27.5, right: 27.5, top: 15), child: _getPassInputWidget(model))),
  285. /// 第三方验证码
  286. Visibility(visible: _showOtherVcode, child: Padding(padding: const EdgeInsets.only(left: 27.5, right: 27.5, top: 15), child: _getOtherVcodeInputWidget(model))),
  287. /// 切换登陆方式tip
  288. Padding(padding: const EdgeInsets.only(top: 15), child: _getChangeTipWidget(model)),
  289. /// 按钮
  290. Padding(padding: const EdgeInsets.only(left: 27.5, right: 27.5, top: 30), child: _getSubmiBtnWidget(model)),
  291. /// 协议
  292. Padding(padding: const EdgeInsets.only(top: 15), child: _getProtoclWidget(model)),
  293. /// 底部提示tip
  294. Visibility(visible: _useVcode, child: Expanded(child: Align(alignment: Alignment.bottomCenter, child: _getBottomTipWidget(model))))
  295. ],
  296. );
  297. }
  298. /// appBar
  299. Widget _getAppBarWidget(LoginModel model) {
  300. return AppBar(
  301. backgroundColor: HexColor.fromHex('#FFFFFF'),
  302. elevation: 0,
  303. title: Text(
  304. model?.mobile?.appBarTitle ?? '登录',
  305. style: TextStyle(color: HexColor.fromHex(model?.mobile?.appBarTitleColor ?? '#333333')),
  306. ),
  307. centerTitle: true,
  308. leading: IconButton(
  309. icon: Icon(
  310. Icons.arrow_back_ios,
  311. size: 22,
  312. color: HexColor.fromHex('#333333'),
  313. ),
  314. onPressed: () => _openPop(),
  315. ),
  316. );
  317. }
  318. /// title
  319. Widget _getTitleWidget(LoginModel model) {
  320. return Align(
  321. alignment: Alignment.centerLeft,
  322. child: Text(
  323. model?.mobile?.title ?? '您好,欢迎登录',
  324. style: TextStyle(color: HexColor.fromHex(model?.mobile?.titleColor ?? '#333333'), fontSize: 25),
  325. ));
  326. }
  327. /// 手机输入框
  328. Widget _getPhoneWidget(LoginModel model) {
  329. return _getCustomInputWidget(
  330. hint: model?.mobile?.inputMobileHintText ?? '请输入您的手机号',
  331. controller: _phoneEdController,
  332. focusNode: _phoneFN,
  333. onChanged: _onChange,
  334. hintColor: model?.mobile?.inputHintColor ?? '#999999',
  335. bgColor: model?.mobile?.inputBgColor ?? '#F7F7F7',
  336. textColor: model?.mobile?.inputTextColor ?? '#333333',
  337. iconUrl: model?.mobile?.inputMobileIcon ?? '');
  338. }
  339. /// 验证码输入框
  340. Widget _getVcodeWidget(LoginModel model) {
  341. return Container(
  342. height: 42,
  343. child: Stack(
  344. children: <Widget>[
  345. _getCustomInputWidget(
  346. controller: _vcodeEdController,
  347. focusNode: _vcodeFN,
  348. onChanged: _onChange,
  349. hintColor: model?.mobile?.inputHintColor ?? '#999999',
  350. hint: model?.mobile?.inputVcodeHintText ?? '请输入您的验证码',
  351. bgColor: model?.mobile?.inputBgColor ?? '#F7F7F7',
  352. textColor: model?.mobile?.inputTextColor ?? '#333333',
  353. iconUrl: model?.mobile?.inputVcodeIcon ?? ''),
  354. Align(alignment: Alignment.centerRight, child: _getVcodeButtonWidget(model)),
  355. ],
  356. ),
  357. );
  358. }
  359. /// 验证码按钮
  360. Widget _getVcodeButtonWidget(LoginModel model) {
  361. return VcodeWidget(
  362. onCallBack: this,
  363. awaitTime: int.parse(model?.mobile?.vcodeTime ?? '60'),
  364. btnAwaitText: '秒',
  365. btnText: '获取验证码',
  366. btnTextColor: model?.mobile?.btnVcodeTextColor ?? '#FFFFFF',
  367. color: model?.mobile?.btnVcodeBgColor ?? '#FF4343',
  368. disabledColor: model?.mobile?.btnVcodeBanBgColor ?? '#DDDDDD',
  369. disabledTextColor: model?.mobile?.btnVcodeBanTextColor ?? '#FFFFFF');
  370. }
  371. /// 第三方验证码输入框
  372. Widget _getOtherVcodeInputWidget(var model) {
  373. return Container(
  374. width: 240,
  375. height: 42,
  376. alignment: Alignment.centerLeft,
  377. child: SlideVerifyWidget(
  378. width: 240,
  379. ),
  380. // child: Row(
  381. // children: <Widget>[
  382. // // 输入框
  383. // Expanded(
  384. // child: _getCustomInputWidget(hint: '请输入右方验证码', hintColor: '#999999', textColor: '#333333', bgColor: '#F7F7F7', iconUrl: null, )
  385. // ),
  386. // // 第三方验证码
  387. // Container(
  388. // margin: const EdgeInsets.only(left: 5),
  389. // width: 100,
  390. // height: double.infinity,
  391. // decoration: BoxDecoration(
  392. // borderRadius: BorderRadius.circular(8),
  393. // color: Colors.red
  394. // ),
  395. // ),
  396. //
  397. // ],
  398. // ),
  399. );
  400. }
  401. /// 密码输入框
  402. Widget _getPassInputWidget(LoginModel model) {
  403. return Container(
  404. height: 42,
  405. child: _getCustomInputWidget(
  406. controller: _passEdController,
  407. focusNode: _passFN,
  408. onChanged: _onChange,
  409. hint: model?.mobile?.inputPassHintText ?? '请输入您的密码',
  410. iconUrl: model?.mobile?.inputPassIcon ?? '',
  411. hintColor: model?.mobile?.inputHintColor ?? '#999999',
  412. textColor: model?.mobile?.inputTextColor ?? '#333333',
  413. bgColor: model?.mobile?.inputBgColor ?? '#F7F7F7'),
  414. );
  415. }
  416. /// 切换登陆方式tip
  417. Widget _getChangeTipWidget(LoginModel model) {
  418. return GestureDetector(
  419. behavior: HitTestBehavior.opaque,
  420. onTap: () => _changeLoginTypeOnClick(),
  421. child: Text(
  422. _useVcode ? model?.mobile?.textUsePassTip ?? '使用密码登录' : model?.mobile?.textUseVcodeTip ?? '使用验证码登陆',
  423. style: TextStyle(fontSize: 12, color: HexColor.fromHex(_useVcode ? model?.mobile?.textUsePassTipColor ?? '#999999' : model?.mobile?.textUseVcodeTipColor ?? '#999999')),
  424. ),
  425. );
  426. }
  427. /// 登陆按钮
  428. Widget _getSubmiBtnWidget(LoginModel model) {
  429. return Material(
  430. child: Container(
  431. height: 52,
  432. width: double.infinity,
  433. color: Colors.white,
  434. child: RaisedButton(
  435. child: Text(
  436. model?.mobile?.btnLoginText ?? '立即登录',
  437. style: TextStyle(fontSize: 15),
  438. ),
  439. textColor: HexColor.fromHex(model?.mobile?.btnLoginTextColor ?? '#FFFFFF'),
  440. color: HexColor.fromHex(model?.mobile?.btnLoginBgColor ?? '#FF3939'),
  441. disabledColor: HexColor.fromHex(model?.mobile?.btnLoginBanBgColor ?? '#F5F5F5'),
  442. disabledTextColor: HexColor.fromHex(model?.mobile?.btnLoginBanTextColor ?? '#999999'),
  443. elevation: 5,
  444. shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(52 / 2)),
  445. onPressed: _canSubmit ? _submitOnClick : null,
  446. ),
  447. ),
  448. );
  449. }
  450. /// 协议
  451. Widget _getProtoclWidget(LoginModel model) {
  452. // return Text('同意《嗨如意用户协议》 及《营私政策》', style: TextStyle(fontSize: 11, color: HexColor.fromHex('#C0C0C0')));
  453. return Row(
  454. mainAxisAlignment: MainAxisAlignment.center,
  455. children: <Widget>[
  456. /// 图标
  457. GestureDetector(
  458. behavior: HitTestBehavior.opaque,
  459. onTap: () => _agreeOnClick(),
  460. child: Padding(
  461. padding: const EdgeInsets.all(8.0),
  462. child: CachedNetworkImage(
  463. imageUrl: _acceptAgreement ? model?.mobile?.protocolSelectIcon ?? '' : model?.mobile?.protocolUnselectIcon ?? '',
  464. width: 12,
  465. ))),
  466. /// 协议文字
  467. RichText(
  468. text: TextSpan(
  469. text: '',
  470. children: model.mobile.protocol.map((item) {
  471. return TextSpan(
  472. text: item?.text,
  473. style: TextStyle(color: HexColor.fromHex(item?.textColor), fontSize: 10),
  474. recognizer: TapGestureRecognizer()
  475. ..onTap = () {
  476. _openAgreement(item.url);
  477. });
  478. }).toList()),
  479. )
  480. ],
  481. );
  482. }
  483. /// 底部提示tip
  484. Widget _getBottomTipWidget(LoginModel model) {
  485. return Padding(
  486. padding: const EdgeInsets.only(bottom: 25),
  487. child: Text(
  488. model?.mobile?.textBottomTip ?? '未注册过的手机将自动注册',
  489. style: TextStyle(fontSize: 11, color: HexColor.fromHex(model?.mobile?.textBottomTipColor ?? '#999999')),
  490. ),
  491. );
  492. }
  493. /// 自定义输入框
  494. Widget _getCustomInputWidget(
  495. {String hint, String hintColor, String bgColor, String textColor, String iconUrl, TextEditingController controller, ValueChanged<String> onChanged, FocusNode focusNode}) {
  496. var border = OutlineInputBorder(borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: HexColor.fromHex(bgColor), width: 0));
  497. return Container(
  498. height: 42,
  499. padding: const EdgeInsets.symmetric(horizontal: 15),
  500. decoration: BoxDecoration(
  501. color: HexColor.fromHex(bgColor),
  502. borderRadius: BorderRadius.circular(8),
  503. ),
  504. child: Row(
  505. mainAxisAlignment: MainAxisAlignment.start,
  506. crossAxisAlignment: CrossAxisAlignment.center,
  507. children: <Widget>[
  508. CachedNetworkImage(
  509. imageUrl: iconUrl ?? '',
  510. width: 10,
  511. ),
  512. Expanded(
  513. child: TextField(
  514. controller: controller,
  515. focusNode: focusNode,
  516. onChanged: onChanged,
  517. expands: false,
  518. style: TextStyle(color: HexColor.fromHex(textColor)),
  519. maxLines: 1,
  520. keyboardType: TextInputType.number,
  521. decoration: InputDecoration(
  522. contentPadding: EdgeInsets.only(top: 30, left: 7.5),
  523. hintText: hint,
  524. hintStyle: TextStyle(fontSize: 13, color: HexColor.fromHex(hintColor)),
  525. hintMaxLines: 1,
  526. filled: true,
  527. fillColor: Colors.transparent,
  528. border: border,
  529. focusedBorder: border,
  530. enabledBorder: border,
  531. disabledBorder: border,
  532. errorBorder: border,
  533. focusedErrorBorder: border,
  534. ),
  535. ),
  536. ),
  537. ],
  538. ),
  539. );
  540. }
  541. }