Initial commit
This commit is contained in:
@@ -0,0 +1,362 @@
|
||||
part of '../di_providers.dart';
|
||||
|
||||
typedef TmdbMediaKey = ({String tmdbId, String mediaType});
|
||||
typedef TmdbSeasonKey = ({String tmdbId, int seasonNumber});
|
||||
|
||||
|
||||
typedef _TmdbReady = ({TmdbRequestConfig config, String language});
|
||||
|
||||
|
||||
final _tmdbReadyProvider = FutureProvider<_TmdbReady?>((ref) async {
|
||||
final settings = await ref.watch(tmdbSettingsProvider.future);
|
||||
if (!settings.canRequest) return null;
|
||||
|
||||
if (settings.accessMode == TmdbAccessMode.smAccount) {
|
||||
final account = await ref.watch(smAccountProvider.future);
|
||||
if (!account.canUseDataPlane) return null;
|
||||
final notifier = ref.read(smAccountProvider.notifier);
|
||||
final token = await notifier.getAccessToken();
|
||||
if (token == null) return null;
|
||||
return (
|
||||
config: TmdbRequestConfig(
|
||||
apiBaseUrl: account.tmdbApiBaseUrl,
|
||||
imageBaseUrl: TmdbSettingsState.officialImageBaseUrl,
|
||||
accessToken: token,
|
||||
apiKey: '',
|
||||
onUnauthorized: () => notifier.getAccessToken(force: true),
|
||||
),
|
||||
language: settings.language,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
config: TmdbRequestConfig(
|
||||
apiBaseUrl: settings.effectiveApiBaseUrl,
|
||||
imageBaseUrl: settings.effectiveImageBaseUrl,
|
||||
accessToken: settings.effectiveAccessToken,
|
||||
apiKey: settings.effectiveApiKey,
|
||||
),
|
||||
language: settings.language,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
final _tmdbCacheScopeProvider = FutureProvider<DiscoverPageCacheScope?>((
|
||||
ref,
|
||||
) async {
|
||||
final settings = await ref.watch(tmdbSettingsProvider.future);
|
||||
if (!settings.canRequest) return null;
|
||||
|
||||
if (settings.accessMode == TmdbAccessMode.smAccount) {
|
||||
final account = await ref.watch(smAccountProvider.future);
|
||||
if (!account.hasActiveSession) return null;
|
||||
return DiscoverPageCacheScope(
|
||||
apiBaseUrl: account.tmdbApiBaseUrl,
|
||||
imageBaseUrl: TmdbSettingsState.officialImageBaseUrl,
|
||||
language: settings.language,
|
||||
);
|
||||
}
|
||||
|
||||
return DiscoverPageCacheScope(
|
||||
apiBaseUrl: settings.effectiveApiBaseUrl,
|
||||
imageBaseUrl: settings.effectiveImageBaseUrl,
|
||||
language: settings.language,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
final tmdbEnrichedDetailProvider =
|
||||
FutureProvider.family<TmdbEnrichedDetail?, TmdbMediaKey>((ref, key) async {
|
||||
final ready = await ref.watch(_tmdbReadyProvider.future);
|
||||
if (ready == null || key.tmdbId.isEmpty) return null;
|
||||
return ref
|
||||
.read(tmdbGatewayProvider)
|
||||
.getEnrichedDetail(
|
||||
ready.config,
|
||||
key.tmdbId,
|
||||
key.mediaType,
|
||||
language: ready.language,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
final tmdbImagesProvider = Provider.family<TmdbImageSet?, TmdbMediaKey>((
|
||||
ref,
|
||||
key,
|
||||
) {
|
||||
return ref.watch(tmdbEnrichedDetailProvider(key)).value?.images;
|
||||
});
|
||||
|
||||
|
||||
final tmdbMediaDetailProvider = Provider.family<TmdbMediaDetail?, TmdbMediaKey>(
|
||||
(ref, key) {
|
||||
return ref.watch(tmdbEnrichedDetailProvider(key)).value?.detail;
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
final tmdbSeasonDetailProvider =
|
||||
FutureProvider.family<TmdbSeasonDetail?, TmdbSeasonKey>((ref, key) async {
|
||||
final ready = await ref.watch(_tmdbReadyProvider.future);
|
||||
if (ready == null || key.tmdbId.isEmpty) return null;
|
||||
return ref
|
||||
.read(tmdbGatewayProvider)
|
||||
.getSeasonDetail(
|
||||
ready.config,
|
||||
key.tmdbId,
|
||||
key.seasonNumber,
|
||||
language: ready.language,
|
||||
);
|
||||
});
|
||||
|
||||
enum _TmdbRecommendationRowKind {
|
||||
trendingDay,
|
||||
trendingWeek,
|
||||
popularMovies,
|
||||
popularTv,
|
||||
airingTodayTv,
|
||||
topRatedMovies,
|
||||
}
|
||||
|
||||
final _tmdbRecommendationRowProvider =
|
||||
AsyncNotifierProvider.family<
|
||||
_TmdbRecommendationRowNotifier,
|
||||
TmdbRecommendationsRes?,
|
||||
_TmdbRecommendationRowKind
|
||||
>(_TmdbRecommendationRowNotifier.new);
|
||||
|
||||
final tmdbTrendingDayProvider = _tmdbRecommendationRowProvider(
|
||||
_TmdbRecommendationRowKind.trendingDay,
|
||||
);
|
||||
|
||||
final tmdbTrendingWeekProvider = _tmdbRecommendationRowProvider(
|
||||
_TmdbRecommendationRowKind.trendingWeek,
|
||||
);
|
||||
|
||||
final tmdbPopularMoviesProvider = _tmdbRecommendationRowProvider(
|
||||
_TmdbRecommendationRowKind.popularMovies,
|
||||
);
|
||||
|
||||
final tmdbPopularTvProvider = _tmdbRecommendationRowProvider(
|
||||
_TmdbRecommendationRowKind.popularTv,
|
||||
);
|
||||
|
||||
final tmdbAiringTodayTvProvider = _tmdbRecommendationRowProvider(
|
||||
_TmdbRecommendationRowKind.airingTodayTv,
|
||||
);
|
||||
|
||||
final tmdbTopRatedMoviesProvider = _tmdbRecommendationRowProvider(
|
||||
_TmdbRecommendationRowKind.topRatedMovies,
|
||||
);
|
||||
|
||||
|
||||
typedef TmdbDiscoverKey = ({
|
||||
String mediaType,
|
||||
String sortBy,
|
||||
String? withGenres,
|
||||
String? voteCountGte,
|
||||
});
|
||||
|
||||
final tmdbDiscoverProvider =
|
||||
AsyncNotifierProvider.family<
|
||||
_TmdbDiscoverRowNotifier,
|
||||
TmdbRecommendationsRes?,
|
||||
TmdbDiscoverKey
|
||||
>(_TmdbDiscoverRowNotifier.new);
|
||||
|
||||
class _TmdbRecommendationRowNotifier
|
||||
extends AsyncNotifier<TmdbRecommendationsRes?> {
|
||||
final _TmdbRecommendationRowKind arg;
|
||||
|
||||
_TmdbRecommendationRowNotifier(this.arg);
|
||||
|
||||
@override
|
||||
Future<TmdbRecommendationsRes?> build() async {
|
||||
final rowKey = _rowKeyForKind(arg);
|
||||
|
||||
final scope = await ref.watch(_tmdbCacheScopeProvider.future);
|
||||
if (scope == null) return null;
|
||||
|
||||
final cached = await _readDiscoverRowCacheByScope(ref, scope, rowKey);
|
||||
if (cached != null) {
|
||||
unawaited(_refreshFromNetwork(arg, rowKey));
|
||||
return cached;
|
||||
}
|
||||
|
||||
final ready = await ref.watch(_tmdbReadyProvider.future);
|
||||
if (ready == null) return null;
|
||||
return _fetchAndCache(arg, ready, rowKey);
|
||||
}
|
||||
|
||||
Future<void> _refreshFromNetwork(
|
||||
_TmdbRecommendationRowKind kind,
|
||||
String rowKey,
|
||||
) async {
|
||||
try {
|
||||
final ready = await ref.read(_tmdbReadyProvider.future);
|
||||
if (ready == null) return;
|
||||
final fresh = await _fetchAndCache(kind, ready, rowKey);
|
||||
if (fresh != null) {
|
||||
state = AsyncValue.data(fresh);
|
||||
}
|
||||
} catch (e) {
|
||||
AppLogger.warn('TMDB', 'refresh discover row $rowKey failed', e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<TmdbRecommendationsRes?> _fetchAndCache(
|
||||
_TmdbRecommendationRowKind kind,
|
||||
_TmdbReady ready,
|
||||
String rowKey,
|
||||
) async {
|
||||
final fresh = await _fetchRow(kind, ready);
|
||||
if (fresh != null) {
|
||||
await _writeDiscoverRowCache(ref, ready, rowKey, fresh);
|
||||
}
|
||||
return fresh;
|
||||
}
|
||||
|
||||
Future<TmdbRecommendationsRes?> _fetchRow(
|
||||
_TmdbRecommendationRowKind kind,
|
||||
_TmdbReady ready,
|
||||
) {
|
||||
final gateway = ref.read(tmdbGatewayProvider);
|
||||
return switch (kind) {
|
||||
_TmdbRecommendationRowKind.trendingDay => gateway.getTrending(
|
||||
ready.config,
|
||||
language: ready.language,
|
||||
mediaType: 'all',
|
||||
timeWindow: 'day',
|
||||
),
|
||||
_TmdbRecommendationRowKind.trendingWeek => gateway.getTrending(
|
||||
ready.config,
|
||||
language: ready.language,
|
||||
mediaType: 'all',
|
||||
timeWindow: 'week',
|
||||
),
|
||||
_TmdbRecommendationRowKind.popularMovies => gateway.getPopularMovies(
|
||||
ready.config,
|
||||
language: ready.language,
|
||||
),
|
||||
_TmdbRecommendationRowKind.popularTv => gateway.getPopularTv(
|
||||
ready.config,
|
||||
language: ready.language,
|
||||
),
|
||||
_TmdbRecommendationRowKind.airingTodayTv => gateway.getAiringTodayTv(
|
||||
ready.config,
|
||||
language: ready.language,
|
||||
),
|
||||
_TmdbRecommendationRowKind.topRatedMovies => gateway.getTopRatedMovies(
|
||||
ready.config,
|
||||
language: ready.language,
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class _TmdbDiscoverRowNotifier extends AsyncNotifier<TmdbRecommendationsRes?> {
|
||||
final TmdbDiscoverKey arg;
|
||||
|
||||
_TmdbDiscoverRowNotifier(this.arg);
|
||||
|
||||
@override
|
||||
Future<TmdbRecommendationsRes?> build() async {
|
||||
final rowKey = _rowKeyForDiscover(arg);
|
||||
|
||||
final scope = await ref.watch(_tmdbCacheScopeProvider.future);
|
||||
if (scope == null) return null;
|
||||
|
||||
final cached = await _readDiscoverRowCacheByScope(ref, scope, rowKey);
|
||||
if (cached != null) {
|
||||
unawaited(_refreshFromNetwork(arg, rowKey));
|
||||
return cached;
|
||||
}
|
||||
|
||||
final ready = await ref.watch(_tmdbReadyProvider.future);
|
||||
if (ready == null) return null;
|
||||
return _fetchAndCache(arg, ready, rowKey);
|
||||
}
|
||||
|
||||
Future<void> _refreshFromNetwork(TmdbDiscoverKey key, String rowKey) async {
|
||||
try {
|
||||
final ready = await ref.read(_tmdbReadyProvider.future);
|
||||
if (ready == null) return;
|
||||
final fresh = await _fetchAndCache(key, ready, rowKey);
|
||||
if (fresh != null) {
|
||||
state = AsyncValue.data(fresh);
|
||||
}
|
||||
} catch (e) {
|
||||
AppLogger.warn('TMDB', 'refresh discover row $rowKey failed', e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<TmdbRecommendationsRes?> _fetchAndCache(
|
||||
TmdbDiscoverKey key,
|
||||
_TmdbReady ready,
|
||||
String rowKey,
|
||||
) async {
|
||||
final fresh = await ref
|
||||
.read(tmdbGatewayProvider)
|
||||
.getDiscover(
|
||||
ready.config,
|
||||
key.mediaType,
|
||||
language: ready.language,
|
||||
sortBy: key.sortBy,
|
||||
withGenres: key.withGenres,
|
||||
voteCountGte: key.voteCountGte,
|
||||
);
|
||||
if (fresh != null) {
|
||||
await _writeDiscoverRowCache(ref, ready, rowKey, fresh);
|
||||
}
|
||||
return fresh;
|
||||
}
|
||||
}
|
||||
|
||||
DiscoverPageCacheScope _cacheScopeForReady(_TmdbReady ready) {
|
||||
return DiscoverPageCacheScope(
|
||||
apiBaseUrl: ready.config.apiBaseUrl,
|
||||
imageBaseUrl: ready.config.imageBaseUrl,
|
||||
language: ready.language,
|
||||
);
|
||||
}
|
||||
|
||||
Future<TmdbRecommendationsRes?> _readDiscoverRowCacheByScope(
|
||||
Ref ref,
|
||||
DiscoverPageCacheScope scope,
|
||||
String rowKey,
|
||||
) async {
|
||||
final store = await ref.read(discoverPageCacheStoreProvider.future);
|
||||
final entry = await store.readRow(scope, rowKey);
|
||||
return entry?.data;
|
||||
}
|
||||
|
||||
Future<void> _writeDiscoverRowCache(
|
||||
Ref ref,
|
||||
_TmdbReady ready,
|
||||
String rowKey,
|
||||
TmdbRecommendationsRes data,
|
||||
) async {
|
||||
final store = await ref.read(discoverPageCacheStoreProvider.future);
|
||||
await store.writeRow(_cacheScopeForReady(ready), rowKey, data);
|
||||
}
|
||||
|
||||
String _rowKeyForKind(_TmdbRecommendationRowKind kind) {
|
||||
return switch (kind) {
|
||||
_TmdbRecommendationRowKind.trendingDay => 'trending:day',
|
||||
_TmdbRecommendationRowKind.trendingWeek => 'trending:week',
|
||||
_TmdbRecommendationRowKind.popularMovies => 'popular:movie',
|
||||
_TmdbRecommendationRowKind.popularTv => 'popular:tv',
|
||||
_TmdbRecommendationRowKind.airingTodayTv => 'airing_today:tv',
|
||||
_TmdbRecommendationRowKind.topRatedMovies => 'top_rated:movie',
|
||||
};
|
||||
}
|
||||
|
||||
String _rowKeyForDiscover(TmdbDiscoverKey key) {
|
||||
return [
|
||||
'discover',
|
||||
key.mediaType,
|
||||
key.sortBy,
|
||||
key.withGenres ?? '',
|
||||
key.voteCountGte ?? '',
|
||||
].join(':');
|
||||
}
|
||||
Reference in New Issue
Block a user