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 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 increment() async { final prefs = await SharedPreferences.getInstance(); final current = await getCount(); await prefs.setInt(_keyCount, current + 1); } /// 残り回数(0 以上) static Future getRemaining() async { final count = await getCount(); return (dailyLimit - count).clamp(0, dailyLimit); } /// 無料枠を使い切っているか static Future isExhausted() async { return await getCount() >= dailyLimit; } } /// ActivityStats / カメラ画面で使う Riverpod プロバイダ。 /// increment() 後に ref.invalidate(apiUsageCountProvider) で UI を更新する。 final apiUsageCountProvider = FutureProvider.autoDispose((ref) async { return ApiUsageService.getCount(); });