ver1.00.00
update2
This commit is contained in:
@@ -1,10 +1,21 @@
|
||||
import com.android.build.api.dsl.SigningConfig
|
||||
import java.util.Properties
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("kotlin-android")
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
}
|
||||
|
||||
// 加载 key.properties 配置 👇
|
||||
val keystoreProperties = Properties()
|
||||
val keystoreFile = rootProject.file("key.properties")
|
||||
if (keystoreFile.exists()) {
|
||||
keystoreFile.inputStream().use { stream ->
|
||||
keystoreProperties.load(stream)
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "org.ast.unionapp"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
@@ -20,21 +31,33 @@ android {
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "org.ast.unionapp"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdk = flutter.minSdkVersion
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
}
|
||||
|
||||
// 签名配置 👇
|
||||
signingConfigs {
|
||||
create("release") {
|
||||
keyAlias = keystoreProperties["keyAlias"] as String
|
||||
keyPassword = keystoreProperties["keyPassword"] as String
|
||||
storeFile = file(keystoreProperties["storeFile"] as String)
|
||||
storePassword = keystoreProperties["storePassword"] as String
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
// 启用正式签名(替换原来的 debug)
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,24 @@
|
||||
allprojects {
|
||||
repositories {
|
||||
// ✅ 阿里云 Maven 镜像(推荐,稳定快速)
|
||||
maven { url = uri("https://maven.aliyun.com/repository/google") }
|
||||
maven { url = uri("https://maven.aliyun.com/repository/public") }
|
||||
maven { url = uri("https://maven.aliyun.com/repository/gradle-plugin") }
|
||||
maven { url = uri("https://storage.googleapis.com/download.flutter.io") }
|
||||
|
||||
// ✅ 腾讯云 Maven 镜像(备用)
|
||||
// maven { url = uri("https://mirrors.cloud.tencent.com/nexus/repository/maven-public/") }
|
||||
|
||||
// 官方源(放在最后作为备用)
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url = uri("https://storage.googleapis.com/download.flutter.io") }
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy {
|
||||
force("androidx.work:work-runtime:2.8.1")
|
||||
force("androidx.work:work-runtime-ktx:2.8.1")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,3 +3,4 @@ distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip
|
||||
org.gradle.java.home=/Users/spasolreisa/Library/Java/JavaVirtualMachines/jbr-17.0.14/Contents/Home
|
||||
@@ -2,8 +2,10 @@ import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:google_mlkit_barcode_scanning/google_mlkit_barcode_scanning.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
// 新增导入
|
||||
import 'package:zxing2/qrcode.dart';
|
||||
import 'package:image/image.dart' as img;
|
||||
|
||||
import '../../providers/user_provider.dart';
|
||||
|
||||
@@ -22,19 +24,13 @@ class _BindAccountPageState extends State<BindAccountPage> {
|
||||
String? _statusMessage;
|
||||
File? _selectedImageFile; // 用户选择的原始二维码截图
|
||||
|
||||
// 条码扫描器
|
||||
final BarcodeScanner _barcodeScanner = BarcodeScanner(
|
||||
formats: [BarcodeFormat.qrCode],
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_qrContentController.dispose();
|
||||
_barcodeScanner.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// 1. 选择图片并识别二维码
|
||||
// 1. 选择图片并识别二维码 (使用 zxing2替代 ML Kit)
|
||||
Future<void> _pickImageAndScan() async {
|
||||
try {
|
||||
final XFile? pickedFile = await _picker.pickImage(source: ImageSource.gallery);
|
||||
@@ -42,33 +38,79 @@ class _BindAccountPageState extends State<BindAccountPage> {
|
||||
|
||||
setState(() {
|
||||
_isProcessing = true;
|
||||
_statusMessage = "正在识别二维码...";
|
||||
_statusMessage = "正在加载图片...";
|
||||
_selectedImageFile = File(pickedFile.path);
|
||||
});
|
||||
|
||||
final inputImage = InputImage.fromFilePath(pickedFile.path);
|
||||
final List<Barcode> barcodes = await _barcodeScanner.processImage(inputImage);
|
||||
// 读取图片字节
|
||||
final bytes = await pickedFile.readAsBytes();
|
||||
|
||||
// 使用 image 库解码图片为 RGB 数据
|
||||
final img.Image? decodedImage = img.decodeImage(bytes);
|
||||
|
||||
if (decodedImage == null) {
|
||||
throw Exception("无法解析图片格式");
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isProcessing = false;
|
||||
_statusMessage = "正在解析二维码...";
|
||||
});
|
||||
|
||||
if (barcodes.isNotEmpty && barcodes.first.rawValue != null) {
|
||||
final String code = barcodes.first.rawValue!;
|
||||
final reader = QRCodeReader();
|
||||
|
||||
try {
|
||||
// 【修复点开始】
|
||||
// 1. 获取 Uint8List (RGBA 格式)
|
||||
final Uint8List rgbaBytes = decodedImage.getBytes(order: img.ChannelOrder.rgba);
|
||||
|
||||
// 2. 将 Uint8List 转换为 Int32List
|
||||
// 注意:这需要底层字节序匹配。大多数现代移动设备是 Little Endian。
|
||||
// Uint8List 的长度必须是 4 的倍数(RGBA 每个像素4字节),这通常成立。
|
||||
final Int32List pixels = Int32List.view(rgbaBytes.buffer);
|
||||
|
||||
// 3. 创建 LuminanceSource
|
||||
// ZXing 的 RGBLuminanceSource 期望 Int32List,其中每个 int 代表一个像素 (0xAARRGGBB 或类似格式)
|
||||
// 由于 image 库给出的是 RGBA (0xRR, 0xGG, 0xBB, 0xAA),直接转换可能导致颜色通道错位,
|
||||
// 但 ZXing 主要关注亮度(灰度),通常 RGBA 直接转 Int32 也能被正确二值化识别,
|
||||
// 如果识别失败,可能需要手动重排字节为 ARGB。
|
||||
|
||||
final source = RGBLuminanceSource(
|
||||
decodedImage.width,
|
||||
decodedImage.height,
|
||||
pixels
|
||||
);
|
||||
// 【修复点结束】
|
||||
|
||||
final binarizer = HybridBinarizer(source);
|
||||
final bitmap = BinaryBitmap(binarizer);
|
||||
|
||||
final result = reader.decode(bitmap);
|
||||
|
||||
if (result != null && result.text != null) {
|
||||
setState(() {
|
||||
_qrContentController.text = result.text!;
|
||||
_statusMessage = "识别成功";
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_statusMessage = "未识别到二维码,请手动输入";
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// 捕获 zxing 内部的 NotFoundException 等
|
||||
print("ZXing Error: $e"); // 调试用
|
||||
setState(() {
|
||||
_qrContentController.text = code;
|
||||
_statusMessage = "识别成功";
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_statusMessage = "未识别到二维码,请手动输入";
|
||||
_statusMessage = "未在图中找到有效二维码";
|
||||
});
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_statusMessage = "识别出错: $e";
|
||||
});
|
||||
} finally {
|
||||
setState(() {
|
||||
_isProcessing = false;
|
||||
_statusMessage = "识别失败: $e";
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -92,7 +134,9 @@ class _BindAccountPageState extends State<BindAccountPage> {
|
||||
});
|
||||
|
||||
try {
|
||||
// 下载背景图用于隐写
|
||||
final httpClient = HttpClient();
|
||||
// 建议在实际生产环境中添加 timeout
|
||||
final request = await httpClient.getUrl(Uri.parse('https://union.godserver.cn/assets/jpeg/20180621142015_5FmGZ-wYXkyL4y.jpeg'));
|
||||
final response = await request.close();
|
||||
final bytes = <int>[];
|
||||
@@ -101,6 +145,9 @@ class _BindAccountPageState extends State<BindAccountPage> {
|
||||
}
|
||||
imageBytes = Uint8List.fromList(bytes);
|
||||
|
||||
// 关闭 client 以释放资源
|
||||
httpClient.close();
|
||||
|
||||
if (imageBytes == null || imageBytes.isEmpty) throw Exception("图片数据无效");
|
||||
|
||||
setState(() {
|
||||
@@ -108,7 +155,6 @@ class _BindAccountPageState extends State<BindAccountPage> {
|
||||
});
|
||||
|
||||
// 调用 Provider
|
||||
// 注意:这里 segaId 参数其实传的是 QR Code 的内容
|
||||
final userProvider = context.read<UserProvider>();
|
||||
final result = await userProvider.uploadStegImage(imageBytes, segaId: qrContent);
|
||||
|
||||
@@ -122,10 +168,13 @@ class _BindAccountPageState extends State<BindAccountPage> {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text("✅ ${result['msg']}"), backgroundColor: Colors.green, duration: const Duration(seconds: 5)),
|
||||
);
|
||||
// 绑定成功后,通常建议刷新用户信息
|
||||
// 绑定成功后,刷新用户信息
|
||||
await userProvider.initUser();
|
||||
|
||||
// 可选:清空输入或跳转
|
||||
// _qrContentController.clear();
|
||||
// Navigator.of(context).pop();
|
||||
} else {
|
||||
// 后端返回 500 或其他错误
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text("❌ ${result['msg']}"), backgroundColor: Colors.red, duration: const Duration(seconds: 5)),
|
||||
);
|
||||
@@ -276,7 +325,7 @@ class _BindAccountPageState extends State<BindAccountPage> {
|
||||
child: Text(
|
||||
_statusMessage!,
|
||||
style: TextStyle(
|
||||
color: _statusMessage!.contains("失败") || _statusMessage!.contains("错误") ? Colors.redAccent : Colors.white70,
|
||||
color: _statusMessage!.contains("失败") || _statusMessage!.contains("错误") || _statusMessage!.contains("未识别") ? Colors.redAccent : Colors.white70,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
|
||||
@@ -77,6 +77,8 @@ class _LoginPageState extends State<LoginPage> {
|
||||
final isDark = theme.brightness == Brightness.dark;
|
||||
|
||||
return Scaffold(
|
||||
// 关键修复1:resizeToAvoidBottomInset = false,防止键盘弹出重绘崩溃
|
||||
resizeToAvoidBottomInset: false,
|
||||
backgroundColor: isDark ? Colors.black : Colors.white,
|
||||
appBar: AppBar(
|
||||
title: Text(isRegisterMode ? '创建账号' : '欢迎回来'),
|
||||
@@ -96,215 +98,217 @@ class _LoginPageState extends State<LoginPage> {
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Transform.translate(
|
||||
offset: const Offset(0, -30),
|
||||
child: SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 400),
|
||||
child: Card(
|
||||
elevation: 8,
|
||||
shadowColor: _pinkColor.withOpacity(0.3),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
color: isDark ? Colors.grey[850] : Colors.white,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 头像/图标区域 - 已修改为黑白粉主题
|
||||
Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: _pinkColor.withOpacity(0.15),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
// 更换为更美观的人物头像类图标
|
||||
isRegisterMode ? Icons.person_add_rounded : Icons.person_rounded,
|
||||
size: 48,
|
||||
color: _pinkColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
Text(
|
||||
isRegisterMode ? '注册新账号' : '账号登录',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isDark ? Colors.white : Colors.black87,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
isRegisterMode ? '请填写以下信息' : '请输入您的凭证以继续',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: isDark ? Colors.grey[400] : Colors.grey[600],
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
if (errorMessage.isNotEmpty)
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: Colors.red.withOpacity(0.3)),
|
||||
child: SingleChildScrollView(
|
||||
// 关键修复2:让滚动视图正常工作
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: MediaQuery.of(context).size.height - 100,
|
||||
),
|
||||
child: Center(
|
||||
child: Transform.translate(
|
||||
offset: const Offset(0, -30),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 400),
|
||||
child: Card(
|
||||
elevation: 8, shadowColor: _pinkColor.withOpacity(0.3),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
color: isDark ? Colors.grey[850] : Colors.white,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: _pinkColor.withOpacity(0.15),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
isRegisterMode ? Icons.person_add_rounded : Icons.person_rounded,
|
||||
size: 48,
|
||||
color: _pinkColor,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.error_outline, color: Colors.red, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
errorMessage,
|
||||
style: const TextStyle(color: Colors.red, fontSize: 13),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
Text(
|
||||
isRegisterMode ? '注册新账号' : '账号登录',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isDark ? Colors.white : Colors.black87,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
isRegisterMode ? '请填写以下信息' : '请输入您的凭证以继续',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: isDark ? Colors.grey[400] : Colors.grey[600],
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
if (errorMessage.isNotEmpty)
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: Colors.red.withOpacity(0.3)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.error_outline, color: Colors.red, size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
errorMessage,
|
||||
style: const TextStyle(color: Colors.red, fontSize: 13),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
TextField(
|
||||
controller: usernameController,
|
||||
enabled: !isLoading,
|
||||
decoration: InputDecoration(
|
||||
labelText: '用户名',
|
||||
prefixIcon: const Icon(Icons.person_outline, color: _pinkColor),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: _pinkColor),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: _pinkColor.withOpacity(0.5)),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: _pinkColor, width: 2),
|
||||
),
|
||||
filled: true,
|
||||
fillColor: isDark ? Colors.grey[800] : Colors.grey[50],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
TextField(
|
||||
controller: passwordController,
|
||||
obscureText: true,
|
||||
enabled: !isLoading,
|
||||
decoration: InputDecoration(
|
||||
labelText: isRegisterMode ? '设置密码' : '密码 / TOTP验证码',
|
||||
prefixIcon: const Icon(Icons.lock_outline, color: _pinkColor),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: _pinkColor),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: _pinkColor.withOpacity(0.5)),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: _pinkColor, width: 2),
|
||||
),
|
||||
filled: true,
|
||||
fillColor: isDark ? Colors.grey[800] : Colors.grey[50],
|
||||
),
|
||||
),
|
||||
|
||||
if (isRegisterMode) ...[
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: inviterController,
|
||||
enabled: !isLoading,
|
||||
decoration: InputDecoration(
|
||||
labelText: '邀请人 ID (可选)',
|
||||
prefixIcon: const Icon(Icons.card_giftcard_outlined, color: _pinkColor),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: _pinkColor),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: _pinkColor.withOpacity(0.5)),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: _pinkColor, width: 2),
|
||||
),
|
||||
filled: true,
|
||||
fillColor: isDark ? Colors.grey[800] : Colors.grey[50],
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 50,
|
||||
child: ElevatedButton(
|
||||
onPressed: isLoading ? null : submit,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _pinkColor,
|
||||
foregroundColor: Colors.black,
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: isLoading
|
||||
? const SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.black),
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
isRegisterMode ? '立即注册' : '登录',
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 用户名输入框
|
||||
TextField(
|
||||
controller: usernameController,
|
||||
enabled: !isLoading,
|
||||
decoration: InputDecoration(
|
||||
labelText: '用户名',
|
||||
prefixIcon: const Icon(Icons.person_outline, color: _pinkColor),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: _pinkColor),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
TextButton(
|
||||
onPressed: isLoading
|
||||
? null
|
||||
: () {
|
||||
setState(() {
|
||||
isRegisterMode = !isRegisterMode;
|
||||
errorMessage = '';
|
||||
});
|
||||
},
|
||||
child: Text(
|
||||
isRegisterMode
|
||||
? '已有账号?去登录'
|
||||
: '没有账号?去注册',
|
||||
style: const TextStyle(
|
||||
color: _pinkColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: _pinkColor.withOpacity(0.5)),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: _pinkColor, width: 2),
|
||||
),
|
||||
filled: true,
|
||||
fillColor: isDark ? Colors.grey[800] : Colors.grey[50],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// 密码输入框
|
||||
TextField(
|
||||
controller: passwordController,
|
||||
obscureText: true,
|
||||
enabled: !isLoading,
|
||||
decoration: InputDecoration(
|
||||
labelText: isRegisterMode ? '设置密码' : '密码 / TOTP验证码',
|
||||
prefixIcon: const Icon(Icons.lock_outline, color: _pinkColor),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: _pinkColor),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: _pinkColor.withOpacity(0.5)),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: _pinkColor, width: 2),
|
||||
),
|
||||
filled: true,
|
||||
fillColor: isDark ? Colors.grey[800] : Colors.grey[50],
|
||||
),
|
||||
),
|
||||
|
||||
if (isRegisterMode) ...[
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: inviterController,
|
||||
enabled: !isLoading,
|
||||
decoration: InputDecoration(
|
||||
labelText: '邀请人 ID (可选)',
|
||||
prefixIcon: const Icon(Icons.card_giftcard_outlined, color: _pinkColor),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: _pinkColor),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: _pinkColor.withOpacity(0.5)),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: _pinkColor, width: 2),
|
||||
),
|
||||
filled: true,
|
||||
fillColor: isDark ? Colors.grey[800] : Colors.grey[50],
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// 登录/注册按钮
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 50,
|
||||
child: ElevatedButton(
|
||||
onPressed: isLoading ? null : submit,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _pinkColor,
|
||||
foregroundColor: Colors.black,
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: isLoading
|
||||
? const SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.black),
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
isRegisterMode ? '立即注册' : '登录',
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// 切换登录/注册
|
||||
TextButton(
|
||||
onPressed: isLoading
|
||||
? null
|
||||
: () {
|
||||
setState(() {
|
||||
isRegisterMode = !isRegisterMode;
|
||||
errorMessage = '';
|
||||
});
|
||||
},
|
||||
child: Text(
|
||||
isRegisterMode
|
||||
? '已有账号?去登录'
|
||||
: '没有账号?去注册',
|
||||
style: const TextStyle(
|
||||
color: _pinkColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <file_selector_linux/file_selector_plugin.h>
|
||||
#include <open_file_linux/open_file_linux_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
|
||||
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) open_file_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "OpenFileLinuxPlugin");
|
||||
open_file_linux_plugin_register_with_registrar(open_file_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
}
|
||||
|
||||
@@ -3,13 +3,9 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
file_selector_linux
|
||||
open_file_linux
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
jni
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
32
pubspec.lock
32
pubspec.lock
@@ -49,6 +49,14 @@ packages:
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: charcode
|
||||
sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -376,22 +384,6 @@ packages:
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
google_mlkit_barcode_scanning:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: google_mlkit_barcode_scanning
|
||||
sha256: "965183a8cd5cef8477ceea5dbdf29c34a739cf0cfbf1bdad54cd3f9f1807afe5"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.10.0"
|
||||
google_mlkit_commons:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: google_mlkit_commons
|
||||
sha256: "046586b381cdd139f7f6a05ad6998f7e339d061bd70158249907358394b5f496"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.6.1"
|
||||
gsettings:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1181,6 +1173,14 @@ packages:
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
zxing2:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: zxing2
|
||||
sha256: "2677c49a3b9ca9457cb1d294fd4bd5041cac6aab8cdb07b216ba4e98945c684f"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.2.4"
|
||||
sdks:
|
||||
dart: ">=3.11.3 <4.0.0"
|
||||
flutter: ">=3.38.4"
|
||||
|
||||
@@ -58,8 +58,8 @@ dev_dependencies:
|
||||
open_file: ^3.3.2
|
||||
webview_flutter: ^4.8.0
|
||||
image: ^4.0.17
|
||||
google_mlkit_barcode_scanning: ^0.10.0 # 用于识别二维码/条形码
|
||||
image_picker: ^1.0.4
|
||||
zxing2: ^0.2.3 # 新增:用于二维码识别
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
@@ -6,18 +6,6 @@
|
||||
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <file_selector_windows/file_selector_windows.h>
|
||||
#include <geolocator_windows/geolocator_windows.h>
|
||||
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
FileSelectorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||
GeolocatorWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("GeolocatorWindows"));
|
||||
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
}
|
||||
|
||||
@@ -3,14 +3,9 @@
|
||||
#
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
file_selector_windows
|
||||
geolocator_windows
|
||||
share_plus
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
jni
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
Reference in New Issue
Block a user