feat(flutter): 去掉剧场封面角标并重做创作页

This commit is contained in:
Zhou Weidong committed 2026-10-20 15:21:54 +08:00
1 parent 72410b2a87
commit 6e9a93c920
11 files changed
+277 -74

No files matched your search

+2
View File
@@ -154,6 +154,8 @@
"createAiSubtitle": "Start from a story or an idea",
"createUploadTitle": "Upload and publish",
"createUploadSubtitle": "Publish a finished short drama",
"createMottoPrimary": "Your imagination",
"createMottoSecondary": "Can be seen by more people",
"weekdayMon": "Mon",
"weekdayTue": "Tue",
"weekdayWed": "Wed",
+2
View File
@@ -133,6 +133,8 @@
"createAiSubtitle": "Mulai dari cerita atau ide",
"createUploadTitle": "Unggah dan terbitkan",
"createUploadSubtitle": "Terbitkan drama pendek yang sudah selesai",
"createMottoPrimary": "Imajinasimu",
"createMottoSecondary": "Bisa dilihat lebih banyak orang",
"weekdayMon": "Sen",
"weekdayTue": "Sel",
"weekdayWed": "Rab",
+12
View File
@@ -664,6 +664,18 @@ abstract class AppLocalizations {
/// **'Publish a finished short drama'**
String get createUploadSubtitle;
/// No description provided for @createMottoPrimary.
///
/// In en, this message translates to:
/// **'Your imagination'**
String get createMottoPrimary;
/// No description provided for @createMottoSecondary.
///
/// In en, this message translates to:
/// **'Can be seen by more people'**
String get createMottoSecondary;
/// No description provided for @weekdayMon.
///
/// In en, this message translates to:
+6
View File
@@ -317,6 +317,12 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get createUploadSubtitle => 'Publish a finished short drama';
@override
String get createMottoPrimary => 'Your imagination';
@override
String get createMottoSecondary => 'Can be seen by more people';
@override
String get weekdayMon => 'Mon';
+6
View File
@@ -318,6 +318,12 @@ class AppLocalizationsId extends AppLocalizations {
String get createUploadSubtitle =>
'Terbitkan drama pendek yang sudah selesai';
@override
String get createMottoPrimary => 'Imajinasimu';
@override
String get createMottoSecondary => 'Bisa dilihat lebih banyak orang';
@override
String get weekdayMon => 'Sen';
+6
View File
@@ -312,6 +312,12 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get createUploadSubtitle => '发布已完成的漫剧';
@override
String get createMottoPrimary => '你的想象';
@override
String get createMottoSecondary => '可以被更多人看见';
@override
String get weekdayMon => '周一';
+2
View File
@@ -133,6 +133,8 @@
"createAiSubtitle": "从故事或想法开始",
"createUploadTitle": "上传发布",
"createUploadSubtitle": "发布已完成的漫剧",
"createMottoPrimary": "你的想象",
"createMottoSecondary": "可以被更多人看见",
"weekdayMon": "周一",
"weekdayTue": "周二",
"weekdayWed": "周三",
+233 -50
View File
@@ -3,11 +3,19 @@
import 'package:flutter/material.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/app_dimens.dart';
import '../../core/theme/app_theme.dart';
import '../../l10n/app_localizations.dart';
/// K28-B 创作页:AI 制作与上传发布入口。
const double _cardHeight = 196;
const double _cardRadius = 20;
const double _arrowSize = 36;
const double _illustrationWidth = 156;
const double _illustrationHeight = 88;
const double _mottoLineWidth = 96;
const List<Color> _aiGradientStart = [Color(0xFFFFE3D0), Color(0xFFFFC9AC)];
const List<Color> _uploadGradient = [Color(0xFFFFEAD9), Color(0xFFFFD3B3)];
/// 创作页:AI 制作与上传发布两张主卡,底部品牌标语。
class CreatePage extends StatelessWidget {
const CreatePage({super.key});
@@ -20,46 +28,72 @@ class CreatePage extends StatelessWidget {
bottom: false,
child: ListView(
key: const Key('create-page'),
padding: const EdgeInsets.fromLTRB(
AppDimens.pageHorizontal,
AppDimens.spaceXl,
AppDimens.pageHorizontal,
AppDimens.spaceXl,
),
padding: const EdgeInsets.fromLTRB(16, 28, 16, 24),
children: [
Text(
l10n.createTab,
style: const TextStyle(
fontFamily: 'Outfit',
color: AppColors.onSurface,
fontSize: AppDimens.heroTitle,
fontSize: 28,
fontWeight: FontWeight.w800,
),
),
const SizedBox(height: AppDimens.spaceSm),
const SizedBox(height: 6),
Text(
l10n.createSubtitle,
style: const TextStyle(
color: AppColors.muted,
fontSize: AppDimens.body,
),
style: const TextStyle(color: AppColors.muted, fontSize: 13),
),
const SizedBox(height: AppDimens.spaceXl),
_CreateEntry(
const SizedBox(height: 24),
_CreateCard(
key: const Key('create-ai'),
icon: Icons.auto_awesome_rounded,
title: l10n.createAiTitle,
subtitle: l10n.createAiSubtitle,
illustration: const _AiIllustration(),
onTap: () => _openPlaceholder(context, l10n.createAiTitle),
),
const SizedBox(height: AppDimens.spaceMd),
_CreateEntry(
const SizedBox(height: 16),
_CreateCard(
key: const Key('create-upload'),
icon: Icons.upload_file_rounded,
title: l10n.createUploadTitle,
subtitle: l10n.createUploadSubtitle,
illustration: const _UploadIllustration(),
onTap: () => _openPlaceholder(context, l10n.createUploadTitle),
),
const SizedBox(height: 32),
Text(
l10n.createMottoPrimary,
key: const Key('create-motto-primary'),
textAlign: TextAlign.center,
style: const TextStyle(
color: AppColors.onSurface,
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 4),
Text(
l10n.createMottoSecondary,
key: const Key('create-motto-secondary'),
textAlign: TextAlign.center,
style: const TextStyle(
color: AppColors.onSurface,
fontSize: 18,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 10),
Center(
child: Container(
key: const Key('create-motto-line'),
width: _mottoLineWidth,
height: 2,
decoration: BoxDecoration(
color: AppColors.blue,
borderRadius: BorderRadius.circular(1),
),
),
),
],
),
),
@@ -73,18 +107,18 @@ class CreatePage extends StatelessWidget {
}
}
class _CreateEntry extends StatelessWidget {
const _CreateEntry({
class _CreateCard extends StatelessWidget {
const _CreateCard({
super.key,
required this.icon,
required this.title,
required this.subtitle,
required this.illustration,
required this.onTap,
});
final IconData icon;
final String title;
final String subtitle;
final Widget illustration;
final VoidCallback onTap;
@override
@@ -92,57 +126,66 @@ class _CreateEntry extends StatelessWidget {
return Container(
decoration: BoxDecoration(
color: AppColors.card,
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
borderRadius: BorderRadius.circular(_cardRadius),
boxShadow: const [AppTheme.cardShadow],
),
child: Material(
color: Colors.transparent,
borderRadius: BorderRadius.circular(AppDimens.cardRadius),
borderRadius: BorderRadius.circular(_cardRadius),
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.all(AppDimens.cardPadding),
child: Row(
child: SizedBox(
height: _cardHeight,
child: Stack(
children: [
Container(
width: AppDimens.buttonLarge,
height: AppDimens.buttonLarge,
decoration: BoxDecoration(
color: AppColors.badgePink,
borderRadius: BorderRadius.circular(AppDimens.innerRadius),
),
child: Icon(icon, color: AppColors.secondary, size: 22),
),
const SizedBox(width: AppDimens.spaceMd),
Expanded(
Padding(
padding: const EdgeInsets.fromLTRB(20, 18, 68, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.onSurface,
fontSize: AppDimens.listTitle,
fontWeight: FontWeight.w700,
fontSize: 22,
fontWeight: FontWeight.w900,
),
),
const SizedBox(height: AppDimens.spaceXs),
const SizedBox(height: 6),
Text(
subtitle,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.muted,
fontSize: AppDimens.caption,
fontSize: 13,
),
),
],
),
),
const Icon(
Icons.chevron_right_rounded,
color: AppColors.subtle,
size: 20,
Positioned(
top: 16,
right: 16,
child: Container(
width: _arrowSize,
height: _arrowSize,
decoration: const BoxDecoration(
color: AppColors.raised,
shape: BoxShape.circle,
),
child: const Icon(
Icons.arrow_forward_rounded,
color: AppColors.blue,
size: 18,
),
),
),
Positioned(right: 14, bottom: 14, child: illustration),
],
),
),
@@ -152,6 +195,146 @@ class _CreateEntry extends StatelessWidget {
}
}
class _AiIllustration extends StatelessWidget {
const _AiIllustration();
@override
Widget build(BuildContext context) {
return Container(
width: _illustrationWidth,
height: _illustrationHeight,
decoration: BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: _aiGradientStart,
),
borderRadius: BorderRadius.circular(16),
),
child: Stack(
children: [
const Positioned(
left: 12,
top: 10,
child: Icon(
Icons.auto_awesome_rounded,
color: AppColors.secondary,
size: 30,
),
),
const Positioned(
right: 14,
top: 8,
child: Icon(
Icons.auto_awesome_rounded,
color: AppColors.blue,
size: 12,
),
),
Positioned(
right: 32,
top: 30,
child: Container(
width: 5,
height: 5,
decoration: BoxDecoration(
color: AppColors.blue.withValues(alpha: 0.55),
shape: BoxShape.circle,
),
),
),
const Positioned(
left: 12,
bottom: 10,
child: Text(
'Every Story Matters',
style: TextStyle(
color: AppColors.muted,
fontSize: 10,
fontWeight: FontWeight.w700,
),
),
),
],
),
);
}
}
class _UploadIllustration extends StatelessWidget {
const _UploadIllustration();
@override
Widget build(BuildContext context) {
return Container(
width: _illustrationWidth,
height: _illustrationHeight,
decoration: BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: _uploadGradient,
),
borderRadius: BorderRadius.circular(16),
),
child: Stack(
children: [
const Positioned(
left: 12,
top: 8,
child: Icon(
Icons.cloud_upload_rounded,
color: AppColors.secondary,
size: 30,
),
),
Positioned(
right: 16,
top: 12,
child: Container(
width: 30,
height: 20,
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.55),
borderRadius: BorderRadius.circular(4),
),
child: const Icon(
Icons.movie_creation_outlined,
color: AppColors.muted,
size: 12,
),
),
),
Positioned(
right: 52,
top: 34,
child: Container(
width: 5,
height: 5,
decoration: BoxDecoration(
color: AppColors.blue.withValues(alpha: 0.55),
shape: BoxShape.circle,
),
),
),
const Positioned(
left: 12,
bottom: 10,
child: Text(
'Ready to publish',
style: TextStyle(
color: AppColors.muted,
fontSize: 10,
fontWeight: FontWeight.w700,
),
),
),
],
),
);
}
}
class _CreatePlaceholderPage extends StatelessWidget {
const _CreatePlaceholderPage({required this.title});
@@ -173,12 +356,12 @@ class _CreatePlaceholderPage extends StatelessWidget {
color: AppColors.blue,
size: 42,
),
const SizedBox(height: AppDimens.spaceMd),
const SizedBox(height: 12),
Text(
l10n.comingSoon,
style: const TextStyle(
color: AppColors.onSurface,
fontSize: AppDimens.sectionTitle,
fontSize: 16,
fontWeight: FontWeight.w700,
),
),
@@ -23,7 +23,6 @@ const double _tagChipFontSize = 11;
const double _heatFontSize = 9;
const double _heatIconSize = 11;
const int _maxTags = 3;
const Color _freeBadgeColor = Color(0xFF12B76A);
const Color _heatBackground = Color(0x99000000);
const Color _tagChipBackground = Color(0xFFF4F1EE);
final RegExp _tagSplitter = RegExp(r'[,/、·]');
@@ -86,9 +85,6 @@ class DramaGridCard extends StatelessWidget {
final Drama drama;
final VoidCallback onTap;
bool get _isFree =>
drama.freeEpisodes > 0 || drama.episodes.any((episode) => episode.isFree);
@override
Widget build(BuildContext context) {
return GestureDetector(
@@ -118,7 +114,6 @@ class DramaGridCard extends StatelessWidget {
fit: StackFit.expand,
children: [
_DiscoverCoverImage(path: drama.posterUrl),
Positioned(top: 8, left: 8, child: _tagBadge()),
Positioned(left: 8, bottom: 8, child: _heatBadge()),
],
),
@@ -126,25 +121,6 @@ class DramaGridCard extends StatelessWidget {
);
}
Widget _tagBadge() {
return Container(
key: Key('drama-tag-${drama.id}'),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: _freeBadgeColor,
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
),
child: Text(
_isFree ? l10n.free : l10n.originalTag,
style: const TextStyle(
color: AppColors.onPrimary,
fontSize: 10,
fontWeight: FontWeight.w700,
),
),
);
}
Widget _heatBadge() {
return Container(
key: Key('drama-heat-${drama.id}'),