129 lines
4.0 KiB
Dart
129 lines
4.0 KiB
Dart
import 'dart:convert';
|
|
import 'dart:io' show File;
|
|
|
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
|
|
import '../../../common/storage/storage_service.dart';
|
|
import '../data/ads_api_client.dart';
|
|
|
|
class SplashAdEntry {
|
|
const SplashAdEntry(this.ad, this.dailyCap, this.assetPath, this.cachedAtMs);
|
|
final AdCreative ad;
|
|
final int dailyCap;
|
|
final String? assetPath;
|
|
final int cachedAtMs;
|
|
bool get valid =>
|
|
ad.isRenderable &&
|
|
DateTime.now().millisecondsSinceEpoch <
|
|
cachedAtMs + const Duration(hours: 24).inMilliseconds &&
|
|
(ad.endAtMs <= 0 || ad.endAtMs > DateTime.now().millisecondsSinceEpoch);
|
|
Map<String, dynamic> toJson() => {
|
|
'ad': ad.toJson(),
|
|
'dailyCap': dailyCap,
|
|
'assetPath': assetPath,
|
|
'cachedAtMs': cachedAtMs,
|
|
};
|
|
factory SplashAdEntry.fromJson(Map<String, dynamic> map) => SplashAdEntry(
|
|
AdCreative.fromJson(Map<String, dynamic>.from(map['ad'] as Map)),
|
|
(map['dailyCap'] as num?)?.toInt() ?? 0,
|
|
map['assetPath'] as String?,
|
|
(map['cachedAtMs'] as num?)?.toInt() ?? 0,
|
|
);
|
|
}
|
|
|
|
class SplashAdController {
|
|
SplashAdController(this._api);
|
|
final AdsApiClient _api;
|
|
static const _entryKey = 'ads.splash.entry';
|
|
static const _dailyKey = 'ads.splash.daily';
|
|
Future<SplashAdEntry?> coldLaunchDecision() async {
|
|
final raw = StorageService.to.preferences.getString(_entryKey);
|
|
if (raw == null) return null;
|
|
try {
|
|
final entry = SplashAdEntry.fromJson(
|
|
Map<String, dynamic>.from(jsonDecode(raw) as Map),
|
|
);
|
|
if (!entry.valid || !_underCap(entry) || !await _ready(entry)) {
|
|
return null;
|
|
}
|
|
return entry;
|
|
} catch (_) {
|
|
await clearCache();
|
|
return null;
|
|
}
|
|
}
|
|
|
|
bool _underCap(SplashAdEntry entry) {
|
|
final value = StorageService.to.preferences.getString(_dailyKey);
|
|
final today = DateTime.now().toIso8601String().substring(0, 10);
|
|
if (value == null) return true;
|
|
final map = Map<String, dynamic>.from(jsonDecode(value) as Map);
|
|
return map['date'] != today ||
|
|
entry.dailyCap <= 0 ||
|
|
((map['count'] as num?)?.toInt() ?? 0) < entry.dailyCap;
|
|
}
|
|
|
|
Future<bool> _ready(SplashAdEntry entry) async =>
|
|
entry.assetPath != null && await File(entry.assetPath!).exists();
|
|
Future<void> markShown(SplashAdEntry entry) async {
|
|
final today = DateTime.now().toIso8601String().substring(0, 10);
|
|
final raw = StorageService.to.preferences.getString(_dailyKey);
|
|
var count = 0;
|
|
if (raw != null) {
|
|
final m = Map<String, dynamic>.from(jsonDecode(raw) as Map);
|
|
if (m['date'] == today) count = (m['count'] as num?)?.toInt() ?? 0;
|
|
}
|
|
await StorageService.to.preferences.setString(
|
|
_dailyKey,
|
|
jsonEncode({'date': today, 'count': count + 1}),
|
|
);
|
|
await _report(entry.ad, 'ad_impression');
|
|
}
|
|
|
|
Future<void> refresh() async {
|
|
try {
|
|
final result = await _api.getAdResult(splashAdPlacement);
|
|
if (!result.enabled || result.ad == null) {
|
|
await _clearEntry();
|
|
return;
|
|
}
|
|
final file = await DefaultCacheManager().getSingleFile(
|
|
result.ad!.imageUrl,
|
|
);
|
|
if (!await file.exists()) return;
|
|
await StorageService.to.preferences.setString(
|
|
_entryKey,
|
|
jsonEncode(
|
|
SplashAdEntry(
|
|
result.ad!,
|
|
result.dailyCap,
|
|
file.path,
|
|
DateTime.now().millisecondsSinceEpoch,
|
|
).toJson(),
|
|
),
|
|
);
|
|
} catch (_) {}
|
|
}
|
|
|
|
Future<void> _report(AdCreative ad, String event, {String? clickId}) async {
|
|
try {
|
|
await _api.report(ad, event, clickId: clickId);
|
|
} catch (_) {}
|
|
}
|
|
|
|
Future<void> skip(SplashAdEntry entry) => _report(entry.ad, 'ad_skip');
|
|
Future<void> clearCache() async {
|
|
await _clearEntry();
|
|
await StorageService.to.preferences.remove(_dailyKey);
|
|
}
|
|
|
|
Future<void> _clearEntry() async {
|
|
await StorageService.to.preferences.remove(_entryKey);
|
|
}
|
|
}
|
|
|
|
final splashAdControllerProvider = Provider(
|
|
(ref) => SplashAdController(ref.watch(adsApiClientProvider)),
|
|
);
|