merge: 合并 K85 收藏与评论数实时刷新
This commit is contained in:
commit
bdd3a53632
11 files changed
+275
-29
No files matched your search
@@ -1,6 +1,7 @@
|
|||||||
// Author: fruit.com.cn
|
// Author: fruit.com.cn
|
||||||
|
|
||||||
import '../models/comment.dart';
|
import '../models/comment.dart';
|
||||||
|
import '../models/json_numbers.dart';
|
||||||
import 'api_client.dart';
|
import 'api_client.dart';
|
||||||
|
|
||||||
/// 评论接口,对应契约 §1.7 的 `/app/comments*`。
|
/// 评论接口,对应契约 §1.7 的 `/app/comments*`。
|
||||||
@@ -29,6 +30,15 @@ class CommentApi {
|
|||||||
return CommentPage.fromJson(data);
|
return CommentPage.fromJson(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 查询单部剧的有效评论总数;后端 Long 兼容数字与字符串。
|
||||||
|
Future<int> count(String dramaId) async {
|
||||||
|
final data = await _client.get(
|
||||||
|
'/app/comments/count',
|
||||||
|
query: {'dramaId': dramaId},
|
||||||
|
);
|
||||||
|
return jsonToInt(data);
|
||||||
|
}
|
||||||
|
|
||||||
/// 发表评论或回复;回复只提交 `parentId`,不自行拼层级。
|
/// 发表评论或回复;回复只提交 `parentId`,不自行拼层级。
|
||||||
Future<DramaComment> publish({
|
Future<DramaComment> publish({
|
||||||
required String dramaId,
|
required String dramaId,
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ abstract interface class CommentRepository {
|
|||||||
int size = commentPageSize,
|
int size = commentPageSize,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// 查询单部剧的有效评论总数。
|
||||||
|
Future<int> count(String dramaId);
|
||||||
|
|
||||||
/// 发表评论或回复。
|
/// 发表评论或回复。
|
||||||
Future<DramaComment> publish({
|
Future<DramaComment> publish({
|
||||||
required String dramaId,
|
required String dramaId,
|
||||||
@@ -44,6 +47,9 @@ class HttpCommentRepository implements CommentRepository {
|
|||||||
size: size,
|
size: size,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<int> count(String dramaId) => _api.count(dramaId);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<DramaComment> publish({
|
Future<DramaComment> publish({
|
||||||
required String dramaId,
|
required String dramaId,
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ class DramaDetailLogic extends ChangeNotifier {
|
|||||||
int? continueIndex;
|
int? continueIndex;
|
||||||
WatchProgress? progress;
|
WatchProgress? progress;
|
||||||
String? progressError;
|
String? progressError;
|
||||||
|
int _favoriteCountDelta = 0;
|
||||||
bool _disposed = false;
|
bool _disposed = false;
|
||||||
|
|
||||||
void notify() {
|
void notify() {
|
||||||
@@ -118,8 +119,26 @@ class DramaDetailLogic extends ChangeNotifier {
|
|||||||
await loadContinue();
|
await loadContinue();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 收藏切换:乐观更新失败时会回滚,由调用方给出提示。
|
/// 详情页展示用的收藏数;没有服务端计数时继续保持缺省展示。
|
||||||
Future<bool> toggleFavorite() => favorites.toggle(drama.id, card: drama);
|
int? get adjustedFavoriteCount {
|
||||||
|
final base = drama.favoriteCount;
|
||||||
|
if (base == null) return null;
|
||||||
|
final adjusted = base + _favoriteCountDelta;
|
||||||
|
return adjusted < 0 ? 0 : adjusted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 收藏切换:乐观状态由控制器负责回滚,这里只在成功后同步计数。
|
||||||
|
Future<bool> toggleFavorite() async {
|
||||||
|
final wasSaved = favorites.isSaved(drama.id);
|
||||||
|
final ok = await favorites.toggle(drama.id, card: drama);
|
||||||
|
if (!ok) return false;
|
||||||
|
final nowSaved = favorites.isSaved(drama.id);
|
||||||
|
if (nowSaved != wasSaved) {
|
||||||
|
_favoriteCountDelta += nowSaved ? 1 : -1;
|
||||||
|
notify();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
|||||||
@@ -142,7 +142,11 @@ class _DramaDetailPageState extends State<DramaDetailPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: DramaStatsCard(l10n: l10n, drama: drama),
|
child: DramaStatsCard(
|
||||||
|
l10n: l10n,
|
||||||
|
drama: drama,
|
||||||
|
favoriteCount: logic.adjustedFavoriteCount,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: DramaSynopsisSection(
|
child: DramaSynopsisSection(
|
||||||
|
|||||||
@@ -29,16 +29,23 @@ const TextStyle _synopsisStyle = TextStyle(
|
|||||||
|
|
||||||
/// 详情页三项数据卡:热度、收藏与点赞,缺失值不做伪造。
|
/// 详情页三项数据卡:热度、收藏与点赞,缺失值不做伪造。
|
||||||
class DramaStatsCard extends StatelessWidget {
|
class DramaStatsCard extends StatelessWidget {
|
||||||
const DramaStatsCard({super.key, required this.l10n, required this.drama});
|
const DramaStatsCard({
|
||||||
|
super.key,
|
||||||
|
required this.l10n,
|
||||||
|
required this.drama,
|
||||||
|
this.favoriteCount,
|
||||||
|
});
|
||||||
|
|
||||||
final AppLocalizations l10n;
|
final AppLocalizations l10n;
|
||||||
final Drama drama;
|
final Drama drama;
|
||||||
|
final int? favoriteCount;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final favorite = drama.favoriteCount == null
|
final currentFavoriteCount = favoriteCount ?? drama.favoriteCount;
|
||||||
|
final favorite = currentFavoriteCount == null
|
||||||
? l10n.episodeCountLabel(drama.episodeCount)
|
? l10n.episodeCountLabel(drama.episodeCount)
|
||||||
: '${formatInteractionCount(drama.favoriteCount!)} ${l10n.favorite}';
|
: '${formatInteractionCount(currentFavoriteCount)} ${l10n.favorite}';
|
||||||
final like = drama.likeCount == null
|
final like = drama.likeCount == null
|
||||||
? '—'
|
? '—'
|
||||||
: '${formatInteractionCount(drama.likeCount!)} ${l10n.like}';
|
: '${formatInteractionCount(drama.likeCount!)} ${l10n.like}';
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import '../../core/data/models/drama.dart';
|
import '../../core/data/models/drama.dart';
|
||||||
|
import '../../core/data/repositories/comment_repository.dart';
|
||||||
import '../../core/data/services/favorite_controller.dart';
|
import '../../core/data/services/favorite_controller.dart';
|
||||||
import '../../core/data/services/download_controller.dart';
|
import '../../core/data/services/download_controller.dart';
|
||||||
import '../../core/data/services/interaction_controller.dart';
|
import '../../core/data/services/interaction_controller.dart';
|
||||||
@@ -99,6 +100,7 @@ class _PlayerPageState extends State<PlayerPage>
|
|||||||
|
|
||||||
/// 收藏切换后的本地增量:基准是详情快照,切换成功按服务端状态 ±1。
|
/// 收藏切换后的本地增量:基准是详情快照,切换成功按服务端状态 ±1。
|
||||||
final Map<String, int> _favoriteDelta = {};
|
final Map<String, int> _favoriteDelta = {};
|
||||||
|
final Map<String, int> _commentCounts = {};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -203,6 +205,18 @@ class _PlayerPageState extends State<PlayerPage>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _openComments(BuildContext context, Drama drama) async {
|
||||||
|
final comments = context.read<CommentRepository>();
|
||||||
|
await openCommentsWithAuth(context, drama, show: logic.showComments);
|
||||||
|
try {
|
||||||
|
final count = await comments.count(drama.id);
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() => _commentCounts[drama.id] = count);
|
||||||
|
} catch (_) {
|
||||||
|
// 评论面板已经关闭,计数刷新失败时保留旧值即可。
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListenableBuilder(
|
return ListenableBuilder(
|
||||||
@@ -446,7 +460,7 @@ class _PlayerPageState extends State<PlayerPage>
|
|||||||
favoriteCount: drama.favoriteCount == null
|
favoriteCount: drama.favoriteCount == null
|
||||||
? null
|
? null
|
||||||
: drama.favoriteCount! + (_favoriteDelta[drama.id] ?? 0),
|
: drama.favoriteCount! + (_favoriteDelta[drama.id] ?? 0),
|
||||||
commentCount: drama.commentCount,
|
commentCount: _commentCounts[drama.id] ?? drama.commentCount,
|
||||||
saved: context.watch<FavoriteController>().isSaved(drama.id),
|
saved: context.watch<FavoriteController>().isSaved(drama.id),
|
||||||
onLike: () async {
|
onLike: () async {
|
||||||
if (await ensureSignedIn(context)) {
|
if (await ensureSignedIn(context)) {
|
||||||
@@ -465,21 +479,8 @@ class _PlayerPageState extends State<PlayerPage>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onSave: () async {
|
onSave: () => unawaited(_toggleSave(context, drama)),
|
||||||
final favorites = context.read<FavoriteController>();
|
onComments: () => unawaited(_openComments(context, drama)),
|
||||||
if (await ensureSignedIn(context)) {
|
|
||||||
if (!context.mounted) return;
|
|
||||||
unawaited(context.read<Haptics>().lightImpact());
|
|
||||||
final ok = await favorites.toggle(drama.id, card: drama);
|
|
||||||
if (ok || !context.mounted) return;
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text(l10n.favoriteToggleFailed)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onComments: () => unawaited(
|
|
||||||
openCommentsWithAuth(context, drama, show: logic.showComments),
|
|
||||||
),
|
|
||||||
onShare: () => logic.shareDrama(context, drama),
|
onShare: () => logic.shareDrama(context, drama),
|
||||||
onDetail: () => unawaited(logic.openDetail(context)),
|
onDetail: () => unawaited(logic.openDetail(context)),
|
||||||
onOpenFullPlayer: () async {
|
onOpenFullPlayer: () async {
|
||||||
@@ -502,7 +503,7 @@ class _PlayerPageState extends State<PlayerPage>
|
|||||||
controller: ready ? videoPort!.controller : null,
|
controller: ready ? videoPort!.controller : null,
|
||||||
liked: interactions.isLiked(drama.id),
|
liked: interactions.isLiked(drama.id),
|
||||||
likeCount: interactions.likeCount(drama.id) ?? drama.likeCount,
|
likeCount: interactions.likeCount(drama.id) ?? drama.likeCount,
|
||||||
commentCount: drama.commentCount,
|
commentCount: _commentCounts[drama.id] ?? drama.commentCount,
|
||||||
favoriteCount: drama.favoriteCount == null
|
favoriteCount: drama.favoriteCount == null
|
||||||
? null
|
? null
|
||||||
: drama.favoriteCount! + (_favoriteDelta[drama.id] ?? 0),
|
: drama.favoriteCount! + (_favoriteDelta[drama.id] ?? 0),
|
||||||
@@ -523,9 +524,7 @@ class _PlayerPageState extends State<PlayerPage>
|
|||||||
onDetail: () => unawaited(logic.openDetail(context)),
|
onDetail: () => unawaited(logic.openDetail(context)),
|
||||||
onSpeed: () => unawaited(_openSpeedSheet(context)),
|
onSpeed: () => unawaited(_openSpeedSheet(context)),
|
||||||
onEpisodes: () => logic.showEpisodes(context),
|
onEpisodes: () => logic.showEpisodes(context),
|
||||||
onComments: () => unawaited(
|
onComments: () => unawaited(_openComments(context, drama)),
|
||||||
openCommentsWithAuth(context, drama, show: logic.showComments),
|
|
||||||
),
|
|
||||||
onLike: () =>
|
onLike: () =>
|
||||||
unawaited(_toggleLike(context, interactions, drama)),
|
unawaited(_toggleLike(context, interactions, drama)),
|
||||||
onSave: () => unawaited(_toggleSave(context, drama)),
|
onSave: () => unawaited(_toggleSave(context, drama)),
|
||||||
|
|||||||
@@ -126,4 +126,31 @@ void main() {
|
|||||||
'/app/comments/2111012753262120962',
|
'/app/comments/2111012753262120962',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should_parse_numeric_comment_count', () async {
|
||||||
|
final adapter = FakeHttpAdapter(
|
||||||
|
(_) async => FakeHttpResponse(envelope(17)),
|
||||||
|
);
|
||||||
|
final api = CommentApi(
|
||||||
|
ApiClient(baseUrl: 'http://127.0.0.1:8081', adapter: adapter),
|
||||||
|
);
|
||||||
|
|
||||||
|
final count = await api.count('5');
|
||||||
|
|
||||||
|
expect(count, 17);
|
||||||
|
expect(adapter.requests.single.method, 'GET');
|
||||||
|
expect(adapter.requests.single.uri.path, '/app/comments/count');
|
||||||
|
expect(adapter.requests.single.queryParameters, {'dramaId': '5'});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should_parse_string_comment_count', () async {
|
||||||
|
final adapter = FakeHttpAdapter(
|
||||||
|
(_) async => FakeHttpResponse(envelope('2111012753262120962')),
|
||||||
|
);
|
||||||
|
final api = CommentApi(
|
||||||
|
ApiClient(baseUrl: 'http://127.0.0.1:8081', adapter: adapter),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(await api.count('5'), 2111012753262120962);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
@@ -9,12 +9,16 @@ import 'package:fruitshorts/pages/drama_detail/drama_detail_logic.dart';
|
|||||||
import '../support/fake_watch_history.dart';
|
import '../support/fake_watch_history.dart';
|
||||||
|
|
||||||
/// 详情页逻辑依赖:票据仓库 + 收藏控制器(测试里都是内存实现)。
|
/// 详情页逻辑依赖:票据仓库 + 收藏控制器(测试里都是内存实现)。
|
||||||
DramaDetailLogic _logic(Drama drama, FakeWatchHistory history) =>
|
DramaDetailLogic _logic(
|
||||||
|
Drama drama,
|
||||||
|
FakeWatchHistory history, {
|
||||||
|
FavoriteController? favorites,
|
||||||
|
}) =>
|
||||||
DramaDetailLogic(
|
DramaDetailLogic(
|
||||||
drama: drama,
|
drama: drama,
|
||||||
history: history,
|
history: history,
|
||||||
playback: FakePlaybackRepository(),
|
playback: FakePlaybackRepository(),
|
||||||
favorites: FavoriteController(InMemoryFavoriteRepository()),
|
favorites: favorites ?? FavoriteController(InMemoryFavoriteRepository()),
|
||||||
);
|
);
|
||||||
|
|
||||||
Drama _card(String id) => Drama(
|
Drama _card(String id) => Drama(
|
||||||
@@ -98,4 +102,55 @@ void main() {
|
|||||||
expect(logic.drama.episodes, hasLength(3));
|
expect(logic.drama.episodes, hasLength(3));
|
||||||
logic.dispose();
|
logic.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should_adjust_favorite_count_only_after_successful_changes', () async {
|
||||||
|
final repository = InMemoryFavoriteRepository(cards: [_favoriteDrama]);
|
||||||
|
final favorites = FavoriteController(repository);
|
||||||
|
await favorites.load();
|
||||||
|
final logic = _logic(
|
||||||
|
_favoriteDrama,
|
||||||
|
FakeWatchHistory(),
|
||||||
|
favorites: favorites,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(logic.adjustedFavoriteCount, 2);
|
||||||
|
expect(await logic.toggleFavorite(), isTrue);
|
||||||
|
expect(logic.adjustedFavoriteCount, 3);
|
||||||
|
|
||||||
|
repository.failToggle = true;
|
||||||
|
expect(await logic.toggleFavorite(), isFalse);
|
||||||
|
expect(logic.adjustedFavoriteCount, 3);
|
||||||
|
|
||||||
|
repository.failToggle = false;
|
||||||
|
expect(await logic.toggleFavorite(), isTrue);
|
||||||
|
expect(logic.adjustedFavoriteCount, 2);
|
||||||
|
logic.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should_decrement_favorite_count_from_saved_state', () async {
|
||||||
|
final repository = InMemoryFavoriteRepository(
|
||||||
|
saved: {'favorite-detail'},
|
||||||
|
cards: [_favoriteDrama],
|
||||||
|
);
|
||||||
|
final favorites = FavoriteController(repository);
|
||||||
|
await favorites.load();
|
||||||
|
final logic = _logic(
|
||||||
|
_favoriteDrama,
|
||||||
|
FakeWatchHistory(),
|
||||||
|
favorites: favorites,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(await logic.toggleFavorite(), isTrue);
|
||||||
|
expect(logic.adjustedFavoriteCount, 1);
|
||||||
|
logic.dispose();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _favoriteDrama = Drama(
|
||||||
|
id: 'favorite-detail',
|
||||||
|
title: 'Favorite detail',
|
||||||
|
synopsis: 'favorite count',
|
||||||
|
posterUrl: '/files/favorite-detail.jpg',
|
||||||
|
favoriteCount: 2,
|
||||||
|
detailLoaded: true,
|
||||||
|
);
|
||||||
@@ -140,11 +140,23 @@ void main() {
|
|||||||
|
|
||||||
expect(decoration().color, AppColors.card);
|
expect(decoration().color, AppColors.card);
|
||||||
expect((decoration().border! as Border).top.color, AppColors.blue);
|
expect((decoration().border! as Border).top.color, AppColors.blue);
|
||||||
|
expect(
|
||||||
|
tester
|
||||||
|
.widget<Text>(find.byKey(const Key('detail-stat-favorite-value')))
|
||||||
|
.data,
|
||||||
|
'3200 Favorite',
|
||||||
|
);
|
||||||
|
|
||||||
await tester.tap(find.byKey(const Key('detail-favorite')));
|
await tester.tap(find.byKey(const Key('detail-favorite')));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(decoration().color, AppColors.blue);
|
expect(decoration().color, AppColors.blue);
|
||||||
|
expect(
|
||||||
|
tester
|
||||||
|
.widget<Text>(find.byKey(const Key('detail-stat-favorite-value')))
|
||||||
|
.data,
|
||||||
|
'3201 Favorite',
|
||||||
|
);
|
||||||
expect(
|
expect(
|
||||||
find.descendant(
|
find.descendant(
|
||||||
of: find.byKey(const Key('detail-favorite')),
|
of: find.byKey(const Key('detail-favorite')),
|
||||||
@@ -152,6 +164,16 @@ void main() {
|
|||||||
),
|
),
|
||||||
findsOneWidget,
|
findsOneWidget,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await tester.tap(find.byKey(const Key('detail-favorite')));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
tester
|
||||||
|
.widget<Text>(find.byKey(const Key('detail-stat-favorite-value')))
|
||||||
|
.data,
|
||||||
|
'3200 Favorite',
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('should_expand_synopsis_when_it_exceeds_three_lines', (
|
testWidgets('should_expand_synopsis_when_it_exceeds_three_lines', (
|
||||||
|
|||||||
@@ -175,6 +175,68 @@ void main() {
|
|||||||
expect(find.byKey(const Key('player-comments-sheet')), findsOneWidget);
|
expect(find.byKey(const Key('player-comments-sheet')), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('should_refresh_comment_count_after_comment_sheet_closes', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
SharedPreferencesAsyncPlatform.instance =
|
||||||
|
InMemorySharedPreferencesAsync.withData({
|
||||||
|
'auth.accessToken': 'access',
|
||||||
|
'auth.refreshToken': 'refresh',
|
||||||
|
'auth.user': '{"uid":"9","nickname":"Viewer","avatarUrl":""}',
|
||||||
|
});
|
||||||
|
final comments = FakeCommentRepository(countResult: 97);
|
||||||
|
await _pumpApp(tester, commentRepository: comments);
|
||||||
|
await _openSeriesPlayer(tester);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
tester
|
||||||
|
.widget<Text>(find.byKey(const Key('player-rail-comments-count')))
|
||||||
|
.data,
|
||||||
|
'96',
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tap(find.byKey(const Key('player-rail-comments')));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.byKey(const Key('player-comments-sheet')), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.binding.handlePopRoute();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(comments.countCalls, ['city-lights-demo']);
|
||||||
|
expect(
|
||||||
|
tester
|
||||||
|
.widget<Text>(find.byKey(const Key('player-rail-comments-count')))
|
||||||
|
.data,
|
||||||
|
'97',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('should_update_home_feed_favorite_count_after_toggle', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
SharedPreferencesAsyncPlatform.instance =
|
||||||
|
InMemorySharedPreferencesAsync.withData({
|
||||||
|
'auth.accessToken': 'access',
|
||||||
|
'auth.refreshToken': 'refresh',
|
||||||
|
'auth.user': '{"uid":"9","nickname":"Viewer","avatarUrl":""}',
|
||||||
|
});
|
||||||
|
final catalog = FakeCatalogRepository(dramas: [_countingDrama]);
|
||||||
|
await _pumpApp(tester, catalog: catalog);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
tester.widget<Text>(find.byKey(const Key('rail-save-count'))).data,
|
||||||
|
'2',
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tap(find.byKey(const Key('rail-save')));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
tester.widget<Text>(find.byKey(const Key('rail-save-count'))).data,
|
||||||
|
'3',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('should_show_center_toggle_only_when_paused', (tester) async {
|
testWidgets('should_show_center_toggle_only_when_paused', (tester) async {
|
||||||
await _pumpApp(tester);
|
await _pumpApp(tester);
|
||||||
await _openSeriesPlayer(tester);
|
await _openSeriesPlayer(tester);
|
||||||
@@ -1211,6 +1273,7 @@ Future<void> _pumpApp(
|
|||||||
FakeCatalogRepository? catalog,
|
FakeCatalogRepository? catalog,
|
||||||
FakePlaybackRepository? playback,
|
FakePlaybackRepository? playback,
|
||||||
FakeInteractionRepository? interactions,
|
FakeInteractionRepository? interactions,
|
||||||
|
FakeCommentRepository? commentRepository,
|
||||||
PlayerFactory? playerFactory,
|
PlayerFactory? playerFactory,
|
||||||
}) async {
|
}) async {
|
||||||
tester.view.physicalSize = size;
|
tester.view.physicalSize = size;
|
||||||
@@ -1226,7 +1289,7 @@ Future<void> _pumpApp(
|
|||||||
watchHistoryRepository: InMemoryWatchHistoryRepository(),
|
watchHistoryRepository: InMemoryWatchHistoryRepository(),
|
||||||
favoriteRepository: InMemoryFavoriteRepository(cards: repository.dramas),
|
favoriteRepository: InMemoryFavoriteRepository(cards: repository.dramas),
|
||||||
playbackRepository: playback ?? FakePlaybackRepository(),
|
playbackRepository: playback ?? FakePlaybackRepository(),
|
||||||
commentRepository: FakeCommentRepository(),
|
commentRepository: commentRepository ?? FakeCommentRepository(),
|
||||||
interactionRepository: interactions ?? FakeInteractionRepository(),
|
interactionRepository: interactions ?? FakeInteractionRepository(),
|
||||||
noticeRepository: FakeNoticeRepository(),
|
noticeRepository: FakeNoticeRepository(),
|
||||||
haptics: FakeHaptics(),
|
haptics: FakeHaptics(),
|
||||||
@@ -1235,6 +1298,27 @@ Future<void> _pumpApp(
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _countingDrama = Drama(
|
||||||
|
id: 'counting-demo',
|
||||||
|
title: 'Counting Demo',
|
||||||
|
synopsis: 'Counting interaction totals',
|
||||||
|
posterUrl: '/files/counting-demo.png',
|
||||||
|
genre: 'Romance',
|
||||||
|
detailLoaded: true,
|
||||||
|
favoriteCount: 2,
|
||||||
|
commentCount: 3,
|
||||||
|
likeCount: 4,
|
||||||
|
episodes: [
|
||||||
|
Episode(
|
||||||
|
id: 'counting-demo-1',
|
||||||
|
number: 1,
|
||||||
|
title: 'Episode 1',
|
||||||
|
isFree: true,
|
||||||
|
videoUrl: 'https://example.test/counting-1.mp4',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
class _RecordingPlayer implements PlayerPort {
|
class _RecordingPlayer implements PlayerPort {
|
||||||
bool playing = false;
|
bool playing = false;
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ class FakeCommentRepository implements CommentRepository {
|
|||||||
this.publishError,
|
this.publishError,
|
||||||
this.deleteError,
|
this.deleteError,
|
||||||
this.pageError,
|
this.pageError,
|
||||||
|
this.countResult = 0,
|
||||||
|
this.countError,
|
||||||
}) : pages = pages ?? const [];
|
}) : pages = pages ?? const [];
|
||||||
|
|
||||||
final List<CommentPage> pages;
|
final List<CommentPage> pages;
|
||||||
@@ -49,9 +51,12 @@ class FakeCommentRepository implements CommentRepository {
|
|||||||
final Object? publishError;
|
final Object? publishError;
|
||||||
final Object? deleteError;
|
final Object? deleteError;
|
||||||
final Object? pageError;
|
final Object? pageError;
|
||||||
|
final int countResult;
|
||||||
|
final Object? countError;
|
||||||
final List<CommentPageCall> pageCalls = [];
|
final List<CommentPageCall> pageCalls = [];
|
||||||
final List<PublishCall> publishCalls = [];
|
final List<PublishCall> publishCalls = [];
|
||||||
final List<String> deleteCalls = [];
|
final List<String> deleteCalls = [];
|
||||||
|
final List<String> countCalls = [];
|
||||||
int _pageIndex = 0;
|
int _pageIndex = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -75,6 +80,14 @@ class FakeCommentRepository implements CommentRepository {
|
|||||||
return pages[_pageIndex++];
|
return pages[_pageIndex++];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<int> count(String dramaId) async {
|
||||||
|
countCalls.add(dramaId);
|
||||||
|
final error = countError;
|
||||||
|
if (error != null) throw error;
|
||||||
|
return countResult;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<DramaComment> publish({
|
Future<DramaComment> publish({
|
||||||
required String dramaId,
|
required String dramaId,
|
||||||
|
|||||||
Reference in new issue
Block a user