Fix android system dns issues

Optimize dns default option

Fix some issues
This commit is contained in:
chen08209
2024-09-18 10:27:53 +08:00
parent 15c64327db
commit 3f0f7f051b
53 changed files with 276 additions and 221 deletions

View File

@@ -64,6 +64,14 @@
<data android:host="install-config" /> <data android:host="install-config" />
</intent-filter> </intent-filter>
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
<action android:name="com.follow.clash.action.START" />
</intent-filter>
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
<action android:name="com.follow.clash.action.STOP" />
</intent-filter>
</activity> </activity>
<!-- <meta-data--> <!-- <meta-data-->

View File

@@ -33,7 +33,7 @@ object GlobalState {
return currentEngine?.plugins?.get(AppPlugin::class.java) as AppPlugin? return currentEngine?.plugins?.get(AppPlugin::class.java) as AppPlugin?
} }
fun getCurrentTitlePlugin(): TilePlugin? { fun getCurrentTilePlugin(): TilePlugin? {
val currentEngine = if (flutterEngine != null) flutterEngine else serviceEngine val currentEngine = if (flutterEngine != null) flutterEngine else serviceEngine
return currentEngine?.plugins?.get(TilePlugin::class.java) as TilePlugin? return currentEngine?.plugins?.get(TilePlugin::class.java) as TilePlugin?
} }

View File

@@ -1,6 +1,8 @@
package com.follow.clash package com.follow.clash
import android.content.Intent
import android.os.Bundle
import com.follow.clash.plugins.AppPlugin import com.follow.clash.plugins.AppPlugin
import com.follow.clash.plugins.ServicePlugin import com.follow.clash.plugins.ServicePlugin
import com.follow.clash.plugins.VpnPlugin import com.follow.clash.plugins.VpnPlugin
@@ -9,6 +11,18 @@ import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngine
class MainActivity : FlutterActivity() { class MainActivity : FlutterActivity() {
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
when (intent.action) {
"com.follow.clash.action.START" -> {
GlobalState.getCurrentTilePlugin()?.handleStart()
}
"com.follow.clash.action.STOP" -> {
GlobalState.getCurrentTilePlugin()?.handleStop()
}
}
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) { override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine) super.configureFlutterEngine(flutterEngine)

View File

@@ -34,9 +34,9 @@ fun Metadata.getProtocol(): Int? {
} }
fun ConnectivityManager.resolvePrimaryDns(network: Network?): String? { fun ConnectivityManager.resolveDns(network: Network?): List<String> {
val properties = getLinkProperties(network) ?: return null val properties = getLinkProperties(network) ?: return listOf()
return properties.dnsServers.firstOrNull()?.asSocketAddressText(53) return properties.dnsServers.map { it.asSocketAddressText(53) }
} }
fun InetAddress.asSocketAddressText(port: Int): String { fun InetAddress.asSocketAddressText(port: Int): String {

View File

@@ -16,7 +16,7 @@ import com.follow.clash.BaseServiceInterface
import com.follow.clash.GlobalState import com.follow.clash.GlobalState
import com.follow.clash.RunState import com.follow.clash.RunState
import com.follow.clash.extensions.getProtocol import com.follow.clash.extensions.getProtocol
import com.follow.clash.extensions.resolvePrimaryDns import com.follow.clash.extensions.resolveDns
import com.follow.clash.models.Props import com.follow.clash.models.Props
import com.follow.clash.models.TunProps import com.follow.clash.models.TunProps
import com.follow.clash.services.FlClashService import com.follow.clash.services.FlClashService
@@ -177,9 +177,11 @@ class VpnPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
val networks = mutableSetOf<Network>() val networks = mutableSetOf<Network>()
fun onUpdateNetwork() { fun onUpdateNetwork() {
val dns = networks.mapNotNull { val dns = networks.flatMap { network ->
connectivity?.resolvePrimaryDns(it) connectivity?.resolveDns(network) ?: emptyList()
}.joinToString(separator = ",") }
.toSet()
.joinToString(",")
scope.launch { scope.launch {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
flutterMethodChannel.invokeMethod("dnsChanged", dns) flutterMethodChannel.invokeMethod("dnsChanged", dns)

View File

@@ -67,15 +67,15 @@ class FlClashTileService : TileService() {
activityTransfer() activityTransfer()
if (GlobalState.runState.value == RunState.STOP) { if (GlobalState.runState.value == RunState.STOP) {
GlobalState.runState.value = RunState.PENDING GlobalState.runState.value = RunState.PENDING
val titlePlugin = GlobalState.getCurrentTitlePlugin() val tilePlugin = GlobalState.getCurrentTilePlugin()
if (titlePlugin != null) { if (tilePlugin != null) {
titlePlugin.handleStart() tilePlugin.handleStart()
} else { } else {
GlobalState.initServiceEngine(applicationContext) GlobalState.initServiceEngine(applicationContext)
} }
} else if (GlobalState.runState.value == RunState.START) { } else if (GlobalState.runState.value == RunState.START) {
GlobalState.runState.value = RunState.PENDING GlobalState.runState.value = RunState.PENDING
GlobalState.getCurrentTitlePlugin()?.handleStop() GlobalState.getCurrentTilePlugin()?.handleStop()
} }
} }

View File

@@ -159,6 +159,22 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT
) )
} }
val stopPendingIntent = if (Build.VERSION.SDK_INT >= 31) {
PendingIntent.getActivity(
this,
0,
Intent("com.follow.clash.action.STOP"),
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
} else {
PendingIntent.getActivity(
this,
0,
Intent("com.follow.clash.action.STOP"),
PendingIntent.FLAG_UPDATE_CURRENT
)
}
with(NotificationCompat.Builder(this, CHANNEL)) { with(NotificationCompat.Builder(this, CHANNEL)) {
setSmallIcon(R.drawable.ic_stat_name) setSmallIcon(R.drawable.ic_stat_name)
setContentTitle("FlClash") setContentTitle("FlClash")
@@ -172,6 +188,7 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
setShowWhen(false) setShowWhen(false)
setOnlyAlertOnce(true) setOnlyAlertOnce(true)
setAutoCancel(true) setAutoCancel(true)
addAction(0, "Stop", stopPendingIntent);
} }
} }
@@ -210,7 +227,7 @@ class FlClashVpnService : VpnService(), BaseServiceInterface {
val isSuccess = super.onTransact(code, data, reply, flags) val isSuccess = super.onTransact(code, data, reply, flags)
if (!isSuccess) { if (!isSuccess) {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
GlobalState.getCurrentTitlePlugin()?.handleStop() GlobalState.getCurrentTilePlugin()?.handleStop()
} }
} }
return isSuccess return isSuccess

