package com.sw.dualscreen.viewmodel import androidx.lifecycle.viewModelScope import com.arcsoft.face.ErrorInfo import com.sw.dualscreen.GlobalData import com.sw.dualscreen.model.request.UserNutritionParam import com.sw.dualscreen.model.request.v2.BindUserOrderRequest import com.sw.dualscreen.model.request.v2.PlaceOrderRequest import com.sw.dualscreen.model.response.ApiResponse import com.sw.dualscreen.model.response.DinnerType import com.sw.dualscreen.model.response.FoodInfo import com.sw.dualscreen.model.response.FoodOrderModel import com.sw.dualscreen.model.response.FoodSearchReq import com.sw.dualscreen.model.response.MemberInfo import com.sw.dualscreen.model.response.UserNutrition import com.sw.dualscreen.model.response.v2.CollectedFoodV2 import com.sw.dualscreen.model.response.v2.FaceVO import com.sw.dualscreen.model.response.v2.NewFoodInfo import com.sw.dualscreen.model.response.v2.NewMemberInfo import com.sw.dualscreen.model.response.v2.SettlementOrder import com.sw.dualscreen.model.response.v2.toFoodInfo import com.sw.dualscreen.model.response.v2.toFoodOrderModel import com.sw.dualscreen.model.response.v2.toMemberInfo import com.sw.dualscreen.network.ApiClient import com.sw.dualscreen.utils.FileUtil import com.sw.dualscreen.utils.SpTool import com.sw.plate.App import com.sw.plate.utils.Base64 import com.sw.plate.utils.ToastUtils import com.sw.plate.utils.arcface.FaceApi import com.sw.plate.utils.arcface.facedb.entity.FaceEntity import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import timber.log.Timber import java.io.File class NetViewModelV2 : BaseViewModel() { companion object { private const val TAG = "NetViewModelV2" const val PAGE_SIZE = 100L } private val faceApi: FaceApi = FaceApi() /** 人脸加载完成 */ private val _loadFaceResult = MutableStateFlow(false) val loadFaceResult: StateFlow = _loadFaceResult /** 饭点类型 早餐/午餐/晚餐 */ private val _dinnerTypeInfo = MutableStateFlow(null) val dinnerTypeInfo: StateFlow = _dinnerTypeInfo fun getDeviceConfig( onSuccess: (config: com.sw.dualscreen.model.response.DeviceConfig?) -> Unit, onFailure: (message: String) -> Unit ) { launch { try { val response = ApiClient.repositoryV2.getDeviceConfig() if (parseResponse(response)) { onSuccess(response.data) } else { onFailure(response.msg ?: "获取设备配置失败") } } catch (e: Exception) { Timber.e(e, "获取设备配置异常") onFailure("网络异常: ${e.message}") } } } /** 获取人脸全量数据(递归分页 + FaceApi 集成) */ fun getFacePage( pageNum: Long = 1L, pageSize: Long = PAGE_SIZE, onSuccess: () -> Unit = {}, onFailure: (String) -> Unit = {} ) { var currentPageNum = pageNum Timber.tag(TAG).d("getFacePage index = $currentPageNum") launch { _loadFaceResult.value = false // 重置持久化时间戳和内存累加器 SpTool.lastFaceTimestamp = 0 lastFaceTimestamp = 0L FileUtil.saveLog("获取人脸全量数据开始,重置时间戳为0") val response = ApiClient.repositoryV2.getFacePage(currentPageNum, pageSize) if (!parseResponse(response)) { onFailure("获取人脸数据失败,${response.msg}(${response.code})") return@launch } withContext(Dispatchers.Default) { val list = response.data ?: emptyList() if (pageNum == 1L && list.isEmpty()) { onFailure("查询人脸数据为空") return@withContext } val faceEntity = list.map { vo -> FaceEntity( vo.userId, // userName Base64.decode(vo.faceFeature), // featureData vo.personType ?: if (vo.member == true) "1" else "2", // userType vo.cardNo ?: "", // cardNo vo.userId ?: "", // userId vo.userFaceId ?: "", // userFaceId vo.member ?: false, // member vo.faceUpdateTimestamp?.toLongOrNull() ?: 0L // faceUpdateTimestamp ) } faceApi.updateFaceData(currentPageNum.toInt(), faceEntity) // 取当前页最大时间戳(后端返回 String 类型,需转为 Long 比较),跨页累加防止中间页有更大值被覆盖 val pageMaxTimestamp = list.maxOf { it.faceUpdateTimestamp?.toLongOrNull() ?: 0L } lastFaceTimestamp = maxOf(lastFaceTimestamp, pageMaxTimestamp) if (list.size >= pageSize) { currentPageNum++ getFacePage(currentPageNum, pageSize, onSuccess, onFailure) return@withContext } SpTool.lastFaceTimestamp = lastFaceTimestamp FileUtil.saveLog("获取人脸[全量]数据结束,时间戳为:$lastFaceTimestamp") _loadFaceResult.value = true onSuccess() } } } /** 获取人脸增量数据(FaceApi 集成) */ fun getFaceIncrement( pageNum: Long = 1L, pageSize: Long = PAGE_SIZE, timestamp: Long, onAllQueryFinished: () -> Unit, onPageQueryFinished: (List) -> Unit, onFailure: (String) -> Unit = {} ) { Timber.tag(TAG).d("getFaceIncrement timestamp=$timestamp") launch { val response = ApiClient.repositoryV2.getFaceIncrement(pageNum, pageSize, timestamp) if (!parseResponse(response)) { onFailure(response.msg ?: "获取增量人脸数据失败") return@launch } withContext(Dispatchers.Default) { val list = response.data ?: emptyList() if (pageNum == 1L && list.isEmpty()) { return@withContext } onPageQueryFinished(list) // 取列表中最大时间戳(后端返回 String 类型,需转为 Long),而非依赖列表最后一条 lastFaceTimestamp = list.maxOf { it.faceUpdateTimestamp?.toLongOrNull() ?: 0L } SpTool.lastFaceTimestamp = lastFaceTimestamp FileUtil.saveLog("获取人脸[增量]数据结束,时间戳为:$lastFaceTimestamp") onAllQueryFinished() } } } fun getFoodByNames( names: String, onSuccess: (list: List) -> Unit, onFailure: (message: String) -> Unit ) { launch { try { val response = ApiClient.repositoryV2.getFoodByNames(names.split(",")) if (parseResponse(response)) { onSuccess(response.data ?: emptyList()) } else { onFailure(response.msg ?: "查询菜品失败") } } catch (e: Exception) { Timber.e(e, "查询菜品异常") onFailure("网络异常: ${e.message}") } } } fun placeOrder( orderRequest: PlaceOrderRequest, onSuccess: (orderNo: String?) -> Unit, onFailure: (message: String) -> Unit ) { launchWithLoading { try { val response = ApiClient.repositoryV2.placeOrder(orderRequest) if (parseResponse(response)) { onSuccess(response.data) } else { onFailure(response.msg ?: "下单失败") } } catch (e: Exception) { Timber.e(e, "开餐下单异常") onFailure("网络异常: ${e.message}") } } } fun bindUserOrder( userId: Long, orderNo: String, mode: Int? = null, onSuccess: () -> Unit, onFailure: (message: String) -> Unit ) { launch { try { val response = ApiClient.repositoryV2.bindUserOrder(userId, orderNo, mode) if (parseResponse(response)) { onSuccess() } else { onFailure(response.msg ?: "绑定订单失败") } } catch (e: Exception) { Timber.e(e, "绑定订单异常") onFailure("网络异常: ${e.message}") } } } fun getCollectPage( pageNum: Long = 1L, pageSize: Long = PAGE_SIZE, foodName: String? = null, onSuccess: (list: List) -> Unit, onFailure: (message: String) -> Unit ) { launch { try { val response = ApiClient.repositoryV2.getCollectPage(pageNum, pageSize, foodName) if (parseResponse(response)) { onSuccess(response.data ?: emptyList()) } else { onFailure(response.msg ?: "获取采集列表失败") } } catch (e: Exception) { Timber.e(e, "获取采集列表异常") onFailure("网络异常: ${e.message}") } } } fun getCollectVectorPage( pageNum: Long = 1L, pageSize: Long = PAGE_SIZE, onSuccess: (list: List) -> Unit, onFailure: (message: String) -> Unit ) { launch { try { val response = ApiClient.repositoryV2.getCollectVectorPage(pageNum, pageSize) if (parseResponse(response)) { onSuccess(response.data ?: emptyList()) } else { onFailure(response.msg ?: "获取采集列表失败") } } catch (e: Exception) { Timber.e(e, "获取采集列表异常") onFailure("网络异常: ${e.message}") } } } /** 上传采集图片(suspend,直接返回 picUrls 列表) */ suspend fun uploadCollect( foodId: Long, foodName: String, version: String, foodVector: String, fileList: List ): List? { Timber.tag(TAG).d("uploadCollect foodId=$foodId, foodName=$foodName") val fileListNotNull = fileList.filter { it.exists() } if (fileListNotNull.isEmpty()) { return null } val response = ApiClient.repositoryV2.uploadCollect( foodId, foodName, version, foodVector, fileListNotNull ) if (!parseResponse(response)) { return null } return response.data } /** 删除采集菜品 */ fun deleteCollect( foodId: String?, version: String?, block: (Boolean) -> Unit ) { Timber.tag(TAG).d("deleteCollect foodId=$foodId, version=$version") val id = foodId?.toLongOrNull() ?: run { block(false) return } launchWithLoading { try { val response = ApiClient.repositoryV2.deleteCollect(id, version ?: "") if (parseResponse(response)) { block(true) } else { block(false) } } catch (e: Exception) { Timber.e(e, "删除采集菜品异常") block(false) } } } // ========== 以下方法暂用 V1 API 实现(待 V2 接口补充) ========== /** 激活虹软人脸识别引擎 */ fun activeEngine() { Timber.tag(TAG).d("activeEngine") faceApi.activeEngine( App.getContext(), GlobalData.appId, GlobalData.sdkKey, GlobalData.activeKey, object : FaceApi.ActiveCallback { override fun onSuccess(activeCode: Int) { Timber.tag(TAG).d("activeEngine activeCode = $activeCode") viewModelScope.launch(Dispatchers.Main) { when (activeCode) { ErrorInfo.MOK -> { ToastUtils.showToast("激活引擎成功") } ErrorInfo.MERR_ASF_ALREADY_ACTIVATED -> { // 引擎已激活,无需再次激活 } else -> { ToastUtils.showToast("激活引擎失败($activeCode)") } } } } override fun onFail(e: Exception?) { viewModelScope.launch(Dispatchers.Main) { ToastUtils.showToast("激活引擎异常,${e?.message}") } } }) } /** 获取支付二维码 */ fun getQrCodeImg( orderId: String, userId: String? = null, totalFee: String? = null, block: (String?) -> Unit ) { Timber.tag(TAG).d("getQrCodeImg orderId = $orderId, userId = $userId") launch { val response = repository.getQrCodeImg( orderNo = orderId, memberId = userId, totalFee = totalFee ) if (parseResponse(response)) { block(response.data) } else { block(null) } } } /** 扫码支付 */ fun qrCodePay( authCode: String, orderNo: String, memberId: String?, block: (Boolean, String?) -> Unit ) { launch { val response = repository.qrCodePay(authCode, orderNo, memberId) if (parseResponse(response)) { block(true, response.data) } else { block(false, null) } } } /** 现金支付 */ fun cashPay(param: HashMap, block: (Boolean) -> Unit) { Timber.tag(TAG).d("cashPay") launchWithLoading { val response = repository.cashPay(param) if (parseResponse(response)) { block(response.data ?: false) } else { block(false) } } } /** 会员支付 */ fun memberPay(param: HashMap, block: (Boolean) -> Unit) { Timber.tag(TAG).d("memberPay") launchWithLoading { val response = repository.memberPay(param) if (parseResponse(response)) { block(true) } else { block(false) } } } /** 查询订单支付状态 */ suspend fun queryOrderState(orderId: String, block: (Boolean) -> Unit) { Timber.tag(TAG).d("queryOrderState") val response = repository.queryOrderState(orderNo = orderId) if (parseResponse(response)) { block(response.data == "1") } else { block(false) } } /** 获取饭点类型 */ fun getDinnerType( onSuccess: (DinnerType?) -> Unit = {}, onFailure: (message: String) -> Unit = {} ) { _dinnerTypeInfo.value = null launch { try { val response = repository.getDinnerType() if (parseResponse(response)) { _dinnerTypeInfo.value = response.data onSuccess(response.data) } else { onFailure(response.msg ?: "获取饭点类型失败") } } catch (e: Exception) { Timber.e(e, "获取饭点类型异常") onFailure("网络异常: ${e.message}") } } } // ========== V1 兼容包装方法(暂用 V1 API,后续逐步迁移至 V2) ========== private var lastFaceTimestamp = 0L /** 搜索食物 */ fun searchByFoodName(foodName: String, action: (List) -> Unit = {}) { Timber.tag(TAG).d("searchFood foodName = $foodName") launchWithLoading { try { val response = ApiClient.repositoryV2.searchFood(name = foodName) if (parseResponse(response)) { val list = (response.data ?: emptyList()).map { it.toFoodInfo() } action(list) } } catch (e: Exception) { Timber.e(e, "搜索菜品异常") } } } /** 获取用户就餐营养数据 */ fun getUserNutritionData( userId: String, block: (UserNutrition?) -> Unit ) { Timber.tag(TAG).d("getUserNutritionData userId = $userId") val uid = userId.toLongOrNull() ?: run { block(null) return } launch { try { val response = ApiClient.repositoryV2.getUserCurrentFood(uid) if (parseResponse(response)) { block(response.data) } else { block(null) } } catch (e: Exception) { Timber.e(e, "获取就餐营养数据异常") block(null) } } } /** 获取订单列表 */ fun getFoodOrderList(userId: String, block: (FoodOrderModel?) -> Unit) { Timber.tag(TAG).d("getFoodOrderList userId = $userId") val uid = userId.toLongOrNull() ?: run { block(null) return } launchWithLoading { try { val response = ApiClient.repositoryV2.getSettlementOrders(uid) if (parseResponse(response)) { block(response.data?.toFoodOrderModel()) } else { block(null) } } catch (e: Exception) { Timber.e(e, "查询订单列表异常") block(null) } } } /** 根据 ID 查询会员信息 */ fun getMemberInfoById(memberId: String, block: (MemberInfo?) -> Unit) { Timber.tag(TAG).d("getMemberInfo memberId=$memberId") val uid = memberId.toLongOrNull() ?: run { block(null) return } launchWithLoading { try { val response = ApiClient.repositoryV2.getMemberInfo(uid) if (parseResponse(response)) { block(response.data?.toMemberInfo()) } else { block(null) } } catch (e: Exception) { Timber.e(e, "查询会员信息异常") block(null) } } } /** 根据手机号查询会员信息 */ fun getMemberInfoByPhone(phone: String, key: String, block: (MemberInfo?) -> Unit) { Timber.tag(TAG).d("getMemberInfoByPhone phone=$phone") launchWithLoading { try { val response = ApiClient.repositoryV2.getMemberInfoByPhone(phone, key) if (parseResponse(response)) { block(response.data?.toMemberInfo()) } else { block(null) } } catch (e: Exception) { Timber.e(e, "查询会员异常") block(null) } } } /** 获取会员折扣 */ fun getMemberDiscount(userId: String, block: (Double?) -> Unit) { Timber.tag(TAG).d("getMemberDiscount userId = $userId") val uid = userId.toLongOrNull() ?: run { block(null) return } launchWithLoading { try { val response = ApiClient.repositoryV2.getMemberDiscount(uid) if (parseResponse(response)) { block(response.data?.toDoubleOrNull()) } else { block(null) } } catch (e: Exception) { Timber.e(e, "查询会员折扣异常") block(null) } } } }