Release/v1.0.0 #1
No files matched your search
@@ -303,14 +303,41 @@ class _DramaDetailPageState extends State<DramaDetailPage> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
key: const Key('detail-episode-chips'),
|
||||
spacing: 6,
|
||||
runSpacing: 6,
|
||||
children: [
|
||||
for (var index = 0; index < drama.episodes.length; index++)
|
||||
_episodeChip(context, drama, index, continueIndex),
|
||||
],
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final sixColumnsWidth = (constraints.maxWidth - 5 * 8) / 6;
|
||||
final columns = sixColumnsWidth >= 40 ? 6 : 5;
|
||||
final selectedIndex = continueIndex ?? 0;
|
||||
final selectedNumber = drama.episodes.isEmpty
|
||||
? null
|
||||
: drama.episodes[selectedIndex].number;
|
||||
final ordered = [
|
||||
for (var index = 0; index < drama.episodes.length; index++)
|
||||
(index: index, episode: drama.episodes[index]),
|
||||
]..sort((a, b) => a.episode.number.compareTo(b.episode.number));
|
||||
return GridView.builder(
|
||||
key: const Key('detail-episode-chips'),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
padding: EdgeInsets.zero,
|
||||
itemCount: ordered.length,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: columns,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
childAspectRatio: 1.35,
|
||||
),
|
||||
itemBuilder: (context, position) {
|
||||
final item = ordered[position];
|
||||
return _episodeChip(
|
||||
context,
|
||||
item.episode,
|
||||
item.index,
|
||||
selectedNumber,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -319,20 +346,17 @@ class _DramaDetailPageState extends State<DramaDetailPage> {
|
||||
|
||||
Widget _episodeChip(
|
||||
BuildContext context,
|
||||
Drama drama,
|
||||
Episode episode,
|
||||
int index,
|
||||
int? selectedIndex,
|
||||
int? selectedNumber,
|
||||
) {
|
||||
final episode = drama.episodes[index];
|
||||
final selected = index == (selectedIndex ?? 0);
|
||||
final selected = episode.number == selectedNumber;
|
||||
final locked = episode.locked || (!episode.isFree && index > 0);
|
||||
return InkWell(
|
||||
key: Key('detail-episode-chip-${episode.number}'),
|
||||
onTap: () => logic.openPlayer(context, index),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
borderRadius: BorderRadius.circular(9),
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(minWidth: 32, minHeight: 32),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: selected
|
||||
@@ -340,7 +364,7 @@ class _DramaDetailPageState extends State<DramaDetailPage> {
|
||||
: locked
|
||||
? AppColors.secondary.withValues(alpha: 0.16)
|
||||
: const Color(0xFF29272B),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
borderRadius: BorderRadius.circular(9),
|
||||
),
|
||||
child: Text(
|
||||
'${episode.number}',
|
||||
|
||||
@@ -5,20 +5,25 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:fruitshorts/core/data/models/drama.dart';
|
||||
import 'package:fruitshorts/core/data/repositories/comment_repository.dart';
|
||||
import 'package:fruitshorts/core/data/repositories/favorite_repository.dart';
|
||||
import 'package:fruitshorts/core/data/repositories/interaction_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/data/services/catalog_controller.dart';
|
||||
import 'package:fruitshorts/core/data/services/favorite_controller.dart';
|
||||
import 'package:fruitshorts/core/data/services/interaction_controller.dart';
|
||||
import 'package:fruitshorts/core/data/services/playback_session.dart';
|
||||
import 'package:fruitshorts/core/platform/haptics.dart';
|
||||
import 'package:fruitshorts/core/theme/app_theme.dart';
|
||||
import 'package:fruitshorts/l10n/app_localizations.dart';
|
||||
import 'package:fruitshorts/pages/drama_detail/drama_detail_view.dart';
|
||||
import 'package:fruitshorts/pages/player/player_view.dart';
|
||||
|
||||
import '../support/fake_catalog.dart';
|
||||
import '../support/fake_haptics.dart';
|
||||
import '../support/fake_interaction_repositories.dart';
|
||||
import '../support/widget_player.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('should_render_preview_hero_cta_synopsis_and_episode_chips', (
|
||||
@@ -78,6 +83,14 @@ void main() {
|
||||
|
||||
await tester.pumpWidget(_host(_detailDrama()));
|
||||
await tester.pumpAndSettle();
|
||||
final grid = tester.widget<GridView>(
|
||||
find.byKey(const Key('detail-episode-chips')),
|
||||
);
|
||||
expect(
|
||||
(grid.gridDelegate as SliverGridDelegateWithFixedCrossAxisCount)
|
||||
.crossAxisCount,
|
||||
6,
|
||||
);
|
||||
await tester.drag(
|
||||
find.byKey(const Key('detail-scroll')),
|
||||
const Offset(0, -520),
|
||||
@@ -87,22 +100,96 @@ void main() {
|
||||
expect(find.byKey(const Key('detail-episode-chip-8')), findsOneWidget);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
testWidgets('should_render_sorted_six_column_grid_and_open_original_index', (
|
||||
tester,
|
||||
) async {
|
||||
tester.view.physicalSize = const Size(390, 844);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
|
||||
await tester.pumpWidget(_host(_detailDrama(count: 80)));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final grid = tester.widget<GridView>(
|
||||
find.byKey(const Key('detail-episode-chips')),
|
||||
);
|
||||
final delegate =
|
||||
grid.gridDelegate as SliverGridDelegateWithFixedCrossAxisCount;
|
||||
expect(delegate.crossAxisCount, 6);
|
||||
expect(delegate.mainAxisSpacing, 8);
|
||||
expect(delegate.crossAxisSpacing, 8);
|
||||
expect(delegate.childAspectRatio, closeTo(1.35, 0.001));
|
||||
|
||||
final chip1 = find.byKey(const Key('detail-episode-chip-1'));
|
||||
final chip2 = find.byKey(const Key('detail-episode-chip-2'));
|
||||
final chip6 = find.byKey(const Key('detail-episode-chip-6'));
|
||||
final chip7 = find.byKey(const Key('detail-episode-chip-7'));
|
||||
expect(tester.getTopLeft(chip1).dy, tester.getTopLeft(chip6).dy);
|
||||
expect(
|
||||
tester.getTopLeft(chip7).dy,
|
||||
greaterThan(tester.getTopLeft(chip1).dy),
|
||||
);
|
||||
expect(tester.getTopLeft(chip1).dx, lessThan(tester.getTopLeft(chip2).dx));
|
||||
expect(tester.getSize(chip1), tester.getSize(chip2));
|
||||
|
||||
await tester.drag(
|
||||
find.byKey(const Key('detail-scroll')),
|
||||
const Offset(0, -420),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
final chip12 = find.byKey(const Key('detail-episode-chip-12'));
|
||||
expect(chip12, findsOneWidget);
|
||||
await tester.tap(chip12);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final player = tester.widget<PlayerPage>(find.byType(PlayerPage));
|
||||
expect(player.initialIndex, 11);
|
||||
});
|
||||
|
||||
testWidgets('should_sort_episode_chips_ascending_by_number', (tester) async {
|
||||
tester.view.physicalSize = const Size(390, 844);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
|
||||
await tester.pumpWidget(_host(_detailDrama(count: 80, descending: true)));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final chip1 = find.byKey(const Key('detail-episode-chip-1'));
|
||||
final chip2 = find.byKey(const Key('detail-episode-chip-2'));
|
||||
final chip6 = find.byKey(const Key('detail-episode-chip-6'));
|
||||
final chip7 = find.byKey(const Key('detail-episode-chip-7'));
|
||||
expect(tester.getTopLeft(chip1).dy, tester.getTopLeft(chip6).dy);
|
||||
expect(tester.getTopLeft(chip1).dx, lessThan(tester.getTopLeft(chip2).dx));
|
||||
expect(
|
||||
tester.getTopLeft(chip7).dy,
|
||||
greaterThan(tester.getTopLeft(chip1).dy),
|
||||
);
|
||||
expect(tester.getSize(chip1), tester.getSize(chip2));
|
||||
});
|
||||
}
|
||||
|
||||
Widget _host(Drama drama) {
|
||||
final catalog = FakeCatalogRepository(dramas: [drama]);
|
||||
final interactions = FakeInteractionRepository();
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
Provider<WatchHistoryRepository>.value(
|
||||
value: InMemoryWatchHistoryRepository(),
|
||||
),
|
||||
Provider<PlaybackRepository>.value(value: FakePlaybackRepository()),
|
||||
Provider<PlayerFactory>.value(value: createWidgetPlayer),
|
||||
Provider(create: (_) => PlaybackCoordinator()),
|
||||
Provider<InteractionRepository>.value(value: interactions),
|
||||
Provider<CommentRepository>.value(value: FakeCommentRepository()),
|
||||
Provider<Haptics>.value(value: FakeHaptics()),
|
||||
ChangeNotifierProvider(
|
||||
create: (_) => FavoriteController(InMemoryFavoriteRepository()),
|
||||
),
|
||||
ChangeNotifierProvider(
|
||||
create: (_) => InteractionController(FakeInteractionRepository()),
|
||||
create: (_) => InteractionController(interactions),
|
||||
),
|
||||
ChangeNotifierProvider(create: (_) => CatalogController(catalog)..load()),
|
||||
],
|
||||
@@ -116,7 +203,11 @@ Widget _host(Drama drama) {
|
||||
);
|
||||
}
|
||||
|
||||
Drama _detailDrama() {
|
||||
Drama _detailDrama({int count = 8, bool descending = false}) {
|
||||
final numbers = List<int>.generate(
|
||||
count,
|
||||
(index) => descending ? count - index : index + 1,
|
||||
);
|
||||
return Drama(
|
||||
id: 'detail-k25',
|
||||
title: 'Detail K25',
|
||||
@@ -126,11 +217,11 @@ Drama _detailDrama() {
|
||||
posterUrl: '/files/detail-k25.png',
|
||||
genre: 'Urban Romance',
|
||||
category: 'Romance',
|
||||
totalEpisodes: 8,
|
||||
totalEpisodes: count,
|
||||
viewCount: 4800000,
|
||||
detailLoaded: true,
|
||||
episodes: [
|
||||
for (var number = 1; number <= 8; number++)
|
||||
for (final number in numbers)
|
||||
Episode(
|
||||
id: 'detail-k25-$number',
|
||||
number: number,
|
||||
|
||||
Reference in new issue
Block a user