基础库
 
 
 
 
 

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