diff --git a/assets/languages/en-US.json b/assets/languages/en-US.json index c43f7da1..23ea7136 100644 --- a/assets/languages/en-US.json +++ b/assets/languages/en-US.json @@ -1,4 +1,5 @@ { + "legal": {"open_failed": "Unable to open the document. Please try again later."}, "device_task_control": { "title": "Task control", diff --git a/assets/languages/zh-CN.json b/assets/languages/zh-CN.json index 6c6f239b..d7b278eb 100644 --- a/assets/languages/zh-CN.json +++ b/assets/languages/zh-CN.json @@ -1,4 +1,5 @@ { + "legal": {"open_failed": "暂时无法打开协议页面,请稍后重试。"}, "device_task_control": { "title": "任务控制", diff --git a/lib/components/legal_document.dart b/lib/components/legal_document.dart new file mode 100644 index 00000000..1b91661f --- /dev/null +++ b/lib/components/legal_document.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../core/localization/app_localizations.dart'; +import 'capsule_toast.dart'; + +enum LegalDocument { + privacy('my.privacy_policy', 'https://ri.satabot.com/web/legal/privacy.html'), + terms('my.user_agreement', 'https://ri.satabot.com/web/legal/terms.html'); + + const LegalDocument(this.titleKey, this.url); + + final String titleKey; + final String url; +} + +Future openLegalDocument( + BuildContext context, + LegalDocument document, +) async { + final errorText = AppLocalizations.of(context).translate('legal.open_failed'); + try { + final opened = await launchUrl( + Uri.parse(document.url), + mode: LaunchMode.inAppBrowserView, + webOnlyWindowName: '_blank', + ); + if (opened) return; + } catch (_) { + // 平台无法打开时保留当前页面,并向用户提供重试提示。 + } + if (context.mounted) CapsuleToast.show(errorText, showCheck: false); +} diff --git a/lib/features/auth/presentation/pages/login_page_v2.dart b/lib/features/auth/presentation/pages/login_page_v2.dart index 7e6adde6..2ffe629e 100644 --- a/lib/features/auth/presentation/pages/login_page_v2.dart +++ b/lib/features/auth/presentation/pages/login_page_v2.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:maibu_satabot_v2/components/legal_document.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:maibu_satabot_v2/components/app_logo.dart'; @@ -547,13 +548,13 @@ class _LoginPageV2State extends State style: const TextStyle(fontSize: 13, color: _grayText), ), _protocolText(loc.translate('login.user_agreement_link'), - () => _showProtocolDetail(loc.translate('login.user_agreement_link'))), + () => openLegalDocument(context, LegalDocument.terms)), Text( loc.translate('login.agree_and'), style: const TextStyle(fontSize: 13, color: _grayText), ), _protocolText(loc.translate('login.privacy_policy_link'), - () => _showProtocolDetail(loc.translate('login.privacy_policy_link'))), + () => openLegalDocument(context, LegalDocument.privacy)), ], ), ), @@ -594,66 +595,7 @@ class _LoginPageV2State extends State } */ - void _showProtocolDetail(String title) { - showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: Colors.white, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical(top: Radius.circular(16)), - ), - builder: (context) => Container( - padding: const EdgeInsets.all(24), - height: MediaQuery.of(context).size.height * 0.7, - child: Column( - children: [ - Text( - title, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: _navy, - ), - ), - const Divider(height: 32, color: _inputBorder), - Expanded( - child: SingleChildScrollView( - child: Text( - (AppLocalizations.of(context) - .translate('login.protocol_detail_text') - .replaceAll('%s', title) + - '\n') * - 20, - style: const TextStyle( - color: _tabUnselectedText, - height: 1.5, - ), - ), - ), - ), - const SizedBox(height: 16), - SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: () => Navigator.pop(context), - style: ElevatedButton.styleFrom( - backgroundColor: _primaryBlue, - foregroundColor: Colors.white, - elevation: 0, - padding: const EdgeInsets.symmetric(vertical: 14), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - child: Text(AppLocalizations.of(context) - .translate('login.understood')), - ), - ), - ], - ), - ), - ); - } + } /// 主按钮:横向蓝色渐变胶囊(#2BAFFE → #1C96F6 → #0B7DEE),带前置箭头。 diff --git a/lib/features/v2/my/presentation/pages/my_page_v2.dart b/lib/features/v2/my/presentation/pages/my_page_v2.dart index d1878718..be55459b 100644 --- a/lib/features/v2/my/presentation/pages/my_page_v2.dart +++ b/lib/features/v2/my/presentation/pages/my_page_v2.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:maibu_satabot_v2/components/legal_document.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get_it/get_it.dart'; import 'package:go_router/go_router.dart'; @@ -317,10 +318,11 @@ class MyPageV2 extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - for (final titleKey in ['my.privacy_policy', 'my.user_agreement']) + for (final document in LegalDocument.values) ListTile( - title: Text(l10n.translate(titleKey)), - subtitle: Text(l10n.translate('my_v2.legal_unavailable')), + title: Text(l10n.translate(document.titleKey)), + trailing: const Icon(Icons.chevron_right), + onTap: () => openLegalDocument(context, document), ), ], ), diff --git a/pubspec.lock b/pubspec.lock index b442e14b..48cabd17 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1578,7 +1578,7 @@ packages: source: hosted version: "3.0.2" url_launcher: - dependency: transitive + dependency: "direct main" description: name: url_launcher sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 diff --git a/pubspec.yaml b/pubspec.yaml index 1927bc21..fbf984b5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -122,6 +122,7 @@ dependencies: intl: ^0.20.2 webview_flutter: ^4.4.2 # 将此行移到这里 + url_launcher: ^6.3.2 geolocator: ^13.0.1