Explorar o código

feat: 调试launch和getNodes接口、增加评价

lilu hai 4 meses
pai
achega
7e75613a08
Modificáronse 69 ficheiros con 4105 adicións e 2435 borrados
  1. 41 1
      .vscode/launch.json
  2. 56 1
      android/app/build.gradle.kts
  3. 5 2
      android/app/src/main/AndroidManifest.xml
  4. 24 3
      android/app/src/main/kotlin/app/xixi/nomo/CoreApi.g.kt
  5. 61 18
      android/app/src/main/kotlin/app/xixi/nomo/CoreApiImpl.kt
  6. 2 2
      android/app/src/main/kotlin/app/xixi/nomo/TProxyService.kt
  7. 4 0
      android/app/src/main/kotlin/app/xixi/nomo/XRayApi.kt
  8. 6 1
      android/app/src/main/kotlin/app/xixi/nomo/XRayService.kt
  9. 1 0
      android/app/src/main/kotlin/app/xixi/nomo/XrayConfig.kt
  10. 4 0
      android/app/src/main/res/values/strings.xml
  11. 10 0
      assets/vectors/boost/pouting_face.svg
  12. 14 0
      assets/vectors/boost/smiling_face_with_hearts.svg
  13. 10 0
      assets/vectors/boost/smirking_face.svg
  14. 10 0
      assets/vectors/boost/woozy_face.svg
  15. 9 0
      assets/vectors/boost/yawning_face.svg
  16. 22 3
      ios/Runner/CoreApi.g.swift
  17. 0 63
      lib/app/api/core/api_core.dart
  18. 4 25
      lib/app/api/core/api_core_paths.dart
  19. 135 0
      lib/app/api/router/api_router.dart
  20. 23 2
      lib/app/app.dart
  21. 1 1
      lib/app/components/country_restricted_overlay.dart
  22. 36 29
      lib/app/components/ix_snackbar.dart
  23. 87 5
      lib/app/constants/api_domains.dart
  24. 8 0
      lib/app/constants/assets.dart
  25. 2 2
      lib/app/constants/configs.dart
  26. 90 3
      lib/app/controllers/api_controller.dart
  27. 70 4
      lib/app/controllers/core_controller.dart
  28. 154 260
      lib/app/data/models/failure.freezed.dart
  29. 7 8
      lib/app/data/models/failure.g.dart
  30. 168 141
      lib/app/data/models/launch/ad_config.freezed.dart
  31. 8 8
      lib/app/data/models/launch/ad_config.g.dart
  32. 4 8
      lib/app/data/models/launch/app_config.dart
  33. 483 448
      lib/app/data/models/launch/app_config.freezed.dart
  34. 94 98
      lib/app/data/models/launch/app_config.g.dart
  35. 391 352
      lib/app/data/models/launch/groups.freezed.dart
  36. 21 29
      lib/app/data/models/launch/groups.g.dart
  37. 3 2
      lib/app/data/models/launch/launch.dart
  38. 199 152
      lib/app/data/models/launch/launch.freezed.dart
  39. 26 24
      lib/app/data/models/launch/launch.g.dart
  40. 47 38
      lib/app/data/models/launch/ranks.freezed.dart
  41. 4 4
      lib/app/data/models/launch/ranks.g.dart
  42. 149 137
      lib/app/data/models/launch/upgrade.freezed.dart
  43. 171 161
      lib/app/data/models/launch/user.freezed.dart
  44. 12 12
      lib/app/data/models/launch/user.g.dart
  45. 195 184
      lib/app/data/models/launch/vpn_config.freezed.dart
  46. 7 18
      lib/app/data/models/launch/vpn_config.g.dart
  47. 8 0
      lib/app/data/sp/ix_sp.dart
  48. 0 5
      lib/app/dialog/custom_dialog.dart
  49. 10 0
      lib/app/modules/login/bindings/login_binding.dart
  50. 46 0
      lib/app/modules/login/controllers/login_controller.dart
  51. 123 0
      lib/app/modules/login/views/login_view.dart
  52. 44 13
      lib/app/modules/node/widgets/node_list.dart
  53. 0 10
      lib/app/modules/signin/bindings/signin_binding.dart
  54. 0 23
      lib/app/modules/signin/controllers/signin_controller.dart
  55. 0 24
      lib/app/modules/signin/views/signin_view.dart
  56. 31 8
      lib/app/modules/signup/controllers/signup_controller.dart
  57. 110 11
      lib/app/modules/signup/views/signup_view.dart
  58. 9 4
      lib/app/modules/splash/controllers/splash_controller.dart
  59. 156 48
      lib/app/modules/splash/views/splash_view.dart
  60. 13 7
      lib/app/routes/app_pages.dart
  61. 2 2
      lib/app/routes/app_routes.dart
  62. 322 0
      lib/app/widgets/feedback_bottom_sheet.dart
  63. 288 0
      lib/app/widgets/ix_text_field.dart
  64. 2 0
      lib/config/theme/dark_theme_colors.dart
  65. 25 2
      lib/pigeons/core_api.g.dart
  66. 7 4
      lib/utils/developer/ix_developer_tools.dart
  67. 7 1
      pigeons/core_api.dart
  68. 20 20
      pubspec.lock
  69. 4 4
      pubspec.yaml

+ 41 - 1
.vscode/launch.json

@@ -20,6 +20,46 @@
             "request": "launch",
             "type": "dart",
             "flutterMode": "release"
-        }
+        },
+        {
+            "name": "universal-dev",
+            "request": "launch",
+            "type": "dart",
+            "flutterMode": "debug",
+            "args": [
+                "--flavor=universalDev",
+                "--dart-define=ENV=dev"
+            ]
+        },
+        {
+            "name": "google-dev",
+            "request": "launch",
+            "type": "dart",
+            "flutterMode": "debug",
+            "args": [
+                "--flavor=googleDev",
+                "--dart-define=ENV=dev"
+            ]
+        },
+        {
+            "name": "universal-prod",
+            "request": "launch",
+            "type": "dart",
+            "flutterMode": "debug",
+            "args": [
+                "--flavor=universalProd",
+                "--dart-define=ENV=prod"
+            ]
+        },
+        {
+            "name": "google-prod",
+            "request": "launch",
+            "type": "dart",
+            "flutterMode": "debug",
+            "args": [
+                "--flavor=googleProd",
+                "--dart-define=ENV=prod"
+            ]
+        },
     ]
 }

+ 56 - 1
android/app/build.gradle.kts

