chore(client): 归档原型阶段未提交改动(binding/logic/view 分层与各页迭代)

为什么:客户端原型在 14d7bd3 之后又做了一轮(页面分层、发现页、分类滑动、播放器控件等),
一直留在工作区没入库。下一批账号链路要改其中的 app.dart / home_view / player_view / mine_view,
先归档这批既存改动,本批的实际 diff 才说得清边界。

只归档,不改行为:当前工作区 flutter analyze 0 问题、flutter test 148 全过。
This commit is contained in:
Vicky committed 2026-10-15 17:53:27 +08:00
1 parent 14d7bd39c7
commit 77d5748dbb
33 files changed
+1264 -1064

No files matched your search

@@ -1,104 +1,36 @@
import '../models/drama.dart';
import '../models/home_feed.dart';
import '../models/play_ticket.dart';
import '../remote/catalog_api.dart';
/// 目录数据来源:首页/发现页聚合、详情、播放票据。
///
/// 首页卡片不带剧集,进详情或播放时才按需取,保证列表接口足够轻。
abstract interface class CatalogRepository {
Future<List<Drama>> fetchDramas();
Future<HomeFeed> fetchHomeFeed();
Future<HomeFeed> fetchDiscoverFeed();
Future<Drama> fetchDrama(String dramaId);
Future<PlayTicket> resolvePlay(String episodeId);
}
class DemoCatalogRepository implements CatalogRepository {
class HttpCatalogRepository implements CatalogRepository {
HttpCatalogRepository(this._api);
final CatalogApi _api;
@override
Future<List<Drama>> fetchDramas() async => const [
Drama(
id: 'the-signal-demo',
title: 'The Signal',
synopsis:
'A mock series for testing vertical episode playback with one reused local clip.',
posterAsset: 'assets/images/stitch_signal_poster_clean.png',
format: DramaFormat.animated,
episodes: [
Episode(
id: 'signal-01',
number: 1,
title: 'Episode 1',
assetPath: 'assets/media/demo_clip.mp4',
isFree: true,
),
Episode(
id: 'signal-02',
number: 2,
title: 'Episode 2',
assetPath: 'assets/media/demo_clip.mp4',
isFree: true,
),
Episode(
id: 'signal-03',
number: 3,
title: 'Episode 3',
assetPath: 'assets/media/demo_clip.mp4',
isFree: true,
),
],
),
Drama(
id: 'city-lights-demo',
title: 'City Lights & Velvet Nights',
synopsis:
'A mock romance series using the same local video for navigation testing.',
posterAsset: 'assets/images/stitch_city_poster_clean.png',
format: DramaFormat.liveAction,
episodes: [
Episode(
id: 'city-01',
number: 1,
title: 'Episode 1',
assetPath: 'assets/media/demo_clip.mp4',
isFree: true,
),
Episode(
id: 'city-02',
number: 2,
title: 'Episode 2',
assetPath: 'assets/media/demo_clip.mp4',
isFree: true,
),
Episode(
id: 'city-03',
number: 3,
title: 'Episode 3',
assetPath: 'assets/media/demo_clip.mp4',
isFree: true,
),
],
),
Drama(
id: 'vengeance-demo',
title: 'Vengeance Rising',
synopsis:
'A mock action series using the same local video for navigation testing.',
posterAsset: 'assets/images/stitch_vengeance_poster_clean.png',
format: DramaFormat.liveAction,
episodes: [
Episode(
id: 'vengeance-01',
number: 1,
title: 'Episode 1',
assetPath: 'assets/media/demo_clip.mp4',
isFree: true,
),
Episode(
id: 'vengeance-02',
number: 2,
title: 'Episode 2',
assetPath: 'assets/media/demo_clip.mp4',
isFree: true,
),
Episode(
id: 'vengeance-03',
number: 3,
title: 'Episode 3',
assetPath: 'assets/media/demo_clip.mp4',
isFree: true,
),
],
),
];
Future<HomeFeed> fetchHomeFeed() => _api.homeFeed();
@override
Future<HomeFeed> fetchDiscoverFeed() => _api.discoverFeed();
@override
Future<Drama> fetchDrama(String dramaId) => _api.dramaDetail(dramaId);
@override
Future<PlayTicket> resolvePlay(String episodeId) =>
_api.playTicket(episodeId);
}
@@ -1,5 +1,7 @@
import 'package:shared_preferences/shared_preferences.dart';
import '../models/drama.dart';
const demoProfileId = 'demo-local-v1';
abstract interface class WatchHistoryRepository {
@@ -60,6 +62,17 @@ class SharedPreferencesWatchHistoryRepository
}
}
/// 续看位置:列表卡片没有剧集列表,取不到首集时退回最近观看的那一集。
extension WatchHistoryResume on WatchHistoryRepository {
Future<Duration?> resumePositionFor(String profileId, Drama drama) async {
final episodeId = drama.episodes.isNotEmpty
? drama.episodes.first.id
: await lastEpisodeId(profileId, drama.id);
if (episodeId == null) return null;
return positionFor(profileId, drama.id, episodeId);
}
}
Duration clampResumePosition(Duration saved, Duration duration) {
if (duration <= const Duration(milliseconds: 500)) return Duration.zero;
if (saved <= Duration.zero) return Duration.zero;