import 'dart:async'; import 'dart:convert'; import 'dart:ui'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:fluwx_no_pay/fluwx_no_pay.dart' as fluwx; import '../../api/user_api.dart'; import 'package:html/dom.dart' as dom; import '../../api/http.dart'; import '../../model/user/user_info.dart'; import '../../ui/common/browser.dart'; import '../../ui/widget/button.dart'; import '../../utils/event_bus_util.dart'; import '../../utils/pageutils.dart'; import '../../utils/push_util.dart'; import '../../utils/string_util.dart'; import '../../utils/ui_constant.dart'; import '../../utils/ui_utils.dart'; import '../../utils/user_util.dart'; import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart'; import '../widget/nav.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: '登录', theme: ThemeData(primaryColor: Color(0xFFF5F5F5)), home: LoginPage(title: ''), ); } } class LoginPage extends StatefulWidget { //阿里云一键登录 static const messageChannel = BasicMessageChannel('AliyunPhoneNumberAuth', StandardMessageCodec()); LoginPage({Key? key, required this.title}) : super(key: key); // This widget is the home page of your application. It is stateful, meaning // that it has a State object (defined below) that contains fields that affect // how it looks. // This class is the configuration for the state. It holds the values (in this // case the title) provided by the parent (in this case the App widget) and // used by the build method of the State. Fields in a Widget subclass are // always marked "final". final String title; @override _LoginPageState createState() => _LoginPageState(); } class _LoginPageState extends State with SingleTickerProviderStateMixin { bool oneKeyLogin = false; bool checked = false; TextEditingController? phoneController = TextEditingController(); TextEditingController? codeController = TextEditingController(); String phone = ""; String code = ""; Timer? timer; //重新发送验证码倒计时 int? reSendSMSTimeLeft; @override void initState() { super.initState(); reSendSMSTimeLeft = -1; //初始化微信登录监听 fluwx.weChatResponseEventHandler.distinct((a, b) => a == b).listen((res) { if (res is fluwx.WeChatAuthResponse) { int errCode = res.errCode; if (errCode == 0) { String? code = res.code; //TODO 把微信登录返回的code传给后台,剩下的事就交给后台处理 } else if (errCode == -4) { //showToast("用户拒绝授权"); } else if (errCode == -2) { // showToast("用户取消授权"); } } }); } bool _aliyunLogin = false; void aliyunOneKeyLogin() async { if (_aliyunLogin) { return; } _aliyunLogin = true; Future.delayed(const Duration(milliseconds: 1000), () { _aliyunLogin = false; }); showLoading(context); Map value = await LoginPage.messageChannel.send({"method": "checkEnv"}) as Map; dismissDialog(context); if (value["code"] == 0) { value = await LoginPage.messageChannel.send({"method": "startLogin"}) as Map; print("返回内容:${jsonEncode(value)}"); if (value["code"] == 0) { Map? resultValue = await UserApiUtil.loginByPhone(context, "", "", value["token"]); LoginPage.messageChannel.send({"method": "closeLogin"}); if (resultValue == null) return; if (resultValue["code"] == 0) { UserInfo user = UserInfo.fromJson(resultValue["data"]); _loginSuccess(user); } else { ToastUtil.toast(value["msg"],context); } } else if (value["code"] == 700000) { //Fluttertoast.showToast(msg: "取消登录了"); } } else { ToastUtil.toast(value["msg"],context); setState(() { oneKeyLogin = false; }); } } void qqLogin() async { Map value = await UserUtil.loginQQ(); } void _loginSuccess(UserInfo user) { UserUtil.setUserInfo(user).then((value) { print("登录成功"); eventBus.fire(LoginEventBus(true)); PushUtil.setAlias(user.id!.toString()).then((value) { Navigator.pop(context); }); }); } @override void dispose() { if (timer != null) { timer!.cancel(); } super.dispose(); } BoxDecoration getItemDecoration(Color bgColor, Color shadowColor) { return BoxDecoration( borderRadius: const BorderRadius.all(Radius.elliptical(10, 10)), color: bgColor, boxShadow: [ BoxShadow( color: shadowColor, blurRadius: 2.0, offset: Offset(0.0, 5.0), //阴影y轴偏移量 spreadRadius: 1 //阴影扩散程度 ) ]); } @override Widget build(BuildContext context) { return Scaffold( resizeToAvoidBottomInset: false, backgroundColor: Colors.white, body: Stack( children: [ Column(children: [ //登录内容区域 Expanded( child: SingleChildScrollView( child: Padding( padding: EdgeInsets.fromLTRB(40, 100, 40, 14), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Image.asset( "assets/imgs/login/ic_login_logo.png", width: 131, ), Container( height: 70, ), Container( constraints: BoxConstraints(minHeight: 200), child: getLoginContent()), Container( height: 30, ), Text( "其他方式登录", style: TextStyle( color: Color(0xFF666666), fontSize: 14), ), Container( height: 21, ), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ // getThirdLoginItem("微信登录", // "assets/images/login/ic_login_wx.png"), // getThirdLoginItem("QQ登录", // "assets/images/login/ic_login_qq.png"), oneKeyLogin ? getThirdLoginItem("手机号登录", "assets/imgs/login/ic_login_phone.png") : getThirdLoginItem("一键登录", "assets/imgs/login/ic_login_onekey.png"), ], ) ])))), //用户协议与隐私政策 Row(children: [ Container( width: 25, ), RoundCheckBox( value: checked, onChanged: (value) { setState(() { checked = value; }); }, ), Expanded( child: Container( child: HtmlWidget( "