@@ -1,3 +1,6 @@
+import java.io.FileInputStream
+import java.util.Properties
+
 plugins {
     id("com.android.application")
     id("kotlin-android")
@@ -5,6 +8,13 @@ plugins {
     id("dev.flutter.flutter-gradle-plugin")
 }
 
+val keystorePropertiesFile = rootProject.file("key.properties")
+val keystoreProperties = Properties()
+
+if (keystorePropertiesFile.exists()) {
+    keystoreProperties.load(FileInputStream(keystorePropertiesFile))
+}
+
 android {
     namespace = "app.xixi.nomo"
     compileSdk = flutter.compileSdkVersion
@@ -28,12 +38,38 @@ android {
         targetSdk = flutter.targetSdkVersion
         versionCode = flutter.versionCode
         versionName = flutter.versionName
+        multiDexEnabled = true
+        manifestPlaceholders.put("CHANNEL", "universal")
         ndk {
             abiFilters.add("armeabi-v7a")
             abiFilters.add("arm64-v8a")
         }
     }
 
+    flavorDimensions += "env"
+    productFlavors {
+        create("googleDev") {
+            dimension = "env"
+//            applicationIdSuffix = ".dev"
+            resValue("string", "app_name", "NOMO Dev")
+            manifestPlaceholders["CHANNEL"] = "google"
+        }
+        create("universalDev") {
+            dimension = "env"
+//            applicationIdSuffix = ".dev"
+            resValue("string", "app_name", "NOMO Dev")
+            manifestPlaceholders["CHANNEL"] = "universal"
+        }
+        create("googleProd") {
+            dimension = "env"
+            manifestPlaceholders["CHANNEL"] = "google"
+        }
+        create("universalProd") {
+            dimension = "env"
+            manifestPlaceholders["CHANNEL"] = "universal"
+        }
+    }
+
     sourceSets {
         getByName("main") {
             jniLibs.srcDirs("libs")
@@ -49,6 +85,20 @@ android {
             excludes += ":META-INF/LICENSE"
             excludes += ":META-INF/LICENSE.md"
             excludes += ":META-INF/NOTICE"
+            excludes += setOf(
+                "META-INF/DebugProbesKt.bin",
+                "lib/x86/*",
+                "lib/x86_64/*"
+            )
+        }
+    }
+
+    signingConfigs {
+        create("release") {
+            keyAlias = keystoreProperties["keyAlias"] as String?
+            keyPassword = keystoreProperties["keyPassword"] as String?
+            storeFile = keystoreProperties["storeFile"]?.let { file(it) }
+            storePassword = keystoreProperties["storePassword"] as String?
         }
     }
 
@@ -56,7 +106,12 @@ android {
         release {
             // TODO: Add your own signing config for the release build.
             // Signing with the debug keys for now, so `flutter run --release` works.
-            signingConfig = signingConfigs.getByName("debug")
+            signingConfig = signingConfigs.getByName("release")
+            isShrinkResources = false
+            isMinifyEnabled = false
+        }
+        debug {
+            signingConfig = signingConfigs.getByName("release")
         }
     }
 }

+ 5 - 2
android/app/src/main/AndroidManifest.xml

@@ -53,7 +53,7 @@
         android:fullBackupContent="false"
         android:hardwareAccelerated="true"
         android:icon="@mipmap/launcher_icon"
-        android:label="NoMo"
+        android:label="@string/app_name"
         android:requestLegacyExternalStorage="true"
         android:usesCleartextTraffic="true"
         tools:targetApi="tiramisu">
@@ -82,7 +82,7 @@
             android:directBootAware="true"
             android:exported="false"
             android:foregroundServiceType="systemExempted"
-            android:label="NoMo"
+            android:label="@string/app_name"
             android:permission="android.permission.BIND_VPN_SERVICE"
             android:process=":nomo_vpn_service"
             tools:ignore="ForegroundServicePermission">
@@ -99,6 +99,9 @@
         <meta-data
             android:name="flutterEmbedding"
             android:value="2"/>
+        <meta-data
+            android:name="channel"
+            android:value="${CHANNEL}" />
     </application>
     <!-- Required to query activities that can process text, see:
          https://developer.android.com/training/package-visibility and

+ 24 - 3
android/app/src/main/kotlin/app/xixi/nomo/CoreApi.g.kt

@@ -62,7 +62,7 @@ val CoreApiPigeonMethodCodec = StandardMethodCodec(CoreApiPigeonCodec())
 interface CoreApi {
   fun getApps(callback: (Result<String?>) -> Unit)
   fun getSystemLocale(): String?
-  fun connect(): Boolean?
+  fun connect(sessionId: String, socksPort: Long, tunnelConfig: String, configJson: String): Boolean?
   fun disconnect(): Boolean?
   fun getRemoteIp(): String?
   fun getAdvertisingId(): String?
@@ -70,6 +70,7 @@ interface CoreApi {
   fun isConnected(): Boolean?
   fun getSimInfo(): String?
   fun reconnect(): Boolean?
+  fun getChannel(): String?
 
   companion object {
     /** The codec used by CoreApi. */
@@ -116,9 +117,14 @@ interface CoreApi {
       run {
         val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.app.xixi.nomo.CoreApi.connect$separatedMessageChannelSuffix", codec)
         if (api != null) {
-          channel.setMessageHandler { _, reply ->
+          channel.setMessageHandler { message, reply ->
+            val args = message as List<Any?>
+            val sessionIdArg = args[0] as String
+            val socksPortArg = args[1] as Long
+            val tunnelConfigArg = args[2] as String
+            val configJsonArg = args[3] as String
             val wrapped: List<Any?> = try {
-              listOf(api.connect())
+              listOf(api.connect(sessionIdArg, socksPortArg, tunnelConfigArg, configJsonArg))
             } catch (exception: Throwable) {
               CoreApiPigeonUtils.wrapError(exception)
             }
@@ -233,6 +239,21 @@ interface CoreApi {
           channel.setMessageHandler(null)
         }
       }
+      run {
+        val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.app.xixi.nomo.CoreApi.getChannel$separatedMessageChannelSuffix", codec)
+        if (api != null) {
+          channel.setMessageHandler { _, reply ->
+            val wrapped: List<Any?> = try {
+              listOf(api.getChannel())
+            } catch (exception: Throwable) {
+              CoreApiPigeonUtils.wrapError(exception)
+            }
+            reply.reply(wrapped)
+          }
+        } else {
+          channel.setMessageHandler(null)
+        }
+      }
     }
   }
 }

+ 61 - 18
android/app/src/main/kotlin/app/xixi/nomo/CoreApiImpl.kt

@@ -28,8 +28,12 @@ import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import java.io.ByteArrayOutputStream
 import java.io.InputStream
-import java.util.UUID
 import androidx.core.graphics.createBitmap
+import com.google.gson.JsonArray
+import com.google.gson.JsonElement
+import com.google.gson.JsonObject
+import com.google.gson.JsonParser
+import com.google.gson.JsonSyntaxException
 import java.util.Locale
 
 // 消息数据类
@@ -47,11 +51,6 @@ data class TimerUpdateMessage(
     val isPaused: Boolean
 )
 
-data class TimerNotificationMessage(
-    val type: String,
-    val message: String
-)
-
 class CoreApiImpl(private val activity: Activity) : CoreApi {
     companion object {
         private const val TAG = "CoreApiImpl"
@@ -63,6 +62,12 @@ class CoreApiImpl(private val activity: Activity) : CoreApi {
 
     // xray服务
     private var xrayApi: XRayApi? = null
+
+    private var sessionId: String = ""
+    private var socksPort: Long = 10808
+    private var tunnelConfig: String = ""
+    private var configJson: String = ""
+
     
     // 事件流处理器实现
     private val eventStreamHandler = object : OnEventChangeStreamHandler() {
@@ -230,7 +235,11 @@ class CoreApiImpl(private val activity: Activity) : CoreApi {
         return bos.toByteArray()
     }
 
-    override fun connect(): Boolean? {
+    override fun connect(sessionId: String, socksPort: Long, tunnelConfig: String, configJson: String): Boolean? {
+        this.sessionId = sessionId
+        this.socksPort = socksPort
+        this.tunnelConfig = tunnelConfig
+        this.configJson = configJson
         Log.i(TAG, "Starting V2Ray with permission check")
         // 检查VPN权限
         val intent = VpnService.prepare(activity)
@@ -277,6 +286,17 @@ class CoreApiImpl(private val activity: Activity) : CoreApi {
         }
     }
 
+    override fun getChannel(): String? {
+        try {
+            val pm: PackageManager = activity.packageManager
+            val appInfo = pm.getApplicationInfo(activity.packageName, PackageManager.GET_META_DATA)
+            val channel = appInfo.metaData?.getString("channel")!!
+            return channel
+        } catch (e: Exception) {
+            throw FlutterError("-1", e.message)
+        }
+    }
+
     override fun isConnected(): Boolean? {
         val isRunning = XRayApi.isServiceRunning(activity)
         VLog.i(TAG, "XRayService is running = $isRunning")
@@ -347,26 +367,37 @@ class CoreApiImpl(private val activity: Activity) : CoreApi {
         VLog.i(TAG, "CoreApiImpl.startXray() 被调用,开始准备配置")
         
         val config = XrayConfig()
-        config.sessionId = UUID.randomUUID().toString()
-        config.socksPort = 10808
+        config.sessionId = sessionId
+        config.socksPort = socksPort.toInt()
+        config.tunnelConfig = tunnelConfig
         
-        VLog.i(TAG, "生成的 sessionId: ${config.sessionId}")
+        VLog.i(TAG, "sessionId: ${config.sessionId}")
         VLog.i(TAG, "socksPort: ${config.socksPort}")
+        VLog.i(TAG, "tunnelConfig: ${config.tunnelConfig}")
         
         val detectOptions = StatusDetectOptions()
         detectOptions.statusDetectInterval = 60
         val detectUrls = ArrayList<String>()
         detectUrls.add("https://www.baidu.com")
+        detectUrls.add("https://www.google.com")
         detectOptions.statusDetectUrls = detectUrls
-        
+        val jsonArray = safeStringToJsonArray(configJson)
         val proxyNodes= ArrayList<ProxyNode>()
-        val proxyNode = ProxyNode()
-        proxyNode.nodeId = "10001"
-        proxyNode.coreConfig = readAssets("connect.json")
-        proxyNodes.add(proxyNode)
-        
-        VLog.i(TAG, "读取配置文件: connect.json")
-        
+        jsonArray?.forEach {
+            if(it.isJsonObject) {
+                val obj: JsonObject = it.asJsonObject
+                val nodeId = obj.get("nodeId").asString
+                val coreConfig = obj.get("coreConfig").asString
+                VLog.i(TAG, "coreConfig: $coreConfig")
+                val proxyNode = ProxyNode()
+                proxyNode.nodeId = nodeId
+//                VLog.i(TAG, "读取配置文件: connect.json")
+//                proxyNode.coreConfig = readAssets("connect.json")
+                proxyNode.coreConfig = coreConfig
+                proxyNodes.add(proxyNode)
+            }
+        }
+
         val startOptions = ProxyStartOptions()
         startOptions.socksPort = config.socksPort
         startOptions.nodes = proxyNodes
@@ -378,6 +409,18 @@ class CoreApiImpl(private val activity: Activity) : CoreApi {
         VLog.i(TAG, "xrayApi.startXray() 返回结果: $result")
     }
 
+    fun safeStringToJsonArray(jsonString: String?): JsonArray? {
+        if (jsonString.isNullOrBlank()) return null
+
+        return try {
+            val element: JsonElement = JsonParser.parseString(jsonString)
+            if (element.isJsonArray) element.asJsonArray else null
+        } catch (e: JsonSyntaxException) {
+            e.printStackTrace()
+            null
+        }
+    }
+
 
     private fun readAssets(fileName: String): String {
         try {

+ 2 - 2
android/app/src/main/kotlin/app/xixi/nomo/TProxyService.kt

@@ -34,8 +34,8 @@ internal class TProxyService {
         }
     }
 
-    fun startTunnel(context: Context, socksPort: Int, tunFd: Int) {
-        val configStr = buildConfig(socksPort)
+    fun startTunnel(context: Context, socksPort: Int, tunnelConfig: String?,  tunFd: Int) {
+        val configStr = if(tunnelConfig.isNullOrEmpty()) buildConfig(socksPort) else tunnelConfig
         val configFileDir = context.cacheDir.absolutePath
         val configFileName = "hev-socks5-tunnel.yaml"
         val cfgFile = File(configFileDir, configFileName)

+ 4 - 0
android/app/src/main/kotlin/app/xixi/nomo/XRayApi.kt

@@ -87,6 +87,7 @@ class XRayApi {
                 mService = null
                 vpnState = VPN_STATE_IDLE
                 xraySvrState = XRAY_SVR_IDLE
+                vpnServiceEvent?.onVpnStatusChange(VPN_STATE_ERROR, "xray service disconnected")
             } finally {
                 lock.unlock()
             }
@@ -241,6 +242,7 @@ class XRayApi {
         val bundle = Bundle().apply {
             putInt("socksPort", config.socksPort)
             putString("sessionId", config.sessionId)
+            putString("tunnelConfig", config.tunnelConfig)
             putString("startOptions", config.getProxyStartOptions())
             putStringArrayList("allowVpnApps", config.allowVpnApps)
             putStringArrayList("disallowVpnApps", config.disallowVpnApps)
@@ -265,6 +267,8 @@ class XRayApi {
         lock.lock()
         try {
             if (xraySvrState != XRAY_SVR_SERVICE_WORKING) {
+                // 服务未启动,点击了关闭
+                vpnServiceEvent?.onVpnStatusChange(VPN_STATE_IDLE, "")
                 VLog.w(TAG, "xray 服务未在运行,当前状态: $xraySvrState")
                 return
             }

+ 6 - 1
android/app/src/main/kotlin/app/xixi/nomo/XRayService.kt

@@ -17,6 +17,7 @@ import android.os.Messenger
 import android.os.ParcelFileDescriptor
 import androidx.core.app.NotificationCompat
 import app.xixi.nomo.XRayApi.Companion.VPN_STATE_CONNECTED
+import app.xixi.nomo.XRayApi.Companion.VPN_STATE_ERROR
 import go.Seq
 import ixvpn_mobile.Ixvpn_mobile
 import ixvpn_mobile.ProxyConnectorHandler
@@ -167,6 +168,7 @@ class XRayService : VpnService() {
         val socksPort = bundle.getInt("socksPort")
         val startOptions = bundle.getString("startOptions")
         val sessionId = bundle.getString("sessionId")
+        val tunnelConfig = bundle.getString("tunnelConfig")
         val allowVpnApps = bundle.getStringArrayList("allowVpnApps") ?: arrayListOf()
         val disallowVpnApps = bundle.getStringArrayList("disallowVpnApps") ?: arrayListOf()
         
@@ -198,7 +200,7 @@ class XRayService : VpnService() {
             tunFileDescriptor = builder.establish()
             tunFileDescriptor?.let { tfd ->
                 VLog.i(TAG, "VPN tunnel established, fd: ${tfd.fd}")
-                tunnelService.startTunnel(this, socksPort, tfd.fd)
+                tunnelService.startTunnel(this, socksPort, tunnelConfig, tfd.fd)
                 Ixvpn_mobile.proxyConnectorStart(sessionId, startOptions, vpnHandler)
                 VLog.i(TAG, "XRay proxy started successfully")
             } ?: run {
@@ -253,6 +255,9 @@ class XRayService : VpnService() {
                 startTimer(0, 0)
 //                startTimer(1, 10000L)
             }
+            else if(status == VPN_STATE_ERROR) {
+                dealStopMsg()
+            }
             replyMessenger?.let { messenger ->
                 try {
                     val msg = Message.obtain().apply {

+ 1 - 0
android/app/src/main/kotlin/app/xixi/nomo/XrayConfig.kt

@@ -5,6 +5,7 @@ import com.google.gson.Gson
 data class XrayConfig(
     var sessionId: String = "",
     var socksPort: Int = 0,
+    var tunnelConfig: String = "",
     var startOptions: ProxyStartOptions? = null,
     var allowVpnApps: ArrayList<String> = ArrayList(),
     var disallowVpnApps: ArrayList<String> = ArrayList()

+ 4 - 0
android/app/src/main/res/values/strings.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="app_name">NOMO</string>
+</resources>

+ 10 - 0
assets/vectors/boost/pouting_face.svg

@@ -0,0 +1,10 @@
+<svg width="37" height="36" viewBox="0 0 37 36" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M18.4934 35.9977C28.4309 35.9977 36.4869 27.9395 36.4869 17.9993C36.4869 8.05912 28.4309 0.000976562 18.4934 0.000976562C8.55594 0.000976562 0.5 8.05912 0.5 17.9993C0.5 27.9395 8.55594 35.9977 18.4934 35.9977Z" fill="#DB2B45" style="fill:#DB2B45;fill:color(display-p3 0.8588 0.1686 0.2706);fill-opacity:1;"/>
+<path d="M3.40556 11.9409C3.55121 10.6132 4.03629 9.34257 4.70121 8.17832C5.37373 7.01661 6.2489 5.96258 7.26845 5.07577C8.28673 4.1877 9.45066 3.46559 10.6944 2.96011C11.9394 2.46097 13.2654 2.15819 14.6003 2.20253C13.444 2.86383 12.3523 3.47573 11.3213 4.1649C10.2891 4.84648 9.31893 5.58125 8.40071 6.37811C7.48248 7.17624 6.61998 8.03643 5.80181 8.96504C4.97604 9.89112 4.21867 10.8869 3.4043 11.9409H3.40556Z" fill="#F1AAB5" style="fill:#F1AAB5;fill:color(display-p3 0.9451 0.6667 0.7098);fill-opacity:1;"/>
+<path d="M36.5008 17.9997C36.5008 27.9395 28.4458 35.9993 18.5061 35.9993C13.0753 35.9993 8.2068 33.5923 4.90625 29.7854C8.06368 32.5357 12.1925 34.1979 16.7077 34.1979C26.6448 34.1979 34.7024 26.1406 34.7024 16.1982C34.7024 11.6945 33.0496 7.57724 30.3139 4.41895C34.1046 7.71786 36.4996 12.5775 36.4996 17.9985L36.5008 17.9997Z" fill="#A42034" style="fill:#A42034;fill:color(display-p3 0.6431 0.1255 0.2039);fill-opacity:1;"/>
+<path d="M25.5361 30.6192C25.4335 30.6192 25.3322 30.5837 25.2574 30.5153C24.0454 29.422 21.4731 28.6669 18.4968 28.6669C15.5205 28.6669 12.9482 29.422 11.7361 30.5153C11.6614 30.5824 11.5613 30.6192 11.4575 30.6192C11.1978 30.6192 11.004 30.3937 11.0496 30.1492C11.5056 27.6826 14.6656 25.7734 18.4968 25.7734C22.328 25.7734 25.488 27.6839 25.9439 30.1492C25.9895 30.3937 25.7945 30.6192 25.5361 30.6192Z" fill="#242B30" style="fill:#242B30;fill:color(display-p3 0.1412 0.1686 0.1882);fill-opacity:1;"/>
+<path d="M24.0537 23.0358C25.3659 23.0358 26.4297 21.5021 26.4297 19.6102C26.4297 17.7183 25.3659 16.1846 24.0537 16.1846C22.7415 16.1846 21.6777 17.7183 21.6777 19.6102C21.6777 21.5021 22.7415 23.0358 24.0537 23.0358Z" fill="#242B30" style="fill:#242B30;fill:color(display-p3 0.1412 0.1686 0.1882);fill-opacity:1;"/>
+<path d="M12.9502 23.0358C14.2624 23.0358 15.3262 21.5021 15.3262 19.6102C15.3262 17.7183 14.2624 16.1846 12.9502 16.1846C11.638 16.1846 10.5742 17.7183 10.5742 19.6102C10.5742 21.5021 11.638 23.0358 12.9502 23.0358Z" fill="#242B30" style="fill:#242B30;fill:color(display-p3 0.1412 0.1686 0.1882);fill-opacity:1;"/>
+<path d="M21.6273 18.8802C21.3576 18.8802 21.0916 18.7611 20.9118 18.5318C20.6027 18.1365 20.6724 17.5665 21.0663 17.2574C23.4321 15.4039 26.2843 14.3714 29.5456 14.1877C30.0459 14.1624 30.4752 14.5424 30.5031 15.0429C30.531 15.5433 30.1485 15.9727 29.6482 16.0006C26.7656 16.1628 24.2554 17.066 22.1872 18.6864C22.0212 18.8168 21.8236 18.8789 21.6286 18.8789L21.6273 18.8802Z" fill="#242B30" style="fill:#242B30;fill:color(display-p3 0.1412 0.1686 0.1882);fill-opacity:1;"/>
+<path d="M15.3753 18.8802C15.645 18.8802 15.911 18.7611 16.0908 18.5318C16.3999 18.1365 16.3302 17.5665 15.9363 17.2574C13.5705 15.4039 10.7183 14.3714 7.45697 14.1877C6.9567 14.1624 6.52734 14.5424 6.49948 15.0429C6.47161 15.5433 6.8541 15.9727 7.35438 16.0006C10.237 16.1628 12.7472 17.066 14.8154 18.6864C14.9813 18.8168 15.1789 18.8789 15.374 18.8789L15.3753 18.8802Z" fill="#242B30" style="fill:#242B30;fill:color(display-p3 0.1412 0.1686 0.1882);fill-opacity:1;"/>
+</svg>

+ 14 - 0
assets/vectors/boost/smiling_face_with_hearts.svg

@@ -0,0 +1,14 @@
+<svg width="37" height="36" viewBox="0 0 37 36" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M18.4934 35.9981C28.4308 35.9981 36.4867 27.9398 36.4867 17.9993C36.4867 8.05883 28.4308 0.000488281 18.4934 0.000488281C8.5559 0.000488281 0.5 8.05883 0.5 17.9993C0.5 27.9398 8.5559 35.9981 18.4934 35.9981Z" fill="#FDCA47" style="fill:#FDCA47;fill:color(display-p3 0.9922 0.7922 0.2784);fill-opacity:1;"/>
+<path d="M3.40556 11.9402C3.55121 10.6125 4.03629 9.34179 4.70121 8.17751C5.37373 7.01577 6.24889 5.9617 7.26843 5.07488C8.28671 4.18678 9.45064 3.46465 10.6944 2.95916C11.9393 2.46 13.2654 2.15721 14.6003 2.20155C13.444 2.86287 12.3522 3.47478 11.3213 4.16398C10.2891 4.84557 9.31892 5.58037 8.40069 6.37725C7.48247 7.17539 6.61998 8.03562 5.80181 8.96425C4.97604 9.89036 4.21867 10.8861 3.4043 11.9402H3.40556Z" fill="#FFE8BB" style="fill:#FFE8BB;fill:color(display-p3 1.0000 0.9098 0.7333);fill-opacity:1;"/>
+<path d="M36.5007 17.9996C36.5007 27.9397 28.4457 35.9997 18.5061 35.9997C13.0753 35.9997 8.20679 33.5926 4.90625 29.7855C8.06367 32.536 12.1925 34.1981 16.7076 34.1981C26.6447 34.1981 34.7023 26.1407 34.7023 16.1981C34.7023 11.6942 33.0495 7.57683 30.3138 4.41846C34.1045 7.71746 36.4994 12.5773 36.4994 17.9983L36.5007 17.9996Z" fill="#F9B700" style="fill:#F9B700;fill:color(display-p3 0.9765 0.7176 0.0000);fill-opacity:1;"/>
+<path d="M27.2446 20.6596C27.5802 20.3999 28.0349 20.7622 27.8563 21.1474C26.4277 24.2297 22.7712 26.4227 18.4853 26.4227C14.1995 26.4227 10.543 24.2297 9.11442 21.1474C8.93584 20.7622 9.39052 20.3999 9.72614 20.6596C12.1262 22.5105 15.1709 23.6178 18.4841 23.6178C21.7973 23.6178 24.842 22.5105 27.242 20.6596H27.2446Z" fill="#664100" style="fill:#664100;fill:color(display-p3 0.4000 0.2549 0.0000);fill-opacity:1;"/>
+<path d="M20.5718 16.9567C20.4997 16.9567 20.4262 16.9479 20.3527 16.9301C19.8677 16.8098 19.5726 16.3195 19.6929 15.8343C20.2311 13.6666 21.7383 12.2109 23.4443 12.2109C25.1503 12.2109 26.6587 13.6679 27.197 15.8343C27.3173 16.3195 27.0222 16.8098 26.5371 16.9301C26.0521 17.0505 25.5619 16.7553 25.4416 16.2701C25.1136 14.9449 24.2916 14.0201 23.4456 14.0201C22.5995 14.0201 21.7788 14.9449 21.4495 16.2701C21.3469 16.6818 20.9784 16.9567 20.5718 16.9567Z" fill="#664100" style="fill:#664100;fill:color(display-p3 0.4000 0.2549 0.0000);fill-opacity:1;"/>
+<path d="M10.6832 16.9567C10.611 16.9567 10.5375 16.9479 10.4641 16.9301C9.97899 16.8098 9.68389 16.3195 9.80421 15.8343C10.3425 13.6666 11.8496 12.2109 13.5556 12.2109C15.2616 12.2109 16.77 13.6679 17.3083 15.8343C17.4286 16.3195 17.1335 16.8098 16.6485 16.9301C16.1634 17.0505 15.6732 16.7553 15.5529 16.2701C15.2249 14.9449 14.4029 14.0201 13.5569 14.0201C12.7109 14.0201 11.8902 14.9449 11.5609 16.2701C11.4583 16.6818 11.0897 16.9567 10.6832 16.9567Z" fill="#664100" style="fill:#664100;fill:color(display-p3 0.4000 0.2549 0.0000);fill-opacity:1;"/>
+<path d="M32.0269 6.14019C31.229 7.98479 29.5952 9.49366 27.5155 10.8214C27.2521 10.9899 26.9139 10.9899 26.6505 10.8214C23.9782 9.11486 22.0442 7.1119 21.6706 4.47675C21.6111 4.05994 21.6161 3.63173 21.7276 3.22505C22.0708 1.98349 23.1967 1.06499 24.543 1.04346C24.5608 1.04346 24.5772 1.04346 24.5937 1.04346C25.4739 1.04346 26.2642 1.42606 26.8076 2.03417C26.9076 2.14693 27.0001 2.26601 27.0824 2.3927C27.1166 2.34076 27.152 2.28882 27.1888 2.23941C29.0202 3.25166 30.6552 4.57557 32.0256 6.13892L32.0269 6.14019Z" fill="#FFAF2C" style="fill:#FFAF2C;fill:color(display-p3 1.0000 0.6863 0.1725);fill-opacity:1;"/>
+<path d="M33.8226 2.55249C33.4768 1.29447 32.3243 0.369629 30.9564 0.369629C29.9128 0.369629 28.9959 0.908062 28.4665 1.72141C27.9371 0.908062 27.0201 0.369629 25.9765 0.369629C24.6087 0.369629 23.4561 1.29447 23.1104 2.55249C22.9989 2.95917 22.9939 3.38738 23.0534 3.80419C23.427 6.43807 25.361 8.4423 28.0333 10.1475C28.2968 10.316 28.6362 10.316 28.8996 10.1475C31.572 8.44103 33.5059 6.43807 33.8796 3.80419C33.9391 3.38611 33.934 2.95917 33.8226 2.55249Z" fill="#DF2540" style="fill:#DF2540;fill:color(display-p3 0.8745 0.1451 0.2510);fill-opacity:1;"/>
+<path d="M14.3183 28.7273C14.0068 30.9215 12.6921 32.7104 10.7809 34.2585C7.45128 32.6724 4.68395 30.0955 2.86523 26.9055C3.36931 25.7931 4.48764 25.019 5.78835 25.019C6.91428 25.019 7.90343 25.6006 8.47463 26.4785C9.04709 25.6006 10.035 25.019 11.1622 25.019C12.6377 25.019 13.8826 26.0174 14.255 27.3742C14.3753 27.8126 14.3804 28.2737 14.3171 28.726L14.3183 28.7273Z" fill="#FFAF2C" style="fill:#FFAF2C;fill:color(display-p3 1.0000 0.6863 0.1725);fill-opacity:1;"/>
+<path d="M12.538 27.3806C12.1644 26.0224 10.9206 25.0254 9.44515 25.0254C8.31922 25.0254 7.33007 25.6056 6.7576 26.4836C6.18641 25.6056 5.19599 25.0254 4.07006 25.0254C2.59331 25.0254 1.35085 26.0237 0.977233 27.3806C0.856914 27.8202 0.851849 28.2813 0.915175 28.7323C1.31793 31.5753 3.40641 33.7378 6.29026 35.5799C6.57523 35.7611 6.93998 35.7611 7.22495 35.5799C10.1088 33.7391 12.196 31.5753 12.6 28.7323C12.6646 28.2813 12.6596 27.8202 12.538 27.3806Z" fill="#DF2540" style="fill:#DF2540;fill:color(display-p3 0.8745 0.1451 0.2510);fill-opacity:1;"/>
+<path d="M34.7109 25.8012C33.6914 27.9182 32.2716 29.8033 30.5479 31.3616C30.4681 31.3489 30.3896 31.3198 30.3187 31.2755C28.3138 29.9959 26.8611 28.4908 26.5812 26.5132C26.5369 26.199 26.5394 25.8785 26.6242 25.5732C26.8839 24.6293 27.7489 23.9351 28.7748 23.9351C29.5588 23.9351 30.2465 24.3392 30.6442 24.9498C31.0406 24.3392 31.7296 23.9351 32.5123 23.9351C33.5394 23.9351 34.4044 24.6293 34.6628 25.5732C34.6843 25.6492 34.6995 25.7239 34.7097 25.7999L34.7109 25.8012Z" fill="#FFAF2C" style="fill:#FFAF2C;fill:color(display-p3 1.0000 0.6863 0.1725);fill-opacity:1;"/>
+<path d="M36.094 25.3676C35.8343 24.4238 34.9693 23.7295 33.9422 23.7295C33.1595 23.7295 32.4705 24.1336 32.0728 24.7443C31.6751 24.1336 30.9874 23.7295 30.2034 23.7295C29.1763 23.7295 28.3112 24.4238 28.0516 25.3676C27.968 25.6729 27.9642 25.9934 28.0085 26.3076C28.2884 28.2853 29.7411 29.7891 31.7473 31.0699C31.9449 31.1966 32.1994 31.1966 32.397 31.0699C34.4032 29.7891 35.8546 28.2853 36.1358 26.3076C36.1801 25.9934 36.1763 25.6729 36.0927 25.3676H36.094Z" fill="#DF2540" style="fill:#DF2540;fill:color(display-p3 0.8745 0.1451 0.2510);fill-opacity:1;"/>
+</svg>

+ 10 - 0
assets/vectors/boost/smirking_face.svg

@@ -0,0 +1,10 @@
+<svg width="37" height="36" viewBox="0 0 37 36" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M18.4934 35.9981C28.4308 35.9981 36.4867 27.9398 36.4867 17.9993C36.4867 8.05884 28.4308 0.000488281 18.4934 0.000488281C8.5559 0.000488281 0.5 8.05884 0.5 17.9993C0.5 27.9398 8.5559 35.9981 18.4934 35.9981Z" fill="#FDCA47" style="fill:#FDCA47;fill:color(display-p3 0.9922 0.7922 0.2784);fill-opacity:1;"/>
+<path d="M3.40556 11.9402C3.55121 10.6125 4.03629 9.34179 4.70121 8.17751C5.37373 7.01577 6.24889 5.96171 7.26843 5.07488C8.28671 4.18679 9.45064 3.46465 10.6944 2.95916C11.9393 2.46 13.2654 2.15721 14.6003 2.20155C13.444 2.86287 12.3522 3.47478 11.3213 4.16398C10.2891 4.84557 9.31892 5.58038 8.40069 6.37726C7.48247 7.1754 6.61998 8.03562 5.80181 8.96425C4.97604 9.89035 4.21867 10.8861 3.4043 11.9402H3.40556Z" fill="#FFE8BB" style="fill:#FFE8BB;fill:color(display-p3 1.0000 0.9098 0.7333);fill-opacity:1;"/>
+<path d="M36.5007 17.9996C36.5007 27.9397 28.4457 35.9997 18.5061 35.9997C13.0753 35.9997 8.20679 33.5926 4.90625 29.7855C8.06367 32.536 12.1925 34.1981 16.7076 34.1981C26.6447 34.1981 34.7023 26.1407 34.7023 16.1981C34.7023 11.6942 33.0495 7.57683 30.3138 4.41846C34.1045 7.71746 36.4994 12.5773 36.4994 17.9983L36.5007 17.9996Z" fill="#F9B700" style="fill:#F9B700;fill:color(display-p3 0.9765 0.7176 0.0000);fill-opacity:1;"/>
+<path d="M31.5749 15.8719C31.2836 15.8719 30.9961 15.7389 30.8074 15.488C29.2357 13.3825 27.1383 12.1979 24.3963 11.8673C23.872 11.8039 23.4984 11.3276 23.5617 10.8031C23.625 10.2786 24.1012 9.90357 24.6256 9.96818C27.8691 10.3597 30.4642 11.8318 32.3412 14.344C32.6578 14.7672 32.5704 15.3664 32.1474 15.6832C31.9751 15.8111 31.775 15.8732 31.5762 15.8732L31.5749 15.8719Z" fill="#664100" style="fill:#664100;fill:color(display-p3 0.4000 0.2549 0.0000);fill-opacity:1;"/>
+<path d="M28.3169 17.2152C28.3511 17.1417 28.3777 17.0632 28.3929 16.9796C28.4777 16.4855 28.1459 16.018 27.6532 15.9331C24.4439 15.382 21.9286 15.8191 19.9642 17.2684C19.5615 17.5649 19.4753 18.1325 19.773 18.5366C19.9503 18.7773 20.2251 18.9052 20.5025 18.9052C20.6886 18.9052 20.8774 18.8482 21.0395 18.7279C22.0236 18.0019 23.2166 17.6067 24.6478 17.5319C24.6275 17.6485 24.6148 17.7676 24.6148 17.8905C24.6148 18.9927 25.4697 19.8871 26.5235 19.8871C27.5772 19.8871 28.4321 18.9927 28.4321 17.8905C28.4321 17.6536 28.3903 17.4268 28.3181 17.2152H28.3169Z" fill="#664100" style="fill:#664100;fill:color(display-p3 0.4000 0.2549 0.0000);fill-opacity:1;"/>
+<path d="M5.46254 15.8719C5.75384 15.8719 6.04134 15.7389 6.23005 15.488C7.80179 13.3825 9.89914 12.1979 12.6411 11.8673C13.1655 11.8039 13.5391 11.3276 13.4758 10.8031C13.4125 10.2786 12.9362 9.90357 12.4119 9.96818C9.16836 10.3597 6.57327 11.8318 4.6963 14.344C4.37967 14.7672 4.46706 15.3664 4.89008 15.6832C5.06232 15.8111 5.26243 15.8732 5.46128 15.8732L5.46254 15.8719Z" fill="#664100" style="fill:#664100;fill:color(display-p3 0.4000 0.2549 0.0000);fill-opacity:1;"/>
+<path d="M16.2016 17.2152C16.2358 17.1417 16.2624 17.0632 16.2776 16.9796C16.3625 16.4855 16.0307 16.018 15.538 15.9331C12.3286 15.382 9.81333 15.8191 7.84897 17.2684C7.44622 17.5649 7.36009 18.1325 7.65772 18.5366C7.83504 18.7773 8.10987 18.9052 8.38724 18.9052C8.57341 18.9052 8.76212 18.8482 8.92424 18.7279C9.90832 18.0019 11.1014 17.6067 12.5325 17.5319C12.5123 17.6485 12.4996 17.7676 12.4996 17.8905C12.4996 18.9927 13.3545 19.8871 14.4082 19.8871C15.462 19.8871 16.3169 18.9927 16.3169 17.8905C16.3169 17.6536 16.2751 17.4268 16.2029 17.2152H16.2016Z" fill="#664100" style="fill:#664100;fill:color(display-p3 0.4000 0.2549 0.0000);fill-opacity:1;"/>
+<path d="M19.74 25.8277C20.9977 25.4501 22.5504 24.619 24.2032 23.6321C24.3299 23.5574 24.4856 23.5511 24.6174 23.6157C24.8111 23.7119 24.9049 23.9387 24.8314 24.1427C24.3438 25.5097 23.5282 26.5739 22.1426 27.1148C21.5879 27.3315 20.9926 27.4278 20.3961 27.4278H13.3922C13.1554 27.4278 12.9629 27.2352 12.9629 26.9983C12.9629 26.7652 13.1478 26.5739 13.3808 26.5688C15.7366 26.513 17.9048 26.3065 19.74 25.8277Z" fill="#664100" style="fill:#664100;fill:color(display-p3 0.4000 0.2549 0.0000);fill-opacity:1;"/>
+</svg>

+ 10 - 0
assets/vectors/boost/woozy_face.svg

@@ -0,0 +1,10 @@
+<svg width="37" height="36" viewBox="0 0 37 36" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M18.4954 35.9975C28.4329 35.9975 36.4888 27.9394 36.4888 17.9992C36.4888 8.05907 28.4329 0.000976562 18.4954 0.000976562C8.5579 0.000976562 0.501953 8.05907 0.501953 17.9992C0.501953 27.9394 8.5579 35.9975 18.4954 35.9975Z" fill="#FDCA47" style="fill:#FDCA47;fill:color(display-p3 0.9922 0.7922 0.2784);fill-opacity:1;"/>
+<path d="M3.40752 11.9404C3.55317 10.6127 4.03824 9.34204 4.70316 8.1778C5.37569 7.01609 6.25085 5.96207 7.2704 5.07527C8.28868 4.1872 9.45259 3.4651 10.6963 2.95962C11.9413 2.46048 13.2674 2.1577 14.6023 2.20204C13.4459 2.86334 12.3542 3.47523 11.3233 4.1644C10.2911 4.84597 9.32089 5.58075 8.40266 6.3776C7.48444 7.17572 6.62193 8.03592 5.80376 8.96453C4.97799 9.8906 4.22062 10.8864 3.40625 11.9404H3.40752Z" fill="#FFE8BB" style="fill:#FFE8BB;fill:color(display-p3 1.0000 0.9098 0.7333);fill-opacity:1;"/>
+<path d="M36.5028 17.9997C36.5028 27.9394 28.4478 35.9992 18.5081 35.9992C13.0773 35.9992 8.20875 33.5922 4.9082 29.7853C8.06564 32.5356 12.1945 34.1977 16.7097 34.1977C26.6468 34.1977 34.7044 26.1405 34.7044 16.1982C34.7044 11.6945 33.0516 7.57722 30.3159 4.41895C34.1066 7.71784 36.5015 12.5775 36.5015 17.9984L36.5028 17.9997Z" fill="#F9B700" style="fill:#F9B700;fill:color(display-p3 0.9765 0.7176 0.0000);fill-opacity:1;"/>
+<path d="M31.2665 13.5376C31.2639 13.525 31.2601 13.5123 31.2563 13.4996C30.5357 10.8671 28.5472 8.69317 25.3455 7.03865C24.9009 6.80935 24.3538 6.98292 24.1246 7.42759C23.8953 7.87226 24.0688 8.41952 24.5134 8.64883C27.2845 10.0804 28.9183 11.8236 29.5073 13.9772C29.639 14.4599 30.138 14.745 30.6206 14.6119C31.0904 14.4827 31.3729 14.0076 31.2652 13.5364L31.2665 13.5376Z" fill="#664100" style="fill:#664100;fill:color(display-p3 0.4000 0.2549 0.0000);fill-opacity:1;"/>
+<path d="M14.3329 6.59011C14.3265 6.60151 14.3202 6.61291 14.3126 6.62431C12.8536 8.93126 10.3168 10.43 6.77176 11.0748C6.27909 11.1647 5.80793 10.8379 5.71801 10.3451C5.62809 9.85227 5.95485 9.381 6.44753 9.29106C9.51631 8.73237 11.5871 7.54278 12.7801 5.65516C13.0474 5.23203 13.6072 5.10535 14.0302 5.37393C14.4418 5.6349 14.5723 6.17204 14.3316 6.59137L14.3329 6.59011Z" fill="#664100" style="fill:#664100;fill:color(display-p3 0.4000 0.2549 0.0000);fill-opacity:1;"/>
+<path d="M21.0306 13.3509C21.5296 12.1183 22.9405 11.5241 24.1906 11.9739C25.5204 12.4515 26.7756 13.7842 26.6248 15.7757C26.5451 17.2668 26.2221 18.4703 25.4837 19.1798C24.9125 19.7283 24.0639 19.8563 23.3306 19.5573C22.3529 19.1595 21.56 18.1751 20.9863 16.4928C20.9508 16.3889 20.923 16.2825 20.9027 16.1748C20.6874 15.0359 20.7292 14.0933 21.0294 13.3509H21.0306Z" fill="#664100" style="fill:#664100;fill:color(display-p3 0.4000 0.2549 0.0000);fill-opacity:1;"/>
+<path d="M13.5244 11.2765C13.9081 11.2587 14.2906 11.3322 14.6275 11.5413C15.6458 12.1734 15.5977 13.5556 15.4115 14.5995C15.2658 15.4102 15.0088 16.2704 14.6123 16.9925C14.24 17.6703 13.6928 18.1061 12.9013 18.0668C11.9489 18.0187 10.8761 17.4562 10.6291 16.4744C10.3631 15.4153 10.4632 14.2701 10.8938 13.268C11.2206 12.5079 11.7576 11.8377 12.5352 11.5109C12.8455 11.3804 13.185 11.2917 13.5231 11.2765H13.5244Z" fill="#664100" style="fill:#664100;fill:color(display-p3 0.4000 0.2549 0.0000);fill-opacity:1;"/>
+<path d="M23.7586 29.3023C22.9962 29.3023 22.2325 29.0768 21.6169 28.6208C21.1369 28.2648 20.7139 27.8607 20.3048 27.4692C19.9781 27.1563 19.6399 26.8332 19.2701 26.5317C18.6431 26.0199 17.7996 25.892 17.0701 26.1998C16.6572 26.3734 16.3431 26.6407 15.9454 26.9777C15.6731 27.2082 15.3629 27.4705 14.9842 27.7314C13.8506 28.5093 11.923 28.5283 10.7743 27.772C10.1587 27.3666 9.59766 26.8332 9.05812 26.139C8.75036 25.7437 8.82255 25.1749 9.21771 24.8671C9.61286 24.5592 10.1815 24.6314 10.4893 25.0267C10.9047 25.5613 11.3239 25.9642 11.771 26.2581C12.3156 26.6166 13.42 26.6065 13.9595 26.2365C14.2622 26.0288 14.5231 25.8083 14.7752 25.5942C15.2299 25.2091 15.6997 24.8113 16.3672 24.5301C17.7173 23.96 19.2688 24.1893 20.4175 25.1268C20.8431 25.4739 21.2066 25.8223 21.5587 26.1593C21.945 26.5279 22.3084 26.8763 22.6973 27.1652C23.3153 27.6238 24.3602 27.587 24.9327 27.0866C26.3373 25.8578 27.196 24.2488 27.5557 22.1674C27.6405 21.6746 28.1104 21.3439 28.6031 21.4288C29.0957 21.5137 29.4263 21.9824 29.3414 22.4765C28.9083 24.9836 27.8558 26.9384 26.1257 28.4523C25.4798 29.0173 24.6199 29.3036 23.7586 29.3036V29.3023Z" fill="#664100" style="fill:#664100;fill:color(display-p3 0.4000 0.2549 0.0000);fill-opacity:1;"/>
+</svg>

+ 9 - 0
assets/vectors/boost/yawning_face.svg

@@ -0,0 +1,9 @@
+<svg width="37" height="36" viewBox="0 0 37 36" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M18.4954 33.8761C28.4329 33.8761 36.4888 26.2929 36.4888 16.9386C36.4888 7.58419 28.4329 0.000976562 18.4954 0.000976562C8.5579 0.000976562 0.501953 7.58419 0.501953 16.9386C0.501953 26.2929 8.5579 33.8761 18.4954 33.8761Z" fill="#FDCA47" style="fill:#FDCA47;fill:color(display-p3 0.9922 0.7922 0.2784);fill-opacity:1;"/>
+<path d="M3.4075 11.2378C3.55315 9.98837 4.03824 8.7926 4.70317 7.69696C5.37569 6.60372 6.25084 5.61181 7.27039 4.77727C8.28867 3.94154 9.45261 3.26199 10.6963 2.7863C11.9413 2.31658 13.2674 2.03164 14.6023 2.07337C13.4459 2.6957 12.3542 3.27153 11.3233 3.92009C10.291 4.56149 9.32089 5.25296 8.40266 6.00285C7.48444 6.75394 6.62194 7.56343 5.80376 8.43732C4.97799 9.30881 4.22062 10.2459 3.40625 11.2378H3.4075Z" fill="#FFE8BB" style="fill:#FFE8BB;fill:color(display-p3 1.0000 0.9098 0.7333);fill-opacity:1;"/>
+<path d="M36.5028 16.941C36.5028 26.295 28.4477 33.8798 18.5081 33.8798C13.0773 33.8798 8.20875 31.6146 4.9082 28.032C8.06564 30.6203 12.1945 32.1845 16.7096 32.1845C26.6467 32.1845 34.7044 24.6021 34.7044 15.2457C34.7044 11.0074 33.0515 7.1328 30.3159 4.16064C34.1066 7.26513 36.5015 11.8384 36.5015 16.9398L36.5028 16.941Z" fill="#F9B700" style="fill:#F9B700;fill:color(display-p3 0.9765 0.7176 0.0000);fill-opacity:1;"/>
+<path d="M18.4845 26.8481C22.5673 26.8481 25.8771 23.5244 25.8771 19.4243C25.8771 15.3242 22.5673 12.0005 18.4845 12.0005C14.4016 12.0005 11.0918 15.3242 11.0918 19.4243C11.0918 23.5244 14.4016 26.8481 18.4845 26.8481Z" fill="#664100" style="fill:#664100;fill:color(display-p3 0.4000 0.2549 0.0000);fill-opacity:1;"/>
+<path d="M28.8776 9.57654C28.6699 9.57654 28.4596 9.51334 28.2823 9.38339C26.38 7.98971 24.27 7.47707 21.8332 7.81566C21.3038 7.88838 20.8098 7.54503 20.7313 7.0455C20.6528 6.54716 21.0188 6.08221 21.5495 6.00829C24.4979 5.59818 27.164 6.24792 29.4741 7.94204C29.8971 8.25201 29.9731 8.82546 29.6438 9.22365C29.4526 9.45494 29.1663 9.57534 28.8776 9.57534V9.57654Z" fill="#664100" style="fill:#664100;fill:color(display-p3 0.4000 0.2549 0.0000);fill-opacity:1;"/>
+<path d="M8.10381 9.57654C8.31152 9.57654 8.52176 9.51334 8.69907 9.38339C10.6014 7.98971 12.7114 7.47707 15.1482 7.81566C15.6776 7.88838 16.1715 7.54503 16.2501 7.0455C16.3286 6.54716 15.9626 6.08221 15.4319 6.00829C12.4834 5.59818 9.81742 6.24792 7.50729 7.94204C7.08427 8.25201 7.00827 8.82546 7.33756 9.22365C7.5288 9.45494 7.81504 9.57534 8.10381 9.57534V9.57654Z" fill="#664100" style="fill:#664100;fill:color(display-p3 0.4000 0.2549 0.0000);fill-opacity:1;"/>
+<path d="M23.5013 25.8809C23.1644 25.4577 22.516 25.3933 22.0917 25.7402L18.0084 29.0927L17.3004 29.6733L16.6798 28.8196L17.9578 27.6751L22.9909 23.1651C23.1999 22.9779 23.3063 22.7263 23.3063 22.4712C23.3063 22.2733 23.2417 22.0742 23.1087 21.9049C22.7541 21.4495 22.0613 21.3815 21.6129 21.7571L17.2485 25.4123L15.5362 26.8466L14.8206 26.0848L16.6646 24.4574L21.7295 19.9866C21.9372 19.8042 22.041 19.5586 22.041 19.3119C22.041 19.0853 21.9524 18.8576 21.7763 18.68C21.4078 18.3092 20.791 18.2854 20.3933 18.6275L15.2955 23.0101L13.3907 24.6481L12.5269 23.8851L14.2658 22.3055L18.9317 18.0648C19.1331 17.8824 19.2344 17.638 19.2344 17.3924C19.2344 17.185 19.1622 16.9763 19.0128 16.8047C18.6518 16.3862 17.992 16.3409 17.5702 16.7057C17.5702 16.7057 12.076 21.4614 12.0748 21.4614L9.5164 23.6753C9.49233 23.6956 9.467 23.7135 9.43787 23.729C9.08958 23.9138 8.68809 23.5823 8.83754 23.233L9.66712 21.3016L10.6107 19.1044C10.6689 18.9685 10.6981 18.8266 10.6981 18.6872C10.6981 18.2925 10.4726 17.9134 10.0838 17.7107C9.53161 17.4246 8.83881 17.5963 8.50698 18.1018C7.35825 19.8543 6.36277 21.7082 5.00126 23.3296C3.64229 24.9486 2.6582 26.4877 2.6582 28.636C2.6582 32.7014 6.09679 35.9991 10.3384 35.9991C12.4763 35.9991 14.4128 35.1598 15.8047 33.8078L15.8085 33.8042L23.3861 27.0898C23.5874 26.9121 23.6913 26.6689 23.6913 26.4257C23.6913 26.235 23.6267 26.043 23.4962 25.8797L23.5013 25.8809Z" fill="#F49104" style="fill:#F49104;fill:color(display-p3 0.9569 0.5686 0.0157);fill-opacity:1;"/>
+</svg>

+ 22 - 3
ios/Runner/CoreApi.g.swift

@@ -92,7 +92,7 @@ var coreApiPigeonMethodCodec = FlutterStandardMethodCodec(readerWriter: CoreApiP
 protocol CoreApi {
   func getApps(completion: @escaping (Result<String?, Error>) -> Void)
   func getSystemLocale() throws -> String?
-  func connect() throws -> Bool?
+  func connect(sessionId: String, socksPort: Int64, tunnelConfig: String, configJson: String) throws -> Bool?
   func disconnect() throws -> Bool?
   func getRemoteIp() throws -> String?
   func getAdvertisingId() throws -> String?
@@ -100,6 +100,7 @@ protocol CoreApi {
   func isConnected() throws -> Bool?
   func getSimInfo() throws -> String?
   func reconnect() throws -> Bool?
+  func getChannel() throws -> String?
 }
 
 /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
@@ -138,9 +139,14 @@ class CoreApiSetup {
     }
     let connectChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.app.xixi.nomo.CoreApi.connect\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
     if let api = api {
-      connectChannel.setMessageHandler { _, reply in
+      connectChannel.setMessageHandler { message, reply in
+        let args = message as! [Any?]
+        let sessionIdArg = args[0] as! String
+        let socksPortArg = args[1] as! Int64
+        let tunnelConfigArg = args[2] as! String
+        let configJsonArg = args[3] as! String
         do {
-          let result = try api.connect()
+          let result = try api.connect(sessionId: sessionIdArg, socksPort: socksPortArg, tunnelConfig: tunnelConfigArg, configJson: configJsonArg)
           reply(wrapResult(result))
         } catch {
           reply(wrapError(error))
@@ -240,6 +246,19 @@ class CoreApiSetup {
     } else {
       reconnectChannel.setMessageHandler(nil)
     }
+    let getChannelChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.app.xixi.nomo.CoreApi.getChannel\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
+    if let api = api {
+      getChannelChannel.setMessageHandler { _, reply in
+        do {
+          let result = try api.getChannel()
+          reply(wrapResult(result))
+        } catch {
+          reply(wrapError(error))
+        }
+      }
+    } else {
+      getChannelChannel.setMessageHandler(nil)
+    }
   }
 }
 

+ 0 - 63
lib/app/api/core/api_core.dart

@@ -262,16 +262,6 @@ class ApiCore extends BaseApi {
     return post(ApiCorePaths.launch, data: data);
   }
 
-  /// 游戏列表
-  Future<ApiResult> getGameList(dynamic data) async {
-    return post(ApiCorePaths.gameList, data: data);
-  }
-
-  /// 根据本地游戏获取游戏列表
-  Future<ApiResult> getLocalGameList(dynamic data) async {
-    return post(ApiCorePaths.localGameList, data: data);
-  }
-
   /// 注册
   Future<ApiResult> register(dynamic data) async {
     return post(ApiCorePaths.register, data: data);
@@ -327,36 +317,11 @@ class ApiCore extends BaseApi {
     return post(ApiCorePaths.getUserConfig, data: data);
   }
 
-  /// 获取广告信息
-  Future<ApiResult> getAdConfig(dynamic data) async {
-    return post(ApiCorePaths.getAdConfig, data: data);
-  }
-
-  /// 提交游戏
-  Future<ApiResult> submitGame(dynamic data) async {
-    return post(ApiCorePaths.submitGame, data: data);
-  }
-
   /// 获取banner
   Future<ApiResult> getBanner(dynamic data) async {
     return post(ApiCorePaths.banner, data: data);
   }
 
-  /// 搜索游戏
-  Future<ApiResult> searchGame(dynamic data) async {
-    return post(ApiCorePaths.searchGame, data: data);
-  }
-
-  /// 游戏搜索列表
-  Future<ApiResult> searchGameList(dynamic data) async {
-    return post(ApiCorePaths.searchGameList, data: data);
-  }
-
-  /// 获取搜索结果游戏列表
-  Future<ApiResult> gameListSearch(dynamic data) async {
-    return post(ApiCorePaths.gameListSearch, data: data);
-  }
-
   /// 获取支持的第三方登录
   Future<ApiResult> providerList(dynamic data) async {
     return post(ApiCorePaths.providerList, data: data);
@@ -371,32 +336,4 @@ class ApiCore extends BaseApi {
   Future<ApiResult> providerLoginCheck(dynamic data) async {
     return post(ApiCorePaths.providerLoginCheck, data: data);
   }
-
-  /// 第三方登录检测回调
-  Future<ApiResult> reportActive(dynamic data) async {
-    return post(ApiCorePaths.reportActive, data: data);
-  }
-
-  /// 上传日志
-  Future<ApiResult> uploadLogFile(dynamic data, String file) async {
-    final String encryptedData = encrypt(data);
-
-    final fileData = await MultipartFile.fromFile(file);
-    final formData = FormData.fromMap({
-      'data': encryptedData,
-      'file': fileData,
-    });
-    final options = Options(headers: {'context-type': 'form-data'});
-
-    final response = await rawRequest(
-      RequestMethod.post,
-      ApiCorePaths.uploadLog,
-      data: formData,
-      options: options,
-    );
-
-    final result = decrypt(response);
-
-    return getApiResult(result);
-  }
 }

+ 4 - 25
lib/app/api/core/api_core_paths.dart

@@ -5,18 +5,12 @@ class ApiCorePaths {
   /// API版本
   static const String _ver = '/api/v1';
 
-  /// 获取全量登录数据
+  /// 获取全量数据
   static const String launch = '$_ver/app/launch';
 
   /// 刷新token
   static const String refreshToken = '$_ver/user/refreshToken';
 
-  /// 获取游戏列表
-  static const String gameList = '$_ver/game/gameList';
-
-  /// 根据本地游戏获取游戏列表
-  static const String localGameList = '$_ver/game/localGameList';
-
   /// 注册
   static const String register = '$_ver/user/register';
 
@@ -52,24 +46,9 @@ class ApiCorePaths {
   /// 获取用户信息
   static const String getUserConfig = '$_ver/app/getUserConfig';
 
-  /// 获取广告信息
-  static const String getAdConfig = '$_ver/app/getAdConfig';
-
-  /// 提交游戏
-  static const String submitGame = '$_ver/game/gameSubmit';
-
   /// 获取banner
   static const String banner = '$_ver/app/bannerList';
 
-  /// 搜索游戏
-  static const String searchGame = '$_ver/game/gameSearch';
-
-  /// 游戏搜索列表
-  static const String searchGameList = '$_ver/game/gameAll';
-
-  /// 获取搜索结果游戏列表
-  static const String gameListSearch = '$_ver/game/gameListSearch';
-
   /// 获取支持的第三方登录
   static const String providerList = '$_ver/user/providerList';
 
@@ -79,9 +58,6 @@ class ApiCorePaths {
   /// 第三方登录检测回调
   static const String providerLoginCheck = '$_ver/user/providerLoginCheck';
 
-  /// 用户活跃指标
-  static const String reportActive = '$_ver/app/reportActive';
-
   /// 日志上传
   static const String uploadLog = '$_ver/event';
 
@@ -93,4 +69,7 @@ class ApiCorePaths {
 
   /// 上传视频
   static const String uploadVideo = '$_ver/issue/uploadVideo';
+
+  /// 获取调度信息
+  static const String getDispatchInfo = '$_ver/app/getNodes';
 }

+ 135 - 0
lib/app/api/router/api_router.dart

@@ -0,0 +1,135 @@
+import 'dart:convert';
+
+import 'package:archive/archive.dart';
+import 'package:dio/dio.dart';
+
+import '../../../utils/crypto.dart';
+import '../../../utils/developer/ix_developer_tools.dart';
+import '../../../utils/log/logger.dart';
+import '../../constants/keys.dart';
+import '../../data/models/api_exception.dart';
+import '../../data/models/api_result.dart';
+import '../../data/models/disconnect_domain.dart';
+import '../../data/sp/ix_sp.dart';
+import '../base/base_api.dart';
+
+import '../core/api_core_paths.dart';
+
+/// 核心API
+class ApiRouter extends BaseApi {
+  static final _instance = ApiRouter._internal();
+
+  factory ApiRouter() => _instance;
+
+  ApiRouter._internal() {
+    _initDio();
+  }
+
+  /// 初始化Dio
+  void _initDio() {
+    // 此处可以设置默认的DIO参数
+    final options = BaseOptions();
+    options.connectTimeout = const Duration(seconds: 15);
+    options.receiveTimeout = const Duration(seconds: 15);
+    options.responseType = ResponseType.bytes;
+    options.headers['content-type'] = 'application/json';
+
+    dio = Dio(options);
+
+    dio.interceptors.add(SimpleApiMonitorInterceptor());
+
+    // 添加拦截器
+    _setupInterceptors();
+  }
+
+  // bool _isRefreshing = false;
+
+  void _setupInterceptors() {
+    dio.interceptors.add(
+      InterceptorsWrapper(
+        onRequest: (options, handler) async {
+          // 在请求发送前添加token
+          final user = IXSP.getUser();
+          if (user != null) {
+            options.headers['Authorization'] = user.accessToken;
+          }
+          return handler.next(options);
+        },
+        onError: (DioException error, handler) async {
+          IXSP.addDisconnectDomain(
+            DisconnectDomain(
+              domain: error.requestOptions.baseUrl,
+              status: error.response?.statusCode ?? -1,
+              msg: error.message ?? '',
+              startTime: DateTime.now().millisecondsSinceEpoch,
+              endTime: DateTime.now().millisecondsSinceEpoch,
+              count: 1,
+            ),
+          );
+          return handler.next(error);
+        },
+      ),
+    );
+  }
+
+  @override
+  Map<String, dynamic>? getDefaultHeader() {
+    // 可以设置自定义Header
+    final headers = {
+      'X-NL-Product-Code': 'nomo',
+      'X-NL-Content-Encoding': 'gzip',
+    };
+    return headers;
+  }
+
+  @override
+  Map<String, dynamic>? getDefaultQuery() {
+    // 可以设置自定义Query
+    return null;
+  }
+
+  @override
+  dynamic encrypt(dynamic input) {
+    try {
+      final data = jsonEncode(input);
+      final bytes = utf8.encode(data);
+      final gzipEncoder = GZipEncoder();
+      final compressedBytes = gzipEncoder.encode(bytes);
+      log('ApiLog', '>>: $data');
+      return Crypto.encryptBytesUint8(compressedBytes, Keys.aesKey, Keys.aesIv);
+    } catch (error) {
+      throw ApiException("-2", "encrypt error: $error");
+    }
+  }
+
+  @override
+  dynamic decrypt(dynamic input) {
+    try {
+      final decryptedBytes = Crypto.decryptBytes(
+        base64Encode(input),
+        Keys.aesKey,
+        Keys.aesIv,
+      );
+      // 使用GZip解压
+      final gzipDecoder = GZipDecoder();
+      final decodeBytes = gzipDecoder.decodeBytes(decryptedBytes);
+      final data = utf8.decode(decodeBytes);
+      log('ApiLog', '<<:$data');
+      return jsonDecode(data);
+    } catch (error) {
+      throw ApiException("-2", "decrypt error: $error");
+    }
+  }
+
+  /// 获取调度信息
+  Future<ApiResult> getDispatchInfo(
+    dynamic data, {
+    CancelToken? cancelToken,
+  }) async {
+    return post(
+      ApiCorePaths.getDispatchInfo,
+      data: data,
+      cancelToken: cancelToken,
+    );
+  }
+}

+ 23 - 2
lib/app/app.dart

@@ -109,11 +109,32 @@ class App extends StatelessWidget {
                 onTap: () {
                   // 使用简化版开发者工具
                   IXDeveloperTools.show();
-                  // 切换主题
-                  // IXTheme.changeTheme();
                 },
               ),
             ),
+
+          Positioned(
+            bottom: 300,
+            right: 10,
+            child: ClickOpacity(
+              child: Container(
+                width: 40,
+                height: 40,
+                decoration: BoxDecoration(
+                  color: Get.reactiveTheme.primaryColor.withValues(alpha: 0.5),
+                  borderRadius: BorderRadius.circular(40),
+                ),
+                child: Icon(
+                  ReactiveTheme.isLightTheme ? Icons.sunny : Icons.nightlight,
+                  color: Get.reactiveTheme.primaryColor,
+                ),
+              ),
+              onTap: () {
+                // 切换主题
+                IXTheme.changeTheme();
+              },
+            ),
+          ),
         ],
       ),
     );

+ 1 - 1
lib/app/components/country_restricted_overlay.dart

@@ -57,7 +57,7 @@ class CountryRestrictedOverlay extends StatelessWidget {
     return WillPopScope(
       onWillPop: () async => false, // 禁止返回
       child: Scaffold(
-        backgroundColor: Get.reactiveTheme.dialogTheme.backgroundColor,
+        backgroundColor: Get.reactiveTheme.scaffoldBackgroundColor,
         body: SafeArea(
           child: Stack(
             alignment: Alignment.center,

+ 36 - 29
lib/app/components/ix_snackbar.dart

@@ -1,5 +1,6 @@
 import 'package:flutter/material.dart';
 import 'package:get/get.dart';
+import 'package:nomo/config/theme/theme_extensions/theme_extension.dart';
 
 import '../constants/assets.dart';
 
@@ -19,8 +20,11 @@ class IXSnackBar {
     return true;
   }
 
-  static showIXSnackBar(
-      {required String title, required String message, Duration? duration}) {
+  static showIXSnackBar({
+    required String title,
+    required String message,
+    Duration? duration,
+  }) {
     if (!_shouldShowSnackbar(message)) return;
     // Get.snackbar(
     //   title,
@@ -39,8 +43,8 @@ class IXSnackBar {
     //   ),
     // );
     Get.rawSnackbar(
-      message: message,
-      backgroundColor: Colors.transparent, // 先让背景透明
+      // message: message,
+      backgroundColor: Get.reactiveTheme.cardColor, // 先让背景透明
       snackStyle: SnackStyle.floating, // 悬浮样式
       duration: duration ?? const Duration(seconds: 3),
       margin: const EdgeInsets.only(top: 10, left: 10, right: 10),
@@ -50,19 +54,20 @@ class IXSnackBar {
       // borderColor: ,
       borderWidth: 1,
       borderRadius: 16,
-      icon: Image.asset(
-        Assets.success,
-        width: 20,
-        height: 20,
+      icon: Image.asset(Assets.success, width: 20, height: 20),
+      messageText: Text(
+        message,
+        style: TextStyle(color: Get.reactiveTheme.textTheme.bodyLarge!.color),
       ),
     );
   }
 
-  static showIXErrorSnackBar(
-      {required String title,
-      required String message,
-      Color? color,
-      Duration? duration}) {
+  static showIXErrorSnackBar({
+    required String title,
+    required String message,
+    Color? color,
+    Duration? duration,
+  }) {
     if (!_shouldShowSnackbar(message)) return;
     // Get.snackbar(
     //   title,
@@ -82,8 +87,8 @@ class IXSnackBar {
     // );
 
     Get.rawSnackbar(
-      message: message,
-      backgroundColor: Colors.transparent, // 先让背景透明
+      // message: message,
+      backgroundColor: Get.reactiveTheme.cardColor, // 先让背景透明
       snackStyle: SnackStyle.floating, // 悬浮样式
       duration: duration ?? const Duration(seconds: 3),
       margin: const EdgeInsets.only(top: 10, left: 10, right: 10),
@@ -93,19 +98,20 @@ class IXSnackBar {
       // borderColor: Palette.snackbarBorderColor,
       borderWidth: 1,
       borderRadius: 16,
-      icon: Image.asset(
-        Assets.error,
-        width: 20,
-        height: 20,
+      icon: Image.asset(Assets.error, width: 20, height: 20),
+      messageText: Text(
+        message,
+        style: TextStyle(color: Get.reactiveTheme.textTheme.bodyLarge!.color),
       ),
     );
   }
 
-  static showIXToast(
-      {String? title,
-      required String message,
-      Color? color,
-      Duration? duration}) {
+  static showIXToast({
+    String? title,
+    required String message,
+    Color? color,
+    Duration? duration,
+  }) {
     if (!_shouldShowSnackbar(message)) return;
     Get.rawSnackbar(
       title: title,
@@ -120,11 +126,12 @@ class IXSnackBar {
     );
   }
 
-  static showIXErrorToast(
-      {String? title,
-      required String message,
-      Color? color,
-      Duration? duration}) {
+  static showIXErrorToast({
+    String? title,
+    required String message,
+    Color? color,
+    Duration? duration,
+  }) {
     if (!_shouldShowSnackbar(message)) return;
     Get.rawSnackbar(
       title: title,

+ 87 - 5
lib/app/constants/api_domains.dart

@@ -3,6 +3,7 @@ import 'dart:io';
 
 import 'package:dio/dio.dart';
 import 'package:dio/io.dart';
+import 'package:nomo/app/api/router/api_router.dart';
 import 'package:shared_preferences/shared_preferences.dart';
 import '../../utils/crypto.dart';
 import '../../utils/developer/ix_developer_tools.dart';
@@ -24,10 +25,12 @@ class ApiDomains {
 
   // 常量定义
   static const String _STORAGE_API_URLS = 'api_urls';
+  static const String _STORAGE_ROUTER_URLS = 'router_urls';
   static const String _STORAGE_LOG_URLS = 'log_urls';
   static const String _STORAGE_FILE_URLS = 'file_urls';
   static const String _STORAGE_BACKUP_URLS = 'backup_api_urls';
   static const String _STORAGE_API_URLS_INDEX = 'api_urls_index';
+  static const String _STORAGE_ROUTER_URLS_INDEX = 'router_urls_index';
   static const String _STORAGE_LOG_URLS_INDEX = 'log_urls_index';
   static const String _STORAGE_FILE_URLS_INDEX = 'file_urls_index';
   static const String _STORAGE_IS_HARDCODED = 'is_hardcoded';
@@ -69,6 +72,9 @@ class ApiDomains {
     "https://service.fkey.club",
   ];
 
+  // 默认Router URL列表
+  final List<String> _defaultRouterUrls = [];
+
   // 默认LOG API URL列表
   final List<String> _defaultLogUrls = ['https://stat.fkey.win'];
 
@@ -84,6 +90,9 @@ class ApiDomains {
   // 当前使用的URL列表
   List<String> _apiUrls = [];
 
+  // 当前使用的Router URL列表
+  List<String> _routerUrls = [];
+
   // 当前使用的LogURL列表
   List<String> _logUrls = [];
 
@@ -96,6 +105,9 @@ class ApiDomains {
   // 当前尝试的索引
   int _currentIndex = 0;
 
+  // 当前使用的Router URL索引
+  int _currentRouterIndex = 0;
+
   // 当前使用的LogURL索引
   int _currentLogIndex = 0;
 
@@ -124,11 +136,13 @@ class ApiDomains {
         // 'https://api.znomo.com', // 测试环境
         "https://nomo-api.clickto.dev", // 开发环境
       ];
+      _routerUrls = [];
       _logUrls = [];
       _fileUrls = [];
       _backupApiUrls = [];
     } else {
       _apiUrls = List.from(_defaultApiUrls);
+      _routerUrls = List.from(_defaultRouterUrls);
       _logUrls = List.from(_defaultLogUrls);
       _fileUrls = List.from(_defaultFileUrls);
       _backupApiUrls = List.from(_defaultBackupApiUrls);
@@ -140,11 +154,13 @@ class ApiDomains {
     try {
       final prefs = await SharedPreferences.getInstance();
       _currentIndex = prefs.getInt(_STORAGE_API_URLS_INDEX) ?? 0;
+      _currentRouterIndex = prefs.getInt(_STORAGE_ROUTER_URLS_INDEX) ?? 0;
       _currentLogIndex = prefs.getInt(_STORAGE_LOG_URLS_INDEX) ?? 0;
       _currentFileIndex = prefs.getInt(_STORAGE_FILE_URLS_INDEX) ?? 0;
       _isUsingHardcodedUrls = prefs.getBool(_STORAGE_IS_HARDCODED) ?? true;
 
       final savedApiUrls = prefs.getStringList(_STORAGE_API_URLS);
+      final savedRouterUrls = prefs.getStringList(_STORAGE_ROUTER_URLS);
       final savedBackupUrls = prefs.getStringList(_STORAGE_BACKUP_URLS);
 
       final savedLogUrls = prefs.getStringList(_STORAGE_LOG_URLS);
@@ -154,6 +170,9 @@ class ApiDomains {
       if (savedApiUrls != null && savedApiUrls.isNotEmpty) {
         _apiUrls = savedApiUrls;
       }
+      if (savedRouterUrls != null && savedRouterUrls.isNotEmpty) {
+        _routerUrls = savedRouterUrls;
+      }
       if (savedBackupUrls != null && savedBackupUrls.isNotEmpty) {
         _backupApiUrls = savedBackupUrls;
       }
@@ -184,6 +203,18 @@ class ApiDomains {
     }
   }
 
+  // 添加Router URL
+  Future<void> addRouterUrls(List<String> urls) async {
+    try {
+      var routerList = List<String>.from(_routerUrls);
+      routerList.insertAll(0, urls);
+      _routerUrls = routerList.toSet().toList();
+      await _saveRouterUrls();
+    } catch (e) {
+      log('ApiDomains', 'Error add Router URL: $e');
+    }
+  }
+
   // 添加logUrl
   Future<void> addLogUrls(List<String> urls) async {
     try {
@@ -219,6 +250,16 @@ class ApiDomains {
     }
   }
 
+  // 保存当前Router URL列表到持久化存储
+  Future<void> _saveRouterUrls() async {
+    try {
+      final prefs = await SharedPreferences.getInstance();
+      await prefs.setStringList(_STORAGE_ROUTER_URLS, _routerUrls);
+    } catch (e) {
+      log('ApiDomains', 'Error saving Router URLs: $e');
+    }
+  }
+
   // 保存当前LogURL列表到持久化存储
   Future<void> _saveLogUrls() async {
     try {
@@ -259,6 +300,16 @@ class ApiDomains {
     }
   }
 
+  // 保存硬编码索引
+  Future<void> _saveRouterUrlsIndex() async {
+    try {
+      final prefs = await SharedPreferences.getInstance();
+      await prefs.setInt(_STORAGE_ROUTER_URLS_INDEX, _currentRouterIndex);
+    } catch (e) {
+      log('ApiDomains', 'Error saving Router URLs index: $e');
+    }
+  }
+
   // 保存LogURL硬编码索引
   Future<void> _saveLogUrlsIndex() async {
     try {
@@ -297,6 +348,15 @@ class ApiDomains {
         log('ApiDomains', 'Add ApiCore baseUrl to index 0: $baseUrl');
       }
 
+      if (launch.appConfig?.routerApiUrls != null &&
+          launch.appConfig!.routerApiUrls!.isNotEmpty) {
+        _routerUrls = launch.appConfig!.routerApiUrls!;
+        final baseUrl = _routerUrls[0];
+        ApiRouter().setbaseUrl(baseUrl);
+        _currentRouterIndex = 0;
+        log('ApiDomains', 'Add Router baseUrl to index 0: $baseUrl');
+      }
+
       if (launch.appConfig?.backupApiUrls != null &&
           launch.appConfig!.backupApiUrls!.isNotEmpty) {
         _backupApiUrls = launch.appConfig!.backupApiUrls!;
@@ -327,6 +387,8 @@ class ApiDomains {
       await _saveIsHardcodedUrls();
       await _saveApiUrls();
       await _saveApiUrlsIndex();
+      await _saveRouterUrls();
+      await _saveRouterUrlsIndex();
       await _saveLogUrls();
       await _saveLogUrlsIndex();
       await _saveFileUrls();
@@ -345,6 +407,14 @@ class ApiDomains {
     return _apiUrls[_currentIndex];
   }
 
+  // 获取当前Router URL
+  String getRouterUrl() {
+    if (_currentRouterIndex >= _routerUrls.length) {
+      _currentRouterIndex = 0;
+    }
+    return _routerUrls[_currentRouterIndex];
+  }
+
   // 获取当前LogURL
   String getLogUrl() {
     if (_currentLogIndex >= _logUrls.length) {
@@ -416,6 +486,23 @@ class ApiDomains {
     }
   }
 
+  // 获取下一次要尝试的Router URL
+  Future<String> getNextRouterUrl() async {
+    // 获取下一个索引
+    int nextIndex = _currentRouterIndex + 1;
+    if (nextIndex >= _routerUrls.length) {
+      _currentRouterIndex = 0;
+      await _saveRouterUrlsIndex();
+      return '';
+    }
+
+    // 返回所有剩余的URL
+    final url = _routerUrls[nextIndex];
+    _currentRouterIndex++;
+    await _saveRouterUrlsIndex();
+    return url;
+  }
+
   // 获取下一次要尝试的LogURL
   Future<String> getNextLogUrl() async {
     // 获取下一个索引
@@ -508,9 +595,4 @@ class ApiDomains {
     await _saveApiUrlsIndex();
     return false;
   }
-
-  // 获取所有logUrl
-  List<String> getAllLogUrls() {
-    return _logUrls;
-  }
 }

+ 8 - 0
lib/app/constants/assets.dart

@@ -50,4 +50,12 @@ class Assets {
 
   static const String premium = 'assets/images/premium.png';
   static const String free = 'assets/images/free.png';
+
+  // 评价
+  static const String poutingFace = 'assets/vectors/boost/pouting_face.svg';
+  static const String woozyFace = 'assets/vectors/boost/woozy_face.svg';
+  static const String yawningFace = 'assets/vectors/boost/yawning_face.svg';
+  static const String smirkingFace = 'assets/vectors/boost/smirking_face.svg';
+  static const String smilingFaceWithHearts =
+      'assets/vectors/boost/smiling_face_with_hearts.svg';
 }

+ 2 - 2
lib/app/constants/configs.dart

@@ -13,7 +13,7 @@ class Configs {
   static var websiteUrl = "https://www.fkey.win";
 
   // 内核使用
-  static const String appName = 'IX VPN';
+  static const String appName = 'NOMO VPN';
   // api使用
-  static const String productCode = "iv_vpn";
+  static const String productCode = "nomo";
 }

+ 90 - 3
lib/app/controllers/api_controller.dart

@@ -4,6 +4,7 @@ import 'dart:io';
 import 'package:device_info_plus/device_info_plus.dart';
 import 'package:dio/dio.dart';
 import 'package:get/get.dart';
+import 'package:nomo/app/api/router/api_router.dart';
 import 'package:nomo/app/data/sp/ix_sp.dart';
 import 'package:package_info_plus/package_info_plus.dart';
 import 'package:play_install_referrer/play_install_referrer.dart';
@@ -53,7 +54,13 @@ class ApiController extends GetxService {
     if (Platform.isIOS) {
       fp.channel = 'apple';
     } else if (Platform.isAndroid) {
-      fp.channel = 'google';
+      try {
+        final channel = await CoreApi().getChannel();
+        fp.channel = channel ?? 'unknown';
+      } catch (e) {
+        log(TAG, 'read app channel error: $e');
+        fp.channel = 'unknown';
+      }
     }
     try {
       final advertisingId = await CoreApi().getAdvertisingId();
@@ -219,8 +226,13 @@ class ApiController extends GetxService {
         await initData(launchData);
 
         return launchData;
-      } on ApiException catch (_) {
-        rethrow;
+      } on ApiException catch (e) {
+        final url = await ApiDomains.instance.getNextApiUrl();
+        log(TAG, 'Launch request failed for URL $url: $e');
+        if (url.isEmpty) {
+          rethrow;
+        }
+        ApiCore().setbaseUrl(url);
       } on Failure catch (_) {
         rethrow;
       } on DioException catch (e) {
@@ -418,4 +430,79 @@ class ApiController extends GetxService {
     }
     return false;
   }
+
+  Future<Launch> getDispatchInfo({CancelToken? cancelToken}) async {
+    while (true) {
+      try {
+        ApiRouter().setbaseUrl(ApiDomains.instance.getRouterUrl());
+        final request = fp.toJson();
+        if (IXSP.getDisconnectDomains().isNotEmpty) {
+          final disconnectDomainList = IXSP
+              .getDisconnectDomains()
+              .map((e) => e.toJson())
+              .toList();
+          request['disconnectDomainList'] = disconnectDomainList;
+        }
+        request['locationId'] = 187;
+        request['locationCode'] = 'auto_mm';
+        final result = await ApiRouter().getDispatchInfo(
+          request,
+          cancelToken: cancelToken,
+        );
+
+        if (!result.success) {
+          throw Failure(
+            code: result.errorCode ?? '',
+            message: result.errorMessage ?? '',
+          );
+        }
+        if (IXSP.getDisconnectDomains().isNotEmpty) {
+          IXSP.clearDisconnectDomains();
+        }
+        // 重置禁用状态
+        IXSP.setLastIsRegionDisabled(false);
+        IXSP.setLastIsUserDisabled(false);
+
+        final launchData = Launch.fromJson(result.data);
+
+        // 更新URL列表
+        await ApiDomains.instance.updateFromLaunch(launchData);
+
+        // 保存app配置
+        await IXSP.saveAppConfig(launchData.appConfig!);
+        // 保存vpn配置
+        // await IXSP.saveVpnConfig(launchData.vpnConfig!);
+
+        return launchData;
+      } on ApiException catch (_) {
+        rethrow;
+      } on Failure catch (_) {
+        rethrow;
+      } on DioException catch (e) {
+        if (e.response?.statusCode == Errors.eRegionNotAvailable ||
+            e.response?.statusCode == Errors.eUserDisabled ||
+            e.response?.statusCode == Errors.eTokenExpired) {
+          rethrow;
+        } else {
+          if (await NetworkHelper.instance.isNetworkAvailable()) {
+            final url = await ApiDomains.instance.getNextRouterUrl();
+            log(TAG, 'Launch request failed for URL $url: $e');
+            if (url.isEmpty) {
+              rethrow;
+            }
+            ApiRouter().setbaseUrl(url);
+          } else {
+            rethrow;
+          }
+        }
+      } catch (e) {
+        final url = await ApiDomains.instance.getNextRouterUrl();
+        log(TAG, 'Launch request failed for URL $url: $e');
+        if (url.isEmpty) {
+          rethrow;
+        }
+        ApiRouter().setbaseUrl(url);
+      }
+    }
+  }
 }

+ 70 - 4
lib/app/controllers/core_controller.dart

@@ -1,17 +1,22 @@
 import 'dart:async';
 import 'dart:convert';
 
+import 'package:dio/dio.dart';
 import 'package:get/get.dart';
+import 'package:uuid/uuid.dart';
 
 import '../../pigeons/core_api.g.dart';
 import '../../utils/haptic_feedback_manager.dart';
 import '../../utils/log/logger.dart';
 import '../constants/enums.dart';
 import '../data/models/vpn_message.dart';
+import '../dialog/error_dialog.dart';
+import '../widgets/feedback_bottom_sheet.dart';
+import 'api_controller.dart';
 
 class CoreController extends GetxService {
   final TAG = 'CoreController';
-
+  final _apiController = Get.find<ApiController>();
   final _state = ConnectionState.disconnected.obs;
   ConnectionState get state => _state.value;
   set state(ConnectionState value) => _state.value = value;
@@ -23,6 +28,8 @@ class CoreController extends GetxService {
   // 事件流订阅
   StreamSubscription<String>? _eventSubscription;
 
+  CancelToken? _cancelToken;
+
   @override
   void onInit() {
     super.onInit();
@@ -54,7 +61,7 @@ class CoreController extends GetxService {
       // 开始连接 - 轻微震动
       state = ConnectionState.connecting;
       HapticFeedbackManager.connectionStart();
-      CoreApi().connect();
+      getDispatchInfo();
     } else {
       // 断开连接
       state = ConnectionState.disconnecting;
@@ -62,6 +69,63 @@ class CoreController extends GetxService {
     }
   }
 
+  Future<void> getDispatchInfo() async {
+    // 如果正在请求中,取消当前请求
+    if (_cancelToken != null) {
+      log(TAG, '取消当前请求,重新发起新请求');
+      _cancelToken?.cancel('取消旧请求,发起新请求');
+    }
+
+    // 创建新的 CancelToken
+    final currentToken = CancelToken();
+    _cancelToken = currentToken;
+
+    try {
+      final launch = await _apiController.getDispatchInfo(
+        cancelToken: currentToken,
+      );
+
+      // 只有当前 token 没有被替换时才清空
+      if (_cancelToken == currentToken) {
+        _cancelToken = null;
+      }
+
+      if (state == ConnectionState.connecting) {
+        final sessionId = Uuid().v4();
+        final socksPort = launch.socksPort!;
+        final tunnelConfig = launch.tunnelConfig!;
+        final configJson = jsonEncode(launch.nodes);
+        CoreApi().connect(sessionId, socksPort, tunnelConfig, configJson);
+      }
+    } on DioException catch (e) {
+      // 只有当前 token 没有被替换时才清空
+      if (_cancelToken == currentToken) {
+        _cancelToken = null;
+      }
+
+      // 如果是取消错误,不处理
+      if (e.type == DioExceptionType.cancel) {
+        log(TAG, '请求已取消');
+        return;
+      }
+
+      if (state == ConnectionState.connecting) {
+        state = ConnectionState.disconnected;
+      }
+      log(TAG, 'getDispatchInfo error: $e');
+    } catch (e) {
+      // 只有当前 token 没有被替换时才清空
+      if (_cancelToken == currentToken) {
+        _cancelToken = null;
+      }
+
+      if (state == ConnectionState.connecting) {
+        state = ConnectionState.disconnected;
+      }
+      log(TAG, 'getDispatchInfo error: $e');
+    }
+  }
+
   /// 开始监听来自 Android 的事件
   void _startListeningToEvents() {
     _eventSubscription = onEventChange().listen(
@@ -154,6 +218,7 @@ class CoreController extends GetxService {
     state = ConnectionState.disconnected;
     timer = "00:00:00";
     HapticFeedbackManager.connectionDisconnected();
+    FeedbackBottomSheet.show();
   }
 
   void _onVpnConnecting() {
@@ -173,9 +238,10 @@ class CoreController extends GetxService {
     log(TAG, 'VPN连接错误: $errorMessage');
     // 显示错误信息
     state = ConnectionState.disconnected;
+    timer = "00:00:00";
     HapticFeedbackManager.connectionDisconnected();
     // 可以显示错误提示
-    Get.snackbar('连接失败', '无法建立连接,请重试');
+    ErrorDialog.show(message: errorMessage);
   }
 
   void _onVpnDisconnecting() {
@@ -190,7 +256,7 @@ class CoreController extends GetxService {
     state = ConnectionState.disconnected;
     HapticFeedbackManager.connectionDisconnected();
     // 可以显示错误提示
-    Get.snackbar('权限拒绝', '无法建立连接,请重试');
+    ErrorDialog.show(message: '权限拒绝');
   }
 
   // 计时器状态处理方法

+ 154 - 260
lib/app/data/models/failure.freezed.dart

@@ -1,5 +1,5 @@
-// GENERATED CODE - DO NOT MODIFY BY HAND
 // coverage:ignore-file
+// GENERATED CODE - DO NOT MODIFY BY HAND
 // ignore_for_file: type=lint
 // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
 
@@ -9,284 +9,178 @@ part of 'failure.dart';
 // FreezedGenerator
 // **************************************************************************
 
-// dart format off
 T _$identity<T>(T value) => value;
 
-/// @nodoc
-mixin _$Failure implements DiagnosticableTreeMixin {
-
- String? get code; String? get message;
-/// Create a copy of Failure
-/// with the given fields replaced by the non-null parameter values.
-@JsonKey(includeFromJson: false, includeToJson: false)
-@pragma('vm:prefer-inline')
-$FailureCopyWith<Failure> get copyWith => _$FailureCopyWithImpl<Failure>(this as Failure, _$identity);
-
-  /// Serializes this Failure to a JSON map.
-  Map<String, dynamic> toJson();
-
-@override
-void debugFillProperties(DiagnosticPropertiesBuilder properties) {
-  properties
-    ..add(DiagnosticsProperty('type', 'Failure'))
-    ..add(DiagnosticsProperty('code', code))..add(DiagnosticsProperty('message', message));
-}
+final _privateConstructorUsedError = UnsupportedError(
+  'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
+);
 
-@override
-bool operator ==(Object other) {
-  return identical(this, other) || (other.runtimeType == runtimeType&&other is Failure&&(identical(other.code, code) || other.code == code)&&(identical(other.message, message) || other.message == message));
+Failure _$FailureFromJson(Map<String, dynamic> json) {
+  return _Failure.fromJson(json);
 }
 
-@JsonKey(includeFromJson: false, includeToJson: false)
-@override
-int get hashCode => Object.hash(runtimeType,code,message);
-
-@override
-String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
-  return 'Failure(code: $code, message: $message)';
-}
+/// @nodoc
+mixin _$Failure {
+  String? get code => throw _privateConstructorUsedError;
+  String? get message => throw _privateConstructorUsedError;
 
+  /// Serializes this Failure to a JSON map.
+  Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
 
+  /// Create a copy of Failure
+  /// with the given fields replaced by the non-null parameter values.
+  @JsonKey(includeFromJson: false, includeToJson: false)
+  $FailureCopyWith<Failure> get copyWith => throw _privateConstructorUsedError;
 }
 
 /// @nodoc
-abstract mixin class $FailureCopyWith<$Res>  {
-  factory $FailureCopyWith(Failure value, $Res Function(Failure) _then) = _$FailureCopyWithImpl;
-@useResult
-$Res call({
- String? code, String? message
-});
-
-
-
-
+abstract class $FailureCopyWith<$Res> {
+  factory $FailureCopyWith(Failure value, $Res Function(Failure) then) =
+      _$FailureCopyWithImpl<$Res, Failure>;
+  @useResult
+  $Res call({String? code, String? message});
 }
+
 /// @nodoc
-class _$FailureCopyWithImpl<$Res>
+class _$FailureCopyWithImpl<$Res, $Val extends Failure>
     implements $FailureCopyWith<$Res> {
-  _$FailureCopyWithImpl(this._self, this._then);
-
-  final Failure _self;
-  final $Res Function(Failure) _then;
-
-/// Create a copy of Failure
-/// with the given fields replaced by the non-null parameter values.
-@pragma('vm:prefer-inline') @override $Res call({Object? code = freezed,Object? message = freezed,}) {
-  return _then(_self.copyWith(
-code: freezed == code ? _self.code : code // ignore: cast_nullable_to_non_nullable
-as String?,message: freezed == message ? _self.message : message // ignore: cast_nullable_to_non_nullable
-as String?,
-  ));
-}
-
-}
-
-
-/// Adds pattern-matching-related methods to [Failure].
-extension FailurePatterns on Failure {
-/// A variant of `map` that fallback to returning `orElse`.
-///
-/// It is equivalent to doing:
-/// ```dart
-/// switch (sealedClass) {
-///   case final Subclass value:
-///     return ...;
-///   case _:
-///     return orElse();
-/// }
-/// ```
-
-@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _Failure value)?  $default,{required TResult orElse(),}){
-final _that = this;
-switch (_that) {
-case _Failure() when $default != null:
-return $default(_that);case _:
-  return orElse();
-
-}
-}
-/// A `switch`-like method, using callbacks.
-///
-/// Callbacks receives the raw object, upcasted.
-/// It is equivalent to doing:
-/// ```dart
-/// switch (sealedClass) {
-///   case final Subclass value:
-///     return ...;
-///   case final Subclass2 value:
-///     return ...;
-/// }
-/// ```
-
-@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _Failure value)  $default,){
-final _that = this;
-switch (_that) {
-case _Failure():
-return $default(_that);case _:
-  throw StateError('Unexpected subclass');
-
-}
-}
-/// A variant of `map` that fallback to returning `null`.
-///
-/// It is equivalent to doing:
-/// ```dart
-/// switch (sealedClass) {
-///   case final Subclass value:
-///     return ...;
-///   case _:
-///     return null;
-/// }
-/// ```
-
-@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _Failure value)?  $default,){
-final _that = this;
-switch (_that) {
-case _Failure() when $default != null:
-return $default(_that);case _:
-  return null;
-
-}
-}
-/// A variant of `when` that fallback to an `orElse` callback.
-///
-/// It is equivalent to doing:
-/// ```dart
-/// switch (sealedClass) {
-///   case Subclass(:final field):
-///     return ...;
-///   case _:
-///     return orElse();
-/// }
-/// ```
-
-@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String? code,  String? message)?  $default,{required TResult orElse(),}) {final _that = this;
-switch (_that) {
-case _Failure() when $default != null:
-return $default(_that.code,_that.message);case _:
-  return orElse();
-
-}
-}
-/// A `switch`-like method, using callbacks.
-///
-/// As opposed to `map`, this offers destructuring.
-/// It is equivalent to doing:
-/// ```dart
-/// switch (sealedClass) {
-///   case Subclass(:final field):
-///     return ...;
-///   case Subclass2(:final field2):
-///     return ...;
-/// }
-/// ```
-
-@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String? code,  String? message)  $default,) {final _that = this;
-switch (_that) {
-case _Failure():
-return $default(_that.code,_that.message);case _:
-  throw StateError('Unexpected subclass');
-
-}
-}
-/// A variant of `when` that fallback to returning `null`
-///
-/// It is equivalent to doing:
-/// ```dart
-/// switch (sealedClass) {
-///   case Subclass(:final field):
-///     return ...;
-///   case _:
-///     return null;
-/// }
-/// ```
-
-@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String? code,  String? message)?  $default,) {final _that = this;
-switch (_that) {
-case _Failure() when $default != null:
-return $default(_that.code,_that.message);case _:
-  return null;
-
-}
-}
-
+  _$FailureCopyWithImpl(this._value, this._then);
+
+  // ignore: unused_field
+  final $Val _value;
+  // ignore: unused_field
+  final $Res Function($Val) _then;
+
+  /// Create a copy of Failure
+  /// with the given fields replaced by the non-null parameter values.
+  @pragma('vm:prefer-inline')
+  @override
+  $Res call({Object? code = freezed, Object? message = freezed}) {
+    return _then(
+      _value.copyWith(
+            code: freezed == code
+                ? _value.code
+                : code // ignore: cast_nullable_to_non_nullable
+                      as String?,
+            message: freezed == message
+                ? _value.message
+                : message // ignore: cast_nullable_to_non_nullable
+                      as String?,
+          )
+          as $Val,
+    );
+  }
 }
 
 /// @nodoc
-@JsonSerializable()
-
-class _Failure with DiagnosticableTreeMixin implements Failure {
-  const _Failure({this.code, this.message});
-  factory _Failure.fromJson(Map<String, dynamic> json) => _$FailureFromJson(json);
-
-@override final  String? code;
-@override final  String? message;
-
-/// Create a copy of Failure
-/// with the given fields replaced by the non-null parameter values.
-@override @JsonKey(includeFromJson: false, includeToJson: false)
-@pragma('vm:prefer-inline')
-_$FailureCopyWith<_Failure> get copyWith => __$FailureCopyWithImpl<_Failure>(this, _$identity);
-
-@override
-Map<String, dynamic> toJson() {
-  return _$FailureToJson(this, );
-}
-@override
-void debugFillProperties(DiagnosticPropertiesBuilder properties) {
-  properties
-    ..add(DiagnosticsProperty('type', 'Failure'))
-    ..add(DiagnosticsProperty('code', code))..add(DiagnosticsProperty('message', message));
-}
-
-@override
-bool operator ==(Object other) {
-  return identical(this, other) || (other.runtimeType == runtimeType&&other is _Failure&&(identical(other.code, code) || other.code == code)&&(identical(other.message, message) || other.message == message));
+abstract class _$$FailureImplCopyWith<$Res> implements $FailureCopyWith<$Res> {
+  factory _$$FailureImplCopyWith(
+    _$FailureImpl value,
+    $Res Function(_$FailureImpl) then,
+  ) = __$$FailureImplCopyWithImpl<$Res>;
+  @override
+  @useResult
+  $Res call({String? code, String? message});
 }
 
-@JsonKey(includeFromJson: false, includeToJson: false)
-@override
-int get hashCode => Object.hash(runtimeType,code,message);
-
-@override
-String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
-  return 'Failure(code: $code, message: $message)';
-}
-
-
-}
-
-/// @nodoc
-abstract mixin class _$FailureCopyWith<$Res> implements $FailureCopyWith<$Res> {
-  factory _$FailureCopyWith(_Failure value, $Res Function(_Failure) _then) = __$FailureCopyWithImpl;
-@override @useResult
-$Res call({
- String? code, String? message
-});
-
-
-
-
-}
 /// @nodoc
-class __$FailureCopyWithImpl<$Res>
-    implements _$FailureCopyWith<$Res> {
-  __$FailureCopyWithImpl(this._self, this._then);
-
-  final _Failure _self;
-  final $Res Function(_Failure) _then;
-
-/// Create a copy of Failure
-/// with the given fields replaced by the non-null parameter values.
-@override @pragma('vm:prefer-inline') $Res call({Object? code = freezed,Object? message = freezed,}) {
-  return _then(_Failure(
-code: freezed == code ? _self.code : code // ignore: cast_nullable_to_non_nullable
-as String?,message: freezed == message ? _self.message : message // ignore: cast_nullable_to_non_nullable
-as String?,
-  ));
+class __$$FailureImplCopyWithImpl<$Res>
+    extends _$FailureCopyWithImpl<$Res, _$FailureImpl>
+    implements _$$FailureImplCopyWith<$Res> {
+  __$$FailureImplCopyWithImpl(
+    _$FailureImpl _value,
+    $Res Function(_$FailureImpl) _then,
+  ) : super(_value, _then);
+
+  /// Create a copy of Failure
+  /// with the given fields replaced by the non-null parameter values.
+  @pragma('vm:prefer-inline')
+  @override
+  $Res call({Object? code = freezed, Object? message = freezed}) {
+    return _then(
+      _$FailureImpl(
+        code: freezed == code
+            ? _value.code
+            : code // ignore: cast_nullable_to_non_nullable
+                  as String?,
+        message: freezed == message
+            ? _value.message
+            : message // ignore: cast_nullable_to_non_nullable
+                  as String?,
+      ),
+    );
+  }
 }
 
-
+/// @nodoc
+@JsonSerializable()
+class _$FailureImpl with DiagnosticableTreeMixin implements _Failure {
+  const _$FailureImpl({this.code, this.message});
+
+  factory _$FailureImpl.fromJson(Map<String, dynamic> json) =>
+      _$$FailureImplFromJson(json);
+
+  @override
+  final String? code;
+  @override
+  final String? message;
+
+  @override
+  String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
+    return 'Failure(code: $code, message: $message)';
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties
+      ..add(DiagnosticsProperty('type', 'Failure'))
+      ..add(DiagnosticsProperty('code', code))
+      ..add(DiagnosticsProperty('message', message));
+  }
+
+  @override
+  bool operator ==(Object other) {
+    return identical(this, other) ||
+        (other.runtimeType == runtimeType &&
+            other is _$FailureImpl &&
+            (identical(other.code, code) || other.code == code) &&
+            (identical(other.message, message) || other.message == message));
+  }
+
+  @JsonKey(includeFromJson: false, includeToJson: false)
+  @override
+  int get hashCode => Object.hash(runtimeType, code, message);
+
+  /// Create a copy of Failure
+  /// with the given fields replaced by the non-null parameter values.
+  @JsonKey(includeFromJson: false, includeToJson: false)
+  @override
+  @pragma('vm:prefer-inline')
+  _$$FailureImplCopyWith<_$FailureImpl> get copyWith =>
+      __$$FailureImplCopyWithImpl<_$FailureImpl>(this, _$identity);
+
+  @override
+  Map<String, dynamic> toJson() {
+    return _$$FailureImplToJson(this);
+  }
+}
+
+abstract class _Failure implements Failure {
+  const factory _Failure({final String? code, final String? message}) =
+      _$FailureImpl;
+
+  factory _Failure.fromJson(Map<String, dynamic> json) = _$FailureImpl.fromJson;
+
+  @override
+  String? get code;
+  @override
+  String? get message;
+
+  /// Create a copy of Failure
+  /// with the given fields replaced by the non-null parameter values.
+  @override
+  @JsonKey(includeFromJson: false, includeToJson: false)
+  _$$FailureImplCopyWith<_$FailureImpl> get copyWith =>
+      throw _privateConstructorUsedError;
 }
-
-// dart format on

+ 7 - 8
lib/app/data/models/failure.g.dart

@@ -6,12 +6,11 @@ part of 'failure.dart';
 // JsonSerializableGenerator
 // **************************************************************************
 
-_Failure _$FailureFromJson(Map<String, dynamic> json) => _Failure(
-  code: json['code'] as String?,
-  message: json['message'] as String?,
-);
+_$FailureImpl _$$FailureImplFromJson(Map<String, dynamic> json) =>
+    _$FailureImpl(
+      code: json['code'] as String?,
+      message: json['message'] as String?,
+    );
 
-Map<String, dynamic> _$FailureToJson(_Failure instance) => <String, dynamic>{
-  'code': instance.code,
-  'message': instance.message,
-};
+Map<String, dynamic> _$$FailureImplToJson(_$FailureImpl instance) =>
+    <String, dynamic>{'code': instance.code, 'message': instance.message};

+ 168 - 141
lib/app/data/models/launch/ad_config.freezed.dart

@@ -12,7 +12,8 @@ part of 'ad_config.dart';
 T _$identity<T>(T value) => value;
 
 final _privateConstructorUsedError = UnsupportedError(
-    'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
+  'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
+);
 
 AdConfig _$AdConfigFromJson(Map<String, dynamic> json) {
   return _AdConfig.fromJson(json);
@@ -61,20 +62,23 @@ class _$AdConfigCopyWithImpl<$Res, $Val extends AdConfig>
     Object? appKey = freezed,
     Object? data = freezed,
   }) {
-    return _then(_value.copyWith(
-      appId: freezed == appId
-          ? _value.appId
-          : appId // ignore: cast_nullable_to_non_nullable
-              as String?,
-      appKey: freezed == appKey
-          ? _value.appKey
-          : appKey // ignore: cast_nullable_to_non_nullable
-              as String?,
-      data: freezed == data
-          ? _value.data
-          : data // ignore: cast_nullable_to_non_nullable
-              as List<Data>?,
-    ) as $Val);
+    return _then(
+      _value.copyWith(
+            appId: freezed == appId
+                ? _value.appId
+                : appId // ignore: cast_nullable_to_non_nullable
+                      as String?,
+            appKey: freezed == appKey
+                ? _value.appKey
+                : appKey // ignore: cast_nullable_to_non_nullable
+                      as String?,
+            data: freezed == data
+                ? _value.data
+                : data // ignore: cast_nullable_to_non_nullable
+                      as List<Data>?,
+          )
+          as $Val,
+    );
   }
 }
 
@@ -82,8 +86,9 @@ class _$AdConfigCopyWithImpl<$Res, $Val extends AdConfig>
 abstract class _$$AdConfigImplCopyWith<$Res>
     implements $AdConfigCopyWith<$Res> {
   factory _$$AdConfigImplCopyWith(
-          _$AdConfigImpl value, $Res Function(_$AdConfigImpl) then) =
-      __$$AdConfigImplCopyWithImpl<$Res>;
+    _$AdConfigImpl value,
+    $Res Function(_$AdConfigImpl) then,
+  ) = __$$AdConfigImplCopyWithImpl<$Res>;
   @override
   @useResult
   $Res call({String? appId, String? appKey, List<Data>? data});
@@ -94,8 +99,9 @@ class __$$AdConfigImplCopyWithImpl<$Res>
     extends _$AdConfigCopyWithImpl<$Res, _$AdConfigImpl>
     implements _$$AdConfigImplCopyWith<$Res> {
   __$$AdConfigImplCopyWithImpl(
-      _$AdConfigImpl _value, $Res Function(_$AdConfigImpl) _then)
-      : super(_value, _then);
+    _$AdConfigImpl _value,
+    $Res Function(_$AdConfigImpl) _then,
+  ) : super(_value, _then);
 
   /// Create a copy of AdConfig
   /// with the given fields replaced by the non-null parameter values.
@@ -106,20 +112,22 @@ class __$$AdConfigImplCopyWithImpl<$Res>
     Object? appKey = freezed,
     Object? data = freezed,
   }) {
-    return _then(_$AdConfigImpl(
-      appId: freezed == appId
-          ? _value.appId
-          : appId // ignore: cast_nullable_to_non_nullable
-              as String?,
-      appKey: freezed == appKey
-          ? _value.appKey
-          : appKey // ignore: cast_nullable_to_non_nullable
-              as String?,
-      data: freezed == data
-          ? _value._data
-          : data // ignore: cast_nullable_to_non_nullable
-              as List<Data>?,
-    ));
+    return _then(
+      _$AdConfigImpl(
+        appId: freezed == appId
+            ? _value.appId
+            : appId // ignore: cast_nullable_to_non_nullable
+                  as String?,
+        appKey: freezed == appKey
+            ? _value.appKey
+            : appKey // ignore: cast_nullable_to_non_nullable
+                  as String?,
+        data: freezed == data
+            ? _value._data
+            : data // ignore: cast_nullable_to_non_nullable
+                  as List<Data>?,
+      ),
+    );
   }
 }
 
@@ -127,7 +135,7 @@ class __$$AdConfigImplCopyWithImpl<$Res>
 @JsonSerializable()
 class _$AdConfigImpl with DiagnosticableTreeMixin implements _AdConfig {
   const _$AdConfigImpl({this.appId, this.appKey, final List<Data>? data})
-      : _data = data;
+    : _data = data;
 
   factory _$AdConfigImpl.fromJson(Map<String, dynamic> json) =>
       _$$AdConfigImplFromJson(json);
@@ -174,7 +182,11 @@ class _$AdConfigImpl with DiagnosticableTreeMixin implements _AdConfig {
   @JsonKey(includeFromJson: false, includeToJson: false)
   @override
   int get hashCode => Object.hash(
-      runtimeType, appId, appKey, const DeepCollectionEquality().hash(_data));
+    runtimeType,
+    appId,
+    appKey,
+    const DeepCollectionEquality().hash(_data),
+  );
 
   /// Create a copy of AdConfig
   /// with the given fields replaced by the non-null parameter values.
@@ -186,17 +198,16 @@ class _$AdConfigImpl with DiagnosticableTreeMixin implements _AdConfig {
 
   @override
   Map<String, dynamic> toJson() {
-    return _$$AdConfigImplToJson(
-      this,
-    );
+    return _$$AdConfigImplToJson(this);
   }
 }
 
 abstract class _AdConfig implements AdConfig {
-  const factory _AdConfig(
-      {final String? appId,
-      final String? appKey,
-      final List<Data>? data}) = _$AdConfigImpl;
+  const factory _AdConfig({
+    final String? appId,
+    final String? appKey,
+    final List<Data>? data,
+  }) = _$AdConfigImpl;
 
   factory _AdConfig.fromJson(Map<String, dynamic> json) =
       _$AdConfigImpl.fromJson;
@@ -244,14 +255,15 @@ abstract class $DataCopyWith<$Res> {
   factory $DataCopyWith(Data value, $Res Function(Data) then) =
       _$DataCopyWithImpl<$Res, Data>;
   @useResult
-  $Res call(
-      {String? type,
-      String? pagePos,
-      String? adId,
-      int? interval,
-      int? timeout,
-      bool? unbounded,
-      bool? preLoad});
+  $Res call({
+    String? type,
+    String? pagePos,
+    String? adId,
+    int? interval,
+    int? timeout,
+    bool? unbounded,
+    bool? preLoad,
+  });
 }
 
 /// @nodoc
@@ -277,54 +289,59 @@ class _$DataCopyWithImpl<$Res, $Val extends Data>
     Object? unbounded = freezed,
     Object? preLoad = freezed,
   }) {
-    return _then(_value.copyWith(
-      type: freezed == type
-          ? _value.type
-          : type // ignore: cast_nullable_to_non_nullable
-              as String?,
-      pagePos: freezed == pagePos
-          ? _value.pagePos
-          : pagePos // ignore: cast_nullable_to_non_nullable
-              as String?,
-      adId: freezed == adId
-          ? _value.adId
-          : adId // ignore: cast_nullable_to_non_nullable
-              as String?,
-      interval: freezed == interval
-          ? _value.interval
-          : interval // ignore: cast_nullable_to_non_nullable
-              as int?,
-      timeout: freezed == timeout
-          ? _value.timeout
-          : timeout // ignore: cast_nullable_to_non_nullable
-              as int?,
-      unbounded: freezed == unbounded
-          ? _value.unbounded
-          : unbounded // ignore: cast_nullable_to_non_nullable
-              as bool?,
-      preLoad: freezed == preLoad
-          ? _value.preLoad
-          : preLoad // ignore: cast_nullable_to_non_nullable
-              as bool?,
-    ) as $Val);
+    return _then(
+      _value.copyWith(
+            type: freezed == type
+                ? _value.type
+                : type // ignore: cast_nullable_to_non_nullable
+                      as String?,
+            pagePos: freezed == pagePos
+                ? _value.pagePos
+                : pagePos // ignore: cast_nullable_to_non_nullable
+                      as String?,
+            adId: freezed == adId
+                ? _value.adId
+                : adId // ignore: cast_nullable_to_non_nullable
+                      as String?,
+            interval: freezed == interval
+                ? _value.interval
+                : interval // ignore: cast_nullable_to_non_nullable
+                      as int?,
+            timeout: freezed == timeout
+                ? _value.timeout
+                : timeout // ignore: cast_nullable_to_non_nullable
+                      as int?,
+            unbounded: freezed == unbounded
+                ? _value.unbounded
+                : unbounded // ignore: cast_nullable_to_non_nullable
+                      as bool?,
+            preLoad: freezed == preLoad
+                ? _value.preLoad
+                : preLoad // ignore: cast_nullable_to_non_nullable
+                      as bool?,
+          )
+          as $Val,
+    );
   }
 }
 
 /// @nodoc
 abstract class _$$DataImplCopyWith<$Res> implements $DataCopyWith<$Res> {
   factory _$$DataImplCopyWith(
-          _$DataImpl value, $Res Function(_$DataImpl) then) =
-      __$$DataImplCopyWithImpl<$Res>;
+    _$DataImpl value,
+    $Res Function(_$DataImpl) then,
+  ) = __$$DataImplCopyWithImpl<$Res>;
   @override
   @useResult
-  $Res call(
-      {String? type,
-      String? pagePos,
-      String? adId,
-      int? interval,
-      int? timeout,
-      bool? unbounded,
-      bool? preLoad});
+  $Res call({
+    String? type,
+    String? pagePos,
+    String? adId,
+    int? interval,
+    int? timeout,
+    bool? unbounded,
+    bool? preLoad,
+  });
 }
 
 /// @nodoc
@@ -332,7 +349,7 @@ class __$$DataImplCopyWithImpl<$Res>
     extends _$DataCopyWithImpl<$Res, _$DataImpl>
     implements _$$DataImplCopyWith<$Res> {
   __$$DataImplCopyWithImpl(_$DataImpl _value, $Res Function(_$DataImpl) _then)
-      : super(_value, _then);
+    : super(_value, _then);
 
   /// Create a copy of Data
   /// with the given fields replaced by the non-null parameter values.
@@ -347,50 +364,53 @@ class __$$DataImplCopyWithImpl<$Res>
     Object? unbounded = freezed,
     Object? preLoad = freezed,
   }) {
-    return _then(_$DataImpl(
-      type: freezed == type
-          ? _value.type
-          : type // ignore: cast_nullable_to_non_nullable
-              as String?,
-      pagePos: freezed == pagePos
-          ? _value.pagePos
-          : pagePos // ignore: cast_nullable_to_non_nullable
-              as String?,
-      adId: freezed == adId
-          ? _value.adId
-          : adId // ignore: cast_nullable_to_non_nullable
-              as String?,
-      interval: freezed == interval
-          ? _value.interval
-          : interval // ignore: cast_nullable_to_non_nullable
-              as int?,
-      timeout: freezed == timeout
-          ? _value.timeout
-          : timeout // ignore: cast_nullable_to_non_nullable
-              as int?,
-      unbounded: freezed == unbounded
-          ? _value.unbounded
-          : unbounded // ignore: cast_nullable_to_non_nullable
-              as bool?,
-      preLoad: freezed == preLoad
-          ? _value.preLoad
-          : preLoad // ignore: cast_nullable_to_non_nullable
-              as bool?,
-    ));
+    return _then(
+      _$DataImpl(
+        type: freezed == type
+            ? _value.type
+            : type // ignore: cast_nullable_to_non_nullable
+                  as String?,
+        pagePos: freezed == pagePos
+            ? _value.pagePos
+            : pagePos // ignore: cast_nullable_to_non_nullable
+                  as String?,
+        adId: freezed == adId
+            ? _value.adId
+            : adId // ignore: cast_nullable_to_non_nullable
+                  as String?,
+        interval: freezed == interval
+            ? _value.interval
+            : interval // ignore: cast_nullable_to_non_nullable
+                  as int?,
+        timeout: freezed == timeout
+            ? _value.timeout
+            : timeout // ignore: cast_nullable_to_non_nullable
+                  as int?,
+        unbounded: freezed == unbounded
+            ? _value.unbounded
+            : unbounded // ignore: cast_nullable_to_non_nullable
+                  as bool?,
+        preLoad: freezed == preLoad
+            ? _value.preLoad
+            : preLoad // ignore: cast_nullable_to_non_nullable
+                  as bool?,
+      ),
+    );
   }
 }
 
 /// @nodoc
 @JsonSerializable()
 class _$DataImpl with DiagnosticableTreeMixin implements _Data {
-  const _$DataImpl(
-      {this.type,
-      this.pagePos,
-      this.adId,
-      this.interval,
-      this.timeout,
-      this.unbounded,
-      this.preLoad});
+  const _$DataImpl({
+    this.type,
+    this.pagePos,
+    this.adId,
+    this.interval,
+    this.timeout,
+    this.unbounded,
+    this.preLoad,
+  });
 
   factory _$DataImpl.fromJson(Map<String, dynamic> json) =>
       _$$DataImplFromJson(json);
@@ -448,7 +468,15 @@ class _$DataImpl with DiagnosticableTreeMixin implements _Data {
   @JsonKey(includeFromJson: false, includeToJson: false)
   @override
   int get hashCode => Object.hash(
-      runtimeType, type, pagePos, adId, interval, timeout, unbounded, preLoad);
+    runtimeType,
+    type,
+    pagePos,
+    adId,
+    interval,
+    timeout,
+    unbounded,
+    preLoad,
+  );
 
   /// Create a copy of Data
   /// with the given fields replaced by the non-null parameter values.
@@ -460,21 +488,20 @@ class _$DataImpl with DiagnosticableTreeMixin implements _Data {
 
   @override
   Map<String, dynamic> toJson() {
-    return _$$DataImplToJson(
-      this,
-    );
+    return _$$DataImplToJson(this);
   }
 }
 
 abstract class _Data implements Data {
-  const factory _Data(
-      {final String? type,
-      final String? pagePos,
-      final String? adId,
-      final int? interval,
-      final int? timeout,
-      final bool? unbounded,
-      final bool? preLoad}) = _$DataImpl;
+  const factory _Data({
+    final String? type,
+    final String? pagePos,
+    final String? adId,
+    final int? interval,
+    final int? timeout,
+    final bool? unbounded,
+    final bool? preLoad,
+  }) = _$DataImpl;
 
   factory _Data.fromJson(Map<String, dynamic> json) = _$DataImpl.fromJson;
 

+ 8 - 8
lib/app/data/models/launch/ad_config.g.dart

@@ -23,14 +23,14 @@ Map<String, dynamic> _$$AdConfigImplToJson(_$AdConfigImpl instance) =>
     };
 
 _$DataImpl _$$DataImplFromJson(Map<String, dynamic> json) => _$DataImpl(
-      type: json['type'] as String?,
-      pagePos: json['pagePos'] as String?,
-      adId: json['adId'] as String?,
-      interval: (json['interval'] as num?)?.toInt(),
-      timeout: (json['timeout'] as num?)?.toInt(),
-      unbounded: json['unbounded'] as bool?,
-      preLoad: json['preLoad'] as bool?,
-    );
+  type: json['type'] as String?,
+  pagePos: json['pagePos'] as String?,
+  adId: json['adId'] as String?,
+  interval: (json['interval'] as num?)?.toInt(),
+  timeout: (json['timeout'] as num?)?.toInt(),
+  unbounded: json['unbounded'] as bool?,
+  preLoad: json['preLoad'] as bool?,
+);
 
 Map<String, dynamic> _$$DataImplToJson(_$DataImpl instance) =>
     <String, dynamic>{

+ 4 - 8
lib/app/data/models/launch/app_config.dart

@@ -11,6 +11,7 @@ class AppConfig with _$AppConfig {
   const factory AppConfig({
     List<String>? apiIps,
     List<String>? apiUrls,
+    List<String>? routerApiUrls,
     List<String>? appStatUrls,
     List<String>? assetUrls,
     List<String>? logFileUploadUrls,
@@ -55,10 +56,7 @@ class AppConfig with _$AppConfig {
 
 @freezed
 class Contacts with _$Contacts {
-  const factory Contacts({
-    String? title,
-    String? url,
-  }) = _Contacts;
+  const factory Contacts({String? title, String? url}) = _Contacts;
 
   factory Contacts.fromJson(Map<String, Object?> json) =>
       _$ContactsFromJson(json);
@@ -66,10 +64,8 @@ class Contacts with _$Contacts {
 
 @freezed
 class SpeedTestTargetList with _$SpeedTestTargetList {
-  const factory SpeedTestTargetList({
-    String? title,
-    String? url,
-  }) = _SpeedTestTargetList;
+  const factory SpeedTestTargetList({String? title, String? url}) =
+      _SpeedTestTargetList;
 
   factory SpeedTestTargetList.fromJson(Map<String, Object?> json) =>
       _$SpeedTestTargetListFromJson(json);

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 483 - 448
lib/app/data/models/launch/app_config.freezed.dart


+ 94 - 98
lib/app/data/models/launch/app_config.g.dart

@@ -6,95 +6,97 @@ part of 'app_config.dart';
 // JsonSerializableGenerator
 // **************************************************************************
 
-_$AppConfigImpl _$$AppConfigImplFromJson(Map<String, dynamic> json) =>
-    _$AppConfigImpl(
-      apiIps:
-          (json['apiIps'] as List<dynamic>?)?.map((e) => e as String).toList(),
-      apiUrls:
-          (json['apiUrls'] as List<dynamic>?)?.map((e) => e as String).toList(),
-      appStatUrls: (json['appStatUrls'] as List<dynamic>?)
-          ?.map((e) => e as String)
-          .toList(),
-      assetUrls: (json['assetUrls'] as List<dynamic>?)
-          ?.map((e) => e as String)
-          .toList(),
-      logFileUploadUrls: (json['logFileUploadUrls'] as List<dynamic>?)
-          ?.map((e) => e as String)
-          .toList(),
-      realityApiUrls: (json['realityApiUrls'] as List<dynamic>?)
-          ?.map((e) => e as String)
-          .toList(),
-      realityAppStatUrls: (json['realityAppStatUrls'] as List<dynamic>?)
-          ?.map((e) => e as String)
-          .toList(),
-      realityLogFileUploadUrls:
-          (json['realityLogFileUploadUrls'] as List<dynamic>?)
-              ?.map((e) => e as String)
-              .toList(),
-      autoPing: json['autoPing'] as bool?,
-      autoPingInterval: (json['autoPingInterval'] as num?)?.toInt(),
-      backupApiUrls: (json['backupApiUrls'] as List<dynamic>?)
-          ?.map((e) => e as String)
-          .toList(),
-      cacheDataEffectiveDays: (json['cacheDataEffectiveDays'] as num?)?.toInt(),
-      contacts: (json['contacts'] as List<dynamic>?)
-          ?.map((e) => Contacts.fromJson(e as Map<String, dynamic>))
-          .toList(),
-      follows: (json['follows'] as List<dynamic>?)
-          ?.map((e) => Contacts.fromJson(e as Map<String, dynamic>))
-          .toList(),
-      providers: (json['providers'] as List<dynamic>?)
-          ?.map((e) => e as String)
-          .toList(),
-      signalThresholds: (json['signalThresholds'] as List<dynamic>?)
-          ?.map((e) => (e as num).toInt())
-          .toList(),
-      packetLossThresholds: (json['packetLossThresholds'] as List<dynamic>?)
-          ?.map((e) => (e as num).toInt())
-          .toList(),
-      skipGeo: json['skipGeo'] == null
-          ? null
-          : SkipGeo.fromJson(json['skipGeo'] as Map<String, dynamic>),
-      speedTestTargetList: (json['speedTestTargetList'] as List<dynamic>?)
-          ?.map((e) => SpeedTestTargetList.fromJson(e as Map<String, dynamic>))
-          .toList(),
-      websiteUrl: json['websiteUrl'] as String?,
-      visitorDisabled: json['visitorDisabled'] as bool?,
-      privacyAgreement: json['privacyAgreement'] == null
-          ? null
-          : PrivacyTerms.fromJson(
-              json['privacyAgreement'] as Map<String, dynamic>),
-      email: json['email'] as String?,
-      blackPkgs: (json['blackPkgs'] as List<dynamic>?)
-          ?.map((e) => e as String)
-          .toList(),
-      whitePkgs: (json['whitePkgs'] as List<dynamic>?)
-          ?.map((e) => e as String)
-          .toList(),
-      accelerationSampleCount:
-          (json['accelerationSampleCount'] as num?)?.toInt(),
-      minAccelerationSampleCount:
-          (json['minAccelerationSampleCount'] as num?)?.toInt(),
-      connectionWarningThreshold:
-          (json['connectionWarningThreshold'] as num?)?.toInt(),
-      connectiveTestUrl: json['connectiveTestUrl'] as String?,
-      disabledLogModules: (json['disabledLogModules'] as List<dynamic>?)
-          ?.map((e) => e as String)
-          .toList(),
-      realTimeDbPath: json['realTimeDbPath'] as String?,
-      boostTimeInterval: (json['boostTimeInterval'] as num?)?.toInt(),
-      pingDisplayMode: (json['pingDisplayMode'] as num?)?.toInt(),
-      peekTimeInterval: (json['peekTimeInterval'] as num?)?.toInt(),
-      enableAd: json['enableAd'] as bool?,
-      adTimeoutDuration: (json['adTimeoutDuration'] as num?)?.toInt(),
-      reportActiveInterval: (json['reportActiveInterval'] as num?)?.toInt(),
-      serverTime: (json['serverTime'] as num?)?.toInt(),
-    );
+_$AppConfigImpl _$$AppConfigImplFromJson(
+  Map<String, dynamic> json,
+) => _$AppConfigImpl(
+  apiIps: (json['apiIps'] as List<dynamic>?)?.map((e) => e as String).toList(),
+  apiUrls: (json['apiUrls'] as List<dynamic>?)
+      ?.map((e) => e as String)
+      .toList(),
+  routerApiUrls: (json['routerApiUrls'] as List<dynamic>?)
+      ?.map((e) => e as String)
+      .toList(),
+  appStatUrls: (json['appStatUrls'] as List<dynamic>?)
+      ?.map((e) => e as String)
+      .toList(),
+  assetUrls: (json['assetUrls'] as List<dynamic>?)
+      ?.map((e) => e as String)
+      .toList(),
+  logFileUploadUrls: (json['logFileUploadUrls'] as List<dynamic>?)
+      ?.map((e) => e as String)
+      .toList(),
+  realityApiUrls: (json['realityApiUrls'] as List<dynamic>?)
+      ?.map((e) => e as String)
+      .toList(),
+  realityAppStatUrls: (json['realityAppStatUrls'] as List<dynamic>?)
+      ?.map((e) => e as String)
+      .toList(),
+  realityLogFileUploadUrls: (json['realityLogFileUploadUrls'] as List<dynamic>?)
+      ?.map((e) => e as String)
+      .toList(),
+  autoPing: json['autoPing'] as bool?,
+  autoPingInterval: (json['autoPingInterval'] as num?)?.toInt(),
+  backupApiUrls: (json['backupApiUrls'] as List<dynamic>?)
+      ?.map((e) => e as String)
+      .toList(),
+  cacheDataEffectiveDays: (json['cacheDataEffectiveDays'] as num?)?.toInt(),
+  contacts: (json['contacts'] as List<dynamic>?)
+      ?.map((e) => Contacts.fromJson(e as Map<String, dynamic>))
+      .toList(),
+  follows: (json['follows'] as List<dynamic>?)
+      ?.map((e) => Contacts.fromJson(e as Map<String, dynamic>))
+      .toList(),
+  providers: (json['providers'] as List<dynamic>?)
+      ?.map((e) => e as String)
+      .toList(),
+  signalThresholds: (json['signalThresholds'] as List<dynamic>?)
+      ?.map((e) => (e as num).toInt())
+      .toList(),
+  packetLossThresholds: (json['packetLossThresholds'] as List<dynamic>?)
+      ?.map((e) => (e as num).toInt())
+      .toList(),
+  skipGeo: json['skipGeo'] == null
+      ? null
+      : SkipGeo.fromJson(json['skipGeo'] as Map<String, dynamic>),
+  speedTestTargetList: (json['speedTestTargetList'] as List<dynamic>?)
+      ?.map((e) => SpeedTestTargetList.fromJson(e as Map<String, dynamic>))
+      .toList(),
+  websiteUrl: json['websiteUrl'] as String?,
+  visitorDisabled: json['visitorDisabled'] as bool?,
+  privacyAgreement: json['privacyAgreement'] == null
+      ? null
+      : PrivacyTerms.fromJson(json['privacyAgreement'] as Map<String, dynamic>),
+  email: json['email'] as String?,
+  blackPkgs: (json['blackPkgs'] as List<dynamic>?)
+      ?.map((e) => e as String)
+      .toList(),
+  whitePkgs: (json['whitePkgs'] as List<dynamic>?)
+      ?.map((e) => e as String)
+      .toList(),
+  accelerationSampleCount: (json['accelerationSampleCount'] as num?)?.toInt(),
+  minAccelerationSampleCount: (json['minAccelerationSampleCount'] as num?)
+      ?.toInt(),
+  connectionWarningThreshold: (json['connectionWarningThreshold'] as num?)
+      ?.toInt(),
+  connectiveTestUrl: json['connectiveTestUrl'] as String?,
+  disabledLogModules: (json['disabledLogModules'] as List<dynamic>?)
+      ?.map((e) => e as String)
+      .toList(),
+  realTimeDbPath: json['realTimeDbPath'] as String?,
+  boostTimeInterval: (json['boostTimeInterval'] as num?)?.toInt(),
+  pingDisplayMode: (json['pingDisplayMode'] as num?)?.toInt(),
+  peekTimeInterval: (json['peekTimeInterval'] as num?)?.toInt(),
+  enableAd: json['enableAd'] as bool?,
+  adTimeoutDuration: (json['adTimeoutDuration'] as num?)?.toInt(),
+  reportActiveInterval: (json['reportActiveInterval'] as num?)?.toInt(),
+  serverTime: (json['serverTime'] as num?)?.toInt(),
+);
 
 Map<String, dynamic> _$$AppConfigImplToJson(_$AppConfigImpl instance) =>
     <String, dynamic>{
       'apiIps': instance.apiIps,
       'apiUrls': instance.apiUrls,
+      'routerApiUrls': instance.routerApiUrls,
       'appStatUrls': instance.appStatUrls,
       'assetUrls': instance.assetUrls,
       'logFileUploadUrls': instance.logFileUploadUrls,
@@ -140,21 +142,15 @@ _$ContactsImpl _$$ContactsImplFromJson(Map<String, dynamic> json) =>
     );
 
 Map<String, dynamic> _$$ContactsImplToJson(_$ContactsImpl instance) =>
-    <String, dynamic>{
-      'title': instance.title,
-      'url': instance.url,
-    };
+    <String, dynamic>{'title': instance.title, 'url': instance.url};
 
 _$SpeedTestTargetListImpl _$$SpeedTestTargetListImplFromJson(
-        Map<String, dynamic> json) =>
-    _$SpeedTestTargetListImpl(
-      title: json['title'] as String?,
-      url: json['url'] as String?,
-    );
+  Map<String, dynamic> json,
+) => _$SpeedTestTargetListImpl(
+  title: json['title'] as String?,
+  url: json['url'] as String?,
+);
 
 Map<String, dynamic> _$$SpeedTestTargetListImplToJson(
-        _$SpeedTestTargetListImpl instance) =>
-    <String, dynamic>{
-      'title': instance.title,
-      'url': instance.url,
-    };
+  _$SpeedTestTargetListImpl instance,
+) => <String, dynamic>{'title': instance.title, 'url': instance.url};

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 391 - 352
lib/app/data/models/launch/groups.freezed.dart


+ 21 - 29
lib/app/data/models/launch/groups.g.dart

@@ -7,37 +7,32 @@ part of 'groups.dart';
 // **************************************************************************
 
 _$GroupsImpl _$$GroupsImplFromJson(Map<String, dynamic> json) => _$GroupsImpl(
-      normal: json['normal'] == null
-          ? null
-          : Normal.fromJson(json['normal'] as Map<String, dynamic>),
-    );
+  normal: json['normal'] == null
+      ? null
+      : Normal.fromJson(json['normal'] as Map<String, dynamic>),
+);
 
 Map<String, dynamic> _$$GroupsImplToJson(_$GroupsImpl instance) =>
-    <String, dynamic>{
-      'normal': instance.normal,
-    };
+    <String, dynamic>{'normal': instance.normal};
 
 _$NormalImpl _$$NormalImplFromJson(Map<String, dynamic> json) => _$NormalImpl(
-      tags: (json['tags'] as List<dynamic>?)
-          ?.map((e) => Tags.fromJson(e as Map<String, dynamic>))
-          .toList(),
-      list: (json['list'] as List<dynamic>?)
-          ?.map((e) => LocationList.fromJson(e as Map<String, dynamic>))
-          .toList(),
-    );
+  tags: (json['tags'] as List<dynamic>?)
+      ?.map((e) => Tags.fromJson(e as Map<String, dynamic>))
+      .toList(),
+  list: (json['list'] as List<dynamic>?)
+      ?.map((e) => LocationList.fromJson(e as Map<String, dynamic>))
+      .toList(),
+);
 
 Map<String, dynamic> _$$NormalImplToJson(_$NormalImpl instance) =>
-    <String, dynamic>{
-      'tags': instance.tags,
-      'list': instance.list,
-    };
+    <String, dynamic>{'tags': instance.tags, 'list': instance.list};
 
 _$TagsImpl _$$TagsImplFromJson(Map<String, dynamic> json) => _$TagsImpl(
-      id: (json['id'] as num?)?.toInt(),
-      name: json['name'] as String?,
-      icon: json['icon'] as String?,
-      sort: (json['sort'] as num?)?.toInt(),
-    );
+  id: (json['id'] as num?)?.toInt(),
+  name: json['name'] as String?,
+  icon: json['icon'] as String?,
+  sort: (json['sort'] as num?)?.toInt(),
+);
 
 Map<String, dynamic> _$$TagsImplToJson(_$TagsImpl instance) =>
     <String, dynamic>{
@@ -95,11 +90,8 @@ Map<String, dynamic> _$$LocationsImplToJson(_$LocationsImpl instance) =>
       'latency': instance.latency,
     };
 
-_$ParamImpl _$$ParamImplFromJson(Map<String, dynamic> json) => _$ParamImpl(
-      g: json['g'] as String?,
-    );
+_$ParamImpl _$$ParamImplFromJson(Map<String, dynamic> json) =>
+    _$ParamImpl(g: json['g'] as String?);
 
 Map<String, dynamic> _$$ParamImplToJson(_$ParamImpl instance) =>
-    <String, dynamic>{
-      'g': instance.g,
-    };
+    <String, dynamic>{'g': instance.g};

+ 3 - 2
lib/app/data/models/launch/launch.dart

@@ -3,7 +3,6 @@ import 'package:flutter/foundation.dart';
 
 import 'ad_config.dart';
 import 'ranks.dart';
-import 'vpn_config.dart';
 import 'groups.dart';
 import 'user.dart';
 import 'app_config.dart';
@@ -18,11 +17,13 @@ class Launch with _$Launch {
     User? userConfig,
     AppConfig? appConfig,
     Upgrade? upgradeConfig,
-    VpnConfig? vpnConfig,
     AdConfig? adConfig,
     Groups? groups,
     List<Ranks>? ranks,
     dynamic exData,
+    List<dynamic>? nodes,
+    String? tunnelConfig,
+    int? socksPort,
   }) = _Launch;
 
   factory Launch.fromJson(Map<String, Object?> json) => _$LaunchFromJson(json);

+ 199 - 152
lib/app/data/models/launch/launch.freezed.dart

@@ -12,7 +12,8 @@ part of 'launch.dart';
 T _$identity<T>(T value) => value;
 
 final _privateConstructorUsedError = UnsupportedError(
-    'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
+  'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
+);
 
 Launch _$LaunchFromJson(Map<String, dynamic> json) {
   return _Launch.fromJson(json);
@@ -23,11 +24,13 @@ mixin _$Launch {
   User? get userConfig => throw _privateConstructorUsedError;
   AppConfig? get appConfig => throw _privateConstructorUsedError;
   Upgrade? get upgradeConfig => throw _privateConstructorUsedError;
-  VpnConfig? get vpnConfig => throw _privateConstructorUsedError;
   AdConfig? get adConfig => throw _privateConstructorUsedError;
   Groups? get groups => throw _privateConstructorUsedError;
   List<Ranks>? get ranks => throw _privateConstructorUsedError;
   dynamic get exData => throw _privateConstructorUsedError;
+  List<dynamic>? get nodes => throw _privateConstructorUsedError;
+  String? get tunnelConfig => throw _privateConstructorUsedError;
+  int? get socksPort => throw _privateConstructorUsedError;
 
   /// Serializes this Launch to a JSON map.
   Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@@ -43,20 +46,22 @@ abstract class $LaunchCopyWith<$Res> {
   factory $LaunchCopyWith(Launch value, $Res Function(Launch) then) =
       _$LaunchCopyWithImpl<$Res, Launch>;
   @useResult
-  $Res call(
-      {User? userConfig,
-      AppConfig? appConfig,
-      Upgrade? upgradeConfig,
-      VpnConfig? vpnConfig,
-      AdConfig? adConfig,
-      Groups? groups,
-      List<Ranks>? ranks,
-      dynamic exData});
+  $Res call({
+    User? userConfig,
+    AppConfig? appConfig,
+    Upgrade? upgradeConfig,
+    AdConfig? adConfig,
+    Groups? groups,
+    List<Ranks>? ranks,
+    dynamic exData,
+    List<dynamic>? nodes,
+    String? tunnelConfig,
+    int? socksPort,
+  });
 
   $UserCopyWith<$Res>? get userConfig;
   $AppConfigCopyWith<$Res>? get appConfig;
   $UpgradeCopyWith<$Res>? get upgradeConfig;
-  $VpnConfigCopyWith<$Res>? get vpnConfig;
   $AdConfigCopyWith<$Res>? get adConfig;
   $GroupsCopyWith<$Res>? get groups;
 }
@@ -79,46 +84,59 @@ class _$LaunchCopyWithImpl<$Res, $Val extends Launch>
     Object? userConfig = freezed,
     Object? appConfig = freezed,
     Object? upgradeConfig = freezed,
-    Object? vpnConfig = freezed,
     Object? adConfig = freezed,
     Object? groups = freezed,
     Object? ranks = freezed,
     Object? exData = freezed,
+    Object? nodes = freezed,
+    Object? tunnelConfig = freezed,
+    Object? socksPort = freezed,
   }) {
-    return _then(_value.copyWith(
-      userConfig: freezed == userConfig
-          ? _value.userConfig
-          : userConfig // ignore: cast_nullable_to_non_nullable
-              as User?,
-      appConfig: freezed == appConfig
-          ? _value.appConfig
-          : appConfig // ignore: cast_nullable_to_non_nullable
-              as AppConfig?,
-      upgradeConfig: freezed == upgradeConfig
-          ? _value.upgradeConfig
-          : upgradeConfig // ignore: cast_nullable_to_non_nullable
-              as Upgrade?,
-      vpnConfig: freezed == vpnConfig
-          ? _value.vpnConfig
-          : vpnConfig // ignore: cast_nullable_to_non_nullable
-              as VpnConfig?,
-      adConfig: freezed == adConfig
-          ? _value.adConfig
-          : adConfig // ignore: cast_nullable_to_non_nullable
-              as AdConfig?,
-      groups: freezed == groups
-          ? _value.groups
-          : groups // ignore: cast_nullable_to_non_nullable
-              as Groups?,
-      ranks: freezed == ranks
-          ? _value.ranks
-          : ranks // ignore: cast_nullable_to_non_nullable
-              as List<Ranks>?,
-      exData: freezed == exData
-          ? _value.exData
-          : exData // ignore: cast_nullable_to_non_nullable
-              as dynamic,
-    ) as $Val);
+    return _then(
+      _value.copyWith(
+            userConfig: freezed == userConfig
+                ? _value.userConfig
+                : userConfig // ignore: cast_nullable_to_non_nullable
+                      as User?,
+            appConfig: freezed == appConfig
+                ? _value.appConfig
+                : appConfig // ignore: cast_nullable_to_non_nullable
+                      as AppConfig?,
+            upgradeConfig: freezed == upgradeConfig
+                ? _value.upgradeConfig
+                : upgradeConfig // ignore: cast_nullable_to_non_nullable
+                      as Upgrade?,
+            adConfig: freezed == adConfig
+                ? _value.adConfig
+                : adConfig // ignore: cast_nullable_to_non_nullable
+                      as AdConfig?,
+            groups: freezed == groups
+                ? _value.groups
+                : groups // ignore: cast_nullable_to_non_nullable
+                      as Groups?,
+            ranks: freezed == ranks
+                ? _value.ranks
+                : ranks // ignore: cast_nullable_to_non_nullable
+                      as List<Ranks>?,
+            exData: freezed == exData
+                ? _value.exData
+                : exData // ignore: cast_nullable_to_non_nullable
+                      as dynamic,
+            nodes: freezed == nodes
+                ? _value.nodes
+                : nodes // ignore: cast_nullable_to_non_nullable
+                      as List<dynamic>?,
+            tunnelConfig: freezed == tunnelConfig
+                ? _value.tunnelConfig
+                : tunnelConfig // ignore: cast_nullable_to_non_nullable
+                      as String?,
+            socksPort: freezed == socksPort
+                ? _value.socksPort
+                : socksPort // ignore: cast_nullable_to_non_nullable
+                      as int?,
+          )
+          as $Val,
+    );
   }
 
   /// Create a copy of Launch
@@ -163,20 +181,6 @@ class _$LaunchCopyWithImpl<$Res, $Val extends Launch>
     });
   }
 
-  /// Create a copy of Launch
-  /// with the given fields replaced by the non-null parameter values.
-  @override
-  @pragma('vm:prefer-inline')
-  $VpnConfigCopyWith<$Res>? get vpnConfig {
-    if (_value.vpnConfig == null) {
-      return null;
-    }
-
-    return $VpnConfigCopyWith<$Res>(_value.vpnConfig!, (value) {
-      return _then(_value.copyWith(vpnConfig: value) as $Val);
-    });
-  }
-
   /// Create a copy of Launch
   /// with the given fields replaced by the non-null parameter values.
   @override
@@ -209,19 +213,23 @@ class _$LaunchCopyWithImpl<$Res, $Val extends Launch>
 /// @nodoc
 abstract class _$$LaunchImplCopyWith<$Res> implements $LaunchCopyWith<$Res> {
   factory _$$LaunchImplCopyWith(
-          _$LaunchImpl value, $Res Function(_$LaunchImpl) then) =
-      __$$LaunchImplCopyWithImpl<$Res>;
+    _$LaunchImpl value,
+    $Res Function(_$LaunchImpl) then,
+  ) = __$$LaunchImplCopyWithImpl<$Res>;
   @override
   @useResult
-  $Res call(
-      {User? userConfig,
-      AppConfig? appConfig,
-      Upgrade? upgradeConfig,
-      VpnConfig? vpnConfig,
-      AdConfig? adConfig,
-      Groups? groups,
-      List<Ranks>? ranks,
-      dynamic exData});
+  $Res call({
+    User? userConfig,
+    AppConfig? appConfig,
+    Upgrade? upgradeConfig,
+    AdConfig? adConfig,
+    Groups? groups,
+    List<Ranks>? ranks,
+    dynamic exData,
+    List<dynamic>? nodes,
+    String? tunnelConfig,
+    int? socksPort,
+  });
 
   @override
   $UserCopyWith<$Res>? get userConfig;
@@ -230,8 +238,6 @@ abstract class _$$LaunchImplCopyWith<$Res> implements $LaunchCopyWith<$Res> {
   @override
   $UpgradeCopyWith<$Res>? get upgradeConfig;
   @override
-  $VpnConfigCopyWith<$Res>? get vpnConfig;
-  @override
   $AdConfigCopyWith<$Res>? get adConfig;
   @override
   $GroupsCopyWith<$Res>? get groups;
@@ -242,8 +248,9 @@ class __$$LaunchImplCopyWithImpl<$Res>
     extends _$LaunchCopyWithImpl<$Res, _$LaunchImpl>
     implements _$$LaunchImplCopyWith<$Res> {
   __$$LaunchImplCopyWithImpl(
-      _$LaunchImpl _value, $Res Function(_$LaunchImpl) _then)
-      : super(_value, _then);
+    _$LaunchImpl _value,
+    $Res Function(_$LaunchImpl) _then,
+  ) : super(_value, _then);
 
   /// Create a copy of Launch
   /// with the given fields replaced by the non-null parameter values.
@@ -253,62 +260,77 @@ class __$$LaunchImplCopyWithImpl<$Res>
     Object? userConfig = freezed,
     Object? appConfig = freezed,
     Object? upgradeConfig = freezed,
-    Object? vpnConfig = freezed,
     Object? adConfig = freezed,
     Object? groups = freezed,
     Object? ranks = freezed,
     Object? exData = freezed,
+    Object? nodes = freezed,
+    Object? tunnelConfig = freezed,
+    Object? socksPort = freezed,
   }) {
-    return _then(_$LaunchImpl(
-      userConfig: freezed == userConfig
-          ? _value.userConfig
-          : userConfig // ignore: cast_nullable_to_non_nullable
-              as User?,
-      appConfig: freezed == appConfig
-          ? _value.appConfig
-          : appConfig // ignore: cast_nullable_to_non_nullable
-              as AppConfig?,
-      upgradeConfig: freezed == upgradeConfig
-          ? _value.upgradeConfig
-          : upgradeConfig // ignore: cast_nullable_to_non_nullable
-              as Upgrade?,
-      vpnConfig: freezed == vpnConfig
-          ? _value.vpnConfig
-          : vpnConfig // ignore: cast_nullable_to_non_nullable
-              as VpnConfig?,
-      adConfig: freezed == adConfig
-          ? _value.adConfig
-          : adConfig // ignore: cast_nullable_to_non_nullable
-              as AdConfig?,
-      groups: freezed == groups
-          ? _value.groups
-          : groups // ignore: cast_nullable_to_non_nullable
-              as Groups?,
-      ranks: freezed == ranks
-          ? _value._ranks
-          : ranks // ignore: cast_nullable_to_non_nullable
-              as List<Ranks>?,
-      exData: freezed == exData
-          ? _value.exData
-          : exData // ignore: cast_nullable_to_non_nullable
-              as dynamic,
-    ));
+    return _then(
+      _$LaunchImpl(
+        userConfig: freezed == userConfig
+            ? _value.userConfig
+            : userConfig // ignore: cast_nullable_to_non_nullable
+                  as User?,
+        appConfig: freezed == appConfig
+            ? _value.appConfig
+            : appConfig // ignore: cast_nullable_to_non_nullable
+                  as AppConfig?,
+        upgradeConfig: freezed == upgradeConfig
+            ? _value.upgradeConfig
+            : upgradeConfig // ignore: cast_nullable_to_non_nullable
+                  as Upgrade?,
+        adConfig: freezed == adConfig
+            ? _value.adConfig
+            : adConfig // ignore: cast_nullable_to_non_nullable
+                  as AdConfig?,
+        groups: freezed == groups
+            ? _value.groups
+            : groups // ignore: cast_nullable_to_non_nullable
+                  as Groups?,
+        ranks: freezed == ranks
+            ? _value._ranks
+            : ranks // ignore: cast_nullable_to_non_nullable
+                  as List<Ranks>?,
+        exData: freezed == exData
+            ? _value.exData
+            : exData // ignore: cast_nullable_to_non_nullable
+                  as dynamic,
+        nodes: freezed == nodes
+            ? _value._nodes
+            : nodes // ignore: cast_nullable_to_non_nullable
+                  as List<dynamic>?,
+        tunnelConfig: freezed == tunnelConfig
+            ? _value.tunnelConfig
+            : tunnelConfig // ignore: cast_nullable_to_non_nullable
+                  as String?,
+        socksPort: freezed == socksPort
+            ? _value.socksPort
+            : socksPort // ignore: cast_nullable_to_non_nullable
+                  as int?,
+      ),
+    );
   }
 }
 
 /// @nodoc
 @JsonSerializable()
 class _$LaunchImpl with DiagnosticableTreeMixin implements _Launch {
-  const _$LaunchImpl(
-      {this.userConfig,
-      this.appConfig,
-      this.upgradeConfig,
-      this.vpnConfig,
-      this.adConfig,
-      this.groups,
-      final List<Ranks>? ranks,
-      this.exData})
-      : _ranks = ranks;
+  const _$LaunchImpl({
+    this.userConfig,
+    this.appConfig,
+    this.upgradeConfig,
+    this.adConfig,
+    this.groups,
+    final List<Ranks>? ranks,
+    this.exData,
+    final List<dynamic>? nodes,
+    this.tunnelConfig,
+    this.socksPort,
+  }) : _ranks = ranks,
+       _nodes = nodes;
 
   factory _$LaunchImpl.fromJson(Map<String, dynamic> json) =>
       _$$LaunchImplFromJson(json);
@@ -320,8 +342,6 @@ class _$LaunchImpl with DiagnosticableTreeMixin implements _Launch {
   @override
   final Upgrade? upgradeConfig;
   @override
-  final VpnConfig? vpnConfig;
-  @override
   final AdConfig? adConfig;
   @override
   final Groups? groups;
@@ -337,10 +357,24 @@ class _$LaunchImpl with DiagnosticableTreeMixin implements _Launch {
 
   @override
   final dynamic exData;
+  final List<dynamic>? _nodes;
+  @override
+  List<dynamic>? get nodes {
+    final value = _nodes;
+    if (value == null) return null;
+    if (_nodes is EqualUnmodifiableListView) return _nodes;
+    // ignore: implicit_dynamic_type
+    return EqualUnmodifiableListView(value);
+  }
+
+  @override
+  final String? tunnelConfig;
+  @override
+  final int? socksPort;
 
   @override
   String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
-    return 'Launch(userConfig: $userConfig, appConfig: $appConfig, upgradeConfig: $upgradeConfig, vpnConfig: $vpnConfig, adConfig: $adConfig, groups: $groups, ranks: $ranks, exData: $exData)';
+    return 'Launch(userConfig: $userConfig, appConfig: $appConfig, upgradeConfig: $upgradeConfig, adConfig: $adConfig, groups: $groups, ranks: $ranks, exData: $exData, nodes: $nodes, tunnelConfig: $tunnelConfig, socksPort: $socksPort)';
   }
 
   @override
@@ -351,11 +385,13 @@ class _$LaunchImpl with DiagnosticableTreeMixin implements _Launch {
       ..add(DiagnosticsProperty('userConfig', userConfig))
       ..add(DiagnosticsProperty('appConfig', appConfig))
       ..add(DiagnosticsProperty('upgradeConfig', upgradeConfig))
-      ..add(DiagnosticsProperty('vpnConfig', vpnConfig))
       ..add(DiagnosticsProperty('adConfig', adConfig))
       ..add(DiagnosticsProperty('groups', groups))
       ..add(DiagnosticsProperty('ranks', ranks))
-      ..add(DiagnosticsProperty('exData', exData));
+      ..add(DiagnosticsProperty('exData', exData))
+      ..add(DiagnosticsProperty('nodes', nodes))
+      ..add(DiagnosticsProperty('tunnelConfig', tunnelConfig))
+      ..add(DiagnosticsProperty('socksPort', socksPort));
   }
 
   @override
@@ -369,27 +405,33 @@ class _$LaunchImpl with DiagnosticableTreeMixin implements _Launch {
                 other.appConfig == appConfig) &&
             (identical(other.upgradeConfig, upgradeConfig) ||
                 other.upgradeConfig == upgradeConfig) &&
-            (identical(other.vpnConfig, vpnConfig) ||
-                other.vpnConfig == vpnConfig) &&
             (identical(other.adConfig, adConfig) ||
                 other.adConfig == adConfig) &&
             (identical(other.groups, groups) || other.groups == groups) &&
             const DeepCollectionEquality().equals(other._ranks, _ranks) &&
-            const DeepCollectionEquality().equals(other.exData, exData));
+            const DeepCollectionEquality().equals(other.exData, exData) &&
+            const DeepCollectionEquality().equals(other._nodes, _nodes) &&
+            (identical(other.tunnelConfig, tunnelConfig) ||
+                other.tunnelConfig == tunnelConfig) &&
+            (identical(other.socksPort, socksPort) ||
+                other.socksPort == socksPort));
   }
 
   @JsonKey(includeFromJson: false, includeToJson: false)
   @override
   int get hashCode => Object.hash(
-      runtimeType,
-      userConfig,
-      appConfig,
-      upgradeConfig,
-      vpnConfig,
-      adConfig,
-      groups,
-      const DeepCollectionEquality().hash(_ranks),
-      const DeepCollectionEquality().hash(exData));
+    runtimeType,
+    userConfig,
+    appConfig,
+    upgradeConfig,
+    adConfig,
+    groups,
+    const DeepCollectionEquality().hash(_ranks),
+    const DeepCollectionEquality().hash(exData),
+    const DeepCollectionEquality().hash(_nodes),
+    tunnelConfig,
+    socksPort,
+  );
 
   /// Create a copy of Launch
   /// with the given fields replaced by the non-null parameter values.
@@ -401,22 +443,23 @@ class _$LaunchImpl with DiagnosticableTreeMixin implements _Launch {
 
   @override
   Map<String, dynamic> toJson() {
-    return _$$LaunchImplToJson(
-      this,
-    );
+    return _$$LaunchImplToJson(this);
   }
 }
 
 abstract class _Launch implements Launch {
-  const factory _Launch(
-      {final User? userConfig,
-      final AppConfig? appConfig,
-      final Upgrade? upgradeConfig,
-      final VpnConfig? vpnConfig,
-      final AdConfig? adConfig,
-      final Groups? groups,
-      final List<Ranks>? ranks,
-      final dynamic exData}) = _$LaunchImpl;
+  const factory _Launch({
+    final User? userConfig,
+    final AppConfig? appConfig,
+    final Upgrade? upgradeConfig,
+    final AdConfig? adConfig,
+    final Groups? groups,
+    final List<Ranks>? ranks,
+    final dynamic exData,
+    final List<dynamic>? nodes,
+    final String? tunnelConfig,
+    final int? socksPort,
+  }) = _$LaunchImpl;
 
   factory _Launch.fromJson(Map<String, dynamic> json) = _$LaunchImpl.fromJson;
 
@@ -427,8 +470,6 @@ abstract class _Launch implements Launch {
   @override
   Upgrade? get upgradeConfig;
   @override
-  VpnConfig? get vpnConfig;
-  @override
   AdConfig? get adConfig;
   @override
   Groups? get groups;
@@ -436,6 +477,12 @@ abstract class _Launch implements Launch {
   List<Ranks>? get ranks;
   @override
   dynamic get exData;
+  @override
+  List<dynamic>? get nodes;
+  @override
+  String? get tunnelConfig;
+  @override
+  int? get socksPort;
 
   /// Create a copy of Launch
   /// with the given fields replaced by the non-null parameter values.

+ 26 - 24
lib/app/data/models/launch/launch.g.dart

@@ -7,38 +7,40 @@ part of 'launch.dart';
 // **************************************************************************
 
 _$LaunchImpl _$$LaunchImplFromJson(Map<String, dynamic> json) => _$LaunchImpl(
-      userConfig: json['userConfig'] == null
-          ? null
-          : User.fromJson(json['userConfig'] as Map<String, dynamic>),
-      appConfig: json['appConfig'] == null
-          ? null
-          : AppConfig.fromJson(json['appConfig'] as Map<String, dynamic>),
-      upgradeConfig: json['upgradeConfig'] == null
-          ? null
-          : Upgrade.fromJson(json['upgradeConfig'] as Map<String, dynamic>),
-      vpnConfig: json['vpnConfig'] == null
-          ? null
-          : VpnConfig.fromJson(json['vpnConfig'] as Map<String, dynamic>),
-      adConfig: json['adConfig'] == null
-          ? null
-          : AdConfig.fromJson(json['adConfig'] as Map<String, dynamic>),
-      groups: json['groups'] == null
-          ? null
-          : Groups.fromJson(json['groups'] as Map<String, dynamic>),
-      ranks: (json['ranks'] as List<dynamic>?)
-          ?.map((e) => Ranks.fromJson(e as Map<String, dynamic>))
-          .toList(),
-      exData: json['exData'],
-    );
+  userConfig: json['userConfig'] == null
+      ? null
+      : User.fromJson(json['userConfig'] as Map<String, dynamic>),
+  appConfig: json['appConfig'] == null
+      ? null
+      : AppConfig.fromJson(json['appConfig'] as Map<String, dynamic>),
+  upgradeConfig: json['upgradeConfig'] == null
+      ? null
+      : Upgrade.fromJson(json['upgradeConfig'] as Map<String, dynamic>),
+  adConfig: json['adConfig'] == null
+      ? null
+      : AdConfig.fromJson(json['adConfig'] as Map<String, dynamic>),
+  groups: json['groups'] == null
+      ? null
+      : Groups.fromJson(json['groups'] as Map<String, dynamic>),
+  ranks: (json['ranks'] as List<dynamic>?)
+      ?.map((e) => Ranks.fromJson(e as Map<String, dynamic>))
+      .toList(),
+  exData: json['exData'],
+  nodes: json['nodes'] as List<dynamic>?,
+  tunnelConfig: json['tunnelConfig'] as String?,
+  socksPort: (json['socksPort'] as num?)?.toInt(),
+);
 
 Map<String, dynamic> _$$LaunchImplToJson(_$LaunchImpl instance) =>
     <String, dynamic>{
       'userConfig': instance.userConfig,
       'appConfig': instance.appConfig,
       'upgradeConfig': instance.upgradeConfig,
-      'vpnConfig': instance.vpnConfig,
       'adConfig': instance.adConfig,
       'groups': instance.groups,
       'ranks': instance.ranks,
       'exData': instance.exData,
+      'nodes': instance.nodes,
+      'tunnelConfig': instance.tunnelConfig,
+      'socksPort': instance.socksPort,
     };

+ 47 - 38
lib/app/data/models/launch/ranks.freezed.dart

@@ -12,7 +12,8 @@ part of 'ranks.dart';
 T _$identity<T>(T value) => value;
 
 final _privateConstructorUsedError = UnsupportedError(
-    'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
+  'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
+);
 
 Ranks _$RanksFromJson(Map<String, dynamic> json) {
   return _Ranks.fromJson(json);
@@ -60,28 +61,32 @@ class _$RanksCopyWithImpl<$Res, $Val extends Ranks>
     Object? name = freezed,
     Object? icon = freezed,
   }) {
-    return _then(_value.copyWith(
-      id: freezed == id
-          ? _value.id
-          : id // ignore: cast_nullable_to_non_nullable
-              as int?,
-      name: freezed == name
-          ? _value.name
-          : name // ignore: cast_nullable_to_non_nullable
-              as String?,
-      icon: freezed == icon
-          ? _value.icon
-          : icon // ignore: cast_nullable_to_non_nullable
-              as String?,
-    ) as $Val);
+    return _then(
+      _value.copyWith(
+            id: freezed == id
+                ? _value.id
+                : id // ignore: cast_nullable_to_non_nullable
+                      as int?,
+            name: freezed == name
+                ? _value.name
+                : name // ignore: cast_nullable_to_non_nullable
+                      as String?,
+            icon: freezed == icon
+                ? _value.icon
+                : icon // ignore: cast_nullable_to_non_nullable
+                      as String?,
+          )
+          as $Val,
+    );
   }
 }
 
 /// @nodoc
 abstract class _$$RanksImplCopyWith<$Res> implements $RanksCopyWith<$Res> {
   factory _$$RanksImplCopyWith(
-          _$RanksImpl value, $Res Function(_$RanksImpl) then) =
-      __$$RanksImplCopyWithImpl<$Res>;
+    _$RanksImpl value,
+    $Res Function(_$RanksImpl) then,
+  ) = __$$RanksImplCopyWithImpl<$Res>;
   @override
   @useResult
   $Res call({int? id, String? name, String? icon});
@@ -92,8 +97,9 @@ class __$$RanksImplCopyWithImpl<$Res>
     extends _$RanksCopyWithImpl<$Res, _$RanksImpl>
     implements _$$RanksImplCopyWith<$Res> {
   __$$RanksImplCopyWithImpl(
-      _$RanksImpl _value, $Res Function(_$RanksImpl) _then)
-      : super(_value, _then);
+    _$RanksImpl _value,
+    $Res Function(_$RanksImpl) _then,
+  ) : super(_value, _then);
 
   /// Create a copy of Ranks
   /// with the given fields replaced by the non-null parameter values.
@@ -104,20 +110,22 @@ class __$$RanksImplCopyWithImpl<$Res>
     Object? name = freezed,
     Object? icon = freezed,
   }) {
-    return _then(_$RanksImpl(
-      id: freezed == id
-          ? _value.id
-          : id // ignore: cast_nullable_to_non_nullable
-              as int?,
-      name: freezed == name
-          ? _value.name
-          : name // ignore: cast_nullable_to_non_nullable
-              as String?,
-      icon: freezed == icon
-          ? _value.icon
-          : icon // ignore: cast_nullable_to_non_nullable
-              as String?,
-    ));
+    return _then(
+      _$RanksImpl(
+        id: freezed == id
+            ? _value.id
+            : id // ignore: cast_nullable_to_non_nullable
+                  as int?,
+        name: freezed == name
+            ? _value.name
+            : name // ignore: cast_nullable_to_non_nullable
+                  as String?,
+        icon: freezed == icon
+            ? _value.icon
+            : icon // ignore: cast_nullable_to_non_nullable
+                  as String?,
+      ),
+    );
   }
 }
 
@@ -175,15 +183,16 @@ class _$RanksImpl with DiagnosticableTreeMixin implements _Ranks {
 
   @override
   Map<String, dynamic> toJson() {
-    return _$$RanksImplToJson(
-      this,
-    );
+    return _$$RanksImplToJson(this);
   }
 }
 
 abstract class _Ranks implements Ranks {
-  const factory _Ranks(
-      {final int? id, final String? name, final String? icon}) = _$RanksImpl;
+  const factory _Ranks({
+    final int? id,
+    final String? name,
+    final String? icon,
+  }) = _$RanksImpl;
 
   factory _Ranks.fromJson(Map<String, dynamic> json) = _$RanksImpl.fromJson;
 

+ 4 - 4
lib/app/data/models/launch/ranks.g.dart

@@ -7,10 +7,10 @@ part of 'ranks.dart';
 // **************************************************************************
 
 _$RanksImpl _$$RanksImplFromJson(Map<String, dynamic> json) => _$RanksImpl(
-      id: (json['id'] as num?)?.toInt(),
-      name: json['name'] as String?,
-      icon: json['icon'] as String?,
-    );
+  id: (json['id'] as num?)?.toInt(),
+  name: json['name'] as String?,
+  icon: json['icon'] as String?,
+);
 
 Map<String, dynamic> _$$RanksImplToJson(_$RanksImpl instance) =>
     <String, dynamic>{

+ 149 - 137
lib/app/data/models/launch/upgrade.freezed.dart

@@ -12,7 +12,8 @@ part of 'upgrade.dart';
 T _$identity<T>(T value) => value;
 
 final _privateConstructorUsedError = UnsupportedError(
-    'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
+  'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
+);
 
 Upgrade _$UpgradeFromJson(Map<String, dynamic> json) {
   return _Upgrade.fromJson(json);
@@ -44,16 +45,17 @@ abstract class $UpgradeCopyWith<$Res> {
   factory $UpgradeCopyWith(Upgrade value, $Res Function(Upgrade) then) =
       _$UpgradeCopyWithImpl<$Res, Upgrade>;
   @useResult
-  $Res call(
-      {int? upgradeType,
-      String? versionName,
-      int? versionCode,
-      String? appStoreUrl,
-      String? websiteUrl,
-      List<String>? directUrls,
-      String? md5,
-      String? info,
-      bool? forced});
+  $Res call({
+    int? upgradeType,
+    String? versionName,
+    int? versionCode,
+    String? appStoreUrl,
+    String? websiteUrl,
+    List<String>? directUrls,
+    String? md5,
+    String? info,
+    bool? forced,
+  });
 }
 
 /// @nodoc
@@ -81,64 +83,69 @@ class _$UpgradeCopyWithImpl<$Res, $Val extends Upgrade>
     Object? info = freezed,
     Object? forced = freezed,
   }) {
-    return _then(_value.copyWith(
-      upgradeType: freezed == upgradeType
-          ? _value.upgradeType
-          : upgradeType // ignore: cast_nullable_to_non_nullable
-              as int?,
-      versionName: freezed == versionName
-          ? _value.versionName
-          : versionName // ignore: cast_nullable_to_non_nullable
-              as String?,
-      versionCode: freezed == versionCode
-          ? _value.versionCode
-          : versionCode // ignore: cast_nullable_to_non_nullable
-              as int?,
-      appStoreUrl: freezed == appStoreUrl
-          ? _value.appStoreUrl
-          : appStoreUrl // ignore: cast_nullable_to_non_nullable
-              as String?,
-      websiteUrl: freezed == websiteUrl
-          ? _value.websiteUrl
-          : websiteUrl // ignore: cast_nullable_to_non_nullable
-              as String?,
-      directUrls: freezed == directUrls
-          ? _value.directUrls
-          : directUrls // ignore: cast_nullable_to_non_nullable
-              as List<String>?,
-      md5: freezed == md5
-          ? _value.md5
-          : md5 // ignore: cast_nullable_to_non_nullable
-              as String?,
-      info: freezed == info
-          ? _value.info
-          : info // ignore: cast_nullable_to_non_nullable
-              as String?,
-      forced: freezed == forced
-          ? _value.forced
-          : forced // ignore: cast_nullable_to_non_nullable
-              as bool?,
-    ) as $Val);
+    return _then(
+      _value.copyWith(
+            upgradeType: freezed == upgradeType
+                ? _value.upgradeType
+                : upgradeType // ignore: cast_nullable_to_non_nullable
+                      as int?,
+            versionName: freezed == versionName
+                ? _value.versionName
+                : versionName // ignore: cast_nullable_to_non_nullable
+                      as String?,
+            versionCode: freezed == versionCode
+                ? _value.versionCode
+                : versionCode // ignore: cast_nullable_to_non_nullable
+                      as int?,
+            appStoreUrl: freezed == appStoreUrl
+                ? _value.appStoreUrl
+                : appStoreUrl // ignore: cast_nullable_to_non_nullable
+                      as String?,
+            websiteUrl: freezed == websiteUrl
+                ? _value.websiteUrl
+                : websiteUrl // ignore: cast_nullable_to_non_nullable
+                      as String?,
+            directUrls: freezed == directUrls
+                ? _value.directUrls
+                : directUrls // ignore: cast_nullable_to_non_nullable
+                      as List<String>?,
+            md5: freezed == md5
+                ? _value.md5
+                : md5 // ignore: cast_nullable_to_non_nullable
+                      as String?,
+            info: freezed == info
+                ? _value.info
+                : info // ignore: cast_nullable_to_non_nullable
+                      as String?,
+            forced: freezed == forced
+                ? _value.forced
+                : forced // ignore: cast_nullable_to_non_nullable
+                      as bool?,
+          )
+          as $Val,
+    );
   }
 }
 
 /// @nodoc
 abstract class _$$UpgradeImplCopyWith<$Res> implements $UpgradeCopyWith<$Res> {
   factory _$$UpgradeImplCopyWith(
-          _$UpgradeImpl value, $Res Function(_$UpgradeImpl) then) =
-      __$$UpgradeImplCopyWithImpl<$Res>;
+    _$UpgradeImpl value,
+    $Res Function(_$UpgradeImpl) then,
+  ) = __$$UpgradeImplCopyWithImpl<$Res>;
   @override
   @useResult
-  $Res call(
-      {int? upgradeType,
-      String? versionName,
-      int? versionCode,
-      String? appStoreUrl,
-      String? websiteUrl,
-      List<String>? directUrls,
-      String? md5,
-      String? info,
-      bool? forced});
+  $Res call({
+    int? upgradeType,
+    String? versionName,
+    int? versionCode,
+    String? appStoreUrl,
+    String? websiteUrl,
+    List<String>? directUrls,
+    String? md5,
+    String? info,
+    bool? forced,
+  });
 }
 
 /// @nodoc
@@ -146,8 +153,9 @@ class __$$UpgradeImplCopyWithImpl<$Res>
     extends _$UpgradeCopyWithImpl<$Res, _$UpgradeImpl>
     implements _$$UpgradeImplCopyWith<$Res> {
   __$$UpgradeImplCopyWithImpl(
-      _$UpgradeImpl _value, $Res Function(_$UpgradeImpl) _then)
-      : super(_value, _then);
+    _$UpgradeImpl _value,
+    $Res Function(_$UpgradeImpl) _then,
+  ) : super(_value, _then);
 
   /// Create a copy of Upgrade
   /// with the given fields replaced by the non-null parameter values.
@@ -164,61 +172,63 @@ class __$$UpgradeImplCopyWithImpl<$Res>
     Object? info = freezed,
     Object? forced = freezed,
   }) {
-    return _then(_$UpgradeImpl(
-      upgradeType: freezed == upgradeType
-          ? _value.upgradeType
-          : upgradeType // ignore: cast_nullable_to_non_nullable
-              as int?,
-      versionName: freezed == versionName
-          ? _value.versionName
-          : versionName // ignore: cast_nullable_to_non_nullable
-              as String?,
-      versionCode: freezed == versionCode
-          ? _value.versionCode
-          : versionCode // ignore: cast_nullable_to_non_nullable
-              as int?,
-      appStoreUrl: freezed == appStoreUrl
-          ? _value.appStoreUrl
-          : appStoreUrl // ignore: cast_nullable_to_non_nullable
-              as String?,
-      websiteUrl: freezed == websiteUrl
-          ? _value.websiteUrl
-          : websiteUrl // ignore: cast_nullable_to_non_nullable
-              as String?,
-      directUrls: freezed == directUrls
-          ? _value._directUrls
-          : directUrls // ignore: cast_nullable_to_non_nullable
-              as List<String>?,
-      md5: freezed == md5
-          ? _value.md5
-          : md5 // ignore: cast_nullable_to_non_nullable
-              as String?,
-      info: freezed == info
-          ? _value.info
-          : info // ignore: cast_nullable_to_non_nullable
-              as String?,
-      forced: freezed == forced
-          ? _value.forced
-          : forced // ignore: cast_nullable_to_non_nullable
-              as bool?,
-    ));
+    return _then(
+      _$UpgradeImpl(
+        upgradeType: freezed == upgradeType
+            ? _value.upgradeType
+            : upgradeType // ignore: cast_nullable_to_non_nullable
+                  as int?,
+        versionName: freezed == versionName
+            ? _value.versionName
+            : versionName // ignore: cast_nullable_to_non_nullable
+                  as String?,
+        versionCode: freezed == versionCode
+            ? _value.versionCode
+            : versionCode // ignore: cast_nullable_to_non_nullable
+                  as int?,
+        appStoreUrl: freezed == appStoreUrl
+            ? _value.appStoreUrl
+            : appStoreUrl // ignore: cast_nullable_to_non_nullable
+                  as String?,
+        websiteUrl: freezed == websiteUrl
+            ? _value.websiteUrl
+            : websiteUrl // ignore: cast_nullable_to_non_nullable
+                  as String?,
+        directUrls: freezed == directUrls
+            ? _value._directUrls
+            : directUrls // ignore: cast_nullable_to_non_nullable
+                  as List<String>?,
+        md5: freezed == md5
+            ? _value.md5
+            : md5 // ignore: cast_nullable_to_non_nullable
+                  as String?,
+        info: freezed == info
+            ? _value.info
+            : info // ignore: cast_nullable_to_non_nullable
+                  as String?,
+        forced: freezed == forced
+            ? _value.forced
+            : forced // ignore: cast_nullable_to_non_nullable
+                  as bool?,
+      ),
+    );
   }
 }
 
 /// @nodoc
 @JsonSerializable()
 class _$UpgradeImpl with DiagnosticableTreeMixin implements _Upgrade {
-  const _$UpgradeImpl(
-      {this.upgradeType,
-      this.versionName,
-      this.versionCode,
-      this.appStoreUrl,
-      this.websiteUrl,
-      final List<String>? directUrls,
-      this.md5,
-      this.info,
-      this.forced})
-      : _directUrls = directUrls;
+  const _$UpgradeImpl({
+    this.upgradeType,
+    this.versionName,
+    this.versionCode,
+    this.appStoreUrl,
+    this.websiteUrl,
+    final List<String>? directUrls,
+    this.md5,
+    this.info,
+    this.forced,
+  }) : _directUrls = directUrls;
 
   factory _$UpgradeImpl.fromJson(Map<String, dynamic> json) =>
       _$$UpgradeImplFromJson(json);
@@ -286,8 +296,10 @@ class _$UpgradeImpl with DiagnosticableTreeMixin implements _Upgrade {
                 other.appStoreUrl == appStoreUrl) &&
             (identical(other.websiteUrl, websiteUrl) ||
                 other.websiteUrl == websiteUrl) &&
-            const DeepCollectionEquality()
-                .equals(other._directUrls, _directUrls) &&
+            const DeepCollectionEquality().equals(
+              other._directUrls,
+              _directUrls,
+            ) &&
             (identical(other.md5, md5) || other.md5 == md5) &&
             (identical(other.info, info) || other.info == info) &&
             (identical(other.forced, forced) || other.forced == forced));
@@ -296,16 +308,17 @@ class _$UpgradeImpl with DiagnosticableTreeMixin implements _Upgrade {
   @JsonKey(includeFromJson: false, includeToJson: false)
   @override
   int get hashCode => Object.hash(
-      runtimeType,
-      upgradeType,
-      versionName,
-      versionCode,
-      appStoreUrl,
-      websiteUrl,
-      const DeepCollectionEquality().hash(_directUrls),
-      md5,
-      info,
-      forced);
+    runtimeType,
+    upgradeType,
+    versionName,
+    versionCode,
+    appStoreUrl,
+    websiteUrl,
+    const DeepCollectionEquality().hash(_directUrls),
+    md5,
+    info,
+    forced,
+  );
 
   /// Create a copy of Upgrade
   /// with the given fields replaced by the non-null parameter values.
@@ -317,23 +330,22 @@ class _$UpgradeImpl with DiagnosticableTreeMixin implements _Upgrade {
 
   @override
   Map<String, dynamic> toJson() {
-    return _$$UpgradeImplToJson(
-      this,
-    );
+    return _$$UpgradeImplToJson(this);
   }
 }
 
 abstract class _Upgrade implements Upgrade {
-  const factory _Upgrade(
-      {final int? upgradeType,
-      final String? versionName,
-      final int? versionCode,
-      final String? appStoreUrl,
-      final String? websiteUrl,
-      final List<String>? directUrls,
-      final String? md5,
-      final String? info,
-      final bool? forced}) = _$UpgradeImpl;
+  const factory _Upgrade({
+    final int? upgradeType,
+    final String? versionName,
+    final int? versionCode,
+    final String? appStoreUrl,
+    final String? websiteUrl,
+    final List<String>? directUrls,
+    final String? md5,
+    final String? info,
+    final bool? forced,
+  }) = _$UpgradeImpl;
 
   factory _Upgrade.fromJson(Map<String, dynamic> json) = _$UpgradeImpl.fromJson;
 

+ 171 - 161
lib/app/data/models/launch/user.freezed.dart

@@ -12,7 +12,8 @@ part of 'user.dart';
 T _$identity<T>(T value) => value;
 
 final _privateConstructorUsedError = UnsupportedError(
-    'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
+  'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
+);
 
 User _$UserFromJson(Map<String, dynamic> json) {
   return _User.fromJson(json);
@@ -47,18 +48,19 @@ abstract class $UserCopyWith<$Res> {
   factory $UserCopyWith(User value, $Res Function(User) then) =
       _$UserCopyWithImpl<$Res, User>;
   @useResult
-  $Res call(
-      {String? country,
-      String? userIp,
-      String? accessToken,
-      String? refreshToken,
-      String? accountKey,
-      String? accountPassword,
-      String? createTime,
-      bool? geographyEea,
-      int? memberLevel,
-      String? account,
-      bool? activated});
+  $Res call({
+    String? country,
+    String? userIp,
+    String? accessToken,
+    String? refreshToken,
+    String? accountKey,
+    String? accountPassword,
+    String? createTime,
+    bool? geographyEea,
+    int? memberLevel,
+    String? account,
+    bool? activated,
+  });
 }
 
 /// @nodoc
@@ -88,74 +90,79 @@ class _$UserCopyWithImpl<$Res, $Val extends User>
     Object? account = freezed,
     Object? activated = freezed,
   }) {
-    return _then(_value.copyWith(
-      country: freezed == country
-          ? _value.country
-          : country // ignore: cast_nullable_to_non_nullable
-              as String?,
-      userIp: freezed == userIp
-          ? _value.userIp
-          : userIp // ignore: cast_nullable_to_non_nullable
-              as String?,
-      accessToken: freezed == accessToken
-          ? _value.accessToken
-          : accessToken // ignore: cast_nullable_to_non_nullable
-              as String?,
-      refreshToken: freezed == refreshToken
-          ? _value.refreshToken
-          : refreshToken // ignore: cast_nullable_to_non_nullable
-              as String?,
-      accountKey: freezed == accountKey
-          ? _value.accountKey
-          : accountKey // ignore: cast_nullable_to_non_nullable
-              as String?,
-      accountPassword: freezed == accountPassword
-          ? _value.accountPassword
-          : accountPassword // ignore: cast_nullable_to_non_nullable
-              as String?,
-      createTime: freezed == createTime
-          ? _value.createTime
-          : createTime // ignore: cast_nullable_to_non_nullable
-              as String?,
-      geographyEea: freezed == geographyEea
-          ? _value.geographyEea
-          : geographyEea // ignore: cast_nullable_to_non_nullable
-              as bool?,
-      memberLevel: freezed == memberLevel
-          ? _value.memberLevel
-          : memberLevel // ignore: cast_nullable_to_non_nullable
-              as int?,
-      account: freezed == account
-          ? _value.account
-          : account // ignore: cast_nullable_to_non_nullable
-              as String?,
-      activated: freezed == activated
-          ? _value.activated
-          : activated // ignore: cast_nullable_to_non_nullable
-              as bool?,
-    ) as $Val);
+    return _then(
+      _value.copyWith(
+            country: freezed == country
+                ? _value.country
+                : country // ignore: cast_nullable_to_non_nullable
+                      as String?,
+            userIp: freezed == userIp
+                ? _value.userIp
+                : userIp // ignore: cast_nullable_to_non_nullable
+                      as String?,
+            accessToken: freezed == accessToken
+                ? _value.accessToken
+                : accessToken // ignore: cast_nullable_to_non_nullable
+                      as String?,
+            refreshToken: freezed == refreshToken
+                ? _value.refreshToken
+                : refreshToken // ignore: cast_nullable_to_non_nullable
+                      as String?,
+            accountKey: freezed == accountKey
+                ? _value.accountKey
+                : accountKey // ignore: cast_nullable_to_non_nullable
+                      as String?,
+            accountPassword: freezed == accountPassword
+                ? _value.accountPassword
+                : accountPassword // ignore: cast_nullable_to_non_nullable
+                      as String?,
+            createTime: freezed == createTime
+                ? _value.createTime
+                : createTime // ignore: cast_nullable_to_non_nullable
+                      as String?,
+            geographyEea: freezed == geographyEea
+                ? _value.geographyEea
+                : geographyEea // ignore: cast_nullable_to_non_nullable
+                      as bool?,
+            memberLevel: freezed == memberLevel
+                ? _value.memberLevel
+                : memberLevel // ignore: cast_nullable_to_non_nullable
+                      as int?,
+            account: freezed == account
+                ? _value.account
+                : account // ignore: cast_nullable_to_non_nullable
+                      as String?,
+            activated: freezed == activated
+                ? _value.activated
+                : activated // ignore: cast_nullable_to_non_nullable
+                      as bool?,
+          )
+          as $Val,
+    );
   }
 }
 
 /// @nodoc
 abstract class _$$UserImplCopyWith<$Res> implements $UserCopyWith<$Res> {
   factory _$$UserImplCopyWith(
-          _$UserImpl value, $Res Function(_$UserImpl) then) =
-      __$$UserImplCopyWithImpl<$Res>;
+    _$UserImpl value,
+    $Res Function(_$UserImpl) then,
+  ) = __$$UserImplCopyWithImpl<$Res>;
   @override
   @useResult
-  $Res call(
-      {String? country,
-      String? userIp,
-      String? accessToken,
-      String? refreshToken,
-      String? accountKey,
-      String? accountPassword,
-      String? createTime,
-      bool? geographyEea,
-      int? memberLevel,
-      String? account,
-      bool? activated});
+  $Res call({
+    String? country,
+    String? userIp,
+    String? accessToken,
+    String? refreshToken,
+    String? accountKey,
+    String? accountPassword,
+    String? createTime,
+    bool? geographyEea,
+    int? memberLevel,
+    String? account,
+    bool? activated,
+  });
 }
 
 /// @nodoc
@@ -163,7 +170,7 @@ class __$$UserImplCopyWithImpl<$Res>
     extends _$UserCopyWithImpl<$Res, _$UserImpl>
     implements _$$UserImplCopyWith<$Res> {
   __$$UserImplCopyWithImpl(_$UserImpl _value, $Res Function(_$UserImpl) _then)
-      : super(_value, _then);
+    : super(_value, _then);
 
   /// Create a copy of User
   /// with the given fields replaced by the non-null parameter values.
@@ -182,70 +189,73 @@ class __$$UserImplCopyWithImpl<$Res>
     Object? account = freezed,
     Object? activated = freezed,
   }) {
-    return _then(_$UserImpl(
-      country: freezed == country
-          ? _value.country
-          : country // ignore: cast_nullable_to_non_nullable
-              as String?,
-      userIp: freezed == userIp
-          ? _value.userIp
-          : userIp // ignore: cast_nullable_to_non_nullable
-              as String?,
-      accessToken: freezed == accessToken
-          ? _value.accessToken
-          : accessToken // ignore: cast_nullable_to_non_nullable
-              as String?,
-      refreshToken: freezed == refreshToken
-          ? _value.refreshToken
-          : refreshToken // ignore: cast_nullable_to_non_nullable
-              as String?,
-      accountKey: freezed == accountKey
-          ? _value.accountKey
-          : accountKey // ignore: cast_nullable_to_non_nullable
-              as String?,
-      accountPassword: freezed == accountPassword
-          ? _value.accountPassword
-          : accountPassword // ignore: cast_nullable_to_non_nullable
-              as String?,
-      createTime: freezed == createTime
-          ? _value.createTime
-          : createTime // ignore: cast_nullable_to_non_nullable
-              as String?,
-      geographyEea: freezed == geographyEea
-          ? _value.geographyEea
-          : geographyEea // ignore: cast_nullable_to_non_nullable
-              as bool?,
-      memberLevel: freezed == memberLevel
-          ? _value.memberLevel
-          : memberLevel // ignore: cast_nullable_to_non_nullable
-              as int?,
-      account: freezed == account
-          ? _value.account
-          : account // ignore: cast_nullable_to_non_nullable
-              as String?,
-      activated: freezed == activated
-          ? _value.activated
-          : activated // ignore: cast_nullable_to_non_nullable
-              as bool?,
-    ));
+    return _then(
+      _$UserImpl(
+        country: freezed == country
+            ? _value.country
+            : country // ignore: cast_nullable_to_non_nullable
+                  as String?,
+        userIp: freezed == userIp
+            ? _value.userIp
+            : userIp // ignore: cast_nullable_to_non_nullable
+                  as String?,
+        accessToken: freezed == accessToken
+            ? _value.accessToken
+            : accessToken // ignore: cast_nullable_to_non_nullable
+                  as String?,
+        refreshToken: freezed == refreshToken
+            ? _value.refreshToken
+            : refreshToken // ignore: cast_nullable_to_non_nullable
+                  as String?,
+        accountKey: freezed == accountKey
+            ? _value.accountKey
+            : accountKey // ignore: cast_nullable_to_non_nullable
+                  as String?,
+        accountPassword: freezed == accountPassword
+            ? _value.accountPassword
+            : accountPassword // ignore: cast_nullable_to_non_nullable
+                  as String?,
+        createTime: freezed == createTime
+            ? _value.createTime
+            : createTime // ignore: cast_nullable_to_non_nullable
+                  as String?,
+        geographyEea: freezed == geographyEea
+            ? _value.geographyEea
+            : geographyEea // ignore: cast_nullable_to_non_nullable
+                  as bool?,
+        memberLevel: freezed == memberLevel
+            ? _value.memberLevel
+            : memberLevel // ignore: cast_nullable_to_non_nullable
+                  as int?,
+        account: freezed == account
+            ? _value.account
+            : account // ignore: cast_nullable_to_non_nullable
+                  as String?,
+        activated: freezed == activated
+            ? _value.activated
+            : activated // ignore: cast_nullable_to_non_nullable
+                  as bool?,
+      ),
+    );
   }
 }
 
 /// @nodoc
 @JsonSerializable()
 class _$UserImpl with DiagnosticableTreeMixin implements _User {
-  const _$UserImpl(
-      {this.country,
-      this.userIp,
-      this.accessToken,
-      this.refreshToken,
-      this.accountKey,
-      this.accountPassword,
-      this.createTime,
-      this.geographyEea,
-      this.memberLevel,
-      this.account,
-      this.activated});
+  const _$UserImpl({
+    this.country,
+    this.userIp,
+    this.accessToken,
+    this.refreshToken,
+    this.accountKey,
+    this.accountPassword,
+    this.createTime,
+    this.geographyEea,
+    this.memberLevel,
+    this.account,
+    this.activated,
+  });
 
   factory _$UserImpl.fromJson(Map<String, dynamic> json) =>
       _$$UserImplFromJson(json);
@@ -268,10 +278,10 @@ class _$UserImpl with DiagnosticableTreeMixin implements _User {
   final bool? geographyEea;
   @override
   final int? memberLevel;
-// 会员等级 1 游客 2 普通用户 3 会员
+  // 会员等级 1 游客 2 普通用户 3 会员
   @override
   final String? account;
-// 用户名
+  // 用户名
   @override
   final bool? activated;
 
@@ -327,18 +337,19 @@ class _$UserImpl with DiagnosticableTreeMixin implements _User {
   @JsonKey(includeFromJson: false, includeToJson: false)
   @override
   int get hashCode => Object.hash(
-      runtimeType,
-      country,
-      userIp,
-      accessToken,
-      refreshToken,
-      accountKey,
-      accountPassword,
-      createTime,
-      geographyEea,
-      memberLevel,
-      account,
-      activated);
+    runtimeType,
+    country,
+    userIp,
+    accessToken,
+    refreshToken,
+    accountKey,
+    accountPassword,
+    createTime,
+    geographyEea,
+    memberLevel,
+    account,
+    activated,
+  );
 
   /// Create a copy of User
   /// with the given fields replaced by the non-null parameter values.
@@ -350,25 +361,24 @@ class _$UserImpl with DiagnosticableTreeMixin implements _User {
 
   @override
   Map<String, dynamic> toJson() {
-    return _$$UserImplToJson(
-      this,
-    );
+    return _$$UserImplToJson(this);
   }
 }
 
 abstract class _User implements User {
-  const factory _User(
-      {final String? country,
-      final String? userIp,
-      final String? accessToken,
-      final String? refreshToken,
-      final String? accountKey,
-      final String? accountPassword,
-      final String? createTime,
-      final bool? geographyEea,
-      final int? memberLevel,
-      final String? account,
-      final bool? activated}) = _$UserImpl;
+  const factory _User({
+    final String? country,
+    final String? userIp,
+    final String? accessToken,
+    final String? refreshToken,
+    final String? accountKey,
+    final String? accountPassword,
+    final String? createTime,
+    final bool? geographyEea,
+    final int? memberLevel,
+    final String? account,
+    final bool? activated,
+  }) = _$UserImpl;
 
   factory _User.fromJson(Map<String, dynamic> json) = _$UserImpl.fromJson;
 

+ 12 - 12
lib/app/data/models/launch/user.g.dart

@@ -7,18 +7,18 @@ part of 'user.dart';
 // **************************************************************************
 
 _$UserImpl _$$UserImplFromJson(Map<String, dynamic> json) => _$UserImpl(
-      country: json['country'] as String?,
-      userIp: json['userIp'] as String?,
-      accessToken: json['accessToken'] as String?,
-      refreshToken: json['refreshToken'] as String?,
-      accountKey: json['accountKey'] as String?,
-      accountPassword: json['accountPassword'] as String?,
-      createTime: json['createTime'] as String?,
-      geographyEea: json['geographyEea'] as bool?,
-      memberLevel: (json['memberLevel'] as num?)?.toInt(),
-      account: json['account'] as String?,
-      activated: json['activated'] as bool?,
-    );
+  country: json['country'] as String?,
+  userIp: json['userIp'] as String?,
+  accessToken: json['accessToken'] as String?,
+  refreshToken: json['refreshToken'] as String?,
+  accountKey: json['accountKey'] as String?,
+  accountPassword: json['accountPassword'] as String?,
+  createTime: json['createTime'] as String?,
+  geographyEea: json['geographyEea'] as bool?,
+  memberLevel: (json['memberLevel'] as num?)?.toInt(),
+  account: json['account'] as String?,
+  activated: json['activated'] as bool?,
+);
 
 Map<String, dynamic> _$$UserImplToJson(_$UserImpl instance) =>
     <String, dynamic>{

+ 195 - 184
lib/app/data/models/launch/vpn_config.freezed.dart

@@ -12,7 +12,8 @@ part of 'vpn_config.dart';
 T _$identity<T>(T value) => value;
 
 final _privateConstructorUsedError = UnsupportedError(
-    'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
+  'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
+);
 
 VpnConfig _$VpnConfigFromJson(Map<String, dynamic> json) {
   return _VpnConfig.fromJson(json);
@@ -43,14 +44,15 @@ abstract class $VpnConfigCopyWith<$Res> {
   factory $VpnConfigCopyWith(VpnConfig value, $Res Function(VpnConfig) then) =
       _$VpnConfigCopyWithImpl<$Res, VpnConfig>;
   @useResult
-  $Res call(
-      {List<String>? cdnRouters,
-      int? autoDisconnectMinutes,
-      ConnectArgs? connectArgs,
-      String? defaultDnsServers,
-      String? forceSkipDomain,
-      List<String>? routers,
-      SkipIps? skipIps});
+  $Res call({
+    List<String>? cdnRouters,
+    int? autoDisconnectMinutes,
+    ConnectArgs? connectArgs,
+    String? defaultDnsServers,
+    String? forceSkipDomain,
+    List<String>? routers,
+    SkipIps? skipIps,
+  });
 
   $ConnectArgsCopyWith<$Res>? get connectArgs;
   $SkipIpsCopyWith<$Res>? get skipIps;
@@ -79,36 +81,39 @@ class _$VpnConfigCopyWithImpl<$Res, $Val extends VpnConfig>
     Object? routers = freezed,
     Object? skipIps = freezed,
   }) {
-    return _then(_value.copyWith(
-      cdnRouters: freezed == cdnRouters
-          ? _value.cdnRouters
-          : cdnRouters // ignore: cast_nullable_to_non_nullable
-              as List<String>?,
-      autoDisconnectMinutes: freezed == autoDisconnectMinutes
-          ? _value.autoDisconnectMinutes
-          : autoDisconnectMinutes // ignore: cast_nullable_to_non_nullable
-              as int?,
-      connectArgs: freezed == connectArgs
-          ? _value.connectArgs
-          : connectArgs // ignore: cast_nullable_to_non_nullable
-              as ConnectArgs?,
-      defaultDnsServers: freezed == defaultDnsServers
-          ? _value.defaultDnsServers
-          : defaultDnsServers // ignore: cast_nullable_to_non_nullable
-              as String?,
-      forceSkipDomain: freezed == forceSkipDomain
-          ? _value.forceSkipDomain
-          : forceSkipDomain // ignore: cast_nullable_to_non_nullable
-              as String?,
-      routers: freezed == routers
-          ? _value.routers
-          : routers // ignore: cast_nullable_to_non_nullable
-              as List<String>?,
-      skipIps: freezed == skipIps
-          ? _value.skipIps
-          : skipIps // ignore: cast_nullable_to_non_nullable
-              as SkipIps?,
-    ) as $Val);
+    return _then(
+      _value.copyWith(
+            cdnRouters: freezed == cdnRouters
+                ? _value.cdnRouters
+                : cdnRouters // ignore: cast_nullable_to_non_nullable
+                      as List<String>?,
+            autoDisconnectMinutes: freezed == autoDisconnectMinutes
+                ? _value.autoDisconnectMinutes
+                : autoDisconnectMinutes // ignore: cast_nullable_to_non_nullable
+                      as int?,
+            connectArgs: freezed == connectArgs
+                ? _value.connectArgs
+                : connectArgs // ignore: cast_nullable_to_non_nullable
+                      as ConnectArgs?,
+            defaultDnsServers: freezed == defaultDnsServers
+                ? _value.defaultDnsServers
+                : defaultDnsServers // ignore: cast_nullable_to_non_nullable
+                      as String?,
+            forceSkipDomain: freezed == forceSkipDomain
+                ? _value.forceSkipDomain
+                : forceSkipDomain // ignore: cast_nullable_to_non_nullable
+                      as String?,
+            routers: freezed == routers
+                ? _value.routers
+                : routers // ignore: cast_nullable_to_non_nullable
+                      as List<String>?,
+            skipIps: freezed == skipIps
+                ? _value.skipIps
+                : skipIps // ignore: cast_nullable_to_non_nullable
+                      as SkipIps?,
+          )
+          as $Val,
+    );
   }
 
   /// Create a copy of VpnConfig
@@ -144,18 +149,20 @@ class _$VpnConfigCopyWithImpl<$Res, $Val extends VpnConfig>
 abstract class _$$VpnConfigImplCopyWith<$Res>
     implements $VpnConfigCopyWith<$Res> {
   factory _$$VpnConfigImplCopyWith(
-          _$VpnConfigImpl value, $Res Function(_$VpnConfigImpl) then) =
-      __$$VpnConfigImplCopyWithImpl<$Res>;
+    _$VpnConfigImpl value,
+    $Res Function(_$VpnConfigImpl) then,
+  ) = __$$VpnConfigImplCopyWithImpl<$Res>;
   @override
   @useResult
-  $Res call(
-      {List<String>? cdnRouters,
-      int? autoDisconnectMinutes,
-      ConnectArgs? connectArgs,
-      String? defaultDnsServers,
-      String? forceSkipDomain,
-      List<String>? routers,
-      SkipIps? skipIps});
+  $Res call({
+    List<String>? cdnRouters,
+    int? autoDisconnectMinutes,
+    ConnectArgs? connectArgs,
+    String? defaultDnsServers,
+    String? forceSkipDomain,
+    List<String>? routers,
+    SkipIps? skipIps,
+  });
 
   @override
   $ConnectArgsCopyWith<$Res>? get connectArgs;
@@ -168,8 +175,9 @@ class __$$VpnConfigImplCopyWithImpl<$Res>
     extends _$VpnConfigCopyWithImpl<$Res, _$VpnConfigImpl>
     implements _$$VpnConfigImplCopyWith<$Res> {
   __$$VpnConfigImplCopyWithImpl(
-      _$VpnConfigImpl _value, $Res Function(_$VpnConfigImpl) _then)
-      : super(_value, _then);
+    _$VpnConfigImpl _value,
+    $Res Function(_$VpnConfigImpl) _then,
+  ) : super(_value, _then);
 
   /// Create a copy of VpnConfig
   /// with the given fields replaced by the non-null parameter values.
@@ -184,52 +192,54 @@ class __$$VpnConfigImplCopyWithImpl<$Res>
     Object? routers = freezed,
     Object? skipIps = freezed,
   }) {
-    return _then(_$VpnConfigImpl(
-      cdnRouters: freezed == cdnRouters
-          ? _value._cdnRouters
-          : cdnRouters // ignore: cast_nullable_to_non_nullable
-              as List<String>?,
-      autoDisconnectMinutes: freezed == autoDisconnectMinutes
-          ? _value.autoDisconnectMinutes
-          : autoDisconnectMinutes // ignore: cast_nullable_to_non_nullable
-              as int?,
-      connectArgs: freezed == connectArgs
-          ? _value.connectArgs
-          : connectArgs // ignore: cast_nullable_to_non_nullable
-              as ConnectArgs?,
-      defaultDnsServers: freezed == defaultDnsServers
-          ? _value.defaultDnsServers
-          : defaultDnsServers // ignore: cast_nullable_to_non_nullable
-              as String?,
-      forceSkipDomain: freezed == forceSkipDomain
-          ? _value.forceSkipDomain
-          : forceSkipDomain // ignore: cast_nullable_to_non_nullable
-              as String?,
-      routers: freezed == routers
-          ? _value._routers
-          : routers // ignore: cast_nullable_to_non_nullable
-              as List<String>?,
-      skipIps: freezed == skipIps
-          ? _value.skipIps
-          : skipIps // ignore: cast_nullable_to_non_nullable
-              as SkipIps?,
-    ));
+    return _then(
+      _$VpnConfigImpl(
+        cdnRouters: freezed == cdnRouters
+            ? _value._cdnRouters
+            : cdnRouters // ignore: cast_nullable_to_non_nullable
+                  as List<String>?,
+        autoDisconnectMinutes: freezed == autoDisconnectMinutes
+            ? _value.autoDisconnectMinutes
+            : autoDisconnectMinutes // ignore: cast_nullable_to_non_nullable
+                  as int?,
+        connectArgs: freezed == connectArgs
+            ? _value.connectArgs
+            : connectArgs // ignore: cast_nullable_to_non_nullable
+                  as ConnectArgs?,
+        defaultDnsServers: freezed == defaultDnsServers
+            ? _value.defaultDnsServers
+            : defaultDnsServers // ignore: cast_nullable_to_non_nullable
+                  as String?,
+        forceSkipDomain: freezed == forceSkipDomain
+            ? _value.forceSkipDomain
+            : forceSkipDomain // ignore: cast_nullable_to_non_nullable
+                  as String?,
+        routers: freezed == routers
+            ? _value._routers
+            : routers // ignore: cast_nullable_to_non_nullable
+                  as List<String>?,
+        skipIps: freezed == skipIps
+            ? _value.skipIps
+            : skipIps // ignore: cast_nullable_to_non_nullable
+                  as SkipIps?,
+      ),
+    );
   }
 }
 
 /// @nodoc
 @JsonSerializable()
 class _$VpnConfigImpl with DiagnosticableTreeMixin implements _VpnConfig {
-  const _$VpnConfigImpl(
-      {final List<String>? cdnRouters,
-      this.autoDisconnectMinutes,
-      this.connectArgs,
-      this.defaultDnsServers,
-      this.forceSkipDomain,
-      final List<String>? routers,
-      this.skipIps})
-      : _cdnRouters = cdnRouters,
-        _routers = routers;
+  const _$VpnConfigImpl({
+    final List<String>? cdnRouters,
+    this.autoDisconnectMinutes,
+    this.connectArgs,
+    this.defaultDnsServers,
+    this.forceSkipDomain,
+    final List<String>? routers,
+    this.skipIps,
+  }) : _cdnRouters = cdnRouters,
+       _routers = routers;
 
   factory _$VpnConfigImpl.fromJson(Map<String, dynamic> json) =>
       _$$VpnConfigImplFromJson(json);
@@ -289,8 +299,10 @@ class _$VpnConfigImpl with DiagnosticableTreeMixin implements _VpnConfig {
     return identical(this, other) ||
         (other.runtimeType == runtimeType &&
             other is _$VpnConfigImpl &&
-            const DeepCollectionEquality()
-                .equals(other._cdnRouters, _cdnRouters) &&
+            const DeepCollectionEquality().equals(
+              other._cdnRouters,
+              _cdnRouters,
+            ) &&
             (identical(other.autoDisconnectMinutes, autoDisconnectMinutes) ||
                 other.autoDisconnectMinutes == autoDisconnectMinutes) &&
             (identical(other.connectArgs, connectArgs) ||
@@ -306,14 +318,15 @@ class _$VpnConfigImpl with DiagnosticableTreeMixin implements _VpnConfig {
   @JsonKey(includeFromJson: false, includeToJson: false)
   @override
   int get hashCode => Object.hash(
-      runtimeType,
-      const DeepCollectionEquality().hash(_cdnRouters),
-      autoDisconnectMinutes,
-      connectArgs,
-      defaultDnsServers,
-      forceSkipDomain,
-      const DeepCollectionEquality().hash(_routers),
-      skipIps);
+    runtimeType,
+    const DeepCollectionEquality().hash(_cdnRouters),
+    autoDisconnectMinutes,
+    connectArgs,
+    defaultDnsServers,
+    forceSkipDomain,
+    const DeepCollectionEquality().hash(_routers),
+    skipIps,
+  );
 
   /// Create a copy of VpnConfig
   /// with the given fields replaced by the non-null parameter values.
@@ -325,21 +338,20 @@ class _$VpnConfigImpl with DiagnosticableTreeMixin implements _VpnConfig {
 
   @override
   Map<String, dynamic> toJson() {
-    return _$$VpnConfigImplToJson(
-      this,
-    );
+    return _$$VpnConfigImplToJson(this);
   }
 }
 
 abstract class _VpnConfig implements VpnConfig {
-  const factory _VpnConfig(
-      {final List<String>? cdnRouters,
-      final int? autoDisconnectMinutes,
-      final ConnectArgs? connectArgs,
-      final String? defaultDnsServers,
-      final String? forceSkipDomain,
-      final List<String>? routers,
-      final SkipIps? skipIps}) = _$VpnConfigImpl;
+  const factory _VpnConfig({
+    final List<String>? cdnRouters,
+    final int? autoDisconnectMinutes,
+    final ConnectArgs? connectArgs,
+    final String? defaultDnsServers,
+    final String? forceSkipDomain,
+    final List<String>? routers,
+    final SkipIps? skipIps,
+  }) = _$VpnConfigImpl;
 
   factory _VpnConfig.fromJson(Map<String, dynamic> json) =
       _$VpnConfigImpl.fromJson;
@@ -389,8 +401,9 @@ mixin _$ConnectArgs {
 /// @nodoc
 abstract class $ConnectArgsCopyWith<$Res> {
   factory $ConnectArgsCopyWith(
-          ConnectArgs value, $Res Function(ConnectArgs) then) =
-      _$ConnectArgsCopyWithImpl<$Res, ConnectArgs>;
+    ConnectArgs value,
+    $Res Function(ConnectArgs) then,
+  ) = _$ConnectArgsCopyWithImpl<$Res, ConnectArgs>;
   @useResult
   $Res call({String? p, String? v});
 }
@@ -409,20 +422,20 @@ class _$ConnectArgsCopyWithImpl<$Res, $Val extends ConnectArgs>
   /// with the given fields replaced by the non-null parameter values.
   @pragma('vm:prefer-inline')
   @override
-  $Res call({
-    Object? p = freezed,
-    Object? v = freezed,
-  }) {
-    return _then(_value.copyWith(
-      p: freezed == p
-          ? _value.p
-          : p // ignore: cast_nullable_to_non_nullable
-              as String?,
-      v: freezed == v
-          ? _value.v
-          : v // ignore: cast_nullable_to_non_nullable
-              as String?,
-    ) as $Val);
+  $Res call({Object? p = freezed, Object? v = freezed}) {
+    return _then(
+      _value.copyWith(
+            p: freezed == p
+                ? _value.p
+                : p // ignore: cast_nullable_to_non_nullable
+                      as String?,
+            v: freezed == v
+                ? _value.v
+                : v // ignore: cast_nullable_to_non_nullable
+                      as String?,
+          )
+          as $Val,
+    );
   }
 }
 
@@ -430,8 +443,9 @@ class _$ConnectArgsCopyWithImpl<$Res, $Val extends ConnectArgs>
 abstract class _$$ConnectArgsImplCopyWith<$Res>
     implements $ConnectArgsCopyWith<$Res> {
   factory _$$ConnectArgsImplCopyWith(
-          _$ConnectArgsImpl value, $Res Function(_$ConnectArgsImpl) then) =
-      __$$ConnectArgsImplCopyWithImpl<$Res>;
+    _$ConnectArgsImpl value,
+    $Res Function(_$ConnectArgsImpl) then,
+  ) = __$$ConnectArgsImplCopyWithImpl<$Res>;
   @override
   @useResult
   $Res call({String? p, String? v});
@@ -442,27 +456,27 @@ class __$$ConnectArgsImplCopyWithImpl<$Res>
     extends _$ConnectArgsCopyWithImpl<$Res, _$ConnectArgsImpl>
     implements _$$ConnectArgsImplCopyWith<$Res> {
   __$$ConnectArgsImplCopyWithImpl(
-      _$ConnectArgsImpl _value, $Res Function(_$ConnectArgsImpl) _then)
-      : super(_value, _then);
+    _$ConnectArgsImpl _value,
+    $Res Function(_$ConnectArgsImpl) _then,
+  ) : super(_value, _then);
 
   /// Create a copy of ConnectArgs
   /// with the given fields replaced by the non-null parameter values.
   @pragma('vm:prefer-inline')
   @override
-  $Res call({
-    Object? p = freezed,
-    Object? v = freezed,
-  }) {
-    return _then(_$ConnectArgsImpl(
-      p: freezed == p
-          ? _value.p
-          : p // ignore: cast_nullable_to_non_nullable
-              as String?,
-      v: freezed == v
-          ? _value.v
-          : v // ignore: cast_nullable_to_non_nullable
-              as String?,
-    ));
+  $Res call({Object? p = freezed, Object? v = freezed}) {
+    return _then(
+      _$ConnectArgsImpl(
+        p: freezed == p
+            ? _value.p
+            : p // ignore: cast_nullable_to_non_nullable
+                  as String?,
+        v: freezed == v
+            ? _value.v
+            : v // ignore: cast_nullable_to_non_nullable
+                  as String?,
+      ),
+    );
   }
 }
 
@@ -516,9 +530,7 @@ class _$ConnectArgsImpl with DiagnosticableTreeMixin implements _ConnectArgs {
 
   @override
   Map<String, dynamic> toJson() {
-    return _$$ConnectArgsImplToJson(
-      this,
-    );
+    return _$$ConnectArgsImplToJson(this);
   }
 }
 
@@ -582,28 +594,29 @@ class _$SkipIpsCopyWithImpl<$Res, $Val extends SkipIps>
   /// with the given fields replaced by the non-null parameter values.
   @pragma('vm:prefer-inline')
   @override
-  $Res call({
-    Object? md5 = freezed,
-    Object? url = freezed,
-  }) {
-    return _then(_value.copyWith(
-      md5: freezed == md5
-          ? _value.md5
-          : md5 // ignore: cast_nullable_to_non_nullable
-              as String?,
-      url: freezed == url
-          ? _value.url
-          : url // ignore: cast_nullable_to_non_nullable
-              as String?,
-    ) as $Val);
+  $Res call({Object? md5 = freezed, Object? url = freezed}) {
+    return _then(
+      _value.copyWith(
+            md5: freezed == md5
+                ? _value.md5
+                : md5 // ignore: cast_nullable_to_non_nullable
+                      as String?,
+            url: freezed == url
+                ? _value.url
+                : url // ignore: cast_nullable_to_non_nullable
+                      as String?,
+          )
+          as $Val,
+    );
   }
 }
 
 /// @nodoc
 abstract class _$$SkipIpsImplCopyWith<$Res> implements $SkipIpsCopyWith<$Res> {
   factory _$$SkipIpsImplCopyWith(
-          _$SkipIpsImpl value, $Res Function(_$SkipIpsImpl) then) =
-      __$$SkipIpsImplCopyWithImpl<$Res>;
+    _$SkipIpsImpl value,
+    $Res Function(_$SkipIpsImpl) then,
+  ) = __$$SkipIpsImplCopyWithImpl<$Res>;
   @override
   @useResult
   $Res call({String? md5, String? url});
@@ -614,27 +627,27 @@ class __$$SkipIpsImplCopyWithImpl<$Res>
     extends _$SkipIpsCopyWithImpl<$Res, _$SkipIpsImpl>
     implements _$$SkipIpsImplCopyWith<$Res> {
   __$$SkipIpsImplCopyWithImpl(
-      _$SkipIpsImpl _value, $Res Function(_$SkipIpsImpl) _then)
-      : super(_value, _then);
+    _$SkipIpsImpl _value,
+    $Res Function(_$SkipIpsImpl) _then,
+  ) : super(_value, _then);
 
   /// Create a copy of SkipIps
   /// with the given fields replaced by the non-null parameter values.
   @pragma('vm:prefer-inline')
   @override
-  $Res call({
-    Object? md5 = freezed,
-    Object? url = freezed,
-  }) {
-    return _then(_$SkipIpsImpl(
-      md5: freezed == md5
-          ? _value.md5
-          : md5 // ignore: cast_nullable_to_non_nullable
-              as String?,
-      url: freezed == url
-          ? _value.url
-          : url // ignore: cast_nullable_to_non_nullable
-              as String?,
-    ));
+  $Res call({Object? md5 = freezed, Object? url = freezed}) {
+    return _then(
+      _$SkipIpsImpl(
+        md5: freezed == md5
+            ? _value.md5
+            : md5 // ignore: cast_nullable_to_non_nullable
+                  as String?,
+        url: freezed == url
+            ? _value.url
+            : url // ignore: cast_nullable_to_non_nullable
+                  as String?,
+      ),
+    );
   }
 }
 
@@ -688,9 +701,7 @@ class _$SkipIpsImpl with DiagnosticableTreeMixin implements _SkipIps {
 
   @override
   Map<String, dynamic> toJson() {
-    return _$$SkipIpsImplToJson(
-      this,
-    );
+    return _$$SkipIpsImplToJson(this);
   }
 }
 

+ 7 - 18
lib/app/data/models/launch/vpn_config.g.dart

@@ -17,8 +17,9 @@ _$VpnConfigImpl _$$VpnConfigImplFromJson(Map<String, dynamic> json) =>
           : ConnectArgs.fromJson(json['connectArgs'] as Map<String, dynamic>),
       defaultDnsServers: json['defaultDnsServers'] as String?,
       forceSkipDomain: json['forceSkipDomain'] as String?,
-      routers:
-          (json['routers'] as List<dynamic>?)?.map((e) => e as String).toList(),
+      routers: (json['routers'] as List<dynamic>?)
+          ?.map((e) => e as String)
+          .toList(),
       skipIps: json['skipIps'] == null
           ? null
           : SkipIps.fromJson(json['skipIps'] as Map<String, dynamic>),
@@ -36,25 +37,13 @@ Map<String, dynamic> _$$VpnConfigImplToJson(_$VpnConfigImpl instance) =>
     };
 
 _$ConnectArgsImpl _$$ConnectArgsImplFromJson(Map<String, dynamic> json) =>
-    _$ConnectArgsImpl(
-      p: json['p'] as String?,
-      v: json['v'] as String?,
-    );
+    _$ConnectArgsImpl(p: json['p'] as String?, v: json['v'] as String?);
 
 Map<String, dynamic> _$$ConnectArgsImplToJson(_$ConnectArgsImpl instance) =>
-    <String, dynamic>{
-      'p': instance.p,
-      'v': instance.v,
-    };
+    <String, dynamic>{'p': instance.p, 'v': instance.v};
 
 _$SkipIpsImpl _$$SkipIpsImplFromJson(Map<String, dynamic> json) =>
-    _$SkipIpsImpl(
-      md5: json['md5'] as String?,
-      url: json['url'] as String?,
-    );
+    _$SkipIpsImpl(md5: json['md5'] as String?, url: json['url'] as String?);
 
 Map<String, dynamic> _$$SkipIpsImplToJson(_$SkipIpsImpl instance) =>
-    <String, dynamic>{
-      'md5': instance.md5,
-      'url': instance.url,
-    };
+    <String, dynamic>{'md5': instance.md5, 'url': instance.url};

+ 8 - 0
lib/app/data/sp/ix_sp.dart

@@ -7,6 +7,7 @@ import '../../../utils/crypto.dart';
 import '../../../utils/log/logger.dart';
 import '../../constants/keys.dart';
 import '../models/disconnect_domain.dart';
+import '../models/launch/app_config.dart';
 import '../models/launch/launch.dart';
 import '../models/launch/upgrade.dart';
 import '../models/launch/user.dart';
@@ -306,6 +307,13 @@ class IXSP {
     await saveLaunch(newLaunch);
   }
 
+  /// 保存app配置
+  static Future<void> saveAppConfig(AppConfig appConfig) async {
+    final launch = getLaunch();
+    final newLaunch = launch!.copyWith(appConfig: appConfig);
+    await saveLaunch(newLaunch);
+  }
+
   /// 清除 Launch 数据
   static Future<bool> clearLaunchData() async {
     try {

+ 0 - 5
lib/app/dialog/custom_dialog.dart

@@ -316,7 +316,6 @@ class _CustomDialogWidget extends StatelessWidget {
       height: 42.w,
       child: ClickOpacity(
         onTap: () {
-          Get.back();
           onPressed?.call();
         },
         child: Container(
@@ -346,7 +345,6 @@ class _CustomDialogWidget extends StatelessWidget {
           Expanded(
             child: ClickOpacity(
               onTap: () {
-                Get.back();
                 onCancel?.call();
               },
               child: Container(
@@ -375,7 +373,6 @@ class _CustomDialogWidget extends StatelessWidget {
         Expanded(
           child: ClickOpacity(
             onTap: () {
-              Get.back();
               onPressed?.call();
             },
             child: Container(
@@ -410,7 +407,6 @@ class _CustomDialogWidget extends StatelessWidget {
         Expanded(
           child: ClickOpacity(
             onTap: () {
-              Get.back();
               onCancel?.call();
             },
             child: Container(
@@ -438,7 +434,6 @@ class _CustomDialogWidget extends StatelessWidget {
         Expanded(
           child: ClickOpacity(
             onTap: () {
-              Get.back();
               onConfirm?.call();
             },
             child: Container(

+ 10 - 0
lib/app/modules/login/bindings/login_binding.dart

@@ -0,0 +1,10 @@
+import 'package:get/get.dart';
+
+import '../controllers/login_controller.dart';
+
+class LoginBinding extends Binding {
+  @override
+  List<Bind> dependencies() {
+    return [Bind.lazyPut(() => LoginController())];
+  }
+}

+ 46 - 0
lib/app/modules/login/controllers/login_controller.dart

@@ -0,0 +1,46 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+
+class LoginController extends GetxController {
+  final usernameController = TextEditingController();
+  final passwordController = TextEditingController();
+  final usernameFocusNode = FocusNode();
+  final passwordFocusNode = FocusNode();
+  final _isLogin = false.obs;
+  bool get isLogin => _isLogin.value;
+  set isLogin(bool value) {
+    _isLogin.value = value;
+  }
+
+  @override
+  void onInit() {
+    super.onInit();
+  }
+
+  @override
+  void onClose() {
+    usernameController.dispose();
+    passwordController.dispose();
+    usernameFocusNode.dispose();
+    passwordFocusNode.dispose();
+    super.onClose();
+  }
+
+  void checkLogin() {
+    final username = usernameController.text;
+    final password = passwordController.text;
+    if (validatorInputValue(username) && validatorInputValue(password)) {
+      isLogin = true;
+    } else {
+      isLogin = false;
+    }
+  }
+
+  //6-20 characters (letters or numbers)
+  bool validatorInputValue(String value) {
+    // 只允许字母和数字并且是6-20位
+    return value.length >= 6 &&
+        value.length <= 20 &&
+        RegExp(r'^[a-zA-Z0-9]+$').hasMatch(value);
+  }
+}

+ 123 - 0
lib/app/modules/login/views/login_view.dart

@@ -0,0 +1,123 @@
+import 'package:flutter/gestures.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:get/get.dart';
+
+import 'package:nomo/app/base/base_view.dart';
+import 'package:nomo/config/theme/theme_extensions/theme_extension.dart';
+
+import '../../../routes/app_pages.dart';
+import '../../../widgets/ix_app_bar.dart';
+import '../../../widgets/ix_text_field.dart';
+import '../../../widgets/submit_btn.dart';
+import '../controllers/login_controller.dart';
+
+class LoginView extends BaseView<LoginController> {
+  const LoginView({super.key});
+
+  @override
+  PreferredSizeWidget? get appBar => IXAppBar(title: '');
+
+  @override
+  Widget buildContent(BuildContext context) {
+    return SafeArea(
+      child: Padding(
+        padding: EdgeInsets.symmetric(horizontal: 14.w),
+        child: SingleChildScrollView(
+          child: Column(
+            crossAxisAlignment: CrossAxisAlignment.start,
+            children: [
+              10.verticalSpaceFromWidth,
+
+              // 标题
+              Text(
+                'Log in',
+                style: TextStyle(
+                  fontSize: 28.sp,
+                  height: 1.2,
+                  color: Get.reactiveTheme.textTheme.bodyLarge!.color,
+                ),
+              ),
+
+              24.verticalSpaceFromWidth,
+
+              // 描述文字
+              Text(
+                'After a successful login, your free trial will be applied, and the remaining membership will be synced to your account for use across all linked devices.',
+                style: TextStyle(
+                  fontSize: 16.sp,
+                  color: Get.reactiveTheme.hintColor,
+                  height: 1.4,
+                ),
+              ),
+
+              20.verticalSpaceFromWidth,
+              IXTextField(
+                hintText: 'Username',
+                prefixIcon: Icons.person_outline,
+                controller: controller.usernameController,
+                focusNode: controller.usernameFocusNode,
+                validator: controller.validatorInputValue,
+                tipText: '6-20 characters (letters or numbers)',
+                errorText: '6-20 characters (letters or numbers)',
+                onChanged: (value) {
+                  controller.checkLogin();
+                },
+              ),
+
+              16.verticalSpaceFromWidth,
+              IXTextField(
+                hintText: 'Password',
+                prefixIcon: Icons.lock_outline,
+                controller: controller.passwordController,
+                focusNode: controller.passwordFocusNode,
+                validator: controller.validatorInputValue,
+                isPassword: true,
+                tipText: '6-20 characters (letters or numbers)',
+                errorText: '6-20 characters (letters or numbers)',
+                onChanged: (value) {
+                  controller.checkLogin();
+                },
+              ),
+              156.verticalSpaceFromWidth,
+              SubmitButton(
+                text: 'Log In',
+                enabled: controller.isLogin,
+                onPressed: () {},
+              ),
+              10.verticalSpaceFromWidth,
+              // 底部注册链接
+              Center(
+                child: RichText(
+                  text: TextSpan(
+                    children: [
+                      TextSpan(
+                        text: 'No account? ',
+                        style: TextStyle(
+                          color: Colors.grey[500],
+                          fontSize: 14.sp,
+                        ),
+                      ),
+                      TextSpan(
+                        text: ' Register now',
+                        recognizer: TapGestureRecognizer()
+                          ..onTap = () {
+                            Get.toNamed(Routes.SIGNUP);
+                          },
+                        style: TextStyle(
+                          color: Get.reactiveTheme.primaryColor,
+                          fontSize: 14.sp,
+                          fontWeight: FontWeight.w400,
+                        ),
+                      ),
+                    ],
+                  ),
+                ),
+              ),
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+}

+ 44 - 13
lib/app/modules/node/widgets/node_list.dart

@@ -193,9 +193,8 @@ class _CountrySection extends StatelessWidget {
         children: [
           ClickOpacity(
             onTap: () => onExpandedChanged(!expanded),
-            child: Container(
-              height: 52.w,
-              padding: EdgeInsets.symmetric(horizontal: 14.w),
+            child: Padding(
+              padding: EdgeInsets.all(14.w),
               child: Row(
                 children: [
                   // 国旗图标
@@ -270,17 +269,49 @@ class _CountrySection extends StatelessWidget {
                           ),
                           Container(
                             alignment: Alignment.centerLeft,
-                            margin: EdgeInsets.symmetric(
-                              vertical: 12.h,
-                              horizontal: 14.w,
+                            margin: EdgeInsets.only(
+                              top: 14.w,
+                              bottom: 14.w,
+                              left: 24.w,
+                              right: 14.w,
                             ),
-                            child: Text(
-                              cityName,
-                              style: TextStyle(
-                                fontSize: 14.sp,
-                                fontWeight: FontWeight.w500,
-                                color: Get.reactiveTheme.hintColor,
-                              ),
+                            child: Row(
+                              children: [
+                                ClipRRect(
+                                  borderRadius: BorderRadius.circular(
+                                    4.r,
+                                  ), // 设置圆角
+                                  child: SvgPicture.asset(
+                                    Assets.getCountryFlagImage(code),
+                                    width: 32.w,
+                                    height: 24.w,
+                                    fit: BoxFit.cover,
+                                    // placeholderBuilder: (context) => Container(
+                                    //   width: 32.w,
+                                    //   height: 24.w,
+                                    //   decoration: BoxDecoration(
+                                    //     borderRadius: BorderRadius.circular(4.r),
+                                    //     color: Colors.grey[200],
+                                    //   ),
+                                    //   alignment: Alignment.center,
+                                    //   child: Icon(
+                                    //     Icons.flag,
+                                    //     size: 16.w,
+                                    //     color: Colors.grey[400],
+                                    //   ),
+                                    // ),
+                                  ),
+                                ),
+                                10.horizontalSpace,
+                                Text(
+                                  cityName,
+                                  style: TextStyle(
+                                    fontSize: 14.sp,
+                                    fontWeight: FontWeight.w500,
+                                    color: Get.reactiveTheme.hintColor,
+                                  ),
+                                ),
+                              ],
                             ),
                           ),
                         ],

+ 0 - 10
lib/app/modules/signin/bindings/signin_binding.dart

@@ -1,10 +0,0 @@
-import 'package:get/get.dart';
-
-import '../controllers/signin_controller.dart';
-
-class SigninBinding extends Binding {
-  @override
-  List<Bind> dependencies() {
-    return [Bind.lazyPut(() => SigninController())];
-  }
-}

+ 0 - 23
lib/app/modules/signin/controllers/signin_controller.dart

@@ -1,23 +0,0 @@
-import 'package:get/get.dart';
-
-class SigninController extends GetxController {
-  //TODO: Implement SigninController
-
-  final count = 0.obs;
-  @override
-  void onInit() {
-    super.onInit();
-  }
-
-  @override
-  void onReady() {
-    super.onReady();
-  }
-
-  @override
-  void onClose() {
-    super.onClose();
-  }
-
-  void increment() => count.value++;
-}

+ 0 - 24
lib/app/modules/signin/views/signin_view.dart

@@ -1,24 +0,0 @@
-import 'package:flutter/material.dart';
-
-import 'package:get/get.dart';
-
-import '../controllers/signin_controller.dart';
-
-class SigninView extends GetView<SigninController> {
-  const SigninView({super.key});
-  @override
-  Widget build(BuildContext context) {
-    return Scaffold(
-      appBar: AppBar(
-        title: const Text('SigninView'),
-        centerTitle: true,
-      ),
-      body: const Center(
-        child: Text(
-          'SigninView is working',
-          style: TextStyle(fontSize: 20),
-        ),
-      ),
-    );
-  }
-}

+ 31 - 8
lib/app/modules/signup/controllers/signup_controller.dart

@@ -1,23 +1,46 @@
+import 'package:flutter/material.dart';
 import 'package:get/get.dart';
 
 class SignupController extends GetxController {
-  //TODO: Implement SignupController
+  final usernameController = TextEditingController();
+  final passwordController = TextEditingController();
+  final usernameFocusNode = FocusNode();
+  final passwordFocusNode = FocusNode();
+  final _isSignup = false.obs;
+  bool get isSignup => _isSignup.value;
+  set isSignup(bool value) {
+    _isSignup.value = value;
+  }
 
-  final count = 0.obs;
   @override
   void onInit() {
     super.onInit();
   }
 
-  @override
-  void onReady() {
-    super.onReady();
-  }
-
   @override
   void onClose() {
+    usernameController.dispose();
+    passwordController.dispose();
+    usernameFocusNode.dispose();
+    passwordFocusNode.dispose();
     super.onClose();
   }
 
-  void increment() => count.value++;
+  void checkSignup() {
+    final username = usernameController.text;
+    final password = passwordController.text;
+    if (validatorInputValue(username) && validatorInputValue(password)) {
+      isSignup = true;
+    } else {
+      isSignup = false;
+    }
+  }
+
+  //6-20 characters (letters or numbers)
+  bool validatorInputValue(String value) {
+    // 只允许字母和数字并且是6-20位
+    return value.length >= 6 &&
+        value.length <= 20 &&
+        RegExp(r'^[a-zA-Z0-9]+$').hasMatch(value);
+  }
 }

+ 110 - 11
lib/app/modules/signup/views/signup_view.dart

@@ -1,22 +1,121 @@
+import 'package:flutter/gestures.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
 
 import 'package:get/get.dart';
+import 'package:nomo/config/theme/theme_extensions/theme_extension.dart';
 
+import '../../../base/base_view.dart';
+import '../../../routes/app_pages.dart';
+import '../../../widgets/ix_app_bar.dart';
+import '../../../widgets/ix_text_field.dart';
+import '../../../widgets/submit_btn.dart';
 import '../controllers/signup_controller.dart';
 
-class SignupView extends GetView<SignupController> {
+class SignupView extends BaseView<SignupController> {
   const SignupView({super.key});
+
   @override
-  Widget build(BuildContext context) {
-    return Scaffold(
-      appBar: AppBar(
-        title: const Text('SignupView'),
-        centerTitle: true,
-      ),
-      body: const Center(
-        child: Text(
-          'SignupView is working',
-          style: TextStyle(fontSize: 20),
+  PreferredSizeWidget? get appBar => IXAppBar(title: '');
+
+  @override
+  Widget buildContent(BuildContext context) {
+    return SafeArea(
+      child: Padding(
+        padding: EdgeInsets.symmetric(horizontal: 14.w),
+        child: SingleChildScrollView(
+          child: Column(
+            crossAxisAlignment: CrossAxisAlignment.start,
+            children: [
+              10.verticalSpaceFromWidth,
+
+              // 标题
+              Text(
+                'Sign up to NOMO',
+                style: TextStyle(
+                  fontSize: 28.sp,
+                  height: 1.2,
+                  color: Get.reactiveTheme.textTheme.bodyLarge!.color,
+                ),
+              ),
+
+              24.verticalSpaceFromWidth,
+
+              // 描述文字
+              Text(
+                'After registration, your free trial will be deducted, and other membership time shifts to your account for multi-device use.',
+                style: TextStyle(
+                  fontSize: 16.sp,
+                  color: Get.reactiveTheme.hintColor,
+                  height: 1.4,
+                ),
+              ),
+
+              20.verticalSpaceFromWidth,
+              IXTextField(
+                hintText: 'Username',
+                prefixIcon: Icons.person_outline,
+                controller: controller.usernameController,
+                focusNode: controller.usernameFocusNode,
+                validator: controller.validatorInputValue,
+                tipText: '6-20 characters (letters or numbers)',
+                errorText: '6-20 characters (letters or numbers)',
+                onChanged: (value) {
+                  controller.checkSignup();
+                },
+              ),
+
+              16.verticalSpaceFromWidth,
+              IXTextField(
+                hintText: 'Password',
+                prefixIcon: Icons.lock_outline,
+                controller: controller.passwordController,
+                focusNode: controller.passwordFocusNode,
+                validator: controller.validatorInputValue,
+                isPassword: true,
+                tipText: '6-20 characters (letters or numbers)',
+                errorText: '6-20 characters (letters or numbers)',
+                onChanged: (value) {
+                  controller.checkSignup();
+                },
+              ),
+              156.verticalSpaceFromWidth,
+              SubmitButton(
+                text: 'Sign Up',
+                enabled: controller.isSignup,
+                onPressed: () {},
+              ),
+              10.verticalSpaceFromWidth,
+              // 底部注册链接
+              Center(
+                child: RichText(
+                  text: TextSpan(
+                    children: [
+                      TextSpan(
+                        text: 'Already have an account? ',
+                        style: TextStyle(
+                          color: Colors.grey[500],
+                          fontSize: 14.sp,
+                        ),
+                      ),
+                      TextSpan(
+                        text: ' Login now',
+                        recognizer: TapGestureRecognizer()
+                          ..onTap = () {
+                            Get.toNamed(Routes.LOGIN);
+                          },
+                        style: TextStyle(
+                          color: Get.reactiveTheme.primaryColor,
+                          fontSize: 14.sp,
+                          fontWeight: FontWeight.w400,
+                        ),
+                      ),
+                    ],
+                  ),
+                ),
+              ),
+            ],
+          ),
         ),
       ),
     );

+ 9 - 4
lib/app/modules/splash/controllers/splash_controller.dart

@@ -20,6 +20,7 @@ class SplashController extends BaseController {
   bool get showLoading => _showLoading.value;
   set showLoading(bool value) => _showLoading.value = value;
 
+  // 是否已经登录
   final _hasLogin = true.obs;
 
   bool get hasLogin => _hasLogin.value;
@@ -89,7 +90,11 @@ class SplashController extends BaseController {
         case MemberLevel.guest:
           //游客禁用,必须登录
           if (appConfig.visitorDisabled ?? true) {
-            // Get.offAllNamed(Routes.HOME);
+            // 延迟1秒后设置为未登录
+            Future.delayed(const Duration(seconds: 1), () {
+              // hasLogin = false;
+              Get.offAllNamed(Routes.HOME);
+            });
           } else {
             //允许游客访问,直接进入主页
             Get.offAllNamed(Routes.HOME);
@@ -145,15 +150,15 @@ class SplashController extends BaseController {
       _apiController.initLaunch(launch);
       handleLaunch(launch);
     } else {
-      Get.dialog(
-        CountryRestrictedOverlay(
+      Get.to(
+        () => CountryRestrictedOverlay(
           type: RestrictedType.network,
           onPressed: () async {
             Navigator.pop(Get.context!);
             initApiInfo();
           },
         ),
-        barrierDismissible: false,
+        transition: Transition.fadeIn,
       );
     }
   }

+ 156 - 48
lib/app/modules/splash/views/splash_view.dart

@@ -6,8 +6,10 @@ import 'package:get/get.dart';
 
 import 'package:nomo/app/base/base_view.dart';
 import 'package:nomo/app/widgets/ix_image.dart';
+import 'package:nomo/app/widgets/submit_btn.dart';
 import 'package:nomo/config/theme/theme_extensions/theme_extension.dart';
 import '../../../constants/assets.dart';
+import '../../../routes/app_pages.dart';
 import '../../../widgets/privacy_agreement.dart';
 import '../controllers/splash_controller.dart';
 
@@ -37,22 +39,123 @@ class SplashView extends BaseView<SplashController> {
       child: Stack(
         alignment: Alignment.topCenter,
         children: [
-          Container(
-            margin: EdgeInsets.only(top: 120.w),
-            child: IXImage(
-              source: Assets.splashCenterBg,
-              width: 356.w,
-              height: 356.w,
-              sourceType: ImageSourceType.asset,
+          // 背景世界地图
+          Obx(
+            () => AnimatedContainer(
+              duration: const Duration(milliseconds: 500),
+              curve: Curves.easeInOut,
+              transform: Matrix4.translationValues(
+                0,
+                !controller.hasLogin ? -120.w : 0,
+                0,
+              ),
+              child: Container(
+                margin: EdgeInsets.only(top: 250.w),
+                child: IXImage(
+                  source: Assets.splashCenterBg,
+                  width: 356.w,
+                  height: 356.w,
+                  sourceType: ImageSourceType.asset,
+                ),
+              ),
             ),
           ),
-          Container(
-            margin: EdgeInsets.only(top: 52.w),
-            child: IXImage(
-              source: Assets.splashLogo,
-              width: 104.w,
-              height: 148.w,
-              sourceType: ImageSourceType.asset,
+          // Logo
+          Obx(
+            () => AnimatedContainer(
+              duration: const Duration(milliseconds: 500),
+              curve: Curves.easeInOut,
+              transform: Matrix4.translationValues(
+                0,
+                !controller.hasLogin ? -120.w : 0,
+                0,
+              ),
+              child: Container(
+                margin: EdgeInsets.only(top: 172.w),
+                child: IXImage(
+                  source: Assets.splashLogo,
+                  width: 104.w,
+                  height: 148.w,
+                  sourceType: ImageSourceType.asset,
+                ),
+              ),
+            ),
+          ),
+          // 文字内容 - 根据 hasLogin 状态渐变显示
+          Obx(
+            () => AnimatedOpacity(
+              opacity: !controller.hasLogin ? 1.0 : 0.0,
+              duration: const Duration(milliseconds: 1000),
+              child: FadeIn(
+                duration: const Duration(milliseconds: 1000),
+                delay: const Duration(milliseconds: 1500),
+                child: Container(
+                  margin: EdgeInsets.only(top: 240.w),
+                  child: Text(
+                    'Secure Your Connection',
+                    style: TextStyle(
+                      fontSize: 28.sp,
+                      color: Get.reactiveTheme.textTheme.bodyLarge!.color,
+                    ),
+                  ),
+                ),
+              ),
+            ),
+          ),
+
+          Obx(
+            () => AnimatedOpacity(
+              opacity: !controller.hasLogin ? 1.0 : 0.0,
+              duration: const Duration(milliseconds: 1000),
+              child: FadeIn(
+                duration: const Duration(milliseconds: 1000),
+                delay: const Duration(milliseconds: 1500),
+                child: Container(
+                  width: 327.w,
+                  margin: EdgeInsets.only(top: 298.w),
+                  padding: EdgeInsets.symmetric(horizontal: 14.w),
+                  child: Text(
+                    'You can log in or register an account to share membership across different devices.',
+                    textAlign: TextAlign.center,
+                    style: TextStyle(
+                      fontSize: 16.sp,
+                      height: 1.4,
+                      color: Get.reactiveTheme.hintColor,
+                    ),
+                  ),
+                ),
+              ),
+            ),
+          ),
+
+          // 按钮区域
+          Obx(
+            () => AnimatedOpacity(
+              opacity: !controller.hasLogin ? 1.0 : 0.0,
+              duration: const Duration(milliseconds: 1000),
+              child: FadeInUp(
+                duration: const Duration(milliseconds: 1000),
+                delay: const Duration(milliseconds: 1500),
+                child: Container(
+                  margin: EdgeInsets.only(top: 420.w),
+                  padding: EdgeInsets.symmetric(horizontal: 14.w),
+                  child: Column(
+                    children: [
+                      // 登录按钮
+                      SubmitButton(
+                        text: 'Log In',
+                        bgColor: Get.reactiveTheme.highlightColor,
+                        onPressed: () => Get.toNamed(Routes.LOGIN),
+                      ),
+                      20.verticalSpaceFromWidth,
+                      SubmitButton(
+                        text: 'Sign Up',
+                        onPressed: () => Get.toNamed(Routes.SIGNUP),
+                      ),
+                    ],
+                  ),
+                ),
+              ),
             ),
           ),
         ],
@@ -62,42 +165,47 @@ class SplashView extends BaseView<SplashController> {
 
   // 构建底部内容
   Widget _buildBottomContent() {
-    return Obx(
-      () => !controller.hasLogin
-          ? FadeInUp(
-              duration: const Duration(milliseconds: 700),
-              delay: const Duration(milliseconds: 800),
-              child: PrivacyAgreement(
-                textColor: Get.reactiveTheme.textTheme.bodyLarge!.color,
-                linkColor: Get.reactiveTheme.primaryColor,
-                height: 1.8,
-              ),
-            )
-          : FadeInUp(
-              duration: const Duration(milliseconds: 600),
-              child: Column(
-                children: [
-                  Obx(
-                    () => controller.showLoading
-                        ? SpinKitRing(
-                            size: 24.w,
-                            lineWidth: 2.w,
-                            color: Get.reactiveTheme.primaryColor,
-                          )
-                        : const SizedBox.shrink(),
-                  ),
-                  16.verticalSpaceFromWidth,
-                  Text(
-                    'V${controller.versionName}',
-                    textAlign: TextAlign.center,
-                    style: TextStyle(
-                      color: Get.reactiveTheme.textTheme.bodyLarge!.color,
-                      fontSize: 14.sp,
+    return Container(
+      height: 100.w,
+      alignment: Alignment.bottomCenter,
+      child: Obx(
+        () => !controller.hasLogin
+            ? FadeInUp(
+                duration: const Duration(milliseconds: 700),
+                delay: const Duration(milliseconds: 800),
+                child: PrivacyAgreement(
+                  textColor: Get.reactiveTheme.textTheme.bodyLarge!.color,
+                  linkColor: Get.reactiveTheme.primaryColor,
+                  height: 1.8,
+                ),
+              )
+            : FadeInUp(
+                duration: const Duration(milliseconds: 600),
+                child: Column(
+                  mainAxisAlignment: MainAxisAlignment.end,
+                  children: [
+                    Obx(
+                      () => controller.showLoading
+                          ? SpinKitRing(
+                              size: 24.w,
+                              lineWidth: 2.w,
+                              color: Get.reactiveTheme.primaryColor,
+                            )
+                          : const SizedBox.shrink(),
                     ),
-                  ),
-                ],
+                    16.verticalSpaceFromWidth,
+                    Text(
+                      'V${controller.versionName}',
+                      textAlign: TextAlign.center,
+                      style: TextStyle(
+                        color: Get.reactiveTheme.textTheme.bodyLarge!.color,
+                        fontSize: 14.sp,
+                      ),
+                    ),
+                  ],
+                ),
               ),
-            ),
+      ),
     );
   }
 }

+ 13 - 7
lib/app/routes/app_pages.dart

@@ -16,6 +16,8 @@ import '../modules/home/bindings/home_binding.dart';
 import '../modules/home/views/home_view.dart';
 import '../modules/language/bindings/language_binding.dart';
 import '../modules/language/views/language_view.dart';
+import '../modules/login/bindings/login_binding.dart';
+import '../modules/login/views/login_view.dart';
 import '../modules/markdown/bindings/markdown_binding.dart';
 import '../modules/markdown/views/markdown_view.dart';
 import '../modules/node/bindings/node_binding.dart';
@@ -28,8 +30,6 @@ import '../modules/routingmode/bindings/routingmode_binding.dart';
 import '../modules/routingmode/views/routingmode_view.dart';
 import '../modules/setting/bindings/setting_binding.dart';
 import '../modules/setting/views/setting_view.dart';
-import '../modules/signin/bindings/signin_binding.dart';
-import '../modules/signin/views/signin_view.dart';
 import '../modules/signup/bindings/signup_binding.dart';
 import '../modules/signup/views/signup_view.dart';
 import '../modules/splash/bindings/splash_binding.dart';
@@ -167,20 +167,26 @@ class AppPages {
         ),
       ],
     ),
-    GetPage(
-      name: _Paths.SIGNIN,
-      page: () => const SigninView(),
-      binding: SigninBinding(),
-    ),
     GetPage(
       name: _Paths.SIGNUP,
       page: () => const SignupView(),
       binding: SignupBinding(),
+      transition: Transition.native,
+      curve: Curves.easeInOut,
     ),
     GetPage(
       name: _Paths.FORGOTPWD,
       page: () => const ForgotpwdView(),
       binding: ForgotpwdBinding(),
+      transition: Transition.native,
+      curve: Curves.easeInOut,
+    ),
+    GetPage(
+      name: _Paths.LOGIN,
+      page: () => const LoginView(),
+      binding: LoginBinding(),
+      transition: Transition.native,
+      curve: Curves.easeInOut,
     ),
   ];
 

+ 2 - 2
lib/app/routes/app_routes.dart

@@ -20,9 +20,9 @@ abstract class Routes {
   static const SPLITTUNNELING = _Paths.SPLITTUNNELING;
   static const SPLITTUNNELING_SELECTAPP =
       _Paths.SPLITTUNNELING + _Paths.SPLITTUNNELING_SELECTAPP;
-  static const SIGNIN = _Paths.SIGNIN;
   static const SIGNUP = _Paths.SIGNUP;
   static const FORGOTPWD = _Paths.FORGOTPWD;
+  static const LOGIN = _Paths.LOGIN;
 }
 
 abstract class _Paths {
@@ -43,7 +43,7 @@ abstract class _Paths {
   static const FEEDBACK = '/feedback';
   static const SPLITTUNNELING = '/splittunneling';
   static const SPLITTUNNELING_SELECTAPP = '/selectapp';
-  static const SIGNIN = '/signin';
   static const SIGNUP = '/signup';
   static const FORGOTPWD = '/forgotpwd';
+  static const LOGIN = '/login';
 }

+ 322 - 0
lib/app/widgets/feedback_bottom_sheet.dart

@@ -0,0 +1,322 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:get/get.dart';
+import 'package:nomo/app/widgets/click_opacity.dart';
+
+import 'submit_btn.dart';
+
+/// 反馈弹窗底部弹出框
+class FeedbackBottomSheet extends StatefulWidget {
+  const FeedbackBottomSheet({super.key});
+
+  /// 显示反馈弹窗
+  static Future<void> show() {
+    return Get.bottomSheet(
+      const FeedbackBottomSheet(),
+      backgroundColor: Colors.transparent,
+      isDismissible: true,
+      enableDrag: true,
+      isScrollControlled: true,
+    );
+  }
+
+  @override
+  State<FeedbackBottomSheet> createState() => _FeedbackBottomSheetState();
+}
+
+class _FeedbackBottomSheetState extends State<FeedbackBottomSheet>
+    with SingleTickerProviderStateMixin {
+  // 选中的表情索引(0-4)
+  int? selectedEmojiIndex;
+
+  // 选中的问题标签
+  final Set<String> selectedIssues = {};
+
+  // 表情列表
+  final List<String> emojis = ['😡', '😥', '🤭', '😏', '🥰'];
+
+  // 每个表情对应的问题标签
+  final Map<int, List<String>> emojiIssues = {
+    0: [
+      // 差评 😡
+      'VPN connection failed',
+      'Internet too slow',
+      'Keeps disconnecting',
+      'App crashes or freezes',
+      'Other issues',
+    ],
+    1: [
+      // 一般偏差 😥
+      'Connection unstable',
+      'Speed not as expected',
+      'Hard to use / confusing UI',
+      'Other issues',
+    ],
+    2: [
+      // 中等 🤭
+      'Works fine but not fast enough',
+      'Limited free servers',
+      'App could be simpler',
+      'Sometimes disconnects',
+      'Nothing special',
+    ],
+    3: [
+      // 好 😏
+      'Easy to use',
+      'Fast connection',
+      'Stable performance',
+      'Useful free version',
+      'Satisfied overall',
+    ],
+    4: [
+      // 很好 🥰
+      'Fast and stable connection',
+      'Great user experience',
+      'Excellent premium features',
+      'Worth recommending',
+      'I love the design',
+    ],
+  };
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      decoration: BoxDecoration(
+        color: Get.theme.highlightColor,
+        borderRadius: BorderRadius.only(
+          topLeft: Radius.circular(16.r),
+          topRight: Radius.circular(16.r),
+        ),
+      ),
+      child: SafeArea(
+        child: Column(
+          mainAxisSize: MainAxisSize.min,
+          children: [
+            // 关闭按钮
+            _buildCloseButton(),
+            // 标题
+            _buildTitle(),
+
+            10.verticalSpaceFromWidth,
+
+            // 副标题
+            _buildSubtitle(),
+
+            10.verticalSpaceFromWidth,
+
+            // 表情选择器
+            _buildEmojiSelector(),
+
+            // 问题标签区域 - 使用 AnimatedSize 实现平滑高度过渡
+            AnimatedSize(
+              duration: const Duration(milliseconds: 300),
+              curve: Curves.easeInOut,
+              child: selectedEmojiIndex != null
+                  ? Column(
+                      mainAxisSize: MainAxisSize.min,
+                      children: [24.verticalSpaceFromWidth, _buildIssueTags()],
+                    )
+                  : const SizedBox.shrink(),
+            ),
+
+            // 发送按钮 - 使用 AnimatedSize 实现平滑显示/隐藏
+            AnimatedSize(
+              duration: const Duration(milliseconds: 300),
+              curve: Curves.easeInOut,
+              child: selectedEmojiIndex != null
+                  ? _buildSendButton()
+                  : 24.verticalSpaceFromWidth,
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+
+  /// 构建关闭按钮
+  Widget _buildCloseButton() {
+    return Align(
+      alignment: Alignment.centerRight,
+      child: ClickOpacity(
+        onTap: () => Navigator.of(Get.context!).pop(),
+        child: Container(
+          width: 24.w,
+          height: 24.w,
+          margin: EdgeInsets.only(right: 8.w, top: 8.w),
+          padding: EdgeInsets.all(4.w),
+          decoration: BoxDecoration(
+            color: Get.theme.cardColor,
+            shape: BoxShape.circle,
+          ),
+          child: Icon(
+            Icons.close_rounded,
+            size: 16.w,
+            color: Get.theme.textTheme.bodyLarge!.color,
+          ),
+        ),
+      ),
+    );
+  }
+
+  /// 构建标题
+  Widget _buildTitle() {
+    return Text(
+      "How's your\nexperience so far ?",
+      textAlign: TextAlign.center,
+      style: TextStyle(
+        fontSize: 22.sp,
+        fontWeight: FontWeight.w500,
+        color: Get.theme.textTheme.bodyLarge!.color,
+        height: 1.3,
+      ),
+    );
+  }
+
+  /// 构建副标题
+  Widget _buildSubtitle() {
+    return Text(
+      "we'd love to know !",
+      textAlign: TextAlign.center,
+      style: TextStyle(
+        fontSize: 16.sp,
+        height: 1.4,
+        color: Get.theme.hintColor,
+      ),
+    );
+  }
+
+  /// 构建表情选择器
+  Widget _buildEmojiSelector() {
+    return Row(
+      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+      children: List.generate(
+        emojis.length,
+        (index) => _buildEmojiButton(index),
+      ),
+    );
+  }
+
+  /// 构建单个表情按钮
+  Widget _buildEmojiButton(int index) {
+    final isSelected = selectedEmojiIndex == index;
+
+    return ClickOpacity(
+      onTap: () {
+        setState(() {
+          if (selectedEmojiIndex == index) {
+            // 如果点击已选中的表情,取消选择
+            selectedEmojiIndex = null;
+          } else {
+            // 选择新的表情
+            selectedEmojiIndex = index;
+          }
+          // 切换表情时清空已选的问题标签
+          selectedIssues.clear();
+        });
+      },
+      child: AnimatedContainer(
+        duration: const Duration(milliseconds: 200),
+        width: 56.w,
+        height: 56.w,
+        decoration: BoxDecoration(
+          shape: BoxShape.circle,
+          color: isSelected ? Get.theme.cardColor : Colors.transparent,
+          border: Border.all(
+            color: isSelected ? Get.theme.dividerColor : Colors.transparent,
+            width: 1.w,
+          ),
+        ),
+        alignment: Alignment.center,
+        child: Text(emojis[index], style: TextStyle(fontSize: 32.sp)),
+      ),
+    );
+  }
+
+  /// 构建问题标签
+  Widget _buildIssueTags() {
+    if (selectedEmojiIndex == null) return const SizedBox.shrink();
+
+    final issues = emojiIssues[selectedEmojiIndex!] ?? [];
+
+    return Padding(
+      padding: EdgeInsets.symmetric(horizontal: 24.w),
+      child: Wrap(
+        spacing: 10.w,
+        runSpacing: 10.h,
+        alignment: WrapAlignment.center,
+        children: issues.map((issue) => _buildIssueTag(issue)).toList(),
+      ),
+    );
+  }
+
+  /// 构建单个问题标签
+  Widget _buildIssueTag(String issue) {
+    final isSelected = selectedIssues.contains(issue);
+
+    return ClickOpacity(
+      onTap: () {
+        setState(() {
+          if (isSelected) {
+            selectedIssues.remove(issue);
+          } else {
+            selectedIssues.add(issue);
+          }
+        });
+      },
+      child: AnimatedContainer(
+        duration: const Duration(milliseconds: 200),
+        padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 8.h),
+        decoration: BoxDecoration(
+          color: isSelected ? Get.theme.primaryColor : Get.theme.cardColor,
+          borderRadius: BorderRadius.circular(24.r),
+          border: Border.all(
+            color: isSelected ? Get.theme.primaryColor : Get.theme.dividerColor,
+            width: 1.w,
+          ),
+        ),
+        child: Text(
+          issue,
+          style: TextStyle(
+            fontSize: 14.sp,
+            color: isSelected
+                ? Get.theme.textTheme.bodyLarge!.color
+                : Get.theme.hintColor,
+          ),
+        ),
+      ),
+    );
+  }
+
+  /// 构建发送按钮
+  Widget _buildSendButton() {
+    // 必须选中表情并且选中至少一个问题标签才能发送
+    final canSend = selectedEmojiIndex != null && selectedIssues.isNotEmpty;
+
+    return Container(
+      margin: EdgeInsets.only(left: 24.w, right: 24.w, top: 24.h, bottom: 10.w),
+      child: SubmitButton(
+        text: 'Send',
+        enabled: canSend,
+        onPressed: canSend ? _handleSend : null,
+      ),
+    );
+  }
+
+  /// 处理发送
+  void _handleSend() {
+    // TODO: 这里可以添加发送反馈的逻辑
+    Get.back();
+
+    // 显示成功提示
+    Get.snackbar(
+      'Thank you!',
+      'Your feedback has been submitted.',
+      snackPosition: SnackPosition.bottom,
+      backgroundColor: Get.theme.primaryColor,
+      colorText: Colors.white,
+      margin: EdgeInsets.all(16.w),
+      borderRadius: 12.r,
+      duration: const Duration(seconds: 2),
+    );
+  }
+}

+ 288 - 0
lib/app/widgets/ix_text_field.dart

@@ -0,0 +1,288 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:get/get.dart';
+import 'package:nomo/config/theme/theme_extensions/theme_extension.dart';
+
+import '../../config/theme/dark_theme_colors.dart';
+import 'click_opacity.dart';
+
+class IXTextField extends StatefulWidget {
+  final String hintText;
+  final IconData prefixIcon;
+  final bool isPassword;
+  final TextEditingController controller;
+  final FocusNode? focusNode;
+  final TextInputType keyboardType;
+  final TextInputAction textInputAction;
+  final Function(String)? onChanged;
+  final bool showClearButton;
+  final String? errorText;
+  final bool enabled;
+  final bool isEmail;
+  final String? tipText;
+  final bool Function(String)? validator;
+  const IXTextField({
+    super.key,
+    required this.hintText,
+    required this.prefixIcon,
+    this.isPassword = false,
+    required this.controller,
+    this.focusNode,
+    this.keyboardType = TextInputType.text,
+    this.textInputAction = TextInputAction.done,
+    this.onChanged,
+    this.showClearButton = true,
+    this.errorText,
+    this.enabled = true,
+    this.isEmail = false,
+    this.tipText,
+    this.validator,
+  });
+
+  @override
+  State<IXTextField> createState() => _IXTextFieldState();
+}
+
+class _IXTextFieldState extends State<IXTextField> {
+  bool _obscureText = true;
+  bool _hasFocus = false;
+  bool _hasError = false;
+
+  @override
+  void initState() {
+    super.initState();
+    widget.focusNode?.addListener(_onFocusChange);
+    // 初始验证
+    if (widget.controller.text.isNotEmpty) {
+      _validateInput(widget.controller.text);
+    }
+  }
+
+  // 添加 didUpdateWidget 监听属性变化
+  @override
+  void didUpdateWidget(IXTextField oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.controller.text.isNotEmpty) {
+      _validateInput(widget.controller.text);
+    }
+  }
+
+  @override
+  void dispose() {
+    widget.focusNode?.removeListener(_onFocusChange);
+    super.dispose();
+  }
+
+  void _onFocusChange() {
+    setState(() {
+      _hasFocus = widget.focusNode?.hasFocus ?? false;
+      // 当失去焦点时进行验证
+      if (!_hasFocus && widget.controller.text.isNotEmpty) {
+        _validateInput(widget.controller.text);
+      }
+    });
+  }
+
+  void _validateInput(String value) {
+    if (widget.validator != null) {
+      // 使用自定义验证器
+      setState(() {
+        _hasError = !widget.validator!(value);
+      });
+    } else if (widget.isEmail) {
+      // 默认电子邮件验证
+      final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
+      setState(() {
+        _hasError = !emailRegex.hasMatch(value);
+      });
+    } else {
+      setState(() {});
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    // 确定边框颜色
+    Color borderColor = Get.reactiveTheme.dividerColor;
+    Color bgColor = Get.reactiveTheme.highlightColor;
+
+    if (_hasFocus) {
+      borderColor = Get.reactiveTheme.shadowColor;
+      bgColor = Get.reactiveTheme.highlightColor;
+    }
+    return Column(
+      crossAxisAlignment: CrossAxisAlignment.start,
+      children: [
+        Container(
+          decoration: BoxDecoration(
+            color: bgColor,
+            borderRadius: BorderRadius.circular(12.r),
+            border: Border.all(color: borderColor, width: 1.0),
+          ),
+          alignment: Alignment.center,
+          height: 50.w,
+          child: TextField(
+            controller: widget.controller,
+            focusNode: widget.focusNode,
+            obscureText: widget.isPassword && _obscureText,
+            keyboardType: widget.keyboardType,
+            cursorHeight: 14,
+            enableSuggestions: false,
+            autocorrect: false,
+            textInputAction: widget.textInputAction,
+            inputFormatters: widget.isPassword
+                ? [LengthLimitingTextInputFormatter(20)]
+                : [],
+            onTap: () {
+              // 不要先unfocus
+              if (widget.focusNode != null) {
+                widget.focusNode!.requestFocus();
+                Future.delayed(const Duration(milliseconds: 150), () {
+                  // 每次需要点击两次才能显示键盘
+                  SystemChannels.textInput.invokeMethod('TextInput.show');
+                });
+              }
+            },
+            onChanged: (value) {
+              if (widget.onChanged != null) {
+                widget.onChanged!(value);
+              }
+
+              // 实时验证
+              if (value.isNotEmpty) {
+                _validateInput(value);
+              } else {
+                setState(() {
+                  _hasError = false;
+                });
+              }
+            },
+            enabled: widget.enabled,
+            style: TextStyle(
+              color: Get.reactiveTheme.textTheme.bodyLarge!.color,
+              fontSize: 14,
+              height: 1.6,
+              fontWeight: FontWeight.w400,
+            ),
+            decoration: InputDecoration(
+              hintText: widget.hintText,
+              hintStyle: TextStyle(
+                color: Get.reactiveTheme.hintColor,
+                fontSize: 14,
+                height: 1.6,
+                fontWeight: FontWeight.w400,
+              ),
+              prefixIcon: Icon(
+                widget.prefixIcon,
+                color: Get.reactiveTheme.hintColor,
+                size: 20,
+              ),
+              suffixIcon: _buildSuffixIcon(),
+              border: InputBorder.none,
+              contentPadding: const EdgeInsets.symmetric(
+                vertical: 12,
+                horizontal: 16,
+              ),
+              isDense: true,
+            ),
+            cursorColor: Get.reactiveTheme.shadowColor,
+          ),
+        ),
+        if (!_hasError && _hasFocus && widget.tipText != null)
+          Padding(
+            padding: EdgeInsets.only(top: 10.w),
+            child: Text(
+              widget.tipText!,
+              style: TextStyle(
+                color: Get.reactiveTheme.shadowColor,
+                fontSize: 13.sp,
+                height: 1.4,
+                fontWeight: FontWeight.w400,
+              ),
+            ),
+          ),
+        if (widget.errorText != null && _hasError)
+          Padding(
+            padding: EdgeInsets.only(top: 10.w),
+            child: Text(
+              widget.errorText!,
+              style: TextStyle(
+                color: DarkThemeColors.errorColor,
+                fontSize: 13.sp,
+                height: 1.4,
+                fontWeight: FontWeight.w400,
+              ),
+            ),
+          ),
+      ],
+    );
+  }
+
+  Widget? _buildSuffixIcon() {
+    if (widget.isPassword) {
+      return Row(
+        mainAxisSize: MainAxisSize.min,
+        mainAxisAlignment: MainAxisAlignment.end,
+        children: [
+          if (widget.showClearButton && widget.controller.text.isNotEmpty)
+            ClickOpacity(
+              child: Icon(
+                Icons.clear,
+                color: Get.reactiveTheme.hintColor,
+                size: 20,
+              ),
+              onTap: () {
+                widget.controller.clear();
+                if (widget.onChanged != null) {
+                  widget.onChanged!('');
+                }
+                setState(() {
+                  _hasError = false;
+                });
+              },
+            ),
+          8.horizontalSpace,
+          ClickOpacity(
+            child: Icon(
+              _obscureText ? Icons.visibility_off : Icons.visibility,
+              color: Get.reactiveTheme.hintColor,
+              size: 20,
+            ),
+            onTap: () {
+              setState(() {
+                _obscureText = !_obscureText;
+              });
+            },
+          ),
+          16.horizontalSpace,
+        ],
+      );
+    } else if (widget.showClearButton && widget.controller.text.isNotEmpty) {
+      return Row(
+        mainAxisSize: MainAxisSize.min,
+        mainAxisAlignment: MainAxisAlignment.end,
+        children: [
+          ClickOpacity(
+            child: Icon(
+              Icons.clear,
+              color: Get.reactiveTheme.hintColor,
+              size: 20,
+            ),
+            onTap: () {
+              widget.controller.clear();
+              if (widget.onChanged != null) {
+                widget.onChanged!('');
+              }
+              setState(() {
+                _hasError = false;
+              });
+            },
+          ),
+          16.horizontalSpace,
+        ],
+      );
+    }
+    return null;
+  }
+}

+ 2 - 0
lib/config/theme/dark_theme_colors.dart

@@ -82,4 +82,6 @@ class DarkThemeColors {
   static const Color textBrand = Color(0xFFFFFFFF);
   static const Color strokes1 = Color(0xFF32343C);
   static const Color strokes2 = Color(0xFF1C1E25);
+
+  static const Color errorColor = Color(0xFFEF0000);
 }

+ 25 - 2
lib/pigeons/core_api.g.dart

@@ -98,14 +98,14 @@ class CoreApi {
     }
   }
 
-  Future<bool?> connect() async {
+  Future<bool?> connect(String sessionId, int socksPort, String tunnelConfig, String configJson) async {
     final String pigeonVar_channelName = 'dev.flutter.pigeon.app.xixi.nomo.CoreApi.connect$pigeonVar_messageChannelSuffix';
     final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
       pigeonVar_channelName,
       pigeonChannelCodec,
       binaryMessenger: pigeonVar_binaryMessenger,
     );
-    final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
+    final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[sessionId, socksPort, tunnelConfig, configJson]);
     final List<Object?>? pigeonVar_replyList =
         await pigeonVar_sendFuture as List<Object?>?;
     if (pigeonVar_replyList == null) {
@@ -281,6 +281,29 @@ class CoreApi {
       return (pigeonVar_replyList[0] as bool?);
     }
   }
+
+  Future<String?> getChannel() async {
+    final String pigeonVar_channelName = 'dev.flutter.pigeon.app.xixi.nomo.CoreApi.getChannel$pigeonVar_messageChannelSuffix';
+    final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
+      pigeonVar_channelName,
+      pigeonChannelCodec,
+      binaryMessenger: pigeonVar_binaryMessenger,
+    );
+    final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
+    final List<Object?>? pigeonVar_replyList =
+        await pigeonVar_sendFuture as List<Object?>?;
+    if (pigeonVar_replyList == null) {
+      throw _createConnectionError(pigeonVar_channelName);
+    } else if (pigeonVar_replyList.length > 1) {
+      throw PlatformException(
+        code: pigeonVar_replyList[0]! as String,
+        message: pigeonVar_replyList[1] as String?,
+        details: pigeonVar_replyList[2],
+      );
+    } else {
+      return (pigeonVar_replyList[0] as String?);
+    }
+  }
 }
 
 Stream<String> onEventChange( {String instanceName = ''}) {

+ 7 - 4
lib/utils/developer/ix_developer_tools.dart

@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
 
 import 'package:get/get.dart';
 import 'package:dio/dio.dart' as dio;
+import 'package:nomo/config/theme/theme_extensions/theme_extension.dart';
 
 import '../../app/constants/keys.dart';
 import '../crypto.dart';
@@ -414,16 +415,18 @@ class _SimpleDeveloperToolsScreenState extends State<SimpleDeveloperToolsScreen>
   @override
   Widget build(BuildContext context) {
     return Scaffold(
-      backgroundColor: Colors.grey[50],
+      backgroundColor: Get.reactiveTheme.scaffoldBackgroundColor,
       appBar: AppBar(
         elevation: 0,
-        backgroundColor: Colors.black,
-        title: const Text(
+        iconTheme: IconThemeData(
+          color: Get.reactiveTheme.textTheme.bodyLarge!.color,
+        ),
+        title: Text(
           '开发者工具',
           style: TextStyle(
             fontWeight: FontWeight.bold,
             fontSize: 18,
-            color: Colors.white,
+            color: Get.reactiveTheme.textTheme.bodyLarge!.color,
           ),
         ),
         bottom: PreferredSize(

+ 7 - 1
pigeons/core_api.dart

@@ -13,7 +13,12 @@ abstract class CoreApi {
   @async
   String? getApps();
   String? getSystemLocale();
-  bool? connect();
+  bool? connect(
+    String sessionId,
+    int socksPort,
+    String tunnelConfig,
+    String configJson,
+  );
   bool? disconnect();
   String? getRemoteIp();
   String? getAdvertisingId();
@@ -21,6 +26,7 @@ abstract class CoreApi {
   bool? isConnected();
   String? getSimInfo();
   bool? reconnect();
+  String? getChannel();
 }
 
 // 如果你需要让原生通知 Flutter 事件变化,可用 EventChannelApi

+ 20 - 20
pubspec.lock

@@ -125,18 +125,18 @@ packages:
     dependency: transitive
     description:
       name: build
-      sha256: ce76b1d48875e3233fde17717c23d1f60a91cc631597e49a400c89b475395b1d
+      sha256: "51dc711996cbf609b90cbe5b335bbce83143875a9d58e4b5c6d3c4f684d3dda7"
       url: "https://pub.dev"
     source: hosted
-    version: "3.1.0"
+    version: "2.5.4"
   build_config:
     dependency: transitive
     description:
       name: build_config
-      sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187"
+      sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
       url: "https://pub.dev"
     source: hosted
-    version: "1.2.0"
+    version: "1.1.2"
   build_daemon:
     dependency: transitive
     description:
@@ -149,26 +149,26 @@ packages:
     dependency: transitive
     description:
       name: build_resolvers
-      sha256: d1d57f7807debd7349b4726a19fd32ec8bc177c71ad0febf91a20f84cd2d4b46
+      sha256: ee4257b3f20c0c90e72ed2b57ad637f694ccba48839a821e87db762548c22a62
       url: "https://pub.dev"
     source: hosted
-    version: "3.0.3"
+    version: "2.5.4"
   build_runner:
     dependency: "direct dev"
     description:
       name: build_runner
-      sha256: b24597fceb695969d47025c958f3837f9f0122e237c6a22cb082a5ac66c3ca30
+      sha256: "382a4d649addbfb7ba71a3631df0ec6a45d5ab9b098638144faf27f02778eb53"
       url: "https://pub.dev"
     source: hosted
-    version: "2.7.1"
+    version: "2.5.4"
   build_runner_core:
     dependency: transitive
     description:
       name: build_runner_core
-      sha256: "066dda7f73d8eb48ba630a55acb50c4a84a2e6b453b1cb4567f581729e794f7b"
+      sha256: "85fbbb1036d576d966332a3f5ce83f2ce66a40bea1a94ad2d5fc29a19a0d3792"
       url: "https://pub.dev"
     source: hosted
-    version: "9.3.1"
+    version: "9.1.2"
   built_collection:
     dependency: transitive
     description:
@@ -676,18 +676,18 @@ packages:
     dependency: "direct dev"
     description:
       name: freezed
-      sha256: "13065f10e135263a4f5a4391b79a8efc5fb8106f8dd555a9e49b750b45393d77"
+      sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c"
       url: "https://pub.dev"
     source: hosted
-    version: "3.2.3"
+    version: "2.5.8"
   freezed_annotation:
     dependency: "direct main"
     description:
       name: freezed_annotation
-      sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8"
+      sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2
       url: "https://pub.dev"
     source: hosted
-    version: "3.1.0"
+    version: "2.4.4"
   frontend_server_client:
     dependency: transitive
     description:
@@ -796,10 +796,10 @@ packages:
     dependency: "direct dev"
     description:
       name: json_serializable
-      sha256: "33a040668b31b320aafa4822b7b1e177e163fc3c1e835c6750319d4ab23aa6fe"
+      sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c
       url: "https://pub.dev"
     source: hosted
-    version: "6.11.1"
+    version: "6.9.5"
   leak_tracker:
     dependency: transitive
     description:
@@ -1281,18 +1281,18 @@ packages:
     dependency: transitive
     description:
       name: source_gen
-      sha256: "7b19d6ba131c6eb98bfcbf8d56c1a7002eba438af2e7ae6f8398b2b0f4f381e3"
+      sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b"
       url: "https://pub.dev"
     source: hosted
-    version: "3.1.0"
+    version: "2.0.0"
   source_helper:
     dependency: transitive
     description:
       name: source_helper
-      sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723"
+      sha256: a447acb083d3a5ef17f983dd36201aeea33fedadb3228fa831f2f0c92f0f3aca
       url: "https://pub.dev"
     source: hosted
-    version: "1.3.8"
+    version: "1.3.7"
   source_span:
     dependency: transitive
     description:

+ 4 - 4
pubspec.yaml

@@ -61,7 +61,6 @@ dependencies:
   encrypt: ^5.0.3 # 加密
   flutter_markdown: ^0.7.7+1 # markdown
   flutter_switch: ^0.3.2 # 开关
-  freezed_annotation: ^3.1.0 # 注解
   json_annotation: ^4.9.0 # 注解
   shared_preferences: ^2.5.3 # 共享偏好
   flutter_screenutil: ^5.9.3 # 屏幕适配
@@ -77,6 +76,7 @@ dependencies:
   flutter_sticky_header: ^0.8.0 # 吸顶列表
   event_bus: ^2.0.0 # 事件总线
   animated_reorderable_list: ^1.3.0 # 可重新排序的动画列表
+  freezed_annotation: ^2.4.4
 
 dev_dependencies:
   flutter_test:
@@ -88,9 +88,9 @@ dev_dependencies:
   # package. See that file for information about deactivating specific lint
   # rules and activating additional ones.
   flutter_lints: ^5.0.0
-  build_runner: ^2.7.1
-  freezed: ^3.2.3
-  json_serializable: ^6.11.1
+  build_runner: ^2.4.14
+  freezed: ^2.5.8
+  json_serializable: ^6.9.3
   pigeon: ^26.0.1
   flutter_launcher_icons: ^0.14.4 # 应用图标
   flutter_native_splash: ^2.4.6 # 应用启动页

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio