diff --git a/app/src/main/java/com/shuwei/dish/match/adapter/GoodsInfoSearchAdapter.kt b/app/src/main/java/com/shuwei/dish/match/adapter/GoodsInfoSearchAdapter.kt index c4623f1..3473697 100644 --- a/app/src/main/java/com/shuwei/dish/match/adapter/GoodsInfoSearchAdapter.kt +++ b/app/src/main/java/com/shuwei/dish/match/adapter/GoodsInfoSearchAdapter.kt @@ -41,6 +41,7 @@ class GoodsInfoSearchAdapter(private var list: MutableList) ): VH { val binding = ListItemSearchGoodsInfoBinding.inflate(LayoutInflater.from(context), parent, false) + return VH(binding) } diff --git a/app/src/main/java/com/shuwei/dish/match/dialog/FoodRecognizeDialog.kt b/app/src/main/java/com/shuwei/dish/match/dialog/FoodRecognizeDialog.kt new file mode 100644 index 0000000..bd396db --- /dev/null +++ b/app/src/main/java/com/shuwei/dish/match/dialog/FoodRecognizeDialog.kt @@ -0,0 +1,60 @@ +package com.shuwei.dish.match.dialog + +import android.annotation.SuppressLint +import android.os.Handler +import android.os.Looper +import android.view.LayoutInflater +import androidx.recyclerview.widget.GridLayoutManager +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.shuwei.dish.match.R +import com.shuwei.dish.match.adapter.GoodsInfoSearchAdapter +import com.shuwei.dish.match.base.BaseActivity +import com.shuwei.dish.match.databinding.DialogFoodRecognizeBinding +import com.shuwei.dish.match.entity.CookFoodGoodsEntity + +/** + * 菜品识别结果弹窗,继承 BottomSheetDialog 确保只初始化一次 + * @param activity 宿主 Activity + * @param onItemSelected 用户点击某一菜品时的回调 + */ +class FoodRecognizeDialog( + private val activity: BaseActivity, + private val onItemSelected: (item: CookFoodGoodsEntity) -> Unit +) : BottomSheetDialog(activity, R.style.BottomSheet) { + + private val binding = DialogFoodRecognizeBinding.inflate(LayoutInflater.from(activity)) + private val list = mutableListOf() + private val adapter = GoodsInfoSearchAdapter(list).apply { + setOnItemClickListener { _, _, position -> + list[position].isClicked = true + notifyItemChanged(position) + Handler(Looper.getMainLooper()).postDelayed({ + onItemSelected(list[position]) + dismiss() + }, 300) + } + } + + init { + setContentView(binding.root) + setCancelable(true) + behavior.skipCollapsed = false + setOnDismissListener { activity.hideStatusBar() } + binding.recyclerView.run { + layoutManager = GridLayoutManager(activity, 2, GridLayoutManager.VERTICAL, false) + adapter = this@FoodRecognizeDialog.adapter + } + } + + /** + * 加载识别结果数据并显示弹窗 + * @param items 识别到的菜品列表 + */ + @SuppressLint("NotifyDataSetChanged") + fun loadData(items: List) { + list.clear() + list.addAll(items) + adapter.notifyDataSetChanged() + if (!isShowing) show() + } +} diff --git a/app/src/main/java/com/shuwei/dish/match/ui/PrepareCookActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/PrepareCookActivity.kt index 15a66cf..f5972cc 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/PrepareCookActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/PrepareCookActivity.kt @@ -1,10 +1,11 @@ package com.shuwei.dish.match.ui import android.annotation.SuppressLint +import android.graphics.Bitmap import android.graphics.Typeface +import android.net.Uri import android.os.Bundle import android.util.Log -import androidx.activity.viewModels import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import com.shuwei.dish.match.R @@ -12,23 +13,34 @@ import com.shuwei.dish.match.adapter.DishPartAdapter import com.shuwei.dish.match.base.BaseActivity import com.shuwei.dish.match.databinding.ActivityPrepareCookBinding import com.shuwei.dish.match.databinding.LayoutFoodRemindBinding -import com.shuwei.dish.match.dialog.FoodSearchDialog import com.shuwei.dish.match.dialog.CommonDialog +import com.shuwei.dish.match.dialog.FoodRecognizeDialog +import com.shuwei.dish.match.dialog.FoodSearchDialog import com.shuwei.dish.match.entity.CookFoodEntity import com.shuwei.dish.match.entity.CookFoodGoodsEntity import com.shuwei.dish.match.entity.FoodRecord -import com.shuwei.dish.match.net.NetViewModel +import android.view.ViewGroup +import androidx.camera.view.PreviewView +import androidx.lifecycle.lifecycleScope +import com.shuwei.dish.match.databinding.LayoutCameraPreviewBinding +import com.shuwei.dish.match.objbox.FoodModule import com.shuwei.dish.match.utils.AddressUtil +import com.shuwei.dish.match.utils.CameraUtils +import com.shuwei.dish.match.utils.ImageUtil import com.shuwei.dish.match.utils.SwipeCallback import com.shuwei.dish.match.utils.WeightUtil import com.shuwei.dish.match.utils.ext.clickWithDebounce import com.shuwei.dish.match.utils.ext.gone +import com.shuwei.dish.match.utils.ext.roundedDecimalPlace import com.shuwei.dish.match.utils.ext.startActivity import com.shuwei.dish.match.utils.ext.toJsonString import com.shuwei.dish.match.utils.ext.toast import com.shuwei.dish.match.utils.ext.visible +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.io.Serializable -import kotlin.getValue +import java.util.concurrent.atomic.AtomicBoolean @SuppressLint("NotifyDataSetChanged") class PrepareCookActivity : BaseActivity() { @@ -40,6 +52,8 @@ class PrepareCookActivity : BaseActivity() { private lateinit var binding: ActivityPrepareCookBinding + private val cameraUtils: CameraUtils by lazy { CameraUtils(this) } + private var food: FoodRecord? = null private var goodsList: MutableList? = null @@ -69,6 +83,7 @@ class PrepareCookActivity : BaseActivity() { binding.tvDishName.text = food?.foodName ?: "" addViewClickListener() + initCamera(binding.flCameraContainer) initRecyclerView() getDishDetail() @@ -81,6 +96,9 @@ class PrepareCookActivity : BaseActivity() { //Log.d(TAG, "addViewClickListener: address=$address,state=$state,weight=$weight") if (address == AddressUtil.ONE) { binding.tvDishPartWeight.text = "${weight}" + if (state == WeightUtil.STATE_STABLE) { + recognizeFood(weight) + } } }) binding.ivWeightClear.setOnClickListener { @@ -134,6 +152,25 @@ class PrepareCookActivity : BaseActivity() { } } + private val isTakingPhoto = AtomicBoolean(false) + + /** + * 重量稳定时触发菜品识别 + * @param weight 当前稳定重量 + */ + private fun recognizeFood(weight: Double) { + if (weight < 10) { + if (recognizeDialog.isShowing) recognizeDialog.dismiss() + return + } + if (isTakingPhoto.get() || recognizeDialog.isShowing) return + isTakingPhoto.set(true) + cameraUtils.takePhoto( + succCallback = cameraSuccessCallback, + failCallback = cameraFailureCallback + ) + } + private fun addFood() { FoodSearchDialog().show(this) { item -> val filterResult = list.firstOrNull { it.goodsId == item.goodsId } @@ -251,6 +288,16 @@ class PrepareCookActivity : BaseActivity() { private var firstReqSize = 0 private val list = mutableListOf() + private val recognizeDialog by lazy { + FoodRecognizeDialog(this) { item -> + // TODO: 处理识别结果选中 + } + .apply { + setOnDismissListener { + isTakingPhoto.set(false) + } + } + } private val dishPartAdapter by lazy { DishPartAdapter(list).apply { setOnItemClickListener { _, _, positon -> @@ -326,6 +373,26 @@ class PrepareCookActivity : BaseActivity() { } } + override fun onResume() { + super.onResume() + cameraUtils.bind() + } + + override fun onPause() { + super.onPause() + cameraUtils.unbind() + } + + /** + * 初始化相机并绑定预览容器 + * @param container 相机预览容器 + */ + fun initCamera(container: ViewGroup) { + cameraUtils.initCamera() + val previewBinding = LayoutCameraPreviewBinding.inflate(layoutInflater, container) + cameraUtils.setPreviewController(previewBinding.previewView) + } + @Suppress("DEPRECATION") @SuppressLint("GestureBackNavigation") @Deprecated("Deprecated in Java") @@ -367,4 +434,49 @@ class PrepareCookActivity : BaseActivity() { }.show() } + private val cameraSuccessCallback: (Uri) -> Unit = { uri -> + Log.d(TAG, "takePhoto success") + lifecycleScope.launch { + withContext(Dispatchers.IO) { + val bitmap = ImageUtil.uriToBitmap(this@PrepareCookActivity, uri) + if (bitmap == null) { + Log.d(TAG, "takePhoto bitmap is null") + isTakingPhoto.set(false) + return@withContext + } + val foodScoreList = FoodModule.getFoodScoreList(bitmap) + if (foodScoreList.isEmpty()) { + Log.d(TAG, "takePhoto foodScoreList is empty") + isTakingPhoto.set(false) + return@withContext + } + withContext(Dispatchers.Main) { + queryFood(foodScoreList) + } + } + } + } + + private val cameraFailureCallback: (String) -> Unit = { errMsg -> + Log.d(TAG, "takePhoto failure") + isTakingPhoto.set(false) + } + + private fun queryFood(list: List) { + Log.d(TAG, "takePhoto queryFood, list=${list.toString()}") + //调用接口成功,返回食材列表设置isTakingPhoto.set(false),暂时写死数据 + val foodList = mutableListOf() + list.forEachIndexed { index, food -> + val foodName = food.name.split("WP").first() + val foodScore = ((1-food.score) * 100).roundedDecimalPlace(2) + foodList.add(CookFoodGoodsEntity( + goodsId = index.toString(), + goodsName = "${foodName}-${foodScore}%", + foodId = index.toString() + )) + } + recognizeDialog.loadData(foodList) + isTakingPhoto.set(false) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/shuwei/dish/match/ui/SelectDishActivity.kt b/app/src/main/java/com/shuwei/dish/match/ui/SelectDishActivity.kt index c7c3371..3499b59 100644 --- a/app/src/main/java/com/shuwei/dish/match/ui/SelectDishActivity.kt +++ b/app/src/main/java/com/shuwei/dish/match/ui/SelectDishActivity.kt @@ -82,18 +82,19 @@ class SelectDishActivity : BaseActivity() { private var configFinished = false fun judgeDeviceConfig(block: () -> Unit) { - if (configFinished) { - block() - return - } - appViewModel.loadSeasoning { - if (it.isEmpty()) { - showDeviceConfigDialog() - return@loadSeasoning - } - configFinished = true - block() - } +// if (configFinished) { +// block() +// return +// } +// appViewModel.loadSeasoning { +// if (it.isEmpty()) { +// showDeviceConfigDialog() +// return@loadSeasoning +// } +// configFinished = true +// block() +// } + block() } private fun showDeviceConfigDialog() { diff --git a/app/src/main/java/com/shuwei/dish/match/utils/GpioUtils.java b/app/src/main/java/com/shuwei/dish/match/utils/GpioUtils.java new file mode 100644 index 0000000..99875bf --- /dev/null +++ b/app/src/main/java/com/shuwei/dish/match/utils/GpioUtils.java @@ -0,0 +1,175 @@ +package com.shuwei.dish.match.utils; + +import android.os.SystemClock; +import android.util.Log; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.DataOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; + +/** + * Created by Administrator on 2018/11/9. + */ +public class GpioUtils { + + private static final String TAG = "GpioUtils"; + + /* + 给export申请权限 + */ + public static void upgradeRootPermissionForExport() { + upgradeRootPermission("/sys/class/gpio/export"); + } + + /* + 配置一个gpio路径 + */ + public static boolean exportGpio(int gpio) { + return writeNode("/sys/class/gpio/export", "" + gpio); + } + + /* + 给获取io口的状态的路径申请权限,该方法需要在exportGpio后调用 + */ + public static void upgradeRootPermissionForGpio(int gpio) { + upgradeRootPermission("/sys/class/gpio/gpio" + gpio + "/direction"); + upgradeRootPermission("/sys/class/gpio/gpio" + gpio + "/value"); + } + + + /* + 设置io口为输入或输出 + */ + public static boolean setGpioDirection(int gpio, int arg) { + String gpioDirection = ""; + if (arg == 0) gpioDirection = "out"; + else if (arg == 1) gpioDirection = "in"; + else return false; + return writeNode("/sys/class/gpio/gpio" + gpio + "/direction", gpioDirection); + } + + /* + 获取io口的状态为输出还是输入 + */ + public static String getGpioDirection(int gpio) { + String gpioDirection = ""; + gpioDirection = readNode("/sys/class/gpio/gpio" + gpio + "/direction"); + return gpioDirection; + } + + /* + 给输出io口写值,高电平或低电平 + */ + public static boolean writeGpioValue(int gpio, String arg) { + return writeNode("/sys/class/gpio/gpio" + gpio + "/value", arg); + } + + //获取当前gpio是高还是低 + public static String getGpioValue(int gpio) { + return readNode("/sys/class/gpio/gpio" + gpio + "/value"); + } + + + private static boolean upgradeRootPermission(String path) { + Process process = null; + DataOutputStream os = null; + try { + String cmd = "chmod 777 " + path; + process = Runtime.getRuntime().exec("su"); //切换到root帐号 + os = new DataOutputStream(process.getOutputStream()); + os.writeBytes(cmd + "\n"); + os.writeBytes("exit\n"); + os.flush(); + process.waitFor(); + } catch (Exception e) { + } finally { + try { + if (os != null) { + os.close(); + } + process.destroy(); + } catch (Exception e) { + } + } + try { + return process.waitFor() == 0; + } catch (InterruptedException e) { + e.printStackTrace(); + } + return false; + } + + private static boolean writeNode(String path, String arg) { + Log.d(TAG, "Gpio_test set node path: " + path + " arg: " + arg); + if (path == null || arg == null) { + Log.e(TAG, "set node error"); + return false; + } + FileWriter fileWriter = null; + BufferedWriter bufferedWriter = null; + try { + fileWriter = new FileWriter(path); + fileWriter.write(arg); + } catch (Exception e) { + Log.e(TAG, "Gpio_test write node error!! path" + path + " arg: " + arg); + e.printStackTrace(); + return false; + } finally { + try { + if (fileWriter != null) { + fileWriter.close(); + } + if (bufferedWriter != null) { + bufferedWriter.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return true; + } + + private static long mTime = 0; + private static int mFailTimes = 0; + + private static String readNode(String path) { + String result = ""; + + FileReader fread = null; + BufferedReader buffer = null; + try { + fread = new FileReader(path); + buffer = new BufferedReader(fread); + String str = null; + while ((str = buffer.readLine()) != null) { + result = str; + break; + } + mFailTimes = 0; + } catch (IOException e) { + Log.e(TAG, "IO Exception"); + e.printStackTrace(); + if (mTime == 0 || SystemClock.uptimeMillis() - mTime < 1000) { + mFailTimes++; + } + if (mFailTimes >= 3) { + Log.d(TAG, "read format node continuous failed three times, exist thread"); + } + } finally { + try { + if (buffer != null) { + buffer.close(); + } + if (fread != null) { + fread.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return result; + } +} diff --git a/app/src/main/java/com/shuwei/dish/match/utils/L.java b/app/src/main/java/com/shuwei/dish/match/utils/L.java new file mode 100644 index 0000000..32484f1 --- /dev/null +++ b/app/src/main/java/com/shuwei/dish/match/utils/L.java @@ -0,0 +1,57 @@ +package com.shuwei.dish.match.utils; + +/** + * Log统一管理类 + */ +public class L { + + private L() { + /* cannot be instantiated */ + throw new UnsupportedOperationException("cannot be instantiated"); + } + + public static boolean isDebug = true;// 是否需要打印bug,可以在application的onCreate函数里面初始化 + private static final String TAG = "mzf"; + + // 下面四个是默认tag的函数 + public static void i(String msg) { + if (isDebug) + android.util.Log.i(TAG, msg); + } + + public static void d(String msg) { + if (isDebug) + android.util.Log.d(TAG, msg); + } + + public static void e(String msg) { + if (isDebug) + android.util.Log.e(TAG, msg); + } + + public static void v(String msg) { + if (isDebug) + android.util.Log.v(TAG, msg); + } + + // 下面是传入自定义tag的函数 + public static void i(String tag, String msg) { + if (isDebug) + android.util.Log.i(tag, msg); + } + + public static void d(String tag, String msg) { + if (isDebug) + android.util.Log.d(tag, msg); + } + + public static void e(String tag, String msg) { + if (isDebug) + android.util.Log.e(tag, msg); + } + + public static void v(String tag, String msg) { + if (isDebug) + android.util.Log.v(tag, msg); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/shuwei/dish/match/utils/LightManager.java b/app/src/main/java/com/shuwei/dish/match/utils/LightManager.java new file mode 100644 index 0000000..2032f39 --- /dev/null +++ b/app/src/main/java/com/shuwei/dish/match/utils/LightManager.java @@ -0,0 +1,25 @@ +package com.shuwei.dish.match.utils; + +public class LightManager { + private static final String TAG = "LightManager"; + + public static void openGreenLight() { + L.e(TAG, "openGreenLight"); + GpioUtils.writeGpioValue(41, "1"); + } + + public static void closeGreenLight() { + L.e(TAG, "closeGreenLight"); + GpioUtils.writeGpioValue(41, "0"); + } + + public static void openRedLight() { + L.e(TAG, "openRedLight"); + GpioUtils.writeGpioValue(40, "1"); + } + + public static void closeRedLight() { + L.e(TAG, "closeRedLight"); + GpioUtils.writeGpioValue(40, "0"); + } +} diff --git a/app/src/main/res/layout/activity_prepare_cook.xml b/app/src/main/res/layout/activity_prepare_cook.xml index 7e8acb6..23b9b1c 100644 --- a/app/src/main/res/layout/activity_prepare_cook.xml +++ b/app/src/main/res/layout/activity_prepare_cook.xml @@ -262,6 +262,11 @@ + + + + + + + + + + +