fix(ui): 修正六屏顶栏与内容间距

This commit is contained in:
Zhou Weidong committed 2026-10-19 16:25:25 +08:00
1 parent e03ea76505
commit 2fd92cdbea
11 files changed
+281 -66

No files matched your search

+7 -7
View File
@@ -257,7 +257,7 @@ class _DiscoverPageState extends State<DiscoverPage> {
tooltip: l10n.search,
onTap: logic.openSearch,
),
const SizedBox(width: 7),
const SizedBox(width: 8),
Semantics(
button: true,
label: widget.signedIn ? l10n.myTab : l10n.logIn,
@@ -266,8 +266,8 @@ class _DiscoverPageState extends State<DiscoverPage> {
customBorder: const CircleBorder(),
onTap: widget.signedIn ? widget.onCoins : widget.onSignIn,
child: Container(
width: 28,
height: 28,
width: 36,
height: 36,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: const LinearGradient(
@@ -279,7 +279,7 @@ class _DiscoverPageState extends State<DiscoverPage> {
child: const Icon(
Icons.person_rounded,
color: AppColors.onSurface,
size: 15,
size: 18,
),
),
),
@@ -601,8 +601,8 @@ class _HeaderIcon extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SizedBox(
width: 28,
height: 28,
width: 36,
height: 36,
child: IconButton(
tooltip: tooltip,
onPressed: onTap,
@@ -611,7 +611,7 @@ class _HeaderIcon extends StatelessWidget {
backgroundColor: const Color(0xFF2C2D33),
foregroundColor: AppColors.onSurface,
),
icon: Icon(icon, size: 14),
icon: Icon(icon, size: 18),
),
);
}
@@ -435,8 +435,8 @@ class _DramaDetailPageState extends State<DramaDetailPage> {
),
],
child: Container(
width: 28,
height: 28,
width: 36,
height: 36,
decoration: const BoxDecoration(
color: Color(0x80000000),
shape: BoxShape.circle,
@@ -536,8 +536,8 @@ class _RoundAction extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
width: 28,
height: 28,
width: 36,
height: 36,
decoration: const BoxDecoration(
color: Color(0x80000000),
shape: BoxShape.circle,
+57 -32
View File
@@ -185,6 +185,7 @@ class _MyPageState extends State<MyPage> {
),
const Spacer(),
_RoundHeaderButton(
key: const Key('mine-search-button'),
tooltip: l10n.search,
icon: Icons.search_rounded,
onTap: () => logic.act(
@@ -194,10 +195,11 @@ class _MyPageState extends State<MyPage> {
onAction: widget.onAction,
),
),
const SizedBox(width: 7),
const SizedBox(width: 8),
Container(
width: 28,
height: 28,
key: const Key('mine-avatar'),
width: 36,
height: 36,
decoration: const BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
@@ -209,7 +211,7 @@ class _MyPageState extends State<MyPage> {
child: const Icon(
Icons.person_rounded,
color: AppColors.onSurface,
size: 15,
size: 18,
),
),
],
@@ -455,37 +457,59 @@ class _MyPageState extends State<MyPage> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
for (var index = 0; index < tabs.length; index++)
Expanded(
child: Semantics(
selected: logic.section == index,
button: true,
child: InkWell(
key: Key('mine-library-tab-$index'),
onTap: () => logic.selectSection(index),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Text(
tabs[index],
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: logic.section == index
? AppColors.blue
: const Color(0xFFE4DBDD),
fontSize: 14,
fontWeight: FontWeight.w800,
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
for (var index = 0; index < tabs.length; index++)
Padding(
padding: EdgeInsets.only(
right: index == tabs.length - 1 ? 0 : 20,
),
child: Semantics(
selected: logic.section == index,
button: true,
child: InkWell(
key: Key('mine-library-tab-$index'),
onTap: () => logic.selectSection(index),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
tabs[index],
maxLines: 1,
style: TextStyle(
color: logic.section == index
? AppColors.blue
: const Color(0xFFE4DBDD),
fontSize: 14,
fontWeight: FontWeight.w800,
),
),
const SizedBox(height: 4),
AnimatedContainer(
duration: const Duration(milliseconds: 160),
width: 18,
height: 2,
decoration: BoxDecoration(
color: logic.section == index
? AppColors.blue
: Colors.transparent,
borderRadius: BorderRadius.circular(1),
),
),
],
),
),
),
),
),
),
],
],
),
),
const SizedBox(height: 10),
const SizedBox(height: 12),
if (logic.section == 1 && context.watch<FavoriteController>().loading)
const Center(
child: Padding(
@@ -935,6 +959,7 @@ class _MyPageState extends State<MyPage> {
class _RoundHeaderButton extends StatelessWidget {
const _RoundHeaderButton({
super.key,
required this.tooltip,
required this.icon,
required this.onTap,
@@ -947,8 +972,8 @@ class _RoundHeaderButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SizedBox(
width: 28,
height: 28,
width: 36,
height: 36,
child: IconButton(
tooltip: tooltip,
onPressed: onTap,
@@ -957,7 +982,7 @@ class _RoundHeaderButton extends StatelessWidget {
backgroundColor: const Color(0xFF2C2D33),
foregroundColor: AppColors.onSurface,
),
icon: Icon(icon, size: 14),
icon: Icon(icon, size: 18),
),
);
}
+4 -4
View File
@@ -803,7 +803,7 @@ class _PlaybackOverlayState extends State<_PlaybackOverlay> {
onPressed: () => _run(widget.onBack!),
)
else
const SizedBox(width: 28),
const SizedBox(width: 36),
const SizedBox(width: 8),
Expanded(
child: Text(
@@ -926,8 +926,8 @@ class _TopAction extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SizedBox(
width: 28,
height: 28,
width: 36,
height: 36,
child: IconButton(
tooltip: tooltip,
onPressed: onPressed,
@@ -936,7 +936,7 @@ class _TopAction extends StatelessWidget {
backgroundColor: const Color(0xFF2C2D33),
foregroundColor: AppColors.onSurface,
),
icon: Icon(icon, size: 15),
icon: Icon(icon, size: 18),
),
);
}
@@ -95,7 +95,7 @@ class HomeFeedOverlay extends StatelessWidget {
icon: Icons.search_rounded,
onPressed: onSearch,
),
const SizedBox(width: 6),
const SizedBox(width: 8),
_RoundIcon(
key: const Key('home-feed-more'),
tooltip: l10n.more,
@@ -147,7 +147,7 @@ class HomeFeedOverlay extends StatelessWidget {
key: const Key('home-feed-info-card'),
radius: 14,
blur: 6,
padding: const EdgeInsets.fromLTRB(12, 11, 12, 11),
padding: const EdgeInsets.all(12),
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
@@ -180,7 +180,7 @@ class HomeFeedOverlay extends StatelessWidget {
],
),
),
const SizedBox(width: 6),
const SizedBox(width: 8),
Flexible(
child: Text(
l10n.episodeOfTotal(episode.number, drama.episodeCount),
@@ -193,9 +193,11 @@ class HomeFeedOverlay extends StatelessWidget {
),
],
),
const SizedBox(height: 7),
const SizedBox(height: 8),
Text(
localizedEpisodeTitle(l10n, episode.number),
episode.title.trim().isEmpty
? localizedDramaTitle(l10n, drama)
: episode.title,
key: const Key('home-feed-episode-title'),
maxLines: 1,
overflow: TextOverflow.ellipsis,
@@ -352,8 +354,8 @@ class _RoundIcon extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SizedBox(
width: 28,
height: 28,
width: 36,
height: 36,
child: IconButton(
tooltip: tooltip,
onPressed: onPressed,
@@ -362,7 +364,7 @@ class _RoundIcon extends StatelessWidget {
backgroundColor: const Color(0xCC2C2D33),
foregroundColor: AppColors.onSurface,
),
icon: Icon(icon, size: 14),
icon: Icon(icon, size: 18),
),
);
}
@@ -39,6 +39,23 @@ void main() {
expect(find.text('Continue Watching'), findsNothing);
expect(find.text('Recommended'), findsOneWidget);
for (final key in const [
Key('discover-search-button'),
Key('discover-profile'),
]) {
expect(tester.getSize(find.byKey(key)), const Size(36, 36));
expect(
tester
.widget<Icon>(
find.descendant(
of: find.byKey(key),
matching: find.byType(Icon),
),
)
.size,
18,
);
}
expect(find.text('For You'), findsNothing);
final grid = tester.widget<SliverGrid>(
find.byKey(const Key('home-all-dramas-grid')),
@@ -33,6 +33,21 @@ void main() {
await tester.pumpAndSettle();
expect(tester.getSize(find.byKey(const Key('detail-hero'))).height, 190);
expect(
tester.getSize(find.byKey(const Key('detail-back'))),
const Size(36, 36),
);
expect(
tester
.widget<Icon>(
find.descendant(
of: find.byKey(const Key('detail-back')),
matching: find.byType(Icon),
),
)
.size,
18,
);
expect(find.byKey(const Key('detail-cta')), findsOneWidget);
expect(tester.getSize(find.byKey(const Key('detail-cta'))).height, 40);
expect(find.byKey(const Key('detail-episode-chip-1')), findsOneWidget);
+82
View File
@@ -6,9 +6,13 @@ import 'package:shared_preferences_platform_interface/in_memory_shared_preferenc
import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart';
import 'package:fruitshorts/app.dart';
import 'package:fruitshorts/core/data/models/drama.dart';
import 'package:fruitshorts/core/data/repositories/favorite_repository.dart';
import 'package:fruitshorts/core/data/repositories/playback_repository.dart';
import 'package:fruitshorts/core/data/repositories/watch_history_repository.dart';
import 'package:fruitshorts/core/theme/app_theme.dart';
import 'package:fruitshorts/l10n/app_localizations.dart';
import 'package:fruitshorts/pages/player/widgets/home_feed_overlay.dart';
import '../support/fake_catalog.dart';
import '../support/fake_category.dart';
@@ -52,6 +56,17 @@ void main() {
expect(find.byKey(const Key('home-feed-episode')), findsOneWidget);
expect(find.byKey(const Key('home-feed-follow-series')), findsOneWidget);
expect(find.byKey(const Key('home-feed-episodes')), findsOneWidget);
for (final key in const [Key('home-feed-search'), Key('home-feed-more')]) {
expect(tester.getSize(find.byKey(key)), const Size(36, 36));
expect(
tester
.widget<Icon>(
find.descendant(of: find.byKey(key), matching: find.byType(Icon)),
)
.size,
18,
);
}
expect(
tester.getSize(find.byKey(const Key('home-feed-follow-series'))).height,
34,
@@ -61,4 +76,71 @@ void main() {
expect(find.byKey(const Key('home-feed-follow')), findsNothing);
expect(tester.takeException(), isNull);
});
testWidgets('should_use_episode_title_then_drama_title_as_fallback', (
tester,
) async {
Future<void> pumpEpisode(Episode episode) async {
await tester.pumpWidget(
MaterialApp(
theme: AppTheme.dark(),
locale: const Locale('en'),
supportedLocales: AppLocalizations.supportedLocales,
localizationsDelegates: AppLocalizations.localizationsDelegates,
home: Builder(
builder: (context) => Scaffold(
body: HomeFeedOverlay(
l10n: AppLocalizations.of(context)!,
drama: _drama,
episode: episode,
liked: false,
saved: false,
onLike: () {},
onSave: () {},
onComments: () {},
onShare: () {},
onEpisodes: () {},
onDownload: () {},
onSearch: () {},
),
),
),
),
);
await tester.pumpAndSettle();
}
await pumpEpisode(
const Episode(
id: 'featured',
number: 2,
title: 'Midnight Contract',
isFree: true,
),
);
expect(
tester
.widget<Text>(find.byKey(const Key('home-feed-episode-title')))
.data,
'Midnight Contract',
);
await pumpEpisode(
const Episode(id: 'fallback', number: 2, title: '', isFree: true),
);
expect(
tester
.widget<Text>(find.byKey(const Key('home-feed-episode-title')))
.data,
'Fallback Drama',
);
});
}
const _drama = Drama(
id: 'featured-drama',
title: 'Fallback Drama',
synopsis: 'synopsis',
posterUrl: '',
totalEpisodes: 8,
);
+47
View File
@@ -10,6 +10,7 @@ import 'package:fruitshorts/core/data/models/watch_progress.dart';
import 'package:fruitshorts/core/data/repositories/favorite_repository.dart';
import 'package:fruitshorts/core/data/repositories/playback_repository.dart';
import 'package:fruitshorts/core/data/repositories/watch_history_repository.dart';
import 'package:fruitshorts/core/theme/app_colors.dart';
import '../support/fake_catalog.dart';
import '../support/fake_category.dart';
@@ -59,11 +60,57 @@ void main() {
expect(find.byKey(const Key('mine-user-row')), findsOneWidget);
expect(find.byKey(const Key('mine-wallet')), findsOneWidget);
for (final key in const [Key('mine-search-button'), Key('mine-avatar')]) {
expect(tester.getSize(find.byKey(key)), const Size(36, 36));
expect(
tester
.widget<Icon>(
find.descendant(of: find.byKey(key), matching: find.byType(Icon)),
)
.size,
18,
);
}
expect(find.byKey(const Key('mine-continue-watching')), findsOneWidget);
expect(find.byKey(const Key('mine-language')), findsOneWidget);
expect(find.byKey(const Key('mine-library-tab-0')), findsOneWidget);
expect(find.byKey(const Key('mine-library-tab-1')), findsOneWidget);
expect(find.byKey(const Key('mine-library-tab-2')), findsOneWidget);
final tab0 = find.byKey(const Key('mine-library-tab-0'));
final tab1 = find.byKey(const Key('mine-library-tab-1'));
final tab2 = find.byKey(const Key('mine-library-tab-2'));
expect(tester.getTopLeft(tab0).dx, lessThan(tester.getTopLeft(tab1).dx));
expect(tester.getTopLeft(tab1).dx, lessThan(tester.getTopLeft(tab2).dx));
expect(
tester.getTopLeft(tab1).dx - tester.getTopRight(tab0).dx,
closeTo(20, 1),
);
expect(
(tester
.widget<AnimatedContainer>(
find.descendant(
of: tab0,
matching: find.byType(AnimatedContainer),
),
)
.decoration
as BoxDecoration)
.color,
AppColors.blue,
);
expect(
(tester
.widget<AnimatedContainer>(
find.descendant(
of: tab1,
matching: find.byType(AnimatedContainer),
),
)
.decoration
as BoxDecoration)
.color,
Colors.transparent,
);
expect(tester.takeException(), isNull);
});
}
+30 -6
View File
@@ -34,7 +34,7 @@ void main() {
await tester.tap(find.byKey(const Key('home-drama-card-city-lights-demo')));
await tester.pumpAndSettle();
_expectFullscreenOverlay();
_expectFullscreenOverlay(tester);
expect(find.byType(PageView), findsOneWidget);
await tester.tap(find.byKey(const Key('player-more')));
await tester.pumpAndSettle();
@@ -56,13 +56,13 @@ void main() {
await tester.tap(find.byKey(const Key('detail-cta')));
await tester.pumpAndSettle();
_expectFullscreenOverlay();
_expectFullscreenOverlay(tester);
await tester.binding.handlePopRoute();
await tester.pumpAndSettle();
await tester.tap(find.byKey(const Key('detail-episode-chip-2')));
await tester.pumpAndSettle();
_expectFullscreenOverlay();
_expectFullscreenOverlay(tester);
expect(
tester.widget<Text>(find.byKey(const Key('player-title'))).data,
contains('Episode 2'),
@@ -156,12 +156,33 @@ void main() {
isFalse,
);
});
testWidgets('should_fit_player_header_on_320_width', (tester) async {
await _pumpApp(tester, size: const Size(320, 568));
await tester.binding.handlePopRoute();
await tester.pumpAndSettle();
await tester.binding.handlePopRoute();
await tester.pumpAndSettle();
await tester.tap(find.byKey(const Key('drawer-open-series')));
await tester.pumpAndSettle();
await tester.tap(find.byKey(const Key('detail-cta')));
await tester.pumpAndSettle();
expect(
tester.getSize(find.byKey(const Key('player-back'))),
const Size(36, 36),
);
expect(tester.takeException(), isNull);
});
}
void _expectFullscreenOverlay() {
void _expectFullscreenOverlay(WidgetTester tester) {
expect(find.byKey(const Key('player-back')), findsOneWidget);
expect(find.byKey(const Key('player-title')), findsOneWidget);
expect(find.byKey(const Key('player-more')), findsOneWidget);
for (final key in const [Key('player-back'), Key('player-more')]) {
expect(tester.getSize(find.byKey(key)), const Size(36, 36));
}
expect(find.byKey(const Key('player-play-toggle')), findsOneWidget);
expect(find.byKey(const Key('player-episodes')), findsOneWidget);
expect(find.byKey(const Key('player-comments')), findsOneWidget);
@@ -172,8 +193,11 @@ void _expectFullscreenOverlay() {
expect(find.byKey(const Key('rail-save')), findsNothing);
}
Future<void> _pumpApp(WidgetTester tester) async {
tester.view.physicalSize = const Size(390, 844);
Future<void> _pumpApp(
WidgetTester tester, {
Size size = const Size(390, 844),
}) async {
tester.view.physicalSize = size;
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
+8 -5
View File
@@ -319,10 +319,8 @@ void main() {
await tester.tap(find.byKey(const Key('tab-my')));
await tester.pumpAndSettle();
await tester.scrollUntilVisible(
find.byKey(const Key('mine-library-tab-1')),
260,
);
await tester.drag(find.byType(ListView), const Offset(0, -320));
await tester.pumpAndSettle();
await tester.tap(find.byKey(const Key('mine-library-tab-1')));
await tester.pumpAndSettle();
expect(find.text('No shows here yet.'), findsOneWidget);
@@ -409,7 +407,12 @@ void main() {
await tester.drag(find.byType(PageView), const Offset(0, -550));
await tester.pumpAndSettle();
expect(find.text('City Lights & Velvet Nights'), findsOneWidget);
expect(find.textContaining('Episode 1'), findsOneWidget);
expect(
tester
.widget<Text>(find.byKey(const Key('home-feed-episode-title')))
.data,
contains('Episode 1'),
);
await tester.tap(find.byKey(const Key('tab-discover')));
await tester.pumpAndSettle();