feat: 任务管理模块 - 列表+详情+抢单/打卡/完成

新增:
- TaskListFragment 分段控件+RecyclerView+下拉刷新
- TaskDetailFragment 信息展示+底部固定操作按钮(foot-btn)
- TaskListAdapter 任务卡片适配器
- TaskItem/TaskDetail 数据类
- TaskApi 新增5个接口(pageList/detail/grab/assign/complete)
- 分段控件/底部按钮/状态标签 drawable

修改:
- nav_main.xml 添加参数和action
- HomeFragment 快捷区点击→任务列表(传tableStatus)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
dongliang
2026-04-27 20:19:35 +09:30
parent 3c9d74f16c
commit 1056386af8
19 changed files with 889 additions and 43 deletions

View File

@@ -0,0 +1,33 @@
package com.xiaoqu.watch.data.task
import com.google.gson.annotations.SerializedName
/**
* 任务详情数据类
* 对应 watchTask/lookTaskDetail API 返回
*/
data class TaskDetail(
@SerializedName("id") val id: Long = 0,
/** 任务编号 */
@SerializedName("no") val no: String = "",
/** 任务名称 */
@SerializedName("taskName") val taskName: String = "",
/** 地点名称 */
@SerializedName("positionName") val positionName: String = "",
/** 积分 */
@SerializedName("point") val point: Int = 0,
/** 状态2=待抢单, 3=待打卡, 4=进行中, 1=已完成 */
@SerializedName("status") val status: Int = 0,
/** 任务类型 */
@SerializedName("taskType") val taskType: Int = 0,
/** 开始时间 */
@SerializedName("beginTime") val beginTime: String = "",
/** 结束时间 */
@SerializedName("endTime") val endTime: String = "",
/** 任务描述 */
@SerializedName("description") val description: String = "",
/** 协作人 */
@SerializedName("executorName") val executorName: String = "",
/** 是否有打卡地点(决定打卡方式:有=NFC打卡无=直接确认) */
@SerializedName("hasPosition") val hasPosition: Boolean = false
)

View File

@@ -0,0 +1,27 @@
package com.xiaoqu.watch.data.task
import com.google.gson.annotations.SerializedName
/**
* 任务列表项数据类
* 对应 watchTask/pageList API 返回
*/
data class TaskItem(
@SerializedName("id") val id: Long = 0,
/** 任务编号 */
@SerializedName("no") val no: String = "",
/** 任务名称 */
@SerializedName("taskName") val taskName: String = "",
/** 地点名称 */
@SerializedName("positionName") val positionName: String = "",
/** 积分 */
@SerializedName("point") val point: Int = 0,
/** 状态2=待抢单, 3=待打卡, 4=进行中, 1=已完成 */
@SerializedName("status") val status: Int = 0,
/** 任务类型0=计划, 1=监控, 2=指派, 3=用户上报, 4=巡检上报, 5=巡检任务 */
@SerializedName("taskType") val taskType: Int = 0,
/** 开始时间 */
@SerializedName("beginTime") val beginTime: String = "",
/** 结束时间 */
@SerializedName("endTime") val endTime: String = ""
)

View File

@@ -1,9 +1,14 @@
package com.xiaoqu.watch.network.api package com.xiaoqu.watch.network.api
import com.xiaoqu.watch.data.task.AttendanceInfo import com.xiaoqu.watch.data.task.AttendanceInfo
import com.xiaoqu.watch.data.task.TaskDetail
import com.xiaoqu.watch.data.task.TaskItem
import com.xiaoqu.watch.data.task.TaskStatistics import com.xiaoqu.watch.data.task.TaskStatistics
import com.xiaoqu.watch.network.ApiResponse import com.xiaoqu.watch.network.ApiResponse
import retrofit2.http.Body
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Query
/** /**
* 任务相关 API 接口 * 任务相关 API 接口
@@ -17,4 +22,24 @@ interface TaskApi {
/** 查询当前考勤状态 */ /** 查询当前考勤状态 */
@GET("watchTask/myCurrentAttendance") @GET("watchTask/myCurrentAttendance")
suspend fun getAttendance(): ApiResponse<AttendanceInfo> suspend fun getAttendance(): ApiResponse<AttendanceInfo>
/** 任务列表(按状态筛选) */
@GET("watchTask/pageList")
suspend fun getTaskList(@Query("status") status: Int): ApiResponse<List<TaskItem>>
/** 任务详情 */
@GET("watchTask/lookTaskDetail")
suspend fun getTaskDetail(@Query("id") taskId: Long): ApiResponse<TaskDetail>
/** 抢单 */
@POST("task/grabTask")
suspend fun grabTask(@Body params: HashMap<String, Any>): ApiResponse<Any>
/** 无场景打卡(直接确认) */
@POST("task/assignToBeginTask")
suspend fun assignToBeginTask(@Body params: HashMap<String, Any>): ApiResponse<Any>
/** 确认完成 */
@POST("task/completeTask")
suspend fun completeTask(@Body params: HashMap<String, Any>): ApiResponse<Any>
} }

