From 5e2d71c25d278398aed6095122bfbebdb6766fb9 Mon Sep 17 00:00:00 2001 From: dongliang Date: Tue, 28 Apr 2026 10:34:44 +0930 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BB=BB=E5=8A=A1=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E5=8D=95=E4=BB=BB=E5=8A=A1=E5=85=A8=E5=B1=8F?= =?UTF-8?q?=E5=B1=95=E7=A4=BA+=E4=B8=8A=E4=B8=8B=E6=BB=91=E5=88=87?= =?UTF-8?q?=E6=8D=A2=EF=BC=88=E6=96=B9=E6=A1=88B=EF=BC=8C=E5=92=8C?= =?UTF-8?q?=E6=97=A7=E7=89=88=E4=B8=80=E8=87=B4=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 核心改动: - queryTaskIds 获取ID列表 → lookTaskDetail 获取当前任务详情 - 上滑下一个、下滑上一个任务 - 顶部显示"第X/Y个任务" - 派单时间+任务名(带类型前缀)+地点+派单号+积分+协作人+描述 - 底部固定操作按钮(抢单/打卡/完成) - 操作成功后刷新列表+详情 - 空状态+Loading状态 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../xiaoqu/watch/ui/task/TaskListFragment.kt | 396 +++++++++++++++--- .../main/res/layout/fragment_task_list.xml | 325 +++++++++----- 2 files changed, 571 insertions(+), 150 deletions(-) diff --git a/app/src/main/java/com/xiaoqu/watch/ui/task/TaskListFragment.kt b/app/src/main/java/com/xiaoqu/watch/ui/task/TaskListFragment.kt index eacc08e..ca89ca7 100644 --- a/app/src/main/java/com/xiaoqu/watch/ui/task/TaskListFragment.kt +++ b/app/src/main/java/com/xiaoqu/watch/ui/task/TaskListFragment.kt @@ -1,93 +1,131 @@ package com.xiaoqu.watch.ui.task +import android.annotation.SuppressLint import android.os.Bundle +import android.view.GestureDetector import android.view.LayoutInflater +import android.view.MotionEvent import android.view.View import android.view.ViewGroup +import android.widget.FrameLayout import android.widget.TextView -import androidx.core.os.bundleOf import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController -import androidx.recyclerview.widget.LinearLayoutManager import com.xiaoqu.watch.R +import com.xiaoqu.watch.data.task.TaskDetail +import com.xiaoqu.watch.data.task.TaskItem import com.xiaoqu.watch.databinding.FragmentTaskListBinding import com.xiaoqu.watch.network.ApiResult import com.xiaoqu.watch.network.api.TaskApi import com.xiaoqu.watch.network.safeApiCall import com.xiaoqu.watch.ui.common.BaseFragment +import com.xiaoqu.watch.ui.widget.QuTipDialog import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject +import kotlin.math.abs /** - * 任务列表页 - * 分段控件切换状态(接单池2/待打卡3/待完成4) - * RecyclerView 显示任务卡片,点击进入详情 + * 任务列表页(单任务全屏展示,上下滑切换) + * + * 流程(和旧版一致): + * 1. queryTaskIds 获取 ID+name 列表 + * 2. lookTaskDetail 获取当前任务完整详情 + * 3. 上滑/下滑切换 taskIndex → 重新获取详情 + * 4. 操作成功 → 刷新详情 → 刷新列表 */ @AndroidEntryPoint class TaskListFragment : BaseFragment() { @Inject lateinit var taskApi: TaskApi - /** 当前选中的状态 */ + /** 任务 ID 列表(queryTaskIds 返回) */ + private var taskList: List = emptyList() + + /** 当前任务索引 */ + private var taskIndex = 0 + + /** 当前任务详情 */ + private var currentDetail: TaskDetail? = null + + /** 当前状态筛选 */ private var currentStatus = 2 - /** 列表适配器 */ - private lateinit var adapter: TaskListAdapter - - /** 分段控件的 3 个 Tab */ + /** 分段控件 Tab */ private lateinit var segTabs: List + /** 提示弹窗 */ + private lateinit var tipDialog: QuTipDialog + + /** 手势检测(上下滑切换任务) */ + private lateinit var gestureDetector: GestureDetector + override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentTaskListBinding { return FragmentTaskListBinding.inflate(inflater, container, false) } + @SuppressLint("ClickableViewAccessibility") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - // 从导航参数获取初始状态(首页快捷区传入) + // 从导航参数获取初始状态 currentStatus = arguments?.getInt("tableStatus", 2) ?: 2 + // 初始化弹窗 + val dialogContainer = requireActivity().findViewById(R.id.dialog_container) + tipDialog = QuTipDialog(dialogContainer) + // 返回按钮 binding.btnBack.setOnClickListener { findNavController().popBackStack() } - // 初始化 RecyclerView - adapter = TaskListAdapter { task -> - // 点击任务卡片 → 跳转详情 - val bundle = bundleOf("taskId" to task.id) - findNavController().navigate(R.id.action_taskList_to_detail, bundle) - } - binding.rvTasks.layoutManager = LinearLayoutManager(requireContext()) - binding.rvTasks.adapter = adapter - - // 初始化分段控件 + // 分段控件 segTabs = listOf(binding.segPool, binding.segPunch, binding.segComplete) binding.segPool.setOnClickListener { switchStatus(2) } binding.segPunch.setOnClickListener { switchStatus(3) } binding.segComplete.setOnClickListener { switchStatus(4) } - // 下拉刷新 - binding.swipeRefresh.setOnRefreshListener { fetchTasks() } + // 上下滑手势(切换任务) + gestureDetector = GestureDetector(requireContext(), object : GestureDetector.SimpleOnGestureListener() { + override fun onFling(e1: MotionEvent?, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean { + if (e1 == null) return false + val dy = e2.y - e1.y + val dx = e2.x - e1.x + // 垂直滑动且幅度 > 水平 + if (abs(dy) > abs(dx) && abs(dy) > 50) { + if (dy < 0) { + // 上滑 → 下一个任务 + nextTask() + } else { + // 下滑 → 上一个任务 + prevTask() + } + return true + } + return false + } + }) - // 设置初始状态并加载数据 + // 给 ScrollView 设置触摸监听 + binding.scrollView.setOnTouchListener { _, event -> + gestureDetector.onTouchEvent(event) + false // 不拦截,让 ScrollView 继续处理滚动 + } + + // 初始化 switchStatus(currentStatus) } - /** 从详情页返回时自动刷新 */ - override fun onResume() { - super.onResume() - fetchTasks() - } + // ===== 状态切换 ===== /** 切换状态 Tab */ private fun switchStatus(status: Int) { currentStatus = status + taskIndex = 0 updateSegmentUI() - updateTitle() - fetchTasks() + fetchTaskIds() } /** 更新分段控件高亮 */ @@ -104,8 +142,266 @@ class TaskListFragment : BaseFragment() { } } - /** 更新页面标题 */ - private fun updateTitle() { + // ===== 数据获取 ===== + + /** 第一步:获取任务 ID 列表 */ + private fun fetchTaskIds() { + showLoading() + viewLifecycleOwner.lifecycleScope.launch { + val params = hashMapOf("status" to currentStatus) + val result = safeApiCall { taskApi.getTaskIds(params) } + + when (result) { + is ApiResult.Success -> { + taskList = result.data ?: emptyList() + Timber.d("任务列表: status=$currentStatus, count=${taskList.size}") + if (taskList.isNotEmpty()) { + // 确保 taskIndex 在范围内 + if (taskIndex >= taskList.size) taskIndex = taskList.size - 1 + fetchCurrentDetail() + } else { + showEmpty() + } + } + is ApiResult.Error -> { + Timber.w("任务列表: API 错误 ${result.code} ${result.message}") + taskList = emptyList() + showEmpty() + } + is ApiResult.NetworkError -> { + Timber.w("任务列表: 网络异常") + taskList = emptyList() + showEmpty() + } + } + } + } + + /** 第二步:获取当前任务详情 */ + private fun fetchCurrentDetail() { + if (taskList.isEmpty()) return + val taskId = taskList[taskIndex].id + + viewLifecycleOwner.lifecycleScope.launch { + val params = hashMapOf("id" to taskId) + val result = safeApiCall { taskApi.getTaskDetail(params) } + + when (result) { + is ApiResult.Success -> { + result.data?.let { detail -> + currentDetail = detail + displayDetail(detail) + setupActionButton(detail) + } + } + is ApiResult.Error -> { + Timber.w("任务详情: API 错误 ${result.code}") + } + is ApiResult.NetworkError -> { + Timber.w("任务详情: 网络异常") + } + } + } + } + + // ===== 上下滑切换 ===== + + /** 下一个任务(上滑) */ + private fun nextTask() { + if (taskIndex < taskList.size - 1) { + taskIndex++ + fetchCurrentDetail() + } + } + + /** 上一个任务(下滑) */ + private fun prevTask() { + if (taskIndex > 0) { + taskIndex-- + fetchCurrentDetail() + } + } + + // ===== UI 显示 ===== + + /** 显示任务详情 */ + private fun displayDetail(detail: TaskDetail) { + binding.taskContent.visibility = View.VISIBLE + binding.tvEmpty.visibility = View.GONE + binding.loadingWrap.visibility = View.GONE + + // 标题:第X/Y个任务 + binding.tvTitle.text = "第${taskIndex + 1}/${taskList.size}个任务" + + // 派单时间 + if (detail.beginTime.isNotEmpty()) { + val timePart = detail.beginTime.split(" ").lastOrNull() ?: detail.beginTime + binding.tvSendTime.text = "${timePart}派单" + binding.tvSendTime.visibility = View.VISIBLE + } else { + binding.tvSendTime.visibility = View.GONE + } + + // 任务名(带类型前缀) + val prefix = when (detail.taskType) { + 2 -> "指派:" + 3, 4 -> "上报:" + 5 -> "巡检:" + else -> "" + } + binding.tvTaskName.text = "$prefix${detail.taskName.ifEmpty { detail.no }}" + + // 地点 + if (detail.positionName.isNotEmpty()) { + binding.tvPosition.text = detail.positionName + binding.tvPosition.visibility = View.VISIBLE + } else { + binding.tvPosition.visibility = View.GONE + } + + // 派单号 + binding.tvNo.text = detail.no + + // 积分 + binding.tvPoints.text = if (detail.point > 0) "+${detail.point}" else "0" + + // 协作人 + if (detail.executorName.isNotEmpty()) { + binding.tvWorkers.text = detail.executorName + binding.rowWorkers.visibility = View.VISIBLE + } else { + binding.rowWorkers.visibility = View.GONE + } + + // 描述 + if (detail.description.isNotEmpty()) { + binding.tvDescription.text = detail.description + binding.tvDescription.visibility = View.VISIBLE + } else { + binding.tvDescription.visibility = View.GONE + } + + // 提示条(待打卡+有场景) + if (detail.status == 3 && detail.hasPosition) { + binding.tvHint.visibility = View.VISIBLE + } else { + binding.tvHint.visibility = View.GONE + } + } + + /** 根据状态设置底部操作按钮 */ + private fun setupActionButton(detail: TaskDetail) { + val btn = binding.btnAction + btn.visibility = View.VISIBLE + + when (detail.status) { + // 待抢单 → 蓝色「抢 单」 + 2 -> { + btn.text = "抢 单" + btn.setBackgroundResource(R.drawable.bg_foot_btn_blue) + btn.setTextColor(requireContext().getColor(R.color.text_primary)) + btn.setOnClickListener { doAction("grab", detail.id) } + } + // 待打卡 + 3 -> { + if (detail.hasPosition) { + // 有场景 → 橙色「开启打卡」 + btn.text = "开启打卡" + btn.setBackgroundResource(R.drawable.bg_foot_btn_orange) + btn.setTextColor(requireContext().getColor(R.color.background)) + btn.setOnClickListener { + // TODO: NFC 打卡流程 + Timber.d("NFC 打卡(后续实现)") + } + } else { + // 无场景 → 绿色「确认打卡」 + btn.text = "确认打卡" + btn.setBackgroundResource(R.drawable.bg_foot_btn_green) + btn.setTextColor(requireContext().getColor(R.color.text_primary)) + btn.setOnClickListener { doAction("assign", detail.id) } + } + } + // 进行中 → 绿色「确认完成」 + 4 -> { + btn.text = "确认完成" + btn.setBackgroundResource(R.drawable.bg_foot_btn_green) + btn.setTextColor(requireContext().getColor(R.color.text_primary)) + btn.setOnClickListener { doAction("complete", detail.id) } + } + // 其他 → 灰色「返回」 + else -> { + btn.text = "返 回" + btn.setBackgroundResource(R.drawable.bg_foot_btn_blue) + btn.setTextColor(requireContext().getColor(R.color.text_primary)) + btn.setOnClickListener { findNavController().popBackStack() } + } + } + } + + // ===== 操作 ===== + + /** 执行任务操作(抢单/打卡/完成) */ + private fun doAction(action: String, taskId: Long) { + viewLifecycleOwner.lifecycleScope.launch { + val params = hashMapOf("id" to taskId) + val result = when (action) { + "grab" -> safeApiCall { taskApi.grabTask(params) } + "assign" -> safeApiCall { taskApi.assignToBeginTask(params) } + "complete" -> safeApiCall { taskApi.completeTask(params) } + else -> return@launch + } + + val successMsg = when (action) { + "grab" -> "抢单成功" + "assign" -> "打卡成功" + "complete" -> "任务已完成" + else -> "操作成功" + } + val failMsg = when (action) { + "grab" -> "抢单失败" + "assign" -> "打卡失败" + "complete" -> "完成失败" + else -> "操作失败" + } + + when (result) { + is ApiResult.Success -> { + Timber.d("任务操作: $successMsg") + tipDialog.show( + status = QuTipDialog.Status.SUCCESS, + title = successMsg, + back = true, step = 0, countdown = 2 + ) + // 操作成功后刷新:重新获取列表和详情 + fetchTaskIds() + } + is ApiResult.Error -> { + tipDialog.show( + status = QuTipDialog.Status.ERROR, + title = failMsg, + desc = result.message, + back = true, step = 0, countdown = 3 + ) + } + is ApiResult.NetworkError -> { + tipDialog.show( + status = QuTipDialog.Status.ERROR, + title = "网络异常", + back = true, step = 0, countdown = 3 + ) + } + } + } + } + + // ===== 状态显示 ===== + + /** 显示 Loading */ + private fun showLoading() { + binding.taskContent.visibility = View.GONE + binding.tvEmpty.visibility = View.GONE + binding.loadingWrap.visibility = View.VISIBLE + binding.btnAction.visibility = View.GONE binding.tvTitle.text = when (currentStatus) { 2 -> "接单池" 3 -> "待打卡" @@ -114,29 +410,17 @@ class TaskListFragment : BaseFragment() { } } - /** 从 API 获取任务列表 */ - private fun fetchTasks() { - viewLifecycleOwner.lifecycleScope.launch { - binding.swipeRefresh.isRefreshing = true - val params = hashMapOf("status" to currentStatus) - val result = safeApiCall { taskApi.getTaskIds(params) } - binding.swipeRefresh.isRefreshing = false - - when (result) { - is ApiResult.Success -> { - val tasks = result.data ?: emptyList() - Timber.d("任务列表: status=$currentStatus, count=${tasks.size}") - adapter.submitList(tasks) - } - is ApiResult.Error -> { - Timber.w("任务列表: API 错误 code=${result.code} ${result.message}") - adapter.submitList(emptyList()) - } - is ApiResult.NetworkError -> { - Timber.w("任务列表: 网络异常") - adapter.submitList(emptyList()) - } - } + /** 显示空状态 */ + private fun showEmpty() { + binding.taskContent.visibility = View.GONE + binding.tvEmpty.visibility = View.VISIBLE + binding.loadingWrap.visibility = View.GONE + binding.btnAction.visibility = View.GONE + binding.tvTitle.text = when (currentStatus) { + 2 -> "接单池" + 3 -> "待打卡" + 4 -> "待完成" + else -> "任务列表" } } } diff --git a/app/src/main/res/layout/fragment_task_list.xml b/app/src/main/res/layout/fragment_task_list.xml index 7564142..495a1c5 100644 --- a/app/src/main/res/layout/fragment_task_list.xml +++ b/app/src/main/res/layout/fragment_task_list.xml @@ -1,103 +1,240 @@ - - + + android:background="@color/background"> - - + + android:layout_height="match_parent" + android:scrollbars="none" + android:paddingStart="21dp" + android:paddingTop="27dp" + android:paddingEnd="21dp" + android:paddingBottom="72dp" + android:clipToPadding="false"> - - - - - - - - - - - - - - - - - - - - - - - + android:layout_height="wrap_content" + android:orientation="vertical"> - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +