diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index ddf3e77..ca5d76c 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -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"> diff --git a/android/app/src/main/ic_launcher-playstore.png b/android/app/src/main/ic_launcher-playstore.png index a1b0d62..dcedc44 100644 Binary files a/android/app/src/main/ic_launcher-playstore.png and b/android/app/src/main/ic_launcher-playstore.png differ diff --git a/android/app/src/main/kotlin/com/follow/clash/GlobalState.kt b/android/app/src/main/kotlin/com/follow/clash/GlobalState.kt index 30197e0..9da48ce 100644 --- a/android/app/src/main/kotlin/com/follow/clash/GlobalState.kt +++ b/android/app/src/main/kotlin/com/follow/clash/GlobalState.kt @@ -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 ===>") } } } diff --git a/android/app/src/main/kotlin/com/follow/clash/plugins/ProxyPlugin.kt b/android/app/src/main/kotlin/com/follow/clash/plugins/ProxyPlugin.kt index b630173..4667522 100644 --- a/android/app/src/main/kotlin/com/follow/clash/plugins/ProxyPlugin.kt +++ b/android/app/src/main/kotlin/com/follow/clash/plugins/ProxyPlugin.kt @@ -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() { diff --git a/android/app/src/main/kotlin/com/follow/clash/services/FlClashVpnService.kt b/android/app/src/main/kotlin/com/follow/clash/services/FlClashVpnService.kt index b304cf7..f5d1480 100644 --- a/android/app/src/main/kotlin/com/follow/clash/services/FlClashVpnService.kt +++ b/android/app/src/main/kotlin/com/follow/clash/services/FlClashVpnService.kt @@ -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 diff --git a/android/app/src/main/res/drawable-anydpi-v24/ic_stat_name.xml b/android/app/src/main/res/drawable-anydpi-v24/ic_stat_name.xml new file mode 100644 index 0000000..b341caf --- /dev/null +++ b/android/app/src/main/res/drawable-anydpi-v24/ic_stat_name.xml @@ -0,0 +1,26 @@ + + + + + + + + + diff --git a/android/app/src/main/res/drawable-hdpi/ic_stat_name.png b/android/app/src/main/res/drawable-hdpi/ic_stat_name.png index 8f86e06..f4ac6c6 100644 Binary files a/android/app/src/main/res/drawable-hdpi/ic_stat_name.png and b/android/app/src/main/res/drawable-hdpi/ic_stat_name.png differ diff --git a/android/app/src/main/res/drawable-mdpi/ic_stat_name.png b/android/app/src/main/res/drawable-mdpi/ic_stat_name.png index bfe8796..61efe33 100644 Binary files a/android/app/src/main/res/drawable-mdpi/ic_stat_name.png and b/android/app/src/main/res/drawable-mdpi/ic_stat_name.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/ic_stat_name.png b/android/app/src/main/res/drawable-xhdpi/ic_stat_name.png index e5f302b..08077fd 100644 Binary files a/android/app/src/main/res/drawable-xhdpi/ic_stat_name.png and b/android/app/src/main/res/drawable-xhdpi/ic_stat_name.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_stat_name.png b/android/app/src/main/res/drawable-xxhdpi/ic_stat_name.png index 51712e9..70cecb6 100644 Binary files a/android/app/src/main/res/drawable-xxhdpi/ic_stat_name.png and b/android/app/src/main/res/drawable-xxhdpi/ic_stat_name.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_stat_name.png b/android/app/src/main/res/drawable-xxxhdpi/ic_stat_name.png deleted file mode 100644 index 24c5846..0000000 Binary files a/android/app/src/main/res/drawable-xxxhdpi/ic_stat_name.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/ic_launcher_foreground.xml b/android/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..e57794c --- /dev/null +++ b/android/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/android/app/src/main/res/drawable/icon.png b/android/app/src/main/res/drawable/icon.png deleted file mode 100644 index 86b78b6..0000000 Binary files a/android/app/src/main/res/drawable/icon.png and /dev/null differ diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index e202e4b..7005cb8 100644 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,6 +1,6 @@ - - + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index e202e4b..7005cb8 100644 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,6 +1,6 @@ - - + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp index c86b31e..064e864 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp deleted file mode 100644 index 286417f..0000000 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp index 20bcbd4..49c42a2 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp index fcdf2a8..f41a9c6 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp deleted file mode 100644 index fd40daf..0000000 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp index ab2adf1..92abe54 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp index 2aa4570..cadc811 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp deleted file mode 100644 index ac146f9..0000000 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp index dc27b6b..f000960 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp index d933821..c2e97a2 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp deleted file mode 100644 index 8f7d717..0000000 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp index 0d7cf0d..e043fa5 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp index 4f88b23..befaabd 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp deleted file mode 100644 index 62216a3..0000000 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp index 185c77b..522d4c9 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/values-night-v27/styles.xml b/android/app/src/main/res/values-night-v27/styles.xml index 50802ba..b3f7858 100644 --- a/android/app/src/main/res/values-night-v27/styles.xml +++ b/android/app/src/main/res/values-night-v27/styles.xml @@ -6,7 +6,7 @@ false shortEdges #121212 - @mipmap/ic_launcher_foreground + @drawable/ic_launcher_foreground @style/NormalTheme \ No newline at end of file diff --git a/android/app/src/main/res/values-v27/styles.xml b/android/app/src/main/res/values-v27/styles.xml index f690aeb..f389f4f 100644 --- a/android/app/src/main/res/values-v27/styles.xml +++ b/android/app/src/main/res/values-v27/styles.xml @@ -6,7 +6,7 @@ false shortEdges @color/ic_launcher_background - @mipmap/ic_launcher_foreground + @drawable/ic_launcher_foreground @style/NormalTheme \ No newline at end of file diff --git a/android/app/src/main/res/values/ic_launcher_background.xml b/android/app/src/main/res/values/ic_launcher_background.xml index d37e588..10b6831 100644 --- a/android/app/src/main/res/values/ic_launcher_background.xml +++ b/android/app/src/main/res/values/ic_launcher_background.xml @@ -1,4 +1,4 @@ - #EFEFEF + #FAFAFA \ No newline at end of file diff --git a/lib/application.dart b/lib/application.dart index d0a088f..bd395ce 100644 --- a/lib/application.dart +++ b/lib/application.dart @@ -137,56 +137,59 @@ class ApplicationState extends State { Widget build(context) { return AppStateContainer( child: ClashMessageContainer( - child: _buildApp( - Selector2( - 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( + 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(), ), ), ); diff --git a/lib/common/constant.dart b/lib/common/constant.dart index 18798b2..f46946e 100644 --- a/lib/common/constant.dart +++ b/lib/common/constant.dart @@ -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", diff --git a/lib/common/request.dart b/lib/common/request.dart index 7c62f89..5ae7b20 100644 --- a/lib/common/request.dart +++ b/lib/common/request.dart @@ -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"; }; diff --git a/lib/common/window.dart b/lib/common/window.dart index dff6507..9e038c6 100644 --- a/lib/common/window.dart +++ b/lib/common/window.dart @@ -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); }); diff --git a/lib/fragments/dashboard/dashboard.dart b/lib/fragments/dashboard/dashboard.dart index e775411..0e3fe22 100644 --- a/lib/fragments/dashboard/dashboard.dart +++ b/lib/fragments/dashboard/dashboard.dart @@ -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 { alignment: Alignment.topCenter, child: SingleChildScrollView( padding: const EdgeInsets.all(16), - child: Selector( - selector: (_, appState) => appState.viewMode, - builder: (_, viewMode, ___) { - final isDesktop = viewMode == ViewMode.desktop; + child: Selector( + 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(), ), ], ); diff --git a/lib/l10n/arb/intl_en.arb b/lib/l10n/arb/intl_en.arb index a6820c9..6b9acd9 100644 --- a/lib/l10n/arb/intl_en.arb +++ b/lib/l10n/arb/intl_en.arb @@ -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", diff --git a/lib/l10n/arb/intl_zh_CN.arb b/lib/l10n/arb/intl_zh_CN.arb index 2f37792..a24bc87 100644 --- a/lib/l10n/arb/intl_zh_CN.arb +++ b/lib/l10n/arb/intl_zh_CN.arb @@ -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", diff --git a/lib/l10n/intl/messages_en.dart b/lib/l10n/intl/messages_en.dart index ba04783..d966643 100644 --- a/lib/l10n/intl/messages_en.dart +++ b/lib/l10n/intl/messages_en.dart @@ -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"), diff --git a/lib/l10n/intl/messages_zh_CN.dart b/lib/l10n/intl/messages_zh_CN.dart index b40a44a..0fd7455 100644 --- a/lib/l10n/intl/messages_zh_CN.dart +++ b/lib/l10n/intl/messages_zh_CN.dart @@ -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("白名单模式"), diff --git a/lib/l10n/l10n.dart b/lib/l10n/l10n.dart index 04e315d..fce2d6b 100644 --- a/lib/l10n/l10n.dart +++ b/lib/l10n/l10n.dart @@ -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: [], diff --git a/lib/models/config.dart b/lib/models/config.dart index 09b5e42..d489aae 100644 --- a/lib/models/config.dart +++ b/lib/models/config.dart @@ -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; diff --git a/lib/pages/home.dart b/lib/pages/home.dart index ef656e6..0c96a4b 100644 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -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( - 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( + 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"), + ), + ); + }, ); } } diff --git a/lib/widgets/android_container.dart b/lib/widgets/android_container.dart index 48f8057..0111af9 100644 --- a/lib/widgets/android_container.dart +++ b/lib/widgets/android_container.dart @@ -19,8 +19,7 @@ class AndroidContainer extends StatefulWidget { class _AndroidContainerState extends State with WidgetsBindingObserver { - - _excludeContainer(Widget child) { + Widget _excludeContainer(Widget child) { return Selector( selector: (_, config) => config.isExclude, builder: (_, isExclude, child) { @@ -31,6 +30,20 @@ class _AndroidContainerState extends State ); } + 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 @override Widget build(BuildContext context) { - return _excludeContainer(widget.child); + return _systemUiOverlayContainer( + _excludeContainer(widget.child), + ); } @override diff --git a/lib/widgets/clash_message_container.dart b/lib/widgets/clash_message_container.dart index 17bf474..cd366ec 100644 --- a/lib/widgets/clash_message_container.dart +++ b/lib/widgets/clash_message_container.dart @@ -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; diff --git a/lib/widgets/scaffold.dart b/lib/widgets/scaffold.dart index 50e6971..d01b987 100644 --- a/lib/widgets/scaffold.dart +++ b/lib/widgets/scaffold.dart @@ -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? 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 { } } - _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>( + 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>( - 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; } } diff --git a/lib/widgets/sheet.dart b/lib/widgets/sheet.dart index dee4e52..323da2d 100644 --- a/lib/widgets/sheet.dart +++ b/lib/widgets/sheet.dart @@ -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, diff --git a/lib/widgets/side_sheet.dart b/lib/widgets/side_sheet.dart index e243280..8b792c2 100644 --- a/lib/widgets/side_sheet.dart +++ b/lib/widgets/side_sheet.dart @@ -85,10 +85,7 @@ class _SideSheetState extends State { ); 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 showModalSideSheet({ 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, diff --git a/lib/widgets/window_container.dart b/lib/widgets/window_container.dart index 9ea82f6..387ee58 100644 --- a/lib/widgets/window_container.dart +++ b/lib/widgets/window_container.dart @@ -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 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 with WindowListener { super.dispose(); } } + +class WindowHeader extends StatefulWidget { + const WindowHeader({super.key}); + + @override + State createState() => _WindowHeaderState(); +} + +class _WindowHeaderState extends State { + final isMaximizedNotifier = ValueNotifier(false); + final isPinNotifier = ValueNotifier(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, + ), + ], + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 1a3089d..cf29d58 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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'