View File

@@ -5,6 +5,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import androidx.core.os.bundleOf
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.xiaoqu.watch.BuildConfig import com.xiaoqu.watch.BuildConfig
@@ -106,18 +107,15 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
tvPunchNum = page.findViewById(R.id.tvPunchNum) tvPunchNum = page.findViewById(R.id.tvPunchNum)
tvCompleteNum = page.findViewById(R.id.tvCompleteNum) tvCompleteNum = page.findViewById(R.id.tvCompleteNum)
// 快捷区卡片点击 → 跳转任务列表(后续实现 // 快捷区卡片点击 → 跳转任务列表(传 tableStatus 参数
page.findViewById<View>(R.id.cardPool)?.setOnClickListener { page.findViewById<View>(R.id.cardPool)?.setOnClickListener {
Timber.d("点击接单池") navigateToTaskList(2)
// TODO: navigate to TaskListFragment with tableStatus=2
} }
page.findViewById<View>(R.id.cardPunch)?.setOnClickListener { page.findViewById<View>(R.id.cardPunch)?.setOnClickListener {
Timber.d("点击待打卡") navigateToTaskList(3)
// TODO: navigate to TaskListFragment with tableStatus=3
} }
page.findViewById<View>(R.id.cardComplete)?.setOnClickListener { page.findViewById<View>(R.id.cardComplete)?.setOnClickListener {
Timber.d("点击待完成") navigateToTaskList(4)
// TODO: navigate to TaskListFragment with tableStatus=4
} }
} }
@@ -261,4 +259,10 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
} }
} }
} }
/** 跳转到任务列表(传 tableStatus 参数) */
private fun navigateToTaskList(tableStatus: Int) {
val bundle = bundleOf("tableStatus" to tableStatus)
findNavController().navigate(R.id.action_home_to_taskList, bundle)
}
} }

View File

