import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:provider/provider.dart'; // 新增导入 import 'package:zxing2/qrcode.dart'; import 'package:image/image.dart' as img; import '../../providers/user_provider.dart'; class BindAccountPage extends StatefulWidget { const BindAccountPage({super.key}); @override State createState() => _BindAccountPageState(); } class _BindAccountPageState extends State { final TextEditingController _qrContentController = TextEditingController(); final ImagePicker _picker = ImagePicker(); bool _isProcessing = false; String? _statusMessage; File? _selectedImageFile; // 用户选择的原始二维码截图 @override void dispose() { _qrContentController.dispose(); super.dispose(); } // 1. 选择图片并识别二维码 (使用 zxing2替代 ML Kit) Future _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 bytes = await pickedFile.readAsBytes(); // 使用 image 库解码图片为 RGB 数据 final img.Image? decodedImage = img.decodeImage(bytes); if (decodedImage == null) { throw Exception("无法解析图片格式"); } setState(() { _statusMessage = "正在解析二维码..."; }); 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(() { _statusMessage = "未在图中找到有效二维码"; }); } } catch (e) { setState(() { _statusMessage = "识别出错: $e"; }); } finally { setState(() { _isProcessing = false; }); } } // 2. 执行隐写并上传 (触发后端绑定/登录逻辑) Future _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(); // 建议在实际生产环境中添加 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 = []; await for (final chunk in response) { bytes.addAll(chunk); } imageBytes = Uint8List.fromList(bytes); // 关闭 client 以释放资源 httpClient.close(); if (imageBytes == null || imageBytes.isEmpty) throw Exception("图片数据无效"); setState(() { _statusMessage = "正在提交绑定请求..."; }); // 调用 Provider final userProvider = context.read(); 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(); // 可选:清空输入或跳转 // _qrContentController.clear(); // Navigator.of(context).pop(); } else { 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: Container( decoration: BoxDecoration( gradient: const LinearGradient( colors: [Colors.purpleAccent, Colors.deepOrange], begin: Alignment.topLeft, // 改成你要的 end: Alignment.bottomRight, // 改成你要的 ), borderRadius: BorderRadius.circular(14), boxShadow: [ BoxShadow( color: Colors.pink.withOpacity(0.4), blurRadius: 8, offset: const Offset(0, 4), ), ], ), child: ElevatedButton.icon( onPressed: _isProcessing ? null : _pickImageAndScan, icon: const Icon(Icons.image_search, size: 20), label: const Text("识别截图", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500)), style: ElevatedButton.styleFrom( backgroundColor: Colors.transparent, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 14), elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), ), ), ), ), ), const SizedBox(width: 12), Expanded( child: Container( decoration: BoxDecoration( // 只改这里:渐变方向 → 参考你的写法 gradient: const LinearGradient( colors: [Colors.white10, Colors.black], begin: Alignment.topLeft, // 改成你要的 end: Alignment.bottomRight, // 改成你要的 ), borderRadius: BorderRadius.circular(14), boxShadow: [ BoxShadow( color: Colors.white.withOpacity(0.4), blurRadius: 8, offset: const Offset(0, 4), ), ], ), child: ElevatedButton.icon( onPressed: _isProcessing ? null : _handleBind, icon: _isProcessing ? const SizedBox( width: 18, height: 18, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : const Icon(Icons.login, size: 20), label: Text( _isProcessing ? "处理中" : "绑定/刷新", style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500), ), style: ElevatedButton.styleFrom( backgroundColor: Colors.transparent, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 14), elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), ), ), ), ), ), ], ), ], ), ), // 状态消息 if (_statusMessage != null) Padding( padding: const EdgeInsets.only(top: 20), child: Text( _statusMessage!, style: TextStyle( color: _statusMessage!.contains("失败") || _statusMessage!.contains("错误") || _statusMessage!.contains("未识别") ? Colors.redAccent : Colors.white70, ), textAlign: TextAlign.center, ), ), ], ), ), ), ), ], ), ); } }