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