- 标注:AI 预标注直写 labels_json(去候选确认两阶段);重叠去重(minIoU);全量标注按钮 - 训练:脚本迁移入 server/training/(Go 化 prepare_yolo/analyze_rfdetr,保留 train_server.py);tflite 产物自检并入训练流程(check_tflite) - 数据目录/权重不进 git;.gitignore 迁移至仓库根
127 lines
4.3 KiB
Dart
127 lines
4.3 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart' show SystemNavigator;
|
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
import '../auth/session_store.dart';
|
|
|
|
/// 使用协议页:首次启动强制展示,同意后记录(secure storage),拒绝则退出应用。
|
|
/// 用途声明:仅限动物观察,禁止近距离打扰与非法使用。
|
|
class TermsScreen extends StatefulWidget {
|
|
const TermsScreen({super.key});
|
|
|
|
static const _storage = FlutterSecureStorage();
|
|
static const acceptedKey = 'terms_accepted';
|
|
|
|
static Future<String?> readAccepted() => _storage.read(key: acceptedKey);
|
|
|
|
@override
|
|
State<TermsScreen> createState() => _TermsScreenState();
|
|
}
|
|
|
|
class _TermsScreenState extends State<TermsScreen> {
|
|
bool _saving = false;
|
|
|
|
static const String _content = '''
|
|
《视野》使用协议
|
|
|
|
感谢您使用「视野」动物观察应用。使用本应用前,请仔细阅读并同意以下条款:
|
|
|
|
一、用途限制
|
|
本应用仅用于动物观察、记录与科普学习,严禁用于任何非法用途。
|
|
|
|
二、禁止行为
|
|
1. 禁止近距离打扰、追逐、驱赶、恐吓动物;
|
|
2. 禁止投喂、诱捕、猎捕、伤害动物;
|
|
3. 禁止利用本应用从事盗猎、动物交易或其他违法犯罪活动;
|
|
4. 禁止进入自然保护区禁区、私人领地等未经许可的区域进行观察。
|
|
|
|
三、法律合规
|
|
您须遵守《中华人民共和国野生动物保护法》及相关法律法规。
|
|
因违法违规使用产生的一切后果由使用者自行承担。
|
|
|
|
四、安全提示
|
|
野外观察存在风险,请结伴而行、注意自身安全。因使用本应用产生的安全事故,本应用不承担责任。
|
|
|
|
五、识别结果说明
|
|
本应用的动物识别结果基于人工智能模型,可能存在误差或漏检,识别结果仅供参考,不构成科学鉴定或法律依据。
|
|
|
|
六、其他
|
|
本协议内容可能适时更新,更新后您继续使用即视为接受。''';
|
|
|
|
Future<void> _accept() async {
|
|
if (_saving) return;
|
|
setState(() => _saving = true);
|
|
await TermsScreen._storage.write(key: TermsScreen.acceptedKey, value: '1');
|
|
if (!mounted) return;
|
|
final token = await context.read<SessionStore>().readToken();
|
|
if (!mounted) return;
|
|
Navigator.of(context)
|
|
.pushReplacementNamed(token == null ? '/login' : '/home');
|
|
}
|
|
|
|
Future<void> _decline() async {
|
|
if (_saving) return;
|
|
final ok = await showDialog<bool>(
|
|
context: context,
|
|
builder: (ctx) => AlertDialog(
|
|
title: const Text('需要同意协议'),
|
|
content: const Text('不同意《使用协议》将无法使用本应用。'),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.of(ctx).pop(false),
|
|
child: const Text('再看看'),
|
|
),
|
|
TextButton(
|
|
onPressed: () => Navigator.of(ctx).pop(true),
|
|
child: const Text('退出'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
if (ok == true && mounted) await SystemNavigator.pop();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(title: const Text('使用协议')),
|
|
body: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Text(
|
|
_content,
|
|
style: const TextStyle(fontSize: 15, height: 1.7),
|
|
),
|
|
),
|
|
bottomNavigationBar: SafeArea(
|
|
child: Padding(
|
|
padding: const EdgeInsets.fromLTRB(20, 8, 20, 16),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: OutlinedButton(
|
|
onPressed: _saving ? null : _decline,
|
|
child: const Text('不同意'),
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: FilledButton(
|
|
onPressed: _saving ? null : _accept,
|
|
child: _saving
|
|
? const SizedBox(
|
|
width: 18,
|
|
height: 18,
|
|
child: CircularProgressIndicator(strokeWidth: 2),
|
|
)
|
|
: const Text('同意并继续'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|