Compare commits

...

1 Commits

Author SHA1 Message Date
chen08209
f679d1e6d8 Optimize request ua
Change android icon

Optimize dashboard
2024-07-20 18:04:59 +08:00
52 changed files with 573 additions and 304 deletions

View File

@@ -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">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -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 ===>")
} }
} }
} }

View File

@@ -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() {

View File

@@ -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

View File

@@ -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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 763 B

After

Width:  |  Height:  |  Size: 532 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 520 B

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 932 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

View 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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

View File

@@ -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>

View File

@@ -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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 812 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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(),
), ),
), ),
); );

View File

@@ -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",

View File

@@ -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";
}; };

View File

@@ -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);
}); });

View File

@@ -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(),
), ),
], ],
); );

View File

@@ -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",

View File

@@ -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",

View File

@@ -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"),

View File

@@ -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("白名单模式"),

View File

@@ -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: [],

View File

@@ -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;

View File

@@ -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"),
),
);
},
),
); );
} }
} }

View File

@@ -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

View File

@@ -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;

View File

@@ -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,
),
);
} }
} }

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,
),
],
),
);
}
}

View File

@@ -1,7 +1,7 @@
name: fl_clash name: fl_clash
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free. description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
publish_to: 'none' publish_to: 'none'
version: 0.8.44+202407183 version: 0.8.45+202407201
environment: environment:
sdk: '>=3.1.0 <4.0.0' sdk: '>=3.1.0 <4.0.0'