feat(ui): 更新品牌启动页与选集交互
This commit is contained in:
1 parent
4778ce2c75
commit
c1cb7d1c11
87 files changed
+919
-552
No files matched your search
@@ -4,19 +4,17 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import '../theme/app_colors.dart';
|
||||
|
||||
const double _regularArtSize = 84;
|
||||
const double _compactArtSize = 60;
|
||||
const double _regularInnerSize = 56;
|
||||
const double _compactInnerSize = 40;
|
||||
const double _regularIconSize = 27;
|
||||
const double _compactIconSize = 21;
|
||||
const double _regularIconSize = 48;
|
||||
const double _compactIconSize = 32;
|
||||
const double _maxContentWidth = 260;
|
||||
const double _regularHorizontal = 24;
|
||||
const double _regularVertical = 28;
|
||||
const double _compactPad = 16;
|
||||
const double _regularGap = 20;
|
||||
const double _compactGap = 12;
|
||||
const double _actionMinHeight = 44;
|
||||
const double _outerArtOpacity = 0.055;
|
||||
const double _outerBorderOpacity = 0.11;
|
||||
const double _innerArtOpacity = 0.11;
|
||||
|
||||
/// 统一空列表占位:柔和图标、标题、可选说明与操作。
|
||||
/// 统一空列表占位:一枚图标、标题、可选说明与操作。
|
||||
class EmptyState extends StatelessWidget {
|
||||
const EmptyState({
|
||||
super.key,
|
||||
@@ -24,6 +22,7 @@ class EmptyState extends StatelessWidget {
|
||||
required this.title,
|
||||
this.subtitle,
|
||||
this.actionLabel,
|
||||
this.actionKey,
|
||||
this.onAction,
|
||||
this.compact = false,
|
||||
});
|
||||
@@ -32,17 +31,12 @@ class EmptyState extends StatelessWidget {
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
final String? actionLabel;
|
||||
final Key? actionKey;
|
||||
final VoidCallback? onAction;
|
||||
final bool compact;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
final dark = Theme.of(context).brightness == Brightness.dark;
|
||||
final iconColor = dark ? AppColors.blueSoft : AppColors.blue;
|
||||
final artSize = compact ? _compactArtSize : _regularArtSize;
|
||||
final innerSize = compact ? _compactInnerSize : _regularInnerSize;
|
||||
final iconSize = compact ? _compactIconSize : _regularIconSize;
|
||||
final hasAction = actionLabel != null && onAction != null;
|
||||
return Center(
|
||||
child: ConstrainedBox(
|
||||
@@ -51,8 +45,8 @@ class EmptyState extends StatelessWidget {
|
||||
child: Padding(
|
||||
key: const Key('empty-state-padding'),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: compact ? 16 : 24,
|
||||
vertical: compact ? 16 : 28,
|
||||
horizontal: compact ? _compactPad : _regularHorizontal,
|
||||
vertical: compact ? _compactPad : _regularVertical,
|
||||
),
|
||||
child: Semantics(
|
||||
container: true,
|
||||
@@ -61,88 +55,16 @@ class EmptyState extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ExcludeSemantics(
|
||||
child: Container(
|
||||
key: const Key('empty-state-art'),
|
||||
width: artSize,
|
||||
height: artSize,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppColors.blue.withValues(alpha: _outerArtOpacity),
|
||||
border: Border.all(
|
||||
color: AppColors.blue.withValues(
|
||||
alpha: _outerBorderOpacity,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Container(
|
||||
key: const Key('empty-state-art-inner'),
|
||||
width: innerSize,
|
||||
height: innerSize,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppColors.blue.withValues(
|
||||
alpha: _innerArtOpacity,
|
||||
),
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
key: const Key('empty-state-icon'),
|
||||
size: iconSize,
|
||||
color: iconColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: _EmptyMark(icon: icon, compact: compact),
|
||||
),
|
||||
SizedBox(height: compact ? 10 : 14),
|
||||
ExcludeSemantics(
|
||||
child: Text(
|
||||
title,
|
||||
key: const Key('empty-state-title'),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: scheme.onSurface,
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
height: 1.35,
|
||||
),
|
||||
),
|
||||
SizedBox(height: compact ? _compactGap : _regularGap),
|
||||
_EmptyCopy(
|
||||
title: title,
|
||||
subtitle: subtitle,
|
||||
actionLabel: hasAction ? actionLabel : null,
|
||||
actionKey: actionKey,
|
||||
onAction: hasAction ? onAction : null,
|
||||
),
|
||||
if (subtitle != null) ...[
|
||||
const SizedBox(height: 6),
|
||||
ExcludeSemantics(
|
||||
child: Text(
|
||||
subtitle!,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: AppColors.muted,
|
||||
fontSize: 12.5,
|
||||
height: 1.45,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (hasAction) ...[
|
||||
const SizedBox(height: 8),
|
||||
TextButton(
|
||||
key: const Key('empty-state-action'),
|
||||
onPressed: onAction,
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: scheme.primary,
|
||||
minimumSize: const Size(44, _actionMinHeight),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14),
|
||||
),
|
||||
child: Text(
|
||||
actionLabel!,
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -151,3 +73,92 @@ class EmptyState extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 空状态只留一枚图标,不加水印底和描边。
|
||||
class _EmptyMark extends StatelessWidget {
|
||||
const _EmptyMark({required this.icon, required this.compact});
|
||||
|
||||
final IconData icon;
|
||||
final bool compact;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final dark = Theme.of(context).brightness == Brightness.dark;
|
||||
return Icon(
|
||||
icon,
|
||||
key: const Key('empty-state-icon'),
|
||||
size: compact ? _compactIconSize : _regularIconSize,
|
||||
color: dark ? AppColors.gold : AppColors.blue,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _EmptyCopy extends StatelessWidget {
|
||||
const _EmptyCopy({
|
||||
required this.title,
|
||||
required this.subtitle,
|
||||
required this.actionLabel,
|
||||
required this.actionKey,
|
||||
required this.onAction,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
final String? actionLabel;
|
||||
final Key? actionKey;
|
||||
final VoidCallback? onAction;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ExcludeSemantics(
|
||||
child: Text(
|
||||
title,
|
||||
key: const Key('empty-state-title'),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: scheme.onSurface,
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
height: 1.35,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (subtitle != null) ...[
|
||||
const SizedBox(height: 6),
|
||||
ExcludeSemantics(
|
||||
child: Text(
|
||||
subtitle!,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: AppColors.muted,
|
||||
fontSize: 12.5,
|
||||
height: 1.45,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (actionLabel != null && onAction != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
TextButton(
|
||||
key: actionKey ?? const Key('empty-state-action'),
|
||||
onPressed: onAction,
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: scheme.primary,
|
||||
minimumSize: const Size(44, _actionMinHeight),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14),
|
||||
),
|
||||
child: Text(actionLabel!, style: const TextStyle(fontSize: 13)),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ class LibraryDramaTile extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: const Color(0xFF1B1C21),
|
||||
color: AppColors.card,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
|
||||
Reference in new issue
Block a user