diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 5164930..0b8db24 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -4,6 +4,8 @@ on: push: tags: - 'v*' +env: + IS_STABLE: ${{ !contains(github.ref, '-') }} jobs: build: @@ -74,7 +76,7 @@ jobs: run: flutter pub get - name: Setup - run: dart setup.dart ${{ matrix.platform }} ${{ matrix.arch && format('--arch {0}', matrix.arch) }} + run: dart setup.dart ${{ matrix.platform }} ${{ matrix.arch && format('--arch {0}', matrix.arch) }} ${{ env.IS_STABLE == 'true' && '--env stable' || '' }} - name: Upload uses: actions/upload-artifact@v4 @@ -88,14 +90,13 @@ jobs: needs: [ build ] steps: - name: Checkout - if: ${{ !contains(github.ref, '+') }} uses: actions/checkout@v4 + if: ${{ env.IS_STABLE == 'true' }} with: fetch-depth: 0 ref: refs/heads/main - - name: Generate - if: ${{ !contains(github.ref, '+') }} + if: ${{ env.IS_STABLE == 'true' }} run: | tags=($(git tag --merged $(git rev-parse HEAD) --sort=-creatordate)) preTag=$(grep -oP '^## \K.*' CHANGELOG.md | head -n 1) @@ -127,7 +128,7 @@ jobs: cat NEW_CHANGELOG.md > CHANGELOG.md - name: Commit - if: ${{ !contains(github.ref, '+') }} + if: ${{ env.IS_STABLE == 'true' }} run: | git add CHANGELOG.md if ! git diff --cached --quiet; then @@ -206,7 +207,7 @@ jobs: run: | python -m pip install --upgrade pip pip install requests - python release.py + python release_telegram.py - name: Patch release.md run: | @@ -214,21 +215,21 @@ jobs: sed "s|VERSION|$version|g" ./.github/release_template.md >> release.md - name: Release - if: ${{ !contains(github.ref, '+') }} + if: ${{ env.IS_STABLE == 'true' }} uses: softprops/action-gh-release@v2 with: files: ./dist/* body_path: './release.md' - name: Create Fdroid Source Dir - if: ${{ !contains(github.ref, '+') }} + if: ${{ env.IS_STABLE == 'true' }} run: | mkdir -p ./tmp cp ./dist/*android-arm64-v8a* ./tmp/ || true echo "Files copied successfully" - name: Push to fdroid repo - if: ${{ !contains(github.ref, '+') }} + if: ${{ env.IS_STABLE == 'true' }} uses: cpina/github-action-push-to-another-repository@v1.7.2 env: SSH_DEPLOY_KEY: ${{ secrets.SSH_DEPLOY_KEY }} @@ -238,7 +239,7 @@ jobs: destination-repository-name: FlClash-fdroid-repo user-name: 'github-actions[bot]' user-email: 'github-actions[bot]@users.noreply.github.com' - target-branch: action-pr + target-branch: main commit-message: Update from ${{ github.ref_name }} target-directory: /tmp/ diff --git a/.gitmodules b/.gitmodules index 7577fac..70bf7cd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,7 @@ [submodule "core/Clash.Meta"] path = core/Clash.Meta url = git@github.com:chen08209/Clash.Meta.git - branch = FlClash-Alpha + branch = FlClash [submodule "plugins/flutter_distributor"] path = plugins/flutter_distributor url = git@github.com:chen08209/flutter_distributor.git diff --git a/analysis_options.yaml b/analysis_options.yaml index 0fbea86..f9b3034 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,8 +1 @@ include: package:flutter_lints/flutter.yaml - -linter: - rules: - -analyzer: - plugins: - - custom_lint diff --git a/android/app/build.gradle b/android/app/build.gradle index fdf1998..bc8d1d0 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -33,7 +33,7 @@ def isRelease = defStoreFile.exists() && defStorePassword != null && defKeyAlias android { namespace "com.follow.clash" - compileSdkVersion 34 + compileSdkVersion 35 ndkVersion "27.1.12297006" compileOptions { @@ -63,7 +63,7 @@ android { defaultConfig { applicationId "com.follow.clash" minSdkVersion 21 - targetSdkVersion 34 + targetSdkVersion 35 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 185ac9c..606a14a 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -10,14 +10,12 @@ - - + + @@ -64,7 +62,9 @@ - + @@ -138,7 +137,7 @@ + android:foregroundServiceType="dataSync"> diff --git a/android/app/src/main/kotlin/com/follow/clash/FlClashApplication.kt b/android/app/src/main/kotlin/com/follow/clash/FlClashApplication.kt index 8326a75..e2b2783 100644 --- a/android/app/src/main/kotlin/com/follow/clash/FlClashApplication.kt +++ b/android/app/src/main/kotlin/com/follow/clash/FlClashApplication.kt @@ -1,7 +1,7 @@ package com.follow.clash; import android.app.Application -import android.content.Context; +import android.content.Context class FlClashApplication : Application() { companion object { diff --git a/android/app/src/main/kotlin/com/follow/clash/GlobalState.kt b/android/app/src/main/kotlin/com/follow/clash/GlobalState.kt index d3fef86..d2b8d4c 100644 --- a/android/app/src/main/kotlin/com/follow/clash/GlobalState.kt +++ b/android/app/src/main/kotlin/com/follow/clash/GlobalState.kt @@ -1,9 +1,7 @@ package com.follow.clash -import android.content.Context import androidx.lifecycle.MutableLiveData import com.follow.clash.plugins.AppPlugin -import com.follow.clash.plugins.ServicePlugin import com.follow.clash.plugins.TilePlugin import com.follow.clash.plugins.VpnPlugin import io.flutter.FlutterInjector diff --git a/android/app/src/main/kotlin/com/follow/clash/plugins/AppPlugin.kt b/android/app/src/main/kotlin/com/follow/clash/plugins/AppPlugin.kt index 8683291..12bdb39 100644 --- a/android/app/src/main/kotlin/com/follow/clash/plugins/AppPlugin.kt +++ b/android/app/src/main/kotlin/com/follow/clash/plugins/AppPlugin.kt @@ -291,16 +291,18 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware private fun getPackages(): List { val packageManager = FlClashApplication.getAppContext().packageManager if (packages.isNotEmpty()) return packages - packageManager?.getInstalledPackages(PackageManager.GET_META_DATA)?.filter { - it.packageName != FlClashApplication.getAppContext().packageName - || it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true - || it.packageName == "android" + packageManager?.getInstalledPackages(PackageManager.GET_META_DATA or PackageManager.GET_PERMISSIONS) + ?.filter { + it.packageName != FlClashApplication.getAppContext().packageName && ( + it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true + || it.packageName == "android" + ) - }?.map { + }?.map { Package( packageName = it.packageName, - label = it.applicationInfo.loadLabel(packageManager).toString(), - isSystem = (it.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) == 1, + label = it.applicationInfo?.loadLabel(packageManager).toString(), + isSystem = (it.applicationInfo?.flags?.and(ApplicationInfo.FLAG_SYSTEM)) == 1, lastUpdateTime = it.lastUpdateTime ) }?.let { packages.addAll(it) } @@ -353,7 +355,7 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware } suspend fun getText(text: String): String? { - return withContext(Dispatchers.Default){ + return withContext(Dispatchers.Default) { channel.awaitResult("getText", text) } } @@ -391,31 +393,33 @@ class AppPlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware }.forEach { if (it.name.matches(chinaAppRegex)) return true } - ZipFile(File(packageInfo.applicationInfo.publicSourceDir)).use { - for (packageEntry in it.entries()) { - if (packageEntry.name.startsWith("firebase-")) return false - } - for (packageEntry in it.entries()) { - if (!(packageEntry.name.startsWith("classes") && packageEntry.name.endsWith( - ".dex" - )) - ) { - continue + packageInfo.applicationInfo?.publicSourceDir?.let { + ZipFile(File(it)).use { + for (packageEntry in it.entries()) { + if (packageEntry.name.startsWith("firebase-")) return false } - if (packageEntry.size > 15000000) { - return true - } - val input = it.getInputStream(packageEntry).buffered() - val dexFile = try { - DexBackedDexFile.fromInputStream(null, input) - } catch (e: Exception) { - return false - } - for (clazz in dexFile.classes) { - val clazzName = - clazz.type.substring(1, clazz.type.length - 1).replace("/", ".") - .replace("$", ".") - if (clazzName.matches(chinaAppRegex)) return true + for (packageEntry in it.entries()) { + if (!(packageEntry.name.startsWith("classes") && packageEntry.name.endsWith( + ".dex" + )) + ) { + continue + } + if (packageEntry.size > 15000000) { + return true + } + val input = it.getInputStream(packageEntry).buffered() + val dexFile = try { + DexBackedDexFile.fromInputStream(null, input) + } catch (e: Exception) { + return false + } + for (clazz in dexFile.classes) { + val clazzName = + clazz.type.substring(1, clazz.type.length - 1).replace("/", ".") + .replace("$", ".") + if (clazzName.matches(chinaAppRegex)) return true + } } } } diff --git a/android/app/src/main/kotlin/com/follow/clash/plugins/ServicePlugin.kt b/android/app/src/main/kotlin/com/follow/clash/plugins/ServicePlugin.kt index 2b768c5..2f7ef69 100644 --- a/android/app/src/main/kotlin/com/follow/clash/plugins/ServicePlugin.kt +++ b/android/app/src/main/kotlin/com/follow/clash/plugins/ServicePlugin.kt @@ -1,6 +1,5 @@ package com.follow.clash.plugins -import com.follow.clash.FlClashApplication import com.follow.clash.GlobalState import com.follow.clash.models.VpnOptions import com.google.gson.Gson diff --git a/android/app/src/main/kotlin/com/follow/clash/plugins/VpnPlugin.kt b/android/app/src/main/kotlin/com/follow/clash/plugins/VpnPlugin.kt index a5f2ffd..c4f6e7e 100644 --- a/android/app/src/main/kotlin/com/follow/clash/plugins/VpnPlugin.kt +++ b/android/app/src/main/kotlin/com/follow/clash/plugins/VpnPlugin.kt @@ -10,7 +10,6 @@ import android.net.NetworkCapabilities import android.net.NetworkRequest import android.os.Build import android.os.IBinder -import android.util.Log import androidx.core.content.getSystemService import com.follow.clash.FlClashApplication import com.follow.clash.GlobalState diff --git a/android/app/src/main/kotlin/com/follow/clash/services/FlClashService.kt b/android/app/src/main/kotlin/com/follow/clash/services/FlClashService.kt index d46a9e3..fa0ba54 100644 --- a/android/app/src/main/kotlin/com/follow/clash/services/FlClashService.kt +++ b/android/app/src/main/kotlin/com/follow/clash/services/FlClashService.kt @@ -7,7 +7,7 @@ import android.app.NotificationManager import android.app.PendingIntent import android.app.Service import android.content.Intent -import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE +import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC import android.os.Binder import android.os.Build import android.os.IBinder @@ -87,6 +87,7 @@ class FlClashService : Service(), BaseServiceInterface { } } } + private suspend fun getNotificationBuilder(): NotificationCompat.Builder { return notificationBuilderDeferred.await() } @@ -100,7 +101,8 @@ class FlClashService : Service(), BaseServiceInterface { } } - @SuppressLint("ForegroundServiceType", "WrongConstant") + + @SuppressLint("ForegroundServiceType") override suspend fun startForeground(title: String, content: String) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val manager = getSystemService(NotificationManager::class.java) @@ -116,7 +118,11 @@ class FlClashService : Service(), BaseServiceInterface { .setContentTitle(title) .setContentText(content).build() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { - startForeground(notificationId, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE) + try { + startForeground(notificationId, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC) + } catch (_: Exception) { + startForeground(notificationId, notification) + } } else { startForeground(notificationId, notification) } diff --git a/android/app/src/main/kotlin/com/follow/clash/services/FlClashVpnService.kt b/android/app/src/main/kotlin/com/follow/clash/services/FlClashVpnService.kt index 7f097ae..37d39a0 100644 --- a/android/app/src/main/kotlin/com/follow/clash/services/FlClashVpnService.kt +++ b/android/app/src/main/kotlin/com/follow/clash/services/FlClashVpnService.kt @@ -6,7 +6,7 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.content.Intent -import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE +import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC import android.net.ProxyInfo import android.net.VpnService import android.os.Binder @@ -45,9 +45,16 @@ class FlClashVpnService : VpnService(), BaseServiceInterface { addAddress(cidr.address, cidr.prefixLength) val routeAddress = options.getIpv4RouteAddress() if (routeAddress.isNotEmpty()) { - routeAddress.forEach { i -> - Log.d("addRoute4", "address: ${i.address} prefixLength:${i.prefixLength}") - addRoute(i.address, i.prefixLength) + try { + routeAddress.forEach { i -> + Log.d( + "addRoute4", + "address: ${i.address} prefixLength:${i.prefixLength}" + ) + addRoute(i.address, i.prefixLength) + } + } catch (_: Exception) { + addRoute("0.0.0.0", 0) } } else { addRoute("0.0.0.0", 0) @@ -58,9 +65,16 @@ class FlClashVpnService : VpnService(), BaseServiceInterface { addAddress(cidr.address, cidr.prefixLength) val routeAddress = options.getIpv6RouteAddress() if (routeAddress.isNotEmpty()) { - routeAddress.forEach { i -> - Log.d("addRoute6", "address: ${i.address} prefixLength:${i.prefixLength}") - addRoute(i.address, i.prefixLength) + try { + routeAddress.forEach { i -> + Log.d( + "addRoute6", + "address: ${i.address} prefixLength:${i.prefixLength}" + ) + addRoute(i.address, i.prefixLength) + } + } catch (_: Exception) { + addRoute("::", 0) } } else { addRoute("::", 0) @@ -165,7 +179,7 @@ class FlClashVpnService : VpnService(), BaseServiceInterface { return notificationBuilderDeferred.await() } - @SuppressLint("ForegroundServiceType", "WrongConstant") + @SuppressLint("ForegroundServiceType") override suspend fun startForeground(title: String, content: String) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val manager = getSystemService(NotificationManager::class.java) @@ -182,7 +196,11 @@ class FlClashVpnService : VpnService(), BaseServiceInterface { .setContentText(content) .build() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { - startForeground(notificationId, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE) + try { + startForeground(notificationId, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC) + } catch (_: Exception) { + startForeground(notificationId, notification) + } } else { startForeground(notificationId, notification) } diff --git a/android/gradle.properties b/android/gradle.properties index 74e2e7b..f1c93ba 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -2,4 +2,4 @@ org.gradle.jvmargs=-Xmx4G android.useAndroidX=true android.enableJetifier=true kotlin_version=1.9.22 -agp_version=8.2.1 +agp_version=8.9.1 diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index d055c79..90df0e8 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,5 +2,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip diff --git a/assets/fonts/JetBrainsMono-Regular.ttf b/assets/fonts/JetBrainsMono-Regular.ttf new file mode 100644 index 0000000..dff66cc Binary files /dev/null and b/assets/fonts/JetBrainsMono-Regular.ttf differ diff --git a/core/Clash.Meta b/core/Clash.Meta index 76b0d7e..f19dad5 160000 --- a/core/Clash.Meta +++ b/core/Clash.Meta @@ -1 +1 @@ -Subproject commit 76b0d7e8bcdaf952c35d71e6852866414a7e1f6e +Subproject commit f19dad529f7d8ac652053f9d090e6780e199eab2 diff --git a/core/common.go b/core/common.go index 8ae5f0d..e34eb7b 100644 --- a/core/common.go +++ b/core/common.go @@ -28,8 +28,12 @@ import ( "sync" ) -func splitByComma(s string) interface{} { - parts := strings.Split(s, ",") +func splitByMultipleSeparators(s string) interface{} { + isSeparator := func(r rune) bool { + return r == ',' || r == ' ' || r == ';' + } + + parts := strings.FieldsFunc(s, isSeparator) if len(parts) > 1 { return parts } @@ -168,18 +172,18 @@ func decorationConfig(profileId string, cfg config.RawConfig) *config.RawConfig return prof } -func genHosts(hosts, patchHosts map[string]any) { +func attachHosts(hosts, patchHosts map[string]any) { for k, v := range patchHosts { if str, ok := v.(string); ok { - hosts[k] = splitByComma(str) + hosts[k] = splitByMultipleSeparators(str) } } } -func modPatchDns(dns *config.RawDNS) { +func updatePatchDns(dns config.RawDNS) { for pair := dns.NameServerPolicy.Oldest(); pair != nil; pair = pair.Next() { if str, ok := pair.Value.(string); ok { - dns.NameServerPolicy.Set(pair.Key, splitByComma(str)) + dns.NameServerPolicy.Set(pair.Key, splitByMultipleSeparators(str)) } } } @@ -191,26 +195,25 @@ func trimArr(arr []string) (r []string) { return } -func overrideRules(rules *[]string) { - var target = "" - for _, line := range *rules { +func overrideRules(rules, patchRules []string) []string { + target := "" + for _, line := range rules { rule := trimArr(strings.Split(line, ",")) - l := len(rule) - if l != 2 { - return + if len(rule) != 2 { + continue } - if strings.ToUpper(rule[0]) == "MATCH" { + if strings.EqualFold(rule[0], "MATCH") { target = rule[1] break } } if target == "" { - return + return rules } - var rulesExt = lo.Map(ips, func(ip string, index int) string { - return fmt.Sprintf("DOMAIN %s %s", ip, target) + rulesExt := lo.Map(ips, func(ip string, _ int) string { + return fmt.Sprintf("DOMAIN,%s,%s", ip, target) }) - *rules = append(rulesExt, *rules...) + return append(append(rulesExt, patchRules...), rules...) } func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfig) { @@ -244,16 +247,20 @@ func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfi for idx := range targetConfig.ProxyGroup { targetConfig.ProxyGroup[idx]["url"] = "" } - genHosts(targetConfig.Hosts, patchConfig.Hosts) + attachHosts(targetConfig.Hosts, patchConfig.Hosts) if configParams.OverrideDns { - modPatchDns(&patchConfig.DNS) + updatePatchDns(patchConfig.DNS) targetConfig.DNS = patchConfig.DNS } else { if targetConfig.DNS.Enable == false { targetConfig.DNS.Enable = true } } - overrideRules(&targetConfig.Rule) + if configParams.OverrideRule { + targetConfig.Rule = overrideRules(patchConfig.Rule, []string{}) + } else { + targetConfig.Rule = overrideRules(targetConfig.Rule, patchConfig.Rule) + } } func patchConfig() { @@ -267,6 +274,7 @@ func patchConfig() { dialer.DefaultInterface.Store(general.Interface) adapter.UnifiedDelay.Store(general.UnifiedDelay) tunnel.SetMode(general.Mode) + tunnel.UpdateRules(currentConfig.Rules, currentConfig.SubRules, currentConfig.RuleProviders) log.SetLevel(general.LogLevel) resolver.DisableIPv6 = !general.IPv6 diff --git a/core/constant.go b/core/constant.go index a9355f6..a4cb3f7 100644 --- a/core/constant.go +++ b/core/constant.go @@ -13,6 +13,7 @@ type ConfigExtendedParams struct { SelectedMap map[string]string `json:"selected-map"` TestURL *string `json:"test-url"` OverrideDns bool `json:"override-dns"` + OverrideRule bool `json:"override-rule"` } type GenerateConfigParams struct { diff --git a/core/go.mod b/core/go.mod index 30037f0..aeb042f 100644 --- a/core/go.mod +++ b/core/go.mod @@ -21,7 +21,7 @@ require ( github.com/coreos/go-iptables v0.8.0 // indirect github.com/dlclark/regexp2 v1.11.5 // indirect github.com/ebitengine/purego v0.8.2 // indirect - github.com/enfein/mieru/v3 v3.11.2 // indirect + github.com/enfein/mieru/v3 v3.13.0 // indirect github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 // indirect github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect @@ -50,21 +50,22 @@ require ( github.com/mdlayher/netlink v1.7.2 // indirect github.com/mdlayher/socket v0.4.1 // indirect github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab // indirect + github.com/metacubex/bart v0.19.0 // indirect github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 // indirect github.com/metacubex/chacha v0.1.1 // indirect github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect - github.com/metacubex/gvisor v0.0.0-20241126021258-5b028898cc5a // indirect + github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b // indirect github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996 // indirect github.com/metacubex/randv2 v0.2.0 // indirect github.com/metacubex/reality v0.0.0-20250219003814-74e8d7850629 // indirect github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925 // indirect github.com/metacubex/sing-shadowsocks v0.2.8 // indirect github.com/metacubex/sing-shadowsocks2 v0.2.2 // indirect - github.com/metacubex/sing-tun v0.4.5 // indirect + github.com/metacubex/sing-tun v0.4.6-0.20250312042506-6d3b4dc05c04 // indirect github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 // indirect github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 // indirect github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 // indirect - github.com/metacubex/utls v1.6.6 // indirect + github.com/metacubex/utls v1.6.8-alpha.4 // indirect github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 // indirect github.com/miekg/dns v1.1.63 // indirect github.com/mroth/weightedrand/v2 v2.1.0 // indirect @@ -74,7 +75,7 @@ require ( github.com/oschwald/maxminddb-golang v1.12.0 // indirect github.com/pierrec/lz4/v4 v4.1.14 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect - github.com/puzpuzpuz/xsync/v3 v3.5.0 // indirect + github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/sagernet/cors v1.2.1 // indirect diff --git a/core/go.sum b/core/go.sum index 63b60b7..4a7fccb 100644 --- a/core/go.sum +++ b/core/go.sum @@ -28,8 +28,8 @@ github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZ github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= -github.com/enfein/mieru/v3 v3.11.2 h1:06KyGbXiiGz2nSHLJDOOkztAVY3cRr3wBMOpYxPotTo= -github.com/enfein/mieru/v3 v3.11.2/go.mod h1:XvVfNsM78lUMSlJJKXJZ0Hn3lAB2o/ETXTbb84x5egw= +github.com/enfein/mieru/v3 v3.13.0 h1:eGyxLGkb+lut9ebmx+BGwLJ5UMbEc/wGIYO0AXEKy98= +github.com/enfein/mieru/v3 v3.13.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM= github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 h1:/5RkVc9Rc81XmMyVqawCiDyrBHZbLAZgTTCqou4mwj8= github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I= github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g= @@ -97,14 +97,16 @@ github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab h1:Chbw+/31UC14YFNr78pESt5Vowlc62zziw05JCUqoL4= github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab/go.mod h1:xVKK8jC5Sd3hfh7WjmCq+HorehIbrBijaUWmcuKjPcI= +github.com/metacubex/bart v0.19.0 h1:XQ9AJeI+WO+phRPkUOoflAFwlqDJnm5BPQpixciJQBY= +github.com/metacubex/bart v0.19.0/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI= github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig= github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro= github.com/metacubex/chacha v0.1.1 h1:OHIv11Nd9CISAIzegpjfupIoZp9DYm6uQw41RxvmU/c= github.com/metacubex/chacha v0.1.1/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88= -github.com/metacubex/gvisor v0.0.0-20241126021258-5b028898cc5a h1:cZ6oNVrsmsi3SNlnSnRio4zOgtQq+/XidwsaNgKICcg= -github.com/metacubex/gvisor v0.0.0-20241126021258-5b028898cc5a/go.mod h1:xBw/SYJPgUMPQ1tklV/brGn2nxhfr3BnvBzNlyi4Nic= +github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b h1:RUh4OdVPz/jDrM9MQ2ySuqu2aeBqcA8rtfWUYLZ8RtI= +github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU= github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996 h1:B+AP/Pj2/jBDS/kCYjz/x+0BCOKfd2VODYevyeIt+Ds= github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996/go.mod h1:ExVjGyEwTUjCFqx+5uxgV7MOoA3fZI+th4D40H35xmY= github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs= @@ -117,16 +119,16 @@ github.com/metacubex/sing-shadowsocks v0.2.8 h1:wIhlaigswzjPw4hej75sEvWte3QR0+AJ github.com/metacubex/sing-shadowsocks v0.2.8/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0= github.com/metacubex/sing-shadowsocks2 v0.2.2 h1:eaf42uVx4Lr21S6MDYs0ZdTvGA0GEhDpb9no4+gdXPo= github.com/metacubex/sing-shadowsocks2 v0.2.2/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q= -github.com/metacubex/sing-tun v0.4.5 h1:kWSyQzuzHI40r50OFBczfWIDvMBMy1RIk+JsXeBPRB0= -github.com/metacubex/sing-tun v0.4.5/go.mod h1:V0N4rr0dWPBEE20ESkTXdbtx2riQYcb6YtwC5w/9wl0= +github.com/metacubex/sing-tun v0.4.6-0.20250312042506-6d3b4dc05c04 h1:B211C+i/I8CWf4I/BaAV0mmkEHrDBJ0XR9EWxjPbFEg= +github.com/metacubex/sing-tun v0.4.6-0.20250312042506-6d3b4dc05c04/go.mod h1:V0N4rr0dWPBEE20ESkTXdbtx2riQYcb6YtwC5w/9wl0= github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 h1:zZp5uct9+/0Hb1jKGyqDjCU4/72t43rs7qOq3Rc9oU8= github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82/go.mod h1:nE7Mdzj/QUDwgRi/8BASPtsxtIFZTHA4Yst5GgwbGCQ= github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 h1:Z6bNy0HLTjx6BKIkV48sV/yia/GP8Bnyb5JQuGgSGzg= github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589/go.mod h1:4NclTLIZuk+QkHVCGrP87rHi/y8YjgPytxTgApJNMhc= github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY= github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= -github.com/metacubex/utls v1.6.6 h1:3D12YKHTf2Z41UPhQU2dWerNWJ5TVQD9gKoQ+H+iLC8= -github.com/metacubex/utls v1.6.6/go.mod h1:+WLFUnXjcpdxXCnyX25nggw8C6YonZ8zOK2Zm/oRvdo= +github.com/metacubex/utls v1.6.8-alpha.4 h1:5EvsCHxDNneaOtAyc8CztoNSpmonLvkvuGs01lIeeEI= +github.com/metacubex/utls v1.6.8-alpha.4/go.mod h1:MEZ5WO/VLKYs/s/dOzEK/mlXOQxc04ESeLzRgjmLYtk= github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ= github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y= github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= @@ -153,8 +155,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/puzpuzpuz/xsync/v3 v3.5.0 h1:i+cMcpEDY1BkNm7lPDkCtE4oElsYLn+EKF8kAu2vXT4= -github.com/puzpuzpuz/xsync/v3 v3.5.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= +github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg= +github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= diff --git a/lib/application.dart b/lib/application.dart index 6cf0f19..416531a 100644 --- a/lib/application.dart +++ b/lib/application.dart @@ -61,16 +61,6 @@ class ApplicationState extends ConsumerState { _autoUpdateGroupTask(); _autoUpdateProfilesTask(); globalState.appController = AppController(context, ref); - globalState.measure = Measure.of(context); - // ref.listenManual(themeSettingProvider.select((state) => state.fontFamily), - // (prev, next) { - // if (prev != next) { - // globalState.measure = Measure.of( - // context, - // fontFamily: next.value, - // ); - // } - // }, fireImmediately: true); WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { final currentContext = globalState.navigatorKey.currentContext; if (currentContext != null) { @@ -98,7 +88,7 @@ class ApplicationState extends ConsumerState { }); } - _buildPlatformWrap(Widget child) { + _buildPlatformState(Widget child) { if (system.isDesktop) { return WindowManager( child: TrayManager( @@ -117,18 +107,7 @@ class ApplicationState extends ConsumerState { ); } - _buildPage(Widget page) { - if (system.isDesktop) { - return WindowHeaderContainer( - child: page, - ); - } - return VpnManager( - child: page, - ); - } - - _buildWrap(Widget child) { + _buildState(Widget child) { return AppStateManager( child: ClashManager( child: ConnectivityManager( @@ -142,6 +121,25 @@ class ApplicationState extends ConsumerState { ); } + _buildPlatformApp(Widget child) { + if (system.isDesktop) { + return WindowHeaderContainer( + child: child, + ); + } + return VpnManager( + child: child, + ); + } + + _buildApp(Widget child) { + return MessageManager( + child: ThemeManager( + child: child, + ), + ); + } + _updateSystemColorSchemes( ColorScheme? lightDynamic, ColorScheme? darkDynamic, @@ -157,8 +155,8 @@ class ApplicationState extends ConsumerState { @override Widget build(context) { - return _buildPlatformWrap( - _buildWrap( + return _buildPlatformState( + _buildState( Consumer( builder: (_, ref, child) { final locale = @@ -168,6 +166,7 @@ class ApplicationState extends ConsumerState { builder: (lightDynamic, darkDynamic) { _updateSystemColorSchemes(lightDynamic, darkDynamic); return MaterialApp( + debugShowCheckedModeBanner: false, navigatorKey: globalState.navigatorKey, localizationsDelegates: const [ AppLocalizations.delegate, @@ -176,14 +175,9 @@ class ApplicationState extends ConsumerState { GlobalWidgetsLocalizations.delegate ], builder: (_, child) { - return MessageManager( - child: LayoutBuilder( - builder: (_, container) { - globalState.appController.updateViewWidth( - container.maxWidth, - ); - return _buildPage(child!); - }, + return AppEnvManager( + child: _buildPlatformApp( + _buildApp(child!), ), ); }, diff --git a/lib/clash/core.dart b/lib/clash/core.dart index 7ea6269..b5ae232 100644 --- a/lib/clash/core.dart +++ b/lib/clash/core.dart @@ -240,7 +240,7 @@ class ClashCore { if (res.isEmpty) { return null; } - return ClashConfigSnippet.fromJson(json.decode(res)); + return Isolate.run(() => ClashConfigSnippet.fromJson(json.decode(res))); } resetTraffic() { diff --git a/lib/common/color.dart b/lib/common/color.dart index 010450f..f81b55e 100644 --- a/lib/common/color.dart +++ b/lib/common/color.dart @@ -1,20 +1,40 @@ import 'package:flutter/material.dart'; extension ColorExtension on Color { - Color get toLight { - return withOpacity(0.8); + Color get opacity80 { + return withAlpha(204); } - Color get toLighter { - return withOpacity(0.6); + Color get opacity60 { + return withAlpha(153); } - Color get toSoft { - return withOpacity(0.15); + Color get opacity50 { + return withAlpha(128); } - Color get toLittle { - return withOpacity(0.03); + Color get opacity38 { + return withAlpha(97); + } + + Color get opacity30 { + return withAlpha(77); + } + + Color get opacity15 { + return withAlpha(38); + } + + Color get opacity10 { + return withAlpha(15); + } + + Color get opacity3 { + return withAlpha(76); + } + + Color get opacity0 { + return withAlpha(0); } Color darken([double amount = .1]) { diff --git a/lib/common/constant.dart b/lib/common/constant.dart index d751c6c..a71dfc3 100644 --- a/lib/common/constant.dart +++ b/lib/common/constant.dart @@ -21,6 +21,11 @@ const baseInfoEdgeInsets = EdgeInsets.symmetric( vertical: 16, horizontal: 16, ); + +double textScaleFactor = min( + WidgetsBinding.instance.platformDispatcher.textScaleFactor, + 1.2, +); const httpTimeoutDuration = Duration(milliseconds: 5000); const moreDuration = Duration(milliseconds: 100); const animateDuration = Duration(milliseconds: 100); @@ -46,7 +51,7 @@ const defaultExternalController = "127.0.0.1:9090"; const maxMobileWidth = 600; const maxLaptopWidth = 840; const defaultTestUrl = "https://www.gstatic.com/generate_204"; -final filter = ImageFilter.blur( +final commonFilter = ImageFilter.blur( sigmaX: 5, sigmaY: 5, tileMode: TileMode.mirror, @@ -76,7 +81,7 @@ const viewModeColumnsMap = { const defaultPrimaryColor = Colors.brown; double getWidgetHeight(num lines) { - return max(lines * 84 + (lines - 1) * 16, 0); + return max(lines * 84 * textScaleFactor + (lines - 1) * 16, 0); } final mainIsolate = "FlClashMainIsolate"; diff --git a/lib/common/function.dart b/lib/common/function.dart index 8fd8a9f..a1e1770 100644 --- a/lib/common/function.dart +++ b/lib/common/function.dart @@ -1,7 +1,7 @@ import 'dart:async'; class Debouncer { - final Map _operations = {}; + final Map _operations = {}; call( dynamic tag, @@ -28,14 +28,15 @@ class Debouncer { cancel(dynamic tag) { _operations[tag]?.cancel(); + _operations[tag] = null; } } class Throttler { - final Map _operations = {}; + final Map _operations = {}; call( - String tag, + dynamic tag, Function func, { List? args, Duration duration = const Duration(milliseconds: 600), @@ -60,6 +61,7 @@ class Throttler { cancel(dynamic tag) { _operations[tag]?.cancel(); + _operations[tag] = null; } } diff --git a/lib/common/iterable.dart b/lib/common/iterable.dart index 802423c..aac4471 100644 --- a/lib/common/iterable.dart +++ b/lib/common/iterable.dart @@ -65,3 +65,12 @@ extension DoubleListExt on List { return -1; } } + +extension MapExt on Map { + getCacheValue(K key, V defaultValue) { + if (this[key] == null) { + this[key] = defaultValue; + } + return this[key]; + } +} diff --git a/lib/common/list.dart b/lib/common/list.dart index 531d56a..ca8add7 100644 --- a/lib/common/list.dart +++ b/lib/common/list.dart @@ -32,7 +32,7 @@ class FixedList { } class FixedMap { - final int maxSize; + int maxSize; final Map _map = {}; final Queue _queue = Queue(); @@ -45,6 +45,7 @@ class FixedMap { } _map[key] = value; _queue.add(key); + return value; } clear() { @@ -52,8 +53,13 @@ class FixedMap { _queue.clear(); } + updateMaxSize(int size){ + maxSize = size; + } + V? get(K key) => _map[key]; + bool containsKey(K key) => _map.containsKey(key); int get length => _map.length; diff --git a/lib/common/measure.dart b/lib/common/measure.dart index 0415d95..736fdec 100644 --- a/lib/common/measure.dart +++ b/lib/common/measure.dart @@ -5,13 +5,11 @@ import 'package:flutter/material.dart'; class Measure { final TextScaler _textScale; final BuildContext context; - final String? _fontFamily; - Measure.of(this.context, {String? fontFamily}) + Measure.of(this.context) : _textScale = TextScaler.linear( - WidgetsBinding.instance.platformDispatcher.textScaleFactor, - ), - _fontFamily = fontFamily ?? ""; + textScaleFactor, + ); Size computeTextSize( Text text, { @@ -20,9 +18,7 @@ class Measure { final textPainter = TextPainter( text: TextSpan( text: text.data, - style: text.style?.copyWith( - fontFamily: _fontFamily, - ), + style: text.style, ), maxLines: text.maxLines, textScaler: _textScale, diff --git a/lib/common/mixin.dart b/lib/common/mixin.dart index e29efbb..7fe72fb 100644 --- a/lib/common/mixin.dart +++ b/lib/common/mixin.dart @@ -1,3 +1,4 @@ +import 'package:fl_clash/models/models.dart'; import 'package:flutter/material.dart'; import 'package:riverpod/riverpod.dart'; import 'context.dart'; @@ -29,8 +30,14 @@ mixin PageMixin on State { final commonScaffoldState = context.commonScaffoldState; commonScaffoldState?.actions = actions; commonScaffoldState?.floatingActionButton = floatingActionButton; - commonScaffoldState?.onSearch = onSearch; commonScaffoldState?.onKeywordsUpdate = onKeywordsUpdate; + commonScaffoldState?.updateSearchState( + (_) => onSearch != null + ? AppBarSearchState( + onSearch: onSearch!, + ) + : null, + ); }); } diff --git a/lib/common/navigator.dart b/lib/common/navigator.dart index b927c34..60e13e5 100644 --- a/lib/common/navigator.dart +++ b/lib/common/navigator.dart @@ -70,7 +70,7 @@ class CommonRoute extends MaterialPageRoute { Duration get transitionDuration => const Duration(milliseconds: 500); @override - Duration get reverseTransitionDuration => const Duration(milliseconds: 250); + Duration get reverseTransitionDuration => const Duration(milliseconds: 500); } final Animatable _kRightMiddleTween = Tween( @@ -194,7 +194,7 @@ class _CommonPageTransitionState extends State { _primaryPositionCurve = CurvedAnimation( parent: widget.primaryRouteAnimation, curve: Curves.fastEaseInToSlowEaseOut, - reverseCurve: Curves.easeInOut, + reverseCurve: Curves.fastEaseInToSlowEaseOut.flipped, ); _secondaryPositionCurve = CurvedAnimation( parent: widget.secondaryRouteAnimation, @@ -218,9 +218,8 @@ class _CommonPageTransitionState extends State { begin: const _CommonEdgeShadowDecoration(), end: _CommonEdgeShadowDecoration( [ - widget.context.colorScheme.inverseSurface.withOpacity( - 0.06, - ), + widget.context.colorScheme.inverseSurface + .withValues(alpha: 0.02), Colors.transparent, ], ), @@ -274,7 +273,7 @@ class _CommonEdgeShadowPainter extends BoxPainter { return; } - final double shadowWidth = 0.03 * configuration.size!.width; + final double shadowWidth = 1 * configuration.size!.width; final double shadowHeight = configuration.size!.height; final double bandWidth = shadowWidth / (colors.length - 1); diff --git a/lib/common/other.dart b/lib/common/other.dart index 4be0653..3bdfaad 100644 --- a/lib/common/other.dart +++ b/lib/common/other.dart @@ -241,11 +241,6 @@ class Other { return "${appName}_${DateTime.now().show}.log"; } - Size getScreenSize() { - final view = WidgetsBinding.instance.platformDispatcher.views.first; - return view.physicalSize / view.devicePixelRatio; - } - Future getLocalIpAddress() async { List interfaces = await NetworkInterface.list( includeLoopback: false, diff --git a/lib/common/protocol.dart b/lib/common/protocol.dart index a4fedf2..129fb73 100644 --- a/lib/common/protocol.dart +++ b/lib/common/protocol.dart @@ -14,15 +14,13 @@ class Protocol { void register(String scheme) { String protocolRegKey = 'Software\\Classes\\$scheme'; - RegistryValue protocolRegValue = const RegistryValue( + RegistryValue protocolRegValue = RegistryValue.string( 'URL Protocol', - RegistryValueType.string, '', ); String protocolCmdRegKey = 'shell\\open\\command'; - RegistryValue protocolCmdRegValue = RegistryValue( + RegistryValue protocolCmdRegValue = RegistryValue.string( '', - RegistryValueType.string, '"${Platform.resolvedExecutable}" "%1"', ); final regKey = Registry.currentUser.createKey(protocolRegKey); @@ -31,4 +29,4 @@ class Protocol { } } -final protocol = Protocol(); \ No newline at end of file +final protocol = Protocol(); diff --git a/lib/common/render.dart b/lib/common/render.dart index 777ebbc..eb52f96 100644 --- a/lib/common/render.dart +++ b/lib/common/render.dart @@ -22,7 +22,7 @@ class Render { } pause() { - debouncer.call( + throttler.call( DebounceTag.renderPause, _pause, duration: Duration(seconds: 5), @@ -30,11 +30,11 @@ class Render { } resume() { - debouncer.cancel(DebounceTag.renderPause); + throttler.cancel(DebounceTag.renderPause); _resume(); } - void _pause() { + void _pause() async { if (_isPaused) return; _isPaused = true; _beginFrame = _dispatcher.onBeginFrame; diff --git a/lib/common/request.dart b/lib/common/request.dart index bc061da..eee6e5a 100644 --- a/lib/common/request.dart +++ b/lib/common/request.dart @@ -83,13 +83,19 @@ class Request { Future checkIp({CancelToken? cancelToken}) async { for (final source in _ipInfoSources.entries) { try { - final response = await _dio.get>( - source.key, - cancelToken: cancelToken, - options: Options( - responseType: ResponseType.json, - ), - ); + final response = await Dio() + .get>( + source.key, + cancelToken: cancelToken, + options: Options( + responseType: ResponseType.json, + ), + ) + .timeout( + Duration( + seconds: 30, + ), + ); if (response.statusCode != 200 || response.data == null) { continue; } diff --git a/lib/common/scroll.dart b/lib/common/scroll.dart index 13e2521..828bdcc 100644 --- a/lib/common/scroll.dart +++ b/lib/common/scroll.dart @@ -2,6 +2,7 @@ import 'dart:math'; import 'dart:ui'; import 'package:fl_clash/common/common.dart'; +import 'package:fl_clash/widgets/scroll.dart'; import 'package:flutter/material.dart'; class BaseScrollBehavior extends MaterialScrollBehavior { @@ -16,8 +17,6 @@ class BaseScrollBehavior extends MaterialScrollBehavior { }; } -class BaseScrollBehavior2 extends ScrollBehavior {} - class HiddenBarScrollBehavior extends BaseScrollBehavior { @override Widget buildScrollbar( @@ -36,8 +35,7 @@ class ShowBarScrollBehavior extends BaseScrollBehavior { Widget child, ScrollableDetails details, ) { - return Scrollbar( - interactive: true, + return CommonAutoHiddenScrollBar( controller: details.controller, child: child, ); diff --git a/lib/common/text.dart b/lib/common/text.dart index 2488cb2..043df40 100644 --- a/lib/common/text.dart +++ b/lib/common/text.dart @@ -1,15 +1,20 @@ +import 'package:fl_clash/enum/enum.dart'; import 'package:flutter/material.dart'; import 'color.dart'; extension TextStyleExtension on TextStyle { - TextStyle get toLight => copyWith(color: color?.toLight); + TextStyle get toLight => copyWith(color: color?.opacity80); - TextStyle get toLighter => copyWith(color: color?.toLighter); + TextStyle get toLighter => copyWith(color: color?.opacity60); TextStyle get toSoftBold => copyWith(fontWeight: FontWeight.w500); TextStyle get toBold => copyWith(fontWeight: FontWeight.bold); + TextStyle get toJetBrainsMono => copyWith( + fontFamily: FontFamily.jetBrainsMono.value, + ); + TextStyle adjustSize(int size) => copyWith( fontSize: fontSize! + size, ); diff --git a/lib/common/theme.dart b/lib/common/theme.dart new file mode 100644 index 0000000..64fdbd1 --- /dev/null +++ b/lib/common/theme.dart @@ -0,0 +1,39 @@ +import 'package:fl_clash/common/common.dart'; +import 'package:flutter/material.dart'; + +class CommonTheme { + final BuildContext context; + final Map _colorMap; + + CommonTheme.of(this.context) : _colorMap = {}; + + Color get darkenSecondaryContainer { + return _colorMap.getCacheValue( + "darkenSecondaryContainer", + context.colorScheme.secondaryContainer.blendDarken(context, factor: 0.1), + ); + } + + Color get darkenSecondaryContainerLighter { + return _colorMap.getCacheValue( + "darkenSecondaryContainerLighter", + context.colorScheme.secondaryContainer + .blendDarken(context, factor: 0.1) + .opacity60, + ); + } + + Color get darken2SecondaryContainer { + return _colorMap.getCacheValue( + "darken2SecondaryContainer", + context.colorScheme.secondaryContainer.blendDarken(context, factor: 0.2), + ); + } + + Color get darken3PrimaryContainer { + return _colorMap.getCacheValue( + "darken3PrimaryContainer", + context.colorScheme.primaryContainer.blendDarken(context, factor: 0.3), + ); + } +} diff --git a/lib/common/window.dart b/lib/common/window.dart index 384ccc2..2fdbfa1 100755 --- a/lib/common/window.dart +++ b/lib/common/window.dart @@ -21,12 +21,12 @@ class Window { await windowManager.ensureInitialized(); WindowOptions windowOptions = WindowOptions( size: Size(props.width, props.height), - minimumSize: const Size(380, 500), + minimumSize: const Size(380, 400), ); if (!Platform.isMacOS || version > 10) { await windowManager.setTitleBarStyle(TitleBarStyle.hidden); } - if(!Platform.isMacOS){ + if (!Platform.isMacOS) { final left = props.left ?? 0; final top = props.top ?? 0; final right = left + props.width; @@ -36,7 +36,7 @@ class Window { } else { final displays = await screenRetriever.getAllDisplays(); final isPositionValid = displays.any( - (display) { + (display) { final displayBounds = Rect.fromLTWH( display.visiblePosition!.dx, display.visiblePosition!.dy, @@ -69,8 +69,10 @@ class Window { await windowManager.setSkipTaskbar(false); } - Future isVisible() async { - return await windowManager.isVisible(); + Future get isVisible async { + final value = await windowManager.isVisible(); + commonPrint.log("window visible check: $value"); + return value; } close() async { diff --git a/lib/controller.dart b/lib/controller.dart index 44b154d..65de038 100644 --- a/lib/controller.dart +++ b/lib/controller.dart @@ -10,12 +10,14 @@ import 'package:fl_clash/common/archive.dart'; import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/providers/providers.dart'; import 'package:fl_clash/state.dart'; +import 'package:fl_clash/widgets/dialog.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:path/path.dart'; import 'package:url_launcher/url_launcher.dart'; import 'common/common.dart'; +import 'fragments/profiles/override_profile.dart'; import 'models/models.dart'; class AppController { @@ -242,6 +244,7 @@ class AppController { } Future updateClashConfig([bool? isPatch]) async { + commonPrint.log("update clash patch: ${isPatch ?? false}"); final commonScaffoldState = globalState.homeScaffoldKey.currentState; if (commonScaffoldState?.mounted != true) return; await commonScaffoldState?.loadingRun(() async { @@ -414,6 +417,9 @@ class AppController { Map? data, bool handleError = false, }) async { + if(globalState.isPre){ + return; + } if (data != null) { final tagName = data['tag_name']; final body = data['body']; @@ -520,37 +526,12 @@ class AppController { _ref.read(delayDataSourceProvider.notifier).setDelay(delay); } - toPage( - int index, { - bool hasAnimate = false, - }) { - final navigations = _ref.read(currentNavigationsStateProvider).value; - if (index > navigations.length - 1) { - return; - } - _ref.read(currentPageLabelProvider.notifier).value = - navigations[index].label; - final isAnimateToPage = _ref.read(appSettingProvider).isAnimateToPage; - final isMobile = - _ref.read(viewWidthProvider.notifier).viewMode == ViewMode.mobile; - if (isAnimateToPage && isMobile || hasAnimate) { - globalState.pageController?.animateToPage( - index, - duration: kTabScrollDuration, - curve: Curves.easeOut, - ); - } else { - globalState.pageController?.jumpToPage(index); - } + toPage(PageLabel pageLabel) { + _ref.read(currentPageLabelProvider.notifier).value = pageLabel; } toProfiles() { - final index = _ref.read(currentNavigationsStateProvider).value.indexWhere( - (element) => element.label == PageLabel.profiles, - ); - if (index != -1) { - toPage(index); - } + toPage(PageLabel.profiles); } initLink() { @@ -587,17 +568,8 @@ class AppController { Future showDisclaimer() async { return await globalState.showCommonDialog( dismissible: false, - child: AlertDialog( - title: Text(appLocalizations.disclaimer), - content: Container( - width: dialogCommonWidth, - constraints: const BoxConstraints(maxHeight: 200), - child: SingleChildScrollView( - child: SelectableText( - appLocalizations.disclaimerDesc, - ), - ), - ), + child: CommonDialog( + title: appLocalizations.disclaimer, actions: [ TextButton( onPressed: () { @@ -615,6 +587,9 @@ class AppController { child: Text(appLocalizations.agree), ) ], + child: SelectableText( + appLocalizations.disclaimerDesc, + ), ), ) ?? false; @@ -680,9 +655,9 @@ class AppController { addProfileFormURL(url); } - updateViewWidth(double width) { + updateViewSize(Size size) { WidgetsBinding.instance.addPostFrameCallback((_) { - _ref.read(viewWidthProvider.notifier).value = width; + _ref.read(viewSizeProvider.notifier).value = size; }); } @@ -741,10 +716,18 @@ class AppController { final providersPath = await appPath.getProvidersPath(profileId); return await Isolate.run(() async { if (profilePath != null) { - await File(profilePath).delete(recursive: true); + final profileFile = File(profilePath); + final isExists = await profileFile.exists(); + if (isExists) { + profileFile.delete(recursive: true); + } } if (providersPath != null) { - await File(providersPath).delete(recursive: true); + final providersFileDir = File(providersPath); + final isExists = await providersFileDir.exists(); + if (isExists) { + providersFileDir.delete(recursive: true); + } } }); } @@ -798,10 +781,10 @@ class AppController { _ref.read(patchClashConfigProvider.notifier).updateState( (state) => state.copyWith(mode: mode), ); - // if (mode == Mode.global) { - // updateCurrentGroupName(GroupName.GLOBAL.name); - // } - // addCheckIpNumDebounce(); + if (mode == Mode.global) { + updateCurrentGroupName(GroupName.GLOBAL.name); + } + addCheckIpNumDebounce(); } updateAutoLaunch() { @@ -813,7 +796,7 @@ class AppController { } updateVisible() async { - final visible = await window?.isVisible(); + final visible = await window?.isVisible; if (visible != null && !visible) { window?.show(); } else { @@ -836,6 +819,38 @@ class AppController { ); } + handleAddOrUpdate(WidgetRef ref, [Rule? rule]) async { + final res = await globalState.showCommonDialog( + child: AddRuleDialog( + rule: rule, + snippet: ref.read( + profileOverrideStateProvider.select( + (state) => state.snippet!, + ), + ), + ), + ); + if (res == null) { + return; + } + ref.read(profileOverrideStateProvider.notifier).updateState( + (state) { + final model = state.copyWith.overrideData!( + rule: state.overrideData!.rule.updateRules( + (rules) { + final index = rules.indexWhere((item) => item.id == res.id); + if (index == -1) { + return List.from([res, ...rules]); + } + return List.from(rules)..[index] = res; + }, + ), + ); + return model; + }, + ); + } + Future exportLogs() async { final logsRaw = _ref.read(logsProvider).list.map( (item) => item.toString(), diff --git a/lib/enum/enum.dart b/lib/enum/enum.dart index b9214db..fe7b2f3 100644 --- a/lib/enum/enum.dart +++ b/lib/enum/enum.dart @@ -220,11 +220,12 @@ enum ProxiesIconStyle { enum FontFamily { twEmoji("Twemoji"), + jetBrainsMono("JetBrainsMono"), icon("Icons"); - final String? value; + final String value; - const FontFamily([this.value]); + const FontFamily(this.value); } enum RouteMode { @@ -384,3 +385,65 @@ enum PageLabel { resources, connections, } + +enum RuleAction { + DOMAIN("DOMAIN"), + DOMAIN_SUFFIX("DOMAIN-SUFFIX"), + DOMAIN_KEYWORD("DOMAIN-KEYWORD"), + DOMAIN_REGEX("DOMAIN-REGEX"), + GEOSITE("GEOSITE"), + IP_CIDR("IP-CIDR"), + IP_CIDR6("IP-CIDR6"), + IP_SUFFIX("IP-SUFFIX"), + IP_ASN("IP-ASN"), + GEOIP("GEOIP"), + SRC_GEOIP("SRC-GEOIP"), + SRC_IP_ASN("SRC-IP-ASN"), + SRC_IP_CIDR("SRC-IP-CIDR"), + SRC_IP_SUFFIX("SRC-IP-SUFFIX"), + DST_PORT("DST-PORT"), + SRC_PORT("SRC-PORT"), + IN_PORT("IN-PORT"), + IN_TYPE("IN-TYPE"), + IN_USER("IN-USER"), + IN_NAME("IN-NAME"), + PROCESS_PATH("PROCESS-PATH"), + PROCESS_PATH_REGEX("PROCESS-PATH-REGEX"), + PROCESS_NAME("PROCESS-NAME"), + PROCESS_NAME_REGEX("PROCESS-NAME-REGEX"), + UID("UID"), + NETWORK("NETWORK"), + DSCP("DSCP"), + RULE_SET("RULE-SET"), + AND("AND"), + OR("OR"), + NOT("NOT"), + SUB_RULE("SUB-RULE"), + MATCH("MATCH"); + + final String value; + + const RuleAction(this.value); +} + +extension RuleActionExt on RuleAction { + bool get hasParams => [ + RuleAction.GEOIP, + RuleAction.IP_ASN, + RuleAction.SRC_IP_ASN, + RuleAction.IP_CIDR, + RuleAction.IP_CIDR6, + RuleAction.IP_SUFFIX, + RuleAction.RULE_SET, + ].contains(this); +} + +enum OverrideRuleType { + override, + added, +} + +enum RuleTarget { + DIRECT, + REJECT, +} diff --git a/lib/fragments/access.dart b/lib/fragments/access.dart index 30bd8f8..b62bd28 100644 --- a/lib/fragments/access.dart +++ b/lib/fragments/access.dart @@ -150,9 +150,17 @@ class _AccessFragmentState extends ConsumerState { return IconButton( onPressed: () async { final res = await showSheet( - title: appLocalizations.proxiesSetting, context: context, - body: AccessControlPanel(), + props: SheetProps( + isScrollControlled: true, + ), + builder: (_, type) { + return AdaptiveSheetScaffold( + type: type, + body: AccessControlPanel(), + title: appLocalizations.proxiesSetting, + ); + }, ); if (res == 1) { _intelligentSelected(); @@ -763,17 +771,19 @@ class _AccessControlPanelState extends ConsumerState { @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.only(bottom: 32), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ..._buildModeSetting(), - ..._buildSortSetting(), - ..._buildSourceSetting(), - ..._buildActionSetting(), - ], + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.only(bottom: 32), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ..._buildModeSetting(), + ..._buildSortSetting(), + ..._buildSourceSetting(), + ..._buildActionSetting(), + ], + ), ), ); } diff --git a/lib/fragments/application_setting.dart b/lib/fragments/application_setting.dart index 15fbc72..baea802 100644 --- a/lib/fragments/application_setting.dart +++ b/lib/fragments/application_setting.dart @@ -276,8 +276,8 @@ class ApplicationSettingFragment extends StatelessWidget { AutoRunItem(), if (Platform.isAndroid) ...[ HiddenItem(), - AnimateTabItem(), ], + AnimateTabItem(), OpenLogsItem(), CloseConnectionsItem(), UsageItem(), diff --git a/lib/fragments/backup_and_recovery.dart b/lib/fragments/backup_and_recovery.dart index c16e10c..b730490 100644 --- a/lib/fragments/backup_and_recovery.dart +++ b/lib/fragments/backup_and_recovery.dart @@ -6,6 +6,7 @@ import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/providers/config.dart'; import 'package:fl_clash/state.dart'; +import 'package:fl_clash/widgets/dialog.dart'; import 'package:fl_clash/widgets/fade_box.dart'; import 'package:fl_clash/widgets/list.dart'; import 'package:fl_clash/widgets/text.dart'; @@ -174,7 +175,7 @@ class BackupAndRecovery extends ConsumerWidget { future: client!.pingCompleter.future, builder: (_, snapshot) { return Center( - child: FadeBox( + child: FadeThroughBox( child: snapshot.connectionState == ConnectionState.waiting ? const SizedBox( @@ -275,30 +276,27 @@ class _RecoveryOptionsDialogState extends State { @override Widget build(BuildContext context) { - return AlertDialog( - title: Text(appLocalizations.recovery), - contentPadding: const EdgeInsets.symmetric( + return CommonDialog( + title: appLocalizations.recovery, + padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 16, ), - content: SizedBox( - width: 250, - child: Wrap( - children: [ - ListItem( - onTap: () { - _handleOnTab(RecoveryOption.onlyProfiles); - }, - title: Text(appLocalizations.recoveryProfiles), - ), - ListItem( - onTap: () { - _handleOnTab(RecoveryOption.all); - }, - title: Text(appLocalizations.recoveryAll), - ) - ], - ), + child: Wrap( + children: [ + ListItem( + onTap: () { + _handleOnTab(RecoveryOption.onlyProfiles); + }, + title: Text(appLocalizations.recoveryProfiles), + ), + ListItem( + onTap: () { + _handleOnTab(RecoveryOption.all); + }, + title: Text(appLocalizations.recoveryAll), + ) + ], ), ); } @@ -351,78 +349,8 @@ class _WebDAVFormDialogState extends ConsumerState { @override Widget build(BuildContext context) { - return AlertDialog( - title: Text(appLocalizations.webDAVConfiguration), - content: Form( - key: _formKey, - child: SizedBox( - width: dialogCommonWidth, - child: Wrap( - runSpacing: 16, - children: [ - TextFormField( - controller: uriController, - maxLines: 5, - minLines: 1, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.link), - border: const OutlineInputBorder(), - labelText: appLocalizations.address, - helperText: appLocalizations.addressHelp, - ), - validator: (String? value) { - if (value == null || value.isEmpty || !value.isUrl) { - return appLocalizations.addressTip; - } - return null; - }, - ), - TextFormField( - controller: userController, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.account_circle), - border: const OutlineInputBorder(), - labelText: appLocalizations.account, - ), - validator: (String? value) { - if (value == null || value.isEmpty) { - return appLocalizations.accountTip; - } - return null; - }, - ), - ValueListenableBuilder( - valueListenable: _obscureController, - builder: (_, obscure, __) { - return TextFormField( - controller: passwordController, - obscureText: obscure, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.password), - border: const OutlineInputBorder(), - suffixIcon: IconButton( - icon: Icon( - obscure ? Icons.visibility : Icons.visibility_off, - ), - onPressed: () { - _obscureController.value = !obscure; - }, - ), - labelText: appLocalizations.password, - ), - validator: (String? value) { - if (value == null || value.isEmpty) { - return appLocalizations.passwordTip; - } - return null; - }, - ); - }, - ), - ], - ), - ), - ), + return CommonDialog( + title: appLocalizations.webDAVConfiguration, actions: [ if (widget.dav != null) TextButton( @@ -434,6 +362,73 @@ class _WebDAVFormDialogState extends ConsumerState { child: Text(appLocalizations.save), ) ], + child: Form( + key: _formKey, + child: Wrap( + runSpacing: 16, + children: [ + TextFormField( + controller: uriController, + maxLines: 5, + minLines: 1, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.link), + border: const OutlineInputBorder(), + labelText: appLocalizations.address, + helperText: appLocalizations.addressHelp, + ), + validator: (String? value) { + if (value == null || value.isEmpty || !value.isUrl) { + return appLocalizations.addressTip; + } + return null; + }, + ), + TextFormField( + controller: userController, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.account_circle), + border: const OutlineInputBorder(), + labelText: appLocalizations.account, + ), + validator: (String? value) { + if (value == null || value.isEmpty) { + return appLocalizations.accountTip; + } + return null; + }, + ), + ValueListenableBuilder( + valueListenable: _obscureController, + builder: (_, obscure, __) { + return TextFormField( + controller: passwordController, + obscureText: obscure, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.password), + border: const OutlineInputBorder(), + suffixIcon: IconButton( + icon: Icon( + obscure ? Icons.visibility : Icons.visibility_off, + ), + onPressed: () { + _obscureController.value = !obscure; + }, + ), + labelText: appLocalizations.password, + ), + validator: (String? value) { + if (value == null || value.isEmpty) { + return appLocalizations.passwordTip; + } + return null; + }, + ); + }, + ), + ], + ), + ), ); } } diff --git a/lib/fragments/config/config.dart b/lib/fragments/config/config.dart index 10659d2..5cbb8bf 100644 --- a/lib/fragments/config/config.dart +++ b/lib/fragments/config/config.dart @@ -2,8 +2,13 @@ import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/fragments/config/dns.dart'; import 'package:fl_clash/fragments/config/general.dart'; import 'package:fl_clash/fragments/config/network.dart'; +import 'package:fl_clash/models/clash_config.dart'; +import 'package:fl_clash/providers/config.dart' show patchClashConfigProvider; import 'package:fl_clash/widgets/widgets.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../state.dart'; class ConfigFragment extends StatefulWidget { const ConfigFragment({super.key}); @@ -16,18 +21,6 @@ class _ConfigFragmentState extends State { @override Widget build(BuildContext context) { List items = [ - ListItem.open( - title: Text(appLocalizations.network), - subtitle: Text(appLocalizations.networkDesc), - leading: const Icon(Icons.vpn_key), - delegate: OpenDelegate( - title: appLocalizations.network, - isScaffold: true, - isBlur: false, - extendPageWidth: 360, - widget: const NetworkListView(), - ), - ), ListItem.open( title: Text(appLocalizations.general), subtitle: Text(appLocalizations.generalDesc), @@ -37,20 +30,52 @@ class _ConfigFragmentState extends State { widget: generateListView( generalItems, ), - isBlur: false, - extendPageWidth: 360, + blur: false, + ), + ), + ListItem.open( + title: Text(appLocalizations.network), + subtitle: Text(appLocalizations.networkDesc), + leading: const Icon(Icons.vpn_key), + delegate: OpenDelegate( + title: appLocalizations.network, + blur: false, + widget: const NetworkListView(), ), ), ListItem.open( title: const Text("DNS"), subtitle: Text(appLocalizations.dnsDesc), leading: const Icon(Icons.dns), - delegate: const OpenDelegate( + delegate: OpenDelegate( title: "DNS", - widget: DnsListView(), - isScaffold: true, - isBlur: false, - extendPageWidth: 360, + action: Consumer(builder: (_, ref, __) { + return IconButton( + onPressed: () async { + final res = await globalState.showMessage( + title: appLocalizations.reset, + message: TextSpan( + text: appLocalizations.resetTip, + ), + ); + if (res != true) { + return; + } + + ref.read(patchClashConfigProvider.notifier).updateState( + (state) => state.copyWith( + dns: defaultDns, + ), + ); + }, + tooltip: appLocalizations.reset, + icon: const Icon( + Icons.replay, + ), + ); + }), + widget: const DnsListView(), + blur: false, ), ) ]; diff --git a/lib/fragments/config/dns.dart b/lib/fragments/config/dns.dart index e162b8d..b851447 100644 --- a/lib/fragments/config/dns.dart +++ b/lib/fragments/config/dns.dart @@ -1,8 +1,6 @@ import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/enum/enum.dart'; -import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/providers/config.dart'; -import 'package:fl_clash/state.dart'; import 'package:fl_clash/widgets/widgets.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -205,7 +203,7 @@ class FakeIpFilterItem extends StatelessWidget { return ListItem.open( title: Text(appLocalizations.fakeipFilter), delegate: OpenDelegate( - isBlur: false, + blur: false, title: appLocalizations.fakeipFilter, widget: Consumer( builder: (_, ref, __) { @@ -213,7 +211,7 @@ class FakeIpFilterItem extends StatelessWidget { patchClashConfigProvider .select((state) => state.dns.fakeIpFilter), ); - return ListPage( + return ListInputPage( title: appLocalizations.fakeipFilter, items: fakeIpFilter, titleBuilder: (item) => Text(item), @@ -227,7 +225,6 @@ class FakeIpFilterItem extends StatelessWidget { ); }, ), - extendPageWidth: 360, ), ); } @@ -242,14 +239,14 @@ class DefaultNameserverItem extends StatelessWidget { title: Text(appLocalizations.defaultNameserver), subtitle: Text(appLocalizations.defaultNameserverDesc), delegate: OpenDelegate( - isBlur: false, + blur: false, title: appLocalizations.defaultNameserver, widget: Consumer(builder: (_, ref, __) { final defaultNameserver = ref.watch( patchClashConfigProvider .select((state) => state.dns.defaultNameserver), ); - return ListPage( + return ListInputPage( title: appLocalizations.defaultNameserver, items: defaultNameserver, titleBuilder: (item) => Text(item), @@ -262,7 +259,6 @@ class DefaultNameserverItem extends StatelessWidget { }, ); }), - extendPageWidth: 360, ), ); } @@ -278,13 +274,13 @@ class NameserverItem extends StatelessWidget { subtitle: Text(appLocalizations.nameserverDesc), delegate: OpenDelegate( title: appLocalizations.nameserver, - isBlur: false, + blur: false, widget: Consumer(builder: (_, ref, __) { final nameserver = ref.watch( patchClashConfigProvider.select((state) => state.dns.nameserver), ); - return ListPage( - title: "域名服务器", + return ListInputPage( + title: appLocalizations.nameserver, items: nameserver, titleBuilder: (item) => Text(item), onChange: (items) { @@ -296,7 +292,6 @@ class NameserverItem extends StatelessWidget { }, ); }), - extendPageWidth: 360, ), ); } @@ -357,28 +352,27 @@ class NameserverPolicyItem extends StatelessWidget { title: Text(appLocalizations.nameserverPolicy), subtitle: Text(appLocalizations.nameserverPolicyDesc), delegate: OpenDelegate( - isBlur: false, + blur: false, title: appLocalizations.nameserverPolicy, widget: Consumer(builder: (_, ref, __) { final nameserverPolicy = ref.watch( patchClashConfigProvider .select((state) => state.dns.nameserverPolicy), ); - return ListPage( + return MapInputPage( title: appLocalizations.nameserverPolicy, - items: nameserverPolicy.entries, + map: nameserverPolicy, titleBuilder: (item) => Text(item.key), subtitleBuilder: (item) => Text(item.value), - onChange: (items) { + onChange: (value) { ref.read(patchClashConfigProvider.notifier).updateState( (state) => state.copyWith.dns( - nameserverPolicy: Map.fromEntries(items), + nameserverPolicy: value, ), ); }, ); }), - extendPageWidth: 360, ), ); } @@ -393,7 +387,7 @@ class ProxyServerNameserverItem extends StatelessWidget { title: Text(appLocalizations.proxyNameserver), subtitle: Text(appLocalizations.proxyNameserverDesc), delegate: OpenDelegate( - isBlur: false, + blur: false, title: appLocalizations.proxyNameserver, widget: Consumer( builder: (_, ref, __) { @@ -401,7 +395,7 @@ class ProxyServerNameserverItem extends StatelessWidget { patchClashConfigProvider .select((state) => state.dns.proxyServerNameserver), ); - return ListPage( + return ListInputPage( title: appLocalizations.proxyNameserver, items: proxyServerNameserver, titleBuilder: (item) => Text(item), @@ -415,7 +409,6 @@ class ProxyServerNameserverItem extends StatelessWidget { ); }, ), - extendPageWidth: 360, ), ); } @@ -430,13 +423,13 @@ class FallbackItem extends StatelessWidget { title: Text(appLocalizations.fallback), subtitle: Text(appLocalizations.fallbackDesc), delegate: OpenDelegate( - isBlur: false, + blur: false, title: appLocalizations.fallback, widget: Consumer(builder: (_, ref, __) { final fallback = ref.watch( patchClashConfigProvider.select((state) => state.dns.fallback), ); - return ListPage( + return ListInputPage( title: appLocalizations.fallback, items: fallback, titleBuilder: (item) => Text(item), @@ -449,7 +442,6 @@ class FallbackItem extends StatelessWidget { }, ); }), - extendPageWidth: 360, ), ); } @@ -518,14 +510,14 @@ class GeositeItem extends StatelessWidget { return ListItem.open( title: const Text("Geosite"), delegate: OpenDelegate( - isBlur: false, + blur: false, title: "Geosite", widget: Consumer(builder: (_, ref, __) { final geosite = ref.watch( patchClashConfigProvider .select((state) => state.dns.fallbackFilter.geosite), ); - return ListPage( + return ListInputPage( title: "Geosite", items: geosite, titleBuilder: (item) => Text(item), @@ -538,7 +530,6 @@ class GeositeItem extends StatelessWidget { }, ); }), - extendPageWidth: 360, ), ); } @@ -552,14 +543,14 @@ class IpcidrItem extends StatelessWidget { return ListItem.open( title: Text(appLocalizations.ipcidr), delegate: OpenDelegate( - isBlur: false, + blur: false, title: appLocalizations.ipcidr, widget: Consumer(builder: (_, ref, ___) { final ipcidr = ref.watch( patchClashConfigProvider .select((state) => state.dns.fallbackFilter.ipcidr), ); - return ListPage( + return ListInputPage( title: appLocalizations.ipcidr, items: ipcidr, titleBuilder: (item) => Text(item), @@ -572,7 +563,6 @@ class IpcidrItem extends StatelessWidget { }, ); }), - extendPageWidth: 360, ), ); } @@ -586,14 +576,14 @@ class DomainItem extends StatelessWidget { return ListItem.open( title: Text(appLocalizations.domain), delegate: OpenDelegate( - isBlur: false, + blur: false, title: appLocalizations.domain, widget: Consumer(builder: (_, ref, __) { final domain = ref.watch( patchClashConfigProvider .select((state) => state.dns.fallbackFilter.domain), ); - return ListPage( + return ListInputPage( title: appLocalizations.domain, items: domain, titleBuilder: (item) => Text(item), @@ -606,7 +596,6 @@ class DomainItem extends StatelessWidget { }, ); }), - extendPageWidth: 360, ), ); } @@ -671,39 +660,8 @@ const dnsItems = [ class DnsListView extends ConsumerWidget { const DnsListView({super.key}); - _initActions(BuildContext context, WidgetRef ref) { - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - context.commonScaffoldState?.actions = [ - IconButton( - onPressed: () async { - final res = await globalState.showMessage( - title: appLocalizations.reset, - message: TextSpan( - text: appLocalizations.resetTip, - ), - ); - if (res != true) { - return; - } - - ref.read(patchClashConfigProvider.notifier).updateState( - (state) => state.copyWith( - dns: defaultDns, - ), - ); - }, - tooltip: appLocalizations.reset, - icon: const Icon( - Icons.replay, - ), - ) - ]; - }); - } - @override Widget build(BuildContext context, ref) { - _initActions(context, ref); return generateListView( dnsItems, ); diff --git a/lib/fragments/config/general.dart b/lib/fragments/config/general.dart index 1dabbf7..71c96c4 100644 --- a/lib/fragments/config/general.dart +++ b/lib/fragments/config/general.dart @@ -199,28 +199,27 @@ class HostsItem extends StatelessWidget { title: const Text("Hosts"), subtitle: Text(appLocalizations.hostsDesc), delegate: OpenDelegate( - isBlur: false, + blur: false, title: "Hosts", widget: Consumer( builder: (_, ref, __) { final hosts = ref .watch(patchClashConfigProvider.select((state) => state.hosts)); - return ListPage( + return MapInputPage( title: "Hosts", - items: hosts.entries, + map: hosts, titleBuilder: (item) => Text(item.key), subtitleBuilder: (item) => Text(item.value), - onChange: (items) { + onChange: (value) { ref.read(patchClashConfigProvider.notifier).updateState( (state) => state.copyWith( - hosts: Map.fromEntries(items), + hosts: value, ), ); }, ); }, ), - extendPageWidth: 360, ), ); } diff --git a/lib/fragments/config/network.dart b/lib/fragments/config/network.dart index c94b485..1826ad9 100644 --- a/lib/fragments/config/network.dart +++ b/lib/fragments/config/network.dart @@ -224,15 +224,14 @@ class BypassDomainItem extends StatelessWidget { title: Text(appLocalizations.bypassDomain), subtitle: Text(appLocalizations.bypassDomainDesc), delegate: OpenDelegate( - isBlur: false, - isScaffold: true, + blur: false, title: appLocalizations.bypassDomain, widget: Consumer( builder: (_, ref, __) { _initActions(context, ref); final bypassDomain = ref.watch( networkSettingProvider.select((state) => state.bypassDomain)); - return ListPage( + return ListInputPage( title: appLocalizations.bypassDomain, items: bypassDomain, titleBuilder: (item) => Text(item), @@ -246,7 +245,6 @@ class BypassDomainItem extends StatelessWidget { ); }, ), - extendPageWidth: 360, ), ); } @@ -298,14 +296,14 @@ class RouteAddressItem extends ConsumerWidget { title: Text(appLocalizations.routeAddress), subtitle: Text(appLocalizations.routeAddressDesc), delegate: OpenDelegate( - isBlur: false, - isScaffold: true, + blur: false, + maxWidth: 360, title: appLocalizations.routeAddress, widget: Consumer( builder: (_, ref, __) { final routeAddress = ref.watch(patchClashConfigProvider .select((state) => state.tun.routeAddress)); - return ListPage( + return ListInputPage( title: appLocalizations.routeAddress, items: routeAddress, titleBuilder: (item) => Text(item), @@ -319,7 +317,6 @@ class RouteAddressItem extends ConsumerWidget { ); }, ), - extendPageWidth: 360, ), ); } diff --git a/lib/fragments/connection/connections.dart b/lib/fragments/connection/connections.dart index 8ae9e23..99af1b7 100644 --- a/lib/fragments/connection/connections.dart +++ b/lib/fragments/connection/connections.dart @@ -125,7 +125,7 @@ class _ConnectionsFragmentState extends ConsumerState return ConnectionItem( key: Key(connection.id), connection: connection, - onClick: (value) { + onClickKeyword: (value) { context.commonScaffoldState?.addKeyword(value); }, trailing: IconButton( diff --git a/lib/fragments/connection/item.dart b/lib/fragments/connection/item.dart index eef2d88..9ce9113 100644 --- a/lib/fragments/connection/item.dart +++ b/lib/fragments/connection/item.dart @@ -9,40 +9,15 @@ import 'package:fl_clash/widgets/widgets.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -class FindProcessBuilder extends StatelessWidget { - final Widget Function(bool value) builder; - - const FindProcessBuilder({ - super.key, - required this.builder, - }); - - @override - Widget build(BuildContext context) { - return Consumer( - builder: (_, ref, __) { - final value = ref.watch( - patchClashConfigProvider.select( - (state) => - state.findProcessMode == FindProcessMode.always && - Platform.isAndroid, - ), - ); - return builder(value); - }, - ); - } -} - -class ConnectionItem extends StatelessWidget { +class ConnectionItem extends ConsumerWidget { final Connection connection; - final Function(String)? onClick; + final Function(String)? onClickKeyword; final Widget? trailing; const ConnectionItem({ super.key, required this.connection, - this.onClick, + this.onClickKeyword, this.trailing, }); @@ -59,7 +34,14 @@ class ConnectionItem extends StatelessWidget { } @override - Widget build(BuildContext context) { + Widget build(BuildContext context, ref) { + final value = ref.watch( + patchClashConfigProvider.select( + (state) => + state.findProcessMode == FindProcessMode.always && + Platform.isAndroid, + ), + ); final title = Text( connection.desc, style: context.textTheme.bodyLarge, @@ -86,70 +68,143 @@ class ConnectionItem extends StatelessWidget { CommonChip( label: chain, onPressed: () { - if (onClick == null) return; - onClick!(chain); + if (onClickKeyword == null) return; + onClickKeyword!(chain); }, ), ], ), ], ); - if (!Platform.isAndroid) { - return ListItem( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 4, - ), - tileTitleAlignment: ListTileTitleAlignment.titleHeight, - title: title, - subtitle: subTitle, - trailing: trailing, - ); - } - return FindProcessBuilder( - builder: (bool value) { - final leading = value - ? GestureDetector( - onTap: () { - if (onClick == null) return; - final process = connection.metadata.process; - if (process.isEmpty) return; - onClick!(process); - }, - child: Container( - margin: const EdgeInsets.only(top: 4), - width: 48, - height: 48, - child: FutureBuilder( - future: _getPackageIcon(connection), - builder: (_, snapshot) { - if (!snapshot.hasData && snapshot.data == null) { - return Container(); - } else { - return Image( - image: snapshot.data!, - gaplessPlayback: true, - width: 48, - height: 48, - ); - } - }, - ), - ), - ) - : null; + return CommonPopupBox( + targetBuilder: (open) { return ListItem( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 4, ), tileTitleAlignment: ListTileTitleAlignment.titleHeight, - leading: leading, + leading: value + ? GestureDetector( + onTap: () { + if (onClickKeyword == null) return; + final process = connection.metadata.process; + if (process.isEmpty) return; + onClickKeyword!(process); + }, + child: Container( + margin: const EdgeInsets.only(top: 4), + width: 48, + height: 48, + child: FutureBuilder( + future: _getPackageIcon(connection), + builder: (_, snapshot) { + if (!snapshot.hasData && snapshot.data == null) { + return Container(); + } else { + return Image( + image: snapshot.data!, + gaplessPlayback: true, + width: 48, + height: 48, + ); + } + }, + ), + ), + ) + : null, title: title, subtitle: subTitle, trailing: trailing, ); + // return InkWell( + // child: GestureDetector( + // onLongPressStart: (details) { + // if (!system.isDesktop) { + // return; + // } + // open( + // offset: details.localPosition.translate( + // 0, + // -12, + // ), + // ); + // }, + // onSecondaryTapDown: (details) { + // if (!system.isDesktop) { + // return; + // } + // open( + // offset: details.localPosition.translate( + // 0, + // -12, + // ), + // ); + // }, + // child: ListItem( + // padding: const EdgeInsets.symmetric( + // horizontal: 16, + // vertical: 4, + // ), + // tileTitleAlignment: ListTileTitleAlignment.titleHeight, + // leading: value + // ? GestureDetector( + // onTap: () { + // if (onClickKeyword == null) return; + // final process = connection.metadata.process; + // if (process.isEmpty) return; + // onClickKeyword!(process); + // }, + // child: Container( + // margin: const EdgeInsets.only(top: 4), + // width: 48, + // height: 48, + // child: FutureBuilder( + // future: _getPackageIcon(connection), + // builder: (_, snapshot) { + // if (!snapshot.hasData && snapshot.data == null) { + // return Container(); + // } else { + // return Image( + // image: snapshot.data!, + // gaplessPlayback: true, + // width: 48, + // height: 48, + // ); + // } + // }, + // ), + // ), + // ) + // : null, + // title: title, + // subtitle: subTitle, + // trailing: trailing, + // ), + // ), + // onTap: () {}, + // ); }, + popup: CommonPopupMenu( + minWidth: 160, + items: [ + PopupMenuItemData( + label: "编辑规则", + onPressed: () { + // _handleShowEditExtendPage(context); + }, + ), + PopupMenuItemData( + label: "设置直连", + onPressed: () {}, + ), + PopupMenuItemData( + label: "一键屏蔽", + onPressed: () {}, + ), + ], + ), ); } } diff --git a/lib/fragments/connection/requests.dart b/lib/fragments/connection/requests.dart index 2971472..69e04e4 100644 --- a/lib/fragments/connection/requests.dart +++ b/lib/fragments/connection/requests.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/models/models.dart'; @@ -20,6 +22,7 @@ class RequestsFragment extends ConsumerStatefulWidget { class _RequestsFragmentState extends ConsumerState with PageMixin { + final GlobalKey _key = GlobalKey(); final _requestsStateNotifier = ValueNotifier(const ConnectionsState()); List _requests = []; @@ -28,8 +31,6 @@ class _RequestsFragmentState extends ConsumerState initialScrollOffset: _preOffset != 0 ? _preOffset : double.maxFinite, ); - final FixedMap _cacheDynamicHeightMap = FixedMap(1000); - double _currentMaxWidth = 0; @override @@ -78,10 +79,6 @@ class _RequestsFragmentState extends ConsumerState } double _calcCacheHeight(Connection item) { - final cacheHeight = _cacheDynamicHeightMap.get(item.id); - if (cacheHeight != null) { - return cacheHeight; - } final size = globalState.measure.computeTextSize( Text( item.desc, @@ -102,14 +99,13 @@ class _RequestsFragmentState extends ConsumerState final lines = (chainSize.height / baseHeight).round(); final computerHeight = size.height + chainSize.height + 24 + 24 * (lines - 1); - _cacheDynamicHeightMap.put(item.id, computerHeight); return computerHeight; } _handleTryClearCache(double maxWidth) { if (_currentMaxWidth != maxWidth) { _currentMaxWidth = maxWidth; - _cacheDynamicHeightMap.clear(); + _key.currentState?.clearCache(); } } @@ -118,7 +114,6 @@ class _RequestsFragmentState extends ConsumerState _requestsStateNotifier.dispose(); _scrollController.dispose(); _currentMaxWidth = 0; - _cacheDynamicHeightMap.clear(); super.dispose(); } @@ -143,9 +138,19 @@ class _RequestsFragmentState extends ConsumerState Widget build(BuildContext context) { return LayoutBuilder( builder: (_, constraints) { - return FindProcessBuilder(builder: (value) { - _handleTryClearCache(constraints.maxWidth - 40 - (value ? 60 : 0)); - return ValueListenableBuilder( + return Consumer( + builder: (_, ref, child) { + final value = ref.watch( + patchClashConfigProvider.select( + (state) => + state.findProcessMode == FindProcessMode.always && + Platform.isAndroid, + ), + ); + _handleTryClearCache(constraints.maxWidth - 40 - (value ? 60 : 0)); + return child!; + }, + child: ValueListenableBuilder( valueListenable: _requestsStateNotifier, builder: (_, state, __) { final connections = state.list; @@ -159,7 +164,7 @@ class _RequestsFragmentState extends ConsumerState (connection) => ConnectionItem( key: Key(connection.id), connection: connection, - onClick: (value) { + onClickKeyword: (value) { context.commonScaffoldState?.addKeyword(value); }, ), @@ -179,12 +184,13 @@ class _RequestsFragmentState extends ConsumerState }, child: CommonScrollBar( controller: _scrollController, - child: ListView.builder( + child: CacheItemExtentListView( + key: _key, reverse: true, shrinkWrap: true, physics: NextClampingScrollPhysics(), controller: _scrollController, - itemExtentBuilder: (index, __) { + itemExtentBuilder: (index) { final widget = items[index]; if (widget.runtimeType == Divider) { return 0; @@ -199,13 +205,21 @@ class _RequestsFragmentState extends ConsumerState return items[index]; }, itemCount: items.length, + keyBuilder: (int index) { + final widget = items[index]; + if (widget.runtimeType == Divider) { + return "divider"; + } + final connection = connections[(index / 2).floor()]; + return connection.id; + }, ), ), ), ); }, - ); - }); + ), + ); }, ); } diff --git a/lib/fragments/dashboard/widgets/intranet_ip.dart b/lib/fragments/dashboard/widgets/intranet_ip.dart index 7022a36..8af89d6 100644 --- a/lib/fragments/dashboard/widgets/intranet_ip.dart +++ b/lib/fragments/dashboard/widgets/intranet_ip.dart @@ -31,7 +31,7 @@ class IntranetIP extends StatelessWidget { child: Consumer( builder: (_, ref, __) { final localIp = ref.watch(localIpProvider); - return FadeBox( + return FadeThroughBox( child: localIp != null ? TooltipText( text: Text( diff --git a/lib/fragments/dashboard/widgets/memory_info.dart b/lib/fragments/dashboard/widgets/memory_info.dart index ed239cd..3e0a90a 100644 --- a/lib/fragments/dashboard/widgets/memory_info.dart +++ b/lib/fragments/dashboard/widgets/memory_info.dart @@ -39,7 +39,7 @@ class _MemoryInfoState extends State { _memoryInfoStateNotifier.value = TrafficValue( value: clashLib != null ? rss : await clashCore.getMemory() + rss, ); - timer = Timer(Duration(seconds: 5), () async { + timer = Timer(Duration(seconds: 2), () async { _updateMemory(); }); }); @@ -47,13 +47,8 @@ class _MemoryInfoState extends State { @override Widget build(BuildContext context) { - final darkenLighter = context.colorScheme.secondaryContainer - .blendDarken(context, factor: 0.1) - .toLighter; - final darken = context.colorScheme.secondaryContainer - .blendDarken(context, factor: 0.1); return SizedBox( - height: getWidgetHeight(2), + height: getWidgetHeight(1), child: CommonCard( info: Info( iconData: Icons.memory, @@ -76,43 +71,94 @@ class _MemoryInfoState extends State { children: [ Text( trafficValue.showValue, - style: context.textTheme.titleLarge?.toLight, + style: + context.textTheme.bodyMedium?.toLight.adjustSize(1), ), SizedBox( width: 8, ), Text( trafficValue.showUnit, - style: context.textTheme.titleLarge?.toLight, + style: + context.textTheme.bodyMedium?.toLight.adjustSize(1), ) ], ), ); }, ), - Flexible( - child: Stack( - children: [ - Positioned.fill( - child: WaveView( - waveAmplitude: 12.0, - waveFrequency: 0.35, - waveColor: darkenLighter, - ), - ), - Positioned.fill( - child: WaveView( - waveAmplitude: 12.0, - waveFrequency: 0.9, - waveColor: darken, - ), - ), - ], - ), - ), ], ), ), ); } } + +// class AnimatedCounter extends StatefulWidget { +// final double value; +// final TextStyle? style; +// +// const AnimatedCounter({ +// super.key, +// required this.value, +// this.style, +// }); +// +// @override +// State createState() => _AnimatedCounterState(); +// } +// +// class _AnimatedCounterState extends State { +// late double _previousValue; +// late double _currentValue; +// +// @override +// void initState() { +// super.initState(); +// _previousValue = widget.value; +// _currentValue = widget.value; +// } +// +// @override +// void didUpdateWidget(AnimatedCounter oldWidget) { +// super.didUpdateWidget(oldWidget); +// if (oldWidget.value != widget.value) { +// // if (_previousValue == _currentValue) { +// // _previousValue = widget.value; +// // _currentValue = widget.value; +// // return; +// // } +// _currentValue = widget.value; +// } +// } +// +// @override +// void dispose() { +// super.dispose(); +// } +// +// @override +// Widget build(BuildContext context) { +// return Text( +// _currentValue.fixed(decimals: 1), +// style: widget.style, +// ); +// return TweenAnimationBuilder( +// tween: Tween( +// begin: _previousValue, +// end: _currentValue, +// ), +// onEnd: () { +// _previousValue = _currentValue; +// }, +// duration: Duration(seconds: 6), +// curve: Curves.easeOut, +// builder: (_, value, ___) { +// return Text( +// value.fixed(decimals: 1), +// style: widget.style, +// ); +// }, +// ); +// } +// } diff --git a/lib/fragments/dashboard/widgets/network_detection.dart b/lib/fragments/dashboard/widgets/network_detection.dart index bec2d8a..37fbd52 100644 --- a/lib/fragments/dashboard/widgets/network_detection.dart +++ b/lib/fragments/dashboard/widgets/network_detection.dart @@ -12,7 +12,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; final _networkDetectionState = ValueNotifier( const NetworkDetectionState( - isTesting: true, + isTesting: false, + isLoading: true, ipInfo: null, ), ); @@ -28,7 +29,6 @@ class _NetworkDetectionState extends ConsumerState { bool? _preIsStart; Timer? _setTimeoutTimer; CancelToken? cancelToken; - Completer? checkedCompleter; @override void initState() { @@ -37,11 +37,14 @@ class _NetworkDetectionState extends ConsumerState { _startCheck(); } }); + if (!_networkDetectionState.value.isTesting && + _networkDetectionState.value.isLoading) { + _startCheck(); + } super.initState(); } _startCheck() async { - await checkedCompleter?.future; if (cancelToken != null) { cancelToken!.cancel(); cancelToken = null; @@ -64,7 +67,7 @@ class _NetworkDetectionState extends ConsumerState { } _clearSetTimeoutTimer(); _networkDetectionState.value = _networkDetectionState.value.copyWith( - isTesting: true, + isLoading: true, ipInfo: null, ); _preIsStart = isStart; @@ -74,16 +77,16 @@ class _NetworkDetectionState extends ConsumerState { } cancelToken = CancelToken(); try { + _networkDetectionState.value = _networkDetectionState.value.copyWith( + isTesting: true, + ); final ipInfo = await request.checkIp(cancelToken: cancelToken); + _networkDetectionState.value = _networkDetectionState.value.copyWith( + isTesting: false, + ); if (ipInfo != null) { - checkedCompleter = Completer(); - checkedCompleter?.complete( - Future.delayed( - Duration(milliseconds: 3000), - ), - ); _networkDetectionState.value = _networkDetectionState.value.copyWith( - isTesting: false, + isLoading: false, ipInfo: ipInfo, ); return; @@ -91,14 +94,14 @@ class _NetworkDetectionState extends ConsumerState { _clearSetTimeoutTimer(); _setTimeoutTimer = Timer(const Duration(milliseconds: 300), () { _networkDetectionState.value = _networkDetectionState.value.copyWith( - isTesting: false, + isLoading: false, ipInfo: null, ); }); } catch (e) { if (e.toString() == "cancelled") { _networkDetectionState.value = _networkDetectionState.value.copyWith( - isTesting: true, + isLoading: true, ipInfo: null, ); } @@ -136,7 +139,7 @@ class _NetworkDetectionState extends ConsumerState { valueListenable: _networkDetectionState, builder: (_, state, __) { final ipInfo = state.ipInfo; - final isTesting = state.isTesting; + final isLoading = state.isLoading; return CommonCard( onPressed: () {}, child: Column( @@ -218,7 +221,7 @@ class _NetworkDetectionState extends ConsumerState { ), child: SizedBox( height: globalState.measure.bodyMediumHeight + 2, - child: FadeBox( + child: FadeThroughBox( child: ipInfo != null ? TooltipText( text: Text( @@ -229,8 +232,8 @@ class _NetworkDetectionState extends ConsumerState { overflow: TextOverflow.ellipsis, ), ) - : FadeBox( - child: isTesting == false && ipInfo == null + : FadeThroughBox( + child: isLoading == false && ipInfo == null ? Text( "timeout", style: context.textTheme.bodyMedium diff --git a/lib/fragments/dashboard/widgets/network_speed.dart b/lib/fragments/dashboard/widgets/network_speed.dart index e9e8bd0..df3befd 100644 --- a/lib/fragments/dashboard/widgets/network_speed.dart +++ b/lib/fragments/dashboard/widgets/network_speed.dart @@ -41,7 +41,7 @@ class _NetworkSpeedState extends State { @override Widget build(BuildContext context) { - final color = context.colorScheme.onSurfaceVariant.toLight; + final color = context.colorScheme.onSurfaceVariant.opacity80; return SizedBox( height: getWidgetHeight(2), child: CommonCard( diff --git a/lib/fragments/dashboard/widgets/outbound_mode.dart b/lib/fragments/dashboard/widgets/outbound_mode.dart index c1a52b9..4c177b5 100644 --- a/lib/fragments/dashboard/widgets/outbound_mode.dart +++ b/lib/fragments/dashboard/widgets/outbound_mode.dart @@ -38,7 +38,7 @@ class OutboundMode extends StatelessWidget { for (final item in Mode.values) Flexible( child: ListItem.radio( - prue: true, + dense: true, horizontalTitleGap: 4, padding: const EdgeInsets.only( left: 12, diff --git a/lib/fragments/dashboard/widgets/quick_options.dart b/lib/fragments/dashboard/widgets/quick_options.dart index c330018..22e4de5 100644 --- a/lib/fragments/dashboard/widgets/quick_options.dart +++ b/lib/fragments/dashboard/widgets/quick_options.dart @@ -16,13 +16,20 @@ class TUNButton extends StatelessWidget { onPressed: () { showSheet( context: context, - body: generateListView(generateSection( - items: [ - if (system.isDesktop) const TUNItem(), - const TunStackItem(), - ], - )), - title: appLocalizations.tun, + builder: (_, type) { + return AdaptiveSheetScaffold( + type: type, + body: generateListView( + generateSection( + items: [ + if (system.isDesktop) const TUNItem(), + const TunStackItem(), + ], + ), + ), + title: appLocalizations.tun, + ); + }, ); }, info: Info( @@ -89,15 +96,20 @@ class SystemProxyButton extends StatelessWidget { onPressed: () { showSheet( context: context, - body: generateListView( - generateSection( - items: [ - SystemProxyItem(), - BypassDomainItem(), - ], - ), - ), - title: appLocalizations.systemProxy, + builder: (_, type) { + return AdaptiveSheetScaffold( + type: type, + body: generateListView( + generateSection( + items: [ + SystemProxyItem(), + BypassDomainItem(), + ], + ), + ), + title: appLocalizations.systemProxy, + ); + }, ); }, info: Info( diff --git a/lib/fragments/dashboard/widgets/traffic_usage.dart b/lib/fragments/dashboard/widgets/traffic_usage.dart index aee3be1..3b073d3 100644 --- a/lib/fragments/dashboard/widgets/traffic_usage.dart +++ b/lib/fragments/dashboard/widgets/traffic_usage.dart @@ -51,10 +51,8 @@ class TrafficUsage extends StatelessWidget { @override Widget build(BuildContext context) { - final primaryColor = - context.colorScheme.surfaceContainer.blendDarken(context, factor: 0.2); - final secondaryColor = - context.colorScheme.primaryContainer.blendDarken(context, factor: 0.3); + final primaryColor = globalState.theme.darken3PrimaryContainer; + final secondaryColor = globalState.theme.darken2SecondaryContainer; return SizedBox( height: getWidgetHeight(2), child: CommonCard( diff --git a/lib/fragments/hotkey.dart b/lib/fragments/hotkey.dart index e11f8bf..6feab46 100644 --- a/lib/fragments/hotkey.dart +++ b/lib/fragments/hotkey.dart @@ -4,6 +4,7 @@ import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/providers/providers.dart'; import 'package:fl_clash/state.dart'; import 'package:fl_clash/widgets/card.dart'; +import 'package:fl_clash/widgets/dialog.dart'; import 'package:fl_clash/widgets/list.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -156,9 +157,28 @@ class _HotKeyRecorderState extends State { @override Widget build(BuildContext context) { - return AlertDialog( - title: Text(IntlExt.actionMessage((widget.hotKeyAction.action.name))), - content: ValueListenableBuilder( + return CommonDialog( + title: IntlExt.actionMessage(widget.hotKeyAction.action.name), + actions: [ + TextButton( + onPressed: () { + _handleRemove(); + }, + child: Text(appLocalizations.remove), + ), + const SizedBox( + width: 8, + ), + TextButton( + onPressed: () { + _handleConfirm(); + }, + child: Text( + appLocalizations.confirm, + ), + ), + ], + child: ValueListenableBuilder( valueListenable: hotKeyActionNotifier, builder: (_, hotKeyAction, ___) { final key = hotKeyAction.key; @@ -191,25 +211,6 @@ class _HotKeyRecorderState extends State { ); }, ), - actions: [ - TextButton( - onPressed: () { - _handleRemove(); - }, - child: Text(appLocalizations.remove), - ), - const SizedBox( - width: 8, - ), - TextButton( - onPressed: () { - _handleConfirm(); - }, - child: Text( - appLocalizations.confirm, - ), - ), - ], ); } } diff --git a/lib/fragments/logs.dart b/lib/fragments/logs.dart index 99be6bf..1333be0 100644 --- a/lib/fragments/logs.dart +++ b/lib/fragments/logs.dart @@ -22,8 +22,8 @@ class _LogsFragmentState extends ConsumerState with PageMixin { final _scrollController = ScrollController( initialScrollOffset: _preOffset != 0 ? _preOffset : double.maxFinite, ); - final FixedMap _cacheDynamicHeightMap = FixedMap(1000); double _currentMaxWidth = 0; + final GlobalKey _key = GlobalKey(); List _logs = []; @@ -90,14 +90,13 @@ class _LogsFragmentState extends ConsumerState with PageMixin { void dispose() { _logsStateNotifier.dispose(); _scrollController.dispose(); - _cacheDynamicHeightMap.clear(); super.dispose(); } _handleTryClearCache(double maxWidth) { if (_currentMaxWidth != maxWidth) { _currentMaxWidth = maxWidth; - _cacheDynamicHeightMap.clear(); + _key.currentState?.clearCache(); } } @@ -116,27 +115,19 @@ class _LogsFragmentState extends ConsumerState with PageMixin { ); } - double _calcCacheHeight(String text) { - final cacheHeight = _cacheDynamicHeightMap.get(text); - if (cacheHeight != null) { - return cacheHeight; - } - final size = globalState.measure.computeTextSize( - Text( - text, - style: globalState.appController.context.textTheme.bodyLarge, - ), - maxWidth: _currentMaxWidth, - ); - _cacheDynamicHeightMap.put(text, size.height); - return size.height; - } - double _getItemHeight(Log log) { final measure = globalState.measure; final bodySmallHeight = measure.bodySmallHeight; final bodyMediumHeight = measure.bodyMediumHeight; - final height = _calcCacheHeight(log.payload ?? ""); + final height = globalState.measure + .computeTextSize( + Text( + log.payload ?? "", + style: globalState.appController.context.textTheme.bodyLarge, + ), + maxWidth: _currentMaxWidth, + ) + .height; return height + bodySmallHeight + 8 + bodyMediumHeight + 40; } @@ -196,7 +187,8 @@ class _LogsFragmentState extends ConsumerState with PageMixin { }, child: CommonScrollBar( controller: _scrollController, - child: ListView.builder( + child: CacheItemExtentListView( + key: _key, reverse: true, shrinkWrap: true, physics: NextClampingScrollPhysics(), @@ -204,7 +196,7 @@ class _LogsFragmentState extends ConsumerState with PageMixin { itemBuilder: (_, index) { return items[index]; }, - itemExtentBuilder: (index, __) { + itemExtentBuilder: (index) { final item = items[index]; if (item.runtimeType == Divider) { return 0; @@ -213,6 +205,14 @@ class _LogsFragmentState extends ConsumerState with PageMixin { return _getItemHeight(log); }, itemCount: items.length, + keyBuilder: (int index) { + final item = items[index]; + if (item.runtimeType == Divider) { + return "divider"; + } + final log = logs[(index / 2).floor()]; + return log.payload ?? ""; + }, ), ), ); @@ -272,11 +272,3 @@ class LogItem extends StatelessWidget { ); } } - -class NoGlowScrollBehavior extends ScrollBehavior { - @override - Widget buildOverscrollIndicator( - BuildContext context, Widget child, ScrollableDetails details) { - return child; // 禁用过度滚动效果 - } -} diff --git a/lib/fragments/profiles/add_profile.dart b/lib/fragments/profiles/add_profile.dart index 96e76b7..0f7a02f 100644 --- a/lib/fragments/profiles/add_profile.dart +++ b/lib/fragments/profiles/add_profile.dart @@ -50,19 +50,19 @@ class AddProfile extends StatelessWidget { return ListView( children: [ ListItem( - leading: const Icon(Icons.qr_code), + leading: const Icon(Icons.qr_code_sharp), title: Text(appLocalizations.qrcode), subtitle: Text(appLocalizations.qrcodeDesc), onTap: _toScan, ), ListItem( - leading: const Icon(Icons.upload_file), + leading: const Icon(Icons.upload_file_sharp), title: Text(appLocalizations.file), subtitle: Text(appLocalizations.fileDesc), onTap: _handleAddProfileFormFile, ), ListItem( - leading: const Icon(Icons.cloud_download), + leading: const Icon(Icons.cloud_download_sharp), title: Text(appLocalizations.url), subtitle: Text(appLocalizations.urlDesc), onTap: _toAdd, @@ -90,9 +90,15 @@ class _URLFormDialogState extends State { @override Widget build(BuildContext context) { - return AlertDialog( - title: Text(appLocalizations.importFromURL), - content: SizedBox( + return CommonDialog( + title: appLocalizations.importFromURL, + actions: [ + TextButton( + onPressed: _handleAddProfileFormURL, + child: Text(appLocalizations.submit), + ) + ], + child: SizedBox( width: 300, child: Wrap( runSpacing: 16, @@ -109,12 +115,6 @@ class _URLFormDialogState extends State { ], ), ), - actions: [ - TextButton( - onPressed: _handleAddProfileFormURL, - child: Text(appLocalizations.submit), - ) - ], ); } } diff --git a/lib/fragments/profiles/edit_profile.dart b/lib/fragments/profiles/edit_profile.dart index 26c3292..1cee6bb 100644 --- a/lib/fragments/profiles/edit_profile.dart +++ b/lib/fragments/profiles/edit_profile.dart @@ -282,7 +282,7 @@ class _EditProfileState extends State { ValueListenableBuilder( valueListenable: fileInfoNotifier, builder: (_, fileInfo, __) { - return FadeBox( + return FadeThroughBox( child: fileInfo == null ? Container() : ListItem( @@ -324,15 +324,13 @@ class _EditProfileState extends State { }, ), ]; - return PopScope( - canPop: false, - onPopInvokedWithResult: (didPop, __) { - if (didPop) return; + return CommonPopScope( + onPop: () { if (fileData == null) { - Navigator.of(context).pop(); - return; + return true; } _handleBack(); + return false; }, child: FloatLayout( floatingWidget: FloatWrapper( diff --git a/lib/fragments/profiles/gen_profile.dart b/lib/fragments/profiles/gen_profile.dart deleted file mode 100644 index aa0d9c1..0000000 --- a/lib/fragments/profiles/gen_profile.dart +++ /dev/null @@ -1,87 +0,0 @@ -import 'package:fl_clash/clash/core.dart'; -import 'package:fl_clash/models/models.dart'; -import 'package:fl_clash/state.dart'; -import 'package:fl_clash/widgets/card.dart'; -import 'package:fl_clash/widgets/scaffold.dart'; -import 'package:flutter/material.dart'; - -class GenProfile extends StatefulWidget { - final String profileId; - - const GenProfile({ - super.key, - required this.profileId, - }); - - @override - State createState() => _GenProfileState(); -} - -class _GenProfileState extends State { - final _currentClashConfigNotifier = ValueNotifier(null); - - @override - void initState() { - super.initState(); - _initCurrentClashConfig(); - } - - _initCurrentClashConfig() async { - final currentProfileId = globalState.config.currentProfileId; - if (currentProfileId == null) { - return; - } - _currentClashConfigNotifier.value = - await clashCore.getProfile(currentProfileId); - } - - @override - Widget build(BuildContext context) { - return CommonScaffold( - body: ValueListenableBuilder( - valueListenable: _currentClashConfigNotifier, - builder: (_, clashConfig, ___) { - if (clashConfig == null) { - return Center( - child: CircularProgressIndicator(), - ); - } - return Padding( - padding: EdgeInsets.all(16), - child: CustomScrollView( - slivers: [ - SliverGrid.builder( - gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: 100, - mainAxisExtent: 50, - mainAxisSpacing: 8, - crossAxisSpacing: 8, - ), - itemCount: clashConfig.proxyGroups.length, - itemBuilder: (BuildContext context, int index) { - return CommonCard( - onPressed: () {}, - child: Text( - clashConfig.proxyGroups[index].name, - ), - ); - }, - ), - SliverList.builder( - itemBuilder: (BuildContext context, int index) { - final rule = clashConfig.rule[index]; - return Text( - rule, - ); - }, - itemCount: clashConfig.rule.length, - ) - ], - ), - ); - }, - ), - title: "自定义", - ); - } -} diff --git a/lib/fragments/profiles/override_profile.dart b/lib/fragments/profiles/override_profile.dart new file mode 100644 index 0000000..bb3d8fb --- /dev/null +++ b/lib/fragments/profiles/override_profile.dart @@ -0,0 +1,957 @@ +import 'dart:ui'; + +import 'package:fl_clash/clash/core.dart'; +import 'package:fl_clash/common/common.dart'; +import 'package:fl_clash/enum/enum.dart'; +import 'package:fl_clash/models/models.dart'; +import 'package:fl_clash/providers/providers.dart'; +import 'package:fl_clash/state.dart'; +import 'package:fl_clash/widgets/widgets.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class OverrideProfile extends StatefulWidget { + final String profileId; + + const OverrideProfile({ + super.key, + required this.profileId, + }); + + @override + State createState() => _OverrideProfileState(); +} + +class _OverrideProfileState extends State { + final GlobalKey _ruleListKey = GlobalKey(); + final _controller = ScrollController(); + double _currentMaxWidth = 0; + + _initState(WidgetRef ref) { + WidgetsBinding.instance.addPostFrameCallback((_) { + Future.delayed(Duration(milliseconds: 300), () async { + final snippet = await clashCore.getProfile(widget.profileId); + final overrideData = ref.read( + getProfileOverrideDataProvider(widget.profileId), + ); + ref.read(profileOverrideStateProvider.notifier).updateState( + (state) => state.copyWith( + snippet: snippet, + overrideData: overrideData, + ), + ); + }); + }); + } + + _handleSave(WidgetRef ref, OverrideData overrideData) { + ref.read(profilesProvider.notifier).updateProfile( + widget.profileId, + (state) => state.copyWith( + overrideData: overrideData, + ), + ); + } + + _handleDelete(WidgetRef ref) async { + final res = await globalState.showMessage( + title: appLocalizations.tip, + message: TextSpan(text: appLocalizations.deleteRuleTip), + ); + if (res != true) { + return; + } + final selectedRules = ref.read( + profileOverrideStateProvider.select( + (state) => state.selectedRules, + ), + ); + ref.read(profileOverrideStateProvider.notifier).updateState( + (state) { + final overrideRule = state.overrideData!.rule.updateRules( + (rules) => List.from( + rules.where( + (item) => !selectedRules.contains(item.id), + ), + ), + ); + return state.copyWith.overrideData!( + rule: overrideRule, + ); + }, + ); + ref.read(profileOverrideStateProvider.notifier).updateState( + (state) => state.copyWith(isEdit: false, selectedRules: {}), + ); + } + + _handleTryClearCache(double maxWidth) { + if (_currentMaxWidth != maxWidth) { + _currentMaxWidth = maxWidth; + _ruleListKey.currentState?.clearCache(); + } + } + + _buildContent() { + return Consumer( + builder: (_, ref, child) { + final isInit = ref.watch( + profileOverrideStateProvider.select( + (state) => state.snippet != null && state.overrideData != null, + ), + ); + if (!isInit) { + return Center( + child: CircularProgressIndicator(), + ); + } + return FadeBox( + child: !isInit + ? Center( + child: CircularProgressIndicator(), + ) + : child!, + ); + }, + child: LayoutBuilder( + builder: (_, constraints) { + _handleTryClearCache(constraints.maxWidth - 104); + return CommonAutoHiddenScrollBar( + controller: _controller, + child: CustomScrollView( + controller: _controller, + slivers: [ + SliverToBoxAdapter( + child: SizedBox( + height: 16, + ), + ), + SliverPadding( + padding: EdgeInsets.symmetric(horizontal: 16), + sliver: SliverToBoxAdapter( + child: OverrideSwitch(), + ), + ), + SliverToBoxAdapter( + child: Padding( + padding: EdgeInsets.only( + left: 8, + right: 8, + ), + child: RuleTitle( + profileId: widget.profileId, + ), + ), + ), + SliverPadding( + padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 0), + sliver: RuleContent( + maxWidth: _currentMaxWidth, + ruleListKey: _ruleListKey, + ), + ), + SliverToBoxAdapter( + child: SizedBox( + height: 16, + ), + ), + ], + ), + ); + }, + ), + ); + } + + @override + Widget build(BuildContext context) { + return ProviderScope( + overrides: [ + profileOverrideStateProvider.overrideWith(() => ProfileOverrideState()), + ], + child: Consumer( + builder: (_, ref, child) { + _initState(ref); + return child!; + }, + child: Consumer( + builder: (_, ref, ___) { + final vm2 = ref.watch( + profileOverrideStateProvider.select( + (state) => VM2( + a: state.isEdit, + b: state.selectedRules.length, + ), + ), + ); + final isEdit = vm2.a; + final editCount = vm2.b; + return CommonScaffold( + title: appLocalizations.override, + body: _buildContent(), + actions: [ + if (!isEdit) + Consumer( + builder: (_, ref, child) { + final overrideData = ref.watch( + getProfileOverrideDataProvider(widget.profileId)); + final newOverrideData = ref.watch( + profileOverrideStateProvider.select( + (state) => state.overrideData, + ), + ); + final equals = overrideData == newOverrideData; + if (equals || newOverrideData == null) { + return SizedBox(); + } + return CommonPopScope( + onPop: () async { + if (equals) { + return true; + } + final res = await globalState.showMessage( + message: TextSpan( + text: appLocalizations.saveChanges, + ), + confirmText: appLocalizations.save, + ); + if (!context.mounted || res != true) { + return true; + } + _handleSave(ref, newOverrideData); + return true; + }, + child: IconButton( + onPressed: () async { + final res = await globalState.showMessage( + message: TextSpan( + text: appLocalizations.saveTip, + ), + confirmText: appLocalizations.tip, + ); + if (res != true) { + return; + } + _handleSave(ref, newOverrideData); + }, + icon: Icon( + Icons.save, + ), + ), + ); + }, + ), + if (editCount == 1) + IconButton( + onPressed: () { + final rule = ref.read(profileOverrideStateProvider.select( + (state) { + return state.overrideData?.rule.rules.firstWhere( + (item) => item.id == state.selectedRules.first, + ); + }, + )); + if (rule == null) { + return; + } + globalState.appController.handleAddOrUpdate( + ref, + rule, + ); + }, + icon: Icon( + Icons.edit, + ), + ), + if (editCount > 0) + IconButton( + onPressed: () { + _handleDelete(ref); + }, + icon: Icon( + Icons.delete, + ), + ) + ], + appBarEditState: AppBarEditState( + isEdit: isEdit, + editCount: editCount, + onExit: () { + ref.read(profileOverrideStateProvider.notifier).updateState( + (state) => state.copyWith( + isEdit: false, + selectedRules: {}, + ), + ); + }, + ), + ); + }, + ), + ), + ); + } +} + +class OverrideSwitch extends ConsumerWidget { + const OverrideSwitch({ + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final enable = ref.watch( + profileOverrideStateProvider.select( + (state) => state.overrideData?.enable, + ), + ); + return CommonCard( + onPressed: () {}, + type: CommonCardType.filled, + radius: 18, + child: ListItem.switchItem( + padding: const EdgeInsets.only( + left: 16, + right: 16, + top: 4, + bottom: 4, + ), + title: Text(appLocalizations.enableOverride), + delegate: SwitchDelegate( + value: enable ?? false, + onChanged: (value) { + ref.read(profileOverrideStateProvider.notifier).updateState( + (state) => state.copyWith.overrideData!( + enable: value, + ), + ); + }, + ), + ), + ); + } +} + +class RuleTitle extends ConsumerWidget { + final String profileId; + + const RuleTitle({ + super.key, + required this.profileId, + }); + + _handleChangeType(WidgetRef ref, isOverrideRule) { + ref.read(profileOverrideStateProvider.notifier).updateState( + (state) => state.copyWith.overrideData!.rule( + type: isOverrideRule + ? OverrideRuleType.added + : OverrideRuleType.override, + ), + ); + } + + @override + Widget build(BuildContext context, ref) { + final vm3 = ref.watch( + profileOverrideStateProvider.select( + (state) { + final overrideRule = state.overrideData?.rule; + return VM3( + a: state.isEdit, + b: state.selectedRules.containsAll( + overrideRule?.rules.map((item) => item.id).toSet() ?? {}, + ), + c: overrideRule?.type == OverrideRuleType.override, + ); + }, + ), + ); + final isEdit = vm3.a; + final isSelectAll = vm3.b; + final isOverrideRule = vm3.c; + return FilledButtonTheme( + data: FilledButtonThemeData( + style: ButtonStyle( + padding: WidgetStatePropertyAll(EdgeInsets.symmetric( + horizontal: 8, + )), + visualDensity: VisualDensity.compact, + ), + ), + child: IconButtonTheme( + data: IconButtonThemeData( + style: ButtonStyle( + padding: WidgetStatePropertyAll(EdgeInsets.zero), + visualDensity: VisualDensity.compact, + iconSize: WidgetStatePropertyAll(20), + ), + ), + child: ListHeader( + title: appLocalizations.rule, + subTitle: isOverrideRule + ? appLocalizations.overrideOriginRules + : appLocalizations.addedOriginRules, + space: 8, + actions: [ + if (!isEdit) + IconButton.filledTonal( + icon: Icon( + isOverrideRule ? Icons.edit_document : Icons.note_add, + ), + onPressed: () { + _handleChangeType( + ref, + isOverrideRule, + ); + }, + ), + !isEdit + ? FilledButton.tonal( + onPressed: () { + globalState.appController.handleAddOrUpdate(ref); + }, + child: Text(appLocalizations.add), + ) + : isSelectAll + ? FilledButton( + onPressed: () { + ref + .read(profileOverrideStateProvider.notifier) + .updateState( + (state) => state.copyWith( + selectedRules: {}, + ), + ); + }, + child: Text(appLocalizations.selectAll), + ) + : FilledButton.tonal( + onPressed: () { + ref + .read(profileOverrideStateProvider.notifier) + .updateState( + (state) => state.copyWith( + selectedRules: state.overrideData?.rule.rules + .map((item) => item.id) + .toSet() ?? + {}, + ), + ); + }, + child: Text(appLocalizations.selectAll), + ), + ], + ), + ), + ); + } +} + +class RuleContent extends ConsumerWidget { + final Key ruleListKey; + final double maxWidth; + + const RuleContent({ + super.key, + required this.ruleListKey, + required this.maxWidth, + }); + + Widget _proxyDecorator( + Widget child, + int index, + Animation animation, + ) { + return AnimatedBuilder( + animation: animation, + builder: (_, Widget? child) { + final double animValue = Curves.easeInOut.transform(animation.value); + final double scale = lerpDouble(1, 1.02, animValue)!; + return Transform.scale( + scale: scale, + child: child, + ); + }, + child: child, + ); + } + + Widget _buildItem(Rule rule, int index) { + return Consumer( + builder: (context, ref, ___) { + final vm2 = ref.watch(profileOverrideStateProvider.select( + (item) => VM2( + a: item.isEdit, + b: item.selectedRules.contains(rule.id), + ), + )); + final isEdit = vm2.a; + final isSelected = vm2.b; + + return Material( + color: Colors.transparent, + child: Container( + margin: EdgeInsets.symmetric( + vertical: 4, + ), + alignment: Alignment.center, + decoration: BoxDecoration( + color: isSelected + ? context.colorScheme.secondaryContainer.opacity80 + : context.colorScheme.surfaceContainer, + borderRadius: BorderRadius.circular(18), + ), + clipBehavior: Clip.hardEdge, + child: ListTile( + minTileHeight: 0, + minVerticalPadding: 0, + titleTextStyle: context.textTheme.bodyMedium?.toJetBrainsMono, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 16, + ), + trailing: SizedBox( + width: 24, + height: 24, + child: !isEdit + ? ReorderableDragStartListener( + index: index, + child: const Icon(Icons.drag_handle), + ) + : CommonCheckBox( + value: isSelected, + isCircle: true, + onChanged: (_) { + _handleSelect(ref, rule); + }, + ), + ), + title: Text(rule.value), + ), + ), + ); + }, + ); + } + + _handleSelect(WidgetRef ref, ruleId) { + if (!ref.read(profileOverrideStateProvider).isEdit) { + return; + } + ref.read(profileOverrideStateProvider.notifier).updateState( + (state) { + final newSelectedRules = Set.from(state.selectedRules); + if (newSelectedRules.contains(ruleId)) { + newSelectedRules.remove(ruleId); + } else { + newSelectedRules.add(ruleId); + } + return state.copyWith( + selectedRules: newSelectedRules, + ); + }, + ); + } + + @override + Widget build(BuildContext context, ref) { + final vm2 = ref.watch( + profileOverrideStateProvider.select( + (state) { + final overrideRule = state.overrideData?.rule; + return VM2( + a: overrideRule?.rules ?? [], + b: overrideRule?.type ?? OverrideRuleType.added, + ); + }, + ), + ); + final rules = vm2.a; + final type = vm2.b; + if (rules.isEmpty) { + return SliverToBoxAdapter( + child: SizedBox( + height: 300, + child: Center( + child: type == OverrideRuleType.added + ? Text( + appLocalizations.noData, + ) + : FilledButton( + onPressed: () { + final rules = ref.read( + profileOverrideStateProvider.select( + (state) => state.snippet?.rule ?? [], + ), + ); + ref + .read(profileOverrideStateProvider.notifier) + .updateState( + (state) { + return state.copyWith.overrideData!.rule( + overrideRules: rules, + ); + }, + ); + }, + child: Text(appLocalizations.getOriginRules), + ), + ), + ), + ); + } + return CacheItemExtentSliverReorderableList( + key: ruleListKey, + itemBuilder: (context, index) { + final rule = rules[index]; + return GestureDetector( + key: ObjectKey(rule), + child: _buildItem( + rule, + index, + ), + onTap: () { + _handleSelect(ref, rule.id); + }, + onLongPress: () { + if (ref.read(profileOverrideStateProvider).isEdit) { + return; + } + ref.read(profileOverrideStateProvider.notifier).updateState( + (state) => state.copyWith( + isEdit: true, + selectedRules: { + rule.id, + }, + ), + ); + }, + ); + }, + proxyDecorator: _proxyDecorator, + itemCount: rules.length, + onReorder: (oldIndex, newIndex) { + if (oldIndex < newIndex) { + newIndex -= 1; + } + final newRules = List.from(rules); + final item = newRules.removeAt(oldIndex); + newRules.insert(newIndex, item); + ref.read(profileOverrideStateProvider.notifier).updateState( + (state) => state.copyWith.overrideData!( + rule: state.overrideData!.rule.updateRules((_) => newRules), + ), + ); + }, + keyBuilder: (int index) { + return rules[index].value; + }, + itemExtentBuilder: (index) { + final rule = rules[index]; + return 40 + + globalState.measure + .computeTextSize( + Text( + rule.value, + style: context.textTheme.bodyMedium?.toJetBrainsMono, + ), + maxWidth: maxWidth, + ) + .height; + }, + ); + } +} + +class AddRuleDialog extends StatefulWidget { + final ClashConfigSnippet snippet; + final Rule? rule; + + const AddRuleDialog({ + super.key, + required this.snippet, + this.rule, + }); + + @override + State createState() => _AddRuleDialogState(); +} + +class _AddRuleDialogState extends State { + late RuleAction _ruleAction; + final _ruleTargetController = TextEditingController(); + final _contentController = TextEditingController(); + final _ruleProviderController = TextEditingController(); + final _subRuleController = TextEditingController(); + bool _noResolve = false; + bool _src = false; + List _targetItems = []; + List _ruleProviderItems = []; + List _subRuleItems = []; + final _formKey = GlobalKey(); + + @override + void initState() { + _initState(); + super.initState(); + } + + _initState() { + _targetItems = [ + ...widget.snippet.proxyGroups.map( + (item) => DropdownMenuEntry( + value: item.name, + label: item.name, + ), + ), + ...RuleTarget.values.map( + (item) => DropdownMenuEntry( + value: item.name, + label: item.name, + ), + ), + ]; + _ruleProviderItems = [ + ...widget.snippet.ruleProvider.map( + (item) => DropdownMenuEntry( + value: item.name, + label: item.name, + ), + ), + ]; + _subRuleItems = [ + ...widget.snippet.subRules.map( + (item) => DropdownMenuEntry( + value: item.name, + label: item.name, + ), + ), + ]; + if (widget.rule != null) { + final parsedRule = ParsedRule.parseString(widget.rule!.value); + _ruleAction = parsedRule.ruleAction; + _contentController.text = parsedRule.content ?? ""; + _ruleTargetController.text = parsedRule.ruleTarget ?? ""; + _ruleProviderController.text = parsedRule.ruleProvider ?? ""; + _subRuleController.text = parsedRule.subRule ?? ""; + _noResolve = parsedRule.noResolve; + _src = parsedRule.src; + return; + } + _ruleAction = RuleAction.values.first; + if (_targetItems.isNotEmpty) { + _ruleTargetController.text = _targetItems.first.value; + } + if (_ruleProviderItems.isNotEmpty) { + _ruleProviderController.text = _ruleProviderItems.first.value; + } + if (_subRuleItems.isNotEmpty) { + _subRuleController.text = _subRuleItems.first.value; + } + } + + @override + void didUpdateWidget(AddRuleDialog oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.rule != widget.rule) { + _initState(); + } + } + + _handleSubmit() { + final res = _formKey.currentState?.validate(); + if (res == false) { + return; + } + final parsedRule = ParsedRule( + ruleAction: _ruleAction, + content: _contentController.text, + ruleProvider: _ruleProviderController.text, + ruleTarget: _ruleTargetController.text, + subRule: _subRuleController.text, + noResolve: _noResolve, + src: _src, + ); + final rule = widget.rule != null + ? widget.rule!.copyWith(value: parsedRule.value) + : Rule.value( + parsedRule.value, + ); + Navigator.of(context).pop(rule); + } + + @override + Widget build(BuildContext context) { + return CommonDialog( + title: appLocalizations.addRule, + actions: [ + TextButton( + onPressed: _handleSubmit, + child: Text( + appLocalizations.confirm, + ), + ), + ], + child: DropdownMenuTheme( + data: DropdownMenuThemeData( + inputDecorationTheme: InputDecorationTheme( + border: OutlineInputBorder(), + labelStyle: context.textTheme.bodyLarge + ?.copyWith(overflow: TextOverflow.ellipsis), + ), + ), + child: Form( + key: _formKey, + child: LayoutBuilder( + builder: (_, constraints) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FilledButton.tonal( + onPressed: () async { + _ruleAction = + await globalState.showCommonDialog( + child: OptionsDialog( + title: appLocalizations.ruleName, + options: RuleAction.values, + textBuilder: (item) => item.value, + value: _ruleAction, + ), + ) ?? + _ruleAction; + setState(() {}); + }, + child: Text(_ruleAction.name), + ), + SizedBox( + height: 24, + ), + _ruleAction == RuleAction.RULE_SET + ? FormField( + validator: (_) { + if (_ruleProviderController.text.isEmpty) { + return appLocalizations.ruleProviderEmptyTip; + } + return null; + }, + builder: (field) { + return DropdownMenu( + expandedInsets: EdgeInsets.zero, + controller: _ruleProviderController, + label: Text(appLocalizations.ruleProviders), + menuHeight: 250, + errorText: field.errorText, + dropdownMenuEntries: _ruleProviderItems, + ); + }, + ) + : TextFormField( + controller: _contentController, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: appLocalizations.content, + ), + validator: (_) { + if (_contentController.text.isEmpty) { + return appLocalizations.contentEmptyTip; + } + return null; + }, + ), + SizedBox( + height: 24, + ), + _ruleAction == RuleAction.SUB_RULE + ? FormField( + validator: (_) { + if (_subRuleController.text.isEmpty) { + return appLocalizations.subRuleEmptyTip; + } + return null; + }, + builder: (filed) { + return DropdownMenu( + width: 200, + controller: _subRuleController, + label: Text(appLocalizations.subRule), + menuHeight: 250, + dropdownMenuEntries: _subRuleItems, + ); + }, + ) + : FormField( + validator: (_) { + if (_ruleTargetController.text.isEmpty) { + return appLocalizations.ruleTargetEmptyTip; + } + return null; + }, + builder: (filed) { + return DropdownMenu( + controller: _ruleTargetController, + initialSelection: filed.value, + label: Text(appLocalizations.ruleTarget), + width: 200, + menuHeight: 250, + enableFilter: true, + dropdownMenuEntries: _targetItems, + errorText: filed.errorText, + ); + }, + ), + if (_ruleAction.hasParams) ...[ + SizedBox( + height: 20, + ), + Wrap( + spacing: 8, + children: [ + CommonCard( + radius: 8, + isSelected: _src, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: 8, vertical: 8), + child: Text( + appLocalizations.sourceIp, + style: context.textTheme.bodyMedium, + ), + ), + onPressed: () { + setState(() { + _src = !_src; + }); + }, + ), + CommonCard( + radius: 8, + isSelected: _noResolve, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: 8, vertical: 8), + child: Text( + appLocalizations.noResolve, + style: context.textTheme.bodyMedium, + ), + ), + onPressed: () { + setState(() { + _noResolve = !_noResolve; + }); + }, + ) + ], + ), + ], + SizedBox( + height: 20, + ), + ], + ); + }, + ), + ), + ), + ); + } +} diff --git a/lib/fragments/profiles/profiles.dart b/lib/fragments/profiles/profiles.dart index 7e0d8bf..2934899 100644 --- a/lib/fragments/profiles/profiles.dart +++ b/lib/fragments/profiles/profiles.dart @@ -3,6 +3,7 @@ import 'dart:ui'; import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/fragments/profiles/edit_profile.dart'; +import 'package:fl_clash/fragments/profiles/override_profile.dart'; import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/providers/providers.dart'; import 'package:fl_clash/state.dart'; @@ -11,7 +12,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'add_profile.dart'; -import 'gen_profile.dart'; class ProfilesFragment extends StatefulWidget { const ProfilesFragment({super.key}); @@ -24,12 +24,17 @@ class _ProfilesFragmentState extends State with PageMixin { Function? applyConfigDebounce; _handleShowAddExtendPage() { - showExtendPage( + showExtend( globalState.navigatorKey.currentState!.context, - body: AddProfile( - context: globalState.navigatorKey.currentState!.context, - ), - title: "${appLocalizations.add}${appLocalizations.profile}", + builder: (_, type) { + return AdaptiveSheetScaffold( + type: type, + body: AddProfile( + context: globalState.navigatorKey.currentState!.context, + ), + title: "${appLocalizations.add}${appLocalizations.profile}", + ); + }, ); } @@ -81,12 +86,13 @@ class _ProfilesFragmentState extends State with PageMixin { onPressed: () { final profiles = globalState.config.profiles; showSheet( - title: appLocalizations.profilesSort, context: context, - body: SizedBox( - height: 400, - child: ReorderableProfiles(profiles: profiles), - ), + builder: (_, type) { + return ReorderableProfilesSheet( + type: type, + profiles: profiles, + ); + }, ); }, icon: const Icon(Icons.sort), @@ -205,13 +211,18 @@ class ProfileItem extends StatelessWidget { } _handleShowEditExtendPage(BuildContext context) { - showExtendPage( + showExtend( context, - body: EditProfile( - profile: profile, - context: context, - ), - title: "${appLocalizations.edit}${appLocalizations.profile}", + builder: (_, type) { + return AdaptiveSheetScaffold( + type: type, + body: EditProfile( + profile: profile, + context: context, + ), + title: "${appLocalizations.edit}${appLocalizations.profile}", + ); + }, ); } @@ -277,7 +288,7 @@ class ProfileItem extends StatelessWidget { _handlePushGenProfilePage(BuildContext context, String id) { BaseNavigator.push( context, - GenProfile( + OverrideProfile( profileId: id, ), ); @@ -285,7 +296,6 @@ class ProfileItem extends StatelessWidget { @override Widget build(BuildContext context) { - final key = GlobalKey(); return CommonCard( isSelected: profile.id == groupValue, onPressed: () { @@ -298,17 +308,16 @@ class ProfileItem extends StatelessWidget { trailing: SizedBox( height: 40, width: 40, - child: FadeBox( + child: FadeThroughBox( child: profile.isUpdating ? const Padding( padding: EdgeInsets.all(8), child: CircularProgressIndicator(), ) : CommonPopupBox( - key: key, popup: CommonPopupMenu( items: [ - ActionItemData( + PopupMenuItemData( icon: Icons.edit_outlined, label: appLocalizations.edit, onPressed: () { @@ -316,52 +325,47 @@ class ProfileItem extends StatelessWidget { }, ), if (profile.type == ProfileType.url) ...[ - ActionItemData( + PopupMenuItemData( icon: Icons.sync_alt_sharp, label: appLocalizations.sync, onPressed: () { updateProfile(); }, ), - // ActionItemData( - // icon: Icons.copy, - // label: appLocalizations.copyLink, - // onPressed: () { - // _handleCopyLink(context); - // }, - // ), ], - // ActionItemData( - // icon: Icons.extension_outlined, - // label: "自定义", - // onPressed: () { - // _handlePushGenProfilePage(context, profile.id); - // }, - // ), - ActionItemData( + PopupMenuItemData( + icon: Icons.extension_outlined, + label: appLocalizations.override, + onPressed: () { + _handlePushGenProfilePage(context, profile.id); + }, + ), + PopupMenuItemData( icon: Icons.file_copy_outlined, label: appLocalizations.exportFile, onPressed: () { _handleExportFile(context); }, ), - ActionItemData( + PopupMenuItemData( icon: Icons.delete_outlined, iconSize: 20, label: appLocalizations.delete, onPressed: () { _handleDeleteProfile(context); }, - type: ActionType.danger, + type: PopupMenuItemType.danger, ), ], ), - target: IconButton( - onPressed: () { - key.currentState?.pop(); - }, - icon: Icon(Icons.more_vert), - ), + targetBuilder: (open) { + return IconButton( + onPressed: () { + open(); + }, + icon: Icon(Icons.more_vert), + ); + }, ), ), ), @@ -397,19 +401,22 @@ class ProfileItem extends StatelessWidget { } } -class ReorderableProfiles extends StatefulWidget { +class ReorderableProfilesSheet extends StatefulWidget { final List profiles; + final SheetType type; - const ReorderableProfiles({ + const ReorderableProfilesSheet({ super.key, required this.profiles, + required this.type, }); @override - State createState() => _ReorderableProfilesState(); + State createState() => + _ReorderableProfilesSheetState(); } -class _ReorderableProfilesState extends State { +class _ReorderableProfilesSheetState extends State { late List profiles; @override @@ -453,74 +460,61 @@ class _ReorderableProfilesState extends State { @override Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - flex: 1, - child: ReorderableListView.builder( - buildDefaultDragHandles: false, - padding: const EdgeInsets.symmetric(horizontal: 12), - proxyDecorator: proxyDecorator, - onReorder: (oldIndex, newIndex) { - setState(() { - if (oldIndex < newIndex) { - newIndex -= 1; - } - final profile = profiles.removeAt(oldIndex); - profiles.insert(newIndex, profile); - }); - }, - itemBuilder: (_, index) { - final profile = profiles[index]; - return Container( - key: Key(profile.id), - padding: const EdgeInsets.symmetric(vertical: 4), - child: CommonCard( - type: CommonCardType.filled, - child: ListTile( - contentPadding: const EdgeInsets.only( - right: 16, - left: 16, - ), - title: Text(profile.label ?? profile.id), - trailing: ReorderableDragStartListener( - index: index, - child: const Icon(Icons.drag_handle), - ), + return AdaptiveSheetScaffold( + type: widget.type, + actions: [ + IconButton( + onPressed: () { + Navigator.of(context).pop(); + globalState.appController.setProfiles(profiles); + }, + icon: Icon( + Icons.save, + ), + ) + ], + body: Padding( + padding: EdgeInsets.only(bottom: 32), + child: ReorderableListView.builder( + buildDefaultDragHandles: false, + padding: const EdgeInsets.symmetric( + horizontal: 12, + ), + proxyDecorator: proxyDecorator, + onReorder: (oldIndex, newIndex) { + setState(() { + if (oldIndex < newIndex) { + newIndex -= 1; + } + final profile = profiles.removeAt(oldIndex); + profiles.insert(newIndex, profile); + }); + }, + itemBuilder: (_, index) { + final profile = profiles[index]; + return Container( + key: Key(profile.id), + padding: const EdgeInsets.symmetric(vertical: 4), + child: CommonCard( + type: CommonCardType.filled, + child: ListTile( + contentPadding: const EdgeInsets.only( + right: 16, + left: 16, + ), + title: Text(profile.label ?? profile.id), + trailing: ReorderableDragStartListener( + index: index, + child: const Icon(Icons.drag_handle), ), ), - ); - }, - itemCount: profiles.length, - ), - ), - Container( - padding: const EdgeInsets.symmetric( - vertical: 16, - horizontal: 24, - ), - child: FilledButton.tonal( - onPressed: () { - Navigator.of(context).pop(); - globalState.appController.setProfiles(profiles); - }, - style: ButtonStyle( - padding: WidgetStateProperty.all( - const EdgeInsets.symmetric(vertical: 8), ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - appLocalizations.confirm, - ), - ], - ), - ), + ); + }, + itemCount: profiles.length, ), - ], + ), + title: appLocalizations.profilesSort, ); } } diff --git a/lib/fragments/proxies/card.dart b/lib/fragments/proxies/card.dart index a6020d4..4433335 100644 --- a/lib/fragments/proxies/card.dart +++ b/lib/fragments/proxies/card.dart @@ -178,7 +178,7 @@ class ProxyCard extends StatelessWidget { style: context.textTheme.bodySmall?.copyWith( overflow: TextOverflow.ellipsis, color: - context.textTheme.bodySmall?.color?.toLight, + context.textTheme.bodySmall?.color?.opacity80, ), ), ), @@ -221,7 +221,7 @@ class _ProxyDesc extends ConsumerWidget { desc, overflow: TextOverflow.ellipsis, style: context.textTheme.bodySmall?.copyWith( - color: context.textTheme.bodySmall?.color?.toLight, + color: context.textTheme.bodySmall?.color?.opacity80, ), ); } diff --git a/lib/fragments/proxies/list.dart b/lib/fragments/proxies/list.dart index 324d3e8..9612a60 100644 --- a/lib/fragments/proxies/list.dart +++ b/lib/fragments/proxies/list.dart @@ -467,10 +467,6 @@ class _ListHeaderState extends State return CommonCard( enterAnimated: widget.enterAnimated, key: widget.key, - borderSide: WidgetStatePropertyAll(BorderSide.none), - backgroundColor: WidgetStatePropertyAll( - context.colorScheme.surfaceContainer, - ), radius: 14, type: CommonCardType.filled, child: Padding( @@ -556,6 +552,7 @@ class _ListHeaderState extends State children: [ if (isExpand) ...[ IconButton( + visualDensity: VisualDensity.standard, onPressed: () { widget.onScrollToSelected(groupName); }, @@ -565,12 +562,13 @@ class _ListHeaderState extends State ), IconButton( onPressed: _delayTest, + visualDensity: VisualDensity.standard, icon: const Icon( Icons.network_ping, ), ), const SizedBox( - width: 4, + width: 6, ), ], AnimatedBuilder( diff --git a/lib/fragments/proxies/providers.dart b/lib/fragments/proxies/providers.dart index 95855ea..22ce8a2 100644 --- a/lib/fragments/proxies/providers.dart +++ b/lib/fragments/proxies/providers.dart @@ -13,8 +13,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; typedef UpdatingMap = Map; class ProvidersView extends ConsumerStatefulWidget { + final SheetType type; + const ProvidersView({ super.key, + required this.type, }); @override @@ -22,25 +25,6 @@ class ProvidersView extends ConsumerStatefulWidget { } class _ProvidersViewState extends ConsumerState { - @override - void initState() { - super.initState(); - WidgetsBinding.instance.addPostFrameCallback( - (_) { - globalState.appController.updateProviders(); - context.commonScaffoldState?.actions = [ - IconButton( - onPressed: () { - _updateProviders(); - }, - icon: const Icon( - Icons.sync, - ), - ) - ]; - }, - ); - } _updateProviders() async { final providers = ref.read(providersProvider); @@ -102,10 +86,24 @@ class _ProvidersViewState extends ConsumerState { title: appLocalizations.ruleProviders, items: ruleProviders, ); - return generateListView([ - ...proxySection, - ...ruleSection, - ]); + return AdaptiveSheetScaffold( + actions: [ + IconButton( + onPressed: () { + _updateProviders(); + }, + icon: const Icon( + Icons.sync, + ), + ) + ], + type: widget.type, + body: generateListView([ + ...proxySection, + ...ruleSection, + ]), + title: appLocalizations.providers, + ); } } @@ -222,7 +220,7 @@ class ProviderItem extends StatelessWidget { trailing: SizedBox( height: 48, width: 48, - child: FadeBox( + child: FadeThroughBox( child: provider.isUpdating ? const Padding( padding: EdgeInsets.all(8), diff --git a/lib/fragments/proxies/proxies.dart b/lib/fragments/proxies/proxies.dart index b414ccc..d454726 100644 --- a/lib/fragments/proxies/proxies.dart +++ b/lib/fragments/proxies/proxies.dart @@ -28,12 +28,13 @@ class _ProxiesFragmentState extends ConsumerState if (_hasProviders) IconButton( onPressed: () { - showExtendPage( - isScaffold: true, - extendPageWidth: 360, + showExtend( context, - body: const ProvidersView(), - title: appLocalizations.providers, + builder: (_, type) { + return ProvidersView( + type: type, + ); + }, ); }, icon: const Icon( @@ -51,11 +52,15 @@ class _ProxiesFragmentState extends ConsumerState ) : IconButton( onPressed: () { - showExtendPage( + showExtend( context, - extendPageWidth: 360, - title: appLocalizations.iconConfiguration, - body: _IconConfigView(), + builder: (_, type) { + return AdaptiveSheetScaffold( + type: type, + body: const _IconConfigView(), + title: appLocalizations.iconConfiguration, + ); + }, ); }, icon: const Icon( @@ -65,9 +70,17 @@ class _ProxiesFragmentState extends ConsumerState IconButton( onPressed: () { showSheet( - title: appLocalizations.proxiesSetting, context: context, - body: const ProxiesSetting(), + props: SheetProps( + isScrollControlled: true, + ), + builder: (_, type) { + return AdaptiveSheetScaffold( + type: type, + body: const ProxiesSetting(), + title: appLocalizations.proxiesSetting, + ); + }, ); }, icon: const Icon( @@ -128,13 +141,11 @@ class _IconConfigView extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final iconMap = ref.watch(proxiesStyleSettingProvider.select((state) => state.iconMap)); - final entries = iconMap.entries.toList(); - return ListPage( + return MapInputPage( title: appLocalizations.iconConfiguration, - items: entries, + map: iconMap, keyLabel: appLocalizations.regExp, valueLabel: appLocalizations.icon, - keyBuilder: (item) => Key(item.key), titleBuilder: (item) => Text(item.key), leadingBuilder: (item) => Container( decoration: BoxDecoration( @@ -151,10 +162,10 @@ class _IconConfigView extends ConsumerWidget { maxLines: 2, overflow: TextOverflow.ellipsis, ), - onChange: (entries) { + onChange: (value) { ref.read(proxiesStyleSettingProvider.notifier).updateState( (state) => state.copyWith( - iconMap: Map.fromEntries(entries), + iconMap: value, ), ); }, diff --git a/lib/fragments/proxies/setting.dart b/lib/fragments/proxies/setting.dart index a654eb3..c2cc09f 100644 --- a/lib/fragments/proxies/setting.dart +++ b/lib/fragments/proxies/setting.dart @@ -248,8 +248,8 @@ class ProxiesSetting extends StatelessWidget { @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.only(bottom: 32), + return SingleChildScrollView( + padding: EdgeInsets.only(bottom: 32), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -268,6 +268,7 @@ class ProxiesSetting extends StatelessWidget { return Container(); }, child: Column( + mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ ..._buildGroupStyleSetting(), diff --git a/lib/fragments/proxies/tab.dart b/lib/fragments/proxies/tab.dart index b476098..9d29656 100644 --- a/lib/fragments/proxies/tab.dart +++ b/lib/fragments/proxies/tab.dart @@ -49,7 +49,7 @@ class ProxiesTabFragmentState extends ConsumerState _buildMoreButton() { return Consumer( builder: (_, ref, ___) { - final isMobileView = ref.watch(viewWidthProvider.notifier).isMobileView; + final isMobileView = ref.watch(isMobileViewProvider); return IconButton( onPressed: _showMoreMenu, icon: isMobileView @@ -67,42 +67,48 @@ class ProxiesTabFragmentState extends ConsumerState _showMoreMenu() { showSheet( context: context, - width: 380, - isScrollControlled: false, - body: SingleChildScrollView( - padding: const EdgeInsets.all(16), - child: Consumer( - builder: (_, ref, __) { - final state = ref.watch(proxiesSelectorStateProvider); - return SizedBox( - width: double.infinity, - child: Wrap( - alignment: WrapAlignment.center, - runSpacing: 8, - spacing: 8, - children: [ - for (final groupName in state.groupNames) - SettingTextCard( - groupName, - onPressed: () { - final index = state.groupNames.indexWhere( - (item) => item == groupName, - ); - if (index == -1) return; - _tabController?.animateTo(index); - globalState.appController - .updateCurrentGroupName(groupName); - Navigator.of(context).pop(); - }, - isSelected: groupName == state.currentGroupName, - ) - ], - ), - ); - }, - ), + props: SheetProps( + isScrollControlled: false, ), - title: appLocalizations.proxyGroup, + builder: (_, type) { + return AdaptiveSheetScaffold( + type: type, + body: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Consumer( + builder: (_, ref, __) { + final state = ref.watch(proxiesSelectorStateProvider); + return SizedBox( + width: double.infinity, + child: Wrap( + alignment: WrapAlignment.center, + runSpacing: 8, + spacing: 8, + children: [ + for (final groupName in state.groupNames) + SettingTextCard( + groupName, + onPressed: () { + final index = state.groupNames.indexWhere( + (item) => item == groupName, + ); + if (index == -1) return; + _tabController?.animateTo(index); + globalState.appController + .updateCurrentGroupName(groupName); + Navigator.of(context).pop(); + }, + isSelected: groupName == state.currentGroupName, + ) + ], + ), + ); + }, + ), + ), + title: appLocalizations.proxyGroup, + ); + }, ); } @@ -238,7 +244,7 @@ class ProxiesTabFragmentState extends ConsumerState begin: Alignment.centerLeft, end: Alignment.centerRight, colors: [ - context.colorScheme.surface.withOpacity(0.1), + context.colorScheme.surface.opacity10, context.colorScheme.surface, ], stops: const [ @@ -319,32 +325,35 @@ class ProxyGroupViewState extends ConsumerState { ); return Align( alignment: Alignment.topCenter, - child: GridView.builder( + child: CommonAutoHiddenScrollBar( controller: _controller, - padding: const EdgeInsets.only( - top: 16, - left: 16, - right: 16, - bottom: 96, + child: GridView.builder( + controller: _controller, + padding: const EdgeInsets.only( + top: 16, + left: 16, + right: 16, + bottom: 96, + ), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: columns, + mainAxisSpacing: 8, + crossAxisSpacing: 8, + mainAxisExtent: getItemHeight(proxyCardType), + ), + itemCount: sortedProxies.length, + itemBuilder: (_, index) { + final proxy = sortedProxies[index]; + return ProxyCard( + testUrl: state.testUrl, + groupType: state.groupType, + type: proxyCardType, + key: ValueKey('$groupName.${proxy.name}'), + proxy: proxy, + groupName: groupName, + ); + }, ), - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: columns, - mainAxisSpacing: 8, - crossAxisSpacing: 8, - mainAxisExtent: getItemHeight(proxyCardType), - ), - itemCount: sortedProxies.length, - itemBuilder: (_, index) { - final proxy = sortedProxies[index]; - return ProxyCard( - testUrl: state.testUrl, - groupType: state.groupType, - type: proxyCardType, - key: ValueKey('$groupName.${proxy.name}'), - proxy: proxy, - groupName: groupName, - ); - }, ), ); } diff --git a/lib/fragments/resources.dart b/lib/fragments/resources.dart index 694bc91..22b7508 100644 --- a/lib/fragments/resources.dart +++ b/lib/fragments/resources.dart @@ -149,7 +149,7 @@ class _GeoDataListItemState extends State { builder: (_, snapshot) { return SizedBox( height: 24, - child: FadeBox( + child: FadeThroughBox( key: Key("fade_box_${geoItem.label}"), child: snapshot.data == null ? const SizedBox( @@ -248,7 +248,7 @@ class _GeoDataListItemState extends State { child: ValueListenableBuilder( valueListenable: isUpdating, builder: (_, isUpdating, ___) { - return FadeBox( + return FadeThroughBox( child: isUpdating ? const Padding( padding: EdgeInsets.all(8), @@ -299,24 +299,8 @@ class _UpdateGeoUrlFormDialogState extends State { @override Widget build(BuildContext context) { - return AlertDialog( - title: Text(widget.title), - content: SizedBox( - width: 300, - child: Wrap( - runSpacing: 16, - children: [ - TextField( - maxLines: 5, - minLines: 1, - controller: urlController, - decoration: const InputDecoration( - border: OutlineInputBorder(), - ), - ), - ], - ), - ), + return CommonDialog( + title: widget.title, actions: [ if (widget.defaultValue != null && urlController.value.text != widget.defaultValue) ...[ @@ -333,6 +317,19 @@ class _UpdateGeoUrlFormDialogState extends State { child: Text(appLocalizations.submit), ) ], + child: Wrap( + runSpacing: 16, + children: [ + TextField( + maxLines: 5, + minLines: 1, + controller: urlController, + decoration: const InputDecoration( + border: OutlineInputBorder(), + ), + ), + ], + ), ); } } diff --git a/lib/fragments/theme.dart b/lib/fragments/theme.dart index d20f0e0..1d62d31 100644 --- a/lib/fragments/theme.dart +++ b/lib/fragments/theme.dart @@ -291,12 +291,12 @@ class _PrimaryColorItem extends ConsumerWidget { itemBuilder: (_, index) { final color = primaryColors[index]; return ColorSchemeBox( - isSelected: color?.value == primaryColor, + isSelected: color?.toARGB32() == primaryColor, primaryColor: color, onPressed: () { ref.read(themeSettingProvider.notifier).updateState( (state) => state.copyWith( - primaryColor: color?.value, + primaryColor: color?.toARGB32(), ), ); }, diff --git a/lib/fragments/tools.dart b/lib/fragments/tools.dart index 9a04a48..9f85d6d 100644 --- a/lib/fragments/tools.dart +++ b/lib/fragments/tools.dart @@ -35,7 +35,6 @@ class _ToolboxFragmentState extends ConsumerState { delegate: OpenDelegate( title: Intl.message(navigationItem.label.name), widget: navigationItem.fragment, - extendPageWidth: 360, ), ); } @@ -65,7 +64,7 @@ class _ToolboxFragmentState extends ConsumerState { ); } - List _getSettingList() { + _getSettingList() { return generateSection( title: appLocalizations.settings, items: [ @@ -75,7 +74,7 @@ class _ToolboxFragmentState extends ConsumerState { if (system.isDesktop) _HotkeyItem(), if (Platform.isWindows) _LoopbackItem(), if (Platform.isAndroid) _AccessItem(), - _OverrideItem(), + _ConfigItem(), _SettingItem(), ], ); @@ -155,7 +154,6 @@ class _ThemeItem extends StatelessWidget { delegate: OpenDelegate( title: appLocalizations.theme, widget: const ThemeFragment(), - extendPageWidth: 360, ), ); } @@ -231,15 +229,15 @@ class _AccessItem extends StatelessWidget { } } -class _OverrideItem extends StatelessWidget { - const _OverrideItem(); +class _ConfigItem extends StatelessWidget { + const _ConfigItem(); @override Widget build(BuildContext context) { return ListItem.open( leading: const Icon(Icons.edit), - title: Text(appLocalizations.override), - subtitle: Text(appLocalizations.overrideDesc), + title: Text(appLocalizations.basicConfig), + subtitle: Text(appLocalizations.basicConfigDesc), delegate: OpenDelegate( title: appLocalizations.override, widget: const ConfigFragment(), diff --git a/lib/l10n/arb/intl_en.arb b/lib/l10n/arb/intl_en.arb index b8d4642..d21add9 100644 --- a/lib/l10n/arb/intl_en.arb +++ b/lib/l10n/arb/intl_en.arb @@ -123,7 +123,6 @@ "project": "Project", "core": "Core", "tabAnimation": "Tab animation", - "tabAnimationDesc": "When enabled, the home tab will add a toggle animation", "desc": "A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.", "startVpn": "Starting VPN...", "stopVpn": "Stopping VPN...", @@ -181,7 +180,6 @@ "requests": "Requests", "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", @@ -249,7 +247,6 @@ "stop": "Stop", "appDesc": "Processing app related settings", "vpnDesc": "Modify VPN related settings", - "generalDesc": "Overwrite general settings", "dnsDesc": "Update DNS related settings", "key": "Key", "value": "Value", @@ -346,5 +343,34 @@ "exportFile": "Export file", "cacheCorrupt": "The cache is corrupt. Do you want to clear it?", "detectionTip": "Relying on third-party api is for reference only", - "listen": "Listen" + "listen": "Listen", + "keyExists": "The current key already exists", + "valueExists": "The current value already exists", + "undo": "undo", + "redo": "redo", + "none": "none", + "basicConfig": "Basic configuration", + "basicConfigDesc": "Modify the basic configuration globally", + "selectedCountTitle": "{count} items have been selected", + "addRule": "Add rule", + "ruleProviderEmptyTip": "Rule provider cannot be empty", + "ruleName": "Rule name", + "content": "Content", + "contentEmptyTip": "Content cannot be empty", + "subRule": "Sub rule", + "subRuleEmptyTip": "Sub rule content cannot be empty", + "ruleTarget": "Rule target", + "ruleTargetEmptyTip": "Rule target cannot be empty", + "sourceIp": "Source IP", + "noResolve": "No resolve IP", + "getOriginRules": "Get original rules", + "overrideOriginRules": "Override the original rule", + "addedOriginRules": "Attach on the original rules", + "enableOverride": "Enable override", + "deleteRuleTip": "Are you sure you want to delete the selected rule?", + "saveChanges": "Do you want to save the changes?", + "generalDesc": "Modify general settings", + "findProcessModeDesc": "There is a certain performance loss after opening", + "tabAnimationDesc": "Effective only in mobile view", + "saveTip": "Are you sure you want to save?" } \ No newline at end of file diff --git a/lib/l10n/arb/intl_ja.arb b/lib/l10n/arb/intl_ja.arb index aac5802..7c64a38 100644 --- a/lib/l10n/arb/intl_ja.arb +++ b/lib/l10n/arb/intl_ja.arb @@ -123,7 +123,6 @@ "project": "プロジェクト", "core": "コア", "tabAnimation": "タブアニメーション", - "tabAnimationDesc": "有効化するとホームタブに切り替えアニメーションを追加", "desc": "ClashMetaベースのマルチプラットフォームプロキシクライアント。シンプルで使いやすく、オープンソースで広告なし。", "startVpn": "VPNを開始中...", "stopVpn": "VPNを停止中...", @@ -181,7 +180,6 @@ "requests": "リクエスト", "requestsDesc": "最近のリクエスト記録を表示", "findProcessMode": "プロセス検出", - "findProcessModeDesc": "有効化するとフラッシュバックのリスクあり", "init": "初期化", "infiniteTime": "長期有効", "expirationTime": "有効期限", @@ -249,7 +247,6 @@ "stop": "停止", "appDesc": "アプリ関連設定の処理", "vpnDesc": "VPN関連設定の変更", - "generalDesc": "一般設定の上書き", "dnsDesc": "DNS関連設定の更新", "key": "キー", "value": "値", @@ -346,5 +343,34 @@ "exportFile": "ファイルをエクスポート", "cacheCorrupt": "キャッシュが破損しています。クリアしますか?", "detectionTip": "サードパーティAPIに依存(参考値)", - "listen": "リスン" + "listen": "リスン", + "keyExists": "現在のキーは既に存在します", + "valueExists": "現在の値は既に存在します", + "undo": "元に戻す", + "redo": "やり直す", + "none": "なし", + "basicConfig": "基本設定", + "basicConfigDesc": "基本設定をグローバルに変更", + "selectedCountTitle": "{count} 項目が選択されています", + "addRule": "ルールを追加", + "ruleProviderEmptyTip": "ルールプロバイダーは必須です", + "ruleName": "ルール名", + "content": "内容", + "contentEmptyTip": "内容は必須です", + "subRule": "サブルール", + "subRuleEmptyTip": "サブルールの内容は必須です", + "ruleTarget": "ルール対象", + "ruleTargetEmptyTip": "ルール対象は必須です", + "sourceIp": "送信元IP", + "noResolve": "IPを解決しない", + "getOriginRules": "元のルールを取得", + "overrideOriginRules": "元のルールを上書き", + "addedOriginRules": "元のルールに追加", + "enableOverride": "上書きを有効化", + "deleteRuleTip": "選択したルールを削除しますか?", + "saveChanges": "変更を保存しますか?", + "generalDesc": "一般設定を変更", + "findProcessModeDesc": "有効化するとパフォーマンスが若干低下します", + "tabAnimationDesc": "モバイル表示でのみ有効", + "saveTip": "保存してもよろしいですか?" } \ No newline at end of file diff --git a/lib/l10n/arb/intl_ru.arb b/lib/l10n/arb/intl_ru.arb index 7971818..5f40f90 100644 --- a/lib/l10n/arb/intl_ru.arb +++ b/lib/l10n/arb/intl_ru.arb @@ -123,7 +123,6 @@ "project": "Проект", "core": "Ядро", "tabAnimation": "Анимация вкладок", - "tabAnimationDesc": "При включении домашняя вкладка добавит анимацию переключения", "desc": "Многоплатформенный прокси-клиент на основе ClashMeta, простой и удобный в использовании, с открытым исходным кодом и без рекламы.", "startVpn": "Запуск VPN...", "stopVpn": "Остановка VPN...", @@ -181,7 +180,6 @@ "requests": "Запросы", "requestsDesc": "Просмотр последних записей запросов", "findProcessMode": "Режим поиска процесса", - "findProcessModeDesc": "Есть риск сбоя после включения", "init": "Инициализация", "infiniteTime": "Долгосрочное действие", "expirationTime": "Время истечения", @@ -249,7 +247,6 @@ "stop": "Стоп", "appDesc": "Обработка настроек, связанных с приложением", "vpnDesc": "Изменение настроек, связанных с VPN", - "generalDesc": "Переопределение общих настроек", "dnsDesc": "Обновление настроек, связанных с DNS", "key": "Ключ", "value": "Значение", @@ -346,5 +343,34 @@ "exportFile": "Экспорт файла", "cacheCorrupt": "Кэш поврежден. Хотите очистить его?", "detectionTip": "Опирается на сторонний API, только для справки", - "listen": "Слушать" + "listen": "Слушать", + "keyExists": "Текущий ключ уже существует", + "valueExists": "Текущее значение уже существует", + "undo": "Отменить", + "redo": "Повторить", + "none": "Нет", + "basicConfig": "Базовая конфигурация", + "basicConfigDesc": "Глобальное изменение базовых настроек", + "selectedCountTitle": "Выбрано {count} элементов", + "addRule": "Добавить правило", + "ruleProviderEmptyTip": "Поставщик правил не может быть пустым", + "ruleName": "Название правила", + "content": "Содержание", + "contentEmptyTip": "Содержание не может быть пустым", + "subRule": "Подправило", + "subRuleEmptyTip": "Содержание подправила не может быть пустым", + "ruleTarget": "Цель правила", + "ruleTargetEmptyTip": "Цель правила не может быть пустой", + "sourceIp": "Исходный IP", + "noResolve": "Не разрешать IP", + "getOriginRules": "Получить оригинальные правила", + "overrideOriginRules": "Переопределить оригинальное правило", + "addedOriginRules": "Добавить к оригинальным правилам", + "enableOverride": "Включить переопределение", + "deleteRuleTip": "Вы уверены, что хотите удалить выбранное правило?", + "saveChanges": "Сохранить изменения?", + "generalDesc": "Изменение общих настроек", + "findProcessModeDesc": "При включении возможны небольшие потери производительности", + "tabAnimationDesc": "Действительно только в мобильном виде", + "saveTip": "Вы уверены, что хотите сохранить?" } \ No newline at end of file diff --git a/lib/l10n/arb/intl_zh_CN.arb b/lib/l10n/arb/intl_zh_CN.arb index 78950e7..7b32e0b 100644 --- a/lib/l10n/arb/intl_zh_CN.arb +++ b/lib/l10n/arb/intl_zh_CN.arb @@ -123,7 +123,6 @@ "project": "项目", "core": "内核", "tabAnimation": "选项卡动画", - "tabAnimationDesc": "开启后,主页选项卡将添加切换动画", "desc": "基于ClashMeta的多平台代理客户端,简单易用,开源无广告。", "startVpn": "正在启动VPN...", "stopVpn": "正在停止VPN...", @@ -169,7 +168,7 @@ "externalControllerDesc": "开启后将可以通过9090端口控制Clash内核", "ipv6Desc": "开启后将可以接收IPv6流量", "app": "应用", - "general": "基础", + "general": "常规", "vpnSystemProxyDesc": "为VpnService附加HTTP代理", "systemProxyDesc": "设置系统代理", "unifiedDelay": "统一延迟", @@ -181,7 +180,6 @@ "requests": "请求", "requestsDesc": "查看最近请求记录", "findProcessMode": "查找进程", - "findProcessModeDesc": "开启后存在闪退风险", "init": "初始化", "infiniteTime": "长期有效", "expirationTime": "到期时间", @@ -249,7 +247,6 @@ "stop": "暂停", "appDesc": "处理应用相关设置", "vpnDesc": "修改VPN相关设置", - "generalDesc": "覆写基础设置", "dnsDesc": "更新DNS相关设置", "key": "键", "value": "值", @@ -346,5 +343,34 @@ "exportFile": "导出文件", "cacheCorrupt": "缓存已损坏,是否清空?", "detectionTip": "依赖第三方api,仅供参考", - "listen": "监听" + "listen": "监听", + "keyExists": "当前键已存在", + "valueExists": "当前值已存在", + "undo": "撤销", + "redo": "重做", + "none": "无", + "basicConfig": "基本配置", + "basicConfigDesc": "全局修改基本配置", + "selectedCountTitle": "已选择 {count} 项", + "addRule": "添加规则", + "ruleProviderEmptyTip": "规则提供者不能为空", + "ruleName": "规则名称", + "content": "内容", + "contentEmptyTip": "内容不能为空", + "subRule": "子规则", + "subRuleEmptyTip": "子规则内容不能为空", + "ruleTarget": "规则目标", + "ruleTargetEmptyTip": "规则目标不能为空", + "sourceIp": "源IP", + "noResolve": "不解析IP", + "getOriginRules": "获取原始规则", + "overrideOriginRules": "覆盖原始规则", + "addedOriginRules": "附加到原始规则", + "enableOverride": "启用覆写", + "deleteRuleTip": "确定要删除选中的规则吗?", + "saveChanges": "是否保存更改?", + "generalDesc": "修改通用设置", + "findProcessModeDesc": "开启后会有一定性能损耗", + "tabAnimationDesc": "仅在移动视图中有效", + "saveTip": "确定要保存吗?" } diff --git a/lib/l10n/intl/messages_en.dart b/lib/l10n/intl/messages_en.dart index 6a83638..8b63e4b 100644 --- a/lib/l10n/intl/messages_en.dart +++ b/lib/l10n/intl/messages_en.dart @@ -20,6 +20,8 @@ typedef String MessageIfAbsent(String messageStr, List args); class MessageLookup extends MessageLookupByLibrary { String get localeName => 'en'; + static String m0(count) => "${count} items have been selected"; + final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { "about": MessageLookupByLibrary.simpleMessage("About"), @@ -44,6 +46,10 @@ class MessageLookup extends MessageLookupByLibrary { "action_tun": MessageLookupByLibrary.simpleMessage("TUN"), "action_view": MessageLookupByLibrary.simpleMessage("Show/Hide"), "add": MessageLookupByLibrary.simpleMessage("Add"), + "addRule": MessageLookupByLibrary.simpleMessage("Add rule"), + "addedOriginRules": MessageLookupByLibrary.simpleMessage( + "Attach on the original rules", + ), "address": MessageLookupByLibrary.simpleMessage("Address"), "addressHelp": MessageLookupByLibrary.simpleMessage( "WebDAV server address", @@ -114,6 +120,10 @@ class MessageLookup extends MessageLookupByLibrary { "Sync data via WebDAV or file", ), "backupSuccess": MessageLookupByLibrary.simpleMessage("Backup success"), + "basicConfig": MessageLookupByLibrary.simpleMessage("Basic configuration"), + "basicConfigDesc": MessageLookupByLibrary.simpleMessage( + "Modify the basic configuration globally", + ), "bind": MessageLookupByLibrary.simpleMessage("Bind"), "blacklistMode": MessageLookupByLibrary.simpleMessage("Blacklist mode"), "bypassDomain": MessageLookupByLibrary.simpleMessage("Bypass domain"), @@ -149,6 +159,10 @@ class MessageLookup extends MessageLookupByLibrary { "View current connections data", ), "connectivity": MessageLookupByLibrary.simpleMessage("Connectivity:"), + "content": MessageLookupByLibrary.simpleMessage("Content"), + "contentEmptyTip": MessageLookupByLibrary.simpleMessage( + "Content cannot be empty", + ), "copy": MessageLookupByLibrary.simpleMessage("Copy"), "copyEnvVar": MessageLookupByLibrary.simpleMessage( "Copying environment variables", @@ -177,6 +191,9 @@ class MessageLookup extends MessageLookupByLibrary { "deleteProfileTip": MessageLookupByLibrary.simpleMessage( "Sure you want to delete the current profile?", ), + "deleteRuleTip": MessageLookupByLibrary.simpleMessage( + "Are you sure you want to delete the selected rule?", + ), "desc": MessageLookupByLibrary.simpleMessage( "A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.", ), @@ -205,6 +222,7 @@ class MessageLookup extends MessageLookupByLibrary { "download": MessageLookupByLibrary.simpleMessage("Download"), "edit": MessageLookupByLibrary.simpleMessage("Edit"), "en": MessageLookupByLibrary.simpleMessage("English"), + "enableOverride": MessageLookupByLibrary.simpleMessage("Enable override"), "entries": MessageLookupByLibrary.simpleMessage(" entries"), "exclude": MessageLookupByLibrary.simpleMessage("Hidden from recent tasks"), "excludeDesc": MessageLookupByLibrary.simpleMessage( @@ -243,13 +261,13 @@ class MessageLookup extends MessageLookupByLibrary { ), "findProcessMode": MessageLookupByLibrary.simpleMessage("Find process"), "findProcessModeDesc": MessageLookupByLibrary.simpleMessage( - "There is a risk of flashback after opening", + "There is a certain performance loss after opening", ), "fontFamily": MessageLookupByLibrary.simpleMessage("FontFamily"), "fourColumns": MessageLookupByLibrary.simpleMessage("Four columns"), "general": MessageLookupByLibrary.simpleMessage("General"), "generalDesc": MessageLookupByLibrary.simpleMessage( - "Overwrite general settings", + "Modify general settings", ), "geoData": MessageLookupByLibrary.simpleMessage("GeoData"), "geodataLoader": MessageLookupByLibrary.simpleMessage( @@ -259,6 +277,9 @@ class MessageLookup extends MessageLookupByLibrary { "Enabling will use the Geo low memory loader", ), "geoipCode": MessageLookupByLibrary.simpleMessage("Geoip code"), + "getOriginRules": MessageLookupByLibrary.simpleMessage( + "Get original rules", + ), "global": MessageLookupByLibrary.simpleMessage("Global"), "go": MessageLookupByLibrary.simpleMessage("Go"), "goDownload": MessageLookupByLibrary.simpleMessage("Go to download"), @@ -302,6 +323,9 @@ class MessageLookup extends MessageLookupByLibrary { "Tcp keep alive interval", ), "key": MessageLookupByLibrary.simpleMessage("Key"), + "keyExists": MessageLookupByLibrary.simpleMessage( + "The current key already exists", + ), "language": MessageLookupByLibrary.simpleMessage("Language"), "layout": MessageLookupByLibrary.simpleMessage("Layout"), "light": MessageLookupByLibrary.simpleMessage("Light"), @@ -366,6 +390,8 @@ class MessageLookup extends MessageLookupByLibrary { "noProxyDesc": MessageLookupByLibrary.simpleMessage( "Please create a profile or add a valid profile", ), + "noResolve": MessageLookupByLibrary.simpleMessage("No resolve IP"), + "none": MessageLookupByLibrary.simpleMessage("none"), "notEmpty": MessageLookupByLibrary.simpleMessage("Cannot be empty"), "notSelectedTip": MessageLookupByLibrary.simpleMessage( "The current proxy group cannot be selected.", @@ -407,6 +433,9 @@ class MessageLookup extends MessageLookupByLibrary { "overrideDnsDesc": MessageLookupByLibrary.simpleMessage( "Turning it on will override the DNS options in the profile", ), + "overrideOriginRules": MessageLookupByLibrary.simpleMessage( + "Override the original rule", + ), "password": MessageLookupByLibrary.simpleMessage("Password"), "passwordTip": MessageLookupByLibrary.simpleMessage( "Password cannot be empty", @@ -483,6 +512,7 @@ class MessageLookup extends MessageLookupByLibrary { "Only recovery profiles", ), "recoverySuccess": MessageLookupByLibrary.simpleMessage("Recovery success"), + "redo": MessageLookupByLibrary.simpleMessage("redo"), "regExp": MessageLookupByLibrary.simpleMessage("RegExp"), "remote": MessageLookupByLibrary.simpleMessage("Remote"), "remoteBackupDesc": MessageLookupByLibrary.simpleMessage( @@ -517,12 +547,27 @@ class MessageLookup extends MessageLookupByLibrary { "routeMode_config": MessageLookupByLibrary.simpleMessage("Use config"), "ru": MessageLookupByLibrary.simpleMessage("Russian"), "rule": MessageLookupByLibrary.simpleMessage("Rule"), + "ruleName": MessageLookupByLibrary.simpleMessage("Rule name"), + "ruleProviderEmptyTip": MessageLookupByLibrary.simpleMessage( + "Rule provider cannot be empty", + ), "ruleProviders": MessageLookupByLibrary.simpleMessage("Rule providers"), + "ruleTarget": MessageLookupByLibrary.simpleMessage("Rule target"), + "ruleTargetEmptyTip": MessageLookupByLibrary.simpleMessage( + "Rule target cannot be empty", + ), "save": MessageLookupByLibrary.simpleMessage("Save"), + "saveChanges": MessageLookupByLibrary.simpleMessage( + "Do you want to save the changes?", + ), + "saveTip": MessageLookupByLibrary.simpleMessage( + "Are you sure you want to save?", + ), "search": MessageLookupByLibrary.simpleMessage("Search"), "seconds": MessageLookupByLibrary.simpleMessage("Seconds"), "selectAll": MessageLookupByLibrary.simpleMessage("Select all"), "selected": MessageLookupByLibrary.simpleMessage("Selected"), + "selectedCountTitle": m0, "settings": MessageLookupByLibrary.simpleMessage("Settings"), "show": MessageLookupByLibrary.simpleMessage("Show"), "shrink": MessageLookupByLibrary.simpleMessage("Shrink"), @@ -533,6 +578,7 @@ class MessageLookup extends MessageLookupByLibrary { "size": MessageLookupByLibrary.simpleMessage("Size"), "sort": MessageLookupByLibrary.simpleMessage("Sort"), "source": MessageLookupByLibrary.simpleMessage("Source"), + "sourceIp": MessageLookupByLibrary.simpleMessage("Source IP"), "stackMode": MessageLookupByLibrary.simpleMessage("Stack mode"), "standard": MessageLookupByLibrary.simpleMessage("Standard"), "start": MessageLookupByLibrary.simpleMessage("Start"), @@ -544,6 +590,10 @@ class MessageLookup extends MessageLookupByLibrary { "stop": MessageLookupByLibrary.simpleMessage("Stop"), "stopVpn": MessageLookupByLibrary.simpleMessage("Stopping VPN..."), "style": MessageLookupByLibrary.simpleMessage("Style"), + "subRule": MessageLookupByLibrary.simpleMessage("Sub rule"), + "subRuleEmptyTip": MessageLookupByLibrary.simpleMessage( + "Sub rule content cannot be empty", + ), "submit": MessageLookupByLibrary.simpleMessage("Submit"), "sync": MessageLookupByLibrary.simpleMessage("Sync"), "system": MessageLookupByLibrary.simpleMessage("System"), @@ -555,7 +605,7 @@ class MessageLookup extends MessageLookupByLibrary { "tab": MessageLookupByLibrary.simpleMessage("Tab"), "tabAnimation": MessageLookupByLibrary.simpleMessage("Tab animation"), "tabAnimationDesc": MessageLookupByLibrary.simpleMessage( - "When enabled, the home tab will add a toggle animation", + "Effective only in mobile view", ), "tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP concurrent"), "tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage( @@ -583,6 +633,7 @@ class MessageLookup extends MessageLookupByLibrary { "unableToUpdateCurrentProfileDesc": MessageLookupByLibrary.simpleMessage( "unable to update current profile", ), + "undo": MessageLookupByLibrary.simpleMessage("undo"), "unifiedDelay": MessageLookupByLibrary.simpleMessage("Unified delay"), "unifiedDelayDesc": MessageLookupByLibrary.simpleMessage( "Remove extra delays such as handshaking", @@ -597,6 +648,9 @@ class MessageLookup extends MessageLookupByLibrary { "useHosts": MessageLookupByLibrary.simpleMessage("Use hosts"), "useSystemHosts": MessageLookupByLibrary.simpleMessage("Use system hosts"), "value": MessageLookupByLibrary.simpleMessage("Value"), + "valueExists": MessageLookupByLibrary.simpleMessage( + "The current value already exists", + ), "view": MessageLookupByLibrary.simpleMessage("View"), "vpnDesc": MessageLookupByLibrary.simpleMessage( "Modify VPN related settings", diff --git a/lib/l10n/intl/messages_ja.dart b/lib/l10n/intl/messages_ja.dart index 38c7fc4..8aa6904 100644 --- a/lib/l10n/intl/messages_ja.dart +++ b/lib/l10n/intl/messages_ja.dart @@ -20,6 +20,8 @@ typedef String MessageIfAbsent(String messageStr, List args); class MessageLookup extends MessageLookupByLibrary { String get localeName => 'ja'; + static String m0(count) => "${count} 項目が選択されています"; + final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { "about": MessageLookupByLibrary.simpleMessage("について"), @@ -42,6 +44,8 @@ class MessageLookup extends MessageLookupByLibrary { "action_tun": MessageLookupByLibrary.simpleMessage("TUN"), "action_view": MessageLookupByLibrary.simpleMessage("表示/非表示"), "add": MessageLookupByLibrary.simpleMessage("追加"), + "addRule": MessageLookupByLibrary.simpleMessage("ルールを追加"), + "addedOriginRules": MessageLookupByLibrary.simpleMessage("元のルールに追加"), "address": MessageLookupByLibrary.simpleMessage("アドレス"), "addressHelp": MessageLookupByLibrary.simpleMessage("WebDAVサーバーアドレス"), "addressTip": MessageLookupByLibrary.simpleMessage("有効なWebDAVアドレスを入力"), @@ -82,6 +86,8 @@ class MessageLookup extends MessageLookupByLibrary { "WebDAVまたはファイルでデータを同期", ), "backupSuccess": MessageLookupByLibrary.simpleMessage("バックアップ成功"), + "basicConfig": MessageLookupByLibrary.simpleMessage("基本設定"), + "basicConfigDesc": MessageLookupByLibrary.simpleMessage("基本設定をグローバルに変更"), "bind": MessageLookupByLibrary.simpleMessage("バインド"), "blacklistMode": MessageLookupByLibrary.simpleMessage("ブラックリストモード"), "bypassDomain": MessageLookupByLibrary.simpleMessage("バイパスドメイン"), @@ -109,6 +115,8 @@ class MessageLookup extends MessageLookupByLibrary { "connections": MessageLookupByLibrary.simpleMessage("接続"), "connectionsDesc": MessageLookupByLibrary.simpleMessage("現在の接続データを表示"), "connectivity": MessageLookupByLibrary.simpleMessage("接続性:"), + "content": MessageLookupByLibrary.simpleMessage("内容"), + "contentEmptyTip": MessageLookupByLibrary.simpleMessage("内容は必須です"), "copy": MessageLookupByLibrary.simpleMessage("コピー"), "copyEnvVar": MessageLookupByLibrary.simpleMessage("環境変数をコピー"), "copyLink": MessageLookupByLibrary.simpleMessage("リンクをコピー"), @@ -133,6 +141,7 @@ class MessageLookup extends MessageLookupByLibrary { "deleteProfileTip": MessageLookupByLibrary.simpleMessage( "現在のプロファイルを削除しますか?", ), + "deleteRuleTip": MessageLookupByLibrary.simpleMessage("選択したルールを削除しますか?"), "desc": MessageLookupByLibrary.simpleMessage( "ClashMetaベースのマルチプラットフォームプロキシクライアント。シンプルで使いやすく、オープンソースで広告なし。", ), @@ -151,6 +160,7 @@ class MessageLookup extends MessageLookupByLibrary { "download": MessageLookupByLibrary.simpleMessage("ダウンロード"), "edit": MessageLookupByLibrary.simpleMessage("編集"), "en": MessageLookupByLibrary.simpleMessage("英語"), + "enableOverride": MessageLookupByLibrary.simpleMessage("上書きを有効化"), "entries": MessageLookupByLibrary.simpleMessage(" エントリ"), "exclude": MessageLookupByLibrary.simpleMessage("最近のタスクから非表示"), "excludeDesc": MessageLookupByLibrary.simpleMessage( @@ -181,18 +191,19 @@ class MessageLookup extends MessageLookupByLibrary { "filterSystemApp": MessageLookupByLibrary.simpleMessage("システムアプリを除外"), "findProcessMode": MessageLookupByLibrary.simpleMessage("プロセス検出"), "findProcessModeDesc": MessageLookupByLibrary.simpleMessage( - "有効化するとフラッシュバックのリスクあり", + "有効化するとパフォーマンスが若干低下します", ), "fontFamily": MessageLookupByLibrary.simpleMessage("フォントファミリー"), "fourColumns": MessageLookupByLibrary.simpleMessage("4列"), "general": MessageLookupByLibrary.simpleMessage("一般"), - "generalDesc": MessageLookupByLibrary.simpleMessage("一般設定の上書き"), + "generalDesc": MessageLookupByLibrary.simpleMessage("一般設定を変更"), "geoData": MessageLookupByLibrary.simpleMessage("地域データ"), "geodataLoader": MessageLookupByLibrary.simpleMessage("Geo低メモリモード"), "geodataLoaderDesc": MessageLookupByLibrary.simpleMessage( "有効化するとGeo低メモリローダーを使用", ), "geoipCode": MessageLookupByLibrary.simpleMessage("GeoIPコード"), + "getOriginRules": MessageLookupByLibrary.simpleMessage("元のルールを取得"), "global": MessageLookupByLibrary.simpleMessage("グローバル"), "go": MessageLookupByLibrary.simpleMessage("移動"), "goDownload": MessageLookupByLibrary.simpleMessage("ダウンロードへ"), @@ -222,6 +233,7 @@ class MessageLookup extends MessageLookupByLibrary { "TCPキープアライブ間隔", ), "key": MessageLookupByLibrary.simpleMessage("キー"), + "keyExists": MessageLookupByLibrary.simpleMessage("現在のキーは既に存在します"), "language": MessageLookupByLibrary.simpleMessage("言語"), "layout": MessageLookupByLibrary.simpleMessage("レイアウト"), "light": MessageLookupByLibrary.simpleMessage("ライト"), @@ -270,6 +282,8 @@ class MessageLookup extends MessageLookupByLibrary { "noProxyDesc": MessageLookupByLibrary.simpleMessage( "プロファイルを作成するか、有効なプロファイルを追加してください", ), + "noResolve": MessageLookupByLibrary.simpleMessage("IPを解決しない"), + "none": MessageLookupByLibrary.simpleMessage("なし"), "notEmpty": MessageLookupByLibrary.simpleMessage("空欄不可"), "notSelectedTip": MessageLookupByLibrary.simpleMessage( "現在のプロキシグループは選択できません", @@ -299,6 +313,7 @@ class MessageLookup extends MessageLookupByLibrary { "overrideDnsDesc": MessageLookupByLibrary.simpleMessage( "有効化するとプロファイルのDNS設定を上書き", ), + "overrideOriginRules": MessageLookupByLibrary.simpleMessage("元のルールを上書き"), "password": MessageLookupByLibrary.simpleMessage("パスワード"), "passwordTip": MessageLookupByLibrary.simpleMessage("パスワードは必須です"), "paste": MessageLookupByLibrary.simpleMessage("貼り付け"), @@ -359,6 +374,7 @@ class MessageLookup extends MessageLookupByLibrary { "recoveryAll": MessageLookupByLibrary.simpleMessage("全データ復元"), "recoveryProfiles": MessageLookupByLibrary.simpleMessage("プロファイルのみ復元"), "recoverySuccess": MessageLookupByLibrary.simpleMessage("復元成功"), + "redo": MessageLookupByLibrary.simpleMessage("やり直す"), "regExp": MessageLookupByLibrary.simpleMessage("正規表現"), "remote": MessageLookupByLibrary.simpleMessage("リモート"), "remoteBackupDesc": MessageLookupByLibrary.simpleMessage( @@ -387,12 +403,21 @@ class MessageLookup extends MessageLookupByLibrary { "routeMode_config": MessageLookupByLibrary.simpleMessage("設定を使用"), "ru": MessageLookupByLibrary.simpleMessage("ロシア語"), "rule": MessageLookupByLibrary.simpleMessage("ルール"), + "ruleName": MessageLookupByLibrary.simpleMessage("ルール名"), + "ruleProviderEmptyTip": MessageLookupByLibrary.simpleMessage( + "ルールプロバイダーは必須です", + ), "ruleProviders": MessageLookupByLibrary.simpleMessage("ルールプロバイダー"), + "ruleTarget": MessageLookupByLibrary.simpleMessage("ルール対象"), + "ruleTargetEmptyTip": MessageLookupByLibrary.simpleMessage("ルール対象は必須です"), "save": MessageLookupByLibrary.simpleMessage("保存"), + "saveChanges": MessageLookupByLibrary.simpleMessage("変更を保存しますか?"), + "saveTip": MessageLookupByLibrary.simpleMessage("保存してもよろしいですか?"), "search": MessageLookupByLibrary.simpleMessage("検索"), "seconds": MessageLookupByLibrary.simpleMessage("秒"), "selectAll": MessageLookupByLibrary.simpleMessage("すべて選択"), "selected": MessageLookupByLibrary.simpleMessage("選択済み"), + "selectedCountTitle": m0, "settings": MessageLookupByLibrary.simpleMessage("設定"), "show": MessageLookupByLibrary.simpleMessage("表示"), "shrink": MessageLookupByLibrary.simpleMessage("縮小"), @@ -401,6 +426,7 @@ class MessageLookup extends MessageLookupByLibrary { "size": MessageLookupByLibrary.simpleMessage("サイズ"), "sort": MessageLookupByLibrary.simpleMessage("並び替え"), "source": MessageLookupByLibrary.simpleMessage("ソース"), + "sourceIp": MessageLookupByLibrary.simpleMessage("送信元IP"), "stackMode": MessageLookupByLibrary.simpleMessage("スタックモード"), "standard": MessageLookupByLibrary.simpleMessage("標準"), "start": MessageLookupByLibrary.simpleMessage("開始"), @@ -410,6 +436,8 @@ class MessageLookup extends MessageLookupByLibrary { "stop": MessageLookupByLibrary.simpleMessage("停止"), "stopVpn": MessageLookupByLibrary.simpleMessage("VPNを停止中..."), "style": MessageLookupByLibrary.simpleMessage("スタイル"), + "subRule": MessageLookupByLibrary.simpleMessage("サブルール"), + "subRuleEmptyTip": MessageLookupByLibrary.simpleMessage("サブルールの内容は必須です"), "submit": MessageLookupByLibrary.simpleMessage("送信"), "sync": MessageLookupByLibrary.simpleMessage("同期"), "system": MessageLookupByLibrary.simpleMessage("システム"), @@ -420,9 +448,7 @@ class MessageLookup extends MessageLookupByLibrary { ), "tab": MessageLookupByLibrary.simpleMessage("タブ"), "tabAnimation": MessageLookupByLibrary.simpleMessage("タブアニメーション"), - "tabAnimationDesc": MessageLookupByLibrary.simpleMessage( - "有効化するとホームタブに切り替えアニメーションを追加", - ), + "tabAnimationDesc": MessageLookupByLibrary.simpleMessage("モバイル表示でのみ有効"), "tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP並列処理"), "tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage("TCP並列処理を許可"), "testUrl": MessageLookupByLibrary.simpleMessage("URLテスト"), @@ -443,6 +469,7 @@ class MessageLookup extends MessageLookupByLibrary { "unableToUpdateCurrentProfileDesc": MessageLookupByLibrary.simpleMessage( "現在のプロファイルを更新できません", ), + "undo": MessageLookupByLibrary.simpleMessage("元に戻す"), "unifiedDelay": MessageLookupByLibrary.simpleMessage("統一遅延"), "unifiedDelayDesc": MessageLookupByLibrary.simpleMessage( "ハンドシェイクなどの余分な遅延を削除", @@ -455,6 +482,7 @@ class MessageLookup extends MessageLookupByLibrary { "useHosts": MessageLookupByLibrary.simpleMessage("ホストを使用"), "useSystemHosts": MessageLookupByLibrary.simpleMessage("システムホストを使用"), "value": MessageLookupByLibrary.simpleMessage("値"), + "valueExists": MessageLookupByLibrary.simpleMessage("現在の値は既に存在します"), "view": MessageLookupByLibrary.simpleMessage("表示"), "vpnDesc": MessageLookupByLibrary.simpleMessage("VPN関連設定の変更"), "vpnEnableDesc": MessageLookupByLibrary.simpleMessage( diff --git a/lib/l10n/intl/messages_ru.dart b/lib/l10n/intl/messages_ru.dart index 5a17d5f..1a0e7eb 100644 --- a/lib/l10n/intl/messages_ru.dart +++ b/lib/l10n/intl/messages_ru.dart @@ -20,6 +20,8 @@ typedef String MessageIfAbsent(String messageStr, List args); class MessageLookup extends MessageLookupByLibrary { String get localeName => 'ru'; + static String m0(count) => "Выбрано ${count} элементов"; + final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { "about": MessageLookupByLibrary.simpleMessage("О программе"), @@ -44,6 +46,10 @@ class MessageLookup extends MessageLookupByLibrary { "action_tun": MessageLookupByLibrary.simpleMessage("TUN"), "action_view": MessageLookupByLibrary.simpleMessage("Показать/Скрыть"), "add": MessageLookupByLibrary.simpleMessage("Добавить"), + "addRule": MessageLookupByLibrary.simpleMessage("Добавить правило"), + "addedOriginRules": MessageLookupByLibrary.simpleMessage( + "Добавить к оригинальным правилам", + ), "address": MessageLookupByLibrary.simpleMessage("Адрес"), "addressHelp": MessageLookupByLibrary.simpleMessage("Адрес сервера WebDAV"), "addressTip": MessageLookupByLibrary.simpleMessage( @@ -114,6 +120,10 @@ class MessageLookup extends MessageLookupByLibrary { "backupSuccess": MessageLookupByLibrary.simpleMessage( "Резервное копирование успешно", ), + "basicConfig": MessageLookupByLibrary.simpleMessage("Базовая конфигурация"), + "basicConfigDesc": MessageLookupByLibrary.simpleMessage( + "Глобальное изменение базовых настроек", + ), "bind": MessageLookupByLibrary.simpleMessage("Привязать"), "blacklistMode": MessageLookupByLibrary.simpleMessage( "Режим черного списка", @@ -155,6 +165,10 @@ class MessageLookup extends MessageLookupByLibrary { "Просмотр текущих данных о соединениях", ), "connectivity": MessageLookupByLibrary.simpleMessage("Связь:"), + "content": MessageLookupByLibrary.simpleMessage("Содержание"), + "contentEmptyTip": MessageLookupByLibrary.simpleMessage( + "Содержание не может быть пустым", + ), "copy": MessageLookupByLibrary.simpleMessage("Копировать"), "copyEnvVar": MessageLookupByLibrary.simpleMessage( "Копирование переменных окружения", @@ -185,6 +199,9 @@ class MessageLookup extends MessageLookupByLibrary { "deleteProfileTip": MessageLookupByLibrary.simpleMessage( "Вы уверены, что хотите удалить текущий профиль?", ), + "deleteRuleTip": MessageLookupByLibrary.simpleMessage( + "Вы уверены, что хотите удалить выбранное правило?", + ), "desc": MessageLookupByLibrary.simpleMessage( "Многоплатформенный прокси-клиент на основе ClashMeta, простой и удобный в использовании, с открытым исходным кодом и без рекламы.", ), @@ -215,6 +232,9 @@ class MessageLookup extends MessageLookupByLibrary { "download": MessageLookupByLibrary.simpleMessage("Скачивание"), "edit": MessageLookupByLibrary.simpleMessage("Редактировать"), "en": MessageLookupByLibrary.simpleMessage("Английский"), + "enableOverride": MessageLookupByLibrary.simpleMessage( + "Включить переопределение", + ), "entries": MessageLookupByLibrary.simpleMessage(" записей"), "exclude": MessageLookupByLibrary.simpleMessage( "Скрыть из последних задач", @@ -259,13 +279,13 @@ class MessageLookup extends MessageLookupByLibrary { "Режим поиска процесса", ), "findProcessModeDesc": MessageLookupByLibrary.simpleMessage( - "Есть риск сбоя после включения", + "При включении возможны небольшие потери производительности", ), "fontFamily": MessageLookupByLibrary.simpleMessage("Семейство шрифтов"), "fourColumns": MessageLookupByLibrary.simpleMessage("Четыре столбца"), "general": MessageLookupByLibrary.simpleMessage("Общие"), "generalDesc": MessageLookupByLibrary.simpleMessage( - "Переопределение общих настроек", + "Изменение общих настроек", ), "geoData": MessageLookupByLibrary.simpleMessage("Геоданные"), "geodataLoader": MessageLookupByLibrary.simpleMessage( @@ -275,6 +295,9 @@ class MessageLookup extends MessageLookupByLibrary { "Включение будет использовать загрузчик геоданных с низким потреблением памяти", ), "geoipCode": MessageLookupByLibrary.simpleMessage("Код Geoip"), + "getOriginRules": MessageLookupByLibrary.simpleMessage( + "Получить оригинальные правила", + ), "global": MessageLookupByLibrary.simpleMessage("Глобальный"), "go": MessageLookupByLibrary.simpleMessage("Перейти"), "goDownload": MessageLookupByLibrary.simpleMessage("Перейти к загрузке"), @@ -322,6 +345,9 @@ class MessageLookup extends MessageLookupByLibrary { "Интервал поддержания TCP-соединения", ), "key": MessageLookupByLibrary.simpleMessage("Ключ"), + "keyExists": MessageLookupByLibrary.simpleMessage( + "Текущий ключ уже существует", + ), "language": MessageLookupByLibrary.simpleMessage("Язык"), "layout": MessageLookupByLibrary.simpleMessage("Макет"), "light": MessageLookupByLibrary.simpleMessage("Светлый"), @@ -392,6 +418,8 @@ class MessageLookup extends MessageLookupByLibrary { "noProxyDesc": MessageLookupByLibrary.simpleMessage( "Пожалуйста, создайте профиль или добавьте действительный профиль", ), + "noResolve": MessageLookupByLibrary.simpleMessage("Не разрешать IP"), + "none": MessageLookupByLibrary.simpleMessage("Нет"), "notEmpty": MessageLookupByLibrary.simpleMessage("Не может быть пустым"), "notSelectedTip": MessageLookupByLibrary.simpleMessage( "Текущая группа прокси не может быть выбрана.", @@ -435,6 +463,9 @@ class MessageLookup extends MessageLookupByLibrary { "overrideDnsDesc": MessageLookupByLibrary.simpleMessage( "Включение переопределит настройки DNS в профиле", ), + "overrideOriginRules": MessageLookupByLibrary.simpleMessage( + "Переопределить оригинальное правило", + ), "password": MessageLookupByLibrary.simpleMessage("Пароль"), "passwordTip": MessageLookupByLibrary.simpleMessage( "Пароль не может быть пустым", @@ -517,6 +548,7 @@ class MessageLookup extends MessageLookupByLibrary { "recoverySuccess": MessageLookupByLibrary.simpleMessage( "Восстановление успешно", ), + "redo": MessageLookupByLibrary.simpleMessage("Повторить"), "regExp": MessageLookupByLibrary.simpleMessage("Регулярное выражение"), "remote": MessageLookupByLibrary.simpleMessage("Удаленный"), "remoteBackupDesc": MessageLookupByLibrary.simpleMessage( @@ -555,12 +587,25 @@ class MessageLookup extends MessageLookupByLibrary { ), "ru": MessageLookupByLibrary.simpleMessage("Русский"), "rule": MessageLookupByLibrary.simpleMessage("Правило"), + "ruleName": MessageLookupByLibrary.simpleMessage("Название правила"), + "ruleProviderEmptyTip": MessageLookupByLibrary.simpleMessage( + "Поставщик правил не может быть пустым", + ), "ruleProviders": MessageLookupByLibrary.simpleMessage("Провайдеры правил"), + "ruleTarget": MessageLookupByLibrary.simpleMessage("Цель правила"), + "ruleTargetEmptyTip": MessageLookupByLibrary.simpleMessage( + "Цель правила не может быть пустой", + ), "save": MessageLookupByLibrary.simpleMessage("Сохранить"), + "saveChanges": MessageLookupByLibrary.simpleMessage("Сохранить изменения?"), + "saveTip": MessageLookupByLibrary.simpleMessage( + "Вы уверены, что хотите сохранить?", + ), "search": MessageLookupByLibrary.simpleMessage("Поиск"), "seconds": MessageLookupByLibrary.simpleMessage("Секунд"), "selectAll": MessageLookupByLibrary.simpleMessage("Выбрать все"), "selected": MessageLookupByLibrary.simpleMessage("Выбрано"), + "selectedCountTitle": m0, "settings": MessageLookupByLibrary.simpleMessage("Настройки"), "show": MessageLookupByLibrary.simpleMessage("Показать"), "shrink": MessageLookupByLibrary.simpleMessage("Сжать"), @@ -571,6 +616,7 @@ class MessageLookup extends MessageLookupByLibrary { "size": MessageLookupByLibrary.simpleMessage("Размер"), "sort": MessageLookupByLibrary.simpleMessage("Сортировка"), "source": MessageLookupByLibrary.simpleMessage("Источник"), + "sourceIp": MessageLookupByLibrary.simpleMessage("Исходный IP"), "stackMode": MessageLookupByLibrary.simpleMessage("Режим стека"), "standard": MessageLookupByLibrary.simpleMessage("Стандартный"), "start": MessageLookupByLibrary.simpleMessage("Старт"), @@ -582,6 +628,10 @@ class MessageLookup extends MessageLookupByLibrary { "stop": MessageLookupByLibrary.simpleMessage("Стоп"), "stopVpn": MessageLookupByLibrary.simpleMessage("Остановка VPN..."), "style": MessageLookupByLibrary.simpleMessage("Стиль"), + "subRule": MessageLookupByLibrary.simpleMessage("Подправило"), + "subRuleEmptyTip": MessageLookupByLibrary.simpleMessage( + "Содержание подправила не может быть пустым", + ), "submit": MessageLookupByLibrary.simpleMessage("Отправить"), "sync": MessageLookupByLibrary.simpleMessage("Синхронизация"), "system": MessageLookupByLibrary.simpleMessage("Система"), @@ -593,7 +643,7 @@ class MessageLookup extends MessageLookupByLibrary { "tab": MessageLookupByLibrary.simpleMessage("Вкладка"), "tabAnimation": MessageLookupByLibrary.simpleMessage("Анимация вкладок"), "tabAnimationDesc": MessageLookupByLibrary.simpleMessage( - "При включении домашняя вкладка добавит анимацию переключения", + "Действительно только в мобильном виде", ), "tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP параллелизм"), "tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage( @@ -623,6 +673,7 @@ class MessageLookup extends MessageLookupByLibrary { "unableToUpdateCurrentProfileDesc": MessageLookupByLibrary.simpleMessage( "невозможно обновить текущий профиль", ), + "undo": MessageLookupByLibrary.simpleMessage("Отменить"), "unifiedDelay": MessageLookupByLibrary.simpleMessage( "Унифицированная задержка", ), @@ -641,6 +692,9 @@ class MessageLookup extends MessageLookupByLibrary { "Использовать системные hosts", ), "value": MessageLookupByLibrary.simpleMessage("Значение"), + "valueExists": MessageLookupByLibrary.simpleMessage( + "Текущее значение уже существует", + ), "view": MessageLookupByLibrary.simpleMessage("Просмотр"), "vpnDesc": MessageLookupByLibrary.simpleMessage( "Изменение настроек, связанных с VPN", diff --git a/lib/l10n/intl/messages_zh_CN.dart b/lib/l10n/intl/messages_zh_CN.dart index b3adcad..5135f85 100644 --- a/lib/l10n/intl/messages_zh_CN.dart +++ b/lib/l10n/intl/messages_zh_CN.dart @@ -20,6 +20,8 @@ typedef String MessageIfAbsent(String messageStr, List args); class MessageLookup extends MessageLookupByLibrary { String get localeName => 'zh_CN'; + static String m0(count) => "已选择 ${count} 项"; + final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { "about": MessageLookupByLibrary.simpleMessage("关于"), @@ -40,6 +42,8 @@ class MessageLookup extends MessageLookupByLibrary { "action_tun": MessageLookupByLibrary.simpleMessage("虚拟网卡"), "action_view": MessageLookupByLibrary.simpleMessage("显示/隐藏"), "add": MessageLookupByLibrary.simpleMessage("添加"), + "addRule": MessageLookupByLibrary.simpleMessage("添加规则"), + "addedOriginRules": MessageLookupByLibrary.simpleMessage("附加到原始规则"), "address": MessageLookupByLibrary.simpleMessage("地址"), "addressHelp": MessageLookupByLibrary.simpleMessage("WebDAV服务器地址"), "addressTip": MessageLookupByLibrary.simpleMessage("请输入有效的WebDAV地址"), @@ -76,6 +80,8 @@ class MessageLookup extends MessageLookupByLibrary { "通过WebDAV或者文件同步数据", ), "backupSuccess": MessageLookupByLibrary.simpleMessage("备份成功"), + "basicConfig": MessageLookupByLibrary.simpleMessage("基本配置"), + "basicConfigDesc": MessageLookupByLibrary.simpleMessage("全局修改基本配置"), "bind": MessageLookupByLibrary.simpleMessage("绑定"), "blacklistMode": MessageLookupByLibrary.simpleMessage("黑名单模式"), "bypassDomain": MessageLookupByLibrary.simpleMessage("排除域名"), @@ -99,6 +105,8 @@ class MessageLookup extends MessageLookupByLibrary { "connections": MessageLookupByLibrary.simpleMessage("连接"), "connectionsDesc": MessageLookupByLibrary.simpleMessage("查看当前连接数据"), "connectivity": MessageLookupByLibrary.simpleMessage("连通性:"), + "content": MessageLookupByLibrary.simpleMessage("内容"), + "contentEmptyTip": MessageLookupByLibrary.simpleMessage("内容不能为空"), "copy": MessageLookupByLibrary.simpleMessage("复制"), "copyEnvVar": MessageLookupByLibrary.simpleMessage("复制环境变量"), "copyLink": MessageLookupByLibrary.simpleMessage("复制链接"), @@ -119,6 +127,7 @@ class MessageLookup extends MessageLookupByLibrary { "delaySort": MessageLookupByLibrary.simpleMessage("按延迟排序"), "delete": MessageLookupByLibrary.simpleMessage("删除"), "deleteProfileTip": MessageLookupByLibrary.simpleMessage("确定要删除当前配置吗?"), + "deleteRuleTip": MessageLookupByLibrary.simpleMessage("确定要删除选中的规则吗?"), "desc": MessageLookupByLibrary.simpleMessage( "基于ClashMeta的多平台代理客户端,简单易用,开源无广告。", ), @@ -137,6 +146,7 @@ class MessageLookup extends MessageLookupByLibrary { "download": MessageLookupByLibrary.simpleMessage("下载"), "edit": MessageLookupByLibrary.simpleMessage("编辑"), "en": MessageLookupByLibrary.simpleMessage("英语"), + "enableOverride": MessageLookupByLibrary.simpleMessage("启用覆写"), "entries": MessageLookupByLibrary.simpleMessage("个条目"), "exclude": MessageLookupByLibrary.simpleMessage("从最近任务中隐藏"), "excludeDesc": MessageLookupByLibrary.simpleMessage("应用在后台时,从最近任务中隐藏应用"), @@ -162,15 +172,16 @@ class MessageLookup extends MessageLookupByLibrary { "fileIsUpdate": MessageLookupByLibrary.simpleMessage("文件有修改,是否保存修改"), "filterSystemApp": MessageLookupByLibrary.simpleMessage("过滤系统应用"), "findProcessMode": MessageLookupByLibrary.simpleMessage("查找进程"), - "findProcessModeDesc": MessageLookupByLibrary.simpleMessage("开启后存在闪退风险"), + "findProcessModeDesc": MessageLookupByLibrary.simpleMessage("开启后会有一定性能损耗"), "fontFamily": MessageLookupByLibrary.simpleMessage("字体"), "fourColumns": MessageLookupByLibrary.simpleMessage("四列"), - "general": MessageLookupByLibrary.simpleMessage("基础"), - "generalDesc": MessageLookupByLibrary.simpleMessage("覆写基础设置"), + "general": MessageLookupByLibrary.simpleMessage("常规"), + "generalDesc": MessageLookupByLibrary.simpleMessage("修改通用设置"), "geoData": MessageLookupByLibrary.simpleMessage("地理数据"), "geodataLoader": MessageLookupByLibrary.simpleMessage("Geo低内存模式"), "geodataLoaderDesc": MessageLookupByLibrary.simpleMessage("开启将使用Geo低内存加载器"), "geoipCode": MessageLookupByLibrary.simpleMessage("Geoip代码"), + "getOriginRules": MessageLookupByLibrary.simpleMessage("获取原始规则"), "global": MessageLookupByLibrary.simpleMessage("全局"), "go": MessageLookupByLibrary.simpleMessage("前往"), "goDownload": MessageLookupByLibrary.simpleMessage("前往下载"), @@ -196,6 +207,7 @@ class MessageLookup extends MessageLookupByLibrary { "just": MessageLookupByLibrary.simpleMessage("刚刚"), "keepAliveIntervalDesc": MessageLookupByLibrary.simpleMessage("TCP保持活动间隔"), "key": MessageLookupByLibrary.simpleMessage("键"), + "keyExists": MessageLookupByLibrary.simpleMessage("当前键已存在"), "language": MessageLookupByLibrary.simpleMessage("语言"), "layout": MessageLookupByLibrary.simpleMessage("布局"), "light": MessageLookupByLibrary.simpleMessage("浅色"), @@ -238,6 +250,8 @@ class MessageLookup extends MessageLookupByLibrary { "noNetwork": MessageLookupByLibrary.simpleMessage("无网络"), "noProxy": MessageLookupByLibrary.simpleMessage("暂无代理"), "noProxyDesc": MessageLookupByLibrary.simpleMessage("请创建配置文件或者添加有效配置文件"), + "noResolve": MessageLookupByLibrary.simpleMessage("不解析IP"), + "none": MessageLookupByLibrary.simpleMessage("无"), "notEmpty": MessageLookupByLibrary.simpleMessage("不能为空"), "notSelectedTip": MessageLookupByLibrary.simpleMessage("当前代理组无法选中"), "nullConnectionsDesc": MessageLookupByLibrary.simpleMessage("暂无连接"), @@ -261,6 +275,7 @@ class MessageLookup extends MessageLookupByLibrary { "overrideDesc": MessageLookupByLibrary.simpleMessage("覆写代理相关配置"), "overrideDns": MessageLookupByLibrary.simpleMessage("覆写DNS"), "overrideDnsDesc": MessageLookupByLibrary.simpleMessage("开启后将覆盖配置中的DNS选项"), + "overrideOriginRules": MessageLookupByLibrary.simpleMessage("覆盖原始规则"), "password": MessageLookupByLibrary.simpleMessage("密码"), "passwordTip": MessageLookupByLibrary.simpleMessage("密码不能为空"), "paste": MessageLookupByLibrary.simpleMessage("粘贴"), @@ -313,6 +328,7 @@ class MessageLookup extends MessageLookupByLibrary { "recoveryAll": MessageLookupByLibrary.simpleMessage("恢复所有数据"), "recoveryProfiles": MessageLookupByLibrary.simpleMessage("仅恢复配置文件"), "recoverySuccess": MessageLookupByLibrary.simpleMessage("恢复成功"), + "redo": MessageLookupByLibrary.simpleMessage("重做"), "regExp": MessageLookupByLibrary.simpleMessage("正则"), "remote": MessageLookupByLibrary.simpleMessage("远程"), "remoteBackupDesc": MessageLookupByLibrary.simpleMessage("备份数据到WebDAV"), @@ -335,12 +351,19 @@ class MessageLookup extends MessageLookupByLibrary { "routeMode_config": MessageLookupByLibrary.simpleMessage("使用配置"), "ru": MessageLookupByLibrary.simpleMessage("俄语"), "rule": MessageLookupByLibrary.simpleMessage("规则"), + "ruleName": MessageLookupByLibrary.simpleMessage("规则名称"), + "ruleProviderEmptyTip": MessageLookupByLibrary.simpleMessage("规则提供者不能为空"), "ruleProviders": MessageLookupByLibrary.simpleMessage("规则提供者"), + "ruleTarget": MessageLookupByLibrary.simpleMessage("规则目标"), + "ruleTargetEmptyTip": MessageLookupByLibrary.simpleMessage("规则目标不能为空"), "save": MessageLookupByLibrary.simpleMessage("保存"), + "saveChanges": MessageLookupByLibrary.simpleMessage("是否保存更改?"), + "saveTip": MessageLookupByLibrary.simpleMessage("确定要保存吗?"), "search": MessageLookupByLibrary.simpleMessage("搜索"), "seconds": MessageLookupByLibrary.simpleMessage("秒"), "selectAll": MessageLookupByLibrary.simpleMessage("全选"), "selected": MessageLookupByLibrary.simpleMessage("已选择"), + "selectedCountTitle": m0, "settings": MessageLookupByLibrary.simpleMessage("设置"), "show": MessageLookupByLibrary.simpleMessage("显示"), "shrink": MessageLookupByLibrary.simpleMessage("紧凑"), @@ -349,6 +372,7 @@ class MessageLookup extends MessageLookupByLibrary { "size": MessageLookupByLibrary.simpleMessage("尺寸"), "sort": MessageLookupByLibrary.simpleMessage("排序"), "source": MessageLookupByLibrary.simpleMessage("来源"), + "sourceIp": MessageLookupByLibrary.simpleMessage("源IP"), "stackMode": MessageLookupByLibrary.simpleMessage("栈模式"), "standard": MessageLookupByLibrary.simpleMessage("标准"), "start": MessageLookupByLibrary.simpleMessage("启动"), @@ -358,6 +382,8 @@ class MessageLookup extends MessageLookupByLibrary { "stop": MessageLookupByLibrary.simpleMessage("暂停"), "stopVpn": MessageLookupByLibrary.simpleMessage("正在停止VPN..."), "style": MessageLookupByLibrary.simpleMessage("风格"), + "subRule": MessageLookupByLibrary.simpleMessage("子规则"), + "subRuleEmptyTip": MessageLookupByLibrary.simpleMessage("子规则内容不能为空"), "submit": MessageLookupByLibrary.simpleMessage("提交"), "sync": MessageLookupByLibrary.simpleMessage("同步"), "system": MessageLookupByLibrary.simpleMessage("系统"), @@ -366,9 +392,7 @@ class MessageLookup extends MessageLookupByLibrary { "systemProxyDesc": MessageLookupByLibrary.simpleMessage("设置系统代理"), "tab": MessageLookupByLibrary.simpleMessage("标签页"), "tabAnimation": MessageLookupByLibrary.simpleMessage("选项卡动画"), - "tabAnimationDesc": MessageLookupByLibrary.simpleMessage( - "开启后,主页选项卡将添加切换动画", - ), + "tabAnimationDesc": MessageLookupByLibrary.simpleMessage("仅在移动视图中有效"), "tcpConcurrent": MessageLookupByLibrary.simpleMessage("TCP并发"), "tcpConcurrentDesc": MessageLookupByLibrary.simpleMessage("开启后允许TCP并发"), "testUrl": MessageLookupByLibrary.simpleMessage("测速链接"), @@ -389,6 +413,7 @@ class MessageLookup extends MessageLookupByLibrary { "unableToUpdateCurrentProfileDesc": MessageLookupByLibrary.simpleMessage( "无法更新当前配置文件", ), + "undo": MessageLookupByLibrary.simpleMessage("撤销"), "unifiedDelay": MessageLookupByLibrary.simpleMessage("统一延迟"), "unifiedDelayDesc": MessageLookupByLibrary.simpleMessage("去除握手等额外延迟"), "unknown": MessageLookupByLibrary.simpleMessage("未知"), @@ -399,6 +424,7 @@ class MessageLookup extends MessageLookupByLibrary { "useHosts": MessageLookupByLibrary.simpleMessage("使用Hosts"), "useSystemHosts": MessageLookupByLibrary.simpleMessage("使用系统Hosts"), "value": MessageLookupByLibrary.simpleMessage("值"), + "valueExists": MessageLookupByLibrary.simpleMessage("当前值已存在"), "view": MessageLookupByLibrary.simpleMessage("查看"), "vpnDesc": MessageLookupByLibrary.simpleMessage("修改VPN相关设置"), "vpnEnableDesc": MessageLookupByLibrary.simpleMessage( diff --git a/lib/l10n/l10n.dart b/lib/l10n/l10n.dart index 214e2fa..a9f505e 100644 --- a/lib/l10n/l10n.dart +++ b/lib/l10n/l10n.dart @@ -945,16 +945,6 @@ class AppLocalizations { ); } - /// `When enabled, the home tab will add a toggle animation` - String get tabAnimationDesc { - return Intl.message( - 'When enabled, the home tab will add a toggle animation', - name: 'tabAnimationDesc', - desc: '', - args: [], - ); - } - /// `A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.` String get desc { return Intl.message( @@ -1435,16 +1425,6 @@ class AppLocalizations { ); } - /// `There is a risk of flashback after opening` - String get findProcessModeDesc { - return Intl.message( - 'There is a risk of flashback after opening', - name: 'findProcessModeDesc', - desc: '', - args: [], - ); - } - /// `Init` String get init { return Intl.message('Init', name: 'init', desc: '', args: []); @@ -1930,16 +1910,6 @@ class AppLocalizations { ); } - /// `Overwrite general settings` - String get generalDesc { - return Intl.message( - 'Overwrite general settings', - name: 'generalDesc', - desc: '', - args: [], - ); - } - /// `Update DNS related settings` String get dnsDesc { return Intl.message( @@ -2694,6 +2664,246 @@ class AppLocalizations { String get listen { return Intl.message('Listen', name: 'listen', desc: '', args: []); } + + /// `The current key already exists` + String get keyExists { + return Intl.message( + 'The current key already exists', + name: 'keyExists', + desc: '', + args: [], + ); + } + + /// `The current value already exists` + String get valueExists { + return Intl.message( + 'The current value already exists', + name: 'valueExists', + desc: '', + args: [], + ); + } + + /// `undo` + String get undo { + return Intl.message('undo', name: 'undo', desc: '', args: []); + } + + /// `redo` + String get redo { + return Intl.message('redo', name: 'redo', desc: '', args: []); + } + + /// `none` + String get none { + return Intl.message('none', name: 'none', desc: '', args: []); + } + + /// `Basic configuration` + String get basicConfig { + return Intl.message( + 'Basic configuration', + name: 'basicConfig', + desc: '', + args: [], + ); + } + + /// `Modify the basic configuration globally` + String get basicConfigDesc { + return Intl.message( + 'Modify the basic configuration globally', + name: 'basicConfigDesc', + desc: '', + args: [], + ); + } + + /// `{count} items have been selected` + String selectedCountTitle(Object count) { + return Intl.message( + '$count items have been selected', + name: 'selectedCountTitle', + desc: '', + args: [count], + ); + } + + /// `Add rule` + String get addRule { + return Intl.message('Add rule', name: 'addRule', desc: '', args: []); + } + + /// `Rule provider cannot be empty` + String get ruleProviderEmptyTip { + return Intl.message( + 'Rule provider cannot be empty', + name: 'ruleProviderEmptyTip', + desc: '', + args: [], + ); + } + + /// `Rule name` + String get ruleName { + return Intl.message('Rule name', name: 'ruleName', desc: '', args: []); + } + + /// `Content` + String get content { + return Intl.message('Content', name: 'content', desc: '', args: []); + } + + /// `Content cannot be empty` + String get contentEmptyTip { + return Intl.message( + 'Content cannot be empty', + name: 'contentEmptyTip', + desc: '', + args: [], + ); + } + + /// `Sub rule` + String get subRule { + return Intl.message('Sub rule', name: 'subRule', desc: '', args: []); + } + + /// `Sub rule content cannot be empty` + String get subRuleEmptyTip { + return Intl.message( + 'Sub rule content cannot be empty', + name: 'subRuleEmptyTip', + desc: '', + args: [], + ); + } + + /// `Rule target` + String get ruleTarget { + return Intl.message('Rule target', name: 'ruleTarget', desc: '', args: []); + } + + /// `Rule target cannot be empty` + String get ruleTargetEmptyTip { + return Intl.message( + 'Rule target cannot be empty', + name: 'ruleTargetEmptyTip', + desc: '', + args: [], + ); + } + + /// `Source IP` + String get sourceIp { + return Intl.message('Source IP', name: 'sourceIp', desc: '', args: []); + } + + /// `No resolve IP` + String get noResolve { + return Intl.message('No resolve IP', name: 'noResolve', desc: '', args: []); + } + + /// `Get original rules` + String get getOriginRules { + return Intl.message( + 'Get original rules', + name: 'getOriginRules', + desc: '', + args: [], + ); + } + + /// `Override the original rule` + String get overrideOriginRules { + return Intl.message( + 'Override the original rule', + name: 'overrideOriginRules', + desc: '', + args: [], + ); + } + + /// `Attach on the original rules` + String get addedOriginRules { + return Intl.message( + 'Attach on the original rules', + name: 'addedOriginRules', + desc: '', + args: [], + ); + } + + /// `Enable override` + String get enableOverride { + return Intl.message( + 'Enable override', + name: 'enableOverride', + desc: '', + args: [], + ); + } + + /// `Are you sure you want to delete the selected rule?` + String get deleteRuleTip { + return Intl.message( + 'Are you sure you want to delete the selected rule?', + name: 'deleteRuleTip', + desc: '', + args: [], + ); + } + + /// `Do you want to save the changes?` + String get saveChanges { + return Intl.message( + 'Do you want to save the changes?', + name: 'saveChanges', + desc: '', + args: [], + ); + } + + /// `Modify general settings` + String get generalDesc { + return Intl.message( + 'Modify general settings', + name: 'generalDesc', + desc: '', + args: [], + ); + } + + /// `There is a certain performance loss after opening` + String get findProcessModeDesc { + return Intl.message( + 'There is a certain performance loss after opening', + name: 'findProcessModeDesc', + desc: '', + args: [], + ); + } + + /// `Effective only in mobile view` + String get tabAnimationDesc { + return Intl.message( + 'Effective only in mobile view', + name: 'tabAnimationDesc', + desc: '', + args: [], + ); + } + + /// `Are you sure you want to save?` + String get saveTip { + return Intl.message( + 'Are you sure you want to save?', + name: 'saveTip', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/main.dart b/lib/main.dart index 3f95d19..91e9edf 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -27,6 +27,7 @@ Future main() async { await globalState.initApp(version); await android?.init(); await window?.init(version); + globalState.isPre = const String.fromEnvironment("APP_ENV") != 'stable'; HttpOverrides.global = FlClashHttpOverrides(); runApp(ProviderScope( child: const Application(), diff --git a/lib/manager/app_state_manager.dart b/lib/manager/app_state_manager.dart index abb64ee..5724b69 100644 --- a/lib/manager/app_state_manager.dart +++ b/lib/manager/app_state_manager.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/state.dart'; import 'package:flutter/material.dart'; @@ -19,6 +21,7 @@ class _AppStateManagerState extends State @override void initState() { super.initState(); + WidgetsBinding.instance.addObserver(this); } @@ -56,3 +59,24 @@ class _AppStateManagerState extends State ); } } + +class AppEnvManager extends StatelessWidget { + final Widget child; + + const AppEnvManager({ + super.key, + required this.child, + }); + + @override + Widget build(BuildContext context) { + if (globalState.isPre) { + return Banner( + message: 'PRE', + location: BannerLocation.topEnd, + child: child, + ); + } + return child; + } +} diff --git a/lib/manager/manager.dart b/lib/manager/manager.dart index 3d435b6..e8208ca 100644 --- a/lib/manager/manager.dart +++ b/lib/manager/manager.dart @@ -7,4 +7,5 @@ export 'app_state_manager.dart'; export 'vpn_manager.dart'; export 'proxy_manager.dart'; export 'connectivity_manager.dart'; -export 'message_manager.dart'; \ No newline at end of file +export 'message_manager.dart'; +export 'theme_manager.dart'; \ No newline at end of file diff --git a/lib/manager/message_manager.dart b/lib/manager/message_manager.dart index 79842cd..11975b7 100644 --- a/lib/manager/message_manager.dart +++ b/lib/manager/message_manager.dart @@ -1,8 +1,9 @@ import 'dart:async'; +import 'dart:math'; import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/models/models.dart'; -import 'package:fl_clash/state.dart'; +import 'package:fl_clash/widgets/fade_box.dart'; import 'package:flutter/material.dart'; class MessageManager extends StatefulWidget { @@ -17,41 +18,49 @@ class MessageManager extends StatefulWidget { State createState() => MessageManagerState(); } -class MessageManagerState extends State - with SingleTickerProviderStateMixin { +class MessageManagerState extends State { final _messagesNotifier = ValueNotifier>([]); - double maxWidth = 0; - Offset offset = Offset.zero; - - late AnimationController _animationController; - - final animationDuration = commonDuration * 2; + final List _bufferMessages = []; + Completer? _messageIngCompleter; @override void initState() { super.initState(); - _animationController = AnimationController( - vsync: this, - duration: Duration(milliseconds: 400), - ); } @override void dispose() { _messagesNotifier.dispose(); - _animationController.dispose(); super.dispose(); } - message(String text) async { + Future message(String text) async { final commonMessage = CommonMessage( id: other.uuidV4, text: text, ); - _messagesNotifier.value = List.from(_messagesNotifier.value) - ..add( - commonMessage, - ); + _bufferMessages.add(commonMessage); + _showMessage(); + } + + _showMessage() async { + if (_messageIngCompleter?.isCompleted == false) { + return; + } + while (_bufferMessages.isNotEmpty) { + final commonMessage = _bufferMessages.removeAt(0); + _messagesNotifier.value = List.from(_messagesNotifier.value) + ..add( + commonMessage, + ); + + _messageIngCompleter = Completer(); + await Future.delayed(Duration(seconds: 1)); + Future.delayed(commonMessage.duration, () { + _handleRemove(commonMessage); + }); + _messageIngCompleter?.complete(true); + } } _handleRemove(CommonMessage commonMessage) async { @@ -64,171 +73,49 @@ class MessageManagerState extends State return Stack( children: [ widget.child, - LayoutBuilder( - builder: (context, container) { - maxWidth = container.maxWidth / 2 + 16; - return SizedBox( - width: maxWidth, - child: ValueListenableBuilder( - valueListenable: globalState.safeMessageOffsetNotifier, - builder: (_, offset, child) { - this.offset = offset; - if (offset == Offset.zero) { - return SizedBox(); - } - return Transform.translate( - offset: offset, - child: child!, - ); - }, - child: Container( - padding: EdgeInsets.only( - right: 0, - left: 8, - top: 0, - bottom: 16, - ), - alignment: Alignment.bottomLeft, - child: Stack( - alignment: Alignment.bottomLeft, - children: [ - SingleChildScrollView( - reverse: true, - physics: NeverScrollableScrollPhysics(), - child: ValueListenableBuilder( - valueListenable: _messagesNotifier, - builder: (_, messages, ___) { - return Column( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - for (final message in messages) ...[ - if (message != messages.first) - SizedBox( - height: 12, - ), - _MessageItem( - key: GlobalObjectKey(message.id), - message: message, - onRemove: _handleRemove, - ), - ], - ], - ); - }, - ), - ), - ], - ), - ), - ), + ValueListenableBuilder( + valueListenable: _messagesNotifier, + builder: (_, messages, __) { + return FadeThroughBox( + alignment: Alignment.topRight, + child: messages.isEmpty + ? SizedBox() + : LayoutBuilder( + key: Key(messages.last.id), + builder: (_, constraints) { + return Card( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(12.0), + ), + ), + elevation: 10, + margin: EdgeInsets.only( + top: kToolbarHeight, + left: 12, + right: 12, + ), + color: context.colorScheme.surfaceContainerHigh, + child: Container( + width: min( + constraints.maxWidth, + 400, + ), + padding: EdgeInsets.symmetric( + horizontal: 12, + vertical: 16, + ), + child: Text( + messages.last.text, + ), + ), + ); + }, + ), ); }, - ) + ), ], ); } } - -class _MessageItem extends StatefulWidget { - final CommonMessage message; - final Function(CommonMessage message) onRemove; - - const _MessageItem({ - super.key, - required this.message, - required this.onRemove, - }); - - @override - State<_MessageItem> createState() => _MessageItemState(); -} - -class _MessageItemState extends State<_MessageItem> - with SingleTickerProviderStateMixin { - late AnimationController _controller; - late Animation _offsetAnimation; - late Animation _fadeAnimation; - - @override - void initState() { - super.initState(); - _controller = AnimationController( - vsync: this, - duration: commonDuration * 1.5, - ); - _offsetAnimation = Tween( - begin: Offset(-1.0, 0.0), - end: Offset.zero, - ).animate(CurvedAnimation( - parent: _controller, - curve: Interval( - 0.0, - 1, - curve: Curves.easeOut, - ), - )); - - _fadeAnimation = Tween( - begin: 0.0, - end: 1, - ).animate(CurvedAnimation( - parent: _controller, - curve: Interval( - 0.0, - 0.2, - curve: Curves.easeIn, - ), - )); - - _controller.forward(); - - Future.delayed( - widget.message.duration, - () async { - await _controller.reverse(); - widget.onRemove( - widget.message, - ); - }, - ); - } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return AnimatedBuilder( - animation: _controller.view, - builder: (_, child) { - return FadeTransition( - opacity: _fadeAnimation, - child: SlideTransition( - position: _offsetAnimation, - child: Material( - elevation: _controller.value * 12, - borderRadius: BorderRadius.circular(8), - color: context.colorScheme.surfaceContainer, - clipBehavior: Clip.none, - child: Padding( - padding: EdgeInsets.symmetric(vertical: 16, horizontal: 16), - child: Text( - widget.message.text, - style: context.textTheme.bodyMedium?.copyWith( - color: context.colorScheme.onSurfaceVariant, - ), - maxLines: 5, - overflow: TextOverflow.ellipsis, - ), - ), - ), - ), - ); - }, - ); - } -} diff --git a/lib/manager/theme_manager.dart b/lib/manager/theme_manager.dart new file mode 100644 index 0000000..feffea5 --- /dev/null +++ b/lib/manager/theme_manager.dart @@ -0,0 +1,38 @@ +import 'package:fl_clash/common/constant.dart'; +import 'package:fl_clash/common/measure.dart'; +import 'package:fl_clash/common/theme.dart'; +import 'package:fl_clash/state.dart'; +import 'package:flutter/material.dart'; + +class ThemeManager extends StatelessWidget { + final Widget child; + + const ThemeManager({ + super.key, + required this.child, + }); + + @override + Widget build(BuildContext context) { + globalState.measure = Measure.of(context); + globalState.theme = CommonTheme.of(context); + return MediaQuery( + data: MediaQuery.of(context).copyWith( + textScaler: TextScaler.linear( + textScaleFactor, + ), + ), + child: LayoutBuilder( + builder: (_, container) { + globalState.appController.updateViewSize( + Size( + container.maxWidth, + container.maxHeight, + ), + ); + return child; + }, + ), + ); + } +} diff --git a/lib/manager/window_manager.dart b/lib/manager/window_manager.dart index 1aebae0..feff26a 100644 --- a/lib/manager/window_manager.dart +++ b/lib/manager/window_manager.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:fl_clash/common/common.dart'; @@ -24,6 +25,7 @@ class WindowManager extends ConsumerStatefulWidget { class _WindowContainerState extends ConsumerState with WindowListener, WindowExtListener { + @override Widget build(BuildContext context) { return widget.child; @@ -271,7 +273,7 @@ class _WindowHeaderState extends State { _updateMaximized(); }, child: Container( - color: context.colorScheme.secondary.toSoft, + color: context.colorScheme.secondary.opacity15, alignment: Alignment.centerLeft, height: kHeaderHeight, ), diff --git a/lib/models/app.dart b/lib/models/app.dart index 94228c7..d7f10b6 100644 --- a/lib/models/app.dart +++ b/lib/models/app.dart @@ -18,7 +18,7 @@ class AppState with _$AppState { @Default([]) List packages, @Default(ColorSchemes()) ColorSchemes colorSchemes, @Default(0) int sortNum, - required double viewWidth, + required Size viewSize, @Default({}) DelayMap delayMap, @Default([]) List groups, @Default(0) int checkIpNum, @@ -35,7 +35,7 @@ class AppState with _$AppState { } extension AppStateExt on AppState { - ViewMode get viewMode => other.getViewMode(viewWidth); + ViewMode get viewMode => other.getViewMode(viewSize.width); bool get isStart => runTime != null; } diff --git a/lib/models/clash_config.dart b/lib/models/clash_config.dart index c34795e..3c84864 100644 --- a/lib/models/clash_config.dart +++ b/lib/models/clash_config.dart @@ -6,6 +6,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import '../enum/enum.dart'; part 'generated/clash_config.freezed.dart'; + part 'generated/clash_config.g.dart'; typedef HostsMap = Map; @@ -122,7 +123,7 @@ class ProxyGroup with _$ProxyGroup { String? filter, @JsonKey(name: "expected-filter") String? excludeFilter, @JsonKey(name: "exclude-type") String? excludeType, - @JsonKey(name: "expected-status") int? expectedStatus, + @JsonKey(name: "expected-status") dynamic expectedStatus, bool? hidden, String? icon, }) = _ProxyGroup; @@ -131,6 +132,16 @@ class ProxyGroup with _$ProxyGroup { _$ProxyGroupFromJson(json); } +@freezed +class RuleProvider with _$RuleProvider { + const factory RuleProvider({ + required String name, + }) = _RuleProvider; + + factory RuleProvider.fromJson(Map json) => + _$RuleProviderFromJson(json); +} + @freezed class Tun with _$Tun { const factory Tun({ @@ -273,11 +284,136 @@ class GeoXUrl with _$GeoXUrl { } } +@freezed +class ParsedRule with _$ParsedRule { + const factory ParsedRule({ + required RuleAction ruleAction, + String? content, + String? ruleTarget, + String? ruleProvider, + String? subRule, + @Default(false) bool noResolve, + @Default(false) bool src, + }) = _ParsedRule; + + factory ParsedRule.parseString(String value) { + final splits = value.split(","); + final shortSplits = splits + .where( + (item) => !item.contains("src") && !item.contains("no-resolve"), + ) + .toList(); + final ruleAction = RuleAction.values.firstWhere( + (item) => item.value == shortSplits.first, + orElse: () => RuleAction.DOMAIN, + ); + String? subRule; + String? ruleTarget; + + if (ruleAction == RuleAction.SUB_RULE) { + subRule = shortSplits.last; + } else { + ruleTarget = shortSplits.last; + } + + String? content; + String? ruleProvider; + + if (ruleAction == RuleAction.RULE_SET) { + ruleProvider = shortSplits.sublist(1, shortSplits.length - 1).join(","); + } else { + content = shortSplits.sublist(1, shortSplits.length - 1).join(","); + } + + return ParsedRule( + ruleAction: ruleAction, + content: content, + src: splits.contains("src"), + ruleProvider: ruleProvider, + noResolve: splits.contains("no-resolve"), + subRule: subRule, + ruleTarget: ruleTarget, + ); + } +} + +extension ParsedRuleExt on ParsedRule { + String get value { + return [ + ruleAction.value, + ruleAction == RuleAction.RULE_SET ? ruleProvider : content, + ruleAction == RuleAction.SUB_RULE ? subRule : ruleTarget, + if (ruleAction.hasParams) ...[ + if (src) "src", + if (noResolve) "no-resolve", + ] + ].join(","); + } +} + +@freezed +class Rule with _$Rule { + const factory Rule({ + required String id, + required String value, + }) = _Rule; + + factory Rule.value(String value) { + return Rule( + value: value, + id: other.uuidV4, + ); + } + + factory Rule.fromJson(Map json) => _$RuleFromJson(json); +} + +@freezed +class SubRule with _$SubRule { + const factory SubRule({ + required String name, + }) = _SubRule; + + factory SubRule.fromJson(Map json) => + _$SubRuleFromJson(json); +} + +_genRule(List? rules) { + if (rules == null) { + return []; + } + return rules + .map( + (item) => Rule.value(item), + ) + .toList(); +} + +List _genRuleProviders(Map json) { + return json.entries.map((entry) => RuleProvider(name: entry.key)).toList(); +} + +List _genSubRules(Map json) { + return json.entries + .map( + (entry) => SubRule( + name: entry.key, + ), + ) + .toList(); +} + @freezed class ClashConfigSnippet with _$ClashConfigSnippet { const factory ClashConfigSnippet({ @Default([]) @JsonKey(name: "proxy-groups") List proxyGroups, - @Default([]) List rule, + @JsonKey(fromJson: _genRule) @Default([]) List rule, + @JsonKey(name: "rule-providers", fromJson: _genRuleProviders) + @Default([]) + List ruleProvider, + @JsonKey(name: "sub-rules", fromJson: _genSubRules) + @Default([]) + List subRules, }) = _ClashConfigSnippet; factory ClashConfigSnippet.fromJson(Map json) => diff --git a/lib/models/common.dart b/lib/models/common.dart index 18f986f..10c60f0 100644 --- a/lib/models/common.dart +++ b/lib/models/common.dart @@ -382,7 +382,7 @@ extension ColorSchemesExt on ColorSchemes { ); } return lightColorScheme != null - ? ColorScheme.fromSeed(seedColor: lightColorScheme!.primary) + ? ColorScheme.fromSeed(seedColor: lightColorScheme!.primary,dynamicSchemeVariant: DynamicSchemeVariant.vibrant) : ColorScheme.fromSeed(seedColor: defaultPrimaryColor); } } @@ -481,13 +481,13 @@ class Field with _$Field { }) = _Field; } -enum ActionType { +enum PopupMenuItemType { primary, danger, } -class ActionItemData { - const ActionItemData({ +class PopupMenuItemData { + const PopupMenuItemData({ this.icon, required this.label, required this.onPressed, @@ -497,7 +497,7 @@ class ActionItemData { final double? iconSize; final String label; - final VoidCallback onPressed; + final VoidCallback? onPressed; final IconData? icon; - final ActionType? type; + final PopupMenuItemType? type; } \ No newline at end of file diff --git a/lib/models/config.dart b/lib/models/config.dart index be10068..bf5a5f3 100644 --- a/lib/models/config.dart +++ b/lib/models/config.dart @@ -37,7 +37,7 @@ const defaultProxiesStyle = ProxiesStyle(); const defaultWindowProps = WindowProps(); const defaultAccessControl = AccessControl(); final defaultThemeProps = ThemeProps().copyWith( - primaryColor: defaultPrimaryColor.value, + primaryColor: defaultPrimaryColor.toARGB32(), themeMode: ThemeMode.dark, ); @@ -121,7 +121,7 @@ extension AccessControlExt on AccessControl { @freezed class WindowProps with _$WindowProps { const factory WindowProps({ - @Default(900) double width, + @Default(750) double width, @Default(600) double height, double? top, double? left, diff --git a/lib/models/core.dart b/lib/models/core.dart index 8d9319b..04373c7 100644 --- a/lib/models/core.dart +++ b/lib/models/core.dart @@ -62,6 +62,7 @@ class ConfigExtendedParams with _$ConfigExtendedParams { @JsonKey(name: "is-patch") required bool isPatch, @JsonKey(name: "selected-map") required SelectedMap selectedMap, @JsonKey(name: "override-dns") required bool overrideDns, + @JsonKey(name: "override-rule") required bool overrideRule, @JsonKey(name: "test-url") required String testUrl, }) = _ConfigExtendedParams; diff --git a/lib/models/generated/app.freezed.dart b/lib/models/generated/app.freezed.dart index b8b94b7..522245d 100644 --- a/lib/models/generated/app.freezed.dart +++ b/lib/models/generated/app.freezed.dart @@ -21,7 +21,7 @@ mixin _$AppState { List get packages => throw _privateConstructorUsedError; ColorSchemes get colorSchemes => throw _privateConstructorUsedError; int get sortNum => throw _privateConstructorUsedError; - double get viewWidth => throw _privateConstructorUsedError; + Size get viewSize => throw _privateConstructorUsedError; Map> get delayMap => throw _privateConstructorUsedError; List get groups => throw _privateConstructorUsedError; @@ -54,7 +54,7 @@ abstract class $AppStateCopyWith<$Res> { List packages, ColorSchemes colorSchemes, int sortNum, - double viewWidth, + Size viewSize, Map> delayMap, List groups, int checkIpNum, @@ -91,7 +91,7 @@ class _$AppStateCopyWithImpl<$Res, $Val extends AppState> Object? packages = null, Object? colorSchemes = null, Object? sortNum = null, - Object? viewWidth = null, + Object? viewSize = null, Object? delayMap = null, Object? groups = null, Object? checkIpNum = null, @@ -126,10 +126,10 @@ class _$AppStateCopyWithImpl<$Res, $Val extends AppState> ? _value.sortNum : sortNum // ignore: cast_nullable_to_non_nullable as int, - viewWidth: null == viewWidth - ? _value.viewWidth - : viewWidth // ignore: cast_nullable_to_non_nullable - as double, + viewSize: null == viewSize + ? _value.viewSize + : viewSize // ignore: cast_nullable_to_non_nullable + as Size, delayMap: null == delayMap ? _value.delayMap : delayMap // ignore: cast_nullable_to_non_nullable @@ -206,7 +206,7 @@ abstract class _$$AppStateImplCopyWith<$Res> List packages, ColorSchemes colorSchemes, int sortNum, - double viewWidth, + Size viewSize, Map> delayMap, List groups, int checkIpNum, @@ -242,7 +242,7 @@ class __$$AppStateImplCopyWithImpl<$Res> Object? packages = null, Object? colorSchemes = null, Object? sortNum = null, - Object? viewWidth = null, + Object? viewSize = null, Object? delayMap = null, Object? groups = null, Object? checkIpNum = null, @@ -277,10 +277,10 @@ class __$$AppStateImplCopyWithImpl<$Res> ? _value.sortNum : sortNum // ignore: cast_nullable_to_non_nullable as int, - viewWidth: null == viewWidth - ? _value.viewWidth - : viewWidth // ignore: cast_nullable_to_non_nullable - as double, + viewSize: null == viewSize + ? _value.viewSize + : viewSize // ignore: cast_nullable_to_non_nullable + as Size, delayMap: null == delayMap ? _value._delayMap : delayMap // ignore: cast_nullable_to_non_nullable @@ -342,7 +342,7 @@ class _$AppStateImpl implements _AppState { final List packages = const [], this.colorSchemes = const ColorSchemes(), this.sortNum = 0, - required this.viewWidth, + required this.viewSize, final Map> delayMap = const {}, final List groups = const [], this.checkIpNum = 0, @@ -382,7 +382,7 @@ class _$AppStateImpl implements _AppState { @JsonKey() final int sortNum; @override - final double viewWidth; + final Size viewSize; final Map> _delayMap; @override @JsonKey() @@ -432,7 +432,7 @@ class _$AppStateImpl implements _AppState { @override String toString() { - return 'AppState(isInit: $isInit, pageLabel: $pageLabel, packages: $packages, colorSchemes: $colorSchemes, sortNum: $sortNum, viewWidth: $viewWidth, delayMap: $delayMap, groups: $groups, checkIpNum: $checkIpNum, brightness: $brightness, runTime: $runTime, providers: $providers, localIp: $localIp, requests: $requests, version: $version, logs: $logs, traffics: $traffics, totalTraffic: $totalTraffic)'; + return 'AppState(isInit: $isInit, pageLabel: $pageLabel, packages: $packages, colorSchemes: $colorSchemes, sortNum: $sortNum, viewSize: $viewSize, delayMap: $delayMap, groups: $groups, checkIpNum: $checkIpNum, brightness: $brightness, runTime: $runTime, providers: $providers, localIp: $localIp, requests: $requests, version: $version, logs: $logs, traffics: $traffics, totalTraffic: $totalTraffic)'; } @override @@ -447,8 +447,8 @@ class _$AppStateImpl implements _AppState { (identical(other.colorSchemes, colorSchemes) || other.colorSchemes == colorSchemes) && (identical(other.sortNum, sortNum) || other.sortNum == sortNum) && - (identical(other.viewWidth, viewWidth) || - other.viewWidth == viewWidth) && + (identical(other.viewSize, viewSize) || + other.viewSize == viewSize) && const DeepCollectionEquality().equals(other._delayMap, _delayMap) && const DeepCollectionEquality().equals(other._groups, _groups) && (identical(other.checkIpNum, checkIpNum) || @@ -477,7 +477,7 @@ class _$AppStateImpl implements _AppState { const DeepCollectionEquality().hash(_packages), colorSchemes, sortNum, - viewWidth, + viewSize, const DeepCollectionEquality().hash(_delayMap), const DeepCollectionEquality().hash(_groups), checkIpNum, @@ -507,7 +507,7 @@ abstract class _AppState implements AppState { final List packages, final ColorSchemes colorSchemes, final int sortNum, - required final double viewWidth, + required final Size viewSize, final Map> delayMap, final List groups, final int checkIpNum, @@ -532,7 +532,7 @@ abstract class _AppState implements AppState { @override int get sortNum; @override - double get viewWidth; + Size get viewSize; @override Map> get delayMap; @override diff --git a/lib/models/generated/clash_config.freezed.dart b/lib/models/generated/clash_config.freezed.dart index 0a6a9b5..1f851b2 100644 --- a/lib/models/generated/clash_config.freezed.dart +++ b/lib/models/generated/clash_config.freezed.dart @@ -37,7 +37,7 @@ mixin _$ProxyGroup { @JsonKey(name: "exclude-type") String? get excludeType => throw _privateConstructorUsedError; @JsonKey(name: "expected-status") - int? get expectedStatus => throw _privateConstructorUsedError; + dynamic get expectedStatus => throw _privateConstructorUsedError; bool? get hidden => throw _privateConstructorUsedError; String? get icon => throw _privateConstructorUsedError; @@ -70,7 +70,7 @@ abstract class $ProxyGroupCopyWith<$Res> { String? filter, @JsonKey(name: "expected-filter") String? excludeFilter, @JsonKey(name: "exclude-type") String? excludeType, - @JsonKey(name: "expected-status") int? expectedStatus, + @JsonKey(name: "expected-status") dynamic expectedStatus, bool? hidden, String? icon}); } @@ -158,7 +158,7 @@ class _$ProxyGroupCopyWithImpl<$Res, $Val extends ProxyGroup> expectedStatus: freezed == expectedStatus ? _value.expectedStatus : expectedStatus // ignore: cast_nullable_to_non_nullable - as int?, + as dynamic, hidden: freezed == hidden ? _value.hidden : hidden // ignore: cast_nullable_to_non_nullable @@ -192,7 +192,7 @@ abstract class _$$ProxyGroupImplCopyWith<$Res> String? filter, @JsonKey(name: "expected-filter") String? excludeFilter, @JsonKey(name: "exclude-type") String? excludeType, - @JsonKey(name: "expected-status") int? expectedStatus, + @JsonKey(name: "expected-status") dynamic expectedStatus, bool? hidden, String? icon}); } @@ -278,7 +278,7 @@ class __$$ProxyGroupImplCopyWithImpl<$Res> expectedStatus: freezed == expectedStatus ? _value.expectedStatus : expectedStatus // ignore: cast_nullable_to_non_nullable - as int?, + as dynamic, hidden: freezed == hidden ? _value.hidden : hidden // ignore: cast_nullable_to_non_nullable @@ -362,7 +362,7 @@ class _$ProxyGroupImpl implements _ProxyGroup { final String? excludeType; @override @JsonKey(name: "expected-status") - final int? expectedStatus; + final dynamic expectedStatus; @override final bool? hidden; @override @@ -394,8 +394,8 @@ class _$ProxyGroupImpl implements _ProxyGroup { other.excludeFilter == excludeFilter) && (identical(other.excludeType, excludeType) || other.excludeType == excludeType) && - (identical(other.expectedStatus, expectedStatus) || - other.expectedStatus == expectedStatus) && + const DeepCollectionEquality() + .equals(other.expectedStatus, expectedStatus) && (identical(other.hidden, hidden) || other.hidden == hidden) && (identical(other.icon, icon) || other.icon == icon)); } @@ -416,7 +416,7 @@ class _$ProxyGroupImpl implements _ProxyGroup { filter, excludeFilter, excludeType, - expectedStatus, + const DeepCollectionEquality().hash(expectedStatus), hidden, icon); @@ -451,7 +451,7 @@ abstract class _ProxyGroup implements ProxyGroup { final String? filter, @JsonKey(name: "expected-filter") final String? excludeFilter, @JsonKey(name: "exclude-type") final String? excludeType, - @JsonKey(name: "expected-status") final int? expectedStatus, + @JsonKey(name: "expected-status") final dynamic expectedStatus, final bool? hidden, final String? icon}) = _$ProxyGroupImpl; @@ -488,7 +488,7 @@ abstract class _ProxyGroup implements ProxyGroup { String? get excludeType; @override @JsonKey(name: "expected-status") - int? get expectedStatus; + dynamic get expectedStatus; @override bool? get hidden; @override @@ -502,6 +502,156 @@ abstract class _ProxyGroup implements ProxyGroup { throw _privateConstructorUsedError; } +RuleProvider _$RuleProviderFromJson(Map json) { + return _RuleProvider.fromJson(json); +} + +/// @nodoc +mixin _$RuleProvider { + String get name => throw _privateConstructorUsedError; + + /// Serializes this RuleProvider to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of RuleProvider + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $RuleProviderCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $RuleProviderCopyWith<$Res> { + factory $RuleProviderCopyWith( + RuleProvider value, $Res Function(RuleProvider) then) = + _$RuleProviderCopyWithImpl<$Res, RuleProvider>; + @useResult + $Res call({String name}); +} + +/// @nodoc +class _$RuleProviderCopyWithImpl<$Res, $Val extends RuleProvider> + implements $RuleProviderCopyWith<$Res> { + _$RuleProviderCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of RuleProvider + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + }) { + return _then(_value.copyWith( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$RuleProviderImplCopyWith<$Res> + implements $RuleProviderCopyWith<$Res> { + factory _$$RuleProviderImplCopyWith( + _$RuleProviderImpl value, $Res Function(_$RuleProviderImpl) then) = + __$$RuleProviderImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String name}); +} + +/// @nodoc +class __$$RuleProviderImplCopyWithImpl<$Res> + extends _$RuleProviderCopyWithImpl<$Res, _$RuleProviderImpl> + implements _$$RuleProviderImplCopyWith<$Res> { + __$$RuleProviderImplCopyWithImpl( + _$RuleProviderImpl _value, $Res Function(_$RuleProviderImpl) _then) + : super(_value, _then); + + /// Create a copy of RuleProvider + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + }) { + return _then(_$RuleProviderImpl( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$RuleProviderImpl implements _RuleProvider { + const _$RuleProviderImpl({required this.name}); + + factory _$RuleProviderImpl.fromJson(Map json) => + _$$RuleProviderImplFromJson(json); + + @override + final String name; + + @override + String toString() { + return 'RuleProvider(name: $name)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RuleProviderImpl && + (identical(other.name, name) || other.name == name)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, name); + + /// Create a copy of RuleProvider + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$RuleProviderImplCopyWith<_$RuleProviderImpl> get copyWith => + __$$RuleProviderImplCopyWithImpl<_$RuleProviderImpl>(this, _$identity); + + @override + Map toJson() { + return _$$RuleProviderImplToJson( + this, + ); + } +} + +abstract class _RuleProvider implements RuleProvider { + const factory _RuleProvider({required final String name}) = + _$RuleProviderImpl; + + factory _RuleProvider.fromJson(Map json) = + _$RuleProviderImpl.fromJson; + + @override + String get name; + + /// Create a copy of RuleProvider + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$RuleProviderImplCopyWith<_$RuleProviderImpl> get copyWith => + throw _privateConstructorUsedError; +} + Tun _$TunFromJson(Map json) { return _Tun.fromJson(json); } @@ -1834,6 +1984,571 @@ abstract class _GeoXUrl implements GeoXUrl { throw _privateConstructorUsedError; } +/// @nodoc +mixin _$ParsedRule { + RuleAction get ruleAction => throw _privateConstructorUsedError; + String? get content => throw _privateConstructorUsedError; + String? get ruleTarget => throw _privateConstructorUsedError; + String? get ruleProvider => throw _privateConstructorUsedError; + String? get subRule => throw _privateConstructorUsedError; + bool get noResolve => throw _privateConstructorUsedError; + bool get src => throw _privateConstructorUsedError; + + /// Create a copy of ParsedRule + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ParsedRuleCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ParsedRuleCopyWith<$Res> { + factory $ParsedRuleCopyWith( + ParsedRule value, $Res Function(ParsedRule) then) = + _$ParsedRuleCopyWithImpl<$Res, ParsedRule>; + @useResult + $Res call( + {RuleAction ruleAction, + String? content, + String? ruleTarget, + String? ruleProvider, + String? subRule, + bool noResolve, + bool src}); +} + +/// @nodoc +class _$ParsedRuleCopyWithImpl<$Res, $Val extends ParsedRule> + implements $ParsedRuleCopyWith<$Res> { + _$ParsedRuleCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ParsedRule + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? ruleAction = null, + Object? content = freezed, + Object? ruleTarget = freezed, + Object? ruleProvider = freezed, + Object? subRule = freezed, + Object? noResolve = null, + Object? src = null, + }) { + return _then(_value.copyWith( + ruleAction: null == ruleAction + ? _value.ruleAction + : ruleAction // ignore: cast_nullable_to_non_nullable + as RuleAction, + content: freezed == content + ? _value.content + : content // ignore: cast_nullable_to_non_nullable + as String?, + ruleTarget: freezed == ruleTarget + ? _value.ruleTarget + : ruleTarget // ignore: cast_nullable_to_non_nullable + as String?, + ruleProvider: freezed == ruleProvider + ? _value.ruleProvider + : ruleProvider // ignore: cast_nullable_to_non_nullable + as String?, + subRule: freezed == subRule + ? _value.subRule + : subRule // ignore: cast_nullable_to_non_nullable + as String?, + noResolve: null == noResolve + ? _value.noResolve + : noResolve // ignore: cast_nullable_to_non_nullable + as bool, + src: null == src + ? _value.src + : src // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ParsedRuleImplCopyWith<$Res> + implements $ParsedRuleCopyWith<$Res> { + factory _$$ParsedRuleImplCopyWith( + _$ParsedRuleImpl value, $Res Function(_$ParsedRuleImpl) then) = + __$$ParsedRuleImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {RuleAction ruleAction, + String? content, + String? ruleTarget, + String? ruleProvider, + String? subRule, + bool noResolve, + bool src}); +} + +/// @nodoc +class __$$ParsedRuleImplCopyWithImpl<$Res> + extends _$ParsedRuleCopyWithImpl<$Res, _$ParsedRuleImpl> + implements _$$ParsedRuleImplCopyWith<$Res> { + __$$ParsedRuleImplCopyWithImpl( + _$ParsedRuleImpl _value, $Res Function(_$ParsedRuleImpl) _then) + : super(_value, _then); + + /// Create a copy of ParsedRule + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? ruleAction = null, + Object? content = freezed, + Object? ruleTarget = freezed, + Object? ruleProvider = freezed, + Object? subRule = freezed, + Object? noResolve = null, + Object? src = null, + }) { + return _then(_$ParsedRuleImpl( + ruleAction: null == ruleAction + ? _value.ruleAction + : ruleAction // ignore: cast_nullable_to_non_nullable + as RuleAction, + content: freezed == content + ? _value.content + : content // ignore: cast_nullable_to_non_nullable + as String?, + ruleTarget: freezed == ruleTarget + ? _value.ruleTarget + : ruleTarget // ignore: cast_nullable_to_non_nullable + as String?, + ruleProvider: freezed == ruleProvider + ? _value.ruleProvider + : ruleProvider // ignore: cast_nullable_to_non_nullable + as String?, + subRule: freezed == subRule + ? _value.subRule + : subRule // ignore: cast_nullable_to_non_nullable + as String?, + noResolve: null == noResolve + ? _value.noResolve + : noResolve // ignore: cast_nullable_to_non_nullable + as bool, + src: null == src + ? _value.src + : src // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc + +class _$ParsedRuleImpl implements _ParsedRule { + const _$ParsedRuleImpl( + {required this.ruleAction, + this.content, + this.ruleTarget, + this.ruleProvider, + this.subRule, + this.noResolve = false, + this.src = false}); + + @override + final RuleAction ruleAction; + @override + final String? content; + @override + final String? ruleTarget; + @override + final String? ruleProvider; + @override + final String? subRule; + @override + @JsonKey() + final bool noResolve; + @override + @JsonKey() + final bool src; + + @override + String toString() { + return 'ParsedRule(ruleAction: $ruleAction, content: $content, ruleTarget: $ruleTarget, ruleProvider: $ruleProvider, subRule: $subRule, noResolve: $noResolve, src: $src)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ParsedRuleImpl && + (identical(other.ruleAction, ruleAction) || + other.ruleAction == ruleAction) && + (identical(other.content, content) || other.content == content) && + (identical(other.ruleTarget, ruleTarget) || + other.ruleTarget == ruleTarget) && + (identical(other.ruleProvider, ruleProvider) || + other.ruleProvider == ruleProvider) && + (identical(other.subRule, subRule) || other.subRule == subRule) && + (identical(other.noResolve, noResolve) || + other.noResolve == noResolve) && + (identical(other.src, src) || other.src == src)); + } + + @override + int get hashCode => Object.hash(runtimeType, ruleAction, content, ruleTarget, + ruleProvider, subRule, noResolve, src); + + /// Create a copy of ParsedRule + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ParsedRuleImplCopyWith<_$ParsedRuleImpl> get copyWith => + __$$ParsedRuleImplCopyWithImpl<_$ParsedRuleImpl>(this, _$identity); +} + +abstract class _ParsedRule implements ParsedRule { + const factory _ParsedRule( + {required final RuleAction ruleAction, + final String? content, + final String? ruleTarget, + final String? ruleProvider, + final String? subRule, + final bool noResolve, + final bool src}) = _$ParsedRuleImpl; + + @override + RuleAction get ruleAction; + @override + String? get content; + @override + String? get ruleTarget; + @override + String? get ruleProvider; + @override + String? get subRule; + @override + bool get noResolve; + @override + bool get src; + + /// Create a copy of ParsedRule + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ParsedRuleImplCopyWith<_$ParsedRuleImpl> get copyWith => + throw _privateConstructorUsedError; +} + +Rule _$RuleFromJson(Map json) { + return _Rule.fromJson(json); +} + +/// @nodoc +mixin _$Rule { + String get id => throw _privateConstructorUsedError; + String get value => throw _privateConstructorUsedError; + + /// Serializes this Rule to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of Rule + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $RuleCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $RuleCopyWith<$Res> { + factory $RuleCopyWith(Rule value, $Res Function(Rule) then) = + _$RuleCopyWithImpl<$Res, Rule>; + @useResult + $Res call({String id, String value}); +} + +/// @nodoc +class _$RuleCopyWithImpl<$Res, $Val extends Rule> + implements $RuleCopyWith<$Res> { + _$RuleCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Rule + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? value = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + value: null == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$RuleImplCopyWith<$Res> implements $RuleCopyWith<$Res> { + factory _$$RuleImplCopyWith( + _$RuleImpl value, $Res Function(_$RuleImpl) then) = + __$$RuleImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String id, String value}); +} + +/// @nodoc +class __$$RuleImplCopyWithImpl<$Res> + extends _$RuleCopyWithImpl<$Res, _$RuleImpl> + implements _$$RuleImplCopyWith<$Res> { + __$$RuleImplCopyWithImpl(_$RuleImpl _value, $Res Function(_$RuleImpl) _then) + : super(_value, _then); + + /// Create a copy of Rule + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? value = null, + }) { + return _then(_$RuleImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + value: null == value + ? _value.value + : value // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$RuleImpl implements _Rule { + const _$RuleImpl({required this.id, required this.value}); + + factory _$RuleImpl.fromJson(Map json) => + _$$RuleImplFromJson(json); + + @override + final String id; + @override + final String value; + + @override + String toString() { + return 'Rule(id: $id, value: $value)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RuleImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.value, value) || other.value == value)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, id, value); + + /// Create a copy of Rule + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$RuleImplCopyWith<_$RuleImpl> get copyWith => + __$$RuleImplCopyWithImpl<_$RuleImpl>(this, _$identity); + + @override + Map toJson() { + return _$$RuleImplToJson( + this, + ); + } +} + +abstract class _Rule implements Rule { + const factory _Rule({required final String id, required final String value}) = + _$RuleImpl; + + factory _Rule.fromJson(Map json) = _$RuleImpl.fromJson; + + @override + String get id; + @override + String get value; + + /// Create a copy of Rule + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$RuleImplCopyWith<_$RuleImpl> get copyWith => + throw _privateConstructorUsedError; +} + +SubRule _$SubRuleFromJson(Map json) { + return _SubRule.fromJson(json); +} + +/// @nodoc +mixin _$SubRule { + String get name => throw _privateConstructorUsedError; + + /// Serializes this SubRule to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of SubRule + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SubRuleCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SubRuleCopyWith<$Res> { + factory $SubRuleCopyWith(SubRule value, $Res Function(SubRule) then) = + _$SubRuleCopyWithImpl<$Res, SubRule>; + @useResult + $Res call({String name}); +} + +/// @nodoc +class _$SubRuleCopyWithImpl<$Res, $Val extends SubRule> + implements $SubRuleCopyWith<$Res> { + _$SubRuleCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SubRule + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + }) { + return _then(_value.copyWith( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$SubRuleImplCopyWith<$Res> implements $SubRuleCopyWith<$Res> { + factory _$$SubRuleImplCopyWith( + _$SubRuleImpl value, $Res Function(_$SubRuleImpl) then) = + __$$SubRuleImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String name}); +} + +/// @nodoc +class __$$SubRuleImplCopyWithImpl<$Res> + extends _$SubRuleCopyWithImpl<$Res, _$SubRuleImpl> + implements _$$SubRuleImplCopyWith<$Res> { + __$$SubRuleImplCopyWithImpl( + _$SubRuleImpl _value, $Res Function(_$SubRuleImpl) _then) + : super(_value, _then); + + /// Create a copy of SubRule + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + }) { + return _then(_$SubRuleImpl( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SubRuleImpl implements _SubRule { + const _$SubRuleImpl({required this.name}); + + factory _$SubRuleImpl.fromJson(Map json) => + _$$SubRuleImplFromJson(json); + + @override + final String name; + + @override + String toString() { + return 'SubRule(name: $name)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SubRuleImpl && + (identical(other.name, name) || other.name == name)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, name); + + /// Create a copy of SubRule + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SubRuleImplCopyWith<_$SubRuleImpl> get copyWith => + __$$SubRuleImplCopyWithImpl<_$SubRuleImpl>(this, _$identity); + + @override + Map toJson() { + return _$$SubRuleImplToJson( + this, + ); + } +} + +abstract class _SubRule implements SubRule { + const factory _SubRule({required final String name}) = _$SubRuleImpl; + + factory _SubRule.fromJson(Map json) = _$SubRuleImpl.fromJson; + + @override + String get name; + + /// Create a copy of SubRule + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SubRuleImplCopyWith<_$SubRuleImpl> get copyWith => + throw _privateConstructorUsedError; +} + ClashConfigSnippet _$ClashConfigSnippetFromJson(Map json) { return _ClashConfigSnippet.fromJson(json); } @@ -1842,7 +2557,12 @@ ClashConfigSnippet _$ClashConfigSnippetFromJson(Map json) { mixin _$ClashConfigSnippet { @JsonKey(name: "proxy-groups") List get proxyGroups => throw _privateConstructorUsedError; - List get rule => throw _privateConstructorUsedError; + @JsonKey(fromJson: _genRule) + List get rule => throw _privateConstructorUsedError; + @JsonKey(name: "rule-providers", fromJson: _genRuleProviders) + List get ruleProvider => throw _privateConstructorUsedError; + @JsonKey(name: "sub-rules", fromJson: _genSubRules) + List get subRules => throw _privateConstructorUsedError; /// Serializes this ClashConfigSnippet to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -1862,7 +2582,11 @@ abstract class $ClashConfigSnippetCopyWith<$Res> { @useResult $Res call( {@JsonKey(name: "proxy-groups") List proxyGroups, - List rule}); + @JsonKey(fromJson: _genRule) List rule, + @JsonKey(name: "rule-providers", fromJson: _genRuleProviders) + List ruleProvider, + @JsonKey(name: "sub-rules", fromJson: _genSubRules) + List subRules}); } /// @nodoc @@ -1882,6 +2606,8 @@ class _$ClashConfigSnippetCopyWithImpl<$Res, $Val extends ClashConfigSnippet> $Res call({ Object? proxyGroups = null, Object? rule = null, + Object? ruleProvider = null, + Object? subRules = null, }) { return _then(_value.copyWith( proxyGroups: null == proxyGroups @@ -1891,7 +2617,15 @@ class _$ClashConfigSnippetCopyWithImpl<$Res, $Val extends ClashConfigSnippet> rule: null == rule ? _value.rule : rule // ignore: cast_nullable_to_non_nullable - as List, + as List, + ruleProvider: null == ruleProvider + ? _value.ruleProvider + : ruleProvider // ignore: cast_nullable_to_non_nullable + as List, + subRules: null == subRules + ? _value.subRules + : subRules // ignore: cast_nullable_to_non_nullable + as List, ) as $Val); } } @@ -1906,7 +2640,11 @@ abstract class _$$ClashConfigSnippetImplCopyWith<$Res> @useResult $Res call( {@JsonKey(name: "proxy-groups") List proxyGroups, - List rule}); + @JsonKey(fromJson: _genRule) List rule, + @JsonKey(name: "rule-providers", fromJson: _genRuleProviders) + List ruleProvider, + @JsonKey(name: "sub-rules", fromJson: _genSubRules) + List subRules}); } /// @nodoc @@ -1924,6 +2662,8 @@ class __$$ClashConfigSnippetImplCopyWithImpl<$Res> $Res call({ Object? proxyGroups = null, Object? rule = null, + Object? ruleProvider = null, + Object? subRules = null, }) { return _then(_$ClashConfigSnippetImpl( proxyGroups: null == proxyGroups @@ -1933,7 +2673,15 @@ class __$$ClashConfigSnippetImplCopyWithImpl<$Res> rule: null == rule ? _value._rule : rule // ignore: cast_nullable_to_non_nullable - as List, + as List, + ruleProvider: null == ruleProvider + ? _value._ruleProvider + : ruleProvider // ignore: cast_nullable_to_non_nullable + as List, + subRules: null == subRules + ? _value._subRules + : subRules // ignore: cast_nullable_to_non_nullable + as List, )); } } @@ -1944,9 +2692,15 @@ class _$ClashConfigSnippetImpl implements _ClashConfigSnippet { const _$ClashConfigSnippetImpl( {@JsonKey(name: "proxy-groups") final List proxyGroups = const [], - final List rule = const []}) + @JsonKey(fromJson: _genRule) final List rule = const [], + @JsonKey(name: "rule-providers", fromJson: _genRuleProviders) + final List ruleProvider = const [], + @JsonKey(name: "sub-rules", fromJson: _genSubRules) + final List subRules = const []}) : _proxyGroups = proxyGroups, - _rule = rule; + _rule = rule, + _ruleProvider = ruleProvider, + _subRules = subRules; factory _$ClashConfigSnippetImpl.fromJson(Map json) => _$$ClashConfigSnippetImplFromJson(json); @@ -1960,18 +2714,36 @@ class _$ClashConfigSnippetImpl implements _ClashConfigSnippet { return EqualUnmodifiableListView(_proxyGroups); } - final List _rule; + final List _rule; @override - @JsonKey() - List get rule { + @JsonKey(fromJson: _genRule) + List get rule { if (_rule is EqualUnmodifiableListView) return _rule; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_rule); } + final List _ruleProvider; + @override + @JsonKey(name: "rule-providers", fromJson: _genRuleProviders) + List get ruleProvider { + if (_ruleProvider is EqualUnmodifiableListView) return _ruleProvider; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_ruleProvider); + } + + final List _subRules; + @override + @JsonKey(name: "sub-rules", fromJson: _genSubRules) + List get subRules { + if (_subRules is EqualUnmodifiableListView) return _subRules; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_subRules); + } + @override String toString() { - return 'ClashConfigSnippet(proxyGroups: $proxyGroups, rule: $rule)'; + return 'ClashConfigSnippet(proxyGroups: $proxyGroups, rule: $rule, ruleProvider: $ruleProvider, subRules: $subRules)'; } @override @@ -1981,7 +2753,10 @@ class _$ClashConfigSnippetImpl implements _ClashConfigSnippet { other is _$ClashConfigSnippetImpl && const DeepCollectionEquality() .equals(other._proxyGroups, _proxyGroups) && - const DeepCollectionEquality().equals(other._rule, _rule)); + const DeepCollectionEquality().equals(other._rule, _rule) && + const DeepCollectionEquality() + .equals(other._ruleProvider, _ruleProvider) && + const DeepCollectionEquality().equals(other._subRules, _subRules)); } @JsonKey(includeFromJson: false, includeToJson: false) @@ -1989,7 +2764,9 @@ class _$ClashConfigSnippetImpl implements _ClashConfigSnippet { int get hashCode => Object.hash( runtimeType, const DeepCollectionEquality().hash(_proxyGroups), - const DeepCollectionEquality().hash(_rule)); + const DeepCollectionEquality().hash(_rule), + const DeepCollectionEquality().hash(_ruleProvider), + const DeepCollectionEquality().hash(_subRules)); /// Create a copy of ClashConfigSnippet /// with the given fields replaced by the non-null parameter values. @@ -2011,7 +2788,11 @@ class _$ClashConfigSnippetImpl implements _ClashConfigSnippet { abstract class _ClashConfigSnippet implements ClashConfigSnippet { const factory _ClashConfigSnippet( {@JsonKey(name: "proxy-groups") final List proxyGroups, - final List rule}) = _$ClashConfigSnippetImpl; + @JsonKey(fromJson: _genRule) final List rule, + @JsonKey(name: "rule-providers", fromJson: _genRuleProviders) + final List ruleProvider, + @JsonKey(name: "sub-rules", fromJson: _genSubRules) + final List subRules}) = _$ClashConfigSnippetImpl; factory _ClashConfigSnippet.fromJson(Map json) = _$ClashConfigSnippetImpl.fromJson; @@ -2020,7 +2801,14 @@ abstract class _ClashConfigSnippet implements ClashConfigSnippet { @JsonKey(name: "proxy-groups") List get proxyGroups; @override - List get rule; + @JsonKey(fromJson: _genRule) + List get rule; + @override + @JsonKey(name: "rule-providers", fromJson: _genRuleProviders) + List get ruleProvider; + @override + @JsonKey(name: "sub-rules", fromJson: _genSubRules) + List get subRules; /// Create a copy of ClashConfigSnippet /// with the given fields replaced by the non-null parameter values. diff --git a/lib/models/generated/clash_config.g.dart b/lib/models/generated/clash_config.g.dart index 1d4ac49..61208c3 100644 --- a/lib/models/generated/clash_config.g.dart +++ b/lib/models/generated/clash_config.g.dart @@ -21,7 +21,7 @@ _$ProxyGroupImpl _$$ProxyGroupImplFromJson(Map json) => filter: json['filter'] as String?, excludeFilter: json['expected-filter'] as String?, excludeType: json['exclude-type'] as String?, - expectedStatus: (json['expected-status'] as num?)?.toInt(), + expectedStatus: json['expected-status'], hidden: json['hidden'] as bool?, icon: json['icon'] as String?, ); @@ -53,6 +53,16 @@ const _$GroupTypeEnumMap = { GroupType.Relay: 'Relay', }; +_$RuleProviderImpl _$$RuleProviderImplFromJson(Map json) => + _$RuleProviderImpl( + name: json['name'] as String, + ); + +Map _$$RuleProviderImplToJson(_$RuleProviderImpl instance) => + { + 'name': instance.name, + }; + _$TunImpl _$$TunImplFromJson(Map json) => _$TunImpl( enable: json['enable'] as bool? ?? false, device: json['device'] as String? ?? appName, @@ -206,6 +216,27 @@ Map _$$GeoXUrlImplToJson(_$GeoXUrlImpl instance) => 'geosite': instance.geosite, }; +_$RuleImpl _$$RuleImplFromJson(Map json) => _$RuleImpl( + id: json['id'] as String, + value: json['value'] as String, + ); + +Map _$$RuleImplToJson(_$RuleImpl instance) => + { + 'id': instance.id, + 'value': instance.value, + }; + +_$SubRuleImpl _$$SubRuleImplFromJson(Map json) => + _$SubRuleImpl( + name: json['name'] as String, + ); + +Map _$$SubRuleImplToJson(_$SubRuleImpl instance) => + { + 'name': instance.name, + }; + _$ClashConfigSnippetImpl _$$ClashConfigSnippetImplFromJson( Map json) => _$ClashConfigSnippetImpl( @@ -213,9 +244,13 @@ _$ClashConfigSnippetImpl _$$ClashConfigSnippetImplFromJson( ?.map((e) => ProxyGroup.fromJson(e as Map)) .toList() ?? const [], - rule: - (json['rule'] as List?)?.map((e) => e as String).toList() ?? - const [], + rule: json['rule'] == null ? const [] : _genRule(json['rule'] as List?), + ruleProvider: json['rule-providers'] == null + ? const [] + : _genRuleProviders(json['rule-providers'] as Map), + subRules: json['sub-rules'] == null + ? const [] + : _genSubRules(json['sub-rules'] as Map), ); Map _$$ClashConfigSnippetImplToJson( @@ -223,6 +258,8 @@ Map _$$ClashConfigSnippetImplToJson( { 'proxy-groups': instance.proxyGroups, 'rule': instance.rule, + 'rule-providers': instance.ruleProvider, + 'sub-rules': instance.subRules, }; _$ClashConfigImpl _$$ClashConfigImplFromJson(Map json) => diff --git a/lib/models/generated/config.freezed.dart b/lib/models/generated/config.freezed.dart index 8ee0a2b..069eb4f 100644 --- a/lib/models/generated/config.freezed.dart +++ b/lib/models/generated/config.freezed.dart @@ -912,7 +912,7 @@ class __$$WindowPropsImplCopyWithImpl<$Res> @JsonSerializable() class _$WindowPropsImpl implements _WindowProps { const _$WindowPropsImpl( - {this.width = 900, this.height = 600, this.top, this.left}); + {this.width = 750, this.height = 600, this.top, this.left}); factory _$WindowPropsImpl.fromJson(Map json) => _$$WindowPropsImplFromJson(json); diff --git a/lib/models/generated/config.g.dart b/lib/models/generated/config.g.dart index de4e17e..6a12be9 100644 --- a/lib/models/generated/config.g.dart +++ b/lib/models/generated/config.g.dart @@ -102,7 +102,7 @@ const _$AccessSortTypeEnumMap = { _$WindowPropsImpl _$$WindowPropsImplFromJson(Map json) => _$WindowPropsImpl( - width: (json['width'] as num?)?.toDouble() ?? 900, + width: (json['width'] as num?)?.toDouble() ?? 750, height: (json['height'] as num?)?.toDouble() ?? 600, top: (json['top'] as num?)?.toDouble(), left: (json['left'] as num?)?.toDouble(), diff --git a/lib/models/generated/core.freezed.dart b/lib/models/generated/core.freezed.dart index 7a6875f..d214e99 100644 --- a/lib/models/generated/core.freezed.dart +++ b/lib/models/generated/core.freezed.dart @@ -667,6 +667,8 @@ mixin _$ConfigExtendedParams { Map get selectedMap => throw _privateConstructorUsedError; @JsonKey(name: "override-dns") bool get overrideDns => throw _privateConstructorUsedError; + @JsonKey(name: "override-rule") + bool get overrideRule => throw _privateConstructorUsedError; @JsonKey(name: "test-url") String get testUrl => throw _privateConstructorUsedError; @@ -690,6 +692,7 @@ abstract class $ConfigExtendedParamsCopyWith<$Res> { {@JsonKey(name: "is-patch") bool isPatch, @JsonKey(name: "selected-map") Map selectedMap, @JsonKey(name: "override-dns") bool overrideDns, + @JsonKey(name: "override-rule") bool overrideRule, @JsonKey(name: "test-url") String testUrl}); } @@ -712,6 +715,7 @@ class _$ConfigExtendedParamsCopyWithImpl<$Res, Object? isPatch = null, Object? selectedMap = null, Object? overrideDns = null, + Object? overrideRule = null, Object? testUrl = null, }) { return _then(_value.copyWith( @@ -727,6 +731,10 @@ class _$ConfigExtendedParamsCopyWithImpl<$Res, ? _value.overrideDns : overrideDns // ignore: cast_nullable_to_non_nullable as bool, + overrideRule: null == overrideRule + ? _value.overrideRule + : overrideRule // ignore: cast_nullable_to_non_nullable + as bool, testUrl: null == testUrl ? _value.testUrl : testUrl // ignore: cast_nullable_to_non_nullable @@ -747,6 +755,7 @@ abstract class _$$ConfigExtendedParamsImplCopyWith<$Res> {@JsonKey(name: "is-patch") bool isPatch, @JsonKey(name: "selected-map") Map selectedMap, @JsonKey(name: "override-dns") bool overrideDns, + @JsonKey(name: "override-rule") bool overrideRule, @JsonKey(name: "test-url") String testUrl}); } @@ -766,6 +775,7 @@ class __$$ConfigExtendedParamsImplCopyWithImpl<$Res> Object? isPatch = null, Object? selectedMap = null, Object? overrideDns = null, + Object? overrideRule = null, Object? testUrl = null, }) { return _then(_$ConfigExtendedParamsImpl( @@ -781,6 +791,10 @@ class __$$ConfigExtendedParamsImplCopyWithImpl<$Res> ? _value.overrideDns : overrideDns // ignore: cast_nullable_to_non_nullable as bool, + overrideRule: null == overrideRule + ? _value.overrideRule + : overrideRule // ignore: cast_nullable_to_non_nullable + as bool, testUrl: null == testUrl ? _value.testUrl : testUrl // ignore: cast_nullable_to_non_nullable @@ -797,6 +811,7 @@ class _$ConfigExtendedParamsImpl implements _ConfigExtendedParams { @JsonKey(name: "selected-map") required final Map selectedMap, @JsonKey(name: "override-dns") required this.overrideDns, + @JsonKey(name: "override-rule") required this.overrideRule, @JsonKey(name: "test-url") required this.testUrl}) : _selectedMap = selectedMap; @@ -819,12 +834,15 @@ class _$ConfigExtendedParamsImpl implements _ConfigExtendedParams { @JsonKey(name: "override-dns") final bool overrideDns; @override + @JsonKey(name: "override-rule") + final bool overrideRule; + @override @JsonKey(name: "test-url") final String testUrl; @override String toString() { - return 'ConfigExtendedParams(isPatch: $isPatch, selectedMap: $selectedMap, overrideDns: $overrideDns, testUrl: $testUrl)'; + return 'ConfigExtendedParams(isPatch: $isPatch, selectedMap: $selectedMap, overrideDns: $overrideDns, overrideRule: $overrideRule, testUrl: $testUrl)'; } @override @@ -837,13 +855,20 @@ class _$ConfigExtendedParamsImpl implements _ConfigExtendedParams { .equals(other._selectedMap, _selectedMap) && (identical(other.overrideDns, overrideDns) || other.overrideDns == overrideDns) && + (identical(other.overrideRule, overrideRule) || + other.overrideRule == overrideRule) && (identical(other.testUrl, testUrl) || other.testUrl == testUrl)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash(runtimeType, isPatch, - const DeepCollectionEquality().hash(_selectedMap), overrideDns, testUrl); + int get hashCode => Object.hash( + runtimeType, + isPatch, + const DeepCollectionEquality().hash(_selectedMap), + overrideDns, + overrideRule, + testUrl); /// Create a copy of ConfigExtendedParams /// with the given fields replaced by the non-null parameter values. @@ -869,6 +894,7 @@ abstract class _ConfigExtendedParams implements ConfigExtendedParams { @JsonKey(name: "selected-map") required final Map selectedMap, @JsonKey(name: "override-dns") required final bool overrideDns, + @JsonKey(name: "override-rule") required final bool overrideRule, @JsonKey(name: "test-url") required final String testUrl}) = _$ConfigExtendedParamsImpl; @@ -885,6 +911,9 @@ abstract class _ConfigExtendedParams implements ConfigExtendedParams { @JsonKey(name: "override-dns") bool get overrideDns; @override + @JsonKey(name: "override-rule") + bool get overrideRule; + @override @JsonKey(name: "test-url") String get testUrl; diff --git a/lib/models/generated/core.g.dart b/lib/models/generated/core.g.dart index 3dd83ad..97533d6 100644 --- a/lib/models/generated/core.g.dart +++ b/lib/models/generated/core.g.dart @@ -69,6 +69,7 @@ _$ConfigExtendedParamsImpl _$$ConfigExtendedParamsImplFromJson( isPatch: json['is-patch'] as bool, selectedMap: Map.from(json['selected-map'] as Map), overrideDns: json['override-dns'] as bool, + overrideRule: json['override-rule'] as bool, testUrl: json['test-url'] as String, ); @@ -78,6 +79,7 @@ Map _$$ConfigExtendedParamsImplToJson( 'is-patch': instance.isPatch, 'selected-map': instance.selectedMap, 'override-dns': instance.overrideDns, + 'override-rule': instance.overrideRule, 'test-url': instance.testUrl, }; diff --git a/lib/models/generated/profile.freezed.dart b/lib/models/generated/profile.freezed.dart index 0dc3aca..4919406 100644 --- a/lib/models/generated/profile.freezed.dart +++ b/lib/models/generated/profile.freezed.dart @@ -238,6 +238,7 @@ mixin _$Profile { bool get autoUpdate => throw _privateConstructorUsedError; Map get selectedMap => throw _privateConstructorUsedError; Set get unfoldSet => throw _privateConstructorUsedError; + OverrideData get overrideData => throw _privateConstructorUsedError; @JsonKey(includeToJson: false, includeFromJson: false) bool get isUpdating => throw _privateConstructorUsedError; @@ -266,9 +267,11 @@ abstract class $ProfileCopyWith<$Res> { bool autoUpdate, Map selectedMap, Set unfoldSet, + OverrideData overrideData, @JsonKey(includeToJson: false, includeFromJson: false) bool isUpdating}); $SubscriptionInfoCopyWith<$Res>? get subscriptionInfo; + $OverrideDataCopyWith<$Res> get overrideData; } /// @nodoc @@ -296,6 +299,7 @@ class _$ProfileCopyWithImpl<$Res, $Val extends Profile> Object? autoUpdate = null, Object? selectedMap = null, Object? unfoldSet = null, + Object? overrideData = null, Object? isUpdating = null, }) { return _then(_value.copyWith( @@ -339,6 +343,10 @@ class _$ProfileCopyWithImpl<$Res, $Val extends Profile> ? _value.unfoldSet : unfoldSet // ignore: cast_nullable_to_non_nullable as Set, + overrideData: null == overrideData + ? _value.overrideData + : overrideData // ignore: cast_nullable_to_non_nullable + as OverrideData, isUpdating: null == isUpdating ? _value.isUpdating : isUpdating // ignore: cast_nullable_to_non_nullable @@ -359,6 +367,16 @@ class _$ProfileCopyWithImpl<$Res, $Val extends Profile> return _then(_value.copyWith(subscriptionInfo: value) as $Val); }); } + + /// Create a copy of Profile + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $OverrideDataCopyWith<$Res> get overrideData { + return $OverrideDataCopyWith<$Res>(_value.overrideData, (value) { + return _then(_value.copyWith(overrideData: value) as $Val); + }); + } } /// @nodoc @@ -379,10 +397,13 @@ abstract class _$$ProfileImplCopyWith<$Res> implements $ProfileCopyWith<$Res> { bool autoUpdate, Map selectedMap, Set unfoldSet, + OverrideData overrideData, @JsonKey(includeToJson: false, includeFromJson: false) bool isUpdating}); @override $SubscriptionInfoCopyWith<$Res>? get subscriptionInfo; + @override + $OverrideDataCopyWith<$Res> get overrideData; } /// @nodoc @@ -408,6 +429,7 @@ class __$$ProfileImplCopyWithImpl<$Res> Object? autoUpdate = null, Object? selectedMap = null, Object? unfoldSet = null, + Object? overrideData = null, Object? isUpdating = null, }) { return _then(_$ProfileImpl( @@ -451,6 +473,10 @@ class __$$ProfileImplCopyWithImpl<$Res> ? _value._unfoldSet : unfoldSet // ignore: cast_nullable_to_non_nullable as Set, + overrideData: null == overrideData + ? _value.overrideData + : overrideData // ignore: cast_nullable_to_non_nullable + as OverrideData, isUpdating: null == isUpdating ? _value.isUpdating : isUpdating // ignore: cast_nullable_to_non_nullable @@ -473,6 +499,7 @@ class _$ProfileImpl implements _Profile { this.autoUpdate = true, final Map selectedMap = const {}, final Set unfoldSet = const {}, + this.overrideData = const OverrideData(), @JsonKey(includeToJson: false, includeFromJson: false) this.isUpdating = false}) : _selectedMap = selectedMap, @@ -517,13 +544,16 @@ class _$ProfileImpl implements _Profile { return EqualUnmodifiableSetView(_unfoldSet); } + @override + @JsonKey() + final OverrideData overrideData; @override @JsonKey(includeToJson: false, includeFromJson: false) final bool isUpdating; @override String toString() { - return 'Profile(id: $id, label: $label, currentGroupName: $currentGroupName, url: $url, lastUpdateDate: $lastUpdateDate, autoUpdateDuration: $autoUpdateDuration, subscriptionInfo: $subscriptionInfo, autoUpdate: $autoUpdate, selectedMap: $selectedMap, unfoldSet: $unfoldSet, isUpdating: $isUpdating)'; + return 'Profile(id: $id, label: $label, currentGroupName: $currentGroupName, url: $url, lastUpdateDate: $lastUpdateDate, autoUpdateDuration: $autoUpdateDuration, subscriptionInfo: $subscriptionInfo, autoUpdate: $autoUpdate, selectedMap: $selectedMap, unfoldSet: $unfoldSet, overrideData: $overrideData, isUpdating: $isUpdating)'; } @override @@ -548,6 +578,8 @@ class _$ProfileImpl implements _Profile { .equals(other._selectedMap, _selectedMap) && const DeepCollectionEquality() .equals(other._unfoldSet, _unfoldSet) && + (identical(other.overrideData, overrideData) || + other.overrideData == overrideData) && (identical(other.isUpdating, isUpdating) || other.isUpdating == isUpdating)); } @@ -566,6 +598,7 @@ class _$ProfileImpl implements _Profile { autoUpdate, const DeepCollectionEquality().hash(_selectedMap), const DeepCollectionEquality().hash(_unfoldSet), + overrideData, isUpdating); /// Create a copy of Profile @@ -596,6 +629,7 @@ abstract class _Profile implements Profile { final bool autoUpdate, final Map selectedMap, final Set unfoldSet, + final OverrideData overrideData, @JsonKey(includeToJson: false, includeFromJson: false) final bool isUpdating}) = _$ProfileImpl; @@ -622,6 +656,8 @@ abstract class _Profile implements Profile { @override Set get unfoldSet; @override + OverrideData get overrideData; + @override @JsonKey(includeToJson: false, includeFromJson: false) bool get isUpdating; @@ -632,3 +668,398 @@ abstract class _Profile implements Profile { _$$ProfileImplCopyWith<_$ProfileImpl> get copyWith => throw _privateConstructorUsedError; } + +OverrideData _$OverrideDataFromJson(Map json) { + return _OverrideData.fromJson(json); +} + +/// @nodoc +mixin _$OverrideData { + bool get enable => throw _privateConstructorUsedError; + OverrideRule get rule => throw _privateConstructorUsedError; + + /// Serializes this OverrideData to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of OverrideData + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $OverrideDataCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $OverrideDataCopyWith<$Res> { + factory $OverrideDataCopyWith( + OverrideData value, $Res Function(OverrideData) then) = + _$OverrideDataCopyWithImpl<$Res, OverrideData>; + @useResult + $Res call({bool enable, OverrideRule rule}); + + $OverrideRuleCopyWith<$Res> get rule; +} + +/// @nodoc +class _$OverrideDataCopyWithImpl<$Res, $Val extends OverrideData> + implements $OverrideDataCopyWith<$Res> { + _$OverrideDataCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of OverrideData + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? enable = null, + Object? rule = null, + }) { + return _then(_value.copyWith( + enable: null == enable + ? _value.enable + : enable // ignore: cast_nullable_to_non_nullable + as bool, + rule: null == rule + ? _value.rule + : rule // ignore: cast_nullable_to_non_nullable + as OverrideRule, + ) as $Val); + } + + /// Create a copy of OverrideData + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $OverrideRuleCopyWith<$Res> get rule { + return $OverrideRuleCopyWith<$Res>(_value.rule, (value) { + return _then(_value.copyWith(rule: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$OverrideDataImplCopyWith<$Res> + implements $OverrideDataCopyWith<$Res> { + factory _$$OverrideDataImplCopyWith( + _$OverrideDataImpl value, $Res Function(_$OverrideDataImpl) then) = + __$$OverrideDataImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({bool enable, OverrideRule rule}); + + @override + $OverrideRuleCopyWith<$Res> get rule; +} + +/// @nodoc +class __$$OverrideDataImplCopyWithImpl<$Res> + extends _$OverrideDataCopyWithImpl<$Res, _$OverrideDataImpl> + implements _$$OverrideDataImplCopyWith<$Res> { + __$$OverrideDataImplCopyWithImpl( + _$OverrideDataImpl _value, $Res Function(_$OverrideDataImpl) _then) + : super(_value, _then); + + /// Create a copy of OverrideData + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? enable = null, + Object? rule = null, + }) { + return _then(_$OverrideDataImpl( + enable: null == enable + ? _value.enable + : enable // ignore: cast_nullable_to_non_nullable + as bool, + rule: null == rule + ? _value.rule + : rule // ignore: cast_nullable_to_non_nullable + as OverrideRule, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$OverrideDataImpl implements _OverrideData { + const _$OverrideDataImpl( + {this.enable = false, this.rule = const OverrideRule()}); + + factory _$OverrideDataImpl.fromJson(Map json) => + _$$OverrideDataImplFromJson(json); + + @override + @JsonKey() + final bool enable; + @override + @JsonKey() + final OverrideRule rule; + + @override + String toString() { + return 'OverrideData(enable: $enable, rule: $rule)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$OverrideDataImpl && + (identical(other.enable, enable) || other.enable == enable) && + (identical(other.rule, rule) || other.rule == rule)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, enable, rule); + + /// Create a copy of OverrideData + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$OverrideDataImplCopyWith<_$OverrideDataImpl> get copyWith => + __$$OverrideDataImplCopyWithImpl<_$OverrideDataImpl>(this, _$identity); + + @override + Map toJson() { + return _$$OverrideDataImplToJson( + this, + ); + } +} + +abstract class _OverrideData implements OverrideData { + const factory _OverrideData({final bool enable, final OverrideRule rule}) = + _$OverrideDataImpl; + + factory _OverrideData.fromJson(Map json) = + _$OverrideDataImpl.fromJson; + + @override + bool get enable; + @override + OverrideRule get rule; + + /// Create a copy of OverrideData + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$OverrideDataImplCopyWith<_$OverrideDataImpl> get copyWith => + throw _privateConstructorUsedError; +} + +OverrideRule _$OverrideRuleFromJson(Map json) { + return _OverrideRule.fromJson(json); +} + +/// @nodoc +mixin _$OverrideRule { + OverrideRuleType get type => throw _privateConstructorUsedError; + List get overrideRules => throw _privateConstructorUsedError; + List get addedRules => throw _privateConstructorUsedError; + + /// Serializes this OverrideRule to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of OverrideRule + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $OverrideRuleCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $OverrideRuleCopyWith<$Res> { + factory $OverrideRuleCopyWith( + OverrideRule value, $Res Function(OverrideRule) then) = + _$OverrideRuleCopyWithImpl<$Res, OverrideRule>; + @useResult + $Res call( + {OverrideRuleType type, List overrideRules, List addedRules}); +} + +/// @nodoc +class _$OverrideRuleCopyWithImpl<$Res, $Val extends OverrideRule> + implements $OverrideRuleCopyWith<$Res> { + _$OverrideRuleCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of OverrideRule + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? type = null, + Object? overrideRules = null, + Object? addedRules = null, + }) { + return _then(_value.copyWith( + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as OverrideRuleType, + overrideRules: null == overrideRules + ? _value.overrideRules + : overrideRules // ignore: cast_nullable_to_non_nullable + as List, + addedRules: null == addedRules + ? _value.addedRules + : addedRules // ignore: cast_nullable_to_non_nullable + as List, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$OverrideRuleImplCopyWith<$Res> + implements $OverrideRuleCopyWith<$Res> { + factory _$$OverrideRuleImplCopyWith( + _$OverrideRuleImpl value, $Res Function(_$OverrideRuleImpl) then) = + __$$OverrideRuleImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {OverrideRuleType type, List overrideRules, List addedRules}); +} + +/// @nodoc +class __$$OverrideRuleImplCopyWithImpl<$Res> + extends _$OverrideRuleCopyWithImpl<$Res, _$OverrideRuleImpl> + implements _$$OverrideRuleImplCopyWith<$Res> { + __$$OverrideRuleImplCopyWithImpl( + _$OverrideRuleImpl _value, $Res Function(_$OverrideRuleImpl) _then) + : super(_value, _then); + + /// Create a copy of OverrideRule + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? type = null, + Object? overrideRules = null, + Object? addedRules = null, + }) { + return _then(_$OverrideRuleImpl( + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as OverrideRuleType, + overrideRules: null == overrideRules + ? _value._overrideRules + : overrideRules // ignore: cast_nullable_to_non_nullable + as List, + addedRules: null == addedRules + ? _value._addedRules + : addedRules // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$OverrideRuleImpl implements _OverrideRule { + const _$OverrideRuleImpl( + {this.type = OverrideRuleType.added, + final List overrideRules = const [], + final List addedRules = const []}) + : _overrideRules = overrideRules, + _addedRules = addedRules; + + factory _$OverrideRuleImpl.fromJson(Map json) => + _$$OverrideRuleImplFromJson(json); + + @override + @JsonKey() + final OverrideRuleType type; + final List _overrideRules; + @override + @JsonKey() + List get overrideRules { + if (_overrideRules is EqualUnmodifiableListView) return _overrideRules; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_overrideRules); + } + + final List _addedRules; + @override + @JsonKey() + List get addedRules { + if (_addedRules is EqualUnmodifiableListView) return _addedRules; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_addedRules); + } + + @override + String toString() { + return 'OverrideRule(type: $type, overrideRules: $overrideRules, addedRules: $addedRules)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$OverrideRuleImpl && + (identical(other.type, type) || other.type == type) && + const DeepCollectionEquality() + .equals(other._overrideRules, _overrideRules) && + const DeepCollectionEquality() + .equals(other._addedRules, _addedRules)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + type, + const DeepCollectionEquality().hash(_overrideRules), + const DeepCollectionEquality().hash(_addedRules)); + + /// Create a copy of OverrideRule + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$OverrideRuleImplCopyWith<_$OverrideRuleImpl> get copyWith => + __$$OverrideRuleImplCopyWithImpl<_$OverrideRuleImpl>(this, _$identity); + + @override + Map toJson() { + return _$$OverrideRuleImplToJson( + this, + ); + } +} + +abstract class _OverrideRule implements OverrideRule { + const factory _OverrideRule( + {final OverrideRuleType type, + final List overrideRules, + final List addedRules}) = _$OverrideRuleImpl; + + factory _OverrideRule.fromJson(Map json) = + _$OverrideRuleImpl.fromJson; + + @override + OverrideRuleType get type; + @override + List get overrideRules; + @override + List get addedRules; + + /// Create a copy of OverrideRule + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$OverrideRuleImplCopyWith<_$OverrideRuleImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/models/generated/profile.g.dart b/lib/models/generated/profile.g.dart index ac8f926..7b76002 100644 --- a/lib/models/generated/profile.g.dart +++ b/lib/models/generated/profile.g.dart @@ -48,6 +48,9 @@ _$ProfileImpl _$$ProfileImplFromJson(Map json) => ?.map((e) => e as String) .toSet() ?? const {}, + overrideData: json['overrideData'] == null + ? const OverrideData() + : OverrideData.fromJson(json['overrideData'] as Map), ); Map _$$ProfileImplToJson(_$ProfileImpl instance) => @@ -62,4 +65,45 @@ Map _$$ProfileImplToJson(_$ProfileImpl instance) => 'autoUpdate': instance.autoUpdate, 'selectedMap': instance.selectedMap, 'unfoldSet': instance.unfoldSet.toList(), + 'overrideData': instance.overrideData, }; + +_$OverrideDataImpl _$$OverrideDataImplFromJson(Map json) => + _$OverrideDataImpl( + enable: json['enable'] as bool? ?? false, + rule: json['rule'] == null + ? const OverrideRule() + : OverrideRule.fromJson(json['rule'] as Map), + ); + +Map _$$OverrideDataImplToJson(_$OverrideDataImpl instance) => + { + 'enable': instance.enable, + 'rule': instance.rule, + }; + +_$OverrideRuleImpl _$$OverrideRuleImplFromJson(Map json) => + _$OverrideRuleImpl( + type: $enumDecodeNullable(_$OverrideRuleTypeEnumMap, json['type']) ?? + OverrideRuleType.added, + overrideRules: (json['overrideRules'] as List?) + ?.map((e) => Rule.fromJson(e as Map)) + .toList() ?? + const [], + addedRules: (json['addedRules'] as List?) + ?.map((e) => Rule.fromJson(e as Map)) + .toList() ?? + const [], + ); + +Map _$$OverrideRuleImplToJson(_$OverrideRuleImpl instance) => + { + 'type': _$OverrideRuleTypeEnumMap[instance.type]!, + 'overrideRules': instance.overrideRules, + 'addedRules': instance.addedRules, + }; + +const _$OverrideRuleTypeEnumMap = { + OverrideRuleType.override: 'override', + OverrideRuleType.added: 'added', +}; diff --git a/lib/models/generated/selector.freezed.dart b/lib/models/generated/selector.freezed.dart index 8346e0e..9aff17d 100644 --- a/lib/models/generated/selector.freezed.dart +++ b/lib/models/generated/selector.freezed.dart @@ -161,6 +161,172 @@ abstract class _VM2 implements VM2 { throw _privateConstructorUsedError; } +/// @nodoc +mixin _$VM3 { + A get a => throw _privateConstructorUsedError; + B get b => throw _privateConstructorUsedError; + C get c => throw _privateConstructorUsedError; + + /// Create a copy of VM3 + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $VM3CopyWith> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $VM3CopyWith { + factory $VM3CopyWith(VM3 value, $Res Function(VM3) then) = + _$VM3CopyWithImpl>; + @useResult + $Res call({A a, B b, C c}); +} + +/// @nodoc +class _$VM3CopyWithImpl> + implements $VM3CopyWith { + _$VM3CopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of VM3 + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? a = freezed, + Object? b = freezed, + Object? c = freezed, + }) { + return _then(_value.copyWith( + a: freezed == a + ? _value.a + : a // ignore: cast_nullable_to_non_nullable + as A, + b: freezed == b + ? _value.b + : b // ignore: cast_nullable_to_non_nullable + as B, + c: freezed == c + ? _value.c + : c // ignore: cast_nullable_to_non_nullable + as C, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$VM3ImplCopyWith + implements $VM3CopyWith { + factory _$$VM3ImplCopyWith( + _$VM3Impl value, $Res Function(_$VM3Impl) then) = + __$$VM3ImplCopyWithImpl; + @override + @useResult + $Res call({A a, B b, C c}); +} + +/// @nodoc +class __$$VM3ImplCopyWithImpl + extends _$VM3CopyWithImpl> + implements _$$VM3ImplCopyWith { + __$$VM3ImplCopyWithImpl( + _$VM3Impl _value, $Res Function(_$VM3Impl) _then) + : super(_value, _then); + + /// Create a copy of VM3 + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? a = freezed, + Object? b = freezed, + Object? c = freezed, + }) { + return _then(_$VM3Impl( + a: freezed == a + ? _value.a + : a // ignore: cast_nullable_to_non_nullable + as A, + b: freezed == b + ? _value.b + : b // ignore: cast_nullable_to_non_nullable + as B, + c: freezed == c + ? _value.c + : c // ignore: cast_nullable_to_non_nullable + as C, + )); + } +} + +/// @nodoc + +class _$VM3Impl implements _VM3 { + const _$VM3Impl({required this.a, required this.b, required this.c}); + + @override + final A a; + @override + final B b; + @override + final C c; + + @override + String toString() { + return 'VM3<$A, $B, $C>(a: $a, b: $b, c: $c)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$VM3Impl && + const DeepCollectionEquality().equals(other.a, a) && + const DeepCollectionEquality().equals(other.b, b) && + const DeepCollectionEquality().equals(other.c, c)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(a), + const DeepCollectionEquality().hash(b), + const DeepCollectionEquality().hash(c)); + + /// Create a copy of VM3 + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$VM3ImplCopyWith> get copyWith => + __$$VM3ImplCopyWithImpl>(this, _$identity); +} + +abstract class _VM3 implements VM3 { + const factory _VM3( + {required final A a, + required final B b, + required final C c}) = _$VM3Impl; + + @override + A get a; + @override + B get b; + @override + C get c; + + /// Create a copy of VM3 + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$VM3ImplCopyWith> get copyWith => + throw _privateConstructorUsedError; +} + /// @nodoc mixin _$StartButtonSelectorState { bool get isInit => throw _privateConstructorUsedError; @@ -498,6 +664,7 @@ abstract class _ProfilesSelectorState implements ProfilesSelectorState { /// @nodoc mixin _$NetworkDetectionState { + bool get isLoading => throw _privateConstructorUsedError; bool get isTesting => throw _privateConstructorUsedError; IpInfo? get ipInfo => throw _privateConstructorUsedError; @@ -514,7 +681,7 @@ abstract class $NetworkDetectionStateCopyWith<$Res> { $Res Function(NetworkDetectionState) then) = _$NetworkDetectionStateCopyWithImpl<$Res, NetworkDetectionState>; @useResult - $Res call({bool isTesting, IpInfo? ipInfo}); + $Res call({bool isLoading, bool isTesting, IpInfo? ipInfo}); } /// @nodoc @@ -533,10 +700,15 @@ class _$NetworkDetectionStateCopyWithImpl<$Res, @pragma('vm:prefer-inline') @override $Res call({ + Object? isLoading = null, Object? isTesting = null, Object? ipInfo = freezed, }) { return _then(_value.copyWith( + isLoading: null == isLoading + ? _value.isLoading + : isLoading // ignore: cast_nullable_to_non_nullable + as bool, isTesting: null == isTesting ? _value.isTesting : isTesting // ignore: cast_nullable_to_non_nullable @@ -558,7 +730,7 @@ abstract class _$$NetworkDetectionStateImplCopyWith<$Res> __$$NetworkDetectionStateImplCopyWithImpl<$Res>; @override @useResult - $Res call({bool isTesting, IpInfo? ipInfo}); + $Res call({bool isLoading, bool isTesting, IpInfo? ipInfo}); } /// @nodoc @@ -575,10 +747,15 @@ class __$$NetworkDetectionStateImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ + Object? isLoading = null, Object? isTesting = null, Object? ipInfo = freezed, }) { return _then(_$NetworkDetectionStateImpl( + isLoading: null == isLoading + ? _value.isLoading + : isLoading // ignore: cast_nullable_to_non_nullable + as bool, isTesting: null == isTesting ? _value.isTesting : isTesting // ignore: cast_nullable_to_non_nullable @@ -595,8 +772,10 @@ class __$$NetworkDetectionStateImplCopyWithImpl<$Res> class _$NetworkDetectionStateImpl implements _NetworkDetectionState { const _$NetworkDetectionStateImpl( - {required this.isTesting, required this.ipInfo}); + {required this.isLoading, required this.isTesting, required this.ipInfo}); + @override + final bool isLoading; @override final bool isTesting; @override @@ -604,7 +783,7 @@ class _$NetworkDetectionStateImpl implements _NetworkDetectionState { @override String toString() { - return 'NetworkDetectionState(isTesting: $isTesting, ipInfo: $ipInfo)'; + return 'NetworkDetectionState(isLoading: $isLoading, isTesting: $isTesting, ipInfo: $ipInfo)'; } @override @@ -612,13 +791,15 @@ class _$NetworkDetectionStateImpl implements _NetworkDetectionState { return identical(this, other) || (other.runtimeType == runtimeType && other is _$NetworkDetectionStateImpl && + (identical(other.isLoading, isLoading) || + other.isLoading == isLoading) && (identical(other.isTesting, isTesting) || other.isTesting == isTesting) && (identical(other.ipInfo, ipInfo) || other.ipInfo == ipInfo)); } @override - int get hashCode => Object.hash(runtimeType, isTesting, ipInfo); + int get hashCode => Object.hash(runtimeType, isLoading, isTesting, ipInfo); /// Create a copy of NetworkDetectionState /// with the given fields replaced by the non-null parameter values. @@ -632,9 +813,12 @@ class _$NetworkDetectionStateImpl implements _NetworkDetectionState { abstract class _NetworkDetectionState implements NetworkDetectionState { const factory _NetworkDetectionState( - {required final bool isTesting, + {required final bool isLoading, + required final bool isTesting, required final IpInfo? ipInfo}) = _$NetworkDetectionStateImpl; + @override + bool get isLoading; @override bool get isTesting; @override @@ -3150,6 +3334,7 @@ abstract class _ProxyState implements ProxyState { mixin _$ClashConfigState { bool get overrideDns => throw _privateConstructorUsedError; ClashConfig get clashConfig => throw _privateConstructorUsedError; + OverrideData get overrideData => throw _privateConstructorUsedError; /// Create a copy of ClashConfigState /// with the given fields replaced by the non-null parameter values. @@ -3164,9 +3349,11 @@ abstract class $ClashConfigStateCopyWith<$Res> { ClashConfigState value, $Res Function(ClashConfigState) then) = _$ClashConfigStateCopyWithImpl<$Res, ClashConfigState>; @useResult - $Res call({bool overrideDns, ClashConfig clashConfig}); + $Res call( + {bool overrideDns, ClashConfig clashConfig, OverrideData overrideData}); $ClashConfigCopyWith<$Res> get clashConfig; + $OverrideDataCopyWith<$Res> get overrideData; } /// @nodoc @@ -3186,6 +3373,7 @@ class _$ClashConfigStateCopyWithImpl<$Res, $Val extends ClashConfigState> $Res call({ Object? overrideDns = null, Object? clashConfig = null, + Object? overrideData = null, }) { return _then(_value.copyWith( overrideDns: null == overrideDns @@ -3196,6 +3384,10 @@ class _$ClashConfigStateCopyWithImpl<$Res, $Val extends ClashConfigState> ? _value.clashConfig : clashConfig // ignore: cast_nullable_to_non_nullable as ClashConfig, + overrideData: null == overrideData + ? _value.overrideData + : overrideData // ignore: cast_nullable_to_non_nullable + as OverrideData, ) as $Val); } @@ -3208,6 +3400,16 @@ class _$ClashConfigStateCopyWithImpl<$Res, $Val extends ClashConfigState> return _then(_value.copyWith(clashConfig: value) as $Val); }); } + + /// Create a copy of ClashConfigState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $OverrideDataCopyWith<$Res> get overrideData { + return $OverrideDataCopyWith<$Res>(_value.overrideData, (value) { + return _then(_value.copyWith(overrideData: value) as $Val); + }); + } } /// @nodoc @@ -3218,10 +3420,13 @@ abstract class _$$ClashConfigStateImplCopyWith<$Res> __$$ClashConfigStateImplCopyWithImpl<$Res>; @override @useResult - $Res call({bool overrideDns, ClashConfig clashConfig}); + $Res call( + {bool overrideDns, ClashConfig clashConfig, OverrideData overrideData}); @override $ClashConfigCopyWith<$Res> get clashConfig; + @override + $OverrideDataCopyWith<$Res> get overrideData; } /// @nodoc @@ -3239,6 +3444,7 @@ class __$$ClashConfigStateImplCopyWithImpl<$Res> $Res call({ Object? overrideDns = null, Object? clashConfig = null, + Object? overrideData = null, }) { return _then(_$ClashConfigStateImpl( overrideDns: null == overrideDns @@ -3249,6 +3455,10 @@ class __$$ClashConfigStateImplCopyWithImpl<$Res> ? _value.clashConfig : clashConfig // ignore: cast_nullable_to_non_nullable as ClashConfig, + overrideData: null == overrideData + ? _value.overrideData + : overrideData // ignore: cast_nullable_to_non_nullable + as OverrideData, )); } } @@ -3257,16 +3467,20 @@ class __$$ClashConfigStateImplCopyWithImpl<$Res> class _$ClashConfigStateImpl implements _ClashConfigState { const _$ClashConfigStateImpl( - {required this.overrideDns, required this.clashConfig}); + {required this.overrideDns, + required this.clashConfig, + required this.overrideData}); @override final bool overrideDns; @override final ClashConfig clashConfig; + @override + final OverrideData overrideData; @override String toString() { - return 'ClashConfigState(overrideDns: $overrideDns, clashConfig: $clashConfig)'; + return 'ClashConfigState(overrideDns: $overrideDns, clashConfig: $clashConfig, overrideData: $overrideData)'; } @override @@ -3277,11 +3491,14 @@ class _$ClashConfigStateImpl implements _ClashConfigState { (identical(other.overrideDns, overrideDns) || other.overrideDns == overrideDns) && (identical(other.clashConfig, clashConfig) || - other.clashConfig == clashConfig)); + other.clashConfig == clashConfig) && + (identical(other.overrideData, overrideData) || + other.overrideData == overrideData)); } @override - int get hashCode => Object.hash(runtimeType, overrideDns, clashConfig); + int get hashCode => + Object.hash(runtimeType, overrideDns, clashConfig, overrideData); /// Create a copy of ClashConfigState /// with the given fields replaced by the non-null parameter values. @@ -3296,12 +3513,15 @@ class _$ClashConfigStateImpl implements _ClashConfigState { abstract class _ClashConfigState implements ClashConfigState { const factory _ClashConfigState( {required final bool overrideDns, - required final ClashConfig clashConfig}) = _$ClashConfigStateImpl; + required final ClashConfig clashConfig, + required final OverrideData overrideData}) = _$ClashConfigStateImpl; @override bool get overrideDns; @override ClashConfig get clashConfig; + @override + OverrideData get overrideData; /// Create a copy of ClashConfigState /// with the given fields replaced by the non-null parameter values. @@ -3780,3 +4000,246 @@ abstract class _VpnState implements VpnState { _$$VpnStateImplCopyWith<_$VpnStateImpl> get copyWith => throw _privateConstructorUsedError; } + +/// @nodoc +mixin _$ProfileOverrideStateModel { + ClashConfigSnippet? get snippet => throw _privateConstructorUsedError; + bool get isEdit => throw _privateConstructorUsedError; + Set get selectedRules => throw _privateConstructorUsedError; + OverrideData? get overrideData => throw _privateConstructorUsedError; + + /// Create a copy of ProfileOverrideStateModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProfileOverrideStateModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProfileOverrideStateModelCopyWith<$Res> { + factory $ProfileOverrideStateModelCopyWith(ProfileOverrideStateModel value, + $Res Function(ProfileOverrideStateModel) then) = + _$ProfileOverrideStateModelCopyWithImpl<$Res, ProfileOverrideStateModel>; + @useResult + $Res call( + {ClashConfigSnippet? snippet, + bool isEdit, + Set selectedRules, + OverrideData? overrideData}); + + $ClashConfigSnippetCopyWith<$Res>? get snippet; + $OverrideDataCopyWith<$Res>? get overrideData; +} + +/// @nodoc +class _$ProfileOverrideStateModelCopyWithImpl<$Res, + $Val extends ProfileOverrideStateModel> + implements $ProfileOverrideStateModelCopyWith<$Res> { + _$ProfileOverrideStateModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProfileOverrideStateModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? snippet = freezed, + Object? isEdit = null, + Object? selectedRules = null, + Object? overrideData = freezed, + }) { + return _then(_value.copyWith( + snippet: freezed == snippet + ? _value.snippet + : snippet // ignore: cast_nullable_to_non_nullable + as ClashConfigSnippet?, + isEdit: null == isEdit + ? _value.isEdit + : isEdit // ignore: cast_nullable_to_non_nullable + as bool, + selectedRules: null == selectedRules + ? _value.selectedRules + : selectedRules // ignore: cast_nullable_to_non_nullable + as Set, + overrideData: freezed == overrideData + ? _value.overrideData + : overrideData // ignore: cast_nullable_to_non_nullable + as OverrideData?, + ) as $Val); + } + + /// Create a copy of ProfileOverrideStateModel + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ClashConfigSnippetCopyWith<$Res>? get snippet { + if (_value.snippet == null) { + return null; + } + + return $ClashConfigSnippetCopyWith<$Res>(_value.snippet!, (value) { + return _then(_value.copyWith(snippet: value) as $Val); + }); + } + + /// Create a copy of ProfileOverrideStateModel + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $OverrideDataCopyWith<$Res>? get overrideData { + if (_value.overrideData == null) { + return null; + } + + return $OverrideDataCopyWith<$Res>(_value.overrideData!, (value) { + return _then(_value.copyWith(overrideData: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$ProfileOverrideStateModelImplCopyWith<$Res> + implements $ProfileOverrideStateModelCopyWith<$Res> { + factory _$$ProfileOverrideStateModelImplCopyWith( + _$ProfileOverrideStateModelImpl value, + $Res Function(_$ProfileOverrideStateModelImpl) then) = + __$$ProfileOverrideStateModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {ClashConfigSnippet? snippet, + bool isEdit, + Set selectedRules, + OverrideData? overrideData}); + + @override + $ClashConfigSnippetCopyWith<$Res>? get snippet; + @override + $OverrideDataCopyWith<$Res>? get overrideData; +} + +/// @nodoc +class __$$ProfileOverrideStateModelImplCopyWithImpl<$Res> + extends _$ProfileOverrideStateModelCopyWithImpl<$Res, + _$ProfileOverrideStateModelImpl> + implements _$$ProfileOverrideStateModelImplCopyWith<$Res> { + __$$ProfileOverrideStateModelImplCopyWithImpl( + _$ProfileOverrideStateModelImpl _value, + $Res Function(_$ProfileOverrideStateModelImpl) _then) + : super(_value, _then); + + /// Create a copy of ProfileOverrideStateModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? snippet = freezed, + Object? isEdit = null, + Object? selectedRules = null, + Object? overrideData = freezed, + }) { + return _then(_$ProfileOverrideStateModelImpl( + snippet: freezed == snippet + ? _value.snippet + : snippet // ignore: cast_nullable_to_non_nullable + as ClashConfigSnippet?, + isEdit: null == isEdit + ? _value.isEdit + : isEdit // ignore: cast_nullable_to_non_nullable + as bool, + selectedRules: null == selectedRules + ? _value._selectedRules + : selectedRules // ignore: cast_nullable_to_non_nullable + as Set, + overrideData: freezed == overrideData + ? _value.overrideData + : overrideData // ignore: cast_nullable_to_non_nullable + as OverrideData?, + )); + } +} + +/// @nodoc + +class _$ProfileOverrideStateModelImpl implements _ProfileOverrideStateModel { + const _$ProfileOverrideStateModelImpl( + {this.snippet, + required this.isEdit, + required final Set selectedRules, + this.overrideData}) + : _selectedRules = selectedRules; + + @override + final ClashConfigSnippet? snippet; + @override + final bool isEdit; + final Set _selectedRules; + @override + Set get selectedRules { + if (_selectedRules is EqualUnmodifiableSetView) return _selectedRules; + // ignore: implicit_dynamic_type + return EqualUnmodifiableSetView(_selectedRules); + } + + @override + final OverrideData? overrideData; + + @override + String toString() { + return 'ProfileOverrideStateModel(snippet: $snippet, isEdit: $isEdit, selectedRules: $selectedRules, overrideData: $overrideData)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProfileOverrideStateModelImpl && + (identical(other.snippet, snippet) || other.snippet == snippet) && + (identical(other.isEdit, isEdit) || other.isEdit == isEdit) && + const DeepCollectionEquality() + .equals(other._selectedRules, _selectedRules) && + (identical(other.overrideData, overrideData) || + other.overrideData == overrideData)); + } + + @override + int get hashCode => Object.hash(runtimeType, snippet, isEdit, + const DeepCollectionEquality().hash(_selectedRules), overrideData); + + /// Create a copy of ProfileOverrideStateModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProfileOverrideStateModelImplCopyWith<_$ProfileOverrideStateModelImpl> + get copyWith => __$$ProfileOverrideStateModelImplCopyWithImpl< + _$ProfileOverrideStateModelImpl>(this, _$identity); +} + +abstract class _ProfileOverrideStateModel implements ProfileOverrideStateModel { + const factory _ProfileOverrideStateModel( + {final ClashConfigSnippet? snippet, + required final bool isEdit, + required final Set selectedRules, + final OverrideData? overrideData}) = _$ProfileOverrideStateModelImpl; + + @override + ClashConfigSnippet? get snippet; + @override + bool get isEdit; + @override + Set get selectedRules; + @override + OverrideData? get overrideData; + + /// Create a copy of ProfileOverrideStateModel + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProfileOverrideStateModelImplCopyWith<_$ProfileOverrideStateModelImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/models/generated/widget.freezed.dart b/lib/models/generated/widget.freezed.dart index e984f42..cc373eb 100644 --- a/lib/models/generated/widget.freezed.dart +++ b/lib/models/generated/widget.freezed.dart @@ -312,121 +312,155 @@ abstract class _CommonMessage implements CommonMessage { } /// @nodoc -mixin _$CommonAppBarState { +mixin _$AppBarState { List get actions => throw _privateConstructorUsedError; - dynamic Function(String)? get onSearch => throw _privateConstructorUsedError; - bool get searching => throw _privateConstructorUsedError; + AppBarSearchState? get searchState => throw _privateConstructorUsedError; + AppBarEditState? get editState => throw _privateConstructorUsedError; - /// Create a copy of CommonAppBarState + /// Create a copy of AppBarState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) - $CommonAppBarStateCopyWith get copyWith => + $AppBarStateCopyWith get copyWith => throw _privateConstructorUsedError; } /// @nodoc -abstract class $CommonAppBarStateCopyWith<$Res> { - factory $CommonAppBarStateCopyWith( - CommonAppBarState value, $Res Function(CommonAppBarState) then) = - _$CommonAppBarStateCopyWithImpl<$Res, CommonAppBarState>; +abstract class $AppBarStateCopyWith<$Res> { + factory $AppBarStateCopyWith( + AppBarState value, $Res Function(AppBarState) then) = + _$AppBarStateCopyWithImpl<$Res, AppBarState>; @useResult $Res call( {List actions, - dynamic Function(String)? onSearch, - bool searching}); + AppBarSearchState? searchState, + AppBarEditState? editState}); + + $AppBarSearchStateCopyWith<$Res>? get searchState; + $AppBarEditStateCopyWith<$Res>? get editState; } /// @nodoc -class _$CommonAppBarStateCopyWithImpl<$Res, $Val extends CommonAppBarState> - implements $CommonAppBarStateCopyWith<$Res> { - _$CommonAppBarStateCopyWithImpl(this._value, this._then); +class _$AppBarStateCopyWithImpl<$Res, $Val extends AppBarState> + implements $AppBarStateCopyWith<$Res> { + _$AppBarStateCopyWithImpl(this._value, this._then); // ignore: unused_field final $Val _value; // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of CommonAppBarState + /// Create a copy of AppBarState /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ Object? actions = null, - Object? onSearch = freezed, - Object? searching = null, + Object? searchState = freezed, + Object? editState = freezed, }) { return _then(_value.copyWith( actions: null == actions ? _value.actions : actions // ignore: cast_nullable_to_non_nullable as List, - onSearch: freezed == onSearch - ? _value.onSearch - : onSearch // ignore: cast_nullable_to_non_nullable - as dynamic Function(String)?, - searching: null == searching - ? _value.searching - : searching // ignore: cast_nullable_to_non_nullable - as bool, + searchState: freezed == searchState + ? _value.searchState + : searchState // ignore: cast_nullable_to_non_nullable + as AppBarSearchState?, + editState: freezed == editState + ? _value.editState + : editState // ignore: cast_nullable_to_non_nullable + as AppBarEditState?, ) as $Val); } + + /// Create a copy of AppBarState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $AppBarSearchStateCopyWith<$Res>? get searchState { + if (_value.searchState == null) { + return null; + } + + return $AppBarSearchStateCopyWith<$Res>(_value.searchState!, (value) { + return _then(_value.copyWith(searchState: value) as $Val); + }); + } + + /// Create a copy of AppBarState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $AppBarEditStateCopyWith<$Res>? get editState { + if (_value.editState == null) { + return null; + } + + return $AppBarEditStateCopyWith<$Res>(_value.editState!, (value) { + return _then(_value.copyWith(editState: value) as $Val); + }); + } } /// @nodoc -abstract class _$$CommonAppBarStateImplCopyWith<$Res> - implements $CommonAppBarStateCopyWith<$Res> { - factory _$$CommonAppBarStateImplCopyWith(_$CommonAppBarStateImpl value, - $Res Function(_$CommonAppBarStateImpl) then) = - __$$CommonAppBarStateImplCopyWithImpl<$Res>; +abstract class _$$AppBarStateImplCopyWith<$Res> + implements $AppBarStateCopyWith<$Res> { + factory _$$AppBarStateImplCopyWith( + _$AppBarStateImpl value, $Res Function(_$AppBarStateImpl) then) = + __$$AppBarStateImplCopyWithImpl<$Res>; @override @useResult $Res call( {List actions, - dynamic Function(String)? onSearch, - bool searching}); + AppBarSearchState? searchState, + AppBarEditState? editState}); + + @override + $AppBarSearchStateCopyWith<$Res>? get searchState; + @override + $AppBarEditStateCopyWith<$Res>? get editState; } /// @nodoc -class __$$CommonAppBarStateImplCopyWithImpl<$Res> - extends _$CommonAppBarStateCopyWithImpl<$Res, _$CommonAppBarStateImpl> - implements _$$CommonAppBarStateImplCopyWith<$Res> { - __$$CommonAppBarStateImplCopyWithImpl(_$CommonAppBarStateImpl _value, - $Res Function(_$CommonAppBarStateImpl) _then) +class __$$AppBarStateImplCopyWithImpl<$Res> + extends _$AppBarStateCopyWithImpl<$Res, _$AppBarStateImpl> + implements _$$AppBarStateImplCopyWith<$Res> { + __$$AppBarStateImplCopyWithImpl( + _$AppBarStateImpl _value, $Res Function(_$AppBarStateImpl) _then) : super(_value, _then); - /// Create a copy of CommonAppBarState + /// Create a copy of AppBarState /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ Object? actions = null, - Object? onSearch = freezed, - Object? searching = null, + Object? searchState = freezed, + Object? editState = freezed, }) { - return _then(_$CommonAppBarStateImpl( + return _then(_$AppBarStateImpl( actions: null == actions ? _value._actions : actions // ignore: cast_nullable_to_non_nullable as List, - onSearch: freezed == onSearch - ? _value.onSearch - : onSearch // ignore: cast_nullable_to_non_nullable - as dynamic Function(String)?, - searching: null == searching - ? _value.searching - : searching // ignore: cast_nullable_to_non_nullable - as bool, + searchState: freezed == searchState + ? _value.searchState + : searchState // ignore: cast_nullable_to_non_nullable + as AppBarSearchState?, + editState: freezed == editState + ? _value.editState + : editState // ignore: cast_nullable_to_non_nullable + as AppBarEditState?, )); } } /// @nodoc -class _$CommonAppBarStateImpl implements _CommonAppBarState { - const _$CommonAppBarStateImpl( - {final List actions = const [], - this.onSearch, - this.searching = false}) +class _$AppBarStateImpl implements _AppBarState { + const _$AppBarStateImpl( + {final List actions = const [], this.searchState, this.editState}) : _actions = actions; final List _actions; @@ -439,59 +473,373 @@ class _$CommonAppBarStateImpl implements _CommonAppBarState { } @override - final dynamic Function(String)? onSearch; + final AppBarSearchState? searchState; @override - @JsonKey() - final bool searching; + final AppBarEditState? editState; @override String toString() { - return 'CommonAppBarState(actions: $actions, onSearch: $onSearch, searching: $searching)'; + return 'AppBarState(actions: $actions, searchState: $searchState, editState: $editState)'; } @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$CommonAppBarStateImpl && + other is _$AppBarStateImpl && const DeepCollectionEquality().equals(other._actions, _actions) && - (identical(other.onSearch, onSearch) || - other.onSearch == onSearch) && - (identical(other.searching, searching) || - other.searching == searching)); + (identical(other.searchState, searchState) || + other.searchState == searchState) && + (identical(other.editState, editState) || + other.editState == editState)); } @override int get hashCode => Object.hash(runtimeType, - const DeepCollectionEquality().hash(_actions), onSearch, searching); + const DeepCollectionEquality().hash(_actions), searchState, editState); - /// Create a copy of CommonAppBarState + /// Create a copy of AppBarState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$CommonAppBarStateImplCopyWith<_$CommonAppBarStateImpl> get copyWith => - __$$CommonAppBarStateImplCopyWithImpl<_$CommonAppBarStateImpl>( - this, _$identity); + _$$AppBarStateImplCopyWith<_$AppBarStateImpl> get copyWith => + __$$AppBarStateImplCopyWithImpl<_$AppBarStateImpl>(this, _$identity); } -abstract class _CommonAppBarState implements CommonAppBarState { - const factory _CommonAppBarState( +abstract class _AppBarState implements AppBarState { + const factory _AppBarState( {final List actions, - final dynamic Function(String)? onSearch, - final bool searching}) = _$CommonAppBarStateImpl; + final AppBarSearchState? searchState, + final AppBarEditState? editState}) = _$AppBarStateImpl; @override List get actions; @override - dynamic Function(String)? get onSearch; + AppBarSearchState? get searchState; @override - bool get searching; + AppBarEditState? get editState; - /// Create a copy of CommonAppBarState + /// Create a copy of AppBarState /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$CommonAppBarStateImplCopyWith<_$CommonAppBarStateImpl> get copyWith => + _$$AppBarStateImplCopyWith<_$AppBarStateImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$AppBarSearchState { + dynamic Function(String) get onSearch => throw _privateConstructorUsedError; + bool get isSearch => throw _privateConstructorUsedError; + + /// Create a copy of AppBarSearchState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $AppBarSearchStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AppBarSearchStateCopyWith<$Res> { + factory $AppBarSearchStateCopyWith( + AppBarSearchState value, $Res Function(AppBarSearchState) then) = + _$AppBarSearchStateCopyWithImpl<$Res, AppBarSearchState>; + @useResult + $Res call({dynamic Function(String) onSearch, bool isSearch}); +} + +/// @nodoc +class _$AppBarSearchStateCopyWithImpl<$Res, $Val extends AppBarSearchState> + implements $AppBarSearchStateCopyWith<$Res> { + _$AppBarSearchStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of AppBarSearchState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? onSearch = null, + Object? isSearch = null, + }) { + return _then(_value.copyWith( + onSearch: null == onSearch + ? _value.onSearch + : onSearch // ignore: cast_nullable_to_non_nullable + as dynamic Function(String), + isSearch: null == isSearch + ? _value.isSearch + : isSearch // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$AppBarSearchStateImplCopyWith<$Res> + implements $AppBarSearchStateCopyWith<$Res> { + factory _$$AppBarSearchStateImplCopyWith(_$AppBarSearchStateImpl value, + $Res Function(_$AppBarSearchStateImpl) then) = + __$$AppBarSearchStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({dynamic Function(String) onSearch, bool isSearch}); +} + +/// @nodoc +class __$$AppBarSearchStateImplCopyWithImpl<$Res> + extends _$AppBarSearchStateCopyWithImpl<$Res, _$AppBarSearchStateImpl> + implements _$$AppBarSearchStateImplCopyWith<$Res> { + __$$AppBarSearchStateImplCopyWithImpl(_$AppBarSearchStateImpl _value, + $Res Function(_$AppBarSearchStateImpl) _then) + : super(_value, _then); + + /// Create a copy of AppBarSearchState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? onSearch = null, + Object? isSearch = null, + }) { + return _then(_$AppBarSearchStateImpl( + onSearch: null == onSearch + ? _value.onSearch + : onSearch // ignore: cast_nullable_to_non_nullable + as dynamic Function(String), + isSearch: null == isSearch + ? _value.isSearch + : isSearch // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc + +class _$AppBarSearchStateImpl implements _AppBarSearchState { + const _$AppBarSearchStateImpl( + {required this.onSearch, this.isSearch = false}); + + @override + final dynamic Function(String) onSearch; + @override + @JsonKey() + final bool isSearch; + + @override + String toString() { + return 'AppBarSearchState(onSearch: $onSearch, isSearch: $isSearch)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AppBarSearchStateImpl && + (identical(other.onSearch, onSearch) || + other.onSearch == onSearch) && + (identical(other.isSearch, isSearch) || + other.isSearch == isSearch)); + } + + @override + int get hashCode => Object.hash(runtimeType, onSearch, isSearch); + + /// Create a copy of AppBarSearchState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$AppBarSearchStateImplCopyWith<_$AppBarSearchStateImpl> get copyWith => + __$$AppBarSearchStateImplCopyWithImpl<_$AppBarSearchStateImpl>( + this, _$identity); +} + +abstract class _AppBarSearchState implements AppBarSearchState { + const factory _AppBarSearchState( + {required final dynamic Function(String) onSearch, + final bool isSearch}) = _$AppBarSearchStateImpl; + + @override + dynamic Function(String) get onSearch; + @override + bool get isSearch; + + /// Create a copy of AppBarSearchState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$AppBarSearchStateImplCopyWith<_$AppBarSearchStateImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$AppBarEditState { + dynamic get editCount => throw _privateConstructorUsedError; + bool get isEdit => throw _privateConstructorUsedError; + dynamic Function() get onExit => throw _privateConstructorUsedError; + + /// Create a copy of AppBarEditState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $AppBarEditStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AppBarEditStateCopyWith<$Res> { + factory $AppBarEditStateCopyWith( + AppBarEditState value, $Res Function(AppBarEditState) then) = + _$AppBarEditStateCopyWithImpl<$Res, AppBarEditState>; + @useResult + $Res call({dynamic editCount, bool isEdit, dynamic Function() onExit}); +} + +/// @nodoc +class _$AppBarEditStateCopyWithImpl<$Res, $Val extends AppBarEditState> + implements $AppBarEditStateCopyWith<$Res> { + _$AppBarEditStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of AppBarEditState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? editCount = freezed, + Object? isEdit = null, + Object? onExit = null, + }) { + return _then(_value.copyWith( + editCount: freezed == editCount + ? _value.editCount + : editCount // ignore: cast_nullable_to_non_nullable + as dynamic, + isEdit: null == isEdit + ? _value.isEdit + : isEdit // ignore: cast_nullable_to_non_nullable + as bool, + onExit: null == onExit + ? _value.onExit + : onExit // ignore: cast_nullable_to_non_nullable + as dynamic Function(), + ) as $Val); + } +} + +/// @nodoc +abstract class _$$AppBarEditStateImplCopyWith<$Res> + implements $AppBarEditStateCopyWith<$Res> { + factory _$$AppBarEditStateImplCopyWith(_$AppBarEditStateImpl value, + $Res Function(_$AppBarEditStateImpl) then) = + __$$AppBarEditStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({dynamic editCount, bool isEdit, dynamic Function() onExit}); +} + +/// @nodoc +class __$$AppBarEditStateImplCopyWithImpl<$Res> + extends _$AppBarEditStateCopyWithImpl<$Res, _$AppBarEditStateImpl> + implements _$$AppBarEditStateImplCopyWith<$Res> { + __$$AppBarEditStateImplCopyWithImpl( + _$AppBarEditStateImpl _value, $Res Function(_$AppBarEditStateImpl) _then) + : super(_value, _then); + + /// Create a copy of AppBarEditState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? editCount = freezed, + Object? isEdit = null, + Object? onExit = null, + }) { + return _then(_$AppBarEditStateImpl( + editCount: freezed == editCount ? _value.editCount! : editCount, + isEdit: null == isEdit + ? _value.isEdit + : isEdit // ignore: cast_nullable_to_non_nullable + as bool, + onExit: null == onExit + ? _value.onExit + : onExit // ignore: cast_nullable_to_non_nullable + as dynamic Function(), + )); + } +} + +/// @nodoc + +class _$AppBarEditStateImpl implements _AppBarEditState { + const _$AppBarEditStateImpl( + {this.editCount = 0, this.isEdit = false, required this.onExit}); + + @override + @JsonKey() + final dynamic editCount; + @override + @JsonKey() + final bool isEdit; + @override + final dynamic Function() onExit; + + @override + String toString() { + return 'AppBarEditState(editCount: $editCount, isEdit: $isEdit, onExit: $onExit)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AppBarEditStateImpl && + const DeepCollectionEquality().equals(other.editCount, editCount) && + (identical(other.isEdit, isEdit) || other.isEdit == isEdit) && + (identical(other.onExit, onExit) || other.onExit == onExit)); + } + + @override + int get hashCode => Object.hash(runtimeType, + const DeepCollectionEquality().hash(editCount), isEdit, onExit); + + /// Create a copy of AppBarEditState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$AppBarEditStateImplCopyWith<_$AppBarEditStateImpl> get copyWith => + __$$AppBarEditStateImplCopyWithImpl<_$AppBarEditStateImpl>( + this, _$identity); +} + +abstract class _AppBarEditState implements AppBarEditState { + const factory _AppBarEditState( + {final dynamic editCount, + final bool isEdit, + required final dynamic Function() onExit}) = _$AppBarEditStateImpl; + + @override + dynamic get editCount; + @override + bool get isEdit; + @override + dynamic Function() get onExit; + + /// Create a copy of AppBarEditState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$AppBarEditStateImplCopyWith<_$AppBarEditStateImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/models/profile.dart b/lib/models/profile.dart index 0653c42..99a9926 100644 --- a/lib/models/profile.dart +++ b/lib/models/profile.dart @@ -8,7 +8,10 @@ import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/enum/enum.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'clash_config.dart'; + part 'generated/profile.freezed.dart'; + part 'generated/profile.g.dart'; typedef SelectedMap = Map; @@ -55,6 +58,7 @@ class Profile with _$Profile { @Default(true) bool autoUpdate, @Default({}) SelectedMap selectedMap, @Default({}) Set unfoldSet, + @Default(OverrideData()) OverrideData overrideData, @JsonKey(includeToJson: false, includeFromJson: false) @Default(false) bool isUpdating, @@ -76,6 +80,51 @@ class Profile with _$Profile { } } +@freezed +class OverrideData with _$OverrideData { + const factory OverrideData({ + @Default(false) bool enable, + @Default(OverrideRule()) OverrideRule rule, + }) = _OverrideData; + + factory OverrideData.fromJson(Map json) => + _$OverrideDataFromJson(json); +} + +extension OverrideDataExt on OverrideData { + List get runningRule { + if (!enable) { + return []; + } + return rule.rules.map((item) => item.value).toList(); + } +} + +@freezed +class OverrideRule with _$OverrideRule { + const factory OverrideRule({ + @Default(OverrideRuleType.added) OverrideRuleType type, + @Default([]) List overrideRules, + @Default([]) List addedRules, + }) = _OverrideRule; + + factory OverrideRule.fromJson(Map json) => + _$OverrideRuleFromJson(json); +} + +extension OverrideRuleExt on OverrideRule { + List get rules => switch (type == OverrideRuleType.override) { + true => overrideRules, + false => addedRules, + }; + + OverrideRule updateRules(List Function(List rules) builder) { + if (type == OverrideRuleType.added) { + return copyWith(addedRules: builder(addedRules)); + } + return copyWith(overrideRules: builder(overrideRules)); + } +} extension ProfilesExt on List { Profile? getProfile(String? profileId) { diff --git a/lib/models/selector.dart b/lib/models/selector.dart index c45f60a..384d3b4 100644 --- a/lib/models/selector.dart +++ b/lib/models/selector.dart @@ -15,6 +15,14 @@ class VM2 with _$VM2 { }) = _VM2; } +@freezed +class VM3 with _$VM3 { + const factory VM3({ + required A a, + required B b, + required C c, + }) = _VM3; +} @freezed class StartButtonSelectorState with _$StartButtonSelectorState { @@ -36,6 +44,7 @@ class ProfilesSelectorState with _$ProfilesSelectorState { @freezed class NetworkDetectionState with _$NetworkDetectionState { const factory NetworkDetectionState({ + required bool isLoading, required bool isTesting, required IpInfo? ipInfo, }) = _NetworkDetectionState; @@ -143,19 +152,18 @@ extension PackageListSelectorStateExt on PackageListSelectorState { return packages .where((item) => isFilterSystemApp ? item.isSystem == false : true) .sorted( - (a, b) { + (a, b) { return switch (sort) { AccessSortType.none => 0, - AccessSortType.name => - other.sortByChar( - other.getPinyin(a.label), - other.getPinyin(b.label), - ), + AccessSortType.name => other.sortByChar( + other.getPinyin(a.label), + other.getPinyin(b.label), + ), AccessSortType.time => b.lastUpdateTime.compareTo(a.lastUpdateTime), }; }, ).sorted( - (a, b) { + (a, b) { final isSelectA = selectedList.contains(a.packageName); final isSelectB = selectedList.contains(b.packageName); if (isSelectA && isSelectB) return 0; @@ -199,6 +207,7 @@ class ClashConfigState with _$ClashConfigState { const factory ClashConfigState({ required bool overrideDns, required ClashConfig clashConfig, + required OverrideData overrideData, }) = _ClashConfigState; } @@ -225,3 +234,13 @@ class VpnState with _$VpnState { required VpnProps vpnProps, }) = _VpnState; } + +@freezed +class ProfileOverrideStateModel with _$ProfileOverrideStateModel { + const factory ProfileOverrideStateModel({ + ClashConfigSnippet? snippet, + required bool isEdit, + required Set selectedRules, + OverrideData? overrideData, + }) = _ProfileOverrideStateModel; +} \ No newline at end of file diff --git a/lib/models/widget.dart b/lib/models/widget.dart index 52dc23a..b25bde2 100644 --- a/lib/models/widget.dart +++ b/lib/models/widget.dart @@ -20,10 +20,27 @@ class CommonMessage with _$CommonMessage { } @freezed -class CommonAppBarState with _$CommonAppBarState { - const factory CommonAppBarState({ +class AppBarState with _$AppBarState { + const factory AppBarState({ @Default([]) List actions, - Function(String)? onSearch, - @Default(false) bool searching, - }) = _CommonAppBarState; + AppBarSearchState? searchState, + AppBarEditState? editState, + }) = _AppBarState; +} + +@freezed +class AppBarSearchState with _$AppBarSearchState { + const factory AppBarSearchState({ + required Function(String) onSearch, + @Default(false) bool isSearch, + }) = _AppBarSearchState; +} + +@freezed +class AppBarEditState with _$AppBarEditState { + const factory AppBarEditState({ + @Default(0) editCount, + @Default(false) bool isEdit, + required Function() onExit, + }) = _AppBarEditState; } diff --git a/lib/pages/editor.dart b/lib/pages/editor.dart index 0b195e4..88e340b 100644 --- a/lib/pages/editor.dart +++ b/lib/pages/editor.dart @@ -1,16 +1,21 @@ import 'package:fl_clash/common/common.dart'; +import 'package:fl_clash/enum/enum.dart'; +import 'package:fl_clash/models/common.dart'; +import 'package:fl_clash/providers/app.dart'; +import 'package:fl_clash/widgets/pop_scope.dart'; +import 'package:fl_clash/widgets/popup.dart'; import 'package:fl_clash/widgets/scaffold.dart'; +import 'package:fl_clash/widgets/scroll.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:re_editor/re_editor.dart'; import 'package:re_highlight/languages/yaml.dart'; import 'package:re_highlight/styles/atom-one-light.dart'; -import '../models/common.dart'; - typedef EditingValueChangeBuilder = Widget Function(CodeLineEditingValue value); -class EditorPage extends StatefulWidget { +class EditorPage extends ConsumerStatefulWidget { final String title; final String content; final Function(BuildContext context, String text)? onSave; @@ -25,17 +30,19 @@ class EditorPage extends StatefulWidget { }); @override - State createState() => _EditorPageState(); + ConsumerState createState() => _EditorPageState(); } -class _EditorPageState extends State { +class _EditorPageState extends ConsumerState { late CodeLineEditingController _controller; + late CodeFindController _findController; final _focusNode = FocusNode(); @override void initState() { super.initState(); _controller = CodeLineEditingController.fromText(widget.content); + _findController = CodeFindController(_controller); if (system.isDesktop) { return; } @@ -65,6 +72,7 @@ class _EditorPageState extends State { @override void dispose() { + _findController.dispose(); _controller.dispose(); _focusNode.dispose(); super.dispose(); @@ -79,35 +87,26 @@ class _EditorPageState extends State { ); } + _handleSearch() { + _findController.findMode(); + } + @override Widget build(BuildContext context) { - return PopScope( - canPop: false, - onPopInvokedWithResult: (didPop, _) async { - if (didPop) return; - if (widget.onPop != null) { - final res = await widget.onPop!(context, _controller.text); - if (res && context.mounted) { - Navigator.of(context).pop(); - } - return; + final isMobileView = ref.watch(isMobileViewProvider); + return CommonPopScope( + onPop: () async { + if (widget.onPop == null) { + return true; } - Navigator.of(context).pop(); + final res = await widget.onPop!(context, _controller.text); + if (res && context.mounted) { + return true; + } + return false; }, child: CommonScaffold( actions: [ - _wrapController( - (value) => IconButton( - onPressed: _controller.canUndo ? _controller.undo : null, - icon: const Icon(Icons.undo), - ), - ), - _wrapController( - (value) => IconButton( - onPressed: _controller.canRedo ? _controller.redo : null, - icon: const Icon(Icons.redo), - ), - ), if (widget.onSave != null) _wrapController( (value) => IconButton( @@ -119,15 +118,50 @@ class _EditorPageState extends State { icon: const Icon(Icons.save_sharp), ), ), + _wrapController( + (value) => CommonPopupBox( + targetBuilder: (open) { + return IconButton( + onPressed: open, + icon: const Icon(Icons.more_vert), + ); + }, + popup: CommonPopupMenu( + items: [ + PopupMenuItemData( + icon: Icons.search, + label: appLocalizations.search, + onPressed: _handleSearch, + ), + PopupMenuItemData( + icon: Icons.undo, + label: appLocalizations.undo, + onPressed: _controller.canUndo ? _controller.undo : null, + ), + PopupMenuItemData( + icon: Icons.redo, + label: appLocalizations.redo, + onPressed: _controller.canRedo ? _controller.redo : null, + ), + ], + ), + ), + ), ], body: CodeEditor( + findController: _findController, + findBuilder: (context, controller, readOnly) => FindPanel( + controller: controller, + readOnly: readOnly, + isMobileView: isMobileView, + ), + padding: EdgeInsets.only( + right: 16, + ), focusNode: _focusNode, scrollbarBuilder: (context, child, details) { - return Scrollbar( + return CommonScrollBar( controller: details.controller, - thickness: 8, - radius: const Radius.circular(2), - interactive: true, child: child, ); }, @@ -156,6 +190,7 @@ class _EditorPageState extends State { controller: _controller, style: CodeEditorStyle( fontSize: 14, + fontFamily: FontFamily.jetBrainsMono.value, codeTheme: CodeHighlightTheme( languages: { 'yaml': CodeHighlightThemeMode( @@ -172,6 +207,247 @@ class _EditorPageState extends State { } } +const double _kDefaultFindPanelHeight = 52; + +class FindPanel extends StatelessWidget implements PreferredSizeWidget { + final CodeFindController controller; + final bool readOnly; + final bool isMobileView; + final double height; + + const FindPanel({ + super.key, + required this.controller, + required this.readOnly, + required this.isMobileView, + }) : height = (isMobileView + ? _kDefaultFindPanelHeight * 2 + : _kDefaultFindPanelHeight) + + 8; + + @override + Size get preferredSize => Size( + double.infinity, + controller.value == null ? 0 : height, + ); + + @override + Widget build(BuildContext context) { + if (controller.value == null) { + return const SizedBox( + width: 0, + height: 0, + ); + } + return Container( + padding: EdgeInsets.symmetric( + vertical: 12, + horizontal: 16, + ), + margin: EdgeInsets.only( + bottom: 8, + ), + color: context.colorScheme.surface, + alignment: Alignment.centerLeft, + height: height, + child: _buildFindInputView(context), + ); + } + + Widget _buildFindInputView(BuildContext context) { + final CodeFindValue value = controller.value!; + final String result; + if (value.result == null) { + result = appLocalizations.none; + } else { + result = '${value.result!.index + 1}/${value.result!.matches.length}'; + } + final bar = Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (!isMobileView) ...[ + ConstrainedBox( + constraints: BoxConstraints(maxWidth: 360), + child: _buildFindInput(context, value), + ), + SizedBox( + width: 12, + ), + ], + Text( + result, + style: context.textTheme.bodyMedium, + ), + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + spacing: 8, + children: [ + _buildIconButton( + onPressed: value.result == null + ? null + : () { + controller.previousMatch(); + }, + icon: Icons.arrow_upward, + ), + _buildIconButton( + onPressed: value.result == null + ? null + : () { + controller.nextMatch(); + }, + icon: Icons.arrow_downward, + ), + SizedBox( + width: 2, + ), + IconButton.filledTonal( + visualDensity: VisualDensity.compact, + onPressed: controller.close, + style: ButtonStyle( + padding: WidgetStatePropertyAll( + EdgeInsets.all(0), + ), + ), + icon: Icon( + Icons.close, + size: 16, + ), + ), + ], + ), + ), + ], + ); + if (isMobileView) { + return Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + bar, + SizedBox( + height: 4, + ), + _buildFindInput(context, value), + ], + ); + } + return bar; + } + + _buildFindInput(BuildContext context, CodeFindValue value) { + return Stack( + alignment: Alignment.center, + children: [ + _buildTextField( + context: context, + onSubmitted: () { + if (value.result == null) { + return; + } + controller.nextMatch(); + controller.findInputFocusNode.requestFocus(); + }, + controller: controller.findInputController, + focusNode: controller.findInputFocusNode, + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + spacing: 8, + children: [ + _buildCheckText( + context: context, + text: 'Aa', + isSelected: value.option.caseSensitive, + onPressed: () { + controller.toggleCaseSensitive(); + }, + ), + _buildCheckText( + context: context, + text: '.*', + isSelected: value.option.regex, + onPressed: () { + controller.toggleRegex(); + }, + ), + SizedBox( + width: 4, + ), + ], + ) + ], + ); + } + + Widget _buildTextField({ + required BuildContext context, + required TextEditingController controller, + required FocusNode focusNode, + required VoidCallback onSubmitted, + }) { + return TextField( + maxLines: 1, + focusNode: focusNode, + style: context.textTheme.bodyMedium, + decoration: InputDecoration( + border: OutlineInputBorder(), + contentPadding: EdgeInsets.symmetric( + horizontal: 12, + ), + ), + onSubmitted: (_) { + onSubmitted(); + }, + controller: controller, + ); + } + + Widget _buildCheckText({ + required BuildContext context, + required String text, + required bool isSelected, + required VoidCallback onPressed, + }) { + return SizedBox( + width: 28, + height: 28, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: isSelected + ? IconButton.filledTonal( + onPressed: onPressed, + padding: EdgeInsets.all(2), + icon: Text(text, style: context.textTheme.bodySmall), + ) + : IconButton( + onPressed: onPressed, + padding: EdgeInsets.all(2), + icon: Text( + text, + style: context.textTheme.bodySmall, + ), + ), + ), + ); + } + + Widget _buildIconButton({ + required IconData icon, + VoidCallback? onPressed, + }) { + return IconButton( + visualDensity: VisualDensity.compact, + onPressed: onPressed, + style: ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.all(0))), + icon: Icon( + icon, + size: 16, + ), + ); + } +} + class ContextMenuControllerImpl implements SelectionToolbarController { OverlayEntry? _overlayEntry; bool _isFirstRender = true; @@ -205,23 +481,23 @@ class ContextMenuControllerImpl implements SelectionToolbarController { final isNotEmpty = controller.selectedText.isNotEmpty; final isAllSelected = controller.isAllSelected; final hasSelected = controller.selectedText.isNotEmpty; - List menus = [ + List menus = [ if (isNotEmpty) - ActionItemData( + PopupMenuItemData( label: appLocalizations.copy, onPressed: controller.copy, ), - ActionItemData( + PopupMenuItemData( label: appLocalizations.paste, onPressed: controller.paste, ), if (isNotEmpty) - ActionItemData( + PopupMenuItemData( label: appLocalizations.cut, onPressed: controller.cut, ), if (hasSelected && !isAllSelected) - ActionItemData( + PopupMenuItemData( label: appLocalizations.selectAll, onPressed: controller.selectAll, ), @@ -235,7 +511,7 @@ class ContextMenuControllerImpl implements SelectionToolbarController { anchorAbove: anchors.primaryAnchor, anchorBelow: anchors.secondaryAnchor ?? Offset.zero, children: menus.asMap().entries.map( - (MapEntry entry) { + (MapEntry entry) { return TextSelectionToolbarTextButton( padding: TextSelectionToolbarTextButton.getPadding( entry.key, @@ -243,7 +519,11 @@ class ContextMenuControllerImpl implements SelectionToolbarController { ), alignment: AlignmentDirectional.centerStart, onPressed: () { - entry.value.onPressed(); + if (entry.value.onPressed == null) { + return; + } + entry.value.onPressed!(); + _removeOverLayEntry(); }, child: Text(entry.value.label), ); diff --git a/lib/pages/home.dart b/lib/pages/home.dart index 19a1ded..ad4452f 100644 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/models/models.dart'; @@ -15,7 +17,7 @@ class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { - return BackScope( + return HomeBackScope( child: Consumer( builder: (_, ref, child) { final state = ref.watch(homeStateProvider); @@ -59,57 +61,74 @@ class _HomePageView extends ConsumerStatefulWidget { } class _HomePageViewState extends ConsumerState<_HomePageView> { - _updatePageController(List navigationItems) { - final pageLabel = globalState.appState.pageLabel; - final index = navigationItems.lastIndexWhere( - (element) => element.label == pageLabel, + late PageController _pageController; + + @override + void initState() { + super.initState(); + _pageController = PageController( + initialPage: _pageIndex, + keepPage: true, ); - final pageIndex = index == -1 ? 0 : index; - if (globalState.pageController != null) { - Future.delayed(Duration(milliseconds: 200), () { - globalState.appController.toPage( - pageIndex, - hasAnimate: true, - ); - }); - } else { - globalState.pageController = PageController( - initialPage: pageIndex, - keepPage: true, + ref.listenManual(currentPageLabelProvider, (prev, next) { + if (prev != next) { + _toPage(next); + } + }); + ref.listenManual(currentNavigationsStateProvider, (prev, next) { + if (prev?.value.length != next.value.length) { + _updatePageController(); + } + }); + } + + int get _pageIndex { + final navigationItems = ref.read(currentNavigationsStateProvider).value; + return navigationItems.indexWhere( + (item) => item.label == globalState.appState.pageLabel, + ); + } + + _toPage(PageLabel pageLabel, [bool ignoreAnimateTo = false]) async { + if (!mounted) { + return; + } + final navigationItems = ref.read(currentNavigationsStateProvider).value; + final index = navigationItems.indexWhere((item) => item.label == pageLabel); + if (index == -1) { + return; + } + final isAnimateToPage = ref.read(appSettingProvider).isAnimateToPage; + final isMobile = ref.read(isMobileViewProvider); + if (isAnimateToPage && isMobile && !ignoreAnimateTo) { + await _pageController.animateToPage( + index, + duration: kTabScrollDuration, + curve: Curves.easeOut, ); + } else { + _pageController.jumpToPage(index); } } - // _handlePageChanged(PageLabel next) { - // debouncer.call(DebounceTag.pageChange, () { - // if (_prevPageLabel == next) { - // return; - // } - // if (_prevPageLabel != null) { - // final prevTabPageKey = GlobalObjectKey(_prevPageLabel!); - // if (prevTabPageKey.currentState is PageMixin) { - // (prevTabPageKey.currentState as PageMixin).onPageHidden(); - // } - // } - // final nextTabPageKey = GlobalObjectKey(next); - // if (nextTabPageKey.currentState is PageMixin) { - // (nextTabPageKey.currentState as PageMixin).onPageShow(); - // } - // _prevPageLabel = next; - // }, duration: commonDuration); - // } + _updatePageController() { + final pageLabel = globalState.appState.pageLabel; + _toPage(pageLabel, true); + } + + @override + void dispose() { + _pageController.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { final navigationItems = ref.watch(currentNavigationsStateProvider).value; - _updatePageController(navigationItems); return PageView.builder( - controller: globalState.pageController, + controller: _pageController, physics: const NeverScrollableScrollPhysics(), itemCount: navigationItems.length, - // onPageChanged: (index) { - // _handlePageChanged(navigationItems[index].label); - // }, itemBuilder: (_, index) { final navigationItem = navigationItems[index]; return KeepScope( @@ -134,26 +153,8 @@ class CommonNavigationBar extends ConsumerWidget { required this.currentIndex, }); - _updateSafeMessageOffset(BuildContext context) { - WidgetsBinding.instance.addPostFrameCallback((_) { - final size = context.size; - if (viewMode == ViewMode.mobile) { - globalState.safeMessageOffsetNotifier.value = Offset( - 0, - -(size?.height ?? 0), - ); - } else { - globalState.safeMessageOffsetNotifier.value = Offset( - size?.width ?? 0, - 0, - ); - } - }); - } - @override Widget build(BuildContext context, ref) { - _updateSafeMessageOffset(context); if (viewMode == ViewMode.mobile) { return NavigationBarTheme( data: _NavigationBarDefaultsM3(context), @@ -166,7 +167,9 @@ class CommonNavigationBar extends ConsumerWidget { ), ) .toList(), - onDestinationSelected: globalState.appController.toPage, + onDestinationSelected: (index) { + globalState.appController.toPage(navigationItems[index].label); + }, selectedIndex: currentIndex, ), ); @@ -205,7 +208,10 @@ class CommonNavigationBar extends ConsumerWidget { ), ) .toList(), - onDestinationSelected: globalState.appController.toPage, + onDestinationSelected: (index) { + globalState.appController + .toPage(navigationItems[index].label); + }, extended: false, selectedIndex: currentIndex, labelType: showLabel @@ -264,7 +270,7 @@ class _NavigationBarDefaultsM3 extends NavigationBarThemeData { return IconThemeData( size: 24.0, color: states.contains(WidgetState.disabled) - ? _colors.onSurfaceVariant.withOpacity(0.38) + ? _colors.onSurfaceVariant.opacity38 : states.contains(WidgetState.selected) ? _colors.onSecondaryContainer : _colors.onSurfaceVariant, @@ -285,10 +291,35 @@ class _NavigationBarDefaultsM3 extends NavigationBarThemeData { return style.apply( overflow: TextOverflow.ellipsis, color: states.contains(WidgetState.disabled) - ? _colors.onSurfaceVariant.withOpacity(0.38) + ? _colors.onSurfaceVariant.opacity38 : states.contains(WidgetState.selected) ? _colors.onSurface : _colors.onSurfaceVariant); }); } } + +class HomeBackScope extends StatelessWidget { + final Widget child; + + const HomeBackScope({super.key, required this.child}); + + @override + Widget build(BuildContext context) { + if (Platform.isAndroid) { + return CommonPopScope( + onPop: () async { + final canPop = Navigator.canPop(context); + if (canPop) { + Navigator.pop(context); + } else { + await globalState.appController.handleBackOrExit(); + } + return false; + }, + child: child, + ); + } + return child; + } +} diff --git a/lib/pages/scan.dart b/lib/pages/scan.dart index 114a76a..1c65c60 100644 --- a/lib/pages/scan.dart +++ b/lib/pages/scan.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:math'; +import 'package:fl_clash/common/color.dart'; import 'package:fl_clash/state.dart'; import 'package:fl_clash/widgets/activate_box.dart'; import 'package:flutter/material.dart'; @@ -188,7 +189,7 @@ class ScannerOverlay extends CustomPainter { ); final backgroundPaint = Paint() - ..color = Colors.black.withOpacity(0.5) + ..color = Colors.black.opacity50 ..style = PaintingStyle.fill ..blendMode = BlendMode.dstOut; diff --git a/lib/providers/app.dart b/lib/providers/app.dart index 25396dd..63e7762 100644 --- a/lib/providers/app.dart +++ b/lib/providers/app.dart @@ -3,6 +3,7 @@ import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/state.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'generated/app.g.dart'; @@ -182,24 +183,44 @@ class RunTime extends _$RunTime with AutoDisposeNotifierMixin { } @riverpod -class ViewWidth extends _$ViewWidth with AutoDisposeNotifierMixin { +class ViewSize extends _$ViewSize with AutoDisposeNotifierMixin { @override - double build() { - return globalState.appState.viewWidth; + Size build() { + return globalState.appState.viewSize; } @override onUpdate(value) { globalState.appState = globalState.appState.copyWith( - viewWidth: value, + viewSize: value, ); } - ViewMode get viewMode => other.getViewMode(state); + ViewMode get viewMode => other.getViewMode(state.width); bool get isMobileView => viewMode == ViewMode.mobile; } +@riverpod +double viewWidth(Ref ref) { + return ref.watch(viewSizeProvider).width; +} + +@riverpod +ViewMode viewMode(Ref ref) { + return other.getViewMode(ref.watch(viewWidthProvider)); +} + +@riverpod +bool isMobileView(Ref ref) { + return ref.watch(viewModeProvider) == ViewMode.mobile; +} + +@riverpod +double viewHeight(Ref ref) { + return ref.watch(viewSizeProvider).height; +} + @riverpod class Init extends _$Init with AutoDisposeNotifierMixin { @override diff --git a/lib/providers/config.dart b/lib/providers/config.dart index df5a244..597cfe3 100644 --- a/lib/providers/config.dart +++ b/lib/providers/config.dart @@ -141,6 +141,15 @@ class Profiles extends _$Profiles with AutoDisposeNotifierMixin { state = profilesTemp; } + updateProfile(String profileId, Profile Function(Profile profile) builder) { + final List profilesTemp = List.from(state); + final index = profilesTemp.indexWhere((element) => element.id == profileId); + if (index != -1) { + profilesTemp[index] = builder(profilesTemp[index]); + } + state = profilesTemp; + } + deleteProfileById(String id) { state = state.where((element) => element.id != id).toList(); } diff --git a/lib/providers/generated/app.g.dart b/lib/providers/generated/app.g.dart index 75d4dc2..8831640 100644 --- a/lib/providers/generated/app.g.dart +++ b/lib/providers/generated/app.g.dart @@ -6,6 +6,70 @@ part of '../app.dart'; // RiverpodGenerator // ************************************************************************** +String _$viewWidthHash() => r'a469c3414170a6616ff3264962e7f160b2edceca'; + +/// See also [viewWidth]. +@ProviderFor(viewWidth) +final viewWidthProvider = AutoDisposeProvider.internal( + viewWidth, + name: r'viewWidthProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$viewWidthHash, + dependencies: null, + allTransitiveDependencies: null, +); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef ViewWidthRef = AutoDisposeProviderRef; +String _$viewModeHash() => r'fbda5aee64803b09b1431b00650ac6e16d044743'; + +/// See also [viewMode]. +@ProviderFor(viewMode) +final viewModeProvider = AutoDisposeProvider.internal( + viewMode, + name: r'viewModeProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$viewModeHash, + dependencies: null, + allTransitiveDependencies: null, +); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef ViewModeRef = AutoDisposeProviderRef; +String _$isMobileViewHash() => r'554c9ed269a02af001e623e596622e2bb2d658e7'; + +/// See also [isMobileView]. +@ProviderFor(isMobileView) +final isMobileViewProvider = AutoDisposeProvider.internal( + isMobileView, + name: r'isMobileViewProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$isMobileViewHash, + dependencies: null, + allTransitiveDependencies: null, +); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef IsMobileViewRef = AutoDisposeProviderRef; +String _$viewHeightHash() => r'410aee5b41388226ab16737f0e85a56f7e9fe801'; + +/// See also [viewHeight]. +@ProviderFor(viewHeight) +final viewHeightProvider = AutoDisposeProvider.internal( + viewHeight, + name: r'viewHeightProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$viewHeightHash, + dependencies: null, + allTransitiveDependencies: null, +); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef ViewHeightRef = AutoDisposeProviderRef; String _$logsHash() => r'56fb8aa9d62a97b026b749d204576a7384084737'; /// See also [Logs]. @@ -139,21 +203,20 @@ final runTimeProvider = AutoDisposeNotifierProvider.internal( ); typedef _$RunTime = AutoDisposeNotifier; -String _$viewWidthHash() => r'2d492c97b26c9634ee945a8d7da1563d98b830bd'; +String _$viewSizeHash() => r'44a8ff7a1fb1a9ad278b999560bef3ce2c9fea2a'; -/// See also [ViewWidth]. -@ProviderFor(ViewWidth) -final viewWidthProvider = - AutoDisposeNotifierProvider.internal( - ViewWidth.new, - name: r'viewWidthProvider', +/// See also [ViewSize]. +@ProviderFor(ViewSize) +final viewSizeProvider = AutoDisposeNotifierProvider.internal( + ViewSize.new, + name: r'viewSizeProvider', debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') ? null : _$viewWidthHash, + const bool.fromEnvironment('dart.vm.product') ? null : _$viewSizeHash, dependencies: null, allTransitiveDependencies: null, ); -typedef _$ViewWidth = AutoDisposeNotifier; +typedef _$ViewSize = AutoDisposeNotifier; String _$initHash() => r'7d3f11c8aff7a1924c5ec8886b2cd2cbdda57c3f'; /// See also [Init]. diff --git a/lib/providers/generated/config.g.dart b/lib/providers/generated/config.g.dart index d2cb84f..b185500 100644 --- a/lib/providers/generated/config.g.dart +++ b/lib/providers/generated/config.g.dart @@ -83,7 +83,7 @@ final themeSettingProvider = ); typedef _$ThemeSetting = AutoDisposeNotifier; -String _$profilesHash() => r'c2bc502d31321274a44901b675fbbab60b4519c3'; +String _$profilesHash() => r'2023af6ceaf273df480897561565cb3be8054ded'; /// See also [Profiles]. @ProviderFor(Profiles) diff --git a/lib/providers/generated/state.g.dart b/lib/providers/generated/state.g.dart index 59f2273..8bca145 100644 --- a/lib/providers/generated/state.g.dart +++ b/lib/providers/generated/state.g.dart @@ -78,7 +78,7 @@ final coreStateProvider = AutoDisposeProvider.internal( @Deprecated('Will be removed in 3.0. Use Ref instead') // ignore: unused_element typedef CoreStateRef = AutoDisposeProviderRef; -String _$clashConfigStateHash() => r'0708f81450e740a471c65d1dd5db0ed0dc702b3c'; +String _$clashConfigStateHash() => r'848f6b2f734d99fb11ec05f73d614be415e9658f'; /// See also [clashConfigState]. @ProviderFor(clashConfigState) @@ -143,7 +143,7 @@ final vpnStateProvider = AutoDisposeProvider.internal( @Deprecated('Will be removed in 3.0. Use Ref instead') // ignore: unused_element typedef VpnStateRef = AutoDisposeProviderRef; -String _$homeStateHash() => r'4c8a996c43a705f6d089a727563c20ff87e13a97'; +String _$homeStateHash() => r'2829f5d6a8548f8a97253a5437bf5c498b17c9ba'; /// See also [homeState]. @ProviderFor(homeState) @@ -469,7 +469,7 @@ final packageListSelectorStateProvider = typedef PackageListSelectorStateRef = AutoDisposeProviderRef; String _$moreToolsSelectorStateHash() => - r'82837b45198a75af9a9ce49b9c3ef97c6f8e9f87'; + r'd27e3eceec2422ad6b6231cf52b892e63c67e365'; /// See also [moreToolsSelectorState]. @ProviderFor(moreToolsSelectorState) @@ -488,7 +488,7 @@ final moreToolsSelectorStateProvider = // ignore: unused_element typedef MoreToolsSelectorStateRef = AutoDisposeProviderRef; -String _$isCurrentPageHash() => r'562702367f009c7b324395ab0a2ad3464784be8c'; +String _$isCurrentPageHash() => r'7c300770aef90da23109d9fcfc3bf26140d8cd08'; /// See also [isCurrentPage]. @ProviderFor(isCurrentPage) @@ -1630,5 +1630,157 @@ class _GetProxyDescProviderElement extends AutoDisposeProviderElement @override Proxy get proxy => (origin as GetProxyDescProvider).proxy; } + +String _$getProfileOverrideDataHash() => + r'a17ec085f1733b63b123ac08aa7737588c048c5f'; + +/// See also [getProfileOverrideData]. +@ProviderFor(getProfileOverrideData) +const getProfileOverrideDataProvider = GetProfileOverrideDataFamily(); + +/// See also [getProfileOverrideData]. +class GetProfileOverrideDataFamily extends Family { + /// See also [getProfileOverrideData]. + const GetProfileOverrideDataFamily(); + + /// See also [getProfileOverrideData]. + GetProfileOverrideDataProvider call( + String profileId, + ) { + return GetProfileOverrideDataProvider( + profileId, + ); + } + + @override + GetProfileOverrideDataProvider getProviderOverride( + covariant GetProfileOverrideDataProvider provider, + ) { + return call( + provider.profileId, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'getProfileOverrideDataProvider'; +} + +/// See also [getProfileOverrideData]. +class GetProfileOverrideDataProvider + extends AutoDisposeProvider { + /// See also [getProfileOverrideData]. + GetProfileOverrideDataProvider( + String profileId, + ) : this._internal( + (ref) => getProfileOverrideData( + ref as GetProfileOverrideDataRef, + profileId, + ), + from: getProfileOverrideDataProvider, + name: r'getProfileOverrideDataProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$getProfileOverrideDataHash, + dependencies: GetProfileOverrideDataFamily._dependencies, + allTransitiveDependencies: + GetProfileOverrideDataFamily._allTransitiveDependencies, + profileId: profileId, + ); + + GetProfileOverrideDataProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.profileId, + }) : super.internal(); + + final String profileId; + + @override + Override overrideWith( + OverrideData? Function(GetProfileOverrideDataRef provider) create, + ) { + return ProviderOverride( + origin: this, + override: GetProfileOverrideDataProvider._internal( + (ref) => create(ref as GetProfileOverrideDataRef), + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + profileId: profileId, + ), + ); + } + + @override + AutoDisposeProviderElement createElement() { + return _GetProfileOverrideDataProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is GetProfileOverrideDataProvider && + other.profileId == profileId; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, profileId.hashCode); + + return _SystemHash.finish(hash); + } +} + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +mixin GetProfileOverrideDataRef on AutoDisposeProviderRef { + /// The parameter `profileId` of this provider. + String get profileId; +} + +class _GetProfileOverrideDataProviderElement + extends AutoDisposeProviderElement + with GetProfileOverrideDataRef { + _GetProfileOverrideDataProviderElement(super.provider); + + @override + String get profileId => (origin as GetProfileOverrideDataProvider).profileId; +} + +String _$profileOverrideStateHash() => + r'16d7c75849ed077d60553e5d2bba4ed54b307971'; + +/// See also [ProfileOverrideState]. +@ProviderFor(ProfileOverrideState) +final profileOverrideStateProvider = AutoDisposeNotifierProvider< + ProfileOverrideState, ProfileOverrideStateModel>.internal( + ProfileOverrideState.new, + name: r'profileOverrideStateProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$profileOverrideStateHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$ProfileOverrideState = AutoDisposeNotifier; // ignore_for_file: type=lint // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/providers/state.dart b/lib/providers/state.dart index baf96c6..42b5ff1 100644 --- a/lib/providers/state.dart +++ b/lib/providers/state.dart @@ -72,9 +72,13 @@ CoreState coreState(Ref ref) { ClashConfigState clashConfigState(Ref ref) { final clashConfig = ref.watch(patchClashConfigProvider); final overrideDns = ref.watch(overrideDnsProvider); + final overrideData = ref.watch(currentProfileProvider.select( + (state) => state?.overrideData, + )); return ClashConfigState( overrideDns: overrideDns, clashConfig: clashConfig, + overrideData: overrideData ?? OverrideData(), ); } @@ -148,7 +152,7 @@ VpnState vpnState(Ref ref) { HomeState homeState(Ref ref) { final pageLabel = ref.watch(currentPageLabelProvider); final navigationItems = ref.watch(currentNavigationsStateProvider).value; - final viewMode = ref.watch(viewWidthProvider.notifier).viewMode; + final viewMode = ref.watch(viewModeProvider); final locale = ref.watch(appSettingProvider).locale; return HomeState( pageLabel: pageLabel, @@ -295,7 +299,7 @@ PackageListSelectorState packageListSelectorState(Ref ref) { @riverpod MoreToolsSelectorState moreToolsSelectorState(Ref ref) { - final viewMode = ref.watch(viewWidthProvider.notifier).viewMode; + final viewMode = ref.watch(viewModeProvider); final navigationItems = ref.watch(navigationsStateProvider.select((state) { return state.value.where((element) { final isMore = element.modes.contains(NavigationItemMode.more); @@ -322,7 +326,7 @@ bool isCurrentPage( return true; } if (handler != null) { - final viewMode = ref.watch(viewWidthProvider.notifier).viewMode; + final viewMode = ref.watch(viewModeProvider); return handler(currentPageLabel, viewMode); } return false; @@ -417,6 +421,9 @@ ProxyCardState _getProxyCardState( final group = groups[index]; final currentSelectedName = group .getCurrentSelectedName(selectedMap[proxyDelayState.proxyName] ?? ''); + if (currentSelectedName.isEmpty) { + return proxyDelayState; + } return _getProxyCardState( groups, selectedMap, @@ -466,3 +473,34 @@ String getProxyDesc(Ref ref, Proxy proxy) { return "${proxy.type}(${state.proxyName.isNotEmpty ? state.proxyName : '*'})"; } } + +@riverpod +class ProfileOverrideState extends _$ProfileOverrideState { + @override + ProfileOverrideStateModel build() { + return ProfileOverrideStateModel( + isEdit: false, + selectedRules: {}, + ); + } + + updateState( + ProfileOverrideStateModel? Function(ProfileOverrideStateModel state) + builder, + ) { + final value = builder(state); + if (value == null) { + return; + } + state = value; + } +} + +@riverpod +OverrideData? getProfileOverrideData(Ref ref, String profileId) { + return ref.watch( + profilesProvider.select( + (state) => state.getProfile(profileId)?.overrideData, + ), + ); +} diff --git a/lib/state.dart b/lib/state.dart index 9d3e692..985d309 100644 --- a/lib/state.dart +++ b/lib/state.dart @@ -2,9 +2,11 @@ import 'dart:async'; import 'package:animations/animations.dart'; import 'package:fl_clash/clash/clash.dart'; +import 'package:fl_clash/common/theme.dart'; import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/l10n/l10n.dart'; import 'package:fl_clash/plugins/service.dart'; +import 'package:fl_clash/widgets/dialog.dart'; import 'package:fl_clash/widgets/scaffold.dart'; import 'package:flutter/material.dart'; import 'package:package_info_plus/package_info_plus.dart'; @@ -23,13 +25,13 @@ class GlobalState { Timer? groupsUpdateTimer; late Config config; late AppState appState; + bool isPre = true; late PackageInfo packageInfo; Function? updateCurrentDelayDebounce; - PageController? pageController; late Measure measure; + late CommonTheme theme; DateTime? startTime; UpdateTasks tasks = []; - final safeMessageOffsetNotifier = ValueNotifier(Offset.zero); final navigatorKey = GlobalKey(); late AppController appController; GlobalKey homeScaffoldKey = GlobalKey(); @@ -46,7 +48,7 @@ class GlobalState { initApp(int version) async { appState = AppState( version: version, - viewWidth: other.getScreenSize().width, + viewSize: Size.zero, requests: FixedList(1000), logs: FixedList(1000), traffics: FixedList(30), @@ -114,7 +116,7 @@ class GlobalState { } Future showMessage({ - required String title, + String? title, required InlineSpan message, String? confirmText, bool cancelable = true, @@ -122,23 +124,8 @@ class GlobalState { return await showCommonDialog( child: Builder( builder: (context) { - return AlertDialog( - title: Text(title), - content: Container( - width: 300, - constraints: const BoxConstraints(maxHeight: 200), - child: SingleChildScrollView( - child: SelectableText.rich( - TextSpan( - style: Theme.of(context).textTheme.labelLarge, - children: [message], - ), - style: const TextStyle( - overflow: TextOverflow.visible, - ), - ), - ), - ), + return CommonDialog( + title: title ?? appLocalizations.tip, actions: [ if (cancelable) TextButton( @@ -154,6 +141,21 @@ class GlobalState { child: Text(confirmText ?? appLocalizations.confirm), ) ], + child: Container( + width: 300, + constraints: const BoxConstraints(maxHeight: 200), + child: SingleChildScrollView( + child: SelectableText.rich( + TextSpan( + style: Theme.of(context).textTheme.labelLarge, + children: [message], + ), + style: const TextStyle( + overflow: TextOverflow.visible, + ), + ), + ), + ), ); }, ), @@ -171,7 +173,7 @@ class GlobalState { barrierDismissible: dismissible, ), builder: (_) => child, - filter: filter, + filter: commonFilter, ); } @@ -251,12 +253,17 @@ class GlobalState { ? defaultBypassPrivateRouteAddress : clashConfig.tun.routeAddress, ), + rule: currentProfile?.overrideData.runningRule ?? [], ), params: ConfigExtendedParams( isPatch: isPatch ?? false, selectedMap: currentProfile?.selectedMap ?? {}, overrideDns: config.overrideDns, testUrl: config.appSetting.testUrl, + overrideRule: + currentProfile?.overrideData.rule.type == OverrideRuleType.override + ? true + : false, ), ); } diff --git a/lib/widgets/back_scope.dart b/lib/widgets/back_scope.dart deleted file mode 100644 index 5f36330..0000000 --- a/lib/widgets/back_scope.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'dart:io'; -import 'package:fl_clash/state.dart'; -import 'package:flutter/widgets.dart'; - -class BackScope extends StatefulWidget { - final Widget child; - - const BackScope({super.key, required this.child}); - - @override - State createState() => _PopContainerState(); -} - -class _PopContainerState extends State { - @override - Widget build(BuildContext context) { - if (Platform.isAndroid) { - return PopScope( - canPop: false, - onPopInvokedWithResult: (_, __) async { - final canPop = Navigator.canPop(context); - if (canPop) { - Navigator.pop(context); - } else { - await globalState.appController.handleBackOrExit(); - } - }, - child: widget.child, - ); - } - return widget.child; - } -} diff --git a/lib/widgets/card.dart b/lib/widgets/card.dart index 9879140..d68c93b 100644 --- a/lib/widgets/card.dart +++ b/lib/widgets/card.dart @@ -85,14 +85,13 @@ class CommonCard extends StatelessWidget { const CommonCard({ super.key, bool? isSelected, - this.type = CommonCardType.filled, + this.type = CommonCardType.plain, this.onPressed, this.selectWidget, - this.backgroundColor, this.radius = 12, required this.child, + this.padding, this.enterAnimated = false, - this.borderSide, this.info, }) : isSelected = isSelected ?? false; @@ -101,20 +100,22 @@ class CommonCard extends StatelessWidget { final void Function()? onPressed; final Widget? selectWidget; final Widget child; + final EdgeInsets? padding; final Info? info; final CommonCardType type; final double radius; - final WidgetStateProperty? backgroundColor; - final WidgetStateProperty? borderSide; + + // final WidgetStateProperty? backgroundColor; + // final WidgetStateProperty? borderSide; BorderSide getBorderSide(BuildContext context, Set states) { final colorScheme = context.colorScheme; - // if (type == CommonCardType.filled) { - // return BorderSide.none; - // } + if (type == CommonCardType.filled) { + return BorderSide.none; + } final hoverColor = isSelected - ? colorScheme.primary.toLight - : colorScheme.primary.toLighter; + ? colorScheme.primary.opacity80 + : colorScheme.primary.opacity60; if (states.contains(WidgetState.hovered) || states.contains(WidgetState.focused) || states.contains(WidgetState.pressed)) { @@ -123,31 +124,21 @@ class CommonCard extends StatelessWidget { ); } return BorderSide( - color: isSelected ? colorScheme.primary : colorScheme.onSurface.toSoft, + color: isSelected + ? colorScheme.primary + : colorScheme.surfaceContainerHighest, ); } Color? getBackgroundColor(BuildContext context, Set states) { - final colorScheme = context.colorScheme; - switch (type) { - case CommonCardType.plain: - if (isSelected) { - return colorScheme.secondaryContainer; - } - if (states.isEmpty) { - return colorScheme.surface; - } - return Theme.of(context) - .outlinedButtonTheme - .style - ?.backgroundColor - ?.resolve(states); - case CommonCardType.filled: - if (isSelected) { - return colorScheme.secondaryContainer; - } - return colorScheme.surfaceContainer; + if (type == CommonCardType.filled) { + return context.colorScheme.surfaceContainer; } + final colorScheme = context.colorScheme; + if (isSelected) { + return colorScheme.secondaryContainer; + } + return colorScheme.surfaceContainerLow; } @override @@ -186,6 +177,7 @@ class CommonCard extends StatelessWidget { } final card = OutlinedButton( + onLongPress: null, clipBehavior: Clip.antiAlias, style: ButtonStyle( padding: const WidgetStatePropertyAll(EdgeInsets.zero), @@ -196,14 +188,12 @@ class CommonCard extends StatelessWidget { ), iconColor: WidgetStatePropertyAll(context.colorScheme.primary), iconSize: WidgetStateProperty.all(20), - backgroundColor: backgroundColor ?? - WidgetStateProperty.resolveWith( - (states) => getBackgroundColor(context, states), - ), - side: borderSide ?? - WidgetStateProperty.resolveWith( - (states) => getBorderSide(context, states), - ), + backgroundColor: WidgetStateProperty.resolveWith( + (states) => getBackgroundColor(context, states), + ), + side: WidgetStateProperty.resolveWith( + (states) => getBorderSide(context, states), + ), ), onPressed: onPressed, child: childWidget, @@ -236,3 +226,36 @@ class SelectIcon extends StatelessWidget { ); } } + +class SettingsBlock extends StatelessWidget { + final String title; + final List settings; + + const SettingsBlock({ + super.key, + required this.title, + required this.settings, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.all(8), + child: Column( + children: [ + InfoHeader( + info: Info( + label: title, + ), + ), + Card( + color: context.colorScheme.surfaceContainer, + child: Column( + children: settings, + ), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/chip.dart b/lib/widgets/chip.dart index eac7ab7..5e81db5 100644 --- a/lib/widgets/chip.dart +++ b/lib/widgets/chip.dart @@ -1,3 +1,4 @@ +import 'package:fl_clash/common/color.dart'; import 'package:fl_clash/enum/enum.dart'; import 'package:flutter/material.dart'; @@ -28,7 +29,7 @@ class CommonChip extends StatelessWidget { materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, onDeleted: onPressed ?? () {}, side: - BorderSide(color: Theme.of(context).dividerColor.withOpacity(0.2)), + BorderSide(color: Theme.of(context).dividerColor.opacity15), labelStyle: Theme.of(context).textTheme.bodyMedium, label: Text(label), ); @@ -42,7 +43,7 @@ class CommonChip extends StatelessWidget { horizontal: 4, ), onPressed: onPressed ?? () {}, - side: BorderSide(color: Theme.of(context).dividerColor.withOpacity(0.2)), + side: BorderSide(color: Theme.of(context).dividerColor.opacity15), labelStyle: Theme.of(context).textTheme.bodyMedium, label: Text(label), ); diff --git a/lib/widgets/dialog.dart b/lib/widgets/dialog.dart new file mode 100644 index 0000000..2c96937 --- /dev/null +++ b/lib/widgets/dialog.dart @@ -0,0 +1,50 @@ +import 'dart:math'; + +import 'package:fl_clash/providers/app.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class CommonDialog extends ConsumerWidget { + final String title; + final Widget? child; + final List? actions; + final EdgeInsets? padding; + final bool overrideScroll; + final Color? backgroundColor; + + const CommonDialog({ + super.key, + required this.title, + this.actions, + this.child, + this.padding, + this.overrideScroll = false, + this.backgroundColor, + }); + + @override + Widget build(BuildContext context, ref) { + final size = ref.watch(viewSizeProvider); + return AlertDialog( + title: Text(title), + actions: actions, + contentPadding: padding, + backgroundColor: backgroundColor, + content: Container( + constraints: BoxConstraints( + maxHeight: min( + size.height - 40, + 500, + ), + maxWidth: 300, + ), + width: size.width - 40, + child: !overrideScroll + ? SingleChildScrollView( + child: child, + ) + : child, + ), + ); + } +} diff --git a/lib/widgets/donut_chart.dart b/lib/widgets/donut_chart.dart index aa6793d..3e41685 100644 --- a/lib/widgets/donut_chart.dart +++ b/lib/widgets/donut_chart.dart @@ -115,11 +115,9 @@ class DonutChartPainter extends CustomPainter { List get interpolatedData { if (oldData.length != newData.length) return newData; - final interpolatedData = List.generate(newData.length, (index) { final oldValue = oldData[index].value; final newValue = newData[index].value; - final logOldValue = _logTransform(oldValue); final logNewValue = _logTransform(newValue); final interpolatedLogValue = diff --git a/lib/widgets/effect.dart b/lib/widgets/effect.dart new file mode 100644 index 0000000..5044ba0 --- /dev/null +++ b/lib/widgets/effect.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; + +class EffectGestureDetector extends StatefulWidget { + final Widget child; + final GestureLongPressCallback? onLongPress; + final GestureTapCallback? onTap; + + const EffectGestureDetector({ + super.key, + required this.child, + this.onLongPress, + this.onTap, + }); + + @override + State createState() => _EffectGestureDetectorState(); +} + +class _EffectGestureDetectorState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + double _scale = 1; + + @override + void initState() { + super.initState(); + _controller = AnimationController(vsync: this); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedScale( + scale: _scale, + duration: kThemeAnimationDuration, + curve: Curves.easeOut, + child: GestureDetector( + onLongPress: widget.onLongPress, + onLongPressStart: (_) { + setState(() { + _scale = 0.95; + }); + }, + onTap: widget.onTap, + onLongPressEnd: (_) { + setState(() { + _scale = 1; + }); + }, + child: widget.child, + ), + ); + } +} diff --git a/lib/widgets/fade_box.dart b/lib/widgets/fade_box.dart index 06ff0dc..956e17c 100644 --- a/lib/widgets/fade_box.dart +++ b/lib/widgets/fade_box.dart @@ -4,10 +4,12 @@ import 'package:flutter/material.dart'; class FadeBox extends StatelessWidget { final Widget child; + final Alignment? alignment; const FadeBox({ super.key, required this.child, + this.alignment, }); @override @@ -19,7 +21,38 @@ class FadeBox extends StatelessWidget { secondaryAnimation, ) { return Container( - alignment: Alignment.centerLeft, + alignment: alignment ?? Alignment.centerLeft, + child: FadeTransition( + opacity: animation, + child: child, + ), + ); + }, + child: child, + ); + } +} + +class FadeThroughBox extends StatelessWidget { + final Widget child; + final Alignment? alignment; + + const FadeThroughBox({ + super.key, + required this.child, + this.alignment, + }); + + @override + Widget build(BuildContext context) { + return PageTransitionSwitcher( + transitionBuilder: ( + child, + animation, + secondaryAnimation, + ) { + return Container( + alignment: alignment ?? Alignment.centerLeft, child: FadeThroughTransition( animation: animation, fillColor: Colors.transparent, diff --git a/lib/widgets/input.dart b/lib/widgets/input.dart index c55a1e1..c67897c 100644 --- a/lib/widgets/input.dart +++ b/lib/widgets/input.dart @@ -1,7 +1,8 @@ import 'package:fl_clash/common/app_localizations.dart'; -import 'package:fl_clash/common/constant.dart'; +import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/models/common.dart'; import 'package:fl_clash/state.dart'; +import 'package:fl_clash/widgets/dialog.dart'; import 'package:fl_clash/widgets/null_status.dart'; import 'package:flutter/material.dart'; @@ -9,7 +10,7 @@ import 'card.dart'; import 'float_layout.dart'; import 'list.dart'; -class OptionsDialog extends StatelessWidget { +class OptionsDialog extends StatefulWidget { final String title; final List options; final T value; @@ -23,38 +24,77 @@ class OptionsDialog extends StatelessWidget { required this.value, }); + @override + State> createState() => _OptionsDialogState(); +} + +class _OptionsDialogState extends State> { + final _defaultValue = ""; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + final context = + GlobalObjectKey(widget.value ?? _defaultValue).currentContext; + if (context != null) { + Scrollable.ensureVisible(context); + } + }); + } + @override Widget build(BuildContext context) { - return AlertDialog( - title: Text(title), - contentPadding: const EdgeInsets.symmetric( + return CommonDialog( + title: widget.title, + padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 16, ), - content: SizedBox( - width: 250, - child: Wrap( - children: [ - for (final option in options) - ListItem.radio( - delegate: RadioDelegate( - value: option, - groupValue: value, - onChanged: (T? value) { - Navigator.of(context).pop(value); - }, - ), - title: Text( - this.textBuilder(option), - ), + child: Wrap( + children: [ + for (final option in widget.options) + ListItem.radio( + key: GlobalObjectKey(option ?? _defaultValue), + delegate: RadioDelegate( + value: option, + groupValue: widget.value, + onChanged: (T? value) { + Navigator.of(context).pop(value); + }, ), - ], - ), + title: Text( + widget.textBuilder(option), + ), + ), + ], ), ); } } +class CommonCheckBox extends StatelessWidget { + final bool? value; + final ValueChanged? onChanged; + final bool isCircle; + + const CommonCheckBox({ + required this.value, + required this.onChanged, + this.isCircle = false, + super.key, + }); + + @override + Widget build(BuildContext context) { + return Checkbox( + shape: isCircle ? const CircleBorder() : null, + value: value, + onChanged: onChanged, + ); + } +} + class InputDialog extends StatefulWidget { final String title; final String value; @@ -104,28 +144,8 @@ class _InputDialogState extends State { @override Widget build(BuildContext context) { - return AlertDialog( - title: Text(title), - content: SizedBox( - width: 300, - child: Wrap( - runSpacing: 16, - children: [ - TextField( - maxLines: 1, - minLines: 1, - controller: textController, - decoration: InputDecoration( - border: const OutlineInputBorder(), - suffixText: suffixText, - ), - onSubmitted: (_) { - _handleUpdate(); - }, - ), - ], - ), - ), + return CommonDialog( + title: title, actions: [ if (widget.resetValue != null && textController.value.text != widget.resetValue) ...[ @@ -142,90 +162,93 @@ class _InputDialogState extends State { child: Text(appLocalizations.submit), ) ], + child: Wrap( + runSpacing: 16, + children: [ + TextField( + maxLines: 1, + minLines: 1, + controller: textController, + decoration: InputDecoration( + border: const OutlineInputBorder(), + suffixText: suffixText, + ), + onSubmitted: (_) { + _handleUpdate(); + }, + ), + ], + ), ); } } -class ListPage extends StatelessWidget { +class ListInputPage extends StatelessWidget { final String title; - final Iterable items; - final Key Function(T item)? keyBuilder; - final Widget Function(T item) titleBuilder; - final Widget Function(T item)? subtitleBuilder; - final Widget Function(T item)? leadingBuilder; - final String? keyLabel; + final List items; + final Widget Function(String item) titleBuilder; + final Widget Function(String item)? subtitleBuilder; + final Widget Function(String item)? leadingBuilder; final String? valueLabel; - final Function(Iterable items) onChange; + final Function(List items) onChange; - const ListPage({ + const ListInputPage({ super.key, required this.title, required this.items, - this.keyBuilder, required this.titleBuilder, required this.onChange, this.leadingBuilder, - this.keyLabel, this.valueLabel, this.subtitleBuilder, }); - bool get isMap => items is Iterable; + _handleAddOrEdit([String? item]) async { + uniqueValidator(String? value) { + final index = items.indexWhere( + (entry) { + return entry == value; + }, + ); + final current = item == value; + if (index != -1 && !current) { + return appLocalizations.valueExists; + } + return null; + } - _handleAddOrEdit([T? item]) async { - final value = await globalState.showCommonDialog( + final valueField = Field( + label: valueLabel ?? appLocalizations.value, + value: item ?? "", + validator: uniqueValidator, + ); + final value = await globalState.showCommonDialog( child: AddDialog( - keyField: isMap - ? Field( - label: this.keyLabel ?? appLocalizations.key, - value: - item == null ? "" : (item as MapEntry).key, - ) - : null, - valueField: Field( - label: this.valueLabel ?? appLocalizations.value, - value: item == null - ? "" - : isMap - ? (item as MapEntry).value - : item as String, - ), + valueField: valueField, title: title, ), ); if (value == null) return; - final entries = List.from( - items, + final index = items.indexWhere( + (entry) { + return entry == item; + }, ); + final nextItems = List.from(items); if (item != null) { - final index = entries.indexWhere( - (entry) { - if (isMap) { - return (entry as MapEntry).key == - (item as MapEntry).key; - } - return entry == item; - }, - ); - if (index != -1) { - entries[index] = value; - } + nextItems[index] = value; } else { - entries.add(value); + nextItems.add(value); } - onChange(entries); + onChange(nextItems); } - _handleDelete(T item) { - final entries = List.from( + _handleDelete(String? item) { + final entries = List.from( items, ); final index = entries.indexWhere( (entry) { - if (isMap) { - return (entry as MapEntry).key == - (item as MapEntry).key; - } return entry == item; }, ); @@ -235,89 +258,6 @@ class ListPage extends StatelessWidget { onChange(entries); } - Widget _buildList() { - final items = this.items.toList(); - if (this.keyBuilder != null) { - return ReorderableListView.builder( - padding: const EdgeInsets.only( - bottom: 16 + 64, - left: 16, - right: 16, - ), - buildDefaultDragHandles: false, - itemCount: items.length, - itemBuilder: (_, index) { - final e = items[index]; - return Padding( - key: keyBuilder!(e), - padding: const EdgeInsets.symmetric(vertical: 8), - child: ReorderableDragStartListener( - index: index, - child: CommonCard( - child: ListItem( - leading: leadingBuilder != null ? leadingBuilder!(e) : null, - title: titleBuilder(e), - subtitle: - subtitleBuilder != null ? subtitleBuilder!(e) : null, - trailing: IconButton( - icon: const Icon(Icons.delete_outline), - onPressed: () { - _handleDelete(e); - }, - ), - ), - onPressed: () { - _handleAddOrEdit(e); - }, - ), - ), - ); - }, - onReorder: (oldIndex, newIndex) { - if (oldIndex < newIndex) { - newIndex -= 1; - } - final nextItems = List.from(items); - final item = nextItems.removeAt(oldIndex); - nextItems.insert(newIndex, item); - onChange(nextItems); - }, - ); - } else { - return ListView.builder( - padding: const EdgeInsets.only( - bottom: 16 + 64, - left: 16, - right: 16, - ), - itemCount: items.length, - itemBuilder: (_, index) { - final e = items[index]; - return Padding( - key: ObjectKey(e.toString()), - padding: const EdgeInsets.symmetric(vertical: 8), - child: CommonCard( - child: ListItem( - leading: leadingBuilder != null ? leadingBuilder!(e) : null, - title: titleBuilder(e), - subtitle: subtitleBuilder != null ? subtitleBuilder!(e) : null, - trailing: IconButton( - icon: const Icon(Icons.delete_outline), - onPressed: () { - _handleDelete(e); - }, - ), - ), - onPressed: () { - _handleAddOrEdit(e); - }, - ), - ); - }, - ); - } - } - @override Widget build(BuildContext context) { return FloatLayout( @@ -331,7 +271,204 @@ class ListPage extends StatelessWidget { ), child: items.isEmpty ? NullStatus(label: appLocalizations.noData) - : _buildList(), + : ReorderableListView.builder( + padding: const EdgeInsets.only( + bottom: 16 + 64, + left: 16, + right: 16, + ), + buildDefaultDragHandles: false, + itemCount: items.length, + itemBuilder: (context, index) { + final e = items[index]; + return Padding( + key: ValueKey(e), + padding: const EdgeInsets.symmetric(vertical: 6), + child: ReorderableDragStartListener( + index: index, + child: CommonCard( + child: ListItem( + leading: + leadingBuilder != null ? leadingBuilder!(e) : null, + title: titleBuilder(e), + subtitle: subtitleBuilder != null + ? subtitleBuilder!(e) + : null, + trailing: IconButton( + icon: const Icon(Icons.delete_outline), + onPressed: () { + _handleDelete(e); + }, + ), + ), + onPressed: () { + _handleAddOrEdit(e); + }, + ), + ), + ); + }, + onReorder: (oldIndex, newIndex) { + if (oldIndex < newIndex) { + newIndex -= 1; + } + final nextItems = List.from(items); + final item = nextItems.removeAt(oldIndex); + nextItems.insert(newIndex, item); + onChange(nextItems); + }, + ), + ); + } +} + +class MapInputPage extends StatelessWidget { + final String title; + final Map map; + final Widget Function(MapEntry item) titleBuilder; + final Widget Function(MapEntry item)? subtitleBuilder; + final Widget Function(MapEntry item)? leadingBuilder; + final String? keyLabel; + final String? valueLabel; + final Function(Map items) onChange; + + const MapInputPage({ + super.key, + required this.title, + required this.map, + required this.titleBuilder, + required this.onChange, + this.leadingBuilder, + this.keyLabel, + this.valueLabel, + this.subtitleBuilder, + }); + + List> get items => + List>.from( + map.entries, + ); + + _handleAddOrEdit([MapEntry? item]) async { + uniqueValidator(String? value) { + final index = items.indexWhere( + (entry) { + return entry.key == value; + }, + ); + final current = item?.key == value; + if (index != -1 && !current) { + return appLocalizations.keyExists; + } + return null; + } + + final keyField = Field( + label: keyLabel ?? appLocalizations.key, + value: item == null ? "" : item.key, + validator: uniqueValidator, + ); + + final valueField = Field( + label: valueLabel ?? appLocalizations.value, + value: item == null ? "" : item.value, + ); + + final value = await globalState.showCommonDialog>( + child: AddDialog( + keyField: keyField, + valueField: valueField, + title: title, + ), + ); + if (value == null) return; + final index = items.indexWhere( + (entry) { + return entry.key == item?.key; + }, + ); + + final nextItems = List>.from(items); + if (item != null) { + nextItems[index] = value; + } else { + nextItems.add(value); + } + onChange(Map.fromEntries(nextItems)); + } + + _handleDelete(MapEntry item) { + final index = items.indexWhere( + (entry) { + return entry.key == item.key; + }, + ); + if (index != -1) { + items.removeAt(index); + } + onChange(Map.fromEntries(items)); + } + + @override + Widget build(BuildContext context) { + return FloatLayout( + floatingWidget: FloatWrapper( + child: FloatingActionButton( + onPressed: () async { + _handleAddOrEdit(); + }, + child: const Icon(Icons.add), + ), + ), + child: items.isEmpty + ? NullStatus(label: appLocalizations.noData) + : ReorderableListView.builder( + padding: const EdgeInsets.only( + bottom: 16 + 64, + left: 16, + right: 16, + ), + buildDefaultDragHandles: false, + itemCount: items.length, + itemBuilder: (_, index) { + final e = items[index]; + return Padding( + key: ValueKey(e.key), + padding: const EdgeInsets.symmetric(vertical: 6), + child: ReorderableDragStartListener( + index: index, + child: CommonCard( + child: ListItem( + leading: + leadingBuilder != null ? leadingBuilder!(e) : null, + title: titleBuilder(e), + subtitle: subtitleBuilder != null + ? subtitleBuilder!(e) + : null, + trailing: IconButton( + icon: const Icon(Icons.delete_outline), + onPressed: () { + _handleDelete(e); + }, + ), + ), + onPressed: () { + _handleAddOrEdit(e); + }, + ), + ), + ); + }, + onReorder: (oldIndex, newIndex) { + if (oldIndex < newIndex) { + newIndex -= 1; + } + final nextItems = List>.from(items); + final item = nextItems.removeAt(oldIndex); + nextItems.insert(newIndex, item); + onChange(Map.fromEntries(nextItems)); + }, + ), ); } } @@ -392,45 +529,31 @@ class _AddDialogState extends State { @override Widget build(BuildContext context) { - return AlertDialog( - title: Text(widget.title), - content: Form( + return CommonDialog( + title: widget.title, + actions: [ + TextButton( + onPressed: _submit, + child: Text(appLocalizations.confirm), + ) + ], + child: Form( key: _formKey, - child: SizedBox( - width: dialogCommonWidth, - child: Wrap( - runSpacing: 16, - children: [ - if (keyField != null) - TextFormField( - maxLines: 2, - minLines: 1, - controller: keyController, - decoration: InputDecoration( - border: const OutlineInputBorder(), - labelText: keyField!.label, - ), - validator: (String? value) { - if (keyField!.validator != null) { - return keyField!.validator!(value); - } - if (value == null || value.isEmpty) { - return appLocalizations.notEmpty; - } - return null; - }, - ), + child: Wrap( + runSpacing: 16, + children: [ + if (keyField != null) TextFormField( - maxLines: 3, + maxLines: 2, minLines: 1, - controller: valueController, + controller: keyController, decoration: InputDecoration( border: const OutlineInputBorder(), - labelText: valueField.label, + labelText: keyField!.label, ), validator: (String? value) { - if (valueField.validator != null) { - return valueField.validator!(value); + if (keyField!.validator != null) { + return keyField!.validator!(value); } if (value == null || value.isEmpty) { return appLocalizations.notEmpty; @@ -438,16 +561,27 @@ class _AddDialogState extends State { return null; }, ), - ], - ), + TextFormField( + maxLines: 3, + minLines: 1, + controller: valueController, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: valueField.label, + ), + validator: (String? value) { + if (valueField.validator != null) { + return valueField.validator!(value); + } + if (value == null || value.isEmpty) { + return appLocalizations.notEmpty; + } + return null; + }, + ), + ], ), ), - actions: [ - TextButton( - onPressed: _submit, - child: Text(appLocalizations.confirm), - ) - ], ); } } diff --git a/lib/widgets/line_chart.dart b/lib/widgets/line_chart.dart index 5fd3c3f..375bac2 100644 --- a/lib/widgets/line_chart.dart +++ b/lib/widgets/line_chart.dart @@ -1,4 +1,5 @@ import 'dart:ui'; +import 'package:fl_clash/common/color.dart'; import 'package:flutter/material.dart'; class Point { @@ -191,8 +192,8 @@ class LineChartPainter extends CustomPainter { begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ - color.withOpacity(0.3), - color.withOpacity(0.1), + color.opacity38, + color.opacity10, ], ); diff --git a/lib/widgets/list.dart b/lib/widgets/list.dart index a78940e..353bb0b 100644 --- a/lib/widgets/list.dart +++ b/lib/widgets/list.dart @@ -49,28 +49,16 @@ class CheckboxDelegate extends Delegate { class OpenDelegate extends Delegate { final Widget widget; final String title; - final double? extendPageWidth; - final bool isBlur; - final bool isScaffold; + final double? maxWidth; + final Widget? action; + final bool blur; const OpenDelegate({ required this.title, required this.widget, - this.extendPageWidth, - this.isBlur = true, - this.isScaffold = false, - }); -} - -class NextDelegate extends Delegate { - final Widget widget; - final String title; - final double? extendPageWidth; - - const NextDelegate({ - required this.title, - required this.widget, - this.extendPageWidth, + this.maxWidth, + this.action, + this.blur = true, }); } @@ -112,10 +100,12 @@ class ListItem extends StatelessWidget { final Widget? subtitle; final EdgeInsets padding; final ListTileTitleAlignment tileTitleAlignment; - final bool? prue; + final bool? dense; final Widget? trailing; final Delegate delegate; final double? horizontalTitleGap; + final TextStyle? titleTextStyle; + final TextStyle? subtitleTextStyle; final void Function()? onTap; const ListItem({ @@ -126,8 +116,10 @@ class ListItem extends StatelessWidget { this.padding = const EdgeInsets.symmetric(horizontal: 16), this.trailing, this.horizontalTitleGap, - this.prue, + this.dense, this.onTap, + this.titleTextStyle, + this.subtitleTextStyle, this.tileTitleAlignment = ListTileTitleAlignment.center, }) : delegate = const Delegate(); @@ -140,7 +132,9 @@ class ListItem extends StatelessWidget { this.trailing, required OpenDelegate this.delegate, this.horizontalTitleGap, - this.prue, + this.dense, + this.titleTextStyle, + this.subtitleTextStyle, this.tileTitleAlignment = ListTileTitleAlignment.center, }) : onTap = null; @@ -153,7 +147,9 @@ class ListItem extends StatelessWidget { this.trailing, required OptionsDelegate this.delegate, this.horizontalTitleGap, - this.prue, + this.dense, + this.titleTextStyle, + this.subtitleTextStyle, this.tileTitleAlignment = ListTileTitleAlignment.center, }) : onTap = null; @@ -166,20 +162,9 @@ class ListItem extends StatelessWidget { this.trailing, required InputDelegate this.delegate, this.horizontalTitleGap, - this.prue, - this.tileTitleAlignment = ListTileTitleAlignment.center, - }) : onTap = null; - - const ListItem.next({ - super.key, - required this.title, - this.subtitle, - this.leading, - this.padding = const EdgeInsets.symmetric(horizontal: 16), - this.trailing, - required NextDelegate this.delegate, - this.horizontalTitleGap, - this.prue, + this.dense, + this.titleTextStyle, + this.subtitleTextStyle, this.tileTitleAlignment = ListTileTitleAlignment.center, }) : onTap = null; @@ -191,7 +176,9 @@ class ListItem extends StatelessWidget { this.padding = const EdgeInsets.only(left: 16, right: 8), required CheckboxDelegate this.delegate, this.horizontalTitleGap, - this.prue, + this.dense, + this.titleTextStyle, + this.subtitleTextStyle, this.tileTitleAlignment = ListTileTitleAlignment.center, }) : trailing = null, onTap = null; @@ -204,7 +191,9 @@ class ListItem extends StatelessWidget { this.padding = const EdgeInsets.only(left: 16, right: 8), required SwitchDelegate this.delegate, this.horizontalTitleGap, - this.prue, + this.dense, + this.titleTextStyle, + this.subtitleTextStyle, this.tileTitleAlignment = ListTileTitleAlignment.center, }) : trailing = null, onTap = null; @@ -217,7 +206,9 @@ class ListItem extends StatelessWidget { this.padding = const EdgeInsets.only(left: 12, right: 16), required RadioDelegate this.delegate, this.horizontalTitleGap = 8, - this.prue, + this.dense, + this.titleTextStyle, + this.subtitleTextStyle, this.tileTitleAlignment = ListTileTitleAlignment.center, }) : leading = null, onTap = null; @@ -227,42 +218,11 @@ class ListItem extends StatelessWidget { Widget? trailing, Widget? leading, }) { - if (prue == true) { - final List children = []; - if (leading != null) { - children.add(leading); - children.add( - SizedBox( - width: horizontalTitleGap, - ), - ); - } - children.add( - Expanded( - child: title, - ), - ); - if (trailing != null) { - children.add( - SizedBox( - width: horizontalTitleGap, - ), - ); - children.add(trailing); - } - return InkWell( - splashFactory: NoSplash.splashFactory, - onTap: onTap, - child: Container( - constraints: BoxConstraints.expand(), - padding: padding, - child: Row( - children: children, - ), - ), - ); - } return ListTile( + key: key, + dense: dense, + titleTextStyle: titleTextStyle, + subtitleTextStyle: subtitleTextStyle, leading: leading ?? this.leading, horizontalTitleGap: horizontalTitleGap, title: title, @@ -286,13 +246,22 @@ class ListItem extends StatelessWidget { openAction() { final isMobile = globalState.appState.viewMode == ViewMode.mobile; if (!isMobile) { - showExtendPage( + showExtend( context, - body: child, - title: openDelegate.title, - extendPageWidth: openDelegate.extendPageWidth, - isBlur: openDelegate.isBlur, - isScaffold: openDelegate.isScaffold, + props: ExtendProps( + blur: openDelegate.blur, + maxWidth: openDelegate.maxWidth, + ), + builder: (_, type) { + return AdaptiveSheetScaffold( + actions: [ + if (openDelegate.action != null) openDelegate.action!, + ], + type: type, + body: child, + title: openDelegate.title, + ); + }, ); return; } @@ -345,31 +314,6 @@ class ListItem extends StatelessWidget { }, ); } - if (delegate is NextDelegate) { - final nextDelegate = delegate as NextDelegate; - return _buildListTile( - onTap: () { - final isMobile = globalState.appState.viewMode == ViewMode.mobile; - if (!isMobile) { - showExtendPage( - context, - body: nextDelegate.widget, - title: nextDelegate.title, - extendPageWidth: nextDelegate.extendPageWidth, - ); - return; - } - - BaseNavigator.push( - context, - CommonScaffold( - key: Key(nextDelegate.title), - body: nextDelegate.widget, - title: nextDelegate.title, - )); - }, - ); - } if (delegate is CheckboxDelegate) { final checkboxDelegate = delegate as CheckboxDelegate; return _buildListTile( @@ -378,7 +322,7 @@ class ListItem extends StatelessWidget { checkboxDelegate.onChanged!(!checkboxDelegate.value); } }, - trailing: Checkbox( + trailing: CommonCheckBox( value: checkboxDelegate.value, onChanged: checkboxDelegate.onChanged, ), @@ -427,42 +371,69 @@ class ListItem extends StatelessWidget { class ListHeader extends StatelessWidget { final String title; + final String? subTitle; final List actions; + final EdgeInsets? padding; + final double? space; const ListHeader({ super.key, required this.title, + this.subTitle, + this.padding, List? actions, + this.space, }) : actions = actions ?? const []; @override Widget build(BuildContext context) { return Container( alignment: Alignment.centerLeft, - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 12, - ), + padding: padding ?? + const EdgeInsets.only( + left: 16, + right: 8, + top: 24, + bottom: 8, + ), child: Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - title, - style: Theme.of(context).textTheme.labelLarge?.copyWith( - color: Theme.of(context).colorScheme.primary, - ), - ), Expanded( - flex: 1, - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.end, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - ...actions, + Text( + title, + style: Theme.of(context).textTheme.labelLarge?.copyWith( + color: Theme.of(context) + .colorScheme + .onSurfaceVariant + .opacity80, + fontWeight: FontWeight.w600, + ), + ), + if (subTitle != null) + Text( + subTitle!, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context).colorScheme.outline, + ), + ), ], ), ), + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + ...genActions( + actions, + space: space, + ), + ], + ), ], ), ); @@ -524,3 +495,132 @@ Widget generateListView(List items) { ), ); } + +class CacheItemExtentListView extends StatefulWidget { + final NullableIndexedWidgetBuilder itemBuilder; + final int itemCount; + final String Function(int index) keyBuilder; + final double Function(int index) itemExtentBuilder; + final ScrollPhysics? physics; + final bool shrinkWrap; + final bool reverse; + final ScrollController controller; + + const CacheItemExtentListView({ + super.key, + this.physics, + this.reverse = false, + this.shrinkWrap = false, + required this.itemBuilder, + required this.controller, + required this.keyBuilder, + required this.itemCount, + required this.itemExtentBuilder, + }); + + @override + State createState() => + CacheItemExtentListViewState(); +} + +class CacheItemExtentListViewState extends State { + late final FixedMap _cacheHeightMap; + + @override + void initState() { + super.initState(); + _cacheHeightMap = FixedMap(widget.itemCount); + } + + clearCache() { + _cacheHeightMap.clear(); + } + + @override + Widget build(BuildContext context) { + _cacheHeightMap.updateMaxSize(widget.itemCount); + return ListView.builder( + itemBuilder: widget.itemBuilder, + itemCount: widget.itemCount, + physics: widget.physics, + reverse: widget.reverse, + shrinkWrap: widget.shrinkWrap, + controller: widget.controller, + itemExtentBuilder: (index, __) { + final key = widget.keyBuilder(index); + if (_cacheHeightMap.containsKey(key)) { + return _cacheHeightMap.get(key); + } + return _cacheHeightMap.put(key, widget.itemExtentBuilder(index)); + }, + ); + } + + @override + void dispose() { + _cacheHeightMap.clear(); + super.dispose(); + } +} + +class CacheItemExtentSliverReorderableList extends StatefulWidget { + final IndexedWidgetBuilder itemBuilder; + final int itemCount; + final String Function(int index) keyBuilder; + final double Function(int index) itemExtentBuilder; + final ReorderCallback onReorder; + final ReorderItemProxyDecorator? proxyDecorator; + + const CacheItemExtentSliverReorderableList({ + super.key, + required this.itemBuilder, + required this.keyBuilder, + required this.itemCount, + required this.itemExtentBuilder, + required this.onReorder, + this.proxyDecorator, + }); + + @override + State createState() => + CacheItemExtentSliverReorderableListState(); +} + +class CacheItemExtentSliverReorderableListState + extends State { + late final FixedMap _cacheHeightMap; + + @override + void initState() { + super.initState(); + _cacheHeightMap = FixedMap(widget.itemCount); + } + + clearCache() { + _cacheHeightMap.clear(); + } + + @override + Widget build(BuildContext context) { + _cacheHeightMap.updateMaxSize(widget.itemCount); + return SliverReorderableList( + itemBuilder: widget.itemBuilder, + itemCount: widget.itemCount, + itemExtentBuilder: (index, __) { + final key = widget.keyBuilder(index); + if (_cacheHeightMap.containsKey(key)) { + return _cacheHeightMap.get(key); + } + return _cacheHeightMap.put(key, widget.itemExtentBuilder(index)); + }, + onReorder: widget.onReorder, + proxyDecorator: widget.proxyDecorator, + ); + } + + @override + void dispose() { + _cacheHeightMap.clear(); + super.dispose(); + } +} diff --git a/lib/widgets/open_container.dart b/lib/widgets/open_container.dart index f0dbfa2..e7ad6f8 100644 --- a/lib/widgets/open_container.dart +++ b/lib/widgets/open_container.dart @@ -82,6 +82,7 @@ class _OpenContainerState extends State> { child: GestureDetector( onTap: widget.tappable ? openContainer : null, child: Material( + color: Colors.transparent, clipBehavior: widget.clipBehavior, child: Builder( key: _closedBuilderKey, diff --git a/lib/widgets/pop_scope.dart b/lib/widgets/pop_scope.dart new file mode 100644 index 0000000..bc2dd06 --- /dev/null +++ b/lib/widgets/pop_scope.dart @@ -0,0 +1,36 @@ +import 'dart:async'; +import 'package:flutter/widgets.dart'; + +class CommonPopScope extends StatelessWidget { + final Widget child; + final FutureOr Function()? onPop; + + const CommonPopScope({ + super.key, + required this.child, + this.onPop, + }); + + @override + Widget build(BuildContext context) { + return PopScope( + canPop: onPop == null ? true : false, + onPopInvokedWithResult: onPop == null + ? null + : (didPop, __) async { + if (didPop) { + return; + } + final res = await onPop!(); + if (!context.mounted) { + return; + } + if (!res) { + return; + } + Navigator.of(context).pop(); + }, + child: child, + ); + } +} diff --git a/lib/widgets/popup.dart b/lib/widgets/popup.dart index e56f827..92e919b 100644 --- a/lib/widgets/popup.dart +++ b/lib/widgets/popup.dart @@ -40,39 +40,41 @@ class CommonPopupRoute extends PopupRoute { parent: animation, curve: Curves.easeIn, ).value; - return ValueListenableBuilder( - valueListenable: offsetNotifier, - builder: (_, value, child) { - return Align( - alignment: align, - child: CustomSingleChildLayout( - delegate: OverflowAwareLayoutDelegate( - offset: value.translate( - 48, - 12, - ), - ), - child: child, - ), - ); - }, - child: AnimatedBuilder( - animation: animation, - builder: (_, Widget? child) { - return Opacity( - opacity: 0.1 + 0.9 * animationValue, - child: Transform.scale( - alignment: align, - scale: 0.8 + 0.2 * animationValue, - child: Transform.translate( - offset: Offset(0, -10) * (1 - animationValue), - child: child!, + return SafeArea( + child: ValueListenableBuilder( + valueListenable: offsetNotifier, + builder: (_, value, child) { + return Align( + alignment: align, + child: CustomSingleChildLayout( + delegate: OverflowAwareLayoutDelegate( + offset: value.translate( + 48, + -8, + ), ), + child: child, ), ); }, - child: builder( - context, + child: AnimatedBuilder( + animation: animation, + builder: (_, Widget? child) { + return Opacity( + opacity: 0.1 + 0.9 * animationValue, + child: Transform.scale( + alignment: align, + scale: 0.8 + 0.2 * animationValue, + child: Transform.translate( + offset: Offset(0, -10) * (1 - animationValue), + child: child!, + ), + ), + ); + }, + child: builder( + context, + ), ), ), ); @@ -82,36 +84,47 @@ class CommonPopupRoute extends PopupRoute { Duration get transitionDuration => const Duration(milliseconds: 150); } +class PopupController extends ValueNotifier { + PopupController() : super(false); + + open() { + value = true; + } + + close() { + value = false; + } +} + +typedef PopupOpen = Function({ + Offset offset, +}); + class CommonPopupBox extends StatefulWidget { - final Widget target; + final Widget Function(PopupOpen open) targetBuilder; final Widget popup; const CommonPopupBox({ super.key, - required this.target, + required this.targetBuilder, required this.popup, }); @override - State createState() => CommonPopupBoxState(); + State createState() => _CommonPopupBoxState(); } -class CommonPopupBoxState extends State { - final _targetOffsetValueNotifier = ValueNotifier(Offset.zero); +class _CommonPopupBoxState extends State { + bool _isOpen = false; + final _targetOffsetValueNotifier = ValueNotifier(Offset.zero); + Offset _offset = Offset.zero; - _handleTargetOffset() { - final renderBox = context.findRenderObject() as RenderBox?; - if (renderBox == null) { - return; - } - _targetOffsetValueNotifier.value = renderBox.localToGlobal( - Offset.zero, - ); - } - - pop() { - _handleTargetOffset(); - Navigator.of(context).push( + _open({Offset offset = Offset.zero}) { + _offset = offset; + _updateOffset(); + _isOpen = true; + Navigator.of(context) + .push( CommonPopupRoute( barrierLabel: other.id, builder: (BuildContext context) { @@ -119,12 +132,38 @@ class CommonPopupBoxState extends State { }, offsetNotifier: _targetOffsetValueNotifier, ), - ); + ) + .then((_) { + _isOpen = false; + }); + } + + _updateOffset() { + final renderBox = context.findRenderObject() as RenderBox?; + if (renderBox == null) { + return; + } + final viewPadding = MediaQuery.of(context).viewPadding; + _targetOffsetValueNotifier.value = renderBox + .localToGlobal( + Offset.zero.translate(viewPadding.right, viewPadding.top), + ) + .translate( + _offset.dx, + _offset.dy, + ); } @override Widget build(BuildContext context) { - return widget.target; + return LayoutBuilder(builder: (_, __) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (_isOpen) { + _updateOffset(); + } + }); + return widget.targetBuilder(_open); + }); } } @@ -142,14 +181,14 @@ class OverflowAwareLayoutDelegate extends SingleChildLayoutDelegate { @override Offset getPositionForChild(Size size, Size childSize) { - final saveOffset = Offset(16, 16); + final safeOffset = Offset(16, 16); double x = (offset.dx - childSize.width).clamp( 0, - size.width - saveOffset.dx - childSize.width, + size.width - safeOffset.dx - childSize.width, ); double y = (offset.dy).clamp( 0, - size.height - saveOffset.dy - childSize.height, + size.height - safeOffset.dy - childSize.height, ); return Offset(x, y); } @@ -161,28 +200,41 @@ class OverflowAwareLayoutDelegate extends SingleChildLayoutDelegate { } class CommonPopupMenu extends StatelessWidget { - final List items; + final List items; + final double? minWidth; const CommonPopupMenu({ super.key, required this.items, + this.minWidth, }); Widget _popupMenuItem( BuildContext context, { - required ActionItemData item, + required PopupMenuItemData item, required int index, }) { - final isDanger = item.type == ActionType.danger; + final isDanger = item.type == PopupMenuItemType.danger; + final onPressed = item.onPressed; + final disabled = onPressed == null; final color = isDanger - ? context.colorScheme.error - : context.colorScheme.onSurfaceVariant; + ? disabled + ? context.colorScheme.error.opacity30 + : context.colorScheme.error + : disabled + ? context.colorScheme.onSurface.opacity30 + : context.colorScheme.onSurface; return InkWell( - onTap: () { - Navigator.of(context).pop(); - item.onPressed(); - }, - child: Padding( + onTap: onPressed != null + ? () { + Navigator.of(context).pop(); + onPressed(); + } + : null, + child: Container( + constraints: BoxConstraints( + minWidth: minWidth ?? 120, + ), padding: EdgeInsets.only( left: 16, right: 64, diff --git a/lib/widgets/scaffold.dart b/lib/widgets/scaffold.dart index a2ec22c..55d3440 100644 --- a/lib/widgets/scaffold.dart +++ b/lib/widgets/scaffold.dart @@ -1,31 +1,40 @@ import 'package:fl_clash/common/common.dart'; +import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/state.dart'; import 'package:fl_clash/widgets/fade_box.dart'; +import 'package:fl_clash/widgets/pop_scope.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import '../enum/enum.dart'; import 'chip.dart'; class CommonScaffold extends StatefulWidget { + final PreferredSizeWidget? appBar; final Widget body; final Widget? bottomNavigationBar; final Widget? sideNavigationBar; - final String title; + final Color? backgroundColor; + final String? title; final Widget? leading; final List? actions; final bool automaticallyImplyLeading; + final bool? centerTitle; + final AppBarEditState? appBarEditState; const CommonScaffold({ super.key, + this.appBar, required this.body, this.sideNavigationBar, + this.backgroundColor, this.bottomNavigationBar, this.leading, - required this.title, + this.title, this.actions, this.automaticallyImplyLeading = true, + this.centerTitle, + this.appBarEditState, }); CommonScaffold.open({ @@ -51,8 +60,7 @@ class CommonScaffold extends StatefulWidget { } class CommonScaffoldState extends State { - final ValueNotifier _appBarState = - ValueNotifier(CommonAppBarState()); + late final ValueNotifier _appBarState; final ValueNotifier _floatingActionButton = ValueNotifier(null); final ValueNotifier> _keywordsNotifier = ValueNotifier([]); final ValueNotifier _loading = ValueNotifier(false); @@ -67,16 +75,46 @@ class CommonScaffoldState extends State { _appBarState.value = _appBarState.value.copyWith(actions: actions); } - set onSearch(Function(String)? onSearch) { - _appBarState.value = _appBarState.value.copyWith(onSearch: onSearch); + bool get _isSearch { + return _appBarState.value.searchState?.isSearch == true; + } + + bool get _isEdit { + return _appBarState.value.editState?.isEdit == true; } set onKeywordsUpdate(Function(List)? onKeywordsUpdate) { _onKeywordsUpdate = onKeywordsUpdate; } - set _searching(bool searching) { - _appBarState.value = _appBarState.value.copyWith(searching: searching); + @override + void initState() { + super.initState(); + _appBarState = ValueNotifier( + AppBarState( + editState: widget.appBarEditState, + ), + ); + } + + updateSearchState( + AppBarSearchState? Function(AppBarSearchState? state) builder, + ) { + _appBarState.value = _appBarState.value.copyWith( + searchState: builder( + _appBarState.value.searchState, + ), + ); + } + + updateEditState( + AppBarEditState? Function(AppBarEditState? state) builder, + ) { + _appBarState.value = _appBarState.value.copyWith( + editState: builder( + _appBarState.value.editState, + ), + ); } set floatingActionButton(Widget? floatingActionButton) { @@ -131,8 +169,8 @@ class CommonScaffoldState extends State { _handleClearInput() { _textController.text = ""; - if (_appBarState.value.onSearch != null) { - _appBarState.value.onSearch!(""); + if (_appBarState.value.searchState != null) { + _appBarState.value.searchState!.onSearch(""); } } @@ -141,12 +179,20 @@ class CommonScaffoldState extends State { _handleClearInput(); return; } - _searching = false; + updateSearchState( + (state) => state?.copyWith( + isSearch: false, + ), + ); } _handleExitSearching() { _handleClearInput(); - _searching = false; + updateSearchState( + (state) => state?.copyWith( + isSearch: false, + ), + ); } @override @@ -161,11 +207,15 @@ class CommonScaffoldState extends State { void didUpdateWidget(CommonScaffold oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.title != widget.title) { - _appBarState.value = CommonAppBarState(); + _appBarState.value = AppBarState(); _floatingActionButton.value = null; _textController.text = ""; _keywordsNotifier.value = []; _onKeywordsUpdate = null; + } else if (oldWidget.appBarEditState != widget.appBarEditState) { + _appBarState.value = _appBarState.value.copyWith( + editState: widget.appBarEditState, + ); } } @@ -184,8 +234,161 @@ class CommonScaffoldState extends State { _keywordsNotifier.value = keywords; } + Widget? _buildLeading() { + if (_isEdit) { + return IconButton( + onPressed: _appBarState.value.editState?.onExit, + icon: Icon(Icons.close), + ); + } + return _isSearch + ? IconButton( + onPressed: _handleExitSearching, + icon: Icon(Icons.arrow_back), + ) + : widget.leading; + } + + Widget _buildTitle(AppBarSearchState? startState) { + return _isSearch + ? TextField( + autofocus: true, + controller: _textController, + style: context.textTheme.titleLarge, + onChanged: (value) { + if (startState != null) { + startState.onSearch(value); + } + }, + decoration: InputDecoration( + hintText: appLocalizations.search, + ), + ) + : Text( + !_isEdit + ? widget.title! + : appLocalizations.selectedCountTitle( + "${_appBarState.value.editState?.editCount ?? 0}", + ), + ); + } + + List _buildActions( + bool hasSearch, + List actions, + ) { + if (_isSearch) { + return genActions([ + IconButton( + onPressed: _handleClear, + icon: Icon(Icons.close), + ), + ]); + } + return genActions( + [ + if (hasSearch) + IconButton( + onPressed: () { + updateSearchState( + (state) => state?.copyWith( + isSearch: true, + ), + ); + }, + icon: Icon(Icons.search), + ), + ...actions + ], + ); + } + + Widget _buildAppBarWrap(Widget appBar) { + if (_isEdit) { + return CommonPopScope( + onPop: () { + if (_isEdit) { + _appBarState.value.editState?.onExit(); + return false; + } + return true; + }, + child: appBar, + ); + } + return _isSearch + ? Theme( + data: _appBarTheme(context), + child: CommonPopScope( + onPop: () { + if (_isSearch) { + _handleExitSearching(); + return false; + } + return true; + }, + child: appBar, + ), + ) + : appBar; + } + + PreferredSizeWidget _buildAppBar() { + return PreferredSize( + preferredSize: const Size.fromHeight(kToolbarHeight), + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + ValueListenableBuilder( + valueListenable: _appBarState, + builder: (_, state, __) { + return _buildAppBarWrap( + AppBar( + centerTitle: widget.centerTitle ?? false, + systemOverlayStyle: SystemUiOverlayStyle( + statusBarColor: Colors.transparent, + statusBarIconBrightness: + Theme.of(context).brightness == Brightness.dark + ? Brightness.light + : Brightness.dark, + systemNavigationBarIconBrightness: + Theme.of(context).brightness == Brightness.dark + ? Brightness.light + : Brightness.dark, + systemNavigationBarColor: widget.bottomNavigationBar != null + ? context.colorScheme.surfaceContainer + : context.colorScheme.surface, + systemNavigationBarDividerColor: Colors.transparent, + ), + automaticallyImplyLeading: widget.automaticallyImplyLeading, + leading: _buildLeading(), + title: _buildTitle(state.searchState), + actions: _buildActions( + state.searchState != null, + state.actions.isNotEmpty + ? state.actions + : widget.actions ?? [], + ), + ), + ); + }, + ), + ValueListenableBuilder( + valueListenable: _loading, + builder: (_, value, __) { + return value == true + ? const LinearProgressIndicator() + : Container(); + }, + ), + ], + ), + ); + } + @override Widget build(BuildContext context) { + assert(widget.appBar != null || widget.title != null); final body = Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -228,121 +431,9 @@ class CommonScaffoldState extends State { ], ); final scaffold = Scaffold( - appBar: PreferredSize( - preferredSize: const Size.fromHeight(kToolbarHeight), - child: Stack( - alignment: Alignment.bottomCenter, - children: [ - ValueListenableBuilder( - valueListenable: _appBarState, - builder: (_, state, __) { - final realActions = [ - if (state.onSearch != null) - IconButton( - onPressed: () { - _searching = true; - }, - icon: Icon(Icons.search), - ), - ...state.actions.isNotEmpty - ? state.actions - : widget.actions ?? [] - ]; - final appBar = AppBar( - centerTitle: false, - systemOverlayStyle: SystemUiOverlayStyle( - statusBarColor: Colors.transparent, - statusBarIconBrightness: - Theme.of(context).brightness == Brightness.dark - ? Brightness.light - : Brightness.dark, - systemNavigationBarIconBrightness: - Theme.of(context).brightness == Brightness.dark - ? Brightness.light - : Brightness.dark, - systemNavigationBarColor: widget.bottomNavigationBar != null - ? context.colorScheme.surfaceContainer - : context.colorScheme.surface, - systemNavigationBarDividerColor: Colors.transparent, - ), - automaticallyImplyLeading: widget.automaticallyImplyLeading, - leading: state.searching - ? IconButton( - onPressed: _handleExitSearching, - icon: Icon(Icons.arrow_back), - ) - : widget.leading, - title: state.searching - ? TextField( - autofocus: true, - controller: _textController, - style: context.textTheme.titleLarge, - onChanged: (value) { - if (state.onSearch != null) { - state.onSearch!(value); - } - }, - decoration: InputDecoration( - hintText: appLocalizations.search, - ), - ) - : Text(widget.title), - actions: [ - if (state.searching) - IconButton( - onPressed: _handleClear, - icon: Icon(Icons.close), - ) - else - Row( - children: [ - ...realActions.separated( - SizedBox( - width: 4, - ), - ) - ], - ), - SizedBox( - width: 8, - ) - ], - ); - return FadeBox( - child: state.searching - ? Theme( - data: _appBarTheme(context), - child: PopScope( - canPop: false, - onPopInvokedWithResult: (didPop, __) { - if (didPop) { - return; - } - if (state.searching) { - _handleExitSearching(); - return; - } - Navigator.of(context).pop(); - }, - child: appBar, - ), - ) - : appBar, - ); - }, - ), - ValueListenableBuilder( - valueListenable: _loading, - builder: (_, value, __) { - return value == true - ? const LinearProgressIndicator() - : Container(); - }, - ), - ], - ), - ), + appBar: widget.appBar ?? _buildAppBar(), body: body, + backgroundColor: widget.backgroundColor, floatingActionButton: ValueListenableBuilder( valueListenable: _floatingActionButton, builder: (_, value, __) { @@ -367,3 +458,16 @@ class CommonScaffoldState extends State { : scaffold; } } + +List genActions(List actions, {double? space}) { + return [ + ...actions.separated( + SizedBox( + width: space ?? 4, + ), + ), + SizedBox( + width: 8, + ) + ]; +} diff --git a/lib/widgets/scroll.dart b/lib/widgets/scroll.dart index fc6abc8..ea9a7e4 100644 --- a/lib/widgets/scroll.dart +++ b/lib/widgets/scroll.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; class CommonScrollBar extends StatelessWidget { - final ScrollController controller; + final ScrollController? controller; final Widget child; const CommonScrollBar({ @@ -23,3 +23,25 @@ class CommonScrollBar extends StatelessWidget { ); } } + +class CommonAutoHiddenScrollBar extends StatelessWidget { + final ScrollController? controller; + final Widget child; + + const CommonAutoHiddenScrollBar({ + super.key, + required this.child, + required this.controller, + }); + + @override + Widget build(BuildContext context) { + return Scrollbar( + controller: controller, + thickness: 8, + radius: const Radius.circular(8), + interactive: true, + child: child, + ); + } +} diff --git a/lib/widgets/sheet.dart b/lib/widgets/sheet.dart index 5abeb70..02c21d0 100644 --- a/lib/widgets/sheet.dart +++ b/lib/widgets/sheet.dart @@ -2,99 +2,184 @@ import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/enum/enum.dart'; import 'package:fl_clash/models/models.dart'; import 'package:fl_clash/state.dart'; -import 'package:fl_clash/widgets/scaffold.dart'; import 'package:flutter/material.dart'; +import 'scaffold.dart'; import 'side_sheet.dart'; -showExtendPage( - BuildContext context, { - required Widget body, - required String title, - double? extendPageWidth, - bool isScaffold = false, - bool isBlur = true, - Widget? action, -}) { - final NavigatorState navigator = Navigator.of(context); - final globalKey = GlobalKey(); - final uniqueBody = Container( - key: globalKey, - child: body, - ); - final isMobile = globalState.appState.viewMode == ViewMode.mobile; - if (isMobile) { - BaseNavigator.push( - context, - CommonScaffold( - title: title, - body: uniqueBody, - ), - ); - return; - } - final isNotSide = isMobile || isScaffold; - navigator.push( - ModalSideSheetRoute( - modalBarrierColor: Colors.black38, - builder: (context) { - final commonScaffold = CommonScaffold( - automaticallyImplyLeading: isNotSide, - actions: isNotSide - ? null - : [ - const SizedBox( - height: kToolbarHeight, - width: kToolbarHeight, - child: CloseButton(), - ), - ], - title: title, - body: uniqueBody, - ); - return SizedBox( - width: isMobile ? context.viewWidth : extendPageWidth ?? 300, - child: commonScaffold, - ); - }, - constraints: const BoxConstraints(), - filter: isBlur ? filter : null, - ), - ); +@immutable +class SheetProps { + final double? maxWidth; + final double? maxHeight; + final bool isScrollControlled; + final bool useSafeArea; + final bool blur; + + const SheetProps({ + this.maxWidth, + this.maxHeight, + this.useSafeArea = true, + this.isScrollControlled = false, + this.blur = true, + }); } +@immutable +class ExtendProps { + final double? maxWidth; + final bool useSafeArea; + final bool blur; + + const ExtendProps({ + this.maxWidth, + this.useSafeArea = true, + this.blur = true, + }); +} + +enum SheetType { + page, + bottomSheet, + sideSheet, +} + +typedef SheetBuilder = Widget Function(BuildContext context, SheetType type); + Future showSheet({ required BuildContext context, - required Widget body, - required String title, - bool isScrollControlled = true, - double width = 320, + required SheetBuilder builder, + SheetProps props = const SheetProps(), }) { final isMobile = globalState.appState.viewMode == ViewMode.mobile; - if (isMobile) { - return showModalBottomSheet( - context: context, - isScrollControlled: isScrollControlled, - builder: (context) { - return SafeArea( - child: body, - ); - }, - showDragHandle: true, - useSafeArea: true, + return switch (isMobile) { + true => showModalBottomSheet( + context: context, + isScrollControlled: props.isScrollControlled, + builder: (_) { + return SafeArea( + child: builder(context, SheetType.bottomSheet), + ); + }, + showDragHandle: false, + useSafeArea: props.useSafeArea, + ), + false => showModalSideSheet( + useSafeArea: props.useSafeArea, + isScrollControlled: props.isScrollControlled, + context: context, + constraints: BoxConstraints( + maxWidth: props.maxWidth ?? 360, + ), + filter: props.blur ? commonFilter : null, + builder: (_) { + return builder(context, SheetType.sideSheet); + }, + ), + }; +} + +Future showExtend( + BuildContext context, { + required SheetBuilder builder, + ExtendProps props = const ExtendProps(), +}) { + final isMobile = globalState.appState.viewMode == ViewMode.mobile; + return switch (isMobile) { + true => BaseNavigator.push( + context, + builder(context, SheetType.page), + ), + false => showModalSideSheet( + useSafeArea: props.useSafeArea, + context: context, + constraints: BoxConstraints( + maxWidth: props.maxWidth ?? 360, + ), + filter: props.blur ? commonFilter : null, + builder: (context) { + return builder(context, SheetType.sideSheet); + }, + ), + }; +} + +class AdaptiveSheetScaffold extends StatefulWidget { + final SheetType type; + final Widget body; + final String title; + final List actions; + + const AdaptiveSheetScaffold({ + super.key, + required this.type, + required this.body, + required this.title, + this.actions = const [], + }); + + @override + State createState() => _AdaptiveSheetScaffoldState(); +} + +class _AdaptiveSheetScaffoldState extends State { + @override + Widget build(BuildContext context) { + final backgroundColor = context.colorScheme.surface; + final bottomSheet = widget.type == SheetType.bottomSheet; + final sideSheet = widget.type == SheetType.sideSheet; + final appBar = AppBar( + forceMaterialTransparency: bottomSheet ? true : false, + automaticallyImplyLeading: bottomSheet + ? false + : widget.actions.isEmpty && sideSheet + ? false + : true, + centerTitle: bottomSheet, + backgroundColor: backgroundColor, + title: Text( + widget.title, + ), + actions: genActions([ + if (widget.actions.isEmpty && sideSheet) CloseButton(), + ...widget.actions, + ]), ); - } else { - return showModalSideSheet( - useSafeArea: true, - isScrollControlled: isScrollControlled, - context: context, - constraints: BoxConstraints( - maxWidth: width, - ), - body: SafeArea( - child: body, - ), - title: title, + if (widget.type == SheetType.bottomSheet) { + final handleSize = Size(32, 4); + return Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + borderRadius: BorderRadius.vertical(top: Radius.circular(28.0)), + color: backgroundColor, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: EdgeInsets.only(top: 16), + child: Container( + alignment: Alignment.center, + height: handleSize.height, + width: handleSize.width, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(handleSize.height / 2), + color: context.colorScheme.onSurfaceVariant, + ), + ), + ), + appBar, + Flexible( + flex: 1, + child: widget.body, + ) + ], + ), + ); + } + return CommonScaffold( + appBar: appBar, + backgroundColor: backgroundColor, + body: widget.body, ); } } diff --git a/lib/widgets/side_sheet.dart b/lib/widgets/side_sheet.dart index b132da7..f2a295d 100644 --- a/lib/widgets/side_sheet.dart +++ b/lib/widgets/side_sheet.dart @@ -1,3 +1,6 @@ +import 'dart:ui'; + +import 'package:fl_clash/common/color.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -530,11 +533,11 @@ class ModalSideSheetRoute extends PopupRoute { @override Widget buildModalBarrier() { - if (barrierColor.alpha != 0 && !offstage) { - assert(barrierColor != barrierColor.withOpacity(0.0)); + if (barrierColor.a != 0 && !offstage) { + assert(barrierColor != barrierColor.opacity0); final Animation color = animation!.drive( ColorTween( - begin: barrierColor.withOpacity(0.0), + begin: barrierColor.opacity0, end: barrierColor, ).chain( CurveTween(curve: barrierCurve), @@ -562,8 +565,7 @@ class ModalSideSheetRoute extends PopupRoute { Future showModalSideSheet({ required BuildContext context, - required Widget body, - required String title, + required WidgetBuilder builder, Color? backgroundColor, String? barrierLabel, double? elevation, @@ -580,6 +582,7 @@ Future showModalSideSheet({ RouteSettings? routeSettings, AnimationController? transitionAnimationController, Offset? anchorPoint, + ImageFilter? filter, }) { assert(debugCheckHasMediaQuery(context)); assert(debugCheckHasMaterialLocalizations(context)); @@ -588,30 +591,8 @@ Future showModalSideSheet({ Navigator.of(context, rootNavigator: useRootNavigator); final MaterialLocalizations localizations = MaterialLocalizations.of(context); return navigator.push(ModalSideSheetRoute( - builder: (context) { - return SafeArea( - child: Column( - children: [ - AppBar( - automaticallyImplyLeading: false, - title: Text(title), - centerTitle: false, - actions: const [ - SizedBox( - height: kToolbarHeight, - width: kToolbarHeight, - child: CloseButton(), - ) - ], - ), - Expanded( - flex: 1, - child: body, - ), - ], - ), - ); - }, + builder: builder, + filter: filter, capturedThemes: InheritedTheme.capture(from: context, to: navigator.context), isScrollControlled: isScrollControlled, @@ -633,3 +614,28 @@ Future showModalSideSheet({ useSafeArea: useSafeArea, )); } + +// class ModalAppBar extends StatelessWidget { +// final String title; +// +// const ModalAppBar({ +// super.key, +// required this.title, +// }); +// +// @override +// Widget build(BuildContext context) { +// return AppBar( +// automaticallyImplyLeading: false, +// title: Text(title), +// centerTitle: false, +// actions: const [ +// SizedBox( +// height: kToolbarHeight, +// width: kToolbarHeight, +// child: CloseButton(), +// ) +// ], +// ); +// } +// } diff --git a/lib/widgets/subscription_info_view.dart b/lib/widgets/subscription_info_view.dart index c7c43a9..5dc0052 100644 --- a/lib/widgets/subscription_info_view.dart +++ b/lib/widgets/subscription_info_view.dart @@ -35,7 +35,7 @@ class SubscriptionInfoView extends StatelessWidget { LinearProgressIndicator( minHeight: 6, value: progress, - backgroundColor: context.colorScheme.primary.toSoft, + backgroundColor: context.colorScheme.primary.opacity15, ), const SizedBox( height: 8, diff --git a/lib/widgets/super_grid.dart b/lib/widgets/super_grid.dart index 563c378..a8336fd 100644 --- a/lib/widgets/super_grid.dart +++ b/lib/widgets/super_grid.dart @@ -99,23 +99,27 @@ class SuperGridState extends State with TickerProviderStateMixin { return; } showSheet( - width: 360, + builder: (_, type) { + return ValueListenableBuilder( + valueListenable: addedChildrenNotifier, + builder: (_, value, __) { + return AdaptiveSheetScaffold( + type: type, + body: _AddedWidgetsModal( + items: value, + onAdd: (gridItem) { + _childrenNotifier.value = List.from(_childrenNotifier.value) + ..add( + gridItem, + ); + }, + ), + title: appLocalizations.add, + ); + }, + ); + }, context: context, - body: ValueListenableBuilder( - valueListenable: addedChildrenNotifier, - builder: (_, value, __) { - return _AddedWidgetsModal( - items: value, - onAdd: (gridItem) { - _childrenNotifier.value = List.from(_childrenNotifier.value) - ..add( - gridItem, - ); - }, - ); - }, - ), - title: appLocalizations.add, ); } @@ -662,7 +666,8 @@ class SuperGridState extends State with TickerProviderStateMixin { crossAxisSpacing: widget.crossAxisSpacing, mainAxisSpacing: widget.mainAxisSpacing, children: [ - for (int i = 0; i < children.length; i++) _builderItem(i), + for (int i = 0; i < children.length; i++) + _builderItem(i), ], ); }, diff --git a/lib/widgets/widgets.dart b/lib/widgets/widgets.dart index 05df389..22ccf6c 100644 --- a/lib/widgets/widgets.dart +++ b/lib/widgets/widgets.dart @@ -1,5 +1,5 @@ export 'animate_grid.dart'; -export 'back_scope.dart'; +export 'pop_scope.dart'; export 'builder.dart'; export 'card.dart'; export 'chip.dart'; @@ -27,3 +27,5 @@ export 'donut_chart.dart'; export 'activate_box.dart'; export 'wave.dart'; export 'scroll.dart'; +export 'dialog.dart'; +export 'effect.dart'; diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 40b439d..9fe12f3 100755 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -99,9 +99,9 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos SPEC CHECKSUMS: - app_links: 9028728e32c83a0831d9db8cf91c526d16cc5468 + app_links: afe860c55c7ef176cea7fb630a2b7d7736de591d connectivity_plus: 2256d3e20624a7749ed21653aafe291a46446fee - device_info_plus: a56e6e74dbbd2bb92f2da12c64ddd4f67a749041 + device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76 dynamic_color: b820c000cc68df65e7ba7ff177cb98404ce56651 file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 295c4ca..0a74486 100755 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -31,6 +31,7 @@ 7AC6855B2B8AF836004C123B /* (null) in Bundle Framework */ = {isa = PBXBuildFile; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; CDD319C761C7664F6008596B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4121E8CCDC7DC35194714CDE /* Pods_Runner.framework */; }; F50091052CF74B7700D43AEA /* FlClashCore in CopyFiles */ = {isa = PBXBuildFile; fileRef = F50091042CF74B7700D43AEA /* FlClashCore */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; + F5AA39AF2DA1D9FB00F5C816 /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = F5AA39AE2DA1D9FB00F5C816 /* LaunchAtLogin */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -135,6 +136,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + F5AA39AF2DA1D9FB00F5C816 /* LaunchAtLogin in Frameworks */, CDD319C761C7664F6008596B /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -326,6 +328,9 @@ Base, ); mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + F5AA39AD2DA1D9FB00F5C816 /* XCRemoteSwiftPackageReference "LaunchAtLogin" */, + ); productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -827,6 +832,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + F5AA39AD2DA1D9FB00F5C816 /* XCRemoteSwiftPackageReference "LaunchAtLogin" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/sindresorhus/LaunchAtLogin"; + requirement = { + branch = main; + kind = branch; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + F5AA39AE2DA1D9FB00F5C816 /* LaunchAtLogin */ = { + isa = XCSwiftPackageProductDependency; + package = F5AA39AD2DA1D9FB00F5C816 /* XCRemoteSwiftPackageReference "LaunchAtLogin" */; + productName = LaunchAtLogin; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift index 0a58fd4..aee339e 100755 --- a/macos/Runner/MainFlutterWindow.swift +++ b/macos/Runner/MainFlutterWindow.swift @@ -1,19 +1,37 @@ import Cocoa import FlutterMacOS import window_manager +import LaunchAtLogin class MainFlutterWindow: NSWindow { - override func awakeFromNib() { - let flutterViewController = FlutterViewController() - let windowFrame = self.frame - self.contentViewController = flutterViewController - self.setFrame(windowFrame, display: true) - - RegisterGeneratedPlugins(registry: flutterViewController) - super.awakeFromNib() - } - override public func order(_ place: NSWindow.OrderingMode, relativeTo otherWin: Int) { - super.order(place, relativeTo: otherWin) - hiddenWindowAtLaunch() - } + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + FlutterMethodChannel( + name: "launch_at_startup", binaryMessenger: flutterViewController.engine.binaryMessenger + ) + .setMethodCallHandler { (_ call: FlutterMethodCall, result: @escaping FlutterResult) in + switch call.method { + case "launchAtStartupIsEnabled": + result(LaunchAtLogin.isEnabled) + case "launchAtStartupSetEnabled": + if let arguments = call.arguments as? [String: Any] { + LaunchAtLogin.isEnabled = arguments["setEnabledValue"] as! Bool + } + result(nil) + default: + result(FlutterMethodNotImplemented) + } + } + + RegisterGeneratedPlugins(registry: flutterViewController) + super.awakeFromNib() + } + override public func order(_ place: NSWindow.OrderingMode, relativeTo otherWin: Int) { + super.order(place, relativeTo: otherWin) + hiddenWindowAtLaunch() + } } diff --git a/plugins/flutter_distributor b/plugins/flutter_distributor index 7e7bcad..44a8396 160000 --- a/plugins/flutter_distributor +++ b/plugins/flutter_distributor @@ -1 +1 @@ -Subproject commit 7e7bcadf2909bc8acf77a0ba5ae056bea9a60567 +Subproject commit 44a8396d30bbc21348f8e499f4340765dbcb564b diff --git a/pubspec.lock b/pubspec.lock index d17caa1..f84e560 100755 --- a/pubspec.lock +++ b/pubspec.lock @@ -42,10 +42,34 @@ packages: dependency: "direct main" description: name: app_links - sha256: "3ced568a5d9e309e99af71285666f1f3117bddd0bd5b3317979dccc1a40cada4" + sha256: "85ed8fc1d25a76475914fff28cc994653bd900bc2c26e4b57a49e097febb54ba" url: "https://pub.dev" source: hosted - version: "3.5.1" + version: "6.4.0" + app_links_linux: + dependency: transitive + description: + name: app_links_linux + sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81 + url: "https://pub.dev" + source: hosted + version: "1.0.3" + app_links_platform_interface: + dependency: transitive + description: + name: app_links_platform_interface + sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + app_links_web: + dependency: transitive + description: + name: app_links_web + sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555 + url: "https://pub.dev" + source: hosted + version: "1.0.4" archive: dependency: "direct main" description: @@ -322,10 +346,10 @@ packages: dependency: "direct main" description: name: device_info_plus - sha256: a7fd703482b391a87d60b6061d04dfdeab07826b96f9abd8f5ed98068acc0074 + sha256: "306b78788d1bb569edb7c55d622953c2414ca12445b41c9117963e03afc5c513" url: "https://pub.dev" source: hosted - version: "10.1.2" + version: "11.3.3" device_info_plus_platform_interface: dependency: transitive description: @@ -386,10 +410,10 @@ packages: dependency: "direct main" description: name: ffi - sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" ffigen: dependency: "direct dev" description: @@ -538,14 +562,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" - google_fonts: - dependency: "direct main" - description: - name: google_fonts - sha256: b1ac0fe2832c9cc95e5e88b57d627c5e68c223b9657f4b96e1487aa9098c7b82 - url: "https://pub.dev" - source: hosted - version: "6.2.1" graphs: dependency: transitive description: @@ -758,10 +774,10 @@ packages: dependency: "direct main" description: name: launch_at_startup - sha256: "93fc5638e088290004fae358bae691486673d469957d461d9dae5b12248593eb" + sha256: "7db33398b76ec0ed9e27f9f4640553e239977437564046625e215be89c91f084" url: "https://pub.dev" source: hosted - version: "0.2.2" + version: "0.5.1" leak_tracker: dependency: transitive description: @@ -994,14 +1010,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" - process_run: - dependency: "direct main" - description: - name: process_run - sha256: a68fa9727392edad97a2a96a77ce8b0c17d28336ba1b284b1dfac9595a4299ea - url: "https://pub.dev" - source: hosted - version: "1.2.2+1" proxy: dependency: "direct main" description: @@ -1037,10 +1045,10 @@ packages: dependency: "direct main" description: name: re_editor - sha256: "2169c114c7877bcaae72d6e8b69cdaa2a9cded69a51e3cf26209dad4a3ed2b9c" + sha256: "17e430f0591dd361992ec2dd6f69191c1853fa46e05432e095310a8f82ee820e" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.7.0" re_highlight: dependency: "direct main" description: @@ -1370,10 +1378,10 @@ packages: dependency: "direct main" description: name: tray_manager - sha256: "80be6c508159a6f3c57983de795209ac13453e9832fd574143b06dceee188ed2" + sha256: c2da0f0f1ddb455e721cf68d05d1281fec75cf5df0a1d3cb67b6ca0bdfd5709d url: "https://pub.dev" source: hosted - version: "0.3.2" + version: "0.4.0" typed_data: dependency: transitive description: @@ -1522,18 +1530,18 @@ packages: dependency: "direct main" description: name: win32 - sha256: "8b338d4486ab3fbc0ba0db9f9b4f5239b6697fcee427939a40e720cbb9ee0a69" + sha256: dc6ecaa00a7c708e5b4d10ee7bec8c270e9276dfcab1783f57e9962d7884305f url: "https://pub.dev" source: hosted - version: "5.9.0" + version: "5.12.0" win32_registry: dependency: "direct main" description: name: win32_registry - sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852" + sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae" url: "https://pub.dev" source: hosted - version: "1.1.5" + version: "2.1.0" window_ext: dependency: "direct main" description: @@ -1590,5 +1598,5 @@ packages: source: hosted version: "2.2.1" sdks: - dart: ">=3.7.0-0 <4.0.0" + dart: ">=3.7.0 <4.0.0" flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index 738c439..eb9c22c 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: fl_clash description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free. publish_to: 'none' -version: 0.8.80+202503101 +version: 0.8.81+202504081 environment: sdk: '>=3.1.0 <4.0.0' @@ -20,14 +20,14 @@ dependencies: path: plugins/proxy window_ext: path: plugins/window_ext - launch_at_startup: ^0.2.2 + launch_at_startup: ^0.5.1 windows_single_instance: ^1.0.1 json_annotation: ^4.9.0 file_picker: ^8.0.3 mobile_scanner: ^6.0.2 - app_links: ^3.5.0 - win32_registry: ^1.1.5 - tray_manager: ^0.3.2 + app_links: ^6.4.0 + win32_registry: ^2.0.0 + tray_manager: ^0.4.0 collection: ^1.18.0 animations: ^2.0.11 package_info_plus: ^8.0.0 @@ -38,23 +38,21 @@ dependencies: dio: ^5.8.0+1 win32: ^5.5.1 ffi: ^2.1.2 - re_editor: ^0.6.0 + re_editor: ^0.7.0 re_highlight: ^0.0.3 archive: ^3.6.1 lpinyin: ^2.0.3 emoji_regex: ^0.0.5 - process_run: ^1.1.0 cached_network_image: ^3.4.0 hotkey_manager: ^0.2.3 uni_platform: ^0.1.3 - device_info_plus: ^10.1.2 + device_info_plus: ^11.3.3 connectivity_plus: ^6.1.0 screen_retriever: ^0.2.0 defer_pointer: ^0.0.2 flutter_riverpod: ^2.6.1 riverpod_annotation: ^2.6.1 riverpod: ^2.6.1 - google_fonts: ^6.2.1 dev_dependencies: flutter_test: sdk: flutter @@ -76,6 +74,9 @@ flutter: - assets/images/ - assets/images/avatars/ fonts: + - family: JetBrainsMono + fonts: + - asset: assets/fonts/JetBrainsMono-Regular.ttf - family: Twemoji fonts: - asset: assets/fonts/Twemoji.Mozilla.ttf diff --git a/release.py b/release_telegram.py similarity index 96% rename from release.py rename to release_telegram.py index f540804..4e4c253 100644 --- a/release.py +++ b/release_telegram.py @@ -5,7 +5,7 @@ import requests TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN") TAG = os.getenv("TAG") -IS_RELEASE = "+" not in TAG +IS_STABLE = "-" not in TAG CHAT_ID = "@FlClash" API_URL = f"http://localhost:8081/bot{TELEGRAM_BOT_TOKEN}/sendMediaGroup" @@ -33,7 +33,7 @@ for file in os.listdir(DIST_DIR): if TAG: text += f"\n**{TAG}**\n" -if IS_RELEASE: +if IS_STABLE: text += f"\nhttps://github.com/chen08209/FlClash/releases/tag/{TAG}\n" diff --git a/setup.dart b/setup.dart index 801ce13..8c5f305 100755 --- a/setup.dart +++ b/setup.dart @@ -358,6 +358,14 @@ class BuildCommand extends Command { ].join(','), help: 'The $name build arch', ); + argParser.addOption( + "env", + valueHelp: [ + "pre", + "stable", + ].join(','), + help: 'The $name build env', + ); } @override @@ -423,12 +431,13 @@ class BuildCommand extends Command { required Target target, required String targets, String args = '', + required String env, }) async { await Build.getDistributor(); await Build.exec( name: name, Build.getExecutable( - "flutter_distributor package --skip-clean --platform ${target.name} --targets $targets --flutter-build-args=verbose $args", + "flutter_distributor package --skip-clean --platform ${target.name} --targets $targets --flutter-build-args=verbose $args --build-dart-define=APP_ENV=$env", ), ); } @@ -448,6 +457,7 @@ class BuildCommand extends Command { final mode = target == Target.android ? Mode.lib : Mode.core; final String out = argResults?["out"] ?? (target.same ? "app" : "core"); final archName = argResults?["arch"]; + final env = argResults?["env"] ?? "pre"; final currentArches = arches.where((element) => element.name == archName).toList(); final arch = currentArches.isEmpty ? null : currentArches.first; @@ -476,6 +486,7 @@ class BuildCommand extends Command { target: target, targets: "exe,zip", args: "--description $archName", + env: env, ); return; case Target.linux: @@ -497,6 +508,7 @@ class BuildCommand extends Command { targets: targets, args: "--description $archName --build-target-platform $defaultTarget", + env: env, ); return; case Target.android: @@ -515,6 +527,7 @@ class BuildCommand extends Command { targets: "apk", args: "--flutter-build-args split-per-abi --build-target-platform ${defaultTargets.join(",")}", + env: env, ); return; case Target.macos: @@ -523,6 +536,7 @@ class BuildCommand extends Command { target: target, targets: "dmg", args: "--description $archName", + env: env, ); return; }