import 'dart:convert'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; // ── Generic SharedPreferences-backed bool notifier ────────────────────────── class BoolPref extends StateNotifier { final String _key; BoolPref(this._key, bool defaultValue) : super(defaultValue) { _load(defaultValue); } Future _load(bool def) async { final p = await SharedPreferences.getInstance(); if (mounted) state = p.getBool(_key) ?? def; } Future set(bool v) async { state = v; final p = await SharedPreferences.getInstance(); await p.setBool(_key, v); } void toggle() => set(!state); } // ── Generic SharedPreferences-backed String-Set notifier ─────────────────── class StringSetPref extends StateNotifier> { final String _key; StringSetPref(this._key) : super({}) { _load(); } Future _load() async { final p = await SharedPreferences.getInstance(); final list = p.getStringList(_key); if (mounted && list != null) state = list.toSet(); } Future add(String v) async { if (state.contains(v)) return; state = {...state, v}; final p = await SharedPreferences.getInstance(); await p.setStringList(_key, state.toList()); } Future remove(String v) async { if (!state.contains(v)) return; state = {...state}..remove(v); final p = await SharedPreferences.getInstance(); await p.setStringList(_key, state.toList()); } } // ── Notification preferences ───────────────────────────────────────────────── final notifDesktopProvider = StateNotifierProvider((ref) => BoolPref('notif_desktop', true)); final notifSoundProvider = StateNotifierProvider((ref) => BoolPref('notif_sound', true)); final notifPreviewProvider = StateNotifierProvider((ref) => BoolPref('notif_preview', true)); final notifMentionOnlyDmProvider = StateNotifierProvider((ref) => BoolPref('notif_mention_dm', false)); // ── Privacy preferences ────────────────────────────────────────────────────── final privacyPresenceProvider = StateNotifierProvider((ref) => BoolPref('privacy_presence', true)); final privacyReadReceiptsProvider = StateNotifierProvider((ref) => BoolPref('privacy_read_receipts', true)); final privacyTypingProvider = StateNotifierProvider((ref) => BoolPref('privacy_typing', true)); // ── Theme persistence helpers ──────────────────────────────────────────────── Future saveThemePrefs({ bool? isDark, int? accentIdx, double? radius, double? motion, }) async { final p = await SharedPreferences.getInstance(); if (isDark != null) await p.setBool('theme_dark', isDark); if (accentIdx != null) await p.setInt('theme_accent', accentIdx); if (radius != null) await p.setDouble('theme_radius', radius); if (motion != null) await p.setDouble('theme_motion', motion); } Future<({bool isDark, int accentIdx, double radius, double motion})> loadThemePrefs() async { final p = await SharedPreferences.getInstance(); return ( isDark: p.getBool('theme_dark') ?? true, accentIdx: p.getInt('theme_accent') ?? 0, radius: p.getDouble('theme_radius') ?? 12.0, motion: p.getDouble('theme_motion') ?? 1.0, ); } // ── Server banner persistence ───────────────────────────────────────────────── // Stored as a single JSON map under 'server_banners': homeserverUrl → mxcUri const _kServerBannersKey = 'server_banners'; Future> loadAllServerBanners() async { final p = await SharedPreferences.getInstance(); final raw = p.getString(_kServerBannersKey); if (raw == null) return {}; try { final decoded = jsonDecode(raw) as Map; return decoded.map((k, v) => MapEntry(k, v as String)); } catch (_) { return {}; } } Future saveServerBanner(String homeserverUrl, String? mxcUri) async { final p = await SharedPreferences.getInstance(); final current = await loadAllServerBanners(); if (mxcUri == null || mxcUri.isEmpty) { current.remove(homeserverUrl); } else { current[homeserverUrl] = mxcUri; } await p.setString(_kServerBannersKey, jsonEncode(current)); }