Compare commits
1 Commits
v0.8.82-pr
...
v0.8.45
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f679d1e6d8 |
@@ -24,8 +24,9 @@
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:extractNativeLibs="true"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:label="FlClash"
|
||||
tools:targetApi="n">
|
||||
tools:targetApi="tiramisu">
|
||||
<activity
|
||||
android:name="com.follow.clash.MainActivity"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
@@ -74,7 +75,7 @@
|
||||
<service
|
||||
android:name=".services.FlClashTileService"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/icon"
|
||||
android:icon="@drawable/ic_stat_name"
|
||||
android:foregroundServiceType="specialUse"
|
||||
android:label="FlClash"
|
||||
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
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.follow.clash.plugins.AppPlugin
|
||||
import com.follow.clash.plugins.ProxyPlugin
|
||||
@@ -56,6 +57,8 @@ object GlobalState {
|
||||
serviceEngine?.dartExecutor?.executeDartEntrypoint(
|
||||
vpnService,
|
||||
)
|
||||
|
||||
Log.e("FlClashVpnService", "initServiceEngine ===>")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.content.pm.PackageManager
|
||||
import android.net.VpnService
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.follow.clash.GlobalState
|
||||
@@ -131,7 +132,13 @@ class ProxyPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAwar
|
||||
}
|
||||
if (GlobalState.runState.value == RunState.START) return
|
||||
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() {
|
||||
|
||||
@@ -13,6 +13,7 @@ import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.Parcel
|
||||
import android.os.RemoteException
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.follow.clash.GlobalState
|
||||
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"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_foreground" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_foreground" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</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:windowLayoutInDisplayCutoutMode">shortEdges</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>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -6,7 +6,7 @@
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</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>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#EFEFEF</color>
|
||||
<color name="ic_launcher_background">#FAFAFA</color>
|
||||
</resources>
|
||||
@@ -137,56 +137,59 @@ class ApplicationState extends State<Application> {
|
||||
Widget build(context) {
|
||||
return AppStateContainer(
|
||||
child: ClashMessageContainer(
|
||||
child: _buildApp(
|
||||
Selector2<AppState, Config, ApplicationSelectorState>(
|
||||
selector: (_, appState, config) => ApplicationSelectorState(
|
||||
locale: config.locale,
|
||||
themeMode: config.themeMode,
|
||||
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(),
|
||||
child: Selector2<AppState, Config, ApplicationSelectorState>(
|
||||
selector: (_, appState, config) => ApplicationSelectorState(
|
||||
locale: config.locale,
|
||||
themeMode: config.themeMode,
|
||||
primaryColor: config.primaryColor,
|
||||
),
|
||||
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 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/clash_config.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'system.dart';
|
||||
|
||||
const appName = "FlClash";
|
||||
const coreName = "clash.meta";
|
||||
@@ -15,6 +17,7 @@ const mmdbFileName = "geoip.metadb";
|
||||
const asnFileName = "ASN.mmdb";
|
||||
const geoIpFileName = "GeoIP.dat";
|
||||
const geoSiteFileName = "GeoSite.dat";
|
||||
final double kHeaderHeight = system.isDesktop ? (Platform.isMacOS ? 28 : 40) : 0;
|
||||
const GeoXMap defaultGeoXMap = {
|
||||
"mmdb":
|
||||
"https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb",
|
||||
|
||||
@@ -36,6 +36,7 @@ class Request {
|
||||
createHttpClient: () {
|
||||
final client = HttpClient();
|
||||
if (!_isStart) return client;
|
||||
client.userAgent = globalState.appController.clashConfig.globalUa;
|
||||
client.findProxy = (url) {
|
||||
return "PROXY localhost:$_port;DIRECT";
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@ class Window {
|
||||
WindowOptions windowOptions = WindowOptions(
|
||||
size: Size(props.width, props.height),
|
||||
minimumSize: const Size(380, 600),
|
||||
titleBarStyle: TitleBarStyle.hidden,
|
||||
);
|
||||
if (props.left != null || props.top != null) {
|
||||
await windowManager.setPosition(
|
||||
@@ -28,6 +29,9 @@ class Window {
|
||||
} else {
|
||||
await windowManager.setAlignment(Alignment.center);
|
||||
}
|
||||
// if(Platform.isWindows){
|
||||
// await windowManager.setTitleBarStyle(TitleBarStyle.hidden);
|
||||
// }
|
||||
await windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||
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/models/models.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'network_detection.dart';
|
||||
import 'outbound_mode.dart';
|
||||
import 'start_button.dart';
|
||||
@@ -29,34 +29,35 @@ class _DashboardFragmentState extends State<DashboardFragment> {
|
||||
alignment: Alignment.topCenter,
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Selector<AppState, ViewMode>(
|
||||
selector: (_, appState) => appState.viewMode,
|
||||
builder: (_, viewMode, ___) {
|
||||
final isDesktop = viewMode == ViewMode.desktop;
|
||||
child: Selector<AppState, double>(
|
||||
selector: (_, appState) => appState.viewWidth,
|
||||
builder: (_, viewWidth, ___) {
|
||||
// final viewMode = other.getViewMode(viewWidth);
|
||||
// final isDesktop = viewMode == ViewMode.desktop;
|
||||
return Grid(
|
||||
crossAxisCount: 12,
|
||||
crossAxisCount: max(4 * ((viewWidth / 320).ceil()), 8),
|
||||
crossAxisSpacing: 16,
|
||||
mainAxisSpacing: 16,
|
||||
children: [
|
||||
children: const [
|
||||
GridItem(
|
||||
crossAxisCellCount: isDesktop ? 8 : 12,
|
||||
child: const NetworkSpeed(),
|
||||
crossAxisCellCount: 8,
|
||||
child: NetworkSpeed(),
|
||||
),
|
||||
GridItem(
|
||||
crossAxisCellCount: isDesktop ? 4 : 6,
|
||||
child: const OutboundMode(),
|
||||
crossAxisCellCount: 4,
|
||||
child: OutboundMode(),
|
||||
),
|
||||
GridItem(
|
||||
crossAxisCellCount: isDesktop ? 4 : 6,
|
||||
child: const NetworkDetection(),
|
||||
crossAxisCellCount: 4,
|
||||
child: NetworkDetection(),
|
||||
),
|
||||
GridItem(
|
||||
crossAxisCellCount: isDesktop ? 4 : 6,
|
||||
child: const TrafficUsage(),
|
||||
crossAxisCellCount: 4,
|
||||
child: TrafficUsage(),
|
||||
),
|
||||
GridItem(
|
||||
crossAxisCellCount: isDesktop ? 4 : 6,
|
||||
child: const IntranetIP(),
|
||||
crossAxisCellCount: 4,
|
||||
child: IntranetIP(),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -177,14 +177,14 @@
|
||||
"geodataLoader": "Geo Low Memory Mode",
|
||||
"geodataLoaderDesc": "Enabling will use the Geo low memory loader",
|
||||
"requests": "Requests",
|
||||
"requestsDesc": "View recently requested data",
|
||||
"requestsDesc": "View recently request records",
|
||||
"findProcessMode": "Find process",
|
||||
"findProcessModeDesc": "There is a risk of flashback after opening",
|
||||
"init": "Init",
|
||||
"infiniteTime": "Long term effective",
|
||||
"expirationTime": "Expiration time",
|
||||
"connections": "Connections",
|
||||
"connectionsDesc": "View current connection",
|
||||
"connectionsDesc": "View current connections data",
|
||||
"nullRequestsDesc": "No requests",
|
||||
"nullConnectionsDesc": "No connections",
|
||||
"intranetIP": "Intranet IP",
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
"qrcode": "二维码",
|
||||
"qrcodeDesc": "扫描二维码获取配置文件",
|
||||
"url": "URL",
|
||||
"urlDesc": "直接上传配置文件",
|
||||
"urlDesc": "通过URL获取配置文件",
|
||||
"file": "文件",
|
||||
"fileDesc": "直接上传配置文件",
|
||||
"name": "名称",
|
||||
@@ -177,14 +177,14 @@
|
||||
"geodataLoader": "Geo低内存模式",
|
||||
"geodataLoaderDesc": "开启将使用Geo低内存加载器",
|
||||
"requests": "请求",
|
||||
"requestsDesc": "查看最近请求数据",
|
||||
"requestsDesc": "查看最近请求记录",
|
||||
"findProcessMode": "查找进程",
|
||||
"findProcessModeDesc": "开启后存在闪退风险",
|
||||
"init": "初始化",
|
||||
"infiniteTime": "长期有效",
|
||||
"expirationTime": "到期时间",
|
||||
"connections": "连接",
|
||||
"connectionsDesc": "查看当前连接",
|
||||
"connectionsDesc": "查看当前连接数据",
|
||||
"nullRequestsDesc": "暂无请求",
|
||||
"nullConnectionsDesc": "暂无连接",
|
||||
"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."),
|
||||
"confirm": MessageLookupByLibrary.simpleMessage("Confirm"),
|
||||
"connections": MessageLookupByLibrary.simpleMessage("Connections"),
|
||||
"connectionsDesc":
|
||||
MessageLookupByLibrary.simpleMessage("View current connection"),
|
||||
"connectionsDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"View current connections data"),
|
||||
"connectivity": MessageLookupByLibrary.simpleMessage("Connectivity:"),
|
||||
"copy": MessageLookupByLibrary.simpleMessage("Copy"),
|
||||
"core": MessageLookupByLibrary.simpleMessage("Core"),
|
||||
@@ -257,7 +257,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Recovery success"),
|
||||
"requests": MessageLookupByLibrary.simpleMessage("Requests"),
|
||||
"requestsDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"View recently requested data"),
|
||||
"View recently request records"),
|
||||
"resources": MessageLookupByLibrary.simpleMessage("Resources"),
|
||||
"resourcesDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"External resource related info"),
|
||||
|
||||
@@ -77,7 +77,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("开启将失去部分应用能力,获得全量的Clash的支持"),
|
||||
"confirm": MessageLookupByLibrary.simpleMessage("确定"),
|
||||
"connections": MessageLookupByLibrary.simpleMessage("连接"),
|
||||
"connectionsDesc": MessageLookupByLibrary.simpleMessage("查看当前连接"),
|
||||
"connectionsDesc": MessageLookupByLibrary.simpleMessage("查看当前连接数据"),
|
||||
"connectivity": MessageLookupByLibrary.simpleMessage("连通性:"),
|
||||
"copy": MessageLookupByLibrary.simpleMessage("复制"),
|
||||
"core": MessageLookupByLibrary.simpleMessage("内核"),
|
||||
@@ -206,7 +206,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"recoveryProfiles": MessageLookupByLibrary.simpleMessage("仅恢复配置文件"),
|
||||
"recoverySuccess": MessageLookupByLibrary.simpleMessage("恢复成功"),
|
||||
"requests": MessageLookupByLibrary.simpleMessage("请求"),
|
||||
"requestsDesc": MessageLookupByLibrary.simpleMessage("查看最近请求数据"),
|
||||
"requestsDesc": MessageLookupByLibrary.simpleMessage("查看最近请求记录"),
|
||||
"resources": MessageLookupByLibrary.simpleMessage("资源"),
|
||||
"resourcesDesc": MessageLookupByLibrary.simpleMessage("外部资源相关信息"),
|
||||
"rule": MessageLookupByLibrary.simpleMessage("规则"),
|
||||
@@ -255,7 +255,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"update": MessageLookupByLibrary.simpleMessage("更新"),
|
||||
"upload": MessageLookupByLibrary.simpleMessage("上传"),
|
||||
"url": MessageLookupByLibrary.simpleMessage("URL"),
|
||||
"urlDesc": MessageLookupByLibrary.simpleMessage("直接上传配置文件"),
|
||||
"urlDesc": MessageLookupByLibrary.simpleMessage("通过URL获取配置文件"),
|
||||
"view": MessageLookupByLibrary.simpleMessage("查看"),
|
||||
"webDAVConfiguration": MessageLookupByLibrary.simpleMessage("WebDAV配置"),
|
||||
"whitelistMode": MessageLookupByLibrary.simpleMessage("白名单模式"),
|
||||
|
||||
@@ -1830,10 +1830,10 @@ class AppLocalizations {
|
||||
);
|
||||
}
|
||||
|
||||
/// `View recently requested data`
|
||||
/// `View recently request records`
|
||||
String get requestsDesc {
|
||||
return Intl.message(
|
||||
'View recently requested data',
|
||||
'View recently request records',
|
||||
name: 'requestsDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
@@ -1900,10 +1900,10 @@ class AppLocalizations {
|
||||
);
|
||||
}
|
||||
|
||||
/// `View current connection`
|
||||
/// `View current connections data`
|
||||
String get connectionsDesc {
|
||||
return Intl.message(
|
||||
'View current connection',
|
||||
'View current connections data',
|
||||
name: 'connectionsDesc',
|
||||
desc: '',
|
||||
args: [],
|
||||
|
||||
@@ -92,7 +92,7 @@ class Config extends ChangeNotifier {
|
||||
_isMinimizeOnExit = true,
|
||||
_isAccessControl = false,
|
||||
_autoCheckUpdate = true,
|
||||
_systemProxy = true,
|
||||
_systemProxy = false,
|
||||
_testUrl = defaultTestUrl,
|
||||
_accessControl = const AccessControl(),
|
||||
_isAnimateToPage = true,
|
||||
@@ -393,7 +393,7 @@ class Config extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
@JsonKey(defaultValue: true)
|
||||
@JsonKey(defaultValue: false)
|
||||
bool get systemProxy {
|
||||
return _systemProxy;
|
||||
}
|
||||
@@ -498,7 +498,6 @@ class Config extends ChangeNotifier {
|
||||
_accessControl = config._accessControl;
|
||||
_isAnimateToPage = config._isAnimateToPage;
|
||||
_autoCheckUpdate = config._autoCheckUpdate;
|
||||
_dav = config._dav;
|
||||
_testUrl = config._testUrl;
|
||||
_isExclude = config._isExclude;
|
||||
_windowProps = config._windowProps;
|
||||
|
||||
@@ -34,97 +34,110 @@ class HomePage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
final extended = viewMode == ViewMode.desktop;
|
||||
return 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,
|
||||
// return 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,
|
||||
// );
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return PopContainer(
|
||||
child: LayoutBuilder(
|
||||
builder: (_, container) {
|
||||
final appController = globalState.appController;
|
||||
final maxWidth = container.maxWidth;
|
||||
if (appController.appState.viewWidth != maxWidth) {
|
||||
globalState.appController.updateViewWidth(maxWidth);
|
||||
}
|
||||
return Selector2<AppState, Config, HomeSelectorState>(
|
||||
selector: (_, appState, config) {
|
||||
return HomeSelectorState(
|
||||
currentLabel: appState.currentLabel,
|
||||
navigationItems: appState.currentNavigationItems,
|
||||
viewMode: other.getViewMode(maxWidth),
|
||||
locale: config.locale,
|
||||
);
|
||||
},
|
||||
builder: (_, state, child) {
|
||||
final viewMode = state.viewMode;
|
||||
final navigationItems = state.navigationItems;
|
||||
final currentLabel = state.currentLabel;
|
||||
final index = navigationItems.lastIndexWhere(
|
||||
(element) => element.label == currentLabel,
|
||||
);
|
||||
final currentIndex = index == -1 ? 0 : index;
|
||||
final navigationBar = _getNavigationBar(
|
||||
context: context,
|
||||
viewMode: viewMode,
|
||||
navigationItems: navigationItems,
|
||||
currentIndex: currentIndex,
|
||||
);
|
||||
final bottomNavigationBar =
|
||||
viewMode == ViewMode.mobile ? navigationBar : null;
|
||||
if (viewMode != ViewMode.mobile) {
|
||||
return Row(
|
||||
children: [
|
||||
navigationBar,
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: CommonScaffold(
|
||||
key: globalState.homeScaffoldKey,
|
||||
title: Intl.message(
|
||||
currentLabel,
|
||||
),
|
||||
body: child!,
|
||||
bottomNavigationBar: bottomNavigationBar,
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
return CommonScaffold(
|
||||
key: globalState.homeScaffoldKey,
|
||||
title: Intl.message(
|
||||
currentLabel,
|
||||
),
|
||||
body: child!,
|
||||
bottomNavigationBar: bottomNavigationBar,
|
||||
);
|
||||
},
|
||||
child: const HomeBody(
|
||||
key: Key("home_boy"),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
return LayoutBuilder(
|
||||
builder: (_, container) {
|
||||
final appController = globalState.appController;
|
||||
final maxWidth = container.maxWidth;
|
||||
if (appController.appState.viewWidth != maxWidth) {
|
||||
globalState.appController.updateViewWidth(maxWidth);
|
||||
}
|
||||
return Selector2<AppState, Config, HomeSelectorState>(
|
||||
selector: (_, appState, config) {
|
||||
return HomeSelectorState(
|
||||
currentLabel: appState.currentLabel,
|
||||
navigationItems: appState.currentNavigationItems,
|
||||
viewMode: other.getViewMode(maxWidth),
|
||||
locale: config.locale,
|
||||
);
|
||||
},
|
||||
builder: (_, state, child) {
|
||||
final viewMode = state.viewMode;
|
||||
final navigationItems = state.navigationItems;
|
||||
final currentLabel = state.currentLabel;
|
||||
final index = navigationItems.lastIndexWhere(
|
||||
(element) => element.label == currentLabel,
|
||||
);
|
||||
final currentIndex = index == -1 ? 0 : index;
|
||||
final navigationBar = _getNavigationBar(
|
||||
context: context,
|
||||
viewMode: viewMode,
|
||||
navigationItems: navigationItems,
|
||||
currentIndex: currentIndex,
|
||||
);
|
||||
final bottomNavigationBar =
|
||||
viewMode == ViewMode.mobile ? navigationBar : null;
|
||||
final sideNavigationBar =
|
||||
viewMode != ViewMode.mobile ? navigationBar : null;
|
||||
return CommonScaffold(
|
||||
key: globalState.homeScaffoldKey,
|
||||
title: Intl.message(
|
||||
currentLabel,
|
||||
),
|
||||
sideNavigationBar: sideNavigationBar,
|
||||
body: child!,
|
||||
bottomNavigationBar: bottomNavigationBar,
|
||||
);
|
||||
},
|
||||
child: const HomeBody(
|
||||
key: Key("home_boy"),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,7 @@ class AndroidContainer extends StatefulWidget {
|
||||
|
||||
class _AndroidContainerState extends State<AndroidContainer>
|
||||
with WidgetsBindingObserver {
|
||||
|
||||
_excludeContainer(Widget child) {
|
||||
Widget _excludeContainer(Widget child) {
|
||||
return Selector<Config, bool>(
|
||||
selector: (_, config) => config.isExclude,
|
||||
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
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -48,7 +61,9 @@ class _AndroidContainerState extends State<AndroidContainer>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _excludeContainer(widget.child);
|
||||
return _systemUiOverlayContainer(
|
||||
_excludeContainer(widget.child),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/plugins/proxy.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fl_clash/plugins/app.dart';
|
||||
|
||||
class ClashMessageContainer extends StatefulWidget {
|
||||
final Widget child;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:fl_clash/common/app_localizations.dart';
|
||||
import 'package:fl_clash/common/system.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -7,6 +6,7 @@ import 'package:flutter/services.dart';
|
||||
class CommonScaffold extends StatefulWidget {
|
||||
final Widget body;
|
||||
final Widget? bottomNavigationBar;
|
||||
final Widget? sideNavigationBar;
|
||||
final String title;
|
||||
final Widget? leading;
|
||||
final List<Widget>? actions;
|
||||
@@ -15,6 +15,7 @@ class CommonScaffold extends StatefulWidget {
|
||||
const CommonScaffold({
|
||||
super.key,
|
||||
required this.body,
|
||||
this.sideNavigationBar,
|
||||
this.bottomNavigationBar,
|
||||
this.leading,
|
||||
required this.title,
|
||||
@@ -92,85 +93,66 @@ class CommonScaffoldState extends State<CommonScaffold> {
|
||||
}
|
||||
}
|
||||
|
||||
_platformContainer({required Widget child}) {
|
||||
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,
|
||||
);
|
||||
}
|
||||
Widget? get _sideNavigationBar => widget.sideNavigationBar;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _platformContainer(
|
||||
child: Scaffold(
|
||||
resizeToAvoidBottomInset: true,
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(kToolbarHeight),
|
||||
child: Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
final scaffold = Scaffold(
|
||||
resizeToAvoidBottomInset: true,
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(kToolbarHeight),
|
||||
child: Stack(
|
||||
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: [
|
||||
ValueListenableBuilder<List<Widget>>(
|
||||
valueListenable: _actions,
|
||||
builder: (_, actions, __) {
|
||||
final realActions =
|
||||
actions.isNotEmpty ? actions : widget.actions;
|
||||
return AppBar(
|
||||
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();
|
||||
},
|
||||
_sideNavigationBar!,
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Material(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: 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,
|
||||
),
|
||||
);
|
||||
)
|
||||
: scaffold;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,20 +28,39 @@ showExtendPage(
|
||||
builder: (_, viewWidth, __) {
|
||||
final isMobile =
|
||||
globalState.appController.appState.viewMode == ViewMode.mobile;
|
||||
final commonScaffold = CommonScaffold(
|
||||
automaticallyImplyLeading: isMobile ? true : false,
|
||||
actions: isMobile
|
||||
? null
|
||||
: [
|
||||
const SizedBox(
|
||||
height: kToolbarHeight,
|
||||
width: kToolbarHeight,
|
||||
child: CloseButton(),
|
||||
final commonScaffold = isMobile
|
||||
? CommonScaffold(
|
||||
actions: isMobile
|
||||
? null
|
||||
: [
|
||||
const SizedBox(
|
||||
height: kToolbarHeight,
|
||||
width: kToolbarHeight,
|
||||
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(
|
||||
duration: kThemeAnimationDuration,
|
||||
width: isMobile ? viewWidth : extendPageWidth ?? 300,
|
||||
|
||||
@@ -85,10 +85,7 @@ class _SideSheetState extends State<SideSheet> {
|
||||
);
|
||||
|
||||
final BoxConstraints constraints = widget.constraints ??
|
||||
const BoxConstraints(
|
||||
maxWidth: 320,
|
||||
minWidth: 320
|
||||
);
|
||||
const BoxConstraints(maxWidth: 320, minWidth: 320);
|
||||
|
||||
final Clip clipBehavior = widget.clipBehavior ?? Clip.none;
|
||||
|
||||
@@ -594,31 +591,16 @@ Future<T?> showModalSideSheet<T>({
|
||||
builder: (context) {
|
||||
return Column(
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 0,
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: kToolbarHeight,
|
||||
width: kToolbarHeight,
|
||||
child: BackButton(),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: kToolbarHeight,
|
||||
width: kToolbarHeight,
|
||||
child: CloseButton(),
|
||||
),
|
||||
],
|
||||
),
|
||||
AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
title: Text(title),
|
||||
actions: const [
|
||||
SizedBox(
|
||||
height: kToolbarHeight,
|
||||
width: kToolbarHeight,
|
||||
child: CloseButton(),
|
||||
)
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
@@ -31,7 +33,22 @@ class _WindowContainerState extends State<WindowContainer> with WindowListener {
|
||||
|
||||
@override
|
||||
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
|
||||
@@ -80,3 +97,171 @@ class _WindowContainerState extends State<WindowContainer> with WindowListener {
|
||||
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
|
||||
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
|
||||
publish_to: 'none'
|
||||
version: 0.8.44+202407183
|
||||
version: 0.8.45+202407201
|
||||
environment:
|
||||
sdk: '>=3.1.0 <4.0.0'
|
||||
|
||||
|
||||