feat: 考勤打卡模块(新方法论首个验证模块)
基于分析产出物开发(discovery-map考勤章节+baseline/05流程6): - PunchFragment 3种状态(未上班/已上班/已下班) - PunchApi 3个接口(getAttendance/onAndOffPunch/revokePunch) - PunchStatus 数据类(字段名基于分析,非猜测) - 上班打卡:蓝牙识别1.5s→确认弹窗→API - 下班打卡:蓝牙识别1.5s→直接提交→低耗电模式 - 撤销打卡:确认弹窗→API→恢复 - 首页下拉手势→考勤页 - 蓝牙用模拟MAC,后续对接 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
20
app/src/main/java/com/xiaoqu/watch/data/punch/PunchStatus.kt
Normal file
20
app/src/main/java/com/xiaoqu/watch/data/punch/PunchStatus.kt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package com.xiaoqu.watch.data.punch
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 考勤状态数据类
|
||||||
|
* 对应 watchTask/myCurrentAttendance API 返回
|
||||||
|
* 来源:discovery-map.md 考勤章节(已验证字段名)
|
||||||
|
*/
|
||||||
|
data class PunchStatus(
|
||||||
|
/** 上班打卡状态:0=未上班, 1=已上班 */
|
||||||
|
@SerializedName("onPunchState") val onPunchState: Int = 0,
|
||||||
|
/** 下班打卡状态:1=已下班, 其他=未下班 */
|
||||||
|
@SerializedName("offPunchState") val offPunchState: Int = 0
|
||||||
|
) {
|
||||||
|
/** 是否已上班 */
|
||||||
|
val isOnDuty: Boolean get() = onPunchState == 1
|
||||||
|
/** 是否已下班 */
|
||||||
|
val isOffDuty: Boolean get() = offPunchState == 1
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import com.xiaoqu.watch.network.EnvConfig
|
|||||||
import com.xiaoqu.watch.network.SignatureInterceptor
|
import com.xiaoqu.watch.network.SignatureInterceptor
|
||||||
import com.xiaoqu.watch.network.UnbindInterceptor
|
import com.xiaoqu.watch.network.UnbindInterceptor
|
||||||
import com.xiaoqu.watch.network.api.CommonApi
|
import com.xiaoqu.watch.network.api.CommonApi
|
||||||
|
import com.xiaoqu.watch.network.api.PunchApi
|
||||||
import com.xiaoqu.watch.network.api.TaskApi
|
import com.xiaoqu.watch.network.api.TaskApi
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
@@ -69,4 +70,10 @@ object NetworkModule {
|
|||||||
fun provideTaskApi(retrofit: Retrofit): TaskApi {
|
fun provideTaskApi(retrofit: Retrofit): TaskApi {
|
||||||
return retrofit.create(TaskApi::class.java)
|
return retrofit.create(TaskApi::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun providePunchApi(retrofit: Retrofit): PunchApi {
|
||||||
|
return retrofit.create(PunchApi::class.java)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
27
app/src/main/java/com/xiaoqu/watch/network/api/PunchApi.kt
Normal file
27
app/src/main/java/com/xiaoqu/watch/network/api/PunchApi.kt
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package com.xiaoqu.watch.network.api
|
||||||
|
|
||||||
|
import com.xiaoqu.watch.data.punch.PunchStatus
|
||||||
|
import com.xiaoqu.watch.network.ApiResponse
|
||||||
|
import retrofit2.http.Body
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.POST
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 考勤打卡 API 接口
|
||||||
|
* 来源:discovery-map.md 考勤章节
|
||||||
|
* 注意:myCurrentAttendance 标注为 GET,如返回 405 需改为 POST
|
||||||
|
*/
|
||||||
|
interface PunchApi {
|
||||||
|
|
||||||
|
/** 查询当前考勤状态 */
|
||||||
|
@GET("watchTask/myCurrentAttendance")
|
||||||
|
suspend fun getAttendance(): ApiResponse<PunchStatus>
|
||||||
|
|
||||||
|
/** 上班/下班打卡 */
|
||||||
|
@POST("watchTask/onAndOffPunch")
|
||||||
|
suspend fun onAndOffPunch(@Body params: HashMap<String, Any>): ApiResponse<Any>
|
||||||
|
|
||||||
|
/** 撤销打卡 */
|
||||||
|
@POST("watchTask/revokePunch")
|
||||||
|
suspend fun revokePunch(@Body params: HashMap<String, Any>): ApiResponse<Any>
|
||||||
|
}
|
||||||
@@ -95,6 +95,9 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||||||
|
|
||||||
// 监听 MQTT 事件
|
// 监听 MQTT 事件
|
||||||
observeEvents()
|
observeEvents()
|
||||||
|
|
||||||
|
// 下拉手势 → 进入考勤页
|
||||||
|
setupPullDownGesture()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== 主页 =====
|
// ===== 主页 =====
|
||||||
@@ -269,4 +272,43 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||||||
val bundle = bundleOf("tableStatus" to tableStatus)
|
val bundle = bundleOf("tableStatus" to tableStatus)
|
||||||
findNavController().navigate(R.id.action_home_to_taskList, bundle)
|
findNavController().navigate(R.id.action_home_to_taskList, bundle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 设置下拉手势 → 进入考勤打卡页 */
|
||||||
|
@android.annotation.SuppressLint("ClickableViewAccessibility")
|
||||||
|
private fun setupPullDownGesture() {
|
||||||
|
val gestureDetector = android.view.GestureDetector(
|
||||||
|
requireContext(),
|
||||||
|
object : android.view.GestureDetector.SimpleOnGestureListener() {
|
||||||
|
override fun onFling(
|
||||||
|
e1: android.view.MotionEvent?,
|
||||||
|
e2: android.view.MotionEvent,
|
||||||
|
velocityX: Float,
|
||||||
|
velocityY: Float
|
||||||
|
): Boolean {
|
||||||
|
if (e1 == null) return false
|
||||||
|
val dy = e2.y - e1.y
|
||||||
|
val dx = e2.x - e1.x
|
||||||
|
// 下拉(dy > 0)且垂直幅度 > 水平
|
||||||
|
if (dy > 80 && kotlin.math.abs(dy) > kotlin.math.abs(dx)) {
|
||||||
|
navigateToPunch()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 给 ViewPager2 内部的 RecyclerView 添加触摸监听
|
||||||
|
binding.viewPager.getChildAt(0)?.setOnTouchListener { _, event ->
|
||||||
|
gestureDetector.onTouchEvent(event)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 跳转到考勤打卡页 */
|
||||||
|
private fun navigateToPunch() {
|
||||||
|
val currentDest = findNavController().currentDestination?.id
|
||||||
|
if (currentDest != R.id.homeFragment) return
|
||||||
|
findNavController().navigate(R.id.action_home_to_punch)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,313 @@
|
|||||||
package com.xiaoqu.watch.ui.punch
|
package com.xiaoqu.watch.ui.punch
|
||||||
|
|
||||||
|
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.punch.PunchStatus
|
||||||
import com.xiaoqu.watch.databinding.FragmentPunchBinding
|
import com.xiaoqu.watch.databinding.FragmentPunchBinding
|
||||||
|
import com.xiaoqu.watch.device.screen.ScreenController
|
||||||
|
import com.xiaoqu.watch.event.AppEvent
|
||||||
|
import com.xiaoqu.watch.event.EventBus
|
||||||
|
import com.xiaoqu.watch.network.ApiResult
|
||||||
|
import com.xiaoqu.watch.network.api.PunchApi
|
||||||
|
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.QuConfirmDialog
|
||||||
|
import com.xiaoqu.watch.ui.widget.QuTipDialog
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 考勤打卡页面
|
||||||
|
* 入口:首页下拉手势
|
||||||
|
* 3 种状态:未上班→上班打卡→已上班→下班打卡/撤销
|
||||||
|
*
|
||||||
|
* 流程(基于 discovery-map.md 考勤章节 + baseline/05 流程6):
|
||||||
|
* 上班:蓝牙识别1.5s → 确认弹窗 → POST onAndOffPunch → 成功
|
||||||
|
* 下班:蓝牙识别1.5s → 直接提交 → POST onAndOffPunch → 成功+低耗电
|
||||||
|
* 撤销:确认弹窗 → POST revokePunch → 恢复上班状态
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class PunchFragment : BaseFragment<FragmentPunchBinding>() {
|
class PunchFragment : BaseFragment<FragmentPunchBinding>() {
|
||||||
|
|
||||||
|
@Inject lateinit var punchApi: PunchApi
|
||||||
|
@Inject lateinit var screenController: ScreenController
|
||||||
|
@Inject lateinit var eventBus: EventBus
|
||||||
|
|
||||||
|
/** 当前考勤状态 */
|
||||||
|
private var punchStatus: PunchStatus? = null
|
||||||
|
|
||||||
|
/** 提示弹窗 */
|
||||||
|
private lateinit var tipDialog: QuTipDialog
|
||||||
|
|
||||||
|
/** 确认弹窗 */
|
||||||
|
private lateinit var confirmDialog: QuConfirmDialog
|
||||||
|
|
||||||
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentPunchBinding {
|
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentPunchBinding {
|
||||||
return FragmentPunchBinding.inflate(inflater, container, false)
|
return FragmentPunchBinding.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)
|
||||||
|
confirmDialog = QuConfirmDialog(dialogContainer)
|
||||||
|
|
||||||
|
// 获取考勤状态
|
||||||
|
fetchAttendance()
|
||||||
|
|
||||||
|
// 监听系统状态事件(电量更新状态栏)
|
||||||
|
observeEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 数据获取 =====
|
||||||
|
|
||||||
|
/** 获取当前考勤状态 */
|
||||||
|
private fun fetchAttendance() {
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
val result = safeApiCall { punchApi.getAttendance() }
|
||||||
|
when (result) {
|
||||||
|
is ApiResult.Success -> {
|
||||||
|
punchStatus = result.data
|
||||||
|
displayStatus(result.data ?: PunchStatus())
|
||||||
|
}
|
||||||
|
is ApiResult.Error -> {
|
||||||
|
Timber.w("考勤: API 错误 ${result.code}")
|
||||||
|
// 默认显示未上班
|
||||||
|
displayStatus(PunchStatus())
|
||||||
|
}
|
||||||
|
is ApiResult.NetworkError -> {
|
||||||
|
Timber.w("考勤: 网络异常")
|
||||||
|
displayStatus(PunchStatus())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== UI 显示 =====
|
||||||
|
|
||||||
|
/** 根据考勤状态更新页面显示(基于业务逻辑矩阵) */
|
||||||
|
private fun displayStatus(status: PunchStatus) {
|
||||||
|
// 重置所有可选元素
|
||||||
|
binding.btnRevoke.visibility = View.GONE
|
||||||
|
binding.lowPowerHint.visibility = View.GONE
|
||||||
|
|
||||||
|
when {
|
||||||
|
// 未上班 → 显示「上班打卡」
|
||||||
|
!status.isOnDuty -> {
|
||||||
|
binding.tvPunchStatus.text = "未上班"
|
||||||
|
binding.tvPunchStatus.setTextColor(requireContext().getColor(R.color.text_secondary))
|
||||||
|
binding.btnPunch.text = "上班打卡"
|
||||||
|
binding.btnPunch.setBackgroundColor(requireContext().getColor(R.color.primary))
|
||||||
|
binding.btnPunch.setOnClickListener { startPunch(0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已上班 + 已下班 → 显示「撤销」+「下班打卡」+ 低耗电提示
|
||||||
|
status.isOnDuty && status.isOffDuty -> {
|
||||||
|
binding.tvPunchStatus.text = "已下班"
|
||||||
|
binding.tvPunchStatus.setTextColor(requireContext().getColor(R.color.text_secondary))
|
||||||
|
binding.btnPunch.text = "下班打卡"
|
||||||
|
binding.btnPunch.setBackgroundColor(requireContext().getColor(R.color.primary))
|
||||||
|
binding.btnPunch.setOnClickListener { startPunch(1) }
|
||||||
|
binding.btnRevoke.visibility = View.VISIBLE
|
||||||
|
binding.btnRevoke.setOnClickListener { doRevoke() }
|
||||||
|
binding.lowPowerHint.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已上班 + 未下班 → 显示「下班打卡」
|
||||||
|
status.isOnDuty && !status.isOffDuty -> {
|
||||||
|
binding.tvPunchStatus.text = "已上班"
|
||||||
|
binding.tvPunchStatus.setTextColor(requireContext().getColor(R.color.success))
|
||||||
|
binding.btnPunch.text = "下班打卡"
|
||||||
|
binding.btnPunch.setBackgroundColor(requireContext().getColor(R.color.primary))
|
||||||
|
binding.btnPunch.setOnClickListener { startPunch(1) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 打卡操作 =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始打卡流程
|
||||||
|
* 1. 显示蓝牙识别 Loading(1.5秒)
|
||||||
|
* 2. 读取 beacons(当前用模拟数据)
|
||||||
|
* 3. 上班:弹确认框;下班:直接提交
|
||||||
|
*/
|
||||||
|
private fun startPunch(punchType: Int) {
|
||||||
|
// 显示蓝牙识别提示
|
||||||
|
tipDialog.show(
|
||||||
|
status = QuTipDialog.Status.LOCATION,
|
||||||
|
title = "蓝牙正在识别…",
|
||||||
|
back = false
|
||||||
|
)
|
||||||
|
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
// 等待 1.5 秒(模拟蓝牙扫描采集)
|
||||||
|
delay(1500)
|
||||||
|
tipDialog.dismiss()
|
||||||
|
|
||||||
|
// 读取蓝牙信标(TODO: 后续从 store.beacons 读取,当前用模拟数据)
|
||||||
|
val beaconMacs = getMockBeaconMacs()
|
||||||
|
|
||||||
|
if (beaconMacs.isEmpty()) {
|
||||||
|
// 无信标 → 提示失败
|
||||||
|
tipDialog.show(
|
||||||
|
status = QuTipDialog.Status.WARNING,
|
||||||
|
title = "打卡失败",
|
||||||
|
desc = "未搜索到蓝牙信标,请重试",
|
||||||
|
back = true, step = 0, countdown = 3
|
||||||
|
)
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
if (punchType == 0) {
|
||||||
|
// 上班 → 弹确认弹窗
|
||||||
|
confirmDialog.showText(
|
||||||
|
text = "确定上班打卡?",
|
||||||
|
onConfirm = { doPunch(punchType, beaconMacs) }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// 下班 → 不弹确认,直接提交
|
||||||
|
doPunch(punchType, beaconMacs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 执行打卡 API 调用 */
|
||||||
|
private fun doPunch(punchType: Int, beaconMacs: List<String>) {
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
val params = hashMapOf<String, Any>(
|
||||||
|
"beaconMacs" to beaconMacs,
|
||||||
|
"punchType" to punchType
|
||||||
|
)
|
||||||
|
val result = safeApiCall { punchApi.onAndOffPunch(params) }
|
||||||
|
|
||||||
|
when (result) {
|
||||||
|
is ApiResult.Success -> {
|
||||||
|
Timber.d("考勤: 打卡成功 punchType=$punchType")
|
||||||
|
tipDialog.show(
|
||||||
|
status = QuTipDialog.Status.SUCCESS,
|
||||||
|
title = "打卡成功",
|
||||||
|
back = true, step = 0, countdown = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// 副作用:更新工作状态和屏幕亮度
|
||||||
|
if (punchType == 0) {
|
||||||
|
// 上班 → 屏幕正常亮度
|
||||||
|
screenController.turnOn()
|
||||||
|
emitWorkState(true)
|
||||||
|
} else {
|
||||||
|
// 下班 → 低耗电(熄屏)
|
||||||
|
screenController.turnOff()
|
||||||
|
emitWorkState(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新考勤状态
|
||||||
|
fetchAttendance()
|
||||||
|
}
|
||||||
|
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 doRevoke() {
|
||||||
|
confirmDialog.showText(
|
||||||
|
text = "确定撤销打卡?",
|
||||||
|
onConfirm = {
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
val params = hashMapOf<String, Any>()
|
||||||
|
val result = safeApiCall { punchApi.revokePunch(params) }
|
||||||
|
|
||||||
|
when (result) {
|
||||||
|
is ApiResult.Success -> {
|
||||||
|
Timber.d("考勤: 撤销成功")
|
||||||
|
tipDialog.show(
|
||||||
|
status = QuTipDialog.Status.SUCCESS,
|
||||||
|
title = "撤销成功",
|
||||||
|
back = true, step = 0, countdown = 2
|
||||||
|
)
|
||||||
|
// 恢复上班状态
|
||||||
|
screenController.turnOn()
|
||||||
|
emitWorkState(true)
|
||||||
|
fetchAttendance()
|
||||||
|
}
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 辅助方法 =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模拟蓝牙信标 MAC 列表
|
||||||
|
* TODO: 蓝牙扫描模块完成后,改为从 store.beacons 读取
|
||||||
|
*/
|
||||||
|
private fun getMockBeaconMacs(): List<String> {
|
||||||
|
return listOf("AA:BB:CC:DD:EE:FF", "11:22:33:44:55:66")
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 发送工作状态变更事件 */
|
||||||
|
private fun emitWorkState(isWorking: Boolean) {
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
eventBus.emit(AppEvent.WorkStateChanged(isWorking))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 监听系统状态事件 */
|
||||||
|
private fun observeEvents() {
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
eventBus.events.collect { event ->
|
||||||
|
when (event) {
|
||||||
|
is AppEvent.BatteryChanged -> {
|
||||||
|
binding.statusBar.updateBattery(event.level, event.isCharging)
|
||||||
|
}
|
||||||
|
is AppEvent.BluetoothStateChanged -> {
|
||||||
|
binding.statusBar.updateBluetooth(event.isOn)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,117 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<!-- 考勤打卡页面(按原型图V3适老化设计)
|
||||||
|
3种状态:未上班 / 已上班 / 已下班
|
||||||
|
120dpi 换算,老年人大字体 -->
|
||||||
|
<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">
|
||||||
|
|
||||||
<!-- TODO: 打卡操作界面 -->
|
<!-- 固定状态栏 -->
|
||||||
|
<com.xiaoqu.watch.ui.widget.StatusBarView
|
||||||
|
android:id="@+id/statusBar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginStart="21dp"
|
||||||
|
android:layout_marginTop="27dp"
|
||||||
|
android:layout_marginEnd="21dp" />
|
||||||
|
|
||||||
</FrameLayout>
|
<!-- 居中内容区 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="21dp"
|
||||||
|
android:paddingEnd="21dp">
|
||||||
|
|
||||||
|
<!-- 考勤状态文字("未上班" / "已上班 07:02" / "已下班 17:05") -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvPunchStatus"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="28sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<!-- 按钮区域 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_marginTop="27dp">
|
||||||
|
|
||||||
|
<!-- 主按钮(上班打卡 / 下班打卡) -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/btnPunch"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:background="@color/primary"
|
||||||
|
android:textColor="@color/text_primary"
|
||||||
|
android:textSize="26sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<!-- 撤销按钮(已上班+已下班时显示) -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/btnRevoke"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:background="@color/grey_button"
|
||||||
|
android:textColor="@color/error"
|
||||||
|
android:textSize="22sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:text="撤销打卡"
|
||||||
|
android:layout_marginTop="11dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 低耗电模式提示(下班后显示) -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/lowPowerHint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_marginTop="21dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="低耗电模式"
|
||||||
|
android:textColor="@color/warning"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="蓝牙扫描已停止\nNFC 已关闭"
|
||||||
|
android:textColor="@color/text_secondary"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:lineSpacingMultiplier="1.5"
|
||||||
|
android:layout_marginTop="8dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- 底部提示(上滑返回) -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="上滑返回"
|
||||||
|
android:textColor="@color/text_secondary"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:paddingBottom="27dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|||||||
@@ -28,6 +28,9 @@
|
|||||||
<!-- 首页 → 任务列表 -->
|
<!-- 首页 → 任务列表 -->
|
||||||
<action android:id="@+id/action_home_to_taskList"
|
<action android:id="@+id/action_home_to_taskList"
|
||||||
app:destination="@id/taskListFragment" />
|
app:destination="@id/taskListFragment" />
|
||||||
|
<!-- 首页 → 考勤打卡(下拉入口) -->
|
||||||
|
<action android:id="@+id/action_home_to_punch"
|
||||||
|
app:destination="@id/punchFragment" />
|
||||||
</fragment>
|
</fragment>
|
||||||
|
|
||||||
<!-- 设备绑定页 -->
|
<!-- 设备绑定页 -->
|
||||||
|
|||||||
Reference in New Issue
Block a user