Compare commits
1 Commits
v0.8.88-pr
...
v0.8.88-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92bf322668 |
1
.github/workflows/build.yaml
vendored
1
.github/workflows/build.yaml
vendored
@@ -52,7 +52,6 @@ jobs:
|
||||
if: startsWith(matrix.platform,'android')
|
||||
run: |
|
||||
echo "${{ secrets.KEYSTORE }}" | base64 --decode > android/app/keystore.jks
|
||||
echo "${{ secrets.SERVICE_JSON }}" | base64 --decode > android/app/google-services.json
|
||||
echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> android/local.properties
|
||||
echo "storePassword=${{ secrets.STORE_PASSWORD }}" >> android/local.properties
|
||||
echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> android/local.properties
|
||||
|
||||
@@ -5,8 +5,6 @@ plugins {
|
||||
id("com.android.application")
|
||||
id("kotlin-android")
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
id("com.google.gms.google-services")
|
||||
id("com.google.firebase.crashlytics")
|
||||
}
|
||||
|
||||
val localPropertiesFile = rootProject.file("local.properties")
|
||||
@@ -20,9 +18,10 @@ val mStoreFile: File = file("keystore.jks")
|
||||
val mStorePassword: String? = localProperties.getProperty("storePassword")
|
||||
val mKeyAlias: String? = localProperties.getProperty("keyAlias")
|
||||
val mKeyPassword: String? = localProperties.getProperty("keyPassword")
|
||||
val isRelease =
|
||||
mStoreFile.exists() && mStorePassword != null && mKeyAlias != null && mKeyPassword != null
|
||||
|
||||
val isRelease = mStoreFile.exists()
|
||||
&& mStorePassword != null
|
||||
&& mKeyAlias != null
|
||||
&& mKeyPassword != null
|
||||
|
||||
android {
|
||||
namespace = "com.follow.clash"
|
||||
@@ -77,7 +76,8 @@ android {
|
||||
}
|
||||
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,6 @@ flutter {
|
||||
source = "../.."
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation(project(":service"))
|
||||
implementation(project(":common"))
|
||||
@@ -102,7 +101,4 @@ dependencies {
|
||||
implementation(libs.smali.dexlib2) {
|
||||
exclude(group = "com.google.guava", module = "guava")
|
||||
}
|
||||
implementation(platform(libs.firebase.bom))
|
||||
implementation(libs.firebase.crashlytics.ndk)
|
||||
implementation(libs.firebase.analytics)
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "000000000000",
|
||||
"project_id": "dev"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:000000000000:android:0000000000000000",
|
||||
"android_client_info": {
|
||||
"package_name": "com.follow.clash"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "0"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:000000000000:android:0000000000000000",
|
||||
"android_client_info": {
|
||||
"package_name": "com.follow.clash.debug"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "0"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3,9 +3,6 @@ package com.follow.clash
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import com.follow.clash.common.GlobalState
|
||||
import com.follow.clash.common.processName
|
||||
import com.google.firebase.FirebaseApp
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
|
||||
class Application : Application() {
|
||||
|
||||
@@ -13,21 +10,4 @@ class Application : Application() {
|
||||
super.attachBaseContext(base)
|
||||
GlobalState.init(this)
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
initRemoteCrashlytics(this)
|
||||
}
|
||||
|
||||
private fun initRemoteCrashlytics(context: Context) {
|
||||
try {
|
||||
if (processName?.endsWith(":remote") == true) {
|
||||
FirebaseApp.initializeApp(context)
|
||||
FirebaseCrashlytics.getInstance().isCrashlyticsCollectionEnabled = true
|
||||
GlobalState.log("init remote crashlytics")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
GlobalState.log("initRemoteCrashlytics error: $e")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.follow.clash.common
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.ActivityManager
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
@@ -21,7 +20,6 @@ import android.os.IBinder
|
||||
import android.os.Looper
|
||||
import android.os.RemoteException
|
||||
import android.util.Log
|
||||
import androidx.core.content.getSystemService
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.delay
|
||||
@@ -69,16 +67,6 @@ val QuickAction.quickIntent: Intent
|
||||
val BroadcastAction.action: String
|
||||
get() = "${GlobalState.application.packageName}.intent.action.${this.name}"
|
||||
|
||||
val Context.processName: String?
|
||||
get() {
|
||||
val pid = android.os.Process.myPid()
|
||||
val activityManager = getSystemService<ActivityManager>()
|
||||
activityManager?.runningAppProcesses?.find { it.pid == pid }?.let {
|
||||
return it.processName
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
val BroadcastAction.quickIntent: Intent
|
||||
get() = Components.BROADCAST_RECEIVER.intent.apply {
|
||||
action = this@quickIntent.action
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.follow.clash.common
|
||||
|
||||
|
||||
import android.app.Application
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
[versions]
|
||||
#agp = "8.10.1"
|
||||
firebaseBom = "34.2.0"
|
||||
minSdk = "23"
|
||||
targetSdk = "36"
|
||||
compileSdk = "36"
|
||||
@@ -11,18 +10,11 @@ coreSplashscreen = "1.0.1"
|
||||
gson = "2.13.1"
|
||||
kotlin = "2.2.10"
|
||||
smaliDexlib2 = "3.0.9"
|
||||
firebaseCrashlyticsKtx = "20.0.1"
|
||||
firebaseCommonKtx = "22.0.0"
|
||||
|
||||
[libraries]
|
||||
build-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
||||
androidx-core = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
|
||||
annotation-jvm = { module = "androidx.annotation:annotation-jvm", version.ref = "annotationJvm" }
|
||||
core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashscreen" }
|
||||
firebase-analytics = { module = "com.google.firebase:firebase-analytics" }
|
||||
firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" }
|
||||
firebase-crashlytics-ndk = { module = "com.google.firebase:firebase-crashlytics-ndk" }
|
||||
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
|
||||
smali-dexlib2 = { module = "com.android.tools.smali:smali-dexlib2", version.ref = "smaliDexlib2" }
|
||||
firebase-crashlytics-ktx = { group = "com.google.firebase", name = "firebase-crashlytics-ktx", version.ref = "firebaseCrashlyticsKtx" }
|
||||
firebase-common-ktx = { group = "com.google.firebase", name = "firebase-common-ktx", version.ref = "firebaseCommonKtx" }
|
||||
smali-dexlib2 = { module = "com.android.tools.smali:smali-dexlib2", version.ref = "smaliDexlib2" }
|
||||
@@ -13,11 +13,7 @@ val Traffic.speedText: String
|
||||
get() = "${up.formatBytes}/s↑ ${down.formatBytes}/s↓"
|
||||
|
||||
fun Core.getSpeedTrafficText(onlyStatisticsProxy: Boolean): String {
|
||||
try {
|
||||
val res = getTraffic(onlyStatisticsProxy)
|
||||
val traffic = Gson().fromJson(res, Traffic::class.java)
|
||||
return traffic.speedText
|
||||
} catch (_: Exception) {
|
||||
return ""
|
||||
}
|
||||
val res = getTraffic(onlyStatisticsProxy)
|
||||
val traffic = Gson().fromJson(res, Traffic::class.java)
|
||||
return traffic.speedText
|
||||
}
|
||||
@@ -18,10 +18,8 @@ pluginManagement {
|
||||
|
||||
plugins {
|
||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||
id("com.android.application") version "8.12.2" apply false
|
||||
id("com.android.application") version "8.12.1" apply false
|
||||
id("org.jetbrains.kotlin.android") version "2.2.10" apply false
|
||||
id("com.google.gms.google-services") version ("4.3.15") apply false
|
||||
id("com.google.firebase.crashlytics") version ("2.8.1") apply false
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -409,7 +409,7 @@
|
||||
"autoSetSystemDns": "Auto set system DNS",
|
||||
"details": "{label} details",
|
||||
"creationTime": "Creation time",
|
||||
"process": "Process",
|
||||
"progress": "Progress",
|
||||
"host": "Host",
|
||||
"destination": "Destination",
|
||||
"destinationGeoIP": "Destination GeoIP",
|
||||
|
||||
@@ -410,7 +410,7 @@
|
||||
"autoSetSystemDns": "オートセットシステムDNS",
|
||||
"details": "{label}詳細",
|
||||
"creationTime": "作成時間",
|
||||
"process": "プロセス",
|
||||
"progress": "進捗",
|
||||
"host": "ホスト",
|
||||
"destination": "宛先",
|
||||
"destinationGeoIP": "宛先地理情報",
|
||||
|
||||
@@ -410,7 +410,7 @@
|
||||
"autoSetSystemDns": "Автоматическая настройка системного DNS",
|
||||
"details": "Детали {}",
|
||||
"creationTime": "Время создания",
|
||||
"process": "процесс",
|
||||
"progress": "Прогресс",
|
||||
"host": "Хост",
|
||||
"destination": "Назначение",
|
||||
"destinationGeoIP": "Геолокация назначения",
|
||||
|
||||
@@ -410,7 +410,7 @@
|
||||
"autoSetSystemDns": "自动设置系统DNS",
|
||||
"details": "{label}详情",
|
||||
"creationTime": "创建时间",
|
||||
"process": "进程",
|
||||
"progress": "进度",
|
||||
"host": "主机",
|
||||
"destination": "目标地址",
|
||||
"destinationGeoIP": "目标地理定位",
|
||||
|
||||
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/metacubex/mihomo/adapter"
|
||||
"github.com/metacubex/mihomo/adapter/outboundgroup"
|
||||
"github.com/metacubex/mihomo/common/observable"
|
||||
@@ -144,7 +145,7 @@ func handleGetTraffic(onlyStatisticsProxy bool) string {
|
||||
}
|
||||
data, err := json.Marshal(traffic)
|
||||
if err != nil {
|
||||
log.Errorln("Error: %s", err)
|
||||
fmt.Println("Error:", err)
|
||||
return ""
|
||||
}
|
||||
return string(data)
|
||||
@@ -158,7 +159,7 @@ func handleGetTotalTraffic(onlyStatisticsProxy bool) string {
|
||||
}
|
||||
data, err := json.Marshal(traffic)
|
||||
if err != nil {
|
||||
log.Errorln("Error: %s", err)
|
||||
fmt.Println("Error:", err)
|
||||
return ""
|
||||
}
|
||||
return string(data)
|
||||
@@ -228,7 +229,7 @@ func handleGetConnections() string {
|
||||
snapshot := statistic.DefaultManager.Snapshot()
|
||||
data, err := json.Marshal(snapshot)
|
||||
if err != nil {
|
||||
log.Errorln("Error: %s", err)
|
||||
fmt.Println("Error:", err)
|
||||
return ""
|
||||
}
|
||||
return string(data)
|
||||
|
||||
@@ -125,8 +125,10 @@ class ApplicationState extends ConsumerState<Application> {
|
||||
builder: (_, child) {
|
||||
return AppEnvManager(
|
||||
child: _buildApp(
|
||||
child: _buildPlatformState(
|
||||
child: _buildState(child: _buildPlatformApp(child: child!)),
|
||||
child: AppSidebarContainer(
|
||||
child: _buildPlatformState(
|
||||
child: _buildState(child: _buildPlatformApp(child: child!)),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -7,17 +7,28 @@ extension BuildContextExtension on BuildContext {
|
||||
return findAncestorStateOfType<CommonScaffoldState>();
|
||||
}
|
||||
|
||||
void showNotifier(String text) {
|
||||
Future<void>? showNotifier(String text) {
|
||||
return findAncestorStateOfType<MessageManagerState>()?.message(text);
|
||||
}
|
||||
|
||||
void showSnackBar(String message, {SnackBarAction? action}) {
|
||||
void showSnackBar(
|
||||
String message, {
|
||||
SnackBarAction? action,
|
||||
}) {
|
||||
final width = viewWidth;
|
||||
EdgeInsets margin;
|
||||
if (width < 600) {
|
||||
margin = const EdgeInsets.only(bottom: 16, right: 16, left: 16);
|
||||
margin = const EdgeInsets.only(
|
||||
bottom: 16,
|
||||
right: 16,
|
||||
left: 16,
|
||||
);
|
||||
} else {
|
||||
margin = EdgeInsets.only(bottom: 16, left: 16, right: width - 316);
|
||||
margin = EdgeInsets.only(
|
||||
bottom: 16,
|
||||
left: 16,
|
||||
right: width - 316,
|
||||
);
|
||||
}
|
||||
ScaffoldMessenger.of(this).showSnackBar(
|
||||
SnackBar(
|
||||
@@ -65,11 +76,8 @@ extension BuildContextExtension on BuildContext {
|
||||
class BackHandleInherited extends InheritedWidget {
|
||||
final Function handleBack;
|
||||
|
||||
const BackHandleInherited({
|
||||
super.key,
|
||||
required this.handleBack,
|
||||
required super.child,
|
||||
});
|
||||
const BackHandleInherited(
|
||||
{super.key, required this.handleBack, required super.child});
|
||||
|
||||
static BackHandleInherited? of(BuildContext context) =>
|
||||
context.dependOnInheritedWidgetOfExactType<BackHandleInherited>();
|
||||
|
||||
@@ -528,9 +528,11 @@ class AppController {
|
||||
|
||||
Future<void> init() async {
|
||||
FlutterError.onError = (details) {
|
||||
commonPrint.log(
|
||||
'exception: ${details.exception} stack: ${details.stack}',
|
||||
);
|
||||
if (kDebugMode) {
|
||||
commonPrint.log(
|
||||
'exception: ${details.exception} stack: ${details.stack}',
|
||||
);
|
||||
}
|
||||
};
|
||||
updateTray(true);
|
||||
autoUpdateProfiles();
|
||||
@@ -620,7 +622,6 @@ class AppController {
|
||||
|
||||
Future<bool> showDisclaimer() async {
|
||||
return await globalState.showCommonDialog<bool>(
|
||||
context: context,
|
||||
dismissible: false,
|
||||
child: CommonDialog(
|
||||
title: appLocalizations.disclaimer,
|
||||
|
||||
@@ -521,7 +521,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Please press the keyboard.",
|
||||
),
|
||||
"preview": MessageLookupByLibrary.simpleMessage("Preview"),
|
||||
"process": MessageLookupByLibrary.simpleMessage("Process"),
|
||||
"profile": MessageLookupByLibrary.simpleMessage("Profile"),
|
||||
"profileAutoUpdateIntervalInvalidValidationDesc":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
@@ -548,6 +547,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
),
|
||||
"profiles": MessageLookupByLibrary.simpleMessage("Profiles"),
|
||||
"profilesSort": MessageLookupByLibrary.simpleMessage("Profiles sort"),
|
||||
"progress": MessageLookupByLibrary.simpleMessage("Progress"),
|
||||
"project": MessageLookupByLibrary.simpleMessage("Project"),
|
||||
"providers": MessageLookupByLibrary.simpleMessage("Providers"),
|
||||
"proxies": MessageLookupByLibrary.simpleMessage("Proxies"),
|
||||
|
||||
@@ -395,7 +395,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"preferH3Desc": MessageLookupByLibrary.simpleMessage("DOHのHTTP/3を優先使用"),
|
||||
"pressKeyboard": MessageLookupByLibrary.simpleMessage("キーボードを押してください"),
|
||||
"preview": MessageLookupByLibrary.simpleMessage("プレビュー"),
|
||||
"process": MessageLookupByLibrary.simpleMessage("プロセス"),
|
||||
"profile": MessageLookupByLibrary.simpleMessage("プロファイル"),
|
||||
"profileAutoUpdateIntervalInvalidValidationDesc":
|
||||
MessageLookupByLibrary.simpleMessage("有効な間隔形式を入力してください"),
|
||||
@@ -418,6 +417,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
),
|
||||
"profiles": MessageLookupByLibrary.simpleMessage("プロファイル一覧"),
|
||||
"profilesSort": MessageLookupByLibrary.simpleMessage("プロファイルの並び替え"),
|
||||
"progress": MessageLookupByLibrary.simpleMessage("進捗"),
|
||||
"project": MessageLookupByLibrary.simpleMessage("プロジェクト"),
|
||||
"providers": MessageLookupByLibrary.simpleMessage("プロバイダー"),
|
||||
"proxies": MessageLookupByLibrary.simpleMessage("プロキシ"),
|
||||
|
||||
@@ -548,7 +548,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Пожалуйста, нажмите клавишу.",
|
||||
),
|
||||
"preview": MessageLookupByLibrary.simpleMessage("Предпросмотр"),
|
||||
"process": MessageLookupByLibrary.simpleMessage("процесс"),
|
||||
"profile": MessageLookupByLibrary.simpleMessage("Профиль"),
|
||||
"profileAutoUpdateIntervalInvalidValidationDesc":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
@@ -575,6 +574,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
),
|
||||
"profiles": MessageLookupByLibrary.simpleMessage("Профили"),
|
||||
"profilesSort": MessageLookupByLibrary.simpleMessage("Сортировка профилей"),
|
||||
"progress": MessageLookupByLibrary.simpleMessage("Прогресс"),
|
||||
"project": MessageLookupByLibrary.simpleMessage("Проект"),
|
||||
"providers": MessageLookupByLibrary.simpleMessage("Провайдеры"),
|
||||
"proxies": MessageLookupByLibrary.simpleMessage("Прокси"),
|
||||
|
||||
@@ -347,7 +347,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"preferH3Desc": MessageLookupByLibrary.simpleMessage("优先使用DOH的http/3"),
|
||||
"pressKeyboard": MessageLookupByLibrary.simpleMessage("请按下按键"),
|
||||
"preview": MessageLookupByLibrary.simpleMessage("预览"),
|
||||
"process": MessageLookupByLibrary.simpleMessage("进程"),
|
||||
"profile": MessageLookupByLibrary.simpleMessage("配置"),
|
||||
"profileAutoUpdateIntervalInvalidValidationDesc":
|
||||
MessageLookupByLibrary.simpleMessage("请输入有效间隔时间格式"),
|
||||
@@ -368,6 +367,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
),
|
||||
"profiles": MessageLookupByLibrary.simpleMessage("配置"),
|
||||
"profilesSort": MessageLookupByLibrary.simpleMessage("配置排序"),
|
||||
"progress": MessageLookupByLibrary.simpleMessage("进度"),
|
||||
"project": MessageLookupByLibrary.simpleMessage("项目"),
|
||||
"providers": MessageLookupByLibrary.simpleMessage("提供者"),
|
||||
"proxies": MessageLookupByLibrary.simpleMessage("代理"),
|
||||
|
||||
@@ -3159,9 +3159,9 @@ class AppLocalizations {
|
||||
);
|
||||
}
|
||||
|
||||
/// `Process`
|
||||
String get process {
|
||||
return Intl.message('Process', name: 'process', desc: '', args: []);
|
||||
/// `Progress`
|
||||
String get progress {
|
||||
return Intl.message('Progress', name: 'progress', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Host`
|
||||
|
||||
@@ -181,99 +181,102 @@ class AppSidebarContainer extends ConsumerWidget {
|
||||
return child;
|
||||
}
|
||||
final currentIndex = navigationState.currentIndex;
|
||||
final showLabel = ref.watch(appSettingProvider).showLabel;
|
||||
final isExtend = ref.watch(appSettingProvider).showLabel;
|
||||
return Row(
|
||||
children: [
|
||||
_buildBackground(
|
||||
context: context,
|
||||
child: Stack(
|
||||
alignment: Alignment.topRight,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (system.isMacOS) SizedBox(height: 22),
|
||||
SizedBox(height: 10),
|
||||
if (!system.isMacOS) ...[
|
||||
ClipRect(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 20),
|
||||
child: AppIcon(),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
],
|
||||
Expanded(
|
||||
child: ScrollConfiguration(
|
||||
behavior: HiddenBarScrollBehavior(),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: NavigationRail(
|
||||
scrollable: true,
|
||||
minExtendedWidth: 200,
|
||||
backgroundColor: Colors.transparent,
|
||||
selectedLabelTextStyle: context
|
||||
.textTheme
|
||||
.labelLarge!
|
||||
.copyWith(
|
||||
color: context.colorScheme.onSurface,
|
||||
),
|
||||
unselectedLabelTextStyle: context
|
||||
.textTheme
|
||||
.labelLarge!
|
||||
.copyWith(
|
||||
color: context.colorScheme.onSurface,
|
||||
),
|
||||
destinations: navigationItems
|
||||
.map(
|
||||
(e) => NavigationRailDestination(
|
||||
icon: e.icon,
|
||||
label: Text(Intl.message(e.label.name)),
|
||||
Stack(
|
||||
alignment: Alignment.topRight,
|
||||
children: [
|
||||
_buildBackground(
|
||||
context: context,
|
||||
child: SafeArea(
|
||||
child: AnimatedSize(
|
||||
curve: Curves.easeOut,
|
||||
alignment: Alignment.centerLeft,
|
||||
duration: commonDuration,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (system.isMacOS) SizedBox(height: 22),
|
||||
SizedBox(height: 10),
|
||||
if (!system.isMacOS) ...[
|
||||
ClipRect(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 20),
|
||||
child: AppIcon(),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
],
|
||||
Expanded(
|
||||
child: ScrollConfiguration(
|
||||
behavior: HiddenBarScrollBehavior(),
|
||||
child: SingleChildScrollView(
|
||||
child: IntrinsicHeight(
|
||||
child: NavigationRail(
|
||||
backgroundColor: Colors.transparent,
|
||||
selectedLabelTextStyle: context
|
||||
.textTheme
|
||||
.labelLarge!
|
||||
.copyWith(
|
||||
color: context.colorScheme.onSurface,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onDestinationSelected: (index) {
|
||||
globalState.appController.toPage(
|
||||
navigationItems[index].label,
|
||||
);
|
||||
},
|
||||
extended: false,
|
||||
selectedIndex: currentIndex,
|
||||
labelType: showLabel
|
||||
? NavigationRailLabelType.all
|
||||
: NavigationRailLabelType.none,
|
||||
unselectedLabelTextStyle: context
|
||||
.textTheme
|
||||
.labelLarge!
|
||||
.copyWith(
|
||||
color: context.colorScheme.onSurface,
|
||||
),
|
||||
destinations: navigationItems
|
||||
.map(
|
||||
(e) => NavigationRailDestination(
|
||||
icon: e.icon,
|
||||
label: Text(Intl.message(e.label.name)),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onDestinationSelected: (index) {
|
||||
globalState.appController.toPage(
|
||||
navigationItems[index].label,
|
||||
);
|
||||
},
|
||||
extended: isExtend,
|
||||
selectedIndex: currentIndex,
|
||||
// labelType: showLabel
|
||||
// ? NavigationRailLabelType.all
|
||||
// : NavigationRailLabelType.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 20),
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(appSettingProvider.notifier)
|
||||
.updateState(
|
||||
(state) =>
|
||||
state.copyWith(showLabel: !state.showLabel),
|
||||
);
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.menu,
|
||||
color: context.colorScheme.onSurfaceVariant,
|
||||
const SizedBox(height: 16),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 20),
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(appSettingProvider.notifier)
|
||||
.updateState(
|
||||
(state) => state.copyWith(
|
||||
showLabel: !state.showLabel,
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.menu,
|
||||
color: context.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
_buildLoading(),
|
||||
],
|
||||
),
|
||||
),
|
||||
_buildLoading(),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
@@ -18,9 +17,8 @@ class MessageManager extends StatefulWidget {
|
||||
|
||||
class MessageManagerState extends State<MessageManager> {
|
||||
final _messagesNotifier = ValueNotifier<List<CommonMessage>>([]);
|
||||
final _bufferMessages = Queue<CommonMessage>();
|
||||
final _activeTimers = <String, Timer>{};
|
||||
bool _isDisplayingMessage = false;
|
||||
final List<CommonMessage> _bufferMessages = [];
|
||||
bool _pushing = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -30,48 +28,38 @@ class MessageManagerState extends State<MessageManager> {
|
||||
@override
|
||||
void dispose() {
|
||||
_messagesNotifier.dispose();
|
||||
for (final timer in _activeTimers.values) {
|
||||
timer.cancel();
|
||||
}
|
||||
_activeTimers.clear();
|
||||
_bufferMessages.clear();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void message(String text) {
|
||||
Future<void> message(String text) async {
|
||||
final commonMessage = CommonMessage(id: utils.uuidV4, text: text);
|
||||
commonPrint.log(text);
|
||||
_bufferMessages.add(commonMessage);
|
||||
commonPrint.log('message: $text');
|
||||
_processQueue();
|
||||
await _showMessage();
|
||||
}
|
||||
|
||||
void _cancelMessage(String id) {
|
||||
_bufferMessages.removeWhere((msg) => msg.id == id);
|
||||
if (_activeTimers.containsKey(id)) {
|
||||
_removeMessage(id);
|
||||
}
|
||||
}
|
||||
|
||||
void _processQueue() {
|
||||
if (_isDisplayingMessage || _bufferMessages.isEmpty) {
|
||||
Future<void> _showMessage() async {
|
||||
if (_pushing == true) {
|
||||
return;
|
||||
}
|
||||
_isDisplayingMessage = true;
|
||||
final message = _bufferMessages.removeFirst();
|
||||
_messagesNotifier.value = List.from(_messagesNotifier.value)..add(message);
|
||||
final timer = Timer(message.duration, () {
|
||||
_removeMessage(message.id);
|
||||
});
|
||||
_activeTimers[message.id] = timer;
|
||||
_pushing = true;
|
||||
while (_bufferMessages.isNotEmpty) {
|
||||
final commonMessage = _bufferMessages.removeAt(0);
|
||||
_messagesNotifier.value = List.from(_messagesNotifier.value)
|
||||
..add(commonMessage);
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
Future.delayed(commonMessage.duration, () {
|
||||
_handleRemove(commonMessage);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _removeMessage(String id) {
|
||||
_activeTimers.remove(id)?.cancel();
|
||||
final currentMessages = List<CommonMessage>.from(_messagesNotifier.value);
|
||||
currentMessages.removeWhere((msg) => msg.id == id);
|
||||
_messagesNotifier.value = currentMessages;
|
||||
_isDisplayingMessage = false;
|
||||
_processQueue();
|
||||
Future<void> _handleRemove(CommonMessage commonMessage) async {
|
||||
_messagesNotifier.value = List<CommonMessage>.from(_messagesNotifier.value)
|
||||
..remove(commonMessage);
|
||||
if (_bufferMessages.isEmpty) {
|
||||
_pushing = false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -95,45 +83,35 @@ class MessageManagerState extends State<MessageManager> {
|
||||
: LayoutBuilder(
|
||||
key: Key(messages.last.id),
|
||||
builder: (_, constraints) {
|
||||
return Dismissible(
|
||||
key: ValueKey(messages.last.id),
|
||||
onDismissed: (_) {
|
||||
_cancelMessage(messages.last.id);
|
||||
},
|
||||
child: Card(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(14),
|
||||
),
|
||||
return Card(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(12.0),
|
||||
),
|
||||
elevation: 10,
|
||||
color: context.colorScheme.surfaceContainerHigh,
|
||||
child: Container(
|
||||
width: min(constraints.maxWidth, 500),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
messages.last.text,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 16),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
_cancelMessage(messages.last.id);
|
||||
},
|
||||
child: Text(appLocalizations.cancel),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
elevation: 10,
|
||||
color: context.colorScheme.surfaceContainerHigh,
|
||||
child: Container(
|
||||
width: min(constraints.maxWidth, 500),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 10,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(child: Text(messages.last.text)),
|
||||
IconButton(
|
||||
visualDensity: VisualDensity.compact,
|
||||
iconSize: 18,
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () {
|
||||
_handleRemove(messages.last);
|
||||
},
|
||||
icon: Icon(Icons.close),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/manager/app_manager.dart';
|
||||
import 'package:fl_clash/providers/providers.dart';
|
||||
import 'package:fl_clash/state.dart';
|
||||
import 'package:fl_clash/widgets/widgets.dart';
|
||||
@@ -17,85 +16,83 @@ class HomePage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return HomeBackScopeContainer(
|
||||
child: AppSidebarContainer(
|
||||
child: Material(
|
||||
color: context.colorScheme.surface,
|
||||
child: Consumer(
|
||||
builder: (context, ref, _) {
|
||||
final state = ref.watch(navigationStateProvider);
|
||||
final isMobile = state.viewMode == ViewMode.mobile;
|
||||
final navigationItems = state.navigationItems;
|
||||
final pageView = _HomePageView(
|
||||
pageBuilder: (_, index) {
|
||||
final navigationItem = state.navigationItems[index];
|
||||
final navigationView = navigationItem.builder(context);
|
||||
final view = KeepScope(
|
||||
keep: navigationItem.keep,
|
||||
child: isMobile
|
||||
? navigationView
|
||||
: Navigator(
|
||||
pages: [MaterialPage(child: navigationView)],
|
||||
onDidRemovePage: (_) {},
|
||||
),
|
||||
);
|
||||
return view;
|
||||
},
|
||||
);
|
||||
final currentIndex = state.currentIndex;
|
||||
final bottomNavigationBar = NavigationBarTheme(
|
||||
data: _NavigationBarDefaultsM3(context),
|
||||
child: NavigationBar(
|
||||
destinations: navigationItems
|
||||
.map(
|
||||
(e) => NavigationDestination(
|
||||
icon: e.icon,
|
||||
label: Intl.message(e.label.name),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onDestinationSelected: (index) {
|
||||
globalState.appController.toPage(
|
||||
navigationItems[index].label,
|
||||
);
|
||||
},
|
||||
selectedIndex: currentIndex,
|
||||
),
|
||||
);
|
||||
if (isMobile) {
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: globalState.appState.systemUiOverlayStyle.copyWith(
|
||||
systemNavigationBarColor:
|
||||
context.colorScheme.surfaceContainer,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: MediaQuery.removePadding(
|
||||
removeTop: false,
|
||||
removeBottom: true,
|
||||
removeLeft: true,
|
||||
removeRight: true,
|
||||
context: context,
|
||||
child: pageView,
|
||||
child: Material(
|
||||
color: context.colorScheme.surface,
|
||||
child: Consumer(
|
||||
builder: (context, ref, _) {
|
||||
final state = ref.watch(navigationStateProvider);
|
||||
final isMobile = state.viewMode == ViewMode.mobile;
|
||||
final navigationItems = state.navigationItems;
|
||||
final pageView = _HomePageView(
|
||||
pageBuilder: (_, index) {
|
||||
final navigationItem = state.navigationItems[index];
|
||||
final navigationView = navigationItem.builder(context);
|
||||
final view = KeepScope(
|
||||
keep: navigationItem.keep,
|
||||
child: isMobile
|
||||
? navigationView
|
||||
: Navigator(
|
||||
pages: [MaterialPage(child: navigationView)],
|
||||
onDidRemovePage: (_) {},
|
||||
),
|
||||
);
|
||||
return view;
|
||||
},
|
||||
);
|
||||
final currentIndex = state.currentIndex;
|
||||
final bottomNavigationBar = NavigationBarTheme(
|
||||
data: _NavigationBarDefaultsM3(context),
|
||||
child: NavigationBar(
|
||||
destinations: navigationItems
|
||||
.map(
|
||||
(e) => NavigationDestination(
|
||||
icon: e.icon,
|
||||
label: Intl.message(e.label.name),
|
||||
),
|
||||
MediaQuery.removePadding(
|
||||
removeTop: true,
|
||||
removeBottom: false,
|
||||
)
|
||||
.toList(),
|
||||
onDestinationSelected: (index) {
|
||||
globalState.appController.toPage(
|
||||
navigationItems[index].label,
|
||||
);
|
||||
},
|
||||
selectedIndex: currentIndex,
|
||||
),
|
||||
);
|
||||
if (isMobile) {
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: globalState.appState.systemUiOverlayStyle.copyWith(
|
||||
systemNavigationBarColor:
|
||||
context.colorScheme.surfaceContainer,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: MediaQuery.removePadding(
|
||||
removeTop: false,
|
||||
removeBottom: true,
|
||||
removeLeft: true,
|
||||
removeRight: true,
|
||||
context: context,
|
||||
child: bottomNavigationBar,
|
||||
child: pageView,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return pageView;
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
MediaQuery.removePadding(
|
||||
removeTop: true,
|
||||
removeBottom: false,
|
||||
removeLeft: true,
|
||||
removeRight: true,
|
||||
context: context,
|
||||
child: bottomNavigationBar,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return pageView;
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -183,14 +183,12 @@ class GlobalState {
|
||||
}
|
||||
|
||||
Future<bool?> showMessage({
|
||||
required InlineSpan message,
|
||||
BuildContext? context,
|
||||
String? title,
|
||||
required InlineSpan message,
|
||||
String? confirmText,
|
||||
bool cancelable = true,
|
||||
}) async {
|
||||
return await showCommonDialog<bool>(
|
||||
context: context,
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
return CommonDialog(
|
||||
@@ -248,12 +246,10 @@ class GlobalState {
|
||||
|
||||
Future<T?> showCommonDialog<T>({
|
||||
required Widget child,
|
||||
BuildContext? context,
|
||||
bool dismissible = true,
|
||||
}) async {
|
||||
return await showModal<T>(
|
||||
useRootNavigator: false,
|
||||
context: context ?? globalState.navigatorKey.currentContext!,
|
||||
context: navigatorKey.currentState!.context,
|
||||
configuration: FadeScaleTransitionConfiguration(
|
||||
barrierColor: Colors.black38,
|
||||
barrierDismissible: dismissible,
|
||||
|
||||
@@ -202,7 +202,7 @@ class TrackerInfoDetailView extends StatelessWidget {
|
||||
return rule;
|
||||
}
|
||||
|
||||
String _getProcessText() {
|
||||
String _getProgressText() {
|
||||
final process = trackerInfo.metadata.process;
|
||||
final uid = trackerInfo.metadata.uid;
|
||||
if (uid != 0) {
|
||||
@@ -297,8 +297,8 @@ class TrackerInfoDetailView extends StatelessWidget {
|
||||
title: appLocalizations.creationTime,
|
||||
desc: trackerInfo.start.showFull,
|
||||
),
|
||||
if (_getProcessText().isNotEmpty)
|
||||
_buildItem(title: appLocalizations.process, desc: _getProcessText()),
|
||||
if (_getProgressText().isNotEmpty)
|
||||
_buildItem(title: appLocalizations.progress, desc: _getProgressText()),
|
||||
_buildItem(
|
||||
title: appLocalizations.networkType,
|
||||
desc: trackerInfo.metadata.network,
|
||||
|
||||
@@ -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.88+2025090701
|
||||
version: 0.8.88+2025090501
|
||||
environment:
|
||||
sdk: '>=3.8.0 <4.0.0'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user