splittunneling_selectapp_controller.dart 7.3 KB

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