feat: app端登录-开发中

This commit is contained in:
guozhigq
2023-11-11 23:18:19 +08:00
parent 13ce50f730
commit d105718fbf
14 changed files with 928 additions and 10 deletions

View File

@ -0,0 +1,204 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:pilipala/http/login.dart';
import 'package:gt3_flutter_plugin/gt3_flutter_plugin.dart';
import 'package:pilipala/models/login/index.dart';
class LoginPageController extends GetxController {
final GlobalKey mobFormKey = GlobalKey<FormState>();
final GlobalKey passwordFormKey = GlobalKey<FormState>();
final GlobalKey msgCodeFormKey = GlobalKey<FormState>();
final TextEditingController mobTextController = TextEditingController();
final TextEditingController passwordTextController = TextEditingController();
final TextEditingController msgCodeTextController = TextEditingController();
final FocusNode mobTextFieldNode = FocusNode();
final FocusNode passwordTextFieldNode = FocusNode();
final FocusNode msgCodeTextFieldNode = FocusNode();
final PageController pageViewController = PageController();
RxInt currentIndex = 0.obs;
final Gt3FlutterPlugin captcha = Gt3FlutterPlugin();
// 默认密码登录
RxInt loginType = 0.obs;
// 监听pageView切换
void onPageChange(int index) {
currentIndex.value = index;
}
// 输入手机号 下一页
void nextStep() async {
if ((mobFormKey.currentState as FormState).validate()) {
await pageViewController.animateToPage(
1,
duration: const Duration(microseconds: 3000),
curve: Curves.easeInOut,
);
passwordTextFieldNode.requestFocus();
}
}
// 上一页
void previousPage() async {
passwordTextFieldNode.unfocus();
await Future.delayed(const Duration(milliseconds: 200));
pageViewController.animateToPage(
0,
duration: const Duration(microseconds: 300),
curve: Curves.easeInOut,
);
}
// 切换登录方式
void changeLoginType() {
loginType.value = loginType.value == 0 ? 1 : 0;
if (loginType.value == 0) {
passwordTextFieldNode.requestFocus();
} else {
msgCodeTextFieldNode.requestFocus();
}
}
// app端密码登录
void loginInByAppPassword() async {
if ((passwordFormKey.currentState as FormState).validate()) {
var webKeyRes = await LoginHttp.getWebKey();
if (webKeyRes['status']) {
String rhash = webKeyRes['data']['hash'];
String key = webKeyRes['data']['key'];
LoginHttp.loginInByMobPwd(
tel: mobTextController.text,
password: passwordTextController.text,
key: key,
rhash: rhash,
);
} else {
SmartDialog.showToast(webKeyRes['msg']);
}
}
}
// 验证码登录
void loginInByCode() {
if ((msgCodeFormKey.currentState as FormState).validate()) {}
}
// app端验证码
void getMsgCode() async {
getCaptcha((data) async {
CaptchaDataModel captchaData = data;
var res = await LoginHttp.sendAppSmsCode(
cid: 86,
tel: 13734077064,
token: captchaData.token!,
challenge: captchaData.geetest!.challenge!,
validate: captchaData.validate!,
seccode: captchaData.seccode!,
);
print(res);
});
}
// 申请极验验证码
Future getCaptcha(oncall) async {
SmartDialog.showLoading(msg: '请求中...');
var result = await LoginHttp.queryCaptcha();
if (result['status']) {
CaptchaDataModel captchaData = result['data'];
var registerData = Gt3RegisterData(
challenge: captchaData.geetest!.challenge,
gt: captchaData.geetest!.gt!,
success: true,
);
captcha.addEventHandler(onShow: (Map<String, dynamic> message) async {
SmartDialog.dismiss();
}, onClose: (Map<String, dynamic> message) async {
SmartDialog.showToast('关闭验证');
}, onResult: (Map<String, dynamic> message) async {
debugPrint("Captcha result: $message");
String code = message["code"];
if (code == "1") {
// 发送 message["result"] 中的数据向 B 端的业务服务接口进行查询
SmartDialog.showToast('验证成功');
captchaData.validate = message['result']['geetest_validate'];
captchaData.seccode = message['result']['geetest_seccode'];
captchaData.geetest!.challenge =
message['result']['geetest_challenge'];
oncall(captchaData);
} else {
// 终端用户完成验证失败,自动重试 If the verification fails, it will be automatically retried.
debugPrint("Captcha result code : $code");
}
}, onError: (Map<String, dynamic> message) async {
String code = message["code"];
// 处理验证中返回的错误 Handling errors returned in verification
if (Platform.isAndroid) {
// Android 平台
if (code == "-2") {
// Dart 调用异常 Call exception
} else if (code == "-1") {
// Gt3RegisterData 参数不合法 Parameter is invalid
} else if (code == "201") {
// 网络无法访问 Network inaccessible
} else if (code == "202") {
// Json 解析错误 Analysis error
} else if (code == "204") {
// WebView 加载超时,请检查是否混淆极验 SDK Load timed out
} else if (code == "204_1") {
// WebView 加载前端页面错误,请查看日志 Error loading front-end page, please check the log
} else if (code == "204_2") {
// WebView 加载 SSLError
} else if (code == "206") {
// gettype 接口错误或返回为 null API error or return null
} else if (code == "207") {
// getphp 接口错误或返回为 null API error or return null
} else if (code == "208") {
// ajax 接口错误或返回为 null API error or return null
} else {
// 更多错误码参考开发文档 More error codes refer to the development document
// https://docs.geetest.com/sensebot/apirefer/errorcode/android
}
}
if (Platform.isIOS) {
// iOS 平台
if (code == "-1009") {
// 网络无法访问 Network inaccessible
} else if (code == "-1004") {
// 无法查找到 HOST Unable to find HOST
} else if (code == "-1002") {
// 非法的 URL Illegal URL
} else if (code == "-1001") {
// 网络超时 Network timeout
} else if (code == "-999") {
// 请求被意外中断, 一般由用户进行取消操作导致 The interrupted request was usually caused by the user cancelling the operation
} else if (code == "-21") {
// 使用了重复的 challenge Duplicate challenges are used
// 检查获取 challenge 是否进行了缓存 Check if the fetch challenge is cached
} else if (code == "-20") {
// 尝试过多, 重新引导用户触发验证即可 Try too many times, lead the user to request verification again
} else if (code == "-10") {
// 预判断时被封禁, 不会再进行图形验证 Banned during pre-judgment, and no more image captcha verification
} else if (code == "-2") {
// Dart 调用异常 Call exception
} else if (code == "-1") {
// Gt3RegisterData 参数不合法 Parameter is invalid
} else {
// 更多错误码参考开发文档 More error codes refer to the development document
// https://docs.geetest.com/sensebot/apirefer/errorcode/ios
}
}
});
captcha.startCaptcha(registerData);
} else {}
}
}

