From e47b362883fa1370eaf2624c088a1fac3ffec487 Mon Sep 17 00:00:00 2001 From: mazengfei <331023091@qq.com> Date: Fri, 31 Oct 2025 09:53:51 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=8F=9C=E5=93=81=E9=87=87?= =?UTF-8?q?=E9=9B=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 7 +- app/src/main/AndroidManifest.xml | 15 ++ .../activity/FoodCollectionActivity.kt | 166 ++++++++++++++---- .../sw/dualscreen/activity/MainActivity.kt | 15 +- .../sw/dualscreen/network/api/ApiService.kt | 18 ++ .../com/sw/dualscreen/objbox/FoodModule.kt | 6 +- .../SecondaryScreenPresentation.kt | 5 +- .../dualscreen/repository/RemoteRepository.kt | 28 +++ .../com/sw/dualscreen/utils/CameraHelper.kt | 68 +++++++ .../sw/dualscreen/utils/FloatBase64Utils.kt | 47 +++++ .../java/com/sw/dualscreen/utils/ImageUtil.kt | 26 +++ .../sw/dualscreen/viewmodel/BaseViewModel.kt | 5 +- .../sw/dualscreen/viewmodel/UserViewModel.kt | 25 ++- .../res/layout/activity_food_collection.xml | 38 ++-- app/src/main/res/layout/activity_main.xml | 3 +- .../res/layout/list_item_food_collection.xml | 22 +-- 16 files changed, 426 insertions(+), 68 deletions(-) create mode 100644 app/src/main/java/com/sw/dualscreen/utils/CameraHelper.kt create mode 100644 app/src/main/java/com/sw/dualscreen/utils/FloatBase64Utils.kt 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" />