diff --git a/lib/app.dart b/lib/app.dart index 8ddc4ee..84f1e7f 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -146,7 +146,8 @@ class _LumaDramaAppState extends State { ); late final CommerceRepository _commerceRepository = - widget.commerceRepository ?? HttpCommerceRepository(CommerceApi(_apiClient)); + widget.commerceRepository ?? + HttpCommerceRepository(CommerceApi(_apiClient)); late final WalletController _walletController = WalletController( _commerceRepository, diff --git a/lib/core/data/models/order.dart b/lib/core/data/models/order.dart index 27c7501..39a9dcf 100644 --- a/lib/core/data/models/order.dart +++ b/lib/core/data/models/order.dart @@ -1,4 +1,4 @@ -// Author: fruit.com.cn +// Author: fruit.com.cn import 'json_numbers.dart'; diff --git a/lib/core/data/models/product.dart b/lib/core/data/models/product.dart index e9072a9..5f277e6 100644 --- a/lib/core/data/models/product.dart +++ b/lib/core/data/models/product.dart @@ -1,4 +1,4 @@ -// Author: fruit.com.cn +// Author: fruit.com.cn import 'json_numbers.dart'; diff --git a/lib/core/data/models/search_result.dart b/lib/core/data/models/search_result.dart index f33781d..9e7b852 100644 --- a/lib/core/data/models/search_result.dart +++ b/lib/core/data/models/search_result.dart @@ -1,4 +1,4 @@ -// Author: fruit.com.cn +// Author: fruit.com.cn import 'drama.dart'; import 'json_numbers.dart'; diff --git a/lib/core/data/remote/commerce_api.dart b/lib/core/data/remote/commerce_api.dart index 7364130..5728a69 100644 --- a/lib/core/data/remote/commerce_api.dart +++ b/lib/core/data/remote/commerce_api.dart @@ -1,4 +1,4 @@ -// Author: fruit.com.cn +// Author: fruit.com.cn import '../models/order.dart'; import '../models/product.dart'; diff --git a/lib/core/data/repositories/playback_repository.dart b/lib/core/data/repositories/playback_repository.dart index fda310e..d68065a 100644 --- a/lib/core/data/repositories/playback_repository.dart +++ b/lib/core/data/repositories/playback_repository.dart @@ -59,7 +59,8 @@ class _CachedTicket { final PlayTicket ticket; final DateTime fetchedAt; - bool isExpired(DateTime now, Duration ttl) => now.difference(fetchedAt) >= ttl; + bool isExpired(DateTime now, Duration ttl) => + now.difference(fetchedAt) >= ttl; } /// 测试与离线场景用:按集返回固定票据,并记录被请求过几次。 diff --git a/lib/core/data/repositories/search_repository.dart b/lib/core/data/repositories/search_repository.dart index 9380011..c59816e 100644 --- a/lib/core/data/repositories/search_repository.dart +++ b/lib/core/data/repositories/search_repository.dart @@ -1,4 +1,4 @@ -// Author: fruit.com.cn +// Author: fruit.com.cn import '../models/search_result.dart'; import '../remote/catalog_api.dart'; diff --git a/lib/core/data/repositories/store_repository.dart b/lib/core/data/repositories/store_repository.dart index d2d2e77..71cb76b 100644 --- a/lib/core/data/repositories/store_repository.dart +++ b/lib/core/data/repositories/store_repository.dart @@ -1,4 +1,4 @@ -// Author: fruit.com.cn +// Author: fruit.com.cn import '../models/order.dart'; import '../models/product.dart'; diff --git a/lib/core/data/services/store_controller.dart b/lib/core/data/services/store_controller.dart index 5e8adb2..e941c13 100644 --- a/lib/core/data/services/store_controller.dart +++ b/lib/core/data/services/store_controller.dart @@ -1,4 +1,4 @@ -// Author: fruit.com.cn +// Author: fruit.com.cn import 'package:flutter/foundation.dart'; diff --git a/lib/pages/discover/discover_binding.dart b/lib/pages/discover/discover_binding.dart index c85dbbe..0f62d70 100644 --- a/lib/pages/discover/discover_binding.dart +++ b/lib/pages/discover/discover_binding.dart @@ -1,4 +1,4 @@ -// Author: fruit.com.cn +// Author: fruit.com.cn import '../../core/data/models/drama.dart'; import '../../core/data/repositories/search_repository.dart'; @@ -10,9 +10,5 @@ class DiscoverBinding { required Drama drama, required WatchHistoryRepository history, SearchRepository? search, - }) => DiscoverLogic( - drama: drama, - history: history, - searchRepository: search, - ); + }) => DiscoverLogic(drama: drama, history: history, searchRepository: search); } diff --git a/lib/pages/discover/discover_logic.dart b/lib/pages/discover/discover_logic.dart index ee27518..55e5972 100644 --- a/lib/pages/discover/discover_logic.dart +++ b/lib/pages/discover/discover_logic.dart @@ -134,7 +134,8 @@ class DiscoverLogic extends ChangeNotifier { void _scheduleSearch(String query) { final alreadyRequested = query == _lastRequestedQuery; - if (alreadyRequested && (searchLoading || searchResults.isNotEmpty || searchError != null)) { + if (alreadyRequested && + (searchLoading || searchResults.isNotEmpty || searchError != null)) { return; } _lastRequestedQuery = query; diff --git a/lib/pages/discover/discover_view.dart b/lib/pages/discover/discover_view.dart index 7d8e370..35f0198 100644 --- a/lib/pages/discover/discover_view.dart +++ b/lib/pages/discover/discover_view.dart @@ -181,7 +181,8 @@ class _DiscoverPageState extends State { padding: const EdgeInsets.fromLTRB(0, 0, 0, 12), child: _languages(l10n, allDramas), ), - ), if (logic.resumeRecord != null && logic.resumeDrama(allDramas) != null) + ), + if (logic.resumeRecord != null && logic.resumeDrama(allDramas) != null) SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 14), @@ -495,6 +496,7 @@ class _DiscoverPageState extends State { if (code == 'en') return l10n.languageEnglish; return code; } + Widget _continueCard( AppLocalizations l10n, Drama drama, diff --git a/lib/pages/discover/widgets/home_drama_grid.dart b/lib/pages/discover/widgets/home_drama_grid.dart index 44ec014..f1ca78a 100644 --- a/lib/pages/discover/widgets/home_drama_grid.dart +++ b/lib/pages/discover/widgets/home_drama_grid.dart @@ -25,24 +25,40 @@ class _HomeDramaGrid extends StatelessWidget { @override Widget build(BuildContext context) { + final loading = _loadingState(); + if (loading != null) return loading; + final search = _searchState(); + if (search != null) return search; + if (dramas.isEmpty) return _emptyState(); + return SliverMainAxisGroup( + slivers: [ + SliverToBoxAdapter(child: _masonry(dramas)), + const SliverToBoxAdapter(child: SizedBox(height: 24)), + ], + ); + } + + Widget? _loadingState() { if (categories.loading && categories.feed.sections.isEmpty) { return const SliverFillRemaining( hasScrollBody: false, child: Center(child: CircularProgressIndicator(strokeWidth: 2)), ); } - if (categories.error != null) { - return _fill( - EmptyState( - key: const Key('home-categories-error'), - icon: Icons.cloud_off_rounded, - title: l10n.categoriesError, - subtitle: l10n.emptyHint, - actionLabel: l10n.retry, - onAction: categories.load, - ), - ); - } + if (categories.error == null) return null; + return _fill( + EmptyState( + key: const Key('home-categories-error'), + icon: Icons.cloud_off_rounded, + title: l10n.categoriesError, + subtitle: l10n.emptyHint, + actionLabel: l10n.retry, + onAction: categories.load, + ), + ); + } + + Widget? _searchState() { if (searching && searchLoading) { return const SliverFillRemaining( hasScrollBody: false, @@ -54,39 +70,33 @@ class _HomeDramaGrid extends StatelessWidget { ), ); } - if (searching && searchError != null) { - return _fill( - EmptyState( - key: const Key('discover-search-error'), - icon: Icons.cloud_off_rounded, - title: errorText(l10n, searchError, l10n.searchFailed), - actionLabel: l10n.retry, - actionKey: const Key('discover-search-retry'), - onAction: onRetrySearch, - ), - ); - } - if (dramas.isEmpty) { - return _fill( - EmptyState( - key: const Key('home-all-dramas-empty'), - icon: searching - ? Icons.search_off_rounded - : Icons.movie_filter_outlined, - title: searching - ? l10n.noSearchResults - : categories.selectedSectionMissing - ? l10n.noCategoryDramas - : l10n.noSearchResults, - subtitle: l10n.emptyHint, - ), - ); - } - return SliverMainAxisGroup( - slivers: [ - SliverToBoxAdapter(child: _masonry(dramas)), - const SliverToBoxAdapter(child: SizedBox(height: 24)), - ], + if (!searching || searchError == null) return null; + return _fill( + EmptyState( + key: const Key('discover-search-error'), + icon: Icons.cloud_off_rounded, + title: errorText(l10n, searchError, l10n.searchFailed), + actionLabel: l10n.retry, + actionKey: const Key('discover-search-retry'), + onAction: onRetrySearch, + ), + ); + } + + Widget _emptyState() { + return _fill( + EmptyState( + key: const Key('home-all-dramas-empty'), + icon: searching + ? Icons.search_off_rounded + : Icons.movie_filter_outlined, + title: searching + ? l10n.noSearchResults + : categories.selectedSectionMissing + ? l10n.noCategoryDramas + : l10n.noSearchResults, + subtitle: l10n.emptyHint, + ), ); } diff --git a/lib/pages/player/player_logic.dart b/lib/pages/player/player_logic.dart index e173466..f7f8ed9 100644 --- a/lib/pages/player/player_logic.dart +++ b/lib/pages/player/player_logic.dart @@ -283,10 +283,7 @@ class PlayerLogic extends ChangeNotifier { } final ready = await ensureTicket(index, forceRefresh: true); if (ready) { - await session.select( - index, - preloadIndex: nextVisibleIndex(index), - ); + await session.select(index, preloadIndex: nextVisibleIndex(index)); } return ready; } finally { diff --git a/lib/pages/player/player_view.dart b/lib/pages/player/player_view.dart index 51bacbf..986f88c 100644 --- a/lib/pages/player/player_view.dart +++ b/lib/pages/player/player_view.dart @@ -991,7 +991,8 @@ class _PlaybackOverlayState extends State<_PlaybackOverlay> { Navigator.pop(sheetContext); widget.onQualityTap(); }, - ), ListTile( + ), + ListTile( key: const Key('player-more-download'), leading: const Icon( Icons.download_outlined, diff --git a/lib/pages/player/widgets/quality_sheet.dart b/lib/pages/player/widgets/quality_sheet.dart index 5f3d69c..fcb6c50 100644 --- a/lib/pages/player/widgets/quality_sheet.dart +++ b/lib/pages/player/widgets/quality_sheet.dart @@ -1,4 +1,4 @@ -// Author: fruit.com.cn +// Author: fruit.com.cn import 'package:flutter/material.dart'; @@ -26,54 +26,60 @@ class QualitySheet extends StatelessWidget { key: const Key('player-quality-sheet'), mainAxisSize: MainAxisSize.min, children: [ - Padding( - padding: const EdgeInsets.fromLTRB(20, 2, 20, 8), - child: Align( - alignment: Alignment.centerLeft, - child: Text( - l10n.playbackQuality, - style: const TextStyle( - color: AppColors.onSurface, - fontSize: 14, - fontWeight: FontWeight.w700, - ), - ), - ), - ), - for (final quality in qualities) - ListTile( - key: Key('player-quality-$quality'), - dense: true, - title: Text( - _label(l10n, quality), - style: TextStyle( - color: quality == selected ? AppColors.blue : AppColors.cream, - fontSize: 13, - fontWeight: quality == selected - ? FontWeight.w700 - : FontWeight.w500, - ), - ), - trailing: quality == selected - ? const Icon( - Icons.check_rounded, - color: AppColors.blue, - size: 18, - ) - : null, - ), - Padding( - padding: const EdgeInsets.fromLTRB(20, 6, 20, 14), - child: Text( - l10n.qualityAutoHint, - style: const TextStyle(color: AppColors.muted, fontSize: 11), - ), - ), + _header(l10n), + for (final quality in qualities) _qualityTile(l10n, quality), + _hint(l10n), ], ), ); } + Widget _header(AppLocalizations l10n) { + return Padding( + padding: const EdgeInsets.fromLTRB(20, 2, 20, 8), + child: Align( + alignment: Alignment.centerLeft, + child: Text( + l10n.playbackQuality, + style: const TextStyle( + color: AppColors.onSurface, + fontSize: 14, + fontWeight: FontWeight.w700, + ), + ), + ), + ); + } + + Widget _qualityTile(AppLocalizations l10n, String quality) { + final active = quality == selected; + return ListTile( + key: Key('player-quality-$quality'), + dense: true, + title: Text( + _label(l10n, quality), + style: TextStyle( + color: active ? AppColors.blue : AppColors.cream, + fontSize: 13, + fontWeight: active ? FontWeight.w700 : FontWeight.w500, + ), + ), + trailing: active + ? const Icon(Icons.check_rounded, color: AppColors.blue, size: 18) + : null, + ); + } + + Widget _hint(AppLocalizations l10n) { + return Padding( + padding: const EdgeInsets.fromLTRB(20, 6, 20, 14), + child: Text( + l10n.qualityAutoHint, + style: const TextStyle(color: AppColors.muted, fontSize: 11), + ), + ); + } + String _label(AppLocalizations l10n, String quality) => quality == autoQuality ? l10n.qualityAuto : quality; } diff --git a/lib/pages/store/recharge_page.dart b/lib/pages/store/recharge_page.dart index 007d971..50645d7 100644 --- a/lib/pages/store/recharge_page.dart +++ b/lib/pages/store/recharge_page.dart @@ -1,4 +1,4 @@ -// Author: fruit.com.cn +// Author: fruit.com.cn import 'package:flutter/material.dart'; diff --git a/lib/pages/store/store_page.dart b/lib/pages/store/store_page.dart index cffb358..e5501bd 100644 --- a/lib/pages/store/store_page.dart +++ b/lib/pages/store/store_page.dart @@ -1,4 +1,4 @@ -// Author: fruit.com.cn +// Author: fruit.com.cn import 'dart:async'; @@ -122,7 +122,9 @@ class _StorePageState extends State { controller.purchaseError, l10n.orderCreateFailed, ); - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message))); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(message))); return; } widget.onPurchased?.call(); @@ -162,80 +164,85 @@ class _ProductTile extends StatelessWidget { children: [ _ProductBadge(vip: product.isVip), const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - product.name, - key: Key('store-product-name-${product.id}'), - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - color: AppColors.onSurface, - fontSize: 14, - fontWeight: FontWeight.w800, - ), - ), - const SizedBox(height: 4), - Text( - _detail(l10n), - style: const TextStyle( - color: AppColors.muted, - fontSize: 11, - ), - ), - ], - ), - ), + Expanded(child: _info(l10n)), const SizedBox(width: 10), - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text( - product.displayPrice, - key: Key('store-product-price-${product.id}'), - style: const TextStyle( - color: AppColors.gold, - fontSize: 13, - fontWeight: FontWeight.w800, - ), - ), - const SizedBox(height: 8), - SizedBox( - width: 88, - height: 32, - child: FilledButton( - key: Key('store-buy-${product.id}'), - onPressed: purchasing ? null : onBuy, - style: FilledButton.styleFrom( - backgroundColor: AppColors.blue, - foregroundColor: AppColors.onPrimary, - padding: EdgeInsets.zero, - textStyle: const TextStyle( - fontSize: 11, - fontWeight: FontWeight.w700, - ), - ), - child: purchasing - ? const SizedBox( - width: 14, - height: 14, - child: CircularProgressIndicator( - strokeWidth: 2, - color: AppColors.onPrimary, - ), - ) - : Text(l10n.buy), - ), - ), - ], - ), + _price(l10n), ], ), ); } + Widget _info(AppLocalizations l10n) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + product.name, + key: Key('store-product-name-${product.id}'), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: AppColors.onSurface, + fontSize: 14, + fontWeight: FontWeight.w800, + ), + ), + const SizedBox(height: 4), + Text( + _detail(l10n), + style: const TextStyle(color: AppColors.muted, fontSize: 11), + ), + ], + ); + } + + Widget _price(AppLocalizations l10n) { + return Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + product.displayPrice, + key: Key('store-product-price-${product.id}'), + style: const TextStyle( + color: AppColors.gold, + fontSize: 13, + fontWeight: FontWeight.w800, + ), + ), + const SizedBox(height: 8), + SizedBox( + width: 88, + height: 32, + child: FilledButton( + key: Key('store-buy-${product.id}'), + onPressed: purchasing ? null : onBuy, + style: FilledButton.styleFrom( + backgroundColor: AppColors.blue, + foregroundColor: AppColors.onPrimary, + padding: EdgeInsets.zero, + textStyle: const TextStyle( + fontSize: 11, + fontWeight: FontWeight.w700, + ), + ), + child: purchasing ? _buyProgress() : Text(l10n.buy), + ), + ), + ], + ); + } + + Widget _buyProgress() { + return const SizedBox( + width: 14, + height: 14, + child: CircularProgressIndicator( + strokeWidth: 2, + color: AppColors.onPrimary, + ), + ); + } + String _detail(AppLocalizations l10n) { if (product.isVip) return l10n.vipDuration(product.vipDays); final base = l10n.walletBalanceShort(product.coinAmount); diff --git a/lib/pages/store/vip_page.dart b/lib/pages/store/vip_page.dart index f6430b9..4df4e10 100644 --- a/lib/pages/store/vip_page.dart +++ b/lib/pages/store/vip_page.dart @@ -1,4 +1,4 @@ -// Author: fruit.com.cn +// Author: fruit.com.cn import 'package:flutter/material.dart'; diff --git a/lib/pages/wallet/wallet_page.dart b/lib/pages/wallet/wallet_page.dart index bc2f055..b9361de 100644 --- a/lib/pages/wallet/wallet_page.dart +++ b/lib/pages/wallet/wallet_page.dart @@ -90,9 +90,8 @@ class _WalletPageState extends State { void _openRecharge(WalletController wallet) { Navigator.of(context).push( MaterialPageRoute( - builder: (_) => RechargePage( - onPurchased: () => unawaited(wallet.load()), - ), + builder: (_) => + RechargePage(onPurchased: () => unawaited(wallet.load())), ), ); } @@ -320,7 +319,9 @@ class _LedgerTile extends StatelessWidget { Widget build(BuildContext context) { final positive = entry.changeAmount >= 0; final color = positive ? AppColors.success : AppColors.accent; - final amount = positive ? '+${entry.changeAmount}' : '${entry.changeAmount}'; + final amount = positive + ? '+${entry.changeAmount}' + : '${entry.changeAmount}'; return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( diff --git a/test/catalog/search_api_test.dart b/test/catalog/search_api_test.dart index 7e54257..e9ff252 100644 --- a/test/catalog/search_api_test.dart +++ b/test/catalog/search_api_test.dart @@ -1,4 +1,4 @@ -// Author: fruit.com.cn +// Author: fruit.com.cn import 'dart:convert'; @@ -9,43 +9,46 @@ import 'package:fruitshorts/core/data/remote/catalog_api.dart'; import '../support/fake_http_adapter.dart'; void main() { - test('should_request_search_with_keyword_and_paging_when_searching', () async { - final adapter = FakeHttpAdapter( - (options) async => FakeHttpResponse( - jsonEncode({ - 'code': 0, - 'message': 'ok', - 'data': { - 'total': 1, - 'list': [ - { - 'id': '2111012753262120962', - 'title': 'Velvet Nights', - 'synopsis': 'A city romance', - 'cover': '/files/velvet.png', - 'genre': 'Romance', - 'totalEpisodes': 12, - 'isHot': true, - 'language': 'id', - }, - ], - }, - }), - ), - ); - final api = CatalogApi( - ApiClient(baseUrl: 'http://127.0.0.1:8081', adapter: adapter), - ); + test( + 'should_request_search_with_keyword_and_paging_when_searching', + () async { + final adapter = FakeHttpAdapter( + (options) async => FakeHttpResponse( + jsonEncode({ + 'code': 0, + 'message': 'ok', + 'data': { + 'total': 1, + 'list': [ + { + 'id': '2111012753262120962', + 'title': 'Velvet Nights', + 'synopsis': 'A city romance', + 'cover': '/files/velvet.png', + 'genre': 'Romance', + 'totalEpisodes': 12, + 'isHot': true, + 'language': 'id', + }, + ], + }, + }), + ), + ); + final api = CatalogApi( + ApiClient(baseUrl: 'http://127.0.0.1:8081', adapter: adapter), + ); - final page = await api.search('velvet'); + final page = await api.search('velvet'); - final request = adapter.requests.single; - expect(request.uri.path, '/app/search'); - expect(request.queryParameters['keyword'], 'velvet'); - expect(request.queryParameters['pageIndex'], '1'); - expect(request.queryParameters['pageSize'], '20'); - expect(page.total, 1); - expect(page.list.single.title, 'Velvet Nights'); - expect(page.list.single.language, 'id'); - }); + final request = adapter.requests.single; + expect(request.uri.path, '/app/search'); + expect(request.queryParameters['keyword'], 'velvet'); + expect(request.queryParameters['pageIndex'], '1'); + expect(request.queryParameters['pageSize'], '20'); + expect(page.total, 1); + expect(page.list.single.title, 'Velvet Nights'); + expect(page.list.single.language, 'id'); + }, + ); } diff --git a/test/commerce/store_api_test.dart b/test/commerce/store_api_test.dart index 89b18fb..7e623c6 100644 --- a/test/commerce/store_api_test.dart +++ b/test/commerce/store_api_test.dart @@ -1,4 +1,4 @@ -// Author: fruit.com.cn +// Author: fruit.com.cn import 'dart:convert'; diff --git a/test/commerce/store_controller_test.dart b/test/commerce/store_controller_test.dart index d1b83df..726fb9d 100644 --- a/test/commerce/store_controller_test.dart +++ b/test/commerce/store_controller_test.dart @@ -1,4 +1,4 @@ -// Author: fruit.com.cn +// Author: fruit.com.cn import 'package:flutter_test/flutter_test.dart'; import 'package:fruitshorts/core/data/models/order.dart'; @@ -81,11 +81,7 @@ AppProduct _product(String name, {int coinAmount = 550, int bonusCoin = 0}) => ); class _FakeStoreRepository implements StoreRepository { - _FakeStoreRepository({ - this.products = const [], - this.order, - this.orderError, - }); + _FakeStoreRepository({this.products = const [], this.order, this.orderError}); final List products; final AppOrder? order; diff --git a/test/discover/discover_language_filter_test.dart b/test/discover/discover_language_filter_test.dart index db69362..003d3db 100644 --- a/test/discover/discover_language_filter_test.dart +++ b/test/discover/discover_language_filter_test.dart @@ -1,4 +1,4 @@ -// Author: fruit.com.cn +// Author: fruit.com.cn import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/test/discover/discover_logic_test.dart b/test/discover/discover_logic_test.dart index 507dfe6..08c02fa 100644 --- a/test/discover/discover_logic_test.dart +++ b/test/discover/discover_logic_test.dart @@ -8,7 +8,11 @@ import 'package:fruitshorts/pages/discover/discover_logic.dart'; import '../support/fake_watch_history.dart'; -Drama _drama(String id, {List numbers = const [1, 2, 3], String language = ''}) => Drama( +Drama _drama( + String id, { + List numbers = const [1, 2, 3], + String language = '', +}) => Drama( language: language, id: id, title: id, @@ -29,15 +33,18 @@ Drama _drama(String id, {List numbers = const [1, 2, 3], String language = void main() { TestWidgetsFlutterBinding.ensureInitialized(); - test('should_resolve_resume_position_of_episode_one_when_constructed', () async { - final history = FakeWatchHistory()..saved = const Duration(seconds: 4); - final logic = DiscoverLogic(history: history, drama: _drama('signal')); + test( + 'should_resolve_resume_position_of_episode_one_when_constructed', + () async { + final history = FakeWatchHistory()..saved = const Duration(seconds: 4); + final logic = DiscoverLogic(history: history, drama: _drama('signal')); - expect(await logic.position, const Duration(seconds: 4)); - expect(history.reads, ['signal']); - expect(logic.searchOpen, isFalse); - logic.dispose(); - }); + expect(await logic.position, const Duration(seconds: 4)); + expect(history.reads, ['signal']); + expect(logic.searchOpen, isFalse); + logic.dispose(); + }, + ); test('should_reread_resume_position_when_drama_is_swapped', () async { final history = FakeWatchHistory(); @@ -51,22 +58,28 @@ void main() { logic.dispose(); }); - test('should_resume_from_last_watch_when_list_card_has_no_episodes', () async { - final history = FakeWatchHistory() - ..saved = const Duration(seconds: 9) - ..lastEpisode = 'signal-2'; - final logic = DiscoverLogic( - history: history, - drama: _drama('signal', numbers: const []), - ); + test( + 'should_resume_from_last_watch_when_list_card_has_no_episodes', + () async { + final history = FakeWatchHistory() + ..saved = const Duration(seconds: 9) + ..lastEpisode = 'signal-2'; + final logic = DiscoverLogic( + history: history, + drama: _drama('signal', numbers: const []), + ); - expect(await logic.position, const Duration(seconds: 9)); - expect(history.reads, ['signal']); - logic.dispose(); - }); + expect(await logic.position, const Duration(seconds: 9)); + expect(history.reads, ['signal']); + logic.dispose(); + }, + ); test('should_filter_visible_dramas_when_language_is_selected', () { - final logic = DiscoverLogic(history: FakeWatchHistory(), drama: _drama('s')); + final logic = DiscoverLogic( + history: FakeWatchHistory(), + drama: _drama('s'), + ); final dramas = [ _drama('zh', language: 'zh-CN'), _drama('id', language: 'id'), @@ -84,7 +97,10 @@ void main() { }); test('should_list_distinct_languages_when_feed_has_cards', () { - final logic = DiscoverLogic(history: FakeWatchHistory(), drama: _drama('s')); + final logic = DiscoverLogic( + history: FakeWatchHistory(), + drama: _drama('s'), + ); final dramas = [ _drama('zh', language: 'zh-CN'), _drama('id', language: 'id'), @@ -109,7 +125,10 @@ void main() { }); test('should_show_search_when_flag_is_on_or_text_is_typed', () { - final logic = DiscoverLogic(history: FakeWatchHistory(), drama: _drama('s')); + final logic = DiscoverLogic( + history: FakeWatchHistory(), + drama: _drama('s'), + ); expect(logic.showSearch, isFalse); @@ -126,7 +145,10 @@ void main() { }); test('should_match_by_id_and_tolerate_missing_id_when_looking_up_drama', () { - final logic = DiscoverLogic(history: FakeWatchHistory(), drama: _drama('s')); + final logic = DiscoverLogic( + history: FakeWatchHistory(), + drama: _drama('s'), + ); final catalog = [_drama('signal'), _drama('city')]; expect(logic.dramaFor('city', catalog)?.id, 'city'); @@ -167,7 +189,12 @@ void main() { // Staying active with a changed drama re-reads history. history.reads.clear(); - sync(_drama('vengeance'), active: true, wasActive: true, dramaChanged: true); + sync( + _drama('vengeance'), + active: true, + wasActive: true, + dramaChanged: true, + ); expect(logic.drama.id, 'vengeance'); expect(history.reads, ['vengeance']); @@ -179,43 +206,50 @@ void main() { logic.dispose(); }); - testWidgets('should_open_and_focus_field_when_search_is_requested_and_page_is_active', ( - tester, - ) async { - final logic = DiscoverLogic(history: FakeWatchHistory(), drama: _drama('s')); - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: TextField( - controller: logic.search, - focusNode: logic.searchFocus, + testWidgets( + 'should_open_and_focus_field_when_search_is_requested_and_page_is_active', + (tester) async { + final logic = DiscoverLogic( + history: FakeWatchHistory(), + drama: _drama('s'), + ); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: TextField( + controller: logic.search, + focusNode: logic.searchFocus, + ), ), ), - ), - ); + ); - logic.syncDrama( - active: true, - wasActive: true, - nextDrama: logic.drama, - dramaChanged: false, - searchRequest: 1, - previousSearchRequest: 0, - isActive: () => true, - ); + logic.syncDrama( + active: true, + wasActive: true, + nextDrama: logic.drama, + dramaChanged: false, + searchRequest: 1, + previousSearchRequest: 0, + isActive: () => true, + ); - expect(logic.searchOpen, isTrue); - await tester.pumpAndSettle(); - expect(logic.searchFocus.hasFocus, isTrue); + expect(logic.searchOpen, isTrue); + await tester.pumpAndSettle(); + expect(logic.searchFocus.hasFocus, isTrue); - await tester.pumpWidget(const SizedBox.shrink()); - logic.dispose(); - }); + await tester.pumpWidget(const SizedBox.shrink()); + logic.dispose(); + }, + ); testWidgets('should_keep_search_field_closed_when_page_is_hidden', ( tester, ) async { - final logic = DiscoverLogic(history: FakeWatchHistory(), drama: _drama('s')); + final logic = DiscoverLogic( + history: FakeWatchHistory(), + drama: _drama('s'), + ); await tester.pumpWidget( MaterialApp( home: Scaffold( @@ -250,7 +284,10 @@ void main() { testWidgets('should_return_choice_and_forward_it_when_act_runs_embedded', ( tester, ) async { - final logic = DiscoverLogic(history: FakeWatchHistory(), drama: _drama('s')); + final logic = DiscoverLogic( + history: FakeWatchHistory(), + drama: _drama('s'), + ); final forwarded = []; final popped = Completer(); late BuildContext pageContext; @@ -297,7 +334,10 @@ void main() { testWidgets('should_show_artwork_notice_when_card_has_no_playable_media', ( tester, ) async { - final logic = DiscoverLogic(history: FakeWatchHistory(), drama: _drama('s')); + final logic = DiscoverLogic( + history: FakeWatchHistory(), + drama: _drama('s'), + ); late BuildContext context; await tester.pumpWidget( @@ -322,8 +362,13 @@ void main() { logic.dispose(); }); - testWidgets('should_forward_chosen_drama_when_card_is_embedded', (tester) async { - final logic = DiscoverLogic(history: FakeWatchHistory(), drama: _drama('s')); + testWidgets('should_forward_chosen_drama_when_card_is_embedded', ( + tester, + ) async { + final logic = DiscoverLogic( + history: FakeWatchHistory(), + drama: _drama('s'), + ); late BuildContext context; Drama? selected; @@ -350,7 +395,10 @@ void main() { }); test('should_do_nothing_when_notifying_after_dispose', () { - final logic = DiscoverLogic(history: FakeWatchHistory(), drama: _drama('s')); + final logic = DiscoverLogic( + history: FakeWatchHistory(), + drama: _drama('s'), + ); var notifications = 0; logic.addListener(() => notifications++); diff --git a/test/discover/discover_search_test.dart b/test/discover/discover_search_test.dart index 92a45da..0ddd7e1 100644 --- a/test/discover/discover_search_test.dart +++ b/test/discover/discover_search_test.dart @@ -1,4 +1,4 @@ -// Author: fruit.com.cn +// Author: fruit.com.cn import 'package:flutter_test/flutter_test.dart'; import 'package:fruitshorts/core/data/models/drama.dart'; @@ -21,23 +21,28 @@ Drama _drama(String id, {String language = 'id'}) => Drama( void main() { TestWidgetsFlutterBinding.ensureInitialized(); - test('should_query_backend_and_render_results_when_search_text_changes', () async { - final repository = _FakeSearchRepository(results: [_drama('velvet')]); - final logic = DiscoverLogic( - history: FakeWatchHistory(), - drama: _drama('signal'), - searchRepository: repository, - )..searchDebounce = Duration.zero; + test( + 'should_query_backend_and_render_results_when_search_text_changes', + () async { + final repository = _FakeSearchRepository(results: [_drama('velvet')]); + final logic = DiscoverLogic( + history: FakeWatchHistory(), + drama: _drama('signal'), + searchRepository: repository, + )..searchDebounce = Duration.zero; - logic.search.text = 'velvet'; - logic.onSearchChanged(); - await pumpEventQueue(); + logic.search.text = 'velvet'; + logic.onSearchChanged(); + await pumpEventQueue(); - expect(repository.calls, [(keyword: 'velvet', pageIndex: 1, pageSize: 20)]); - expect(logic.searchLoading, isFalse); - expect(logic.visibleDramas(const []).single.id, 'velvet'); - logic.dispose(); - }); + expect(repository.calls, [ + (keyword: 'velvet', pageIndex: 1, pageSize: 20), + ]); + expect(logic.searchLoading, isFalse); + expect(logic.visibleDramas(const []).single.id, 'velvet'); + logic.dispose(); + }, + ); test('should_keep_server_results_when_text_only_loses_whitespace', () async { final repository = _FakeSearchRepository(results: [_drama('velvet')]); diff --git a/test/playback/play_ticket_quality_test.dart b/test/playback/play_ticket_quality_test.dart index 00a55c7..809a7b5 100644 --- a/test/playback/play_ticket_quality_test.dart +++ b/test/playback/play_ticket_quality_test.dart @@ -1,4 +1,4 @@ -// Author: fruit.com.cn +// Author: fruit.com.cn import 'package:flutter_test/flutter_test.dart'; import 'package:fruitshorts/core/data/models/play_ticket.dart'; diff --git a/test/playback/player_quality_test.dart b/test/playback/player_quality_test.dart index 41e9df8..b470753 100644 --- a/test/playback/player_quality_test.dart +++ b/test/playback/player_quality_test.dart @@ -28,12 +28,7 @@ void main() { ), ); logic.sessionEpisodes = [ - Episode( - id: '12', - number: 1, - title: 'Episode 1', - isFree: true, - ), + Episode(id: '12', number: 1, title: 'Episode 1', isFree: true), ]; final ready = await logic.ensureTicket(0); diff --git a/test/player/quality_sheet_test.dart b/test/player/quality_sheet_test.dart index d9c8024..8ff9f6f 100644 --- a/test/player/quality_sheet_test.dart +++ b/test/player/quality_sheet_test.dart @@ -1,4 +1,4 @@ -// Author: fruit.com.cn +// Author: fruit.com.cn import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/test/store/store_pages_test.dart b/test/store/store_pages_test.dart index c851692..de4fe9e 100644 --- a/test/store/store_pages_test.dart +++ b/test/store/store_pages_test.dart @@ -1,4 +1,4 @@ -// Author: fruit.com.cn +// Author: fruit.com.cn import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/test/support/fake_catalog.dart b/test/support/fake_catalog.dart index 9cb85f4..4b49b52 100644 --- a/test/support/fake_catalog.dart +++ b/test/support/fake_catalog.dart @@ -1,4 +1,4 @@ -import 'package:fruitshorts/core/data/models/drama.dart'; +import 'package:fruitshorts/core/data/models/drama.dart'; import 'package:fruitshorts/core/data/models/home_feed.dart'; import 'package:fruitshorts/core/data/models/search_result.dart'; import 'package:fruitshorts/core/data/repositories/catalog_repository.dart';