Replace configuration page with new settings interface featuring LLM config, custom headers, preset prompts, and custom buttons sections

This commit is contained in:
2026-03-14 20:02:31 +08:00
parent b984bbf52d
commit 9eae35bc7c
5 changed files with 921 additions and 402 deletions

View File

@@ -1,295 +1,433 @@
package com.example.flomo_ai
import android.annotation.SuppressLint
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.graphics.Color
import android.text.method.PasswordTransformationMethod
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.ImageButton
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import androidx.core.content.ContextCompat
import android.content.Context
import android.util.Log
import androidx.core.content.res.ResourcesCompat
import android.app.Activity
import android.content.Intent
import android.net.Uri
import androidx.activity.result.contract.ActivityResultContracts
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import org.json.JSONObject
import java.util.*
// Data classes for the new settings structure
data class HeaderConfig(val key: String, val value: String)
data class PromptConfig(val id: String, val title: String, val content: String, val expanded: Boolean = false)
data class ButtonConfig(val id: String, val label: String, val action: String, val apiUrl: String? = null, val apiMethod: String? = null, val apiBodyTemplate: String? = null, val expanded: Boolean = false)
class SecondActivity : AppCompatActivity() {
private lateinit var etApiName: EditText
private lateinit var etApiUrl: EditText
// View references
private lateinit var etBaseUrl: EditText
private lateinit var etApiKey: EditText
private lateinit var etApiSecretKey: EditText
private lateinit var etApiModel: EditText
private lateinit var btnSave: Button
private lateinit var llConfigList: LinearLayout
private lateinit var btnToggleApiKey: ImageButton
private lateinit var etModel: EditText
private lateinit var llHeadersList: LinearLayout
private lateinit var btnAddHeader: Button
private lateinit var llPromptsList: LinearLayout
private lateinit var btnAddPrompt: Button
private lateinit var tvEmptyPrompts: TextView
private lateinit var llButtonsList: LinearLayout
private lateinit var btnAddButton: Button
private lateinit var tvEmptyButtons: TextView
private var configs = mutableListOf<APIConfig>()
private var editingId: Long? = null
// Data storage
private var headerConfigs = mutableListOf<HeaderConfig>()
private var promptConfigs = mutableListOf<PromptConfig>()
private var buttonConfigs = mutableListOf<ButtonConfig>()
private val pickImageLauncher = registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
if (uri!= null) {
// 处理选中的图片
}
}
private fun setButtonListeners() {
val buttonIds = listOf(
R.id.button_holo_red_light,
R.id.button_holo_green_light,
R.id.button_holo_blue_light,
R.id.button_holo_orange_light
)
buttonIds.forEach { buttonId ->
findViewById<Button>(buttonId).setOnClickListener {
val sharedPrefs = getSharedPreferences("APIConfigs", Context.MODE_PRIVATE)
val editor = sharedPrefs.edit()
// 获取颜色资源 ID
val colorResId = getColorForButtonId(buttonId)
// 使用正则表达式从资源名称中提取颜色值并存储
val colorValue = extractColorValue(buttonId)
editor.putString("buttonColor", colorValue)
editor.apply()
Log.d("SharedPrefsDebug", "Stored color value in SecondActivity: $colorValue")
}
}
}
private fun getColorForButtonId(buttonId: Int): Int {
return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
// API 23 及以上使用 Context.getColor()
baseContext.getColor(findColorResourceId(buttonId))
} else {
// 低版本使用 ResourcesCompat.getColor()
ResourcesCompat.getColor(resources, findColorResourceId(buttonId), null)
}
}
private fun findColorResourceId(buttonId: Int): Int {
return when (buttonId) {
R.id.button_holo_red_light -> android.R.color.holo_red_light
R.id.button_holo_green_light -> android.R.color.holo_green_light
R.id.button_holo_blue_light -> android.R.color.holo_blue_light
R.id.button_holo_orange_light -> android.R.color.holo_orange_light
else -> -1
}
}
private fun extractColorValue(buttonId: Int): String {
val resourceName = resources.getResourceEntryName(buttonId)
val regex = Regex(".*_(.*)_light")
val matchResult = regex.find(resourceName)
return matchResult?.groupValues?.get(1)?: ""
}
// API config (for backward compatibility with existing API calls)
private lateinit var apiConfig: APIConfig
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(androidx.appcompat.R.style.Theme_AppCompat)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
// Initialize views
initViews()
loadConfigs()
displayConfigs()
val btnGoBack: Button = findViewById(R.id.btnGoBack)
btnGoBack.setOnClickListener {
// Load existing configurations
loadConfigurations()
// Setup UI based on loaded data
setupUI()
// Back button functionality
findViewById<Button>(R.id.btnBack).setOnClickListener {
finish()
}
btnSave.setOnClickListener {
if (editingId != null) {
updateConfig()
} else {
addConfig()
}
}
//颜色按钮的监听
setButtonListeners()
// 背景按钮的监听
val buttonChooseImage = findViewById<Button>(R.id.buttonChooseImage)
buttonChooseImage.setOnClickListener {
pickImageLauncher.launch(null)
// Add header button
btnAddHeader.setOnClickListener {
addHeaderEntry()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_PICK_IMAGE && resultCode == RESULT_OK && data!= null) {
val selectedImageUri: Uri? = data.data
if (selectedImageUri!= null) {
// 将选择的图片 URI 传递回 MainActivity
val resultIntent = Intent()
resultIntent.putExtra("selectedImageUri", selectedImageUri.toString())
setResult(RESULT_OK, resultIntent)
finish()
}
// Add prompt button
btnAddPrompt.setOnClickListener {
addPromptEntry()
}
}
companion object {
const val REQUEST_CODE_PICK_IMAGE = 1000
// Add button
btnAddButton.setOnClickListener {
addButtonEntry()
}
}
private fun initViews() {
etApiName = findViewById(R.id.etApiName)
etApiUrl = findViewById(R.id.etApiUrl)
etBaseUrl = findViewById(R.id.etBaseUrl)
etApiKey = findViewById(R.id.etApiKey)
etApiSecretKey = findViewById(R.id.etApiSecretKey)
etApiModel = findViewById(R.id.etApiModel)
btnSave = findViewById(R.id.btnSave)
llConfigList = findViewById(R.id.llConfigList)
btnToggleApiKey = findViewById(R.id.btnToggleApiKey)
etModel = findViewById(R.id.etModel)
llHeadersList = findViewById(R.id.llHeadersList)
btnAddHeader = findViewById(R.id.btnAddHeader)
llPromptsList = findViewById(R.id.llPromptsList)
btnAddPrompt = findViewById(R.id.btnAddPrompt)
tvEmptyPrompts = findViewById(R.id.tvEmptyPrompts)
llButtonsList = findViewById(R.id.llButtonsList)
btnAddButton = findViewById(R.id.btnAddButton)
tvEmptyButtons = findViewById(R.id.tvEmptyButtons)
// Setup API key toggle
btnToggleApiKey.setOnClickListener {
val isPassword = etApiKey.transformationMethod is PasswordTransformationMethod
etApiKey.transformationMethod = if (isPassword) null else PasswordTransformationMethod()
// Move cursor to end
etApiKey.setSelection(etApiKey.text.length)
// Toggle icon
val iconRes = if (isPassword)
android.R.drawable.ic_lock_idle_lock else
android.R.drawable.ic_lock_idle_unlocked
btnToggleApiKey.setImageResource(iconRes)
}
}
private fun loadConfigs() {
// 获取一个名为 "APIConfigs" 的共享偏好设置
val sharedPrefs = getSharedPreferences("APIConfigs", MODE_PRIVATE)
private fun loadConfigurations() {
// Load shared preferences
val sharedPrefs = getSharedPreferences("APIConfigs", Context.MODE_PRIVATE)
val json = sharedPrefs.getString("configs", null)
if (json != null) {
// 创建一个 TypeToken 的实例,用于表示一个包含 APIConfig 对象的列表类型
val type = object : TypeToken<List<APIConfig>>() {}.type
configs = Gson().fromJson(json, type)
try {
// Try to load as new format first
val settings = Gson().fromJson(json, SettingsData::class.java)
headerConfigs = settings.headerConfigs?.toMutableList() ?: mutableListOf()
promptConfigs = settings.promptConfigs?.toMutableList() ?: mutableListOf()
buttonConfigs = settings.buttonConfigs?.toMutableList() ?: mutableListOf()
// Load LLM config
etBaseUrl.setText(settings.llmConfig?.baseUrl ?: "https://api.openai.com/v1")
etApiKey.setText(settings.llmConfig?.apiKey ?: "")
etModel.setText(settings.llmConfig?.model ?: "gpt-4o")
// Update API key visibility based on whether it has text
updateApiKeyVisibility()
} catch (e: Exception) {
// If new format fails, try to load old format for migration
try {
val type = object : TypeToken<List<APIConfig>>() {}.type
val oldConfigs = Gson().fromJson<List<APIConfig>>(json, type)
if (oldConfigs.isNotEmpty()) {
val oldConfig = oldConfigs[0]
etBaseUrl.setText(oldConfig.url)
etApiKey.setText(oldConfig.key)
etModel.setText(oldConfig.model)
updateApiKeyVisibility()
}
} catch (e2: Exception) {
// If both fail, use defaults
etBaseUrl.setText("https://api.openai.com/v1")
etModel.setText("gpt-4o")
updateApiKeyVisibility()
}
}
} else {
// No saved config, use defaults
etBaseUrl.setText("https://api.openai.com/v1")
etModel.setText("gpt-4o")
updateApiKeyVisibility()
}
}
private fun saveConfigs() {
val sharedPrefs = getSharedPreferences("APIConfigs", MODE_PRIVATE)
val json = Gson().toJson(configs)
private fun updateApiKeyVisibility() {
val isEmpty = etApiKey.text.toString().isEmpty()
etApiKey.transformationMethod = if (isEmpty) null else PasswordTransformationMethod()
val iconRes = if (isEmpty || etApiKey.transformationMethod == null)
android.R.drawable.ic_lock_idle_lock else
android.R.drawable.ic_lock_idle_unlocked
btnToggleApiKey.setImageResource(iconRes)
// Keep cursor at end
etApiKey.setSelection(etApiKey.text.length)
}
private fun setupUI() {
// Setup headers
llHeadersList.removeAllViews()
if (headerConfigs.isEmpty()) {
addHeaderEntry() // Add one empty entry by default
} else {
for (header in headerConfigs) {
addHeaderEntry(header.key, header.value)
}
}
// Setup prompts
llPromptsList.removeAllViews()
if (promptConfigs.isEmpty()) {
// Add default prompts from JSON
promptConfigs.add(PromptConfig("default-1", "翻译助手", "你是一个专业翻译,请将用户输入的内容翻译成中文,保持原意,语言自然流畅。"))
promptConfigs.add(PromptConfig("default-2", "代码解释", "你是一个资深程序员,请详细解释用户提供的代码,用中文说明其功能和逻辑。"))
}
for (prompt in promptConfigs) {
addPromptEntry(prompt)
}
updateEmptyStates()
// Setup buttons
llButtonsList.removeAllViews()
if (buttonConfigs.isEmpty()) {
// Add default buttons from JSON
buttonConfigs.add(ButtonConfig("btn-copy", "复制结果", "copy"))
buttonConfigs.add(ButtonConfig(
"btn-webhook",
"发送到飞书",
"api",
"https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxx",
"POST",
"{\"msg_type\": \"text\", \"content\": {\"text\": \"{output}\"}}"
))
}
for (button in buttonConfigs) {
addButtonEntry(button)
}
updateEmptyStates()
}
private fun addHeaderEntry(key: String = "", value: String = "") {
val view = layoutInflater.inflate(R.layout.header_entry, null)
val etKey = view.findViewById<EditText>(R.id.etHeaderKey)
val etValue = view.findViewById<EditText>(R.id.etHeaderValue)
val btnRemove = view.findViewById<ImageButton>(R.id.btnRemoveHeader)
etKey.setText(key)
etValue.setText(value)
btnRemove.setOnClickListener {
llHeadersList.removeView(view)
updateEmptyStates()
}
llHeadersList.addView(view)
updateEmptyStates()
}
private fun addPromptEntry(config: PromptConfig = PromptConfig("", "", "")) {
val view = layoutInflater.inflate(R.layout.prompt_entry, null)
val etTitle = view.findViewById<EditText>(R.id.etPromptTitle)
val etContent = view.findViewById<EditText>(R.id.etPromptContent)
val btnExpand = view.findViewById<Button>(R.id.btnExpandPrompt)
val btnRemove = view.findViewById<ImageButton>(R.id.btnRemovePrompt)
val vDivider = view.findViewById<View>(R.id.viewDivider)
etTitle.setText(config.title)
etContent.setText(config.content)
// Set expanded state (we'd need to store this in the view tag or similar)
btnRemove.setOnClickListener {
llPromptsList.removeView(view)
updateEmptyStates()
}
// For simplicity, we're not implementing expand/collapse here
// but in a full implementation we would toggle the content visibility
llPromptsList.addView(view)
updateEmptyStates()
}
private fun addButtonEntry(config: ButtonConfig = ButtonConfig("", "", "")) {
val view = layoutInflater.inflate(R.layout.button_entry, null)
val etLabel = view.findViewById<EditText>(R.id.etButtonLabel)
val btnAction = view.findViewById<Button>(R.id.btnButtonAction)
val btnRemove = view.findViewById<ImageButton>(R.id.btnRemoveButton)
val llApiFields = view.findViewById<LinearLayout>(R.id.llApiFields)
etLabel.setText(config.label)
btnAction.text = when (config.action) {
"copy" -> "复制输出内容"
"api" -> "提交到第三方 API"
else -> "未知操作"
}
// Show/hide API fields based on action
llApiFields.visibility = if (config.action == "api") View.VISIBLE else View.GONE
btnAction.setOnClickListener {
// Cycle through action options
val newAction = when (btnAction.text.toString()) {
"复制输出内容" -> "提交到第三方 API"
"提交到第三方 API" -> "未知操作"
else -> "复制输出内容"
}
btnAction.text = when (newAction) {
"复制输出内容" -> "复制输出内容"
"提交到第三方 API" -> "提交到第三方 API"
else -> "未知操作"
}
llApiFields.visibility = if (newAction == "提交到第三方 API") View.VISIBLE else View.GONE
}
// If we have existing API config, populate fields
if (config.action == "api") {
// In a full implementation, we would populate the API fields here
}
btnRemove.setOnClickListener {
llButtonsList.removeView(view)
updateEmptyStates()
}
llButtonsList.addView(view)
updateEmptyStates()
}
private fun updateEmptyStates() {
// Update prompts empty state
tvEmptyPrompts.visibility = if (llPromptsList.childCount == 0) View.VISIBLE else View.GONE
// Update buttons empty state
tvEmptyButtons.visibility = if (llButtonsList.childCount == 0) View.VISIBLE else View.GONE
}
// Save all configurations when leaving or explicitly saving
override fun onPause() {
super.onPause()
saveConfigurations()
}
private fun saveConfigurations() {
// Update header configs from UI
headerConfigs.clear()
for (i in 0 until llHeadersList.childCount) {
val view = llHeadersList.getChildAt(i)
val key = view.findViewById<EditText>(R.id.etHeaderKey).text.toString()
val value = view.findViewById<EditText>(R.id.etHeaderValue).text.toString()
if (key.isNotBlank() && value.isNotBlank()) {
headerConfigs.add(HeaderConfig(key, value))
}
}
// Update prompt configs from UI
promptConfigs.clear()
for (i in 0 until llPromptsList.childCount) {
val view = llPromptsList.getChildAt(i)
val title = view.findViewById<EditText>(R.id.etPromptTitle).text.toString()
val content = view.findViewById<EditText>(R.id.etPromptContent).text.toString()
if (title.isNotBlank() && content.isNotBlank()) {
promptConfigs.add(PromptConfig(
if (title.equals("翻译助手", ignoreCase = true)) "default-1"
else if (title.equals("代码解释", ignoreCase = true)) "default-2"
else UUID.randomUUID().toString(),
title,
content
))
}
}
// Update button configs from UI
buttonConfigs.clear()
for (i in 0 until llButtonsList.childCount) {
val view = llButtonsList.getChildAt(i)
val label = view.findViewById<EditText>(R.id.etButtonLabel).text.toString()
val actionText = view.findViewById<Button>(R.id.btnButtonAction).text.toString()
val action = when (actionText) {
"复制输出内容" -> "copy"
"提交到第三方 API" -> "api"
else -> "unknown"
}
if (label.isNotBlank()) {
buttonConfigs.add(ButtonConfig(
if (label.equals("复制结果", ignoreCase = true)) "btn-copy"
else if (label.equals("发送到飞书", ignoreCase = true)) "btn-webhook"
else UUID.randomUUID().toString(),
label,
action
))
}
}
// Save LLM config
val llmConfig = LLMConfig(
baseUrl = etBaseUrl.text.toString(),
apiKey = etApiKey.text.toString(),
model = etModel.text.toString()
)
// Save everything
val settingsData = SettingsData(
llmConfig = llmConfig,
headerConfigs = headerConfigs,
promptConfigs = promptConfigs,
buttonConfigs = buttonConfigs
)
val json = Gson().toJson(settingsData)
val sharedPrefs = getSharedPreferences("APIConfigs", Context.MODE_PRIVATE)
sharedPrefs.edit().putString("configs", json).apply()
// Also update the legacy APIConfig for backward compatibility
apiConfig = APIConfig(
System.currentTimeMillis(),
"llm-config",
etBaseUrl.text.toString(),
etApiKey.text.toString(),
"",
etModel.text.toString()
)
}
private fun addConfig() {
val name = etApiName.text.toString()
val url = etApiUrl.text.toString()
val key = etApiKey.text.toString()
val secretKey = etApiSecretKey.text.toString()
val model = etApiModel.text.toString()
// Legacy APIConfig class for backward compatibility with existing code
data class APIConfig(
val id: Long,
val name: String,
val url: String,
val key: String,
val secretKey: String,
val model: String
)
// 生成唯一的 id
val id = System.currentTimeMillis()
// 创建新的配置项
val newConfig = APIConfig(id, name, url, key, secretKey, model)
// 添加配置项
configs.add(newConfig)
// 保存配置
saveConfigs()
// 显示配置
displayConfigs()
// 清空输入框
clearInputs()
}
// New data classes for settings structure
data class LLMConfig(
val baseUrl: String,
val apiKey: String,
val model: String
)
private fun updateConfig() {
val name = etApiName.text.toString()
val url = etApiUrl.text.toString()
val key = etApiKey.text.toString()
val secretKey = etApiSecretKey.text.toString()
val model = etApiModel.text.toString()
// 获取编辑的配置项 id
val id = editingId ?: return
// 更新配置项
val updatedConfig = APIConfig(id, name, url, key, secretKey, model)
val existingConfigIndex = configs.indexOfFirst { it.id == id }
if (existingConfigIndex != -1) {
configs[existingConfigIndex] = updatedConfig
}
// 保存配置
saveConfigs()
// 显示配置
displayConfigs()
// 清空输入框
clearInputs()
// 重置编辑状态
editingId = null
}
@SuppressLint("MissingInflatedId")
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<TextView>(R.id.tvName)
tvName.text = "Name: ${config.name}"
tvName.setTextColor(Color.BLACK)
// 获取并设置 URL 的 TextView 前景色和背景色
val tvUrl = configView.findViewById<TextView>(R.id.tvUrl)
tvUrl.text = "URL: ${config.url.take(32)}..."
tvUrl.setTextColor(Color.BLACK)
// 获取并设置 Key 的 TextView 前景色和背景色
val tvKey = configView.findViewById<TextView>(R.id.tvKey)
tvKey.text = "Key: ${config.key.take(4)}..."
tvKey.setTextColor(Color.BLACK)
// 获取并设置 SecretKey 的 TextView 前景色和背景色
val tvSecretKey = configView.findViewById<TextView>(R.id.tvSecretKey)
tvSecretKey.text = "Secret Key: ${config.secretKey.take(4)}..."
tvSecretKey.setTextColor(Color.BLACK)
// 获取并设置 model 的 TextView 前景色和背景色
val tvApiModel = configView.findViewById<TextView>(R.id.tvApiModel)
tvApiModel.text = "Model: ${config.model}"
tvApiModel.setTextColor(Color.BLACK)
// 设置编辑按钮点击事件
configView.findViewById<Button>(R.id.btnEdit).setOnClickListener {
editConfig(config)
}
// 设置删除按钮点击事件
configView.findViewById<Button>(R.id.btnDelete).setOnClickListener {
deleteConfig(config.id)
}
// 将包含配置信息的视图添加到父布局中
llConfigList.addView(configView)
}
}
private fun editConfig(config: APIConfig) {
etApiName.setText(config.name)
etApiUrl.setText(config.url)
etApiKey.setText(config.key)
etApiSecretKey.setText(config.secretKey)
etApiModel.setText(config.model)
// 设置编辑状态
editingId = config.id
btnSave.text = "更新配置"
}
private fun deleteConfig(id: Long) {
configs.removeAll { it.id == id }
saveConfigs()
displayConfigs()
}
private fun clearInputs() {
etApiName.text.clear()
etApiUrl.text.clear()
etApiKey.text.clear()
etApiSecretKey.text.clear()
etApiModel.text.clear()
btnSave.text = "保存配置"
}
}
data class APIConfig(
val id: Long,
val name: String,
val url: String,
val key: String,
val secretKey: String,
val model: String
)
data class SettingsData(
val llmConfig: LLMConfig?,
val headerConfigs: List<HeaderConfig>?,
val promptConfigs: List<PromptConfig>?,
val buttonConfigs: List<ButtonConfig>?
)
}

