优化配置页面和首页布局:添加自定义提示词功能,简化UI设计,去掉圆角边框,添加返回首页按钮

This commit is contained in:
2026-03-15 15:52:51 +08:00
parent 9eae35bc7c
commit c6ae059e65
18 changed files with 1574 additions and 721 deletions

View File

@@ -6,85 +6,114 @@ import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.Gravity
import android.view.View
import android.widget.AdapterView
import android.util.Log
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.EditText
import android.widget.ImageButton
import android.widget.Spinner
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.delay
import android.util.Log
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
private lateinit var inputEditText: EditText
private lateinit var configButton: Button
private lateinit var submitToZhiPuAIButton: Button
private lateinit var submitToSparkAIButton: Button
private lateinit var tabLayout: TabLayout
private lateinit var submitToServerButton: Button
private lateinit var statusText: TextView
private lateinit var outputStatusLabel: TextView
private lateinit var outputTextView: TextView
private lateinit var promptSelector: Spinner
private lateinit var promptContentText: TextView
// Data classes matching SecondActivity
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)
data class LLMConfig(val baseUrl: String, val apiKey: String, val model: String)
data class SettingsData(
val llmConfig: LLMConfig?,
val headerConfigs: List<HeaderConfig>?,
val promptConfigs: List<PromptConfig>?,
val buttonConfigs: List<ButtonConfig>?
)
@SuppressLint("MissingInflatedId", "CutPasteId", "SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("MainActivity", "onCreate: Starting MainActivity")
setContentView(R.layout.activity_main)
Log.d("MainActivity", "onCreate: Layout set")
// Initialize views
val promptSelector = findViewById<Spinner>(R.id.promptSelector)
val inputEditText = findViewById<EditText>(R.id.inputEditText)
promptSelector = findViewById<Spinner>(R.id.promptSelector)
promptContentText = findViewById<TextView>(R.id.promptContentText)
inputEditText = findViewById<EditText>(R.id.inputEditText)
val sendButton = findViewById<Button>(R.id.sendButton)
val stopButton = findViewById<Button>(R.id.stopButton)
val outputStatusLabel = findViewById<TextView>(R.id.outputStatusLabel)
val outputTextView = findViewById<TextView>(R.id.outputTextView)
val copyButton = findViewById<Button>(R.id.copyButton)
outputStatusLabel = findViewById<TextView>(R.id.outputStatusLabel)
outputTextView = findViewById<TextView>(R.id.outputTextView)
val btnCopyResult = findViewById<Button>(R.id.btnCopyResult)
val headerTitle = findViewById<TextView>(R.id.headerTitle)
val headerModelName = findViewById<TextView>(R.id.headerModelName)
Log.d("MainActivity", "onCreate: Views initialized")
// Set header values from JSON
headerTitle.text = "AI优化"
headerModelName.text = "gpt-4o"
// Setup prompt selector
val promptOptions = listOf("无系统提示词", "翻译助手", "代码解释")
val adapter = ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, promptOptions)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
promptSelector.adapter = adapter
promptSelector.setSelection(0) // Default to "无系统提示词"
// Load prompts from configuration
loadPromptsFromConfig()
// Setup initial output state
outputStatusLabel.text = "等待发送"
outputTextView.text = "发送消息后结果将在此显示"
// Send button click listener
// Setup prompt selector listener
promptSelector.setOnItemSelectedListener(object : android.widget.AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: android.widget.AdapterView<*>, view: android.view.View?, position: Int, id: Long) {
val selectedTitle = promptSelector.getItemAtPosition(position) as String
// Get the prompt map from tag
@Suppress("UNCHECKED_CAST")
val promptMap = promptSelector.tag as? MutableMap<String, String>
val content = if (promptMap != null && promptMap.containsKey(selectedTitle)) {
promptMap[selectedTitle] ?: "无特殊指令"
} else {
"无特殊指令"
}
promptContentText.text = content
Log.d("MainActivity", "Prompt selected: $selectedTitle, content: $content")
}
override fun onNothingSelected(parent: android.widget.AdapterView<*>) {
promptContentText.text = "无特殊指令"
}
})
sendButton.setOnClickListener {
Log.d("MainActivity", "Send button clicked")
// Test log
Log.e("MainActivity", "TEST ERROR LOG")
val inputText = inputEditText.text.toString()
if (inputText.isNotEmpty()) {
outputStatusLabel.text = "连接中…"
outputTextView.text = "正在生成..."
// Simulate API call with coroutine
CoroutineScope(Dispatchers.Main).launch {
try {
// Simulate network delay
Log.d("MainActivity", "Starting text processing")
delay(1000)
// For demo purposes, we'll show a sample optimized text
val optimizedText = "今天阳光明媚,微风拂面,我漫步于公园之中,享受这难得的惬意时光。"
outputStatusLabel.text = "已完成"
outputTextView.text = optimizedText
// Update selected prompt ID in JSON structure (simulated)
val selectedPromptId = when (promptSelector.selectedItemPosition) {
0 -> "none"
1 -> "default-1"
@@ -92,8 +121,8 @@ class MainActivity : AppCompatActivity() {
else -> "none"
}
// In a real app, you would update your JSON state here
Log.d("MainActivity", "Selected prompt ID: $selectedPromptId")
Log.d("MainActivity", "Text processing completed successfully")
} catch (e: Exception) {
outputStatusLabel.text = "发生错误"
outputTextView.text = "错误: ${e.message}"
@@ -101,169 +130,128 @@ class MainActivity : AppCompatActivity() {
}
}
} else {
Log.w("MainActivity", "Input text is empty")
Toast.makeText(this, "请输入内容", Toast.LENGTH_SHORT).show()
}
}
// Stop button click listener
stopButton.setOnClickListener {
Log.d("MainActivity", "Stop button clicked")
outputStatusLabel.text = "已停止"
Toast.makeText(this, "生成已停止", Toast.LENGTH_SHORT).show()
}
// Copy button click listener
copyButton.setOnClickListener {
val textToCopy = outputTextView.text.toString()
if (textToCopy.isNotEmpty() && textToCopy != "发送消息后结果将在此显示") {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("优化结果", textToCopy)
clipboard.setPrimaryClip(clip)
Toast.makeText(this, "结果已复制到剪贴板", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "没有可复制的内容", Toast.LENGTH_SHORT).show()
}
}
// Custom copy result button
btnCopyResult.setOnClickListener {
Log.d("MainActivity", "btnCopyResult clicked")
val textToCopy = outputTextView.text.toString()
if (textToCopy.isNotEmpty() && textToCopy != "发送消息后结果将在此显示") {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("优化结果", textToCopy)
clipboard.setPrimaryClip(clip)
Toast.makeText(this, "结果已复制到剪贴板", Toast.LENGTH_SHORT).show()
Log.d("MainActivity", "Text copied to clipboard")
} else {
Toast.makeText(this, "没有可复制的内容", Toast.LENGTH_SHORT).show()
Log.w("MainActivity", "No text to copy")
}
}
// Keep existing functionality for other buttons (config, etc.)
// 点击配置按钮
val configButton = findViewById<Button>(R.id.configButton)
try {
configButton = findViewById<Button>(R.id.configButton)
Log.d("MainActivity", "Config button found: $configButton")
configButton.setOnClickListener {
Log.d("MainActivity", "Config button clicked, launching SecondActivity")
Toast.makeText(this, "Opening settings...", Toast.LENGTH_SHORT).show()
try {
val intent = Intent(this, SecondActivity::class.java)
startActivity(intent)
Log.d("MainActivity", "SecondActivity started successfully")
} catch (e: Exception) {
Log.e("MainActivity", "Error starting SecondActivity", e)
Toast.makeText(this, "Error opening settings: ${e.message}", Toast.LENGTH_SHORT).show()
}
// 提交到flomo的服务器按钮
val submitToServerButton = findViewById<Button>(R.id.submitToServerButton)
val inputEditText = findViewById<EditText>(R.id.inputEditText)
submitToServerButton.setOnClickListener {
val textFromEditText = inputEditText.text.toString()
submitToServer(textFromEditText)
}
// 创建4个按钮 (保留原有的标签功能)
val tabLayout = findViewById<TabLayout>(R.id.tabLayout)
// 维持原来的创建标签按钮的代码
(1..4).forEach { tabIndex ->
tabLayout.newTab().apply {
text = "标签示例$tabIndex"
tabLayout.addTab(this)
}
Log.d("MainActivity", "onCreate: Completed successfully")
} catch (e: Exception) {
Log.e("MainActivity", "onCreate: Error setting up config button", e)
throw e
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == SecondActivity.REQUEST_CODE_PICK_IMAGE && resultCode == Activity.RESULT_OK && data != null) {
val selectedImageUri = data.getStringExtra("selectedImageUri")
if (selectedImageUri != null) {
val imageView = findViewById<ImageView>(R.id.imageViewBackground) // 假设你的背景是一个ImageView
imageView.setImageURI(Uri.parse(selectedImageUri))
}
}
}
override fun onStart() {
super.onStart()
// 获取从其他 Activity 传递过来的按钮颜色值,如果没有传递颜色值,则默认值为透明色。
val statusTextView = findViewById<TextView>(R.id.statusTextView)
updateStatusTextViewColor(statusTextView)
}
private fun updateStatusTextViewColor(statusTextView: TextView) {
private fun loadPromptsFromConfig() {
Log.d("MainActivity", "loadPromptsFromConfig: Starting")
try {
// Load shared preferences
val sharedPrefs = getSharedPreferences("APIConfigs", Context.MODE_PRIVATE)
val colorValue = sharedPrefs.getString("buttonColor", "")
Log.d("SharedPrefsDebug", "读取颜色值 MainActivity: $colorValue")
val json = sharedPrefs.getString("configs", null)
if (colorValue.isNullOrEmpty()) {
statusTextView.setBackgroundResource(android.R.color.holo_blue_dark)
} else {
// 根据提取的颜色值设置背景颜色,这里只是一个示例,实际应用中可能需要更复杂的逻辑
when (colorValue) {
"red" -> statusTextView.setBackgroundResource(android.R.color.holo_red_light)
"green" -> statusTextView.setBackgroundResource(android.R.color.holo_green_light)
"blue" -> statusTextView.setBackgroundResource(android.R.color.holo_blue_light)
"orange" -> statusTextView.setBackgroundResource(android.R.color.holo_orange_light)
else -> {
// 如果颜色值不在预定义的列表中,使用默认颜色
statusTextView.setBackgroundResource(android.R.color.holo_blue_dark)
Log.e("SharedPrefsError", "Invalid color value: $colorValue")
}
val promptTitles = mutableListOf<String>()
val promptContents = mutableListOf<String>()
// Add default prompt
promptTitles.add("无系统提示词")
promptContents.add("无特殊指令")
if (json != null) {
try {
val settings = Gson().fromJson(json, SettingsData::class.java)
val promptConfigs = settings.promptConfigs ?: listOf()
for (prompt in promptConfigs) {
if (prompt.title.isNotBlank() && prompt.content.isNotBlank()) {
promptTitles.add(prompt.title)
promptContents.add(prompt.content)
}
}
Log.d("MainActivity", "Loaded ${promptConfigs.size} custom prompts from config")
} catch (e: Exception) {
// 捕获并记录任何异常
Log.e("SharedPrefsError", "更新状态文本视图颜色时出错\n: ${e.message}", e)
// 设置默认颜色
statusTextView.setBackgroundResource(android.R.color.holo_blue_dark)
Log.e("MainActivity", "Error loading prompts from config", e)
}
}
private fun getBitmapFromUri(uri: Uri): Bitmap? {
val inputStream: InputStream? = contentResolver.openInputStream(uri)
return BitmapFactory.decodeStream(inputStream)
}
@SuppressLint("SetTextI18n")
private fun submitToServer(content: String) {
CoroutineScope(Dispatchers.Main).launch {
statusText.text = "提交到flomo服务器..."
val result = withContext(Dispatchers.IO) {
postDataToServer(content)
}
when (result) {
is Result.Success -> {
findViewById<EditText>(R.id.inputEditText).setText("")
statusText.text = "提交成功!"
}
is Result.Error -> {
statusText.text = "提交失误: ${result.exception.message}"
}
}
}
}
// 提交到笔记服务器flomo服务器
private 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)
}
Log.d("MainActivity", "No saved config found, using default prompts")
}
sealed class Result {
data class Success(val response: Response) : Result()
data class Error(val exception: Exception) : Result()
// Add default prompts if no custom prompts found
if (promptTitles.size == 1) {
promptTitles.add("翻译助手")
promptContents.add("将输入的文本翻译成指定语言")
promptTitles.add("代码解释")
promptContents.add("解释代码的功能和逻辑")
}
// Store prompt contents in a map for easy access
val promptMap = mutableMapOf<String, String>()
for (i in promptTitles.indices) {
promptMap[promptTitles[i]] = promptContents[i]
}
// Save prompt map to use in onItemSelectedListener
promptSelector.tag = promptMap
val adapter = ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, promptTitles)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
promptSelector.adapter = adapter
promptSelector.setSelection(0)
Log.d("MainActivity", "loadPromptsFromConfig: Completed with ${promptTitles.size} prompts")
} catch (e: Exception) {
Log.e("MainActivity", "loadPromptsFromConfig: Error", e)
// Fallback to default prompts
val promptTitles = listOf("无系统提示词", "翻译助手", "代码解释")
val promptContents = listOf("无特殊指令", "将输入的文本翻译成指定语言", "解释代码的功能和逻辑")
val promptMap = mutableMapOf<String, String>()
for (i in promptTitles.indices) {
promptMap[promptTitles[i]] = promptContents[i]
}
promptSelector.tag = promptMap
val adapter = ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, promptTitles)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
promptSelector.adapter = adapter
promptSelector.setSelection(0)
}
}
}

View File

@@ -0,0 +1,269 @@
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.os.Bundle
import android.view.Gravity
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.EditText
import android.widget.Spinner
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.delay
import android.util.Log
class MainActivity : AppCompatActivity() {
private lateinit var inputEditText: EditText
private lateinit var configButton: Button
private lateinit var submitToZhiPuAIButton: Button
private lateinit var submitToSparkAIButton: Button
private lateinit var tabLayout: TabLayout
private lateinit var submitToServerButton: Button
private lateinit var statusText: TextView
@SuppressLint("MissingInflatedId", "CutPasteId", "SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Initialize views
val promptSelector = findViewById<Spinner>(R.id.promptSelector)
val inputEditText = findViewById<EditText>(R.id.inputEditText)
val sendButton = findViewById<Button>(R.id.sendButton)
val stopButton = findViewById<Button>(R.id.stopButton)
val outputStatusLabel = findViewById<TextView>(R.id.outputStatusLabel)
val outputTextView = findViewById<TextView>(R.id.outputTextView)
val copyButton = findViewById<Button>(R.id.copyButton)
val btnCopyResult = findViewById<Button>(R.id.btnCopyResult)
val headerTitle = findViewById<TextView>(R.id.headerTitle)
val headerModelName = findViewById<TextView>(R.id.headerModelName)
// Set header values from JSON
headerTitle.text = "AI优化"
headerModelName.text = "gpt-4o"
// Setup prompt selector
val promptOptions = listOf("无系统提示词", "翻译助手", "代码解释")
val adapter = ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, promptOptions)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
promptSelector.adapter = adapter
promptSelector.setSelection(0) // Default to "无系统提示词"
// Setup initial output state
outputStatusLabel.text = "等待发送"
outputTextView.text = "发送消息后结果将在此显示"
// Send button click listener
sendButton.setOnClickListener {
val inputText = inputEditText.text.toString()
if (inputText.isNotEmpty()) {
outputStatusLabel.text = "连接中…"
outputTextView.text = "正在生成..."
// Simulate API call with coroutine
CoroutineScope(Dispatchers.Main).launch {
try {
// Simulate network delay
delay(1000)
// For demo purposes, we'll show a sample optimized text
val optimizedText = "今天阳光明媚,微风拂面,我漫步于公园之中,享受这难得的惬意时光。"
outputStatusLabel.text = "已完成"
outputTextView.text = optimizedText
// Update selected prompt ID in JSON structure (simulated)
val selectedPromptId = when (promptSelector.selectedItemPosition) {
0 -> "none"
1 -> "default-1"
2 -> "default-2"
else -> "none"
}
// In a real app, you would update your JSON state here
Log.d("MainActivity", "Selected prompt ID: $selectedPromptId")
} catch (e: Exception) {
outputStatusLabel.text = "发生错误"
outputTextView.text = "错误: ${e.message}"
Log.e("MainActivity", "Error processing request", e)
}
}
} else {
Toast.makeText(this, "请输入内容", Toast.LENGTH_SHORT).show()
}
}
// Stop button click listener
stopButton.setOnClickListener {
outputStatusLabel.text = "已停止"
Toast.makeText(this, "生成已停止", Toast.LENGTH_SHORT).show()
}
// Copy button click listener
copyButton.setOnClickListener {
val textToCopy = outputTextView.text.toString()
if (textToCopy.isNotEmpty() && textToCopy != "发送消息后结果将在此显示") {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("优化结果", textToCopy)
clipboard.setPrimaryClip(clip)
Toast.makeText(this, "结果已复制到剪贴板", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "没有可复制的内容", Toast.LENGTH_SHORT).show()
}
}
// Custom copy result button
btnCopyResult.setOnClickListener {
val textToCopy = outputTextView.text.toString()
if (textToCopy.isNotEmpty() && textToCopy != "发送消息后结果将在此显示") {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("优化结果", textToCopy)
clipboard.setPrimaryClip(clip)
Toast.makeText(this, "结果已复制到剪贴板", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "没有可复制的内容", Toast.LENGTH_SHORT).show()
}
}
// Keep existing functionality for other buttons (config, etc.)
// 点击配置按钮
val configButton = findViewById<Button>(R.id.configButton)
configButton.setOnClickListener {
val intent = Intent(this, SecondActivity::class.java)
startActivity(intent)
}
// 提交到flomo的服务器按钮
val submitToServerButton = findViewById<Button>(R.id.submitToServerButton)
val inputEditText = findViewById<EditText>(R.id.inputEditText)
submitToServerButton.setOnClickListener {
val textFromEditText = inputEditText.text.toString()
submitToServer(textFromEditText)
}
// 创建4个按钮 (保留原有的标签功能)
val tabLayout = findViewById<TabLayout>(R.id.tabLayout)
// 维持原来的创建标签按钮的代码
(1..4).forEach { tabIndex ->
tabLayout.newTab().apply {
text = "标签示例$tabIndex"
tabLayout.addTab(this)
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == SecondActivity.REQUEST_CODE_PICK_IMAGE && resultCode == Activity.RESULT_OK && data != null) {
val selectedImageUri = data.getStringExtra("selectedImageUri")
if (selectedImageUri != null) {
val imageView = findViewById<ImageView>(R.id.imageViewBackground) // 假设你的背景是一个ImageView
imageView.setImageURI(Uri.parse(selectedImageUri))
}
}
}
override fun onStart() {
super.onStart()
// 获取从其他 Activity 传递过来的按钮颜色值,如果没有传递颜色值,则默认值为透明色。
val statusTextView = findViewById<TextView>(R.id.statusTextView)
updateStatusTextViewColor(statusTextView)
}
private fun updateStatusTextViewColor(statusTextView: TextView) {
try {
val sharedPrefs = getSharedPreferences("APIConfigs", Context.MODE_PRIVATE)
val colorValue = sharedPrefs.getString("buttonColor", "")
Log.d("SharedPrefsDebug", "读取颜色值 MainActivity: $colorValue")
if (colorValue.isNullOrEmpty()) {
statusTextView.setBackgroundResource(android.R.color.holo_blue_dark)
} else {
// 根据提取的颜色值设置背景颜色,这里只是一个示例,实际应用中可能需要更复杂的逻辑
when (colorValue) {
"red" -> statusTextView.setBackgroundResource(android.R.color.holo_red_light)
"green" -> statusTextView.setBackgroundResource(android.R.color.holo_green_light)
"blue" -> statusTextView.setBackgroundResource(android.R.color.holo_blue_light)
"orange" -> statusTextView.setBackgroundResource(android.R.color.holo_orange_light)
else -> {
// 如果颜色值不在预定义的列表中,使用默认颜色
statusTextView.setBackgroundResource(android.R.color.holo_blue_dark)
Log.e("SharedPrefsError", "Invalid color value: $colorValue")
}
}
}
} catch (e: Exception) {
// 捕获并记录任何异常
Log.e("SharedPrefsError", "更新状态文本视图颜色时出错\n: ${e.message}", e)
// 设置默认颜色
statusTextView.setBackgroundResource(android.R.color.holo_blue_dark)
}
}
private fun getBitmapFromUri(uri: Uri): Bitmap? {
val inputStream: InputStream? = contentResolver.openInputStream(uri)
return BitmapFactory.decodeStream(inputStream)
}
@SuppressLint("SetTextI18n")
private fun submitToServer(content: String) {
CoroutineScope(Dispatchers.Main).launch {
statusText.text = "提交到flomo服务器..."
val result = withContext(Dispatchers.IO) {
postDataToServer(content)
}
when (result) {
is Result.Success -> {
findViewById<EditText>(R.id.inputEditText).setText("")
statusText.text = "提交成功!"
}
is Result.Error -> {
statusText.text = "提交失误: ${result.exception.message}"
}
}
}
}
// 提交到笔记服务器flomo服务器
private 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()
}
}

