diff --git a/.dart_tool/package_config.json b/.dart_tool/package_config.json index 69f77f3..2b334b6 100644 --- a/.dart_tool/package_config.json +++ b/.dart_tool/package_config.json @@ -680,7 +680,7 @@ "languageVersion": "2.1" } ], - "generated": "2020-09-11T04:26:30.812379Z", + "generated": "2020-09-11T11:58:25.076408Z", "generator": "pub", "generatorVersion": "2.7.2" } diff --git a/lib/pages/home_page/home_page.dart b/lib/pages/home_page/home_page.dart index fd0fa13..95c4399 100644 --- a/lib/pages/home_page/home_page.dart +++ b/lib/pages/home_page/home_page.dart @@ -1,7 +1,9 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart'; +import 'package:provider/provider.dart'; import 'package:zhiying_base_widget/pages/home_page/home_page_bloc.dart'; +import 'package:zhiying_base_widget/pages/login_page/notifier/user_info_notifier.dart'; import 'package:zhiying_comm/zhiying_comm.dart'; import 'package:zhiying_comm/util/base_bloc.dart'; @@ -17,9 +19,14 @@ class _HomePageState extends State { @override Widget build(BuildContext context) { print('home_page build'); - return BlocProvider( - bloc: HomePageBloc(), - child: _HomePageContainer(), + return MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: UserInfoNotifier()), + ], + child: BlocProvider( + bloc: HomePageBloc(), + child: _HomePageContainer(), + ), ); } } 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..115bf50 --- /dev/null +++ b/lib/pages/login_page/account/bloc/login_account_bloc.dart @@ -0,0 +1,63 @@ +import 'dart:async'; +import 'package:bloc/bloc.dart'; +import 'package:flutter/cupertino.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..a6ef75c --- /dev/null +++ b/lib/pages/login_page/account/bloc/login_account_repository.dart @@ -0,0 +1,54 @@ +import 'package:zhiying_base_widget/pages/login_page/account/bloc/bloc.dart'; +import 'package:zhiying_base_widget/pages/login_page/model/login_user.dart'; +import 'package:zhiying_base_widget/pages/login_page/login_util.dart'; +import 'package:zhiying_base_widget/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'; + +/// +/// 账户登陆 +/// +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])){ + LoginUser loginUser = LoginUser.fromJson(result[GlobalConfig.HTTP_RESPONSE_KEY_DATA]); + + return loginUser; + } + 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..aec903f --- /dev/null +++ b/lib/pages/login_page/account/bloc/login_account_state.dart @@ -0,0 +1,57 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:zhiying_base_widget/pages/login_page/model/login_user.dart'; +import 'package:zhiying_base_widget/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 LoginUser 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..13d3f87 --- /dev/null +++ b/lib/pages/login_page/account/login_account_page.dart @@ -0,0 +1,576 @@ +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_base_widget/pages/login_page/account/bloc/bloc.dart'; +import 'package:zhiying_base_widget/pages/login_page/model/login_model.dart'; +import 'package:zhiying_base_widget/pages/login_page/notifier/user_info_notifier.dart'; +import 'package:zhiying_comm/zhiying_comm.dart'; +import 'bloc/login_account_repository.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('跳转到邀请码页面'); + } + + /// 登陆成功页面 + void _openLoginSuccessPage(){ + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute(builder: (BuildContext context) => PageFactory.create('homePage', null)), + (Route route) => false, + ); + } + + /// 登陆 + 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 = false; + + /// 是否显示第三方验证码 + 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) {}, + buildWhen: (prev, current) { + // 验证码登陆失败 + if(current is LoginAccountTypeVcodeLoginErrorState){ + Fluttertoast.showToast(msg: '登陆失败'); + return false; + } + // 验证码登陆成功 + if(current is LoginAccountTypeVcodeLoginSuccessState){ + if(current?.model?.registerInviteCodeEnable == '0'){ + Fluttertoast.showToast(msg: '登陆成功~'); + /// 缓存登陆数据 + Provider.of(context, listen: false).setUserInfo(current.model); + /// 打开也买 + _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 Container(); + }, + ); + } + + /// 主页面 + 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: Icon( + Icons.arrow_back_ios, + size: 22, + color: HexColor.fromHex('#333333'), + ), + ); + } + + /// 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/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/login_bloc.dart b/lib/pages/login_page/bloc/login_bloc.dart index 3d46804..c1da81a 100644 --- a/lib/pages/login_page/bloc/login_bloc.dart +++ b/lib/pages/login_page/bloc/login_bloc.dart @@ -1,8 +1,14 @@ 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(); @@ -11,5 +17,22 @@ class LoginBloc extends Bloc { 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_repository.dart b/lib/pages/login_page/bloc/login_repository.dart index e8401a5..3f426a9 100644 --- a/lib/pages/login_page/bloc/login_repository.dart +++ b/lib/pages/login_page/bloc/login_repository.dart @@ -1,19 +1,26 @@ - +import 'package:zhiying_base_widget/pages/login_page/model/login_model.dart'; import 'package:zhiying_comm/util/net_util.dart'; import 'package:zhiying_comm/util/empty_util.dart'; +import 'package:zhiying_comm/util/global_config.dart'; -class LoginRepository{ - +class LoginRepository { /// 获取页面数据 - Future fetchNetPageData() async{ - + Future fetchNetPageData() async { + var result = await NetUtil.post('/api/v1/sign/in', method: NetMethod.GET); + 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; } /// 获取缓存的页面数据 - Future fetchCachePageData() async{ - + 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; } - - - -} \ No newline at end of file +} diff --git a/lib/pages/login_page/bloc/login_state.dart b/lib/pages/login_page/bloc/login_state.dart index 8c56b3d..5ed35ad 100644 --- a/lib/pages/login_page/bloc/login_state.dart +++ b/lib/pages/login_page/bloc/login_state.dart @@ -1,10 +1,31 @@ import 'package:equatable/equatable.dart'; +import 'package:zhiying_base_widget/pages/login_page/model/login_model.dart'; abstract class LoginState extends Equatable { const LoginState(); -} -class InitialLoginState extends 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..97b3c75 --- /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 { + LoginInviteRepostitory 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 LoginInviteQuerySuccess(model: data); + else + yield LoginInviteQueryError(); + } + + /// 提交 + Stream _mapSubmitEventToState(LoginInviteSubmitEvent event) async* { + var data = await repostitory.submitInvite(event); + if (!EmptyUtil.isEmpty(data)) + yield LoginInviteSubmitSuccess(model: data); + else + yield LoginInviteSubmitError(); + } +} 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..d65448b --- /dev/null +++ b/lib/pages/login_page/invite/bloc/login_invite_repository.dart @@ -0,0 +1,40 @@ +import 'package:zhiying_base_widget/pages/login_page/invite/bloc/bloc.dart'; +import 'package:zhiying_base_widget/pages/login_page/invite/model/login_invite_user.dart'; +import 'package:zhiying_base_widget/pages/login_page/model/login_model.dart'; +import 'package:zhiying_base_widget/pages/login_page/model/login_user.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 '../../login_util.dart'; + +class LoginInviteRepostitory { + /// 获取数据,如果缓存有,则获取缓存的数据 + Future fetchData() async { + var result = await LoginUtil.getLoginModel(); + if (!EmptyUtil.isEmpty(result)) { + return result; + } + return null; + } + + /// 获取邀请人信息 + Future fetchInviteUserInfo(LoginInviteQueryEvent event) async { + var result = await NetUtil.post('/api/v1/user/invite/${event.num}'); + if (NetUtil.isSuccess(result) && !EmptyUtil.isEmpty(result[GlobalConfig.HTTP_RESPONSE_KEY_DATA])) { + LoginInviteUser model = LoginInviteUser.fromJson(result[GlobalConfig.HTTP_RESPONSE_KEY_DATA]); + return model; + } else { + return null; + } + } + + /// 提交 + Future submitInvite(LoginInviteSubmitEvent event) async { + var result = await NetUtil.post('/api/v1/user/invite/ack', params: {'mobile': event.mobile, 'superior_user_id': event.num}); + if (NetUtil.isSuccess(result) && !EmptyUtil.isEmpty(result[GlobalConfig.HTTP_RESPONSE_KEY_DATA])) { + LoginUser model = LoginUser.fromJson(result[GlobalConfig.HTTP_RESPONSE_KEY_DATA]); + return model; + } + 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..ebb0a79 --- /dev/null +++ b/lib/pages/login_page/invite/bloc/login_invite_state.dart @@ -0,0 +1,75 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:zhiying_base_widget/pages/login_page/invite/model/login_invite_user.dart'; +import 'package:zhiying_base_widget/pages/login_page/model/login_model.dart'; +import 'package:zhiying_base_widget/pages/login_page/model/login_user.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 LoginUser model; + + const LoginInviteSubmitSuccess({@required this.model}); + + @override + List get props => [this.model]; +} + +/// 邀请码失败 +class LoginInviteSubmitError extends LoginInviteState { + final String msg; + + const LoginInviteSubmitError({this.msg}); + + @override + List get props => [this.msg]; +} + +/// 查询邀请人成功 +class LoginInviteQuerySuccess extends LoginInviteState { + final LoginInviteUser model; + + const LoginInviteQuerySuccess({@required this.model}); + + @override + List get props => [this.model]; +} + +/// 查询邀请人失败 +class LoginInviteQueryError extends LoginInviteState { + final String msg; + + const LoginInviteQueryError({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..952afb3 --- /dev/null +++ b/lib/pages/login_page/invite/login_invite_page.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:zhiying_base_widget/pages/login_page/invite/bloc/bloc.dart'; +import 'package:zhiying_base_widget/pages/login_page/invite/bloc/login_invite_repository.dart'; +import 'package:zhiying_base_widget/pages/login_page/model/login_model.dart'; +import 'package:zhiying_comm/zhiying_comm.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: LoginInviteRepostitory())..add(LoginInviteInitEvent()), + child: LoginInvitePageContainer(), + ), + ); + } +} + +/// +/// 邀请 +class LoginInvitePageContainer extends StatefulWidget { + @override + _LoginInvitePageContainerState createState() => _LoginInvitePageContainerState(); +} + +class _LoginInvitePageContainerState extends State { + + @override + Widget build(BuildContext context) { + return BlocConsumer( + listener: (context, state) {}, + buildWhen: (previous, current) { + return true; + }, + builder: (context, state) { + /// 骨架屏 + return Container(); + }, + ); + } + + /// 主视图 + Widget _getMainWidget(LoginModel model) { + return Column(children: [ + + ],); + } + + /// appBar + Widget _getAppBar(LoginModel model) {} + + /// title + Widget _getTitleWidget(LoginModel model) {} + + /// 邀请码输入框 + Widget _getInviteInputWidget(LoginModel model) {} + + /// 邀请人信息 + Widget _getInviteInfoWidget(LoginInviteUser model) {} + + /// 按钮 + Widget _getSubmitButtomWidget(LoginModel model) {} +} 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..23a7816 --- /dev/null +++ b/lib/pages/login_page/invite/model/login_invite_user.dart @@ -0,0 +1,21 @@ +class LoginInviteUser { + String userId; + String nickname; + String appName; + + LoginInviteUser({this.userId, this.nickname, this.appName}); + + LoginInviteUser.fromJson(Map json) { + userId = json['user_id']; + nickname = json['nickname']; + appName = json['app_name']; + } + + Map toJson() { + final Map data = new Map(); + data['user_id'] = this.userId; + data['nickname'] = this.nickname; + data['app_name'] = this.appName; + return data; + } +} diff --git a/lib/pages/login_page/login_page.dart b/lib/pages/login_page/login_page.dart index 805c083..0692b21 100644 --- a/lib/pages/login_page/login_page.dart +++ b/lib/pages/login_page/login_page.dart @@ -1,24 +1,30 @@ +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_base_widget/pages/login_page/bloc/bloc.dart'; import 'package:zhiying_base_widget/pages/login_page/bloc/login_bloc.dart'; +import 'package:zhiying_base_widget/pages/login_page/bloc/login_repository.dart'; +import 'package:zhiying_base_widget/pages/login_page/model/login_model.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'; /// /// 登陆页面 /// class LoginPage extends StatelessWidget { - final Map data; - LoginPage(this.data, {Key key}) : super(key: key); + + const LoginPage(this.data, {Key key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( + backgroundColor: HexColor.fromHex('#FFFFFF'), body: BlocProvider( - create: (_) => LoginBloc()..add(LoginInitEvent()), - child: SafeArea( - child: LoginPageContainer(), - ), + create: (_) => LoginBloc(repository: LoginRepository())..add(LoginInitEvent()), + child: LoginPageContainer(), ), ); } @@ -30,8 +36,250 @@ class LoginPageContainer extends StatefulWidget { } class _LoginPageContainerState extends State { + + /// 微信登陆 + void _loginClick(String type) { + print('登陆$type'); + if(type == 'mobile'){ + Navigator.push(context, MaterialPageRoute( + builder: (_) => PageFactory.create('login_account', null) + )); + } + } + + /// 第三方登陆 + 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 Container(); + 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 Container(); + }, + ); + } + + /// 主视图 + 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: Icon( + Icons.arrow_back_ios, + size: 22, + color: HexColor.fromHex('#333333'), + ), + ), + 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_util.dart b/lib/pages/login_page/login_util.dart new file mode 100644 index 0000000..86b562e --- /dev/null +++ b/lib/pages/login_page/login_util.dart @@ -0,0 +1,56 @@ + +import 'package:zhiying_base_widget/pages/login_page/model/login_model.dart'; +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'; + +/// 登陆数据管理工具类 +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); + 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; + } + +} \ No newline at end of file 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/model/login_user.dart b/lib/pages/login_page/model/login_user.dart new file mode 100644 index 0000000..9dc33a7 --- /dev/null +++ b/lib/pages/login_page/model/login_user.dart @@ -0,0 +1,43 @@ +/// token : "6209c60befba0f34c3ade079409337713ddab9c5" +/// user_id : "55" +/// username : "1328603811x" +/// perms : ["app_zhiyingshenghuo_access"] +/// register_invite_code_enable : "0" + +class LoginUser { + String token; + String userId; + String username; + List perms; + String registerInviteCodeEnable; + + LoginUser( + {this.token, + this.userId, + this.username, + this.perms, + this.registerInviteCodeEnable}); + + LoginUser.fromJson(Map json) { + token = json['token']; + userId = json['user_id']; + username = json['username']; + perms = json['perms'].cast(); + registerInviteCodeEnable = json['register_invite_code_enable']; + } + + Map toJson() { + final Map data = new Map(); + data['token'] = this.token; + data['user_id'] = this.userId; + data['username'] = this.username; + data['perms'] = this.perms; + data['register_invite_code_enable'] = this.registerInviteCodeEnable; + return data; + } + + @override + String toString() { + return 'LoginUser{token: $token, userId: $userId, username: $username, perms: $perms, registerInviteCodeEnable: $registerInviteCodeEnable}'; + } +} diff --git a/lib/pages/login_page/notifier/user_info_notifier.dart b/lib/pages/login_page/notifier/user_info_notifier.dart new file mode 100644 index 0000000..75ca6fb --- /dev/null +++ b/lib/pages/login_page/notifier/user_info_notifier.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:zhiying_base_widget/pages/login_page/model/login_user.dart'; +import 'package:zhiying_comm/util/empty_util.dart'; + +/// +/// 用户信息 +/// +class UserInfoNotifier with ChangeNotifier { + LoginUser userInfo; + + /// 更新用户数据 + void setUserInfo(LoginUser loginUser) { + print('${loginUser.toString()}'); + this.userInfo = loginUser; + // 缓存数据 TODO + + } + + /// 退出登陆 + void unLogin(){ + this.userInfo = null; + // 清除缓存数据 TODO + } + + /// 获取登陆数据 + LoginUser getLoginUserInfo(){ + if(null != userInfo){ + return userInfo; + } + // TODO 需要读取缓存的数据? + return null; + } + + +} 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/register.dart b/lib/register.dart index 95794de..81fb630 100644 --- a/lib/register.dart +++ b/lib/register.dart @@ -1,4 +1,6 @@ import 'package:zhiying_base_widget/pages/home_page/home_page.dart'; +import 'package:zhiying_base_widget/pages/login_page/login_page.dart'; +import 'package:zhiying_base_widget/pages/login_page/quick/login_quick_page.dart'; import 'package:zhiying_base_widget/pages/main_page/main_page.dart'; import 'package:zhiying_base_widget/pages/wallet_page/wallet_page.dart'; import 'package:zhiying_base_widget/widgets/home/home_banner/home_banner_creater.dart'; @@ -17,6 +19,9 @@ import 'package:zhiying_base_widget/widgets/wallet/wallet_detail/wallet_detail.d import 'package:zhiying_base_widget/widgets/wallet/wallet_income/wallet_income.dart'; import 'package:zhiying_comm/zhiying_comm.dart'; +import 'pages/login_page/account/login_account_page.dart'; +import 'pages/login_page/invite/login_invite_page.dart'; + class BaseWidgetRegister { /// 初始化方法 static void init() { @@ -30,6 +35,10 @@ class BaseWidgetRegister { PageFactory.regist('index', (model) => MainPage(model)); PageFactory.regist('profile', (model) => MainPage(model)); PageFactory.regist('category', (model) => WalletPage()); + PageFactory.regist('login', (model) => LoginPage(model)); + PageFactory.regist('login_quick', (model) => LoginQuickPage(model)); + PageFactory.regist('login_account', (model) => LoginAccountPage(model)); + PageFactory.regist('login_invite', (model) => LoginInvitePage()); } // 注册控件 diff --git a/lib/widgets/home/home_quick_entry/home_quick_entry_widget.dart b/lib/widgets/home/home_quick_entry/home_quick_entry_widget.dart index 9ecab6a..dd7fcc2 100644 --- a/lib/widgets/home/home_quick_entry/home_quick_entry_widget.dart +++ b/lib/widgets/home/home_quick_entry/home_quick_entry_widget.dart @@ -168,14 +168,19 @@ class HomeQuickEntryItem extends StatelessWidget { HomeQuickEntryItem({this.data}); - _itemOnClick(){ - + /// 子图标的点击 + _itemOnClick(context){ + Navigator.push(context, MaterialPageRoute( + builder: (context){ + return PageFactory.create('login', null); + } + )); } @override Widget build(BuildContext context) { return GestureDetector( - onTap: () => _itemOnClick(), + onTap: () => _itemOnClick(context), child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/widgets/home_slide_banner/bloc/home_slide_banner_repository.dart b/lib/widgets/home_slide_banner/bloc/home_slide_banner_repository.dart new file mode 100644 index 0000000..672cb31 --- /dev/null +++ b/lib/widgets/home_slide_banner/bloc/home_slide_banner_repository.dart @@ -0,0 +1,34 @@ + +import 'package:flutter/cupertino.dart'; +import 'package:zhiying_base_widget/widgets/home_slide_banner/model/home_slide_banner_model.dart'; +import 'package:zhiying_comm/util/net_util.dart'; +import 'package:zhiying_comm/util/empty_util.dart'; +import 'package:zhiying_comm/util/global_config.dart'; + +class HomeSlideBannerRepository{ + + /// 获取缓存数据 + Future> fetchCachedDate({@required int id}) async{ + var cached = await NetUtil.getRequestCachedData('/api/v1/mod', params: {'ids': [id]}); + if(!EmptyUtil.isEmpty(cached)){ + HomeSlideBannerModel model = HomeSlideBannerModel.fromJson(cached); + if(null != model && !EmptyUtil.isEmpty(model.items)){ + return model.items; + } + } + return null; + } + + /// 获取数据 + Future> fetchData({@required int id}) async{ + var params = await NetUtil.post('/api/v1/mod', params: {'ids': [id]}); + if(NetUtil.isSuccess(params)){ + HomeSlideBannerModel model = HomeSlideBannerModel.fromJson(params[GlobalConfig.HTTP_RESPONSE_KEY_DATA]); + if(null != model && !EmptyUtil.isEmpty(model.items)){ + return model.items; + } + } + return null; + } + +}