实现了双屏摄像头显示,添加了部分代码

This commit is contained in:
zxj
2025-07-31 11:40:56 +08:00
parent 56e0a85a4c
commit de4c5f1c75
86 changed files with 3446 additions and 809 deletions
@@ -0,0 +1,75 @@
package com.sw.dualscreen.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.sw.dualscreen.GlobalData
import com.sw.dualscreen.model.response.ApiResponse
import com.sw.dualscreen.model.response.EquipmentInfo
import com.sw.dualscreen.network.ApiClient
import com.sw.dualscreen.repository.RemoteRepository
import com.sw.plate.utils.ToastUtils
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import timber.log.Timber
abstract class BaseViewModel() : ViewModel() {
protected val repository = RemoteRepository(ApiClient.apiService)
// 显示进度条
private val _showLoading = MutableStateFlow<Boolean>(false)
val showLoading: StateFlow<Boolean> = _showLoading
/**
* 带进度条的请求
*/
protected fun launchWithLoading(block: suspend () -> Unit) {
viewModelScope.launch {
try {
_showLoading.value = true
block()
} catch (e: Exception) {
// 错误处理可被子类重写
handleError(e)
} finally {
_showLoading.value = false
}
}
}
/**
* 不带进度条的请求
*/
protected fun launch(block: suspend () -> Unit) {
viewModelScope.launch() {
try {
block()
} catch (e: Exception) {
// 错误处理可被子类重写
handleError(e)
}
}
}
protected open fun parseResponse(response: ApiResponse<*>): Boolean {
if (response.isSuccess()) {
return true
}
Timber.d("msg = ${response.message}, code = ${response.code}")
ToastUtils.showToast("${response.message}(${response.code})")
return false
}
fun parseEquipmentInfo(equipmentInfo: EquipmentInfo) {
GlobalData.appBaseUrl = equipmentInfo.appPackageUrl!!
GlobalData.sdkKey = equipmentInfo.arcsoftSdkKey!!
GlobalData.appId = equipmentInfo.arcsoftAppId!!
// GlobalData.activeKey = equipmentInfo.arcsoftActiveKey!!
GlobalData.restId = equipmentInfo.canteenId!!
}
fun handleError(exception: Exception) {
Timber.d("handleError ${exception.message}")
}
}
@@ -0,0 +1,66 @@
package com.sw.dualscreen.viewmodel
import com.sw.dualscreen.GlobalKey
import com.sw.dualscreen.model.response.EquipmentInfo
import com.sw.dualscreen.utils.GsonUtils
import com.sw.inbound.utils.SPUtil
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import timber.log.Timber
/**
* 初始化的viewmodel
*/
class DeviceViewModel : BaseViewModel() {
private val _deviceInfoResult = MutableStateFlow<Boolean?>(null)
val deviceInfoResult: StateFlow<Boolean?> = _deviceInfoResult
/**
* 获取token
*/
fun getDeviceToken(deviceId: String = "3ea47dc0-3cf0-3c2f-909c-265a9a65572e") {
launchWithLoading {
val response = repository.getDeviceToken(deviceId)
if (parseResponse(response)) {
val response1 = repository.getDeviceInfo(deviceId, response.result!!)
if (parseResponse(response1)) {
val equipmentInfo = response1.result
if (equipmentInfo == null) return@launchWithLoading
try {
parseEquipmentInfo(equipmentInfo)
SPUtil.getInstance()
.put(GlobalKey.KEY_EQUIPMENT_INFO, GsonUtils.toJson(equipmentInfo))
_deviceInfoResult.value = true
} catch (e: Exception) {
Timber.e(e)
}
}
}
}
}
/**
* 检查缓存数据
*/
fun checkEquipmentInfo(): Boolean {
val equipmentInfoStr = SPUtil.getInstance().get(GlobalKey.KEY_EQUIPMENT_INFO, "")
if (equipmentInfoStr == null) {
Timber.e("获取缓存设备信息失败")
return false
}
val equipmentInfo =
GsonUtils.fromJson<EquipmentInfo>(equipmentInfoStr, EquipmentInfo::class.java)
if (equipmentInfo == null) {
Timber.e("解析缓存设备信息失败")
return false
}
try {
parseEquipmentInfo(equipmentInfo)
return true
} catch (e: Exception) {
Timber.e(e)
return false
}
}
}
@@ -1,8 +0,0 @@
package com.sw.dualscreen.viewmodel
import androidx.lifecycle.ViewModel
class MyViewModel : ViewModel() {
}
@@ -0,0 +1,182 @@
package com.sw.dualscreen.viewmodel
import androidx.lifecycle.viewModelScope
import com.arcsoft.face.ErrorInfo
import com.sw.dualscreen.GlobalData
import com.sw.dualscreen.GlobalKey
import com.sw.dualscreen.model.request.UserNutritionParam
import com.sw.dualscreen.model.response.FoodInfo
import com.sw.dualscreen.model.response.NutritionData
import com.sw.dualscreen.model.response.UserFaceModel
import com.sw.inbound.utils.SPUtil
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.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
class UserViewModel : BaseViewModel() {
private val faceApi: FaceApi = FaceApi()
// 识别出的列表
private val _identifiedFoodInfoList = MutableStateFlow<List<FoodInfo>?>(null)
val identifiedFoodInfoList: StateFlow<List<FoodInfo>?> = _identifiedFoodInfoList
// 搜索出的食物列表
private val _foodInfoList = MutableStateFlow<List<FoodInfo>?>(null)
val foodInfoList: StateFlow<List<FoodInfo>?> = _foodInfoList
// 当前食物信息
private val _currentFoodInfo = MutableStateFlow<FoodInfo?>(null)
val currentFoodInfo: StateFlow<FoodInfo?> = _currentFoodInfo
// 就餐数据
private val _nutritionData = MutableStateFlow<NutritionData?>(null)
val nutritionData: StateFlow<NutritionData?> = _nutritionData
/**
* 获取token
*/
fun getEquipmentToken(qrcodeId: String = "3ea47dc0-3cf0-3c2f-909c-265a9a65572e") {
launchWithLoading {
val response = repository.getEquipmentToken(qrcodeId)
if (parseResponse(response)) {
// 缓存token
SPUtil.getInstance().put(GlobalKey.KEY_TOKEN, response.result)
// 首次运行获取人脸数据
if (SPUtil.getInstance().get(GlobalKey.KEY_FIRST_RUN, false) != true) {
getUserFaceCache(index = 0)
}
}
}
}
/**
* 获取人脸数据
*/
fun getUserFaceCache(index: Int = 0) {
Timber.d("getUserFaceCache index = $index")
launch {
val response = repository.getUserFaceCache(index)
if (parseResponse(response)) {
// 获取成功一次后缓存状态
SPUtil.getInstance().put(GlobalKey.KEY_FIRST_RUN, true)
withContext(Dispatchers.Default) {
val list: List<UserFaceModel> = response.result?.data ?: emptyList()
val faceEntity = list.map {
FaceEntity(it.userId, null, Base64.decode(it.faceFeatureString))
}
faceApi.updateFaceData(index, faceEntity)
activeEngine()
}
val nextPageIndex = response.result?.nextPageIndex ?: -1
if (nextPageIndex > 0) {
getUserFaceCache(nextPageIndex)
}
}
}
}
/**
* 激活人脸识别引擎
*/
fun activeEngine() {
Timber.d("activeEngine")
faceApi.activeEngine(
App.getContext(),
GlobalData.appId,
GlobalData.sdkKey,
GlobalData.activeKey,
object : FaceApi.ActiveCallback {
override fun onSuccess(activeCode: Int) {
Timber.d("activeEngine activeCode = $activeCode")
viewModelScope.launch(Dispatchers.Main) {
when (activeCode) {
ErrorInfo.MOK -> {
ToastUtils.showToast("激活引擎成功")
}
ErrorInfo.MERR_ASF_ALREADY_ACTIVATED -> {
// ToastUtils.showToast("引擎已激活,无需再次激活")
}
else -> {
ToastUtils.showToast("激活引擎失败($activeCode)")
}
}
}
}
override fun onFail(e: Exception?) {
viewModelScope.launch(Dispatchers.Main) {
ToastUtils.showToast("激活引擎异常,${e?.message}")
}
}
})
}
/**
* 重置用户信息
*/
fun resetUserInfo() {
Timber.d("resetUserInfo")
}
fun identifiedFoodList() {
launchWithLoading {
delay(5000)
_identifiedFoodInfoList.value = List(6) {
FoodInfo(
id = "185225109358109491${it}",
foodName = "豆芽炒粉条$it",
imgUrl = "http://101.201.149.156:9002/temp/20221119/62dc2d17-6853-4277-96ed-a4ef4c8654ea_1668840050258.png"
)
}
}
}
/**
* 搜索食物
*/
fun searchByFoodName(foodName: String) {
launchWithLoading {
val response = repository.getRestInfoFoodsByType(foodName = foodName)
if (parseResponse(response)) {
_foodInfoList.value = response.result
}
}
}
/**
* 获取用户就餐数据
*/
fun getUserNutritionData(userId: String, foodId: String) {
launchWithLoading {
val response = repository.getUserNutritionData(userId = userId, foodId = foodId)
if (parseResponse(response)) {
_nutritionData.value = response.result
}
}
}
/**
* 提交就餐数据
*/
fun postUserNutritionData(param: UserNutritionParam) {
launchWithLoading {
val response = repository.postUserNutritionData(param)
if (parseResponse(response)) {
}
}
}
}