splittunneling_selectapp_controller.dart 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. import 'dart:convert';
  2. import 'package:get/get.dart';
  3. import 'package:nomo/app/base/base_controller.dart';
  4. import 'package:nomo/app/data/sp/ix_sp.dart';
  5. import 'package:nomo/pigeons/core_api.g.dart';
  6. import 'package:nomo/utils/log/logger.dart';
  7. /// 分流隧道模式枚举
  8. enum SplitTunnelingMode {
  9. exclude, // 排除选中的应用
  10. include, // 仅包含选中的应用
  11. none, // 不选择任何应用
  12. }
  13. /// 应用数据模型
  14. class AppInfo {
  15. final String name;
  16. final String packageName;
  17. final String icon;
  18. AppInfo({required this.name, required this.packageName, required this.icon});
  19. }
  20. class SplittunnelingSelectappController extends BaseController {
  21. // 排除模式选中的应用列表
  22. final RxList<AppInfo> excludeSelectedApps = <AppInfo>[].obs;
  23. // 包含模式选中的应用列表
  24. final RxList<AppInfo> includeSelectedApps = <AppInfo>[].obs;
  25. // 所有应用列表
  26. final RxList<AppInfo> allApps = <AppInfo>[].obs;
  27. // 当前模式
  28. final Rx<SplitTunnelingMode> currentMode = SplitTunnelingMode.exclude.obs;
  29. static const String TAG = 'SplittunnelingSelectappController';
  30. // 缓存键
  31. static const String _cachedAppsKey = 'splittunneling_cached_apps';
  32. static const String _excludeSelectedAppsKey =
  33. 'splittunneling_exclude_selected_apps';
  34. static const String _includeSelectedAppsKey =
  35. 'splittunneling_include_selected_apps';
  36. @override
  37. void onInit() {
  38. super.onInit();
  39. _loadCachedData();
  40. getApps();
  41. }
  42. /// 加载缓存数据
  43. void _loadCachedData() {
  44. // 加载缓存的应用数据
  45. final cachedAppsJson = IXSP.getString(_cachedAppsKey);
  46. if (cachedAppsJson != null) {
  47. try {
  48. final cachedApps = jsonDecode(cachedAppsJson) as List;
  49. final apps = cachedApps
  50. .map(
  51. (app) => AppInfo(
  52. name: app['name'],
  53. packageName: app['packageName'],
  54. icon: app['icon'],
  55. ),
  56. )
  57. .toList();
  58. allApps.value = apps;
  59. setSuccess();
  60. } catch (e) {
  61. log(TAG, '加载缓存应用数据失败: $e');
  62. }
  63. }
  64. // 加载当前模式
  65. currentMode.value = Get.arguments as SplitTunnelingMode;
  66. _loadSelectedApps(currentMode.value);
  67. }
  68. /// 初始化应用数据
  69. Future<void> getApps() async {
  70. // 如果已经有缓存数据,先显示缓存,然后异步更新
  71. if (allApps.isNotEmpty) {
  72. setSuccess();
  73. } else {
  74. setLoading();
  75. }
  76. final newApps = <AppInfo>[];
  77. final appsJson = await CoreApi().getApps();
  78. if (appsJson != null) {
  79. final apps = jsonDecode(appsJson);
  80. for (dynamic app in apps) {
  81. if (app is Map) {
  82. String appName = app['appName'];
  83. String packageName = app['packageName'];
  84. String icon = app['icon'];
  85. newApps.add(
  86. AppInfo(name: appName, packageName: packageName, icon: icon),
  87. );
  88. }
  89. }
  90. // 更新应用列表
  91. allApps.value = newApps;
  92. // 缓存应用数据
  93. _cacheAppsData(newApps);
  94. // 重新加载选中的应用(因为应用列表可能已更新)
  95. _loadSelectedApps(currentMode.value);
  96. if (allApps.isNotEmpty) {
  97. setSuccess();
  98. } else {
  99. setEmpty();
  100. }
  101. }
  102. }
  103. /// 缓存应用数据
  104. void _cacheAppsData(List<AppInfo> apps) {
  105. try {
  106. final appsJson = jsonEncode(
  107. apps
  108. .map(
  109. (app) => {
  110. 'name': app.name,
  111. 'packageName': app.packageName,
  112. 'icon': app.icon,
  113. },
  114. )
  115. .toList(),
  116. );
  117. IXSP.setString(_cachedAppsKey, appsJson);
  118. } catch (e) {
  119. log(TAG, '缓存应用数据失败: $e');
  120. }
  121. }
  122. /// 加载选中的应用
  123. void _loadSelectedApps(SplitTunnelingMode mode) {
  124. final key = mode == SplitTunnelingMode.exclude
  125. ? _excludeSelectedAppsKey
  126. : _includeSelectedAppsKey;
  127. final selectedAppsJson = IXSP.getString(key);
  128. if (selectedAppsJson != null) {
  129. try {
  130. final selectedPackageNames =
  131. jsonDecode(selectedAppsJson) as List<dynamic>;
  132. // 按照保存的顺序重建列表
  133. final selectedAppsList = <AppInfo>[];
  134. for (final packageName in selectedPackageNames) {
  135. final app = allApps.firstWhere(
  136. (app) => app.packageName == packageName,
  137. orElse: () => AppInfo(name: '', packageName: packageName, icon: ''),
  138. );
  139. if (app.name.isNotEmpty) {
  140. // 只添加有效的应用
  141. selectedAppsList.add(app);
  142. }
  143. }
  144. if (mode == SplitTunnelingMode.exclude) {
  145. excludeSelectedApps.value = selectedAppsList;
  146. } else {
  147. includeSelectedApps.value = selectedAppsList;
  148. }
  149. } catch (e) {
  150. log(
  151. TAG,
  152. '加载${mode == SplitTunnelingMode.exclude ? "排除" : "包含"}模式选中应用数据失败: $e',
  153. );
  154. }
  155. }
  156. }
  157. /// 保存选中的应用
  158. void _saveSelectedApps(SplitTunnelingMode mode) {
  159. try {
  160. final selectedApps = mode == SplitTunnelingMode.exclude
  161. ? excludeSelectedApps
  162. : includeSelectedApps;
  163. final selectedPackageNames = selectedApps
  164. .map((app) => app.packageName)
  165. .toList();
  166. final selectedAppsJson = jsonEncode(selectedPackageNames);
  167. final key = mode == SplitTunnelingMode.exclude
  168. ? _excludeSelectedAppsKey
  169. : _includeSelectedAppsKey;
  170. IXSP.setString(key, selectedAppsJson);
  171. } catch (e) {
  172. log(
  173. TAG,
  174. '保存${mode == SplitTunnelingMode.exclude ? "排除" : "包含"}模式选中应用数据失败: $e',
  175. );
  176. }
  177. }
  178. /// 切换应用选择状态
  179. void toggleAppSelection(AppInfo app) {
  180. if (currentMode.value == SplitTunnelingMode.exclude) {
  181. if (excludeSelectedApps.contains(app)) {
  182. excludeSelectedApps.remove(app);
  183. } else {
  184. excludeSelectedApps.add(app);
  185. }
  186. _saveSelectedApps(SplitTunnelingMode.exclude);
  187. } else {
  188. if (includeSelectedApps.contains(app)) {
  189. includeSelectedApps.remove(app);
  190. } else {
  191. includeSelectedApps.add(app);
  192. }
  193. _saveSelectedApps(SplitTunnelingMode.include);
  194. }
  195. }
  196. /// 检查应用是否已选择
  197. bool isAppSelected(AppInfo app) {
  198. if (currentMode.value == SplitTunnelingMode.exclude) {
  199. return excludeSelectedApps.contains(app);
  200. } else {
  201. return includeSelectedApps.contains(app);
  202. }
  203. }
  204. /// 取消选择所有应用
  205. void deselectAllApps() {
  206. if (currentMode.value == SplitTunnelingMode.exclude) {
  207. excludeSelectedApps.clear();
  208. _saveSelectedApps(SplitTunnelingMode.exclude);
  209. } else {
  210. includeSelectedApps.clear();
  211. _saveSelectedApps(SplitTunnelingMode.include);
  212. }
  213. }
  214. /// 选择所有应用
  215. void selectAllApps() {
  216. if (currentMode.value == SplitTunnelingMode.exclude) {
  217. excludeSelectedApps.value = List.from(allApps);
  218. _saveSelectedApps(SplitTunnelingMode.exclude);
  219. } else {
  220. includeSelectedApps.value = List.from(allApps);
  221. _saveSelectedApps(SplitTunnelingMode.include);
  222. }
  223. }
  224. /// 获取当前模式选中的应用列表
  225. List<AppInfo> get selectedApps {
  226. return currentMode.value == SplitTunnelingMode.exclude
  227. ? excludeSelectedApps
  228. : includeSelectedApps;
  229. }
  230. /// 获取未选择的应用列表
  231. List<AppInfo> get unselectedApps {
  232. return allApps.where((app) => !isAppSelected(app)).toList();
  233. }
  234. }