View File

@ -0,0 +1,4 @@
library login;
export './controller.dart';
export 'view.dart';

362
lib/pages/login/view.dart Normal file
View File

@ -0,0 +1,362 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'controller.dart';
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final LoginPageController _loginPageCtr = Get.put(LoginPageController());
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: Obx(
() => _loginPageCtr.currentIndex.value == 0
? IconButton(
onPressed: () async {
_loginPageCtr.mobTextFieldNode.unfocus();
await Future.delayed(const Duration(milliseconds: 200));
Get.back();
},
icon: const Icon(Icons.close_outlined),
)
: IconButton(
onPressed: () => _loginPageCtr.previousPage(),
icon: const Icon(Icons.arrow_back),
),
),
),
body: PageView(
physics: const NeverScrollableScrollPhysics(),
controller: _loginPageCtr.pageViewController,
onPageChanged: (int index) => _loginPageCtr.onPageChange(index),
children: [
Padding(
padding: EdgeInsets.only(
left: 20,
right: 20,
top: 10,
bottom: MediaQuery.of(context).padding.bottom + 10,
),
child: Form(
key: _loginPageCtr.mobFormKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
Text(
'登录',
style: Theme.of(context).textTheme.titleLarge!.copyWith(
letterSpacing: 1,
height: 2.1,
fontSize: 34,
fontWeight: FontWeight.w500),
),
Row(
children: [
Text(
'请使用您的 BiliBili 账号登录。',
style: Theme.of(context).textTheme.titleSmall!,
),
GestureDetector(
onTap: () {},
child: const Icon(Icons.info_outline, size: 16),
)
],
),
Container(
margin: const EdgeInsets.only(top: 38, bottom: 15),
child: TextFormField(
controller: _loginPageCtr.mobTextController,
focusNode: _loginPageCtr.mobTextFieldNode,
keyboardType: TextInputType.number,
decoration: InputDecoration(
isDense: true,
labelText: '输入手机号码',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(6.0),
),
),
// 校验用户名
validator: (v) {
return v!.trim().isNotEmpty ? null : "手机号码不能为空";
},
onSaved: (val) {
print(val);
},
onEditingComplete: () {
_loginPageCtr.nextStep();
},
),
),
GestureDetector(
onTap: () {
Get.offNamed(
'/webview',
parameters: {
'url':
'https://passport.bilibili.com/h5-app/passport/login',
'type': 'login',
'pageTitle': '登录bilibili',
},
);
},
child: Padding(
padding: const EdgeInsets.only(left: 2),
child: Text(
'使用网页端登录',
style: TextStyle(
color: Theme.of(context).colorScheme.primary),
),
),
),
const Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(onPressed: () {}, child: const Text('中国大陆')),
TextButton(
style: TextButton.styleFrom(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
foregroundColor:
Theme.of(context).colorScheme.onPrimary,
backgroundColor:
Theme.of(context).colorScheme.primary, // 设置按钮背景色
),
onPressed: () => _loginPageCtr.nextStep(),
child: const Text('下一步'),
)
],
),
],
),
),
),
Padding(
padding: EdgeInsets.only(
left: 20,
right: 20,
top: 10,
bottom: MediaQuery.of(context).padding.bottom + 10,
),
child: Obx(
() => _loginPageCtr.loginType.value == 0
? Form(
key: _loginPageCtr.passwordFormKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
Row(
children: [
Text(
'密码登录',
style: Theme.of(context)
.textTheme
.titleLarge!
.copyWith(
letterSpacing: 1,
height: 2.1,
fontSize: 34,
fontWeight: FontWeight.w500),
),
const SizedBox(width: 4),
IconButton(
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.resolveWith(
(states) {
return Theme.of(context)
.colorScheme
.primary
.withOpacity(0.1);
}),
),
onPressed: () =>
_loginPageCtr.changeLoginType(),
icon: const Icon(Icons.swap_vert_outlined),
)
],
),
Text(
'请输入您的 BiliBili 密码。',
style: Theme.of(context).textTheme.titleSmall!,
),
Container(
margin: const EdgeInsets.only(top: 38, bottom: 15),
child: TextFormField(
controller: _loginPageCtr.passwordTextController,
focusNode: _loginPageCtr.passwordTextFieldNode,
keyboardType: TextInputType.visiblePassword,
decoration: InputDecoration(
isDense: true,
labelText: '输入密码',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(6.0),
),
),
// 校验用户名
validator: (v) {
return v!.trim().isNotEmpty ? null : "密码不能为空";
},
onSaved: (val) {
print(val);
},
),
),
const Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => _loginPageCtr.previousPage(),
child: const Text('上一步'),
),
const SizedBox(width: 15),
TextButton(
style: TextButton.styleFrom(
padding:
const EdgeInsets.fromLTRB(20, 0, 20, 0),
foregroundColor:
Theme.of(context).colorScheme.onPrimary,
backgroundColor: Theme.of(context)
.colorScheme
.primary, // 设置按钮背景色
),
onPressed: () =>
_loginPageCtr.loginInByAppPassword(),
child: const Text('确认登录'),
)
],
),
],
),
)
: Form(
key: _loginPageCtr.msgCodeFormKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
Row(
children: [
Text(
'验证码登录',
style: Theme.of(context)
.textTheme
.titleLarge!
.copyWith(
letterSpacing: 1,
height: 2.1,
fontSize: 34,
fontWeight: FontWeight.w500),
),
const SizedBox(width: 4),
IconButton(
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.resolveWith(
(states) {
return Theme.of(context)
.colorScheme
.primary
.withOpacity(0.1);
}),
),
onPressed: () =>
_loginPageCtr.changeLoginType(),
icon: const Icon(Icons.swap_vert_outlined),
)
],
),
Text(
'请输入收到到验证码。',
style: Theme.of(context).textTheme.titleSmall!,
),
Container(
margin: const EdgeInsets.only(top: 38, bottom: 15),
child: Stack(
children: [
TextFormField(
controller:
_loginPageCtr.msgCodeTextController,
focusNode: _loginPageCtr.msgCodeTextFieldNode,
maxLength: 6,
keyboardType: TextInputType.number,
decoration: InputDecoration(
isDense: true,
labelText: '输入验证码',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(6.0),
),
),
// 校验用户名
validator: (v) {
return v!.trim().isNotEmpty
? null
: "验证码不能为空";
},
onSaved: (val) {
print(val);
},
),
Positioned(
right: 8,
top: 4,
child: Center(
child: TextButton(
onPressed: () =>
_loginPageCtr.getMsgCode(),
child: const Text('获取验证码'),
),
),
),
],
),
),
const Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => _loginPageCtr.previousPage(),
child: const Text('上一步'),
),
const SizedBox(width: 15),
TextButton(
style: TextButton.styleFrom(
padding:
const EdgeInsets.fromLTRB(20, 0, 20, 0),
foregroundColor:
Theme.of(context).colorScheme.onPrimary,
backgroundColor: Theme.of(context)
.colorScheme
.primary, // 设置按钮背景色
),
onPressed: () => _loginPageCtr.loginInByCode(),
child: const Text('确认登录'),
)
],
),
],
),
),
),
),
],
),
);
}
}

View File

@ -41,6 +41,7 @@ class MineController extends GetxController {
'pageTitle': '登录bilibili',
},
);
// Get.toNamed('/loginPage');
} else {
int mid = userInfo.value.mid!;
String face = userInfo.value.face!;