View File

@@ -8,10 +8,12 @@ import android.content.Intent
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.text.method.PasswordTransformationMethod
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
@@ -45,12 +47,16 @@ class SecondActivity : AppCompatActivity() {
private lateinit var etModel: EditText
private lateinit var llHeadersList: LinearLayout
private lateinit var btnAddHeader: Button
private lateinit var llPromptsList: LinearLayout
private lateinit var layoutHeaderContent: LinearLayout
private lateinit var ivHeaderArrow: ImageView
private lateinit var layoutHeaderToggle: LinearLayout
// Prompt view references
private lateinit var llPromptList: 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 lateinit var layoutPromptContent: LinearLayout
private lateinit var ivPromptArrow: ImageView
private lateinit var layoutPromptToggle: LinearLayout
// Data storage
private var headerConfigs = mutableListOf<HeaderConfig>()
@@ -62,75 +68,150 @@ class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("SecondActivity", "onCreate: Starting SecondActivity")
try {
setContentView(R.layout.activity_second)
Log.d("SecondActivity", "onCreate: Layout set")
} catch (e: Exception) {
Log.e("SecondActivity", "onCreate: Error setting layout", e)
throw e
}
try {
// Initialize views
initViews()
Log.d("SecondActivity", "onCreate: Views initialized")
// Load existing configurations
loadConfigurations()
Log.d("SecondActivity", "onCreate: Configurations loaded")
// Setup UI based on loaded data
setupUI()
Log.d("SecondActivity", "onCreate: UI setup completed")
// Back button functionality
findViewById<Button>(R.id.btnBack).setOnClickListener {
findViewById<ImageButton>(R.id.btnBack).setOnClickListener {
Log.d("SecondActivity", "Back button clicked")
finish()
}
// Home button functionality
findViewById<Button>(R.id.btnHome).setOnClickListener {
Log.d("SecondActivity", "Home button clicked")
// Create intent to go back to MainActivity
val intent = Intent(this, MainActivity::class.java)
// Clear the activity stack to start fresh
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)
finish()
}
// Add header button
btnAddHeader.setOnClickListener {
Log.d("SecondActivity", "Add header button clicked")
addHeaderEntry()
}
// Add prompt button
btnAddPrompt.setOnClickListener {
Log.d("SecondActivity", "Add prompt button clicked")
addPromptEntry()
}
// Add button
btnAddButton.setOnClickListener {
addButtonEntry()
Log.d("SecondActivity", "onCreate: Completed successfully")
} catch (e: Exception) {
Log.e("SecondActivity", "onCreate: Error during initialization", e)
throw e
}
}
private fun initViews() {
Log.d("SecondActivity", "initViews: Starting")
try {
etBaseUrl = findViewById(R.id.etBaseUrl)
etApiKey = findViewById(R.id.etApiKey)
btnToggleApiKey = findViewById(R.id.btnToggleApiKey)
etModel = findViewById(R.id.etModel)
// Header Section
llHeadersList = findViewById(R.id.llHeadersList)
btnAddHeader = findViewById(R.id.btnAddHeader)
llPromptsList = findViewById(R.id.llPromptsList)
layoutHeaderContent = findViewById(R.id.layoutHeaderContent)
ivHeaderArrow = findViewById(R.id.ivHeaderArrow)
layoutHeaderToggle = findViewById(R.id.layoutHeaderToggle)
// Prompt Section
llPromptList = findViewById(R.id.llPromptList)
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)
layoutPromptContent = findViewById(R.id.layoutPromptContent)
ivPromptArrow = findViewById(R.id.ivPromptArrow)
layoutPromptToggle = findViewById(R.id.layoutPromptToggle)
Log.d("SecondActivity", "initViews: All views found")
// Setup API key toggle
btnToggleApiKey.setOnClickListener {
Log.d("SecondActivity", "API key toggle clicked")
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)
// Update icon based on state
if (isPassword) {
btnToggleApiKey.setImageResource(android.R.drawable.ic_menu_view) // Show eye
} else {
btnToggleApiKey.setImageResource(android.R.drawable.ic_lock_idle_lock) // Show lock
}
}
// Setup Header Toggle (Fold/Unfold)
layoutHeaderToggle.setOnClickListener {
Log.d("SecondActivity", "Header toggle clicked")
val isExpanded = layoutHeaderContent.visibility == View.VISIBLE
if (isExpanded) {
layoutHeaderContent.visibility = View.GONE
ivHeaderArrow.rotation = 0f // Point right
} else {
layoutHeaderContent.visibility = View.VISIBLE
ivHeaderArrow.rotation = 90f // Point down
}
}
// Setup Prompt Toggle (Fold/Unfold)
layoutPromptToggle.setOnClickListener {
Log.d("SecondActivity", "Prompt toggle clicked")
val isExpanded = layoutPromptContent.visibility == View.VISIBLE
if (isExpanded) {
layoutPromptContent.visibility = View.GONE
ivPromptArrow.rotation = 0f // Point right
} else {
layoutPromptContent.visibility = View.VISIBLE
ivPromptArrow.rotation = 90f // Point down
}
}
Log.d("SecondActivity", "initViews: Completed")
} catch (e: Exception) {
Log.e("SecondActivity", "initViews: Error finding views", e)
throw e
}
}
private fun loadConfigurations() {
Log.d("SecondActivity", "loadConfigurations: Starting")
// Load shared preferences
val sharedPrefs = getSharedPreferences("APIConfigs", Context.MODE_PRIVATE)
val json = sharedPrefs.getString("configs", null)
Log.d("SecondActivity", "loadConfigurations: JSON loaded: ${json?.substring(0, minOf(json.length, 100))}")
if (json != null) {
try {
// Try to load as new format first
Log.d("SecondActivity", "loadConfigurations: Trying to parse as SettingsData")
val settings = Gson().fromJson(json, SettingsData::class.java)
Log.d("SecondActivity", "loadConfigurations: SettingsData parsed successfully")
headerConfigs = settings.headerConfigs?.toMutableList() ?: mutableListOf()
promptConfigs = settings.promptConfigs?.toMutableList() ?: mutableListOf()
buttonConfigs = settings.buttonConfigs?.toMutableList() ?: mutableListOf()
@@ -144,8 +225,10 @@ class SecondActivity : AppCompatActivity() {
updateApiKeyVisibility()
} catch (e: Exception) {
Log.e("SecondActivity", "loadConfigurations: Error parsing SettingsData", e)
// If new format fails, try to load old format for migration
try {
Log.d("SecondActivity", "loadConfigurations: Trying to parse as List<APIConfig>")
val type = object : TypeToken<List<APIConfig>>() {}.type
val oldConfigs = Gson().fromJson<List<APIConfig>>(json, type)
if (oldConfigs.isNotEmpty()) {
@@ -156,6 +239,7 @@ class SecondActivity : AppCompatActivity() {
updateApiKeyVisibility()
}
} catch (e2: Exception) {
Log.e("SecondActivity", "loadConfigurations: Error parsing List<APIConfig>", e2)
// If both fail, use defaults
etBaseUrl.setText("https://api.openai.com/v1")
etModel.setText("gpt-4o")
@@ -164,19 +248,17 @@ class SecondActivity : AppCompatActivity() {
}
} else {
// No saved config, use defaults
Log.d("SecondActivity", "loadConfigurations: No saved config, using defaults")
etBaseUrl.setText("https://api.openai.com/v1")
etModel.setText("gpt-4o")
updateApiKeyVisibility()
}
Log.d("SecondActivity", "loadConfigurations: Completed")
}
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)
}
@@ -193,35 +275,14 @@ class SecondActivity : AppCompatActivity() {
}
// Setup prompts
llPromptsList.removeAllViews()
llPromptList.removeAllViews()
if (promptConfigs.isEmpty()) {
// Add default prompts from JSON
promptConfigs.add(PromptConfig("default-1", "翻译助手", "你是一个专业翻译,请将用户输入的内容翻译成中文,保持原意,语言自然流畅。"))
promptConfigs.add(PromptConfig("default-2", "代码解释", "你是一个资深程序员,请详细解释用户提供的代码,用中文说明其功能和逻辑。"))
}
addPromptEntry() // Add one empty entry by default
} else {
for (prompt in promptConfigs) {
addPromptEntry(prompt)
addPromptEntry(prompt.title, prompt.content)
}
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 = "") {
@@ -235,89 +296,25 @@ class SecondActivity : AppCompatActivity() {
btnRemove.setOnClickListener {
llHeadersList.removeView(view)
updateEmptyStates()
}
llHeadersList.addView(view)
updateEmptyStates()
}
private fun addPromptEntry(config: PromptConfig = PromptConfig("", "", "")) {
private fun addPromptEntry(title: String = "", content: String = "") {
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)
etTitle.setText(title)
etContent.setText(content)
btnRemove.setOnClickListener {
llPromptsList.removeView(view)
updateEmptyStates()
llPromptList.removeView(view)
}
// 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
llPromptList.addView(view)
}
// Save all configurations when leaving or explicitly saving
@@ -340,42 +337,18 @@ class SecondActivity : AppCompatActivity() {
// Update prompt configs from UI
promptConfigs.clear()
for (i in 0 until llPromptsList.childCount) {
val view = llPromptsList.getChildAt(i)
for (i in 0 until llPromptList.childCount) {
val view = llPromptList.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
))
promptConfigs.add(PromptConfig(id = "prompt_$i", title = title, content = content))
}
}
// Update button configs from UI
// Note: Buttons are removed from this UI as per new design.
// We keep the list empty for data compatibility.
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(

View File

@@ -0,0 +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.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 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() {
// View references
private lateinit var etBaseUrl: EditText
private lateinit var etApiKey: EditText
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
// Data storage
private var headerConfigs = mutableListOf<HeaderConfig>()
private var promptConfigs = mutableListOf<PromptConfig>()
private var buttonConfigs = mutableListOf<ButtonConfig>()
// API config (for backward compatibility with existing API calls)
private lateinit var apiConfig: APIConfig
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
// Initialize views
initViews()
// Load existing configurations
loadConfigurations()
// Setup UI based on loaded data
setupUI()
// Back button functionality
findViewById<Button>(R.id.btnBack).setOnClickListener {
finish()
}
// Add header button
btnAddHeader.setOnClickListener {
addHeaderEntry()
}
// Add prompt button
btnAddPrompt.setOnClickListener {
addPromptEntry()
}
// Add button
btnAddButton.setOnClickListener {
addButtonEntry()
}
}
private fun initViews() {
etBaseUrl = findViewById(R.id.etBaseUrl)
etApiKey = findViewById(R.id.etApiKey)
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 loadConfigurations() {
// Load shared preferences
val sharedPrefs = getSharedPreferences("APIConfigs", Context.MODE_PRIVATE)
val json = sharedPrefs.getString("configs", null)
if (json != null) {
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 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()
)
}
// 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
)
// New data classes for settings structure
data class LLMConfig(
val baseUrl: String,
val apiKey: String,
val model: String
)
data class SettingsData(
val llmConfig: LLMConfig?,
val headerConfigs: List<HeaderConfig>?,
val promptConfigs: List<PromptConfig>?,
val buttonConfigs: List<ButtonConfig>?
)
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/secondary" />
<corners android:radius="12dp" />
</shape>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/primary" />
<corners android:radius="16dp" />
</shape>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/transparent" />
<stroke
android:width="2dp"
android:color="@color/primary" />
<corners android:radius="16dp" />
</shape>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@android:color/transparent" />
<solid android:color="@color/surface" />
<stroke
android:width="2dp"
android:color="@color/foreground_color" />
<corners android:radius="8dp" />
android:width="1dp"
android:color="@color/border" />
<corners android:radius="16dp" />
</shape>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#42A5F5"
android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
</vector>

View File

@@ -1,30 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/mainLinearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
android:background="@drawable/appbackground"
android:backgroundTint="@color/semi_transparent_background"
android:backgroundTintMode="src_over">
android:background="@color/background">
<!-- Header -->
<LinearLayout
<!-- 顶部导航栏 - 去掉圆角 -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="16dp">
android:background="@color/surface"
android:padding="16dp">
<TextView
android:id="@+id/headerTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="AI优化"
android:textSize="20sp"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@android:color/black"
android:layout_marginBottom="4dp"/>
android:textColor="@color/text_primary"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"/>
<TextView
android:id="@+id/headerModelName"
@@ -32,40 +31,75 @@
android:layout_height="wrap_content"
android:text="gpt-4o"
android:textSize="14sp"
android:textColor="@android:color/darker_gray"/>
android:textColor="@color/text_hint"
android:layout_alignParentStart="true"
android:layout_below="@id/headerTitle"/>
</LinearLayout>
<Button
android:id="@+id/configButton"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:text="配置"
android:textSize="14sp"
android:textColor="@color/white"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:layout_marginTop="4dp"
android:background="@drawable/button_config_bg"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:minWidth="0dp"
android:minHeight="0dp"/>
<!-- Prompt Selector -->
</RelativeLayout>
<!-- 提示词选择区域 - 去掉圆角 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="16dp">
android:background="@color/surface"
android:padding="16dp"
android:layout_marginTop="1dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="提示词"
android:textSize="14sp"
android:textColor="@android:color/black"
android:layout_marginBottom="4dp"/>
android:textColor="@color/text_primary"
android:layout_marginBottom="8dp"/>
<Spinner
android:id="@+id/promptSelector"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_height="48dp"
android:background="@drawable/edittext_border"
android:spinnerMode="dropdown"/>
android:spinnerMode="dropdown"
android:padding="8dp"/>
<!-- 提示词内容显示区域 -->
<TextView
android:id="@+id/promptContentText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:padding="4dp"
android:textSize="12sp"
android:textColor="@color/text_hint"
android:minLines="1"
android:maxLines="3"/>
</LinearLayout>
<!-- Input Area -->
<!-- 输入区域 - 去掉圆角 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="16dp">
android:background="@color/surface"
android:padding="16dp"
android:layout_marginTop="1dp">
<EditText
android:id="@+id/inputEditText"
@@ -75,53 +109,66 @@
android:hint="输入待发送内容…"
android:inputType="textMultiLine"
android:minLines="5"
android:padding="8sp"
android:textSize="14sp"/>
android:padding="12dp"
android:textSize="16sp"
android:textColor="@color/text_secondary"
android:textColorHint="@color/text_hint"
android:gravity="top|start"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="8dp">
android:layout_marginTop="12dp">
<Button
android:id="@+id/sendButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="48dp"
android:layout_weight="1"
android:text="发送"
android:textSize="14sp"
android:layout_marginEnd="4dp"/>
android:textSize="16sp"
android:textColor="@color/white"
android:layout_marginEnd="8dp"
android:background="@drawable/button_primary_bg"
android:minWidth="0dp"
android:minHeight="0dp"/>
<Button
android:id="@+id/stopButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="48dp"
android:layout_weight="1"
android:text="停止生成"
android:textSize="14sp"
android:layout_marginStart="4dp"/>
android:textSize="16sp"
android:textColor="@color/primary"
android:layout_marginStart="8dp"
android:background="@drawable/button_secondary_bg"
android:minWidth="0dp"
android:minHeight="0dp"/>
</LinearLayout>
</LinearLayout>
<!-- Output Area -->
<!-- 输出区域 - 去掉圆角 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginBottom="16dp">
android:background="@color/surface"
android:padding="16dp"
android:layout_marginTop="1dp">
<TextView
android:id="@+id/outputStatusLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="等待发送"
android:textSize="12sp"
android:textColor="@android:color/holo_blue_dark"
android:layout_marginBottom="4dp"/>
android:textSize="14sp"
android:textColor="@color/primary"
android:layout_marginBottom="8dp"/>
<TextView
android:id="@+id/outputTextView"
@@ -129,36 +176,32 @@
android:layout_height="0dp"
android:layout_weight="1"
android:background="@drawable/edittext_border"
android:padding="8sp"
android:textSize="14sp"
android:padding="12dp"
android:textSize="16sp"
android:text="发送消息后结果将在此显示"
android:textColor="@android:color/black"/>
<Button
android:id="@+id/copyButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="复制"
android:textSize="12sp"
android:layout_gravity="end"
android:layout_marginTop="4dp"/>
android:textColor="@color/text_secondary"/>
</LinearLayout>
<!-- Custom Buttons -->
<!-- 复制结果按钮 - 去掉圆角 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
android:orientation="vertical"
android:background="@color/surface"
android:padding="16dp"
android:layout_marginTop="1dp">
<Button
android:id="@+id/btnCopyResult"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="复制结果"
android:textSize="14sp"
android:layout_marginEnd="4dp"/>
android:textSize="16sp"
android:textColor="@color/primary"
android:background="@drawable/button_secondary_bg"
android:minWidth="0dp"
android:minHeight="0dp"/>
</LinearLayout>

View File

@@ -1,373 +1,420 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
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:padding="16dp">
android:background="@color/background"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Header -->
<LinearLayout
<!-- 1. 顶部导航栏 -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="24dp">
android:background="@color/surface"
android:elevation="4dp"
android:paddingStart="16dp"
android:paddingTop="12dp"
android:paddingEnd="16dp"
android:paddingBottom="12dp">
<Button
<!-- 返回按钮 -->
<ImageButton
android:id="@+id/btnBack"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="返回主界面"
android:layout_gravity="start"
android:layout_marginBottom="8dp"/>
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="返回"
android:src="@android:drawable/ic_media_previous"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- 标题和副标题容器 -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/tvAutoSaveStatus"
app:layout_constraintStart_toEndOf="@id/btnBack"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/headerTitle"
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="设置"
android:textSize="24sp"
android:textStyle="bold"
android:textColor="@android:color/black"
android:layout_marginBottom="4dp"/>
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/headerSubtitle"
android:id="@+id/tvSubtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="配置自动保存到本地"
android:textSize="14sp"
android:textColor="@android:color/darker_gray"/>
android:textColor="@color/text_hint"
android:textSize="14sp" />
</LinearLayout>
<!-- LLM Config Section -->
<!-- 自动保存状态图标 -->
<ImageView
android:id="@+id/tvAutoSaveStatus"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_check_circle"
android:tint="@color/success"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btnHome"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginEnd="8dp" />
<!-- 返回首页按钮 -->
<Button
android:id="@+id/btnHome"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:text="首页"
android:textSize="14sp"
android:textColor="@color/primary"
android:background="@drawable/button_secondary_bg"
android:minWidth="0dp"
android:minHeight="0dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- 2. 大模型配置卡片 -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
app:cardBackgroundColor="@color/surface"
app:cardCornerRadius="16dp"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="24dp">
android:padding="16dp">
<!-- 卡片标题 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="大模型配置"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="8dp"/>
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="设置 API 接口地址、密钥与模型"
android:textSize="14sp"
android:textColor="@android:color/darker_gray"
android:layout_marginBottom="16dp"/>
android:textColor="@color/text_hint"
android:textSize="14sp" />
<!-- Base URL Field -->
<!-- Base URL -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="12dp">
android:layout_marginTop="16dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Base URL"
android:textSize="14sp"
android:layout_marginBottom="4dp"/>
android:textColor="@color/text_secondary"
android:textSize="14sp" />
<EditText
android:id="@+id/etBaseUrl"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginTop="8dp"
android:background="@drawable/edittext_border"
android:hint="https://api.openai.com/v1"
android:inputType="textUri"
android:background="@drawable/edittext_border"
android:padding="8sp"/>
android:padding="12dp"
android:textColor="@color/text_secondary"
android:textColorHint="@color/text_hint"
android:textSize="16sp" />
</LinearLayout>
<!-- API Key Field -->
<!-- API Key -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="12dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
android:layout_marginTop="16dp"
android:orientation="vertical">
<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"/>
android:textColor="@color/text_secondary"
android:textSize="14sp" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginTop="8dp"
android:background="@drawable/edittext_border">
<EditText
android:id="@+id/etApiKey"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:hint="sk-..."
android:inputType="textPassword"
android:paddingStart="12dp"
android:paddingEnd="48dp"
android:textColor="@color/text_secondary"
android:textColorHint="@color/text_hint"
android:textSize="16sp" />
<ImageButton
android:id="@+id/btnToggleApiKey"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@android:drawable/ic_lock_idle_lock"
android:layout_alignParentEnd="true"
android:layout_centerInParent="true"
android:layout_marginEnd="12dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:layout_gravity="center_vertical"/>
android:contentDescription="显示/隐藏"
android:src="@android:drawable/ic_lock_idle_lock"
app:tint="@color/text_hint" />
</RelativeLayout>
</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 -->
<!-- Model -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="12dp">
android:layout_marginTop="16dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="模型"
android:textSize="14sp"
android:layout_marginBottom="4dp"/>
android:text="Model"
android:textColor="@color/text_secondary"
android:textSize="14sp" />
<EditText
android:id="@+id/etModel"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginTop="8dp"
android:background="@drawable/edittext_border"
android:hint="gpt-4o"
android:inputType="text"
android:background="@drawable/edittext_border"
android:padding="8sp"/>
android:padding="12dp"
android:textColor="@color/text_secondary"
android:textColorHint="@color/text_hint"
android:textSize="16sp" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- 3. 自定义提示词卡片 -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
app:cardBackgroundColor="@color/surface"
app:cardCornerRadius="16dp"
app:cardElevation="2dp">
<!-- Custom Headers Section -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="24dp">
android:padding="16dp">
<!-- 标题栏 -->
<LinearLayout
android:id="@+id/layoutPromptToggle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:clickable="true"
android:focusable="true">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="自定义提示词"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="设置系统级指令和角色扮演"
android:textColor="@color/text_hint"
android:textSize="14sp" />
</LinearLayout>
<ImageView
android:id="@+id/ivPromptArrow"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical"
android:src="@android:drawable/ic_media_play"
android:tint="@color/primary"
android:rotation="0" />
</LinearLayout>
<!-- 折叠内容区域 -->
<LinearLayout
android:id="@+id/layoutPromptContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone"
android:layout_marginTop="16dp">
<!-- 添加按钮 -->
<Button
android:id="@+id/btnAddPrompt"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:text="+ 添加提示词"
android:textSize="14sp"
android:backgroundTint="@color/primary"
android:textColor="@color/white" />
<!-- 提示词列表容器 -->
<LinearLayout
android:id="@+id/llPromptList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="12dp" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- 4. 自定义请求头折叠卡片 -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="0dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
app:cardBackgroundColor="@color/surface"
app:cardCornerRadius="16dp"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- 可点击的标题栏 -->
<LinearLayout
android:id="@+id/layoutHeaderToggle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:clickable="true"
android:focusable="true">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="自定义请求头"
android:textColor="@color/text_primary"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="8dp"/>
android:textStyle="bold" />
<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"/>
android:layout_marginTop="4dp"
android:text="向请求附加额外的HTTP Header"
android:textColor="@color/text_hint"
android:textSize="14sp" />
</LinearLayout>
<!-- Headers List -->
<ImageView
android:id="@+id/ivHeaderArrow"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical"
android:src="@android:drawable/ic_media_play"
android:tint="@color/primary"
android:rotation="0" />
</LinearLayout>
<!-- 折叠内容区域 -->
<LinearLayout
android:id="@+id/layoutHeaderContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone"
android:layout_marginTop="16dp">
<!-- 添加按钮 -->
<Button
android:id="@+id/btnAddHeader"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:text="+ 添加请求头"
android:textSize="14sp"
android:backgroundTint="@color/primary"
android:textColor="@color/white" />
<!-- 请求头列表容器 -->
<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"/>
android:layout_marginTop="12dp" />
</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>
</androidx.cardview.widget.CardView>
</LinearLayout>
</ScrollView>

View File

@@ -5,7 +5,7 @@
android:orientation="horizontal"
android:layout_marginBottom="8dp"
android:background="@drawable/edittext_border"
android:padding="8sp">
android:padding="8dp">
<EditText
android:id="@+id/etHeaderKey"
@@ -14,7 +14,7 @@
android:layout_weight="1"
android:hint="Header 名称"
android:inputType="text"
android:padding="4sp"/>
android:padding="4dp"/>
<View
android:layout_width="1dp"
@@ -29,7 +29,7 @@
android:layout_weight="2"
android:hint="值"
android:inputType="text"
android:padding="4sp"/>
android:padding="4dp"/>
<ImageButton
android:id="@+id/btnRemoveHeader"

View File

@@ -3,15 +3,15 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="12dp"
android:layout_marginBottom="8dp"
android:background="@drawable/edittext_border"
android:padding="12sp">
android:padding="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8sp">
android:layout_marginBottom="4dp">
<EditText
android:id="@+id/etPromptTitle"
@@ -20,7 +20,8 @@
android:layout_weight="1"
android:hint="提示词标题"
android:inputType="text"
android:padding="4sp"/>
android:padding="4dp"
android:textSize="14sp"/>
<ImageButton
android:id="@+id/btnRemovePrompt"
@@ -28,33 +29,17 @@
android:layout_height="24dp"
android:src="@android:drawable/ic_menu_close_clear_cancel"
android:background="?attr/selectableItemBackgroundBorderless"
android:layout_gravity="end"/>
android:layout_gravity="center_vertical"/>
</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"/>
android:hint="提示词内容"
android:inputType="textMultiLine"
android:minLines="3"
android:padding="4dp"
android:textSize="14sp"/>
</LinearLayout>

View File

@@ -7,8 +7,33 @@
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="foreground_color">#eFeFeF</color> <!-- 示例前景色(白色) -->
<color name="background_color">#333333</color> <!-- 示例背景色(深灰色) -->
<color name="foreground_color">#eFeFeF</color>
<color name="background_color">#333333</color>
<color name="gray_light">#CCCCCC</color>
<color name="semi_transparent_background">#19000000</color>
<!-- 核心色值 -->
<color name="primary">#42A5F5</color>
<color name="primary_variant">#1976D2</color>
<color name="secondary">#81C784</color>
<color name="secondary_variant">#4CAF50</color>
<!-- 中性色 -->
<color name="background">#F5F5F5</color>
<color name="surface">#FAFAFA</color>
<color name="surface_variant">#EEEEEE</color> <!-- 用于卡片阴影/边框 -->
<!-- 文本色 -->
<color name="text_primary">#212121</color>
<color name="text_secondary">#424242</color>
<color name="text_hint">#757575</color>
<color name="text_disabled">#BDBDBD</color>
<!-- 边框色 -->
<color name="border">#E0E0E0</color>
<!-- 状态色 -->
<color name="success">#4CAF50</color>
<color name="warning">#FF9800</color>
<color name="error">#F44336</color>
</resources>

21
test_logcat.bat Normal file
View File

@@ -0,0 +1,21 @@
@echo off
echo 测试日志系统...
echo.
echo 当前设备:
adb devices
echo.
echo 清除旧日志...
adb logcat -c
echo.
echo 等待5秒让日志系统稳定...
timeout /t 5 /nobreak >nul
echo.
echo 测试日志输出(应显示系统日志):
adb logcat -d -v time | findstr /C:"I/" | head -n 5
echo.
echo 如果看到上述日志,说明日志系统工作正常
echo 现在请在模拟器中运行APP然后按回车查看日志...
pause >nul
echo.
echo 开始监控应用日志...
adb logcat -v time -s MainActivity:* SecondActivity:*

18
view_detailed_logs.bat Normal file
View File

@@ -0,0 +1,18 @@
@echo off
echo ========================================
echo 应用日志查看工具
echo ========================================
echo.
echo 1. 清除旧日志...
adb logcat -c
echo.
echo 2. 开始监控应用日志...
echo 标签: MainActivity, SecondActivity
echo 按 Ctrl+C 停止监控
echo.
echo ========================================
echo.
adb logcat -v time -s MainActivity:* SecondActivity:*

10
view_logs.bat Normal file
View File

@@ -0,0 +1,10 @@
@echo off
echo 正在清除旧日志...
adb logcat -c
echo.
echo 正在启动日志监控MainActivity 和 SecondActivity...
echo 按 Ctrl+C 停止监控
echo.
adb logcat -s MainActivity:* SecondActivity:*