Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f679d1e6d8 |
@@ -24,8 +24,9 @@
|
|||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
android:extractNativeLibs="true"
|
android:extractNativeLibs="true"
|
||||||
|
android:enableOnBackInvokedCallback="true"
|
||||||
android:label="FlClash"
|
android:label="FlClash"
|
||||||
tools:targetApi="n">
|
tools:targetApi="tiramisu">
|
||||||
<activity
|
<activity
|
||||||
android:name="com.follow.clash.MainActivity"
|
android:name="com.follow.clash.MainActivity"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
@@ -74,7 +75,7 @@
|
|||||||
<service
|
<service
|
||||||
android:name=".services.FlClashTileService"
|
android:name=".services.FlClashTileService"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:icon="@drawable/icon"
|
android:icon="@drawable/ic_stat_name"
|
||||||
android:foregroundServiceType="specialUse"
|
android:foregroundServiceType="specialUse"
|
||||||
android:label="FlClash"
|
android:label="FlClash"
|
||||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 11 KiB |
@@ -1,6 +1,7 @@
|
|||||||
package com.follow.clash
|
package com.follow.clash
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import com.follow.clash.plugins.AppPlugin
|
import com.follow.clash.plugins.AppPlugin
|
||||||
import com.follow.clash.plugins.ProxyPlugin
|
import com.follow.clash.plugins.ProxyPlugin
|
||||||
@@ -56,6 +57,8 @@ object GlobalState {
|
|||||||
serviceEngine?.dartExecutor?.executeDartEntrypoint(
|
serviceEngine?.dartExecutor?.executeDartEntrypoint(
|
||||||
vpnService,
|
vpnService,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Log.e("FlClashVpnService", "initServiceEngine ===>")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import android.content.pm.PackageManager
|
|||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
|
import android.util.Log
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.follow.clash.GlobalState
|
import com.follow.clash.GlobalState
|
||||||
@@ -131,7 +132,13 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
|
|||||||
}
|
}
|
||||||
if (GlobalState.runState.value == RunState.START) return
|
if (GlobalState.runState.value == RunState.START) return
|
||||||
GlobalState.runState.value = RunState.START
|
GlobalState.runState.value = RunState.START
|
||||||
flutterMethodChannel.invokeMethod("started", flClashVpnService?.start(port, props))
|
val intent = VpnService.prepare(context)
|
||||||
|
if (intent != null) {
|
||||||
|
stopVpn()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val fd = flClashVpnService?.start(port, props)
|
||||||
|
flutterMethodChannel.invokeMethod("started", fd)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stopVpn() {
|
private fun stopVpn() {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import android.os.Build
|
|||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.os.RemoteException
|
import android.os.RemoteException
|
||||||
|
import android.util.Log
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import com.follow.clash.GlobalState
|
import com.follow.clash.GlobalState
|
||||||
import com.follow.clash.MainActivity
|
import com.follow.clash.MainActivity
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="240"
|
||||||
|
android:viewportHeight="240"
|
||||||
|
android:tint="#FFFFFF">
|
||||||
|
<group android:scaleX="2.0264318"
|
||||||
|
android:scaleY="2.0264318"
|
||||||
|
android:translateX="-123.17181"
|
||||||
|
android:translateY="-123.17181">
|
||||||
|
<group android:scaleX="0.61"
|
||||||
|
android:scaleY="0.61"
|
||||||
|
android:translateX="46.8"
|
||||||
|
android:translateY="46.8">
|
||||||
|
<path
|
||||||
|
android:pathData="M67.67,115.76L144.04,39.39A18,18 0,0 1,169.5 39.39L169.5,39.39A18,18 0,0 1,169.5 64.85L93.13,141.21A18,18 0,0 1,67.67 141.21L67.67,141.21A18,18 0,0 1,67.67 115.76z"
|
||||||
|
android:fillColor="#6666FF"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M98.79,146.87L136.97,108.69A18,18 72.93,0 1,162.43 108.69L162.43,108.69A18,18 72.93,0 1,162.43 134.14L124.24,172.33A18,18 76.87,0 1,98.79 172.33L98.79,172.33A18,18 76.87,0 1,98.79 146.87z"
|
||||||
|
android:fillColor="#326CBF"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M129.9,177.98L129.9,177.98A18,18 107.07,0 1,155.36 177.98L155.36,177.98A18,18 107.07,0 1,155.36 203.44L155.36,203.44A18,18 107.07,0 1,129.9 203.44L129.9,203.44A18,18 107.07,0 1,129.9 177.98z"
|
||||||
|
android:fillColor="#56afee"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</vector>
|
||||||
|
Before Width: | Height: | Size: 763 B After Width: | Height: | Size: 532 B |
|
Before Width: | Height: | Size: 520 B After Width: | Height: | Size: 311 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 671 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 932 B |
|
Before Width: | Height: | Size: 2.1 KiB |
25
android/app/src/main/res/drawable/ic_launcher_foreground.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="240"
|
||||||
|
android:viewportHeight="240">
|
||||||
|
<group android:scaleX="0.90244895"
|
||||||
|
android:scaleY="0.90244895"
|
||||||
|
android:translateX="12.708843"
|
||||||
|
android:translateY="10.703402">
|
||||||
|
<group android:scaleX="0.61"
|
||||||
|
android:scaleY="0.61"
|
||||||
|
android:translateX="46.8"
|
||||||
|
android:translateY="46.8">
|
||||||
|
<path
|
||||||
|
android:pathData="M67.67,115.76L144.04,39.39A18,18 0,0 1,169.5 39.39L169.5,39.39A18,18 0,0 1,169.5 64.85L93.13,141.21A18,18 0,0 1,67.67 141.21L67.67,141.21A18,18 0,0 1,67.67 115.76z"
|
||||||
|
android:fillColor="#6666FF"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M98.79,146.87L136.97,108.69A18,18 72.93,0 1,162.43 108.69L162.43,108.69A18,18 72.93,0 1,162.43 134.14L124.24,172.33A18,18 76.87,0 1,98.79 172.33L98.79,172.33A18,18 76.87,0 1,98.79 146.87z"
|
||||||
|
android:fillColor="#326CBF"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M129.9,177.98L129.9,177.98A18,18 107.07,0 1,155.36 177.98L155.36,177.98A18,18 107.07,0 1,155.36 203.44L155.36,203.44A18,18 107.07,0 1,129.9 203.44L129.9,203.44A18,18 107.07,0 1,129.9 177.98z"
|
||||||
|
android:fillColor="#56afee"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</vector>
|
||||||
|
Before Width: | Height: | Size: 118 KiB |
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
<monochrome android:drawable="@mipmap/ic_launcher_foreground" />
|
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
<monochrome android:drawable="@mipmap/ic_launcher_foreground" />
|
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 812 B |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 7.1 KiB |
@@ -6,7 +6,7 @@
|
|||||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||||
<item name="android:windowSplashScreenBackground" tools:targetApi="s">#121212</item>
|
<item name="android:windowSplashScreenBackground" tools:targetApi="s">#121212</item>
|
||||||
<item name="android:windowSplashScreenAnimatedIcon" tools:targetApi="s">@mipmap/ic_launcher_foreground</item>
|
<item name="android:windowSplashScreenAnimatedIcon" tools:targetApi="s">@drawable/ic_launcher_foreground</item>
|
||||||
<item name="postSplashScreenTheme">@style/NormalTheme</item>
|
<item name="postSplashScreenTheme">@style/NormalTheme</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||||
<item name="android:windowSplashScreenBackground" tools:targetApi="s">@color/ic_launcher_background</item>
|
<item name="android:windowSplashScreenBackground" tools:targetApi="s">@color/ic_launcher_background</item>
|
||||||
<item name="android:windowSplashScreenAnimatedIcon" tools:targetApi="s">@mipmap/ic_launcher_foreground</item>
|
<item name="android:windowSplashScreenAnimatedIcon" tools:targetApi="s">@drawable/ic_launcher_foreground</item>
|
||||||
<item name="postSplashScreenTheme">@style/NormalTheme</item>
|
<item name="postSplashScreenTheme">@style/NormalTheme</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="ic_launcher_background">#EFEFEF</color>
|
<color name="ic_launcher_background">#FAFAFA</color>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -137,56 +137,59 @@ class ApplicationState extends State<Application> {
|
|||||||
Widget build(context) {
|
Widget build(context) {
|
||||||
return AppStateContainer(
|
return AppStateContainer(
|
||||||
child: ClashMessageContainer(
|
child: ClashMessageContainer(
|
||||||
child: _buildApp(
|
child: Selector2<AppState, Config, ApplicationSelectorState>(
|
||||||
Selector2<AppState, Config, ApplicationSelectorState>(
|
selector: (_, appState, config) => ApplicationSelectorState(
|
||||||
selector: (_, appState, config) => ApplicationSelectorState(
|
locale: config.locale,
|
||||||
locale: config.locale,
|
themeMode: config.themeMode,
|
||||||
themeMode: config.themeMode,
|
primaryColor: config.primaryColor,
|
||||||
primaryColor: config.primaryColor,
|
|
||||||
),
|
|
||||||
builder: (_, state, child) {
|
|
||||||
return DynamicColorBuilder(
|
|
||||||
builder: (lightDynamic, darkDynamic) {
|
|
||||||
_updateSystemColorSchemes(lightDynamic, darkDynamic);
|
|
||||||
return MaterialApp(
|
|
||||||
navigatorKey: globalState.navigatorKey,
|
|
||||||
localizationsDelegates: const [
|
|
||||||
AppLocalizations.delegate,
|
|
||||||
GlobalMaterialLocalizations.delegate,
|
|
||||||
GlobalCupertinoLocalizations.delegate,
|
|
||||||
GlobalWidgetsLocalizations.delegate
|
|
||||||
],
|
|
||||||
scrollBehavior: BaseScrollBehavior(),
|
|
||||||
title: appName,
|
|
||||||
locale: other.getLocaleForString(state.locale),
|
|
||||||
supportedLocales:
|
|
||||||
AppLocalizations.delegate.supportedLocales,
|
|
||||||
themeMode: state.themeMode,
|
|
||||||
theme: ThemeData(
|
|
||||||
useMaterial3: true,
|
|
||||||
pageTransitionsTheme: _pageTransitionsTheme,
|
|
||||||
colorScheme: _getAppColorScheme(
|
|
||||||
brightness: Brightness.light,
|
|
||||||
systemColorSchemes: systemColorSchemes,
|
|
||||||
primaryColor: state.primaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
darkTheme: ThemeData(
|
|
||||||
useMaterial3: true,
|
|
||||||
pageTransitionsTheme: _pageTransitionsTheme,
|
|
||||||
colorScheme: _getAppColorScheme(
|
|
||||||
brightness: Brightness.dark,
|
|
||||||
systemColorSchemes: systemColorSchemes,
|
|
||||||
primaryColor: state.primaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
home: child,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: const HomePage(),
|
|
||||||
),
|
),
|
||||||
|
builder: (_, state, child) {
|
||||||
|
return DynamicColorBuilder(
|
||||||
|
builder: (lightDynamic, darkDynamic) {
|
||||||
|
_updateSystemColorSchemes(lightDynamic, darkDynamic);
|
||||||
|
return MaterialApp(
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
navigatorKey: globalState.navigatorKey,
|
||||||
|
localizationsDelegates: const [
|
||||||
|
AppLocalizations.delegate,
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
GlobalCupertinoLocalizations.delegate,
|
||||||
|
GlobalWidgetsLocalizations.delegate
|
||||||
|
],
|
||||||
|
builder: (_, child) {
|
||||||
|
return PopContainer(
|
||||||
|
child: _buildApp(child!),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
scrollBehavior: BaseScrollBehavior(),
|
||||||
|
title: appName,
|
||||||
|
locale: other.getLocaleForString(state.locale),
|
||||||
|
supportedLocales: AppLocalizations.delegate.supportedLocales,
|
||||||
|
themeMode: state.themeMode,
|
||||||
|
theme: ThemeData(
|
||||||
|
useMaterial3: true,
|
||||||
|
pageTransitionsTheme: _pageTransitionsTheme,
|
||||||
|
colorScheme: _getAppColorScheme(
|
||||||
|
brightness: Brightness.light,
|
||||||
|
systemColorSchemes: systemColorSchemes,
|
||||||
|
primaryColor: state.primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
darkTheme: ThemeData(
|
||||||
|
useMaterial3: true,
|
||||||
|
pageTransitionsTheme: _pageTransitionsTheme,
|
||||||
|
colorScheme: _getAppColorScheme(
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
systemColorSchemes: systemColorSchemes,
|
||||||
|
primaryColor: state.primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
home: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const HomePage(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
|
import 'dart:io';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:fl_clash/enum/enum.dart';
|
import 'package:fl_clash/enum/enum.dart';
|
||||||
import 'package:fl_clash/models/clash_config.dart';
|
import 'package:fl_clash/models/clash_config.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'system.dart';
|
||||||
|
|
||||||
const appName = "FlClash";
|
const appName = "FlClash";
|
||||||
const coreName = "clash.meta";
|
const coreName = "clash.meta";
|
||||||
@@ -15,6 +17,7 @@ const mmdbFileName = "geoip.metadb";
|
|||||||
const asnFileName = "ASN.mmdb";
|
const asnFileName = "ASN.mmdb";
|
||||||
const geoIpFileName = "GeoIP.dat";
|
const geoIpFileName = "GeoIP.dat";
|
||||||
const geoSiteFileName = "GeoSite.dat";
|
const geoSiteFileName = "GeoSite.dat";
|
||||||
|
final double kHeaderHeight = system.isDesktop ? (Platform.isMacOS ? 28 : 40) : 0;
|
||||||
const GeoXMap defaultGeoXMap = {
|
const GeoXMap defaultGeoXMap = {
|
||||||
"mmdb":
|
"mmdb":
|
||||||
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb",
|
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb",
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ class Request {
|
|||||||
createHttpClient: () {
|
createHttpClient: () {
|
||||||
final client = HttpClient();
|
final client = HttpClient();
|
||||||
if (!_isStart) return client;
|
if (!_isStart) return client;
|
||||||
|
client.userAgent = globalState.appController.clashConfig.globalUa;
|
||||||
client.findProxy = (url) {
|
client.findProxy = (url) {
|
||||||
return "PROXY localhost:$_port;DIRECT";
|
return "PROXY localhost:$_port;DIRECT";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ class Window {
|
|||||||
WindowOptions windowOptions = WindowOptions(
|
WindowOptions windowOptions = WindowOptions(
|
||||||
size: Size(props.width, props.height),
|
size: Size(props.width, props.height),
|
||||||
minimumSize: const Size(380, 600),
|
minimumSize: const Size(380, 600),
|
||||||
|
titleBarStyle: TitleBarStyle.hidden,
|
||||||
);
|
);
|
||||||
if (props.left != null || props.top != null) {
|
if (props.left != null || props.top != null) {
|
||||||
await windowManager.setPosition(
|
await windowManager.setPosition(
|
||||||
@@ -28,6 +29,9 @@ class Window {
|
|||||||
} else {
|
} else {
|
||||||
await windowManager.setAlignment(Alignment.center);
|
await windowManager.setAlignment(Alignment.center);
|
||||||
}
|
}
|
||||||
|
// if(Platform.isWindows){
|
||||||
|
// await windowManager.setTitleBarStyle(TitleBarStyle.hidden);
|
||||||
|
// }
|
||||||
await windowManager.waitUntilReadyToShow(windowOptions, () async {
|
await windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||||
await windowManager.setPreventClose(true);
|
await windowManager.setPreventClose(true);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import 'package:fl_clash/enum/enum.dart';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:fl_clash/fragments/dashboard/intranet_ip.dart';
|
import 'package:fl_clash/fragments/dashboard/intranet_ip.dart';
|
||||||
import 'package:fl_clash/models/models.dart';
|
import 'package:fl_clash/models/models.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:fl_clash/widgets/widgets.dart';
|
import 'package:fl_clash/widgets/widgets.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'network_detection.dart';
|
import 'network_detection.dart';
|
||||||
import 'outbound_mode.dart';
|
import 'outbound_mode.dart';
|
||||||
import 'start_button.dart';
|
import 'start_button.dart';
|
||||||
@@ -29,34 +29,35 @@ class _DashboardFragmentState extends State<DashboardFragment> {
|
|||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Selector<AppState, ViewMode>(
|
child: Selector<AppState, double>(
|
||||||
selector: (_, appState) => appState.viewMode,
|
selector: (_, appState) => appState.viewWidth,
|
||||||
builder: (_, viewMode, ___) {
|
builder: (_, viewWidth, ___) {
|
||||||
final isDesktop = viewMode == ViewMode.desktop;
|
// final viewMode = other.getViewMode(viewWidth);
|
||||||
|
// final isDesktop = viewMode == ViewMode.desktop;
|
||||||
return Grid(
|
return Grid(
|
||||||
crossAxisCount: 12,
|
crossAxisCount: max(4 * ((viewWidth / 320).ceil()), 8),
|
||||||
crossAxisSpacing: 16,
|
crossAxisSpacing: 16,
|
||||||
mainAxisSpacing: 16,
|
mainAxisSpacing: 16,
|
||||||
children: [
|
children: const [
|
||||||
GridItem(
|
GridItem(
|
||||||
crossAxisCellCount: isDesktop ? 8 : 12,
|
crossAxisCellCount: 8,
|
||||||
child: const NetworkSpeed(),
|
child: NetworkSpeed(),
|
||||||
),
|
),
|
||||||
GridItem(
|
GridItem(
|
||||||
crossAxisCellCount: isDesktop ? 4 : 6,
|
crossAxisCellCount: 4,
|
||||||
child: const OutboundMode(),
|
child: OutboundMode(),
|
||||||
),
|
),
|
||||||
GridItem(
|
GridItem(
|
||||||
crossAxisCellCount: isDesktop ? 4 : 6,
|
crossAxisCellCount: 4,
|
||||||
child: const NetworkDetection(),
|
child: NetworkDetection(),
|
||||||
),
|
),
|
||||||
GridItem(
|
GridItem(
|
||||||
crossAxisCellCount: isDesktop ? 4 : 6,
|
crossAxisCellCount: 4,
|
||||||
child: const TrafficUsage(),
|
child: TrafficUsage(),
|
||||||
),
|
),
|
||||||
GridItem(
|
GridItem(
|
||||||
crossAxisCellCount: isDesktop ? 4 : 6,
|
crossAxisCellCount: 4,
|
||||||
child: const IntranetIP(),
|
child: IntranetIP(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -177,14 +177,14 @@
|
|||||||
"geodataLoader": "Geo Low Memory Mode",
|
"geodataLoader": "Geo Low Memory Mode",
|
||||||
"geodataLoaderDesc": "Enabling will use the Geo low memory loader",
|
"geodataLoaderDesc": "Enabling will use the Geo low memory loader",
|
||||||
"requests": "Requests",
|
"requests": "Requests",
|
||||||
"requestsDesc": "View recently requested data",
|
"requestsDesc": "View recently request records",
|
||||||
"findProcessMode": "Find process",
|
"findProcessMode": "Find process",
|
||||||
"findProcessModeDesc": "There is a risk of flashback after opening",
|
"findProcessModeDesc": "There is a risk of flashback after opening",
|
||||||
"init": "Init",
|
"init": "Init",
|
||||||
"infiniteTime": "Long term effective",
|
"infiniteTime": "Long term effective",
|
||||||
"expirationTime": "Expiration time",
|
"expirationTime": "Expiration time",
|
||||||
"connections": "Connections",
|
"connections": "Connections",
|
||||||
"connectionsDesc": "View current connection",
|
"connectionsDesc": "View current connections data",
|
||||||
"nullRequestsDesc": "No requests",
|
"nullRequestsDesc": "No requests",
|
||||||
"nullConnectionsDesc": "No connections",
|
"nullConnectionsDesc": "No connections",
|
||||||
"intranetIP": "Intranet IP",
|
"intranetIP": "Intranet IP",
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
"qrcode": "二维码",
|
"qrcode": "二维码",
|
||||||
"qrcodeDesc": "扫描二维码获取配置文件",
|
"qrcodeDesc": "扫描二维码获取配置文件",
|
||||||
"url": "URL",
|
"url": "URL",
|
||||||
"urlDesc": "直接上传配置文件",
|
"urlDesc": "通过URL获取配置文件",
|
||||||
"file": "文件",
|
"file": "文件",
|
||||||
"fileDesc": "直接上传配置文件",
|
"fileDesc": "直接上传配置文件",
|
||||||
"name": "名称",
|
"name": "名称",
|
||||||
@@ -177,14 +177,14 @@
|
|||||||
"geodataLoader": "Geo低内存模式",
|
"geodataLoader": "Geo低内存模式",
|
||||||
"geodataLoaderDesc": "开启将使用Geo低内存加载器",
|
"geodataLoaderDesc": "开启将使用Geo低内存加载器",
|
||||||
"requests": "请求",
|
"requests": "请求",
|
||||||
"requestsDesc": "查看最近请求数据",
|
"requestsDesc": "查看最近请求记录",
|
||||||
"findProcessMode": "查找进程",
|
"findProcessMode": "查找进程",
|
||||||
"findProcessModeDesc": "开启后存在闪退风险",
|
"findProcessModeDesc": "开启后存在闪退风险",
|
||||||
"init": "初始化",
|
"init": "初始化",
|
||||||
"infiniteTime": "长期有效",
|
"infiniteTime": "长期有效",
|
||||||
"expirationTime": "到期时间",
|
"expirationTime": "到期时间",
|
||||||
"connections": "连接",
|
"connections": "连接",
|
||||||
"connectionsDesc": "查看当前连接",
|
"connectionsDesc": "查看当前连接数据",
|
||||||
"nullRequestsDesc": "暂无请求",
|
"nullRequestsDesc": "暂无请求",
|
||||||
"nullConnectionsDesc": "暂无连接",
|
"nullConnectionsDesc": "暂无连接",
|
||||||
"intranetIP": "内网 IP",
|
"intranetIP": "内网 IP",
|
||||||
|
|||||||
@@ -94,8 +94,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"Opening it will lose part of its application ability and gain the support of full amount of Clash."),
|
"Opening it will lose part of its application ability and gain the support of full amount of Clash."),
|
||||||
"confirm": MessageLookupByLibrary.simpleMessage("Confirm"),
|
"confirm": MessageLookupByLibrary.simpleMessage("Confirm"),
|
||||||
"connections": MessageLookupByLibrary.simpleMessage("Connections"),
|
"connections": MessageLookupByLibrary.simpleMessage("Connections"),
|
||||||
"connectionsDesc":
|
"connectionsDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
MessageLookupByLibrary.simpleMessage("View current connection"),
|
"View current connections data"),
|
||||||
"connectivity": MessageLookupByLibrary.simpleMessage("Connectivity:"),
|
"connectivity": MessageLookupByLibrary.simpleMessage("Connectivity:"),
|
||||||
"copy": MessageLookupByLibrary.simpleMessage("Copy"),
|
"copy": MessageLookupByLibrary.simpleMessage("Copy"),
|
||||||
"core": MessageLookupByLibrary.simpleMessage("Core"),
|
"core": MessageLookupByLibrary.simpleMessage("Core"),
|
||||||
@@ -257,7 +257,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
MessageLookupByLibrary.simpleMessage("Recovery success"),
|
MessageLookupByLibrary.simpleMessage("Recovery success"),
|
||||||
"requests": MessageLookupByLibrary.simpleMessage("Requests"),
|
"requests": MessageLookupByLibrary.simpleMessage("Requests"),
|
||||||
"requestsDesc": MessageLookupByLibrary.simpleMessage(
|
"requestsDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
"View recently requested data"),
|
"View recently request records"),
|
||||||
"resources": MessageLookupByLibrary.simpleMessage("Resources"),
|
"resources": MessageLookupByLibrary.simpleMessage("Resources"),
|
||||||
"resourcesDesc": MessageLookupByLibrary.simpleMessage(
|
"resourcesDesc": MessageLookupByLibrary.simpleMessage(
|
||||||
"External resource related info"),
|
"External resource related info"),
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
MessageLookupByLibrary.simpleMessage("开启将失去部分应用能力,获得全量的Clash的支持"),
|
MessageLookupByLibrary.simpleMessage("开启将失去部分应用能力,获得全量的Clash的支持"),
|
||||||
"confirm": MessageLookupByLibrary.simpleMessage("确定"),
|
"confirm": MessageLookupByLibrary.simpleMessage("确定"),
|
||||||
"connections": MessageLookupByLibrary.simpleMessage("连接"),
|
"connections": MessageLookupByLibrary.simpleMessage("连接"),
|
||||||
"connectionsDesc": MessageLookupByLibrary.simpleMessage("查看当前连接"),
|
"connectionsDesc": MessageLookupByLibrary.simpleMessage("查看当前连接数据"),
|
||||||
"connectivity": MessageLookupByLibrary.simpleMessage("连通性:"),
|
"connectivity": MessageLookupByLibrary.simpleMessage("连通性:"),
|
||||||
"copy": MessageLookupByLibrary.simpleMessage("复制"),
|
"copy": MessageLookupByLibrary.simpleMessage("复制"),
|
||||||
"core": MessageLookupByLibrary.simpleMessage("内核"),
|
"core": MessageLookupByLibrary.simpleMessage("内核"),
|
||||||
@@ -206,7 +206,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"recoveryProfiles": MessageLookupByLibrary.simpleMessage("仅恢复配置文件"),
|
"recoveryProfiles": MessageLookupByLibrary.simpleMessage("仅恢复配置文件"),
|
||||||
"recoverySuccess": MessageLookupByLibrary.simpleMessage("恢复成功"),
|
"recoverySuccess": MessageLookupByLibrary.simpleMessage("恢复成功"),
|
||||||
"requests": MessageLookupByLibrary.simpleMessage("请求"),
|
"requests": MessageLookupByLibrary.simpleMessage("请求"),
|
||||||
"requestsDesc": MessageLookupByLibrary.simpleMessage("查看最近请求数据"),
|
"requestsDesc": MessageLookupByLibrary.simpleMessage("查看最近请求记录"),
|
||||||
"resources": MessageLookupByLibrary.simpleMessage("资源"),
|
"resources": MessageLookupByLibrary.simpleMessage("资源"),
|
||||||
"resourcesDesc": MessageLookupByLibrary.simpleMessage("外部资源相关信息"),
|
"resourcesDesc": MessageLookupByLibrary.simpleMessage("外部资源相关信息"),
|
||||||
"rule": MessageLookupByLibrary.simpleMessage("规则"),
|
"rule": MessageLookupByLibrary.simpleMessage("规则"),
|
||||||
@@ -255,7 +255,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
"update": MessageLookupByLibrary.simpleMessage("更新"),
|
"update": MessageLookupByLibrary.simpleMessage("更新"),
|
||||||
"upload": MessageLookupByLibrary.simpleMessage("上传"),
|
"upload": MessageLookupByLibrary.simpleMessage("上传"),
|
||||||
"url": MessageLookupByLibrary.simpleMessage("URL"),
|
"url": MessageLookupByLibrary.simpleMessage("URL"),
|
||||||
"urlDesc": MessageLookupByLibrary.simpleMessage("直接上传配置文件"),
|
"urlDesc": MessageLookupByLibrary.simpleMessage("通过URL获取配置文件"),
|
||||||
"view": MessageLookupByLibrary.simpleMessage("查看"),
|
"view": MessageLookupByLibrary.simpleMessage("查看"),
|
||||||
"webDAVConfiguration": MessageLookupByLibrary.simpleMessage("WebDAV配置"),
|
"webDAVConfiguration": MessageLookupByLibrary.simpleMessage("WebDAV配置"),
|
||||||
"whitelistMode": MessageLookupByLibrary.simpleMessage("白名单模式"),
|
"whitelistMode": MessageLookupByLibrary.simpleMessage("白名单模式"),
|
||||||
|
|||||||
@@ -1830,10 +1830,10 @@ class AppLocalizations {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `View recently requested data`
|
/// `View recently request records`
|
||||||
String get requestsDesc {
|
String get requestsDesc {
|
||||||
return Intl.message(
|
return Intl.message(
|
||||||
'View recently requested data',
|
'View recently request records',
|
||||||
name: 'requestsDesc',
|
name: 'requestsDesc',
|
||||||
desc: '',
|
desc: '',
|
||||||
args: [],
|
args: [],
|
||||||
@@ -1900,10 +1900,10 @@ class AppLocalizations {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `View current connection`
|
/// `View current connections data`
|
||||||
String get connectionsDesc {
|
String get connectionsDesc {
|
||||||
return Intl.message(
|
return Intl.message(
|
||||||
'View current connection',
|
'View current connections data',
|
||||||
name: 'connectionsDesc',
|
name: 'connectionsDesc',
|
||||||
desc: '',
|
desc: '',
|
||||||
args: [],
|
args: [],
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ class Config extends ChangeNotifier {
|
|||||||
_isMinimizeOnExit = true,
|
_isMinimizeOnExit = true,
|
||||||
_isAccessControl = false,
|
_isAccessControl = false,
|
||||||
_autoCheckUpdate = true,
|
_autoCheckUpdate = true,
|
||||||
_systemProxy = true,
|
_systemProxy = false,
|
||||||
_testUrl = defaultTestUrl,
|
_testUrl = defaultTestUrl,
|
||||||
_accessControl = const AccessControl(),
|
_accessControl = const AccessControl(),
|
||||||
_isAnimateToPage = true,
|
_isAnimateToPage = true,
|
||||||
@@ -393,7 +393,7 @@ class Config extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(defaultValue: true)
|
@JsonKey(defaultValue: false)
|
||||||
bool get systemProxy {
|
bool get systemProxy {
|
||||||
return _systemProxy;
|
return _systemProxy;
|
||||||
}
|
}
|
||||||
@@ -498,7 +498,6 @@ class Config extends ChangeNotifier {
|
|||||||
_accessControl = config._accessControl;
|
_accessControl = config._accessControl;
|
||||||
_isAnimateToPage = config._isAnimateToPage;
|
_isAnimateToPage = config._isAnimateToPage;
|
||||||
_autoCheckUpdate = config._autoCheckUpdate;
|
_autoCheckUpdate = config._autoCheckUpdate;
|
||||||
_dav = config._dav;
|
|
||||||
_testUrl = config._testUrl;
|
_testUrl = config._testUrl;
|
||||||
_isExclude = config._isExclude;
|
_isExclude = config._isExclude;
|
||||||
_windowProps = config._windowProps;
|
_windowProps = config._windowProps;
|
||||||
|
|||||||
@@ -34,97 +34,110 @@ class HomePage extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
final extended = viewMode == ViewMode.desktop;
|
final extended = viewMode == ViewMode.desktop;
|
||||||
return NavigationRail(
|
// return NavigationRail(
|
||||||
backgroundColor: context.colorScheme.surfaceContainer,
|
// backgroundColor: context.colorScheme.surfaceContainer,
|
||||||
groupAlignment: -0.8,
|
// groupAlignment: -0.8,
|
||||||
destinations: navigationItems
|
// destinations: navigationItems
|
||||||
.map(
|
// .map(
|
||||||
(e) => NavigationRailDestination(
|
// (e) => NavigationRailDestination(
|
||||||
icon: e.icon,
|
// icon: e.icon,
|
||||||
label: Text(
|
// label: Text(
|
||||||
Intl.message(e.label),
|
// Intl.message(e.label),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
)
|
// )
|
||||||
.toList(),
|
// .toList(),
|
||||||
onDestinationSelected: globalState.appController.toPage,
|
// onDestinationSelected: globalState.appController.toPage,
|
||||||
extended: extended,
|
// extended: extended,
|
||||||
minExtendedWidth: 172,
|
// minExtendedWidth: 172,
|
||||||
selectedIndex: currentIndex,
|
// selectedIndex: currentIndex,
|
||||||
labelType: extended
|
// labelType: extended
|
||||||
? NavigationRailLabelType.none
|
// ? NavigationRailLabelType.none
|
||||||
: NavigationRailLabelType.selected,
|
// : NavigationRailLabelType.selected,
|
||||||
|
// );
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(16).copyWith(
|
||||||
|
right: 0,
|
||||||
|
),
|
||||||
|
color: context.colorScheme.surface,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
child: NavigationRail(
|
||||||
|
backgroundColor: context.colorScheme.surfaceContainer,
|
||||||
|
groupAlignment: -0.8,
|
||||||
|
destinations: navigationItems
|
||||||
|
.map(
|
||||||
|
(e) => NavigationRailDestination(
|
||||||
|
icon: e.icon,
|
||||||
|
label: Text(
|
||||||
|
Intl.message(e.label),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
onDestinationSelected: globalState.appController.toPage,
|
||||||
|
extended: extended,
|
||||||
|
minExtendedWidth: 172,
|
||||||
|
selectedIndex: currentIndex,
|
||||||
|
labelType: extended
|
||||||
|
? NavigationRailLabelType.none
|
||||||
|
: NavigationRailLabelType.selected,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return PopContainer(
|
return LayoutBuilder(
|
||||||
child: LayoutBuilder(
|
builder: (_, container) {
|
||||||
builder: (_, container) {
|
final appController = globalState.appController;
|
||||||
final appController = globalState.appController;
|
final maxWidth = container.maxWidth;
|
||||||
final maxWidth = container.maxWidth;
|
if (appController.appState.viewWidth != maxWidth) {
|
||||||
if (appController.appState.viewWidth != maxWidth) {
|
globalState.appController.updateViewWidth(maxWidth);
|
||||||
globalState.appController.updateViewWidth(maxWidth);
|
}
|
||||||
}
|
return Selector2<AppState, Config, HomeSelectorState>(
|
||||||
return Selector2<AppState, Config, HomeSelectorState>(
|
selector: (_, appState, config) {
|
||||||
selector: (_, appState, config) {
|
return HomeSelectorState(
|
||||||
return HomeSelectorState(
|
currentLabel: appState.currentLabel,
|
||||||
currentLabel: appState.currentLabel,
|
navigationItems: appState.currentNavigationItems,
|
||||||
navigationItems: appState.currentNavigationItems,
|
viewMode: other.getViewMode(maxWidth),
|
||||||
viewMode: other.getViewMode(maxWidth),
|
locale: config.locale,
|
||||||
locale: config.locale,
|
);
|
||||||
);
|
},
|
||||||
},
|
builder: (_, state, child) {
|
||||||
builder: (_, state, child) {
|
final viewMode = state.viewMode;
|
||||||
final viewMode = state.viewMode;
|
final navigationItems = state.navigationItems;
|
||||||
final navigationItems = state.navigationItems;
|
final currentLabel = state.currentLabel;
|
||||||
final currentLabel = state.currentLabel;
|
final index = navigationItems.lastIndexWhere(
|
||||||
final index = navigationItems.lastIndexWhere(
|
(element) => element.label == currentLabel,
|
||||||
(element) => element.label == currentLabel,
|
);
|
||||||
);
|
final currentIndex = index == -1 ? 0 : index;
|
||||||
final currentIndex = index == -1 ? 0 : index;
|
final navigationBar = _getNavigationBar(
|
||||||
final navigationBar = _getNavigationBar(
|
context: context,
|
||||||
context: context,
|
viewMode: viewMode,
|
||||||
viewMode: viewMode,
|
navigationItems: navigationItems,
|
||||||
navigationItems: navigationItems,
|
currentIndex: currentIndex,
|
||||||
currentIndex: currentIndex,
|
);
|
||||||
);
|
final bottomNavigationBar =
|
||||||
final bottomNavigationBar =
|
viewMode == ViewMode.mobile ? navigationBar : null;
|
||||||
viewMode == ViewMode.mobile ? navigationBar : null;
|
final sideNavigationBar =
|
||||||
if (viewMode != ViewMode.mobile) {
|
viewMode != ViewMode.mobile ? navigationBar : null;
|
||||||
return Row(
|
return CommonScaffold(
|
||||||
children: [
|
key: globalState.homeScaffoldKey,
|
||||||
navigationBar,
|
title: Intl.message(
|
||||||
Expanded(
|
currentLabel,
|
||||||
flex: 1,
|
),
|
||||||
child: CommonScaffold(
|
sideNavigationBar: sideNavigationBar,
|
||||||
key: globalState.homeScaffoldKey,
|
body: child!,
|
||||||
title: Intl.message(
|
bottomNavigationBar: bottomNavigationBar,
|
||||||
currentLabel,
|
);
|
||||||
),
|
},
|
||||||
body: child!,
|
child: const HomeBody(
|
||||||
bottomNavigationBar: bottomNavigationBar,
|
key: Key("home_boy"),
|
||||||
),
|
),
|
||||||
)
|
);
|
||||||
],
|
},
|
||||||
);
|
|
||||||
}
|
|
||||||
return CommonScaffold(
|
|
||||||
key: globalState.homeScaffoldKey,
|
|
||||||
title: Intl.message(
|
|
||||||
currentLabel,
|
|
||||||
),
|
|
||||||
body: child!,
|
|
||||||
bottomNavigationBar: bottomNavigationBar,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: const HomeBody(
|
|
||||||
key: Key("home_boy"),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ class AndroidContainer extends StatefulWidget {
|
|||||||
|
|
||||||
class _AndroidContainerState extends State<AndroidContainer>
|
class _AndroidContainerState extends State<AndroidContainer>
|
||||||
with WidgetsBindingObserver {
|
with WidgetsBindingObserver {
|
||||||
|
Widget _excludeContainer(Widget child) {
|
||||||
_excludeContainer(Widget child) {
|
|
||||||
return Selector<Config, bool>(
|
return Selector<Config, bool>(
|
||||||
selector: (_, config) => config.isExclude,
|
selector: (_, config) => config.isExclude,
|
||||||
builder: (_, isExclude, child) {
|
builder: (_, isExclude, child) {
|
||||||
@@ -31,6 +30,20 @@ class _AndroidContainerState extends State<AndroidContainer>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _systemUiOverlayContainer(Widget child) {
|
||||||
|
return AnnotatedRegion(
|
||||||
|
value: SystemUiOverlayStyle(
|
||||||
|
statusBarColor: Colors.transparent,
|
||||||
|
statusBarIconBrightness: Theme.of(context).brightness == Brightness.dark
|
||||||
|
? Brightness.light
|
||||||
|
: Brightness.dark,
|
||||||
|
systemNavigationBarColor: Colors.transparent,
|
||||||
|
systemNavigationBarDividerColor: Colors.transparent,
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -48,7 +61,9 @@ class _AndroidContainerState extends State<AndroidContainer>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return _excludeContainer(widget.child);
|
return _systemUiOverlayContainer(
|
||||||
|
_excludeContainer(widget.child),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'package:fl_clash/models/models.dart';
|
|||||||
import 'package:fl_clash/plugins/proxy.dart';
|
import 'package:fl_clash/plugins/proxy.dart';
|
||||||
import 'package:fl_clash/state.dart';
|
import 'package:fl_clash/state.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:fl_clash/plugins/app.dart';
|
|
||||||
|
|
||||||
class ClashMessageContainer extends StatefulWidget {
|
class ClashMessageContainer extends StatefulWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:fl_clash/common/app_localizations.dart';
|
import 'package:fl_clash/common/common.dart';
|
||||||
import 'package:fl_clash/common/system.dart';
|
|
||||||
import 'package:fl_clash/state.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';
|
||||||
@@ -7,6 +6,7 @@ import 'package:flutter/services.dart';
|
|||||||
class CommonScaffold extends StatefulWidget {
|
class CommonScaffold extends StatefulWidget {
|
||||||
final Widget body;
|
final Widget body;
|
||||||
final Widget? bottomNavigationBar;
|
final Widget? bottomNavigationBar;
|
||||||
|
final Widget? sideNavigationBar;
|
||||||
final String title;
|
final String title;
|
||||||
final Widget? leading;
|
final Widget? leading;
|
||||||
final List<Widget>? actions;
|
final List<Widget>? actions;
|
||||||
@@ -15,6 +15,7 @@ class CommonScaffold extends StatefulWidget {
|
|||||||
const CommonScaffold({
|
const CommonScaffold({
|
||||||
super.key,
|
super.key,
|
||||||
required this.body,
|
required this.body,
|
||||||
|
this.sideNavigationBar,
|
||||||
this.bottomNavigationBar,
|
this.bottomNavigationBar,
|
||||||
this.leading,
|
this.leading,
|
||||||
required this.title,
|
required this.title,
|
||||||
@@ -92,85 +93,66 @@ class CommonScaffoldState extends State<CommonScaffold> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_platformContainer({required Widget child}) {
|
Widget? get _sideNavigationBar => widget.sideNavigationBar;
|
||||||
if (system.isDesktop) {
|
|
||||||
return child;
|
|
||||||
}
|
|
||||||
return AnnotatedRegion(
|
|
||||||
value: SystemUiOverlayStyle(
|
|
||||||
statusBarColor: Colors.transparent,
|
|
||||||
statusBarIconBrightness: Theme.of(context).brightness == Brightness.dark
|
|
||||||
? Brightness.light
|
|
||||||
: Brightness.dark,
|
|
||||||
systemNavigationBarColor: Colors.transparent,
|
|
||||||
systemNavigationBarDividerColor: Colors.transparent,
|
|
||||||
),
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return _platformContainer(
|
final scaffold = Scaffold(
|
||||||
child: Scaffold(
|
resizeToAvoidBottomInset: true,
|
||||||
resizeToAvoidBottomInset: true,
|
appBar: PreferredSize(
|
||||||
appBar: PreferredSize(
|
preferredSize: const Size.fromHeight(kToolbarHeight),
|
||||||
preferredSize: const Size.fromHeight(kToolbarHeight),
|
child: Stack(
|
||||||
child: Stack(
|
alignment: Alignment.bottomCenter,
|
||||||
alignment: Alignment.bottomCenter,
|
children: [
|
||||||
|
ValueListenableBuilder<List<Widget>>(
|
||||||
|
valueListenable: _actions,
|
||||||
|
builder: (_, actions, __) {
|
||||||
|
final realActions =
|
||||||
|
actions.isNotEmpty ? actions : widget.actions;
|
||||||
|
return AppBar(
|
||||||
|
centerTitle: false,
|
||||||
|
automaticallyImplyLeading: widget.automaticallyImplyLeading,
|
||||||
|
leading: widget.leading,
|
||||||
|
title: Text(widget.title),
|
||||||
|
actions: [
|
||||||
|
...?realActions,
|
||||||
|
const SizedBox(
|
||||||
|
width: 8,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ValueListenableBuilder(
|
||||||
|
valueListenable: _loading,
|
||||||
|
builder: (_, value, __) {
|
||||||
|
return value == true
|
||||||
|
? const LinearProgressIndicator()
|
||||||
|
: Container();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: widget.body,
|
||||||
|
bottomNavigationBar: widget.bottomNavigationBar,
|
||||||
|
);
|
||||||
|
return _sideNavigationBar != null
|
||||||
|
? Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
ValueListenableBuilder<List<Widget>>(
|
_sideNavigationBar!,
|
||||||
valueListenable: _actions,
|
Expanded(
|
||||||
builder: (_, actions, __) {
|
flex: 1,
|
||||||
final realActions =
|
child: Material(
|
||||||
actions.isNotEmpty ? actions : widget.actions;
|
child: Container(
|
||||||
return AppBar(
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
automaticallyImplyLeading: widget.automaticallyImplyLeading,
|
child: scaffold,
|
||||||
leading: widget.leading,
|
),
|
||||||
title: Text(widget.title),
|
),
|
||||||
actions: [
|
|
||||||
...?realActions,
|
|
||||||
const SizedBox(
|
|
||||||
width: 8,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ValueListenableBuilder(
|
|
||||||
valueListenable: _loading,
|
|
||||||
builder: (_, value, __) {
|
|
||||||
return value == true
|
|
||||||
? const LinearProgressIndicator()
|
|
||||||
: Container();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
)
|
||||||
),
|
: scaffold;
|
||||||
body: widget.body,
|
|
||||||
bottomNavigationBar: widget.bottomNavigationBar,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AppIcon extends StatelessWidget {
|
|
||||||
const AppIcon({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
margin: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 16,
|
|
||||||
vertical: 16,
|
|
||||||
),
|
|
||||||
width: 30,
|
|
||||||
height: 30,
|
|
||||||
child: const CircleAvatar(
|
|
||||||
foregroundImage: AssetImage("assets/images/launch_icon.png"),
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,20 +28,39 @@ showExtendPage(
|
|||||||
builder: (_, viewWidth, __) {
|
builder: (_, viewWidth, __) {
|
||||||
final isMobile =
|
final isMobile =
|
||||||
globalState.appController.appState.viewMode == ViewMode.mobile;
|
globalState.appController.appState.viewMode == ViewMode.mobile;
|
||||||
final commonScaffold = CommonScaffold(
|
final commonScaffold = isMobile
|
||||||
automaticallyImplyLeading: isMobile ? true : false,
|
? CommonScaffold(
|
||||||
actions: isMobile
|
actions: isMobile
|
||||||
? null
|
? null
|
||||||
: [
|
: [
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: kToolbarHeight,
|
height: kToolbarHeight,
|
||||||
width: kToolbarHeight,
|
width: kToolbarHeight,
|
||||||
child: CloseButton(),
|
child: CloseButton(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
title: title,
|
||||||
|
body: uniqueBody,
|
||||||
|
)
|
||||||
|
: Column(
|
||||||
|
children: [
|
||||||
|
AppBar(
|
||||||
|
automaticallyImplyLeading: false,
|
||||||
|
title: Text(title),
|
||||||
|
actions: const [
|
||||||
|
SizedBox(
|
||||||
|
height: kToolbarHeight,
|
||||||
|
width: kToolbarHeight,
|
||||||
|
child: CloseButton(),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: uniqueBody,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
title: title,
|
);
|
||||||
body: uniqueBody,
|
|
||||||
);
|
|
||||||
return AnimatedContainer(
|
return AnimatedContainer(
|
||||||
duration: kThemeAnimationDuration,
|
duration: kThemeAnimationDuration,
|
||||||
width: isMobile ? viewWidth : extendPageWidth ?? 300,
|
width: isMobile ? viewWidth : extendPageWidth ?? 300,
|
||||||
|
|||||||
@@ -85,10 +85,7 @@ class _SideSheetState extends State<SideSheet> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final BoxConstraints constraints = widget.constraints ??
|
final BoxConstraints constraints = widget.constraints ??
|
||||||
const BoxConstraints(
|
const BoxConstraints(maxWidth: 320, minWidth: 320);
|
||||||
maxWidth: 320,
|
|
||||||
minWidth: 320
|
|
||||||
);
|
|
||||||
|
|
||||||
final Clip clipBehavior = widget.clipBehavior ?? Clip.none;
|
final Clip clipBehavior = widget.clipBehavior ?? Clip.none;
|
||||||
|
|
||||||
@@ -594,31 +591,16 @@ Future<T?> showModalSideSheet<T>({
|
|||||||
builder: (context) {
|
builder: (context) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
Flexible(
|
AppBar(
|
||||||
flex: 0,
|
automaticallyImplyLeading: false,
|
||||||
child: Row(
|
title: Text(title),
|
||||||
children: [
|
actions: const [
|
||||||
const SizedBox(
|
SizedBox(
|
||||||
height: kToolbarHeight,
|
height: kToolbarHeight,
|
||||||
width: kToolbarHeight,
|
width: kToolbarHeight,
|
||||||
child: BackButton(),
|
child: CloseButton(),
|
||||||
),
|
)
|
||||||
const SizedBox(
|
],
|
||||||
width: 8,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
title,
|
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: kToolbarHeight,
|
|
||||||
width: kToolbarHeight,
|
|
||||||
child: CloseButton(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
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';
|
||||||
@@ -31,7 +33,22 @@ class _WindowContainerState extends State<WindowContainer> with WindowListener {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return _autoLaunchContainer(widget.child);
|
return Stack(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: kHeaderHeight,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: _autoLaunchContainer(widget.child),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const WindowHeader(),
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -80,3 +97,171 @@ class _WindowContainerState extends State<WindowContainer> with WindowListener {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class WindowHeader extends StatefulWidget {
|
||||||
|
const WindowHeader({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<WindowHeader> createState() => _WindowHeaderState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WindowHeaderState extends State<WindowHeader> {
|
||||||
|
final isMaximizedNotifier = ValueNotifier<bool>(false);
|
||||||
|
final isPinNotifier = ValueNotifier<bool>(false);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_initNotifier();
|
||||||
|
}
|
||||||
|
|
||||||
|
_initNotifier() async {
|
||||||
|
isMaximizedNotifier.value = await windowManager.isMaximized();
|
||||||
|
isPinNotifier.value = await windowManager.isAlwaysOnTop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
isMaximizedNotifier.dispose();
|
||||||
|
isPinNotifier.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateMaximized() {
|
||||||
|
isMaximizedNotifier.value = !isMaximizedNotifier.value;
|
||||||
|
switch (isMaximizedNotifier.value) {
|
||||||
|
case true:
|
||||||
|
windowManager.maximize();
|
||||||
|
case false:
|
||||||
|
windowManager.unmaximize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_updatePin() {
|
||||||
|
isPinNotifier.value = !isPinNotifier.value;
|
||||||
|
windowManager.setAlwaysOnTop(isPinNotifier.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
_buildActions() {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
_updatePin();
|
||||||
|
},
|
||||||
|
icon: ValueListenableBuilder(
|
||||||
|
valueListenable: isPinNotifier,
|
||||||
|
builder: (_, value, ___) {
|
||||||
|
return value
|
||||||
|
? const Icon(
|
||||||
|
Icons.push_pin,
|
||||||
|
)
|
||||||
|
: const Icon(
|
||||||
|
Icons.push_pin_outlined,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
windowManager.minimize();
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.remove),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
_updateMaximized();
|
||||||
|
},
|
||||||
|
icon: ValueListenableBuilder(
|
||||||
|
valueListenable: isMaximizedNotifier,
|
||||||
|
builder: (_, value, ___) {
|
||||||
|
return value
|
||||||
|
? const Icon(
|
||||||
|
Icons.filter_none,
|
||||||
|
size: 20,
|
||||||
|
)
|
||||||
|
: const Icon(
|
||||||
|
Icons.crop_square,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
windowManager.close();
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
),
|
||||||
|
// const SizedBox(
|
||||||
|
// width: 8,
|
||||||
|
// ),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Material(
|
||||||
|
child: Stack(
|
||||||
|
alignment: AlignmentDirectional.center,
|
||||||
|
children: [
|
||||||
|
Positioned(
|
||||||
|
child: GestureDetector(
|
||||||
|
behavior: HitTestBehavior.translucent,
|
||||||
|
onPanStart: (_) {
|
||||||
|
windowManager.startDragging();
|
||||||
|
},
|
||||||
|
onDoubleTap: () {
|
||||||
|
_updateMaximized();
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
color: context.colorScheme.surfaceContainerHigh,
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
height: kHeaderHeight,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (!Platform.isMacOS) ...[
|
||||||
|
const Positioned(
|
||||||
|
left: 0,
|
||||||
|
child: AppIcon(),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
right: 0,
|
||||||
|
child: _buildActions(),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppIcon extends StatelessWidget {
|
||||||
|
const AppIcon({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.only(left: 8),
|
||||||
|
child: const Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 28,
|
||||||
|
height: 28,
|
||||||
|
child: CircleAvatar(
|
||||||
|
foregroundImage: AssetImage("assets/images/launch_icon.png"),
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 8,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
appName,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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.44+202407183
|
version: 0.8.45+202407201
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.1.0 <4.0.0'
|
sdk: '>=3.1.0 <4.0.0'
|
||||||
|
|
||||||
|
|||||||