ponshu-room-lite/lib/services/api_usage_service.dart

76 lines
2.8 KiB
Dart
Raw Normal View History

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// Gemini API 日次使用回数をローカルで追跡するサービス。
///
/// - 無料枠: 1プロジェクトあたり 20回/日
/// - リセット時刻: UTC 08:00= 17:00 JST 冬時間 / 16:00 JST 夏時間)
/// ※ Gemini API は Pacific Time 基準でリセットされるため、UTC+9 の日本では
/// 冬(PST=UTC-8)は 17:00 JST、夏(PDT=UTC-7)は 16:00 JST となる。
class ApiUsageService {
static const int dailyLimit = 20;
static const _keyCount = 'gemini_usage_count';
static const _keyWindowStart = 'gemini_window_start';
/// 現在のクォータウィンドウ開始時刻UTC 08:00を返す
static DateTime getCurrentWindowStart() {
final now = DateTime.now().toUtc();
final todayReset = DateTime.utc(now.year, now.month, now.day, 8, 0, 0);
return now.isBefore(todayReset)
? todayReset.subtract(const Duration(days: 1))
: todayReset;
}
/// 次のリセット時刻(端末のローカル時間で返す)
static DateTime getNextResetTime() {
return getCurrentWindowStart().add(const Duration(days: 1)).toLocal();
}
/// 今日の使用回数(ウィンドウが変わっていれば自動リセット)
static Future<int> getCount() async {
final prefs = await SharedPreferences.getInstance();
final windowStart = getCurrentWindowStart();
final storedStr = prefs.getString(_keyWindowStart);
if (storedStr != null) {
final storedWindow = DateTime.parse(storedStr);
if (storedWindow.isBefore(windowStart)) {
// 新しいウィンドウ → リセット
await prefs.setInt(_keyCount, 0);
await prefs.setString(_keyWindowStart, windowStart.toIso8601String());
return 0;
}
} else {
// 初回起動 → ウィンドウ開始時刻を記録
await prefs.setString(_keyWindowStart, windowStart.toIso8601String());
}
return prefs.getInt(_keyCount) ?? 0;
}
/// 使用回数を 1 増やす
static Future<void> increment() async {
final prefs = await SharedPreferences.getInstance();
final current = await getCount();
await prefs.setInt(_keyCount, current + 1);
}
/// 残り回数0 以上)
static Future<int> getRemaining() async {
final count = await getCount();
return (dailyLimit - count).clamp(0, dailyLimit);
}
/// 無料枠を使い切っているか
static Future<bool> isExhausted() async {
return await getCount() >= dailyLimit;
}
}
/// ActivityStats / カメラ画面で使う Riverpod プロバイダ。
/// increment() 後に ref.invalidate(apiUsageCountProvider) で UI を更新する。
final apiUsageCountProvider = FutureProvider.autoDispose<int>((ref) async {
return ApiUsageService.getCount();
});