View File

@@ -3,185 +3,371 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp"
tools:ignore="ExtraText">
<Button
android:id="@+id/btnGoBack"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/back_to_main" />
<TextView
android:layout_width="wrap_content"
android:layout_height="30dp"
android:gravity="center_vertical"
android:text="文字处理AI区的配置"
android:textSize="16sp" />
<EditText
android:id="@+id/etApiButtonName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="API 按钮显示名称"
android:minHeight="48dp" />
<EditText
android:id="@+id/etApiName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="API 名称"
android:minHeight="48dp" />
<EditText
android:id="@+id/etApiUrl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="API URL"
android:minHeight="48dp" />
<EditText
android:id="@+id/etApiKey"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="@string/api_key"
android:minHeight="48dp" />
<EditText
android:id="@+id/etApiSecretKey"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="API Secret Key"
android:minHeight="48dp" />
<EditText
android:id="@+id/etApiModel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="API 模型类型"
android:minHeight="48dp" />
<Button
android:id="@+id/btnSave"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/save_config" />
android:orientation="vertical">
<!-- Header -->
<LinearLayout
android:id="@+id/llConfigList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="vertical" />
<!-- 图片选择区域 -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/textViewImageHint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="点击按钮选择图片作为背景"
android:layout_below="@id/buttonChooseImage"
android:layout_centerHorizontal="true"
android:layout_marginTop="10dp" />
android:orientation="vertical"
android:layout_marginBottom="24dp">
<Button
android:id="@+id/buttonChooseImage"
android:id="@+id/btnBack"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="选择图片"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp" />
</RelativeLayout>
<!-- 颜色选择区域 -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:text="返回主界面"
android:layout_gravity="start"
android:layout_marginBottom="8dp"/>
<TextView
android:id="@+id/statustextView"
android:id="@+id/headerTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="点击以下按钮,设置主页状态栏的颜色"
android:textSize="16sp" />
android:text="设置"
android:textSize="24sp"
android:textStyle="bold"
android:textColor="@android:color/black"
android:layout_marginBottom="4dp"/>
<androidx.constraintlayout.widget.ConstraintLayout
<TextView
android:id="@+id/headerSubtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="配置自动保存到本地"
android:textSize="14sp"
android:textColor="@android:color/darker_gray"/>
</LinearLayout>
<!-- LLM Config Section -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="24dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="大模型配置"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="8dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="设置 API 接口地址、密钥与模型"
android:textSize="14sp"
android:textColor="@android:color/darker_gray"
android:layout_marginBottom="16dp"/>
<!-- Base URL Field -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/statustextView">
android:orientation="vertical"
android:layout_marginBottom="12dp">
<Button
android:id="@+id/button_holo_red_light"
android:layout_width="0dp"
android:layout_height="20dp"
android:layout_marginEnd="5dp"
android:background="@android:color/holo_red_light"
app:layout_constraintEnd_toStartOf="@+id/button_holo_green_light"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="TouchTargetSizeCheck,SpeakableTextPresentCheck" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Base URL"
android:textSize="14sp"
android:layout_marginBottom="4dp"/>
<Button
android:id="@+id/button_holo_green_light"
android:layout_width="0dp"
android:layout_height="20dp"
android:layout_marginEnd="5dp"
android:background="@android:color/holo_green_light"
app:layout_constraintEnd_toStartOf="@+id/button_holo_blue_light"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toEndOf="@+id/button_holo_red_light"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="TouchTargetSizeCheck,SpeakableTextPresentCheck" />
<EditText
android:id="@+id/etBaseUrl"
android:layout_width="match_parent"
android:layout_height="48dp"
android:hint="https://api.openai.com/v1"
android:inputType="textUri"
android:background="@drawable/edittext_border"
android:padding="8sp"/>
<Button
android:id="@+id/button_holo_blue_light"
android:layout_width="0dp"
android:layout_height="20dp"
android:layout_marginEnd="5dp"
android:background="@android:color/holo_blue_light"
app:layout_constraintEnd_toStartOf="@+id/button_holo_orange_light"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toEndOf="@+id/button_holo_green_light"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="TouchTargetSizeCheck,SpeakableTextPresentCheck" />
</LinearLayout>
<Button
android:id="@+id/button_holo_orange_light"
android:layout_width="0dp"
android:layout_height="20dp"
android:layout_marginEnd="5dp"
android:background="@android:color/holo_orange_light"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toEndOf="@+id/button_holo_blue_light"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="TouchTargetSizeCheck,SpeakableTextPresentCheck" />
<!-- API Key Field -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="12dp">
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
</RelativeLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="API Key"
android:textSize="14sp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="8dp"/>
<ImageButton
android:id="@+id/btnToggleApiKey"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@android:drawable/ic_lock_idle_lock"
android:background="?attr/selectableItemBackgroundBorderless"
android:layout_gravity="center_vertical"/>
</LinearLayout>
<EditText
android:id="@+id/etApiKey"
android:layout_width="match_parent"
android:layout_height="48dp"
android:hint="sk-..."
android:inputType="textPassword"
android:background="@drawable/edittext_border"
android:padding="8sp"/>
</LinearLayout>
<!-- Model Field -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="12dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="模型"
android:textSize="14sp"
android:layout_marginBottom="4dp"/>
<EditText
android:id="@+id/etModel"
android:layout_width="match_parent"
android:layout_height="48dp"
android:hint="gpt-4o"
android:inputType="text"
android:background="@drawable/edittext_border"
android:padding="8sp"/>
</LinearLayout>
</LinearLayout>
<!-- Custom Headers Section -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="24dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="自定义请求头"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="8dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="向请求附加额外的 HTTP Header"
android:textSize="14sp"
android:textColor="@android:color/darker_gray"
android:layout_marginBottom="16dp"/>
<!-- Headers List -->
<LinearLayout
android:id="@+id/llHeadersList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="12dp">
<!-- Default Header Entry -->
<LinearLayout
android:id="@+id/headerEntryTemplate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp"
android:background="@drawable/edittext_border"
android:padding="8sp">
<EditText
android:id="@+id/etHeaderKey"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="Header 名称"
android:inputType="text"
android:padding="4sp"/>
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="@android:color/darker_gray"
android:layout_marginHorizontal="8dp"/>
<EditText
android:id="@+id/etHeaderValue"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:hint="值"
android:inputType="text"
android:padding="4sp"/>
<ImageButton
android:id="@+id/btnRemoveHeader"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@android:drawable/ic_menu_close_clear_cancel"
android:background="?attr/selectableItemBackgroundBorderless"
android:layout_gravity="center_vertical"/>
</LinearLayout>
</LinearLayout>
<!-- Add Header Button -->
<Button
android:id="@+id/btnAddHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="添加请求头"
android:layout_gravity="start"
android:background="@drawable/edittext_border"
android:padding="8sp"/>
</LinearLayout>
<!-- Preset Prompts Section -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="24dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="预设提示词"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="8dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="管理可快速选用的系统提示词"
android:textSize="14sp"
android:textColor="@android:color/darker_gray"
android:layout_marginBottom="16dp"/>
<!-- Prompts List -->
<LinearLayout
android:id="@+id/llPromptsList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="12dp">
<!-- Prompt entries will be added dynamically -->
</LinearLayout>
<!-- Add Prompt Button -->
<Button
android:id="@+id/btnAddPrompt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="添加提示词"
android:layout_gravity="start"
android:background="@drawable/edittext_border"
android:padding="8sp"/>
<!-- Empty State -->
<TextView
android:id="@+id/tvEmptyPrompts"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="暂无预设提示词"
android:textSize="14sp"
android:textColor="@android:color/darker_gray"
android:layout_gravity="center"
android:layout_marginTop="12dp"
android:visibility="gone"/>
</LinearLayout>
<!-- Custom Buttons Section -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="24dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="自定义按钮"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="8dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="配置主界面中显示的操作按钮"
android:textSize="14sp"
android:textColor="@android:color/darker_gray"
android:layout_marginBottom="16dp"/>
<!-- Buttons List -->
<LinearLayout
android:id="@+id/llButtonsList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="12dp">
<!-- Button entries will be added dynamically -->
</LinearLayout>
<!-- Add Button -->
<Button
android:id="@+id/btnAddButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="添加按钮"
android:layout_gravity="start"
android:background="@drawable/edittext_border"
android:padding="8sp"/>
<!-- Empty State -->
<TextView
android:id="@+id/tvEmptyButtons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="暂无自定义按钮"
android:textSize="14sp"
android:textColor="@android:color/darker_gray"
android:layout_gravity="center"
android:layout_marginTop="12dp"
android:visibility="gone"/>
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@@ -0,0 +1,93 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="12dp"
android:background="@drawable/edittext_border"
android:padding="12sp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8sp">
<EditText
android:id="@+id/etButtonLabel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:hint="按钮标签"
android:inputType="text"
android:padding="4sp"/>
<Button
android:id="@+id/btnButtonAction"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="复制输出内容"
android:layout_marginStart="8sp"
android:padding="4sp"/>
<ImageButton
android:id="@+id/btnRemoveButton"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@android:drawable/ic_menu_close_clear_cancel"
android:background="?attr/selectableItemBackgroundBorderless"
android:layout_gravity="end"
android:layout_marginStart="8sp"/>
</LinearLayout>
<!-- API Configuration Fields (shown when action is "api") -->
<LinearLayout
android:id="@+id/llApiFields"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
<EditText
android:id="@+id/etApiUrl"
android:layout_width="match_parent"
android:layout_height="48dp"
android:hint="API URL"
android:inputType="textUri"
android:background="@android:color/white"
android:padding="8sp"
android:layout_marginBottom="4sp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/etApiMethod"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="HTTP Method (POST/GET)"
android:inputType="text"
android:background="@android:color/white"
android:padding="8sp"
android:layout_marginEnd="4sp"/>
<EditText
android:id="@+id/etApiBodyTemplate"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:hint="请求体模板 (使用 {output} 占位符)"
android:inputType="textMultiLine"
android:minLines="2"
android:background="@android:color/white"
android:padding="8sp"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp"
android:background="@drawable/edittext_border"
android:padding="8sp">
<EditText
android:id="@+id/etHeaderKey"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="Header 名称"
android:inputType="text"
android:padding="4sp"/>
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="@android:color/darker_gray"
android:layout_marginHorizontal="8dp"/>
<EditText
android:id="@+id/etHeaderValue"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:hint="值"
android:inputType="text"
android:padding="4sp"/>
<ImageButton
android:id="@+id/btnRemoveHeader"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@android:drawable/ic_menu_close_clear_cancel"
android:background="?attr/selectableItemBackgroundBorderless"
android:layout_gravity="center_vertical"/>
</LinearLayout>

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="12dp"
android:background="@drawable/edittext_border"
android:padding="12sp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8sp">
<EditText
android:id="@+id/etPromptTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="提示词标题"
android:inputType="text"
android:padding="4sp"/>
<ImageButton
android:id="@+id/btnRemovePrompt"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@android:drawable/ic_menu_close_clear_cancel"
android:background="?attr/selectableItemBackgroundBorderless"
android:layout_gravity="end"/>
</LinearLayout>
<EditText
android:id="@+id/etPromptContent"
android:layout_width="match_parent"
android:layout_height="80dp"
android:hint="提示词内容System Prompt"
android:inputType="textMultiLine"
android:minLines="4"
android:background="@android:color/white"
android:padding="8sp"/>
<Button
android:id="@+id/btnExpandPrompt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="展开"
android:layout_gravity="end"
android:layout_marginTop="4sp"/>
<View
android:id="@+id/viewDivider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@android:color/darker_gray"
android:layout_marginVertical="8sp"/>
</LinearLayout>