// app_providers.dart — 语言 / 主题 / 套餐视角等基础状态(Riverpod) import 'dart:ui' as ui; 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(); } } /// 当前语言。首启无用户选择时**跟随系统语言**(设备 locale → 支持的 AppLang, /// 命不中回退英文);用户在设置/账户页显式切换后持久化到 shared_preferences /// (key `pg_lang`,存枚举 name),此后一律以用户选择为准、不再看系统。 class LocaleNotifier extends StateNotifier { LocaleNotifier() : super(_systemDefault()) { _load(); } static const _key = 'pg_lang'; /// 设备语言 → 支持的 AppLang。取平台首选 locale 的语言码(zh/ja/ko/ru/es 命中 /// 对应语种,其余一律英文)。构造期同步可用,无需 await。 static AppLang _systemDefault() { try { final code = ui.PlatformDispatcher.instance.locale.languageCode.toLowerCase(); for (final l in AppLang.values) { if (l.name == code) return l; } } catch (_) { /* 取系统 locale 失败 → 英文 */ } return AppLang.en; } 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 (_) { /* 读失败保持系统默认 */ } } 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; });