采集向量数据接口调试

This commit is contained in:
2026-01-06 18:49:33 +08:00
parent fc04db9268
commit 378e6088e7
20 changed files with 468 additions and 241 deletions
+2 -2
View File
@@ -4,10 +4,10 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-12-26T06:22:06.750685100Z"> <DropdownSelection timestamp="2025-12-31T07:06:25.405532800Z">
<Target type="DEFAULT_BOOT"> <Target type="DEFAULT_BOOT">
<handle> <handle>
<DeviceId pluginId="Default" identifier="serial=192.168.1.2:5555;connection=bfafe870" /> <DeviceId pluginId="Default" identifier="serial=192.168.1.45:5555;connection=52f5d52f" />
</handle> </handle>
</Target> </Target>
</DropdownSelection> </DropdownSelection>
+30 -12
View File
@@ -5,7 +5,7 @@
"entities": [ "entities": [
{ {
"id": "1:594331511073099531", "id": "1:594331511073099531",
"lastPropertyId": "4:1528837175750321569", "lastPropertyId": "9:4394472487596452023",
"name": "Food", "name": "Food",
"properties": [ "properties": [
{ {
@@ -14,22 +14,37 @@
"type": 6, "type": 6,
"flags": 1 "flags": 1
}, },
{
"id": "2:340951913928211738",
"name": "name",
"type": 9
},
{
"id": "3:5484846939472684412",
"name": "foodIdx",
"type": 5
},
{ {
"id": "4:1528837175750321569", "id": "4:1528837175750321569",
"name": "foodVector", "name": "foodVector",
"indexId": "1:6010838397086628487", "indexId": "1:6010838397086628487",
"type": 28, "type": 28,
"flags": 8 "flags": 8
},
{
"id": "5:5561277398349179032",
"name": "collectId",
"type": 9
},
{
"id": "6:8313847979709019172",
"name": "foodId",
"type": 9
},
{
"id": "7:1375536603452792462",
"name": "foodName",
"type": 9
},
{
"id": "8:7942065542192169896",
"name": "version",
"type": 9
},
{
"id": "9:4394472487596452023",
"name": "otherField",
"type": 9
} }
], ],
"relations": [] "relations": []
@@ -43,7 +58,10 @@
"modelVersionParserMinimum": 5, "modelVersionParserMinimum": 5,
"retiredEntityUids": [], "retiredEntityUids": [],
"retiredIndexUids": [], "retiredIndexUids": [],
"retiredPropertyUids": [], "retiredPropertyUids": [
340951913928211738,
5484846939472684412
],
"retiredRelationUids": [], "retiredRelationUids": [],
"version": 1 "version": 1
} }
@@ -20,8 +20,13 @@ object GlobalData {
/** /**
* 具体业务baseurl * 具体业务baseurl
*/ */
// var appBaseUrl: String = "http://192.168.1.201:14801" var appBaseUrl: String = "http://192.168.1.201:14801"
var appBaseUrl: String = "https://dev.yixiong-tech.com:8081" // var appBaseUrl: String = "https://dev.yixiong-tech.com:8081"
/**
* 菜品识别模型版本号,后边以接口返回为准
*/
var foodModelVersion: String = "1.0.0"
/** /**
* 横排数量 * 横排数量
@@ -4,10 +4,10 @@ import android.annotation.SuppressLint
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.core.widget.addTextChangedListener import androidx.core.widget.addTextChangedListener
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.sw.dualscreen.GlobalData
import com.sw.dualscreen.R import com.sw.dualscreen.R
import com.sw.dualscreen.adapter.CollectedFoodNewAdapter import com.sw.dualscreen.adapter.CollectedFoodNewAdapter
import com.sw.dualscreen.databinding.ActivityCollectedFoodBinding import com.sw.dualscreen.databinding.ActivityCollectedFoodBinding
import com.sw.dualscreen.databinding.LayoutEmptySearchBinding
import com.sw.dualscreen.dialog.WarnDialog import com.sw.dualscreen.dialog.WarnDialog
import com.sw.dualscreen.ext.addOnActionSearchListener import com.sw.dualscreen.ext.addOnActionSearchListener
import com.sw.dualscreen.ext.gone import com.sw.dualscreen.ext.gone
@@ -15,12 +15,10 @@ import com.sw.dualscreen.ext.hideKeyboard
import com.sw.dualscreen.ext.visible import com.sw.dualscreen.ext.visible
import com.sw.dualscreen.objbox.CollectedFoodInfo import com.sw.dualscreen.objbox.CollectedFoodInfo
import com.sw.dualscreen.objbox.Food import com.sw.dualscreen.objbox.Food
import com.sw.dualscreen.objbox.ObjectBox
import com.sw.dualscreen.viewmodel.BaseViewModel import com.sw.dualscreen.viewmodel.BaseViewModel
import com.sw.dualscreen.viewmodel.UserViewModel import com.sw.dualscreen.viewmodel.UserViewModel
import com.sw.plate.utils.ToastUtils import com.sw.plate.utils.ToastUtils
import io.objectbox.Box import io.objectbox.Box
import io.objectbox.kotlin.boxFor
class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() { class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
@@ -42,7 +40,7 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
CollectedFoodNewAdapter(list).apply { CollectedFoodNewAdapter(list).apply {
// isStateViewEnable = true // isStateViewEnable = true
addOnItemChildClickListener(R.id.ivDeleteFood) { _, _, position -> addOnItemChildClickListener(R.id.ivDeleteFood) { _, _, position ->
deleteGoods(position) loadDeleteDialog(position)
} }
} }
} }
@@ -182,17 +180,22 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
//} //}
} }
private fun deleteGoods(position: Int) { private fun loadDeleteDialog(position: Int) {
WarnDialog( WarnDialog(
context = context, context = context,
content = "请确认是否删除菜品:${list[position].foodName}", content = "请确认是否删除菜品:${list[position].foodName}",
confirmBlock = { confirmBlock = {
deleteGoods(position)
}).show()
}
private fun deleteGoods(position: Int) {
val food = list[position]
showWaitingDialog("加载中……")
viewModel.deleteCollectFood(food.foodId, GlobalData.foodModelVersion) { deleteSuccess ->
if (deleteSuccess) {
Thread { Thread {
runOnUiThread { val filterIdList = box?.all?.filter { it.foodName == food.foodName }?.map { it.id }
showWaitingDialog("加载中……")
}
val name = list[position].foodName
val filterIdList = box?.all?.filter { it.name == name }?.map { it.id }
box?.removeByIds(filterIdList) box?.removeByIds(filterIdList)
runOnUiThread { runOnUiThread {
hideWaitingDialog() hideWaitingDialog()
@@ -203,7 +206,8 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
} }
} }
}.start() }.start()
}).show() }
}
} }
@@ -1,37 +1,86 @@
package com.sw.dualscreen.activity package com.sw.dualscreen.activity
import android.content.Intent import android.content.Intent
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat.startActivity
import androidx.lifecycle.lifecycleScope
import com.sw.dualscreen.GlobalData import com.sw.dualscreen.GlobalData
import com.sw.dualscreen.databinding.ActivityInitBinding import com.sw.dualscreen.databinding.ActivityInitBinding
import com.sw.dualscreen.utils.QRCodeUtil import com.sw.dualscreen.model.response.FoodVector
import com.sw.dualscreen.objbox.Food
import com.sw.dualscreen.objbox.FoodModule
import com.sw.dualscreen.objbox.ObjectBox
import com.sw.dualscreen.viewmodel.BaseViewModel import com.sw.dualscreen.viewmodel.BaseViewModel
import com.sw.dualscreen.viewmodel.DeviceViewModel import com.sw.dualscreen.viewmodel.UserViewModel
import com.sw.plate.utils.AppUtil import io.objectbox.Box
import kotlinx.coroutines.flow.drop import io.objectbox.kotlin.boxFor
import kotlinx.coroutines.launch
class InitActivity : BaseActivity<ActivityInitBinding>() { class InitActivity : BaseActivity<ActivityInitBinding>() {
private val viewModel by viewModels<DeviceViewModel>() companion object {
private const val PAGE_SIZE = 100
}
private val userViewModel by viewModels<UserViewModel>()
private var pageNum = 1
override fun getViewModel(): BaseViewModel { override fun getViewModel(): BaseViewModel {
return viewModel return userViewModel
} }
override fun inflateViewBinding(): ActivityInitBinding { override fun inflateViewBinding(): ActivityInitBinding {
return ActivityInitBinding.inflate(layoutInflater) return ActivityInitBinding.inflate(layoutInflater)
} }
private fun getCollectedFoodVector(block:()-> Unit) {
userViewModel.getCollectedFoodVector(param = mutableMapOf(
"pageNum" to "$pageNum",
"pageSize" to "$PAGE_SIZE",
"version" to GlobalData.foodModelVersion
)) { items ->
val list = items ?: emptyList()
if (pageNum == 1 && list.isEmpty()) {
block()
return@getCollectedFoodVector
}
if (list.size >= PAGE_SIZE) {
saveFoodVector(list)
pageNum++
getCollectedFoodVector(block)
return@getCollectedFoodVector
}
if (list.isNotEmpty()) {
saveFoodVector(list)
block()
}
}
}
private val box by lazy { ObjectBox.boxStore.boxFor(Food::class) }
private fun saveFoodVector(list: List<FoodVector>) {
val vectorList = list.map { vt ->
val foodVector = vt.foodVector?.removeSurrounding("[", "]")?.split(",")?.map { it.toFloatOrNull()?:0.0f }?.toFloatArray()
Food(
collectId = vt.id,
foodId = vt.foodId,
foodName = vt.foodName,
version = vt.version,
foodVector = foodVector
)
}
box.put(vectorList)
}
override fun initialize() { override fun initialize() {
super.initialize() super.initialize()
binding.root.postDelayed({ showWaitingDialog("加载中……")
goMainActivity() FoodModule.init(this) {
}, 1000) getCollectedFoodVector {
runOnUiThread {
hideWaitingDialog()
binding.root.postDelayed({
goMainActivity()
}, 1000)
}
}
}
// val isSuccess = viewModel.checkEquipmentInfo() // val isSuccess = viewModel.checkEquipmentInfo()
// if (isSuccess) { // if (isSuccess) {
// goMainActivity() // goMainActivity()
@@ -50,12 +99,12 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
override fun registerDataChange() { override fun registerDataChange() {
super.registerDataChange() super.registerDataChange()
lifecycleScope.launch { // lifecycleScope.launch {
viewModel.deviceInfoResult.drop(1).collect { // viewModel.deviceInfoResult.drop(1).collect {
if (it != true) return@collect // if (it != true) return@collect
goMainActivity() // goMainActivity()
} // }
} // }
} }
private fun goMainActivity() { private fun goMainActivity() {
@@ -137,7 +137,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
super.initialize() super.initialize()
isPageVisible = true isPageVisible = true
addBackEventListener() addBackEventListener()
FoodModule.init(this) //FoodModule.init(this)
initView() initView()
viewModel.getUserFaceCache(pageNo = 1) viewModel.getUserFaceCache(pageNo = 1)
//setupSecondaryDisplay() //setupSecondaryDisplay()
@@ -353,77 +353,37 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
LightManager.closeRedLight() LightManager.closeRedLight()
LightManager.openGreenLight() LightManager.openGreenLight()
showWaitingDialog("识别中,请稍后……") showWaitingDialog("识别中,请稍后……")
takePhoto(successCallback = { photoUri -> takePhoto(
lastPhotoUri = photoUri successCallback = { photoUri ->
Timber.tag(TAG).d("main,takePhoto耗时:${System.currentTimeMillis() - startTime}") onTakePhotoSuccess(photoUri)
startTime = System.currentTimeMillis() failCount = 0
Timber.d("registerDataChange photoUri = ${photoUri.path}") }, failureCallback = {
// viewModel.getIdentifiedFoodList() runOnUiThread {
if (failCount >= 10) {
ImageUtil.uriToBitmap(this, photoUri)?.let { bitmap -> ToastUtils.showToast("相机异常,请稍后重试")
Timber.tag(TAG).d("main,uriToBitmap耗时:${System.currentTimeMillis() - startTime}") shutdownCamera()
startTime = System.currentTimeMillis() setupCamera()
Timber.d("registerDataChange photoUri 拿到bitmap") return@runOnUiThread
// val bmp = BitmapCropper.cropCenter(bitmap, 1300, 900)
Timber.d("registerDataChange photoUri bitmap裁剪完成")
val file = BitmapSaver.saveToAppFilesDir(
bitmap,
this,
"IMG_CROP_${System.currentTimeMillis()}.jpg"
)
Timber.tag(TAG)
.d("main,saveToAppFilesDir耗时:${System.currentTimeMillis() - startTime}")
startTime = System.currentTimeMillis()
Timber.d("registerDataChange photoUri bitmap保存文件路径:${file?.absolutePath}")
//val nameList = FoodModule.queryFood(bitmap)
val scoreList = FoodModule.getFoodScoreList(bitmap)
val recDataJson = GsonUtils.toJson(scoreList)
Timber.tag(TAG)
.d("main,getFoodScoreList耗时:${System.currentTimeMillis() - startTime}")
startTime = System.currentTimeMillis()
Timber.d("registerDataChange photoUri 拿到识别数据:$recDataJson")
val nameList = scoreList.map { it.name }
val foodName = nameList.joinToString(separator = ",")
hideWaitingDialog()
Timber.d("registerDataChange photoUri 识别数据名称:$foodName")
if (TextUtils.isEmpty(foodName)) {
LightManager.closeGreenLight()
LightManager.closeRedLight()
//binding.layoutRescan.visibility = View.VISIBLE
binding.flPay.visible()
binding.tvToSearch.let {
it.text = "未识别到,去手动搜索"
it.visible()
}
} else {
//binding.layoutRescan.visibility = View.GONE
showWaitingDialog("正在查询菜品信息,请稍后……")
viewModel.getFoodInfo(foodName) { list ->
runOnUiThread {
hideWaitingDialog()
Timber.tag(TAG)
.d("main,getFoodInfo耗时:${System.currentTimeMillis() - startTime}")
startTime = System.currentTimeMillis()
updateFoodInfo(list.toMutableList(), scoreList)
}
} }
} }
failCount++
} hideWaitingDialog()
}, failureCallback = {
hideWaitingDialog()
// ToastUtils.showToast("拍照异常,请手动放置后重新识别") // ToastUtils.showToast("拍照异常,请手动放置后重新识别")
shutdownCamera() shutdownCamera()
setupCamera() setupCamera()
binding.root.postDelayed({ binding.root.postDelayed({
recognizeFood() recognizeFood()
}, 500) }, 1000)
}) })
} }
/**
* 识别相机异常次数
*/
private var failCount = 0
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
private fun updateFoodInfo(list: MutableList<FoodInfo>, scoreList: List<IdNameScore>) { private fun updateFoodInfo(list: MutableList<FoodInfo>, scoreList: List<IdNameScore>) {
val queryData = GsonUtils.toJson(list) val queryData = GsonUtils.toJson(list)
@@ -434,12 +394,19 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
val score = scoreItem?.score ?: 0.0 val score = scoreItem?.score ?: 0.0
foodInfo.score = ((1 - score) * 10000).roundToInt() foodInfo.score = ((1 - score) * 10000).roundToInt()
} }
val list2 = try {
val orderList = scoreList.map { it.name.trim() }
list.sortedBy { orderList.indexOf(it.foodName) }
} catch (e: Exception) {
e.printStackTrace()
list
}
searchFoodList.clear() searchFoodList.clear()
searchFoodList.addAll(list) searchFoodList.addAll(list2)
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()
//adapter.updateData(list) //adapter.updateData(list)
if (list.isNotEmpty()) { if (list2.isNotEmpty()) {
checkedItem = list[0] checkedItem = list2[0]
checkedItem!!.photoUri = lastPhotoUri checkedItem!!.photoUri = lastPhotoUri
checkedItem!!.isChecked = true checkedItem!!.isChecked = true
adapter.notifyItemChanged(0) adapter.notifyItemChanged(0)
@@ -616,7 +583,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun onError(exception: ImageCaptureException) { override fun onError(exception: ImageCaptureException) {
Timber.e("拍照失败: ${exception.message}") Timber.e("拍照失败: ${exception.message}")
failureCallback() failureCallback()
ToastUtils.showToast("拍照失败: ${exception.message}") //ToastUtils.showToast("拍照失败: ${exception.message}")
// 恢复图像分析 // 恢复图像分析
isAnalyzing = true isAnalyzing = true
} }
@@ -1090,4 +1057,64 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
// }.start() // }.start()
// } // }
private fun onTakePhotoSuccess(photoUri: Uri) {
lastPhotoUri = photoUri
Timber.tag(TAG).d("main,takePhoto耗时:${System.currentTimeMillis() - startTime}")
startTime = System.currentTimeMillis()
Timber.d("registerDataChange photoUri = ${photoUri.path}")
// viewModel.getIdentifiedFoodList()
ImageUtil.uriToBitmap(this, photoUri)?.let { bitmap ->
Timber.tag(TAG).d("main,uriToBitmap耗时:${System.currentTimeMillis() - startTime}")
startTime = System.currentTimeMillis()
Timber.d("registerDataChange photoUri 拿到bitmap")
// val bmp = BitmapCropper.cropCenter(bitmap, 1300, 900)
Timber.d("registerDataChange photoUri bitmap裁剪完成")
val file = BitmapSaver.saveToAppFilesDir(
bitmap,
this,
"IMG_CROP_${System.currentTimeMillis()}.jpg"
)
Timber.tag(TAG)
.d("main,saveToAppFilesDir耗时:${System.currentTimeMillis() - startTime}")
startTime = System.currentTimeMillis()
Timber.d("registerDataChange photoUri bitmap保存文件路径:${file?.absolutePath}")
//val nameList = FoodModule.queryFood(bitmap)
val scoreList = FoodModule.getFoodScoreList(bitmap)
val recDataJson = GsonUtils.toJson(scoreList)
Timber.tag(TAG)
.d("main,getFoodScoreList耗时:${System.currentTimeMillis() - startTime}")
startTime = System.currentTimeMillis()
Timber.d("registerDataChange photoUri 拿到识别数据:$recDataJson")
val nameList = scoreList.map { it.name }
val foodName = nameList.joinToString(separator = ",")
hideWaitingDialog()
Timber.d("registerDataChange photoUri 识别数据名称:$foodName")
if (TextUtils.isEmpty(foodName)) {
LightManager.closeGreenLight()
LightManager.closeRedLight()
//binding.layoutRescan.visibility = View.VISIBLE
binding.flPay.visible()
binding.tvToSearch.let {
it.text = "未识别到,去手动搜索"
it.visible()
}
} else {
//binding.layoutRescan.visibility = View.GONE
showWaitingDialog("正在查询菜品信息,请稍后……")
viewModel.getFoodInfo(foodName) { list ->
runOnUiThread {
hideWaitingDialog()
Timber.tag(TAG)
.d("main,getFoodInfo耗时:${System.currentTimeMillis() - startTime}")
startTime = System.currentTimeMillis()
updateFoodInfo(list.toMutableList(), scoreList)
}
}
}
}
}
} }
@@ -8,11 +8,11 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.camera.view.PreviewView import androidx.camera.view.PreviewView
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import com.sw.dualscreen.GlobalData
import com.sw.dualscreen.R import com.sw.dualscreen.R
import com.sw.dualscreen.activity.CollectedFoodActivity import com.sw.dualscreen.activity.CollectedFoodActivity
import com.sw.dualscreen.activity.SettingActivity import com.sw.dualscreen.activity.SettingActivity
@@ -51,7 +51,7 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
private var selectedFoodId: String? = "" private var selectedFoodId: String? = ""
private var selectedFoodName: String? = "" private var selectedFoodName: String? = ""
private var box: Box<Food>? = null private val box by lazy { ObjectBox.boxStore.boxFor(Food::class) }
private val foodCollectionList = mutableListOf<FoodCollectionBean>().apply { private val foodCollectionList = mutableListOf<FoodCollectionBean>().apply {
repeat(MAX_COUNT) { repeat(MAX_COUNT) {
add(FoodCollectionBean(isShowCamera = true)) add(FoodCollectionBean(isShowCamera = true))
@@ -126,7 +126,6 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
// targetWidth = 1300, targetHeight = 900, // targetWidth = 1300, targetHeight = 900,
// //offsetX = 30, offsetY = 100 // //offsetX = 30, offsetY = 100
// ) // )
initBox()
val imageVector = try { val imageVector = try {
FoodModule.bitmap2FloatArray(bitmap) FoodModule.bitmap2FloatArray(bitmap)
} catch (e: Exception) { } catch (e: Exception) {
@@ -236,9 +235,12 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
it.layoutManager = GridLayoutManager(context, 2) it.layoutManager = GridLayoutManager(context, 2)
it.adapter = searchFoodAdapter it.adapter = searchFoodAdapter
} }
searchInfo()
} }
@SuppressLint("NotifyDataSetChanged")
private fun upload() { private fun upload() {
lifecycleScope.launch { lifecycleScope.launch {
val totalFileCount = foodCollectionList.count { it.imageFile != null } val totalFileCount = foodCollectionList.count { it.imageFile != null }
@@ -247,31 +249,53 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
//params["placeId"] = restId.toRequestBody() //params["placeId"] = restId.toRequestBody()
params["foodId"] = checkedItem!!.foodId.toRequestBody() params["foodId"] = checkedItem!!.foodId.toRequestBody()
params["foodName"] = checkedItem!!.foodName!!.toRequestBody() params["foodName"] = checkedItem!!.foodName!!.toRequestBody()
//params["foodVector"] = foodVector.toRequestBody() params["version"] = GlobalData.foodModelVersion.toRequestBody()
ImageUploader( ImageUploader(
totalList = foodCollectionList, totalList = foodCollectionList,
uploadImage = { batch -> uploadImage = { batch ->
val files = batch.map { it.imageFile } val files = batch.map { it.imageFile }
(requireActivity() as SettingActivity).viewModel.uploadCollectFoodPics( val foodVectorList =
files, batch.filter { it.imageVector != null }.map { it.imageVector!!.joinToString(separator = ",", prefix = "[", postfix = "]")}
params val foodVectorJson =
) foodVectorList.joinToString(separator = ",", prefix = "[", postfix = "]")
Timber.tag(TAG).d("json=$foodVectorJson")
params["foodVector"] = foodVectorJson.toRequestBody()
settingActivity?.viewModel?.uploadCollectFoodPics(files, params)
}, },
onProgress = { count, batch -> onProgress = { count, batch, idList->
activity?.runOnUiThread { activity?.runOnUiThread {
settingActivity?.showWaitingDialog2("图片上传中$count/$totalFileCount") settingActivity?.showWaitingDialog2("图片上传中$count/$totalFileCount")
batch.forEach { //batch.forEach {
it.uploadSuccess = true // it.uploadSuccess = true
} //}
Thread {
val foodList = batch.mapIndexed { index, it ->
Food(
collectId = if (index < idList.size) idList[index] else null,
foodId = checkedItem!!.foodId,
foodName = checkedItem!!.foodName,
foodVector = it.imageVector,
version = GlobalData.foodModelVersion
)
}
box.put(foodList)
activity?.runOnUiThread {
batch.forEach { it.uploadSuccess = true }
collectionAdapter.notifyDataSetChanged()
}
}.start()
} }
}, },
onError = { onError = {
settingActivity?.hideWaitingDialog()
activity?.runOnUiThread { activity?.runOnUiThread {
ToastUtils.showToast("上传失败,请稍后重试") ToastUtils.showToast("上传失败,请稍后重试")
} }
}, },
onComplete = { onComplete = {
vectorThread() //vectorThread()
settingActivity?.hideWaitingDialog()
} }
).processUploads() ).processUploads()
} }
@@ -316,33 +340,34 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
}.start() }.start()
} }
private fun initBox() { private fun image2VectorTask(imageVector: FloatArray?, position: Int) {
if (box == null) { if (imageVector == null) return
box = ObjectBox.boxStore.boxFor(Food::class) //// val imageVector = FoodModule.bitmap2FloatArray(item.bitmap!!)
} ////
} ////// val base64Str = FloatBase64Utils.floatArrayToBase64(imageVector)
////// Timber.tag("mzf1").e(base64Str)
private fun image2VectorTask(imageVector: FloatArray, position: Int) { //////// viewModel.postImageData(
initBox() //////// context,
// val imageVector = FoodModule.bitmap2FloatArray(item.bitmap!!) //////// foodId = selectedFoodId.toString(),
// //////// foodName = selectedFoodName.toString(),
//// val base64Str = FloatBase64Utils.floatArrayToBase64(imageVector) //////// foodVector = base64Str,
//// Timber.tag("mzf1").e(base64Str) //////// uri = item.imageUri!!
////// viewModel.postImageData( //////// )
////// context, // box.put(
////// foodId = selectedFoodId.toString(), // Food(
////// foodName = selectedFoodName.toString(), // collectId = null,
////// foodVector = base64Str, // foodId = checkedItem!!.foodId,
////// uri = item.imageUri!! // foodName = checkedItem!!.foodName,
////// ) // foodVector = imageVector,
box?.put(Food(name = checkedItem!!.foodName, foodIdx = 0, foodVector = imageVector)) // version = "1.0.0"
foodCollectionList[position].let { // )
// it.imageVector = imageVector // )
it.isFinish = true // if (position > -1) {
} // foodCollectionList[position].isFinish = true
activity?.runOnUiThread { // activity?.runOnUiThread {
collectionAdapter.notifyItemChanged(position) // collectionAdapter.notifyItemChanged(position)
} // }
// }
} }
@@ -119,13 +119,13 @@ data class RespCodeMsg(
) )
data class PostEvent(var name:String="") data class PostEvent(var name: String = "")
data class PaySuccessEvent( data class PaySuccessEvent(
var name: String = "" var name: String = ""
) )
data class ChargeModeEvent( data class ChargeModeEvent(
var chargeMode:Int = 0 var chargeMode: Int = 0
) )
data class ClickBackEvent( data class ClickBackEvent(
@@ -133,7 +133,15 @@ data class ClickBackEvent(
) )
data class DeviceConfig( data class DeviceConfig(
var arcsoftAppId:String?=null, var arcsoftAppId: String? = null,
var arcsoftSdkKey:String?=null, var arcsoftSdkKey: String? = null,
var arcsoftActiveKey:String?=null var arcsoftActiveKey: String? = null
)
data class FoodVector(
val id: String? = null,
val foodId: String? = null,
val foodName: String? = null,
val foodVector: String? = null,
val version: String? = null
) )
@@ -7,8 +7,9 @@ import com.sw.dualscreen.model.response.DeviceConfig
import com.sw.dualscreen.model.response.DinnerType import com.sw.dualscreen.model.response.DinnerType
import com.sw.dualscreen.model.response.FaceData import com.sw.dualscreen.model.response.FaceData
import com.sw.dualscreen.model.response.FoodInfo import com.sw.dualscreen.model.response.FoodInfo
import com.sw.dualscreen.model.response.FoodSearchReq
import com.sw.dualscreen.model.response.FoodOrder import com.sw.dualscreen.model.response.FoodOrder
import com.sw.dualscreen.model.response.FoodSearchReq
import com.sw.dualscreen.model.response.FoodVector
import com.sw.dualscreen.model.response.MemberInfo import com.sw.dualscreen.model.response.MemberInfo
import com.sw.dualscreen.model.response.UserFaceModel import com.sw.dualscreen.model.response.UserFaceModel
import com.sw.dualscreen.model.response.UserNutrition import com.sw.dualscreen.model.response.UserNutrition
@@ -16,12 +17,16 @@ import com.sw.dualscreen.objbox.CollectedFoodInfo
import okhttp3.MultipartBody import okhttp3.MultipartBody
import okhttp3.RequestBody import okhttp3.RequestBody
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.FieldMap
import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Multipart import retrofit2.http.Multipart
import retrofit2.http.POST import retrofit2.http.POST
import retrofit2.http.Part import retrofit2.http.Part
import retrofit2.http.PartMap import retrofit2.http.PartMap
import retrofit2.http.Query import retrofit2.http.Query
import retrofit2.http.QueryMap
import retrofit2.http.Url import retrofit2.http.Url
interface ApiService { interface ApiService {
@@ -246,11 +251,11 @@ interface ApiService {
@Multipart @Multipart
@POST @POST
suspend fun uploadCollectFoodPics( suspend fun uploadCollectFoodPics(
// @Url url: String = "http://192.168.1.201:14801/terminal/neglect/dishCollectionVectorData/add",
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/boothMachine/app/dishCollectionVectorData/add", @Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/boothMachine/app/dishCollectionVectorData/add",
@PartMap params: HashMap<String, RequestBody>, @PartMap params: HashMap<String, RequestBody>,
@Part foodPics: List<MultipartBody.Part> @Part foodPics: List<MultipartBody.Part>
): ApiResponse<String> ): ApiResponse<List<String>?>
/** /**
* 获取设备配置数据 * 获取设备配置数据
@@ -268,4 +273,22 @@ interface ApiService {
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/common/app/faceFeature/increment/list", @Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/common/app/faceFeature/increment/list",
@Body param: Map<String, Long> @Body param: Map<String, Long>
): ApiResponse<List<UserFaceModel>?> ): ApiResponse<List<UserFaceModel>?>
/**
* 档口机向量数据分页查询
*/
@POST
suspend fun getCollectedFoodVector(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/boothMachine/app/collectionVectorPage",
@Body param: MutableMap<String, String>
): ApiResponse<List<FoodVector>?>
/**
* 档口机向量数据分页查询
*/
@DELETE
suspend fun deleteCollectFood(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/boothMachine/app/deleteBoothMachineCollection",
@QueryMap param: MutableMap<String, String?>
): ApiResponse<Any?>
} }
@@ -28,11 +28,11 @@ data class FoodCollectionBean(
) )
data class CollectedFoodInfo( data class CollectedFoodInfo(
var id: String? = null, // var id: String? = null,
var placeId: String? = null, // var placeId: String? = null,
var foodId: String? = null, var foodId: String? = null,
var foodName: String? = null, var foodName: String? = null,
var foodPic: Any? = null, // var foodPic: Any? = null,
var foodVector: Any? = null, // var foodVector: Any? = null,
var foodCount: Int = 0 var foodCount: Int = 0
) )
@@ -8,8 +8,11 @@ import io.objectbox.annotation.VectorDistanceType
@Entity @Entity
data class Food( data class Food(
@Id var id: Long = 0, @Id var id: Long = 0,
var name: String? = null, var collectId: String? = null,
var foodIdx: Int = 0, var foodId: String? = null,
var foodName: String? = null,
var version: String? = null,
var otherField: String? = null,
@HnswIndex(dimensions = 512, distanceType = VectorDistanceType.DOT_PRODUCT) @HnswIndex(dimensions = 512, distanceType = VectorDistanceType.DOT_PRODUCT)
var foodVector: FloatArray? = null var foodVector: FloatArray? = null
) { ) {
@@ -20,8 +23,11 @@ data class Food(
other as Food other as Food
if (id != other.id) return false if (id != other.id) return false
if (foodIdx != other.foodIdx) return false if (collectId != other.collectId) return false
if (name != other.name) return false if (foodId != other.foodId) return false
if (foodName != other.foodName) return false
if (version != other.version) return false
if (otherField != other.otherField) return false
if (!foodVector.contentEquals(other.foodVector)) return false if (!foodVector.contentEquals(other.foodVector)) return false
return true return true
@@ -29,9 +35,14 @@ data class Food(
override fun hashCode(): Int { override fun hashCode(): Int {
var result = id.hashCode() var result = id.hashCode()
result = 31 * result + foodIdx result = 31 * result + (collectId?.hashCode() ?: 0)
result = 31 * result + (name?.hashCode() ?: 0) result = 31 * result + (foodId?.hashCode() ?: 0)
result = 31 * result + (foodName?.hashCode() ?: 0)
result = 31 * result + (version?.hashCode() ?: 0)
result = 31 * result + (otherField?.hashCode() ?: 0)
result = 31 * result + (foodVector?.contentHashCode() ?: 0) result = 31 * result + (foodVector?.contentHashCode() ?: 0)
return result return result
} }
} }
@@ -3,7 +3,6 @@ package com.sw.dualscreen.objbox
import android.content.Context import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.net.Uri import android.net.Uri
import android.util.SparseLongArray
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.sw.dualscreen.MyApp import com.sw.dualscreen.MyApp
@@ -26,7 +25,7 @@ import java.io.InputStream
object FoodModule { object FoodModule {
private const val THRESHOLD = 0.8 private const val THRESHOLD = 0.8
private var module_mobile: Module?=null private var module_mobile: Module? = null
private lateinit var box: Box<Food> private lateinit var box: Box<Food>
private lateinit var embeddingsList: List<List<Float>> private lateinit var embeddingsList: List<List<Float>>
private lateinit var labelsList: IntArray private lateinit var labelsList: IntArray
@@ -35,16 +34,18 @@ object FoodModule {
val NO_STD_RGB = floatArrayOf(1.0f, 1.0f, 1.0f) val NO_STD_RGB = floatArrayOf(1.0f, 1.0f, 1.0f)
val DEFAULT_FOOD_INDEX = -1 val DEFAULT_FOOD_INDEX = -1
fun init(context: Context) { fun init(context: Context, block: () -> Unit = {}) {
Thread { Thread {
module_mobile = Module.load(copyAssetToCache(context, "best_embedding_model_mobile.pt")) module_mobile = Module.load(copyAssetToCache(context, "best_embedding_model_mobile.pt"))
box = ObjectBox.boxStore.boxFor(Food::class) box = ObjectBox.boxStore.boxFor(Food::class)
//if (box.all.isNotEmpty()) { //初始化默认重新拉取数据,先清空本地数据
// box.removeAll() if (box.all.isNotEmpty()) {
//} box.removeAll()
}
//if (box.all.isEmpty()) { //if (box.all.isEmpty()) {
// initDefFoodData(context) // initDefFoodData(context)
//} //}
block()
}.start() }.start()
} }
@@ -100,16 +101,25 @@ object FoodModule {
val idScoreList = query.findIdsWithScores() val idScoreList = query.findIdsWithScores()
val nameScoreList = mutableListOf<IdNameScore>() val nameScoreList = mutableListOf<IdNameScore>()
idScoreList.forEach { idScoreList.forEach {
nameScoreList.add(IdNameScore(id = it.id, name = box.get(it.id).name?:"", score = it.score)) nameScoreList.add(
IdNameScore(
id = it.id,
name = box.get(it.id).foodName ?: "",
score = it.score
)
)
} }
val nameScoreData = GsonUtils.toJson(nameScoreList) val nameScoreData = GsonUtils.toJson(nameScoreList)
Timber.tag("FoodModule").d("registerDataChange,queryFood,耗时:${System.currentTimeMillis()-startTime},数据:$nameScoreData") Timber.tag("FoodModule")
.d("registerDataChange,queryFood,耗时:${System.currentTimeMillis() - startTime},数据:$nameScoreData")
return nameScoreList return nameScoreList
} }
fun getFoodScoreList(bitmap: Bitmap, queryCount: Int = 15): List<IdNameScore> { fun getFoodScoreList(bitmap: Bitmap, queryCount: Int = 15): List<IdNameScore> {
val startTime = System.currentTimeMillis() val startTime = System.currentTimeMillis()
val floatArray = bitmap2FloatArray(bitmap) val floatArray = bitmap2FloatArray(bitmap)
Timber.tag("FoodModule").d("registerDataChange,bitmap2FloatArray,耗时:${System.currentTimeMillis()-startTime}") Timber.tag("FoodModule")
.d("registerDataChange,bitmap2FloatArray,耗时:${System.currentTimeMillis() - startTime}")
val nameScoreList = queryFoodNameScore(floatArray, queryCount) val nameScoreList = queryFoodNameScore(floatArray, queryCount)
if (nameScoreList.isEmpty()) { if (nameScoreList.isEmpty()) {
return emptyList() return emptyList()
@@ -136,6 +146,7 @@ object FoodModule {
//Timber.tag("FoodModule").d("getFoodScoreList数据:${sortedScoreList.toJsonString()}") //Timber.tag("FoodModule").d("getFoodScoreList数据:${sortedScoreList.toJsonString()}")
return sortedScoreList return sortedScoreList
} }
fun queryFood(floatArray: FloatArray, queryCount: Int = 15): List<String> { fun queryFood(floatArray: FloatArray, queryCount: Int = 15): List<String> {
val query: Query<Food> = val query: Query<Food> =
box.query(Food_.foodVector.nearestNeighbors(floatArray, queryCount)).build() box.query(Food_.foodVector.nearestNeighbors(floatArray, queryCount)).build()
@@ -144,10 +155,10 @@ object FoodModule {
val map = mutableMapOf<String, Int>() val map = mutableMapOf<String, Int>()
query.findIdsWithScores().forEach { query.findIdsWithScores().forEach {
val food = box.get(it.id) val food = box.get(it.id)
Timber.d("${food.name}|${food.foodIdx}|${it.score}") Timber.d("${food.foodName}|${food.foodId}|${food.collectId}|${food.version}|${food.otherField}|${it.score}")
if (1 - it.score >= THRESHOLD) { if (1 - it.score >= THRESHOLD) {
//FoodQueryResult(id = it.id, name = food.name, foodIdx = food.foodIdx, score = it.score) //FoodQueryResult(id = it.id, name = food.name, foodIdx = food.foodIdx, score = it.score)
food.name?.let { key -> food.foodName?.let { key ->
val count = map[key] ?: 0 val count = map[key] ?: 0
map.put(key, count + 1) map.put(key, count + 1)
} }
@@ -167,16 +178,16 @@ object FoodModule {
} }
data class IdNameScore( data class IdNameScore(
val id:Long, val id: Long,
var name:String, var name: String,
val score: Double val score: Double
) )
fun initDefFoodData(context: Context, action:()-> Unit={}) { fun initDefFoodData(context: Context, action: () -> Unit = {}) {
val count = box.all.count { it.foodIdx == DEFAULT_FOOD_INDEX } //val count = box.all.count { it.foodIdx == DEFAULT_FOOD_INDEX }
if (count > 0) { //if (count > 0) {
return // return
} //}
val embeddingsJson = AssetsTool.readAssetsFile(context, "data/embeddings.json") val embeddingsJson = AssetsTool.readAssetsFile(context, "data/embeddings.json")
val labelsJson = AssetsTool.readAssetsFile(context, "data/labels.json") val labelsJson = AssetsTool.readAssetsFile(context, "data/labels.json")
val classInfoJson = AssetsTool.readAssetsFile(context, "data/class_info.json") val classInfoJson = AssetsTool.readAssetsFile(context, "data/class_info.json")
@@ -192,7 +203,13 @@ object FoodModule {
val classIdx = labelsList[index] val classIdx = labelsList[index]
val foodName = foodMap["$classIdx"] val foodName = foodMap["$classIdx"]
val array = floatList.toFloatArray() val array = floatList.toFloatArray()
box.put(Food(name = foodName, foodVector = array, foodIdx = DEFAULT_FOOD_INDEX)) box.put(
Food(
foodName = foodName,
foodVector = array,
//foodIdx = DEFAULT_FOOD_INDEX
)
)
} }
action() action()
} }
@@ -25,6 +25,7 @@ import io.objectbox.android.ObjectBoxLiveData
import io.objectbox.exception.DbException import io.objectbox.exception.DbException
import io.objectbox.exception.FileCorruptException import io.objectbox.exception.FileCorruptException
import io.objectbox.sync.Sync import io.objectbox.sync.Sync
import timber.log.Timber
import java.io.File import java.io.File
import java.util.Date import java.util.Date
import java.util.zip.GZIPOutputStream import java.util.zip.GZIPOutputStream
@@ -65,7 +66,7 @@ object ObjectBox {
.androidContext(context.applicationContext) .androidContext(context.applicationContext)
.build() .build()
} catch (e: DbException) { } catch (e: DbException) {
if (e.javaClass.equals(DbException::class.java) || e is FileCorruptException) { if (e.javaClass == DbException::class.java || e is FileCorruptException) {
// Failed to build BoxStore due to database file issue, store message; // Failed to build BoxStore due to database file issue, store message;
// checked in NoteListActivity to notify user. // checked in NoteListActivity to notify user.
dbExceptionMessage = e.toString() dbExceptionMessage = e.toString()
@@ -77,8 +78,9 @@ object ObjectBox {
} }
// if (BuildConfig.DEBUG) { // if (BuildConfig.DEBUG) {
var syncAvailable = if (Sync.isAvailable()) "available" else "unavailable" val syncAvailable = if (Sync.isAvailable()) "available" else "unavailable"
Log.d(TAG,"Using ObjectBox ${BoxStore.getVersion()} (${BoxStore.getVersionNative()}, sync $syncAvailable)") Timber.tag(TAG)
.d("Using ObjectBox ${BoxStore.getVersion()} (${BoxStore.getVersionNative()}, sync $syncAvailable)")
// Enable ObjectBox Admin on debug builds. // Enable ObjectBox Admin on debug builds.
// https://docs.objectbox.io/data-browser // https://docs.objectbox.io/data-browser
Admin(boxStore).start(context.applicationContext) Admin(boxStore).start(context.applicationContext)
@@ -94,7 +96,7 @@ object ObjectBox {
// Do not copy if database file is still in use. // Do not copy if database file is still in use.
// If it would be open, the copy will likely get corrupted // If it would be open, the copy will likely get corrupted
// as BoxStore may currently write data to the file. // as BoxStore may currently write data to the file.
Log.e(TAG, "Database file is still in use, can not copy.") Timber.tag(TAG).e("Database file is still in use, can not copy.")
return false return false
} }
@@ -9,6 +9,7 @@ import com.sw.dualscreen.model.response.FaceData
import com.sw.dualscreen.model.response.FoodInfo import com.sw.dualscreen.model.response.FoodInfo
import com.sw.dualscreen.model.response.FoodSearchReq import com.sw.dualscreen.model.response.FoodSearchReq
import com.sw.dualscreen.model.response.FoodOrder import com.sw.dualscreen.model.response.FoodOrder
import com.sw.dualscreen.model.response.FoodVector
import com.sw.dualscreen.model.response.MemberInfo import com.sw.dualscreen.model.response.MemberInfo
import com.sw.dualscreen.model.response.UserFaceModel import com.sw.dualscreen.model.response.UserFaceModel
import com.sw.dualscreen.model.response.UserNutrition import com.sw.dualscreen.model.response.UserNutrition
@@ -281,7 +282,7 @@ class RemoteRepository constructor(
suspend fun uploadCollectFoodPics( suspend fun uploadCollectFoodPics(
fileList: List<File>, fileList: List<File>,
params: HashMap<String, RequestBody> params: HashMap<String, RequestBody>
): ApiResponse<String> { ): ApiResponse<List<String>?> {
// 准备文件参数 // 准备文件参数
val fileParts = mutableListOf<MultipartBody.Part>() val fileParts = mutableListOf<MultipartBody.Part>()
fileList.forEachIndexed { index, file -> fileList.forEachIndexed { index, file ->
@@ -302,4 +303,19 @@ class RemoteRepository constructor(
suspend fun getDeviceConfig(): ApiResponse<DeviceConfig?> { suspend fun getDeviceConfig(): ApiResponse<DeviceConfig?> {
return safeApiCall { apiService.getDeviceConfig() } return safeApiCall { apiService.getDeviceConfig() }
} }
suspend fun getCollectedFoodVector(param: MutableMap<String, String>): ApiResponse<List<FoodVector>?> {
return safeApiCall {
apiService.getCollectedFoodVector(param = param)
}
}
suspend fun deleteCollectFood(foodId:String?, version: String?): ApiResponse<Any?> {
return safeApiCall {
apiService.deleteCollectFood(param = mutableMapOf(
"foodId" to foodId,
"version" to version
))
}
}
} }
@@ -5,8 +5,8 @@ import java.util.concurrent.atomic.AtomicInteger
class ImageUploader( class ImageUploader(
private val totalList: List<FoodCollectionBean>, private val totalList: List<FoodCollectionBean>,
private val uploadImage: suspend (List<FoodCollectionBean>)-> Boolean, private val uploadImage: suspend (List<FoodCollectionBean>)-> List<String>?,
private val onProgress: (Int, List<FoodCollectionBean>) -> Unit, private val onProgress: (Int, List<FoodCollectionBean>, List<String>) -> Unit,
private val onError:(List<FoodCollectionBean>) -> Unit, private val onError:(List<FoodCollectionBean>) -> Unit,
private val onComplete:() -> Unit private val onComplete:() -> Unit
) { ) {
@@ -16,18 +16,18 @@ class ImageUploader(
private val uploadedCount = AtomicInteger(0) private val uploadedCount = AtomicInteger(0)
suspend fun processUploads() { suspend fun processUploads() {
val batches = totalList.chunked(BATCH_SIZE) val batches = totalList.filter { it.imageVector!=null }.chunked(BATCH_SIZE)
for (batch in batches) { for (batch in batches) {
val success = uploadImage(batch) val idList = uploadImage(batch)
if (!success) { if (idList.isNullOrEmpty()) {
//println("上传失败,终止流程") //println("上传失败,终止流程")
onError(batch) onError(batch)
return return
} }
val fileCount = batch.count { it.imageFile!=null } val fileCount = batch.count { it.imageFile!=null }
uploadedCount.addAndGet(fileCount) uploadedCount.addAndGet(fileCount)
onProgress(uploadedCount.get(), batch) onProgress(uploadedCount.get(), batch, idList)
//println("已上传 ${uploadedCount.get()}/$totalImages") //println("已上传 ${uploadedCount.get()}/$totalImages")
} }
@@ -1,20 +1,14 @@
package com.sw.dualscreen.viewmodel package com.sw.dualscreen.viewmodel
import com.sw.dualscreen.GlobalData
import com.sw.dualscreen.GlobalKey
import com.sw.dualscreen.model.response.EquipmentInfo
import com.sw.dualscreen.utils.GsonUtils
import com.sw.dualscreen.utils.SPUtil
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import timber.log.Timber
/** /**
* 初始化的viewmodel * 初始化的viewmodel
*/ */
class DeviceViewModel : BaseViewModel() { class DeviceViewModel : BaseViewModel() {
private val _deviceInfoResult = MutableStateFlow<Boolean?>(null) // private val _deviceInfoResult = MutableStateFlow<Boolean?>(null)
val deviceInfoResult: StateFlow<Boolean?> = _deviceInfoResult // val deviceInfoResult: StateFlow<Boolean?> = _deviceInfoResult
/** /**
* 获取token * 获取token
@@ -9,6 +9,7 @@ import com.sw.dualscreen.model.response.DeviceConfig
import com.sw.dualscreen.model.response.DinnerType import com.sw.dualscreen.model.response.DinnerType
import com.sw.dualscreen.model.response.FoodInfo import com.sw.dualscreen.model.response.FoodInfo
import com.sw.dualscreen.model.response.FoodOrder import com.sw.dualscreen.model.response.FoodOrder
import com.sw.dualscreen.model.response.FoodVector
import com.sw.dualscreen.model.response.MemberInfo import com.sw.dualscreen.model.response.MemberInfo
import com.sw.dualscreen.model.response.UserFaceModel import com.sw.dualscreen.model.response.UserFaceModel
import com.sw.dualscreen.model.response.UserFaceModel2 import com.sw.dualscreen.model.response.UserFaceModel2
@@ -539,17 +540,17 @@ class UserViewModel : BaseViewModel() {
suspend fun uploadCollectFoodPics( suspend fun uploadCollectFoodPics(
files: List<File?>, files: List<File?>,
params: HashMap<String, RequestBody> params: HashMap<String, RequestBody>
): Boolean { ): List<String>? {
Timber.tag(TAG).d("uploadCollectFoodPics") Timber.tag(TAG).d("uploadCollectFoodPics")
val fileList = files.filterNotNull() val fileList = files.filterNotNull()
if (fileList.isEmpty()) { if (fileList.isEmpty()) {
return true return null
} }
val resp = repository.uploadCollectFoodPics(fileList, params) val resp = repository.uploadCollectFoodPics(fileList, params)
// if (!parseResponse(resp)) { if (resp.code != "00000") {
// return false return null
// } }
return resp.code == "00000" return resp.data
} }
fun getDeviceConfig(block: (DeviceConfig?) -> Unit) { fun getDeviceConfig(block: (DeviceConfig?) -> Unit) {
@@ -563,4 +564,28 @@ class UserViewModel : BaseViewModel() {
} }
} }
} }
fun getCollectedFoodVector(param: MutableMap<String, String>, block: (List<FoodVector>?) -> Unit) {
Timber.tag(TAG).d("getCollectedFoodVector")
launchWithLoading {
val response = repository.getCollectedFoodVector(param)
if (parseResponse(response)) {
block(response.data)
} else {
block(null)
}
}
}
fun deleteCollectFood(foodId:String?, version: String?, block: (Boolean) -> Unit) {
Timber.tag(TAG).d("deleteCollectFood")
launchWithLoading {
val response = repository.deleteCollectFood(foodId, version)
if (parseResponse(response)) {
block(true)
} else {
block(false)
}
}
}
} }
+9 -6
View File
@@ -13,22 +13,23 @@
<RadioGroup <RadioGroup
android:id="@+id/rgSetting" android:id="@+id/rgSetting"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="130dp" android:layout_height="90dp"
android:gravity="center_vertical"
android:orientation="horizontal"> android:orientation="horizontal">
<RadioButton <RadioButton
android:id="@+id/rbBusiness" android:id="@+id/rbBusiness"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="60dp" android:layout_height="match_parent"
android:layout_marginStart="50dp" android:layout_marginStart="50dp"
android:layout_marginEnd="25dp" android:layout_marginEnd="25dp"
android:layout_weight="1" android:layout_weight="1"
android:button="@null" android:button="@null"
tools:checked="true" tools:checked="true"
android:gravity="center" android:gravity="center_horizontal"
android:maxLines="1" android:maxLines="1"
android:text="经营设置" android:text="经营设置"
android:paddingTop="6dp"
android:paddingBottom="32dp"
android:textColor="@color/setting_radio" android:textColor="@color/setting_radio"
android:textSize="30sp" android:textSize="30sp"
android:drawableBottom="@drawable/line_shape_horizontal" android:drawableBottom="@drawable/line_shape_horizontal"
@@ -37,14 +38,16 @@
<RadioButton <RadioButton
android:id="@+id/rbCollect" android:id="@+id/rbCollect"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="60dp" android:layout_height="match_parent"
android:layout_marginStart="25dp" android:layout_marginStart="25dp"
android:layout_marginEnd="50dp" android:layout_marginEnd="50dp"
android:layout_weight="1" android:layout_weight="1"
android:button="@null" android:button="@null"
android:gravity="center" android:gravity="center_horizontal"
android:maxLines="1" android:maxLines="1"
android:text="餐品采集" android:text="餐品采集"
android:paddingTop="6dp"
android:paddingBottom="32dp"
android:textColor="@color/setting_radio" android:textColor="@color/setting_radio"
android:textSize="30sp" android:textSize="30sp"
android:drawableBottom="@drawable/line_shape_horizontal" android:drawableBottom="@drawable/line_shape_horizontal"
+12 -12
View File
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="400dp" android:layout_width="500dp"
android:layout_height="260dp" android:layout_height="300dp"
android:gravity="center" android:gravity="center"
android:orientation="vertical" android:orientation="vertical"
android:background="@drawable/bg_white_radius12" android:background="@drawable/bg_white_radius12"
@@ -23,7 +23,7 @@
android:gravity="center" android:gravity="center"
tools:text="存在未收货的物品,请确认是否放弃收货,若放弃则数据不会保存?" tools:text="存在未收货的物品,请确认是否放弃收货,若放弃则数据不会保存?"
android:textColor="#ff141428" android:textColor="#ff141428"
android:textSize="20sp" /> android:textSize="22sp" />
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -31,26 +31,26 @@
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal"> android:orientation="horizontal">
<TextView <androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnCancel" android:id="@+id/btnCancel"
android:layout_width="120dp" android:layout_width="130dp"
android:layout_height="50dp" android:layout_height="60dp"
android:background="@drawable/bg_white_stroke_blue_ripple" android:background="@drawable/bg_white_stroke_blue_ripple"
android:text="取消" android:text="取消"
android:gravity="center" android:gravity="center"
android:textColor="#FF0033CC" android:textColor="#FF0033CC"
android:textSize="20sp" /> android:textSize="22sp" />
<TextView <androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnConfirm" android:id="@+id/btnConfirm"
android:layout_width="120dp" android:layout_width="130dp"
android:layout_height="50dp" android:layout_height="60dp"
android:layout_marginStart="30dp" android:layout_marginStart="40dp"
android:background="@drawable/bg_blue_ripple" android:background="@drawable/bg_blue_ripple"
android:text="确认" android:text="确认"
android:gravity="center" android:gravity="center"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="20sp" /> android:textSize="22sp" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
@@ -39,12 +39,12 @@
<ImageView <ImageView
android:id="@+id/ivDeleteFood" android:id="@+id/ivDeleteFood"
android:layout_width="60dp" android:layout_width="80dp"
android:layout_height="60dp" android:layout_height="80dp"
android:layout_marginEnd="75dp" android:layout_marginEnd="75dp"
android:paddingHorizontal="10dp" android:paddingHorizontal="13dp"
android:src="@drawable/ic_close2" android:src="@drawable/ic_close2"
tools:ignore="ContentDescription" tools:ignore="ContentDescription"
android:visibility="gone"/> android:visibility="visible"/>
</LinearLayout> </LinearLayout>