diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2986a42..e6b0c77 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -35,6 +35,13 @@
android:exported="false"
android:label="图片预览"
android:theme="@style/Theme.Chick_mood.NoActionBar" />
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/chick_mood/ui/adapter/MoodRecordAdapter.kt b/app/src/main/java/com/chick_mood/ui/adapter/MoodRecordAdapter.kt
index 9c379cd..4527754 100644
--- a/app/src/main/java/com/chick_mood/ui/adapter/MoodRecordAdapter.kt
+++ b/app/src/main/java/com/chick_mood/ui/adapter/MoodRecordAdapter.kt
@@ -10,6 +10,12 @@ import androidx.recyclerview.widget.RecyclerView
import com.chick_mood.data.model.Emotion
import com.chick_mood.data.model.MoodRecord
import com.daodaoshi.chick_mood.R
+import com.daodaoshi.chick_mood.ImageUtils
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import android.util.Log
/**
* 心情记录适配器 - 用于ViewPager2横向展示历史心情记录
@@ -55,6 +61,11 @@ class MoodRecordAdapter(
}
}
+ /**
+ * 获取当前数据集
+ */
+ fun getData(): List = moodRecords
+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MoodRecordViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_mood_record, parent, false)
@@ -188,9 +199,8 @@ class MoodRecordAdapter(
// 设置图片内容 - 当前模型只支持单张图片
if (record.hasImage()) {
ivSingleImage.visibility = View.VISIBLE
- // TODO: 使用Glide或其他图片加载库加载实际图片
- // 现在先使用占位符
- ivSingleImage.setImageResource(R.drawable.placeholder_background)
+ // 加载实际图片
+ loadImageFromPath(record.imagePath)
}
}
@@ -230,5 +240,27 @@ class MoodRecordAdapter(
onDetailClick(record)
}
}
+
+ /**
+ * 从文件路径加载图片
+ */
+ private fun loadImageFromPath(imagePath: String?) {
+ imagePath?.let { path ->
+ CoroutineScope(Dispatchers.IO).launch {
+ val bitmap = ImageUtils.loadBitmapFromPath(path)
+
+ withContext(Dispatchers.Main) {
+ if (bitmap != null) {
+ ivSingleImage.setImageBitmap(bitmap)
+ Log.d("MoodRecordAdapter", "卡片图片加载成功: $path")
+ } else {
+ // 如果加载失败,使用占位图
+ ivSingleImage.setImageResource(R.drawable.placeholder_background)
+ Log.w("MoodRecordAdapter", "卡片图片加载失败,使用占位图: $path")
+ }
+ }
+ }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/daodaoshi/chick_mood/DetailActivity.kt b/app/src/main/java/com/daodaoshi/chick_mood/DetailActivity.kt
index 56d7624..789e484 100644
--- a/app/src/main/java/com/daodaoshi/chick_mood/DetailActivity.kt
+++ b/app/src/main/java/com/daodaoshi/chick_mood/DetailActivity.kt
@@ -14,6 +14,7 @@ import androidx.appcompat.app.AppCompatActivity
import com.chick_mood.data.database.SimpleDatabaseManager
import com.chick_mood.data.model.Emotion
import com.chick_mood.data.model.MoodRecord
+import com.daodaoshi.chick_mood.ImageUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -39,6 +40,10 @@ class DetailActivity : AppCompatActivity() {
private var currentRecord: MoodRecord? = null
private val databaseManager = SimpleDatabaseManager.getInstance(this)
+ companion object {
+ private const val TAG = "DetailActivity"
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_detail)
@@ -93,8 +98,8 @@ class DetailActivity : AppCompatActivity() {
// 设置图片内容(如果有)
if (record.imagePath != null) {
imageContent.visibility = View.VISIBLE
- // TODO: 加载实际图片
- imageContent.setImageResource(R.drawable.ic_placeholder_image)
+ // 加载实际图片
+ loadImage(record.imagePath)
} else {
imageContent.visibility = View.GONE
}
@@ -157,8 +162,7 @@ class DetailActivity : AppCompatActivity() {
// 设置点击事件
llEdit.setOnClickListener {
dialog.dismiss()
- // TODO: 跳转到编辑页
- showToast("编辑功能待实现")
+ jumpToEditPage()
}
llFavorite.setOnClickListener {
@@ -396,10 +400,46 @@ class DetailActivity : AppCompatActivity() {
}
}
+ /**
+ * 跳转到编辑页面
+ */
+ private fun jumpToEditPage() {
+ currentRecord?.let { record ->
+ try {
+ val intent = Intent(this, EditActivity::class.java).apply {
+ putExtra("record_id", record.id)
+ }
+ startActivity(intent)
+ } catch (e: Exception) {
+ showToast("无法打开编辑页面")
+ }
+ }
+ }
+
/**
* 显示Toast消息
*/
private fun showToast(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
+
+ /**
+ * 加载并显示图片
+ */
+ private fun loadImage(imagePath: String) {
+ CoroutineScope(Dispatchers.IO).launch {
+ val bitmap = ImageUtils.loadBitmapFromPath(imagePath)
+
+ withContext(Dispatchers.Main) {
+ if (bitmap != null) {
+ imageContent.setImageBitmap(bitmap)
+ Log.d(TAG, "详情页图片加载成功: $imagePath")
+ } else {
+ // 如果加载失败,使用占位图
+ imageContent.setImageResource(R.drawable.ic_placeholder_image)
+ Log.w(TAG, "详情页图片加载失败,使用占位图: $imagePath")
+ }
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/daodaoshi/chick_mood/EditActivity.kt b/app/src/main/java/com/daodaoshi/chick_mood/EditActivity.kt
new file mode 100644
index 0000000..d0316e3
--- /dev/null
+++ b/app/src/main/java/com/daodaoshi/chick_mood/EditActivity.kt
@@ -0,0 +1,512 @@
+package com.daodaoshi.chick_mood
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import android.text.Editable
+import android.text.TextWatcher
+import android.util.Log
+import android.view.View
+import android.widget.EditText
+import android.widget.FrameLayout
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+import com.chick_mood.data.database.SimpleDatabaseManager
+import com.chick_mood.data.model.Emotion
+import com.chick_mood.data.model.MoodRecord
+import com.daodaoshi.chick_mood.ImageUtils
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import java.text.SimpleDateFormat
+import java.util.*
+
+/**
+ * 心情记录编辑页
+ * 用于编辑已存在的心情记录,支持修改文本内容和配图
+ */
+class EditActivity : AppCompatActivity() {
+
+ private lateinit var backButton: ImageView
+ private lateinit var timeText: TextView
+ private lateinit var chickImage: ImageView
+ private lateinit var moodValueText: TextView
+ private lateinit var moodText: TextView
+ private lateinit var textInput: EditText
+ private lateinit var charCountText: TextView
+ private lateinit var imageContainer: View
+ private lateinit var imagePreviewContainer: FrameLayout
+ private lateinit var imagePreview: ImageView
+ private lateinit var addImageButton: View
+ private lateinit var removeImageButton: ImageView
+ private lateinit var saveButton: TextView
+
+ private var currentRecord: MoodRecord? = null
+ private var originalText: String = ""
+ private var originalImagePath: String? = null
+ private var currentImagePath: String? = null
+ private val databaseManager = SimpleDatabaseManager.getInstance(this)
+
+ // 图片选择器
+ private val imagePickerLauncher = registerForActivityResult(
+ androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult()
+ ) { result ->
+ if (result.resultCode == Activity.RESULT_OK) {
+ result.data?.data?.let { uri ->
+ // 处理选中的图片URI,保存到应用私有存储
+ handleImageSelection(uri)
+ }
+ }
+ }
+
+ companion object {
+ private const val MAX_TEXT_LENGTH = 500
+ private const val TAG = "EditActivity"
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ Log.d(TAG, "EditActivity onCreate started")
+
+ setContentView(R.layout.activity_edit)
+ Log.d(TAG, "Layout set successfully")
+
+ try {
+ initViews()
+ Log.d(TAG, "Views initialized")
+
+ loadData()
+ Log.d(TAG, "Data loading started")
+
+ setupClickListeners()
+ setupTextWatcher()
+ Log.d(TAG, "Listeners setup completed")
+ } catch (e: Exception) {
+ Log.e(TAG, "Error in onCreate", e)
+ showToast("初始化失败")
+ finish()
+ }
+ }
+
+ /**
+ * 初始化视图组件
+ */
+ private fun initViews() {
+ try {
+ backButton = findViewById(R.id.back_button)
+ timeText = findViewById(R.id.time_text)
+ chickImage = findViewById(R.id.chick_image)
+ moodValueText = findViewById(R.id.mood_value_text)
+ moodText = findViewById(R.id.mood_text)
+ textInput = findViewById(R.id.text_input)
+ charCountText = findViewById(R.id.char_count_text)
+ imageContainer = findViewById(R.id.image_container)
+ imagePreviewContainer = findViewById(R.id.image_preview)
+ imagePreview = findViewById(R.id.preview_image)
+ addImageButton = findViewById(R.id.add_image_button)
+ removeImageButton = findViewById(R.id.remove_image_button)
+ saveButton = findViewById(R.id.save_button)
+ Log.d(TAG, "All views found successfully")
+ } catch (e: Exception) {
+ Log.e(TAG, "Error initializing views", e)
+ throw e
+ }
+ }
+
+ /**
+ * 加载心情记录数据
+ */
+ private fun loadData() {
+ val recordId = intent.getLongExtra("record_id", -1L)
+ if (recordId == -1L) {
+ finish()
+ return
+ }
+
+ loadRecordFromDatabase(recordId)
+ }
+
+ /**
+ * 从数据库加载心情记录
+ */
+ private fun loadRecordFromDatabase(recordId: Long) {
+ CoroutineScope(Dispatchers.IO).launch {
+ try {
+ val record = databaseManager.getMoodRecord(recordId)
+
+ withContext(Dispatchers.Main) {
+ if (record != null) {
+ currentRecord = record
+ originalText = record.textContent ?: ""
+ originalImagePath = record.imagePath
+ currentImagePath = record.imagePath
+ displayRecord(record)
+ setupImageState()
+ } else {
+ showToast("记录不存在")
+ finish()
+ }
+ }
+ } catch (e: Exception) {
+ withContext(Dispatchers.Main) {
+ showToast("加载记录失败")
+ finish()
+ }
+ }
+ }
+ }
+
+ /**
+ * 显示心情记录
+ */
+ private fun displayRecord(record: MoodRecord) {
+ // 设置时间显示
+ timeText.text = formatTime(record.timestamp)
+
+ // 设置小鸡形象
+ chickImage.setImageResource(getEmotionIconResource(record.emotion))
+
+ // 设置心情值和文案
+ moodValueText.text = record.moodIntensity.toString()
+ moodText.text = generateMoodText(record.moodIntensity, record.emotion)
+
+ // 设置文本内容
+ textInput.setText(record.textContent ?: "")
+ updateCharCount()
+
+ // 图片状态在setupImageState中处理
+ }
+
+ /**
+ * 设置图片状态
+ */
+ private fun setupImageState() {
+ if (currentImagePath != null) {
+ // 有图片状态
+ addImageButton.visibility = View.GONE
+ imagePreviewContainer.visibility = View.VISIBLE
+ removeImageButton.visibility = View.VISIBLE
+ Log.d(TAG, "设置有图片状态,图片路径: $currentImagePath")
+ // 加载实际图片
+ val imagePath = currentImagePath ?: ""
+ loadImage(imagePath)
+ } else {
+ // 无图片状态
+ addImageButton.visibility = View.VISIBLE
+ imagePreviewContainer.visibility = View.GONE
+ removeImageButton.visibility = View.GONE
+ Log.d(TAG, "设置无图片状态")
+ }
+ }
+
+ /**
+ * 设置点击监听器
+ */
+ private fun setupClickListeners() {
+ backButton.setOnClickListener {
+ handleBackPressed()
+ }
+
+ addImageButton.setOnClickListener {
+ pickImageFromGallery()
+ }
+
+ removeImageButton.setOnClickListener {
+ removeImage()
+ }
+
+ imagePreview.setOnClickListener {
+ currentImagePath?.let { previewImage(it) }
+ }
+
+ saveButton.setOnClickListener {
+ saveRecord()
+ }
+ }
+
+ /**
+ * 设置文本监听器
+ */
+ private fun setupTextWatcher() {
+ textInput.addTextChangedListener(object : TextWatcher {
+ override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
+
+ override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
+ updateCharCount()
+ }
+
+ override fun afterTextChanged(s: Editable?) {
+ // 检查文本是否超过最大长度
+ if (s != null && s.length > MAX_TEXT_LENGTH) {
+ textInput.setText(s.substring(0, MAX_TEXT_LENGTH))
+ textInput.setSelection(MAX_TEXT_LENGTH)
+ }
+ }
+ })
+ }
+
+ /**
+ * 更新字符计数
+ */
+ private fun updateCharCount() {
+ val currentLength = textInput.text?.length ?: 0
+ charCountText.text = "$currentLength/$MAX_TEXT_LENGTH"
+
+ // 超过限制时显示红色警告
+ if (currentLength > MAX_TEXT_LENGTH) {
+ charCountText.setTextColor(getColor(R.color.error_color))
+ } else {
+ charCountText.setTextColor(getColor(R.color.text_secondary))
+ }
+ }
+
+ /**
+ * 处理返回键点击
+ */
+ private fun handleBackPressed() {
+ if (hasUnsavedChanges()) {
+ showExitConfirmDialog()
+ } else {
+ finish()
+ }
+ }
+
+ /**
+ * 检查是否有未保存的更改
+ */
+ private fun hasUnsavedChanges(): Boolean {
+ val currentText = textInput.text?.toString() ?: ""
+ return currentText != originalText || currentImagePath != originalImagePath
+ }
+
+ /**
+ * 显示退出确认对话框
+ */
+ private fun showExitConfirmDialog() {
+ val dialogView = layoutInflater.inflate(R.layout.dialog_exit_edit_confirm, null)
+ val dialog = android.app.AlertDialog.Builder(this)
+ .setView(dialogView)
+ .create()
+
+ dialog.window?.setBackgroundDrawableResource(android.R.color.transparent)
+ dialog.setCanceledOnTouchOutside(false)
+
+ val tvContinueEdit = dialogView.findViewById(R.id.tv_continue_edit)
+ val tvExit = dialogView.findViewById(R.id.tv_exit)
+
+ tvContinueEdit.setOnClickListener {
+ dialog.dismiss()
+ }
+
+ tvExit.setOnClickListener {
+ dialog.dismiss()
+ finish()
+ }
+
+ dialog.show()
+ }
+
+ /**
+ * 从相册选择图片
+ */
+ private fun pickImageFromGallery() {
+ try {
+ val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
+ type = "image/*"
+ putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("image/jpeg", "image/png"))
+ putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false)
+ }
+ imagePickerLauncher.launch(intent)
+ } catch (e: Exception) {
+ showToast("无法打开相册")
+ }
+ }
+
+ /**
+ * 移除图片
+ */
+ private fun removeImage() {
+ // 先删除图片文件
+ removeImageFile()
+ // 然后清空路径并更新UI
+ currentImagePath = null
+ setupImageState()
+ }
+
+ /**
+ * 预览图片
+ */
+ private fun previewImage(imagePath: String) {
+ val intent = Intent(this, ImagePreviewActivity::class.java).apply {
+ putExtra("image_path", imagePath)
+ }
+ startActivity(intent)
+ }
+
+ /**
+ * 保存记录
+ */
+ private fun saveRecord() {
+ currentRecord?.let { record ->
+ val updatedText = textInput.text?.toString()?.trim() ?: ""
+
+ CoroutineScope(Dispatchers.IO).launch {
+ try {
+ val updatedRecord = record.copy(
+ textContent = if (updatedText.isEmpty()) null else updatedText,
+ imagePath = currentImagePath,
+ timestamp = record.timestamp // 保持原始时间戳不变
+ )
+
+ databaseManager.updateMoodRecord(updatedRecord)
+
+ withContext(Dispatchers.Main) {
+ showToast("保存成功")
+
+ // 直接跳转到首页并传递编辑的记录ID用于定位
+ val homeIntent = Intent(this@EditActivity, MainActivitySimple::class.java).apply {
+ putExtra("edited_record_id", record.id)
+ flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
+ }
+ startActivity(homeIntent)
+ finish()
+ }
+ } catch (e: Exception) {
+ withContext(Dispatchers.Main) {
+ showToast("保存失败")
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * 格式化时间显示
+ */
+ private fun formatTime(timestamp: Long): String {
+ val today = Calendar.getInstance().apply {
+ set(Calendar.HOUR_OF_DAY, 0)
+ set(Calendar.MINUTE, 0)
+ set(Calendar.SECOND, 0)
+ set(Calendar.MILLISECOND, 0)
+ }.timeInMillis
+
+ val yesterday = today - 24 * 60 * 60 * 1000L
+
+ val date = Date(timestamp)
+ val timeFormat = SimpleDateFormat("HH:mm", Locale.getDefault())
+ val timeString = timeFormat.format(date)
+
+ return when {
+ timestamp >= today -> "今天 $timeString"
+ timestamp >= yesterday -> "昨天 $timeString"
+ else -> {
+ val dateFormat = SimpleDateFormat("M月d日 HH:mm", Locale.getDefault())
+ dateFormat.format(date)
+ }
+ }
+ }
+
+ /**
+ * 生成心情文案
+ */
+ private fun generateMoodText(moodValue: Int, emotion: Emotion): String {
+ val degree = when {
+ moodValue <= 20 -> "有些"
+ moodValue <= 40 -> "非常"
+ moodValue <= 60 -> "超级"
+ moodValue <= 80 -> "这也太"
+ else -> "完全无法控制"
+ }
+
+ return "$degree ${emotion.displayName}"
+ }
+
+ /**
+ * 获取情绪图标资源
+ */
+ private fun getEmotionIconResource(emotion: Emotion): Int {
+ return when (emotion) {
+ Emotion.HAPPY -> R.drawable.ic_emotion_happy
+ Emotion.ANGRY -> R.drawable.ic_emotion_angry
+ Emotion.SAD -> R.drawable.ic_emotion_sad
+ Emotion.WORRIED -> R.drawable.ic_emotion_worried
+ Emotion.LONELY -> R.drawable.ic_emotion_lonely
+ Emotion.SCARED -> R.drawable.ic_emotion_scared
+ }
+ }
+
+ /**
+ * 显示Toast消息
+ */
+ private fun showToast(message: String) {
+ android.widget.Toast.makeText(this, message, android.widget.Toast.LENGTH_SHORT).show()
+ }
+
+ /**
+ * 处理图片选择
+ */
+ private fun handleImageSelection(uri: android.net.Uri) {
+ CoroutineScope(Dispatchers.IO).launch {
+ try {
+ // 将图片保存到应用私有存储
+ val savedPath = ImageUtils.saveImageToInternalStorage(this@EditActivity, uri)
+
+ withContext(Dispatchers.Main) {
+ if (savedPath != null) {
+ currentImagePath = savedPath
+ setupImageState()
+ Log.d(TAG, "图片保存成功: $savedPath")
+ } else {
+ showToast("保存图片失败")
+ }
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "处理图片选择失败", e)
+ withContext(Dispatchers.Main) {
+ showToast("处理图片失败")
+ }
+ }
+ }
+ }
+
+ /**
+ * 加载并显示图片
+ */
+ private fun loadImage(imagePath: String) {
+ CoroutineScope(Dispatchers.IO).launch {
+ val bitmap = ImageUtils.loadBitmapFromPath(imagePath)
+
+ withContext(Dispatchers.Main) {
+ if (bitmap != null) {
+ imagePreview.setImageBitmap(bitmap)
+ Log.d(TAG, "图片加载成功: $imagePath")
+ } else {
+ // 如果加载失败,使用占位图
+ imagePreview.setImageResource(R.drawable.ic_placeholder_image)
+ Log.w(TAG, "图片加载失败,使用占位图: $imagePath")
+ }
+ }
+ }
+ }
+
+ /**
+ * 移除图片文件
+ */
+ private fun removeImageFile() {
+ currentImagePath?.let { path ->
+ val success = ImageUtils.deleteImageFile(path)
+ if (success) {
+ Log.d(TAG, "图片文件删除成功: $path")
+ } else {
+ Log.w(TAG, "图片文件删除失败: $path")
+ }
+ }
+ }
+
+ override fun onBackPressed() {
+ handleBackPressed()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/daodaoshi/chick_mood/ImagePreviewActivity.kt b/app/src/main/java/com/daodaoshi/chick_mood/ImagePreviewActivity.kt
index e4d7f19..2b9f6b6 100644
--- a/app/src/main/java/com/daodaoshi/chick_mood/ImagePreviewActivity.kt
+++ b/app/src/main/java/com/daodaoshi/chick_mood/ImagePreviewActivity.kt
@@ -1,11 +1,17 @@
package com.daodaoshi.chick_mood
import android.os.Bundle
+import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import com.daodaoshi.chick_mood.databinding.ActivityImagePreviewBinding
+import com.daodaoshi.chick_mood.ImageUtils
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
/**
* 图片预览Activity
@@ -52,9 +58,8 @@ class ImagePreviewActivity : AppCompatActivity() {
// 获取图片路径并加载图片
val imagePath = intent.getStringExtra("image_path")
if (imagePath != null) {
- // TODO: 使用图片加载库加载实际图片
- // 这里暂时使用占位图
- binding.imagePreview.setImageResource(R.drawable.ic_placeholder_image)
+ // 加载实际图片
+ loadImage(imagePath)
} else {
finish() // 没有图片路径则关闭
}
@@ -101,6 +106,26 @@ class ImagePreviewActivity : AppCompatActivity() {
}
}
+ /**
+ * 加载并显示图片
+ */
+ private fun loadImage(imagePath: String) {
+ CoroutineScope(Dispatchers.IO).launch {
+ val bitmap = ImageUtils.loadBitmapFromPath(imagePath)
+
+ withContext(Dispatchers.Main) {
+ if (bitmap != null) {
+ binding.imagePreview.setImageBitmap(bitmap)
+ Log.d("ImagePreviewActivity", "预览页图片加载成功: $imagePath")
+ } else {
+ // 如果加载失败,使用占位图
+ binding.imagePreview.setImageResource(R.drawable.ic_placeholder_image)
+ Log.w("ImagePreviewActivity", "预览页图片加载失败,使用占位图: $imagePath")
+ }
+ }
+ }
+ }
+
/**
* 处理双击缩放
*/
diff --git a/app/src/main/java/com/daodaoshi/chick_mood/ImageUtils.kt b/app/src/main/java/com/daodaoshi/chick_mood/ImageUtils.kt
new file mode 100644
index 0000000..b8a2fda
--- /dev/null
+++ b/app/src/main/java/com/daodaoshi/chick_mood/ImageUtils.kt
@@ -0,0 +1,108 @@
+package com.daodaoshi.chick_mood
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.net.Uri
+import android.provider.MediaStore
+import android.util.Log
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import java.io.File
+import java.io.FileOutputStream
+import java.io.IOException
+import java.io.InputStream
+import java.io.OutputStream
+
+/**
+ * 图片工具类
+ * 用于处理图片加载、保存和URI转换
+ */
+object ImageUtils {
+
+ private const val TAG = "ImageUtils"
+
+ /**
+ * 将URI转换为文件路径
+ */
+ suspend fun uriToFilePath(context: Context, uri: Uri): String? {
+ return withContext(Dispatchers.IO) {
+ try {
+ val filePathColumn = arrayOf(MediaStore.Images.Media.DATA)
+ context.contentResolver.query(uri, filePathColumn, null, null, null)?.use { cursor ->
+ val columnIndex = cursor?.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
+ cursor?.moveToFirst()
+ cursor?.getString(columnIndex ?: 0)
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "转换URI到文件路径失败", e)
+ null
+ }
+ }
+ }
+
+ /**
+ * 将URI保存到应用私有存储
+ */
+ suspend fun saveImageToInternalStorage(context: Context, uri: Uri): String? {
+ return withContext(Dispatchers.IO) {
+ try {
+ val inputStream: InputStream? = context.contentResolver.openInputStream(uri)
+ val fileName = "image_${System.currentTimeMillis()}.jpg"
+ val file = File(context.filesDir, fileName)
+
+ inputStream?.use { input ->
+ FileOutputStream(file).use { output ->
+ input.copyTo(output)
+ }
+ }
+
+ Log.d(TAG, "图片保存成功: ${file.absolutePath}")
+ file.absolutePath
+ } catch (e: IOException) {
+ Log.e(TAG, "保存图片失败", e)
+ null
+ }
+ }
+ }
+
+ /**
+ * 从文件路径加载Bitmap
+ */
+ suspend fun loadBitmapFromPath(imagePath: String): Bitmap? {
+ return withContext(Dispatchers.IO) {
+ try {
+ val file = File(imagePath)
+ if (file.exists()) {
+ BitmapFactory.decodeFile(file.absolutePath)
+ } else {
+ Log.w(TAG, "图片文件不存在: $imagePath")
+ null
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "加载图片失败", e)
+ null
+ }
+ }
+ }
+
+ /**
+ * 删除图片文件
+ */
+ fun deleteImageFile(imagePath: String): Boolean {
+ return try {
+ val file = File(imagePath)
+ if (file.exists()) {
+ file.delete()
+ Log.d(TAG, "图片删除成功: $imagePath")
+ true
+ } else {
+ Log.w(TAG, "图片文件不存在: $imagePath")
+ true
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "删除图片失败", e)
+ false
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/daodaoshi/chick_mood/MainActivitySimple.kt b/app/src/main/java/com/daodaoshi/chick_mood/MainActivitySimple.kt
index 6874a5a..a922f7e 100644
--- a/app/src/main/java/com/daodaoshi/chick_mood/MainActivitySimple.kt
+++ b/app/src/main/java/com/daodaoshi/chick_mood/MainActivitySimple.kt
@@ -41,6 +41,7 @@ class MainActivitySimple : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var timeFormatter: SimpleDateFormat
private lateinit var moodRecordAdapter: MoodRecordAdapter
+ private var recordIdToLocate: Long = -1L
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -59,14 +60,17 @@ class MainActivitySimple : AppCompatActivity() {
// 初始化ViewPager2适配器
initMoodRecordAdapter()
- // 运行数据库测试
- runDatabaseTest()
+ // 数据库测试已禁用,避免创建额外的测试记录
+ // runDatabaseTest()
// 创建测试数据(如果数据库为空)
createTestDataIfNeeded()
// 加载历史记录数据
loadMoodRecords()
+
+ // 检查是否有编辑完成返回的记录需要定位
+ checkAndLocateEditedRecordFromIntent()
}
/**
@@ -182,7 +186,7 @@ class MainActivitySimple : AppCompatActivity() {
)
val dbManager = SimpleDatabaseManager.getInstance(this@MainActivitySimple)
- dbManager.createMoodRecord(
+ val insertedId = dbManager.createMoodRecord(
emotion = newRecord.emotion,
intensity = newRecord.moodIntensity,
noteText = newRecord.textContent ?: "",
@@ -190,6 +194,8 @@ class MainActivitySimple : AppCompatActivity() {
)
withContext(Dispatchers.Main) {
+ // 设置需要定位的新记录ID
+ recordIdToLocate = insertedId
// 重新加载数据以显示新记录
loadMoodRecords()
}
@@ -265,8 +271,8 @@ class MainActivitySimple : AppCompatActivity() {
override fun onResume() {
super.onResume()
- // TODO: 每次进入页面时更新时间 - 暂时注释掉
- // updateTimeIndicator(System.currentTimeMillis())
+ // 每次返回首页时重新加载数据,确保显示最新的数据库状态
+ loadMoodRecords()
}
/**
@@ -323,6 +329,9 @@ class MainActivitySimple : AppCompatActivity() {
} else {
showContentStateUI()
moodRecordAdapter.updateData(records)
+
+ // 检查是否需要定位记录
+ checkAndLocateEditedRecord()
}
}
@@ -581,6 +590,81 @@ class MainActivitySimple : AppCompatActivity() {
)
}
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: android.content.Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+
+ if (resultCode == RESULT_OK) {
+ // 处理编辑页面返回的结果
+ data?.getLongExtra("edited_record_id", -1L)?.let { editedRecordId: Long ->
+ if (editedRecordId != -1L) {
+ // 找到编辑的记录在列表中的位置并定位
+ locateRecordInList(editedRecordId)
+ }
+ }
+ }
+ }
+
+ /**
+ * 在列表中定位记录
+ */
+ private fun locateRecordInList(recordId: Long) {
+ try {
+ Log.d(TAG, "开始定位记录ID: $recordId")
+
+ // 确保ViewPager2已经准备好
+ binding.vpHistoryRecords.post {
+ // 从适配器获取最新数据
+ val records = moodRecordAdapter.getData()
+ Log.d(TAG, "当前适配器数据量: ${records.size}")
+
+ val position = records.indexOfFirst { record -> record.id == recordId }
+ Log.d(TAG, "查找记录ID $recordId,找到位置: $position")
+
+ if (position != -1 && position < moodRecordAdapter.itemCount) {
+ // 使用setCurrentItem定位,第二个参数为true表示平滑滚动
+ binding.vpHistoryRecords.setCurrentItem(position, true)
+ Log.d(TAG, "已定位到位置: $position")
+
+ // 延迟一点再次确认定位,确保定位成功
+ binding.vpHistoryRecords.postDelayed({
+ val currentPosition = binding.vpHistoryRecords.currentItem
+ Log.d(TAG, "定位后当前位置: $currentPosition")
+ if (currentPosition != position) {
+ Log.w(TAG, "定位可能不准确,尝试重新定位")
+ binding.vpHistoryRecords.setCurrentItem(position, true)
+ }
+ }, 300)
+ } else {
+ Log.w(TAG, "未找到记录ID: $recordId,或位置超出范围")
+ }
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "定位记录失败", e)
+ }
+ }
+
+ /**
+ * 检查是否有编辑完成返回的记录需要定位
+ */
+ private fun checkAndLocateEditedRecordFromIntent() {
+ val editedRecordId = intent.getLongExtra("edited_record_id", -1L)
+ if (editedRecordId != -1L) {
+ Log.d(TAG, "检测到编辑完成的记录ID: $editedRecordId")
+ recordIdToLocate = editedRecordId
+ }
+ }
+
+ /**
+ * 检查是否需要定位记录(在数据加载完成后调用)
+ */
+ private fun checkAndLocateEditedRecord() {
+ if (recordIdToLocate != -1L) {
+ Log.d(TAG, "开始定位记录ID: $recordIdToLocate")
+ locateRecordInList(recordIdToLocate)
+ recordIdToLocate = -1L // 重置
+ }
+ }
+
override fun onDestroy() {
super.onDestroy()
// TODO: 清理资源 - 暂时注释掉
diff --git a/app/src/main/res/drawable/ic_add_photo.xml b/app/src/main/res/drawable/ic_add_photo.xml
new file mode 100644
index 0000000..34681a0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_add_photo.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/shape_button_primary.xml b/app/src/main/res/drawable/shape_button_primary.xml
new file mode 100644
index 0000000..96354ea
--- /dev/null
+++ b/app/src/main/res/drawable/shape_button_primary.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/shape_edit_text_background.xml b/app/src/main/res/drawable/shape_edit_text_background.xml
new file mode 100644
index 0000000..a9cbd4c
--- /dev/null
+++ b/app/src/main/res/drawable/shape_edit_text_background.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_edit.xml b/app/src/main/res/layout/activity_edit.xml
new file mode 100644
index 0000000..945258c
--- /dev/null
+++ b/app/src/main/res/layout/activity_edit.xml
@@ -0,0 +1,231 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_exit_edit_confirm.xml b/app/src/main/res/layout/dialog_exit_edit_confirm.xml
new file mode 100644
index 0000000..8affb43
--- /dev/null
+++ b/app/src/main/res/layout/dialog_exit_edit_confirm.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/claude.md b/claude.md
index 1e66df2..220248a 100644
--- a/claude.md
+++ b/claude.md
@@ -6,9 +6,9 @@
**项目名称:** 别摇小鸡心情记录App
**开发平台:** Android原生(Kotlin)
-**当前版本:** V1.2.0
-**更新日期:** 2025-10-23
-**开发状态:** ✅ 详情页功能完成,数据一致性完美
+**当前版本:** V1.3.0
+**更新日期:** 2025-10-24
+**开发状态:** ✅ 编辑页功能完成,图片显示系统完善
---
@@ -32,6 +32,31 @@
- ✅ 操作按钮功能(收藏、分享、查看详情)
- ✅ ViewPager2横向滑动展示
- ✅ 顶部按钮基础功能(更多、统计)
+
+**V1.2.0 详情页功能:**
+- ✅ 详情页基础布局和UI组件
+- ✅ 心情文案生成逻辑(程度+心情类型)
+- ✅ 更多操作弹窗(编辑、收藏、分享、删除)
+- ✅ 删除功能和二次确认弹窗
+- ✅ 收藏功能和状态持久化存储
+- ✅ 分享功能集成(系统分享)
+- ✅ 图片放大查看功能
+- ✅ 首页跳转逻辑完善
+- ✅ 真实数据库集成
+- ✅ 测试数据生成
+- ✅ 数据一致性保证
+
+**V1.3.0 编辑页功能:**
+- ✅ 编辑页基础布局和UI组件
+- ✅ 返回键退出确认逻辑
+- ✅ 文本输入框功能(最大500字符,支持emoji)
+- ✅ 图片选择和预览功能(系统相册,单图)
+- ✅ 保存功能和首页定位逻辑
+- ✅ 详情页编辑跳转集成
+- ✅ 图片显示系统完善(首页、详情页、编辑页、预览页)
+- ✅ 图片选择、保存、加载、删除完整流程
+- ✅ 定位机制优化(编辑和新增记录自动定位)
+- ✅ 数据一致性修复(禁用额外测试记录)
- 📋 摇晃检测与心情值计算(待开发)
- 📋 小鸡动画响应系统(待开发)
@@ -94,14 +119,27 @@
- **模块5.6** ✅ 图片放大查看功能
- **模块5.7** ✅ 分享功能集成(系统分享)
+### ✅ 已完成模块
+
+**阶段6:编辑页功能实现**
+- **模块6.1** ✅ 编辑页基础布局和UI组件
+- **模块6.2** ✅ 返回键退出确认逻辑
+- **模块6.3** ✅ 文本输入框功能(最大500字符,支持emoji)
+- **模块6.4** ✅ 图片选择和预览功能(系统相册,单图)
+- **模块6.5** ✅ 保存功能和首页定位逻辑
+- **模块6.6** ✅ 详情页编辑跳转集成
+- **模块6.7** ✅ 图片显示系统完善(全链路支持)
+- **模块6.8** ✅ 定位机制优化(编辑和新增记录自动定位)
+- **模块6.9** ✅ 数据一致性修复(禁用额外测试记录)
+
### 🔄 开发中模块
-**阶段6:核心功能完善(下一步重点)**
-- **模块6.1** 📋 摇晃检测功能实现
-- **模块6.2** 📋 心情值计算逻辑
-- **模块6.3** 📋 小鸡动画响应系统
-- **模块6.4** 📋 侧边抽屉功能(更多按钮)
-- **模块6.5** 📋 统计页面实现(统计按钮)
+**阶段7:核心功能完善(下一步重点)**
+- **模块7.1** 📋 摇晃检测功能实现
+- **模块7.2** 📋 心情值计算逻辑
+- **模块7.3** 📋 小鸡动画响应系统
+- **模块7.4** 📋 侧边抽屉功能(更多按钮)
+- **模块7.5** 📋 统计页面实现(统计按钮)
---
@@ -287,6 +325,19 @@ Chick_Mood/
## 🔄 更新记录
+**2025-10-24 (V1.3.0 编辑页功能完成)**
+- ✅ **编辑页完整实现**:包含基础布局、UI组件和退出确认机制
+- ✅ **返回键退出确认逻辑**:检测编辑行为,弹出二次确认弹窗
+- ✅ **文本输入功能**:支持最大500字符、emoji输入、实时字符计数
+- ✅ **图片选择和预览功能**:系统相册单图选择、真实图片显示、删除功能
+- ✅ **保存功能和首页定位**:保存后直接返回首页,自动定位到编辑/新增记录
+- ✅ **详情页编辑跳转**:完整集成编辑页跳转逻辑
+- ✅ **图片显示系统完善**:首页卡片、详情页、编辑页、预览页全链路真实图片显示
+- ✅ **图片工具类实现**:ImageUtils提供完整的图片保存、加载、删除API
+- ✅ **定位机制优化**:编辑保存后和新增记录后自动定位,双重验证确保准确性
+- ✅ **数据一致性修复**:禁用数据库测试功能,避免产生额外测试记录
+- ✅ **主题兼容性修复**:解决TextInputLayout与AppCompat主题冲突问题
+
**2025-10-23 (V1.2.0 详情页功能完成)**
- ✅ **详情页完整实现**:包含基础布局、UI组件和四种内容状态支持
- ✅ **心情文案生成逻辑**:根据心情值动态生成"程度+心情类型"文案(0-20有些→20-40非常→40-60超级→60-80这也太→80-100完全无法控制)
@@ -372,4 +423,46 @@ Chick_Mood/
---
+## 📄 编辑页功能详细说明
+
+### 🎯 功能概述
+编辑页是用于修改已存在心情记录的页面,支持编辑文本内容和配图,但核心的情绪类型和心情值不可修改,确保记录的原始性和一致性。
+
+### 📱 页面结构
+1. **顶部导航栏**
+ - 返回按钮:点击返回上一页,有编辑行为时弹出确认弹窗
+ - 时间显示:记录创建时间(格式:今天/昨天 HH:mm,不可修改)
+
+2. **只读内容区域**
+ - 小鸡形象:根据情绪类型显示对应图标(不可修改)
+ - 心情值:0-100数值显示(不可修改)
+ - 心情文案:动态生成的"程度+心情类型"组合(不可修改)
+
+3. **可编辑区域**
+ - 文本输入框:最大500字符,支持emoji,固定高度可滚动
+ - 配图区域:支持系统相册单图选择、预览和删除
+
+4. **保存按钮**
+ - 位置:页面底部固定
+ - 功能:保存编辑内容并返回首页,自动定位到编辑记录
+
+### 🔄 交互逻辑
+- **退出确认机制**:检测文本或图片变化,弹出二次确认弹窗
+- **图片管理**:无图片时显示添加占位符,有图片时显示缩略图和删除按钮
+- **保存流程**:收集编辑数据 → 更新数据库 → 返回首页 → 自动定位
+- **交互反馈**:保存成功Toast提示"保存成功",返回动画向左推动
+
+### 📝 编辑规范
+- **可编辑内容**:文本描述(最大500字符)、配图(单张,系统相册选择)
+- **不可编辑内容**:情绪类型、心情值、时间、心情文案
+- **文本输入**:支持emoji,固定高度输入框,支持垂直滚动
+- **图片处理**:暂时不做大小和压缩限制,使用系统默认加载策略
+
+### 🎨 状态支持
+- **基础状态**:无文本无图片的初始编辑状态
+- **文本编辑状态**:有文本无图片的编辑状态
+- **完整编辑状态**:有文本有图片的完整编辑状态
+
+---
+
*此文档将随着开发进展持续更新,记录重要的技术决策和进度变化。*
\ No newline at end of file
diff --git a/project_img/编辑页.png b/project_img/编辑页.png
deleted file mode 100644
index af9a2e8..0000000
Binary files a/project_img/编辑页.png and /dev/null differ
diff --git a/project_img/编辑页/无文本无图片@1x.png b/project_img/编辑页/无文本无图片@1x.png
new file mode 100644
index 0000000..af65a22
Binary files /dev/null and b/project_img/编辑页/无文本无图片@1x.png differ
diff --git a/project_img/编辑页/有文本输入有图片@1x.png b/project_img/编辑页/有文本输入有图片@1x.png
new file mode 100644
index 0000000..fefa123
Binary files /dev/null and b/project_img/编辑页/有文本输入有图片@1x.png differ
diff --git a/project_img/编辑页/未保存直接退出@1x.png b/project_img/编辑页/未保存直接退出@1x.png
new file mode 100644
index 0000000..e5e1bb3
Binary files /dev/null and b/project_img/编辑页/未保存直接退出@1x.png differ