import 'dart:io'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import '../../core/responsive/responsive.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../core/config/app_config.dart'; import '../../core/config/app_info.dart'; import '../../core/theme/app_theme.dart'; import '../../core/update/app_updater.dart'; import '../../core/utils/dialog_util.dart'; import '../../providers/feedback_provider.dart'; import '../../providers/update_provider.dart'; /// 「关于我们」独立页面(左侧菜单项)。 class AboutScreen extends ConsumerStatefulWidget { const AboutScreen({super.key}); @override ConsumerState createState() => _AboutScreenState(); } class _AboutScreenState extends ConsumerState { @override Widget build(BuildContext context) { final appVersion = ref.watch(appVersionProvider).valueOrNull ?? 'v1.0.0'; final updateInfo = ref.watch(updateProvider).valueOrNull; return SingleChildScrollView( padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // ── 版本信息 ── _AboutSection( title: '版本信息', children: [ _AboutRow(label: '当前版本', value: appVersion), if (updateInfo != null && updateInfo.hasUpdate) _AboutRow( label: '最新版本', value: 'v${updateInfo.latestVersion}', valueColor: AppTheme.success, trailing: TextButton( onPressed: () => startInAppUpdate(context, updateInfo), child: const Text('立即更新'), ), ) else _AboutRow( label: '最新版本', value: updateInfo != null ? '已是最新' : '检查中…', valueColor: AppTheme.textSecondary, trailing: TextButton( onPressed: () async { final messenger = ScaffoldMessenger.of(context); messenger.showSnackBar( const SnackBar(content: Text('正在检查更新…'))); await ref.read(updateProvider.notifier).forceCheck(); if (!context.mounted) return; final r = ref.read(updateProvider).valueOrNull; messenger.hideCurrentSnackBar(); final String msg; if (r == null) { msg = '检查更新失败,请检查网络'; } else if (r.hasUpdate) { msg = '发现新版本 v${r.latestVersion}'; } else { msg = '已是最新版本(v${r.latestVersion})'; } messenger.showSnackBar(SnackBar(content: Text(msg))); }, child: const Text('检查更新'), ), ), ], ), const SizedBox(height: 20), // ── 帮助与文档 ── _AboutSection( title: '帮助与文档', children: [ const _AboutRow(label: '使用手册', value: '查看产品使用文档与操作指南'), const SizedBox(height: 8), Wrap( spacing: 8, children: [ OutlinedButton.icon( onPressed: () async { final url = Uri.parse( AppInfo.docsUrl.isNotEmpty ? AppInfo.docsUrl : '${AppInfo.website}/docs/'); if (await canLaunchUrl(url)) launchUrl(url); }, icon: const Icon(Icons.menu_book_outlined, size: 16), label: const Text('打开文档'), ), OutlinedButton.icon( onPressed: () async { final url = Uri.parse('${AppInfo.website}/changelog/'); if (await canLaunchUrl(url)) launchUrl(url); }, icon: const Icon(Icons.history_outlined, size: 16), label: const Text('更新日志'), ), ], ), ], ), const SizedBox(height: 20), // ── 关于我们 ── _AboutSection( title: '关于我们', children: [ _AboutRow(label: '服务商', value: AppInfo.provider), _AboutRow( label: '官方网站', value: AppInfo.website, trailing: AppInfo.website.isNotEmpty ? TextButton( onPressed: () async { final uri = Uri.parse(AppInfo.website); if (await canLaunchUrl(uri)) launchUrl(uri); }, child: const Text('访问', style: TextStyle(fontSize: 12)), ) : null, ), _AboutRow(label: '联系邮箱', value: AppInfo.email), if (AppInfo.phone.isNotEmpty) _AboutRow(label: '联系电话', value: AppInfo.phone), if (AppInfo.wechat.isNotEmpty) _AboutRow(label: '微信', value: AppInfo.wechat), const _AboutRow(label: '服务时间', value: '周一至周五 9:00–18:00'), const SizedBox(height: 8), Wrap( spacing: 8, children: [ OutlinedButton.icon( onPressed: () async { final uri = Uri.parse( 'mailto:${AppInfo.email}?subject=酒库管理系统咨询'); if (await canLaunchUrl(uri)) launchUrl(uri); }, icon: const Icon(Icons.email_outlined, size: 16), label: const Text('发送邮件'), ), ], ), ], ), const SizedBox(height: 20), // ── 扫码防伪 ── const _AboutSection( title: '扫码防伪', children: [ _AboutRow( label: '功能说明', value: '每件商品可生成专属二维码,顾客扫码即可查看商品名称、系列、规格及批次信息,帮助验证商品真实性。', ), ], ), const SizedBox(height: 20), // ── 法律信息 ── _AboutSection( title: '法律信息', children: [ if (AppInfo.termsUrl.isNotEmpty) _AboutRow( label: '服务条款', value: '查看服务条款', trailing: TextButton( onPressed: () async { final uri = Uri.parse(AppInfo.termsUrl); if (await canLaunchUrl(uri)) launchUrl(uri); }, child: const Text('查看', style: TextStyle(fontSize: 12)), ), ), if (AppInfo.privacyUrl.isNotEmpty) _AboutRow( label: '隐私政策', value: '查看隐私政策', trailing: TextButton( onPressed: () async { final uri = Uri.parse(AppInfo.privacyUrl); if (await canLaunchUrl(uri)) launchUrl(uri); }, child: const Text('查看', style: TextStyle(fontSize: 12)), ), ), _AboutRow(label: '版权', value: '© ${DateTime.now().year} ${AppInfo.provider}'), ], ), const SizedBox(height: 20), // ── 系统信息 ── _AboutSection( title: '系统信息', children: [ _AboutRow( label: '运行平台', value: kIsWeb ? 'Web' : Platform.isAndroid ? 'Android' : Platform.isIOS ? 'iOS' : Platform.isMacOS ? 'macOS' : Platform.isWindows ? 'Windows' : Platform.operatingSystem, ), _AboutRow(label: '应用版本', value: appVersion), FutureBuilder( future: PackageInfo.fromPlatform(), builder: (ctx, snap) => _AboutRow( label: '构建号', value: snap.data?.buildNumber ?? '-', ), ), ], ), const SizedBox(height: 20), // ── 意见反馈 ── _AboutSection( title: '意见反馈', children: [ const _AboutRow( label: '问题反馈', value: '遇到 Bug 或有功能建议,欢迎告知我们', ), const SizedBox(height: 8), Wrap( spacing: 8, children: [ ElevatedButton.icon( onPressed: () => _showFeedbackDialog(isBug: true), icon: const Icon(Icons.bug_report_outlined, size: 16), label: const Text('反馈 Bug'), ), OutlinedButton.icon( onPressed: () => _showFeedbackDialog(isBug: false), icon: const Icon(Icons.lightbulb_outline, size: 16), label: const Text('功能建议'), ), ], ), ], ), ], ), ); } void _showFeedbackDialog({required bool isBug}) { showAppDialog( context: context, builder: (_) => _FeedbackDialog(type: isBug ? 'bug' : 'suggestion'), ); } } /// 反馈表单弹窗:文字 + 附图,直接提交后台。 class _FeedbackDialog extends ConsumerStatefulWidget { final String type; // bug / suggestion const _FeedbackDialog({required this.type}); @override ConsumerState<_FeedbackDialog> createState() => _FeedbackDialogState(); } class _FeedbackDialogState extends ConsumerState<_FeedbackDialog> { final _ctrl = TextEditingController(); final List _images = []; // 已上传的图片 URL bool _uploading = false; bool _submitting = false; static const _maxImages = 9; @override void dispose() { _ctrl.dispose(); super.dispose(); } Future _addImages() async { final result = await FilePicker.platform.pickFiles( type: FileType.image, allowMultiple: true, withData: true, ); if (result == null) return; setState(() => _uploading = true); try { final repo = ref.read(feedbackRepositoryProvider); for (final f in result.files) { if (_images.length >= _maxImages) break; if (f.bytes == null) continue; final url = await repo.uploadImage(bytes: f.bytes!, filename: f.name); if (!mounted) return; setState(() => _images.add(url)); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text('图片上传失败:$e'))); } } finally { if (mounted) setState(() => _uploading = false); } } Future _submit() async { if (_ctrl.text.trim().isEmpty && _images.isEmpty) { ScaffoldMessenger.of(context) .showSnackBar(const SnackBar(content: Text('请填写反馈内容或添加图片'))); return; } setState(() => _submitting = true); try { await ref.read(feedbackRepositoryProvider).submit( type: widget.type, content: _ctrl.text.trim(), images: _images, ); if (!mounted) return; Navigator.pop(context); ScaffoldMessenger.of(context) .showSnackBar(const SnackBar(content: Text('反馈已提交,感谢您的反馈!'))); } catch (e) { if (!mounted) return; setState(() => _submitting = false); ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text('提交失败:$e'))); } } @override Widget build(BuildContext context) { final isBug = widget.type == 'bug'; return AlertDialog( title: Text(isBug ? '反馈 Bug' : '功能建议'), content: SizedBox( width: context.dialogWidth(440), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ TextField( controller: _ctrl, maxLines: 6, decoration: InputDecoration( hintText: isBug ? '请描述问题的复现步骤和预期行为…' : '请描述您希望增加的功能…', border: const OutlineInputBorder(), ), ), const SizedBox(height: 12), Wrap( spacing: 8, runSpacing: 8, children: [ for (int i = 0; i < _images.length; i++) _thumb(i), if (_images.length < _maxImages) _addButton(), ], ), if (_uploading) const Padding( padding: EdgeInsets.only(top: 8), child: Row(children: [ SizedBox( width: 14, height: 14, child: CircularProgressIndicator(strokeWidth: 2)), SizedBox(width: 8), Text('图片上传中…', style: TextStyle(fontSize: 12)), ]), ), ], ), ), actions: [ TextButton( onPressed: _submitting ? null : () => Navigator.pop(context), child: const Text('取消'), ), ElevatedButton( onPressed: (_submitting || _uploading) ? null : _submit, child: Text(_submitting ? '提交中…' : '提交'), ), ], ); } Widget _thumb(int i) { return Stack( clipBehavior: Clip.none, children: [ ClipRRect( borderRadius: BorderRadius.circular(6), child: Image.network( '${AppConfig.baseUrl}${_images[i]}', width: 64, height: 64, fit: BoxFit.cover, errorBuilder: (_, __, ___) => Container( width: 64, height: 64, color: Colors.grey.shade200, child: const Icon(Icons.broken_image, size: 20), ), ), ), Positioned( right: -8, top: -8, child: GestureDetector( onTap: () => setState(() => _images.removeAt(i)), child: Container( decoration: const BoxDecoration( color: Colors.black54, shape: BoxShape.circle), padding: const EdgeInsets.all(2), child: const Icon(Icons.close, size: 14, color: Colors.white), ), ), ), ], ); } Widget _addButton() { return InkWell( onTap: _uploading ? null : _addImages, child: Container( width: 64, height: 64, decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade400), borderRadius: BorderRadius.circular(6), ), child: const Icon(Icons.add_a_photo_outlined, color: Colors.grey), ), ); } } // ── 关于页辅助 widgets ────────────────────────────────────── class _AboutSection extends StatelessWidget { final String title; final List children; const _AboutSection({required this.title, required this.children}); @override Widget build(BuildContext context) { return Card( child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: AppTheme.primaryDark)), const Divider(height: 24), ...children, ], ), ), ); } } class _AboutRow extends StatelessWidget { final String label; final String value; final Color? valueColor; final Widget? trailing; const _AboutRow({ required this.label, required this.value, this.valueColor, this.trailing, }); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(vertical: 6), child: Row( children: [ SizedBox( width: 88, child: Text(label, style: const TextStyle( fontSize: 13, color: AppTheme.textSecondary)), ), Expanded( child: Text(value, style: TextStyle( fontSize: 13, color: valueColor ?? AppTheme.textPrimary, fontWeight: FontWeight.w500)), ), if (trailing != null) trailing!, ], ), ); } }