diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index c9cfa0b..e1ef242 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -105,12 +105,15 @@ dependencies {
implementation(libs.core)
implementation(libs.android.core)
- implementation (libs.pytorch.android)
- implementation (libs.pytorch.android.torchvision)
+ implementation(libs.pytorch.android)
+ implementation(libs.pytorch.android.torchvision)
val objectboxVersion = "5.0.1"
debugImplementation("io.objectbox:objectbox-android-objectbrowser:$objectboxVersion")
// releaseImplementation("io.objectbox:objectbox-android:$objectboxVersion")
+
+ implementation(libs.androidx.recyclerview)
+
}
apply(plugin = "io.objectbox")
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0c10c05..c80b2ef 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -51,6 +51,21 @@
android:name=".activity.MainActivity"
android:exported="true">
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/sw/dualscreen/activity/FoodCollectionActivity.kt b/app/src/main/java/com/sw/dualscreen/activity/FoodCollectionActivity.kt
index b48ec71..3917590 100644
--- a/app/src/main/java/com/sw/dualscreen/activity/FoodCollectionActivity.kt
+++ b/app/src/main/java/com/sw/dualscreen/activity/FoodCollectionActivity.kt
@@ -1,7 +1,14 @@
package com.sw.dualscreen.activity
import android.annotation.SuppressLint
+import android.content.Context
+import android.content.Intent
+import android.graphics.Typeface
import android.net.Uri
+import android.util.Log
+import android.view.View
+import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
@@ -13,17 +20,28 @@ import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
+import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
+import com.example.utils.FloatBase64Utils
import com.google.common.util.concurrent.ListenableFuture
import com.sw.dualscreen.R
import com.sw.dualscreen.adapter.FoodCollectionAdapter
+import com.sw.dualscreen.adapter.GenericItemAdapter
+import com.sw.dualscreen.adapter.GridSpacingItemDecoration
+import com.sw.dualscreen.adapter.dpToPx
import com.sw.dualscreen.databinding.ActivityFoodCollectionBinding
+import com.sw.dualscreen.databinding.ItemSearchFoodInfoBinding
+import com.sw.dualscreen.databinding.ItemSearchFoodInfoBinding.inflate
+import com.sw.dualscreen.model.response.FoodInfo
import com.sw.dualscreen.objbox.Food
import com.sw.dualscreen.objbox.FoodCollectionBean
import com.sw.dualscreen.objbox.FoodModule
import com.sw.dualscreen.objbox.ObjectBox
import com.sw.dualscreen.presentation.SecondaryScreenPresentation
import com.sw.dualscreen.sdk.SensorScaleUtils
+import com.sw.dualscreen.utils.CameraHelper
+import com.sw.dualscreen.utils.Debouncer
+import com.sw.dualscreen.utils.GlideUtils
import com.sw.dualscreen.utils.ImageUtil
import com.sw.dualscreen.utils.StorageUtils
import com.sw.dualscreen.view.CustomBottomSheetDialog
@@ -32,6 +50,7 @@ import com.sw.dualscreen.viewmodel.UserViewModel
import com.sw.plate.utils.ToastUtils
import io.objectbox.Box
import io.objectbox.kotlin.boxFor
+import kotlinx.coroutines.launch
import org.pytorch.IValue
import org.pytorch.Module
import org.pytorch.torchvision.TensorImageUtils
@@ -50,6 +69,7 @@ class FoodCollectionActivity : BaseActivity() {
private val executor = Executors.newSingleThreadExecutor()
private val viewModel by viewModels()
+ private var selectedFoodId: String? = ""
private var selectedFoodName: String? = ""
private var bottomSheetDialog: CustomBottomSheetDialog? = null
@@ -60,11 +80,17 @@ class FoodCollectionActivity : BaseActivity() {
private var box: Box? = null
private val list: MutableList = mutableListOf()
+ private lateinit var cameraHelper: CameraHelper
+
+ private lateinit var foodAdapter: GenericItemAdapter
+ private val debouncer = Debouncer(2000)
+
private val adapter: FoodCollectionAdapter by lazy {
FoodCollectionAdapter(list).apply {
setOnItemClickListener { _, _, position ->
if (list[position].isShowCamera) {
- takePhoto()
+// takePhoto()
+ cameraHelper.openCamera()
}
}
addOnItemChildClickListener(R.id.ivDelete) { _, _, position ->
@@ -84,7 +110,28 @@ class FoodCollectionActivity : BaseActivity() {
}
override fun initialize() {
- binding.ivBack.setOnClickListener { finish() }
+
+ // 初始化 CameraHelper
+ cameraHelper = CameraHelper(
+ context = this,
+ caller = this,
+ authority = "${packageName}.fileprovider"
+ ) { uri, path ->
+ if (list.size < 6) {
+ val insertIndex = if (list.isEmpty()) 0 else list.size - 1
+ list.add(insertIndex, FoodCollectionBean(imageUri = uri))
+ } else {
+ list[list.size - 1] = FoodCollectionBean(imageUri = uri)
+ }
+
+ adapter.notifyDataSetChanged()
+ }
+
+ binding.ivBack.setOnClickListener {
+ val intent = Intent(this, MainActivity::class.java)
+ startActivity(intent)
+ finish()
+ }
list.add(FoodCollectionBean(isShowCamera = true))
binding.rvFoodCollection.let {
it.layoutManager = GridLayoutManager(this, 3, GridLayoutManager.VERTICAL, false)
@@ -92,11 +139,7 @@ class FoodCollectionActivity : BaseActivity() {
}
binding.btnFoodSearch.setOnClickListener {
- bottomSheetDialog = CustomBottomSheetDialog.newInstance(viewModel) {
- selectedFoodName = it.foodName
- binding.tvSelectedFood.text = selectedFoodName
- }
- bottomSheetDialog!!.show(supportFragmentManager, "CustomBottomSheetDialog")
+ searchInfo()
}
binding.btnSave.setOnClickListener {
@@ -110,39 +153,41 @@ class FoodCollectionActivity : BaseActivity() {
}
vectorThread()
}
- }
- private fun takePhoto() {
- StorageUtils.requestCameraPermission(this) {
- val outputFile = File(this.filesDir, "IMG_${System.currentTimeMillis()}.jpg")
- outputFileUri =
- FileProvider.getUriForFile(this, "${this.packageName}.fileprovider", outputFile)
- takePictureLauncher.launch(outputFileUri!!)
- }
- }
-
- private var outputFileUri: Uri? = null
-
- @SuppressLint("NotifyDataSetChanged")
- private val takePictureLauncher =
- registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
- if (success) {
- //Loading.show(this)
- if (list.size < 6) {
- list.add(list.size - 1, FoodCollectionBean(imageUri = outputFileUri))
- } else {
- list[list.size - 1] = FoodCollectionBean(imageUri = outputFileUri)
- }
- adapter.notifyDataSetChanged()
+ binding.editFoodName.setOnEditorActionListener { v, actionId, event ->
+ if (actionId == EditorInfo.IME_ACTION_SEARCH) {
+ searchInfo()
+ val imm =
+ v.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+ imm.hideSoftInputFromWindow(v.windowToken, 0)
+ true
+ } else {
+ false
}
}
+ foodAdapter = createAdapter()
+ binding.recyclerview.layoutManager = GridLayoutManager(context, 2)
+ // 添加间距装饰(12dp)
+ binding.recyclerview.addItemDecoration(
+ GridSpacingItemDecoration(
+ spanCount = 2,
+ spacing = dpToPx(30),
+ includeEdge = false // 包含边缘间距
+ )
+ )
+ binding.recyclerview.adapter = foodAdapter
+
+ }
+
private fun vectorThread() {
//Loading.show(this)
showWaitingDialog("加载中……")
Thread {
list.forEachIndexed { index, it ->
- image2VectorTask(it.imageUri!!, index)
+
+ if (!it.isShowCamera)
+ image2VectorTask(it.imageUri!!, index)
}
runOnUiThread {
window.decorView.postDelayed({
@@ -169,17 +214,76 @@ class FoodCollectionActivity : BaseActivity() {
NO_STD_RGB // [0.229, 0.224, 0.225] TORCHVISION_NORM_STD_RGB
)
val outputTensor = module!!.forward(IValue.from(inputTensor)).toTensor()
+
+ val base64Str = FloatBase64Utils.floatArrayToBase64(outputTensor.dataAsFloatArray)
+ Timber.tag("mzf1").e(base64Str)
val imageVector = outputTensor.dataAsFloatArray
box?.put(Food(name = selectedFoodName, foodIdx = 0, foodVector = imageVector))
list[position].let {
it.imageVector = imageVector
it.isFinish = true
+
+ Timber
+ viewModel.postImageData(
+ context,
+ foodId = selectedFoodId.toString(),
+ foodName = selectedFoodName.toString(),
+ foodVector = base64Str,
+ uri = imageUri
+ )
}
runOnUiThread {
adapter.notifyItemChanged(position)
}
+ Timber.tag("registerDataChange").e("box.all.size=${box?.all?.size}")
+ }
+ }
+
+
+ private fun searchInfo() {
+ debouncer.debounce {
+ viewModel.searchByFoodName(binding.editFoodName.text.toString())
+ }
+ }
+
+ override fun registerDataChange() {
+ super.registerDataChange()
+ lifecycleScope.launch {
+ viewModel.searchFoodInfoList.collect {
+ foodAdapter.updateData(it)
+ }
}
}
+ private var checkedItem: FoodInfo? = null
+ private fun createAdapter(): GenericItemAdapter {
+ return GenericItemAdapter(
+ items = emptyList(),
+ bindingInflater = ItemSearchFoodInfoBinding::inflate,
+ bindCallback = { item, position ->
+ this.tvName.text = item.foodName
+ if (item.id != checkedItem?.id) {
+ this.tvName.typeface = Typeface.defaultFromStyle(Typeface.NORMAL)
+ this.tvName.setTextColor(resources.getColor(R.color.search_normal))
+ this.llRoot.setBackgroundResource(R.drawable.grid_search_item_normal)
+ } else {
+ this.tvName.typeface = Typeface.defaultFromStyle(Typeface.BOLD)
+ this.tvName.setTextColor(resources.getColor(R.color.search_checked))
+ this.llRoot.setBackgroundResource(R.drawable.grid_search_item_checked)
+ }
+
+ this.llRoot.setOnClickListener {
+ Timber.d("itemClick ${item.foodName}, position = $position")
+ checkedItem = item
+// viewModel.updateCurrentItem(item)
+// itemClickCallback(item)
+ selectedFoodName = item.foodName
+ selectedFoodId = item.id
+
+ foodAdapter.notifyDataSetChanged()
+ }
+ }
+ )
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/sw/dualscreen/activity/MainActivity.kt b/app/src/main/java/com/sw/dualscreen/activity/MainActivity.kt
index cbe8e2c..30d16a0 100644
--- a/app/src/main/java/com/sw/dualscreen/activity/MainActivity.kt
+++ b/app/src/main/java/com/sw/dualscreen/activity/MainActivity.kt
@@ -1,6 +1,7 @@
package com.sw.dualscreen.activity
import android.content.Context
+import android.content.Intent
import android.graphics.Outline
import android.graphics.Typeface
import android.hardware.display.DisplayManager
@@ -43,6 +44,7 @@ import com.sw.dualscreen.utils.ImageUtil
import com.sw.dualscreen.view.CustomBottomSheetDialog
import com.sw.dualscreen.viewmodel.BaseViewModel
import com.sw.dualscreen.viewmodel.UserViewModel
+import com.sw.plate.utils.LightManager
import com.sw.plate.utils.ToastUtils
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
import kotlinx.coroutines.launch
@@ -93,6 +95,11 @@ class MainActivity : BaseActivity() {
private fun initView() {
viewModel.activeEngine()
+ binding.foodDisplayLayout.setOnClickListener {
+ val intent = Intent(this, FoodCollectionActivity::class.java)
+ startActivity(intent)
+ finish()
+ }
binding.previewView.outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
outline.setRoundRect(0, 0, view.width, view.height, 12f.dp)
@@ -202,6 +209,8 @@ class MainActivity : BaseActivity() {
if (weight - lastWeight > 0.1 && presentation?.mealPickupMode == 0) { // 大于100g
debouncer.debounce {
+ LightManager.openRedLight()
+ LightManager.openGreenLight()
takePhoto { photoUri ->
lastPhotoUri = photoUri
Timber.d("registerDataChange photoUri = ${photoUri.path}")
@@ -211,7 +220,11 @@ class MainActivity : BaseActivity() {
Timber.d("registerDataChange photoUri 拿到bitmap")
val bmp = BitmapCropper.cropCenter(bitmap, 1300, 900)
Timber.d("registerDataChange photoUri bitmap裁剪完成")
- val file = BitmapSaver.saveToAppFilesDir(bmp, this, "IMG_CROP_${System.currentTimeMillis()}.jpg")
+ val file = BitmapSaver.saveToAppFilesDir(
+ bmp,
+ this,
+ "IMG_CROP_${System.currentTimeMillis()}.jpg"
+ )
Timber.d("registerDataChange photoUri bitmap保存文件路径:${file?.absolutePath}")
val resultList = FoodModule.queryFood(bmp)
Timber.d("registerDataChange photoUri 拿到识别数据")
diff --git a/app/src/main/java/com/sw/dualscreen/network/api/ApiService.kt b/app/src/main/java/com/sw/dualscreen/network/api/ApiService.kt
index b8918c5..12b6d01 100644
--- a/app/src/main/java/com/sw/dualscreen/network/api/ApiService.kt
+++ b/app/src/main/java/com/sw/dualscreen/network/api/ApiService.kt
@@ -8,10 +8,15 @@ import com.sw.dualscreen.model.response.EquipmentInfo
import com.sw.dualscreen.model.response.FoodInfo
import com.sw.dualscreen.model.response.UserFaceInfo
import com.sw.dualscreen.model.response.UserNutritionData
+import okhttp3.MultipartBody
+import okhttp3.RequestBody
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
+import retrofit2.http.Multipart
import retrofit2.http.POST
+import retrofit2.http.Part
+import retrofit2.http.PartMap
import retrofit2.http.Query
import retrofit2.http.Url
@@ -109,4 +114,17 @@ interface ApiService {
@Query("restId") restId: String,
@Query("foodNames") foodName: String,
): ApiResponse>
+
+
+ /**
+ * 提交采集图片数据
+ */
+ @Multipart
+ @POST
+ suspend fun postImageData(
+ @Url url: String = "http://192.168.1.201:14801/terminal/neglect/dishCollectionVectorData/add",
+ @PartMap params: Map,
+ @Part image: MultipartBody.Part?
+ ): ApiResponse
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/sw/dualscreen/objbox/FoodModule.kt b/app/src/main/java/com/sw/dualscreen/objbox/FoodModule.kt
index d9f17f0..50a976f 100644
--- a/app/src/main/java/com/sw/dualscreen/objbox/FoodModule.kt
+++ b/app/src/main/java/com/sw/dualscreen/objbox/FoodModule.kt
@@ -32,9 +32,9 @@ object FoodModule {
// if (box.all.isNotEmpty()) {
// box.removeAll()
// }
- if (box.all.isEmpty()) {
- initFoodData(context)
- }
+// if (box.all.isEmpty()) {
+// initFoodData(context)
+// }
}
fun queryFood(bitmap: Bitmap, queryCount:Int = 15): List {
diff --git a/app/src/main/java/com/sw/dualscreen/presentation/SecondaryScreenPresentation.kt b/app/src/main/java/com/sw/dualscreen/presentation/SecondaryScreenPresentation.kt
index 43bd799..3bc1e1e 100644
--- a/app/src/main/java/com/sw/dualscreen/presentation/SecondaryScreenPresentation.kt
+++ b/app/src/main/java/com/sw/dualscreen/presentation/SecondaryScreenPresentation.kt
@@ -162,7 +162,7 @@ class SecondaryScreenPresentation(
Timber.d("step1")
currentStep = 1
LightManager.closeGreenLight()
- LightManager.openRedLight()
+ LightManager.closeRedLight()
//回复上一次的设置
// lastWeight = 0.0
dinnerTypeInfo = null
@@ -188,7 +188,7 @@ class SecondaryScreenPresentation(
fun step2(foodInfo: FoodInfo) {
Timber.d("step2")
currentStep = 2
- LightManager.openRedLight()
+ LightManager.closeRedLight()
LightManager.openGreenLight()
dinnerTypeInfo = null
userNutritionData = null
@@ -231,6 +231,7 @@ class SecondaryScreenPresentation(
fun step3() {
Timber.d("step3")
LightManager.closeRedLight()
+ LightManager.openGreenLight()
currentStep = 3
stepChangeCallback(currentStep)
if (currentFood == null) {
diff --git a/app/src/main/java/com/sw/dualscreen/repository/RemoteRepository.kt b/app/src/main/java/com/sw/dualscreen/repository/RemoteRepository.kt
index ab8ad53..4698238 100644
--- a/app/src/main/java/com/sw/dualscreen/repository/RemoteRepository.kt
+++ b/app/src/main/java/com/sw/dualscreen/repository/RemoteRepository.kt
@@ -1,5 +1,7 @@
package com.sw.dualscreen.repository
+import android.content.Context
+import android.net.Uri
import com.sw.dualscreen.GlobalData
import com.sw.dualscreen.model.request.UserNutritionParam
import com.sw.dualscreen.model.response.ApiResponse
@@ -9,6 +11,9 @@ import com.sw.dualscreen.model.response.FoodInfo
import com.sw.dualscreen.model.response.UserFaceInfo
import com.sw.dualscreen.model.response.UserNutritionData
import com.sw.dualscreen.network.api.ApiService
+import com.sw.dualscreen.utils.ImageUtil
+import okhttp3.RequestBody
+import okhttp3.RequestBody.Companion.toRequestBody
/**
* 远程数据处理
@@ -115,4 +120,27 @@ class RemoteRepository constructor(
): ApiResponse> {
return safeApiCall { apiService.getFoodInfo(restId = restId, foodName = foodName) }
}
+ /**
+ * 获取菜品信息
+ * @param restId 从device服务获取的canteenId字段
+ * @param foodName 菜品名称,多个使用逗号拼接
+ */
+ suspend fun postImageData(
+ context: Context,
+ restId: String = GlobalData.restId,
+ foodId: String,
+ foodName: String,
+ foodVector: String,
+ uri: Uri,
+ ): ApiResponse {
+
+ val params = HashMap()
+ params["placeId"] = restId.toRequestBody()
+ params["foodId"] = foodId.toRequestBody()
+ params["foodName"] = foodName.toRequestBody()
+ params["foodVector"] = foodVector.toRequestBody()
+ val imagePart = ImageUtil.uriToMultipart(context, uri, "foodPic")
+
+ return safeApiCall { apiService.postImageData(params = params, image = imagePart) }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/sw/dualscreen/utils/CameraHelper.kt b/app/src/main/java/com/sw/dualscreen/utils/CameraHelper.kt
new file mode 100644
index 0000000..34f9f2f
--- /dev/null
+++ b/app/src/main/java/com/sw/dualscreen/utils/CameraHelper.kt
@@ -0,0 +1,68 @@
+package com.sw.dualscreen.utils
+
+import android.content.Context
+import android.net.Uri
+import android.provider.OpenableColumns
+import android.widget.Toast
+import androidx.activity.result.ActivityResultCaller
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.core.content.FileProvider
+import java.io.File
+import java.io.FileOutputStream
+
+/**
+ * 简洁相机工具类
+ * 调用系统相机拍照并返回照片路径
+ */
+class CameraHelper(
+ private val context: Context,
+ caller: ActivityResultCaller,
+ private val authority: String,
+ private val onResult: (uri: Uri, filePath: String?) -> Unit
+) {
+
+ private lateinit var imageUri: Uri
+ private val takePictureLauncher: ActivityResultLauncher
+
+ init {
+ takePictureLauncher =
+ caller.registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
+ if (success) {
+ val path = getRealPathFromUri(context, imageUri)
+ onResult(imageUri, path)
+ } else {
+ Toast.makeText(context, "拍照取消", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+
+ /** 打开系统相机 */
+ fun openCamera() {
+ // 1. 创建存储图片的文件
+ val photoFile = File(context.externalCacheDir, "IMG_${System.currentTimeMillis()}.jpg")
+ photoFile.parentFile?.mkdirs() // 确保目录存在
+
+ // 2. 生成 content:// Uri
+ imageUri = FileProvider.getUriForFile(context, authority, photoFile)
+
+ // 3. 启动相机
+ takePictureLauncher.launch(imageUri)
+ }
+
+ /** 可选:将 content:// 转为实际路径 */
+ private fun getRealPathFromUri(context: Context, uri: Uri): String? {
+ var filePath: String? = null
+ context.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
+ val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
+ cursor.moveToFirst()
+ val fileName = cursor.getString(nameIndex)
+ val file = File(context.cacheDir, fileName)
+ context.contentResolver.openInputStream(uri)?.use { input ->
+ FileOutputStream(file).use { output -> input.copyTo(output) }
+ }
+ filePath = file.absolutePath
+ }
+ return filePath
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/sw/dualscreen/utils/FloatBase64Utils.kt b/app/src/main/java/com/sw/dualscreen/utils/FloatBase64Utils.kt
new file mode 100644
index 0000000..f5cdb33
--- /dev/null
+++ b/app/src/main/java/com/sw/dualscreen/utils/FloatBase64Utils.kt
@@ -0,0 +1,47 @@
+package com.example.utils
+
+import android.util.Base64
+import java.nio.ByteBuffer
+import java.nio.ByteOrder
+
+/**
+ * 工具类:float[] 与 Base64 字符串互转
+ * 无需外部依赖,适用于 Android 原生项目。
+ */
+object FloatBase64Utils {
+
+ /**
+ * float数组 → Base64字符串
+ * @param values FloatArray
+ * @return Base64字符串(无换行)
+ */
+ fun floatArrayToBase64(values: FloatArray): String {
+ if (values.isEmpty()) return ""
+
+ val buffer = ByteBuffer.allocate(values.size * 4)
+ buffer.order(ByteOrder.BIG_ENDIAN) // 默认大端序,可改 LITTLE_ENDIAN
+ values.forEach { buffer.putFloat(it) }
+
+ val bytes = buffer.array()
+ return Base64.encodeToString(bytes, Base64.NO_WRAP)
+ }
+
+ /**
+ * Base64字符串 → float数组
+ * @param base64 Base64字符串
+ * @return FloatArray
+ */
+ fun base64ToFloatArray(base64: String?): FloatArray {
+ if (base64.isNullOrEmpty()) return FloatArray(0)
+
+ val bytes = Base64.decode(base64, Base64.NO_WRAP)
+ val buffer = ByteBuffer.wrap(bytes)
+ buffer.order(ByteOrder.BIG_ENDIAN)
+
+ val result = FloatArray(bytes.size / 4)
+ for (i in result.indices) {
+ result[i] = buffer.getFloat()
+ }
+ return result
+ }
+}
diff --git a/app/src/main/java/com/sw/dualscreen/utils/ImageUtil.kt b/app/src/main/java/com/sw/dualscreen/utils/ImageUtil.kt
index 915e13d..aa59848 100644
--- a/app/src/main/java/com/sw/dualscreen/utils/ImageUtil.kt
+++ b/app/src/main/java/com/sw/dualscreen/utils/ImageUtil.kt
@@ -4,6 +4,12 @@ import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.MultipartBody
+import okhttp3.RequestBody
+import java.io.File
+import java.io.FileOutputStream
+import java.io.InputStream
object ImageUtil {
@@ -18,4 +24,24 @@ object ImageUtil {
}
}
+ fun uriToMultipart(context: Context, uri: Uri, partName: String): MultipartBody.Part? {
+ try {
+ val contentResolver = context.contentResolver
+ val inputStream: InputStream = contentResolver.openInputStream(uri) ?: return null
+
+ // 临时文件(避免直接访问SAF文件)
+ val tempFile = File.createTempFile("upload_", ".jpg", context.cacheDir)
+
+ val outputStream = FileOutputStream(tempFile)
+ inputStream.copyTo(outputStream)
+ inputStream.close()
+ outputStream.close()
+
+ val requestFile = RequestBody.create("image/*".toMediaTypeOrNull(), tempFile)
+ return MultipartBody.Part.createFormData(partName, tempFile.name, requestFile)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ return null
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/sw/dualscreen/viewmodel/BaseViewModel.kt b/app/src/main/java/com/sw/dualscreen/viewmodel/BaseViewModel.kt
index e056706..99593a1 100644
--- a/app/src/main/java/com/sw/dualscreen/viewmodel/BaseViewModel.kt
+++ b/app/src/main/java/com/sw/dualscreen/viewmodel/BaseViewModel.kt
@@ -52,7 +52,10 @@ abstract class BaseViewModel() : ViewModel() {
}
protected open fun parseResponse(response: ApiResponse<*>): Boolean {
- if (response.isSuccess()) {
+ if (response.isSuccess()
+ || response.code == 200
+ || response.code == 0
+ ) {
return true
}
Timber.d("msg = ${response.message}, code = ${response.code}")
diff --git a/app/src/main/java/com/sw/dualscreen/viewmodel/UserViewModel.kt b/app/src/main/java/com/sw/dualscreen/viewmodel/UserViewModel.kt
index 5bf659a..ccdb7d1 100644
--- a/app/src/main/java/com/sw/dualscreen/viewmodel/UserViewModel.kt
+++ b/app/src/main/java/com/sw/dualscreen/viewmodel/UserViewModel.kt
@@ -1,5 +1,7 @@
package com.sw.dualscreen.viewmodel
+import android.content.Context
+import android.net.Uri
import androidx.lifecycle.viewModelScope
import com.arcsoft.face.ErrorInfo
import com.sw.dualscreen.GlobalData
@@ -48,7 +50,7 @@ class UserViewModel : BaseViewModel() {
* 人脸加载完成
*/
private val _loadFaceResult = MutableStateFlow(false)
- val loadFaceResult : StateFlow = _loadFaceResult
+ val loadFaceResult: StateFlow = _loadFaceResult
private val _identifiedFoodInfoList2 = MutableStateFlow>(emptyList())
val identifiedFoodInfoList2: StateFlow> = _identifiedFoodInfoList2
@@ -91,7 +93,7 @@ class UserViewModel : BaseViewModel() {
val nextPageIndex = response.result?.nextPageIndex ?: -1
if (nextPageIndex > 0) {
getUserFaceCache(nextPageIndex)
- } else{
+ } else {
_loadFaceResult.value = true
}
}
@@ -217,4 +219,23 @@ class UserViewModel : BaseViewModel() {
}
}
}
+
+ fun postImageData(
+ context: Context,
+ foodId: String,
+ foodName: String,
+ foodVector: String,
+ uri: Uri
+ ) {
+ Timber.d("postImageData")
+ launchWithLoading {
+ val response = repository.postImageData(
+ context, foodId = foodId, foodName = foodName, foodVector = foodVector,
+ uri = uri
+ )
+ if (parseResponse(response)) {
+ Timber.tag("mzf").d("code=${response.code}")
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_food_collection.xml b/app/src/main/res/layout/activity_food_collection.xml
index eadbb47..ae3b145 100644
--- a/app/src/main/res/layout/activity_food_collection.xml
+++ b/app/src/main/res/layout/activity_food_collection.xml
@@ -8,12 +8,13 @@
+ android:layout_height="68dp"
+ android:layout_marginTop="24dp">
@@ -23,7 +24,7 @@
android:layout_gravity="center"
android:text="菜品采集"
android:textColor="@color/black"
- android:textSize="18sp" />
+ android:textSize="26sp" />
-
@@ -61,24 +67,28 @@
android:id="@+id/btnFoodSearch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginEnd="6dp"
+ android:layout_marginLeft="12dp"
android:text="菜品检索"
android:textColor="#ffffff"
android:textSize="16sp" />
-
+ android:layout_marginStart="24dp"
+ android:layout_marginTop="24dp"
+ android:layout_marginEnd="24dp"
+ android:layout_weight="1" />
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 61df63f..4335c2c 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -24,9 +24,9 @@
@@ -43,6 +43,7 @@
android:text="羊肉臊子荞面饸饹" />
+ android:background="@drawable/bg_gray">
+ tools:src="@drawable/ic_camera256" />
+ android:src="@drawable/ic_delete_red" />
+ android:padding="5dp"
+ android:src="@drawable/ic_finish" />
\ No newline at end of file