merge: 修正详情选集等宽网格
This commit is contained in:
commit
f4e1a5424e
2 files changed
+135
-20
No files matched your search
@@ -303,14 +303,41 @@ class _DramaDetailPageState extends State<DramaDetailPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Wrap(
|
LayoutBuilder(
|
||||||
key: const Key('detail-episode-chips'),
|
builder: (context, constraints) {
|
||||||
spacing: 6,
|
final sixColumnsWidth = (constraints.maxWidth - 5 * 8) / 6;
|
||||||
runSpacing: 6,
|
final columns = sixColumnsWidth >= 40 ? 6 : 5;
|
||||||
children: [
|
final selectedIndex = continueIndex ?? 0;
|
||||||
for (var index = 0; index < drama.episodes.length; index++)
|
final selectedNumber = drama.episodes.isEmpty
|
||||||
_episodeChip(context, drama, index, continueIndex),
|
? 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(
|
Widget _episodeChip(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
Drama drama,
|
Episode episode,
|
||||||
int index,
|
int index,
|
||||||
int? selectedIndex,
|
int? selectedNumber,
|
||||||
) {
|
) {
|
||||||
final episode = drama.episodes[index];
|
final selected = episode.number == selectedNumber;
|
||||||
final selected = index == (selectedIndex ?? 0);
|
|
||||||
final locked = episode.locked || (!episode.isFree && index > 0);
|
final locked = episode.locked || (!episode.isFree && index > 0);
|
||||||
return InkWell(
|
return InkWell(
|
||||||
key: Key('detail-episode-chip-${episode.number}'),
|
key: Key('detail-episode-chip-${episode.number}'),
|
||||||
onTap: () => logic.openPlayer(context, index),
|
onTap: () => logic.openPlayer(context, index),
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(9),
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: const BoxConstraints(minWidth: 32, minHeight: 32),
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: selected
|
color: selected
|
||||||
@@ -340,7 +364,7 @@ class _DramaDetailPageState extends State<DramaDetailPage> {
|
|||||||
: locked
|
: locked
|
||||||
? AppColors.secondary.withValues(alpha: 0.16)
|
? AppColors.secondary.withValues(alpha: 0.16)
|
||||||
: const Color(0xFF29272B),
|
: const Color(0xFF29272B),
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(9),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'${episode.number}',
|
'${episode.number}',
|
||||||
|
|||||||
@@ -5,20 +5,25 @@ import 'package:flutter_test/flutter_test.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:fruitshorts/core/data/models/drama.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/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/playback_repository.dart';
|
||||||
import 'package:fruitshorts/core/data/repositories/watch_history_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/catalog_controller.dart';
|
||||||
import 'package:fruitshorts/core/data/services/favorite_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/interaction_controller.dart';
|
||||||
|
import 'package:fruitshorts/core/data/services/playback_session.dart';
|
||||||
import 'package:fruitshorts/core/platform/haptics.dart';
|
import 'package:fruitshorts/core/platform/haptics.dart';
|
||||||
import 'package:fruitshorts/core/theme/app_theme.dart';
|
import 'package:fruitshorts/core/theme/app_theme.dart';
|
||||||
import 'package:fruitshorts/l10n/app_localizations.dart';
|
import 'package:fruitshorts/l10n/app_localizations.dart';
|
||||||
import 'package:fruitshorts/pages/drama_detail/drama_detail_view.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_catalog.dart';
|
||||||
import '../support/fake_haptics.dart';
|
import '../support/fake_haptics.dart';
|
||||||
import '../support/fake_interaction_repositories.dart';
|
import '../support/fake_interaction_repositories.dart';
|
||||||
|
import '../support/widget_player.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('should_render_preview_hero_cta_synopsis_and_episode_chips', (
|
testWidgets('should_render_preview_hero_cta_synopsis_and_episode_chips', (
|
||||||
@@ -78,6 +83,14 @@ void main() {
|
|||||||
|
|
||||||
await tester.pumpWidget(_host(_detailDrama()));
|
await tester.pumpWidget(_host(_detailDrama()));
|
||||||
await tester.pumpAndSettle();
|
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(
|
await tester.drag(
|
||||||
find.byKey(const Key('detail-scroll')),
|
find.byKey(const Key('detail-scroll')),
|
||||||
const Offset(0, -520),
|
const Offset(0, -520),
|
||||||
@@ -87,22 +100,96 @@ void main() {
|
|||||||
expect(find.byKey(const Key('detail-episode-chip-8')), findsOneWidget);
|
expect(find.byKey(const Key('detail-episode-chip-8')), findsOneWidget);
|
||||||
expect(tester.takeException(), isNull);
|
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) {
|
Widget _host(Drama drama) {
|
||||||
final catalog = FakeCatalogRepository(dramas: [drama]);
|
final catalog = FakeCatalogRepository(dramas: [drama]);
|
||||||
|
final interactions = FakeInteractionRepository();
|
||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
Provider<WatchHistoryRepository>.value(
|
Provider<WatchHistoryRepository>.value(
|
||||||
value: InMemoryWatchHistoryRepository(),
|
value: InMemoryWatchHistoryRepository(),
|
||||||
),
|
),
|
||||||
Provider<PlaybackRepository>.value(value: FakePlaybackRepository()),
|
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()),
|
Provider<Haptics>.value(value: FakeHaptics()),
|
||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(
|
||||||
create: (_) => FavoriteController(InMemoryFavoriteRepository()),
|
create: (_) => FavoriteController(InMemoryFavoriteRepository()),
|
||||||
),
|
),
|
||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(
|
||||||
create: (_) => InteractionController(FakeInteractionRepository()),
|
create: (_) => InteractionController(interactions),
|
||||||
),
|
),
|
||||||
ChangeNotifierProvider(create: (_) => CatalogController(catalog)..load()),
|
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(
|
return Drama(
|
||||||
id: 'detail-k25',
|
id: 'detail-k25',
|
||||||
title: 'Detail K25',
|
title: 'Detail K25',
|
||||||
@@ -126,11 +217,11 @@ Drama _detailDrama() {
|
|||||||
posterUrl: '/files/detail-k25.png',
|
posterUrl: '/files/detail-k25.png',
|
||||||
genre: 'Urban Romance',
|
genre: 'Urban Romance',
|
||||||
category: 'Romance',
|
category: 'Romance',
|
||||||
totalEpisodes: 8,
|
totalEpisodes: count,
|
||||||
viewCount: 4800000,
|
viewCount: 4800000,
|
||||||
detailLoaded: true,
|
detailLoaded: true,
|
||||||
episodes: [
|
episodes: [
|
||||||
for (var number = 1; number <= 8; number++)
|
for (final number in numbers)
|
||||||
Episode(
|
Episode(
|
||||||
id: 'detail-k25-$number',
|
id: 'detail-k25-$number',
|
||||||
number: number,
|
number: number,
|
||||||
|
|||||||
Reference in new issue
Block a user