feat(nfc-task): NFC 任务打卡模块(单个+批量+硬件开锁)
REQ-20260506-0024 新增 NfcTaskManager: - startTaskPunch(taskId):任务详情页单个 NFC 打卡 - startActivePunch():返回键触发主动打卡 → checkNfcType 判断:hardwareNfcFlag=true 硬件开锁 / false 批量打卡 - NFC 超时自动关闭 + 震动/音效反馈 - isScanning 标记防重复触发 TaskApi 增加 3 个接口: - POST watchTask/nfcToBeginTask(单个打卡) - POST watchTask/nfcBatchBeginTask(批量打卡) - GET nfcInfo/nfcOpenLock(类型判断) 返回键逻辑修正: - 原:返回键→考勤打卡 - 现:返回键→主动打卡(批量任务 or 硬件开锁) - 考勤打卡只从下拉面板触发 - onBackKeyPressed 改为返回 Boolean(true=已处理) TaskDetailFragment: - "开启打卡"按钮填充 NFC 打卡逻辑(替换 TODO) - 扫描中按钮变灰禁用,成功/失败用 QuTipDialog Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,9 +9,11 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import com.xiaoqu.watch.databinding.ActivityMainBinding
|
import com.xiaoqu.watch.databinding.ActivityMainBinding
|
||||||
import com.xiaoqu.watch.event.AppEvent
|
import com.xiaoqu.watch.event.AppEvent
|
||||||
import com.xiaoqu.watch.event.EventBus
|
import com.xiaoqu.watch.event.EventBus
|
||||||
|
import com.xiaoqu.watch.data.prefs.UserPrefs
|
||||||
import com.xiaoqu.watch.device.screen.ScreenController
|
import com.xiaoqu.watch.device.screen.ScreenController
|
||||||
import com.xiaoqu.watch.device.sensor.AccelerometerWakeController
|
import com.xiaoqu.watch.device.sensor.AccelerometerWakeController
|
||||||
import com.xiaoqu.watch.service.manager.BluetoothScanManager
|
import com.xiaoqu.watch.service.manager.BluetoothScanManager
|
||||||
|
import com.xiaoqu.watch.service.manager.NfcTaskManager
|
||||||
import com.xiaoqu.watch.service.manager.NotificationManager
|
import com.xiaoqu.watch.service.manager.NotificationManager
|
||||||
import com.xiaoqu.watch.service.manager.SystemStateMonitor
|
import com.xiaoqu.watch.service.manager.SystemStateMonitor
|
||||||
import com.xiaoqu.watch.service.manager.UpdateManager
|
import com.xiaoqu.watch.service.manager.UpdateManager
|
||||||
@@ -45,6 +47,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
@Inject lateinit var updateManager: UpdateManager
|
@Inject lateinit var updateManager: UpdateManager
|
||||||
@Inject lateinit var screenController: ScreenController
|
@Inject lateinit var screenController: ScreenController
|
||||||
@Inject lateinit var bluetoothScanManager: BluetoothScanManager
|
@Inject lateinit var bluetoothScanManager: BluetoothScanManager
|
||||||
|
/** NFC 任务打卡管理器 */
|
||||||
|
@Inject lateinit var nfcTaskManager: NfcTaskManager
|
||||||
|
@Inject lateinit var userPrefs: UserPrefs
|
||||||
/** OTA 更新弹窗 */
|
/** OTA 更新弹窗 */
|
||||||
lateinit var updateDialog: UpdateDialogView
|
lateinit var updateDialog: UpdateDialogView
|
||||||
lateinit var notificationBanner: NotificationBannerView
|
lateinit var notificationBanner: NotificationBannerView
|
||||||
@@ -130,8 +135,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
/** 下拉回调(由 HomeFragment 注册) */
|
/** 下拉回调(由 HomeFragment 注册) */
|
||||||
var onSwipeDown: (() -> Unit)? = null
|
var onSwipeDown: (() -> Unit)? = null
|
||||||
|
|
||||||
/** 返回键回调(由 HomeFragment 注册,触发 NFC 打卡) */
|
/** 返回键回调(由 HomeFragment 注册)。返回 true = 已处理,不触发主动打卡 */
|
||||||
var onBackKeyPressed: (() -> Unit)? = null
|
var onBackKeyPressed: (() -> Boolean)? = null
|
||||||
|
|
||||||
private var touchStartY = 0f
|
private var touchStartY = 0f
|
||||||
private var touchStartX = 0f
|
private var touchStartX = 0f
|
||||||
@@ -167,20 +172,45 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 物理返回键拦截:
|
* 物理返回键拦截:
|
||||||
* - 已绑定用户 → 开启 NFC 打卡模式(后续模块实现)
|
* - 已绑定用户 → 主动打卡(批量任务打卡 or 硬件开锁)
|
||||||
|
* - NFC 扫描中 → 忽略(防重复)
|
||||||
* - 未绑定 → 无操作
|
* - 未绑定 → 无操作
|
||||||
* - 所有情况阻止默认页面回退
|
* - 所有情况阻止默认页面回退
|
||||||
|
*
|
||||||
|
* 注意:考勤打卡只从下拉面板触发,不走返回键
|
||||||
*/
|
*/
|
||||||
private fun setupBackButton() {
|
private fun setupBackButton() {
|
||||||
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
||||||
override fun handleOnBackPressed() {
|
override fun handleOnBackPressed() {
|
||||||
Timber.d("Back button pressed - intercepted")
|
Timber.d("Back button pressed - intercepted")
|
||||||
// 已绑定用户 → 触发 NFC 打卡(由 HomeFragment 注册回调)
|
|
||||||
onBackKeyPressed?.invoke()
|
// 由 HomeFragment 注册的回调处理(面板展<E69DBF><E5B195>时收回)
|
||||||
|
val callback = onBackKeyPressed
|
||||||
|
if (callback != null && callback()) {
|
||||||
|
// 回调返回 true = 已处理,不继续
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 回调返回 false 或无回调 → 触发主动打卡
|
||||||
|
startActivePunchFromBackKey()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 返回键触发主动打卡(批量任务 or 硬件开锁) */
|
||||||
|
private fun startActivePunchFromBackKey() {
|
||||||
|
// 已在 NFC 扫描中 → 忽略
|
||||||
|
if (nfcTaskManager.isScanning) return
|
||||||
|
// 未绑定 → 忽略
|
||||||
|
if (!userPrefs.isBound) return
|
||||||
|
|
||||||
|
Timber.d("返回键: 触发主动打卡")
|
||||||
|
nfcTaskManager.startActivePunch { success, message ->
|
||||||
|
if (message.isNotEmpty()) {
|
||||||
|
android.widget.Toast.makeText(this@MainActivity, message, android.widget.Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ===== OTA 更新 =====
|
// ===== OTA 更新 =====
|
||||||
|
|
||||||
/** 设置更新弹窗按钮回调 */
|
/** 设置更新弹窗按钮回调 */
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.xiaoqu.watch.data.task
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
/**
|
||||||
|
* checkNfcType API 响应数据
|
||||||
|
* GET nfcInfo/nfcOpenLock 返回,判断 NFC 卡是硬件开锁还是任务打卡
|
||||||
|
*/
|
||||||
|
data class NfcTypeResult(
|
||||||
|
/** true=硬件开锁设备,false=任务打卡信标 */
|
||||||
|
@SerializedName("hardwareNfcFlag") val hardwareNfcFlag: Boolean = false,
|
||||||
|
/** 开锁状态(仅 hardwareNfcFlag=true 时有意义):0=开锁成功 1=设备离线 2=开锁失败 3=无权限 */
|
||||||
|
@SerializedName("status") val status: Int = 0,
|
||||||
|
/** 附加信息 */
|
||||||
|
@SerializedName("content") val content: String? = null
|
||||||
|
)
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.xiaoqu.watch.network.api
|
package com.xiaoqu.watch.network.api
|
||||||
|
|
||||||
|
import com.xiaoqu.watch.data.task.NfcTypeResult
|
||||||
import com.xiaoqu.watch.data.task.TaskDetail
|
import com.xiaoqu.watch.data.task.TaskDetail
|
||||||
import com.xiaoqu.watch.data.task.TaskItem
|
import com.xiaoqu.watch.data.task.TaskItem
|
||||||
import com.xiaoqu.watch.data.task.TaskStatistics
|
import com.xiaoqu.watch.data.task.TaskStatistics
|
||||||
@@ -37,4 +38,16 @@ interface TaskApi {
|
|||||||
/** 确认完成 */
|
/** 确认完成 */
|
||||||
@POST("task/completeTask")
|
@POST("task/completeTask")
|
||||||
suspend fun completeTask(@Body params: HashMap<String, Any>): ApiResponse<Any>
|
suspend fun completeTask(@Body params: HashMap<String, Any>): ApiResponse<Any>
|
||||||
|
|
||||||
|
/** NFC 单个任务打卡(有场景打卡) */
|
||||||
|
@POST("watchTask/nfcToBeginTask")
|
||||||
|
suspend fun nfcToBeginTask(@Body params: HashMap<String, Any>): ApiResponse<Any>
|
||||||
|
|
||||||
|
/** NFC 批量任务打卡(返回键主动打卡) */
|
||||||
|
@POST("watchTask/nfcBatchBeginTask")
|
||||||
|
suspend fun nfcBatchBeginTask(@Body params: HashMap<String, Any>): ApiResponse<Any>
|
||||||
|
|
||||||
|
/** 检查 NFC 类型(批量打卡 or 硬件开锁) */
|
||||||
|
@GET("nfcInfo/nfcOpenLock")
|
||||||
|
suspend fun checkNfcType(@Query("nfcId") nfcId: String): ApiResponse<NfcTypeResult>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,276 @@
|
|||||||
|
package com.xiaoqu.watch.service.manager
|
||||||
|
|
||||||
|
import com.xiaoqu.watch.device.nfc.NfcController
|
||||||
|
import com.xiaoqu.watch.device.sensor.VibrationController
|
||||||
|
import com.xiaoqu.watch.network.ApiResult
|
||||||
|
import com.xiaoqu.watch.network.api.TaskApi
|
||||||
|
import com.xiaoqu.watch.network.safeApiCall
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NFC 任务打卡管理器
|
||||||
|
*
|
||||||
|
* 统一处理三种 NFC 打卡场景:
|
||||||
|
* 1. 任务单个打卡(任务详情页"开启打卡"按钮)
|
||||||
|
* 2. 主动批量打卡(返回键触发,checkNfcType → nfcBatchBeginTask)
|
||||||
|
* 3. 硬件开锁(返回键触发,checkNfcType → 根据 status 播放语音)
|
||||||
|
*
|
||||||
|
* 与 PunchViewModel(考勤打卡)独立,互不干扰。
|
||||||
|
* 通过 isScanning 防止两者同时操作 NFC。
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
class NfcTaskManager @Inject constructor(
|
||||||
|
private val nfcController: NfcController,
|
||||||
|
private val vibrationController: VibrationController,
|
||||||
|
private val taskApi: TaskApi
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
/** 默认 NFC 超时(毫秒),可被 MQTT type=4 的 nfcOpenTime 覆盖 */
|
||||||
|
private const val DEFAULT_NFC_TIMEOUT_MS = 20_000L
|
||||||
|
// 震动方案 planId
|
||||||
|
private const val PLAN_PUNCH_SUCCESS = 4
|
||||||
|
private const val PLAN_PUNCH_FAIL = 7
|
||||||
|
private const val PLAN_NFC_OPEN = 8
|
||||||
|
private const val PLAN_NFC_CLOSE = 9
|
||||||
|
private const val PLAN_OFFLINE = 10
|
||||||
|
private const val PLAN_OPEN_FAILED = 11
|
||||||
|
private const val PLAN_NO_AUTH = 12
|
||||||
|
private const val PLAN_OPENING = 13
|
||||||
|
}
|
||||||
|
|
||||||
|
/** NFC 是否正在扫描(供外部互斥检查) */
|
||||||
|
@Volatile
|
||||||
|
var isScanning = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
/** NFC 超时时间(毫秒),通过 MQTT type=4 更新 */
|
||||||
|
var nfcTimeoutMs: Long = DEFAULT_NFC_TIMEOUT_MS
|
||||||
|
|
||||||
|
/** 协程作用域 */
|
||||||
|
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||||
|
|
||||||
|
/** 超时 Job */
|
||||||
|
private var timeoutJob: Job? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 场景 1:任务单个打卡
|
||||||
|
* 开启 NFC → 读卡 → POST nfcToBeginTask → 回调结果
|
||||||
|
*
|
||||||
|
* @param taskId 任务 ID
|
||||||
|
* @param onResult 结果回调(主线程),success + message
|
||||||
|
*/
|
||||||
|
fun startTaskPunch(taskId: Long, onResult: (success: Boolean, message: String) -> Unit) {
|
||||||
|
if (isScanning) {
|
||||||
|
Timber.d("NFC任务打卡: 已在扫描中,忽略")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isScanning = true
|
||||||
|
Timber.d("NFC任<EFBFBD><EFBFBD>打卡: 开启单个打卡, taskId=%d", taskId)
|
||||||
|
|
||||||
|
// 开启 NFC + 音效
|
||||||
|
vibrationController.executeByPlanId(PLAN_NFC_OPEN)
|
||||||
|
nfcController.open()
|
||||||
|
|
||||||
|
// 开始扫描
|
||||||
|
nfcController.startScan { nfcId ->
|
||||||
|
Timber.d("NFC任务打卡: 读到卡号 %s", nfcId)
|
||||||
|
cancelTimeout()
|
||||||
|
nfcController.stopScan()
|
||||||
|
nfcController.close()
|
||||||
|
|
||||||
|
// 调用打卡 API
|
||||||
|
scope.launch {
|
||||||
|
val params = hashMapOf<String, Any>("id" to taskId, "nfcId" to nfcId)
|
||||||
|
val result = safeApiCall { taskApi.nfcToBeginTask(params) }
|
||||||
|
when (result) {
|
||||||
|
is ApiResult.Success -> {
|
||||||
|
Timber.d("NFC任务打卡: 打卡成功")
|
||||||
|
vibrationController.executeByPlanId(PLAN_PUNCH_SUCCESS)
|
||||||
|
onResult(true, "打卡成功")
|
||||||
|
}
|
||||||
|
is ApiResult.Error -> {
|
||||||
|
Timber.w("NFC任务打卡: 打卡失败 - %s", result.message)
|
||||||
|
vibrationController.executeByPlanId(PLAN_PUNCH_FAIL)
|
||||||
|
onResult(false, result.message ?: "<EFBFBD><EFBFBD>卡失败")
|
||||||
|
}
|
||||||
|
is ApiResult.NetworkError -> {
|
||||||
|
Timber.w(result.exception, "NFC任务打卡: 网络异常")
|
||||||
|
vibrationController.executeByPlanId(PLAN_PUNCH_FAIL)
|
||||||
|
onResult(false, "网络异常")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isScanning = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动超时
|
||||||
|
startTimeout {
|
||||||
|
Timber.d("NFC任务打卡: 超时自动关闭")
|
||||||
|
closeNfc()
|
||||||
|
onResult(false, "超<EFBFBD><EFBFBD><EFBFBD>")
|
||||||
|
isScanning = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 场景 2+3:主动打卡(<E58DA1><EFBC88><EFBFBD>回键触发)
|
||||||
|
* 开启 NFC → 读卡 → checkNfcType → 批量打卡 or 硬件开锁
|
||||||
|
*
|
||||||
|
* @param onResult 结果回调(主线程),success + message
|
||||||
|
*/
|
||||||
|
fun startActivePunch(onResult: (success: Boolean, message: String) -> Unit) {
|
||||||
|
if (isScanning) {
|
||||||
|
Timber.d("NFC主动打卡: 已在扫<E59CA8><E689AB>中,忽略")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isScanning = true
|
||||||
|
Timber.d("NFC主动<EFBFBD><EFBFBD>卡: 开启(返回键触发)")
|
||||||
|
|
||||||
|
// 开启 NFC + <20><>效
|
||||||
|
vibrationController.executeByPlanId(PLAN_NFC_OPEN)
|
||||||
|
nfcController.open()
|
||||||
|
|
||||||
|
// 开始扫描
|
||||||
|
nfcController.startScan { nfcId ->
|
||||||
|
Timber.d("NFC主动打卡: 读到<E8AFBB><E588B0><EFBFBD>号 %s", nfcId)
|
||||||
|
cancelTimeout()
|
||||||
|
nfcController.stopScan()
|
||||||
|
nfcController.close()
|
||||||
|
vibrationController.executeByPlanId(PLAN_NFC_CLOSE)
|
||||||
|
|
||||||
|
// 判断类型:批量打卡 or 硬件开锁
|
||||||
|
scope.launch {
|
||||||
|
handleNfcType(nfcId, onResult)
|
||||||
|
isScanning = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动超时
|
||||||
|
startTimeout {
|
||||||
|
Timber.d("NFC主动打卡: 超时自动关闭")
|
||||||
|
closeNfc()
|
||||||
|
vibrationController.executeByPlanId(PLAN_NFC_CLOSE)
|
||||||
|
onResult(false, "")
|
||||||
|
isScanning = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 取消 NFC 扫描 */
|
||||||
|
fun cancel() {
|
||||||
|
if (!isScanning) return
|
||||||
|
Timber.d("NFC任务: 手动取消")
|
||||||
|
cancelTimeout()
|
||||||
|
closeNfc()
|
||||||
|
vibrationController.executeByPlanId(PLAN_NFC_CLOSE)
|
||||||
|
isScanning = false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断 NFC 类型并处理
|
||||||
|
* hardwareNfcFlag=true → 硬件开锁(播<EFBC88><E692AD><EFBFBD>语音)
|
||||||
|
* hardwareNfcFlag=false → 批量任务打卡(调 API)
|
||||||
|
*/
|
||||||
|
private suspend fun handleNfcType(nfcId: String, onResult: (Boolean, String) -> Unit) {
|
||||||
|
val result = safeApiCall { taskApi.checkNfcType(nfcId) }
|
||||||
|
when (result) {
|
||||||
|
is ApiResult.Success -> {
|
||||||
|
val data = result.data
|
||||||
|
if (data == null) {
|
||||||
|
onResult(false, "数据异常")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.hardwareNfcFlag) {
|
||||||
|
// 硬件开锁
|
||||||
|
handleHardwareUnlock(data.status, onResult)
|
||||||
|
} else {
|
||||||
|
// 批量任务打卡
|
||||||
|
handleBatchPunch(nfcId, onResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is ApiResult.Error -> {
|
||||||
|
Timber.w("NFC主动打卡: checkNfcType 失败 - %s", result.message)
|
||||||
|
vibrationController.executeByPlanId(PLAN_PUNCH_FAIL)
|
||||||
|
onResult(false, result.message ?: "判断失败")
|
||||||
|
}
|
||||||
|
is ApiResult.NetworkError -> {
|
||||||
|
Timber.w(result.exception, "NFC主动打卡: 网络异常")
|
||||||
|
vibrationController.executeByPlanId(PLAN_PUNCH_FAIL)
|
||||||
|
onResult(false, "网络异常")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 硬件开锁:根据 status 播放对应语音 */
|
||||||
|
private fun handleHardwareUnlock(status: Int, onResult: (Boolean, String) -> Unit) {
|
||||||
|
Timber.d("NFC<EFBFBD><EFBFBD>动打卡: 硬件开锁, status=%d", status)
|
||||||
|
when (status) {
|
||||||
|
0 -> {
|
||||||
|
vibrationController.executeByPlanId(PLAN_OPENING)
|
||||||
|
onResult(true, "正在开锁")
|
||||||
|
}
|
||||||
|
1 -> {
|
||||||
|
vibrationController.executeByPlanId(PLAN_OFFLINE)
|
||||||
|
onResult(false, "设备离线")
|
||||||
|
}
|
||||||
|
2 -> {
|
||||||
|
vibrationController.executeByPlanId(PLAN_OPEN_FAILED)
|
||||||
|
onResult(false, "开锁失败")
|
||||||
|
}
|
||||||
|
3 -> {
|
||||||
|
vibrationController.executeByPlanId(PLAN_NO_AUTH)
|
||||||
|
onResult(false, "无开锁权限")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
onResult(false, "<EFBFBD><EFBFBD>知状态")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 批量任务打卡 */
|
||||||
|
private suspend fun handleBatchPunch(nfcId: String, onResult: (Boolean, String) -> Unit) {
|
||||||
|
Timber.d("NFC主动打卡: 批量打卡, nfcId=%s", nfcId)
|
||||||
|
val params = hashMapOf<String, Any>("nfcId" to nfcId)
|
||||||
|
val result = safeApiCall { taskApi.nfcBatchBeginTask(params) }
|
||||||
|
when (result) {
|
||||||
|
is ApiResult.Success -> {
|
||||||
|
Timber.d("NFC主动打卡: 批量打卡成功")
|
||||||
|
vibrationController.executeByPlanId(PLAN_PUNCH_SUCCESS)
|
||||||
|
onResult(true, "打卡成功")
|
||||||
|
}
|
||||||
|
is ApiResult.Error -> {
|
||||||
|
Timber.w("NFC主动打卡: 批量打卡失败 - %s", result.message)
|
||||||
|
vibrationController.executeByPlanId(PLAN_PUNCH_FAIL)
|
||||||
|
onResult(false, result.message ?: "打卡失败")
|
||||||
|
}
|
||||||
|
is ApiResult.NetworkError -> {
|
||||||
|
Timber.w(result.exception, "NFC主动打卡: 网络<E7BD91><E7BB9C>常")
|
||||||
|
vibrationController.executeByPlanId(PLAN_PUNCH_FAIL)
|
||||||
|
onResult(false, "网络异常")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 关闭 NFC 硬件 */
|
||||||
|
private fun closeNfc() {
|
||||||
|
nfcController.stopScan()
|
||||||
|
nfcController.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 启动超时协程 */
|
||||||
|
private fun startTimeout(onTimeout: () -> Unit) {
|
||||||
|
timeoutJob = scope.launch {
|
||||||
|
delay(nfcTimeoutMs)
|
||||||
|
onTimeout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 取消超时协程 */
|
||||||
|
private fun cancelTimeout() {
|
||||||
|
timeoutJob?.cancel()
|
||||||
|
timeoutJob = null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -264,24 +264,15 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 物理返回键触发 NFC 打卡(已绑定用户在首页时)
|
// 物理返回键回调:面板已展开时收回面板
|
||||||
|
// 注意:主动打卡(批量任务+开锁)由 MainActivity 直接处理,不走这里
|
||||||
|
// 考勤打卡只从下拉面<E68B89><E99DA2>触发
|
||||||
mainActivity.onBackKeyPressed = {
|
mainActivity.onBackKeyPressed = {
|
||||||
if (userPrefs.isBound) {
|
|
||||||
// 面板已展开 → 收回面板
|
|
||||||
if (punchPanel.isShowing) {
|
if (punchPanel.isShowing) {
|
||||||
punchPanel.dismiss()
|
punchPanel.dismiss()
|
||||||
|
true // 已处理,不触发主动打卡
|
||||||
} else {
|
} else {
|
||||||
// 展开面板并自动开始上班/下班打卡
|
false // 未处理,让 MainActivity 触发主动打卡
|
||||||
showPunchPanel()
|
|
||||||
// 延迟一帧等面板展开后,根据状态自动触发打卡
|
|
||||||
binding.root.post {
|
|
||||||
val state = punchViewModel.uiState.value
|
|
||||||
when {
|
|
||||||
state.onPunchState == 0 -> punchViewModel.startPunch(0)
|
|
||||||
state.onPunchState == 1 -> punchViewModel.startPunch(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import com.xiaoqu.watch.databinding.FragmentTaskDetailBinding
|
|||||||
import com.xiaoqu.watch.network.ApiResult
|
import com.xiaoqu.watch.network.ApiResult
|
||||||
import com.xiaoqu.watch.network.api.TaskApi
|
import com.xiaoqu.watch.network.api.TaskApi
|
||||||
import com.xiaoqu.watch.network.safeApiCall
|
import com.xiaoqu.watch.network.safeApiCall
|
||||||
|
import com.xiaoqu.watch.service.manager.NfcTaskManager
|
||||||
import com.xiaoqu.watch.ui.common.BaseFragment
|
import com.xiaoqu.watch.ui.common.BaseFragment
|
||||||
import com.xiaoqu.watch.ui.widget.QuTipDialog
|
import com.xiaoqu.watch.ui.widget.QuTipDialog
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
@@ -28,6 +29,7 @@ import javax.inject.Inject
|
|||||||
class TaskDetailFragment : BaseFragment<FragmentTaskDetailBinding>() {
|
class TaskDetailFragment : BaseFragment<FragmentTaskDetailBinding>() {
|
||||||
|
|
||||||
@Inject lateinit var taskApi: TaskApi
|
@Inject lateinit var taskApi: TaskApi
|
||||||
|
@Inject lateinit var nfcTaskManager: NfcTaskManager
|
||||||
|
|
||||||
/** 当前任务数据 */
|
/** 当前任务数据 */
|
||||||
private var taskDetail: TaskDetail? = null
|
private var taskDetail: TaskDetail? = null
|
||||||
@@ -116,13 +118,12 @@ class TaskDetailFragment : BaseFragment<FragmentTaskDetailBinding>() {
|
|||||||
// 待打卡
|
// 待打卡
|
||||||
3 -> {
|
3 -> {
|
||||||
if (detail.hasPosition) {
|
if (detail.hasPosition) {
|
||||||
// 有场景 → 橙色「开启打卡」(NFC,后续实现)
|
// 有场景 → 橙色「开启打卡」(NFC 任务打卡)
|
||||||
btn.text = "开启打卡"
|
btn.text = "开启打卡"
|
||||||
btn.setBackgroundResource(R.drawable.bg_foot_btn_orange)
|
btn.setBackgroundResource(R.drawable.bg_foot_btn_orange)
|
||||||
btn.setTextColor(requireContext().getColor(R.color.background))
|
btn.setTextColor(requireContext().getColor(R.color.background))
|
||||||
btn.setOnClickListener {
|
btn.setOnClickListener {
|
||||||
// TODO: NFC 打卡流程
|
startNfcTaskPunch(detail.id)
|
||||||
Timber.d("任务详情: NFC 打卡(后续实现)")
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 无场景 → 绿色「确认打卡」
|
// 无场景 → 绿色「确认打卡」
|
||||||
@@ -212,6 +213,39 @@ class TaskDetailFragment : BaseFragment<FragmentTaskDetailBinding>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** NFC 任务打卡(有场景打卡) */
|
||||||
|
private fun startNfcTaskPunch(taskId: Long) {
|
||||||
|
val btn = binding.btnAction
|
||||||
|
// 切换到扫描状态
|
||||||
|
btn.text = "贴近信标..."
|
||||||
|
btn.isEnabled = false
|
||||||
|
btn.setBackgroundResource(R.drawable.bg_foot_btn_grey)
|
||||||
|
|
||||||
|
nfcTaskManager.startTaskPunch(taskId) { success, message ->
|
||||||
|
if (success) {
|
||||||
|
tipDialog.show(
|
||||||
|
status = QuTipDialog.Status.SUCCESS,
|
||||||
|
title = "打卡成功",
|
||||||
|
back = true, step = 1, countdown = 2,
|
||||||
|
onBack = { findNavController().popBackStack() }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// 失败或超时 → 恢复按钮
|
||||||
|
if (message != "超时") {
|
||||||
|
tipDialog.show(
|
||||||
|
status = QuTipDialog.Status.ERROR,
|
||||||
|
title = "打卡失败",
|
||||||
|
desc = message,
|
||||||
|
back = false, step = 0, countdown = 3
|
||||||
|
)
|
||||||
|
}
|
||||||
|
btn.text = "开启打卡"
|
||||||
|
btn.isEnabled = true
|
||||||
|
btn.setBackgroundResource(R.drawable.bg_foot_btn_orange)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 确认完成操作 */
|
/** 确认完成操作 */
|
||||||
private fun doCompleteTask(taskId: Long) {
|
private fun doCompleteTask(taskId: Long) {
|
||||||
viewLifecycleOwner.lifecycleScope.launch {
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
|||||||
7
app/src/main/res/drawable/bg_foot_btn_grey.xml
Normal file
7
app/src/main/res/drawable/bg_foot_btn_grey.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- 底部按钮:灰色(NFC 扫描中禁用态) -->
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="#FF666666" />
|
||||||
|
<corners android:bottomLeftRadius="59dp" android:bottomRightRadius="59dp" />
|
||||||
|
</shape>
|
||||||
Reference in New Issue
Block a user