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 @@ + + + + + + + + + + \ 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