- 引导页协议多语言支持(languageId传递) - 登录页双书名号修复 + 注册页协议勾选 - 个人中心页面多语言(18个翻译键) - 网络断开提示增加关闭/刷新按钮 - 了解我们:新增秋叶qy开发者 + ayk签名修改 + 贡献者精简 + 微风暴微信搜索 - iOS快捷按钮重复修复(删除Info.plist静态定义) - 测试账号123456警告提示 - 扫码登录自动跳转(HTTP轮询+WebSocket双通道) - 登录页老用户按钮改次要色 - Syncfusion图表崩溃修复(DeferredBuilder+animationDuration:0) - macOS标题栏跟随软件夜间模式 - 平台兼容分发渠道弹窗 - 软件著作权图片+交叉水印 - 桌面小部件平台兼容说明默认收起 - iOS/macOS图标更新+名称确认为闲言 - 12个语言文件补全roleNative+7个分发渠道翻译字段
1513 lines
50 KiB
Dart
1513 lines
50 KiB
Dart
// ============================================================
|
|
// 闲言APP — 翻译守护插件详情页
|
|
// 创建时间: 2026-05-25
|
|
// 更新时间: 2026-05-25
|
|
// 作用: 翻译守护插件配置、使用预览和记录
|
|
// 上次更新: 新增插件健康状态指示器
|
|
// ============================================================
|
|
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:flutter_slidable/flutter_slidable.dart';
|
|
import 'package:syncfusion_flutter_charts/charts.dart';
|
|
|
|
import 'package:xianyan/core/theme/app_theme.dart';
|
|
import 'package:xianyan/core/theme/app_spacing.dart';
|
|
import 'package:xianyan/core/theme/app_radius.dart';
|
|
import 'package:xianyan/core/theme/app_typography.dart';
|
|
import 'package:xianyan/core/storage/database/app_database.dart';
|
|
import 'package:xianyan/features/mine/settings/providers/plugin_provider.dart';
|
|
import 'package:xianyan/features/mine/settings/providers/translate_record_provider.dart';
|
|
import 'package:xianyan/features/discover/models/translate_language.dart';
|
|
import 'package:xianyan/shared/widgets/containers/deferred_builder.dart';
|
|
import 'package:xianyan/shared/widgets/containers/glass_container.dart';
|
|
import 'package:xianyan/shared/widgets/feedback/app_toast.dart';
|
|
import 'package:xianyan/shared/widgets/adaptive/adaptive_back_button.dart';
|
|
import 'package:xianyan/features/mine/settings/services/plugin_health_service.dart';
|
|
|
|
// ============================================================
|
|
// 翻译引擎名称映射
|
|
// ============================================================
|
|
|
|
const Map<String, String> _engineNames = {
|
|
'bing': 'Bing Translator',
|
|
'google': 'Google Translate',
|
|
'mymemory': 'MyMemory API',
|
|
'libre': 'LibreTranslate',
|
|
'appworlds': 'AppWorlds 翻译',
|
|
};
|
|
|
|
const Map<String, String> _engineEmoji = {
|
|
'bing': '🅱️',
|
|
'google': '🔍',
|
|
'mymemory': '💾',
|
|
'libre': '📖',
|
|
'appworlds': '🌐',
|
|
};
|
|
|
|
const List<String> _fallbackChain = [
|
|
'bing',
|
|
'mymemory',
|
|
'appworlds',
|
|
'google',
|
|
'libre',
|
|
];
|
|
|
|
// ============================================================
|
|
// 翻译守护插件详情页
|
|
// ============================================================
|
|
|
|
class TranslatePluginPage extends ConsumerStatefulWidget {
|
|
const TranslatePluginPage({super.key});
|
|
|
|
@override
|
|
ConsumerState<TranslatePluginPage> createState() =>
|
|
_TranslatePluginPageState();
|
|
}
|
|
|
|
class _TranslatePluginPageState extends ConsumerState<TranslatePluginPage> {
|
|
bool _previewTranslating = false;
|
|
bool _previewTranslated = false;
|
|
Map<String, int> _trendData = {};
|
|
List<HealthCheckResult> _healthResults = [];
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
Future.microtask(() {
|
|
_loadTrendData();
|
|
_checkHealth();
|
|
}).catchError((_) {});
|
|
}
|
|
|
|
/// 检查翻译插件健康状态
|
|
Future<void> _checkHealth() async {
|
|
final results = await PluginHealthService.instance.checkTranslateHealth();
|
|
if (mounted) {
|
|
setState(() => _healthResults = results);
|
|
}
|
|
}
|
|
|
|
Future<void> _loadTrendData() async {
|
|
final data = await ref
|
|
.read(translateRecordListProvider.notifier)
|
|
.getCountByDate();
|
|
if (mounted) {
|
|
setState(() => _trendData = data);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final ext = AppTheme.ext(context);
|
|
final pluginState = ref.watch(pluginProvider);
|
|
final records = ref.watch(translateRecordListProvider);
|
|
|
|
return CupertinoPageScaffold(
|
|
backgroundColor: ext.bgPrimary,
|
|
navigationBar: CupertinoNavigationBar(
|
|
leading: const AdaptiveBackButton(),
|
|
previousPageTitle: '插件',
|
|
middle: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(CupertinoIcons.globe, size: 18, color: ext.accent),
|
|
const SizedBox(width: 6),
|
|
Text(
|
|
'翻译守护',
|
|
style: AppTypography.title3.copyWith(color: ext.textPrimary),
|
|
),
|
|
],
|
|
),
|
|
backgroundColor: ext.bgPrimary.withValues(alpha: 0.85),
|
|
border: null,
|
|
),
|
|
child: SafeArea(
|
|
bottom: false,
|
|
child: CustomScrollView(
|
|
physics: const BouncingScrollPhysics(),
|
|
slivers: [
|
|
SliverToBoxAdapter(
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: AppSpacing.md,
|
|
vertical: AppSpacing.md,
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildHeader(ext),
|
|
const SizedBox(height: AppSpacing.md),
|
|
_buildPreviewBanner(ext),
|
|
const SizedBox(height: AppSpacing.lg),
|
|
_buildEnableToggle(ext, pluginState),
|
|
const SizedBox(height: AppSpacing.md),
|
|
_buildHealthStatus(ext),
|
|
const SizedBox(height: AppSpacing.lg),
|
|
_buildUsagePreviewSection(ext),
|
|
const SizedBox(height: AppSpacing.lg),
|
|
_buildTranslateSettingsSection(ext, pluginState),
|
|
const SizedBox(height: AppSpacing.lg),
|
|
_buildDisplaySettingsSection(ext, pluginState),
|
|
const SizedBox(height: AppSpacing.lg),
|
|
_buildRecordsSection(ext, pluginState, records),
|
|
const SizedBox(height: AppSpacing.lg),
|
|
_buildScenariosSection(ext),
|
|
const SizedBox(height: AppSpacing.lg),
|
|
_buildFeedbackSection(ext),
|
|
const SizedBox(height: AppSpacing.xl),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// ============================================================
|
|
// 头部 — 图标 + 名称 + 版本
|
|
// ============================================================
|
|
|
|
Widget _buildHeader(AppThemeExtension ext) {
|
|
return Center(
|
|
child: Column(
|
|
children: [
|
|
Container(
|
|
width: 72,
|
|
height: 72,
|
|
decoration: BoxDecoration(
|
|
gradient: const LinearGradient(
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
colors: [Color(0xFF6C63FF), Color(0xFF3B82F6)],
|
|
),
|
|
borderRadius: AppRadius.xlBorder,
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: const Color(0xFF6C63FF).withValues(alpha: 0.35),
|
|
blurRadius: 16,
|
|
offset: const Offset(0, 6),
|
|
),
|
|
],
|
|
),
|
|
child: const Center(
|
|
child: Icon(
|
|
CupertinoIcons.globe,
|
|
size: 36,
|
|
color: CupertinoColors.white,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: AppSpacing.md),
|
|
Text(
|
|
'翻译守护',
|
|
style: AppTypography.title2.copyWith(
|
|
color: ext.textPrimary,
|
|
fontWeight: FontWeight.w700,
|
|
),
|
|
),
|
|
const SizedBox(height: AppSpacing.xs),
|
|
Text(
|
|
'v1.0.0 · 翻译增强插件',
|
|
style: AppTypography.footnote.copyWith(color: ext.textHint),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// ============================================================
|
|
// 预览横幅 — 渐变背景 + 标语
|
|
// ============================================================
|
|
|
|
Widget _buildPreviewBanner(AppThemeExtension ext) {
|
|
return Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: AppSpacing.lg,
|
|
vertical: AppSpacing.lg,
|
|
),
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
colors: [
|
|
const Color(0xFF6C63FF).withValues(alpha: 0.15),
|
|
const Color(0xFF3B82F6).withValues(alpha: 0.15),
|
|
const Color(0xFF06B6D4).withValues(alpha: 0.10),
|
|
],
|
|
),
|
|
borderRadius: AppRadius.xlBorder,
|
|
border: Border.all(
|
|
color: const Color(0xFF6C63FF).withValues(alpha: 0.15),
|
|
),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Text(
|
|
'🌍 一键翻译,跨越语言',
|
|
style: AppTypography.headline.copyWith(
|
|
color: ext.textPrimary,
|
|
fontWeight: FontWeight.w700,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const SizedBox(height: AppSpacing.sm),
|
|
Text(
|
|
'支持30+语言 · 自动检测 · 多引擎降级',
|
|
style: AppTypography.subhead.copyWith(color: ext.textSecondary),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// ============================================================
|
|
// 启用开关行
|
|
// ============================================================
|
|
|
|
Widget _buildEnableToggle(AppThemeExtension ext, PluginState pluginState) {
|
|
return GlassContainer(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: AppSpacing.md,
|
|
vertical: AppSpacing.md,
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 36,
|
|
height: 36,
|
|
decoration: BoxDecoration(
|
|
color:
|
|
(pluginState.translateEnabled
|
|
? ext.successColor
|
|
: ext.textHint)
|
|
.withValues(alpha: 0.12),
|
|
borderRadius: AppRadius.mdBorder,
|
|
),
|
|
child: Center(
|
|
child: Icon(
|
|
pluginState.translateEnabled
|
|
? CupertinoIcons.checkmark_seal_fill
|
|
: CupertinoIcons.checkmark_seal,
|
|
size: 18,
|
|
color: pluginState.translateEnabled
|
|
? ext.successColor
|
|
: ext.textHint,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: AppSpacing.md),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'启用翻译守护',
|
|
style: AppTypography.subhead.copyWith(
|
|
color: ext.textPrimary,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
pluginState.translateEnabled
|
|
? '翻译守护已开启,将自动为内容提供翻译'
|
|
: '开启后可在卡片和列表中快速翻译',
|
|
style: AppTypography.caption1.copyWith(color: ext.textHint),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
CupertinoSwitch(
|
|
value: pluginState.translateEnabled,
|
|
onChanged: (v) {
|
|
ref.read(pluginProvider.notifier).setTranslateEnabled(v);
|
|
},
|
|
activeTrackColor: ext.successColor,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// ============================================================
|
|
// 健康状态指示器
|
|
// ============================================================
|
|
|
|
/// 插件健康状态指示器
|
|
Widget _buildHealthStatus(AppThemeExtension ext) {
|
|
if (_healthResults.isEmpty) return const SizedBox.shrink();
|
|
final overall = PluginHealthService.instance.getOverallStatus(
|
|
_healthResults,
|
|
);
|
|
final icon = PluginHealthService.instance.getStatusIcon(overall);
|
|
final label = PluginHealthService.instance.getStatusLabel(overall);
|
|
|
|
return Container(
|
|
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: AppSpacing.md,
|
|
vertical: AppSpacing.sm,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: overall == HealthStatus.healthy
|
|
? ext.successColor.withValues(alpha: 0.08)
|
|
: ext.destructiveColor.withValues(alpha: 0.08),
|
|
borderRadius: AppRadius.mdBorder,
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Text(icon, style: const TextStyle(fontSize: 14)),
|
|
const SizedBox(width: AppSpacing.xs),
|
|
Text(
|
|
'插件状态: $label',
|
|
style: AppTypography.footnote.copyWith(
|
|
color: overall == HealthStatus.healthy
|
|
? ext.successColor
|
|
: ext.destructiveColor,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
const Spacer(),
|
|
GestureDetector(
|
|
onTap: _checkHealth,
|
|
child: Icon(CupertinoIcons.refresh, size: 16, color: ext.textHint),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// ============================================================
|
|
// 使用预览区 — 模拟翻译卡片
|
|
// ============================================================
|
|
|
|
Widget _buildUsagePreviewSection(AppThemeExtension ext) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildSectionHeader(ext, '👁 使用预览'),
|
|
const SizedBox(height: AppSpacing.sm),
|
|
GlassContainer(
|
|
padding: EdgeInsets.zero,
|
|
child: Column(
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.all(AppSpacing.md),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Container(
|
|
width: 28,
|
|
height: 28,
|
|
decoration: BoxDecoration(
|
|
color: ext.accent.withValues(alpha: 0.12),
|
|
borderRadius: AppRadius.smBorder,
|
|
),
|
|
child: const Center(
|
|
child: Text('🃏', style: TextStyle(fontSize: 14)),
|
|
),
|
|
),
|
|
const SizedBox(width: AppSpacing.sm),
|
|
Text(
|
|
'每日推荐卡片',
|
|
style: AppTypography.caption1.copyWith(
|
|
color: ext.textHint,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: AppSpacing.md),
|
|
Text(
|
|
'The only way to do great work is to love what you do.',
|
|
style: AppTypography.body.copyWith(
|
|
color: ext.textPrimary,
|
|
height: 1.5,
|
|
),
|
|
),
|
|
const SizedBox(height: AppSpacing.md),
|
|
if (_previewTranslated)
|
|
_buildPreviewTranslationResult(ext)
|
|
else
|
|
_buildPreviewTranslateButton(ext),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildPreviewTranslateButton(AppThemeExtension ext) {
|
|
return GestureDetector(
|
|
onTap: _simulatePreviewTranslate,
|
|
child: Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: AppSpacing.md,
|
|
vertical: AppSpacing.sm + 2,
|
|
),
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [
|
|
const Color(0xFF6C63FF).withValues(alpha: 0.12),
|
|
const Color(0xFF3B82F6).withValues(alpha: 0.12),
|
|
],
|
|
),
|
|
borderRadius: AppRadius.mdBorder,
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
_previewTranslating
|
|
? CupertinoActivityIndicator(color: ext.accent, radius: 8)
|
|
: Icon(CupertinoIcons.globe, size: 16, color: ext.accent),
|
|
const SizedBox(width: AppSpacing.xs),
|
|
Text(
|
|
_previewTranslating ? '翻译中…' : '点击翻译',
|
|
style: AppTypography.subhead.copyWith(
|
|
color: ext.accent,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildPreviewTranslationResult(AppThemeExtension ext) {
|
|
return Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(AppSpacing.md),
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [
|
|
const Color(0xFF6C63FF).withValues(alpha: 0.06),
|
|
const Color(0xFF3B82F6).withValues(alpha: 0.06),
|
|
],
|
|
),
|
|
borderRadius: AppRadius.mdBorder,
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(
|
|
CupertinoIcons.checkmark_circle_fill,
|
|
size: 14,
|
|
color: ext.successColor,
|
|
),
|
|
const SizedBox(width: AppSpacing.xs),
|
|
Text(
|
|
'翻译结果 · Bing',
|
|
style: AppTypography.caption2.copyWith(
|
|
color: ext.successColor,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: AppSpacing.sm),
|
|
Text(
|
|
'成就伟大工作的唯一途径,就是热爱你所做的事。',
|
|
style: AppTypography.body.copyWith(
|
|
color: ext.textPrimary,
|
|
fontWeight: FontWeight.w500,
|
|
height: 1.5,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> _simulatePreviewTranslate() async {
|
|
setState(() => _previewTranslating = true);
|
|
await Future<void>.delayed(const Duration(milliseconds: 1200));
|
|
if (mounted) {
|
|
setState(() {
|
|
_previewTranslating = false;
|
|
_previewTranslated = true;
|
|
});
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// 翻译设置区
|
|
// ============================================================
|
|
|
|
Widget _buildTranslateSettingsSection(
|
|
AppThemeExtension ext,
|
|
PluginState pluginState,
|
|
) {
|
|
final currentEngineName =
|
|
_engineNames[pluginState.translateEngineId] ?? 'Bing Translator';
|
|
final currentEngineEmoji =
|
|
_engineEmoji[pluginState.translateEngineId] ?? '🌐';
|
|
final targetLang = TranslateLanguage.findByCode(
|
|
pluginState.translateTargetLang,
|
|
);
|
|
final targetLangDisplay = targetLang != null
|
|
? '${targetLang.flag} ${targetLang.name}'
|
|
: pluginState.translateTargetLang;
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildSectionHeader(ext, '⚙️ 翻译设置'),
|
|
const SizedBox(height: AppSpacing.sm),
|
|
GlassContainer(
|
|
padding: EdgeInsets.zero,
|
|
child: Column(
|
|
children: [
|
|
_buildNavigationRow(
|
|
ext,
|
|
icon: CupertinoIcons.gear_solid,
|
|
iconBg: ext.accent.withValues(alpha: 0.12),
|
|
iconColor: ext.accent,
|
|
emoji: currentEngineEmoji,
|
|
title: '翻译引擎',
|
|
trailing: Text(
|
|
currentEngineName,
|
|
style: AppTypography.caption1.copyWith(color: ext.textHint),
|
|
overflow: TextOverflow.ellipsis,
|
|
maxLines: 1,
|
|
),
|
|
onTap: () => _showEnginePicker(ext, pluginState),
|
|
),
|
|
_buildDivider(ext),
|
|
_buildNavigationRow(
|
|
ext,
|
|
icon: CupertinoIcons.flag_fill,
|
|
iconBg: const Color(0xFF06B6D4).withValues(alpha: 0.12),
|
|
iconColor: const Color(0xFF06B6D4),
|
|
title: '目标语言',
|
|
trailing: Text(
|
|
targetLangDisplay,
|
|
style: AppTypography.caption1.copyWith(color: ext.textHint),
|
|
overflow: TextOverflow.ellipsis,
|
|
maxLines: 1,
|
|
),
|
|
onTap: () => _showLanguagePicker(ext, pluginState),
|
|
),
|
|
_buildDivider(ext),
|
|
_buildToggleRow(
|
|
ext,
|
|
icon: CupertinoIcons.search,
|
|
iconBg: ext.successColor.withValues(alpha: 0.12),
|
|
iconColor: ext.successColor,
|
|
title: '自动检测源语言',
|
|
subtitle: '自动识别输入文本的语言',
|
|
value: pluginState.autoDetectLang,
|
|
onChanged: (v) =>
|
|
ref.read(pluginProvider.notifier).setAutoDetectLang(v),
|
|
),
|
|
_buildDivider(ext),
|
|
_buildNavigationRow(
|
|
ext,
|
|
icon: CupertinoIcons.arrow_2_circlepath,
|
|
iconBg: ext.warningColor.withValues(alpha: 0.12),
|
|
iconColor: ext.warningColor,
|
|
title: '降级策略',
|
|
trailing: _buildFallbackChainDisplay(ext),
|
|
onTap: () => _showFallbackInfo(ext),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildFallbackChainDisplay(AppThemeExtension ext) {
|
|
return Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: _fallbackChain.map((id) {
|
|
final emoji = _engineEmoji[id] ?? '🔗';
|
|
return Padding(
|
|
padding: const EdgeInsets.only(right: 2),
|
|
child: Text(emoji, style: const TextStyle(fontSize: 12)),
|
|
);
|
|
}).toList(),
|
|
);
|
|
}
|
|
|
|
// ============================================================
|
|
// 显示设置区
|
|
// ============================================================
|
|
|
|
Widget _buildDisplaySettingsSection(
|
|
AppThemeExtension ext,
|
|
PluginState pluginState,
|
|
) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildSectionHeader(ext, '🖥 显示设置'),
|
|
const SizedBox(height: AppSpacing.sm),
|
|
GlassContainer(
|
|
padding: EdgeInsets.zero,
|
|
child: Column(
|
|
children: [
|
|
_buildToggleRow(
|
|
ext,
|
|
icon: CupertinoIcons.bolt_fill,
|
|
iconBg: ext.accent.withValues(alpha: 0.12),
|
|
iconColor: ext.accent,
|
|
title: '自动翻译',
|
|
subtitle: '滑动卡片时自动翻译内容',
|
|
value: pluginState.autoTranslate,
|
|
onChanged: (v) =>
|
|
ref.read(pluginProvider.notifier).setAutoTranslate(v),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
// ============================================================
|
|
// 翻译记录区
|
|
// ============================================================
|
|
|
|
Widget _buildRecordsSection(
|
|
AppThemeExtension ext,
|
|
PluginState pluginState,
|
|
List<TranslateRecord> records,
|
|
) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildSectionHeader(ext, '📊 翻译记录'),
|
|
const SizedBox(height: AppSpacing.sm),
|
|
GlassContainer(
|
|
padding: EdgeInsets.zero,
|
|
child: Column(
|
|
children: [
|
|
_buildStatRow(ext, pluginState.translateCount),
|
|
_buildDivider(ext),
|
|
_buildTrendChart(ext),
|
|
_buildDivider(ext),
|
|
_buildRecentRecords(ext, records),
|
|
_buildDivider(ext),
|
|
_buildDestructiveRow(
|
|
ext,
|
|
icon: CupertinoIcons.trash,
|
|
title: '清除翻译记录',
|
|
onTap: () => _showClearConfirm(ext),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildStatRow(AppThemeExtension ext, int count) {
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: AppSpacing.md,
|
|
vertical: AppSpacing.md,
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 32,
|
|
height: 32,
|
|
decoration: BoxDecoration(
|
|
color: ext.accent.withValues(alpha: 0.12),
|
|
borderRadius: AppRadius.mdBorder,
|
|
),
|
|
child: Center(
|
|
child: Icon(
|
|
CupertinoIcons.chart_bar_fill,
|
|
size: 16,
|
|
color: ext.accent,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: AppSpacing.md),
|
|
Expanded(
|
|
child: Text(
|
|
'累计翻译',
|
|
style: AppTypography.subhead.copyWith(
|
|
color: ext.textPrimary,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: AppSpacing.sm,
|
|
vertical: AppSpacing.xs,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: ext.accent.withValues(alpha: 0.1),
|
|
borderRadius: AppRadius.smBorder,
|
|
),
|
|
child: Text(
|
|
'$count 次',
|
|
style: AppTypography.callout.copyWith(
|
|
color: ext.accent,
|
|
fontWeight: FontWeight.w700,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildTrendChart(AppThemeExtension ext) {
|
|
final now = DateTime.now();
|
|
final rawValues = List.generate(7, (i) {
|
|
final date = now.subtract(Duration(days: 6 - i));
|
|
final key =
|
|
'${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
|
return _trendData[key]?.toDouble() ?? 0.0;
|
|
});
|
|
final dayLabels = List.generate(7, (i) {
|
|
final date = now.subtract(Duration(days: 6 - i));
|
|
const weekDays = ['一', '二', '三', '四', '五', '六', '日'];
|
|
return weekDays[date.weekday - 1];
|
|
});
|
|
final maxVal = rawValues.isEmpty
|
|
? 10.0
|
|
: (rawValues.reduce((a, b) => a > b ? a : b) * 1.2).clamp(
|
|
1.0,
|
|
double.infinity,
|
|
);
|
|
|
|
final barData = List.generate(7, (i) {
|
|
final date = now.subtract(Duration(days: 6 - i));
|
|
final key =
|
|
'${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
|
return _TranslateDayData(
|
|
dayLabels[i],
|
|
_trendData[key]?.toDouble() ?? 0.0,
|
|
);
|
|
});
|
|
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: AppSpacing.md,
|
|
vertical: AppSpacing.sm,
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.only(bottom: AppSpacing.sm),
|
|
child: Text(
|
|
'最近7天翻译趋势',
|
|
style: AppTypography.caption1.copyWith(
|
|
color: ext.textHint,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
SizedBox(
|
|
height: 120,
|
|
child: DeferredBuilder(builder: (context) => SfCartesianChart(
|
|
plotAreaBorderWidth: 0,
|
|
primaryXAxis: CategoryAxis(
|
|
majorGridLines: const MajorGridLines(width: 0),
|
|
labelStyle: AppTypography.caption2.copyWith(
|
|
color: ext.textHint,
|
|
),
|
|
),
|
|
primaryYAxis: NumericAxis(
|
|
maximum: maxVal,
|
|
isVisible: false,
|
|
majorGridLines: const MajorGridLines(width: 0),
|
|
),
|
|
tooltipBehavior: TooltipBehavior(),
|
|
series: <CartesianSeries<_TranslateDayData, String>>[
|
|
ColumnSeries<_TranslateDayData, String>(
|
|
dataSource: barData,
|
|
xValueMapper: (_TranslateDayData d, _) => d.day,
|
|
yValueMapper: (_TranslateDayData d, _) => d.count,
|
|
color: ext.accent,
|
|
width: 0.6,
|
|
animationDuration: 0,
|
|
borderRadius: const BorderRadius.only(
|
|
topLeft: Radius.circular(AppRadius.sm),
|
|
topRight: Radius.circular(AppRadius.sm),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
)),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildRecentRecords(
|
|
AppThemeExtension ext,
|
|
List<TranslateRecord> records,
|
|
) {
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: AppSpacing.md,
|
|
vertical: AppSpacing.sm,
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.only(bottom: AppSpacing.sm),
|
|
child: Text(
|
|
'最近翻译',
|
|
style: AppTypography.caption1.copyWith(
|
|
color: ext.textHint,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
if (records.isEmpty)
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: AppSpacing.md),
|
|
child: Center(
|
|
child: Text(
|
|
'暂无翻译记录',
|
|
style: AppTypography.caption1.copyWith(color: ext.textHint),
|
|
),
|
|
),
|
|
)
|
|
else
|
|
...records.take(10).map((record) => _buildRecordItem(ext, record)),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildRecordItem(AppThemeExtension ext, TranslateRecord record) {
|
|
final engineEmoji = _engineEmoji[record.engineId] ?? '🌐';
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: AppSpacing.sm),
|
|
child: Slidable(
|
|
endActionPane: ActionPane(
|
|
motion: const ScrollMotion(),
|
|
extentRatio: 0.25,
|
|
children: [
|
|
SlidableAction(
|
|
onPressed: (_) {
|
|
ref
|
|
.read(translateRecordListProvider.notifier)
|
|
.deleteRecord(record.id);
|
|
AppToast.showSuccess('已删除记录');
|
|
},
|
|
backgroundColor: ext.destructiveColor,
|
|
foregroundColor: CupertinoColors.white,
|
|
icon: CupertinoIcons.delete,
|
|
label: '删除',
|
|
borderRadius: AppRadius.mdBorder,
|
|
),
|
|
],
|
|
),
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: AppSpacing.sm,
|
|
vertical: AppSpacing.sm,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: ext.bgSecondary,
|
|
borderRadius: AppRadius.mdBorder,
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Text(engineEmoji, style: const TextStyle(fontSize: 14)),
|
|
const SizedBox(width: AppSpacing.sm),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
record.sourceText,
|
|
style: AppTypography.caption1.copyWith(
|
|
color: ext.textSecondary,
|
|
),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
record.translatedText,
|
|
style: AppTypography.caption1.copyWith(
|
|
color: ext.textPrimary,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(width: AppSpacing.xs),
|
|
Text(
|
|
_formatRelativeTime(record.createdAt),
|
|
style: AppTypography.caption2.copyWith(color: ext.textHint),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// ============================================================
|
|
// 使用场景区
|
|
// ============================================================
|
|
|
|
Widget _buildScenariosSection(AppThemeExtension ext) {
|
|
final scenarios = [
|
|
{'icon': '🃏', 'label': '每日推荐卡片', 'enabled': true},
|
|
{'icon': '📖', 'label': '句子广场列表', 'enabled': true},
|
|
{'icon': '📝', 'label': '笔记编辑', 'enabled': false},
|
|
{'icon': '🔍', 'label': '搜索结果', 'enabled': false},
|
|
];
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildSectionHeader(ext, '🎯 使用场景'),
|
|
const SizedBox(height: AppSpacing.sm),
|
|
Wrap(
|
|
spacing: AppSpacing.sm,
|
|
runSpacing: AppSpacing.sm,
|
|
children: scenarios.map((s) {
|
|
final isEnabled = s['enabled'] as bool;
|
|
final icon = s['icon'] as String;
|
|
final label = s['label'] as String;
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: AppSpacing.md,
|
|
vertical: AppSpacing.sm,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: isEnabled
|
|
? ext.accent.withValues(alpha: 0.1)
|
|
: ext.bgSecondary,
|
|
borderRadius: AppRadius.pillBorder,
|
|
border: isEnabled
|
|
? Border.all(color: ext.accent.withValues(alpha: 0.25))
|
|
: null,
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(icon, style: const TextStyle(fontSize: 14)),
|
|
const SizedBox(width: AppSpacing.xs),
|
|
Text(
|
|
'$label${isEnabled ? '' : ' (即将支持)'}',
|
|
style: AppTypography.caption1.copyWith(
|
|
color: isEnabled ? ext.accent : ext.textHint,
|
|
fontWeight: isEnabled
|
|
? FontWeight.w600
|
|
: FontWeight.normal,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
// ============================================================
|
|
// 反馈与建议区
|
|
// ============================================================
|
|
|
|
Widget _buildFeedbackSection(AppThemeExtension ext) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildSectionHeader(ext, '💬 反馈与建议'),
|
|
const SizedBox(height: AppSpacing.sm),
|
|
GlassContainer(
|
|
padding: EdgeInsets.zero,
|
|
child: _buildNavigationRow(
|
|
ext,
|
|
icon: CupertinoIcons.chat_bubble_2_fill,
|
|
iconBg: ext.accent.withValues(alpha: 0.12),
|
|
iconColor: ext.accent,
|
|
title: '提交意见',
|
|
onTap: () {
|
|
showCupertinoDialog<void>(
|
|
context: context,
|
|
builder: (ctx) => CupertinoAlertDialog(
|
|
title: const Text('提示'),
|
|
content: const Text('当前时段不支持提交'),
|
|
actions: [
|
|
CupertinoDialogAction(
|
|
isDefaultAction: true,
|
|
child: const Text('好的'),
|
|
onPressed: () => Navigator.pop(ctx),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
// ============================================================
|
|
// 通用组件 — 区块标题
|
|
// ============================================================
|
|
|
|
Widget _buildSectionHeader(AppThemeExtension ext, String title) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(left: AppSpacing.xs),
|
|
child: Text(
|
|
title,
|
|
style: AppTypography.subhead.copyWith(
|
|
color: ext.textSecondary,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// ============================================================
|
|
// 通用组件 — 导航行
|
|
// ============================================================
|
|
|
|
Widget _buildNavigationRow(
|
|
AppThemeExtension ext, {
|
|
required IconData icon,
|
|
required Color iconBg,
|
|
required Color iconColor,
|
|
String? emoji,
|
|
required String title,
|
|
Widget? trailing,
|
|
VoidCallback? onTap,
|
|
}) {
|
|
return GestureDetector(
|
|
behavior: HitTestBehavior.opaque,
|
|
onTap: onTap,
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: AppSpacing.md,
|
|
vertical: AppSpacing.md,
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 32,
|
|
height: 32,
|
|
decoration: BoxDecoration(
|
|
color: iconBg,
|
|
borderRadius: AppRadius.mdBorder,
|
|
),
|
|
child: Center(
|
|
child: emoji != null
|
|
? Text(emoji, style: const TextStyle(fontSize: 16))
|
|
: Icon(icon, size: 16, color: iconColor),
|
|
),
|
|
),
|
|
const SizedBox(width: AppSpacing.md),
|
|
Expanded(
|
|
child: Text(
|
|
title,
|
|
style: AppTypography.subhead.copyWith(
|
|
color: ext.textPrimary,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
),
|
|
if (trailing != null) trailing,
|
|
const SizedBox(width: AppSpacing.xs),
|
|
Icon(CupertinoIcons.chevron_right, size: 14, color: ext.textHint),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// ============================================================
|
|
// 通用组件 — 开关行
|
|
// ============================================================
|
|
|
|
Widget _buildToggleRow(
|
|
AppThemeExtension ext, {
|
|
required IconData icon,
|
|
required Color iconBg,
|
|
required Color iconColor,
|
|
required String title,
|
|
String? subtitle,
|
|
required bool value,
|
|
required ValueChanged<bool> onChanged,
|
|
}) {
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: AppSpacing.md,
|
|
vertical: AppSpacing.sm + 2,
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 32,
|
|
height: 32,
|
|
decoration: BoxDecoration(
|
|
color: iconBg,
|
|
borderRadius: AppRadius.mdBorder,
|
|
),
|
|
child: Center(child: Icon(icon, size: 16, color: iconColor)),
|
|
),
|
|
const SizedBox(width: AppSpacing.md),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
title,
|
|
style: AppTypography.subhead.copyWith(
|
|
color: ext.textPrimary,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
if (subtitle != null)
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 2),
|
|
child: Text(
|
|
subtitle,
|
|
style: AppTypography.caption1.copyWith(
|
|
color: ext.textHint,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
CupertinoSwitch(
|
|
value: value,
|
|
onChanged: onChanged,
|
|
activeTrackColor: ext.successColor,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// ============================================================
|
|
// 通用组件 — 危险操作行
|
|
// ============================================================
|
|
|
|
Widget _buildDestructiveRow(
|
|
AppThemeExtension ext, {
|
|
required IconData icon,
|
|
required String title,
|
|
VoidCallback? onTap,
|
|
}) {
|
|
return GestureDetector(
|
|
behavior: HitTestBehavior.opaque,
|
|
onTap: onTap,
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: AppSpacing.md,
|
|
vertical: AppSpacing.md,
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 32,
|
|
height: 32,
|
|
decoration: BoxDecoration(
|
|
color: ext.destructiveColor.withValues(alpha: 0.12),
|
|
borderRadius: AppRadius.mdBorder,
|
|
),
|
|
child: Center(
|
|
child: Icon(icon, size: 16, color: ext.destructiveColor),
|
|
),
|
|
),
|
|
const SizedBox(width: AppSpacing.md),
|
|
Expanded(
|
|
child: Text(
|
|
title,
|
|
style: AppTypography.subhead.copyWith(
|
|
color: ext.destructiveColor,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
),
|
|
Icon(CupertinoIcons.chevron_right, size: 14, color: ext.textHint),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// ============================================================
|
|
// 通用组件 — 分割线
|
|
// ============================================================
|
|
|
|
Widget _buildDivider(AppThemeExtension ext) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(left: 60),
|
|
child: Divider(height: 0.5, color: ext.overlaySubtle),
|
|
);
|
|
}
|
|
|
|
// ============================================================
|
|
// 引擎选择弹窗
|
|
// ============================================================
|
|
|
|
void _showEnginePicker(AppThemeExtension ext, PluginState pluginState) {
|
|
showCupertinoModalPopup<void>(
|
|
context: context,
|
|
builder: (ctx) => CupertinoActionSheet(
|
|
title: const Text('选择翻译引擎'),
|
|
actions: _engineNames.entries.map((entry) {
|
|
final isSelected = pluginState.translateEngineId == entry.key;
|
|
return CupertinoActionSheetAction(
|
|
onPressed: () {
|
|
Navigator.pop(ctx);
|
|
ref.read(pluginProvider.notifier).setTranslateEngineId(entry.key);
|
|
AppToast.showSuccess('已切换至 ${entry.value}');
|
|
},
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Text('${_engineEmoji[entry.key] ?? "🔗"} ${entry.value}'),
|
|
if (isSelected) ...[
|
|
const SizedBox(width: 8),
|
|
Icon(
|
|
CupertinoIcons.checkmark_circle_fill,
|
|
size: 16,
|
|
color: ext.successColor,
|
|
),
|
|
],
|
|
],
|
|
),
|
|
);
|
|
}).toList(),
|
|
cancelButton: CupertinoActionSheetAction(
|
|
isDestructiveAction: true,
|
|
child: const Text('取消'),
|
|
onPressed: () => Navigator.pop(ctx),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// ============================================================
|
|
// 目标语言选择弹窗
|
|
// ============================================================
|
|
|
|
void _showLanguagePicker(AppThemeExtension ext, PluginState pluginState) {
|
|
final popularLangs = TranslateLanguage.popular;
|
|
final otherLangs = TranslateLanguage.supported
|
|
.where((l) => !l.isPopular)
|
|
.toList();
|
|
|
|
showCupertinoModalPopup<void>(
|
|
context: context,
|
|
builder: (ctx) => CupertinoActionSheet(
|
|
title: const Text('选择目标语言'),
|
|
actions: [
|
|
CupertinoActionSheetAction(
|
|
child: const Text('🔍 自动检测'),
|
|
onPressed: () {
|
|
Navigator.pop(ctx);
|
|
ref.read(pluginProvider.notifier).setTranslateTargetLang('auto');
|
|
},
|
|
),
|
|
...popularLangs.map((lang) {
|
|
final isSelected = pluginState.translateTargetLang == lang.code;
|
|
return CupertinoActionSheetAction(
|
|
onPressed: () {
|
|
Navigator.pop(ctx);
|
|
ref
|
|
.read(pluginProvider.notifier)
|
|
.setTranslateTargetLang(lang.code);
|
|
AppToast.showSuccess('目标语言: ${lang.flag} ${lang.name}');
|
|
},
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Text('${lang.flag} ${lang.name}'),
|
|
if (isSelected) ...[
|
|
const SizedBox(width: 8),
|
|
Icon(
|
|
CupertinoIcons.checkmark_circle_fill,
|
|
size: 16,
|
|
color: ext.successColor,
|
|
),
|
|
],
|
|
],
|
|
),
|
|
);
|
|
}),
|
|
...otherLangs.map((lang) {
|
|
final isSelected = pluginState.translateTargetLang == lang.code;
|
|
return CupertinoActionSheetAction(
|
|
onPressed: () {
|
|
Navigator.pop(ctx);
|
|
ref
|
|
.read(pluginProvider.notifier)
|
|
.setTranslateTargetLang(lang.code);
|
|
AppToast.showSuccess('目标语言: ${lang.flag} ${lang.name}');
|
|
},
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Text('${lang.flag} ${lang.name}'),
|
|
if (isSelected) ...[
|
|
const SizedBox(width: 8),
|
|
Icon(
|
|
CupertinoIcons.checkmark_circle_fill,
|
|
size: 16,
|
|
color: ext.successColor,
|
|
),
|
|
],
|
|
],
|
|
),
|
|
);
|
|
}),
|
|
],
|
|
cancelButton: CupertinoActionSheetAction(
|
|
isDestructiveAction: true,
|
|
child: const Text('取消'),
|
|
onPressed: () => Navigator.pop(ctx),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// ============================================================
|
|
// 降级策略说明弹窗
|
|
// ============================================================
|
|
|
|
void _showFallbackInfo(AppThemeExtension ext) {
|
|
showCupertinoDialog<void>(
|
|
context: context,
|
|
builder: (ctx) => CupertinoAlertDialog(
|
|
title: const Text('🔄 降级策略'),
|
|
content: Column(
|
|
children: [
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
'当首选翻译引擎不可用时,将按以下顺序自动切换:',
|
|
style: AppTypography.footnote.copyWith(color: ext.textSecondary),
|
|
),
|
|
const SizedBox(height: AppSpacing.md),
|
|
..._fallbackChain.asMap().entries.map((entry) {
|
|
final idx = entry.key;
|
|
final engineId = entry.value;
|
|
final name = _engineNames[engineId] ?? engineId;
|
|
final emoji = _engineEmoji[engineId] ?? '🔗';
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 22,
|
|
height: 22,
|
|
decoration: BoxDecoration(
|
|
color: ext.accent.withValues(alpha: 0.1),
|
|
borderRadius: AppRadius.xsBorder,
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
'${idx + 1}',
|
|
style: AppTypography.caption2.copyWith(
|
|
color: ext.accent,
|
|
fontWeight: FontWeight.w700,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: AppSpacing.sm),
|
|
Text(emoji, style: const TextStyle(fontSize: 16)),
|
|
const SizedBox(width: AppSpacing.xs),
|
|
Text(
|
|
name,
|
|
style: AppTypography.subhead.copyWith(
|
|
color: ext.textPrimary,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}),
|
|
],
|
|
),
|
|
actions: [
|
|
CupertinoDialogAction(
|
|
isDefaultAction: true,
|
|
child: const Text('知道了'),
|
|
onPressed: () => Navigator.pop(ctx),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// ============================================================
|
|
// 清除记录确认弹窗
|
|
// ============================================================
|
|
|
|
void _showClearConfirm(AppThemeExtension ext) {
|
|
showCupertinoDialog<void>(
|
|
context: context,
|
|
builder: (ctx) => CupertinoAlertDialog(
|
|
title: const Text('清除翻译记录'),
|
|
content: const Text('确定要清除所有翻译使用记录吗?此操作不可恢复。'),
|
|
actions: [
|
|
CupertinoDialogAction(
|
|
child: const Text('取消'),
|
|
onPressed: () => Navigator.pop(ctx),
|
|
),
|
|
CupertinoDialogAction(
|
|
isDestructiveAction: true,
|
|
child: const Text('清除'),
|
|
onPressed: () {
|
|
Navigator.pop(ctx);
|
|
ref.read(translateRecordListProvider.notifier).clearAll();
|
|
ref.read(pluginProvider.notifier).clearTranslateRecords();
|
|
_loadTrendData();
|
|
AppToast.showSuccess('翻译记录已清除');
|
|
},
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// ============================================================
|
|
// 相对时间格式化
|
|
// ============================================================
|
|
|
|
String _formatRelativeTime(int createdAtMs) {
|
|
final diff = DateTime.now().millisecondsSinceEpoch - createdAtMs;
|
|
if (diff < 60000) return '刚刚';
|
|
if (diff < 3600000) return '${diff ~/ 60000}分钟前';
|
|
if (diff < 86400000) return '${diff ~/ 3600000}小时前';
|
|
return '${diff ~/ 86400000}天前';
|
|
}
|
|
}
|
|
|
|
class _TranslateDayData {
|
|
final String day;
|
|
final double count;
|
|
const _TranslateDayData(this.day, this.count);
|
|
}
|