fix(ui): 打磨推荐追剧我的设置页间距与图标

This commit is contained in:
Vicky authored and trae-solo-agent committed 2026-10-28 16:36:51 +08:00
1 parent de7da64886
commit 5346be40a9
33 files changed
+1408 -811

No files matched your search

@@ -20,6 +20,8 @@ import '../../home/widgets/home_feed_card.dart';
const double _loadMoreThreshold = 240;
const int _gridColumns = 3;
// 标题两行39 + 上间距6 + 标签间距6 + 标签13 = 64,比 homeCardBodyExtent 更紧凑。
const double _cardBodyExtent = 64;
/// 推荐页「全部短剧」筛选和三列列表。
class AllDramasFilterGrid extends StatefulWidget {
@@ -94,7 +96,7 @@ class _AllDramasFilterGridState extends State<AllDramasFilterGrid> {
final itemWidth =
(width - AppDimens.allDramasHorizontal * 2 - AppDimens.allDramasGridGap * 2) /
_gridColumns;
return itemWidth / AppDimens.cardCoverAspectRatio + AppDimens.homeCardBodyExtent;
return itemWidth / AppDimens.cardCoverAspectRatio + _cardBodyExtent;
}
Widget _dramaList(AppLocalizations l10n, double itemExtent) {
@@ -309,7 +311,7 @@ class _AllDramasFilterGridState extends State<AllDramasFilterGrid> {
? AppColors.filterChipTextSelected
: AppColors.filterChipTextInactive,
fontSize: AppDimens.filterChipFont,
fontWeight: selected ? FontWeight.w600 : FontWeight.w400,
fontWeight: FontWeight.w400,
height: 1,
),
),
@@ -6,7 +6,6 @@ import '../../../core/data/models/drama.dart';
import '../../../core/data/models/playlist_detail.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/theme/app_dimens.dart';
import '../../../core/theme/app_theme.dart';
import '../../../core/widgets/app_pull_to_refresh.dart';
import '../../../core/widgets/paged_list_footer.dart';
import '../../../core/widgets/remote_image.dart';
@@ -19,6 +18,8 @@ const int _collageCoverCount = 5;
const double _loadMoreThreshold = 200;
const double _pageHorizontal = 14;
const double _gridGap = 10;
// 标题两行39 + 上间距6 + 标签间距6 + 标签13 = 64,比 homeCardBodyExtent 更紧凑。
const double _cardBodyExtent = 64;
const double _sprocketBand = 22;
const Color _filmBody = Color(0xFF161311);
const Color _filmHole = Color(0xFFE6D3BE);
@@ -111,13 +112,23 @@ class _PlaylistDetailPageState extends State<PlaylistDetailPage> {
Widget build(BuildContext context) {
final width = MediaQuery.sizeOf(context).width;
final itemWidth = (width - _pageHorizontal * 2 - _gridGap) / 2;
final itemExtent =
itemWidth / AppDimens.cardCoverAspectRatio + AppDimens.homeCardBodyExtent;
final itemExtent = itemWidth / AppDimens.cardCoverAspectRatio + _cardBodyExtent;
return Scaffold(
key: const Key('playlist-detail-page'),
backgroundColor: AppColors.ink,
body: DecoratedBox(
decoration: const BoxDecoration(gradient: AppTheme.pageGradient),
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
AppColors.followingGlow,
AppColors.followingGlowMid,
AppColors.appBackground,
],
stops: [0, 0.14, 0.3],
),
),
child: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -176,7 +187,7 @@ class _PlaylistDetailPageState extends State<PlaylistDetailPage> {
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: _gridGap,
mainAxisSpacing: 16,
mainAxisSpacing: 10,
mainAxisExtent: itemExtent,
),
delegate: SliverChildBuilderDelegate(
@@ -228,7 +239,7 @@ class _PlaylistDetailPageState extends State<PlaylistDetailPage> {
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: AppDimens.recommendSectionTitle,
fontWeight: FontWeight.w700,
fontWeight: FontWeight.w400,
),
),
const SizedBox(height: 2),
@@ -5,13 +5,14 @@ import 'package:flutter/material.dart';
import '../../../core/data/models/content_category.dart';
import '../../../core/theme/app_colors.dart';
import '../../../l10n/app_localizations.dart';
import 'search_gift_glyphs.dart';
const double _navHeight = 56;
const double _tabGap = 20;
const double _tabFont = 17;
const double _searchHitSize = 40;
const double _searchIconSize = 26;
const double _giftIconSize = 30;
const double _giftIconSize = 26;
const double _underlineWidth = 26;
const double _underlineHeight = 3;
@@ -146,7 +147,7 @@ class _RecommendChannelNavState extends State<RecommendChannelNav> {
),
iconSize: _searchIconSize,
color: AppColors.textPrimary,
icon: const Icon(Icons.search_rounded),
icon: SearchGlyph(size: _searchIconSize, color: AppColors.textPrimary),
),
),
if (widget.onGift != null)
@@ -162,12 +163,7 @@ class _RecommendChannelNavState extends State<RecommendChannelNav> {
height: _searchHitSize,
),
iconSize: _giftIconSize,
icon: Image.asset(
'assets/icon/gift.png',
width: _giftIconSize,
height: _giftIconSize,
fit: BoxFit.contain,
),
icon: GiftGlyph(size: _giftIconSize),
),
),
const SizedBox(width: 8),
@@ -200,6 +196,7 @@ class _RecommendChannelNavState extends State<RecommendChannelNav> {
fontSize: _tabFont,
fontWeight: selected ? FontWeight.w700 : FontWeight.w400,
letterSpacing: 0,
height: 1.0,
shadows: const [
Shadow(color: AppColors.mediaBlack, blurRadius: 6),
],
@@ -92,7 +92,7 @@ class RecommendSectionsView extends StatelessWidget {
dramas: hits.take(_sectionPreviewCount).toList(),
onMore: () => onMore(RecommendSectionKind.hits),
),
const SizedBox(height: AppDimens.spaceXl),
const SizedBox(height: AppDimens.recommendSectionGap),
_section(
context,
l10n,
@@ -102,7 +102,7 @@ class RecommendSectionsView extends StatelessWidget {
dramas: newest.take(_sectionPreviewCount).toList(),
onMore: () => onMore(RecommendSectionKind.newReleases),
),
const SizedBox(height: AppDimens.spaceXl),
const SizedBox(height: AppDimens.recommendSectionGap),
_section(
context,
l10n,
@@ -150,7 +150,7 @@ class RecommendSectionsView extends StatelessWidget {
final sections = <Widget>[];
for (final playlist in remote.items) {
if (sections.isNotEmpty) {
sections.add(const SizedBox(height: AppDimens.spaceXl));
sections.add(const SizedBox(height: AppDimens.recommendSectionGap));
}
sections.add(
_section(
@@ -238,7 +238,7 @@ class RecommendSectionsView extends StatelessWidget {
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: AppDimens.recommendSectionTitle,
fontWeight: FontWeight.w700,
fontWeight: FontWeight.w400,
),
),
const SizedBox(height: 2),
@@ -277,7 +277,7 @@ class RecommendSectionsView extends StatelessWidget {
],
),
),
const SizedBox(height: AppDimens.spaceMd),
const SizedBox(height: AppDimens.spaceXs),
SizedBox(
height: AppDimens.recommendRowHeight,
child: ListView.separated(
@@ -5,13 +5,14 @@ import 'package:flutter/material.dart';
import '../../../core/theme/app_colors.dart';
import '../../../l10n/app_localizations.dart';
import '../recommend_types.dart';
import 'search_gift_glyphs.dart';
const double _navHeight = 56;
const double _tabGap = 20;
const double _tabFont = 17;
const double _searchHitSize = 40;
const double _searchIconSize = 26;
const double _giftIconSize = 30;
const double _giftIconSize = 26;
const double _underlineWidth = 26;
const double _underlineHeight = 3;
@@ -87,7 +88,7 @@ class RecommendTopNav extends StatelessWidget {
),
iconSize: _searchIconSize,
color: AppColors.textPrimary,
icon: const Icon(Icons.search_rounded),
icon: SearchGlyph(size: _searchIconSize, color: AppColors.textPrimary),
),
),
if (onGift != null)
@@ -103,12 +104,7 @@ class RecommendTopNav extends StatelessWidget {
height: _searchHitSize,
),
iconSize: _giftIconSize,
icon: Image.asset(
'assets/icon/gift.png',
width: _giftIconSize,
height: _giftIconSize,
fit: BoxFit.contain,
),
icon: GiftGlyph(size: _giftIconSize),
),
),
const SizedBox(width: 8),
@@ -142,6 +138,7 @@ class RecommendTopNav extends StatelessWidget {
fontSize: _tabFont,
fontWeight: selected ? FontWeight.w700 : FontWeight.w400,
letterSpacing: 0,
height: 1.0,
shadows: shadows,
),
),
@@ -0,0 +1,78 @@
// Author: fruit.com.cn
import 'dart:math' as math;
import 'package:flutter/material.dart';
/// 首页右上角搜索/礼物图标的自定义样式:
/// 搜索为粗线条放大镜(Material 图标无法调线条粗细),礼物为 emoji 红金礼盒。
/// 粗线条白色放大镜,圆环在左上、手柄朝右下。
class SearchGlyph extends StatelessWidget {
const SearchGlyph({super.key, required this.size, required this.color});
final double size;
final Color color;
@override
Widget build(BuildContext context) {
return CustomPaint(
size: Size.square(size),
painter: _SearchPainter(color: color),
);
}
}
class _SearchPainter extends CustomPainter {
const _SearchPainter({required this.color});
final Color color;
@override
void paint(Canvas canvas, Size size) {
final stroke = size.width * 0.09;
final paint = Paint()
..color = color
..style = PaintingStyle.stroke
..strokeWidth = stroke
..strokeCap = StrokeCap.round;
final circleDiameter = size.width * 0.7;
final direction = math.pi / 4;
// 图形只占画布左上角,先平移让它视觉居中,否则会比文字基线偏高。
final drawnExtent =
circleDiameter / 2 +
(circleDiameter / 2 + size.width * 0.12) * math.sin(direction) +
stroke / 2;
final centering = (size.width - drawnExtent) / 2;
canvas.translate(centering, centering);
final center = Offset(circleDiameter / 2, circleDiameter / 2);
canvas.drawCircle(center, circleDiameter / 2 - stroke / 2, paint);
final start = center + Offset.fromDirection(direction, circleDiameter / 2 - stroke * 0.2);
final end = center + Offset.fromDirection(direction, circleDiameter / 2 + size.width * 0.12);
canvas.drawLine(start, end, paint);
}
@override
bool shouldRepaint(covariant _SearchPainter oldDelegate) =>
oldDelegate.color != color;
}
/// emoji 礼盒(红盒金丝带,随系统 emoji 风格渲染)。
class GiftGlyph extends StatelessWidget {
const GiftGlyph({super.key, required this.size});
final double size;
@override
Widget build(BuildContext context) {
return SizedBox.square(
dimension: size,
child: FittedBox(
child: Text('🎁', style: TextStyle(fontSize: size, height: 1)),
),
);
}
}