diff --git a/apps/mobile/lib/app/theme.dart b/apps/mobile/lib/app/theme.dart index a27a5cd..aa4c1e4 100644 --- a/apps/mobile/lib/app/theme.dart +++ b/apps/mobile/lib/app/theme.dart @@ -31,7 +31,7 @@ abstract final class AppTheme { static const Color _surface = Color(0xFFFFFFFF); static const Color _onSurface = Color(0xFF172033); static const Color _background = Color(0xFFF6F8FB); - static const Color _textSecondary = Color(0xFF5E6878); + static const Color _textSecondary = Color(0xFF69778E); // UI06: app-preview --muted static const Color _divider = Color(0xFFE5EAF1); static const Color _waiting = Color(0xFF996300); static const Color _onWaiting = Color(0xFFFFFFFF); @@ -141,14 +141,15 @@ abstract final class AppTheme { ), ), - // 卡片:白色表面(深色深表面)、圆角 16、轻阴影。 + // 卡片:白色表面、圆角 16、细边框无阴影(app-preview .card 形态)。 cardTheme: CardThemeData( color: colorScheme.surface, surfaceTintColor: Colors.transparent, - elevation: 1, - shadowColor: Colors.black.withValues(alpha: dark ? 0.4 : 0.08), + elevation: 0, + shadowColor: Colors.transparent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(kRadiusCard), + side: BorderSide(color: dark ? _dividerDark : kColorCardBorder), ), ), @@ -179,15 +180,26 @@ abstract final class AppTheme { actionTextColor: colorScheme.inversePrimary, ), - // 底部导航:白色表面(深色深表面)+ 派生容器色指示器。 + // 底部导航(app-preview .nav):白底 + 顶部细线 + **无胶囊指示器**; + // 选中 primary 蓝图标/文字,未选次文字色。 navigationBarTheme: NavigationBarThemeData( backgroundColor: colorScheme.surface, surfaceTintColor: Colors.transparent, - indicatorColor: colorScheme.secondaryContainer, - height: 80, - labelTextStyle: WidgetStatePropertyAll( - textTheme.labelMedium!.copyWith( - color: colorScheme.onSurface, + indicatorColor: Colors.transparent, + height: 68, + iconTheme: WidgetStateProperty.resolveWith( + (states) => IconThemeData( + size: 24, + color: states.contains(WidgetState.selected) + ? colorScheme.primary + : (dark ? _textSecondaryDark : kColorTextSecondary), + ), + ), + labelTextStyle: WidgetStateProperty.resolveWith( + (states) => textTheme.labelMedium!.copyWith( + color: states.contains(WidgetState.selected) + ? colorScheme.primary + : (dark ? _textSecondaryDark : kColorTextSecondary), fontWeight: FontWeight.w500, ), ), @@ -207,7 +219,7 @@ abstract final class AppTheme { Color color, { double width = 1, }) => OutlineInputBorder( - borderRadius: BorderRadius.circular(kRadiusInput), + borderRadius: BorderRadius.circular(kRadiusField), borderSide: BorderSide(color: color, width: width), ); diff --git a/apps/mobile/lib/pages/albums_tab.dart b/apps/mobile/lib/pages/albums_tab.dart index 3449114..71bc0b9 100644 --- a/apps/mobile/lib/pages/albums_tab.dart +++ b/apps/mobile/lib/pages/albums_tab.dart @@ -192,11 +192,12 @@ class AlbumCard extends StatelessWidget { final AlbumSummary album; /// 底部渐变:透明 → black54,只压暗下半部(白字对比度)。 + /// UI06:app-preview .scrim——30% 起渐变到 #0C1728E8(只压暗下部)。 static const LinearGradient _scrim = LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, - stops: [0.42, 1.0], - colors: [Color(0x00000000), Color(0x8A000000)], + stops: [0.30, 1.0], + colors: [Color(0x00000000), Color(0xE80C1728)], ); @override diff --git a/apps/mobile/lib/pages/home_page.dart b/apps/mobile/lib/pages/home_page.dart index 2860e05..7ede701 100644 --- a/apps/mobile/lib/pages/home_page.dart +++ b/apps/mobile/lib/pages/home_page.dart @@ -88,7 +88,18 @@ class _UploadTabIcon extends StatelessWidget { if (count <= 0) return Icon(icon); return Semantics( label: AppLocalizations.of(context).badgeUploading(count), - child: Badge.count(count: count, child: Icon(icon)), + // UI06:app-preview .badge——红底 #F15B64 白字 + 白边(与导航白底分隔)。 + child: Badge.count( + count: count, + backgroundColor: const Color(0xFFF15B64), + textStyle: const TextStyle( + fontSize: 10, + color: Colors.white, + fontWeight: FontWeight.w600, + ), + smallSize: 16, + child: Icon(icon), + ), ); } } diff --git a/apps/mobile/lib/pages/login_page.dart b/apps/mobile/lib/pages/login_page.dart index ad5afb4..c125b1a 100644 --- a/apps/mobile/lib/pages/login_page.dart +++ b/apps/mobile/lib/pages/login_page.dart @@ -119,7 +119,12 @@ class _LoginPageState extends ConsumerState { const SizedBox(height: kSpace16), Text( l10n.loginTitle, - style: theme.textTheme.titleLarge, + // UI06:app-preview .login-content h1——27px/700。 + style: theme.textTheme.titleLarge?.copyWith( + fontSize: 27, + fontWeight: FontWeight.w700, + letterSpacing: -0.5, + ), ), const SizedBox(height: kSpace8), Text( diff --git a/apps/mobile/lib/pages/queue_tab.dart b/apps/mobile/lib/pages/queue_tab.dart index 3bb6741..c6a57e5 100644 --- a/apps/mobile/lib/pages/queue_tab.dart +++ b/apps/mobile/lib/pages/queue_tab.dart @@ -29,6 +29,7 @@ import '../features/uploads/queue_controller.dart'; import '../features/uploads/queue_l10n.dart'; import '../features/uploads/task_state.dart'; import '../features/uploads/task_store.dart'; +import '../ui/components/segment_tabs.dart'; import '../l10n/app_localizations.dart'; import '../ui/components/empty_state.dart'; import '../ui/components/error_state.dart'; @@ -102,63 +103,50 @@ class _SummaryCard extends ConsumerWidget { ].join(' · '); final light = theme.brightness == Brightness.light; - return Card( - margin: const EdgeInsets.fromLTRB(kSpace16, kSpace12, kSpace16, 0), - child: Padding( - padding: const EdgeInsets.all(kSpace16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Icon( - Icons.summarize_outlined, - size: 16, - color: theme.colorScheme.onSurfaceVariant, - ), - const SizedBox(width: kSpace8), - Expanded( - child: Text( - scope, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: theme.textTheme.bodySmall, - ), - ), - ], - ), - const SizedBox(height: kSpace8), - // 已完成突出(§4 页面 14:摘要突出已完成数)。 - _SummaryCount( - value: completedSummaryOf(counts), - label: l10n.uploadsCompleted, - color: light ? kColorSuccess : kColorSuccessDark, - emphasized: true, - ), - const SizedBox(height: kSpace4), - Wrap( - spacing: kSpace16, - runSpacing: kSpace4, - children: [ - _SummaryCount( - value: uploadingSummaryOf(counts), - label: l10n.uploadsUploading, - color: theme.colorScheme.primary, - ), - _SummaryCount( - value: waitingSummaryOf(counts), - label: l10n.uploadsWaiting, - color: light ? kColorWaiting : kColorWaitingDark, - ), - _SummaryCount( - value: failedCountOf(counts), - label: l10n.queueSummaryNeedAttention, - color: light ? kColorError : kColorErrorDark, - ), - ], - ), - ], - ), + // UI06:摘要直接排页背景(无白卡包裹);范围行 13px、主数字 32px、 + // 副行 13px 中性(app-preview 示例 14)。 + return Padding( + padding: const EdgeInsets.fromLTRB(kSpace16, kSpace16, kSpace16, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + scope, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: theme.textTheme.bodySmall, + ), + const SizedBox(height: kSpace8), + // 已完成突出(§4 页面 14:摘要突出已完成数)。 + _SummaryCount( + value: completedSummaryOf(counts), + label: l10n.uploadsCompleted, + color: light ? kColorSuccess : kColorSuccessDark, + emphasized: true, + ), + const SizedBox(height: kSpace4), + Wrap( + spacing: kSpace16, + runSpacing: kSpace4, + children: [ + _SummaryCount( + value: uploadingSummaryOf(counts), + label: l10n.uploadsUploading, + color: theme.colorScheme.primary, + ), + _SummaryCount( + value: waitingSummaryOf(counts), + label: l10n.uploadsWaiting, + color: light ? kColorWaiting : kColorWaitingDark, + ), + _SummaryCount( + value: failedCountOf(counts), + label: l10n.queueSummaryNeedAttention, + color: light ? kColorError : kColorErrorDark, + ), + ], + ), + ], ), ); } @@ -189,12 +177,19 @@ class _SummaryCount extends StatelessWidget { children: [ Text( '$value', - style: (emphasized - ? theme.textTheme.titleLarge?.copyWith( - fontWeight: FontWeight.w700, - ) - : theme.textTheme.titleMedium) - ?.copyWith(fontFeatures: const [FontFeature.tabularFigures()]), + style: + (emphasized + // UI06:主数字 32/40(app-preview 示例 14 .metric) + ? theme.textTheme.titleLarge?.copyWith( + fontSize: 32, + height: 40 / 32, + fontWeight: FontWeight.w700, + letterSpacing: -0.6, + ) + : theme.textTheme.titleMedium) + ?.copyWith( + fontFeatures: const [FontFeature.tabularFigures()], + ), ), const SizedBox(width: kSpace4), Text(label, style: theme.textTheme.bodySmall?.copyWith(color: color)), @@ -232,34 +227,20 @@ class _PrimaryFilterBar extends ConsumerWidget { child: Row( children: [ Expanded( - child: SegmentedButton( - // 已完成经筛选菜单选择时三段不亮(属次级筛选,非主段)。 - selected: filter.state == QueueStateFilter.completed - ? const {} - : {filter.state}, - emptySelectionAllowed: true, - showSelectedIcon: false, - style: SegmentedButton.styleFrom( - minimumSize: const Size.fromHeight(44), - padding: const EdgeInsets.symmetric(horizontal: kSpace12), - ), + // UI06:轻量分段(app-preview .tabs——#EDF1F6 轨道/选中实蓝, + // 替换 M3 SegmentedButton 黑边胶囊)。 + // 已完成经筛选菜单选择时三段不亮(属次级筛选,非主段)。 + child: SegmentTabs( + value: filter.state == QueueStateFilter.completed + ? null + : filter.state, segments: [ - ButtonSegment( - value: QueueStateFilter.all, - label: Text(l10n.uploadsAll), - ), - ButtonSegment( - value: QueueStateFilter.inProgress, - label: Text(l10n.uploadsActive), - ), - ButtonSegment( - value: QueueStateFilter.needsAttention, - label: Text(l10n.uploadsNeedsAttention), - ), + (QueueStateFilter.all, l10n.uploadsAll), + (QueueStateFilter.inProgress, l10n.uploadsActive), + (QueueStateFilter.needsAttention, l10n.uploadsNeedsAttention), ], - onSelectionChanged: (selection) => ref - .read(queueFilterProvider.notifier) - .setStateGroup(selection.first), + onChanged: (value) => + ref.read(queueFilterProvider.notifier).setStateGroup(value), ), ), IconButton( @@ -295,8 +276,9 @@ class _FilterSheet extends ConsumerWidget { void close() => Navigator.of(context).pop(); // 相册组值:'' = 全部相册(选中相册不在列表时回退全部)。 - final albumValue = - albums.any((a) => a.id == filter.albumId) ? (filter.albumId ?? '') : ''; + final albumValue = albums.any((a) => a.id == filter.albumId) + ? (filter.albumId ?? '') + : ''; return SafeArea( child: ListView( shrinkWrap: true, @@ -369,7 +351,9 @@ class _FilterSheet extends ConsumerWidget { ), title: Text(l10n.queueStateCompleted), onTap: () { - ref.read(queueFilterProvider.notifier).setStateGroup( + ref + .read(queueFilterProvider.notifier) + .setStateGroup( filter.state == QueueStateFilter.completed ? QueueStateFilter.all : QueueStateFilter.completed, @@ -488,9 +472,7 @@ class _TaskTile extends ConsumerWidget { return Padding( // 行键(task.id)供 widget 测试行级锚定(状态徽章/动作图标定位)。 key: ValueKey('queue-task-${task.id}'), - padding: const EdgeInsets.fromLTRB( - kSpace16, kSpace8, kSpace16, kSpace8, - ), + padding: const EdgeInsets.fromLTRB(kSpace16, kSpace8, kSpace16, kSpace8), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -504,8 +486,9 @@ class _TaskTile extends ConsumerWidget { task.filename, maxLines: 1, overflow: TextOverflow.ellipsis, - style: theme.textTheme.bodyMedium - ?.copyWith(fontWeight: FontWeight.w600), + style: theme.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.w600, + ), ), const SizedBox(height: 2), Text( @@ -563,8 +546,8 @@ class _TaskTile extends ConsumerWidget { task.state == UploadTaskState.uploadedConfirming) _ProgressLine( taskId: task.id, - confirming: task.state == - UploadTaskState.uploadedConfirming, + confirming: + task.state == UploadTaskState.uploadedConfirming, ), ], ), @@ -577,18 +560,17 @@ class _TaskTile extends ConsumerWidget { /// 徽章语义变体(§4 页面 14:进行=主色、等待=琥珀、失败=红、完成=绿、 /// 取消=灰)。 - static BadgeVariant _badgeVariant(UploadTaskState state) => - switch (state) { - UploadTaskState.preparing || - UploadTaskState.uploading || - UploadTaskState.uploadedConfirming => BadgeVariant.info, - UploadTaskState.queued || - UploadTaskState.waitingNetwork => BadgeVariant.warning, - UploadTaskState.preparationFailed || - UploadTaskState.retryableFailed => BadgeVariant.error, - UploadTaskState.completed => BadgeVariant.success, - UploadTaskState.cancelled => BadgeVariant.neutral, - }; + static BadgeVariant _badgeVariant(UploadTaskState state) => switch (state) { + UploadTaskState.preparing || + UploadTaskState.uploading || + UploadTaskState.uploadedConfirming => BadgeVariant.info, + UploadTaskState.queued || + UploadTaskState.waitingNetwork => BadgeVariant.warning, + UploadTaskState.preparationFailed || + UploadTaskState.retryableFailed => BadgeVariant.error, + UploadTaskState.completed => BadgeVariant.success, + UploadTaskState.cancelled => BadgeVariant.neutral, + }; /// 徽章文案 = §4.4 界面含义列(确认中不显示「已公开」等超集语义)。 static String _badgeLabel(AppLocalizations l10n, UploadTaskState state) => @@ -911,7 +893,9 @@ class _BatchRetryBar extends ConsumerWidget { final account = ref.read(sessionAccountProvider).value; if (account == null) return; final engine = ref.read(uploadEngineProvider); - final tasks = await ref.read(taskStoreProvider).listByAccount( + final tasks = await ref + .read(taskStoreProvider) + .listByAccount( account, states: const {UploadTaskState.retryableFailed}, ); diff --git a/apps/mobile/lib/ui/components/segment_tabs.dart b/apps/mobile/lib/ui/components/segment_tabs.dart new file mode 100644 index 0000000..4127703 --- /dev/null +++ b/apps/mobile/lib/ui/components/segment_tabs.dart @@ -0,0 +1,118 @@ +/// 轻量分段选择器(UI06 对齐 app-preview .tabs)。 +/// +/// 形态:#EDF1F6 轨道、padding 4、整体圆角 9;选中实底、未选次文字色。 +/// 两种选中风格: +/// - [SegmentTabsStyle.solid]:主蓝实底白字(上传中心筛选等状态筛选); +/// - [SegmentTabsStyle.light]:#DBEAFF 底 + #145BB9 字(Details/Access 类 +/// 内容页签——与状态筛选区分,见 app-preview .tabs.light)。 +/// +/// 替换默认 [SegmentedButton](M3 黑边胶囊形态与示例不符)。 +library; + +import 'package:flutter/material.dart'; + +import '../tokens.dart'; + +enum SegmentTabsStyle { solid, light } + +class SegmentTabs extends StatelessWidget { + const SegmentTabs({ + required this.value, + required this.onChanged, + required this.segments, + this.style = SegmentTabsStyle.solid, + super.key, + }); + + /// 当前值;null = 无高亮(次级筛选接管时的中间态)。 + final T? value; + final ValueChanged onChanged; + + /// 选项:值 + 本地化标签。 + final List<(T, String)> segments; + + final SegmentTabsStyle style; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final light = style == SegmentTabsStyle.light; + return Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: kColorSegTrack, + borderRadius: BorderRadius.circular(9), + ), + child: Row( + children: [ + for (final (v, label) in segments) + Expanded( + child: _TabButton( + label: label, + selected: v == value, + light: light, + primary: theme.colorScheme.primary, + onTap: () => onChanged(v), + ), + ), + ], + ), + ); + } +} + +class _TabButton extends StatelessWidget { + const _TabButton({ + required this.label, + required this.selected, + required this.light, + required this.primary, + required this.onTap, + }); + + final String label; + final bool selected; + final bool light; + final Color primary; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + final Color bg; + final Color fg; + if (selected) { + bg = light ? kColorSegLightActive : primary; + fg = light ? kColorSegLightActiveText : Colors.white; + } else { + bg = Colors.transparent; + fg = kColorTextSecondary; + } + return Semantics( + button: true, + selected: selected, + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(7), + child: Container( + alignment: Alignment.center, + height: 36, + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5), + decoration: BoxDecoration( + color: bg, + borderRadius: BorderRadius.circular(7), + ), + child: Text( + label, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 12, + fontWeight: selected ? FontWeight.w600 : FontWeight.w500, + color: fg, + ), + ), + ), + ), + ); + } +} diff --git a/apps/mobile/lib/ui/tokens.dart b/apps/mobile/lib/ui/tokens.dart index 718eb7b..602138f 100644 --- a/apps/mobile/lib/ui/tokens.dart +++ b/apps/mobile/lib/ui/tokens.dart @@ -29,8 +29,24 @@ const Color kColorInfo = Color(0xFF1A73E8); /// 分隔线:轻分隔,不用粗边框(#E5EAF1)。 const Color kColorDivider = Color(0xFFE5EAF1); -/// 次文字:时间、地点、说明(#5E6878)。 -const Color kColorTextSecondary = Color(0xFF5E6878); +/// 次文字:时间、地点、说明(app-preview --muted #69778E)。 +const Color kColorTextSecondary = Color(0xFF69778E); + +/// 浅蓝软底(app-preview --soft #EDF4FF):信息提示、tag 底、选中页签。 +const Color kColorSoft = Color(0xFFEDF4FF); + +/// 卡片细边框(app-preview .card/.group border #EDF0F5;卡为边框形态非阴影)。 +const Color kColorCardBorder = Color(0xFFEDF0F5); + +/// 分段轨道底(app-preview .tabs #EDF1F6)。 +const Color kColorSegTrack = Color(0xFFEDF1F6); + +/// 输入框默认边框(app-preview .field input #DBE3EF)。 +const Color kColorFieldBorder = Color(0xFFDBE3EF); + +/// 浅蓝选中分段(app-preview .tabs.light active:#DBEAFF 底 + #145BB9 字)。 +const Color kColorSegLightActive = Color(0xFFDBEAFF); +const Color kColorSegLightActiveText = Color(0xFF145BB9); // ── 语义色(深色变体:深色表面对应提亮值)────────────────────────────── @@ -64,10 +80,11 @@ const double kSpace16 = 16.0; const double kSpace24 = 24.0; const double kSpace32 = 32.0; -// ── 圆角(卡片 16 / 输入框与按钮 12 / 小标签 8)──────────────────────── +// ── 圆角(卡片 16 / 按钮 12 / 输入框 10 / 小标签 8)────────────────────── const double kRadiusCard = 16.0; const double kRadiusInput = 12.0; +const double kRadiusField = 10.0; const double kRadiusBadge = 8.0; // ── 动效时长(轻量 150-220ms / 轮播 250-350ms)──────────────────────── diff --git a/apps/mobile/test/app/theme_test.dart b/apps/mobile/test/app/theme_test.dart index ffe933d..b35fa15 100644 --- a/apps/mobile/test/app/theme_test.dart +++ b/apps/mobile/test/app/theme_test.dart @@ -31,7 +31,7 @@ void main() { expect(AppTheme.light.dividerTheme.color, kColorDivider); expect(kColorDivider, const Color(0xFFE5EAF1)); expect(scheme.onSurfaceVariant, kColorTextSecondary); - expect(kColorTextSecondary, const Color(0xFF5E6878)); + expect(kColorTextSecondary, const Color(0xFF69778E)); // UI06: app-preview --muted }); }); @@ -104,10 +104,10 @@ void main() { ); }); - test('输入框圆角 12', () { + test('输入框圆角 10(UI06: app-preview .field)', () { final border = theme.inputDecorationTheme.border as OutlineInputBorder; - expect(border.borderRadius, BorderRadius.circular(kRadiusInput)); - expect(kRadiusInput, 12); + expect(border.borderRadius, BorderRadius.circular(kRadiusField)); + expect(kRadiusField, 10); }); test('Dialog 圆角 16、BottomSheet 顶部圆角 16', () { @@ -125,11 +125,8 @@ void main() { expect(kRadiusCard, 16); }); - test('底部导航指示器色已配置', () { - expect( - theme.navigationBarTheme.indicatorColor, - theme.colorScheme.secondaryContainer, - ); + test('底部导航无胶囊指示器(UI06: app-preview .nav)', () { + expect(theme.navigationBarTheme.indicatorColor, Colors.transparent); }); }); diff --git a/docs/designs/ui06-implementation/app-after/01-login.png b/docs/designs/ui06-implementation/app-after/01-login.png new file mode 100644 index 0000000..3fd6925 Binary files /dev/null and b/docs/designs/ui06-implementation/app-after/01-login.png differ diff --git a/docs/designs/ui06-implementation/app-after/05-albums.png b/docs/designs/ui06-implementation/app-after/05-albums.png new file mode 100644 index 0000000..c347cca Binary files /dev/null and b/docs/designs/ui06-implementation/app-after/05-albums.png differ diff --git a/docs/designs/ui06-implementation/app-after/06-album-detail.png b/docs/designs/ui06-implementation/app-after/06-album-detail.png new file mode 100644 index 0000000..95ab6c8 Binary files /dev/null and b/docs/designs/ui06-implementation/app-after/06-album-detail.png differ diff --git a/docs/designs/ui06-implementation/app-after/14-uploads.png b/docs/designs/ui06-implementation/app-after/14-uploads.png new file mode 100644 index 0000000..8a198b1 Binary files /dev/null and b/docs/designs/ui06-implementation/app-after/14-uploads.png differ diff --git a/docs/plans/progress/UI06-验收记录.md b/docs/plans/progress/UI06-验收记录.md index 33ebeae..f5772eb 100644 --- a/docs/plans/progress/UI06-验收记录.md +++ b/docs/plans/progress/UI06-验收记录.md @@ -17,6 +17,7 @@ | B3d 治理批 | 10 账号 + 11 同步 + 12 用量 | `9471562` | ✅ 完成 | | B3e 收尾批 | 13/14 展示位 + 17–19 异常页 | `9471562` | ✅ 完成 | | B4 | 后台全量回归验收 | `9471562` | ✅ 已部署 `ui06-9471562-20260923` 待用户终验 | +| A0–A7 | App 线(对照 app-preview) | 本次提交 | ✅ 完成(analyze 0 / test 397 全绿 / 真机 4 页证据) | | B4 | 后台全量回归验收 | | 待执行 | | A0–A7 | App 线(B4 后启动) | | 待执行 | @@ -125,9 +126,42 @@ - 保留项:游标分页(useCursorList)✅ / 批量部分成功 ✅ / 账号自保护 ✅ / settings 语义 ✅ / translations 合并 ✅ / e2e 选择器全部适配(albums/governance/embed/users/login/locale) - 登记偏差:①登录摄影素材为概念图裁片占位(待正式品牌素材);②深色模式保持可用未重设计;③17–19 沿用 ArtException 版式;④无封面相册显示中性占位(cover=null 真实数据状态) -## 6. App 线记录 +## 6. App 线记录(2026-09-23,用户指令「先都改完再统一测试」——各批中途关口合并为终验) -(待后台终验后启动 A0) +### 6.1 A0 偏差复核(代码级,以 app-preview styles.css 实测为准) + +UI-03 完成态与 app-preview 的核心差异(修前): +1. 根导航 NavigationBar 保留 `indicatorColor: secondaryContainer` 胶囊 ❌ +2. 上传主筛选用 M3 SegmentedButton(黑边胶囊形态)❌ +3. 上传摘要被白色大卡包裹、主数字 24px ❌(示例:直排背景 + 32px) +4. token 差异:次文字 #5E6878(示例 --muted #69778E)、输入圆角 12(示例 10)、卡片阴影形态(示例 1px #EDF0F5 边框无阴影)、相册卡 scrim 0.42 起(示例 30% 起 #0C1728E8)❌ +5. 登录主标题 24px(示例 27px)❌ +6. 导航角标无白边(示例 #F15B64 + 2px 白边)❌ + +已达标项(无需改):AlbumCard 16:9/白字/状态行、详情 hero/工具区、登录拼贴、字阶体系、间距体系。 + +### 6.2 A2 公共组件与 token(与 A3 样板页合并实施) + +- `ui/tokens.dart`:kColorTextSecondary→#69778E;新增 kColorSoft(#EDF4FF)/kColorCardBorder(#EDF0F5)/kColorSegTrack(#EDF1F6)/kColorSegLightActive(#DBEAFF+#145BB9)/kColorFieldBorder(#DBE3EF);kRadiusField=10 +- `app/theme.dart`:导航 **indicatorColor 透明** + iconTheme/label 显式选中蓝未选次色 + 高度 68;卡片 **去阴影改 1px 边框**;输入圆角 10;_textSecondary 同步 #69778E +- 新组件 `ui/components/segment_tabs.dart`:轻量分段(#EDF1F6 轨道/半径 9/选中实蓝白字 36 高;light 变体浅蓝选中供内容页签;支持 null 无高亮次级筛选态) +- queue_tab:SegmentedButton→SegmentTabs(completed 次级筛选时三段不亮语义保留);**摘要去卡直排 + 主数字 32/40**;导航角标红底白边;相册卡 scrim 30% 起 #0C1728E8;登录主标题 27/700 +- 语义色(成功/错误/等待)保持 UI-03R 深色版:HTML 装饰值(#279966/#E74E58/#D89020)在浅色文字位对比度不达标(<4.5:1),**登记偏差**供用户定夺 + +### 6.3 A7 验证与真机证据 + +- `flutter analyze` 0 issues ✅;`flutter test` **397/397 全绿** ✅(theme_test 三处旧规格断言随 UI06 值更新:次色/输入圆角 10/无胶囊) +- 真机:MuMu 模拟器(Android 15, 540×960)安装 debug APK(--dart-define API_BASE=测试服务器 32005 / VIEWER_BASE=32006),真实账号登录成功 +- 截图 4 张(`ui06-implementation/app-after/`):01 登录(拼贴+品牌+表单)、05 相册列表(真实封面卡+无胶囊导航)、06 详情(164 hero+工具区)、14 上传(**无卡摘要+32px 数字+轻分段+无胶囊**)——均确认达标 +- 未做(登记):320/430/横屏/大字体/zh 深色抽查矩阵(UI-03R §10.1)——397 项 widget 测试覆盖主要布局断言,视口矩阵随 G2 真机关口补 + +### 6.4 App 线登记偏差汇总 + +| # | 偏差 | 原因 | +|---|---|---| +| 1 | 语义色未采用 HTML 装饰值(#279966 等) | 浅色文字对比度 <4.5:1;保持 UI-03R 深色版(视觉差 <10%) | +| 2 | 视口矩阵抽查未跑 | 随 G2 真机关口;widget 测试已覆盖主布局 | +| 3 | 生产登录拼贴素材 | 沿用 UI-03 已有合规素材(非本次替换范围) | ## 4. B2 记录