登录即表明同意 用户协议 和 隐私政策 

", textStyle: const TextStyle(color: Color(0xFF999999)), onTapUrl: (url) { String? title = ""; if (url == Constant.PROTOCOL_URL) { title = "用户协议"; } else { title = "隐私政策"; } NavigatorUtil.navigateToNextPage( context, BrowserPage( title: title, url: url, ), (data) {}); return true; }))), ]) ]), //关闭按钮 Positioned( top: 30, left: 20, child: InkWell( onTap: () { popPage(context); }, child: Icon( Icons.close, size: 30, ))) ], )); } Widget getLoginContent() { return oneKeyLogin ? Container( child: Column(children: [ MyFillButton( "本机号码一键登录", 10, height: 45, fontSize: 17, onClick: () { aliyunOneKeyLogin(); }, ) ])) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( alignment: Alignment.centerLeft, padding: EdgeInsets.fromLTRB(20, 0, 5, 0), decoration: BoxDecoration( color: const Color(0xFFF5F5F5), borderRadius: BorderRadius.circular(10)), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Image.asset( "assets/imgs/login/icon_phone.png", width: 14, height: 20, ), Container(width: 14), Expanded( child: TextField( style: TextStyle(color: Color(0xFF333333), fontSize: 17), onChanged: (value) { setState(() { phone = value; }); }, textAlign: TextAlign.start, keyboardType: TextInputType.phone, controller: phoneController, maxLength: 11, decoration: InputDecoration( counterText: "", hintText: "请输入手机号", hintStyle: TextStyle(color: Color(0xFFCCCCCC), fontSize: 17), contentPadding: EdgeInsets.only(bottom: 3), border: InputBorder.none, focusedBorder: InputBorder.none, ), )), ], ), ), Container(height: 10), Container( alignment: Alignment.centerLeft, padding: EdgeInsets.fromLTRB(20, 0, 7, 0), decoration: BoxDecoration( color: const Color(0xFFF5F5F5), borderRadius: BorderRadius.circular(10)), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Image.asset( "assets/imgs/login/icon_code.png", width: 16, ), Container(width: 14), Expanded( child: TextField( style: TextStyle(color: Color(0xFF333333), fontSize: 17), onChanged: (value) { setState(() { phone = value; }); }, textAlign: TextAlign.start, keyboardType: TextInputType.phone, controller: codeController, maxLength: 8, decoration: const InputDecoration( counterText: "", hintText: "请输入验证码", hintStyle: TextStyle(color: Color(0xFFCCCCCC), fontSize: 17), contentPadding: EdgeInsets.only(bottom: 3), border: InputBorder.none, focusedBorder: InputBorder.none, ), )), MyFillButton( (reSendSMSTimeLeft! > 0 ? (reSendSMSTimeLeft.toString() + "S重新获取") : reSendSMSTimeLeft == -1 ? "获取验证码" : "重新获取") .toString(), 10, height: 34, padding: const EdgeInsets.fromLTRB(10, 0, 10, 0), enable: (reSendSMSTimeLeft! < 1 && StringUtil.isMobile(phoneController!.value.text)), onClick: () { if (!(reSendSMSTimeLeft! < 1 && StringUtil.isMobile(phoneController!.value.text))) { return; } //发送验证码 UserApiUtil.sendSMS( context, phoneController!.value.text) .then((value) { if (value != null && value["code"] == 0) { setState(() { reSendSMSTimeLeft = 60; //倒计时 timer = Timer.periodic(const Duration(seconds: 1), (timer) { if (reSendSMSTimeLeft! > 0) { setState(() { reSendSMSTimeLeft = reSendSMSTimeLeft! - 1; }); } else { timer.cancel(); } }); }); } else { ToastUtil.toast(value!["msg"],context); } }); }, ), ], ), ), Container(height: 10), const Text("未注册的手机号注册后系统会自动创建账户", style: TextStyle(color: Color(0xFFA0A0A0), fontSize: 12)), Container(height: 20), MyFillButton( "登录", 10, height: 45, color: const Color(0xFFFF2B4B), fontSize: 17, enable: StringUtil.isMobile(phoneController!.value.text) && codeController!.value.text.length >= 4, onClick: () { if (!(StringUtil.isMobile(phoneController!.value.text) && codeController!.value.text.length >= 4)) { return; } if (!checked) { ToastUtil.toast("请同意用户协议与隐私政策",context); return; } UserApiUtil.loginByPhone(context, phoneController!.value.text, codeController!.value.text, "") .then((value) { print("结果: $value"); if (value!["code"] == 0) { UserInfo user = UserInfo.fromJson(value["data"]); _loginSuccess(user); } else { ToastUtil.toast(value["msg"],context); } }); }, ), ], ); } Widget getThirdLoginItem(String name, String iconAsset) { return InkWell( onTap: () { if (name == "一键登录" || name == "手机号登录") { setState(() { oneKeyLogin = !oneKeyLogin; }); } else if (name == "QQ登录") { qqLogin(); } else if (name == "微信登录") { UserUtil.loginWX(); } }, child: Container( constraints: BoxConstraints(minWidth: 80), child: Column( children: [ Image.asset( iconAsset, height: 49, ), Container( height: 8, ), Text( name, style: const TextStyle(color: Color(0xFF9DAAB3), fontSize: 12), ) ], )), ); } }