@@ -1,15 +1,247 @@
package com.xiaoqu.watch.ui.task package com.xiaoqu.watch.ui.task
import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.xiaoqu.watch.R
import com.xiaoqu.watch.data.task.TaskDetail
import com.xiaoqu.watch.databinding.FragmentTaskDetailBinding import com.xiaoqu.watch.databinding.FragmentTaskDetailBinding
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.common.BaseFragment
import com.xiaoqu.watch.ui.widget.QuTipDialog
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
/**
* 任务详情页
* 显示任务信息 + 底部固定操作按钮(按状态不同显示不同操作)
*/
@AndroidEntryPoint @AndroidEntryPoint
class TaskDetailFragment : BaseFragment<FragmentTaskDetailBinding>() { class TaskDetailFragment : BaseFragment<FragmentTaskDetailBinding>() {
@Inject lateinit var taskApi: TaskApi
/** 当前任务数据 */
private var taskDetail: TaskDetail? = null
/** 提示弹窗 */
private lateinit var tipDialog: QuTipDialog
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentTaskDetailBinding { override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentTaskDetailBinding {
return FragmentTaskDetailBinding.inflate(inflater, container, false) return FragmentTaskDetailBinding.inflate(inflater, container, false)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 初始化弹窗
val dialogContainer = requireActivity().findViewById<FrameLayout>(R.id.dialog_container)
tipDialog = QuTipDialog(dialogContainer)
// 返回按钮
binding.btnBack.setOnClickListener {
findNavController().popBackStack()
}
// 从导航参数获取任务 ID
val taskId = arguments?.getLong("taskId", 0) ?: 0
if (taskId > 0) {
fetchDetail(taskId)
}
}
/** 获取任务详情 */
private fun fetchDetail(taskId: Long) {
viewLifecycleOwner.lifecycleScope.launch {
val result = safeApiCall { taskApi.getTaskDetail(taskId) }
when (result) {
is ApiResult.Success -> {
result.data?.let { detail ->
taskDetail = detail
displayDetail(detail)
setupActionButton(detail)
}
}
is ApiResult.Error -> {
Timber.w("任务详情: API 错误 ${result.code}")
}
is ApiResult.NetworkError -> {
Timber.w("任务详情: 网络异常")
}
}
}
}
/** 展示任务信息 */
private fun displayDetail(detail: TaskDetail) {
binding.tvTaskName.text = detail.taskName
binding.tvTaskNo.text = detail.no
binding.tvPosition.text = detail.positionName.ifEmpty { "" }
binding.tvPoints.text = if (detail.point > 0) "+${detail.point}" else "0"
binding.tvTime.text = detail.beginTime
// 状态标签
when (detail.status) {
2 -> binding.tvStatus.text = "待抢单"
3 -> binding.tvStatus.text = "待打卡"
4 -> binding.tvStatus.text = "进行中"
else -> binding.tvStatus.text = "已完成"
}
// 提示条(待打卡+有场景时显示)
if (detail.status == 3 && detail.hasPosition) {
binding.tvHint.visibility = View.VISIBLE
}
}
/** 根据任务状态设置底部操作按钮 */
private fun setupActionButton(detail: TaskDetail) {
val btn = binding.btnAction
when (detail.status) {
// 待抢单 → 蓝色「抢 单」
2 -> {
btn.text = "抢 单"
btn.setBackgroundResource(R.drawable.bg_foot_btn_blue)
btn.setOnClickListener { doGrabTask(detail.id) }
}
// 待打卡
3 -> {
if (detail.hasPosition) {
// 有场景 → 橙色「开启打卡」NFC后续实现
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.setOnClickListener { doAssignBeginTask(detail.id) }
}
}
// 进行中 → 绿色「确认完成」
4 -> {
btn.text = "确认完成"
btn.setBackgroundResource(R.drawable.bg_foot_btn_green)
btn.setOnClickListener { doCompleteTask(detail.id) }
}
// 已完成 → 隐藏按钮
else -> {
btn.visibility = View.GONE
}
}
}
/** 抢单操作 */
private fun doGrabTask(taskId: Long) {
viewLifecycleOwner.lifecycleScope.launch {
val params = hashMapOf<String, Any>("id" to taskId)
val result = safeApiCall { taskApi.grabTask(params) }
when (result) {
is ApiResult.Success -> {
Timber.d("任务详情: 抢单成功")
tipDialog.show(
status = QuTipDialog.Status.SUCCESS,
title = "抢单成功",
back = true, step = 1, countdown = 2,
onBack = { findNavController().popBackStack() }
)
}
is ApiResult.Error -> {
tipDialog.show(
status = QuTipDialog.Status.ERROR,
title = "抢单失败",
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
)
}
}
}
}
/** 无场景打卡操作 */
private fun doAssignBeginTask(taskId: Long) {
viewLifecycleOwner.lifecycleScope.launch {
val params = hashMapOf<String, Any>("id" to taskId)
val result = safeApiCall { taskApi.assignToBeginTask(params) }
when (result) {
is ApiResult.Success -> {
Timber.d("任务详情: 打卡成功")
tipDialog.show(
status = QuTipDialog.Status.SUCCESS,
title = "打卡成功",
back = true, step = 1, countdown = 2,
onBack = { findNavController().popBackStack() }
)
}
is ApiResult.Error -> {
tipDialog.show(
status = QuTipDialog.Status.ERROR,
title = "打卡失败",
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
)
}
}
}
}
/** 确认完成操作 */
private fun doCompleteTask(taskId: Long) {
viewLifecycleOwner.lifecycleScope.launch {
val params = hashMapOf<String, Any>("id" to taskId)
val result = safeApiCall { taskApi.completeTask(params) }
when (result) {
is ApiResult.Success -> {
Timber.d("任务详情: 完成成功")
tipDialog.show(
status = QuTipDialog.Status.SUCCESS,
title = "任务已完成",
back = true, step = 1, countdown = 2,
onBack = { findNavController().popBackStack() }
)
}
is ApiResult.Error -> {
tipDialog.show(
status = QuTipDialog.Status.ERROR,
title = "操作失败",
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
)
}
}
}
}
} }

View File

@@ -0,0 +1,88 @@
package com.xiaoqu.watch.ui.task
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.xiaoqu.watch.R
import com.xiaoqu.watch.data.task.TaskItem
/**
* 任务列表 RecyclerView 适配器
* 显示任务卡片:任务名 + 地点+时间 + 积分+状态标签
*/
class TaskListAdapter(
private val onItemClick: (TaskItem) -> Unit
) : RecyclerView.Adapter<TaskListAdapter.TaskViewHolder>() {
private val tasks = mutableListOf<TaskItem>()
/** 更新数据 */
fun submitList(newTasks: List<TaskItem>) {
tasks.clear()
tasks.addAll(newTasks)
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TaskViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_task_card, parent, false)
return TaskViewHolder(view)
}
override fun onBindViewHolder(holder: TaskViewHolder, position: Int) {
holder.bind(tasks[position])
}
override fun getItemCount(): Int = tasks.size
inner class TaskViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val tvTaskName: TextView = view.findViewById(R.id.tvTaskName)
private val tvTaskSub: TextView = view.findViewById(R.id.tvTaskSub)
private val tvPoints: TextView = view.findViewById(R.id.tvPoints)
private val tvStatus: TextView = view.findViewById(R.id.tvStatus)
fun bind(task: TaskItem) {
// 任务名
tvTaskName.text = task.taskName
// 地点 + 时间
val sub = StringBuilder()
if (task.positionName.isNotEmpty()) sub.appendLine(task.positionName)
if (task.beginTime.isNotEmpty()) sub.append(task.beginTime)
tvTaskSub.text = sub.toString()
// 积分
tvPoints.text = if (task.point > 0) "+${task.point}" else ""
// 状态标签
val context = itemView.context
when (task.status) {
2 -> {
tvStatus.text = "待抢单"
tvStatus.setTextColor(context.getColor(R.color.primary))
tvStatus.setBackgroundResource(R.drawable.bg_pill_blue)
}
3 -> {
tvStatus.text = "待打卡"
tvStatus.setTextColor(context.getColor(R.color.warning))
tvStatus.setBackgroundResource(R.drawable.bg_pill_orange)
}
4 -> {
tvStatus.text = "进行中"
tvStatus.setTextColor(context.getColor(R.color.success))
tvStatus.setBackgroundResource(R.drawable.bg_pill_green)
}
else -> {
tvStatus.text = "已完成"
tvStatus.setTextColor(context.getColor(R.color.text_secondary))
tvStatus.background = null
}
}
// 点击事件
itemView.setOnClickListener { onItemClick(task) }
}
}
}

