ix_sp.dart 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. import 'package:flutter/material.dart';
  2. import 'package:get/get.dart';
  3. import 'package:shared_preferences/shared_preferences.dart';
  4. import 'dart:convert';
  5. import '../../../config/translations/localization_service.dart';
  6. import '../../../utils/crypto.dart';
  7. import '../../../utils/log/logger.dart';
  8. import '../../constants/keys.dart';
  9. import '../models/disconnect_domain.dart';
  10. import '../models/launch/app_config.dart';
  11. import '../models/launch/groups.dart';
  12. import '../models/launch/launch.dart';
  13. import '../models/launch/upgrade.dart';
  14. import '../models/launch/user.dart';
  15. import '../../modules/home/controllers/home_controller.dart';
  16. import '../../modules/node/controllers/node_controller.dart';
  17. class IXSP {
  18. // prevent making instance
  19. IXSP._();
  20. // get storage
  21. static late SharedPreferences _sharedPreferences;
  22. // STORING KEYS
  23. /// Key for storing the device ID
  24. static const String _deviceIdKey = 'omon_device_id';
  25. static const String _fcmTokenKey = 'fcm_token';
  26. static const String _currentLocalKey = 'current_local';
  27. static const String _lightThemeKey = 'is_theme_light';
  28. static const String _launchGame = 'is_launch_game';
  29. static const String _ignoreVersionKey = 'ignore_version';
  30. static const String _isNewInstallKey = 'is_new_install';
  31. static const String _launchDataKey = 'launch_data';
  32. //记录网络报错的域名信息
  33. static const String _disconnectDomainsKey = 'disconnect_domains';
  34. //记录最后一次是否是地区禁用
  35. static const String _isRegionDisabledKey = 'is_region_disabled';
  36. //记录最后一次是否是用户禁用
  37. static const String _isUserDisabledKey = 'is_user_disabled';
  38. //记录最后一次是否是设备禁用
  39. static const String _isDeviceDisabledKey = 'is_device_disabled';
  40. //记录最后一次上传的性能日志boostSessionId
  41. static const String _lastMetricsLogKey = 'last_metrics_log';
  42. //记录最后一次上传的es日志boostSessionId
  43. static const String _lastESLogKey = 'last_es_log';
  44. //记录是否开启debug log
  45. static const String _enableDebugLogKey = 'enable_debug_log';
  46. //记录是否开启ping 0默认 1开启 2关闭
  47. static const String _enablePingModeKey = 'enable_ping_mode';
  48. /// init get storage services
  49. static Future<void> init() async {
  50. _sharedPreferences = await SharedPreferences.getInstance();
  51. }
  52. static setStorage(SharedPreferences sharedPreferences) {
  53. _sharedPreferences = sharedPreferences;
  54. }
  55. /// set theme current type as light theme
  56. static Future<void> setThemeIsLight(bool lightTheme) =>
  57. _sharedPreferences.setBool(_lightThemeKey, lightTheme);
  58. /// get if the current theme type is light
  59. static bool getThemeIsLight() =>
  60. _sharedPreferences.getBool(_lightThemeKey) ??
  61. true; // todo set the default theme (true for light, false for dark)
  62. /// save current locale
  63. static Future<void> setCurrentLanguage(String languageCode) =>
  64. _sharedPreferences.setString(_currentLocalKey, languageCode);
  65. /// get current locale
  66. static Locale getCurrentLocal() {
  67. String? langCode = _sharedPreferences.getString(_currentLocalKey);
  68. // default language is english
  69. if (langCode == null) {
  70. return LocalizationService.defaultLanguage;
  71. }
  72. return LocalizationService.supportedLanguages[langCode]!;
  73. }
  74. /// save generated fcm token
  75. static Future<void> setFcmToken(String token) =>
  76. _sharedPreferences.setString(_fcmTokenKey, token);
  77. /// get authorization token
  78. static String getFcmToken() =>
  79. _sharedPreferences.getString(_fcmTokenKey) ?? '';
  80. /// set launch game
  81. static Future<void> setLaunchGame(bool isLaunch) =>
  82. _sharedPreferences.setBool(_launchGame, isLaunch);
  83. /// get launch game
  84. static bool getLaunchGame() =>
  85. _sharedPreferences.getBool(_launchGame) ?? false;
  86. /// clear all data from shared pref
  87. static Future<void> clear() async => await _sharedPreferences.clear();
  88. /// Save unique ID to shared preferences
  89. static Future<void> setDeviceId(String deviceId) =>
  90. _sharedPreferences.setString(_deviceIdKey, deviceId);
  91. /// Get unique ID from shared preferences
  92. static String? getDeviceId() => _sharedPreferences.getString(_deviceIdKey);
  93. /// Save ignore version to shared preferences
  94. static Future<void> setIgnoreVersion(int version) =>
  95. _sharedPreferences.setInt(_ignoreVersionKey, version);
  96. /// Get ignore version from shared preferences
  97. static int getIgnoreVersion() =>
  98. _sharedPreferences.getInt(_ignoreVersionKey) ?? 0;
  99. /// Save is new install to shared preferences
  100. static Future<void> setIsNewInstall(bool isNewInstall) =>
  101. _sharedPreferences.setBool(_isNewInstallKey, isNewInstall);
  102. /// Get is new install from shared preferences
  103. static bool getIsNewInstall() =>
  104. _sharedPreferences.getBool(_isNewInstallKey) ?? true;
  105. /// 保存 DisconnectDomain 列表,合并 domain+status 相同的数据
  106. static Future<void> addDisconnectDomain(DisconnectDomain newItem) async {
  107. try {
  108. final prefs = _sharedPreferences;
  109. // 读取已存在的数据
  110. final oldJson = prefs.getString(_disconnectDomainsKey);
  111. List<DisconnectDomain> allList = [];
  112. if (oldJson != null && oldJson.isNotEmpty) {
  113. final List<dynamic> oldList = jsonDecode(oldJson);
  114. allList = oldList.map((e) => DisconnectDomain.fromJson(e)).toList();
  115. }
  116. // 合并新旧数据
  117. allList.add(newItem);
  118. // 按 domain+status 分组合并
  119. final Map<String, DisconnectDomain> merged = {};
  120. for (var item in allList) {
  121. final key = '${item.domain}_${item.status}';
  122. if (!merged.containsKey(key)) {
  123. merged[key] = DisconnectDomain(
  124. domain: item.domain,
  125. status: item.status,
  126. msg: item.msg,
  127. startTime: item.startTime,
  128. endTime: item.endTime,
  129. count: item.count,
  130. );
  131. } else {
  132. final exist = merged[key]!;
  133. exist.startTime = exist.startTime < item.startTime
  134. ? exist.startTime
  135. : item.startTime;
  136. exist.endTime = exist.endTime > item.endTime
  137. ? exist.endTime
  138. : item.endTime;
  139. exist.count += item.count;
  140. // msg 以最新的为准
  141. exist.msg = item.msg;
  142. }
  143. }
  144. // 保存合并后的数据
  145. final mergedList = merged.values.toList();
  146. await prefs.setString(
  147. _disconnectDomainsKey,
  148. jsonEncode(mergedList.map((e) => e.toJson()).toList()),
  149. );
  150. } catch (e) {
  151. log('IVSP', 'addDisconnectDomain error: $e');
  152. }
  153. }
  154. /// 读取 DisconnectDomain 列表
  155. static List<DisconnectDomain> getDisconnectDomains() {
  156. final jsonStr = _sharedPreferences.getString(_disconnectDomainsKey);
  157. if (jsonStr == null || jsonStr.isEmpty) return [];
  158. final List<dynamic> list = jsonDecode(jsonStr);
  159. return list.map((e) => DisconnectDomain.fromJson(e)).toList();
  160. }
  161. /// 清空 DisconnectDomain 列表
  162. static Future<void> clearDisconnectDomains() async {
  163. await _sharedPreferences.remove(_disconnectDomainsKey);
  164. }
  165. // 记录最后一次是否是用户禁用
  166. static Future<void> setLastIsUserDisabled(bool isUserDisabled) async {
  167. await _sharedPreferences.setBool(_isUserDisabledKey, isUserDisabled);
  168. }
  169. // 获取最后一次是否是用户禁用
  170. static bool getLastIsUserDisabled() {
  171. return _sharedPreferences.getBool(_isUserDisabledKey) ?? false;
  172. }
  173. // 记录最后一次是否是地区禁用
  174. static Future<void> setLastIsRegionDisabled(bool isRegionDisabled) async {
  175. await _sharedPreferences.setBool(_isRegionDisabledKey, isRegionDisabled);
  176. }
  177. // 获取最后一次是否是地区禁用
  178. static bool getLastIsRegionDisabled() {
  179. return _sharedPreferences.getBool(_isRegionDisabledKey) ?? false;
  180. }
  181. // 记录最后一次是否是设备禁用
  182. static Future<void> setLastIsDeviceDisabled(bool isDeviceDisabled) async {
  183. await _sharedPreferences.setBool(_isDeviceDisabledKey, isDeviceDisabled);
  184. }
  185. // 获取最后一次是否是设备禁用
  186. static bool getLastIsDeviceDisabled() {
  187. return _sharedPreferences.getBool(_isDeviceDisabledKey) ?? false;
  188. }
  189. /// Save last metrics log
  190. static Future<void> setLastMetricsLog(String log) async {
  191. await _sharedPreferences.setString(_lastMetricsLogKey, log);
  192. }
  193. /// Get last metrics log
  194. static String getLastMetricsLog() =>
  195. _sharedPreferences.getString(_lastMetricsLogKey) ?? '';
  196. /// Save last es log
  197. static Future<void> setLastESLog(String log) async {
  198. await _sharedPreferences.setString(_lastESLogKey, log);
  199. }
  200. /// Get last es log
  201. static String getLastESLog() =>
  202. _sharedPreferences.getString(_lastESLogKey) ?? '';
  203. /// Save enable debug log
  204. static Future<void> setEnableDebugLog(bool enable) async {
  205. await _sharedPreferences.setBool(_enableDebugLogKey, enable);
  206. }
  207. /// Get enable debug log
  208. static bool getEnableDebugLog() =>
  209. _sharedPreferences.getBool(_enableDebugLogKey) ?? false;
  210. /// Save enable ping
  211. static Future<void> setEnablePingMode(int model) async {
  212. await _sharedPreferences.setInt(_enablePingModeKey, model);
  213. }
  214. /// Get enable ping
  215. static int getEnablePingMode() =>
  216. _sharedPreferences.getInt(_enablePingModeKey) ?? 0;
  217. //记录当前选中的节点
  218. static const String _selectedLocationKey = 'selected_location';
  219. //记录最近选择的节点列表(最多3个)
  220. static const String _recentLocationsKey = 'recent_locations';
  221. /// 保存当前选中的节点
  222. static Future<void> saveSelectedLocation(
  223. Map<String, dynamic> location,
  224. ) async {
  225. try {
  226. await _sharedPreferences.setString(
  227. _selectedLocationKey,
  228. jsonEncode(location),
  229. );
  230. log('IXSP', 'Selected location saved successfully');
  231. } catch (e) {
  232. log('IXSP', 'Error saving selected location: $e');
  233. }
  234. }
  235. /// 获取当前选中的节点
  236. static Map<String, dynamic>? getSelectedLocation() {
  237. try {
  238. final jsonData = _sharedPreferences.getString(_selectedLocationKey);
  239. if (jsonData == null) {
  240. return null;
  241. }
  242. return jsonDecode(jsonData) as Map<String, dynamic>;
  243. } catch (e) {
  244. log('IXSP', 'Error getting selected location: $e');
  245. return null;
  246. }
  247. }
  248. /// 保存最近选择的节点列表
  249. static Future<void> saveRecentLocations(
  250. List<Map<String, dynamic>> locations,
  251. ) async {
  252. try {
  253. await _sharedPreferences.setString(
  254. _recentLocationsKey,
  255. jsonEncode(locations),
  256. );
  257. log('IXSP', 'Recent locations saved successfully');
  258. } catch (e) {
  259. log('IXSP', 'Error saving recent locations: $e');
  260. }
  261. }
  262. /// 获取最近选择的节点列表
  263. static List<Map<String, dynamic>> getRecentLocations() {
  264. try {
  265. final jsonData = _sharedPreferences.getString(_recentLocationsKey);
  266. if (jsonData == null || jsonData.isEmpty) {
  267. return [];
  268. }
  269. final List<dynamic> list = jsonDecode(jsonData);
  270. return list.map((e) => e as Map<String, dynamic>).toList();
  271. } catch (e) {
  272. log('IXSP', 'Error getting recent locations: $e');
  273. return [];
  274. }
  275. }
  276. /// 保存 Launch 数据
  277. static Future<bool> saveLaunch(Launch launch) async {
  278. try {
  279. // 获取旧的 Launch 数据,用于比较 groups
  280. final oldLaunch = getLaunch();
  281. final oldGroups = oldLaunch?.groups;
  282. final newGroups = launch.groups;
  283. final jsonData = jsonEncode(launch.toJson());
  284. // 保存数据
  285. await _sharedPreferences.setString(
  286. _launchDataKey,
  287. Crypto.encrypt(jsonData, Keys.aesKey, Keys.aesIv),
  288. );
  289. log('IXSP', 'Launch data saved successfully');
  290. // 检查 groups 是否有变化,如果有则通知更新
  291. if (_isGroupsChanged(oldGroups, newGroups)) {
  292. log('IXSP', 'Groups data changed, notifying controllers');
  293. _notifyControllersOnGroupsChanged();
  294. }
  295. return true;
  296. } catch (e) {
  297. log('IXSP', 'Error saving launch data: $e');
  298. return false;
  299. }
  300. }
  301. /// 检查 Groups 数据是否有变化
  302. static bool _isGroupsChanged(Groups? oldGroups, Groups? newGroups) {
  303. // 如果两者都为 null,没有变化
  304. if (oldGroups == null && newGroups == null) return false;
  305. // 如果一个为 null 一个不为 null,有变化
  306. if (oldGroups == null || newGroups == null) return true;
  307. // 比较 JSON 序列化后的字符串
  308. try {
  309. final oldJson = jsonEncode(oldGroups.toJson());
  310. final newJson = jsonEncode(newGroups.toJson());
  311. return oldJson != newJson;
  312. } catch (e) {
  313. log('IXSP', 'Error comparing groups: $e');
  314. return true; // 出错时默认认为有变化
  315. }
  316. }
  317. /// 通知控制器 Groups 数据变化
  318. static void _notifyControllersOnGroupsChanged() {
  319. try {
  320. // 通知 HomeController
  321. if (Get.isRegistered<HomeController>()) {
  322. Get.find<HomeController>().refreshOnLaunchChanged();
  323. }
  324. // 通知 NodeController
  325. if (Get.isRegistered<NodeController>()) {
  326. Get.find<NodeController>().refreshOnLaunchChanged();
  327. }
  328. } catch (e) {
  329. log('IXSP', 'Error notifying controllers: $e');
  330. }
  331. }
  332. /// 获取 Launch 数据
  333. static Launch? getLaunch() {
  334. try {
  335. final jsonData = _sharedPreferences.getString(_launchDataKey);
  336. if (jsonData == null) {
  337. return null;
  338. }
  339. final Map<String, dynamic> map = jsonDecode(
  340. Crypto.decrypt(jsonData, Keys.aesKey, Keys.aesIv),
  341. );
  342. return Launch.fromJson(map);
  343. } catch (e) {
  344. log('IXSP', 'Error getting launch data: $e');
  345. return null;
  346. }
  347. }
  348. /// 获取用户信息
  349. static User? getUser() {
  350. final launch = getLaunch();
  351. return launch?.userConfig;
  352. }
  353. /// 获取app配置
  354. static AppConfig? getAppConfig() {
  355. final launch = getLaunch();
  356. return launch?.appConfig;
  357. }
  358. /// 获取升级信息
  359. static Upgrade? getUpgrade() {
  360. final launch = getLaunch();
  361. return launch?.upgradeConfig;
  362. }
  363. /// 保存用户信息
  364. static Future<void> saveUser(User user) async {
  365. final launch = getLaunch();
  366. final newLaunch = launch!.copyWith(userConfig: user);
  367. await saveLaunch(newLaunch);
  368. }
  369. /// 保存app配置
  370. static Future<void> saveAppConfig(AppConfig appConfig) async {
  371. final launch = getLaunch();
  372. final newLaunch = launch!.copyWith(appConfig: appConfig);
  373. await saveLaunch(newLaunch);
  374. }
  375. /// 保存 Groups 数据
  376. static Future<void> saveGroups(Groups groups) async {
  377. final launch = getLaunch();
  378. final newLaunch = launch!.copyWith(groups: groups);
  379. await saveLaunch(newLaunch);
  380. }
  381. /// 清除 Launch 数据
  382. static Future<bool> clearLaunchData() async {
  383. try {
  384. await _sharedPreferences.remove(_launchDataKey);
  385. log('IXSP', 'Launch data cleared');
  386. return true;
  387. } catch (e) {
  388. log('IXSP', 'Error clearing launch data: $e');
  389. return false;
  390. }
  391. }
  392. /// 通用获取字符串方法
  393. static String? getString(String key) {
  394. return _sharedPreferences.getString(key);
  395. }
  396. /// 通用设置字符串方法
  397. static Future<bool> setString(String key, String value) async {
  398. try {
  399. return await _sharedPreferences.setString(key, value);
  400. } catch (e) {
  401. log('IXSP', 'Error setting string for key $key: $e');
  402. return false;
  403. }
  404. }
  405. }