import 'dart:convert'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_timezone/flutter_timezone.dart'; import '../api/endpoints/me.dart'; import '../library/library_providers.dart' show dioProvider; import '../models/user.dart'; const _kServerUrl = 'server_url'; const _kSessionToken = 'session_token'; const _kCurrentUser = 'current_user'; const _kTzLastSentAt = 'tz_last_sent_at'; const _weeklyMs = 7 * 24 * 60 * 60 * 1000; final secureStorageProvider = Provider( (ref) => const FlutterSecureStorage(), ); final serverUrlProvider = FutureProvider((ref) async { return ref.watch(secureStorageProvider).read(key: _kServerUrl); }); final sessionTokenProvider = FutureProvider((ref) async { return ref.watch(secureStorageProvider).read(key: _kSessionToken); }); class AuthController extends AsyncNotifier { late FlutterSecureStorage _storage; @override Future build() async { _storage = ref.watch(secureStorageProvider); final raw = await _storage.read(key: _kCurrentUser); if (raw == null) return null; final user = User.fromJson(jsonDecode(raw) as Map); // Fire-and-forget timezone send on app start with an existing // session — no-op when last_sent_at is fresh. // ignore: unawaited_futures _sendTimezoneIfStale(); return user; } Future setServerUrl(String url) async { await _storage.write(key: _kServerUrl, value: url); ref.invalidate(serverUrlProvider); } Future setSession({required String token, required String userJson}) async { await _storage.write(key: _kSessionToken, value: token); await _storage.write(key: _kCurrentUser, value: userJson); ref.invalidate(sessionTokenProvider); state = AsyncData(User.fromJson(jsonDecode(userJson) as Map)); // ignore: unawaited_futures _sendTimezoneIfStale(); } Future clearSession() async { await _storage.delete(key: _kSessionToken); await _storage.delete(key: _kCurrentUser); ref.invalidate(sessionTokenProvider); state = const AsyncData(null); } /// Sends the device's current IANA timezone to PUT /api/me/timezone /// when the last successful send was >7 days ago (or never). /// Cadence persists in flutter_secure_storage so it survives app /// restarts. Failures are swallowed: the server keeps its previous /// value (or 'UTC' default) until the next attempt. Future _sendTimezoneIfStale() async { try { final lastStr = await _storage.read(key: _kTzLastSentAt); final lastMs = lastStr == null ? 0 : int.tryParse(lastStr) ?? 0; final nowMs = DateTime.now().millisecondsSinceEpoch; if (nowMs - lastMs < _weeklyMs) return; final tz = await FlutterTimezone.getLocalTimezone(); if (tz.isEmpty) return; final dio = await ref.read(dioProvider.future); await MeApi(dio).putTimezone(tz); await _storage.write(key: _kTzLastSentAt, value: nowMs.toString()); } catch (_) { // Non-fatal — server falls back to UTC or last-known value. } } } final authControllerProvider = AsyncNotifierProvider(AuthController.new);