feat(flutter): 应用浅色暖橙视觉主题
This commit is contained in:
1 parent
0d2367431d
commit
634b63e16c
56 files changed
+941
-409
No files matched your search
@@ -8,7 +8,7 @@ import '../../../l10n/app_localizations.dart';
|
||||
|
||||
const double _searchHeight = 36;
|
||||
|
||||
/// Fixed home header with catalogue search, weekday, and profile entry.
|
||||
/// 首页固定顶栏:搜索、星期与头像入口。
|
||||
class HomeBrowseHeader extends StatelessWidget {
|
||||
const HomeBrowseHeader({
|
||||
super.key,
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
|
||||
import '../../../core/data/models/drama.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import '../../../core/theme/app_dimens.dart';
|
||||
import '../../../core/theme/app_theme.dart';
|
||||
import '../../../core/widgets/remote_image.dart';
|
||||
import '../../../l10n/app_localizations.dart';
|
||||
|
||||
@@ -12,8 +13,10 @@ const int _columns = 2;
|
||||
const double _columnGap = 10;
|
||||
const double _cardGap = 10;
|
||||
const double _coverAspectRatio = 3 / 4;
|
||||
const double _addSize = 28;
|
||||
const double _addIconSize = 18;
|
||||
|
||||
/// Two-column drama grid used by the browse-first home page.
|
||||
/// 首页浏览态使用的双列剧目网格。
|
||||
class HomeDramaGrid extends StatelessWidget {
|
||||
const HomeDramaGrid({
|
||||
super.key,
|
||||
@@ -57,7 +60,7 @@ class HomeDramaGrid extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Home catalogue card with title, genre, free badge, episode count, and heat.
|
||||
/// 首页剧卡:封面、角标、标题、题材、集数与热度。
|
||||
class HomeDramaCard extends StatelessWidget {
|
||||
const HomeDramaCard({
|
||||
super.key,
|
||||
@@ -72,21 +75,35 @@ class HomeDramaCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
key: Key('home-drama-card-${drama.id}'),
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(AppDimens.coverRadius),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_cover(),
|
||||
const SizedBox(height: AppDimens.spaceXs),
|
||||
_title(),
|
||||
const SizedBox(height: 2),
|
||||
_genre(),
|
||||
const SizedBox(height: AppDimens.spaceXs),
|
||||
_metadata(),
|
||||
],
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.card,
|
||||
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
|
||||
boxShadow: const [AppTheme.cardShadow],
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
key: Key('home-drama-card-${drama.id}'),
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(AppDimens.spaceSm),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_cover(),
|
||||
const SizedBox(height: AppDimens.spaceSm),
|
||||
_title(),
|
||||
const SizedBox(height: AppDimens.spaceXs),
|
||||
_genre(),
|
||||
const SizedBox(height: AppDimens.spaceXs),
|
||||
_metadata(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -104,7 +121,10 @@ class HomeDramaCard extends StatelessWidget {
|
||||
showFallbackIcon: true,
|
||||
fallbackAspectRatio: _coverAspectRatio,
|
||||
),
|
||||
if (_isFree) Positioned(top: 6, left: 6, child: _freeBadge()),
|
||||
Positioned(top: 8, left: 8, child: _tagBadge()),
|
||||
if (_lockedCount > 0)
|
||||
Positioned(top: 8, right: 8, child: _lockedBadge()),
|
||||
Positioned(right: 8, bottom: 8, child: _episodeBadge()),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -115,17 +135,29 @@ class HomeDramaCard extends StatelessWidget {
|
||||
drama.episodes.any((episode) => episode.isFree);
|
||||
}
|
||||
|
||||
Widget _freeBadge() {
|
||||
int get _lockedCount {
|
||||
final explicit = drama.episodes.where((episode) => episode.locked).length;
|
||||
if (explicit > 0) return explicit;
|
||||
final inferred = drama.episodeCount - drama.freeEpisodes;
|
||||
return inferred > 0 ? inferred : 0;
|
||||
}
|
||||
|
||||
Widget _tagBadge() {
|
||||
final label = _isFree ? l10n.free : l10n.originalTag;
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
key: Key('home-drama-tag-${drama.id}'),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: AppDimens.spaceSm,
|
||||
vertical: 3,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.blue,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
color: AppColors.badgePink,
|
||||
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
|
||||
),
|
||||
child: Text(
|
||||
l10n.free,
|
||||
label,
|
||||
style: const TextStyle(
|
||||
color: AppColors.onPrimary,
|
||||
color: AppColors.badgePinkText,
|
||||
fontSize: AppDimens.cardMeta,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
@@ -133,6 +165,51 @@ class HomeDramaCard extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _lockedBadge() {
|
||||
return Container(
|
||||
key: Key('home-drama-locked-${drama.id}'),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.overlayDark,
|
||||
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.lock_rounded, color: AppColors.onPrimary, size: 11),
|
||||
const SizedBox(width: 2),
|
||||
Text(
|
||||
l10n.lockedEpisodeCount(_lockedCount),
|
||||
style: const TextStyle(
|
||||
color: AppColors.onPrimary,
|
||||
fontSize: 9,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _episodeBadge() {
|
||||
return Container(
|
||||
key: Key('home-drama-episodes-badge-${drama.id}'),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.badgeBeige,
|
||||
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
|
||||
),
|
||||
child: Text(
|
||||
l10n.allEpisodesBadge(drama.episodeCount),
|
||||
style: const TextStyle(
|
||||
color: AppColors.onSurface,
|
||||
fontSize: 9,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _title() {
|
||||
return Text(
|
||||
drama.title,
|
||||
@@ -141,7 +218,7 @@ class HomeDramaCard extends StatelessWidget {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: AppColors.onSurface,
|
||||
fontSize: AppDimens.previewSectionTitle,
|
||||
fontSize: AppDimens.cardTitle,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
);
|
||||
@@ -162,24 +239,44 @@ class HomeDramaCard extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget _metadata() {
|
||||
return Wrap(
|
||||
spacing: AppDimens.spaceSm,
|
||||
runSpacing: AppDimens.spaceXs,
|
||||
return Row(
|
||||
children: [
|
||||
Text(
|
||||
l10n.episodeCount(drama.episodeCount),
|
||||
key: Key('home-drama-episode-count-${drama.id}'),
|
||||
style: const TextStyle(
|
||||
color: AppColors.subtle,
|
||||
fontSize: AppDimens.cardViews,
|
||||
Expanded(
|
||||
child: Wrap(
|
||||
spacing: AppDimens.spaceSm,
|
||||
runSpacing: AppDimens.spaceXs,
|
||||
children: [
|
||||
Text(
|
||||
l10n.episodeCount(drama.episodeCount),
|
||||
key: Key('home-drama-episode-count-${drama.id}'),
|
||||
style: const TextStyle(
|
||||
color: AppColors.subtle,
|
||||
fontSize: AppDimens.cardViews,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
l10n.viewCountLabel(drama.viewCount),
|
||||
key: Key('home-drama-views-${drama.id}'),
|
||||
style: const TextStyle(
|
||||
color: AppColors.subtle,
|
||||
fontSize: AppDimens.cardViews,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text(
|
||||
l10n.viewCountLabel(drama.viewCount),
|
||||
key: Key('home-drama-views-${drama.id}'),
|
||||
style: const TextStyle(
|
||||
color: AppColors.subtle,
|
||||
fontSize: AppDimens.cardViews,
|
||||
Container(
|
||||
key: Key('home-drama-add-${drama.id}'),
|
||||
width: _addSize,
|
||||
height: _addSize,
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.blue,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.add_rounded,
|
||||
color: AppColors.onPrimary,
|
||||
size: _addIconSize,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
Reference in new issue
Block a user