From 5f7597f47d5d4e09b33ad8e8e627ecedeb27c8e1 Mon Sep 17 00:00:00 2001 From: PH2 <1293456824@qq.com> Date: Tue, 15 Sep 2020 14:32:50 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81login=E6=A8=A1=E5=9D=97=E7=A7=BB?= =?UTF-8?q?=E5=8A=A8=E5=88=B0comm=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/login_page.dart | 13 - lib/pages/login_page/account/bloc/bloc.dart | 3 + .../account/bloc/login_account_bloc.dart | 63 ++ .../account/bloc/login_account_event.dart | 48 ++ .../bloc/login_account_repository.dart | 59 ++ .../account/bloc/login_account_state.dart | 61 ++ .../account/login_account_page.dart | 589 ++++++++++++++++++ .../login_page/account/login_account_sk.dart | 69 ++ .../account/widget/slide_verify_widget.dart | 191 ++++++ .../account/widget/vcode_widget.dart | 121 ++++ lib/pages/login_page/bloc/bloc.dart | 3 + lib/pages/login_page/bloc/login_bloc.dart | 38 ++ lib/pages/login_page/bloc/login_event.dart | 10 + .../login_page/bloc/login_repository.dart | 26 + lib/pages/login_page/bloc/login_state.dart | 31 + lib/pages/login_page/invite/bloc/bloc.dart | 3 + .../invite/bloc/login_invite_bloc.dart | 61 ++ .../invite/bloc/login_invite_event.dart | 29 + .../invite/bloc/login_invite_repository.dart | 48 ++ .../invite/bloc/login_invite_state.dart | 76 +++ .../login_page/invite/login_invite_page.dart | 341 ++++++++++ .../invite/model/login_invite_user.dart | 30 + lib/pages/login_page/login_page.dart | 297 +++++++++ lib/pages/login_page/login_page_sk.dart | 87 +++ lib/pages/login_page/login_util.dart | 53 ++ lib/pages/login_page/model/login_model.dart | 561 +++++++++++++++++ .../login_page/quick/login_quick_page.dart | 171 +++++ lib/util/router_util.dart | 16 +- pubspec.yaml | 7 +- 29 files changed, 3081 insertions(+), 24 deletions(-) delete mode 100644 lib/pages/login_page.dart create mode 100644 lib/pages/login_page/account/bloc/bloc.dart create mode 100644 lib/pages/login_page/account/bloc/login_account_bloc.dart create mode 100644 lib/pages/login_page/account/bloc/login_account_event.dart create mode 100644 lib/pages/login_page/account/bloc/login_account_repository.dart create mode 100644 lib/pages/login_page/account/bloc/login_account_state.dart create mode 100644 lib/pages/login_page/account/login_account_page.dart create mode 100644 lib/pages/login_page/account/login_account_sk.dart create mode 100644 lib/pages/login_page/account/widget/slide_verify_widget.dart create mode 100644 lib/pages/login_page/account/widget/vcode_widget.dart create mode 100644 lib/pages/login_page/bloc/bloc.dart create mode 100644 lib/pages/login_page/bloc/login_bloc.dart create mode 100644 lib/pages/login_page/bloc/login_event.dart create mode 100644 lib/pages/login_page/bloc/login_repository.dart create mode 100644 lib/pages/login_page/bloc/login_state.dart create mode 100644 lib/pages/login_page/invite/bloc/bloc.dart create mode 100644 lib/pages/login_page/invite/bloc/login_invite_bloc.dart create mode 100644 lib/pages/login_page/invite/bloc/login_invite_event.dart create mode 100644 lib/pages/login_page/invite/bloc/login_invite_repository.dart create mode 100644 lib/pages/login_page/invite/bloc/login_invite_state.dart create mode 100644 lib/pages/login_page/invite/login_invite_page.dart create mode 100644 lib/pages/login_page/invite/model/login_invite_user.dart create mode 100644 lib/pages/login_page/login_page.dart create mode 100644 lib/pages/login_page/login_page_sk.dart create mode 100644 lib/pages/login_page/login_util.dart create mode 100644 lib/pages/login_page/model/login_model.dart create mode 100644 lib/pages/login_page/quick/login_quick_page.dart diff --git a/lib/pages/login_page.dart b/lib/pages/login_page.dart deleted file mode 100644 index db53dc1..0000000 --- a/lib/pages/login_page.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:flutter/material.dart'; - -class LoginPage extends StatefulWidget { - @override - _LoginPageState createState() => _LoginPageState(); -} - -class _LoginPageState extends State { - @override - Widget build(BuildContext context) { - return Material(child: Center(child: Text('登录页'),),); - } -} diff --git a/lib/pages/login_page/account/bloc/bloc.dart b/lib/pages/login_page/account/bloc/bloc.dart new file mode 100644 index 0000000..2c754e9 --- /dev/null +++ b/lib/pages/login_page/account/bloc/bloc.dart @@ -0,0 +1,3 @@ +export 'login_account_bloc.dart'; +export 'login_account_event.dart'; +export 'login_account_state.dart'; \ No newline at end of file diff --git a/lib/pages/login_page/account/bloc/login_account_bloc.dart b/lib/pages/login_page/account/bloc/login_account_bloc.dart new file mode 100644 index 0000000..7e1e039 --- /dev/null +++ b/lib/pages/login_page/account/bloc/login_account_bloc.dart @@ -0,0 +1,63 @@ +import 'dart:async'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:zhiying_comm/util/empty_util.dart'; +import './bloc.dart'; +import 'login_account_repository.dart'; + +class LoginAccountBloc extends Bloc { + LoginAccountRepository repository; + + LoginAccountBloc({@required this.repository}); + + @override + LoginAccountState get initialState => LoginAccountInitial(); + + @override + Stream mapEventToState(LoginAccountEvent event) async* { + /// 获取数据 + if (event is LoginAccountInitEvent) { + yield* _mapInitEventToState(); + } + + /// 获取验证码 + if (event is LoginAccountGetVcodeEvent) { + yield* _mapGetVcodeEventToState(event); + } + + /// 登陆(验证码) + if (event is LoginAccountTypeVcodeEvent) { + yield* _mapLoginTypeVcodeEventToState(event); + } + } + + /// 获取数据 + Stream _mapInitEventToState() async* { + var data = await repository.fetchData(); + if (!EmptyUtil.isEmpty(data)) + yield LoginAccountLoadedState(model: data); + else + yield LoginAccountErrorState(); + } + + /// 获取验证码 + Stream _mapGetVcodeEventToState(LoginAccountGetVcodeEvent event) async* { + bool result = await repository.fetchGetVcode(event); + if (result) + yield LoginAccountGetVcodeSuccessState(); + else + yield LoginAccountGetVcodeErrorState(); + } + + /// 验证码登陆 + Stream _mapLoginTypeVcodeEventToState(LoginAccountTypeVcodeEvent event) async* { + var result = await repository.loginTypeVcode(event); + if (!EmptyUtil.isEmpty(result)) + yield LoginAccountTypeVcodeLoginSuccessState(model: result); + else + yield LoginAccountTypeVcodeLoginErrorState(); + } + + /// 密码登陆 + Stream _mapLoginTypePassEventToState(LoginAccountTypePasswordEvent event) async* {} +} diff --git a/lib/pages/login_page/account/bloc/login_account_event.dart b/lib/pages/login_page/account/bloc/login_account_event.dart new file mode 100644 index 0000000..b0e6d3b --- /dev/null +++ b/lib/pages/login_page/account/bloc/login_account_event.dart @@ -0,0 +1,48 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/cupertino.dart'; + +abstract class LoginAccountEvent extends Equatable { + const LoginAccountEvent(); + + @override + List get props => []; +} + +/// 初始化事件 +class LoginAccountInitEvent extends LoginAccountEvent {} + + +/// 获取验证码 +class LoginAccountGetVcodeEvent extends LoginAccountEvent{ + final String mobile; + const LoginAccountGetVcodeEvent({@required this.mobile}); + @override + List get props => [mobile]; +} + +/// 核对验证码 +class LoginAccountCheckVcodeEvent extends LoginAccountEvent{ + /// 手机号码 + final String mobile; + /// 验证码 + final String captcha; + const LoginAccountCheckVcodeEvent({@required this.mobile, @required this.captcha}); + @override + List get props => [this.mobile, this.captcha]; +} + +/// 登陆事件(密码登陆) +class LoginAccountTypePasswordEvent extends LoginAccountEvent{ + final String username; + final String password; + const LoginAccountTypePasswordEvent({@required this.username, @required this.password}); +} + +/// 登陆事件(验证码登陆) +class LoginAccountTypeVcodeEvent extends LoginAccountEvent{ + final String mobile; + final String captcha; + const LoginAccountTypeVcodeEvent({@required this.mobile, @required this.captcha}); + @override + List get props => [this.mobile, this.captcha]; +} \ No newline at end of file diff --git a/lib/pages/login_page/account/bloc/login_account_repository.dart b/lib/pages/login_page/account/bloc/login_account_repository.dart new file mode 100644 index 0000000..38e8285 --- /dev/null +++ b/lib/pages/login_page/account/bloc/login_account_repository.dart @@ -0,0 +1,59 @@ +import 'package:zhiying_comm/pages/login_page/model/login_model.dart'; +import 'package:zhiying_comm/util/empty_util.dart'; +import 'package:zhiying_comm/util/global_config.dart'; +import 'package:zhiying_comm/util/net_util.dart'; +import 'package:zhiying_comm/models/user/user_info_model.dart'; + +import '../../login_util.dart'; +import 'bloc.dart'; + + +/// +/// 账户登陆 +/// +class LoginAccountRepository{ + + /// 获取数据,如果缓存有,则获取缓存的数据 + Future fetchData() async{ + var result = await LoginUtil.getLoginModel(); + if(!EmptyUtil.isEmpty(result)){ + return result; + } + return null; + } + + /// 获取验证码 + Future fetchGetVcode(LoginAccountGetVcodeEvent event) async{ + print('mobile = ${event.mobile}'); + var result = await NetUtil.post('/api/v1/sign/sms/fast/in', params: {'mobile': event.mobile}); + if(NetUtil.isSuccess(result)){ + return true; + } + return false; + } + + /// 密码登陆 + Future loginTypePass(LoginAccountTypePasswordEvent event) async{ + // var result = await NetUtil.post('/api/v1/sign/in', params: {'username'}); + } + + /// 验证码登陆 + Future loginTypeVcode(LoginAccountTypeVcodeEvent event) async{ + var result = await NetUtil.post('/api/v1/sign/fast/in', params: {'mobile': event.mobile, 'captcha': event.captcha}); + if(NetUtil.isSuccess(result) && !EmptyUtil.isEmpty(result[GlobalConfig.HTTP_RESPONSE_KEY_DATA])){ + UserInfoModel loginUser = UserInfoModel.fromJson(result[GlobalConfig.HTTP_RESPONSE_KEY_DATA]); + if(null != loginUser){ + loginUser.mobile = event.mobile; + return loginUser; + } + return null; + } + return null; + } + + /// 检查验证码 + Future checkVcode(LoginAccountCheckVcodeEvent event) async{ + + } + +} \ No newline at end of file diff --git a/lib/pages/login_page/account/bloc/login_account_state.dart b/lib/pages/login_page/account/bloc/login_account_state.dart new file mode 100644 index 0000000..a98673a --- /dev/null +++ b/lib/pages/login_page/account/bloc/login_account_state.dart @@ -0,0 +1,61 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:zhiying_comm/models/user/user_info_model.dart'; +import 'package:zhiying_comm/pages/login_page/model/login_model.dart'; + +abstract class LoginAccountState extends Equatable { + const LoginAccountState(); + + @override + List get props => []; +} + +/// 初始化状态 +class LoginAccountInitial extends LoginAccountState {} + +/// 数据加载完毕状态 +class LoginAccountLoadedState extends LoginAccountState { + final LoginModel model; + + const LoginAccountLoadedState({@required this.model}); + + @override + List get props => [this.model]; +} + +/// 数据加载出错状态 +class LoginAccountErrorState extends LoginAccountState {} + +/// 验证码下发成功的状态 +class LoginAccountGetVcodeSuccessState extends LoginAccountState { + final String msg; + + const LoginAccountGetVcodeSuccessState({this.msg}); + + @override + List get props => [this.msg]; +} + +/// 验证码下发失败的状态 +class LoginAccountGetVcodeErrorState extends LoginAccountState { + final String msg; + + const LoginAccountGetVcodeErrorState({this.msg}); +} + +/// 验证码登陆成功 +class LoginAccountTypeVcodeLoginSuccessState extends LoginAccountState { + final UserInfoModel model; + + const LoginAccountTypeVcodeLoginSuccessState({@required this.model}); + + @override + List get props => [this.model]; +} + +/// 验证码登陆失败 +class LoginAccountTypeVcodeLoginErrorState extends LoginAccountState { + final String msg; + + const LoginAccountTypeVcodeLoginErrorState({this.msg}); +} diff --git a/lib/pages/login_page/account/login_account_page.dart b/lib/pages/login_page/account/login_account_page.dart new file mode 100644 index 0000000..426789e --- /dev/null +++ b/lib/pages/login_page/account/login_account_page.dart @@ -0,0 +1,589 @@ +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, MaterialPageRoute( + // 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('切换登陆'); + + setState(() { + _useVcode = !_useVcode; + }); + // 清除缓存 + if (_useVcode) { + _passEdController?.clear(); + _passFN?.unfocus(); + } else { + _vcodeEdController?.clear(); + _vcodeFN?.unfocus(); + } + } + + /// 同意协议 + 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 == '0') { + 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: _passFN, + 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, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/login_page/account/login_account_sk.dart b/lib/pages/login_page/account/login_account_sk.dart new file mode 100644 index 0000000..cf14f96 --- /dev/null +++ b/lib/pages/login_page/account/login_account_sk.dart @@ -0,0 +1,69 @@ +import 'package:shimmer/shimmer.dart'; +import 'package:flutter/material.dart'; +import 'package:zhiying_comm/util/color.dart'; + +/// +/// 登陆页面的骨架屏 +/// +class LoginAccountSkeleton extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: HexColor.fromHex('#FFFFFF'), + elevation: 0, + title: Text( + '登录', + style: TextStyle(color: HexColor.fromHex('#333333')), + ), + centerTitle: true, + leading: IconButton( + icon: Icon( + Icons.arrow_back_ios, + size: 22, + color: HexColor.fromHex('#333333'), + ), + onPressed: () {}, + ), + ), + body: Container( + padding: const EdgeInsets.symmetric(horizontal: 27.5), + width: double.infinity, + height: double.infinity, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /// 标题 + Padding( padding: const EdgeInsets.only(top: 40), child: _shimmerWidget(width: 175, height: 20),), + Padding( padding: const EdgeInsets.only(top: 30), child: _shimmerWidget(width: 320, height: 42),), + Padding( padding: const EdgeInsets.only(top: 15), child: _shimmerWidget(width: 320, height: 42),), + Align( alignment: Alignment.center, child: Padding( padding: const EdgeInsets.only(top: 15), child: _shimmerWidget(width: 72, height: 13),)), + Padding( padding: const EdgeInsets.only(top: 30), child: _shimmerWidget(width: 320, height: 52.7, radius: 30),), + Align( alignment: Alignment.center, child: Padding( padding: const EdgeInsets.only(top: 12.5), child: _shimmerWidget(width: 220, height: 15),)), + Expanded( + child: Align( + alignment: Alignment.bottomCenter, + child: Padding( + padding: const EdgeInsets.only(bottom: 25), + child: _shimmerWidget(width: 132, height: 15), + ), + ), + ) + ], + ), + ), + ); + } + + Widget _shimmerWidget({double width, double height, double radius = 0}) { + return Shimmer.fromColors( + baseColor: Colors.grey[300], + highlightColor: Colors.grey[100], + child: Container( + width: width, + height: height, + decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(radius)), + ), + ); + } +} diff --git a/lib/pages/login_page/account/widget/slide_verify_widget.dart b/lib/pages/login_page/account/widget/slide_verify_widget.dart new file mode 100644 index 0000000..7dc0ddc --- /dev/null +++ b/lib/pages/login_page/account/widget/slide_verify_widget.dart @@ -0,0 +1,191 @@ +import 'package:flutter/material.dart'; + +class SlideVerifyWidget extends StatefulWidget{ + + /// 背景色 + final Color backgroundColor; + /// 滑动过的颜色 + final Color slideColor; + /// 边框颜色 + final Color borderColor; + + final double height; + final double width; + + final VoidCallback verifySuccessListener; + + const SlideVerifyWidget({ + Key key, + this.backgroundColor = Colors.blueGrey, + this.slideColor = Colors.green, + this.borderColor = Colors.grey, + this.height = 44, + this.width = 240, + this.verifySuccessListener + }) : super(key: key); + + @override + State createState() { + return SlideVerifyState(); + } + +} + +class SlideVerifyState extends State with TickerProviderStateMixin{ + + double height; + double width ; + + double sliderDistance = 0; + + double initial = 0.0; + + /// 滑动块宽度 + double sliderWidth = 64; + + /// 验证是否通过,滑动到最右方为通过 + bool verifySuccess = false; + + /// 是否允许拖动 + bool enableSlide = true; + + AnimationController _animationController; + Animation _curve; + + @override + void initState() { + super.initState(); + this.width = widget.width; + this.height = widget.height; + _initAnimation(); + } + + @override + void dispose() { + _animationController?.dispose(); + super.dispose(); + } + + + @override + Widget build(BuildContext context) { + return GestureDetector( + onHorizontalDragStart: (DragStartDetails details){ + if(!enableSlide){ + return; + } + initial = details.globalPosition.dx; + }, + onHorizontalDragUpdate: (DragUpdateDetails details){ + if(!enableSlide){ + return; + } + sliderDistance = details.globalPosition.dx - initial; + if(sliderDistance < 0){ + sliderDistance = 0; + } + /// 当滑动到最右边时,通知验证成功,并禁止滑动 + if(sliderDistance > width - sliderWidth){ + sliderDistance = width - sliderWidth; + enableSlide = false; + verifySuccess = true; + if(widget.verifySuccessListener != null){ + widget.verifySuccessListener(); + } + } + setState(() { + }); + }, + onHorizontalDragEnd: (DragEndDetails details){ + /// 滑动松开时,如果未达到最右边,启动回弹动画 + if(enableSlide){ + enableSlide = false; + _animationController.forward(); + } + }, + child: Container( + height: height, + width: width, + decoration: BoxDecoration( + color: widget.backgroundColor, + border: Border.all(color: widget.borderColor), + /// 圆角实现 + borderRadius: BorderRadius.all(new Radius.circular(height)) + ), + child: Stack( + children: [ + Positioned( + top: 0, + left: 0, + child: Container( + height: height - 2, + /// 当slider滑动到距左边只有两三像素距离时,已滑动背景会有一点点渲染出边框范围, + /// 因此当滑动距离小于1时,直接将宽度设置为0,解决滑动块返回左边时导致的绿色闪动,但如果是缓慢滑动到左边该问题仍没解决 + width: sliderDistance < 1? 0 : sliderDistance + sliderWidth / 2, + decoration: BoxDecoration( + color: widget.slideColor, + /// 圆角实现 + borderRadius: BorderRadius.all(new Radius.circular(height / 2)) + ), + ), + ), + Center( + child: Text(verifySuccess?"验证成功":"请按住滑块,拖动到最右边", style: TextStyle(color: verifySuccess?Colors.white:Colors.black54, fontSize: 14),), + ), + Positioned( + top: 0, + /// 此处将sliderDistance距离往左偏2是解决当滑动块滑动到最右边时遮挡外部边框 + left: sliderDistance > sliderWidth ? sliderDistance - 2 : sliderDistance, + child: Container( + width: sliderWidth, + height: height - 2 , + alignment: Alignment.center, + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: widget.borderColor), + /// 圆角实现 + borderRadius: BorderRadius.all(new Radius.circular(height)) + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(width: 6,), + Image.asset("assets/images/ic_safety.png", height: 24, width: 24,), + Image.asset("assets/images/ic_next_primary.png", height: 16, width: 16,), + /// 因为向右箭头有透明边距导致两个箭头间隔过大,因此将第二个箭头向左偏移,如果切图无边距则不用偏移 + Transform( + transform: Matrix4.translationValues(-8, 0, 0), + child: Image.asset("assets/images/ic_next_primary.png", height: 16, width: 16,), + ), + ], + ), + ), + ) + ], + ), + ), + ); + } + + /// 回弹动画 + void _initAnimation(){ + _animationController = AnimationController( + duration: const Duration(milliseconds: 300), vsync: this); + _curve = CurvedAnimation(parent: _animationController, curve: Curves.easeOut); + _curve.addListener(() { + setState(() { + sliderDistance = sliderDistance - sliderDistance * _curve.value; + if(sliderDistance <= 0){ + sliderDistance = 0; + } + }); + }); + _animationController.addStatusListener((status) { + if(status == AnimationStatus.completed){ + enableSlide = true; + _animationController.reset(); + } + }); + } + +} diff --git a/lib/pages/login_page/account/widget/vcode_widget.dart b/lib/pages/login_page/account/widget/vcode_widget.dart new file mode 100644 index 0000000..8552f5d --- /dev/null +++ b/lib/pages/login_page/account/widget/vcode_widget.dart @@ -0,0 +1,121 @@ + +import 'dart:async'; + +import 'package:zhiying_comm/zhiying_comm.dart'; +import 'package:flutter/material.dart'; + + +class VcodeWidget extends StatefulWidget { + // 按钮文字 + final String btnText; + // 按钮等待文字 + final String btnAwaitText; + // 按钮文字的颜色 + final String btnTextColor; + // 按钮的背景颜色 + final String color; + // 禁用文字的颜色 + final String disabledTextColor; + // 禁用按钮的背景颜色 + final String disabledColor; + // 等待时长 + final int awaitTime; + // 高 + final double height; + final double width; + final double border; + final OnClickListener onCallBack; + + + const VcodeWidget({ + this.btnText, + this.btnAwaitText, + this.btnTextColor, + this.color, + this.disabledTextColor, + this.disabledColor, + this.awaitTime, + this.onCallBack, + this.border = 8, + this.height = 30, + this.width = 82}); + + @override + _VcodeWidgetState createState() => _VcodeWidgetState(); +} + +class _VcodeWidgetState extends State { + + /// 是否可以获取验证码 + bool _canGetVcode = true; + /// 倒计时 + Timer _timer; + int _countdownTime = 0; + + /// 获取验证码 + void _getVcodeOnClick() { + // print('获取验证码'); + if(widget.onCallBack.onVcodeClick()) { + int count = widget?.awaitTime ?? 60; + startCountdownTimer(count); + } + } + + /// 开始倒计时 + void startCountdownTimer(int countTime) { + const oneSec = const Duration(seconds: 1); + setState(() { + _canGetVcode = false; + _countdownTime = countTime; + }); + + var callback = (timer) => { + setState(() { + if (_countdownTime < 1) { + _canGetVcode = true; + _timer.cancel(); + _countdownTime = countTime; // 重置倒计时 + } else { + _countdownTime = _countdownTime - 1; + _canGetVcode = false; + } + }) + }; + + _timer = Timer.periodic(oneSec, callback); + } + + @override + void dispose() { + _timer?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Material( + child: Container( + alignment: Alignment.center, + height: widget?.height ?? 30, + width: widget?.width ?? 82, + margin: const EdgeInsets.only(right: 6), + child: RaisedButton( + onPressed: _canGetVcode ? ()=> _getVcodeOnClick() : null, + padding: EdgeInsets.zero, + child: Text(_canGetVcode ? ( widget?.btnText ?? '获取验证码') : '${_countdownTime}${widget?.btnAwaitText ?? '秒'}', + style: TextStyle(color: HexColor.fromHex( widget?.btnTextColor ?? '#FFFFFF'), fontSize: 11)), + disabledTextColor: HexColor.fromHex( widget?.disabledTextColor ?? '#FFFFFF'), + disabledColor: HexColor.fromHex( widget?.disabledColor ?? '#DDDDDD'), + color: HexColor.fromHex( widget?.color ?? '#FF4343'), + textColor: HexColor.fromHex( widget?.btnTextColor ?? '#FFFFFF'), + elevation: 4, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(widget?.border ?? 8)), + ), + ), + ); + } +} + +abstract class OnClickListener{ + bool onVcodeClick(); +} diff --git a/lib/pages/login_page/bloc/bloc.dart b/lib/pages/login_page/bloc/bloc.dart new file mode 100644 index 0000000..9111e4e --- /dev/null +++ b/lib/pages/login_page/bloc/bloc.dart @@ -0,0 +1,3 @@ +export 'login_bloc.dart'; +export 'login_event.dart'; +export 'login_state.dart'; \ No newline at end of file diff --git a/lib/pages/login_page/bloc/login_bloc.dart b/lib/pages/login_page/bloc/login_bloc.dart new file mode 100644 index 0000000..c1da81a --- /dev/null +++ b/lib/pages/login_page/bloc/login_bloc.dart @@ -0,0 +1,38 @@ +import 'dart:async'; +import 'package:bloc/bloc.dart'; +import './bloc.dart'; +import 'login_repository.dart'; +import 'package:zhiying_comm/util/empty_util.dart'; + +class LoginBloc extends Bloc { + LoginRepository repository; + + LoginBloc({this.repository}); + + @override + LoginState get initialState => InitialLoginState(); + + @override + Stream mapEventToState( + LoginEvent event, + ) async* { + // TODO: Add Logic + final currentState = state; + + /// 初始化 + if (event is LoginInitEvent) { + yield* _mapLoginInitEventToState(event); + } + } + + /// 获取页面数据 + Stream _mapLoginInitEventToState(LoginInitEvent event) async* { + var cache = await repository.fetchCachePageData(); + if (!EmptyUtil.isEmpty(cache)) yield LoginCacheState( model: cache); + var result = await repository.fetchNetPageData(); + if (!EmptyUtil.isEmpty(result)) + yield LoginLoadedState(model: result); + else + yield LoginErrorState(); + } +} diff --git a/lib/pages/login_page/bloc/login_event.dart b/lib/pages/login_page/bloc/login_event.dart new file mode 100644 index 0000000..7883b15 --- /dev/null +++ b/lib/pages/login_page/bloc/login_event.dart @@ -0,0 +1,10 @@ +import 'package:equatable/equatable.dart'; + +abstract class LoginEvent extends Equatable { + const LoginEvent(); + @override + List get props => []; +} + +/// 初始化 +class LoginInitEvent extends LoginEvent{} diff --git a/lib/pages/login_page/bloc/login_repository.dart b/lib/pages/login_page/bloc/login_repository.dart new file mode 100644 index 0000000..01ae60a --- /dev/null +++ b/lib/pages/login_page/bloc/login_repository.dart @@ -0,0 +1,26 @@ +import 'package:zhiying_comm/pages/login_page/model/login_model.dart'; +import '../login_util.dart'; + +class LoginRepository { + /// 获取页面数据 + Future fetchNetPageData() async { + // var result = await NetUtil.post('/api/v1/sign/in', method: NetMethod.GET, cache: true); + // if (NetUtil.isSuccess(result) && !EmptyUtil.isEmpty(result[GlobalConfig.HTTP_RESPONSE_KEY_DATA])) { + // LoginModel model = LoginModel.fromJson(result[GlobalConfig.HTTP_RESPONSE_KEY_DATA]); + // return model; + // } + // return null; + return await LoginUtil.fetchNetPageData(); + } + + /// 获取缓存的页面数据 + Future fetchCachePageData() async { + // var result = await NetUtil.getRequestCachedData('/api/v1/sign/in'); + // if (!EmptyUtil.isEmpty(result)) { + // LoginModel model = LoginModel.fromJson(result); + // return model; + // } + // return null; + return await LoginUtil.fetchCachePageData(); + } +} diff --git a/lib/pages/login_page/bloc/login_state.dart b/lib/pages/login_page/bloc/login_state.dart new file mode 100644 index 0000000..2fd5158 --- /dev/null +++ b/lib/pages/login_page/bloc/login_state.dart @@ -0,0 +1,31 @@ +import 'package:equatable/equatable.dart'; +import 'package:zhiying_comm/pages/login_page/model/login_model.dart'; + +abstract class LoginState extends Equatable { + const LoginState(); + + @override + List get props => []; +} + +/// 初始化状态 +class InitialLoginState extends LoginState {} + +/// 缓存数据状态 +class LoginCacheState extends LoginState { + final LoginModel model; + const LoginCacheState({this.model}); + @override + List get props => [this.model]; +} + +/// 数据加载完毕状态 +class LoginLoadedState extends LoginState { + final LoginModel model; + const LoginLoadedState({this.model}); + @override + List get props => [this.model]; +} + +/// 数据加载出错 +class LoginErrorState extends LoginState {} diff --git a/lib/pages/login_page/invite/bloc/bloc.dart b/lib/pages/login_page/invite/bloc/bloc.dart new file mode 100644 index 0000000..d951fca --- /dev/null +++ b/lib/pages/login_page/invite/bloc/bloc.dart @@ -0,0 +1,3 @@ +export 'login_invite_bloc.dart'; +export 'login_invite_event.dart'; +export 'login_invite_state.dart'; \ No newline at end of file diff --git a/lib/pages/login_page/invite/bloc/login_invite_bloc.dart b/lib/pages/login_page/invite/bloc/login_invite_bloc.dart new file mode 100644 index 0000000..6a48db1 --- /dev/null +++ b/lib/pages/login_page/invite/bloc/login_invite_bloc.dart @@ -0,0 +1,61 @@ +import 'dart:async'; +import 'package:bloc/bloc.dart'; +import 'package:flutter/cupertino.dart'; +import './bloc.dart'; +import 'package:zhiying_comm/util/empty_util.dart'; +import 'login_invite_repository.dart'; + +class LoginInviteBloc extends Bloc { + LoginInviteRepository repostitory; + + LoginInviteBloc({@required this.repostitory}); + + @override + LoginInviteState get initialState => LoginInviteInitial(); + + @override + Stream mapEventToState( + LoginInviteEvent event, + ) async* { + /// 初始化 + if (event is LoginInviteInitEvent) { + yield* _mapInitEventToState(event); + } + + /// 查询邀请人 + if (event is LoginInviteQueryEvent) { + yield* _mapQueryEventToState(event); + } + + /// 提交 + if (event is LoginInviteSubmitEvent) { + yield* _mapSubmitEventToState(event); + } + } + + /// 获取页面数据 + Stream _mapInitEventToState(LoginInviteInitEvent event) async* { + var data = await repostitory.fetchData(); + if (!EmptyUtil.isEmpty(data)) { + yield LoginInviteLoadedState(model: data); + } + } + + /// 查询邀请人 + Stream _mapQueryEventToState(LoginInviteQueryEvent event) async* { + var data = await repostitory.fetchInviteUserInfo(event); + if (!EmptyUtil.isEmpty(data)) + yield LoginInviteQuerySuccessState(model: data, pageMdel: repostitory?.pageModel); + else + yield LoginInviteQueryErrorState(); + } + + /// 提交 + Stream _mapSubmitEventToState(LoginInviteSubmitEvent event) async* { + var data = await repostitory.submitInvite(event); + if (!EmptyUtil.isEmpty(data)) + yield LoginInviteSubmitSuccess(model: data); + else + yield LoginInviteSubmitErrorState(); + } +} diff --git a/lib/pages/login_page/invite/bloc/login_invite_event.dart b/lib/pages/login_page/invite/bloc/login_invite_event.dart new file mode 100644 index 0000000..7de68a9 --- /dev/null +++ b/lib/pages/login_page/invite/bloc/login_invite_event.dart @@ -0,0 +1,29 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/cupertino.dart'; + +abstract class LoginInviteEvent extends Equatable { + const LoginInviteEvent(); + @override + List get props => []; +} + +/// 初始化 +class LoginInviteInitEvent extends LoginInviteEvent {} + +/// 查询邀请人方法 +class LoginInviteQueryEvent extends LoginInviteEvent { + final String num; + const LoginInviteQueryEvent({@required this.num}); + @override + List get props => [this.num]; +} + +/// 提交方法 +class LoginInviteSubmitEvent extends LoginInviteEvent { + final String num; + final String mobile; + const LoginInviteSubmitEvent({@required this.num, @required this.mobile}); + + @override + List get props => [this.num, this.mobile]; +} diff --git a/lib/pages/login_page/invite/bloc/login_invite_repository.dart b/lib/pages/login_page/invite/bloc/login_invite_repository.dart new file mode 100644 index 0000000..4e70c8a --- /dev/null +++ b/lib/pages/login_page/invite/bloc/login_invite_repository.dart @@ -0,0 +1,48 @@ +import 'package:zhiying_comm/pages/login_page/invite/model/login_invite_user.dart'; +import 'package:zhiying_comm/pages/login_page/model/login_model.dart'; +import 'package:zhiying_comm/util/empty_util.dart'; +import 'package:zhiying_comm/util/net_util.dart'; +import 'package:zhiying_comm/util/global_config.dart'; +import 'package:zhiying_comm/models/user/user_info_model.dart'; +import '../../login_util.dart'; +import 'bloc.dart'; + +class LoginInviteRepository { + LoginModel pageModel; + + /// 获取数据,如果缓存有,则获取缓存的数据 + Future fetchData() async { + pageModel = await LoginUtil.getLoginModel(); + if (!EmptyUtil.isEmpty(pageModel)) { + return pageModel; + } + return null; + } + + /// 获取邀请人信息 + Future fetchInviteUserInfo(LoginInviteQueryEvent event) async { + var result = await NetUtil.post('/api/v1/user/invite/${event.num}', method: NetMethod.GET); + if (NetUtil.isSuccess(result) && !EmptyUtil.isEmpty(result[GlobalConfig.HTTP_RESPONSE_KEY_DATA])) { + LoginInviteUser model = LoginInviteUser.fromJson(result[GlobalConfig.HTTP_RESPONSE_KEY_DATA]); + if(null != model) + return model; + else return null; + } else { + return null; + } + } + + /// 提交 + Future submitInvite(LoginInviteSubmitEvent event) async { + var result = await NetUtil.post('/api/v1/user/invite/ack', params: {'mobile': event.mobile, 'parent_uid': event.num}); + if (NetUtil.isSuccess(result) && !EmptyUtil.isEmpty(result[GlobalConfig.HTTP_RESPONSE_KEY_DATA])) { + UserInfoModel model = UserInfoModel.fromJson(result[GlobalConfig.HTTP_RESPONSE_KEY_DATA]); + if (null != model) { + model.mobile = event.mobile; + return model; + } + return null; + } + return null; + } +} diff --git a/lib/pages/login_page/invite/bloc/login_invite_state.dart b/lib/pages/login_page/invite/bloc/login_invite_state.dart new file mode 100644 index 0000000..241f18d --- /dev/null +++ b/lib/pages/login_page/invite/bloc/login_invite_state.dart @@ -0,0 +1,76 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:zhiying_comm/models/user/user_info_model.dart'; +import 'package:zhiying_comm/pages/login_page/invite/model/login_invite_user.dart'; +import 'package:zhiying_comm/pages/login_page/model/login_model.dart'; + +abstract class LoginInviteState extends Equatable { + const LoginInviteState(); + + @override + List get props => []; +} + +/// 初始化状态 +class LoginInviteInitial extends LoginInviteState {} + +/// 数据加载完毕状态 +class LoginInviteLoadedState extends LoginInviteState { + final LoginModel model; + + const LoginInviteLoadedState({@required this.model}); + + @override + List get props => [this.model]; +} + +/// 数据加载出错 +class LoginInviteErrorState extends LoginInviteState { + final String msg; + + const LoginInviteErrorState({this.msg}); + + @override + List get props => [this.msg]; +} + +/// 邀请码成功 +class LoginInviteSubmitSuccess extends LoginInviteState { + final UserInfoModel model; + + const LoginInviteSubmitSuccess({@required this.model}); + + @override + List get props => [this.model]; +} + +/// 邀请码失败 +class LoginInviteSubmitErrorState extends LoginInviteState { + final String msg; + + const LoginInviteSubmitErrorState({this.msg}); + + @override + List get props => [this.msg]; +} + +/// 查询邀请人成功 +class LoginInviteQuerySuccessState extends LoginInviteState { + final LoginInviteUser model; + final LoginModel pageMdel; + + const LoginInviteQuerySuccessState({@required this.model, @required this.pageMdel}); + + @override + List get props => [this.model]; +} + +/// 查询邀请人失败 +class LoginInviteQueryErrorState extends LoginInviteState { + final String msg; + + const LoginInviteQueryErrorState({this.msg}); + + @override + List get props => [this.msg]; +} diff --git a/lib/pages/login_page/invite/login_invite_page.dart b/lib/pages/login_page/invite/login_invite_page.dart new file mode 100644 index 0000000..2630a4e --- /dev/null +++ b/lib/pages/login_page/invite/login_invite_page.dart @@ -0,0 +1,341 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:provider/provider.dart'; +import 'package:zhiying_comm/pages/login_page/model/login_model.dart'; +import 'package:zhiying_comm/zhiying_comm.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:zhiying_comm/util/empty_util.dart'; + +import 'bloc/bloc.dart'; +import 'bloc/login_invite_repository.dart'; +import 'model/login_invite_user.dart'; + +/// +/// 邀请页面 +/// +class LoginInvitePage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: HexColor.fromHex('#FFFFFF'), + body: BlocProvider( + create: (_) => LoginInviteBloc(repostitory: LoginInviteRepository())..add(LoginInviteInitEvent()), + child: LoginInvitePageContainer(), + ), + ); + } +} + +/// +/// 邀请 +class LoginInvitePageContainer extends StatefulWidget { + @override + _LoginInvitePageContainerState createState() => _LoginInvitePageContainerState(); +} + +class _LoginInvitePageContainerState extends State { + TextEditingController _editingController; + FocusNode _focusNode; + bool _showInviteInfo = false; + + /// 返回上一页 + void _openPop() { + if (Navigator.canPop(context)) { + Navigator.pop(context); + } + } + + /// 注册成功跳转 + void _successJump(){ + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute(builder: (BuildContext context) => PageFactory.create('homePage', null)), + (Route route) => false, + ); + } + + /// 输入框输入变化 + void _onChange(String string) { + setState(() { + _showInviteInfo = false; + }); + print('$string'); + if (!EmptyUtil.isEmpty(string) && string.length >= 3) { + _queryInviteInfo(string); + } + } + + /// 查询邀请人 + void _queryInviteInfo(String inviteNum) { + if (!EmptyUtil.isEmpty(inviteNum) && inviteNum.length < 3) { + return; + } + BlocProvider.of(context).add(LoginInviteQueryEvent(num: inviteNum)); + } + + /// 填写邀请啊吗 + void _submitOnClick(LoginInviteUser inviteUser) async { + _focusNode.unfocus(); + + /// 邀请码 + String inviteNum = inviteUser?.userId ?? ''; + + /// 手机号 + UserInfoModel model = await Provider.of(context, listen: false)?.getUserInfoModel(); + String mobile = model?.mobile ?? ''; + if (!EmptyUtil.isEmpty(inviteNum) && !EmptyUtil.isEmpty(mobile)) { + BlocProvider.of(context).add(LoginInviteSubmitEvent(mobile: mobile, num: inviteNum)); + } + } + + @override + void initState() { + _editingController = TextEditingController(); + _focusNode = FocusNode(); + super.initState(); + } + + @override + void didChangeDependencies() { + + super.didChangeDependencies(); + } + + @override + void dispose() { + _editingController?.dispose(); + _focusNode?.unfocus(); + _focusNode?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return BlocConsumer( + listener: (context, state) {}, + buildWhen: (previous, current) { + /// 提交失败 + if (current is LoginInviteSubmitErrorState) { + return false; + } + + /// 数据加载出错 + if (current is LoginInviteErrorState) { + return false; + } + /// 查询邀请人失败 + if(current is LoginInviteQueryErrorState){ + return false; + } + /// 邀请人查询成功 + if(current is LoginInviteQuerySuccessState){ + _showInviteInfo = true; + return true; + } + /// 邀请码成功 跳转 + if (current is LoginInviteSubmitSuccess) { + // 缓存数据 + Provider.of(context, listen: false).setUserInfo(current.model); + _successJump(); + return false; + } + return true; + }, + builder: (context, state) { + print(state); + if (state is LoginInviteLoadedState) { + return _getMainWidget(state.model, null); + } + if (state is LoginInviteQuerySuccessState) { + return _getMainWidget(state.pageMdel, state.model); + } + + /// 骨架屏 + return Container(); + }, + ); + } + + /// 主视图 + Widget _getMainWidget(LoginModel model, LoginInviteUser inviteUser) { + return Column( + children: [ + /// appbar + _getAppBar(model), + + /// 标题 + 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: _getInviteInputWidget(model)), + + /// 邀请人信息 + Visibility( + visible: inviteUser != null && _showInviteInfo, + child: Padding(padding: const EdgeInsets.only(left: 27.5, right: 27.5, top: 8), child: _getInviteInfoWidget(inviteUser))), + + /// 按钮 + Padding(padding: const EdgeInsets.only(left: 27.5, right: 27.5, top: 30), child: _getSubmiBtnWidget(model, inviteUser)), + ], + ); + } + + /// appBar + Widget _getAppBar(LoginModel model) { + return AppBar( + backgroundColor: HexColor.fromHex('#FFFFFF'), + elevation: 0, + title: Text( + model?.invite?.appBarTitle ?? '登录', + style: TextStyle(color: HexColor.fromHex(model?.invite?.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?.invite?.title ?? '输入邀请码', + style: TextStyle(color: HexColor.fromHex(model?.invite?.titleColor ?? '#333333'), fontSize: 25), + )); + } + + /// 邀请码输入框 + Widget _getInviteInputWidget(LoginModel model) { + return _getCustomInputWidget( + hint: model?.invite?.inputInviteText ?? '请输入邀请码', + controller: _editingController, + focusNode: _focusNode, + onChanged: _onChange, + hintColor: model?.invite?.inputHintColor ?? '#999999', + bgColor: model?.invite?.inputBgColor ?? '#F7F7F7', + textColor: model?.invite?.inputInviteTextColor ?? '#333333', + iconUrl: model?.invite?.inputInviteIcon ?? ''); + } + + /// 邀请人信息 + Widget _getInviteInfoWidget(LoginInviteUser model) { + return Container( + // height: 77.5, + decoration: BoxDecoration(borderRadius: BorderRadius.circular(8), border: Border.all(color: HexColor.fromHex('#E8E8E8'), width: 0.5)), + padding: const EdgeInsets.all(15), + child: Row( + children: [ + /// 头像 + CircleAvatar( + radius: 23.5, + backgroundImage: CachedNetworkImageProvider(model?.avatar ?? ''), + ), + const SizedBox(width: 13), + Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /// 名字 + Text( + '${model?.nickname}', + style: TextStyle(color: HexColor.fromHex(model?.nickNameColor), fontSize: 13), + ), + + /// 邀请 + RichText( + text: TextSpan(text: '邀请您进入', style: TextStyle(fontSize: 11, color: HexColor.fromHex(model?.nickNameColor)), children: [ + TextSpan( + text: '${model?.appName}', + style: TextStyle(fontSize: 11, color: HexColor.fromHex(model?.appNameColor)), + ), + ]), + ) + ], + ) + ], + ), + ); + } + + /// 按钮 + Widget _getSubmiBtnWidget(LoginModel model, LoginInviteUser inviteUser) { + return Material( + child: Container( + height: 52, + width: double.infinity, + color: Colors.white, + child: RaisedButton( + child: Text( + model?.invite?.btnSubmitText ?? '进入智莺生活', + style: TextStyle(fontSize: 15), + ), + textColor: HexColor.fromHex(model?.invite?.btnSubmitTextColor ?? '#FFFFFF'), + color: HexColor.fromHex(model?.invite?.btnSubmitBgColor ?? '#FF3939'), + disabledColor: HexColor.fromHex(model?.invite?.btnBanBgColor ?? '#F5F5F5'), + disabledTextColor: HexColor.fromHex(model?.invite?.btnBanTextColor ?? '#999999'), + elevation: 5, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(52 / 2)), + onPressed: _showInviteInfo && inviteUser != null ? ()=> _submitOnClick(inviteUser) : null, + ), + ), + ); + } + + /// 自定义输入框 + 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, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/login_page/invite/model/login_invite_user.dart b/lib/pages/login_page/invite/model/login_invite_user.dart new file mode 100644 index 0000000..0b0fbd5 --- /dev/null +++ b/lib/pages/login_page/invite/model/login_invite_user.dart @@ -0,0 +1,30 @@ +class LoginInviteUser { + String userId; + String nickname; + String appName; + String nickNameColor; + String appNameColor; + String avatar; + + LoginInviteUser({this.userId, this.nickname, this.appName}); + + LoginInviteUser.fromJson(Map json) { + userId = json['user_id']?.toString(); + nickname = json['nickname']?.toString(); + appName = json['app_name']?.toString(); + avatar = json['avatar']?.toString(); + appNameColor = json['app_name_color']?.toString(); + nickNameColor = json['nickname_color']?.toString(); + } + + Map toJson() { + final Map data = new Map(); + data['user_id'] = this.userId; + data['nickname'] = this.nickname; + data['app_name'] = this.appName; + data['nickname_color'] = this.nickNameColor; + data['app_name_color'] = this.appNameColor; + data['avatar'] = this.avatar; + return data; + } +} diff --git a/lib/pages/login_page/login_page.dart b/lib/pages/login_page/login_page.dart new file mode 100644 index 0000000..472378a --- /dev/null +++ b/lib/pages/login_page/login_page.dart @@ -0,0 +1,297 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:zhiying_comm/pages/login_page/account/login_account_page.dart'; +import 'package:zhiying_comm/util/empty_util.dart'; +import 'package:zhiying_comm/zhiying_comm.dart'; +import 'package:cached_network_image/cached_network_image.dart'; + +import 'bloc/bloc.dart'; +import 'bloc/login_repository.dart'; +import 'login_page_sk.dart'; +import 'model/login_model.dart'; + +/// +/// 登陆页面 +/// +class LoginPage extends StatelessWidget { + final Map data; + + const LoginPage(this.data, {Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: HexColor.fromHex('#FFFFFF'), + body: BlocProvider( + create: (_) => LoginBloc(repository: LoginRepository())..add(LoginInitEvent()), + child: LoginPageContainer(), + ), + ); + } +} + +class LoginPageContainer extends StatefulWidget { + @override + _LoginPageContainerState createState() => _LoginPageContainerState(); +} + +class _LoginPageContainerState extends State { + + /// 微信登陆 + void _loginClick(String type) { + print('登陆$type'); + if(type == 'mobile'){ + Navigator.push(context, MaterialPageRoute( + builder: (_) => LoginAccountPage(null) + )); + } + } + + /// 返回上一页 + void _openPop(){ + if(Navigator.canPop(context)){ + Navigator.pop(context); + } + } + + /// 第三方登陆 + void _otherLoginClick(BottomIcons model) { + print('第三方登陆${model.type}'); + } + + /// 跳到用户协议 + void _jumpUserAgreement(String url) { + if(!EmptyUtil.isEmpty(url)) { + print('协议'); + } + } + + /// 展开关闭其它登陆 + void _showOrColoseOtherLogin() { + setState(() { + _showOther = !_showOther; + }); + } + + final _sizedHeight50 = const SizedBox(height: 50); + final _sizedHeight9 = const SizedBox(height: 9); + final _sizedHeight18 = const SizedBox(height: 18); + final _sizedHeight21 = const SizedBox(height: 21); + bool _showOther = true; + + @override + Widget build(BuildContext context) { + return BlocConsumer( + listener: (context, state) { + // Fluttertoast.showToast(msg: '网络异常'); + }, + buildWhen: (prev, current) { + if (current is LoginErrorState) { + return false; + } + return true; + }, + builder: (context, state) { + if (state is LoginCacheState) { + return _getMainWidget(state.model); + } + if (state is LoginLoadedState) { + return _getMainWidget(state.model); + } + return LoginPageSkeleton(); // 骨架屏幕 + }, + ); + } + + /// 主视图 + Widget _getMainWidget(LoginModel model) { + return Column( + children: [ + /// 头部 + _headWidget(model), + _sizedHeight50, + + /// 按钮 + _buttonsWidget(model), + _sizedHeight9, + + /// 协议 + _protocolWidget(model), + + /// 其它登陆方式 + _otherLoginWidget(model), + ], + ); + } + + /// 头部Widget + Widget _headWidget(LoginModel model) { + return Container( + height: 228 + MediaQuery.of(context).padding.top, + width: double.infinity, + decoration: BoxDecoration( + image: DecorationImage( + image: CachedNetworkImageProvider(model?.main?.backgroundImg ?? ''), + fit: BoxFit.fill, + ), + ), + child: Stack( + alignment: Alignment.center, + children: [ + AppBar( + backgroundColor: Colors.transparent, + elevation: 0, + leading: IconButton( + icon: Icon( + Icons.arrow_back_ios, + size: 22, + color: HexColor.fromHex('#333333'), + ), + onPressed: ()=> _openPop(), + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + + /// logo + Container( + margin: EdgeInsets.only(bottom: 12, top: MediaQuery.of(context).padding.top), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(14), + boxShadow: [ + BoxShadow(color: Colors.grey[300], offset: Offset(0.0, 0.0), blurRadius: 10.0, spreadRadius: 1.0), + BoxShadow(color: Colors.grey[300], offset: Offset(0.0, 0.0)), + ], + ), + height: 80, + width: 80, + child: CachedNetworkImage(imageUrl: model?.logoImg ?? ''), + ), + + /// logo 名字 + CachedNetworkImage( imageUrl: model?.main?.appNameImg ?? '', width: 90,), + + ], + ), + ], + ), + ); + } + + /// 按钮 + Widget _buttonsWidget(LoginModel model) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 27.5), + child: Column( + children: model.main.importanceLogin.map((item) { + return Padding( + padding: const EdgeInsets.only(bottom: 8), + child: _customButton( + height: 40, + text: item?.btnText, + iconUrl: item?.btnMobileIcon ?? '', + textColor: item?.btnTextColor, + bgColor: item?.btnBgColor, + borderColor: item?.btnBorderColor, + onTap: () => _loginClick(item?.type)), + ); + }).toList(), + ), + ); + } + + /// 协议 + Widget _protocolWidget(LoginModel model) { + return RichText( + text: TextSpan(text: '', children: model.main.agreements.map((item){ + return TextSpan(text: item?.text, style: TextStyle(color: HexColor.fromHex(item?.textColor), fontSize: 10),recognizer: TapGestureRecognizer()..onTap = (){ + _jumpUserAgreement(item?.url); + }); + }).toList()), + ); + } + + /// 其它登陆方式 + Widget _otherLoginWidget(LoginModel model) { + return Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + _getOtherLoginTitle(model), + _sizedHeight18, + Visibility(visible: _showOther, child: _getOtherLoginIcons(model)), + Visibility(visible: _showOther, child: _sizedHeight21), + ], + ), + ); + } + + /// 其它登陆方式的title + Widget _getOtherLoginTitle(LoginModel model) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => _showOrColoseOtherLogin(), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text('${model?.main?.otherIconsTitle}', style: TextStyle(fontSize: 13, color: HexColor.fromHex(model?.main?.otherIconsTitleColor))), + SizedBox(width: 5.5), + RotatedBox( + quarterTurns: _showOther ? 0 : 2, + child: CachedNetworkImage(imageUrl: model?.main?.otherExpansionIcon ?? '', width: 12), + ), + ], + ), + ); + } + + /// 其它登陆方式的按钮 + Widget _getOtherLoginIcons(LoginModel model) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: model.main.bottomIcons.map((item) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => _otherLoginClick(item), + child: Container( + width: 30, + margin: const EdgeInsets.symmetric(horizontal: 20), + child: CachedNetworkImage(imageUrl: item?.img ?? ''), + ), + ); + }).toList(), + ); + } + + /// 自定义按钮 + Widget _customButton({double height, String text, String iconUrl, String textColor, String bgColor, String borderColor, GestureTapCallback onTap}) { + return GestureDetector( + onTap: onTap, + child: Container( + width: double.infinity, + height: height ?? 0, + decoration: BoxDecoration( + border: Border.all(color: HexColor.fromHex(borderColor), width: 0.5), + borderRadius: BorderRadius.circular(height ?? 0 / 2), + color: HexColor.fromHex(bgColor), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // icon + CachedNetworkImage(imageUrl: iconUrl, width: 12), + SizedBox(width: 8), + // text + Text(text, style: TextStyle(color: HexColor.fromHex(textColor), fontSize: 15)) + ], + ), + ), + ); + } +} diff --git a/lib/pages/login_page/login_page_sk.dart b/lib/pages/login_page/login_page_sk.dart new file mode 100644 index 0000000..84acc17 --- /dev/null +++ b/lib/pages/login_page/login_page_sk.dart @@ -0,0 +1,87 @@ +import 'package:shimmer/shimmer.dart'; +import 'package:flutter/material.dart'; + +/// +/// 登陆页面的骨架屏 +/// +class LoginPageSkeleton extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top), + width: double.infinity, + height: double.infinity, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + height: 230, + child: Column( + children: [ + /// logn + Padding(padding: const EdgeInsets.only(top: 60), child: _logo()), + + /// appName + Padding(padding: const EdgeInsets.only(top: 12), child: _appName()), + ], + ), + ), + + /// btn + Padding(padding: const EdgeInsets.only(top: 50, left: 28, right: 28), child: _shimmerWidget(width: 320, height: 40, radius: 20)), + Padding(padding: const EdgeInsets.only(top: 8, left: 28, right: 28), child: _shimmerWidget(width: 320, height: 40, radius: 20)), + + /// 协议 + Padding(padding: const EdgeInsets.only(top: 9, left: 28, right: 28), child: _shimmerWidget(width: 250, height: 14, radius: 0)), + + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Padding(padding: const EdgeInsets.only(bottom: 18), child: _shimmerWidget(width: 78, height: 18, radius: 0)), + ], + ), + ), + ], + ), + ), + ); + } + + Widget _logo() { + return Shimmer.fromColors( + baseColor: Colors.grey[300], + highlightColor: Colors.grey[100], + child: Container( + height: 80, + width: 80, + decoration: BoxDecoration(borderRadius: BorderRadius.circular(15), color: Colors.white), + ), + ); + } + + Widget _appName() { + return Shimmer.fromColors( + baseColor: Colors.grey[300], + highlightColor: Colors.grey[100], + child: Container( + width: 90, + height: 22.5, + color: Colors.white, + ), + ); + } + + Widget _shimmerWidget({double width, double height, double radius = 0}) { + return Shimmer.fromColors( + baseColor: Colors.grey[300], + highlightColor: Colors.grey[100], + child: Container( + width: width, + height: height, + decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(radius)), + ), + ); + } +} diff --git a/lib/pages/login_page/login_util.dart b/lib/pages/login_page/login_util.dart new file mode 100644 index 0000000..6418fb2 --- /dev/null +++ b/lib/pages/login_page/login_util.dart @@ -0,0 +1,53 @@ +import 'package:zhiying_comm/zhiying_comm.dart'; +import 'package:zhiying_comm/util/shared_prefe_util.dart'; +import 'package:zhiying_comm/util/empty_util.dart'; +import 'package:zhiying_comm/util/global_config.dart'; + +import 'model/login_model.dart'; + +/// 登陆数据管理工具类 +class LoginUtil { + static final String _URL = '/api/v1/sign/in'; + + static void init() async { + String key = _getCacheKey(); + + /// 清除数据缓存 + await SharedPreferencesUtil.setNetCacheResult(key, ''); + } + + /// 获取数据 + static Future getLoginModel() async { + var cache = await fetchCachePageData(); + if (!EmptyUtil.isEmpty(cache)) return cache; + var result = await fetchNetPageData(); + if (!EmptyUtil.isEmpty(result)) return result; + + return null; + } + + /// 获取缓存的key + static String _getCacheKey() { + return NetUtil.getRequestParamsCachedKey(null, _URL); + } + + /// 获取缓存的页面数据 + static Future fetchCachePageData() async { + var result = await NetUtil.getRequestCachedData(_URL); + if (!EmptyUtil.isEmpty(result)) { + LoginModel model = LoginModel.fromJson(result); + return model; + } + return null; + } + + /// 获取页面数据 + static Future fetchNetPageData() async { + var result = await NetUtil.post(_URL, method: NetMethod.GET, cache: true); + if (NetUtil.isSuccess(result) && !EmptyUtil.isEmpty(result[GlobalConfig.HTTP_RESPONSE_KEY_DATA])) { + LoginModel model = LoginModel.fromJson(result[GlobalConfig.HTTP_RESPONSE_KEY_DATA]); + return model; + } + return null; + } +} diff --git a/lib/pages/login_page/model/login_model.dart b/lib/pages/login_page/model/login_model.dart new file mode 100644 index 0000000..600520d --- /dev/null +++ b/lib/pages/login_page/model/login_model.dart @@ -0,0 +1,561 @@ +/// back_img : "http://ossq.izhyin.cn/back_up.png" +/// logo_img : "http://ossq.izhyin.cn/login_logo.png" +/// main : {"app_bar_title":"","app_bar_title_color":"","app_bar_bg_color":"","app_name_img":"http://ossq.izhyin.cn/login_app_name.png","background_img":"http://ossq.izhyin.cn/login_bg.png","agreements":[{"text":"登录表示您已阅读并同意","text_color":"#C0C0C0","url":""},{"text":"《用户协议》","text_color":"#FF4242","url":"http://www.hairuyi.com/?mod=appapi&act=privacy&ctrl=index&type=protocol"},{"text":"与","text_color":"#C0C0C0","url":""},{"text":"《隐私政策》","text_color":"#FF4242","url":"http://www.hairuyi.com/?mod=appapi&act=privacy&ctrl=index&type=privacyPolicy"}],"importance_login":[{"type":"mobile","btn_text":"手机登录","btn_text_color":"#FFFFFF","btn_border_color":"#FF4242","btn_bg_color":"#FF4242","btn_mobile_icon":"http://ossq.izhyin.cn/login_mobile_icon.png"},{"type":"wechat","btn_text":"微信登录","btn_text_color":"#FF4242","btn_border_color":"#FF4242","btn_bg_color":"#FFFFFF","btn_mobile_icon":"http://ossq.izhyin.cn/login_wechat_icon.png"}],"other_icons_title":"其它方式登录","other_icons_title_color":"#333333","other_expansion_icon":"http://ossq.izhyin.cn/login_other_expansion_icon.png","bottom_icons":[{"type":"qq","img":"http://ossq.izhyin.cn/login_qq_icon.png"},{"type":"taobao","img":"http://ossq.izhyin.cn/login_taobao_icon.png"},{"type":"apple","img":"http://ossq.izhyin.cn/login_apple_icon.png"}]} +/// mobile : {"vcode_time":"60","app_bar_title":"登录","app_bar_title_color":"#333333","app_bar_bg_color":"#FFFFFF","title":"您好,欢迎登录","title_color":"#333333","input_hint_color":"#999999","input_text_color":"#333333","input_bg_color":"#F7F7F7","input_mobile_icon":"xxxx","input_mobile_hint_text":"请输入您的手机号","input_vcode_icon":"xxxx","input_vcode_hint_text":"请输入您的验证码","input_other_code_icon":"xxx","input_other_code_icon_text":"请输入右方验证码","input_pass_icon":"http://xxx","input_pass_hint_text":"请输入您的密码","btn_login_text":"立即登录","btn_login_text_color":"#FFFFFF","btn_login_bg_color":"#FF3939","btn_login_shadow_color":"#FF0000","btn_login_ban_bg_color":"#F5F5F5","btn_login_ban_text_color":"#999999","btn_vcode_text":"获取验证码","btn_vcode_bg_color":"#FF4343","btn_vcode_ban_text_color":"#FFFFFF","btn_vcode_ban_bg_color":"#DDDDDD","text_use_pass_tip":"使用密码登录","text_use_vcode_tip":"使用验证码登录","text_use_pass_tip_color":"#999999","text_use_vcode_tip_color":"#999999","text_bottom_tip":"未注册过的手机将自动注册","text_bottom_tip_color":"#999999","protocol_select_icon":"http://xxxxx","protocol_unselect_icon":"http://xxxx","protocol":[{"text":"同意","text_color":"#C0C0C0","url":""},{"text":"《智莺生活用户协议》","text_color":"#FF3939","url":"http://www.hairuyi.com/?mod=appapi&act=privacy&ctrl=index&type=protocol"},{"text":"及","text_color":"#C0C0C0","url":""},{"text":"《隐私政策》","text_color":"#FF3939","url":"http://www.hairuyi.com/?mod=appapi&act=privacy&ctrl=index&type=privacyPolicy"}]} +/// invite : {"app_bar_title":"登陆","app_bar_title_color":"#333333","app_bar_bg_color":"#FFFFFF","title":"输入邀请码","title_color":"#333333","input_hint_color":"#999999","input_bg_color":"#F7F7F7","btn_ban_bg_color":"#F5F5F5","btn_ban_text_color":"#999999","input_invite_icon":"http://xxxx","input_invite_text":"请输入邀请码","input_invite_text_color":"#999999","btn_submit_text":"进入智莺生活","btn_submit_text_color":"#FFFFFF","btn_submit_bg_color":"#FF3939","btn_submit_shadow_color":"#FF0000"} +/// quick : {"app_bar_title":"","app_bar_bg_color":"","app_bar_title_color":"#FFFFFF","account_color":"#333333","text_tip":"切换账号","text_tip_color":"#FF3939","btn_submit_text":"立即登录","btn_submit_text_color":"#FFFFFF","btn_submit_bg_color":"#FF3939","btn_submit_shadow_color":"#FF0000","protocol_select_icon":"http://xxxx","protocol_unselect_icon":"http://xxxxx","text_bottom_tip":"中国电信提供认证服务","text_bottom_tip_color":"#C0C0C0","protocol":[{"text":"同意","text_color":"#C0C0C0","url":""},{"text":"《智莺生活用户协议》","text_color":"#FF3939","url":"http://www.hairuyi.com/?mod=appapi&act=privacy&ctrl=index&type=protocol"},{"text":"及","text_color":"#C0C0C0","url":""},{"text":"《隐私政策》","text_color":"#FF3939","url":"http://www.hairuyi.com/?mod=appapi&act=privacy&ctrl=index&type=privacyPolicy"}]} +/// flash_login_enable : "1" +class LoginModel { + String backImg; + String logoImg; + Main main; + Mobile mobile; + Invite invite; + Quick quick; + String flashLoginEnable; + + LoginModel( + {this.backImg, + this.logoImg, + this.main, + this.mobile, + this.invite, + this.quick, + this.flashLoginEnable}); + + LoginModel.fromJson(Map json) { + backImg = json['back_img']; + logoImg = json['logo_img']; + main = json['main'] != null ? new Main.fromJson(json['main']) : null; + mobile = + json['mobile'] != null ? new Mobile.fromJson(json['mobile']) : null; + invite = + json['invite'] != null ? new Invite.fromJson(json['invite']) : null; + quick = json['quick'] != null ? new Quick.fromJson(json['quick']) : null; + flashLoginEnable = json['flash_login_enable']; + } + + Map toJson() { + final Map data = new Map(); + data['back_img'] = this.backImg; + data['logo_img'] = this.logoImg; + if (this.main != null) { + data['main'] = this.main.toJson(); + } + if (this.mobile != null) { + data['mobile'] = this.mobile.toJson(); + } + if (this.invite != null) { + data['invite'] = this.invite.toJson(); + } + if (this.quick != null) { + data['quick'] = this.quick.toJson(); + } + data['flash_login_enable'] = this.flashLoginEnable; + return data; + } +} + +class Main { + String appBarTitle; + String appBarTitleColor; + String appBarBgColor; + String appNameImg; + String backgroundImg; + List agreements; + List importanceLogin; + String otherIconsTitle; + String otherIconsTitleColor; + String otherExpansionIcon; + List bottomIcons; + + Main( + {this.appBarTitle, + this.appBarTitleColor, + this.appBarBgColor, + this.appNameImg, + this.backgroundImg, + this.agreements, + this.importanceLogin, + this.otherIconsTitle, + this.otherIconsTitleColor, + this.otherExpansionIcon, + this.bottomIcons}); + + Main.fromJson(Map json) { + appBarTitle = json['app_bar_title']; + appBarTitleColor = json['app_bar_title_color']; + appBarBgColor = json['app_bar_bg_color']; + appNameImg = json['app_name_img']; + backgroundImg = json['background_img']; + if (json['agreements'] != null) { + agreements = new List(); + json['agreements'].forEach((v) { + agreements.add(new Agreements.fromJson(v)); + }); + } + if (json['importance_login'] != null) { + importanceLogin = new List(); + json['importance_login'].forEach((v) { + importanceLogin.add(new ImportanceLogin.fromJson(v)); + }); + } + otherIconsTitle = json['other_icons_title']; + otherIconsTitleColor = json['other_icons_title_color']; + otherExpansionIcon = json['other_expansion_icon']; + if (json['bottom_icons'] != null) { + bottomIcons = new List(); + json['bottom_icons'].forEach((v) { + bottomIcons.add(new BottomIcons.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + data['app_bar_title'] = this.appBarTitle; + data['app_bar_title_color'] = this.appBarTitleColor; + data['app_bar_bg_color'] = this.appBarBgColor; + data['app_name_img'] = this.appNameImg; + data['background_img'] = this.backgroundImg; + if (this.agreements != null) { + data['agreements'] = this.agreements.map((v) => v.toJson()).toList(); + } + if (this.importanceLogin != null) { + data['importance_login'] = + this.importanceLogin.map((v) => v.toJson()).toList(); + } + data['other_icons_title'] = this.otherIconsTitle; + data['other_icons_title_color'] = this.otherIconsTitleColor; + data['other_expansion_icon'] = this.otherExpansionIcon; + if (this.bottomIcons != null) { + data['bottom_icons'] = this.bottomIcons.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class Agreements { + String text; + String textColor; + String url; + + Agreements({this.text, this.textColor, this.url}); + + Agreements.fromJson(Map json) { + text = json['text']; + textColor = json['text_color']; + url = json['url']; + } + + Map toJson() { + final Map data = new Map(); + data['text'] = this.text; + data['text_color'] = this.textColor; + data['url'] = this.url; + return data; + } +} + +class ImportanceLogin { + String type; + String btnText; + String btnTextColor; + String btnBorderColor; + String btnBgColor; + String btnMobileIcon; + + ImportanceLogin( + {this.type, + this.btnText, + this.btnTextColor, + this.btnBorderColor, + this.btnBgColor, + this.btnMobileIcon}); + + ImportanceLogin.fromJson(Map json) { + type = json['type']; + btnText = json['btn_text']; + btnTextColor = json['btn_text_color']; + btnBorderColor = json['btn_border_color']; + btnBgColor = json['btn_bg_color']; + btnMobileIcon = json['btn_mobile_icon']; + } + + Map toJson() { + final Map data = new Map(); + data['type'] = this.type; + data['btn_text'] = this.btnText; + data['btn_text_color'] = this.btnTextColor; + data['btn_border_color'] = this.btnBorderColor; + data['btn_bg_color'] = this.btnBgColor; + data['btn_mobile_icon'] = this.btnMobileIcon; + return data; + } +} + +class BottomIcons { + String type; + String img; + + BottomIcons({this.type, this.img}); + + BottomIcons.fromJson(Map json) { + type = json['type']; + img = json['img']; + } + + Map toJson() { + final Map data = new Map(); + data['type'] = this.type; + data['img'] = this.img; + return data; + } +} + +class Mobile { + String vcodeTime; + String appBarTitle; + String appBarTitleColor; + String appBarBgColor; + String title; + String titleColor; + String inputHintColor; + String inputTextColor; + String inputBgColor; + String inputMobileIcon; + String inputMobileHintText; + String inputVcodeIcon; + String inputVcodeHintText; + String inputOtherCodeIcon; + String inputOtherCodeIconText; + String inputPassIcon; + String inputPassHintText; + String btnLoginText; + String btnLoginTextColor; + String btnLoginBgColor; + String btnLoginShadowColor; + String btnLoginBanBgColor; + String btnLoginBanTextColor; + String btnVcodeText; + String btnVcodeTextColor; + String btnVcodeBgColor; + String btnVcodeBanTextColor; + String btnVcodeBanBgColor; + String textUsePassTip; + String textUseVcodeTip; + String textUsePassTipColor; + String textUseVcodeTipColor; + String textBottomTip; + String textBottomTipColor; + String protocolSelectIcon; + String protocolUnselectIcon; + List protocol; + + Mobile( + {this.vcodeTime, + this.appBarTitle, + this.appBarTitleColor, + this.appBarBgColor, + this.title, + this.titleColor, + this.inputHintColor, + this.inputTextColor, + this.inputBgColor, + this.inputMobileIcon, + this.inputMobileHintText, + this.inputVcodeIcon, + this.inputVcodeHintText, + this.inputOtherCodeIcon, + this.inputOtherCodeIconText, + this.inputPassIcon, + this.inputPassHintText, + this.btnLoginText, + this.btnLoginTextColor, + this.btnLoginBgColor, + this.btnLoginShadowColor, + this.btnLoginBanBgColor, + this.btnLoginBanTextColor, + this.btnVcodeText, + this.btnVcodeTextColor, + this.btnVcodeBgColor, + this.btnVcodeBanTextColor, + this.btnVcodeBanBgColor, + this.textUsePassTip, + this.textUseVcodeTip, + this.textUsePassTipColor, + this.textUseVcodeTipColor, + this.textBottomTip, + this.textBottomTipColor, + this.protocolSelectIcon, + this.protocolUnselectIcon, + this.protocol}); + + Mobile.fromJson(Map json) { + vcodeTime = json['vcode_time']; + appBarTitle = json['app_bar_title']; + appBarTitleColor = json['app_bar_title_color']; + appBarBgColor = json['app_bar_bg_color']; + title = json['title']; + titleColor = json['title_color']; + inputHintColor = json['input_hint_color']; + inputTextColor = json['input_text_color']; + inputBgColor = json['input_bg_color']; + inputMobileIcon = json['input_mobile_icon']; + inputMobileHintText = json['input_mobile_hint_text']; + inputVcodeIcon = json['input_vcode_icon']; + inputVcodeHintText = json['input_vcode_hint_text']; + inputOtherCodeIcon = json['input_other_code_icon']; + inputOtherCodeIconText = json['input_other_code_icon_text']; + inputPassIcon = json['input_pass_icon']; + inputPassHintText = json['input_pass_hint_text']; + btnLoginText = json['btn_login_text']; + btnLoginTextColor = json['btn_login_text_color']; + btnLoginBgColor = json['btn_login_bg_color']; + btnLoginShadowColor = json['btn_login_shadow_color']; + btnLoginBanBgColor = json['btn_login_ban_bg_color']; + btnLoginBanTextColor = json['btn_login_ban_text_color']; + btnVcodeText = json['btn_vcode_text']; + btnVcodeTextColor = json['btnVcodeTextColor']; + btnVcodeBgColor = json['btn_vcode_bg_color']; + btnVcodeBanTextColor = json['btn_vcode_ban_text_color']; + btnVcodeBanBgColor = json['btn_vcode_ban_bg_color']; + textUsePassTip = json['text_use_pass_tip']; + textUseVcodeTip = json['text_use_vcode_tip']; + textUsePassTipColor = json['text_use_pass_tip_color']; + textUseVcodeTipColor = json['text_use_vcode_tip_color']; + textBottomTip = json['text_bottom_tip']; + textBottomTipColor = json['text_bottom_tip_color']; + protocolSelectIcon = json['protocol_select_icon']; + protocolUnselectIcon = json['protocol_unselect_icon']; + if (json['protocol'] != null) { + protocol = new List(); + json['protocol'].forEach((v) { + protocol.add(new Protocol.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + data['vcode_time'] = this.vcodeTime; + data['app_bar_title'] = this.appBarTitle; + data['app_bar_title_color'] = this.appBarTitleColor; + data['app_bar_bg_color'] = this.appBarBgColor; + data['title'] = this.title; + data['title_color'] = this.titleColor; + data['input_hint_color'] = this.inputHintColor; + data['input_text_color'] = this.inputTextColor; + data['input_bg_color'] = this.inputBgColor; + data['input_mobile_icon'] = this.inputMobileIcon; + data['input_mobile_hint_text'] = this.inputMobileHintText; + data['input_vcode_icon'] = this.inputVcodeIcon; + data['input_vcode_hint_text'] = this.inputVcodeHintText; + data['input_other_code_icon'] = this.inputOtherCodeIcon; + data['input_other_code_icon_text'] = this.inputOtherCodeIconText; + data['input_pass_icon'] = this.inputPassIcon; + data['input_pass_hint_text'] = this.inputPassHintText; + data['btn_login_text'] = this.btnLoginText; + data['btn_login_text_color'] = this.btnLoginTextColor; + data['btn_login_bg_color'] = this.btnLoginBgColor; + data['btn_login_shadow_color'] = this.btnLoginShadowColor; + data['btn_login_ban_bg_color'] = this.btnLoginBanBgColor; + data['btn_login_ban_text_color'] = this.btnLoginBanTextColor; + data['btn_vcode_text'] = this.btnVcodeText; + data['btnVcodeTextColor'] = this.btnVcodeTextColor; + data['btn_vcode_bg_color'] = this.btnVcodeBgColor; + data['btn_vcode_ban_text_color'] = this.btnVcodeBanTextColor; + data['btn_vcode_ban_bg_color'] = this.btnVcodeBanBgColor; + data['text_use_pass_tip'] = this.textUsePassTip; + data['text_use_vcode_tip'] = this.textUseVcodeTip; + data['text_use_pass_tip_color'] = this.textUsePassTipColor; + data['text_use_vcode_tip_color'] = this.textUseVcodeTipColor; + data['text_bottom_tip'] = this.textBottomTip; + data['text_bottom_tip_color'] = this.textBottomTipColor; + data['protocol_select_icon'] = this.protocolSelectIcon; + data['protocol_unselect_icon'] = this.protocolUnselectIcon; + if (this.protocol != null) { + data['protocol'] = this.protocol.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class Invite { + String appBarTitle; + String appBarTitleColor; + String appBarBgColor; + String title; + String titleColor; + String inputHintColor; + String inputBgColor; + String btnBanBgColor; + String btnBanTextColor; + String inputInviteIcon; + String inputInviteText; + String inputInviteTextColor; + String btnSubmitText; + String btnSubmitTextColor; + String btnSubmitBgColor; + String btnSubmitShadowColor; + + Invite( + {this.appBarTitle, + this.appBarTitleColor, + this.appBarBgColor, + this.title, + this.titleColor, + this.inputHintColor, + this.inputBgColor, + this.btnBanBgColor, + this.btnBanTextColor, + this.inputInviteIcon, + this.inputInviteText, + this.inputInviteTextColor, + this.btnSubmitText, + this.btnSubmitTextColor, + this.btnSubmitBgColor, + this.btnSubmitShadowColor}); + + Invite.fromJson(Map json) { + appBarTitle = json['app_bar_title']; + appBarTitleColor = json['app_bar_title_color']; + appBarBgColor = json['app_bar_bg_color']; + title = json['title']; + titleColor = json['title_color']; + inputHintColor = json['input_hint_color']; + inputBgColor = json['input_bg_color']; + btnBanBgColor = json['btn_ban_bg_color']; + btnBanTextColor = json['btn_ban_text_color']; + inputInviteIcon = json['input_invite_icon']; + inputInviteText = json['input_invite_text']; + inputInviteTextColor = json['input_invite_text_color']; + btnSubmitText = json['btn_submit_text']; + btnSubmitTextColor = json['btn_submit_text_color']; + btnSubmitBgColor = json['btn_submit_bg_color']; + btnSubmitShadowColor = json['btn_submit_shadow_color']; + } + + Map toJson() { + final Map data = new Map(); + data['app_bar_title'] = this.appBarTitle; + data['app_bar_title_color'] = this.appBarTitleColor; + data['app_bar_bg_color'] = this.appBarBgColor; + data['title'] = this.title; + data['title_color'] = this.titleColor; + data['input_hint_color'] = this.inputHintColor; + data['input_bg_color'] = this.inputBgColor; + data['btn_ban_bg_color'] = this.btnBanBgColor; + data['btn_ban_text_color'] = this.btnBanTextColor; + data['input_invite_icon'] = this.inputInviteIcon; + data['input_invite_text'] = this.inputInviteText; + data['input_invite_text_color'] = this.inputInviteTextColor; + data['btn_submit_text'] = this.btnSubmitText; + data['btn_submit_text_color'] = this.btnSubmitTextColor; + data['btn_submit_bg_color'] = this.btnSubmitBgColor; + data['btn_submit_shadow_color'] = this.btnSubmitShadowColor; + return data; + } +} + +class Quick { + String appBarTitle; + String appBarBgColor; + String appBarTitleColor; + String accountColor; + String textTip; + String textTipColor; + String btnSubmitText; + String btnSubmitTextColor; + String btnSubmitBgColor; + String btnSubmitShadowColor; + String protocolSelectIcon; + String protocolUnselectIcon; + String textBottomTip; + String textBottomTipColor; + List protocol; + + Quick( + {this.appBarTitle, + this.appBarBgColor, + this.appBarTitleColor, + this.accountColor, + this.textTip, + this.textTipColor, + this.btnSubmitText, + this.btnSubmitTextColor, + this.btnSubmitBgColor, + this.btnSubmitShadowColor, + this.protocolSelectIcon, + this.protocolUnselectIcon, + this.textBottomTip, + this.textBottomTipColor, + this.protocol}); + + Quick.fromJson(Map json) { + appBarTitle = json['app_bar_title']; + appBarBgColor = json['app_bar_bg_color']; + appBarTitleColor = json['app_bar_title_color']; + accountColor = json['account_color']; + textTip = json['text_tip']; + textTipColor = json['text_tip_color']; + btnSubmitText = json['btn_submit_text']; + btnSubmitTextColor = json['btn_submit_text_color']; + btnSubmitBgColor = json['btn_submit_bg_color']; + btnSubmitShadowColor = json['btn_submit_shadow_color']; + protocolSelectIcon = json['protocol_select_icon']; + protocolUnselectIcon = json['protocol_unselect_icon']; + textBottomTip = json['text_bottom_tip']; + textBottomTipColor = json['text_bottom_tip_color']; + if (json['protocol'] != null) { + protocol = new List(); + json['protocol'].forEach((v) { + protocol.add(new Protocol.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + data['app_bar_title'] = this.appBarTitle; + data['app_bar_bg_color'] = this.appBarBgColor; + data['app_bar_title_color'] = this.appBarTitleColor; + data['account_color'] = this.accountColor; + data['text_tip'] = this.textTip; + data['text_tip_color'] = this.textTipColor; + data['btn_submit_text'] = this.btnSubmitText; + data['btn_submit_text_color'] = this.btnSubmitTextColor; + data['btn_submit_bg_color'] = this.btnSubmitBgColor; + data['btn_submit_shadow_color'] = this.btnSubmitShadowColor; + data['protocol_select_icon'] = this.protocolSelectIcon; + data['protocol_unselect_icon'] = this.protocolUnselectIcon; + data['text_bottom_tip'] = this.textBottomTip; + data['text_bottom_tip_color'] = this.textBottomTipColor; + if (this.protocol != null) { + data['protocol'] = this.protocol.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class Protocol { + String text; + String textColor; + String url; + + Protocol({this.text, this.textColor, this.url}); + + Protocol.fromJson(Map json) { + text = json['text']; + textColor = json['text_color']; + url = json['url']; + } + + Map toJson() { + final Map data = new Map(); + data['text'] = this.text; + data['text_color'] = this.textColor; + data['url'] = this.url; + return data; + } +} diff --git a/lib/pages/login_page/quick/login_quick_page.dart b/lib/pages/login_page/quick/login_quick_page.dart new file mode 100644 index 0000000..4eed223 --- /dev/null +++ b/lib/pages/login_page/quick/login_quick_page.dart @@ -0,0 +1,171 @@ +import 'package:flutter/material.dart'; +import 'package:zhiying_comm/zhiying_comm.dart'; +import 'package:cached_network_image/cached_network_image.dart'; + +/// +/// 快速登陆 +/// +class LoginQuickPage extends StatelessWidget { + final Map model; + + const LoginQuickPage(this.model, {Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + body: LoginQuickContainerPage(), + ); + } +} + +class LoginQuickContainerPage extends StatefulWidget { + @override + _LoginQuickContainerPageState createState() => _LoginQuickContainerPageState(); +} + +class _LoginQuickContainerPageState extends State { + + /// 登陆事件 + void _submitOnClick() { + print('点击登陆'); + } + + /// 切换账号 + void _changeAccount() { + print('切换账号'); + } + + /// 同意or取消用户协议 + void _agree(){ + + } + + final _sizedBoxHeight30 = const SizedBox(height: 30); + final _sizedBoxHeight35 = const SizedBox(height: 35); + final _sizedBoxHeight20 = const SizedBox(height: 20); + final _sizedBoxHeight16 = const SizedBox(height: 16); + final _sizedBoxHeight28 = const SizedBox(height: 28); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + /// appbar + _getAppBarWidget(), + _sizedBoxHeight30, + + /// logo + _getLogoWidget(null), + _sizedBoxHeight35, + + /// 账号 + _getAccountWidget(null), +// _sizedBoxHeight20, + + /// 切换账号提示 + _changeAccountTipWidget(null), +// _sizedBoxHeight20, + + /// 登陆按钮 + _submitButton(null), + _sizedBoxHeight16, + + /// 协议 + _protocolWidget(null), + + /// 底部tip + Expanded( + child: Align( + child: _bottomTipWidget(null), + alignment: Alignment.bottomCenter, + ), + ) + ], + ); + } + + /// 底部提示 + Widget _bottomTipWidget(var model) { + return Container( + margin: const EdgeInsets.only(bottom: 28), child: Text('中国电信提供认证服务', style: TextStyle(fontSize: 11, color: HexColor.fromHex('#C0C0C0')))); + } + + /// 协议 + Widget _protocolWidget(var model) { + return Container( + child: Text('《嗨如意用户协议》', style: TextStyle(fontSize: 11, color: HexColor.fromHex('#C0C0C0'))), + ); + } + + /// 立即登陆按钮 + Widget _submitButton(var model) { + return Material( + child: Container( + height: 52, + width: double.infinity, + color: Colors.white, + padding: const EdgeInsets.symmetric(horizontal: 27.5), + child: RaisedButton( + child: Text( + '立即登录', + style: TextStyle(fontSize: 15), + ), + textColor: HexColor.fromHex('#FFFFFF'), + color: HexColor.fromHex('#FF3939'), + disabledColor: HexColor.fromHex('#F5F5F5'), + disabledTextColor: HexColor.fromHex('#999999'), + elevation: 5, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(52 / 2)), + onPressed: _submitOnClick, + ), + ), + ); + } + + /// 切换账号提示 + Widget _changeAccountTipWidget(var model) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => _changeAccount(), child: Container( margin: const EdgeInsets.symmetric(vertical: 20), child: Text('切换账号', style: TextStyle(fontSize: 13, color: HexColor.fromHex('#FF3939'))))); + } + + /// 账号 + Widget _getAccountWidget(var model) { + return Text('158****3158', style: TextStyle(fontSize: 25, color: HexColor.fromHex('#333333'))); + } + + /// login + Widget _getLogoWidget(var model) { + return Container( + margin: EdgeInsets.only(bottom: 12, top: MediaQuery.of(context).padding.top), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(14), + boxShadow: [ + BoxShadow(color: Colors.grey[300], offset: Offset(0.0, 0.0), blurRadius: 10.0, spreadRadius: 1.0), + BoxShadow(color: Colors.grey[300], offset: Offset(0.0, 0.0)), + ], + ), + height: 80, + width: 80, + child: CachedNetworkImage( + imageUrl: model?.logoImg ?? '', + fit: BoxFit.fill, + ), + ); + } + + /// appBar + Widget _getAppBarWidget() { + return AppBar( + backgroundColor: Colors.transparent, + elevation: 0, + leading: Icon( + Icons.arrow_back_ios, + size: 22, + color: HexColor.fromHex('#333333'), + ), + ); + } +} diff --git a/lib/util/router_util.dart b/lib/util/router_util.dart index eba8678..4442d09 100644 --- a/lib/util/router_util.dart +++ b/lib/util/router_util.dart @@ -1,11 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:zhiying_comm/pages/login_page.dart'; +import 'package:zhiying_comm/pages/login_page/login_page.dart'; import 'package:zhiying_comm/zhiying_comm.dart'; import 'package:provider/provider.dart'; class RouterUtil { - - static Future route(Map model, BuildContext context) { + static Future route(Map model, BuildContext context) async { // 唯一跳转标识 String skipIdentifier = model['skip_identifier'].toString(); // 需要登录 @@ -13,21 +12,18 @@ class RouterUtil { // 需要淘宝授权 String requiredTaobaoAuth = model['required_taobao_auth'].toString(); - if (requiredLogin == '1') { - UserInfoModel user = Provider.of(context, listen: false).getUserInfoModel(); + UserInfoModel user = await Provider.of(context, listen: false).getUserInfoModel(); print(user.toString()); if (user?.token == null || user.token == '') { print('need login...'); - return Navigator.of(context) - .push(MaterialPageRoute(builder: (BuildContext context) { - return LoginPage(); + return Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) { + return LoginPage(model); })); } } - return Navigator.of(context) - .push(MaterialPageRoute(builder: (BuildContext context) { + return Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) { if (PageFactory.hasRegisted(skipIdentifier)) { return PageFactory.create(skipIdentifier, model); } diff --git a/pubspec.yaml b/pubspec.yaml index cc91d2c..ba9a5e2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,9 @@ dependencies: package_info: ^0.4.0+17 device_info: ^0.4.0+1 flutter_native_image: ^0.0.5 + # toast提示 fluttertoast: 4.0.1 + # 图片缓存 cached_network_image: ^2.2.0+1 equatable: ^1.2.0 json_serializable: ^3.3.0 @@ -25,7 +27,10 @@ dependencies: flutter_screenutil: ^1.1.0 # 缓存 shared_preferences: ^0.5.10 - + # bloc + flutter_bloc: ^4.0.1 + # 骨架屏 + shimmer: ^1.1.1 dev_dependencies: