Files
FlClash/lib/application.dart
T

194 lines
6.0 KiB
Dart
Raw Normal View History

2024-05-06 10:32:39 +08:00
import 'dart:async';
2025-12-16 11:23:09 +08:00
import 'dart:io';
2024-12-03 21:47:12 +08:00
2025-05-02 02:24:12 +08:00
import 'package:connectivity_plus/connectivity_plus.dart';
2024-04-30 23:38:49 +08:00
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/common/window.dart';
import 'package:fl_clash/bootstrap.dart';
import 'package:fl_clash/common/system_dns.dart';
2024-12-03 21:47:12 +08:00
import 'package:fl_clash/l10n/l10n.dart';
2024-09-08 21:21:21 +08:00
import 'package:fl_clash/manager/hotkey_manager.dart';
import 'package:fl_clash/manager/manager.dart';
2024-10-27 16:59:23 +08:00
import 'package:fl_clash/plugins/app.dart';
2025-04-09 16:46:14 +08:00
import 'package:fl_clash/providers/providers.dart';
2024-04-30 23:38:49 +08:00
import 'package:fl_clash/state.dart';
import 'package:fl_clash/ui/ui.dart';
import 'package:material_ui/material_ui.dart';
2026-08-05 10:58:23 +08:00
import 'package:flutter/services.dart';
2025-02-09 18:39:38 +08:00
import 'package:flutter_riverpod/flutter_riverpod.dart';
2024-04-30 23:38:49 +08:00
import 'pages/pages.dart';
Widget buildManagerStack({
required bool isDesktop,
required Future<void> Function(List<ConnectivityResult> results)
onConnectivityChanged,
Stream<ProcessSignal>? terminateSignals,
required Widget child,
}) {
final platformApp = isDesktop
? WindowHeaderContainer(child: child)
: VpnManager(child: child);
final state = AppStateManager(
child: CoreManager(
child: ConnectivityManager(
onConnectivityChanged: onConnectivityChanged,
child: platformApp,
),
),
);
final platformState = isDesktop
? WindowManager(
terminateSignals: terminateSignals,
child: TrayManager(
child: HotKeyManager(child: ProxyManager(child: state)),
),
)
: AndroidManager(child: TileManager(child: state));
return AppEnvManager(
child: LocaleManager(
child: StatusManager(child: ThemeManager(child: platformState)),
),
);
}
2025-02-09 18:39:38 +08:00
class Application extends ConsumerStatefulWidget {
2025-07-31 17:09:18 +08:00
const Application({super.key});
2024-04-30 23:38:49 +08:00
@override
2025-02-09 18:39:38 +08:00
ConsumerState<Application> createState() => ApplicationState();
2024-04-30 23:38:49 +08:00
}
2025-02-09 18:39:38 +08:00
class ApplicationState extends ConsumerState<Application> {
2024-12-09 01:40:39 +08:00
Timer? _autoUpdateProfilesTaskTimer;
2025-12-16 11:23:09 +08:00
bool _preHasVpn = false;
// Windows can only watch SIGINT and SIGHUP.
final _terminateSignals = system.isDesktop && !system.isWindows
? ProcessSignal.sigterm.watch()
: null;
2024-04-30 23:38:49 +08:00
@override
void initState() {
super.initState();
2026-08-05 10:58:23 +08:00
SystemNavigator.setFrameworkHandlesBack(true);
2024-04-30 23:38:49 +08:00
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
2026-02-02 10:14:13 +08:00
if (globalState.navigatorKey.currentContext != null) {
await bootstrap.attach();
2025-12-16 11:23:09 +08:00
} else {
exit(0);
2024-06-13 23:43:42 +08:00
}
2025-12-16 11:23:09 +08:00
_autoUpdateProfilesTask();
2026-02-02 10:14:13 +08:00
_initLink();
if (!safeModeBuild) {
unawaited(app?.initShortcuts());
}
2024-04-30 23:38:49 +08:00
});
}
2026-02-02 10:14:13 +08:00
void _initLink() {
linkManager.initAppLinksListen((url) async {
unawaited(window?.show());
final message = currentAppLocalizations.createProfileFromUrlTip(url);
final parts = message.split(url);
final res = await dialogs.showMessage(
2026-02-02 10:14:13 +08:00
title: currentAppLocalizations.addProfile,
message: TextSpan(
children: [
TextSpan(text: parts.first),
2026-02-02 10:14:13 +08:00
TextSpan(
text: url,
2026-02-02 10:14:13 +08:00
style: TextStyle(
color: context.colorScheme.primary,
decoration: TextDecoration.underline,
decorationColor: context.colorScheme.primary,
),
),
if (parts.length > 1) TextSpan(text: parts.last),
2026-02-02 10:14:13 +08:00
],
),
);
if (res != true) return;
unawaited(
ref.read(profilesActionProvider.notifier).addProfileFromLink(url),
);
2026-02-02 10:14:13 +08:00
});
}
2025-06-07 01:48:34 +08:00
void _autoUpdateProfilesTask() {
2025-01-09 18:38:52 +08:00
_autoUpdateProfilesTaskTimer = Timer(const Duration(minutes: 20), () async {
2026-02-02 10:14:13 +08:00
await ref.read(profilesActionProvider.notifier).autoUpdateProfiles();
await ref.read(resourcesActionProvider.notifier).autoUpdate();
if (!mounted) {
return;
}
2024-12-09 01:40:39 +08:00
_autoUpdateProfilesTask();
});
2024-09-08 21:21:21 +08:00
}
Future<void> _handleConnectivityChanged(
List<ConnectivityResult> results,
) async {
commonPrint.log('connectivityChanged ${results.toString()}');
unawaited(systemDnsCoordinator?.resync() ?? Future.value());
unawaited(ref.read(systemActionProvider.notifier).updateLocalIp());
final hasVpn = results.contains(ConnectivityResult.vpn);
if (_preHasVpn == hasVpn) {
ref.read(routeTrackerProvider.notifier).bumpHostEpoch();
2024-04-30 23:38:49 +08:00
}
_preHasVpn = hasVpn;
2025-03-12 17:15:31 +08:00
}
2024-04-30 23:38:49 +08:00
@override
Widget build(context) {
2025-07-31 17:09:18 +08:00
return Consumer(
builder: (_, ref, child) {
final locale = ref.watch(
appSettingProvider.select((state) => state.locale),
);
final themeProps = ref.watch(themeSettingProvider);
return MaterialApp(
debugShowCheckedModeBanner: false,
navigatorKey: globalState.navigatorKey,
2026-08-05 10:58:23 +08:00
onNavigationNotification: (_) => true,
2025-07-31 17:09:18 +08:00
localizationsDelegates: const [
AppLocalizations.delegate,
AppUiLocalizations.delegate,
...GlobalMaterialLocalizations.delegates,
2025-07-31 17:09:18 +08:00
],
builder: (context, child) {
return ThemeCrossFade(
child: buildManagerStack(
isDesktop: system.isDesktop,
onConnectivityChanged: _handleConnectivityChanged,
terminateSignals: _terminateSignals,
child: RemoteFocusAdapter(
enabled: system.isTV,
floatingKeyboard: app?.floatingKeyboard,
child: child!,
),
),
2024-12-09 01:40:39 +08:00
);
},
scrollBehavior: const BaseScrollBehavior(),
2025-07-31 17:09:18 +08:00
title: appName,
locale: getLocaleForString(locale),
2025-07-31 17:09:18 +08:00
supportedLocales: AppLocalizations.delegate.supportedLocales,
themeMode: themeProps.themeMode,
theme: watchAppTheme(ref, Brightness.light),
darkTheme: watchAppTheme(ref, Brightness.dark),
themeAnimationStyle: AnimationStyle.noAnimation,
home: KeyboardInsetHold(child: child!),
2025-07-31 17:09:18 +08:00
);
},
child: const HomePage(),
2024-04-30 23:38:49 +08:00
);
}
@override
2026-08-05 10:55:33 +08:00
void dispose() {
2024-04-30 23:38:49 +08:00
linkManager.destroy();
2024-12-09 01:40:39 +08:00
_autoUpdateProfilesTaskTimer?.cancel();
2024-04-30 23:38:49 +08:00
super.dispose();
}
}