View File

@@ -1,15 +1,141 @@
package com.xiaoqu.watch.ui.task package com.xiaoqu.watch.ui.task
import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
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.databinding.FragmentTaskListBinding 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.common.BaseFragment
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
/**
* 任务列表页
* 分段控件切换状态接单池2/待打卡3/待完成4
* RecyclerView 显示任务卡片,点击进入详情
*/
@AndroidEntryPoint @AndroidEntryPoint
class TaskListFragment : BaseFragment<FragmentTaskListBinding>() { class TaskListFragment : BaseFragment<FragmentTaskListBinding>() {
@Inject lateinit var taskApi: TaskApi
/** 当前选中的状态 */
private var currentStatus = 2
/** 列表适配器 */
private lateinit var adapter: TaskListAdapter
/** 分段控件的 3 个 Tab */
private lateinit var segTabs: List<TextView>
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentTaskListBinding { override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentTaskListBinding {
return FragmentTaskListBinding.inflate(inflater, container, false) return FragmentTaskListBinding.inflate(inflater, container, false)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 从导航参数获取初始状态(首页快捷区传入)
currentStatus = arguments?.getInt("tableStatus", 2) ?: 2
// 返回按钮
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() }
// 设置初始状态并加载数据
switchStatus(currentStatus)
}
/** 从详情页返回时自动刷新 */
override fun onResume() {
super.onResume()
fetchTasks()
}
/** 切换状态 Tab */
private fun switchStatus(status: Int) {
currentStatus = status
updateSegmentUI()
updateTitle()
fetchTasks()
}
/** 更新分段控件高亮 */
private fun updateSegmentUI() {
val statusList = listOf(2, 3, 4)
for (i in segTabs.indices) {
if (statusList[i] == currentStatus) {
segTabs[i].setBackgroundResource(R.drawable.bg_seg_active)
segTabs[i].setTextColor(requireContext().getColor(R.color.text_primary))
} else {
segTabs[i].background = null
segTabs[i].setTextColor(requireContext().getColor(R.color.text_secondary))
}
}
}
/** 更新页面标题 */
private fun updateTitle() {
binding.tvTitle.text = when (currentStatus) {
2 -> "接单池"
3 -> "待打卡"
4 -> "待完成"
else -> "任务列表"
}
}
/** 从 API 获取任务列表 */
private fun fetchTasks() {
viewLifecycleOwner.lifecycleScope.launch {
binding.swipeRefresh.isRefreshing = true
val result = safeApiCall { taskApi.getTaskList(currentStatus) }
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())
}
}
}
}
} }

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 底部按钮:蓝色(抢单) -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FF3B9EFF" />
<corners android:bottomLeftRadius="59dp" android:bottomRightRadius="59dp" />
</shape>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 底部按钮:绿色(确认打卡/确认完成) -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FF4ADE80" />
<corners android:bottomLeftRadius="59dp" android:bottomRightRadius="59dp" />
</shape>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 底部按钮:橙色(开启打卡) -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FFFFB340" />
<corners android:bottomLeftRadius="59dp" android:bottomRightRadius="59dp" />
</shape>

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 分段控件选中项背景:白色 12% 圆角 -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#1FFFFFFF" />
<corners android:radius="8dp" />
</shape>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 分段控件背景:白色 8% 圆角 -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#14FFFFFF" />
<corners android:radius="11dp" />
</shape>

