feat: 用户上报任务显示图片附件
- 新建 PicItem 数据类,uploadPic 改为强类型 - 布局加 picContainer 横排展示区 - showPictures 方法:最多3张缩略图横排,HTTP 加载 - 三个状态页面都显示图片(和语音同级) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
12
app/src/main/java/com/xiaoqu/watch/data/task/PicItem.kt
Normal file
12
app/src/main/java/com/xiaoqu/watch/data/task/PicItem.kt
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.xiaoqu.watch.data.task
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
/**
|
||||
* 图片附件数据类
|
||||
* 用户上报任务可能包含图片描述
|
||||
*/
|
||||
data class PicItem(
|
||||
/** 图片 URL */
|
||||
@SerializedName("url") val url: String = ""
|
||||
)
|
||||
@@ -54,8 +54,8 @@ data class TaskDetail(
|
||||
@SerializedName("standard") val standard: String = "",
|
||||
/** 任务要求(黄色提示) */
|
||||
@SerializedName("taskRequire") val taskRequire: String? = null,
|
||||
/** 图片附件 */
|
||||
@SerializedName("uploadPic") val uploadPic: List<Any>? = null,
|
||||
/** 图片附件(用户上报任务可能包含图片) */
|
||||
@SerializedName("uploadPic") val uploadPic: List<PicItem>? = null,
|
||||
/** 语音附件(用户上报任务可能包含语音描述) */
|
||||
@SerializedName("voice") val voice: List<VoiceItem>? = null,
|
||||
/** 打卡标志 */
|
||||
@@ -67,6 +67,9 @@ data class TaskDetail(
|
||||
/** 是否有语音附件(用户上报任务) */
|
||||
val hasVoice: Boolean get() = !voice.isNullOrEmpty()
|
||||
|
||||
/** 是否有图片附件(用户上报任务) */
|
||||
val hasPictures: Boolean get() = !uploadPic.isNullOrEmpty()
|
||||
|
||||
/** 地点显示文字(多个用逗号分隔) */
|
||||
val positionText: String get() = taskPositions?.joinToString(",") ?: ""
|
||||
|
||||
|
||||
@@ -260,6 +260,8 @@ class TaskListFragment : BaseFragment<FragmentTaskListBinding>() {
|
||||
binding.tvPosition.visibility = View.GONE
|
||||
binding.tvTimeInfo.visibility = View.GONE
|
||||
binding.tvPoints.visibility = View.GONE
|
||||
binding.picContainer.visibility = View.GONE
|
||||
binding.picContainer.removeAllViews()
|
||||
binding.tvNote.visibility = View.GONE
|
||||
binding.btnVoice.visibility = View.GONE
|
||||
binding.blockGoWhere.visibility = View.GONE
|
||||
@@ -284,6 +286,7 @@ class TaskListFragment : BaseFragment<FragmentTaskListBinding>() {
|
||||
// 备注/描述
|
||||
showNote(detail)
|
||||
showVoice(detail)
|
||||
showPictures(detail)
|
||||
}
|
||||
|
||||
// ===== 待打卡:地点最优先,其他次要 =====
|
||||
@@ -314,6 +317,7 @@ class TaskListFragment : BaseFragment<FragmentTaskListBinding>() {
|
||||
// 备注和语音放在指引块后面(次要信息)
|
||||
showNote(detail)
|
||||
showVoice(detail)
|
||||
showPictures(detail)
|
||||
}
|
||||
|
||||
// ===== 进行中/待完成:地点+打卡时间+完成指引 =====
|
||||
@@ -331,6 +335,7 @@ class TaskListFragment : BaseFragment<FragmentTaskListBinding>() {
|
||||
}
|
||||
showNote(detail)
|
||||
showVoice(detail)
|
||||
showPictures(detail)
|
||||
if (!detail.confirmTime.isNullOrEmpty()) {
|
||||
binding.tvCheckinTime.text = "${detail.confirmTime} 已打卡"
|
||||
binding.tvCheckinTime.visibility = View.VISIBLE
|
||||
@@ -404,6 +409,62 @@ class TaskListFragment : BaseFragment<FragmentTaskListBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示图片缩略图(用户上报任务 taskType=3/4 可能有图片附件)
|
||||
* 横排展示,最多3张,点击查看大图
|
||||
*/
|
||||
private fun showPictures(detail: TaskDetail) {
|
||||
if (!detail.hasPictures) return
|
||||
|
||||
val pics = detail.uploadPic!!.filter { it.url.isNotEmpty() }
|
||||
if (pics.isEmpty()) return
|
||||
|
||||
binding.picContainer.visibility = View.VISIBLE
|
||||
binding.picContainer.removeAllViews()
|
||||
|
||||
// 计算每张图片尺寸:容器宽度 / 最多3列,留 gap
|
||||
val maxCount = minOf(pics.size, 3)
|
||||
val gap = 8 // dp
|
||||
val containerWidth = resources.displayMetrics.widthPixels -
|
||||
(21 * 2 * resources.displayMetrics.density).toInt() // 减去 safe area padding
|
||||
val imgSize = (containerWidth - gap * (maxCount - 1) *
|
||||
resources.displayMetrics.density.toInt()) / maxCount
|
||||
|
||||
for ((index, pic) in pics.take(3).withIndex()) {
|
||||
val imageView = android.widget.ImageView(requireContext()).apply {
|
||||
layoutParams = android.widget.LinearLayout.LayoutParams(imgSize, imgSize).apply {
|
||||
if (index > 0) marginStart = (gap * resources.displayMetrics.density).toInt()
|
||||
}
|
||||
scaleType = android.widget.ImageView.ScaleType.CENTER_CROP
|
||||
setBackgroundColor(requireContext().getColor(R.color.card_background))
|
||||
}
|
||||
// 用 HTTP 加载图片(和语音一样的 TLS 问题)
|
||||
val httpUrl = pic.url.replace("https://", "http://")
|
||||
loadImage(imageView, httpUrl)
|
||||
binding.picContainer.addView(imageView)
|
||||
}
|
||||
}
|
||||
|
||||
/** 异步加载图片到 ImageView */
|
||||
private fun loadImage(imageView: android.widget.ImageView, url: String) {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
try {
|
||||
val bitmap = kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) {
|
||||
val request = Request.Builder().url(url).build()
|
||||
val response = downloadClient.newCall(request).execute()
|
||||
if (response.isSuccessful) {
|
||||
response.body?.byteStream()?.let {
|
||||
android.graphics.BitmapFactory.decodeStream(it)
|
||||
}
|
||||
} else null
|
||||
}
|
||||
bitmap?.let { imageView.setImageBitmap(it) }
|
||||
} catch (e: Exception) {
|
||||
Timber.w(e, "图片加载失败: $url")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 更新语音按钮的图标和背景 */
|
||||
private fun updateVoiceUI(playing: Boolean) {
|
||||
if (playing) {
|
||||
|
||||
Reference in New Issue
Block a user