Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
530817b268 | ||
|
|
721dd20251 |
@@ -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: 12 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
|
||||
|
||||
|
Before Width: | Height: | Size: 763 B After Width: | Height: | Size: 618 B |
|
Before Width: | Height: | Size: 520 B After Width: | Height: | Size: 423 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 803 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.6 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.924"
|
||||
android:scaleY="0.924"
|
||||
android:translateX="9.12"
|
||||
android:translateY="9.12">
|
||||
<group android:scaleX="0.63461536"
|
||||
android:scaleY="0.63461536"
|
||||
android:translateX="45.96154"
|
||||
android:translateY="43.846153">
|
||||
<path
|
||||
android:pathData="M60.65,89.6L154.18,35.6A18,18 107.59,0 1,178.77 42.19L178.77,42.19A18,18 107.59,0 1,172.18 66.78L78.65,120.78A18,18 106.67,0 1,54.06 114.19L54.06,114.19A18,18 106.67,0 1,60.65 89.6z"
|
||||
android:fillColor="#6666FB"/>
|
||||
<path
|
||||
android:pathData="M84.65,131.17L131.42,104.17A18,18 107.83,0 1,156 110.76L156,110.76A18,18 107.83,0 1,149.42 135.35L102.65,162.35A18,18 106.67,0 1,78.06 155.76L78.06,155.76A18,18 106.67,0 1,84.65 131.17z"
|
||||
android:fillColor="#336AB6"/>
|
||||
<path
|
||||
android:pathData="M108.65,172.74L108.65,172.74A18,18 116.03,0 1,133.24 179.33L133.24,179.33A18,18 116.03,0 1,126.65 203.92L126.65,203.92A18,18 116.03,0 1,102.06 197.33L102.06,197.33A18,18 116.03,0 1,108.65 172.74z"
|
||||
android:fillColor="#5CA8E9"/>
|
||||
</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.3 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 886 B |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 2.8 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>
|
||||
|
Before Width: | Height: | Size: 9.4 KiB |
BIN
assets/images/avatars/arue.jpg
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
assets/images/avatars/june2.jpg
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
assets/images/icon.ico
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
assets/images/icon.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
assets/images/icon_monochrome.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 118 KiB |
@@ -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",
|
||||
|
||||
@@ -101,9 +101,9 @@ class Other {
|
||||
|
||||
String getTrayIconPath() {
|
||||
if (Platform.isWindows) {
|
||||
return "assets/images/app_icon.ico";
|
||||
return "assets/images/icon.ico";
|
||||
} else {
|
||||
return "assets/images/launch_icon.png";
|
||||
return "assets/images/icon_monochrome.png";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,16 +1,29 @@
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/list.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
@immutable
|
||||
class Contributor {
|
||||
final String avatar;
|
||||
final String name;
|
||||
final String link;
|
||||
|
||||
const Contributor({
|
||||
required this.avatar,
|
||||
required this.name,
|
||||
required this.link,
|
||||
});
|
||||
}
|
||||
|
||||
class AboutFragment extends StatelessWidget {
|
||||
const AboutFragment({super.key});
|
||||
|
||||
_checkUpdate(BuildContext context) async {
|
||||
final commonScaffoldState = context.commonScaffoldState;
|
||||
if (commonScaffoldState?.mounted != true) return;
|
||||
final data =
|
||||
await commonScaffoldState?.loadingRun<Map<String, dynamic>?>(
|
||||
final data = await commonScaffoldState?.loadingRun<Map<String, dynamic>?>(
|
||||
request.checkForUpdate,
|
||||
title: appLocalizations.checkUpdate,
|
||||
);
|
||||
@@ -20,84 +33,40 @@ class AboutFragment extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView(
|
||||
padding: kMaterialListPadding.copyWith(
|
||||
top: 16,
|
||||
bottom: 16,
|
||||
),
|
||||
children: [
|
||||
ListTile(
|
||||
title: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Wrap(
|
||||
spacing: 16,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/images/launch_icon.png',
|
||||
width: 100,
|
||||
height: 100,
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
appName,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
Text(
|
||||
globalState.packageInfo.version,
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
Text(
|
||||
appLocalizations.desc,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
ListTile(
|
||||
List<Widget> _buildMoreSection(BuildContext context) {
|
||||
return generateSection(
|
||||
separated: false,
|
||||
title: appLocalizations.more,
|
||||
items: [
|
||||
ListItem(
|
||||
title: Text(appLocalizations.checkUpdate),
|
||||
onTap: () {
|
||||
_checkUpdate(context);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
ListItem(
|
||||
title: const Text("Telegram"),
|
||||
onTap: () {
|
||||
launchUrl(
|
||||
Uri.parse("https://t.me/+G-veVtwBOl4wODc1"),
|
||||
globalState.openUrl(
|
||||
"https://t.me/+G-veVtwBOl4wODc1",
|
||||
);
|
||||
},
|
||||
trailing: const Icon(Icons.launch),
|
||||
),
|
||||
ListTile(
|
||||
ListItem(
|
||||
title: Text(appLocalizations.project),
|
||||
onTap: () {
|
||||
launchUrl(
|
||||
Uri.parse("https://github.com/$repository"),
|
||||
globalState.openUrl(
|
||||
"https://github.com/$repository",
|
||||
);
|
||||
},
|
||||
trailing: const Icon(Icons.launch),
|
||||
),
|
||||
ListTile(
|
||||
ListItem(
|
||||
title: Text(appLocalizations.core),
|
||||
onTap: () {
|
||||
launchUrl(
|
||||
Uri.parse("https://github.com/chen08209/Clash.Meta/tree/FlClash"),
|
||||
globalState.openUrl(
|
||||
"https://github.com/chen08209/Clash.Meta/tree/FlClash",
|
||||
);
|
||||
},
|
||||
trailing: const Icon(Icons.launch),
|
||||
@@ -105,4 +74,139 @@ class AboutFragment extends StatelessWidget {
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildContributorsSection() {
|
||||
const contributors = [
|
||||
Contributor(
|
||||
avatar: "assets/images/avatars/june2.jpg",
|
||||
name: "June2",
|
||||
link: "https://t.me/Jibadong",
|
||||
),
|
||||
Contributor(
|
||||
avatar: "assets/images/avatars/arue.jpg",
|
||||
name: "Arue",
|
||||
link: "https://t.me/xrcm6868",
|
||||
),
|
||||
];
|
||||
return generateSection(
|
||||
separated: false,
|
||||
title: appLocalizations.contributors,
|
||||
items: [
|
||||
ListItem(
|
||||
title: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Wrap(
|
||||
spacing: 24,
|
||||
children: [
|
||||
for (final contributor in contributors)
|
||||
Avatar(
|
||||
contributor: contributor,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final items = [
|
||||
ListTile(
|
||||
title: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Wrap(
|
||||
spacing: 16,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Image.asset(
|
||||
'assets/images/icon.png',
|
||||
width: 64,
|
||||
height: 64,
|
||||
),
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
appName,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
Text(
|
||||
globalState.packageInfo.version,
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
Text(
|
||||
appLocalizations.desc,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
..._buildContributorsSection(),
|
||||
..._buildMoreSection(context),
|
||||
];
|
||||
return Padding(
|
||||
padding: kMaterialListPadding.copyWith(
|
||||
top: 16,
|
||||
bottom: 16,
|
||||
),
|
||||
child: generateListView(items),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Avatar extends StatelessWidget {
|
||||
final Contributor contributor;
|
||||
|
||||
const Avatar({
|
||||
super.key,
|
||||
required this.contributor,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
splashColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
hoverColor: Colors.transparent,
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 36,
|
||||
height: 36,
|
||||
child: CircleAvatar(
|
||||
foregroundImage: AssetImage(
|
||||
contributor.avatar,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Text(
|
||||
contributor.name,
|
||||
style: context.textTheme.bodySmall,
|
||||
)
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
globalState.openUrl(contributor.link);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,8 +113,7 @@ class _BackupAndRecoveryState extends State<BackupAndRecovery> {
|
||||
return Center(
|
||||
child: FadeBox(
|
||||
key: const Key("fade_box_1"),
|
||||
child: snapshot.connectionState ==
|
||||
ConnectionState.waiting
|
||||
child: snapshot.connectionState == ConnectionState.waiting
|
||||
? const SizedBox(
|
||||
width: 12,
|
||||
height: 12,
|
||||
@@ -159,12 +158,12 @@ class _BackupAndRecoveryState extends State<BackupAndRecovery> {
|
||||
ListHeader(
|
||||
title: appLocalizations.backupAndRecovery),
|
||||
ListItem(
|
||||
onTab: _backup,
|
||||
onTap: _backup,
|
||||
title: Text(appLocalizations.backup),
|
||||
subtitle: Text(appLocalizations.backupDesc),
|
||||
),
|
||||
ListItem(
|
||||
onTab: _handleRecovery,
|
||||
onTap: _handleRecovery,
|
||||
title: Text(appLocalizations.recovery),
|
||||
subtitle: Text(appLocalizations.recoveryDesc),
|
||||
),
|
||||
@@ -341,13 +340,13 @@ class _RecoveryOptionsDialogState extends State<RecoveryOptionsDialog> {
|
||||
child: Wrap(
|
||||
children: [
|
||||
ListItem(
|
||||
onTab: () {
|
||||
onTap: () {
|
||||
_handleOnTab(RecoveryOption.onlyProfiles);
|
||||
},
|
||||
title: Text(appLocalizations.recoveryProfiles),
|
||||
),
|
||||
ListItem(
|
||||
onTab: () {
|
||||
onTap: () {
|
||||
_handleOnTab(RecoveryOption.all);
|
||||
},
|
||||
title: Text(appLocalizations.recoveryAll),
|
||||
|
||||
@@ -210,7 +210,7 @@ class _ConfigFragmentState extends State<ConfigFragment> {
|
||||
leading: const Icon(Icons.info_outline),
|
||||
title: Text(appLocalizations.logLevel),
|
||||
subtitle: Text(value.name),
|
||||
onTab: () {
|
||||
onTap: () {
|
||||
_showLogLevelDialog(value);
|
||||
},
|
||||
);
|
||||
@@ -223,7 +223,7 @@ class _ConfigFragmentState extends State<ConfigFragment> {
|
||||
leading: const Icon(Icons.computer_outlined),
|
||||
title: const Text("UA"),
|
||||
subtitle: Text(value ?? appLocalizations.defaultText),
|
||||
onTab: () {
|
||||
onTap: () {
|
||||
_showUaDialog(value);
|
||||
},
|
||||
);
|
||||
@@ -236,7 +236,7 @@ class _ConfigFragmentState extends State<ConfigFragment> {
|
||||
leading: const Icon(Icons.timeline),
|
||||
title: Text(appLocalizations.testUrl),
|
||||
subtitle: Text(value),
|
||||
onTab: () {
|
||||
onTap: () {
|
||||
_modifyTestUrl(value);
|
||||
},
|
||||
);
|
||||
@@ -246,7 +246,7 @@ class _ConfigFragmentState extends State<ConfigFragment> {
|
||||
selector: (_, clashConfig) => clashConfig.mixedPort,
|
||||
builder: (_, mixedPort, __) {
|
||||
return ListItem(
|
||||
onTab: () {
|
||||
onTap: () {
|
||||
_modifyMixedPort(mixedPort);
|
||||
},
|
||||
leading: const Icon(Icons.adjust_outlined),
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -48,19 +48,19 @@ class AddProfile extends StatelessWidget {
|
||||
leading: const Icon(Icons.qr_code),
|
||||
title: Text(appLocalizations.qrcode),
|
||||
subtitle: Text(appLocalizations.qrcodeDesc),
|
||||
onTab: _toScan,
|
||||
onTap: _toScan,
|
||||
),
|
||||
ListItem(
|
||||
leading: const Icon(Icons.upload_file),
|
||||
title: Text(appLocalizations.file),
|
||||
subtitle: Text(appLocalizations.fileDesc),
|
||||
onTab: _handleAddProfileFormFile,
|
||||
onTap: _handleAddProfileFormFile,
|
||||
),
|
||||
ListItem(
|
||||
leading: const Icon(Icons.cloud_download),
|
||||
title: Text(appLocalizations.url),
|
||||
subtitle: Text(appLocalizations.urlDesc),
|
||||
onTab: _toAdd,
|
||||
onTap: _toAdd,
|
||||
)
|
||||
],
|
||||
);
|
||||
|
||||
@@ -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",
|
||||
@@ -211,5 +211,8 @@
|
||||
"sort": "Sort",
|
||||
"columns": "Columns",
|
||||
"proxiesSetting": "Proxies setting",
|
||||
"proxyGroup": "Proxy group"
|
||||
"proxyGroup": "Proxy group",
|
||||
"go": "Go",
|
||||
"externalLink": "External link",
|
||||
"contributors": "Contributors"
|
||||
}
|
||||
@@ -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",
|
||||
@@ -211,5 +211,8 @@
|
||||
"sort": "排序",
|
||||
"columns": "列数",
|
||||
"proxiesSetting": "代理设置",
|
||||
"proxyGroup": "代理组"
|
||||
"proxyGroup": "代理组",
|
||||
"go": "前往",
|
||||
"externalLink": "外部链接",
|
||||
"contributors": "贡献者"
|
||||
}
|
||||
@@ -94,9 +94,10 @@ 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:"),
|
||||
"contributors": MessageLookupByLibrary.simpleMessage("Contributors"),
|
||||
"copy": MessageLookupByLibrary.simpleMessage("Copy"),
|
||||
"core": MessageLookupByLibrary.simpleMessage("Core"),
|
||||
"coreInfo": MessageLookupByLibrary.simpleMessage("Core info"),
|
||||
@@ -135,6 +136,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("ExternalController"),
|
||||
"externalControllerDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Once enabled, the Clash kernel can be controlled on port 9090"),
|
||||
"externalLink": MessageLookupByLibrary.simpleMessage("External link"),
|
||||
"externalResources":
|
||||
MessageLookupByLibrary.simpleMessage("External resources"),
|
||||
"file": MessageLookupByLibrary.simpleMessage("File"),
|
||||
@@ -153,6 +155,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"geodataLoaderDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Enabling will use the Geo low memory loader"),
|
||||
"global": MessageLookupByLibrary.simpleMessage("Global"),
|
||||
"go": MessageLookupByLibrary.simpleMessage("Go"),
|
||||
"goDownload": MessageLookupByLibrary.simpleMessage("Go to download"),
|
||||
"hours": MessageLookupByLibrary.simpleMessage("Hours"),
|
||||
"importFromURL":
|
||||
@@ -257,7 +260,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,8 +77,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("开启将失去部分应用能力,获得全量的Clash的支持"),
|
||||
"confirm": MessageLookupByLibrary.simpleMessage("确定"),
|
||||
"connections": MessageLookupByLibrary.simpleMessage("连接"),
|
||||
"connectionsDesc": MessageLookupByLibrary.simpleMessage("查看当前连接"),
|
||||
"connectionsDesc": MessageLookupByLibrary.simpleMessage("查看当前连接数据"),
|
||||
"connectivity": MessageLookupByLibrary.simpleMessage("连通性:"),
|
||||
"contributors": MessageLookupByLibrary.simpleMessage("贡献者"),
|
||||
"copy": MessageLookupByLibrary.simpleMessage("复制"),
|
||||
"core": MessageLookupByLibrary.simpleMessage("内核"),
|
||||
"coreInfo": MessageLookupByLibrary.simpleMessage("内核信息"),
|
||||
@@ -111,6 +112,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"externalController": MessageLookupByLibrary.simpleMessage("外部控制器"),
|
||||
"externalControllerDesc":
|
||||
MessageLookupByLibrary.simpleMessage("开启后将可以通过9090端口控制Clash内核"),
|
||||
"externalLink": MessageLookupByLibrary.simpleMessage("外部链接"),
|
||||
"externalResources": MessageLookupByLibrary.simpleMessage("外部资源"),
|
||||
"file": MessageLookupByLibrary.simpleMessage("文件"),
|
||||
"fileDesc": MessageLookupByLibrary.simpleMessage("直接上传配置文件"),
|
||||
@@ -125,6 +127,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"geodataLoaderDesc":
|
||||
MessageLookupByLibrary.simpleMessage("开启将使用Geo低内存加载器"),
|
||||
"global": MessageLookupByLibrary.simpleMessage("全局"),
|
||||
"go": MessageLookupByLibrary.simpleMessage("前往"),
|
||||
"goDownload": MessageLookupByLibrary.simpleMessage("前往下载"),
|
||||
"hours": MessageLookupByLibrary.simpleMessage("小时"),
|
||||
"importFromURL": MessageLookupByLibrary.simpleMessage("从URL导入"),
|
||||
@@ -206,7 +209,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 +258,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: [],
|
||||
@@ -2179,6 +2179,36 @@ class AppLocalizations {
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Go`
|
||||
String get go {
|
||||
return Intl.message(
|
||||
'Go',
|
||||
name: 'go',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `External link`
|
||||
String get externalLink {
|
||||
return Intl.message(
|
||||
'External link',
|
||||
name: 'externalLink',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Contributors`
|
||||
String get contributors {
|
||||
return Intl.message(
|
||||
'Contributors',
|
||||
name: 'contributors',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -13,6 +13,20 @@ typedef OnSelected = void Function(int index);
|
||||
class HomePage extends StatelessWidget {
|
||||
const HomePage({super.key});
|
||||
|
||||
_navigationBarContainer({
|
||||
required BuildContext context,
|
||||
required Widget child,
|
||||
}) {
|
||||
// if (!system.isDesktop) return child;
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16).copyWith(
|
||||
right: 0,
|
||||
),
|
||||
color: context.colorScheme.surface,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
_getNavigationBar({
|
||||
required BuildContext context,
|
||||
required ViewMode viewMode,
|
||||
@@ -34,9 +48,31 @@ class HomePage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
final extended = viewMode == ViewMode.desktop;
|
||||
return _navigationBarContainer(
|
||||
context: context,
|
||||
child: NavigationRail(
|
||||
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,
|
||||
groupAlignment: -0.95,
|
||||
destinations: navigationItems
|
||||
.map(
|
||||
(e) => NavigationRailDestination(
|
||||
@@ -59,72 +95,55 @@ class HomePage extends StatelessWidget {
|
||||
|
||||
@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"),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:fl_clash/plugins/proxy.dart';
|
||||
import 'package:fl_clash/widgets/scaffold.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'controller.dart';
|
||||
|
||||
@@ -123,7 +124,7 @@ class GlobalState {
|
||||
}) async {
|
||||
appState.isInit = clashCore.isInit;
|
||||
if (!appState.isInit) {
|
||||
if(Platform.isAndroid){
|
||||
if (Platform.isAndroid) {
|
||||
clashCore.setProps(
|
||||
Props(
|
||||
accessControl: config.isAccessControl ? config.accessControl : null,
|
||||
@@ -261,6 +262,17 @@ class GlobalState {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
openUrl(String url) {
|
||||
showMessage(
|
||||
message: TextSpan(text: url),
|
||||
title: appLocalizations.externalLink,
|
||||
confirmText: appLocalizations.go,
|
||||
onTab: () {
|
||||
launchUrl(Uri.parse(url));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final globalState = GlobalState();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -78,7 +78,7 @@ class ListItem<T> extends StatelessWidget {
|
||||
final Widget? trailing;
|
||||
final Delegate delegate;
|
||||
final double? horizontalTitleGap;
|
||||
final void Function()? onTab;
|
||||
final void Function()? onTap;
|
||||
|
||||
const ListItem({
|
||||
super.key,
|
||||
@@ -89,7 +89,7 @@ class ListItem<T> extends StatelessWidget {
|
||||
this.trailing,
|
||||
this.horizontalTitleGap,
|
||||
this.prue,
|
||||
this.onTab,
|
||||
this.onTap,
|
||||
this.tileTitleAlignment = ListTileTitleAlignment.center,
|
||||
}) : delegate = const Delegate();
|
||||
|
||||
@@ -104,7 +104,7 @@ class ListItem<T> extends StatelessWidget {
|
||||
this.horizontalTitleGap,
|
||||
this.prue,
|
||||
this.tileTitleAlignment = ListTileTitleAlignment.center,
|
||||
}) : onTab = null;
|
||||
}) : onTap = null;
|
||||
|
||||
const ListItem.next({
|
||||
super.key,
|
||||
@@ -117,7 +117,7 @@ class ListItem<T> extends StatelessWidget {
|
||||
this.horizontalTitleGap,
|
||||
this.prue,
|
||||
this.tileTitleAlignment = ListTileTitleAlignment.center,
|
||||
}) : onTab = null;
|
||||
}) : onTap = null;
|
||||
|
||||
const ListItem.checkbox({
|
||||
super.key,
|
||||
@@ -130,7 +130,7 @@ class ListItem<T> extends StatelessWidget {
|
||||
this.prue,
|
||||
this.tileTitleAlignment = ListTileTitleAlignment.center,
|
||||
}) : trailing = null,
|
||||
onTab = null;
|
||||
onTap = null;
|
||||
|
||||
const ListItem.switchItem({
|
||||
super.key,
|
||||
@@ -143,7 +143,7 @@ class ListItem<T> extends StatelessWidget {
|
||||
this.prue,
|
||||
this.tileTitleAlignment = ListTileTitleAlignment.center,
|
||||
}) : trailing = null,
|
||||
onTab = null;
|
||||
onTap = null;
|
||||
|
||||
const ListItem.radio({
|
||||
super.key,
|
||||
@@ -156,10 +156,10 @@ class ListItem<T> extends StatelessWidget {
|
||||
this.prue,
|
||||
this.tileTitleAlignment = ListTileTitleAlignment.center,
|
||||
}) : leading = null,
|
||||
onTab = null;
|
||||
onTap = null;
|
||||
|
||||
_buildListTile({
|
||||
void Function()? onTab,
|
||||
void Function()? onTap,
|
||||
Widget? trailing,
|
||||
Widget? leading,
|
||||
}) {
|
||||
@@ -188,7 +188,7 @@ class ListItem<T> extends StatelessWidget {
|
||||
}
|
||||
return InkWell(
|
||||
splashFactory: NoSplash.splashFactory,
|
||||
onTap: onTab,
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: padding,
|
||||
child: Row(
|
||||
@@ -203,7 +203,7 @@ class ListItem<T> extends StatelessWidget {
|
||||
title: title,
|
||||
subtitle: subtitle,
|
||||
titleAlignment: tileTitleAlignment,
|
||||
onTap: onTab,
|
||||
onTap: onTap,
|
||||
trailing: trailing ?? this.trailing,
|
||||
contentPadding: padding,
|
||||
);
|
||||
@@ -230,7 +230,7 @@ class ListItem<T> extends StatelessWidget {
|
||||
action();
|
||||
}
|
||||
|
||||
return _buildListTile(onTab: openAction);
|
||||
return _buildListTile(onTap: openAction);
|
||||
},
|
||||
openBuilder: (_, action) {
|
||||
return CommonScaffold.open(
|
||||
@@ -245,7 +245,7 @@ class ListItem<T> extends StatelessWidget {
|
||||
if (delegate is NextDelegate) {
|
||||
final nextDelegate = delegate as NextDelegate;
|
||||
return _buildListTile(
|
||||
onTab: () {
|
||||
onTap: () {
|
||||
final isMobile =
|
||||
globalState.appController.appState.viewMode == ViewMode.mobile;
|
||||
if (!isMobile) {
|
||||
@@ -272,7 +272,7 @@ class ListItem<T> extends StatelessWidget {
|
||||
if (delegate is CheckboxDelegate) {
|
||||
final checkboxDelegate = delegate as CheckboxDelegate;
|
||||
return _buildListTile(
|
||||
onTab: () {
|
||||
onTap: () {
|
||||
if (checkboxDelegate.onChanged != null) {
|
||||
checkboxDelegate.onChanged!(!checkboxDelegate.value);
|
||||
}
|
||||
@@ -286,7 +286,7 @@ class ListItem<T> extends StatelessWidget {
|
||||
if (delegate is SwitchDelegate) {
|
||||
final switchDelegate = delegate as SwitchDelegate;
|
||||
return _buildListTile(
|
||||
onTab: () {
|
||||
onTap: () {
|
||||
if (switchDelegate.onChanged != null) {
|
||||
switchDelegate.onChanged!(!switchDelegate.value);
|
||||
}
|
||||
@@ -300,7 +300,7 @@ class ListItem<T> extends StatelessWidget {
|
||||
if (delegate is RadioDelegate) {
|
||||
final radioDelegate = delegate as RadioDelegate<T>;
|
||||
return _buildListTile(
|
||||
onTab: () {
|
||||
onTap: () {
|
||||
if (radioDelegate.onChanged != null) {
|
||||
radioDelegate.onChanged!(radioDelegate.value);
|
||||
}
|
||||
@@ -319,7 +319,7 @@ class ListItem<T> extends StatelessWidget {
|
||||
}
|
||||
|
||||
return _buildListTile(
|
||||
onTab: onTab,
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,17 @@ 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),
|
||||
centerTitle: false,
|
||||
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,170 @@ 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(
|
||||
onPanStart: (_) {
|
||||
windowManager.startDragging();
|
||||
},
|
||||
onDoubleTap: () {
|
||||
_updateMaximized();
|
||||
},
|
||||
child: Container(
|
||||
color: context.colorScheme.surface,
|
||||
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: 24,
|
||||
height: 24,
|
||||
child: CircleAvatar(
|
||||
foregroundImage: AssetImage("assets/images/icon.png"),
|
||||
backgroundColor: Colors.transparent,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Text(
|
||||
appName,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
display_name: FlClash
|
||||
|
||||
icon: ./assets/images/launch_icon.png
|
||||
icon: ./assets/images/icon.png
|
||||
|
||||
keywords:
|
||||
- FlClash
|
||||
|
||||
@@ -8,7 +8,7 @@ priority: optional
|
||||
section: x11
|
||||
installed_size: 6604
|
||||
essential: false
|
||||
icon: ./assets/images/launch_icon.png
|
||||
icon: ./assets/images/icon.png
|
||||
|
||||
|
||||
keywords:
|
||||
|
||||
@@ -8,7 +8,7 @@ priority: optional
|
||||
section: x11
|
||||
installed_size: 6604
|
||||
essential: false
|
||||
icon: ./assets/images/launch_icon.png
|
||||
icon: ./assets/images/icon.png
|
||||
|
||||
keywords:
|
||||
- FlClash
|
||||
|
||||
|
Before Width: | Height: | Size: 222 KiB After Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 645 B After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 5.8 KiB |
@@ -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.46+202407221
|
||||
environment:
|
||||
sdk: '>=3.1.0 <4.0.0'
|
||||
|
||||
@@ -58,6 +58,7 @@ flutter:
|
||||
assets:
|
||||
- assets/data/
|
||||
- assets/images/
|
||||
- assets/images/avatars/
|
||||
ffigen:
|
||||
name: "ClashFFI"
|
||||
output: 'lib/clash/generated/clash_ffi.dart'
|
||||
|
||||
@@ -5,7 +5,7 @@ publisher_url: https://github.com/chen08209/FlClash
|
||||
display_name: FlClash
|
||||
executable_name: FlClash.exe
|
||||
output_base_file_name: FlClash.exe
|
||||
setup_icon_file: ..\windows\runner\resources\app_icon.ico
|
||||
setup_icon_file: ..\windows\runner\resources\icon.ico
|
||||
locales:
|
||||
- lang: zh
|
||||
file: ..\windows\packaging\exe\ChineseSimplified.isl
|
||||
|
||||
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.5 KiB |