diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.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/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..bf5ee8c
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+flomo-ai
\ No newline at end of file
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..7643783
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..79ee123
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..b589d56
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
new file mode 100644
index 0000000..b268ef3
--- /dev/null
+++ b/.idea/deploymentTargetSelector.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..0897082
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..6806f5a
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
new file mode 100644
index 0000000..0e1c4ec
--- /dev/null
+++ b/.idea/kotlinc.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/migrations.xml b/.idea/migrations.xml
new file mode 100644
index 0000000..f8051a6
--- /dev/null
+++ b/.idea/migrations.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..8978d23
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/templateLanguages.xml b/.idea/templateLanguages.xml
new file mode 100644
index 0000000..8a787d3
--- /dev/null
+++ b/.idea/templateLanguages.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apkkey.jks b/apkkey.jks
new file mode 100644
index 0000000..277ea04
Binary files /dev/null and b/apkkey.jks differ
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
new file mode 100644
index 0000000..1b6a7e8
--- /dev/null
+++ b/app/build.gradle.kts
@@ -0,0 +1,80 @@
+plugins {
+ alias(libs.plugins.android.application)
+ alias(libs.plugins.kotlin.android)
+}
+
+android {
+ namespace = "com.example.flomo_ai"
+ compileSdk = 34
+
+ defaultConfig {
+ applicationId = "com.example.flomo_ai"
+ minSdk = 28
+ targetSdk = 34
+ versionCode = 1
+ versionName = "1.0"
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ vectorDrawables {
+ useSupportLibrary = true
+ }
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = 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"
+ }
+ buildFeatures {
+ compose = true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion = "1.5.1"
+ }
+ packaging {
+ resources {
+ excludes += "/META-INF/{AL2.0,LGPL2.1}"
+ }
+ }
+ buildToolsVersion = "34.0.0"
+}
+
+dependencies {
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.androidx.lifecycle.runtime.ktx)
+ implementation(libs.androidx.activity.compose)
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.androidx.ui)
+ implementation(libs.androidx.ui.graphics)
+ implementation(libs.androidx.ui.tooling.preview)
+ implementation(libs.androidx.material3)
+ implementation(libs.appcompat)
+ implementation(libs.material)
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.junit)
+ androidTestImplementation(libs.androidx.espresso.core)
+ androidTestImplementation(platform(libs.androidx.compose.bom))
+ androidTestImplementation(libs.androidx.ui.test.junit4)
+ debugImplementation(libs.androidx.ui.tooling)
+ debugImplementation(libs.androidx.ui.test.manifest)
+ implementation("com.google.code.gson:gson:2.10.1")
+ implementation("io.github.nefilim.kjwt:kjwt-jwks:0.9.0")
+ implementation("com.nimbusds:nimbus-jose-jwt:9.40")
+ implementation("com.squareup.okhttp3:okhttp:4.12.0")
+ implementation("com.squareup.okhttp3:logging-interceptor:4.9.3")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0")
+ implementation("com.squareup.moshi:moshi-kotlin:1.12.0")
+
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# 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/app/release/app-release.apk b/app/release/app-release.apk
new file mode 100644
index 0000000..90d0a09
Binary files /dev/null and b/app/release/app-release.apk differ
diff --git a/app/release/baselineProfiles/0/app-release.dm b/app/release/baselineProfiles/0/app-release.dm
new file mode 100644
index 0000000..7caf241
Binary files /dev/null and b/app/release/baselineProfiles/0/app-release.dm differ
diff --git a/app/release/baselineProfiles/1/app-release.dm b/app/release/baselineProfiles/1/app-release.dm
new file mode 100644
index 0000000..780585f
Binary files /dev/null and b/app/release/baselineProfiles/1/app-release.dm differ
diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json
new file mode 100644
index 0000000..e9d71d6
--- /dev/null
+++ b/app/release/output-metadata.json
@@ -0,0 +1,37 @@
+{
+ "version": 3,
+ "artifactType": {
+ "type": "APK",
+ "kind": "Directory"
+ },
+ "applicationId": "com.example.flomo_ai",
+ "variantName": "release",
+ "elements": [
+ {
+ "type": "SINGLE",
+ "filters": [],
+ "attributes": [],
+ "versionCode": 1,
+ "versionName": "1.0",
+ "outputFile": "app-release.apk"
+ }
+ ],
+ "elementType": "File",
+ "baselineProfiles": [
+ {
+ "minApi": 28,
+ "maxApi": 30,
+ "baselineProfiles": [
+ "baselineProfiles/1/app-release.dm"
+ ]
+ },
+ {
+ "minApi": 31,
+ "maxApi": 2147483647,
+ "baselineProfiles": [
+ "baselineProfiles/0/app-release.dm"
+ ]
+ }
+ ],
+ "minSdkVersionForDexing": 28
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/example/flomo_ai/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/example/flomo_ai/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..5cb7863
--- /dev/null
+++ b/app/src/androidTest/java/com/example/flomo_ai/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.example.flomo_ai
+
+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.example.flomo_ai", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..ba43fe8
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/example/flomo_ai/MainActivity.kt b/app/src/main/java/com/example/flomo_ai/MainActivity.kt
new file mode 100644
index 0000000..e1180d7
--- /dev/null
+++ b/app/src/main/java/com/example/flomo_ai/MainActivity.kt
@@ -0,0 +1,288 @@
+package com.example.flomo_ai
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.Gravity
+import android.widget.Button
+import android.widget.EditText
+import android.widget.TextView
+
+import androidx.appcompat.app.AppCompatActivity
+import com.google.android.material.tabs.TabLayout
+import com.google.gson.Gson
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+import okhttp3.Interceptor
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.Response
+import okhttp3.RequestBody.Companion.toRequestBody
+import com.google.gson.JsonObject
+import okhttp3.MediaType.Companion.toMediaType
+
+import com.google.gson.reflect.TypeToken
+import android.util.Log
+import java.io.IOException
+import java.net.UnknownHostException
+import org.json.JSONObject
+
+import com.squareup.moshi.Moshi
+import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
+
+// 定义请求体数据类
+data class ChatRequest(
+ val model: String,
+ val messages: List
+)
+
+data class Message(
+ val role: String,
+ val content: String
+)
+
+
+// 从返回的 JSON 响应中提取标签列表
+fun extractLabels(responseBody: String): List? {
+ try {
+ // 假设 responseBody 中包含一个完整的 JSON 对象,我们需要先提取出其中的 content 部分
+ val fullJsonObject = Gson().fromJson(responseBody, JsonObject::class.java)
+ val choicesArray = fullJsonObject.getAsJsonArray("choices")
+ if (choicesArray.size() > 0) {
+ val firstChoice = choicesArray.get(0).asJsonObject
+ val messageObject = firstChoice.get("message").asJsonObject
+ val content = messageObject.get("content").asString
+ // 从 content 中提取出 labels
+ val startIndex = content.indexOf("\"labels\": [") + "\"labels\": [".length
+ val endIndex = content.indexOf("]", startIndex)
+ val labelsStr = content.substring(startIndex, endIndex)
+ // 处理引号
+ //val processedLabelsStr = labelsStr.replace("\"", "")
+ val labels = labelsStr.split("\", \"")
+ val processedLabels = mutableListOf()
+ for (label in labels) {
+ // 假设 label 是原始字符串
+ var processedLabel = label
+ // 去掉单引号和双引号
+ if (label.contains("'")) {
+ processedLabel = processedLabel.replace("'", "")
+ }
+ if (label.contains("\"")) {
+ processedLabel = processedLabel.replace("\"", "")
+ }
+ // 去掉所有空格
+ processedLabel = processedLabel.replace(" ", "")
+ processedLabels.add(processedLabel)
+ }
+ return processedLabels
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ Log.e("ExtractLabels", "Error during extraction: ${e.message}")
+ }
+ return null
+}
+
+class MainActivity : AppCompatActivity() {
+
+ private lateinit var inputEditText: EditText
+ private lateinit var configButton: Button
+ private lateinit var submitToAIButton: Button
+ private lateinit var tabLayout: TabLayout
+ private lateinit var submitToServerButton: Button
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+
+ inputEditText = findViewById(R.id.inputEditText)
+ inputEditText.gravity = Gravity.START or Gravity.TOP
+
+ configButton = findViewById(R.id.configButton)
+
+ submitToAIButton = findViewById(R.id.submitToAIButton)
+
+ submitToAIButton.setOnClickListener {
+ // 创建 OkHttpClient,点击智谱AI分析返回标签
+ val client = OkHttpClient.Builder()
+ .addInterceptor(object : Interceptor {
+ override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
+ val originalRequest = chain.request()
+ val newRequest = originalRequest.newBuilder().build()
+ return chain.proceed(newRequest)
+ }
+ })
+ .build()
+ // 假设这是一个 EditText 元素
+ val inputEditText = findViewById(R.id.inputEditText)
+
+ // 获取 EditText 中的文本内容,创建request的body
+ val textFromEditText = inputEditText.text.toString()
+ val combinedText =
+ "$textFromEditText。请为以上文章分析并给出 4 个最合理的标签,没有其他内容。以 JSON 格式输出,格式为 labels: [标签 1, 标签 2, 标签 3, 标签 4]"
+
+ val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
+ val jsonAdapter = moshi.adapter(ChatRequest::class.java)
+
+ val requestBody = ChatRequest(
+ model = "glm-4-flash",
+ messages = listOf(Message(role = "user", content = combinedText))
+ )
+
+ val requestBodyJson = jsonAdapter.toJson(requestBody)
+ val mediaType = "application/json; charset=utf-8".toMediaType()
+ val body = requestBodyJson.toRequestBody(mediaType)
+
+ // 从配置中读取 api_key
+ val sharedPrefs = getSharedPreferences("APIConfigs", MODE_PRIVATE)
+ val allConfigsJson = sharedPrefs.getString("configs", null)
+ var api_key = ""
+ var api_url = ""
+ if (allConfigsJson != null) {
+ val type = object : TypeToken>() {}.type
+ val allConfigs = Gson().fromJson>(allConfigsJson, type)
+ val zhipuConfig = allConfigs.find { it.name == "zhipu" }
+
+ if (zhipuConfig != null) {
+ api_key = zhipuConfig.key
+ api_url = zhipuConfig.url
+ }
+ }
+
+ // 创建请求
+ val request = Request.Builder()
+ .url("$api_url")
+ .post(body)
+ .header("Authorization", "Bearer $api_key")
+ .header("Content-Type", "application/json")
+ .build()
+
+ // 使用协程在后台线程中发送请求
+ CoroutineScope(Dispatchers.Main).launch {
+ try {
+ // 模拟可能出现异常的网络操作,这里需要替换为你的实际网络请求相关代码
+ // 比如使用 OkHttp 或者其他网络库进行请求
+ val response = withContext(Dispatchers.IO) {
+ client.newCall(request).execute()
+ }
+ if (response.isSuccessful) {
+ val responseBody = response.body?.string() // 将响应体转换为字符串
+ responseBody?.let {
+ // 处理响应 JSON 数据
+ print("return message is $responseBody")
+ val labels = extractLabels(responseBody)
+ labels?.let {
+ if (labels != null && labels.size == 4) {
+ for (i in 0 until 4) {
+ val tab = tabLayout.getTabAt(i)
+ if (tab != null) {
+ tab.text = labels[i]
+ tab.view.setOnClickListener {
+ val currentText =
+ findViewById(R.id.inputEditText).text.toString()
+ val buttonText = tab.text.toString()
+ findViewById(R.id.inputEditText).setText("$currentText\n#$buttonText")
+ }
+ }
+ }
+ findViewById(R.id.statusTextView).text = "标签已经获取并更新"
+ }
+ } ?: run {
+ findViewById(R.id.statusTextView).text = "没有更新"
+ }
+ }
+ } else {
+ findViewById(R.id.statusTextView).text = "没有响应,没有更新"
+ }
+ } catch (e: UnknownHostException) {
+ findViewById(R.id.statusTextView).text ="UnknownHostException: ${e.message}"
+ } catch (e: IOException) {
+ findViewById(R.id.statusTextView).text = "IOException: ${e.message}"
+ }
+
+ }
+ }
+
+ tabLayout = findViewById(R.id.tabLayout)
+ submitToServerButton = findViewById(R.id.submitToServerButton)
+
+ configButton.setOnClickListener {
+ val intent = Intent(this, SecondActivity::class.java)
+ startActivity(intent)
+ }
+
+ submitToServerButton = findViewById(R.id.submitToServerButton)
+ inputEditText = findViewById(R.id.inputEditText)
+
+ submitToServerButton.setOnClickListener {
+ val textFromEditText = inputEditText.text.toString()
+ submitToServer(textFromEditText)
+ }
+
+
+ // Setup TabLayout using a loop
+ val tabLayout = findViewById(R.id.tabLayout)
+ // 维持原来的创建标签按钮的代码
+ (1..4).forEach { tabIndex ->
+ val tabButton = tabLayout.newTab().apply {
+ text = "标签示例$tabIndex"
+ tabLayout.addTab(this)
+ }
+
+ }
+
+ }
+
+ private fun submitToServer(content: String) {
+ val statusTextView = findViewById(R.id.statusTextView)
+
+ CoroutineScope(Dispatchers.Main).launch {
+ statusTextView.text = "提交到flomo服务器..."
+
+ val result = withContext(Dispatchers.IO) {
+ postDataToServer(content)
+ }
+ when (result) {
+ is Result.Success -> {
+ findViewById(R.id.inputEditText).setText("")
+ statusTextView.text = "提交成功!"
+ }
+ is Result.Error -> {
+ statusTextView.text = "提交失误: ${result.exception.message}"
+ }
+ }
+ }
+ }
+
+ // 提交到笔记服务器,flomo服务器
+ private suspend fun postDataToServer(content: String): Result {
+ return try {
+ val client = OkHttpClient()
+ val mediaType = "application/json".toMediaType()
+ val json = JSONObject().apply {
+ put("content", content)
+ }.toString()
+ val body = json.toRequestBody(mediaType)
+ val request = Request.Builder()
+ .url("https://flomoapp.com/iwh/MTY5NTQy/b671d4930ecd1eae63e50cc0cb8ca4ae/")
+ .post(body)
+ .build()
+ val response = client.newCall(request).execute()
+ if (response.isSuccessful) {
+ Result.Success(response)
+ } else {
+ Result.Error(Exception("服务器返回错误: ${response.code}"))
+ }
+ } catch (e: Exception) {
+ Result.Error(e)
+ }
+ }
+
+ sealed class Result {
+ data class Success(val response: Response) : Result()
+ data class Error(val exception: Exception) : Result()
+ }
+}
+
diff --git a/app/src/main/java/com/example/flomo_ai/SecondActivity.kt b/app/src/main/java/com/example/flomo_ai/SecondActivity.kt
new file mode 100644
index 0000000..958c431
--- /dev/null
+++ b/app/src/main/java/com/example/flomo_ai/SecondActivity.kt
@@ -0,0 +1,189 @@
+package com.example.flomo_ai
+
+import android.os.Bundle
+import android.widget.Button
+import android.widget.EditText
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+import java.util.UUID
+
+class SecondActivity : AppCompatActivity() {
+ private lateinit var etApiName: EditText
+ private lateinit var etApiUrl: EditText
+ private lateinit var etApiKey: EditText
+ private lateinit var etApiSecretKey: EditText
+ private lateinit var btnSave: Button
+ private lateinit var llConfigList: LinearLayout
+
+ private var configs = mutableListOf()
+ private var editingId: Long? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ setTheme(androidx.appcompat.R.style.Theme_AppCompat)
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_second)
+
+ initViews()
+ loadConfigs()
+ displayConfigs()
+
+ val btnGoBack: Button = findViewById(R.id.btnGoBack)
+ btnGoBack.setOnClickListener {
+ finish()
+ }
+
+ btnSave.setOnClickListener {
+ if (editingId != null) {
+ updateConfig()
+ } else {
+ addConfig()
+ }
+ }
+ }
+
+ private fun initViews() {
+ etApiName = findViewById(R.id.etApiName)
+ etApiUrl = findViewById(R.id.etApiUrl)
+ etApiKey = findViewById(R.id.etApiKey)
+ etApiSecretKey = findViewById(R.id.etApiSecretKey)
+ btnSave = findViewById(R.id.btnSave)
+ llConfigList = findViewById(R.id.llConfigList)
+ }
+
+ private fun loadConfigs() {
+ val sharedPrefs = getSharedPreferences("APIConfigs", MODE_PRIVATE)
+ val json = sharedPrefs.getString("configs", null)
+ if (json != null) {
+ val type = object : TypeToken>() {}.type
+ configs = Gson().fromJson(json, type)
+ }
+ }
+
+ private fun saveConfigs() {
+ val sharedPrefs = getSharedPreferences("APIConfigs", MODE_PRIVATE)
+ val json = Gson().toJson(configs)
+ sharedPrefs.edit().putString("configs", json).apply()
+ }
+
+ private fun addConfig() {
+ val name = etApiName.text.toString()
+ val url = etApiUrl.text.toString()
+ val key = etApiKey.text.toString()
+ val secretKey = etApiSecretKey.text.toString()
+
+ // 生成唯一的 id
+ val id = System.currentTimeMillis()
+ // 创建新的配置项
+ val newConfig = APIConfig(id, name, url, key, secretKey)
+ // 添加配置项
+ configs.add(newConfig)
+ // 保存配置
+ saveConfigs()
+ // 显示配置
+ displayConfigs()
+ // 清空输入框
+ clearInputs()
+ }
+
+ private fun updateConfig() {
+ val name = etApiName.text.toString()
+ val url = etApiUrl.text.toString()
+ val key = etApiKey.text.toString()
+ val secretKey = etApiSecretKey.text.toString()
+
+ // 获取编辑的配置项 id
+ val id = editingId ?: return
+ // 更新配置项
+ val updatedConfig = APIConfig(id, name, url, key, secretKey)
+ val existingConfigIndex = configs.indexOfFirst { it.id == id }
+ if (existingConfigIndex != -1) {
+ configs[existingConfigIndex] = updatedConfig
+ }
+ // 保存配置
+ saveConfigs()
+ // 显示配置
+ displayConfigs()
+ // 清空输入框
+ clearInputs()
+ // 重置编辑状态
+ editingId = null
+
+ }
+
+ private fun displayConfigs() {
+ llConfigList.removeAllViews()
+ for (config in configs) {
+ // 为每个配置项加载对应的布局文件
+ val configView = layoutInflater.inflate(R.layout.item_api_config, null)
+ // 设置各项文本信息
+ // 获取并设置 Name 的 TextView 前景色和背景色
+ val tvName = configView.findViewById(R.id.tvName)
+ tvName.setTextColor(resources.getColor(R.color.background_color))
+ tvName.text = "Name: ${config.name}"
+
+ // 获取并设置 URL 的 TextView 前景色和背景色
+ val tvUrl = configView.findViewById(R.id.tvUrl)
+ tvUrl.setTextColor(resources.getColor(R.color.background_color))
+ tvUrl.text = "URL: ${config.url}"
+
+ // 获取并设置 Key 的 TextView 前景色和背景色
+ val tvKey = configView.findViewById(R.id.tvKey).also {
+ it.setTextColor(resources.getColor(R.color.background_color))
+ }
+ tvKey.text = "Key: ${config.key.take(4)}..."
+
+ // 获取并设置 SecretKey 的 TextView 前景色和背景色
+ val tvSecretKey = configView.findViewById(R.id.tvSecretKey)
+ tvSecretKey.setTextColor(resources.getColor(R.color.background_color))
+ tvSecretKey.text = "Secret Key: ${config.secretKey.take(4)}..."
+
+ // 设置编辑按钮点击事件
+ configView.findViewById