View File

@@ -47,7 +47,7 @@ func Start(tunProps Props) (*sing_tun.Listener, error) {
options := LC.Tun{ options := LC.Tun{
Enable: true, Enable: true,
Device: sing_tun.InterfaceName, Device: sing_tun.InterfaceName,
Stack: constant.TunSystem, Stack: constant.TunMixed,
DNSHijack: dnsHijack, DNSHijack: dnsHijack,
AutoRoute: false, AutoRoute: false,
AutoDetectInterface: false, AutoDetectInterface: false,

View File

@@ -1,6 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:dynamic_color/dynamic_color.dart'; import 'package:dynamic_color/dynamic_color.dart';
import 'package:fl_clash/l10n/l10n.dart'; import 'package:fl_clash/l10n/l10n.dart';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
@@ -29,6 +27,9 @@ runAppWithPreferences(
ChangeNotifierProvider<Config>( ChangeNotifierProvider<Config>(
create: (_) => config, create: (_) => config,
), ),
ChangeNotifierProvider<AppFlowingState>(
create: (_) => AppFlowingState(),
),
ChangeNotifierProxyProvider2<Config, ClashConfig, AppState>( ChangeNotifierProxyProvider2<Config, ClashConfig, AppState>(
create: (_) => appState, create: (_) => appState,
update: (_, config, clashConfig, appState) { update: (_, config, clashConfig, appState) {
@@ -85,6 +86,7 @@ class ApplicationState extends State<Application> {
super.initState(); super.initState();
_initTimer(); _initTimer();
globalState.appController = AppController(context); globalState.appController = AppController(context);
globalState.measure = Measure.of(context);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
final currentContext = globalState.navigatorKey.currentContext; final currentContext = globalState.navigatorKey.currentContext;
if (currentContext != null) { if (currentContext != null) {
@@ -179,8 +181,15 @@ class ApplicationState extends State<Application> {
GlobalWidgetsLocalizations.delegate GlobalWidgetsLocalizations.delegate
], ],
builder: (_, child) { builder: (_, child) {
return MediaManager( return LayoutBuilder(
child: _buildPage(child!), builder: (_, container) {
final appController = globalState.appController;
final maxWidth = container.maxWidth;
if (appController.appState.viewWidth != maxWidth) {
globalState.appController.updateViewWidth(maxWidth);
}
return _buildPage(child!);
},
); );
}, },
scrollBehavior: BaseScrollBehavior(), scrollBehavior: BaseScrollBehavior(),

View File

@@ -11,7 +11,7 @@ extension BuildContextExtension on BuildContext {
return MediaQuery.of(this).size; return MediaQuery.of(this).size;
} }
double get width { double get viewWidth {
return appSize.width; return appSize.width;
} }

View File

@@ -11,9 +11,13 @@ class FlClashHttpOverrides extends HttpOverrides {
client.badCertificateCallback = (_, __, ___) => true; client.badCertificateCallback = (_, __, ___) => true;
client.findProxy = (url) { client.findProxy = (url) {
debugPrint("find $url"); debugPrint("find $url");
final port = globalState.appController.clashConfig.mixedPort; final appController = globalState.appController;
final isStart = globalState.appController.appState.isStart; final port = appController.clashConfig.mixedPort;
final isStart = appController.appFlowingState.isStart;
if (!isStart) return "DIRECT"; if (!isStart) return "DIRECT";
if (appController.appState.groups.isEmpty) {
return "DIRECT";
}
return "PROXY localhost:$port"; return "PROXY localhost:$port";
}; };
return client; return client;

View File

@@ -62,6 +62,6 @@ extension DoubleListExt on List<double> {
} }
} }
return -1; // 这行理论上不会执行到,但为了完整性保留 return -1;
} }
} }

View File

@@ -6,7 +6,9 @@ class Measure {
final TextScaler _textScale; final TextScaler _textScale;
late BuildContext context; late BuildContext context;
Measure.of(this.context) : _textScale = MediaQuery.of(context).textScaler; Measure.of(this.context)
: _textScale = TextScaler.linear(
WidgetsBinding.instance.platformDispatcher.textScaleFactor);
Size computeTextSize(Text text) { Size computeTextSize(Text text) {
final textPainter = TextPainter( final textPainter = TextPainter(

View File

@@ -220,6 +220,12 @@ class Other {
String getBackupFileName() { String getBackupFileName() {
return "${appName}_backup_${DateTime.now().show}.zip"; return "${appName}_backup_${DateTime.now().show}.zip";
} }
Size getScreenSize() {
final view = WidgetsBinding.instance.platformDispatcher.views.first;
return view.physicalSize / view.devicePixelRatio;
}
} }
final other = Other(); final other = Other();

View File

@@ -3,7 +3,6 @@ import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart';
import 'package:fl_clash/plugins/app.dart'; import 'package:fl_clash/plugins/app.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:window_manager/window_manager.dart';
import 'window.dart'; import 'window.dart';

View File

@@ -19,6 +19,7 @@ import 'common/common.dart';
class AppController { class AppController {
final BuildContext context; final BuildContext context;
late AppState appState; late AppState appState;
late AppFlowingState appFlowingState;
late Config config; late Config config;
late ClashConfig clashConfig; late ClashConfig clashConfig;
late Function updateClashConfigDebounce; late Function updateClashConfigDebounce;
@@ -30,6 +31,7 @@ class AppController {
appState = context.read<AppState>(); appState = context.read<AppState>();
config = context.read<Config>(); config = context.read<Config>();
clashConfig = context.read<ClashConfig>(); clashConfig = context.read<ClashConfig>();
appFlowingState = context.read<AppFlowingState>();
updateClashConfigDebounce = debounce<Function()>(() async { updateClashConfigDebounce = debounce<Function()>(() async {
await updateClashConfig(); await updateClashConfig();
}); });
@@ -60,9 +62,9 @@ class AppController {
} else { } else {
await globalState.handleStop(); await globalState.handleStop();
clashCore.resetTraffic(); clashCore.resetTraffic();
appState.traffics = []; appFlowingState.traffics = [];
appState.totalTraffic = Traffic(); appFlowingState.totalTraffic = Traffic();
appState.runTime = null; appFlowingState.runTime = null;
addCheckIpNumDebounce(); addCheckIpNumDebounce();
} }
} }
@@ -76,15 +78,15 @@ class AppController {
if (startTime != null) { if (startTime != null) {
final startTimeStamp = startTime.millisecondsSinceEpoch; final startTimeStamp = startTime.millisecondsSinceEpoch;
final nowTimeStamp = DateTime.now().millisecondsSinceEpoch; final nowTimeStamp = DateTime.now().millisecondsSinceEpoch;
appState.runTime = nowTimeStamp - startTimeStamp; appFlowingState.runTime = nowTimeStamp - startTimeStamp;
} else { } else {
appState.runTime = null; appFlowingState.runTime = null;
} }
} }
updateTraffic() { updateTraffic() {
globalState.updateTraffic( globalState.updateTraffic(
appState: appState, appFlowingState: appFlowingState,
); );
} }
@@ -163,7 +165,7 @@ class AppController {
try { try {
updateProfile(profile); updateProfile(profile);
} catch (e) { } catch (e) {
appState.addLog( appFlowingState.addLog(
Log( Log(
logLevel: LogLevel.info, logLevel: LogLevel.info,
payload: e.toString(), payload: e.toString(),
@@ -241,7 +243,7 @@ class AppController {
clashCore.startLog(); clashCore.startLog();
} else { } else {
clashCore.stopLog(); clashCore.stopLog();
appState.logs = []; appFlowingState.logs = [];
} }
} }
@@ -546,7 +548,7 @@ class AppController {
} }
updateStart() { updateStart() {
updateStatus(!appState.isStart); updateStatus(!appFlowingState.isStart);
} }
updateAutoLaunch() { updateAutoLaunch() {

View File

@@ -1,5 +1,3 @@
import 'dart:io';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/config.dart'; import 'package:fl_clash/models/config.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';

View File

@@ -1,5 +1,4 @@
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:fl_clash/common/app_localizations.dart';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
@@ -18,7 +17,15 @@ class OverrideItem extends StatelessWidget {
commonScaffoldState?.actions = [ commonScaffoldState?.actions = [
IconButton( IconButton(
onPressed: () { onPressed: () {
globalState.appController.clashConfig.dns = const Dns(); globalState.showMessage(
title: appLocalizations.resetDns,
message: TextSpan(
text: appLocalizations.dnsResetTip,
),
onTab: () {
globalState.appController.clashConfig.dns = const Dns();
Navigator.of(context).pop();
});
}, },
tooltip: appLocalizations.resetDns, tooltip: appLocalizations.resetDns,
icon: const Icon( icon: const Icon(

View File

@@ -1,4 +1,3 @@
import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';

View File

@@ -26,9 +26,10 @@ class _NetworkDetectionState extends State<NetworkDetection> {
_checkIp() async { _checkIp() async {
final appState = globalState.appController.appState; final appState = globalState.appController.appState;
final appFlowingState = globalState.appController.appFlowingState;
final isInit = appState.isInit; final isInit = appState.isInit;
if (!isInit) return; if (!isInit) return;
final isStart = appState.isStart; final isStart = appFlowingState.isStart;
if (_preIsStart == false && _preIsStart == isStart) return; if (_preIsStart == false && _preIsStart == isStart) return;
networkDetectionState.value = networkDetectionState.value.copyWith( networkDetectionState.value = networkDetectionState.value.copyWith(
isTesting: true, isTesting: true,

View File

@@ -116,8 +116,8 @@ class _NetworkSpeedState extends State<NetworkSpeed> {
label: appLocalizations.networkSpeed, label: appLocalizations.networkSpeed,
iconData: Icons.speed_sharp, iconData: Icons.speed_sharp,
), ),
child: Selector<AppState, List<Traffic>>( child: Selector<AppFlowingState, List<Traffic>>(
selector: (_, appState) => appState.traffics, selector: (_, appFlowingState) => appFlowingState.traffics,
builder: (_, traffics, __) { builder: (_, traffics, __) {
return Container( return Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),

View File

@@ -34,7 +34,7 @@ class _StartButtonState extends State<StartButton>
handleSwitchStart() { handleSwitchStart() {
final appController = globalState.appController; final appController = globalState.appController;
if (isStart == appController.appState.isStart) { if (isStart == appController.appFlowingState.isStart) {
isStart = !isStart; isStart = !isStart;
updateController(); updateController();
appController.updateStatus(isStart); appController.updateStatus(isStart);
@@ -50,8 +50,8 @@ class _StartButtonState extends State<StartButton>
} }
Widget _updateControllerContainer(Widget child) { Widget _updateControllerContainer(Widget child) {
return Selector<AppState, bool>( return Selector<AppFlowingState, bool>(
selector: (_, appState) => appState.isStart, selector: (_, appFlowingState) => appFlowingState.isStart,
builder: (_, isStart, child) { builder: (_, isStart, child) {
if (isStart != this.isStart) { if (isStart != this.isStart) {
this.isStart = isStart; this.isStart = isStart;
@@ -127,8 +127,8 @@ class _StartButtonState extends State<StartButton>
); );
}, },
child: _updateControllerContainer( child: _updateControllerContainer(
Selector<AppState, int?>( Selector<AppFlowingState, int?>(
selector: (_, appState) => appState.runTime, selector: (_, appFlowingState) => appFlowingState.runTime,
builder: (_, int? value, __) { builder: (_, int? value, __) {
final text = other.getTimeText(value); final text = other.getTimeText(value);
return Text( return Text(

View File

@@ -56,8 +56,8 @@ class TrafficUsage extends StatelessWidget {
label: appLocalizations.trafficUsage, label: appLocalizations.trafficUsage,
iconData: Icons.data_saver_off, iconData: Icons.data_saver_off,
), ),
child: Selector<AppState, Traffic>( child: Selector<AppFlowingState, Traffic>(
selector: (_, appState) => appState.totalTraffic, selector: (_, appFlowingState) => appFlowingState.totalTraffic,
builder: (_, totalTraffic, __) { builder: (_, totalTraffic, __) {
final upTotalTrafficValue = totalTraffic.up; final upTotalTrafficValue = totalTraffic.up;
final downTotalTrafficValue = totalTraffic.down; final downTotalTrafficValue = totalTraffic.down;

View File

@@ -29,14 +29,14 @@ class _LogsFragmentState extends State<LogsFragment> {
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
final appState = globalState.appController.appState; final appFlowingState = globalState.appController.appFlowingState;
logsNotifier.value = logsNotifier.value.copyWith(logs: appState.logs); logsNotifier.value = logsNotifier.value.copyWith(logs: appFlowingState.logs);
if (timer != null) { if (timer != null) {
timer?.cancel(); timer?.cancel();
timer = null; timer = null;
} }
timer = Timer.periodic(const Duration(milliseconds: 200), (timer) { timer = Timer.periodic(const Duration(milliseconds: 200), (timer) {
final logs = appState.logs; final logs = appFlowingState.logs;
if (!const ListEquality<Log>().equals( if (!const ListEquality<Log>().equals(
logsNotifier.value.logs, logsNotifier.value.logs,
logs, logs,

View File

@@ -133,7 +133,7 @@ class ProxyCard extends StatelessWidget {
final measure = globalState.measure; final measure = globalState.measure;
final delayText = _buildDelayText(); final delayText = _buildDelayText();
final proxyNameText = _buildProxyNameText(context); final proxyNameText = _buildProxyNameText(context);
return currentGroupProxyNameBuilder( return currentSelectedProxyNameBuilder(
groupName: groupName, groupName: groupName,
builder: (currentGroupName) { builder: (currentGroupName) {
return Stack( return Stack(
@@ -214,23 +214,9 @@ class ProxyCard extends StatelessWidget {
config.currentSelectedMap[groupName]; config.currentSelectedMap[groupName];
return selectedProxyName ?? ''; return selectedProxyName ?? '';
}, },
builder: (_, value, __) { builder: (_, value, child) {
if (value != proxy.name) return Container(); if (value != proxy.name) return Container();
return Positioned.fill( return child!;
child: Container(
alignment: Alignment.topRight,
margin: const EdgeInsets.all(8),
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
shape: BoxShape.circle,
color:
Theme.of(context).colorScheme.secondaryContainer,
),
child: const SelectIcon(),
),
),
);
}, },
child: Positioned.fill( child: Positioned.fill(
child: Container( child: Container(

View File

@@ -6,7 +6,7 @@ import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
Widget currentGroupProxyNameBuilder({ Widget currentSelectedProxyNameBuilder({
required String groupName, required String groupName,
required Widget Function(String currentGroupName) builder, required Widget Function(String currentGroupName) builder,
}) { }) {
@@ -16,8 +16,8 @@ Widget currentGroupProxyNameBuilder({
final selectedProxyName = config.currentSelectedMap[groupName]; final selectedProxyName = config.currentSelectedMap[groupName];
return group?.getCurrentSelectedName(selectedProxyName ?? "") ?? ""; return group?.getCurrentSelectedName(selectedProxyName ?? "") ?? "";
}, },
builder: (_, currentGroupName, ___) { builder: (_, currentSelectedProxyName, ___) {
return builder(currentGroupName); return builder(currentSelectedProxyName);
}, },
); );
} }

View File

@@ -487,7 +487,7 @@ class _ListHeaderState extends State<ListHeader>
), ),
Flexible( Flexible(
flex: 1, flex: 1,
child: currentGroupProxyNameBuilder( child: currentSelectedProxyNameBuilder(
groupName: groupName, groupName: groupName,
builder: (currentGroupName) { builder: (currentGroupName) {
return Row( return Row(

View File

@@ -321,7 +321,7 @@ class ProxyGroupViewState extends State<ProxyGroupView> {
top: 16, top: 16,
left: 16, left: 16,
right: 16, right: 16,
bottom: 80, bottom: 96,
), ),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: columns, crossAxisCount: columns,

View File

@@ -303,5 +303,6 @@
"inputCorrectHotkey": "Please enter the correct hotkey", "inputCorrectHotkey": "Please enter the correct hotkey",
"hotkeyConflict": "Hotkey conflict", "hotkeyConflict": "Hotkey conflict",
"remove": "Remove", "remove": "Remove",
"noHotKey": "No HotKey" "noHotKey": "No HotKey",
"dnsResetTip": "Make sure to reset the DNS"
} }

View File

@@ -303,5 +303,6 @@
"inputCorrectHotkey": "请输入正确的快捷键", "inputCorrectHotkey": "请输入正确的快捷键",
"hotkeyConflict": "快捷键冲突", "hotkeyConflict": "快捷键冲突",
"remove": "移除", "remove": "移除",
"noHotKey": "暂无快捷键" "noHotKey": "暂无快捷键",
"dnsResetTip": "确定重置DNS"
} }

View File

@@ -146,6 +146,8 @@ class MessageLookup extends MessageLookupByLibrary {
"dnsDesc": "dnsDesc":
MessageLookupByLibrary.simpleMessage("Update DNS related settings"), MessageLookupByLibrary.simpleMessage("Update DNS related settings"),
"dnsMode": MessageLookupByLibrary.simpleMessage("DNS mode"), "dnsMode": MessageLookupByLibrary.simpleMessage("DNS mode"),
"dnsResetTip":
MessageLookupByLibrary.simpleMessage("Make sure to reset the DNS"),
"doYouWantToPass": "doYouWantToPass":
MessageLookupByLibrary.simpleMessage("Do you want to pass"), MessageLookupByLibrary.simpleMessage("Do you want to pass"),
"domain": MessageLookupByLibrary.simpleMessage("Domain"), "domain": MessageLookupByLibrary.simpleMessage("Domain"),

View File

@@ -120,6 +120,7 @@ class MessageLookup extends MessageLookupByLibrary {
"discovery": MessageLookupByLibrary.simpleMessage("发现新版本"), "discovery": MessageLookupByLibrary.simpleMessage("发现新版本"),
"dnsDesc": MessageLookupByLibrary.simpleMessage("更新DNS相关设置"), "dnsDesc": MessageLookupByLibrary.simpleMessage("更新DNS相关设置"),
"dnsMode": MessageLookupByLibrary.simpleMessage("DNS模式"), "dnsMode": MessageLookupByLibrary.simpleMessage("DNS模式"),
"dnsResetTip": MessageLookupByLibrary.simpleMessage("确定重置DNS"),
"doYouWantToPass": MessageLookupByLibrary.simpleMessage("是否要通过"), "doYouWantToPass": MessageLookupByLibrary.simpleMessage("是否要通过"),
"domain": MessageLookupByLibrary.simpleMessage("域名"), "domain": MessageLookupByLibrary.simpleMessage("域名"),
"download": MessageLookupByLibrary.simpleMessage("下载"), "download": MessageLookupByLibrary.simpleMessage("下载"),

View File

@@ -3099,6 +3099,16 @@ class AppLocalizations {
args: [], args: [],
); );
} }
/// `Make sure to reset the DNS`
String get dnsResetTip {
return Intl.message(
'Make sure to reset the DNS',
name: 'dnsResetTip',
desc: '',
args: [],
);
}
} }
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> { class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {

View File

@@ -2,7 +2,6 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:fl_clash/clash/clash.dart'; import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/common/http.dart';
import 'package:fl_clash/plugins/app.dart'; import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/plugins/tile.dart'; import 'package:fl_clash/plugins/tile.dart';
import 'package:fl_clash/plugins/vpn.dart'; import 'package:fl_clash/plugins/vpn.dart';

View File

@@ -1,6 +1,5 @@
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/plugins/app.dart'; import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';

View File

@@ -137,7 +137,7 @@ class _ClashContainerState extends State<ClashManager>
@override @override
void onLog(Log log) { void onLog(Log log) {
globalState.appController.appState.addLog(log); globalState.appController.appFlowingState.addLog(log);
if (log.logLevel == LogLevel.error) { if (log.logLevel == LogLevel.error) {
globalState.appController.showSnackBar(log.payload ?? ''); globalState.appController.showSnackBar(log.payload ?? '');
} }

View File

@@ -21,9 +21,9 @@ class ProxyManager extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Selector3<AppState, Config, ClashConfig, ProxyState>( return Selector3<AppFlowingState, Config, ClashConfig, ProxyState>(
selector: (_, appState, config, clashConfig) => ProxyState( selector: (_, appFlowingState, config, clashConfig) => ProxyState(
isStart: appState.isStart, isStart: appFlowingState.isStart,
systemProxy: config.desktopProps.systemProxy, systemProxy: config.desktopProps.systemProxy,
port: clashConfig.mixedPort, port: clashConfig.mixedPort,
), ),

View File

@@ -42,7 +42,7 @@ class _TrayContainerState extends State<TrayManager> with TrayListener {
WidgetsBinding.instance.platformDispatcher.platformBrightness, WidgetsBinding.instance.platformDispatcher.platformBrightness,
), ),
); );
if(!Platform.isLinux){ if (!Platform.isLinux) {
await trayManager.setToolTip( await trayManager.setToolTip(
appName, appName,
); );
@@ -138,11 +138,12 @@ class _TrayContainerState extends State<TrayManager> with TrayListener {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Selector3<AppState, Config, ClashConfig, TrayState>( return Selector4<AppState, AppFlowingState, Config, ClashConfig, TrayState>(
selector: (_, appState, config, clashConfig) => TrayState( selector: (_, appState, appFlowingState, config, clashConfig) =>
TrayState(
mode: clashConfig.mode, mode: clashConfig.mode,
autoLaunch: config.autoLaunch, autoLaunch: config.autoLaunch,
isStart: appState.isStart, isStart: appFlowingState.isStart,
locale: config.locale, locale: config.locale,
systemProxy: config.desktopProps.systemProxy, systemProxy: config.desktopProps.systemProxy,
tunEnable: clashConfig.tun.enable, tunEnable: clashConfig.tun.enable,

View File

@@ -24,8 +24,8 @@ class _VpnContainerState extends State<VpnManager> {
showTip() { showTip() {
vpnTipDebounce ??= debounce<Function()>(() async { vpnTipDebounce ??= debounce<Function()>(() async {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
final appState = globalState.appController.appState; final appFlowingState = globalState.appController.appFlowingState;
if (appState.isStart) { if (appFlowingState.isStart) {
globalState.showSnackBar( globalState.showSnackBar(
context, context,
message: appLocalizations.vpnTip, message: appLocalizations.vpnTip,

View File

@@ -12,12 +12,8 @@ typedef DelayMap = Map<String, int?>;
class AppState with ChangeNotifier { class AppState with ChangeNotifier {
List<NavigationItem> _navigationItems; List<NavigationItem> _navigationItems;
int? _runTime;
bool _isInit; bool _isInit;
VersionInfo? _versionInfo; VersionInfo? _versionInfo;
List<Traffic> _traffics;
Traffic _totalTraffic;
List<Log> _logs;
String _currentLabel; String _currentLabel;
SystemColorSchemes _systemColorSchemes; SystemColorSchemes _systemColorSchemes;
num _sortNum; num _sortNum;
@@ -42,16 +38,13 @@ class AppState with ChangeNotifier {
}) : _navigationItems = [], }) : _navigationItems = [],
_isInit = false, _isInit = false,
_currentLabel = "dashboard", _currentLabel = "dashboard",
_traffics = [], _viewWidth = other.getScreenSize().width,
_logs = [],
_viewWidth = 0,
_selectedMap = selectedMap, _selectedMap = selectedMap,
_sortNum = 0, _sortNum = 0,
_checkIpNum = 0, _checkIpNum = 0,
_requests = [], _requests = [],
_mode = mode, _mode = mode,
_brightness = null, _brightness = null,
_totalTraffic = Traffic(),
_delayMap = {}, _delayMap = {},
_groups = [], _groups = [],
_providers = [], _providers = [],
@@ -101,17 +94,6 @@ class AppState with ChangeNotifier {
} }
} }
bool get isStart => _runTime != null;
int? get runTime => _runTime;
set runTime(int? value) {
if (_runTime != value) {
_runTime = value;
notifyListeners();
}
}
String getDesc(String type, String proxyName) { String getDesc(String type, String proxyName) {
final groupTypeNamesList = GroupType.values.map((e) => e.name).toList(); final groupTypeNamesList = GroupType.values.map((e) => e.name).toList();
if (!groupTypeNamesList.contains(type)) { if (!groupTypeNamesList.contains(type)) {
@@ -158,33 +140,6 @@ class AppState with ChangeNotifier {
} }
} }
List<Traffic> get traffics => _traffics;
set traffics(List<Traffic> value) {
if (_traffics != value) {
_traffics = value;
notifyListeners();
}
}
addTraffic(Traffic traffic) {
_traffics = List.from(_traffics)..add(traffic);
const maxLength = 60;
if (_traffics.length > maxLength) {
_traffics = _traffics.sublist(_traffics.length - maxLength);
}
notifyListeners();
}
Traffic get totalTraffic => _totalTraffic;
set totalTraffic(Traffic value) {
if (_totalTraffic != value) {
_totalTraffic = value;
notifyListeners();
}
}
List<Connection> get requests => _requests; List<Connection> get requests => _requests;
set requests(List<Connection> value) { set requests(List<Connection> value) {
@@ -203,24 +158,6 @@ class AppState with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
List<Log> get logs => _logs;
set logs(List<Log> value) {
if (_logs != value) {
_logs = value;
notifyListeners();
}
}
addLog(Log log) {
_logs = List.from(_logs)..add(log);
final maxLength = Platform.isAndroid ? 1000 : 60;
if (_logs.length > maxLength) {
_logs = _logs.sublist(_logs.length - maxLength);
}
notifyListeners();
}
SystemColorSchemes get systemColorSchemes => _systemColorSchemes; SystemColorSchemes get systemColorSchemes => _systemColorSchemes;
set systemColorSchemes(SystemColorSchemes value) { set systemColorSchemes(SystemColorSchemes value) {
@@ -383,3 +320,71 @@ class AppState with ChangeNotifier {
} }
} }
} }
class AppFlowingState with ChangeNotifier {
int? _runTime;
List<Log> _logs;
List<Traffic> _traffics;
Traffic _totalTraffic;
AppFlowingState()
: _logs = [],
_traffics = [],
_totalTraffic = Traffic();
bool get isStart => _runTime != null;
int? get runTime => _runTime;
set runTime(int? value) {
if (_runTime != value) {
_runTime = value;
notifyListeners();
}
}
List<Log> get logs => _logs;
set logs(List<Log> value) {
if (_logs != value) {
_logs = value;
notifyListeners();
}
}
addLog(Log log) {
_logs = List.from(_logs)..add(log);
final maxLength = Platform.isAndroid ? 1000 : 60;
if (_logs.length > maxLength) {
_logs = _logs.sublist(_logs.length - maxLength);
}
notifyListeners();
}
List<Traffic> get traffics => _traffics;
set traffics(List<Traffic> value) {
if (_traffics != value) {
_traffics = value;
notifyListeners();
}
}
addTraffic(Traffic traffic) {
_traffics = List.from(_traffics)..add(traffic);
const maxLength = 60;
if (_traffics.length > maxLength) {
_traffics = _traffics.sublist(_traffics.length - maxLength);
}
notifyListeners();
}
Traffic get totalTraffic => _totalTraffic;
set totalTraffic(Traffic value) {
if (_totalTraffic != value) {
_totalTraffic = value;
notifyListeners();
}
}
}

View File

@@ -52,7 +52,7 @@ class Dns with _$Dns {
@Default(false) @JsonKey(name: "prefer-h3") bool preferH3, @Default(false) @JsonKey(name: "prefer-h3") bool preferH3,
@Default(true) @JsonKey(name: "use-hosts") bool useHosts, @Default(true) @JsonKey(name: "use-hosts") bool useHosts,
@Default(true) @JsonKey(name: "use-system-hosts") bool useSystemHosts, @Default(true) @JsonKey(name: "use-system-hosts") bool useSystemHosts,
@Default(true) @JsonKey(name: "respect-rules") bool respectRules, @Default(false) @JsonKey(name: "respect-rules") bool respectRules,
@Default(false) bool ipv6, @Default(false) bool ipv6,
@Default(["223.5.5.5"]) @Default(["223.5.5.5"])
@JsonKey(name: "default-nameserver") @JsonKey(name: "default-nameserver")

View File

@@ -1,4 +1,3 @@
import 'dart:convert';
import 'dart:math'; import 'dart:math';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';

View File

@@ -772,7 +772,7 @@ class _$DnsImpl implements _Dns {
@JsonKey(name: "prefer-h3") this.preferH3 = false, @JsonKey(name: "prefer-h3") this.preferH3 = false,
@JsonKey(name: "use-hosts") this.useHosts = true, @JsonKey(name: "use-hosts") this.useHosts = true,
@JsonKey(name: "use-system-hosts") this.useSystemHosts = true, @JsonKey(name: "use-system-hosts") this.useSystemHosts = true,
@JsonKey(name: "respect-rules") this.respectRules = true, @JsonKey(name: "respect-rules") this.respectRules = false,
this.ipv6 = false, this.ipv6 = false,
@JsonKey(name: "default-nameserver") @JsonKey(name: "default-nameserver")
final List<String> defaultNameserver = const ["223.5.5.5"], final List<String> defaultNameserver = const ["223.5.5.5"],

View File

@@ -141,7 +141,7 @@ _$DnsImpl _$$DnsImplFromJson(Map<String, dynamic> json) => _$DnsImpl(
preferH3: json['prefer-h3'] as bool? ?? false, preferH3: json['prefer-h3'] as bool? ?? false,
useHosts: json['use-hosts'] as bool? ?? true, useHosts: json['use-hosts'] as bool? ?? true,
useSystemHosts: json['use-system-hosts'] as bool? ?? true, useSystemHosts: json['use-system-hosts'] as bool? ?? true,
respectRules: json['respect-rules'] as bool? ?? true, respectRules: json['respect-rules'] as bool? ?? false,
ipv6: json['ipv6'] as bool? ?? false, ipv6: json['ipv6'] as bool? ?? false,
defaultNameserver: (json['default-nameserver'] as List<dynamic>?) defaultNameserver: (json['default-nameserver'] as List<dynamic>?)
?.map((e) => e as String) ?.map((e) => e as String)

View File

@@ -1,4 +1,3 @@
import 'package:collection/collection.dart';
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart'; import 'package:fl_clash/state.dart';
@@ -155,56 +154,47 @@ class HomePage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BackScope( return BackScope(
child: LayoutBuilder( child: Selector2<AppState, Config, HomeState>(
builder: (_, container) { selector: (_, appState, config) {
final appController = globalState.appController; return HomeState(
final maxWidth = container.maxWidth; currentLabel: appState.currentLabel,
if (appController.appState.viewWidth != maxWidth) { navigationItems: appState.currentNavigationItems,
globalState.appController.updateViewWidth(maxWidth); viewMode: appState.viewMode,
} locale: config.locale,
return Selector2<AppState, Config, HomeState>(
selector: (_, appState, config) {
return HomeState(
currentLabel: appState.currentLabel,
navigationItems: appState.currentNavigationItems,
viewMode: other.getViewMode(maxWidth),
locale: config.locale,
);
},
shouldRebuild: (prev, next) {
return prev != next;
},
builder: (_, state, child) {
final viewMode = state.viewMode;
final navigationItems = state.navigationItems;
final currentLabel = state.currentLabel;
final index = navigationItems.lastIndexWhere(
(element) => element.label == currentLabel,
);
final currentIndex = index == -1 ? 0 : index;
final navigationBar = _getNavigationBar(
context: context,
viewMode: viewMode,
navigationItems: navigationItems,
currentIndex: currentIndex,
);
final bottomNavigationBar =
viewMode == ViewMode.mobile ? navigationBar : null;
final sideNavigationBar =
viewMode != ViewMode.mobile ? navigationBar : null;
return CommonScaffold(
key: globalState.homeScaffoldKey,
title: Intl.message(
currentLabel,
),
sideNavigationBar: sideNavigationBar,
body: child!,
bottomNavigationBar: bottomNavigationBar,
);
},
child: _buildPageView(),
); );
}, },
shouldRebuild: (prev, next) {
return prev != next;
},
builder: (_, state, child) {
final viewMode = state.viewMode;
final navigationItems = state.navigationItems;
final currentLabel = state.currentLabel;
final index = navigationItems.lastIndexWhere(
(element) => element.label == currentLabel,
);
final currentIndex = index == -1 ? 0 : index;
final navigationBar = _getNavigationBar(
context: context,
viewMode: viewMode,
navigationItems: navigationItems,
currentIndex: currentIndex,
);
final bottomNavigationBar =
viewMode == ViewMode.mobile ? navigationBar : null;
final sideNavigationBar =
viewMode != ViewMode.mobile ? navigationBar : null;
return CommonScaffold(
key: globalState.homeScaffoldKey,
title: Intl.message(
currentLabel,
),
sideNavigationBar: sideNavigationBar,
body: child!,
bottomNavigationBar: bottomNavigationBar,
);
},
child: _buildPageView(),
), ),
); );
} }

View File

@@ -3,9 +3,7 @@ import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:isolate'; import 'dart:isolate';
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';

View File

@@ -1,7 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:isolate'; import 'dart:isolate';
import 'package:fl_clash/state.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
class Service { class Service {
@@ -28,4 +27,4 @@ class Service {
} }
final service = final service =
Platform.isAndroid && !globalState.isVpnService ? Service() : null; Platform.isAndroid ? Service() : null;

View File

@@ -6,8 +6,6 @@ import 'dart:isolate';
import 'package:fl_clash/clash/clash.dart'; import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
class Vpn { class Vpn {
@@ -105,4 +103,4 @@ class Vpn {
} }
} }
final vpn = Platform.isAndroid && globalState.isVpnService ? Vpn() : null; final vpn = Platform.isAndroid ? Vpn() : null;

View File

@@ -76,7 +76,7 @@ class GlobalState {
required ClashConfig clashConfig, required ClashConfig clashConfig,
}) async { }) async {
clashCore.start(); clashCore.start();
if (vpn != null) { if (globalState.isVpnService) {
await vpn?.startVpn(clashConfig.mixedPort); await vpn?.startVpn(clashConfig.mixedPort);
startListenUpdate(); startListenUpdate();
return; return;
@@ -222,7 +222,7 @@ class GlobalState {
} }
updateTraffic({ updateTraffic({
AppState? appState, AppFlowingState? appFlowingState,
}) { }) {
final traffic = clashCore.getTraffic(); final traffic = clashCore.getTraffic();
if (Platform.isAndroid && isVpnService == true) { if (Platform.isAndroid && isVpnService == true) {
@@ -231,9 +231,9 @@ class GlobalState {
content: "$traffic", content: "$traffic",
); );
} else { } else {
if (appState != null) { if (appFlowingState != null) {
appState.addTraffic(traffic); appFlowingState.addTraffic(traffic);
appState.totalTraffic = clashCore.getTotalTraffic(); appFlowingState.totalTraffic = clashCore.getTotalTraffic();
} }
} }
} }
@@ -243,7 +243,7 @@ class GlobalState {
required String message, required String message,
SnackBarAction? action, SnackBarAction? action,
}) { }) {
final width = context.width; final width = context.viewWidth;
EdgeInsets margin; EdgeInsets margin;
if (width < 600) { if (width < 600) {
margin = const EdgeInsets.only( margin = const EdgeInsets.only(

View File

@@ -1,6 +1,5 @@
import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'text.dart'; import 'text.dart';

View File

@@ -462,7 +462,6 @@ class _OpenContainerRoute<T> extends ModalRoute<T> {
); );
TweenSequence<Color?>? colorTween; TweenSequence<Color?>? colorTween;
TweenSequence<double>? closedOpacityTween, openOpacityTween; TweenSequence<double>? closedOpacityTween, openOpacityTween;
Animatable<Color?>? scrimTween;
switch (animation.status) { switch (animation.status) {
case AnimationStatus.dismissed: case AnimationStatus.dismissed:
case AnimationStatus.forward: case AnimationStatus.forward:

View File

@@ -53,7 +53,7 @@ showExtendPage(
body: uniqueBody, body: uniqueBody,
); );
return SizedBox( return SizedBox(
width: isMobile ? context.width : extendPageWidth ?? 300, width: isMobile ? context.viewWidth : extendPageWidth ?? 300,
child: commonScaffold, child: commonScaffold,
); );
}, },

View File

@@ -1,7 +1,7 @@
name: fl_clash name: fl_clash
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free. description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
publish_to: 'none' publish_to: 'none'
version: 0.8.60+202409171 version: 0.8.61+202409201
environment: environment:
sdk: '>=3.1.0 <4.0.0' sdk: '>=3.1.0 <4.0.0'
flutter: 3.22.0 flutter: 3.22.0