1
This commit is contained in:
@@ -6,6 +6,7 @@ import 'auth/session_store.dart';
|
||||
import 'camera/camera_screen.dart';
|
||||
import 'container.dart';
|
||||
import 'home/home_screen.dart';
|
||||
import 'legal/terms_screen.dart';
|
||||
import 'payment/paywall_screen.dart';
|
||||
|
||||
class ObserverApp extends StatelessWidget {
|
||||
@@ -31,6 +32,7 @@ class ObserverApp extends StatelessWidget {
|
||||
initialRoute: '/gate',
|
||||
routes: {
|
||||
'/gate': (_) => const StartupGate(),
|
||||
'/terms': (_) => const TermsScreen(),
|
||||
'/login': (_) => const AuthScreen(),
|
||||
'/home': (_) => const HomeScreen(),
|
||||
'/paywall': (_) => const PaywallScreen(),
|
||||
@@ -57,6 +59,13 @@ class _StartupGateState extends State<StartupGate> {
|
||||
}
|
||||
|
||||
Future<void> _check() async {
|
||||
// 首次启动强制协议;同意后不再展示(拒绝则退出应用)
|
||||
final accepted = await TermsScreen.readAccepted();
|
||||
if (!mounted) return;
|
||||
if (accepted != '1') {
|
||||
Navigator.of(context).pushReplacementNamed('/terms');
|
||||
return;
|
||||
}
|
||||
final session = context.read<SessionStore>();
|
||||
final token = await session.readToken();
|
||||
if (!mounted) return;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
/// 全局配置占位:接入真实支付前需替换以下值。
|
||||
class AppConfig {
|
||||
/// 后端服务器地址(订单创建/授权查询/套餐价格)
|
||||
/// 默认 Android 模拟器 10.0.2.2;iOS 模拟器构建时传
|
||||
/// --dart-define=API_BASE_URL=http://127.0.0.1:8080
|
||||
static const String apiBaseUrl =
|
||||
String.fromEnvironment('API_BASE_URL', defaultValue: 'http://10.0.2.2:8080');
|
||||
/// 默认指向线上域名;本地联调构建时用 --dart-define=API_BASE_URL=http://127.0.0.1:8080 覆盖
|
||||
static const String apiBaseUrl = String.fromEnvironment('API_BASE_URL',
|
||||
defaultValue: 'http://observer.redpowerfuture.com');
|
||||
|
||||
/// 微信开放平台 AppID(需在微信开放平台注册包名+签名)
|
||||
static const String wechatAppId = 'wx0000000000000000';
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
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('同意并继续'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user