feat(app): 完成 P0 钱包解锁、下载落盘与手机号登录

This commit is contained in:
Zhou Weidong committed 2026-10-19 23:46:47 +08:00
1 parent de941c841c
commit 136ee74618
47 files changed
+3264 -70

No files matched your search

+102
View File
@@ -4,7 +4,12 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:fruitshorts/core/data/models/drama.dart';
import 'package:fruitshorts/core/data/models/play_ticket.dart';
import 'package:fruitshorts/core/data/models/player_port.dart';
import 'package:fruitshorts/core/data/models/download_license.dart';
import 'package:fruitshorts/core/data/models/download_policy.dart';
import 'package:fruitshorts/core/data/repositories/download_repository.dart';
import 'package:fruitshorts/core/data/services/android_picture_in_picture.dart';
import 'package:fruitshorts/core/data/services/download_controller.dart';
import 'package:fruitshorts/core/data/services/download_storage.dart';
import 'package:fruitshorts/core/data/services/playback_session.dart';
import 'package:fruitshorts/pages/player/player_logic.dart';
import 'package:fruitshorts/pages/player/player_view.dart';
@@ -116,6 +121,34 @@ void main() {
});
});
test('should_play_local_file_without_ticket_when_episode_is_downloaded',
() async {
final playback = FakePlaybackRepository();
final downloads = DownloadController(
_EmptyDownloadRepository(),
storage: _LocalStorage(),
);
await downloads.restore();
final drama = _drama('signal');
final logic = PlayerLogic(
history: FakeWatchHistory(),
playback: playback,
pictureInPicture: _pip(),
downloads: downloads,
);
logic.attach(
PlayerPage(drama: drama, initialIndex: 0),
isMounted: () => true,
);
logic.start(factory: _RecordingFactory().create, coordinator: PlaybackCoordinator());
await pumpEventQueue();
await logic.session.playbackSettled;
expect(playback.requests, isEmpty);
expect(logic.sessionEpisodes.first.videoUrl, 'file:///local/signal-1.mp4');
});
group('surface flags', () {
test('should_set_immersive_when_picture_in_picture_callback_fires', () async {
final factory = _RecordingFactory();
@@ -430,6 +463,75 @@ void main() {
});
}
class _LocalStorage implements DownloadStorage {
@override
Future<Map<String, String>> restore() async => {
'signal-1': 'file:///local/signal-1.mp4',
};
@override
Future<String?> localPathFor(String episodeId) async =>
episodeId == 'signal-1' ? 'file:///local/signal-1.mp4' : null;
@override
Future<DownloadedMedia> download({
required String episodeId,
required String url,
DownloadProgress? onProgress,
}) async => throw UnimplementedError();
@override
Future<void> delete(String episodeId) async {}
@override
Future<void> clear() async {}
}
class _EmptyDownloadRepository implements DownloadRepository {
@override
Future<DownloadPolicy> fetchPolicy() async => const DownloadPolicy(
maxPerDevice: 20,
licenseDays: 30,
wifiOnly: false,
maxQuality: 'AUTO',
maxStorageBytes: 0,
used: 0,
remaining: 20,
expirePolicyText: '',
);
@override
Future<List<DownloadLicense>> list() async => const [];
@override
Future<DownloadLicense> grant(String episodeId, {String? quality}) async =>
throw UnimplementedError();
@override
Future<void> updateSettings({bool? wifiOnly, String? maxQuality}) async {}
@override
Future<DownloadLicense> refreshUrl(String episodeId) async =>
throw UnimplementedError();
@override
Future<DownloadLicense> reportProgress(
String episodeId, {
String? deviceId,
String? status,
int? progressPercent,
int? downloadedBytes,
int? totalBytes,
String? errorMsg,
}) async => throw UnimplementedError();
@override
Future<void> remove(String episodeId) async {}
@override
Future<int> clear() async => 0;
}
/// Counts native-style allocations so leaks are visible to a test.
class _RecordingFactory {
final List<_RecordingPlayer> created = [];