feat(ui06): App line A0-A7 — nav pill removal, light segments, bare summary, token alignment to app-preview
- tokens:次文字 #69778E、soft/card-border/seg 色组、输入圆角 10(kRadiusField) - theme:导航 indicatorColor 透明+显式选中蓝;卡片去阴影改 1px #EDF0F5 边框;输入边框同步 - 新组件 SegmentTabs(#EDF1F6 轨道/选中实蓝/light 浅蓝变体/null 无高亮)替换 M3 SegmentedButton - 上传中心:摘要去白卡直排+主数字 32/40;completed 次级筛选无高亮语义保留 - 相册卡 scrim 30% 起 #0C1728E8;导航角标红底白字白边;登录主标题 27/700 - theme_test 三处旧规格断言随 UI06 值更新;397/397 全绿、analyze 0 issues - 真机证据:MuMu + 测试服务器 API,登录/相册/详情/上传 4 页截图确认达标 - 登记偏差:语义色保持对比度合规深色版;视口矩阵随 G2;详见验收记录 §6 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -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),
|
||||
);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +119,12 @@ class _LoginPageState extends ConsumerState<LoginPage> {
|
||||
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(
|
||||
|
||||
@@ -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<QueueStateFilter>(
|
||||
// 已完成经筛选菜单选择时三段不亮(属次级筛选,非主段)。
|
||||
selected: filter.state == QueueStateFilter.completed
|
||||
? const <QueueStateFilter>{}
|
||||
: <QueueStateFilter>{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<QueueStateFilter>(
|
||||
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},
|
||||
);
|
||||
|
||||
@@ -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<T> 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<T> 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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)────────────────────────
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 118 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 259 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
@@ -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 记录
|
||||
|
||||
|
||||
Reference in New Issue
Block a user