diff --git a/lib/pages/discover/discover_view.dart b/lib/pages/discover/discover_view.dart index c468231..f8122c6 100644 --- a/lib/pages/discover/discover_view.dart +++ b/lib/pages/discover/discover_view.dart @@ -257,7 +257,7 @@ class _DiscoverPageState extends State { 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 { 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 { 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), ), ); } diff --git a/lib/pages/drama_detail/drama_detail_view.dart b/lib/pages/drama_detail/drama_detail_view.dart index fc8d774..2cdfe14 100644 --- a/lib/pages/drama_detail/drama_detail_view.dart +++ b/lib/pages/drama_detail/drama_detail_view.dart @@ -435,8 +435,8 @@ class _DramaDetailPageState extends State { ), ], 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, diff --git a/lib/pages/mine/mine_view.dart b/lib/pages/mine/mine_view.dart index 3a21c53..bc1e7a5 100644 --- a/lib/pages/mine/mine_view.dart +++ b/lib/pages/mine/mine_view.dart @@ -185,6 +185,7 @@ class _MyPageState extends State { ), 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 { 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 { child: const Icon( Icons.person_rounded, color: AppColors.onSurface, - size: 15, + size: 18, ), ), ], @@ -455,37 +457,59 @@ class _MyPageState extends State { 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().loading) const Center( child: Padding( @@ -935,6 +959,7 @@ class _MyPageState extends State { 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), ), ); } diff --git a/lib/pages/player/player_view.dart b/lib/pages/player/player_view.dart index 5b757ed..7649661 100644 --- a/lib/pages/player/player_view.dart +++ b/lib/pages/player/player_view.dart @@ -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), ), ); } diff --git a/lib/pages/player/widgets/home_feed_overlay.dart b/lib/pages/player/widgets/home_feed_overlay.dart index c6653ba..30eadf0 100644 --- a/lib/pages/player/widgets/home_feed_overlay.dart +++ b/lib/pages/player/widgets/home_feed_overlay.dart @@ -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), ), ); } diff --git a/test/discover/home_all_dramas_grid_test.dart b/test/discover/home_all_dramas_grid_test.dart index 79d4be9..f7b747a 100644 --- a/test/discover/home_all_dramas_grid_test.dart +++ b/test/discover/home_all_dramas_grid_test.dart @@ -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( + find.descendant( + of: find.byKey(key), + matching: find.byType(Icon), + ), + ) + .size, + 18, + ); + } expect(find.text('For You'), findsNothing); final grid = tester.widget( find.byKey(const Key('home-all-dramas-grid')), diff --git a/test/drama_detail/drama_detail_view_test.dart b/test/drama_detail/drama_detail_view_test.dart index 7fee3c7..66352a7 100644 --- a/test/drama_detail/drama_detail_view_test.dart +++ b/test/drama_detail/drama_detail_view_test.dart @@ -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( + 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); diff --git a/test/home/home_preview_layout_test.dart b/test/home/home_preview_layout_test.dart index ca085e3..c7428f1 100644 --- a/test/home/home_preview_layout_test.dart +++ b/test/home/home_preview_layout_test.dart @@ -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( + 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 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(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(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, +); diff --git a/test/mine/mine_preview_layout_test.dart b/test/mine/mine_preview_layout_test.dart index cb9d013..d9c9946 100644 --- a/test/mine/mine_preview_layout_test.dart +++ b/test/mine/mine_preview_layout_test.dart @@ -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( + 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( + find.descendant( + of: tab0, + matching: find.byType(AnimatedContainer), + ), + ) + .decoration + as BoxDecoration) + .color, + AppColors.blue, + ); + expect( + (tester + .widget( + find.descendant( + of: tab1, + matching: find.byType(AnimatedContainer), + ), + ) + .decoration + as BoxDecoration) + .color, + Colors.transparent, + ); expect(tester.takeException(), isNull); }); } diff --git a/test/player/player_fullscreen_test.dart b/test/player/player_fullscreen_test.dart index 2c9607b..7f71cad 100644 --- a/test/player/player_fullscreen_test.dart +++ b/test/player/player_fullscreen_test.dart @@ -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(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 _pumpApp(WidgetTester tester) async { - tester.view.physicalSize = const Size(390, 844); +Future _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); diff --git a/test/widget_test.dart b/test/widget_test.dart index aa33829..430b533 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -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(find.byKey(const Key('home-feed-episode-title'))) + .data, + contains('Episode 1'), + ); await tester.tap(find.byKey(const Key('tab-discover'))); await tester.pumpAndSettle();