diff --git a/android-app/.gitignore b/android-app/.gitignore
deleted file mode 100644
index aa724b7..0000000
--- a/android-app/.gitignore
+++ /dev/null
@@ -1,15 +0,0 @@
-*.iml
-.gradle
-/local.properties
-/.idea/caches
-/.idea/libraries
-/.idea/modules.xml
-/.idea/workspace.xml
-/.idea/navEditor.xml
-/.idea/assetWizardSettings.xml
-.DS_Store
-/build
-/captures
-.externalNativeBuild
-.cxx
-local.properties
diff --git a/android-app/app/.gitignore b/android-app/app/.gitignore
deleted file mode 100644
index 42afabf..0000000
--- a/android-app/app/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
\ No newline at end of file
diff --git a/android-app/app/build.gradle b/android-app/app/build.gradle
deleted file mode 100644
index 9479e51..0000000
--- a/android-app/app/build.gradle
+++ /dev/null
@@ -1,45 +0,0 @@
-plugins {
- id 'com.android.application'
- id 'org.jetbrains.kotlin.android'
-}
-
-android {
- namespace 'com.flux'
- compileSdk 34
-
- defaultConfig {
- applicationId "com.flux"
- minSdk 24
- targetSdk 34
- versionCode 1
- versionName "1.0.1"
-
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- }
-
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
- }
- }
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
- kotlinOptions {
- jvmTarget = '1.8'
- }
-}
-
-dependencies {
-
- implementation 'androidx.core:core-ktx:1.9.0'
- implementation 'androidx.appcompat:appcompat:1.7.1'
- implementation 'com.google.android.material:material:1.12.0'
- implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
- implementation 'androidx.webkit:webkit:1.8.0'
- testImplementation 'junit:junit:4.13.2'
- androidTestImplementation 'androidx.test.ext:junit:1.3.0'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0'
-}
\ No newline at end of file
diff --git a/android-app/app/proguard-rules.pro b/android-app/app/proguard-rules.pro
deleted file mode 100644
index 481bb43..0000000
--- a/android-app/app/proguard-rules.pro
+++ /dev/null
@@ -1,21 +0,0 @@
-# Add project specific ProGuard rules here.
-# You can control the set of applied configuration files using the
-# proguardFiles setting in build.gradle.
-#
-# For more details, see
-# http://developer.android.com/guide/developing/tools/proguard.html
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
-
-# Uncomment this to preserve the line number information for
-# debugging stack traces.
-#-keepattributes SourceFile,LineNumberTable
-
-# If you keep the line number information, uncomment this to
-# hide the original source file name.
-#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/android-app/app/release/flux.apk b/android-app/app/release/flux.apk
deleted file mode 100644
index a04c67f..0000000
Binary files a/android-app/app/release/flux.apk and /dev/null differ
diff --git a/android-app/app/release/output-metadata.json b/android-app/app/release/output-metadata.json
deleted file mode 100644
index 0b32f7f..0000000
--- a/android-app/app/release/output-metadata.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "version": 3,
- "artifactType": {
- "type": "APK",
- "kind": "Directory"
- },
- "applicationId": "com.flux",
- "variantName": "release",
- "elements": [
- {
- "type": "SINGLE",
- "filters": [],
- "attributes": [],
- "versionCode": 1,
- "versionName": "1.0.1",
- "outputFile": "app-release.apk"
- }
- ],
- "elementType": "File"
-}
\ No newline at end of file
diff --git a/android-app/app/src/androidTest/java/com/flux/ExampleInstrumentedTest.kt b/android-app/app/src/androidTest/java/com/flux/ExampleInstrumentedTest.kt
deleted file mode 100644
index 1f124ec..0000000
--- a/android-app/app/src/androidTest/java/com/flux/ExampleInstrumentedTest.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.flux
-
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.ext.junit.runners.AndroidJUnit4
-
-import org.junit.Test
-import org.junit.runner.RunWith
-
-import org.junit.Assert.*
-
-/**
- * Instrumented test, which will execute on an Android device.
- *
- * See [testing documentation](http://d.android.com/tools/testing).
- */
-@RunWith(AndroidJUnit4::class)
-class ExampleInstrumentedTest {
- @Test
- fun useAppContext() {
- // Context of the app under test.
- val appContext = InstrumentationRegistry.getInstrumentation().targetContext
- assertEquals("com.flux", appContext.packageName)
- }
-}
\ No newline at end of file
diff --git a/android-app/app/src/main/AndroidManifest.xml b/android-app/app/src/main/AndroidManifest.xml
deleted file mode 100644
index 952f2e9..0000000
--- a/android-app/app/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/android-app/app/src/main/ic_launcher-playstore.png b/android-app/app/src/main/ic_launcher-playstore.png
deleted file mode 100644
index 80c80a0..0000000
Binary files a/android-app/app/src/main/ic_launcher-playstore.png and /dev/null differ
diff --git a/android-app/app/src/main/java/com/flux/MainActivity.kt b/android-app/app/src/main/java/com/flux/MainActivity.kt
deleted file mode 100644
index c766e9a..0000000
--- a/android-app/app/src/main/java/com/flux/MainActivity.kt
+++ /dev/null
@@ -1,214 +0,0 @@
-package com.flux
-
-import androidx.core.content.edit
-import android.graphics.Color
-import android.os.Build
-import android.os.Bundle
-import android.view.View
-import android.view.WindowInsetsController
-import androidx.appcompat.app.AppCompatActivity
-import android.webkit.*
-import android.content.res.Configuration
-import org.json.JSONArray
-import org.json.JSONObject
-
-
-class MainActivity : AppCompatActivity() {
-
- private lateinit var webView: WebView
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
-
- webView = findViewById(R.id.webView)
- WebView.setWebContentsDebuggingEnabled(true)
- setupWebView()
- loadUrl()
-
- setupSystemBars()
- setupRootBackground()
- }
-
- private fun setupWebView() {
- val settings = webView.settings
- settings.javaScriptEnabled = true
- settings.domStorageEnabled = true
- settings.databaseEnabled = true
- settings.loadWithOverviewMode = true
- settings.useWideViewPort = true
- settings.setSupportZoom(true)
- settings.builtInZoomControls = true
- settings.displayZoomControls = false
-
- // 隐藏滚动条但不影响滚动
- webView.isVerticalScrollBarEnabled = false
- webView.isHorizontalScrollBarEnabled = false
- webView.scrollBarStyle = View.SCROLLBARS_INSIDE_OVERLAY
- webView.overScrollMode = View.OVER_SCROLL_NEVER
-
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
- }
-
- val isDarkTheme = resources.configuration.uiMode and
- Configuration.UI_MODE_NIGHT_MASK ==
- Configuration.UI_MODE_NIGHT_YES
-
- webView.setBackgroundColor(if (isDarkTheme) Color.BLACK else Color.WHITE)
-
-
- // 添加JavaScript接口
- webView.addJavascriptInterface(object {
-
- @android.webkit.JavascriptInterface
- fun getPanelAddresses(callback: String) {
- val sharedPrefs = getSharedPreferences("panel_config", MODE_PRIVATE)
- val addresses = sharedPrefs.getString("panel_addresses", "[]")
- webView.post {
- webView.evaluateJavascript("window.$callback($addresses);", null)
- }
- }
-
- @android.webkit.JavascriptInterface
- fun savePanelAddress(name: String, address: String) {
- val json = JSONObject()
- json.put("name", name)
- json.put("address", address)
- json.put("inx", false)
- val sharedPrefs = getSharedPreferences("panel_config", MODE_PRIVATE)
- val addresses = sharedPrefs.getString("panel_addresses", "[]")
- val jsonArray = JSONArray(addresses)
- jsonArray.put(json)
- sharedPrefs.edit {
- putString("panel_addresses", jsonArray.toString())
- }
- webView.post {
- webView.evaluateJavascript("window.setPanelAddresses(${jsonArray.toString()});", null)
- }
- }
-
- @android.webkit.JavascriptInterface
- fun setCurrentPanelAddress(name: String) {
-
- val sharedPrefs = getSharedPreferences("panel_config", MODE_PRIVATE)
- val addresses = sharedPrefs.getString("panel_addresses", "[]")
- val jsonArray = JSONArray(addresses)
- for (i in 0 until jsonArray.length()) {
- val obj = jsonArray.getJSONObject(i)
- if (name == obj.getString("name")) {
- obj.put("inx", true)
- }else{
- obj.put("inx", false)
- }
- }
- sharedPrefs.edit {
- putString("panel_addresses", jsonArray.toString())
- }
- webView.post {
- webView.evaluateJavascript("window.setPanelAddresses(${jsonArray.toString()});", null)
- }
- }
-
- @android.webkit.JavascriptInterface
- fun deletePanelAddress(name: String) {
- val jsonArray = JSONArray()
- val sharedPrefs = getSharedPreferences("panel_config", MODE_PRIVATE)
- val addresses = sharedPrefs.getString("panel_addresses", "[]")
- val jsonArraya = JSONArray(addresses)
- for (i in 0 until jsonArraya.length()) {
- val obj = jsonArraya.getJSONObject(i)
- if (name != obj.getString("name")) {
- jsonArray.put(obj)
- }
- }
- sharedPrefs.edit {
- putString("panel_addresses", jsonArray.toString())
- }
- webView.post {
- webView.evaluateJavascript("window.setPanelAddresses(${jsonArray.toString()});", null)
- }
- }
- }, "JsInterface")
-
- }
-
- private fun loadUrl() {
- //webView.loadUrl("http://192.168.100.9:3000")
- webView.loadUrl("file:///android_asset/index.html")
- }
-
- private fun setupSystemBars() {
- val isDarkTheme = resources.configuration.uiMode and
- Configuration.UI_MODE_NIGHT_MASK ==
- Configuration.UI_MODE_NIGHT_YES
-
- if (isDarkTheme) {
- window.statusBarColor = Color.BLACK
- window.navigationBarColor = Color.BLACK
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- window.insetsController?.setSystemBarsAppearance(
- 0,
- WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS or
- WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
- )
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- @Suppress("DEPRECATION")
- window.decorView.systemUiVisibility =
- window.decorView.systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- @Suppress("DEPRECATION")
- window.decorView.systemUiVisibility =
- window.decorView.systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
- }
- }
- } else {
- window.statusBarColor = Color.WHITE
- window.navigationBarColor = Color.WHITE
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- window.insetsController?.setSystemBarsAppearance(
- WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS or
- WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS,
- WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS or
- WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
- )
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- @Suppress("DEPRECATION")
- window.decorView.systemUiVisibility =
- window.decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- @Suppress("DEPRECATION")
- window.decorView.systemUiVisibility =
- window.decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
- }
- }
- }
- }
-
- override fun onBackPressed() {
- if (webView.canGoBack()) webView.goBack() else super.onBackPressed()
- }
-
- override fun onConfigurationChanged(newConfig: Configuration) {
- super.onConfigurationChanged(newConfig)
- setupSystemBars()
- setupWebViewBackground()
- setupRootBackground()
- }
-
- private fun setupWebViewBackground() {
- val isDarkTheme = resources.configuration.uiMode and
- Configuration.UI_MODE_NIGHT_MASK ==
- Configuration.UI_MODE_NIGHT_YES
- webView.setBackgroundColor(if (isDarkTheme) Color.BLACK else Color.WHITE)
- }
-
- private fun setupRootBackground() {
- val isDarkTheme = resources.configuration.uiMode and
- Configuration.UI_MODE_NIGHT_MASK ==
- Configuration.UI_MODE_NIGHT_YES
- val rootView = findViewById(android.R.id.content)
- rootView.setBackgroundColor(if (isDarkTheme) Color.BLACK else Color.WHITE)
- }
-}
diff --git a/android-app/app/src/main/res/drawable/ic_launcher_background.xml b/android-app/app/src/main/res/drawable/ic_launcher_background.xml
deleted file mode 100644
index ca3826a..0000000
--- a/android-app/app/src/main/res/drawable/ic_launcher_background.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/android-app/app/src/main/res/drawable/ic_launcher_foreground.xml b/android-app/app/src/main/res/drawable/ic_launcher_foreground.xml
deleted file mode 100644
index 2b068d1..0000000
--- a/android-app/app/src/main/res/drawable/ic_launcher_foreground.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/android-app/app/src/main/res/layout/activity_main.xml b/android-app/app/src/main/res/layout/activity_main.xml
deleted file mode 100644
index 7f11c64..0000000
--- a/android-app/app/src/main/res/layout/activity_main.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/android-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
deleted file mode 100644
index c4a603d..0000000
--- a/android-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/android-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
deleted file mode 100644
index c4a603d..0000000
--- a/android-app/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/android-app/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/android-app/app/src/main/res/mipmap-hdpi/ic_launcher.webp
deleted file mode 100644
index 3c965f7..0000000
Binary files a/android-app/app/src/main/res/mipmap-hdpi/ic_launcher.webp and /dev/null differ
diff --git a/android-app/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/android-app/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
deleted file mode 100644
index b0cc786..0000000
Binary files a/android-app/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp and /dev/null differ
diff --git a/android-app/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/android-app/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
deleted file mode 100644
index 9c7cc2c..0000000
Binary files a/android-app/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/android-app/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/android-app/app/src/main/res/mipmap-mdpi/ic_launcher.webp
deleted file mode 100644
index 15b2494..0000000
Binary files a/android-app/app/src/main/res/mipmap-mdpi/ic_launcher.webp and /dev/null differ
diff --git a/android-app/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/android-app/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
deleted file mode 100644
index bdc56f3..0000000
Binary files a/android-app/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp and /dev/null differ
diff --git a/android-app/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/android-app/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
deleted file mode 100644
index 39f430d..0000000
Binary files a/android-app/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/android-app/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/android-app/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
deleted file mode 100644
index 0a50157..0000000
Binary files a/android-app/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and /dev/null differ
diff --git a/android-app/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/android-app/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
deleted file mode 100644
index 1412240..0000000
Binary files a/android-app/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp and /dev/null differ
diff --git a/android-app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/android-app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
deleted file mode 100644
index 80f2aef..0000000
Binary files a/android-app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/android-app/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/android-app/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
deleted file mode 100644
index 6794017..0000000
Binary files a/android-app/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and /dev/null differ
diff --git a/android-app/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/android-app/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
deleted file mode 100644
index ebce376..0000000
Binary files a/android-app/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp and /dev/null differ
diff --git a/android-app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/android-app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
deleted file mode 100644
index d3178ed..0000000
Binary files a/android-app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/android-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/android-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
deleted file mode 100644
index 99e51c9..0000000
Binary files a/android-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and /dev/null differ
diff --git a/android-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/android-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
deleted file mode 100644
index 1fee8d3..0000000
Binary files a/android-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp and /dev/null differ
diff --git a/android-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/android-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
deleted file mode 100644
index 2f58dc7..0000000
Binary files a/android-app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/android-app/app/src/main/res/values-night/themes.xml b/android-app/app/src/main/res/values-night/themes.xml
deleted file mode 100644
index 6252187..0000000
--- a/android-app/app/src/main/res/values-night/themes.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/android-app/app/src/main/res/values/colors.xml b/android-app/app/src/main/res/values/colors.xml
deleted file mode 100644
index aacedae..0000000
--- a/android-app/app/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
- #FF000000
- #FFFFFFFF
-
-
\ No newline at end of file
diff --git a/android-app/app/src/main/res/values/strings.xml b/android-app/app/src/main/res/values/strings.xml
deleted file mode 100644
index fc68ff2..0000000
--- a/android-app/app/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
- Flux
-
\ No newline at end of file
diff --git a/android-app/app/src/main/res/values/themes.xml b/android-app/app/src/main/res/values/themes.xml
deleted file mode 100644
index 560737c..0000000
--- a/android-app/app/src/main/res/values/themes.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/android-app/app/src/main/res/xml/backup_rules.xml b/android-app/app/src/main/res/xml/backup_rules.xml
deleted file mode 100644
index fa0f996..0000000
--- a/android-app/app/src/main/res/xml/backup_rules.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/android-app/app/src/main/res/xml/data_extraction_rules.xml b/android-app/app/src/main/res/xml/data_extraction_rules.xml
deleted file mode 100644
index 9ee9997..0000000
--- a/android-app/app/src/main/res/xml/data_extraction_rules.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/android-app/app/src/test/java/com/flux/ExampleUnitTest.kt b/android-app/app/src/test/java/com/flux/ExampleUnitTest.kt
deleted file mode 100644
index 091c9c7..0000000
--- a/android-app/app/src/test/java/com/flux/ExampleUnitTest.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.flux
-
-import org.junit.Test
-
-import org.junit.Assert.*
-
-/**
- * Example local unit test, which will execute on the development machine (host).
- *
- * See [testing documentation](http://d.android.com/tools/testing).
- */
-class ExampleUnitTest {
- @Test
- fun addition_isCorrect() {
- assertEquals(4, 2 + 2)
- }
-}
\ No newline at end of file
diff --git a/android-app/build.gradle b/android-app/build.gradle
deleted file mode 100644
index 8228565..0000000
--- a/android-app/build.gradle
+++ /dev/null
@@ -1,5 +0,0 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-plugins {
- id 'com.android.application' version '8.2.2' apply false
- id 'org.jetbrains.kotlin.android' version '1.8.0' apply false
-}
\ No newline at end of file
diff --git a/android-app/gradle.properties b/android-app/gradle.properties
deleted file mode 100644
index 3c5031e..0000000
--- a/android-app/gradle.properties
+++ /dev/null
@@ -1,23 +0,0 @@
-# Project-wide Gradle settings.
-# IDE (e.g. Android Studio) users:
-# Gradle settings configured through the IDE *will override*
-# any settings specified in this file.
-# For more details on how to configure your build environment visit
-# http://www.gradle.org/docs/current/userguide/build_environment.html
-# Specifies the JVM arguments used for the daemon process.
-# The setting is particularly useful for tweaking memory settings.
-org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
-# When configured, Gradle will run in incubating parallel mode.
-# This option should only be used with decoupled projects. More details, visit
-# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-# org.gradle.parallel=true
-# AndroidX package structure to make it clearer which packages are bundled with the
-# Android operating system, and which are packaged with your app's APK
-# https://developer.android.com/topic/libraries/support-library/androidx-rn
-android.useAndroidX=true
-# Kotlin code style for this project: "official" or "obsolete":
-kotlin.code.style=official
-# Enables namespacing of each library's R class so that its R class includes only the
-# resources declared in the library itself and none from the library's dependencies,
-# thereby reducing the size of the R class for that library
-android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/android-app/gradle/wrapper/gradle-wrapper.properties b/android-app/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index bd927bb..0000000
--- a/android-app/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-#Wed Aug 20 09:15:27 CST 2025
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
diff --git a/android-app/gradlew b/android-app/gradlew
deleted file mode 100755
index 4f906e0..0000000
--- a/android-app/gradlew
+++ /dev/null
@@ -1,185 +0,0 @@
-#!/usr/bin/env sh
-
-#
-# Copyright 2015 the original author or authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# https://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-##############################################################################
-##
-## Gradle start up script for UN*X
-##
-##############################################################################
-
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn () {
- echo "$*"
-}
-
-die () {
- echo
- echo "$*"
- echo
- exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-nonstop=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
- NONSTOP* )
- nonstop=true
- ;;
-esac
-
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
-
-
-# Determine the Java command to use to start the JVM.
-if [ -n "$JAVA_HOME" ] ; then
- if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
- # IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
- else
- JAVACMD="$JAVA_HOME/bin/java"
- fi
- if [ ! -x "$JAVACMD" ] ; then
- die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
- fi
-else
- JAVACMD="java"
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-fi
-
-# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
- MAX_FD_LIMIT=`ulimit -H -n`
- if [ $? -eq 0 ] ; then
- if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
- MAX_FD="$MAX_FD_LIMIT"
- fi
- ulimit -n $MAX_FD
- if [ $? -ne 0 ] ; then
- warn "Could not set maximum file descriptor limit: $MAX_FD"
- fi
- else
- warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
- fi
-fi
-
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
- GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
-
-# For Cygwin or MSYS, switch paths to Windows format before running java
-if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
- APP_HOME=`cygpath --path --mixed "$APP_HOME"`
- CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-
- JAVACMD=`cygpath --unix "$JAVACMD"`
-
- # We build the pattern for arguments to be converted via cygpath
- ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
- SEP=""
- for dir in $ROOTDIRSRAW ; do
- ROOTDIRS="$ROOTDIRS$SEP$dir"
- SEP="|"
- done
- OURCYGPATTERN="(^($ROOTDIRS))"
- # Add a user-defined pattern to the cygpath arguments
- if [ "$GRADLE_CYGPATTERN" != "" ] ; then
- OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
- fi
- # Now convert the arguments - kludge to limit ourselves to /bin/sh
- i=0
- for arg in "$@" ; do
- CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
- CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
-
- if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
- eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
- else
- eval `echo args$i`="\"$arg\""
- fi
- i=`expr $i + 1`
- done
- case $i in
- 0) set -- ;;
- 1) set -- "$args0" ;;
- 2) set -- "$args0" "$args1" ;;
- 3) set -- "$args0" "$args1" "$args2" ;;
- 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
- esac
-fi
-
-# Escape application args
-save () {
- for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
- echo " "
-}
-APP_ARGS=`save "$@"`
-
-# Collect all arguments for the java command, following the shell quoting and substitution rules
-eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
-
-exec "$JAVACMD" "$@"
diff --git a/android-app/gradlew.bat b/android-app/gradlew.bat
deleted file mode 100644
index ac1b06f..0000000
--- a/android-app/gradlew.bat
+++ /dev/null
@@ -1,89 +0,0 @@
-@rem
-@rem Copyright 2015 the original author or authors.
-@rem
-@rem Licensed under the Apache License, Version 2.0 (the "License");
-@rem you may not use this file except in compliance with the License.
-@rem You may obtain a copy of the License at
-@rem
-@rem https://www.apache.org/licenses/LICENSE-2.0
-@rem
-@rem Unless required by applicable law or agreed to in writing, software
-@rem distributed under the License is distributed on an "AS IS" BASIS,
-@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-@rem See the License for the specific language governing permissions and
-@rem limitations under the License.
-@rem
-
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Resolve any "." and ".." in APP_HOME to make it shorter.
-for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto execute
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto execute
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
diff --git a/android-app/settings.gradle b/android-app/settings.gradle
deleted file mode 100644
index 4627d16..0000000
--- a/android-app/settings.gradle
+++ /dev/null
@@ -1,17 +0,0 @@
-pluginManagement {
- repositories {
- google()
- mavenCentral()
- gradlePluginPortal()
- }
-}
-dependencyResolutionManagement {
- repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
- repositories {
- google()
- mavenCentral()
- }
-}
-
-rootProject.name = "Flux"
-include ':app'
diff --git a/flux.ipa b/flux.ipa
deleted file mode 100644
index a9fbd74..0000000
Binary files a/flux.ipa and /dev/null differ
diff --git a/go-backend/internal/http/handler/control_plane.go b/go-backend/internal/http/handler/control_plane.go
index 2a91cb3..95046b7 100644
--- a/go-backend/internal/http/handler/control_plane.go
+++ b/go-backend/internal/http/handler/control_plane.go
@@ -238,6 +238,7 @@ func (h *Handler) resolveUserTunnelAndLimiter(userID, tunnelID int64) (int64, *i
FROM user_tunnel ut
LEFT JOIN speed_limit sl ON sl.id = ut.speed_id
WHERE ut.user_id = ? AND ut.tunnel_id = ?
+ ORDER BY ut.id ASC
LIMIT 1
`, userID, tunnelID)
var userTunnelID int64
@@ -256,6 +257,32 @@ func (h *Handler) resolveUserTunnelAndLimiter(userID, tunnelID int64) (int64, *i
return userTunnelID, &v, nil
}
+func (h *Handler) listUserTunnelIDs(userID, tunnelID int64) ([]int64, error) {
+ rows, err := h.repo.DB().Query(`
+ SELECT id
+ FROM user_tunnel
+ WHERE user_id = ? AND tunnel_id = ?
+ ORDER BY id ASC
+ `, userID, tunnelID)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+
+ out := make([]int64, 0)
+ for rows.Next() {
+ var id int64
+ if err := rows.Scan(&id); err != nil {
+ return nil, err
+ }
+ out = append(out, id)
+ }
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
func (h *Handler) syncForwardServices(forward *forwardRecord, method string, allowFallbackAdd bool) error {
if h == nil || forward == nil {
return errors.New("invalid forward sync context")
@@ -311,20 +338,62 @@ func (h *Handler) controlForwardServices(forward *forwardRecord, commandType str
if err != nil {
return err
}
- base := buildForwardServiceBase(forward.ID, forward.UserID, userTunnelID)
- payload := map[string]interface{}{
- "services": buildForwardControlServiceNames(base, commandType),
+ userTunnelIDs, err := h.listUserTunnelIDs(forward.UserID, forward.TunnelID)
+ if err != nil {
+ return err
}
+ bases := buildForwardServiceBaseCandidates(forward.ID, forward.UserID, userTunnelID, userTunnelIDs)
seen := map[int64]struct{}{}
for _, fp := range ports {
if _, ok := seen[fp.NodeID]; ok {
continue
}
seen[fp.NodeID] = struct{}{}
- _, err := h.sendNodeCommand(fp.NodeID, commandType, payload, false, tolerateNotFound)
- if err != nil {
- return err
+
+ var lastNotFoundErr error
+ nodeHandled := false
+
+ for _, base := range bases {
+ payload := map[string]interface{}{
+ "services": buildForwardControlServiceNames(base, commandType),
+ }
+ _, err := h.sendNodeCommand(fp.NodeID, commandType, payload, false, false)
+ if err == nil {
+ nodeHandled = true
+ break
+ }
+
+ if !isNotFoundError(err) {
+ return err
+ }
+ lastNotFoundErr = err
+
+ if shouldTryLegacySingleService(commandType) {
+ legacyPayload := map[string]interface{}{
+ "services": []string{base},
+ }
+ _, legacyErr := h.sendNodeCommand(fp.NodeID, commandType, legacyPayload, false, false)
+ if legacyErr == nil {
+ nodeHandled = true
+ break
+ }
+ if !isNotFoundError(legacyErr) {
+ return legacyErr
+ }
+ lastNotFoundErr = legacyErr
+ }
}
+
+ if nodeHandled {
+ continue
+ }
+ if tolerateNotFound {
+ continue
+ }
+ if lastNotFoundErr != nil {
+ return lastNotFoundErr
+ }
+ return errors.New("service control failed")
}
return nil
}
@@ -852,6 +921,31 @@ func buildForwardServiceBase(forwardID, userID, userTunnelID int64) string {
return fmt.Sprintf("%d_%d_%d", forwardID, userID, userTunnelID)
}
+func buildForwardServiceBaseCandidates(forwardID, userID, preferredUserTunnelID int64, userTunnelIDs []int64) []string {
+ orderedIDs := make([]int64, 0, len(userTunnelIDs)+2)
+ seen := make(map[int64]struct{}, len(userTunnelIDs)+2)
+
+ appendID := func(id int64) {
+ if _, ok := seen[id]; ok {
+ return
+ }
+ seen[id] = struct{}{}
+ orderedIDs = append(orderedIDs, id)
+ }
+
+ appendID(preferredUserTunnelID)
+ for _, id := range userTunnelIDs {
+ appendID(id)
+ }
+ appendID(0)
+
+ bases := make([]string, 0, len(orderedIDs))
+ for _, id := range orderedIDs {
+ bases = append(bases, buildForwardServiceBase(forwardID, userID, id))
+ }
+ return bases
+}
+
func buildForwardControlServiceNames(base, commandType string) []string {
names := []string{base + "_tcp", base + "_udp"}
if strings.EqualFold(strings.TrimSpace(commandType), "DeleteService") {
@@ -860,6 +954,19 @@ func buildForwardControlServiceNames(base, commandType string) []string {
return names
}
+func shouldTryLegacySingleService(commandType string) bool {
+ cmd := strings.ToLower(strings.TrimSpace(commandType))
+ return cmd == "pauseservice" || cmd == "resumeservice"
+}
+
+func isNotFoundError(err error) bool {
+ if err == nil {
+ return false
+ }
+ msg := strings.ToLower(strings.TrimSpace(err.Error()))
+ return strings.Contains(msg, "not found") || strings.Contains(msg, "不存在")
+}
+
func buildForwardServiceConfigs(baseName string, forward *forwardRecord, tunnel *tunnelRecord, node *nodeRecord, port int, limiter *int) []map[string]interface{} {
protocols := []string{"tcp", "udp"}
services := make([]map[string]interface{}, 0, 2)
diff --git a/go-backend/internal/http/handler/control_plane_test.go b/go-backend/internal/http/handler/control_plane_test.go
index f34cb92..91c3a9b 100644
--- a/go-backend/internal/http/handler/control_plane_test.go
+++ b/go-backend/internal/http/handler/control_plane_test.go
@@ -25,3 +25,31 @@ func TestBuildForwardControlServiceNamesDelete(t *testing.T) {
t.Fatalf("expected %v, got %v", want, got)
}
}
+
+func TestBuildForwardServiceBaseCandidates(t *testing.T) {
+ got := buildForwardServiceBaseCandidates(12, 34, 56, []int64{56, 78, 90})
+ want := []string{"12_34_56", "12_34_78", "12_34_90", "12_34_0"}
+ if !reflect.DeepEqual(got, want) {
+ t.Fatalf("expected %v, got %v", want, got)
+ }
+}
+
+func TestBuildForwardServiceBaseCandidatesWithZeroPreferred(t *testing.T) {
+ got := buildForwardServiceBaseCandidates(12, 34, 0, []int64{78, 0, 90})
+ want := []string{"12_34_0", "12_34_78", "12_34_90"}
+ if !reflect.DeepEqual(got, want) {
+ t.Fatalf("expected %v, got %v", want, got)
+ }
+}
+
+func TestShouldTryLegacySingleService(t *testing.T) {
+ if !shouldTryLegacySingleService("PauseService") {
+ t.Fatalf("PauseService should require legacy fallback")
+ }
+ if !shouldTryLegacySingleService("resumeService") {
+ t.Fatalf("ResumeService should require legacy fallback")
+ }
+ if shouldTryLegacySingleService("DeleteService") {
+ t.Fatalf("DeleteService should not require legacy fallback")
+ }
+}
diff --git a/ios-app/flux.xcodeproj/project.pbxproj b/ios-app/flux.xcodeproj/project.pbxproj
deleted file mode 100644
index 4606e6a..0000000
--- a/ios-app/flux.xcodeproj/project.pbxproj
+++ /dev/null
@@ -1,563 +0,0 @@
-// !$*UTF8*$!
-{
- archiveVersion = 1;
- classes = {
- };
- objectVersion = 77;
- objects = {
-
-/* Begin PBXContainerItemProxy section */
- 296FEB8A2E56AAF900DC77A3 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 296FEB742E56AAF700DC77A3 /* Project object */;
- proxyType = 1;
- remoteGlobalIDString = 296FEB7B2E56AAF700DC77A3;
- remoteInfo = flux;
- };
- 296FEB942E56AAF900DC77A3 /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 296FEB742E56AAF700DC77A3 /* Project object */;
- proxyType = 1;
- remoteGlobalIDString = 296FEB7B2E56AAF700DC77A3;
- remoteInfo = flux;
- };
-/* End PBXContainerItemProxy section */
-
-/* Begin PBXFileReference section */
- 296FEB7C2E56AAF700DC77A3 /* flux.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = flux.app; sourceTree = BUILT_PRODUCTS_DIR; };
- 296FEB892E56AAF900DC77A3 /* fluxTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = fluxTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
- 296FEB932E56AAF900DC77A3 /* fluxUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = fluxUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
-/* End PBXFileReference section */
-
-/* Begin PBXFileSystemSynchronizedRootGroup section */
- 296FEB7E2E56AAF700DC77A3 /* flux */ = {
- isa = PBXFileSystemSynchronizedRootGroup;
- path = flux;
- sourceTree = "";
- };
- 296FEB8C2E56AAF900DC77A3 /* fluxTests */ = {
- isa = PBXFileSystemSynchronizedRootGroup;
- path = fluxTests;
- sourceTree = "";
- };
- 296FEB962E56AAF900DC77A3 /* fluxUITests */ = {
- isa = PBXFileSystemSynchronizedRootGroup;
- path = fluxUITests;
- sourceTree = "";
- };
-/* End PBXFileSystemSynchronizedRootGroup section */
-
-/* Begin PBXFrameworksBuildPhase section */
- 296FEB792E56AAF700DC77A3 /* Frameworks */ = {
- isa = PBXFrameworksBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 296FEB862E56AAF900DC77A3 /* Frameworks */ = {
- isa = PBXFrameworksBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 296FEB902E56AAF900DC77A3 /* Frameworks */ = {
- isa = PBXFrameworksBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXFrameworksBuildPhase section */
-
-/* Begin PBXGroup section */
- 296FEB732E56AAF700DC77A3 = {
- isa = PBXGroup;
- children = (
- 296FEB7E2E56AAF700DC77A3 /* flux */,
- 296FEB8C2E56AAF900DC77A3 /* fluxTests */,
- 296FEB962E56AAF900DC77A3 /* fluxUITests */,
- 296FEB7D2E56AAF700DC77A3 /* Products */,
- );
- sourceTree = "";
- };
- 296FEB7D2E56AAF700DC77A3 /* Products */ = {
- isa = PBXGroup;
- children = (
- 296FEB7C2E56AAF700DC77A3 /* flux.app */,
- 296FEB892E56AAF900DC77A3 /* fluxTests.xctest */,
- 296FEB932E56AAF900DC77A3 /* fluxUITests.xctest */,
- );
- name = Products;
- sourceTree = "";
- };
-/* End PBXGroup section */
-
-/* Begin PBXNativeTarget section */
- 296FEB7B2E56AAF700DC77A3 /* flux */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = 296FEB9D2E56AAF900DC77A3 /* Build configuration list for PBXNativeTarget "flux" */;
- buildPhases = (
- 296FEB782E56AAF700DC77A3 /* Sources */,
- 296FEB792E56AAF700DC77A3 /* Frameworks */,
- 296FEB7A2E56AAF700DC77A3 /* Resources */,
- );
- buildRules = (
- );
- dependencies = (
- );
- fileSystemSynchronizedGroups = (
- 296FEB7E2E56AAF700DC77A3 /* flux */,
- );
- name = flux;
- packageProductDependencies = (
- );
- productName = flux;
- productReference = 296FEB7C2E56AAF700DC77A3 /* flux.app */;
- productType = "com.apple.product-type.application";
- };
- 296FEB882E56AAF900DC77A3 /* fluxTests */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = 296FEBA02E56AAF900DC77A3 /* Build configuration list for PBXNativeTarget "fluxTests" */;
- buildPhases = (
- 296FEB852E56AAF900DC77A3 /* Sources */,
- 296FEB862E56AAF900DC77A3 /* Frameworks */,
- 296FEB872E56AAF900DC77A3 /* Resources */,
- );
- buildRules = (
- );
- dependencies = (
- 296FEB8B2E56AAF900DC77A3 /* PBXTargetDependency */,
- );
- fileSystemSynchronizedGroups = (
- 296FEB8C2E56AAF900DC77A3 /* fluxTests */,
- );
- name = fluxTests;
- packageProductDependencies = (
- );
- productName = fluxTests;
- productReference = 296FEB892E56AAF900DC77A3 /* fluxTests.xctest */;
- productType = "com.apple.product-type.bundle.unit-test";
- };
- 296FEB922E56AAF900DC77A3 /* fluxUITests */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = 296FEBA32E56AAF900DC77A3 /* Build configuration list for PBXNativeTarget "fluxUITests" */;
- buildPhases = (
- 296FEB8F2E56AAF900DC77A3 /* Sources */,
- 296FEB902E56AAF900DC77A3 /* Frameworks */,
- 296FEB912E56AAF900DC77A3 /* Resources */,
- );
- buildRules = (
- );
- dependencies = (
- 296FEB952E56AAF900DC77A3 /* PBXTargetDependency */,
- );
- fileSystemSynchronizedGroups = (
- 296FEB962E56AAF900DC77A3 /* fluxUITests */,
- );
- name = fluxUITests;
- packageProductDependencies = (
- );
- productName = fluxUITests;
- productReference = 296FEB932E56AAF900DC77A3 /* fluxUITests.xctest */;
- productType = "com.apple.product-type.bundle.ui-testing";
- };
-/* End PBXNativeTarget section */
-
-/* Begin PBXProject section */
- 296FEB742E56AAF700DC77A3 /* Project object */ = {
- isa = PBXProject;
- attributes = {
- BuildIndependentTargetsInParallel = 1;
- LastSwiftUpdateCheck = 1640;
- LastUpgradeCheck = 1640;
- TargetAttributes = {
- 296FEB7B2E56AAF700DC77A3 = {
- CreatedOnToolsVersion = 16.4;
- };
- 296FEB882E56AAF900DC77A3 = {
- CreatedOnToolsVersion = 16.4;
- TestTargetID = 296FEB7B2E56AAF700DC77A3;
- };
- 296FEB922E56AAF900DC77A3 = {
- CreatedOnToolsVersion = 16.4;
- TestTargetID = 296FEB7B2E56AAF700DC77A3;
- };
- };
- };
- buildConfigurationList = 296FEB772E56AAF700DC77A3 /* Build configuration list for PBXProject "flux" */;
- developmentRegion = en;
- hasScannedForEncodings = 0;
- knownRegions = (
- en,
- Base,
- );
- mainGroup = 296FEB732E56AAF700DC77A3;
- minimizedProjectReferenceProxies = 1;
- preferredProjectObjectVersion = 77;
- productRefGroup = 296FEB7D2E56AAF700DC77A3 /* Products */;
- projectDirPath = "";
- projectRoot = "";
- targets = (
- 296FEB7B2E56AAF700DC77A3 /* flux */,
- 296FEB882E56AAF900DC77A3 /* fluxTests */,
- 296FEB922E56AAF900DC77A3 /* fluxUITests */,
- );
- };
-/* End PBXProject section */
-
-/* Begin PBXResourcesBuildPhase section */
- 296FEB7A2E56AAF700DC77A3 /* Resources */ = {
- isa = PBXResourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 296FEB872E56AAF900DC77A3 /* Resources */ = {
- isa = PBXResourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 296FEB912E56AAF900DC77A3 /* Resources */ = {
- isa = PBXResourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXResourcesBuildPhase section */
-
-/* Begin PBXSourcesBuildPhase section */
- 296FEB782E56AAF700DC77A3 /* Sources */ = {
- isa = PBXSourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 296FEB852E56AAF900DC77A3 /* Sources */ = {
- isa = PBXSourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
- 296FEB8F2E56AAF900DC77A3 /* Sources */ = {
- isa = PBXSourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
-/* End PBXSourcesBuildPhase section */
-
-/* Begin PBXTargetDependency section */
- 296FEB8B2E56AAF900DC77A3 /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- target = 296FEB7B2E56AAF700DC77A3 /* flux */;
- targetProxy = 296FEB8A2E56AAF900DC77A3 /* PBXContainerItemProxy */;
- };
- 296FEB952E56AAF900DC77A3 /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- target = 296FEB7B2E56AAF700DC77A3 /* flux */;
- targetProxy = 296FEB942E56AAF900DC77A3 /* PBXContainerItemProxy */;
- };
-/* End PBXTargetDependency section */
-
-/* Begin XCBuildConfiguration section */
- 296FEB9B2E56AAF900DC77A3 /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
- CLANG_ANALYZER_NONNULL = YES;
- CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
- CLANG_ENABLE_MODULES = YES;
- CLANG_ENABLE_OBJC_ARC = YES;
- CLANG_ENABLE_OBJC_WEAK = YES;
- CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
- CLANG_WARN_BOOL_CONVERSION = YES;
- CLANG_WARN_COMMA = YES;
- CLANG_WARN_CONSTANT_CONVERSION = YES;
- CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
- CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
- CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
- CLANG_WARN_EMPTY_BODY = YES;
- CLANG_WARN_ENUM_CONVERSION = YES;
- CLANG_WARN_INFINITE_RECURSION = YES;
- CLANG_WARN_INT_CONVERSION = YES;
- CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
- CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
- CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
- CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
- CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
- CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
- CLANG_WARN_STRICT_PROTOTYPES = YES;
- CLANG_WARN_SUSPICIOUS_MOVE = YES;
- CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
- CLANG_WARN_UNREACHABLE_CODE = YES;
- CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- COPY_PHASE_STRIP = NO;
- DEBUG_INFORMATION_FORMAT = dwarf;
- ENABLE_STRICT_OBJC_MSGSEND = YES;
- ENABLE_TESTABILITY = YES;
- ENABLE_USER_SCRIPT_SANDBOXING = YES;
- GCC_C_LANGUAGE_STANDARD = gnu17;
- GCC_DYNAMIC_NO_PIC = NO;
- GCC_NO_COMMON_BLOCKS = YES;
- GCC_OPTIMIZATION_LEVEL = 0;
- GCC_PREPROCESSOR_DEFINITIONS = (
- "DEBUG=1",
- "$(inherited)",
- );
- GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
- GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
- GCC_WARN_UNDECLARED_SELECTOR = YES;
- GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 18.5;
- LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
- MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
- MTL_FAST_MATH = YES;
- ONLY_ACTIVE_ARCH = YES;
- SDKROOT = iphoneos;
- SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
- SWIFT_OPTIMIZATION_LEVEL = "-Onone";
- };
- name = Debug;
- };
- 296FEB9C2E56AAF900DC77A3 /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ALWAYS_SEARCH_USER_PATHS = NO;
- ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
- CLANG_ANALYZER_NONNULL = YES;
- CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
- CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
- CLANG_ENABLE_MODULES = YES;
- CLANG_ENABLE_OBJC_ARC = YES;
- CLANG_ENABLE_OBJC_WEAK = YES;
- CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
- CLANG_WARN_BOOL_CONVERSION = YES;
- CLANG_WARN_COMMA = YES;
- CLANG_WARN_CONSTANT_CONVERSION = YES;
- CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
- CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
- CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
- CLANG_WARN_EMPTY_BODY = YES;
- CLANG_WARN_ENUM_CONVERSION = YES;
- CLANG_WARN_INFINITE_RECURSION = YES;
- CLANG_WARN_INT_CONVERSION = YES;
- CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
- CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
- CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
- CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
- CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
- CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
- CLANG_WARN_STRICT_PROTOTYPES = YES;
- CLANG_WARN_SUSPICIOUS_MOVE = YES;
- CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
- CLANG_WARN_UNREACHABLE_CODE = YES;
- CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- COPY_PHASE_STRIP = NO;
- DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
- ENABLE_NS_ASSERTIONS = NO;
- ENABLE_STRICT_OBJC_MSGSEND = YES;
- ENABLE_USER_SCRIPT_SANDBOXING = YES;
- GCC_C_LANGUAGE_STANDARD = gnu17;
- GCC_NO_COMMON_BLOCKS = YES;
- GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
- GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
- GCC_WARN_UNDECLARED_SELECTOR = YES;
- GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
- GCC_WARN_UNUSED_FUNCTION = YES;
- GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 18.5;
- LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
- MTL_ENABLE_DEBUG_INFO = NO;
- MTL_FAST_MATH = YES;
- SDKROOT = iphoneos;
- SWIFT_COMPILATION_MODE = wholemodule;
- VALIDATE_PRODUCT = YES;
- };
- name = Release;
- };
- 296FEB9E2E56AAF900DC77A3 /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
- CODE_SIGN_IDENTITY = "Apple Development";
- CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_TEAM = XRXD4YN2Y6;
- ENABLE_PREVIEWS = YES;
- GENERATE_INFOPLIST_FILE = YES;
- INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
- INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
- INFOPLIST_KEY_UILaunchScreen_Generation = YES;
- INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
- INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
- IPHONEOS_DEPLOYMENT_TARGET = 15.6;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- );
- MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = bqlpfy.flux;
- PRODUCT_NAME = "$(TARGET_NAME)";
- PROVISIONING_PROFILE_SPECIFIER = "";
- SWIFT_EMIT_LOC_STRINGS = YES;
- SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2";
- };
- name = Debug;
- };
- 296FEB9F2E56AAF900DC77A3 /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
- CODE_SIGN_IDENTITY = "Apple Development";
- CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_TEAM = XRXD4YN2Y6;
- ENABLE_PREVIEWS = YES;
- GENERATE_INFOPLIST_FILE = YES;
- INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
- INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
- INFOPLIST_KEY_UILaunchScreen_Generation = YES;
- INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
- INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
- IPHONEOS_DEPLOYMENT_TARGET = 15.6;
- LD_RUNPATH_SEARCH_PATHS = (
- "$(inherited)",
- "@executable_path/Frameworks",
- );
- MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = bqlpfy.flux;
- PRODUCT_NAME = "$(TARGET_NAME)";
- PROVISIONING_PROFILE_SPECIFIER = "";
- SWIFT_EMIT_LOC_STRINGS = YES;
- SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2";
- };
- name = Release;
- };
- 296FEBA12E56AAF900DC77A3 /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- BUNDLE_LOADER = "$(TEST_HOST)";
- CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
- GENERATE_INFOPLIST_FILE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 18.5;
- MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = bqlpfy.fluxTests;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_EMIT_LOC_STRINGS = NO;
- SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2";
- TEST_HOST = "$(BUILT_PRODUCTS_DIR)/flux.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/flux";
- };
- name = Debug;
- };
- 296FEBA22E56AAF900DC77A3 /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- BUNDLE_LOADER = "$(TEST_HOST)";
- CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
- GENERATE_INFOPLIST_FILE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 18.5;
- MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = bqlpfy.fluxTests;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_EMIT_LOC_STRINGS = NO;
- SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2";
- TEST_HOST = "$(BUILT_PRODUCTS_DIR)/flux.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/flux";
- };
- name = Release;
- };
- 296FEBA42E56AAF900DC77A3 /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
- GENERATE_INFOPLIST_FILE = YES;
- MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = bqlpfy.fluxUITests;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_EMIT_LOC_STRINGS = NO;
- SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2";
- TEST_TARGET_NAME = flux;
- };
- name = Debug;
- };
- 296FEBA52E56AAF900DC77A3 /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
- GENERATE_INFOPLIST_FILE = YES;
- MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = bqlpfy.fluxUITests;
- PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_EMIT_LOC_STRINGS = NO;
- SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2";
- TEST_TARGET_NAME = flux;
- };
- name = Release;
- };
-/* End XCBuildConfiguration section */
-
-/* Begin XCConfigurationList section */
- 296FEB772E56AAF700DC77A3 /* Build configuration list for PBXProject "flux" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 296FEB9B2E56AAF900DC77A3 /* Debug */,
- 296FEB9C2E56AAF900DC77A3 /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
- 296FEB9D2E56AAF900DC77A3 /* Build configuration list for PBXNativeTarget "flux" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 296FEB9E2E56AAF900DC77A3 /* Debug */,
- 296FEB9F2E56AAF900DC77A3 /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
- 296FEBA02E56AAF900DC77A3 /* Build configuration list for PBXNativeTarget "fluxTests" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 296FEBA12E56AAF900DC77A3 /* Debug */,
- 296FEBA22E56AAF900DC77A3 /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
- 296FEBA32E56AAF900DC77A3 /* Build configuration list for PBXNativeTarget "fluxUITests" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- 296FEBA42E56AAF900DC77A3 /* Debug */,
- 296FEBA52E56AAF900DC77A3 /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
-/* End XCConfigurationList section */
- };
- rootObject = 296FEB742E56AAF700DC77A3 /* Project object */;
-}
diff --git a/ios-app/flux.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios-app/flux.xcodeproj/project.xcworkspace/contents.xcworkspacedata
deleted file mode 100644
index 919434a..0000000
--- a/ios-app/flux.xcodeproj/project.xcworkspace/contents.xcworkspacedata
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
diff --git a/ios-app/flux.xcodeproj/project.xcworkspace/xcuserdata/helei.xcuserdatad/Bookmarks/bookmarks.plist b/ios-app/flux.xcodeproj/project.xcworkspace/xcuserdata/helei.xcuserdatad/Bookmarks/bookmarks.plist
deleted file mode 100644
index 782cd07..0000000
--- a/ios-app/flux.xcodeproj/project.xcworkspace/xcuserdata/helei.xcuserdatad/Bookmarks/bookmarks.plist
+++ /dev/null
@@ -1,217 +0,0 @@
-
-
-
-
- top-level-items
-
-
- children
-
-
- children
-
-
- destination
-
- rebasable-url
-
- base
- workspace
- payload
-
- relative-path
- flux/dist/assets/bg-Ck8MrDZ5.jpg
-
-
- type
- DVTDocumentLocation
-
- type
- bookmark
-
-
- destination
-
- rebasable-url
-
- base
- workspace
- payload
-
- relative-path
- flux/dist/assets/favicon-DI5FVxIz.ico
-
-
- type
- DVTDocumentLocation
-
- type
- bookmark
-
-
- destination
-
- rebasable-url
-
- base
- workspace
- payload
-
- relative-path
- flux/dist/assets/index-Bf4rZ0gS.css
-
-
- type
- DVTDocumentLocation
-
- type
- bookmark
-
-
- destination
-
- rebasable-url
-
- base
- workspace
- payload
-
- relative-path
- flux/dist/assets/index-ByTRlFFk.js
-
-
- type
- DVTDocumentLocation
-
- type
- bookmark
-
-
- destination
-
- rebasable-url
-
- base
- workspace
- payload
-
- relative-path
- flux/dist/assets/index-Drx943H4.js
-
-
- type
- DVTDocumentLocation
-
- type
- bookmark
-
-
- destination
-
- rebasable-url
-
- base
- workspace
- payload
-
- relative-path
- flux/dist/assets/index-legacy-BYgRAl5I.js
-
-
- type
- DVTDocumentLocation
-
- type
- bookmark
-
-
- destination
-
- rebasable-url
-
- base
- workspace
- payload
-
- relative-path
- flux/dist/assets/index-legacy-DUPUAMxs.js
-
-
- type
- DVTDocumentLocation
-
- type
- bookmark
-
-
- destination
-
- rebasable-url
-
- base
- workspace
- payload
-
- relative-path
- flux/dist/assets/polyfills-legacy-DIE4kOxF.js
-
-
- type
- DVTDocumentLocation
-
- type
- bookmark
-
-
- title
- assets
- type
- group
-
-
- destination
-
- rebasable-url
-
- base
- workspace
- payload
-
- relative-path
- flux/dist/index.html
-
-
- type
- DVTDocumentLocation
-
- type
- bookmark
-
-
- destination
-
- rebasable-url
-
- base
- workspace
- payload
-
- relative-path
- flux/dist/vite.svg
-
-
- type
- DVTDocumentLocation
-
- type
- bookmark
-
-
- title
- dist
- type
- group
-
-
-
-
diff --git a/ios-app/flux.xcodeproj/project.xcworkspace/xcuserdata/helei.xcuserdatad/UserInterfaceState.xcuserstate b/ios-app/flux.xcodeproj/project.xcworkspace/xcuserdata/helei.xcuserdatad/UserInterfaceState.xcuserstate
deleted file mode 100644
index f3de742..0000000
Binary files a/ios-app/flux.xcodeproj/project.xcworkspace/xcuserdata/helei.xcuserdatad/UserInterfaceState.xcuserstate and /dev/null differ
diff --git a/ios-app/flux.xcodeproj/xcuserdata/helei.xcuserdatad/xcschemes/xcschememanagement.plist b/ios-app/flux.xcodeproj/xcuserdata/helei.xcuserdatad/xcschemes/xcschememanagement.plist
deleted file mode 100644
index 95cbac0..0000000
--- a/ios-app/flux.xcodeproj/xcuserdata/helei.xcuserdatad/xcschemes/xcschememanagement.plist
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
- SchemeUserState
-
- flux.xcscheme_^#shared#^_
-
- orderHint
- 0
-
-
-
-
diff --git a/ios-app/flux/Assets.xcassets/AccentColor.colorset/Contents.json b/ios-app/flux/Assets.xcassets/AccentColor.colorset/Contents.json
deleted file mode 100644
index eb87897..0000000
--- a/ios-app/flux/Assets.xcassets/AccentColor.colorset/Contents.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "colors" : [
- {
- "idiom" : "universal"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- }
-}
diff --git a/ios-app/flux/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios-app/flux/Assets.xcassets/AppIcon.appiconset/Contents.json
deleted file mode 100644
index 2305880..0000000
--- a/ios-app/flux/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ /dev/null
@@ -1,35 +0,0 @@
-{
- "images" : [
- {
- "idiom" : "universal",
- "platform" : "ios",
- "size" : "1024x1024"
- },
- {
- "appearances" : [
- {
- "appearance" : "luminosity",
- "value" : "dark"
- }
- ],
- "idiom" : "universal",
- "platform" : "ios",
- "size" : "1024x1024"
- },
- {
- "appearances" : [
- {
- "appearance" : "luminosity",
- "value" : "tinted"
- }
- ],
- "idiom" : "universal",
- "platform" : "ios",
- "size" : "1024x1024"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- }
-}
diff --git a/ios-app/flux/Assets.xcassets/Contents.json b/ios-app/flux/Assets.xcassets/Contents.json
deleted file mode 100644
index 73c0059..0000000
--- a/ios-app/flux/Assets.xcassets/Contents.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "info" : {
- "author" : "xcode",
- "version" : 1
- }
-}
diff --git a/ios-app/flux/ContentView.swift b/ios-app/flux/ContentView.swift
deleted file mode 100644
index 3c0b074..0000000
--- a/ios-app/flux/ContentView.swift
+++ /dev/null
@@ -1,220 +0,0 @@
-import SwiftUI
-import WebKit
-
-
-struct WebView: UIViewRepresentable {
- let fileName: String?
- let urlString: String?
-
- func makeCoordinator() -> Coordinator {
- Coordinator()
- }
-
- func makeUIView(context: Context) -> WKWebView {
- let config = WKWebViewConfiguration()
- let userContentController = WKUserContentController()
-
- // 注册 JS -> Native 消息通道
- userContentController.add(context.coordinator, name: "getPanelAddresses")
- userContentController.add(context.coordinator, name: "savePanelAddress")
- userContentController.add(context.coordinator, name: "setCurrentPanelAddress")
- userContentController.add(context.coordinator, name: "deletePanelAddress")
-
- // 注入 CSS 隐藏滚动条
- let css = "body::-webkit-scrollbar { display: none; }"
- let cssJS = """
- var style = document.createElement('style');
- style.innerHTML = '\(css)';
- document.head.appendChild(style);
- """
- let cssScript = WKUserScript(source: cssJS, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
- userContentController.addUserScript(cssScript)
-
- config.userContentController = userContentController
-
- let webView = WKWebView(frame: .zero, configuration: config)
- context.coordinator.webView = webView
-
- // 去掉滚动条和弹性
- webView.scrollView.showsHorizontalScrollIndicator = false
- webView.scrollView.showsVerticalScrollIndicator = false
- webView.scrollView.bounces = false
-
- // 加载本地 HTML 或远程 URL
- if let fileName = fileName,
- let htmlURL = Bundle.main.url(forResource: fileName, withExtension: "html") {
- webView.loadFileURL(htmlURL, allowingReadAccessTo: htmlURL.deletingLastPathComponent())
- } else if let urlString = urlString,
- let url = URL(string: urlString) {
- webView.load(URLRequest(url: url))
- }
-
- return webView
- }
-
- func updateUIView(_ uiView: WKWebView, context: Context) {}
-
- class Coordinator: NSObject, WKScriptMessageHandler {
- weak var webView: WKWebView?
- private let storageKey = "panel_addresses"
-
-
- func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
- switch message.name {
- case "getPanelAddresses":
- handleGetPanelAddresses(message: message)
- case "savePanelAddress":
- handleSavePanelAddress(message: message)
- case "setCurrentPanelAddress":
- handleSetCurrentPanelAddress(message: message)
- case "deletePanelAddress":
- handleDeletePanelAddress(message: message)
- default:
- break
- }
- }
-
-
- private func handleGetPanelAddresses(message: WKScriptMessage) {
- let callbackName: String
- if let cb = message.body as? String, !cb.isEmpty {
- callbackName = cb
- } else {
- callbackName = "setPanelAddresses"
- }
-
- let addresses = loadAddressesJSONString()
- let js = "window.\(callbackName)(\(addresses));"
- evaluate(js: js)
- }
-
- private func handleSavePanelAddress(message: WKScriptMessage) {
- var name: String?
- var address: String?
-
- if let dict = message.body as? [String: Any] {
- name = dict["name"] as? String
- address = dict["address"] as? String
- } else if let arr = message.body as? [Any], arr.count >= 2 {
- name = arr[0] as? String
- address = arr[1] as? String
- } else if let jsonString = message.body as? String,
- let data = jsonString.data(using: .utf8),
- let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
- name = dict["name"] as? String
- address = dict["address"] as? String
- }
-
- guard let panelName = name, let panelAddress = address, !panelName.isEmpty, !panelAddress.isEmpty else {
- return
- }
-
- var array = loadAddressesArray()
- let newItem: [String: Any] = [
- "name": panelName,
- "address": panelAddress,
- "inx": false
- ]
- array.append(newItem)
- saveAddressesArray(array)
-
- let js = "window.setPanelAddresses(\(toJSONString(array)));"
- evaluate(js: js)
- }
-
- private func handleSetCurrentPanelAddress(message: WKScriptMessage) {
- var name: String?
- if let str = message.body as? String {
- name = str
- } else if let dict = message.body as? [String: Any] {
- name = dict["name"] as? String
- }
-
- guard let targetName = name else { return }
-
- var array = loadAddressesArray()
- for i in 0.. String {
- let defaults = UserDefaults.standard
- if let s = defaults.string(forKey: storageKey), !s.isEmpty {
- return s
- }
- return "[]"
- }
-
- private func loadAddressesArray() -> [[String: Any]] {
- let s = loadAddressesJSONString()
- guard let data = s.data(using: .utf8),
- let json = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]] else {
- return []
- }
- return json
- }
-
- private func saveAddressesArray(_ array: [[String: Any]]) {
- let jsonString = toJSONString(array)
- let defaults = UserDefaults.standard
- defaults.set(jsonString, forKey: storageKey)
- }
-
- private func toJSONString(_ array: [[String: Any]]) -> String {
- if let data = try? JSONSerialization.data(withJSONObject: array, options: []),
- let s = String(data: data, encoding: .utf8) {
- return s
- }
- return "[]"
- }
-
- private func evaluate(js: String) {
- DispatchQueue.main.async { [weak self] in
- self?.webView?.evaluateJavaScript(js, completionHandler: nil)
- }
- }
- }
-}
-
-
-
-struct ContentView: View {
- var body: some View {
- //WebView(fileName: nil, urlString: "http://192.168.100.9:3000")
- WebView(fileName: "index", urlString: nil)
- }
-}
-
-
-#Preview {
- ContentView()
-}
diff --git a/ios-app/flux/fluxApp.swift b/ios-app/flux/fluxApp.swift
deleted file mode 100644
index 9aaf9e9..0000000
--- a/ios-app/flux/fluxApp.swift
+++ /dev/null
@@ -1,17 +0,0 @@
-//
-// fluxApp.swift
-// flux
-//
-// Created by 何磊 on 21/08/2025.
-//
-
-import SwiftUI
-
-@main
-struct fluxApp: App {
- var body: some Scene {
- WindowGroup {
- ContentView()
- }
- }
-}
diff --git a/ios-app/fluxTests/fluxTests.swift b/ios-app/fluxTests/fluxTests.swift
deleted file mode 100644
index 14d1a5e..0000000
--- a/ios-app/fluxTests/fluxTests.swift
+++ /dev/null
@@ -1,17 +0,0 @@
-//
-// fluxTests.swift
-// fluxTests
-//
-// Created by 何磊 on 21/08/2025.
-//
-
-import Testing
-@testable import flux
-
-struct fluxTests {
-
- @Test func example() async throws {
- // Write your test here and use APIs like `#expect(...)` to check expected conditions.
- }
-
-}
diff --git a/ios-app/fluxUITests/fluxUITests.swift b/ios-app/fluxUITests/fluxUITests.swift
deleted file mode 100644
index ae643f3..0000000
--- a/ios-app/fluxUITests/fluxUITests.swift
+++ /dev/null
@@ -1,41 +0,0 @@
-//
-// fluxUITests.swift
-// fluxUITests
-//
-// Created by 何磊 on 21/08/2025.
-//
-
-import XCTest
-
-final class fluxUITests: XCTestCase {
-
- override func setUpWithError() throws {
- // Put setup code here. This method is called before the invocation of each test method in the class.
-
- // In UI tests it is usually best to stop immediately when a failure occurs.
- continueAfterFailure = false
-
- // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
- }
-
- override func tearDownWithError() throws {
- // Put teardown code here. This method is called after the invocation of each test method in the class.
- }
-
- @MainActor
- func testExample() throws {
- // UI tests must launch the application that they test.
- let app = XCUIApplication()
- app.launch()
-
- // Use XCTAssert and related functions to verify your tests produce the correct results.
- }
-
- @MainActor
- func testLaunchPerformance() throws {
- // This measures how long it takes to launch your application.
- measure(metrics: [XCTApplicationLaunchMetric()]) {
- XCUIApplication().launch()
- }
- }
-}
diff --git a/ios-app/fluxUITests/fluxUITestsLaunchTests.swift b/ios-app/fluxUITests/fluxUITestsLaunchTests.swift
deleted file mode 100644
index c6874a4..0000000
--- a/ios-app/fluxUITests/fluxUITestsLaunchTests.swift
+++ /dev/null
@@ -1,33 +0,0 @@
-//
-// fluxUITestsLaunchTests.swift
-// fluxUITests
-//
-// Created by 何磊 on 21/08/2025.
-//
-
-import XCTest
-
-final class fluxUITestsLaunchTests: XCTestCase {
-
- override class var runsForEachTargetApplicationUIConfiguration: Bool {
- true
- }
-
- override func setUpWithError() throws {
- continueAfterFailure = false
- }
-
- @MainActor
- func testLaunch() throws {
- let app = XCUIApplication()
- app.launch()
-
- // Insert steps here to perform after app launch but before taking a screenshot,
- // such as logging into a test account or navigating somewhere in the app
-
- let attachment = XCTAttachment(screenshot: app.screenshot())
- attachment.name = "Launch Screen"
- attachment.lifetime = .keepAlways
- add(attachment)
- }
-}