mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
feat: 新版登录页:以APP接口和新界面全面重构网页版登录;更新二维码与极验插件;更新版本号
This commit is contained in:
@@ -1,204 +1,439 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:PiliPalaX/common/constants.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:PiliPalaX/http/login.dart';
|
||||
import 'package:gt3_flutter_plugin/gt3_flutter_plugin.dart';
|
||||
import 'package:PiliPalaX/models/login/index.dart';
|
||||
import '../../utils/login.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:webview_cookie_manager/webview_cookie_manager.dart';
|
||||
|
||||
class LoginPageController extends GetxController {
|
||||
final GlobalKey mobFormKey = GlobalKey<FormState>();
|
||||
final GlobalKey passwordFormKey = GlobalKey<FormState>();
|
||||
final GlobalKey msgCodeFormKey = GlobalKey<FormState>();
|
||||
import '../../http/constants.dart';
|
||||
import '../../http/init.dart';
|
||||
import '../../http/user.dart';
|
||||
import '../../utils/storage.dart';
|
||||
import '../home/controller.dart';
|
||||
import '../media/controller.dart';
|
||||
|
||||
final TextEditingController mobTextController = TextEditingController();
|
||||
class LoginPageController extends GetxController
|
||||
with GetSingleTickerProviderStateMixin {
|
||||
final TextEditingController telTextController = TextEditingController();
|
||||
final TextEditingController usernameTextController = TextEditingController();
|
||||
final TextEditingController passwordTextController = TextEditingController();
|
||||
final TextEditingController msgCodeTextController = TextEditingController();
|
||||
final TextEditingController smsCodeTextController = TextEditingController();
|
||||
|
||||
final FocusNode mobTextFieldNode = FocusNode();
|
||||
final FocusNode passwordTextFieldNode = FocusNode();
|
||||
final FocusNode msgCodeTextFieldNode = FocusNode();
|
||||
Rx<Map<String, dynamic>> codeInfo = Rx<Map<String, dynamic>>({});
|
||||
|
||||
final PageController pageViewController = PageController();
|
||||
|
||||
RxInt currentIndex = 0.obs;
|
||||
late TabController tabController;
|
||||
|
||||
final Gt3FlutterPlugin captcha = Gt3FlutterPlugin();
|
||||
|
||||
// 默认密码登录
|
||||
RxInt loginType = 0.obs;
|
||||
CaptchaDataModel captchaData = CaptchaDataModel();
|
||||
RxInt qrCodeLeftTime = 180.obs;
|
||||
Rx<String> statusQRCode = ''.obs;
|
||||
|
||||
// 监听pageView切换
|
||||
void onPageChange(int index) {
|
||||
currentIndex.value = index;
|
||||
Map<String, dynamic> selectedCountryCodeId =
|
||||
Constants.internationalDialingPrefix.first;
|
||||
String captchaKey = '';
|
||||
RxInt smsSendCooldown = 0.obs;
|
||||
int smsSendTimestamp = 0;
|
||||
|
||||
// 定时器
|
||||
Timer? qrCodeTimer;
|
||||
Timer? smsSendCooldownTimer;
|
||||
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
tabController = TabController(length: 3, vsync: this)
|
||||
..addListener(_handleTabChange);
|
||||
}
|
||||
|
||||
// 输入手机号 下一页
|
||||
void nextStep() async {
|
||||
if ((mobFormKey.currentState as FormState).validate()) {
|
||||
await pageViewController.animateToPage(
|
||||
1,
|
||||
duration: const Duration(microseconds: 3000),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
passwordTextFieldNode.requestFocus();
|
||||
}
|
||||
@override
|
||||
void onClose() {
|
||||
tabController.removeListener(_handleTabChange);
|
||||
tabController.dispose();
|
||||
qrCodeTimer?.cancel();
|
||||
smsSendCooldownTimer?.cancel();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
// 上一页
|
||||
void previousPage() async {
|
||||
passwordTextFieldNode.unfocus();
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
pageViewController.animateToPage(
|
||||
0,
|
||||
duration: const Duration(microseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
void refreshQRCode() {
|
||||
LoginHttp.getHDcode().then((res) {
|
||||
if (res['status']) {
|
||||
qrCodeTimer?.cancel();
|
||||
codeInfo.value = res;
|
||||
codeInfo.refresh();
|
||||
print("codeInfo");
|
||||
print(codeInfo);
|
||||
qrCodeTimer = Timer.periodic(const Duration(milliseconds: 1000), (t) {
|
||||
qrCodeLeftTime.value = 180 - t.tick;
|
||||
if (qrCodeLeftTime <= 0) {
|
||||
t.cancel();
|
||||
statusQRCode.value = '二维码已过期,请刷新';
|
||||
qrCodeLeftTime = 0.obs;
|
||||
return;
|
||||
}
|
||||
|
||||
// 切换登录方式
|
||||
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,
|
||||
);
|
||||
LoginHttp.codePoll(codeInfo.value['data']['auth_code'])
|
||||
.then((value) async {
|
||||
if (value['status']) {
|
||||
t.cancel();
|
||||
statusQRCode.value = '扫码成功';
|
||||
print(value['data']);
|
||||
await afterLoginByApp(
|
||||
value['data'], value['data']['cookie_info']);
|
||||
Get.back();
|
||||
} else if (value['code'] == 86038) {
|
||||
t.cancel();
|
||||
qrCodeLeftTime = 0.obs;
|
||||
} else {
|
||||
statusQRCode.value = value['msg'];
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
SmartDialog.showToast(webKeyRes['msg']);
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _handleTabChange() {
|
||||
print('tabController.index ${tabController.index}');
|
||||
if (tabController.index == 2) {
|
||||
if (qrCodeTimer == null || qrCodeTimer!.isActive == false) {
|
||||
refreshQRCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 验证码登录
|
||||
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 afterLoginByApp(Map<String, dynamic> token_info, cookie_info) async {
|
||||
Box localCache = GStorage.localCache;
|
||||
localCache.put(LocalCacheKey.accessKey, {
|
||||
'mid': token_info['mid'],
|
||||
'value': token_info['access_token'],
|
||||
'refresh': token_info['refresh_token']
|
||||
});
|
||||
List<dynamic> cookieInfo = cookie_info['cookies'];
|
||||
print("cookieInfo");
|
||||
print(cookieInfo);
|
||||
List<Cookie> cookies = [];
|
||||
String cookieStrings = cookieInfo.map((cookie) {
|
||||
String cstr =
|
||||
'${cookie['name']}=${cookie['value']};Domain=.bilibili.com;Path=/;';
|
||||
cookies.add(Cookie.fromSetCookieValue(cstr));
|
||||
return cstr;
|
||||
}).join('');
|
||||
List<String> Urls = [
|
||||
HttpString.baseUrl,
|
||||
HttpString.apiBaseUrl,
|
||||
HttpString.tUrl
|
||||
];
|
||||
for (var url in Urls) {
|
||||
await Request.cookieManager.cookieJar
|
||||
.saveFromResponse(Uri.parse(url), cookies);
|
||||
}
|
||||
print(cookieStrings);
|
||||
print(Request.cookieManager.cookieJar
|
||||
.loadForRequest(Uri.parse(HttpString.apiBaseUrl)));
|
||||
Request.dio.options.headers['cookie'] = cookieStrings;
|
||||
print(Request.dio.options);
|
||||
try {
|
||||
await WebviewCookieManager().setCookies(cookies);
|
||||
} catch (e) {
|
||||
SmartDialog.showToast('webview设置cookie失败,$e');
|
||||
}
|
||||
final result = await UserHttp.userInfo();
|
||||
if (result['status'] && result['data'].isLogin) {
|
||||
SmartDialog.showToast('登录成功,当前采用「'
|
||||
'${GStorage.setting.get(SettingBoxKey.defaultRcmdType, defaultValue: 'web')}'
|
||||
'端」推荐');
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
await userInfoCache.put('userInfoCache', result['data']);
|
||||
final HomeController homeCtr = Get.find<HomeController>();
|
||||
homeCtr.updateLoginStatus(true);
|
||||
homeCtr.userFace.value = result['data'].face;
|
||||
final MediaController mediaCtr = Get.find<MediaController>();
|
||||
mediaCtr.mid = result['data'].mid;
|
||||
await LoginUtils.refreshLoginStatus(true);
|
||||
} else {
|
||||
// 获取用户信息失败
|
||||
SmartDialog.showNotify(
|
||||
msg: '登录失败,请检查cookie是否正确,${result['message']}',
|
||||
notifyType: NotifyType.warning);
|
||||
}
|
||||
}
|
||||
|
||||
// 申请极验验证码
|
||||
Future getCaptcha(oncall) async {
|
||||
SmartDialog.showLoading(msg: '请求中...');
|
||||
var result = await LoginHttp.queryCaptcha();
|
||||
SmartDialog.dismiss();
|
||||
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 {
|
||||
}, 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"];
|
||||
Future getCaptcha(geeGt, geeChallenge, onSuccess) async {
|
||||
var registerData = Gt3RegisterData(
|
||||
challenge: geeChallenge,
|
||||
gt: geeGt,
|
||||
success: true,
|
||||
);
|
||||
|
||||
// 处理验证中返回的错误 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
|
||||
captcha.addEventHandler(
|
||||
onShow: (Map<String, dynamic> message) async {},
|
||||
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 = GeetestData(
|
||||
challenge: message['result']['geetest_challenge'],
|
||||
gt: geeGt,
|
||||
);
|
||||
onSuccess();
|
||||
} else {
|
||||
// 更多错误码参考开发文档 More error codes refer to the development document
|
||||
// https://docs.geetest.com/sensebot/apirefer/errorcode/android
|
||||
// 终端用户完成验证失败,自动重试 If the verification fails, it will be automatically retried.
|
||||
debugPrint("Captcha result code : $code");
|
||||
}
|
||||
},
|
||||
onError: (Map<String, dynamic> message) async {
|
||||
SmartDialog.showToast("Captcha onError: $message");
|
||||
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
|
||||
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);
|
||||
}
|
||||
|
||||
// app端密码登录
|
||||
void loginByPassword() async {
|
||||
String username = usernameTextController.text;
|
||||
String password = passwordTextController.text;
|
||||
if (username.isEmpty || password.isEmpty) {
|
||||
SmartDialog.showToast('用户名或密码不能为空');
|
||||
return;
|
||||
}
|
||||
// if ((passwordFormKey.currentState as FormState).validate()) {
|
||||
var webKeyRes = await LoginHttp.getWebKey();
|
||||
print(webKeyRes);
|
||||
if (!webKeyRes['status']) {
|
||||
SmartDialog.showToast(webKeyRes['msg']);
|
||||
return;
|
||||
}
|
||||
String salt = webKeyRes['data']['hash'];
|
||||
String key = webKeyRes['data']['key'];
|
||||
print(key);
|
||||
var res = await LoginHttp.loginByPwd(
|
||||
username: username,
|
||||
password: password,
|
||||
key: key,
|
||||
salt: salt,
|
||||
gee_validate: captchaData.validate,
|
||||
gee_seccode: captchaData.seccode,
|
||||
gee_challenge: captchaData.geetest?.challenge,
|
||||
recaptcha_token: captchaData.token,
|
||||
);
|
||||
print(res);
|
||||
if (res['status']) {
|
||||
SmartDialog.showToast('登录成功');
|
||||
var data = res['data'];
|
||||
for (var key in data.keys) {
|
||||
print('$key: ${data[key]}');
|
||||
}
|
||||
await afterLoginByApp(data['token_info'], data['cookie_info']);
|
||||
Get.back();
|
||||
} else {
|
||||
// handle login result
|
||||
switch (res['code']) {
|
||||
case 0:
|
||||
// login success
|
||||
break;
|
||||
case -105:
|
||||
String captureUrl = res['data']['url'];
|
||||
Uri captureUri = Uri.parse(captureUrl);
|
||||
captchaData.token = captureUri.queryParameters['recaptcha_token']!;
|
||||
String geeGt = captureUri.queryParameters['gee_gt']!;
|
||||
String geeChallenge = captureUri.queryParameters['gee_challenge']!;
|
||||
|
||||
getCaptcha(geeGt, geeChallenge, () {
|
||||
loginByPassword();
|
||||
});
|
||||
break;
|
||||
default:
|
||||
SmartDialog.showToast(res['msg']);
|
||||
// login failed
|
||||
break;
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
// 短信验证码登录
|
||||
void loginBySmsCode() async {
|
||||
if (telTextController.text.isEmpty) {
|
||||
SmartDialog.showToast('手机号不能为空');
|
||||
return;
|
||||
}
|
||||
if (captchaKey.isEmpty) {
|
||||
SmartDialog.showToast('请先点击获取验证码');
|
||||
return;
|
||||
}
|
||||
if (smsCodeTextController.text.isEmpty) {
|
||||
SmartDialog.showToast('验证码不能为空');
|
||||
return;
|
||||
}
|
||||
if (DateTime.now().millisecondsSinceEpoch - smsSendTimestamp >
|
||||
1000 * 60 * 5) {
|
||||
SmartDialog.showToast('验证码已过期,请重新获取');
|
||||
return;
|
||||
}
|
||||
var webKeyRes = await LoginHttp.getWebKey();
|
||||
if (!webKeyRes['status']) {
|
||||
SmartDialog.showToast(webKeyRes['msg']);
|
||||
return;
|
||||
}
|
||||
String key = webKeyRes['data']['key'];
|
||||
var res = await LoginHttp.loginBySms(
|
||||
tel: telTextController.text,
|
||||
code: smsCodeTextController.text,
|
||||
captchaKey: captchaKey,
|
||||
cid: selectedCountryCodeId['country_id'],
|
||||
key: key,
|
||||
);
|
||||
print(res);
|
||||
if (res['status']) {
|
||||
SmartDialog.showToast('登录成功');
|
||||
var data = res['data'];
|
||||
for (var key in data.keys) {
|
||||
print('$key: ${data[key]}');
|
||||
}
|
||||
await afterLoginByApp(data['token_info'], data['cookie_info']);
|
||||
Get.back();
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
}
|
||||
|
||||
// app端验证码
|
||||
void sendSmsCode() async {
|
||||
if (telTextController.text.isEmpty) {
|
||||
SmartDialog.showToast('手机号不能为空');
|
||||
return;
|
||||
}
|
||||
// String? guestId;
|
||||
// var webKeyRes = await LoginHttp.getWebKey();
|
||||
// if (!webKeyRes['status']) {
|
||||
// SmartDialog.showToast(webKeyRes['msg']);
|
||||
// } else {
|
||||
// String key = webKeyRes['data']['key'];
|
||||
// var guestIdRes = await LoginHttp.getGuestId(key);
|
||||
// if (!guestIdRes['status']) {
|
||||
// SmartDialog.showToast(guestIdRes['msg']);
|
||||
// } else {
|
||||
// guestId = guestIdRes['data']['guest_id'];
|
||||
// }
|
||||
// }
|
||||
|
||||
var res = await LoginHttp.sendSmsCode(
|
||||
tel: telTextController.text,
|
||||
cid: selectedCountryCodeId['country_id'],
|
||||
// deviceTouristId: guestId,
|
||||
gee_validate: captchaData.validate,
|
||||
gee_seccode: captchaData.seccode,
|
||||
gee_challenge: captchaData.geetest?.challenge,
|
||||
recaptcha_token: captchaData.token,
|
||||
);
|
||||
print(res);
|
||||
if (res['status']) {
|
||||
SmartDialog.showToast('发送成功');
|
||||
smsSendTimestamp = DateTime.now().millisecondsSinceEpoch;
|
||||
smsSendCooldown.value = 60;
|
||||
captchaKey = res['data']['captcha_key'];
|
||||
smsSendCooldownTimer = Timer.periodic(Duration(seconds: 1), (timer) {
|
||||
smsSendCooldown.value = 60 - timer.tick;
|
||||
if (smsSendCooldown <= 0) {
|
||||
smsSendCooldownTimer?.cancel();
|
||||
smsSendCooldown.value = 0;
|
||||
}
|
||||
});
|
||||
captcha.startCaptcha(registerData);
|
||||
} else {}
|
||||
} else {
|
||||
// handle login result
|
||||
switch (res['code']) {
|
||||
case 0:
|
||||
case -105:
|
||||
String captureUrl = res['data']['recaptcha_url'];
|
||||
Uri captureUri = Uri.parse(captureUrl);
|
||||
captchaData.token = captureUri.queryParameters['recaptcha_token']!;
|
||||
String geeGt = captureUri.queryParameters['gee_gt']!;
|
||||
String geeChallenge = captureUri.queryParameters['gee_challenge']!;
|
||||
getCaptcha(geeGt, geeChallenge, () {
|
||||
sendSmsCode();
|
||||
});
|
||||
break;
|
||||
default:
|
||||
SmartDialog.showToast(res['msg']);
|
||||
// login failed
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:PiliPalaX/common/constants.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
import 'package:saver_gallery/saver_gallery.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
import 'controller.dart';
|
||||
|
||||
@@ -12,355 +21,463 @@ class LoginPage extends StatefulWidget {
|
||||
|
||||
class _LoginPageState extends State<LoginPage> {
|
||||
final LoginPageController _loginPageCtr = Get.put(LoginPageController());
|
||||
|
||||
// late Future<Map<String, dynamic>> codeFuture;
|
||||
// 二维码生成时间
|
||||
bool showPassword = false;
|
||||
GlobalKey globalKey = GlobalKey();
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: Obx(
|
||||
() => _loginPageCtr.currentIndex.value == 0
|
||||
? IconButton(
|
||||
tooltip: '关闭',
|
||||
onPressed: () async {
|
||||
_loginPageCtr.mobTextFieldNode.unfocus();
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
Get.back();
|
||||
},
|
||||
icon: const Icon(Icons.close_outlined),
|
||||
)
|
||||
: IconButton(
|
||||
tooltip: '返回',
|
||||
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,
|
||||
void dispose() {
|
||||
_loginPageCtr.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Widget loginByQRCode() {
|
||||
return Column(
|
||||
children: [
|
||||
const SizedBox(height: 20),
|
||||
const Text('使用 bilibili 官方 App 扫码登录'),
|
||||
const SizedBox(height: 20),
|
||||
Obx(() => Text('剩余有效时间: ${_loginPageCtr.qrCodeLeftTime} 秒',
|
||||
style:
|
||||
const TextStyle(fontFeatures: [FontFeature.tabularFigures()]))),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// const SizedBox(width: 20),
|
||||
TextButton.icon(
|
||||
onPressed: _loginPageCtr.refreshQRCode,
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: const Text('刷新二维码'),
|
||||
),
|
||||
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),
|
||||
TextButton.icon(
|
||||
onPressed: () async {
|
||||
SmartDialog.showLoading(msg: '正在生成截图');
|
||||
RenderRepaintBoundary boundary = globalKey.currentContext!
|
||||
.findRenderObject()! as RenderRepaintBoundary;
|
||||
var image = await boundary.toImage();
|
||||
ByteData? byteData =
|
||||
await image.toByteData(format: ImageByteFormat.png);
|
||||
Uint8List pngBytes = byteData!.buffer.asUint8List();
|
||||
SmartDialog.dismiss();
|
||||
SmartDialog.showLoading(msg: '正在保存至图库');
|
||||
String picName =
|
||||
"PiliPalaX_loginQRCode_${DateTime.now().toString().replaceAll(' ', '_').replaceAll(':', '-').split('.').first}";
|
||||
final SaveResult result = await SaverGallery.saveImage(
|
||||
Uint8List.fromList(pngBytes),
|
||||
name: picName,
|
||||
fileExtension: 'png',
|
||||
// 保存到 PiliPalaX文件夹
|
||||
androidRelativePath: "Pictures/PiliPalaX",
|
||||
androidExistNotSave: false,
|
||||
);
|
||||
SmartDialog.dismiss();
|
||||
if (result.isSuccess) {
|
||||
await SmartDialog.showToast('「$picName」已保存 ');
|
||||
} else {
|
||||
await SmartDialog.showToast('保存失败,${result.errorMessage}');
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.save),
|
||||
label: const Text('保存至相册'),
|
||||
),
|
||||
],
|
||||
),
|
||||
RepaintBoundary(
|
||||
key: globalKey,
|
||||
child: Obx(() => QrImageView(
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
eyeStyle: QrEyeStyle(
|
||||
eyeShape: QrEyeShape.square,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'请使用您的 BiliBili 账号登录。',
|
||||
style: Theme.of(context).textTheme.titleSmall!,
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {},
|
||||
child: const Icon(Icons.info_outline, size: 16),
|
||||
)
|
||||
],
|
||||
dataModuleStyle: QrDataModuleStyle(
|
||||
dataModuleShape: QrDataModuleShape.square,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
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('下一步'),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
data: _loginPageCtr.codeInfo.value['data']?['url'] ?? "",
|
||||
size: 200,
|
||||
semanticsLabel: '二维码',
|
||||
))),
|
||||
const SizedBox(height: 10),
|
||||
Obx(() => Text(_loginPageCtr.statusQRCode.value)),
|
||||
Obx(() => GestureDetector(
|
||||
onTap: () {
|
||||
//以外部方式打开此链接
|
||||
launchUrlString(
|
||||
_loginPageCtr.codeInfo.value['data']?['url'] ?? "",
|
||||
mode: LaunchMode.externalApplication);
|
||||
},
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
|
||||
child: Text(_loginPageCtr.codeInfo.value['data']?['url'] ?? "",
|
||||
style: Theme.of(context).textTheme.labelSmall!.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withOpacity(0.4))),
|
||||
),
|
||||
)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text('请务必在 PiliPalaX 开源仓库等可信渠道下载安装。',
|
||||
style: Theme.of(context).textTheme.labelSmall!.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withOpacity(0.4)))),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget loginByPassword() {
|
||||
return Column(
|
||||
children: [
|
||||
const SizedBox(height: 20),
|
||||
const Text('使用账号密码登录'),
|
||||
const SizedBox(height: 10),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
child: TextField(
|
||||
controller: _loginPageCtr.usernameTextController,
|
||||
inputFormatters: [FilteringTextInputFormatter.deny(RegExp(r"\s"))],
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.account_box),
|
||||
border: const UnderlineInputBorder(),
|
||||
labelText: '账号',
|
||||
hintText: '邮箱/手机号',
|
||||
suffixIcon: IconButton(
|
||||
onPressed: _loginPageCtr.usernameTextController.clear,
|
||||
icon: const Icon(Icons.clear),
|
||||
),
|
||||
),
|
||||
),
|
||||
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(
|
||||
tooltip: '切换至验证码登录',
|
||||
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(
|
||||
tooltip: '切换至密码登录',
|
||||
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('确认登录'),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
child: TextField(
|
||||
obscureText: !showPassword,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
inputFormatters: [FilteringTextInputFormatter.deny(RegExp(r"\s"))],
|
||||
controller: _loginPageCtr.passwordTextController,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.lock),
|
||||
border: const UnderlineInputBorder(),
|
||||
labelText: '密码',
|
||||
suffixIcon: IconButton(
|
||||
onPressed: _loginPageCtr.passwordTextController.clear,
|
||||
icon: const Icon(Icons.clear),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
const SizedBox(width: 10),
|
||||
Checkbox(
|
||||
value: showPassword,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
showPassword = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
const Text('显示密码'),
|
||||
const Spacer(),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
//https://passport.bilibili.com/h5-app/passport/login/findPassword
|
||||
//https://passport.bilibili.com/passport/findPassword
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SimpleDialog(
|
||||
title: const Text('忘记密码?'),
|
||||
contentPadding:
|
||||
const EdgeInsets.fromLTRB(0.0, 2.0, 0.0, 16.0),
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.fromLTRB(25, 0, 25, 10),
|
||||
child: Text("试试扫码、手机号登录,或选择")),
|
||||
ListTile(
|
||||
title: const Text(
|
||||
'找回密码(手机版)',
|
||||
),
|
||||
leading: const Icon(Icons.smartphone_outlined),
|
||||
subtitle: const Text(
|
||||
'https://passport.bilibili.com/h5-app/passport/login/findPassword',
|
||||
),
|
||||
dense: false,
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
Get.toNamed('/webview', parameters: {
|
||||
'url':
|
||||
'https://passport.bilibili.com/h5-app/passport/login/findPassword',
|
||||
'type': 'url',
|
||||
'pageTitle': '忘记密码',
|
||||
});
|
||||
}),
|
||||
ListTile(
|
||||
title: const Text(
|
||||
'找回密码(电脑版)',
|
||||
),
|
||||
leading: const Icon(Icons.desktop_windows_outlined),
|
||||
subtitle: const Text(
|
||||
'https://passport.bilibili.com/pc/passport/findPassword',
|
||||
),
|
||||
dense: false,
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
Get.toNamed('/webview', parameters: {
|
||||
'url':
|
||||
'https://passport.bilibili.com/pc/passport/findPassword',
|
||||
'type': 'url',
|
||||
'pageTitle': '忘记密码',
|
||||
'uaType': 'pc'
|
||||
});
|
||||
}),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: const Text('忘记密码'),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
],
|
||||
),
|
||||
OutlinedButton.icon(
|
||||
onPressed: _loginPageCtr.loginByPassword,
|
||||
icon: const Icon(Icons.login_outlined),
|
||||
label: const Text('登录'),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(
|
||||
'根据 bilibili 官方登录接口规范,密码将在本地加盐、加密后传输。\n'
|
||||
'盐与公钥均由官方提供;以 RSA/ECB/PKCS1Padding 方式加密。\n'
|
||||
'账号密码仅用于该登录接口,不予保存;本地仅存储登录凭证。\n'
|
||||
'请务必在 PiliPalaX 开源仓库等可信渠道下载安装。',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.labelSmall!.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withOpacity(0.4)))),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget loginBySmS() {
|
||||
return Column(
|
||||
children: [
|
||||
const SizedBox(height: 20),
|
||||
const Text('使用手机短信验证码登录'),
|
||||
const SizedBox(height: 10),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
child: Container(
|
||||
decoration: UnderlineTabIndicator(
|
||||
borderSide: BorderSide(
|
||||
color:
|
||||
Theme.of(context).colorScheme.outline.withOpacity(0.4)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 12),
|
||||
const Icon(Icons.phone),
|
||||
const SizedBox(width: 12),
|
||||
PopupMenuButton<Map<String, dynamic>>(
|
||||
padding: EdgeInsets.zero,
|
||||
tooltip: '选择国际冠码,'
|
||||
'当前为${_loginPageCtr.selectedCountryCodeId['cname']},'
|
||||
'+${_loginPageCtr.selectedCountryCodeId['country_id']}',
|
||||
//position: PopupMenuPosition.under,
|
||||
onSelected: (Map<String, dynamic> type) {},
|
||||
itemBuilder: (BuildContext context) => Constants
|
||||
.internationalDialingPrefix
|
||||
.map((Map<String, dynamic> item) {
|
||||
return PopupMenuItem<Map<String, dynamic>>(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_loginPageCtr.selectedCountryCodeId = item;
|
||||
});
|
||||
},
|
||||
value: item,
|
||||
// height: menuItemHeight,
|
||||
child: Row(children: [
|
||||
Text(item['cname']),
|
||||
const Spacer(),
|
||||
Text("+${item['country_id']}")
|
||||
]),
|
||||
);
|
||||
}).toList(),
|
||||
child: Text(
|
||||
"+${_loginPageCtr.selectedCountryCodeId['country_id']}"),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
SizedBox(
|
||||
height: 24, // 这里设置固定高度
|
||||
child: VerticalDivider(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline
|
||||
.withOpacity(0.5),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _loginPageCtr.telTextController,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: <TextInputFormatter>[
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
],
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
labelText: '手机号',
|
||||
suffixIcon: IconButton(
|
||||
onPressed: _loginPageCtr.telTextController.clear,
|
||||
icon: const Icon(Icons.clear),
|
||||
),
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
child: Container(
|
||||
decoration: UnderlineTabIndicator(
|
||||
borderSide: BorderSide(
|
||||
color:
|
||||
Theme.of(context).colorScheme.outline.withOpacity(0.4)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _loginPageCtr.smsCodeTextController,
|
||||
decoration: const InputDecoration(
|
||||
prefixIcon: Icon(Icons.key),
|
||||
border: InputBorder.none,
|
||||
labelText: '验证码',
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: <TextInputFormatter>[
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
],
|
||||
),
|
||||
),
|
||||
Obx(() => TextButton.icon(
|
||||
onPressed: _loginPageCtr.smsSendCooldown > 0
|
||||
? null
|
||||
: _loginPageCtr.sendSmsCode,
|
||||
icon: const Icon(Icons.send),
|
||||
label: Text(_loginPageCtr.smsSendCooldown > 0
|
||||
? '等待${_loginPageCtr.smsSendCooldown}秒'
|
||||
: '获取验证码'),
|
||||
)),
|
||||
],
|
||||
),
|
||||
)),
|
||||
const SizedBox(height: 20),
|
||||
OutlinedButton.icon(
|
||||
onPressed: _loginPageCtr.loginBySmsCode,
|
||||
icon: const Icon(Icons.login_outlined),
|
||||
label: const Text('登录'),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(
|
||||
'手机号仅用于 bilibili 官方发送验证码与登录接口,不予保存;\n'
|
||||
'本地仅存储登录凭证。\n'
|
||||
'请务必在 PiliPalaX 开源仓库等可信渠道下载安装。',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.labelSmall!.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withOpacity(0.4)))),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return OrientationBuilder(builder: (context, orientation) {
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
tooltip: '关闭',
|
||||
icon: const Icon(Icons.close_outlined),
|
||||
onPressed: Get.back),
|
||||
title: Row(children: [
|
||||
const Text('登录'),
|
||||
if (orientation == Orientation.landscape) ...[
|
||||
const Spacer(),
|
||||
Flexible(
|
||||
child: TabBar(
|
||||
dividerHeight: 0,
|
||||
tabs: const [
|
||||
Tab(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [Icon(Icons.lock), Text(' 密码')])),
|
||||
Tab(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [Icon(Icons.key), Text(' 短信')])),
|
||||
Tab(
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [Icon(Icons.qr_code), Text(' 扫码')])),
|
||||
],
|
||||
controller: _loginPageCtr.tabController,
|
||||
))
|
||||
]
|
||||
]),
|
||||
bottom: orientation == Orientation.portrait
|
||||
? TabBar(
|
||||
tabs: const [
|
||||
Tab(icon: Icon(Icons.lock), text: '密码'),
|
||||
Tab(icon: Icon(Icons.key), text: '短信'),
|
||||
Tab(icon: Icon(Icons.qr_code), text: '扫码'),
|
||||
],
|
||||
controller: _loginPageCtr.tabController,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
body: TabBarView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
controller: _loginPageCtr.tabController,
|
||||
children: [
|
||||
tabViewOuter(loginByPassword()),
|
||||
tabViewOuter(loginBySmS()),
|
||||
tabViewOuter(loginByQRCode()),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget tabViewOuter(child) {
|
||||
return SingleChildScrollView(
|
||||
child: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: SizedBox(
|
||||
height: 500,
|
||||
width: 600,
|
||||
child: child,
|
||||
)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,22 +36,20 @@ class MineController extends GetxController {
|
||||
|
||||
onLogin() async {
|
||||
if (!userLogin.value) {
|
||||
Get.toNamed(
|
||||
'/webview',
|
||||
parameters: {
|
||||
'url': 'https://passport.bilibili.com/h5-app/passport/login',
|
||||
'type': 'login',
|
||||
'pageTitle': '登录bilibili',
|
||||
},
|
||||
);
|
||||
// Get.toNamed('/loginPage');
|
||||
// Get.toNamed(
|
||||
// '/webview',
|
||||
// parameters: {
|
||||
// 'url': 'https://passport.bilibili.com/h5-app/passport/login',
|
||||
// 'type': 'login',
|
||||
// 'pageTitle': '登录bilibili',
|
||||
// },
|
||||
// );
|
||||
Get.toNamed('/loginPage', preventDuplicates: false);
|
||||
} else {
|
||||
int mid = userInfo.value.mid!;
|
||||
String face = userInfo.value.face!;
|
||||
Get.toNamed(
|
||||
'/member?mid=$mid',
|
||||
arguments: {'face': face},
|
||||
);
|
||||
Get.toNamed('/member?mid=$mid',
|
||||
arguments: {'face': face}, preventDuplicates: false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:PiliPalaX/models/common/theme_type.dart';
|
||||
import 'package:PiliPalaX/utils/feed_back.dart';
|
||||
import 'package:PiliPalaX/utils/login.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
import '../../models/common/dynamic_badge_mode.dart';
|
||||
import '../../models/common/nav_bar_config.dart';
|
||||
import '../main/index.dart';
|
||||
@@ -32,7 +33,8 @@ class SettingController extends GetxController {
|
||||
super.onInit();
|
||||
userInfo = userInfoCache.get('userInfoCache');
|
||||
userLogin.value = userInfo != null;
|
||||
hiddenSettingUnlocked.value = setting.get(SettingBoxKey.hiddenSettingUnlocked, defaultValue: false);
|
||||
hiddenSettingUnlocked.value =
|
||||
setting.get(SettingBoxKey.hiddenSettingUnlocked, defaultValue: false);
|
||||
feedBackEnable.value =
|
||||
setting.get(SettingBoxKey.feedBackEnable, defaultValue: false);
|
||||
toastOpacity.value =
|
||||
@@ -65,12 +67,23 @@ class SettingController extends GetxController {
|
||||
// 清空cookie
|
||||
await Request.cookieManager.cookieJar.deleteAll();
|
||||
Request.dio.options.headers['cookie'] = '';
|
||||
|
||||
// 清空本地存储的用户标识
|
||||
userInfoCache.put('userInfoCache', null);
|
||||
localCache
|
||||
.put(LocalCacheKey.accessKey, {'mid': -1, 'value': ''});
|
||||
|
||||
localCache.put(LocalCacheKey.accessKey,
|
||||
{'mid': -1, 'value': '', 'refresh': ''});
|
||||
try {
|
||||
final WebViewController controller = WebViewController();
|
||||
controller.clearCache();
|
||||
controller.clearLocalStorage();
|
||||
WebViewCookieManager().clearCookies();
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
userLogin.value = false;
|
||||
if (Get.isRegistered<MainController>()) {
|
||||
MainController mainController = Get.find<MainController>();
|
||||
mainController.userLogin.value = false;
|
||||
}
|
||||
await LoginUtils.refreshLoginStatus(false);
|
||||
Get.back();
|
||||
},
|
||||
@@ -107,8 +120,7 @@ class SettingController extends GetxController {
|
||||
dynamicBadgeType.value = result;
|
||||
setting.put(SettingBoxKey.dynamicBadgeMode, result.code);
|
||||
MainController mainController = Get.put(MainController());
|
||||
mainController.dynamicBadgeType =
|
||||
DynamicBadgeMode.values[result.code];
|
||||
mainController.dynamicBadgeType = DynamicBadgeMode.values[result.code];
|
||||
if (mainController.dynamicBadgeType != DynamicBadgeMode.hidden) {
|
||||
mainController.getUnreadDynamic();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:PiliPalaX/utils/cookie.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
@@ -68,42 +67,26 @@ class _PrivacySettingState extends State<PrivacySetting> {
|
||||
subtitle: Text('已拉黑用户', style: subTitleStyle),
|
||||
leading: const Icon(Icons.block),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () async {
|
||||
if (!userLogin) {
|
||||
SmartDialog.showToast('请先登录');
|
||||
return;
|
||||
}
|
||||
var res = await MemberHttp.cookieToKey();
|
||||
if (res['status']) {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
} else {
|
||||
SmartDialog.showToast("刷新失败:${res['msg']}");
|
||||
}
|
||||
},
|
||||
dense: false,
|
||||
title: Text('刷新access_key', style: titleStyle),
|
||||
leading: const Icon(Icons.perm_device_info_outlined),
|
||||
subtitle: Text(
|
||||
'用于app端推荐接口的用户凭证。刷新有小概率导致其他设备下线。若app端未推荐个性化内容,可尝试刷新或清除本app数据后重新登录',
|
||||
style: subTitleStyle),
|
||||
),
|
||||
if (hiddenSettingUnlocked)
|
||||
ListTile(
|
||||
title: Text(
|
||||
'导入/导出cookie',
|
||||
style: titleStyle,
|
||||
),
|
||||
subtitle: Text(
|
||||
'cookie代表您的登录状态,仅供高级用户使用',
|
||||
style: subTitleStyle,
|
||||
),
|
||||
leading: const Icon(Icons.cookie_outlined),
|
||||
dense: false,
|
||||
onTap: () {
|
||||
import_export_cookies(titleStyle, subTitleStyle);
|
||||
},
|
||||
),
|
||||
// ListTile(
|
||||
// onTap: () async {
|
||||
// if (!userLogin) {
|
||||
// SmartDialog.showToast('请先登录');
|
||||
// return;
|
||||
// }
|
||||
// var res = await MemberHttp.cookieToKey();
|
||||
// if (res['status']) {
|
||||
// SmartDialog.showToast(res['msg']);
|
||||
// } else {
|
||||
// SmartDialog.showToast("刷新失败:${res['msg']}");
|
||||
// }
|
||||
// },
|
||||
// dense: false,
|
||||
// title: Text('刷新access_key', style: titleStyle),
|
||||
// leading: const Icon(Icons.perm_device_info_outlined),
|
||||
// subtitle: Text(
|
||||
// '用于app端推荐接口的用户凭证。若app端未推荐个性化内容,可尝试刷新或清除本app数据后重新登录',
|
||||
// style: subTitleStyle),
|
||||
// ),
|
||||
ListTile(
|
||||
onTap: () {
|
||||
MineController.onChangeAnonymity(context);
|
||||
@@ -150,147 +133,4 @@ class _PrivacySettingState extends State<PrivacySetting> {
|
||||
);
|
||||
}
|
||||
|
||||
void import_export_cookies(TextStyle titleStyle, TextStyle subTitleStyle) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SimpleDialog(
|
||||
title: const Text('导入/导出cookie', style: TextStyle(color: Colors.red)),
|
||||
children: [
|
||||
ListTile(
|
||||
title: Text(
|
||||
'导出cookie至剪贴板',
|
||||
style: titleStyle.copyWith(color: Colors.red),
|
||||
),
|
||||
leading: const Icon(
|
||||
Icons.warning_amber,
|
||||
color: Colors.red,
|
||||
),
|
||||
subtitle: Text(
|
||||
'泄露账号cookie等同于绕过账号密码与验证码直接登录,可导致隐私泄露、风控、毁号、盗号等各类问题。\n'
|
||||
'你应妥善保管该cookie且仅供自己使用。你承诺,不会利用本服务进行任何违法或不当的活动。你承诺,对所进行的一切活动'
|
||||
'(包括但不限于网上点击同意或提交各类规则协议或购买服务、分享资讯或图片等)负全部责任。\n'
|
||||
'你承诺、理解、同意并确认,在你的账户遭到未获授权的使用,或者发生其他任何安全问题时,'
|
||||
'作者不对上述情形产生的任何直接或间接的遗失或损害承担责任。',
|
||||
style: subTitleStyle.copyWith(color: Colors.redAccent),
|
||||
),
|
||||
dense: false,
|
||||
onTap: () async {
|
||||
Navigator.of(context).pop();
|
||||
if (!userLogin) {
|
||||
SmartDialog.showToast('请先登录');
|
||||
return;
|
||||
}
|
||||
final String cookie = await CookieTool.exportCookie();
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('导出cookie(危险)',
|
||||
style: TextStyle(color: Colors.red)),
|
||||
content: Text(cookie),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
await Clipboard.setData(
|
||||
ClipboardData(text: cookie));
|
||||
},
|
||||
child: const Text('复制(危险)',
|
||||
style: TextStyle(color: Colors.red)),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text('取消'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
ListTile(
|
||||
title: Text(
|
||||
'从剪贴板导入cookie',
|
||||
style: titleStyle,
|
||||
),
|
||||
leading: const Icon(
|
||||
Icons.warning_amber,
|
||||
color: Colors.red,
|
||||
),
|
||||
subtitle: Text(
|
||||
'导入将覆盖当前登录状态,你应自行对利用服务从事的所有行为及结果承担责任,请慎用',
|
||||
style: subTitleStyle,
|
||||
),
|
||||
dense: false,
|
||||
onTap: () async {
|
||||
ClipboardData? data = await Clipboard.getData('text/plain');
|
||||
if (data == null || data.text == null || data.text == '') {
|
||||
SmartDialog.showToast('未检测到剪贴板内容');
|
||||
return;
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('导入剪贴板中的cookie'),
|
||||
content: Text(data.text!),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Get.back();
|
||||
},
|
||||
child: const Text('取消'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Get.back();
|
||||
final String cookie = data.text!;
|
||||
try {
|
||||
await CookieTool.importCookie(cookie);
|
||||
await SmartDialog.showToast('已导入');
|
||||
await CookieTool.onSet();
|
||||
final result = await UserHttp.userInfo();
|
||||
if (result['status'] &&
|
||||
result['data'].isLogin) {
|
||||
SmartDialog.showToast('登录成功,当前采用「'
|
||||
'${GStorage.setting.get(SettingBoxKey.defaultRcmdType, defaultValue: 'web')}'
|
||||
'端」推荐');
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
await userInfoCache.put(
|
||||
'userInfoCache', result['data']);
|
||||
final HomeController homeCtr =
|
||||
Get.find<HomeController>();
|
||||
homeCtr.updateLoginStatus(true);
|
||||
homeCtr.userFace.value = result['data'].face;
|
||||
final MediaController mediaCtr =
|
||||
Get.find<MediaController>();
|
||||
mediaCtr.mid = result['data'].mid;
|
||||
await LoginUtils.refreshLoginStatus(true);
|
||||
Get.back();
|
||||
} else {
|
||||
// 获取用户信息失败
|
||||
SmartDialog.showNotify(
|
||||
msg:
|
||||
'登录失败,请检查cookie是否正确,${result['message']}',
|
||||
notifyType: NotifyType.warning);
|
||||
}
|
||||
} catch (e) {
|
||||
SmartDialog.showToast('导入失败:$e');
|
||||
}
|
||||
},
|
||||
child: const Text('确认'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,16 @@
|
||||
// ignore_for_file: avoid_print
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:PiliPalaX/http/init.dart';
|
||||
import 'package:PiliPalaX/http/user.dart';
|
||||
import 'package:PiliPalaX/pages/home/index.dart';
|
||||
import 'package:PiliPalaX/pages/media/index.dart';
|
||||
import 'package:PiliPalaX/utils/cookie.dart';
|
||||
import 'package:PiliPalaX/utils/event_bus.dart';
|
||||
import 'package:PiliPalaX/utils/id_utils.dart';
|
||||
import 'package:PiliPalaX/utils/login.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
|
||||
class WebviewController extends GetxController {
|
||||
String url = '';
|
||||
RxString type = ''.obs;
|
||||
String pageTitle = '';
|
||||
String uaType = '';
|
||||
final WebViewController controller = WebViewController();
|
||||
RxInt loadProgress = 0.obs;
|
||||
RxBool loadShow = true.obs;
|
||||
@@ -31,13 +22,9 @@ class WebviewController extends GetxController {
|
||||
url = Get.parameters['url']!;
|
||||
type.value = Get.parameters['type']!;
|
||||
pageTitle = Get.parameters['pageTitle']!;
|
||||
uaType = Get.parameters['uaType'] ?? 'mob';
|
||||
|
||||
if (type.value == 'login') {
|
||||
controller.clearCache();
|
||||
controller.clearLocalStorage();
|
||||
WebViewCookieManager().clearCookies();
|
||||
}
|
||||
webviewInit();
|
||||
webviewInit(uaType: uaType);
|
||||
}
|
||||
|
||||
webviewInit({String uaType = 'mob'}) {
|
||||
@@ -85,13 +72,7 @@ class WebviewController extends GetxController {
|
||||
// 加载完成
|
||||
onUrlChange: (UrlChange urlChange) async {
|
||||
loadShow.value = false;
|
||||
String url = urlChange.url ?? '';
|
||||
if (type.value == 'login' &&
|
||||
(url.startsWith(
|
||||
'https://passport.bilibili.com/web/sso/exchange_cookie') ||
|
||||
url.startsWith('https://m.bilibili.com/'))) {
|
||||
confirmLogin(url);
|
||||
}
|
||||
// String url = urlChange.url ?? '';
|
||||
},
|
||||
onWebResourceError: (WebResourceError error) {},
|
||||
onNavigationRequest: (NavigationRequest request) {
|
||||
@@ -112,52 +93,4 @@ class WebviewController extends GetxController {
|
||||
..loadRequest(Uri.parse(url));
|
||||
}
|
||||
|
||||
confirmLogin(url) async {
|
||||
var content = '';
|
||||
if (url != null) {
|
||||
content = '${content + url}; \n';
|
||||
}
|
||||
try {
|
||||
await CookieTool.onSet();
|
||||
final result = await UserHttp.userInfo();
|
||||
if (result['status'] && result['data'].isLogin) {
|
||||
SmartDialog.showToast('登录成功,当前采用「'
|
||||
'${GStorage.setting.get(SettingBoxKey.defaultRcmdType, defaultValue: 'web')}'
|
||||
'端」推荐');
|
||||
try {
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
await userInfoCache.put('userInfoCache', result['data']);
|
||||
|
||||
final HomeController homeCtr = Get.find<HomeController>();
|
||||
homeCtr.updateLoginStatus(true);
|
||||
homeCtr.userFace.value = result['data'].face;
|
||||
final MediaController mediaCtr = Get.find<MediaController>();
|
||||
mediaCtr.mid = result['data'].mid;
|
||||
await LoginUtils.refreshLoginStatus(true);
|
||||
} catch (err) {
|
||||
SmartDialog.show(builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('登录遇到问题'),
|
||||
content: Text(err.toString()),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => controller.reload(),
|
||||
child: const Text('确认'),
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
Get.back();
|
||||
} else {
|
||||
// 获取用户信息失败
|
||||
SmartDialog.showToast(result['msg']);
|
||||
Clipboard.setData(ClipboardData(text: result['msg']));
|
||||
}
|
||||
} catch (e) {
|
||||
SmartDialog.showNotify(msg: e.toString(), notifyType: NotifyType.warning);
|
||||
content = content + e.toString();
|
||||
Clipboard.setData(ClipboardData(text: content));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,25 +36,14 @@ class _WebviewPageState extends State<WebviewPage> {
|
||||
icon: Icon(Icons.refresh_outlined,
|
||||
color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
if (_webviewController.type.value != 'login')
|
||||
IconButton(
|
||||
tooltip: '用外部浏览器打开',
|
||||
onPressed: () {
|
||||
launchUrl(Uri.parse(_webviewController.url));
|
||||
},
|
||||
icon: Icon(Icons.open_in_browser_outlined,
|
||||
color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
if (_webviewController.type.value == 'login') ...<Widget>[
|
||||
TextButton(
|
||||
onPressed: () => _webviewController.confirmLogin(null),
|
||||
child: const Text('刷新登录态'),
|
||||
),
|
||||
TextButton(
|
||||
child: const Text('电脑版'),
|
||||
onPressed: () => _webviewController.webviewInit(uaType: 'pc'),
|
||||
)
|
||||
],
|
||||
IconButton(
|
||||
tooltip: '用外部浏览器打开',
|
||||
onPressed: () {
|
||||
launchUrl(Uri.parse(_webviewController.url));
|
||||
},
|
||||
icon: Icon(Icons.open_in_browser_outlined,
|
||||
color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
const SizedBox(width: 12)
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user