Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30dc3f4d13 | ||
|
|
2c5f8525a7 |
15
README.md
15
README.md
@@ -6,18 +6,9 @@
|
|||||||
|
|
||||||
## FlClash
|
## FlClash
|
||||||
|
|
||||||
<p style="text-align: left;">
|
[](https://github.com/chen08209/FlClash/releases/)[](https://github.com/chen08209/FlClash/releases/)[](LICENSE)
|
||||||
<a href="https://github.com/chen08209/FlClash/releases/latest">
|
|
||||||
<img alt="release" src="https://img.shields.io/github/v/release/chen08209/FlClash?logo=github"/>
|
[](https://t.me/FlClash)
|
||||||
</a>
|
|
||||||
<a href="https://t.me/FlClash">
|
|
||||||
<img alt="channel" src="https://img.shields.io/badge/Telegram-Channel-blue?logo=telegram"/>
|
|
||||||
</a>
|
|
||||||
<img alt="downloads" src="https://img.shields.io/github/downloads/chen08209/FlClash/total"/>
|
|
||||||
<a href="LICENSE">
|
|
||||||
<img alt="license" src="https://img.shields.io/github/license/chen08209/FlClash"/>
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
|
A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
|
||||||
|
|
||||||
|
|||||||
@@ -6,18 +6,10 @@
|
|||||||
|
|
||||||
## FlClash
|
## FlClash
|
||||||
|
|
||||||
<p style="text-align: left;">
|
[](https://github.com/chen08209/FlClash/releases/)[](https://github.com/chen08209/FlClash/releases/)[](LICENSE)
|
||||||
<a href="https://github.com/chen08209/FlClash/releases/latest">
|
|
||||||
<img alt="release" src="https://img.shields.io/github/v/release/chen08209/FlClash?logo=github"/>
|
[](https://t.me/FlClash)
|
||||||
</a>
|
|
||||||
<a href="https://t.me/FlClash">
|
|
||||||
<img alt="channel" src="https://img.shields.io/badge/Telegram-Channel-blue?logo=telegram"/>
|
|
||||||
</a>
|
|
||||||
<img alt="downloads" src="https://img.shields.io/github/downloads/chen08209/FlClash/total"/>
|
|
||||||
<a href="LICENSE">
|
|
||||||
<img alt="license" src="https://img.shields.io/github/license/chen08209/FlClash"/>
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
基于ClashMeta的多平台代理客户端,简单易用,开源无广告。
|
基于ClashMeta的多平台代理客户端,简单易用,开源无广告。
|
||||||
|
|
||||||
|
|||||||
@@ -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-->
|
||||||
|
|||||||
@@ -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?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -62,6 +62,6 @@ extension DoubleListExt on List<double> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1; // 这行理论上不会执行到,但为了完整性保留
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
@@ -303,5 +303,6 @@
|
|||||||
"inputCorrectHotkey": "请输入正确的快捷键",
|
"inputCorrectHotkey": "请输入正确的快捷键",
|
||||||
"hotkeyConflict": "快捷键冲突",
|
"hotkeyConflict": "快捷键冲突",
|
||||||
"remove": "移除",
|
"remove": "移除",
|
||||||
"noHotKey": "暂无快捷键"
|
"noHotKey": "暂无快捷键",
|
||||||
|
"dnsResetTip": "确定重置DNS"
|
||||||
}
|
}
|
||||||
@@ -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"),
|
||||||
|
|||||||
@@ -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("下载"),
|
||||||
|
|||||||
@@ -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> {
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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 ?? '');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
|||||||
@@ -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"],
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user