// app_providers.dart — 语言 / 主题 / 套餐视角等基础状态(Riverpod) import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../l10n/app_text.dart'; import '../l10n/strings_en.dart'; import '../l10n/strings_es.dart'; import '../l10n/strings_ja.dart'; import '../l10n/strings_ko.dart'; import '../l10n/strings_ru.dart'; import '../l10n/strings_zh.dart'; import 'account_providers.dart'; import 'auth_provider.dart'; /// AppLang → 文案资源实例。供 [appTextProvider] 与「拿不到 Consumer 的场景」 /// (model / 非 Consumer 的 tile,只有 AppLang)复用,避免各处重写 switch。 AppText appTextFor(AppLang lang) { switch (lang) { case AppLang.zh: return const StringsZh(); case AppLang.en: return const StringsEn(); case AppLang.ja: return const StringsJa(); case AppLang.ko: return const StringsKo(); case AppLang.ru: return const StringsRu(); case AppLang.es: return const StringsEs(); } } /// 当前语言(单显)。默认英文(国际化默认语种);用户选择持久化到 /// shared_preferences(key `pg_lang`,存枚举 name),重启保留 —— 原来无持久化, /// 切了语言重启会丢。设置/账户页经 `.notifier).set(lang)` 切换。 class LocaleNotifier extends StateNotifier { LocaleNotifier() : super(AppLang.en) { _load(); } static const _key = 'pg_lang'; Future _load() async { try { final saved = (await SharedPreferences.getInstance()).getString(_key); if (saved != null) { for (final l in AppLang.values) { if (l.name == saved) { state = l; break; } } } } catch (_) { /* 读失败保持默认 en */ } } Future set(AppLang lang) async { state = lang; try { await (await SharedPreferences.getInstance()).setString(_key, lang.name); } catch (_) { /* 持久化失败忽略,本次会话仍生效 */ } } } final localeProvider = StateNotifierProvider((ref) => LocaleNotifier()); /// 由语言派生的文案资源——UI 一律通过它取文案,不写死字面量。 final appTextProvider = Provider((ref) => appTextFor(ref.watch(localeProvider))); /// 主题模式。默认跟随系统;设置页可显式切深色。 final themeModeProvider = StateProvider((ref) => ThemeMode.system); /// 是否免费档——派生自真实账户(meProvider.plan == 'free')。 /// 免费版才显示额度卡与免费横幅;加载中默认按免费处理。 final isFreePlanProvider = Provider((ref) { final me = ref.watch(meProvider).valueOrNull; return me?.isFree ?? true; }); /// 账户状态是否还没就绪——连接页据此先转圈再渲染,避免「先闪免费广告再跳会员」。 /// 覆盖两段窗口:① auth 还在从存储读 token(此时 isLoggedIn=false,me 会同步返回 /// 免费默认,故必须看 auth.isLoading);② 已登录但 me 还在拉真实值(此时 me 可能已有 /// 过期免费默认值,故看 me.isLoading 而非 !hasValue)。未登录(确定访客)不转圈。 final meLoadingProvider = Provider((ref) { final auth = ref.watch(authProvider); if (auth.isLoading) return true; if (!auth.isLoggedIn) return false; return ref.watch(meProvider).isLoading; });