part of '../action.dart'; @Riverpod(keepAlive: true) class SystemAction extends _$SystemAction { CoreController get _core => ref.read(coreHandlerProvider); SystemExitCoordinator? _exitCoordinator; @override void build() {} Future> getPackages() async { ref.read(packagesProvider.notifier).value = await app?.getPackages() ?? []; return ref.read(packagesProvider); } Future isInstalledAppsPermissionGranted() async { return await app?.isInstalledAppsPermissionGranted() ?? true; } Future requestInstalledAppsPermission() async { return await app?.requestInstalledAppsPermission() ?? false; } Future handleExit([bool needSave = true]) { final coordinator = _exitCoordinator ??= SystemExitCoordinator( watchdogDuration: exitWatchdogDuration, closeWindow: closeWindow, closeCore: closeCore, exitApplication: exitApplication, ); return coordinator.exit(cleanup: () => cleanupExitResources(needSave)); } @protected Duration get exitWatchdogDuration => const Duration(seconds: 3); @protected Future cleanupExitResources(bool needSave) async { final saveOperation = needSave ? _savePreferencesSafely() : null; final tray = trayPort; if (tray != null) { await tray.shutdown().onError((error, stackTrace) { commonPrint.log( 'Tray shutdown failed: ${compactError(error)}', logLevel: LogLevel.error, ); }); } await Future.wait([ ?saveOperation, bootGuard.markClosed(), if (systemDnsCoordinator != null) systemDnsCoordinator!.shutdown(), ?systemProxyQueue?.close(), ]); } Future _savePreferencesSafely() async { try { await savePreferences(); } catch (error) { commonPrint.log( 'Preferences save failed: ${compactError(error)}', logLevel: LogLevel.error, ); } } @protected Future savePreferences() async { final port = windowPort; if (port != null) { try { final current = ref.read(windowSettingProvider); final geometry = await port.captureNormalGeometry(current); if (geometry != null) { ref.read(windowSettingProvider.notifier).value = geometry; } } catch (error) { commonPrint.log( 'Window geometry capture failed: ${compactError(error)}', logLevel: LogLevel.warning, ); } } await preferences.saveConfig(ref.read(configProvider)); } @protected Future closeWindow() async { await windowPort?.close(); } @protected Future closeCore() async { await _core.close(); commonPrint.log('exit'); } @protected Future exitApplication() async { await system.exit(); windowPort?.forceExit(); } Future handleClose([bool exit = true]) async { if (ref.read(appSettingProvider).minimizeOnExit || !exit) { if (system.isDesktop) { await _savePreferencesSafely(); } await system.back(); await windowPort?.hide(); } else { await handleExit(); } } Future updateVisible() async { await windowPort?.toggle(); } void updateTun() { ref .read(patchClashConfigProvider.notifier) .update((state) => state.copyWith.tun(enable: !state.tun.enable)); } void updateSystemProxy() { ref .read(networkSettingProvider.notifier) .update((state) => state.copyWith(systemProxy: !state.systemProxy)); } Future copyProxyEnv() async { final port = ref.read( patchClashConfigProvider.select((state) => state.mixedPort), ); await Clipboard.setData( ClipboardData(text: proxyEnvCommand(port, isWindows: system.isWindows)), ); } void updateAutoLaunch() { ref .read(appSettingProvider.notifier) .update((state) => state.copyWith(autoLaunch: !state.autoLaunch)); } Future updateTray() async { await trayPort?.update( trayState: ref.read(trayStateProvider), traffic: ref.read( trafficsProvider.select( (state) => state.list.safeLast(const Traffic()), ), ), read: globalState.container.read, ); } Future updateLocalIp() async { ref.read(localIpProvider.notifier).value = null; await Future.delayed(commonDuration); ref.read(localIpProvider.notifier).value = await getLocalIpAddress(); } }