293 lines
11 KiB
Dart
293 lines
11 KiB
Dart
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 '../../providers/user_provider.dart';
|
|
|
|
class BindAccountPage extends StatefulWidget {
|
|
const BindAccountPage({super.key});
|
|
|
|
@override
|
|
State<BindAccountPage> createState() => _BindAccountPageState();
|
|
}
|
|
|
|
class _BindAccountPageState extends State<BindAccountPage> {
|
|
final TextEditingController _qrContentController = TextEditingController();
|
|
final ImagePicker _picker = ImagePicker();
|
|
|
|
bool _isProcessing = false;
|
|
String? _statusMessage;
|
|
File? _selectedImageFile; // 用户选择的原始二维码截图
|
|
|
|
// 条码扫描器
|
|
final BarcodeScanner _barcodeScanner = BarcodeScanner(
|
|
formats: [BarcodeFormat.qrCode],
|
|
);
|
|
|
|
@override
|
|
void dispose() {
|
|
_qrContentController.dispose();
|
|
_barcodeScanner.close();
|
|
super.dispose();
|
|
}
|
|
|
|
// 1. 选择图片并识别二维码
|
|
Future<void> _pickImageAndScan() async {
|
|
try {
|
|
final XFile? pickedFile = await _picker.pickImage(source: ImageSource.gallery);
|
|
if (pickedFile == null) return;
|
|
|
|
setState(() {
|
|
_isProcessing = true;
|
|
_statusMessage = "正在识别二维码...";
|
|
_selectedImageFile = File(pickedFile.path);
|
|
});
|
|
|
|
final inputImage = InputImage.fromFilePath(pickedFile.path);
|
|
final List<Barcode> barcodes = await _barcodeScanner.processImage(inputImage);
|
|
|
|
setState(() {
|
|
_isProcessing = false;
|
|
});
|
|
|
|
if (barcodes.isNotEmpty && barcodes.first.rawValue != null) {
|
|
final String code = barcodes.first.rawValue!;
|
|
setState(() {
|
|
_qrContentController.text = code;
|
|
_statusMessage = "识别成功";
|
|
});
|
|
} else {
|
|
setState(() {
|
|
_statusMessage = "未识别到二维码,请手动输入";
|
|
});
|
|
}
|
|
|
|
} catch (e) {
|
|
setState(() {
|
|
_isProcessing = false;
|
|
_statusMessage = "识别失败: $e";
|
|
});
|
|
}
|
|
}
|
|
|
|
// 2. 执行隐写并上传 (触发后端绑定/登录逻辑)
|
|
Future<void> _handleBind() async {
|
|
final qrContent = _qrContentController.text.trim();
|
|
|
|
if (qrContent.isEmpty) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text("请输入或扫描二维码内容"), backgroundColor: Colors.orange),
|
|
);
|
|
return;
|
|
}
|
|
|
|
Uint8List? imageBytes;
|
|
|
|
setState(() {
|
|
_isProcessing = true;
|
|
_statusMessage = "正在处理图片...";
|
|
});
|
|
|
|
try {
|
|
final httpClient = HttpClient();
|
|
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>[];
|
|
await for (final chunk in response) {
|
|
bytes.addAll(chunk);
|
|
}
|
|
imageBytes = Uint8List.fromList(bytes);
|
|
|
|
if (imageBytes == null || imageBytes.isEmpty) throw Exception("图片数据无效");
|
|
|
|
setState(() {
|
|
_statusMessage = "正在提交绑定请求...";
|
|
});
|
|
|
|
// 调用 Provider
|
|
// 注意:这里 segaId 参数其实传的是 QR Code 的内容
|
|
final userProvider = context.read<UserProvider>();
|
|
final result = await userProvider.uploadStegImage(imageBytes, segaId: qrContent);
|
|
|
|
setState(() {
|
|
_isProcessing = false;
|
|
});
|
|
|
|
// 处理后端返回
|
|
if (mounted) {
|
|
if (result['code'] == 200) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text("✅ ${result['msg']}"), backgroundColor: Colors.green, duration: const Duration(seconds: 5)),
|
|
);
|
|
// 绑定成功后,通常建议刷新用户信息
|
|
await userProvider.initUser();
|
|
} else {
|
|
// 后端返回 500 或其他错误
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text("❌ ${result['msg']}"), backgroundColor: Colors.red, duration: const Duration(seconds: 5)),
|
|
);
|
|
}
|
|
}
|
|
|
|
} catch (e) {
|
|
setState(() {
|
|
_isProcessing = false;
|
|
_statusMessage = "网络或处理错误: $e";
|
|
});
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text("错误: $e"), backgroundColor: Colors.red),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text("绑定 / 刷新 Sega 账号"),
|
|
backgroundColor: Colors.black.withOpacity(0.5),
|
|
elevation: 0,
|
|
),
|
|
body: Stack(
|
|
fit: StackFit.expand,
|
|
children: [
|
|
// 背景图
|
|
Image.network(
|
|
'https://union.godserver.cn/assets/jpeg/20180621142015_5FmGZ-wYXkyL4y.jpeg',
|
|
fit: BoxFit.cover,
|
|
loadingBuilder: (context, child, loadingProgress) {
|
|
if (loadingProgress == null) return child;
|
|
return Container(color: Colors.black);
|
|
},
|
|
errorBuilder: (context, error, stackTrace) => Container(color: Colors.grey[900]),
|
|
),
|
|
|
|
// 遮罩
|
|
Container(color: Colors.black.withOpacity(0.75)),
|
|
|
|
// 内容
|
|
SafeArea(
|
|
child: Center(
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(24.0),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Icon(Icons.qr_code_scanner, size: 60, color: Colors.white70),
|
|
const SizedBox(height: 16),
|
|
const Text(
|
|
"请提供 Sega 登录二维码",
|
|
style: TextStyle(fontSize: 18, color: Colors.white, fontWeight: FontWeight.bold),
|
|
),
|
|
const SizedBox(height: 8),
|
|
const Text(
|
|
"支持识别截图或手动输入二维码内容\n用于绑定账号或刷新登录状态",
|
|
style: TextStyle(fontSize: 14, color: Colors.white60),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const SizedBox(height: 30),
|
|
|
|
// 输入卡片
|
|
Container(
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(16),
|
|
border: Border.all(color: Colors.white.withOpacity(0.2)),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
TextField(
|
|
controller: _qrContentController,
|
|
style: const TextStyle(color: Colors.white),
|
|
maxLines: 10,
|
|
decoration: InputDecoration(
|
|
labelText: "二维码内容 (QR Data)",
|
|
labelStyle: const TextStyle(color: Colors.white70),
|
|
hintText: "例如: SGWCMAID...",
|
|
hintStyle: const TextStyle(color: Colors.white38),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderSide: const BorderSide(color: Colors.white38),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderSide: const BorderSide(color: Colors.white),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 20),
|
|
|
|
// 预览图
|
|
if (_selectedImageFile != null)
|
|
ClipRRect(
|
|
borderRadius: BorderRadius.circular(8),
|
|
child: Image.file(_selectedImageFile!, height: 120, fit: BoxFit.contain),
|
|
),
|
|
|
|
if (_selectedImageFile != null) const SizedBox(height: 15),
|
|
|
|
// 按钮组
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: ElevatedButton.icon(
|
|
onPressed: _isProcessing ? null : _pickImageAndScan,
|
|
icon: const Icon(Icons.image_search),
|
|
label: const Text("识别截图"),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.blueAccent,
|
|
foregroundColor: Colors.white,
|
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: ElevatedButton.icon(
|
|
onPressed: _isProcessing ? null : _handleBind,
|
|
icon: _isProcessing
|
|
? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white))
|
|
: const Icon(Icons.login),
|
|
label: Text(_isProcessing ? "处理中" : "绑定/刷新"),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.green,
|
|
foregroundColor: Colors.white,
|
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// 状态消息
|
|
if (_statusMessage != null)
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 20),
|
|
child: Text(
|
|
_statusMessage!,
|
|
style: TextStyle(
|
|
color: _statusMessage!.contains("失败") || _statusMessage!.contains("错误") ? Colors.redAccent : Colors.white70,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
} |