- 引导页协议多语言支持(languageId传递) - 登录页双书名号修复 + 注册页协议勾选 - 个人中心页面多语言(18个翻译键) - 网络断开提示增加关闭/刷新按钮 - 了解我们:新增秋叶qy开发者 + ayk签名修改 + 贡献者精简 + 微风暴微信搜索 - iOS快捷按钮重复修复(删除Info.plist静态定义) - 测试账号123456警告提示 - 扫码登录自动跳转(HTTP轮询+WebSocket双通道) - 登录页老用户按钮改次要色 - Syncfusion图表崩溃修复(DeferredBuilder+animationDuration:0) - macOS标题栏跟随软件夜间模式 - 平台兼容分发渠道弹窗 - 软件著作权图片+交叉水印 - 桌面小部件平台兼容说明默认收起 - iOS/macOS图标更新+名称确认为闲言 - 12个语言文件补全roleNative+7个分发渠道翻译字段
1094 lines
34 KiB
Dart
1094 lines
34 KiB
Dart
/// ============================================================
|
|
/// 闲言APP — 了解我们页面(分区组件)
|
|
/// 创建时间: 2026-05-29
|
|
/// 更新时间: 2026-06-02
|
|
/// 作用: 官方网站、开发者、团队、QQ群、贡献者与特别鸣谢等分区
|
|
/// 上次更新: 团队新增原生栈成员,简化贡献者和特别鸣谢列表
|
|
/// ============================================================
|
|
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/services.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 '../../../../core/constants/app_constants.dart';
|
|
import '../../../../shared/widgets/containers/glass_container.dart';
|
|
import '../../../../l10n/translations.dart';
|
|
import 'about_shared_widgets.dart';
|
|
import 'learn_us_widgets.dart';
|
|
|
|
// ============================================================
|
|
// 官方网站区域
|
|
// ============================================================
|
|
|
|
class OfficialSiteSection extends StatelessWidget {
|
|
const OfficialSiteSection({super.key, required this.ext, required this.t});
|
|
|
|
final AppThemeExtension ext;
|
|
final T t;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final links = [
|
|
LinkItemData(
|
|
icon: CupertinoIcons.globe,
|
|
label: t.about.officialSite,
|
|
url: 'https://s2ss.com/',
|
|
),
|
|
LinkItemData(
|
|
icon: CupertinoIcons.doc_text,
|
|
label: t.about.downloadPage,
|
|
url: 'https://s2ss.com/download',
|
|
),
|
|
];
|
|
|
|
return GlassContainer(
|
|
depth: GlassDepth.elevated,
|
|
padding: EdgeInsets.zero,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
AboutSectionTitle(
|
|
icon: CupertinoIcons.globe,
|
|
title: t.about.officialSiteTitle,
|
|
ext: ext,
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(
|
|
AppSpacing.md,
|
|
0,
|
|
AppSpacing.md,
|
|
AppSpacing.sm,
|
|
),
|
|
child: Text(
|
|
t.about.officialSiteDesc,
|
|
style: AppTypography.caption1.copyWith(color: ext.textHint),
|
|
),
|
|
),
|
|
...links.map(
|
|
(link) => Column(
|
|
children: [
|
|
LinkTile(link: link, ext: ext, t: t),
|
|
if (link != links.last) AboutDivider(ext: ext, leftIndent: 36),
|
|
],
|
|
),
|
|
),
|
|
AboutDivider(ext: ext, leftIndent: 36),
|
|
GestureDetector(
|
|
onTap: () => _showProductsSheet(context),
|
|
behavior: HitTestBehavior.opaque,
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: AppSpacing.md,
|
|
vertical: AppSpacing.md,
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 36,
|
|
height: 36,
|
|
decoration: BoxDecoration(
|
|
color: ext.iconTintYellow.withValues(alpha: 0.12),
|
|
borderRadius: AppRadius.smBorder,
|
|
),
|
|
child: Icon(
|
|
CupertinoIcons.square_grid_2x2,
|
|
size: 18,
|
|
color: ext.iconTintYellow,
|
|
),
|
|
),
|
|
const SizedBox(width: AppSpacing.md),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
t.about.otherProducts,
|
|
style: AppTypography.body.copyWith(
|
|
fontWeight: FontWeight.w500,
|
|
color: ext.textPrimary,
|
|
),
|
|
),
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
'情景诗词 · 小妈厨房',
|
|
style: AppTypography.caption2.copyWith(
|
|
color: ext.textHint,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Icon(
|
|
CupertinoIcons.chevron_right,
|
|
size: 14,
|
|
color: ext.textHint,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showProductsSheet(BuildContext context) {
|
|
final products = [
|
|
_ProductData(
|
|
emoji: '🌤️',
|
|
name: t.about.productPoetry,
|
|
icp: '滇ICP备2022000863号-15A',
|
|
desc: t.about.productPoetryDesc,
|
|
url: 'https://poe.vogov.cn/app.html',
|
|
color: ext.iconTintYellow,
|
|
),
|
|
_ProductData(
|
|
emoji: '🍳',
|
|
name: t.about.productKitchen,
|
|
icp: '滇ICP备2022000863号-16A',
|
|
desc: t.about.productKitchenDesc,
|
|
url: 'https://eat.wktyl.com/',
|
|
color: ext.iconTintMint,
|
|
),
|
|
];
|
|
|
|
showCupertinoModalPopup<void>(
|
|
context: context,
|
|
builder: (ctx) => Container(
|
|
constraints: BoxConstraints(
|
|
maxHeight: MediaQuery.of(ctx).size.height * 0.65,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: ext.bgElevated,
|
|
borderRadius: const BorderRadius.vertical(
|
|
top: Radius.circular(AppRadius.xl),
|
|
),
|
|
),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
const SizedBox(height: AppSpacing.sm),
|
|
Container(
|
|
width: 36,
|
|
height: 4,
|
|
decoration: BoxDecoration(
|
|
color: ext.textHint.withValues(alpha: 0.3),
|
|
borderRadius: AppRadius.smBorder,
|
|
),
|
|
),
|
|
const SizedBox(height: AppSpacing.md),
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
|
|
child: Row(
|
|
children: [
|
|
const Text('📦', style: TextStyle(fontSize: 18)),
|
|
const SizedBox(width: AppSpacing.sm),
|
|
Text(
|
|
t.about.otherProducts,
|
|
style: AppTypography.title3.copyWith(
|
|
fontWeight: FontWeight.w700,
|
|
color: ext.textPrimary,
|
|
),
|
|
),
|
|
const Spacer(),
|
|
GestureDetector(
|
|
onTap: () => Navigator.pop(ctx),
|
|
child: Container(
|
|
width: 28,
|
|
height: 28,
|
|
decoration: BoxDecoration(
|
|
color: ext.textHint.withValues(alpha: 0.12),
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Icon(
|
|
CupertinoIcons.xmark,
|
|
size: 14,
|
|
color: ext.textSecondary,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: AppSpacing.sm),
|
|
Flexible(
|
|
child: ListView.separated(
|
|
shrinkWrap: true,
|
|
physics: const BouncingScrollPhysics(),
|
|
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
|
|
itemCount: products.length,
|
|
separatorBuilder: (_, __) =>
|
|
const SizedBox(height: AppSpacing.sm),
|
|
itemBuilder: (_, index) => _ProductCard(
|
|
product: products[index],
|
|
ext: ext,
|
|
onTap: () {
|
|
Navigator.pop(ctx);
|
|
launchAboutUrl(context, products[index].url);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: AppSpacing.xl),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// 开发者区域
|
|
// ============================================================
|
|
|
|
class DeveloperSection extends StatelessWidget {
|
|
const DeveloperSection({super.key, required this.ext, required this.t});
|
|
|
|
final AppThemeExtension ext;
|
|
final T t;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return GlassContainer(
|
|
depth: GlassDepth.elevated,
|
|
padding: EdgeInsets.zero,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
AboutSectionTitle(
|
|
icon: CupertinoIcons.building_2_fill,
|
|
title: t.about.developer,
|
|
ext: ext,
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: AppSpacing.md,
|
|
vertical: AppSpacing.md,
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 44,
|
|
height: 44,
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [
|
|
ext.accentLight,
|
|
ext.accent.withValues(alpha: 0.05),
|
|
],
|
|
),
|
|
borderRadius: AppRadius.mdBorder,
|
|
),
|
|
child: Icon(
|
|
CupertinoIcons.building_2_fill,
|
|
size: 22,
|
|
color: ext.accent,
|
|
),
|
|
),
|
|
const SizedBox(width: AppSpacing.md),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
t.about.companyName,
|
|
style: AppTypography.headline.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
color: ext.textPrimary,
|
|
),
|
|
),
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
t.about.companyDesc,
|
|
style: AppTypography.caption1.copyWith(
|
|
color: ext.textHint,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
AboutDivider(ext: ext, leftIndent: 36),
|
|
EmailTile(ext: ext, t: t),
|
|
AboutDivider(ext: ext, leftIndent: 36),
|
|
WeChatTile(ext: ext, t: t),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// 团队信息区域
|
|
// ============================================================
|
|
|
|
class TeamSection extends StatelessWidget {
|
|
const TeamSection({super.key, required this.ext, required this.t});
|
|
|
|
final AppThemeExtension ext;
|
|
final T t;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final members = [
|
|
TeamMemberData(
|
|
'💻',
|
|
t.about.roleDesign,
|
|
t.about.member1,
|
|
t.about.member1Sig,
|
|
t.about.member1Social,
|
|
),
|
|
TeamMemberData(
|
|
'🎨',
|
|
t.about.roleUIUX,
|
|
t.about.member2,
|
|
t.about.member2Sig,
|
|
t.about.member2Social,
|
|
),
|
|
TeamMemberData(
|
|
'⚙️',
|
|
t.about.roleBackend,
|
|
t.about.member3,
|
|
t.about.member3Sig,
|
|
t.about.member3Social,
|
|
),
|
|
TeamMemberData(
|
|
'🌐',
|
|
t.about.roleSupport,
|
|
t.about.member4,
|
|
t.about.member4Sig,
|
|
'',
|
|
),
|
|
TeamMemberData(
|
|
'📱',
|
|
t.about.roleNative,
|
|
'秋叶qy',
|
|
'春风若有怜花意,可否许我再少年',
|
|
'',
|
|
),
|
|
];
|
|
|
|
return GlassContainer(
|
|
depth: GlassDepth.elevated,
|
|
padding: EdgeInsets.zero,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
AboutSectionTitle(
|
|
icon: CupertinoIcons.person_2_fill,
|
|
title: t.about.teamInfo,
|
|
ext: ext,
|
|
),
|
|
...members.map(
|
|
(m) => Column(
|
|
children: [
|
|
TeamMemberTile(member: m, ext: ext),
|
|
if (m != members.last) AboutDivider(ext: ext, leftIndent: 36),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// QQ群区域
|
|
// ============================================================
|
|
|
|
class QQGroupSection extends StatelessWidget {
|
|
const QQGroupSection({super.key, required this.ext, required this.t});
|
|
|
|
final AppThemeExtension ext;
|
|
final T t;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
const qqGroupUrl =
|
|
'http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Hg7woyP9rwz8ib3nwy38CjSyGQ0IcVQi&authKey=SVsg4NVfh0IEmvUIUQ3y2LMHzhTrMXGnvz0bgP2DBcQ5z71tRbPdu7M2prgXd894&noverify=0&group_code=271129018';
|
|
|
|
final isTelegram = t.about.qqGroupTelegram.isNotEmpty;
|
|
|
|
return GlassContainer(
|
|
depth: GlassDepth.elevated,
|
|
padding: EdgeInsets.zero,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
AboutSectionTitle(
|
|
icon: isTelegram
|
|
? CupertinoIcons.paperplane_fill
|
|
: CupertinoIcons.chat_bubble_2_fill,
|
|
title: isTelegram ? t.about.qqGroupTelegram : t.about.qqGroup,
|
|
ext: ext,
|
|
),
|
|
if (isTelegram)
|
|
_buildTelegramTile(context)
|
|
else
|
|
_buildQQGroupTile(context, qqGroupUrl),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildQQGroupTile(BuildContext context, String qqGroupUrl) {
|
|
return GestureDetector(
|
|
onTap: () => _showQQGroupDialog(context, qqGroupUrl),
|
|
behavior: HitTestBehavior.opaque,
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: AppSpacing.md,
|
|
vertical: AppSpacing.md,
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 36,
|
|
height: 36,
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFF12B7F5).withValues(alpha: 0.12),
|
|
borderRadius: AppRadius.smBorder,
|
|
),
|
|
child: const Center(
|
|
child: Text('🐧', style: TextStyle(fontSize: 18)),
|
|
),
|
|
),
|
|
const SizedBox(width: AppSpacing.md),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'${AppConstants.appName} v${AppVersion.version}',
|
|
style: AppTypography.body.copyWith(
|
|
fontWeight: FontWeight.w500,
|
|
color: ext.textPrimary,
|
|
),
|
|
),
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
t.about.qqGroupDesc,
|
|
style: AppTypography.caption2.copyWith(color: ext.textHint),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
GestureDetector(
|
|
onTap: () {
|
|
Clipboard.setData(const ClipboardData(text: '271129018'));
|
|
showCupertinoDialog<void>(
|
|
context: context,
|
|
builder: (ctx) => CupertinoAlertDialog(
|
|
content: Text(t.about.copied),
|
|
actions: [
|
|
CupertinoDialogAction(
|
|
isDefaultAction: true,
|
|
onPressed: () => Navigator.pop(ctx),
|
|
child: const Text('OK'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
child: Container(
|
|
padding: const EdgeInsets.all(AppSpacing.xs),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFF12B7F5).withValues(alpha: 0.1),
|
|
borderRadius: AppRadius.smBorder,
|
|
),
|
|
child: const Icon(
|
|
CupertinoIcons.doc_on_clipboard,
|
|
size: 14,
|
|
color: Color(0xFF12B7F5),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: AppSpacing.xs),
|
|
const Icon(
|
|
CupertinoIcons.arrow_up_right_square,
|
|
size: 16,
|
|
color: Color(0xFF12B7F5),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildTelegramTile(BuildContext context) {
|
|
final telegramUrl = t.about.qqGroupTelegramDesc;
|
|
return GestureDetector(
|
|
onTap: () => launchAboutUrl(context, telegramUrl),
|
|
behavior: HitTestBehavior.opaque,
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: AppSpacing.md,
|
|
vertical: AppSpacing.md,
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 36,
|
|
height: 36,
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFF0088CC).withValues(alpha: 0.12),
|
|
borderRadius: AppRadius.smBorder,
|
|
),
|
|
child: const Center(
|
|
child: Text('✈️', style: TextStyle(fontSize: 18)),
|
|
),
|
|
),
|
|
const SizedBox(width: AppSpacing.md),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
t.about.qqGroupTelegram,
|
|
style: AppTypography.body.copyWith(
|
|
fontWeight: FontWeight.w500,
|
|
color: ext.textPrimary,
|
|
),
|
|
),
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
telegramUrl,
|
|
style: AppTypography.caption2.copyWith(
|
|
color: const Color(0xFF0088CC),
|
|
),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const Icon(
|
|
CupertinoIcons.arrow_up_right_square,
|
|
size: 16,
|
|
color: Color(0xFF0088CC),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showQQGroupDialog(BuildContext context, String url) {
|
|
showCupertinoDialog<void>(
|
|
context: context,
|
|
builder: (ctx) => CupertinoAlertDialog(
|
|
title: Text(t.about.joinQQGroup),
|
|
content: Text(t.about.qqGroupConfirm),
|
|
actions: [
|
|
CupertinoDialogAction(
|
|
isDestructiveAction: true,
|
|
onPressed: () => Navigator.of(ctx).pop(),
|
|
child: Text(t.common.cancel),
|
|
),
|
|
CupertinoDialogAction(
|
|
isDefaultAction: true,
|
|
onPressed: () {
|
|
Navigator.of(ctx).pop();
|
|
launchAboutUrl(context, url);
|
|
},
|
|
child: Text(t.common.confirm),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// 贡献者与特别鸣谢区域
|
|
// ============================================================
|
|
|
|
class ContributorsSection extends StatelessWidget {
|
|
const ContributorsSection({super.key, required this.ext, required this.t});
|
|
|
|
final AppThemeExtension ext;
|
|
final T t;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return GlassContainer(
|
|
depth: GlassDepth.elevated,
|
|
padding: EdgeInsets.zero,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
AboutSectionTitle(
|
|
icon: CupertinoIcons.star_circle,
|
|
title: t.about.contributors,
|
|
ext: ext,
|
|
),
|
|
GestureDetector(
|
|
onTap: () => _showContributorsSheet(context),
|
|
behavior: HitTestBehavior.opaque,
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: AppSpacing.md,
|
|
vertical: AppSpacing.md,
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 36,
|
|
height: 36,
|
|
decoration: BoxDecoration(
|
|
color: ext.iconTintYellow.withValues(alpha: 0.12),
|
|
borderRadius: AppRadius.smBorder,
|
|
),
|
|
child: Icon(
|
|
CupertinoIcons.group_solid,
|
|
size: 18,
|
|
color: ext.iconTintYellow,
|
|
),
|
|
),
|
|
const SizedBox(width: AppSpacing.md),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
t.about.contributors,
|
|
style: AppTypography.body.copyWith(
|
|
fontWeight: FontWeight.w500,
|
|
color: ext.textPrimary,
|
|
),
|
|
),
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
t.about.contributorsDesc,
|
|
style: AppTypography.caption2.copyWith(
|
|
color: ext.textHint,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Icon(
|
|
CupertinoIcons.chevron_right,
|
|
size: 14,
|
|
color: ext.textHint,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
AboutDivider(ext: ext, leftIndent: 36),
|
|
GestureDetector(
|
|
onTap: () => _showSpecialThanksSheet(context),
|
|
behavior: HitTestBehavior.opaque,
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: AppSpacing.md,
|
|
vertical: AppSpacing.md,
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 36,
|
|
height: 36,
|
|
decoration: BoxDecoration(
|
|
color: ext.iconTintPurple.withValues(alpha: 0.12),
|
|
borderRadius: AppRadius.smBorder,
|
|
),
|
|
child: Icon(
|
|
CupertinoIcons.heart_circle,
|
|
size: 18,
|
|
color: ext.iconTintPurple,
|
|
),
|
|
),
|
|
const SizedBox(width: AppSpacing.md),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
t.about.specialThanks,
|
|
style: AppTypography.body.copyWith(
|
|
fontWeight: FontWeight.w500,
|
|
color: ext.textPrimary,
|
|
),
|
|
),
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
t.about.specialThanksDesc,
|
|
style: AppTypography.caption2.copyWith(
|
|
color: ext.textHint,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Icon(
|
|
CupertinoIcons.chevron_right,
|
|
size: 14,
|
|
color: ext.textHint,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showContributorsSheet(BuildContext context) {
|
|
final contributors = [
|
|
ContributorData('佳佳', t.about.contributorRole5),
|
|
];
|
|
|
|
showCupertinoModalPopup<void>(
|
|
context: context,
|
|
builder: (ctx) => Container(
|
|
constraints: BoxConstraints(
|
|
maxHeight: MediaQuery.of(ctx).size.height * 0.65,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: ext.bgElevated,
|
|
borderRadius: const BorderRadius.vertical(
|
|
top: Radius.circular(AppRadius.xl),
|
|
),
|
|
),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
const SizedBox(height: AppSpacing.sm),
|
|
Container(
|
|
width: 36,
|
|
height: 4,
|
|
decoration: BoxDecoration(
|
|
color: ext.textHint.withValues(alpha: 0.3),
|
|
borderRadius: AppRadius.smBorder,
|
|
),
|
|
),
|
|
const SizedBox(height: AppSpacing.md),
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
|
|
child: Row(
|
|
children: [
|
|
const Text('🌟', style: TextStyle(fontSize: 18)),
|
|
const SizedBox(width: AppSpacing.sm),
|
|
Text(
|
|
t.about.contributors,
|
|
style: AppTypography.title3.copyWith(
|
|
fontWeight: FontWeight.w700,
|
|
color: ext.textPrimary,
|
|
),
|
|
),
|
|
const Spacer(),
|
|
GestureDetector(
|
|
onTap: () => Navigator.pop(ctx),
|
|
child: Container(
|
|
width: 28,
|
|
height: 28,
|
|
decoration: BoxDecoration(
|
|
color: ext.textHint.withValues(alpha: 0.12),
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Icon(
|
|
CupertinoIcons.xmark,
|
|
size: 14,
|
|
color: ext.textSecondary,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: AppSpacing.sm),
|
|
Flexible(
|
|
child: ListView.separated(
|
|
shrinkWrap: true,
|
|
physics: const BouncingScrollPhysics(),
|
|
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
|
|
itemCount: contributors.length,
|
|
separatorBuilder: (_, __) =>
|
|
const SizedBox(height: AppSpacing.sm),
|
|
itemBuilder: (_, index) => _ContributorCard(
|
|
contributor: contributors[index],
|
|
ext: ext,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: AppSpacing.xl),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showSpecialThanksSheet(BuildContext context) {
|
|
final thanks = [
|
|
ContributorData('Tools/Plugins', t.about.specialThanksTools),
|
|
];
|
|
|
|
showCupertinoModalPopup<void>(
|
|
context: context,
|
|
builder: (ctx) => Container(
|
|
constraints: BoxConstraints(
|
|
maxHeight: MediaQuery.of(ctx).size.height * 0.65,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: ext.bgElevated,
|
|
borderRadius: const BorderRadius.vertical(
|
|
top: Radius.circular(AppRadius.xl),
|
|
),
|
|
),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
const SizedBox(height: AppSpacing.sm),
|
|
Container(
|
|
width: 36,
|
|
height: 4,
|
|
decoration: BoxDecoration(
|
|
color: ext.textHint.withValues(alpha: 0.3),
|
|
borderRadius: AppRadius.smBorder,
|
|
),
|
|
),
|
|
const SizedBox(height: AppSpacing.md),
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
|
|
child: Row(
|
|
children: [
|
|
const Text('💝', style: TextStyle(fontSize: 18)),
|
|
const SizedBox(width: AppSpacing.sm),
|
|
Text(
|
|
t.about.specialThanks,
|
|
style: AppTypography.title3.copyWith(
|
|
fontWeight: FontWeight.w700,
|
|
color: ext.textPrimary,
|
|
),
|
|
),
|
|
const Spacer(),
|
|
GestureDetector(
|
|
onTap: () => Navigator.pop(ctx),
|
|
child: Container(
|
|
width: 28,
|
|
height: 28,
|
|
decoration: BoxDecoration(
|
|
color: ext.textHint.withValues(alpha: 0.12),
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Icon(
|
|
CupertinoIcons.xmark,
|
|
size: 14,
|
|
color: ext.textSecondary,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: AppSpacing.sm),
|
|
Flexible(
|
|
child: ListView.separated(
|
|
shrinkWrap: true,
|
|
physics: const BouncingScrollPhysics(),
|
|
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
|
|
itemCount: thanks.length,
|
|
separatorBuilder: (_, __) =>
|
|
const SizedBox(height: AppSpacing.sm),
|
|
itemBuilder: (_, index) =>
|
|
_ContributorCard(contributor: thanks[index], ext: ext),
|
|
),
|
|
),
|
|
const SizedBox(height: AppSpacing.xl),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _ContributorCard extends StatelessWidget {
|
|
const _ContributorCard({required this.contributor, required this.ext});
|
|
|
|
final ContributorData contributor;
|
|
final AppThemeExtension ext;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(AppSpacing.md),
|
|
decoration: BoxDecoration(
|
|
color: ext.bgPrimary,
|
|
borderRadius: AppRadius.lgBorder,
|
|
border: Border.all(color: ext.textHint.withValues(alpha: 0.1)),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 36,
|
|
height: 36,
|
|
decoration: BoxDecoration(
|
|
color: ext.accent.withValues(alpha: 0.12),
|
|
borderRadius: AppRadius.mdBorder,
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
contributor.initial,
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: ext.accent,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: AppSpacing.md),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
contributor.name,
|
|
style: AppTypography.body.copyWith(
|
|
fontWeight: FontWeight.w600,
|
|
color: ext.textPrimary,
|
|
),
|
|
),
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
contributor.role,
|
|
style: AppTypography.caption2.copyWith(color: ext.textHint),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class ContributorData {
|
|
const ContributorData(this.name, this.role);
|
|
|
|
final String name;
|
|
final String role;
|
|
|
|
/// 获取名称首字符作为头像占位
|
|
/// 英文名取首字母大写,中文名取第一个汉字
|
|
String get initial {
|
|
if (name.isEmpty) return '?';
|
|
final firstChar = name[0];
|
|
if (RegExp(r'[a-zA-Z]').hasMatch(firstChar)) {
|
|
return firstChar.toUpperCase();
|
|
}
|
|
return firstChar;
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// 其他产品数据与卡片
|
|
// ============================================================
|
|
|
|
class _ProductData {
|
|
const _ProductData({
|
|
required this.emoji,
|
|
required this.name,
|
|
required this.icp,
|
|
required this.desc,
|
|
required this.url,
|
|
required this.color,
|
|
});
|
|
|
|
final String emoji;
|
|
final String name;
|
|
final String icp;
|
|
final String desc;
|
|
final String url;
|
|
final Color color;
|
|
}
|
|
|
|
class _ProductCard extends StatelessWidget {
|
|
const _ProductCard({
|
|
required this.product,
|
|
required this.ext,
|
|
required this.onTap,
|
|
});
|
|
|
|
final _ProductData product;
|
|
final AppThemeExtension ext;
|
|
final VoidCallback onTap;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return GestureDetector(
|
|
onTap: onTap,
|
|
behavior: HitTestBehavior.opaque,
|
|
child: Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(AppSpacing.md),
|
|
decoration: BoxDecoration(
|
|
color: ext.bgPrimary,
|
|
borderRadius: AppRadius.lgBorder,
|
|
border: Border.all(color: ext.textHint.withValues(alpha: 0.1)),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 40,
|
|
height: 40,
|
|
decoration: BoxDecoration(
|
|
color: product.color.withValues(alpha: 0.12),
|
|
borderRadius: AppRadius.mdBorder,
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
product.emoji,
|
|
style: const TextStyle(fontSize: 18),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: AppSpacing.md),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
product.name,
|
|
style: AppTypography.body.copyWith(
|
|
fontWeight: FontWeight.w600,
|
|
color: ext.textPrimary,
|
|
),
|
|
),
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
product.icp,
|
|
style: AppTypography.caption1.copyWith(color: ext.textHint),
|
|
),
|
|
const SizedBox(height: 1),
|
|
Text(
|
|
product.desc,
|
|
style: AppTypography.caption2.copyWith(color: ext.textHint),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(width: AppSpacing.sm),
|
|
Icon(CupertinoIcons.chevron_right, size: 14, color: ext.textHint),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|