mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-05-29 12:38:34 +00:00
feat: 新版登录页:以APP接口和新界面全面重构网页版登录;更新二维码与极验插件;更新版本号
This commit is contained in:
@@ -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,
|
||||
)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user