@@ -1,13 +0,0 @@ | |||
import 'package:flutter/material.dart'; | |||
class LoginPage extends StatefulWidget { | |||
@override | |||
_LoginPageState createState() => _LoginPageState(); | |||
} | |||
class _LoginPageState extends State<LoginPage> { | |||
@override | |||
Widget build(BuildContext context) { | |||
return Material(child: Center(child: Text('登录页'),),); | |||
} | |||
} |
@@ -0,0 +1,3 @@ | |||
export 'login_account_bloc.dart'; | |||
export 'login_account_event.dart'; | |||
export 'login_account_state.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<LoginAccountEvent, LoginAccountState> { | |||
LoginAccountRepository repository; | |||
LoginAccountBloc({@required this.repository}); | |||
@override | |||
LoginAccountState get initialState => LoginAccountInitial(); | |||
@override | |||
Stream<LoginAccountState> 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<LoginAccountState> _mapInitEventToState() async* { | |||
var data = await repository.fetchData(); | |||
if (!EmptyUtil.isEmpty(data)) | |||
yield LoginAccountLoadedState(model: data); | |||
else | |||
yield LoginAccountErrorState(); | |||
} | |||
/// 获取验证码 | |||
Stream<LoginAccountState> _mapGetVcodeEventToState(LoginAccountGetVcodeEvent event) async* { | |||
bool result = await repository.fetchGetVcode(event); | |||
if (result) | |||
yield LoginAccountGetVcodeSuccessState(); | |||
else | |||
yield LoginAccountGetVcodeErrorState(); | |||
} | |||
/// 验证码登陆 | |||
Stream<LoginAccountState> _mapLoginTypeVcodeEventToState(LoginAccountTypeVcodeEvent event) async* { | |||
var result = await repository.loginTypeVcode(event); | |||
if (!EmptyUtil.isEmpty(result)) | |||
yield LoginAccountTypeVcodeLoginSuccessState(model: result); | |||
else | |||
yield LoginAccountTypeVcodeLoginErrorState(); | |||
} | |||
/// 密码登陆 | |||
Stream<LoginAccountState> _mapLoginTypePassEventToState(LoginAccountTypePasswordEvent event) async* {} | |||
} |
@@ -0,0 +1,48 @@ | |||
import 'package:equatable/equatable.dart'; | |||
import 'package:flutter/cupertino.dart'; | |||
abstract class LoginAccountEvent extends Equatable { | |||
const LoginAccountEvent(); | |||
@override | |||
List<Object> get props => []; | |||
} | |||
/// 初始化事件 | |||
class LoginAccountInitEvent extends LoginAccountEvent {} | |||
/// 获取验证码 | |||
class LoginAccountGetVcodeEvent extends LoginAccountEvent{ | |||
final String mobile; | |||
const LoginAccountGetVcodeEvent({@required this.mobile}); | |||
@override | |||
List<Object> get props => [mobile]; | |||
} | |||
/// 核对验证码 | |||
class LoginAccountCheckVcodeEvent extends LoginAccountEvent{ | |||
/// 手机号码 | |||
final String mobile; | |||
/// 验证码 | |||
final String captcha; | |||
const LoginAccountCheckVcodeEvent({@required this.mobile, @required this.captcha}); | |||
@override | |||
List<Object> 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<Object> get props => [this.mobile, this.captcha]; | |||
} |
@@ -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<LoginModel> fetchData() async{ | |||
var result = await LoginUtil.getLoginModel(); | |||
if(!EmptyUtil.isEmpty(result)){ | |||
return result; | |||
} | |||
return null; | |||
} | |||
/// 获取验证码 | |||
Future<bool> 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<bool> loginTypePass(LoginAccountTypePasswordEvent event) async{ | |||
// var result = await NetUtil.post('/api/v1/sign/in', params: {'username'}); | |||
} | |||
/// 验证码登陆 | |||
Future<UserInfoModel> 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<bool> checkVcode(LoginAccountCheckVcodeEvent event) async{ | |||
} | |||
} |
@@ -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<Object> get props => []; | |||
} | |||
/// 初始化状态 | |||
class LoginAccountInitial extends LoginAccountState {} | |||
/// 数据加载完毕状态 | |||
class LoginAccountLoadedState extends LoginAccountState { | |||
final LoginModel model; | |||
const LoginAccountLoadedState({@required this.model}); | |||
@override | |||
List<Object> get props => [this.model]; | |||
} | |||
/// 数据加载出错状态 | |||
class LoginAccountErrorState extends LoginAccountState {} | |||
/// 验证码下发成功的状态 | |||
class LoginAccountGetVcodeSuccessState extends LoginAccountState { | |||
final String msg; | |||
const LoginAccountGetVcodeSuccessState({this.msg}); | |||
@override | |||
List<Object> 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<Object> get props => [this.model]; | |||
} | |||
/// 验证码登陆失败 | |||
class LoginAccountTypeVcodeLoginErrorState extends LoginAccountState { | |||
final String msg; | |||
const LoginAccountTypeVcodeLoginErrorState({this.msg}); | |||
} |
@@ -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<String, dynamic> 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<LoginAccountBloc>( | |||
create: (_) => LoginAccountBloc(repository: LoginAccountRepository())..add(LoginAccountInitEvent()), | |||
child: LoginAccountPageContianer(), | |||
), | |||
); | |||
} | |||
} | |||
/// 啦啦啦 | |||
class LoginAccountPageContianer extends StatefulWidget { | |||
@override | |||
_LoginAccountPageContianerState createState() => _LoginAccountPageContianerState(); | |||
} | |||
/// | |||
/// 主体逻辑 | |||
/// | |||
class _LoginAccountPageContianerState extends State<LoginAccountPageContianer> 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<dynamic> route) => false, | |||
); | |||
} | |||
/// 返回上一页 | |||
void _openPop() { | |||
if (Navigator.canPop(context)) { | |||
Navigator.pop(context); | |||
} | |||
} | |||
/// 登陆 | |||
void _submitOnClick() { | |||
print('登陆'); | |||
if (_checkParam(true)) { | |||
if (_useVcode) { | |||
BlocProvider.of<LoginAccountBloc>(context).add(LoginAccountTypeVcodeEvent(mobile: _phoneEdController?.text?.toString()?.trim() ?? '', captcha: _vcodeEdController?.text?.toString()?.trim() ?? '')); | |||
} else { | |||
BlocProvider.of<LoginAccountBloc>(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<LoginAccountBloc>(context).add(LoginAccountGetVcodeEvent(mobile: _phoneEdController?.text?.toString()?.trim() ?? '')); | |||
return true; | |||
} | |||
return false; | |||
} | |||
@override | |||
Widget build(BuildContext context) { | |||
return BlocConsumer<LoginAccountBloc, LoginAccountState>( | |||
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<UserInfoNotifier>(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: <Widget>[ | |||
/// 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: <Widget>[ | |||
_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: <Widget>[ | |||
// // 输入框 | |||
// 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: <Widget>[ | |||
/// 图标 | |||
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<String> 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: <Widget>[ | |||
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, | |||
), | |||
), | |||
), | |||
], | |||
), | |||
); | |||
} | |||
} |
@@ -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: <Widget>[ | |||
/// 标题 | |||
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)), | |||
), | |||
); | |||
} | |||
} |
@@ -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<StatefulWidget> createState() { | |||
return SlideVerifyState(); | |||
} | |||
} | |||
class SlideVerifyState extends State<SlideVerifyWidget> 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: <Widget>[ | |||
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: <Widget>[ | |||
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(); | |||
} | |||
}); | |||
} | |||
} |
@@ -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<VcodeWidget> { | |||
/// 是否可以获取验证码 | |||
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(); | |||
} |
@@ -0,0 +1,3 @@ | |||
export 'login_bloc.dart'; | |||
export 'login_event.dart'; | |||
export 'login_state.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<LoginEvent, LoginState> { | |||
LoginRepository repository; | |||
LoginBloc({this.repository}); | |||
@override | |||
LoginState get initialState => InitialLoginState(); | |||
@override | |||
Stream<LoginState> mapEventToState( | |||
LoginEvent event, | |||
) async* { | |||
// TODO: Add Logic | |||
final currentState = state; | |||
/// 初始化 | |||
if (event is LoginInitEvent) { | |||
yield* _mapLoginInitEventToState(event); | |||
} | |||
} | |||
/// 获取页面数据 | |||
Stream<LoginState> _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(); | |||
} | |||
} |
@@ -0,0 +1,10 @@ | |||
import 'package:equatable/equatable.dart'; | |||
abstract class LoginEvent extends Equatable { | |||
const LoginEvent(); | |||
@override | |||
List<Object> get props => []; | |||
} | |||
/// 初始化 | |||
class LoginInitEvent extends LoginEvent{} |
@@ -0,0 +1,26 @@ | |||
import 'package:zhiying_comm/pages/login_page/model/login_model.dart'; | |||
import '../login_util.dart'; | |||
class LoginRepository { | |||
/// 获取页面数据 | |||
Future<LoginModel> 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<LoginModel> 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(); | |||
} | |||
} |
@@ -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<Object> get props => []; | |||
} | |||
/// 初始化状态 | |||
class InitialLoginState extends LoginState {} | |||
/// 缓存数据状态 | |||
class LoginCacheState extends LoginState { | |||
final LoginModel model; | |||
const LoginCacheState({this.model}); | |||
@override | |||
List<Object> get props => [this.model]; | |||
} | |||
/// 数据加载完毕状态 | |||
class LoginLoadedState extends LoginState { | |||
final LoginModel model; | |||
const LoginLoadedState({this.model}); | |||
@override | |||
List<Object> get props => [this.model]; | |||
} | |||
/// 数据加载出错 | |||
class LoginErrorState extends LoginState {} |
@@ -0,0 +1,3 @@ | |||
export 'login_invite_bloc.dart'; | |||
export 'login_invite_event.dart'; | |||
export 'login_invite_state.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<LoginInviteEvent, LoginInviteState> { | |||
LoginInviteRepository repostitory; | |||
LoginInviteBloc({@required this.repostitory}); | |||
@override | |||
LoginInviteState get initialState => LoginInviteInitial(); | |||
@override | |||
Stream<LoginInviteState> 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<LoginInviteState> _mapInitEventToState(LoginInviteInitEvent event) async* { | |||
var data = await repostitory.fetchData(); | |||
if (!EmptyUtil.isEmpty(data)) { | |||
yield LoginInviteLoadedState(model: data); | |||
} | |||
} | |||
/// 查询邀请人 | |||
Stream<LoginInviteState> _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<LoginInviteState> _mapSubmitEventToState(LoginInviteSubmitEvent event) async* { | |||
var data = await repostitory.submitInvite(event); | |||
if (!EmptyUtil.isEmpty(data)) | |||
yield LoginInviteSubmitSuccess(model: data); | |||
else | |||
yield LoginInviteSubmitErrorState(); | |||
} | |||
} |
@@ -0,0 +1,29 @@ | |||
import 'package:equatable/equatable.dart'; | |||
import 'package:flutter/cupertino.dart'; | |||
abstract class LoginInviteEvent extends Equatable { | |||
const LoginInviteEvent(); | |||
@override | |||
List<Object> get props => []; | |||
} | |||
/// 初始化 | |||
class LoginInviteInitEvent extends LoginInviteEvent {} | |||
/// 查询邀请人方法 | |||
class LoginInviteQueryEvent extends LoginInviteEvent { | |||
final String num; | |||
const LoginInviteQueryEvent({@required this.num}); | |||
@override | |||
List<Object> get props => [this.num]; | |||
} | |||
/// 提交方法 | |||
class LoginInviteSubmitEvent extends LoginInviteEvent { | |||
final String num; | |||
final String mobile; | |||
const LoginInviteSubmitEvent({@required this.num, @required this.mobile}); | |||
@override | |||
List<Object> get props => [this.num, this.mobile]; | |||
} |
@@ -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<LoginModel> fetchData() async { | |||
pageModel = await LoginUtil.getLoginModel(); | |||
if (!EmptyUtil.isEmpty(pageModel)) { | |||
return pageModel; | |||
} | |||
return null; | |||
} | |||
/// 获取邀请人信息 | |||
Future<LoginInviteUser> 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<UserInfoModel> 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; | |||
} | |||
} |
@@ -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<Object> get props => []; | |||
} | |||
/// 初始化状态 | |||
class LoginInviteInitial extends LoginInviteState {} | |||
/// 数据加载完毕状态 | |||
class LoginInviteLoadedState extends LoginInviteState { | |||
final LoginModel model; | |||
const LoginInviteLoadedState({@required this.model}); | |||
@override | |||
List<Object> get props => [this.model]; | |||
} | |||
/// 数据加载出错 | |||
class LoginInviteErrorState extends LoginInviteState { | |||
final String msg; | |||
const LoginInviteErrorState({this.msg}); | |||
@override | |||
List<Object> get props => [this.msg]; | |||
} | |||
/// 邀请码成功 | |||
class LoginInviteSubmitSuccess extends LoginInviteState { | |||
final UserInfoModel model; | |||
const LoginInviteSubmitSuccess({@required this.model}); | |||
@override | |||
List<Object> get props => [this.model]; | |||
} | |||
/// 邀请码失败 | |||
class LoginInviteSubmitErrorState extends LoginInviteState { | |||
final String msg; | |||
const LoginInviteSubmitErrorState({this.msg}); | |||
@override | |||
List<Object> get props => [this.msg]; | |||
} | |||
/// 查询邀请人成功 | |||
class LoginInviteQuerySuccessState extends LoginInviteState { | |||
final LoginInviteUser model; | |||
final LoginModel pageMdel; | |||
const LoginInviteQuerySuccessState({@required this.model, @required this.pageMdel}); | |||
@override | |||
List<Object> get props => [this.model]; | |||
} | |||
/// 查询邀请人失败 | |||
class LoginInviteQueryErrorState extends LoginInviteState { | |||
final String msg; | |||
const LoginInviteQueryErrorState({this.msg}); | |||
@override | |||
List<Object> get props => [this.msg]; | |||
} |
@@ -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<LoginInvitePageContainer> { | |||
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<dynamic> 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<LoginInviteBloc>(context).add(LoginInviteQueryEvent(num: inviteNum)); | |||
} | |||
/// 填写邀请啊吗 | |||
void _submitOnClick(LoginInviteUser inviteUser) async { | |||
_focusNode.unfocus(); | |||
/// 邀请码 | |||
String inviteNum = inviteUser?.userId ?? ''; | |||
/// 手机号 | |||
UserInfoModel model = await Provider.of<UserInfoNotifier>(context, listen: false)?.getUserInfoModel(); | |||
String mobile = model?.mobile ?? ''; | |||
if (!EmptyUtil.isEmpty(inviteNum) && !EmptyUtil.isEmpty(mobile)) { | |||
BlocProvider.of<LoginInviteBloc>(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<LoginInviteBloc, LoginInviteState>( | |||
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<UserInfoNotifier>(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: <Widget>[ | |||
/// 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: <Widget>[ | |||
/// 头像 | |||
CircleAvatar( | |||
radius: 23.5, | |||
backgroundImage: CachedNetworkImageProvider(model?.avatar ?? ''), | |||
), | |||
const SizedBox(width: 13), | |||
Column( | |||
mainAxisAlignment: MainAxisAlignment.spaceAround, | |||
crossAxisAlignment: CrossAxisAlignment.start, | |||
children: <Widget>[ | |||
/// 名字 | |||
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<String> 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: <Widget>[ | |||
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, | |||
), | |||
), | |||
), | |||
], | |||
), | |||
); | |||
} | |||
} |
@@ -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<String, dynamic> 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<String, dynamic> toJson() { | |||
final Map<String, dynamic> data = new Map<String, dynamic>(); | |||
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; | |||
} | |||
} |
@@ -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<String, dynamic> data; | |||
const LoginPage(this.data, {Key key}) : super(key: key); | |||
@override | |||
Widget build(BuildContext context) { | |||
return Scaffold( | |||
backgroundColor: HexColor.fromHex('#FFFFFF'), | |||
body: BlocProvider<LoginBloc>( | |||
create: (_) => LoginBloc(repository: LoginRepository())..add(LoginInitEvent()), | |||
child: LoginPageContainer(), | |||
), | |||
); | |||
} | |||
} | |||
class LoginPageContainer extends StatefulWidget { | |||
@override | |||
_LoginPageContainerState createState() => _LoginPageContainerState(); | |||
} | |||
class _LoginPageContainerState extends State<LoginPageContainer> { | |||
/// 微信登陆 | |||
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<LoginBloc, LoginState>( | |||
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: <Widget>[ | |||
/// 头部 | |||
_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: <Widget>[ | |||
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: <Widget>[ | |||
/// 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: <Widget>[ | |||
_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: <Widget>[ | |||
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: <Widget>[ | |||
// icon | |||
CachedNetworkImage(imageUrl: iconUrl, width: 12), | |||
SizedBox(width: 8), | |||
// text | |||
Text(text, style: TextStyle(color: HexColor.fromHex(textColor), fontSize: 15)) | |||
], | |||
), | |||
), | |||
); | |||
} | |||
} |
@@ -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: <Widget>[ | |||
Container( | |||
height: 230, | |||
child: Column( | |||
children: <Widget>[ | |||
/// 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: <Widget>[ | |||
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)), | |||
), | |||
); | |||
} | |||
} |
@@ -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<LoginModel> 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<LoginModel> fetchCachePageData() async { | |||
var result = await NetUtil.getRequestCachedData(_URL); | |||
if (!EmptyUtil.isEmpty(result)) { | |||
LoginModel model = LoginModel.fromJson(result); | |||
return model; | |||
} | |||
return null; | |||
} | |||
/// 获取页面数据 | |||
static Future<LoginModel> 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; | |||
} | |||
} |
@@ -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<String, dynamic> 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<String, dynamic> toJson() { | |||
final Map<String, dynamic> data = new Map<String, dynamic>(); | |||
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> agreements; | |||
List<ImportanceLogin> importanceLogin; | |||
String otherIconsTitle; | |||
String otherIconsTitleColor; | |||
String otherExpansionIcon; | |||
List<BottomIcons> 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<String, dynamic> 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<Agreements>(); | |||
json['agreements'].forEach((v) { | |||
agreements.add(new Agreements.fromJson(v)); | |||
}); | |||
} | |||
if (json['importance_login'] != null) { | |||
importanceLogin = new List<ImportanceLogin>(); | |||
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<BottomIcons>(); | |||
json['bottom_icons'].forEach((v) { | |||
bottomIcons.add(new BottomIcons.fromJson(v)); | |||
}); | |||
} | |||
} | |||
Map<String, dynamic> toJson() { | |||
final Map<String, dynamic> data = new Map<String, dynamic>(); | |||
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<String, dynamic> json) { | |||
text = json['text']; | |||
textColor = json['text_color']; | |||
url = json['url']; | |||
} | |||
Map<String, dynamic> toJson() { | |||
final Map<String, dynamic> data = new Map<String, dynamic>(); | |||
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<String, dynamic> 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<String, dynamic> toJson() { | |||
final Map<String, dynamic> data = new Map<String, dynamic>(); | |||
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<String, dynamic> json) { | |||
type = json['type']; | |||
img = json['img']; | |||
} | |||
Map<String, dynamic> toJson() { | |||
final Map<String, dynamic> data = new Map<String, dynamic>(); | |||
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> 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<String, dynamic> 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<Protocol>(); | |||
json['protocol'].forEach((v) { | |||
protocol.add(new Protocol.fromJson(v)); | |||
}); | |||
} | |||
} | |||
Map<String, dynamic> toJson() { | |||
final Map<String, dynamic> data = new Map<String, dynamic>(); | |||
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<String, dynamic> 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<String, dynamic> toJson() { | |||
final Map<String, dynamic> data = new Map<String, dynamic>(); | |||
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> 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<String, dynamic> 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<Protocol>(); | |||
json['protocol'].forEach((v) { | |||
protocol.add(new Protocol.fromJson(v)); | |||
}); | |||
} | |||
} | |||
Map<String, dynamic> toJson() { | |||
final Map<String, dynamic> data = new Map<String, dynamic>(); | |||
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<String, dynamic> json) { | |||
text = json['text']; | |||
textColor = json['text_color']; | |||
url = json['url']; | |||
} | |||
Map<String, dynamic> toJson() { | |||
final Map<String, dynamic> data = new Map<String, dynamic>(); | |||
data['text'] = this.text; | |||
data['text_color'] = this.textColor; | |||
data['url'] = this.url; | |||
return data; | |||
} | |||
} |
@@ -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<String, dynamic> 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<LoginQuickContainerPage> { | |||
/// 登陆事件 | |||
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: <Widget>[ | |||
/// 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'), | |||
), | |||
); | |||
} | |||
} |
@@ -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<String, dynamic> model, BuildContext context) { | |||
static Future route(Map<String, dynamic> 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<UserInfoNotifier>(context, listen: false).getUserInfoModel(); | |||
UserInfoModel user = await Provider.of<UserInfoNotifier>(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); | |||
} | |||
@@ -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: | |||