feat(reward): 新增客户端奖励中心

This commit is contained in:
Zhou Weidong committed 2026-11-13 13:43:38 +08:00
1 parent 67670d46b1
commit bea08110c9
68 files changed
+3987 -332

No files matched your search

+5 -5
View File
@@ -63,7 +63,7 @@ void main() {
logic.openProfile();
expect(logic.tab, 3);
expect(logic.tab, 4);
logic.dispose();
});
@@ -159,10 +159,10 @@ void main() {
expect(logic.tab, 1);
logic.myAction(MyPageAction.following);
expect(logic.tab, 2);
expect(logic.tab, 3);
logic.openFollowingHistory();
expect(logic.tab, 2);
expect(logic.tab, 3);
expect(logic.followingHistoryRequest, 1);
logic.myAction(MyPageAction.resume);
@@ -179,10 +179,10 @@ void main() {
final logic = HomeLogic(dramas);
logic.discoverAction(DiscoverPageAction.mine);
expect(logic.tab, 3);
expect(logic.tab, 4);
logic.discoverAction(DiscoverPageAction.following);
expect(logic.tab, 2);
expect(logic.tab, 3);
logic.discoverAction(DiscoverPageAction.home);
expect(logic.tab, 0);
+138 -64
View File
@@ -3,10 +3,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:fruitshorts/app.dart';
import 'package:fruitshorts/core/data/models/reward.dart';
import 'package:fruitshorts/core/data/models/wallet.dart';
import 'package:fruitshorts/core/data/repositories/commerce_repository.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/reward_repository.dart';
import 'package:fruitshorts/core/data/repositories/watch_history_repository.dart';
import 'package:fruitshorts/core/theme/app_colors.dart';
import 'package:fruitshorts/core/theme/app_dimens.dart';
@@ -26,6 +28,7 @@ const _keys = [
Key('home-tabs-bar'),
Key('tab-home'),
Key('tab-recommend'),
Key('tab-reward'),
Key('tab-follow'),
Key('tab-mine'),
];
@@ -40,7 +43,7 @@ void main() {
});
});
testWidgets('should_theme_four_tabs_and_switch_selected_state_when_tapped', (
testWidgets('should_theme_five_tabs_and_switch_selected_state_when_tapped', (
tester,
) async {
final haptics = FakeHaptics();
@@ -83,6 +86,13 @@ void main() {
.map((key) => tester.getCenter(find.byKey(key)).dx)
.toList();
expect(tabCenters, orderedEquals(tabCenters.toList()..sort()));
expect(
find.descendant(
of: find.byKey(const Key('tab-reward')),
matching: find.text('Rewards'),
),
findsOneWidget,
);
expect(
find.descendant(
of: find.byKey(const Key('tab-home')),
@@ -90,6 +100,13 @@ void main() {
),
findsOneWidget,
);
expect(
find.descendant(
of: find.byKey(const Key('tab-reward')),
matching: find.byType(RootNavGlyph),
),
findsOneWidget,
);
expect(
find.descendant(
of: find.byKey(const Key('tab-recommend')),
@@ -128,17 +145,6 @@ void main() {
?.color,
AppColors.navUnselected,
);
expect(
tester
.widget<AnimatedScale>(
find.descendant(
of: find.byKey(const Key('tab-home')),
matching: find.byType(AnimatedScale),
),
)
.duration,
lessThanOrEqualTo(const Duration(milliseconds: 200)),
);
final selectedHomeIcon = tester.widget<RootNavGlyph>(
find.descendant(
of: find.byKey(const Key('tab-home')),
@@ -239,64 +245,72 @@ void main() {
);
});
testWidgets('should_map_four_tabs_to_home_recommend_following_and_me', (
tester,
) async {
// 追剧页头部的双语分段文案在测试字体(Ahem)下比真机字体宽约 98pt,
// 这里放宽视口宽度,避免测字体撑破 390 宽度下的分段行。
await _pumpApp(tester, size: const Size(520, 844));
testWidgets(
'should_map_five_tabs_to_home_recommend_reward_following_and_me',
(tester) async {
// 追剧页头部的双语分段文案在测试字体(Ahem)下比真机字体宽约 98pt,
// 这里放宽视口宽度,避免测字体撑破 390 宽度下的分段行。
await _pumpApp(tester, size: const Size(520, 844));
expect(find.byKey(const Key('tab-home')), findsOneWidget);
expect(find.byKey(const Key('tab-recommend')), findsOneWidget);
expect(find.byKey(const Key('tab-follow')), findsOneWidget);
expect(find.byKey(const Key('tab-mine')), findsOneWidget);
expect(find.byKey(const Key('tab-create')), findsNothing);
expect(find.byKey(const Key('recommend-feed-player')), findsOneWidget);
expect(find.byKey(const Key('tab-home')), findsOneWidget);
expect(find.byKey(const Key('tab-recommend')), findsOneWidget);
expect(find.byKey(const Key('tab-reward')), findsOneWidget);
expect(find.byKey(const Key('tab-follow')), findsOneWidget);
expect(find.byKey(const Key('tab-mine')), findsOneWidget);
expect(find.byKey(const Key('tab-create')), findsNothing);
expect(find.byKey(const Key('recommend-feed-player')), findsOneWidget);
await tester.tap(find.byKey(const Key('tab-recommend')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('recommend-channel-nav')), findsOneWidget);
_expectNoFloatingOverlays();
await tester.tap(find.byKey(const Key('tab-recommend')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('recommend-channel-nav')), findsOneWidget);
_expectNoFloatingOverlays();
await tester.tap(find.byKey(const Key('tab-follow')));
await tester.pumpAndSettle();
final favorites = find.byType(FollowingPage);
expect(favorites, findsOneWidget);
expect(find.byKey(const Key('following-page')), findsOneWidget);
expect(find.byType(AppBottomNav), findsOneWidget);
expect(
find.descendant(of: favorites, matching: find.byType(Scaffold)),
findsNothing,
);
expect(Navigator.canPop(tester.element(favorites)), isFalse);
_expectNoFloatingOverlays();
expect(
find.descendant(
of: find.byKey(const Key('tab-follow')),
matching: find.byType(RootNavGlyph),
),
findsOneWidget,
);
await tester.tap(find.byKey(const Key('tab-reward')));
await tester.pumpAndSettle();
expect(find.text('Reward Center'), findsOneWidget);
expect(find.text('Daily task'), findsOneWidget);
_expectNoFloatingOverlays();
await tester.tap(find.byKey(const Key('tab-mine')));
await tester.pumpAndSettle();
expect(find.text('Viewer'), findsOneWidget);
expect(find.byKey(const Key('mine-menu')), findsOneWidget);
expect(find.byKey(const Key('mine-wallet')), findsOneWidget);
expect(find.byKey(const Key('mine-edit')), findsOneWidget);
expect(find.byKey(const Key('mine-library-tab-0')), findsNothing);
_expectNoFloatingOverlays();
await tester.tap(find.byKey(const Key('tab-follow')));
await tester.pumpAndSettle();
final favorites = find.byType(FollowingPage);
expect(favorites, findsOneWidget);
expect(find.byKey(const Key('following-page')), findsOneWidget);
expect(find.byType(AppBottomNav), findsOneWidget);
expect(
find.descendant(of: favorites, matching: find.byType(Scaffold)),
findsNothing,
);
expect(Navigator.canPop(tester.element(favorites)), isFalse);
_expectNoFloatingOverlays();
expect(
find.descendant(
of: find.byKey(const Key('tab-follow')),
matching: find.byType(RootNavGlyph),
),
findsOneWidget,
);
await tester.tap(find.byKey(const Key('tab-follow')));
await tester.pumpAndSettle();
expect(favorites, findsOneWidget);
expect(find.byType(AppBottomNav), findsOneWidget);
expect(Navigator.canPop(tester.element(favorites)), isFalse);
await tester.tap(find.byKey(const Key('tab-mine')));
await tester.pumpAndSettle();
expect(find.text('Viewer'), findsOneWidget);
expect(find.byKey(const Key('mine-menu')), findsOneWidget);
expect(find.byKey(const Key('mine-wallet')), findsOneWidget);
expect(find.byKey(const Key('mine-edit')), findsOneWidget);
expect(find.byKey(const Key('mine-library-tab-0')), findsNothing);
_expectNoFloatingOverlays();
await tester.tap(find.byKey(const Key('tab-home')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('recommend-feed-player')), findsOneWidget);
});
await tester.tap(find.byKey(const Key('tab-follow')));
await tester.pumpAndSettle();
expect(favorites, findsOneWidget);
expect(find.byType(AppBottomNav), findsOneWidget);
expect(Navigator.canPop(tester.element(favorites)), isFalse);
await tester.tap(find.byKey(const Key('tab-home')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('recommend-feed-player')), findsOneWidget);
},
);
testWidgets('should_not_haptic_when_ordinary_list_item_is_opened', (
tester,
@@ -337,6 +351,7 @@ Future<void> _pumpApp(
commentRepository: FakeCommentRepository(),
interactionRepository: FakeInteractionRepository(),
commerceRepository: const _WalletCommerceRepository(),
rewardRepository: const _RewardRepository(),
haptics: haptics ?? FakeHaptics(),
),
);
@@ -371,6 +386,65 @@ class _WalletCommerceRepository implements CommerceRepository {
}
}
class _RewardRepository implements RewardRepository {
const _RewardRepository();
@override
Future<RewardOverview> fetchOverview(String locale) async {
return const RewardOverview(
serverDate: '2026-11-05',
timezone: 'Asia/Jakarta',
coinBalance: 120,
checkin: RewardCheckinState(
taskId: 'checkin',
checkedInToday: false,
streak: 1,
currentDay: 2,
),
sections: [
RewardSection(
code: 'DAILY',
title: 'Daily task',
tasks: [
RewardTask(
id: 'watch',
code: 'WATCH',
type: 'WATCH_DURATION',
title: 'Watch a drama',
subtitle: 'Watch for five minutes',
ctaText: 'Go',
actionType: 'OPEN_HOME',
actionUrl: '',
progressValue: 0,
targetValue: 300,
rewardCoin: 20,
status: 'IN_PROGRESS',
),
],
),
],
);
}
@override
Future<RewardActionResult> checkIn() async {
return const RewardActionResult(
rewardCoin: 10,
coinBalance: 130,
alreadyCompleted: false,
);
}
@override
Future<RewardActionResult> claim(String taskId) async {
return const RewardActionResult(
rewardCoin: 20,
coinBalance: 140,
alreadyCompleted: false,
);
}
}
void _expectNoFloatingOverlays() {
expect(find.byKey(const Key('mine-mini-player')), findsNothing);
expect(find.byType(FloatingActionButton), findsNothing);
+2 -1
View File
@@ -24,7 +24,7 @@ void main() {
InMemorySharedPreferencesAsync.empty();
});
testWidgets('should_render_four_tabs_and_open_following_page', (
testWidgets('should_render_five_tabs_and_open_following_page', (
tester,
) async {
// 追剧页头部双语分段在测试字体(Ahem)下比真机字体宽约 98pt,
@@ -189,6 +189,7 @@ void main() {
const _tabKeys = [
Key('tab-home'),
Key('tab-recommend'),
Key('tab-reward'),
Key('tab-follow'),
Key('tab-mine'),
];
+13 -1
View File
@@ -8,7 +8,7 @@ import 'package:fruitshorts/l10n/app_localizations.dart';
void main() {
testWidgets(
'should_render_four_ordered_glyphs_with_home_selected_when_root_nav_builds',
'should_render_five_ordered_glyphs_with_home_selected_when_root_nav_builds',
(tester) async {
await tester.pumpWidget(
MaterialApp(
@@ -32,7 +32,19 @@ void main() {
false,
false,
false,
false,
]);
final tabKeys = [
const Key('tab-home'),
const Key('tab-recommend'),
const Key('tab-reward'),
const Key('tab-follow'),
const Key('tab-mine'),
];
final centers = tabKeys
.map((key) => tester.getCenter(find.byKey(key)).dx)
.toList();
expect(centers, orderedEquals(centers.toList()..sort()));
},
);
}