520
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -3,44 +3,90 @@
|
||||
/// 创建时间: 2026-05-19
|
||||
/// 更新时间: 2026-05-19
|
||||
/// 作用: 定义所有协议/政策类型,供全局调用
|
||||
/// 上次更新: 新增webUrl在线版地址
|
||||
/// 上次更新: 新增多语言支持(titleEn/subtitleEn/titleFor/webUrlFor)
|
||||
/// ============================================================
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
enum AgreementType {
|
||||
privacyPolicy('privacy-policy', '隐私政策', CupertinoIcons.shield_lefthalf_fill),
|
||||
privacyPolicy(
|
||||
'privacy-policy',
|
||||
'隐私政策',
|
||||
'Privacy Policy',
|
||||
'How we collect, use, store and protect your personal information',
|
||||
CupertinoIcons.shield_lefthalf_fill,
|
||||
),
|
||||
userServiceAgreement(
|
||||
'user-service-agreement',
|
||||
'用户服务协议',
|
||||
'User Service Agreement',
|
||||
'Terms and rules for using Xianyan APP',
|
||||
CupertinoIcons.doc_text_fill,
|
||||
),
|
||||
accountAgreement(
|
||||
'account-agreement',
|
||||
'账号使用协议',
|
||||
'Account Agreement',
|
||||
'Rules for account registration, security, and deletion',
|
||||
CupertinoIcons.person_crop_circle_badge_checkmark,
|
||||
),
|
||||
memberBenefits('member-benefits', '会员权益说明', CupertinoIcons.star_circle_fill),
|
||||
memberBenefits(
|
||||
'member-benefits',
|
||||
'会员权益说明',
|
||||
'Member Benefits',
|
||||
'Membership levels, benefits and usage instructions',
|
||||
CupertinoIcons.star_circle_fill,
|
||||
),
|
||||
disclaimer(
|
||||
'disclaimer',
|
||||
'免责声明及内容版权归属',
|
||||
'Disclaimer & Copyright',
|
||||
'Disclaimers, content copyright and intellectual property notices',
|
||||
CupertinoIcons.exclamationmark_shield_fill,
|
||||
),
|
||||
childrenPrivacy(
|
||||
'children-privacy',
|
||||
'儿童隐私政策',
|
||||
'Children\'s Privacy Policy',
|
||||
'Rules for protecting children\'s personal information',
|
||||
CupertinoIcons.person_crop_circle_fill,
|
||||
),
|
||||
permissionUsage(
|
||||
'permission-usage',
|
||||
'软件权限使用说明',
|
||||
'Permission Usage',
|
||||
'App permissions and their purposes',
|
||||
CupertinoIcons.lock_shield_fill,
|
||||
),
|
||||
appIntroduction('app-introduction', '软件介绍', CupertinoIcons.info_circle_fill),
|
||||
beginnerGuide('beginner-guide', '新手指引', CupertinoIcons.lightbulb_fill),
|
||||
devTeam('dev-team', '开发团队', CupertinoIcons.group_solid);
|
||||
appIntroduction(
|
||||
'app-introduction',
|
||||
'软件介绍',
|
||||
'About Xianyan',
|
||||
'Xianyan APP features and platform support',
|
||||
CupertinoIcons.info_circle_fill,
|
||||
),
|
||||
beginnerGuide(
|
||||
'beginner-guide',
|
||||
'新手指引',
|
||||
'Beginner\'s Guide',
|
||||
'Feature tour and operation guide',
|
||||
CupertinoIcons.lightbulb_fill,
|
||||
),
|
||||
devTeam(
|
||||
'dev-team',
|
||||
'开发团队',
|
||||
'Development Team',
|
||||
'Team information and contact details',
|
||||
CupertinoIcons.group_solid,
|
||||
);
|
||||
|
||||
const AgreementType(this.route, this.title, this.icon);
|
||||
const AgreementType(
|
||||
this.route,
|
||||
this.title,
|
||||
this.titleEn,
|
||||
this.subtitleEn,
|
||||
this.icon,
|
||||
);
|
||||
|
||||
static const String _webBase = 'https://tools.wktyl.com/agreements';
|
||||
|
||||
@@ -48,8 +94,43 @@ enum AgreementType {
|
||||
|
||||
final String title;
|
||||
|
||||
final String titleEn;
|
||||
|
||||
final String subtitleEn;
|
||||
|
||||
final IconData icon;
|
||||
|
||||
/// 根据语言ID获取标题
|
||||
String titleFor(String? languageId) {
|
||||
final isEn = languageId == 'en';
|
||||
return isEn ? titleEn : title;
|
||||
}
|
||||
|
||||
/// 根据语言ID获取副标题
|
||||
String subtitleFor(String? languageId) {
|
||||
final isEn = languageId == 'en';
|
||||
return isEn ? subtitleEn : _subtitleZh;
|
||||
}
|
||||
|
||||
String get _subtitleZh => switch (this) {
|
||||
privacyPolicy => '我们如何收集、使用、存储和保护您的个人信息',
|
||||
userServiceAgreement => '使用闲言APP的服务条款与规则',
|
||||
accountAgreement => '账号注册、安全、注销等相关规定',
|
||||
memberBenefits => '会员等级、权益与使用说明',
|
||||
disclaimer => '免责条款、内容版权与知识产权声明',
|
||||
childrenPrivacy => '儿童及未成年人个人信息保护规则',
|
||||
permissionUsage => '软件所需权限及使用目的说明',
|
||||
appIntroduction => '闲言APP功能介绍与平台支持',
|
||||
beginnerGuide => '功能导览与操作指引',
|
||||
devTeam => '开发团队信息与联系方式',
|
||||
};
|
||||
|
||||
/// 根据语言ID获取在线URL
|
||||
String webUrlFor(String? languageId) {
|
||||
final base = '$_webBase/$route.html';
|
||||
return languageId == 'en' ? '$base?lang=en' : base;
|
||||
}
|
||||
|
||||
String get webUrl => '$_webBase/$route.html';
|
||||
|
||||
static const String webIndexUrl = '$_webBase/';
|
||||
|
||||
@@ -3,34 +3,39 @@
|
||||
/// 创建时间: 2026-05-19
|
||||
/// 更新时间: 2026-05-19
|
||||
/// 作用: 展示所有协议/政策入口列表,点击跳转对应协议详情
|
||||
/// 上次更新: 初始创建,支持动态主题
|
||||
/// 上次更新: 支持多语言(中文+英文),根据当前语言显示对应标题和副标题
|
||||
/// ============================================================
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart' show Divider;
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../../core/theme/app_theme.dart';
|
||||
import '../../../core/theme/app_spacing.dart';
|
||||
import '../../../core/theme/app_typography.dart';
|
||||
import '../../../core/theme/app_radius.dart';
|
||||
import '../../../l10n/app_locale.dart';
|
||||
import '../../../core/router/app_nav_extension.dart';
|
||||
import '../../../shared/widgets/glass_container.dart';
|
||||
import '../data/agreement_types.dart';
|
||||
import '../data/agreement_data.dart';
|
||||
|
||||
class AgreementListPage extends StatelessWidget {
|
||||
class AgreementListPage extends ConsumerWidget {
|
||||
const AgreementListPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final ext = AppTheme.ext(context);
|
||||
final languageId = ref.watch(appLocaleProvider).toLanguageTag().startsWith('en')
|
||||
? 'en'
|
||||
: null;
|
||||
|
||||
return CupertinoPageScaffold(
|
||||
backgroundColor: ext.bgPrimary,
|
||||
navigationBar: CupertinoNavigationBar(
|
||||
middle: Text(
|
||||
'软件协议',
|
||||
languageId == 'en' ? 'Agreements' : '软件协议',
|
||||
style: AppTypography.title3.copyWith(
|
||||
color: ext.textPrimary,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -48,17 +53,17 @@ class AgreementListPage extends StatelessWidget {
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
_HeaderCard(ext: ext),
|
||||
_HeaderCard(ext: ext, languageId: languageId),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
_PrivacySection(ext: ext),
|
||||
_PrivacySection(ext: ext, languageId: languageId),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
_ServiceSection(ext: ext),
|
||||
_ServiceSection(ext: ext, languageId: languageId),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
_ContentSection(ext: ext),
|
||||
_ContentSection(ext: ext, languageId: languageId),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
_GuideSection(ext: ext),
|
||||
_GuideSection(ext: ext, languageId: languageId),
|
||||
const SizedBox(height: AppSpacing.xl),
|
||||
_Footer(ext: ext),
|
||||
_Footer(ext: ext, languageId: languageId),
|
||||
const SizedBox(height: AppSpacing.xxl),
|
||||
],
|
||||
),
|
||||
@@ -69,12 +74,14 @@ class AgreementListPage extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _HeaderCard extends StatelessWidget {
|
||||
const _HeaderCard({required this.ext});
|
||||
const _HeaderCard({required this.ext, required this.languageId});
|
||||
|
||||
final AppThemeExtension ext;
|
||||
final String? languageId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isEn = languageId == 'en';
|
||||
return GlassContainer(
|
||||
depth: GlassDepth.elevated,
|
||||
padding: const EdgeInsets.all(AppSpacing.lg),
|
||||
@@ -106,7 +113,7 @@ class _HeaderCard extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'软件协议与政策',
|
||||
isEn ? 'Agreements & Policies' : '软件协议与政策',
|
||||
style: AppTypography.title3.copyWith(
|
||||
color: ext.textPrimary,
|
||||
fontWeight: FontWeight.w700,
|
||||
@@ -114,7 +121,9 @@ class _HeaderCard extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: AppSpacing.xs),
|
||||
Text(
|
||||
'请仔细阅读以下协议与政策,了解您的权利与义务',
|
||||
isEn
|
||||
? 'Please read the following agreements and policies carefully'
|
||||
: '请仔细阅读以下协议与政策,了解您的权利与义务',
|
||||
style: AppTypography.caption1.copyWith(
|
||||
color: ext.textSecondary,
|
||||
),
|
||||
@@ -129,16 +138,19 @@ class _HeaderCard extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _PrivacySection extends StatelessWidget {
|
||||
const _PrivacySection({required this.ext});
|
||||
const _PrivacySection({required this.ext, required this.languageId});
|
||||
|
||||
final AppThemeExtension ext;
|
||||
final String? languageId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isEn = languageId == 'en';
|
||||
return _SectionGroup(
|
||||
ext: ext,
|
||||
title: '隐私与安全',
|
||||
title: isEn ? 'Privacy & Security' : '隐私与安全',
|
||||
icon: CupertinoIcons.shield_lefthalf_fill,
|
||||
languageId: languageId,
|
||||
items: const [
|
||||
AgreementType.privacyPolicy,
|
||||
AgreementType.childrenPrivacy,
|
||||
@@ -149,16 +161,19 @@ class _PrivacySection extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _ServiceSection extends StatelessWidget {
|
||||
const _ServiceSection({required this.ext});
|
||||
const _ServiceSection({required this.ext, required this.languageId});
|
||||
|
||||
final AppThemeExtension ext;
|
||||
final String? languageId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isEn = languageId == 'en';
|
||||
return _SectionGroup(
|
||||
ext: ext,
|
||||
title: '服务条款',
|
||||
title: isEn ? 'Service Terms' : '服务条款',
|
||||
icon: CupertinoIcons.doc_text_fill,
|
||||
languageId: languageId,
|
||||
items: const [
|
||||
AgreementType.userServiceAgreement,
|
||||
AgreementType.accountAgreement,
|
||||
@@ -169,32 +184,38 @@ class _ServiceSection extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _ContentSection extends StatelessWidget {
|
||||
const _ContentSection({required this.ext});
|
||||
const _ContentSection({required this.ext, required this.languageId});
|
||||
|
||||
final AppThemeExtension ext;
|
||||
final String? languageId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isEn = languageId == 'en';
|
||||
return _SectionGroup(
|
||||
ext: ext,
|
||||
title: '内容与版权',
|
||||
title: isEn ? 'Content & Copyright' : '内容与版权',
|
||||
icon: CupertinoIcons.doc_on_doc_fill,
|
||||
languageId: languageId,
|
||||
items: const [AgreementType.disclaimer],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _GuideSection extends StatelessWidget {
|
||||
const _GuideSection({required this.ext});
|
||||
const _GuideSection({required this.ext, required this.languageId});
|
||||
|
||||
final AppThemeExtension ext;
|
||||
final String? languageId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isEn = languageId == 'en';
|
||||
return _SectionGroup(
|
||||
ext: ext,
|
||||
title: '了解闲言',
|
||||
title: isEn ? 'About Xianyan' : '了解闲言',
|
||||
icon: CupertinoIcons.info_circle_fill,
|
||||
languageId: languageId,
|
||||
items: const [
|
||||
AgreementType.appIntroduction,
|
||||
AgreementType.beginnerGuide,
|
||||
@@ -209,12 +230,14 @@ class _SectionGroup extends StatelessWidget {
|
||||
required this.ext,
|
||||
required this.title,
|
||||
required this.icon,
|
||||
required this.languageId,
|
||||
required this.items,
|
||||
});
|
||||
|
||||
final AppThemeExtension ext;
|
||||
final String title;
|
||||
final IconData icon;
|
||||
final String? languageId;
|
||||
final List<AgreementType> items;
|
||||
|
||||
@override
|
||||
@@ -258,17 +281,22 @@ class _SectionGroup extends StatelessWidget {
|
||||
if (i > 0) {
|
||||
widgets.add(_SectionDivider(ext: ext));
|
||||
}
|
||||
widgets.add(_AgreementTile(ext: ext, type: items[i]));
|
||||
widgets.add(_AgreementTile(ext: ext, type: items[i], languageId: languageId));
|
||||
}
|
||||
return widgets;
|
||||
}
|
||||
}
|
||||
|
||||
class _AgreementTile extends StatelessWidget {
|
||||
const _AgreementTile({required this.ext, required this.type});
|
||||
const _AgreementTile({
|
||||
required this.ext,
|
||||
required this.type,
|
||||
required this.languageId,
|
||||
});
|
||||
|
||||
final AppThemeExtension ext;
|
||||
final AgreementType type;
|
||||
final String? languageId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -297,7 +325,7 @@ class _AgreementTile extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
type.title,
|
||||
type.titleFor(languageId),
|
||||
style: AppTypography.body.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: ext.textPrimary,
|
||||
@@ -305,7 +333,7 @@ class _AgreementTile extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
AgreementData.getSubtitle(type),
|
||||
AgreementData.getSubtitle(type, languageId: languageId),
|
||||
style: AppTypography.caption1.copyWith(
|
||||
color: ext.textSecondary,
|
||||
),
|
||||
@@ -342,9 +370,12 @@ class _SectionDivider extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _Footer extends StatelessWidget {
|
||||
const _Footer({required this.ext});
|
||||
const _Footer({required this.ext, required this.languageId});
|
||||
|
||||
final AppThemeExtension ext;
|
||||
final String? languageId;
|
||||
|
||||
bool get _isEn => languageId == 'en';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -374,7 +405,7 @@ class _Footer extends StatelessWidget {
|
||||
Icon(CupertinoIcons.globe, size: 14, color: ext.accent),
|
||||
const SizedBox(width: AppSpacing.xs),
|
||||
Text(
|
||||
'在线版协议',
|
||||
_isEn ? 'Online Version' : '在线版协议',
|
||||
style: AppTypography.caption1.copyWith(
|
||||
color: ext.accent,
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -393,13 +424,17 @@ class _Footer extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
Text(
|
||||
'使用闲言APP即表示您同意上述协议与政策',
|
||||
_isEn
|
||||
? 'By using Xianyan APP, you agree to the above agreements and policies'
|
||||
: '使用闲言APP即表示您同意上述协议与政策',
|
||||
style: AppTypography.caption1.copyWith(color: ext.textHint),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: AppSpacing.xs),
|
||||
Text(
|
||||
'如有疑问请联系 21981550@qq.com',
|
||||
_isEn
|
||||
? 'If you have questions, please contact 21981550@qq.com'
|
||||
: '如有疑问请联系 21981550@qq.com',
|
||||
style: AppTypography.caption1.copyWith(color: ext.textHint),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
|
||||
@@ -3,38 +3,47 @@
|
||||
/// 创建时间: 2026-05-19
|
||||
/// 更新时间: 2026-05-19
|
||||
/// 作用: 通用协议/政策内容展示页面,接收AgreementType参数
|
||||
/// 上次更新: 支持重要字段高亮、动态主题与多语言预留
|
||||
/// 上次更新: 支持多语言(中文+英文),根据当前语言显示对应协议内容
|
||||
/// ============================================================
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart' show Divider;
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../../core/theme/app_theme.dart';
|
||||
import '../../../core/theme/app_spacing.dart';
|
||||
import '../../../core/theme/app_typography.dart';
|
||||
import '../../../core/theme/app_radius.dart';
|
||||
import '../../../l10n/app_locale.dart';
|
||||
import '../../../shared/widgets/glass_container.dart';
|
||||
import '../data/agreement_types.dart';
|
||||
import '../data/agreement_data.dart';
|
||||
|
||||
class AgreementPage extends StatelessWidget {
|
||||
class AgreementPage extends ConsumerWidget {
|
||||
const AgreementPage({super.key, required this.type});
|
||||
|
||||
final AgreementType type;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final ext = AppTheme.ext(context);
|
||||
final content = AgreementData.getContent(type);
|
||||
final updateDate = AgreementData.getUpdateDate(type);
|
||||
final languageId =
|
||||
ref.watch(appLocaleProvider).toLanguageTag().startsWith('en')
|
||||
? 'en'
|
||||
: null;
|
||||
final content = AgreementData.getContent(type, languageId: languageId);
|
||||
final updateDate = AgreementData.getUpdateDate(
|
||||
type,
|
||||
languageId: languageId,
|
||||
);
|
||||
final sections = _parseSections(content);
|
||||
|
||||
return CupertinoPageScaffold(
|
||||
backgroundColor: ext.bgPrimary,
|
||||
navigationBar: CupertinoNavigationBar(
|
||||
middle: Text(
|
||||
type.title,
|
||||
type.titleFor(languageId),
|
||||
style: AppTypography.title3.copyWith(
|
||||
color: ext.textPrimary,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -48,11 +57,16 @@ class AgreementPage extends StatelessWidget {
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: const EdgeInsets.all(AppSpacing.md),
|
||||
children: [
|
||||
_AgreementHeader(ext: ext, type: type),
|
||||
_AgreementHeader(ext: ext, type: type, languageId: languageId),
|
||||
const SizedBox(height: AppSpacing.lg),
|
||||
...sections.map((s) => _AgreementSection(ext: ext, section: s)),
|
||||
const SizedBox(height: AppSpacing.xl),
|
||||
_AgreementFooter(ext: ext, updateDate: updateDate, type: type),
|
||||
_AgreementFooter(
|
||||
ext: ext,
|
||||
updateDate: updateDate,
|
||||
type: type,
|
||||
languageId: languageId,
|
||||
),
|
||||
const SizedBox(height: AppSpacing.xxl),
|
||||
],
|
||||
),
|
||||
@@ -98,11 +112,16 @@ class AgreementPage extends StatelessWidget {
|
||||
|
||||
bool _isHeading(String line) {
|
||||
final headingPatterns = [
|
||||
RegExp(r'^[一二三四五六七八九十]+、'),
|
||||
RegExp(r'^[一二三四五六七八九十零]+、'),
|
||||
RegExp(r'^\d+\.\d+'),
|
||||
RegExp(r'^闲言APP'),
|
||||
RegExp(r'^Xianyan APP'),
|
||||
RegExp(r'^更新日期'),
|
||||
RegExp(r'^Updated'),
|
||||
RegExp(r'^生效日期'),
|
||||
RegExp(r'^Effective'),
|
||||
RegExp(r'^Zero\.'),
|
||||
RegExp(r'^[IVX]+\.\s'),
|
||||
];
|
||||
for (final pattern in headingPatterns) {
|
||||
if (pattern.hasMatch(line)) return true;
|
||||
@@ -118,10 +137,15 @@ class _Section {
|
||||
}
|
||||
|
||||
class _AgreementHeader extends StatelessWidget {
|
||||
const _AgreementHeader({required this.ext, required this.type});
|
||||
const _AgreementHeader({
|
||||
required this.ext,
|
||||
required this.type,
|
||||
required this.languageId,
|
||||
});
|
||||
|
||||
final AppThemeExtension ext;
|
||||
final AgreementType type;
|
||||
final String? languageId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -145,7 +169,7 @@ class _AgreementHeader extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
type.title,
|
||||
type.titleFor(languageId),
|
||||
style: AppTypography.title3.copyWith(
|
||||
color: ext.textPrimary,
|
||||
fontWeight: FontWeight.w700,
|
||||
@@ -153,7 +177,7 @@ class _AgreementHeader extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: AppSpacing.xs),
|
||||
Text(
|
||||
AgreementData.getSubtitle(type),
|
||||
AgreementData.getSubtitle(type, languageId: languageId),
|
||||
style: AppTypography.caption1.copyWith(
|
||||
color: ext.textSecondary,
|
||||
),
|
||||
@@ -213,7 +237,7 @@ class _RichBody extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: lines.where((l) => l.trim().isNotEmpty).map((line) {
|
||||
final trimmed = line.trim();
|
||||
final isBullet = trimmed.startsWith('•');
|
||||
final isBullet = trimmed.startsWith('•') || trimmed.startsWith('-');
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: isBullet ? AppSpacing.sm : 0,
|
||||
@@ -309,11 +333,15 @@ class _AgreementFooter extends StatelessWidget {
|
||||
required this.ext,
|
||||
required this.updateDate,
|
||||
required this.type,
|
||||
required this.languageId,
|
||||
});
|
||||
|
||||
final AppThemeExtension ext;
|
||||
final String updateDate;
|
||||
final AgreementType type;
|
||||
final String? languageId;
|
||||
|
||||
bool get _isEn => languageId == 'en';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -327,7 +355,7 @@ class _AgreementFooter extends StatelessWidget {
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
GestureDetector(
|
||||
onTap: () => launchUrl(
|
||||
Uri.parse(type.webUrl),
|
||||
Uri.parse(type.webUrlFor(languageId)),
|
||||
mode: LaunchMode.externalApplication,
|
||||
),
|
||||
child: Container(
|
||||
@@ -349,7 +377,7 @@ class _AgreementFooter extends StatelessWidget {
|
||||
Icon(CupertinoIcons.globe, size: 14, color: ext.accent),
|
||||
const SizedBox(width: AppSpacing.xs),
|
||||
Text(
|
||||
'在线版',
|
||||
_isEn ? 'Online Version' : '在线版',
|
||||
style: AppTypography.caption1.copyWith(
|
||||
color: ext.accent,
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -358,7 +386,7 @@ class _AgreementFooter extends StatelessWidget {
|
||||
const SizedBox(width: AppSpacing.xs),
|
||||
Flexible(
|
||||
child: Text(
|
||||
type.webUrl,
|
||||
type.webUrlFor(languageId),
|
||||
style: AppTypography.caption2.copyWith(
|
||||
color: ext.accent.withValues(alpha: 0.7),
|
||||
),
|
||||
@@ -371,13 +399,15 @@ class _AgreementFooter extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
Text(
|
||||
'最后更新日期:$updateDate',
|
||||
_isEn ? 'Last updated: $updateDate' : '最后更新日期:$updateDate',
|
||||
style: AppTypography.caption1.copyWith(color: ext.textHint),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: AppSpacing.xs),
|
||||
Text(
|
||||
'弥勒市朋普镇微风暴网络科技工作室',
|
||||
_isEn
|
||||
? 'Mile City Pengpu Town Weifengbao Network Technology Studio'
|
||||
: '弥勒市朋普镇微风暴网络科技工作室',
|
||||
style: AppTypography.caption1.copyWith(color: ext.textHint),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
|
||||
@@ -21,7 +21,7 @@ import '../../models/transfer_device.dart';
|
||||
class BluetoothPairingService {
|
||||
BluetoothPairingService();
|
||||
|
||||
static const _bleChannel = MethodChannel('com.xianyan/ble');
|
||||
static const _bleChannel = MethodChannel('apps.xy.xianyan/ble');
|
||||
|
||||
bool _isScanning = false;
|
||||
bool get isScanning => _isScanning;
|
||||
|
||||
@@ -46,7 +46,7 @@ class HotspotConfig {
|
||||
class HotspotService {
|
||||
HotspotService();
|
||||
|
||||
static const _hotspotChannel = MethodChannel('com.xianyan/hotspot');
|
||||
static const _hotspotChannel = MethodChannel('apps.xy.xianyan/hotspot');
|
||||
|
||||
bool _isHotspotActive = false;
|
||||
bool get isHotspotActive => _isHotspotActive;
|
||||
|
||||
@@ -207,14 +207,6 @@ mixin HomeInteractionMixin on Notifier<HomeState> {
|
||||
final feedId = extractFeedId(id);
|
||||
|
||||
if (feedId > 0) {
|
||||
try {
|
||||
await FeedService.action(
|
||||
action: 'view',
|
||||
feedType: feedType,
|
||||
feedId: feedId,
|
||||
);
|
||||
} catch (_) {}
|
||||
|
||||
try {
|
||||
await FeedService.fetchDetail(type: feedType, id: feedId);
|
||||
} catch (_) {}
|
||||
|
||||
@@ -109,20 +109,19 @@ class FeedService {
|
||||
/// 互动操作
|
||||
///
|
||||
/// 支持: like/unlike/favorite/unfavorite/share/dislike/
|
||||
/// readlater/unreadlater/rating/view
|
||||
/// 需要登录(view操作不强制登录)。
|
||||
/// comment/readlater/unreadlater/rating/block/unblock/
|
||||
/// report/comment_like/comment_unlike/readtime
|
||||
/// 需要登录(view操作由fetchDetail自动处理)。
|
||||
static Future<bool> action({
|
||||
required String action,
|
||||
required String feedType,
|
||||
required int feedId,
|
||||
String? extra,
|
||||
}) async {
|
||||
if (action != 'view') {
|
||||
final loggedIn = await SecureStorage.isLoggedIn;
|
||||
if (!loggedIn) {
|
||||
Log.i('Feed互动本地记录: 未登录, action=$action, 仅本地生效');
|
||||
return true;
|
||||
}
|
||||
final loggedIn = await SecureStorage.isLoggedIn;
|
||||
if (!loggedIn) {
|
||||
Log.i('Feed互动本地记录: 未登录, action=$action, 仅本地生效');
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -435,11 +434,15 @@ class FeedService {
|
||||
'limit': limit,
|
||||
};
|
||||
if (seenIds != null && seenIds.isNotEmpty) {
|
||||
final trimmedIds = seenIds.length > 30 ? seenIds.sublist(0, 30) : seenIds;
|
||||
final trimmedIds = seenIds.length > 30
|
||||
? seenIds.sublist(0, 30)
|
||||
: seenIds;
|
||||
params['seen_ids'] = trimmedIds.join(',');
|
||||
}
|
||||
if (seenHashes != null && seenHashes.isNotEmpty) {
|
||||
final trimmedHashes = seenHashes.length > 30 ? seenHashes.sublist(0, 30) : seenHashes;
|
||||
final trimmedHashes = seenHashes.length > 30
|
||||
? seenHashes.sublist(0, 30)
|
||||
: seenHashes;
|
||||
params['seen_hashes'] = trimmedHashes.join(',');
|
||||
}
|
||||
|
||||
@@ -488,10 +491,7 @@ class FeedService {
|
||||
try {
|
||||
final response = await _api.post<Map<String, dynamic>>(
|
||||
'/api/feed/preferences',
|
||||
data: {
|
||||
'action': 'save',
|
||||
'data': jsonEncode(preferences.toJson()),
|
||||
},
|
||||
data: {'action': 'save', 'data': jsonEncode(preferences.toJson())},
|
||||
);
|
||||
final data = response.data as Map<String, dynamic>;
|
||||
return data['code'] == 1;
|
||||
|
||||
@@ -373,7 +373,7 @@ class _FeedbackSection extends StatelessWidget {
|
||||
Navigator.pop(ctx);
|
||||
launchUrl(
|
||||
Uri.parse(
|
||||
'https://appgallery.huawei.com/app/detail?id=com.xianyan.app',
|
||||
'https://appgallery.huawei.com/app/detail?id=apps.xy.xianyan',
|
||||
),
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/// 创建时间: 2026-05-19
|
||||
/// 更新时间: 2026-05-19
|
||||
/// 作用: 定义所有桌面小部件的类型、元数据和平台标识
|
||||
/// 上次更新: 增加深度链接路由、数据键前缀、主题键
|
||||
/// 上次更新: 新增qualifiedAndroidName属性,修复Android端ClassNotFoundException
|
||||
/// ============================================================
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
@@ -66,6 +66,9 @@ extension WidgetTypeX on WidgetType {
|
||||
WidgetType.checkin => 'CheckinProvider',
|
||||
};
|
||||
|
||||
String get qualifiedAndroidName =>
|
||||
'apps.xy.xianyan.widget.$androidProviderName';
|
||||
|
||||
String get iosWidgetKind => switch (this) {
|
||||
WidgetType.dailySentence => 'DailySentenceWidget',
|
||||
WidgetType.readlater => 'ReadlaterWidget',
|
||||
@@ -141,5 +144,6 @@ extension WidgetTypeX on WidgetType {
|
||||
String get themeKey => '${dataKeyPrefix}_theme';
|
||||
|
||||
static List<WidgetType> get all =>
|
||||
WidgetType.values..sort((a, b) => a.priority.compareTo(b.priority));
|
||||
List<WidgetType>.from(WidgetType.values)
|
||||
..sort((a, b) => a.priority.compareTo(b.priority));
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/// 创建时间: 2026-05-19
|
||||
/// 更新时间: 2026-05-19
|
||||
/// 作用: 管理小部件安装状态、数据推送和交互
|
||||
/// 上次更新: 增加pushThemeToAllWidgets、addWidget支持全类型
|
||||
/// 上次更新: requestPinWidget增加qualifiedAndroidName参数,修复Android类名拼接错误
|
||||
/// ============================================================
|
||||
|
||||
import 'dart:ui';
|
||||
@@ -92,6 +92,7 @@ class WidgetNotifier extends Notifier<WidgetState> {
|
||||
Future<void> requestPinWidget(WidgetType type) async {
|
||||
try {
|
||||
await HomeWidget.requestPinWidget(
|
||||
qualifiedAndroidName: type.qualifiedAndroidName,
|
||||
androidName: type.androidProviderName,
|
||||
ohosName: type.ohosFormName,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user