View File

@@ -1,9 +1,117 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- 任务详情页ScrollView 信息 + 底部固定操作按钮 -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/background"> android:background="@color/background">
<!-- TODO: 任务详情 + 操作按钮 --> <!-- 可滚动内容区(底部留出按钮空间) -->
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none"
android:paddingStart="21dp"
android:paddingTop="27dp"
android:paddingEnd="21dp"
android:paddingBottom="72dp"
android:clipToPadding="false">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- 页面头部:返回 + 标题 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_marginBottom="8dp">
<TextView
android:id="@+id/btnBack"
android:layout_width="32dp"
android:layout_height="32dp"
android:gravity="center"
android:text=""
android:textColor="@color/primary"
android:textSize="27sp" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="任务详情"
android:textColor="@color/text_primary"
android:textSize="20sp"
android:textStyle="bold"
android:layout_marginEnd="32dp" />
</LinearLayout>
<!-- 任务信息行 -->
<LinearLayout style="@style/ConfigRow">
<TextView style="@style/ConfigLabel" android:text="任务" />
<TextView android:id="@+id/tvTaskName" style="@style/ConfigValue" />
</LinearLayout>
<LinearLayout style="@style/ConfigRow">
<TextView style="@style/ConfigLabel" android:text="编号" />
<TextView android:id="@+id/tvTaskNo" style="@style/ConfigValue"
android:textColor="@color/text_secondary" android:textSize="15sp" />
</LinearLayout>
<LinearLayout style="@style/ConfigRow">
<TextView style="@style/ConfigLabel" android:text="地点" />
<TextView android:id="@+id/tvPosition" style="@style/ConfigValue"
android:textColor="@color/primary" />
</LinearLayout>
<LinearLayout style="@style/ConfigRow">
<TextView style="@style/ConfigLabel" android:text="积分" />
<TextView android:id="@+id/tvPoints" style="@style/ConfigValue"
android:textColor="@color/warning" />
</LinearLayout>
<LinearLayout style="@style/ConfigRow">
<TextView style="@style/ConfigLabel" android:text="派单" />
<TextView android:id="@+id/tvTime" style="@style/ConfigValue" />
</LinearLayout>
<LinearLayout style="@style/ConfigRow">
<TextView style="@style/ConfigLabel" android:text="状态" />
<TextView android:id="@+id/tvStatus" style="@style/ConfigValue" />
</LinearLayout>
<!-- 提示条(待打卡+有场景时显示) -->
<TextView
android:id="@+id/tvHint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="11dp"
android:text="请将手表贴近打卡信标"
android:textColor="@color/warning"
android:textSize="16sp"
android:textStyle="bold"
android:visibility="gone" />
</LinearLayout>
</ScrollView>
<!-- 底部固定操作按钮 -->
<TextView
android:id="@+id/btnAction"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:gravity="center"
android:padding="19dp"
android:textColor="@color/text_primary"
android:textSize="21sp"
android:textStyle="bold"
android:letterSpacing="0.05" />
</FrameLayout> </FrameLayout>

