Files
Ichino/lib/widgets/app_card.dart
T
Bad0RANG3 45214b11a0 refactor: 抽取高危功能页共享脚手架与传输层日志
诊断:功能票 / 解锁歌曲 / 收藏品 / 旅行伙伴 / 数值修改 / 一键跑图 /
万花筒 / 高危 hub 这些页面各自抄了一份同样的脚手架——冷却计时器、_Step
枚举、自动退登开关、卡片/提示/进度卡;TitleApiService.getUserPreview 还
重复实现了整套传输层;服务层有 37 处裸 print 会把 token / JSESSIONID
打进日志。

- 新增共享原语,7 个功能页切换到:
  * widgets/cooldown_mixin.dart  CooldownMixin(登录后冷却倒计时)
  * widgets/step_progress.dart   RiskStep + StepProgressCard
  * widgets/app_card.dart        AppCard / SectionTitle / AutoLogoutToggle
  * widgets/app_notice.dart      AppNotice + context.showSnack
- TitleApiService: getUserPreview 收敛进通用 _callApi;新增 fromHolder()
  工厂与 config getter,消除散落的 isConfigured + config!
- 传输层 print -> ApiLog(仅 debug 输出),删除全部 avoid_print 忽略
- UserAllPayloadBuilder 新增 placeholderMusicData()(原 5 处重复)
- LoginPage 从 main.dart 抽到 pages/login_page.dart,入口只留 main + MyApp
- 更新 README / CLAUDE.md 目录结构与已过时的 web/依赖说明

验证:flutter analyze 0 issue(含 info);flutter test 96/96 通过。
纯结构重构,行为保持不变;仅有意统一了功能票 / 音乐风险 / hub 的
卡片与步骤视觉样式。

净删除约 2176 行重复代码。
2026-09-28 14:50:51 +08:00

127 lines
3.2 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import '../config/strings.dart';
/// 全站统一的卡片外壳:无阴影、16 圆角、outline 描边。
///
/// 之前每个页面各写一份 `Card(elevation: 0, shape: RoundedRectangleBorder(...))`,
/// 改一处圆角/描边要改十几个文件。
class AppCard extends StatelessWidget {
final Widget child;
final EdgeInsetsGeometry padding;
final Color? borderColor;
final EdgeInsetsGeometry? margin;
const AppCard({
super.key,
required this.child,
this.padding = const EdgeInsets.all(20),
this.borderColor,
this.margin,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Card(
elevation: 0,
margin: margin,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: BorderSide(
color: borderColor ??
theme.colorScheme.outline.withValues(alpha: 0.3),
),
),
child: Padding(padding: padding, child: child),
);
}
}
/// 卡片内的标题行:图标 + 彩色标题。
class SectionTitle extends StatelessWidget {
final IconData icon;
final String title;
final Color? color;
final double iconSize;
const SectionTitle({
super.key,
required this.icon,
required this.title,
this.color,
this.iconSize = 18,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final tint = color ?? theme.colorScheme.primary;
return Row(
children: [
Icon(icon, size: iconSize, color: tint),
const SizedBox(width: 8),
Expanded(
child: Text(
title,
style: theme.textTheme.labelLarge?.copyWith(
color: tint,
fontWeight: FontWeight.w600,
),
),
),
],
);
}
}
/// 「完成后自动退出登录并返回标题页」勾选框。
///
/// 六个功能页原本各抄一份 `InkWell + Checkbox + Text`,现在统一成一个卡片。
class AutoLogoutToggle extends StatelessWidget {
final bool value;
final bool enabled;
final ValueChanged<bool>? onChanged;
final String label;
const AutoLogoutToggle({
super.key,
required this.value,
required this.enabled,
required this.onChanged,
this.label = AppStrings.autoLogoutAndExit,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return AppCard(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
child: InkWell(
borderRadius: BorderRadius.circular(16),
onTap: enabled ? () => onChanged?.call(!value) : null,
child: Row(
children: [
Checkbox(
value: value,
onChanged:
enabled ? (v) => onChanged?.call(v ?? false) : null,
),
Expanded(
child: Text(
label,
style: theme.textTheme.bodyMedium?.copyWith(
color: enabled
? null
: theme.colorScheme.onSurfaceVariant
.withValues(alpha: 0.6),
),
),
),
],
),
),
);
}
}