View File

@@ -1,9 +1,103 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <!-- 任务列表页:返回按钮 + 分段控件 + RecyclerView -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/background"> android:background="@color/background"
android:orientation="vertical"
android:paddingStart="21dp"
android:paddingTop="27dp"
android:paddingEnd="21dp">
<!-- TODO: RecyclerView 任务列表 --> <!-- 页面头部:返回 + 标题 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_marginBottom="8dp">
</FrameLayout> <!-- 返回按钮 -->
<TextView
android:id="@+id/btnBack"
android:layout_width="32dp"
android:layout_height="32dp"
android:gravity="center"
android:text=""
android:textColor="@color/primary"
android:textSize="27sp" />
<!-- 标题 -->
<TextView
android:id="@+id/tvTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="接单池"
android:textColor="@color/text_primary"
android:textSize="20sp"
android:textStyle="bold"
android:layout_marginEnd="32dp" />
</LinearLayout>
<!-- 分段控件:接单池 / 待打卡 / 待完成 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_seg_ctrl"
android:padding="3dp"
android:layout_marginBottom="13dp">
<TextView
android:id="@+id/segPool"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:padding="8dp"
android:text="接单池"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/segPunch"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:padding="8dp"
android:text="待打卡"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/segComplete"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:padding="8dp"
android:text="待完成"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
<!-- 任务列表(下拉刷新 + RecyclerView -->
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefresh"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvTasks"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="27dp" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout>

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 任务卡片RecyclerView item
显示:任务名 + 地点 + 时间 + 积分 + 状态标签 -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_seg_active"
android:orientation="vertical"
android:padding="16dp"
android:layout_marginBottom="11dp">
<!-- 任务名(大字) -->
<TextView
android:id="@+id/tvTaskName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/text_primary"
android:textSize="20sp"
android:textStyle="bold"
android:maxLines="1"
android:ellipsize="end" />
<!-- 地点 + 时间 -->
<TextView
android:id="@+id/tvTaskSub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/text_secondary"
android:textSize="16sp"
android:lineSpacingMultiplier="1.7"
android:layout_marginTop="8dp" />
<!-- 积分 + 状态标签 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:layout_marginTop="8dp">
<!-- 积分 -->
<TextView
android:id="@+id/tvPoints"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor="@color/warning"
android:textSize="19sp"
android:textStyle="bold" />
<!-- 状态标签 -->
<TextView
android:id="@+id/tvStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="13dp"
android:paddingEnd="13dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:textSize="15sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>

View File

@@ -4,66 +4,60 @@
android:id="@+id/nav_main" android:id="@+id/nav_main"
app:startDestination="@id/splashFragment"> app:startDestination="@id/splashFragment">
<!-- 启动分发页(检查绑定状态 → Home 或 Bind --> <!-- 启动分发页 -->
<fragment <fragment
android:id="@+id/splashFragment" android:id="@+id/splashFragment"
android:name="com.xiaoqu.watch.ui.common.SplashFragment" android:name="com.xiaoqu.watch.ui.common.SplashFragment"
android:label="启动"> android:label="启动">
<action android:id="@+id/action_splash_to_home"
<!-- splash → home已绑定 -->
<action
android:id="@+id/action_splash_to_home"
app:destination="@id/homeFragment" app:destination="@id/homeFragment"
app:popUpTo="@id/splashFragment" app:popUpTo="@id/splashFragment" app:popUpToInclusive="true" />
app:popUpToInclusive="true" /> <action android:id="@+id/action_splash_to_bind"
<!-- splash → bind未绑定 -->
<action
android:id="@+id/action_splash_to_bind"
app:destination="@id/bindFragment" app:destination="@id/bindFragment"
app:popUpTo="@id/splashFragment" app:popUpTo="@id/splashFragment" app:popUpToInclusive="true" />
app:popUpToInclusive="true" />
</fragment> </fragment>
<!-- 首页(含 ViewPager2 左右滑动:设置页 / 主页) --> <!-- 首页 -->
<fragment <fragment
android:id="@+id/homeFragment" android:id="@+id/homeFragment"
android:name="com.xiaoqu.watch.ui.home.HomeFragment" android:name="com.xiaoqu.watch.ui.home.HomeFragment"
android:label="首页"> android:label="首页">
<action android:id="@+id/action_home_to_bind"
<!-- home → bind解绑后 -->
<action
android:id="@+id/action_home_to_bind"
app:destination="@id/bindFragment" app:destination="@id/bindFragment"
app:popUpTo="@id/homeFragment" app:popUpTo="@id/homeFragment" app:popUpToInclusive="true" />
app:popUpToInclusive="true" /> <!-- 首页 → 任务列表 -->
<action android:id="@+id/action_home_to_taskList"
app:destination="@id/taskListFragment" />
</fragment> </fragment>
<!-- 设备绑定页(全屏二维码) --> <!-- 设备绑定页 -->
<fragment <fragment
android:id="@+id/bindFragment" android:id="@+id/bindFragment"
android:name="com.xiaoqu.watch.ui.bind.BindFragment" android:name="com.xiaoqu.watch.ui.bind.BindFragment"
android:label="设备绑定"> android:label="设备绑定">
<action android:id="@+id/action_bind_to_home"
<!-- bind → home绑定成功后 -->
<action
android:id="@+id/action_bind_to_home"
app:destination="@id/homeFragment" app:destination="@id/homeFragment"
app:popUpTo="@id/bindFragment" app:popUpTo="@id/bindFragment" app:popUpToInclusive="true" />
app:popUpToInclusive="true" />
</fragment> </fragment>
<!-- 任务列表 --> <!-- 任务列表(接收 tableStatus 参数) -->
<fragment <fragment
android:id="@+id/taskListFragment" android:id="@+id/taskListFragment"
android:name="com.xiaoqu.watch.ui.task.TaskListFragment" android:name="com.xiaoqu.watch.ui.task.TaskListFragment"
android:label="任务列表" /> android:label="任务列表">
<argument android:name="tableStatus" android:defaultValue="2" app:argType="integer" />
<!-- 任务列表 → 任务详情 -->
<action android:id="@+id/action_taskList_to_detail"
app:destination="@id/taskDetailFragment" />
</fragment>
<!-- 任务详情 --> <!-- 任务详情(接收 taskId 参数) -->
<fragment <fragment
android:id="@+id/taskDetailFragment" android:id="@+id/taskDetailFragment"
android:name="com.xiaoqu.watch.ui.task.TaskDetailFragment" android:name="com.xiaoqu.watch.ui.task.TaskDetailFragment"
android:label="任务详情" /> android:label="任务详情">
<argument android:name="taskId" android:defaultValue="0" app:argType="long" />
</fragment>
<!-- 打卡页 --> <!-- 打卡页 -->
<fragment <fragment