Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
428852a07e | ||
|
|
96863e2078 | ||
|
|
41036cdbf9 |
@@ -11,3 +11,5 @@ local.properties
|
|||||||
.claude/
|
.claude/
|
||||||
app/debug/
|
app/debug/
|
||||||
app/release/
|
app/release/
|
||||||
|
# 临时忽略 assets 下的 .pt 模型文件,正式文件就绪后取消此行
|
||||||
|
app/src/main/assets/*.pt
|
||||||
|
|||||||
@@ -137,8 +137,8 @@ dependencies {
|
|||||||
implementation(libs.androidx.camera.extensions)
|
implementation(libs.androidx.camera.extensions)
|
||||||
|
|
||||||
//pytorch
|
//pytorch
|
||||||
// implementation (libs.pytorch.android)
|
implementation (libs.pytorch.android)
|
||||||
// implementation (libs.pytorch.android.torchvision)
|
implementation (libs.pytorch.android.torchvision)
|
||||||
|
|
||||||
// objectbox
|
// objectbox
|
||||||
if (isDebug) {
|
if (isDebug) {
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ class GoodsInfoSearchAdapter(private var list: MutableList<CookFoodGoodsEntity>)
|
|||||||
): VH {
|
): VH {
|
||||||
val binding =
|
val binding =
|
||||||
ListItemSearchGoodsInfoBinding.inflate(LayoutInflater.from(context), parent, false)
|
ListItemSearchGoodsInfoBinding.inflate(LayoutInflater.from(context), parent, false)
|
||||||
|
|
||||||
return VH(binding)
|
return VH(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import android.content.SharedPreferences
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.shuwei.dish.match.R
|
import com.shuwei.dish.match.R
|
||||||
import com.shuwei.dish.match.db.DatabaseProvider
|
import com.shuwei.dish.match.db.DatabaseProvider
|
||||||
|
import com.shuwei.dish.match.objbox.ObjectBox
|
||||||
import com.shuwei.dish.match.scale.ScaleServiceManager
|
import com.shuwei.dish.match.scale.ScaleServiceManager
|
||||||
import com.shuwei.dish.match.ui.InitActivity.Companion.TAG
|
import com.shuwei.dish.match.ui.InitActivity.Companion.TAG
|
||||||
import com.shuwei.dish.match.utils.AppUtil
|
import com.shuwei.dish.match.utils.AppUtil
|
||||||
@@ -44,7 +45,7 @@ class BaseApp : Application() {
|
|||||||
// GlobalData.deviceId = "39a7abdd06b3c7ab"
|
// GlobalData.deviceId = "39a7abdd06b3c7ab"
|
||||||
val filter = IntentFilter(Intent.ACTION_BOOT_COMPLETED)
|
val filter = IntentFilter(Intent.ACTION_BOOT_COMPLETED)
|
||||||
registerReceiver(BootReceiver(), filter)
|
registerReceiver(BootReceiver(), filter)
|
||||||
// ObjectBox.init(this)
|
ObjectBox.init(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTerminate() {
|
override fun onTerminate() {
|
||||||
|
|||||||
@@ -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<CookFoodGoodsEntity>()
|
||||||
|
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<CookFoodGoodsEntity>) {
|
||||||
|
list.clear()
|
||||||
|
list.addAll(items)
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
if (!isShowing) show()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,308 +1,308 @@
|
|||||||
//package com.shuwei.dish.match.objbox
|
package com.shuwei.dish.match.objbox
|
||||||
//
|
|
||||||
//import android.content.Context
|
import android.content.Context
|
||||||
//import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
//import android.util.Log
|
import android.util.Log
|
||||||
//import androidx.core.graphics.scale
|
import androidx.core.graphics.scale
|
||||||
//import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
//import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
//import com.shuwei.dish.match.utils.AssetsTool
|
import com.shuwei.dish.match.utils.AssetsTool
|
||||||
//import com.shuwei.dish.match.utils.LogSaveUtil
|
import com.shuwei.dish.match.utils.LogSaveUtil
|
||||||
//import com.shuwei.dish.match.utils.ext.toJsonString
|
import com.shuwei.dish.match.utils.ext.toJsonString
|
||||||
//import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
//import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
//import org.pytorch.IValue
|
import org.pytorch.IValue
|
||||||
//import org.pytorch.Module
|
import org.pytorch.Module
|
||||||
//import org.pytorch.torchvision.TensorImageUtils
|
import org.pytorch.torchvision.TensorImageUtils
|
||||||
//import java.io.File
|
import java.io.File
|
||||||
//import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
//import java.io.IOException
|
import java.io.IOException
|
||||||
//import java.io.InputStream
|
import java.io.InputStream
|
||||||
//
|
|
||||||
//
|
|
||||||
//object FoodModule {
|
object FoodModule {
|
||||||
//
|
|
||||||
// private lateinit var module_mobile: Module
|
private lateinit var module_mobile: Module
|
||||||
//
|
|
||||||
// private val NO_MEAN_RGB = floatArrayOf(0.0f, 0.0f, 0.0f)
|
private val NO_MEAN_RGB = floatArrayOf(0.0f, 0.0f, 0.0f)
|
||||||
// private val NO_STD_RGB = floatArrayOf(1.0f, 1.0f, 1.0f)
|
private val NO_STD_RGB = floatArrayOf(1.0f, 1.0f, 1.0f)
|
||||||
//
|
|
||||||
// // 1. 定义你的模型固定输入尺寸 (根据你的tflite模型修改,比如224x224)
|
// 1. 定义你的模型固定输入尺寸 (根据你的tflite模型修改,比如224x224)
|
||||||
// private const val MODEL_INPUT_WIDTH = 300
|
private const val MODEL_INPUT_WIDTH = 300
|
||||||
// private const val MODEL_INPUT_HEIGHT = 300
|
private const val MODEL_INPUT_HEIGHT = 300
|
||||||
// const val DEFAULT_FOOD_INDEX = -1
|
const val DEFAULT_FOOD_INDEX = -1
|
||||||
// const val DEFAULT_QUERY_COUNT = 50
|
const val DEFAULT_QUERY_COUNT = 50
|
||||||
//
|
|
||||||
// const val BAG_RATE = 0.05
|
const val BAG_RATE = 0.05
|
||||||
//
|
|
||||||
// suspend fun init(context: Context) {
|
suspend fun init(context: Context) {
|
||||||
// withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
// module_mobile = Module.load(copyAssetToCache(context, "best_embedding_model_mobile.pt"))
|
module_mobile = Module.load(copyAssetToCache(context, "best_embedding_model_mobile.pt"))
|
||||||
// initDefFoodData(context)
|
initDefFoodData(context)
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
//// fun uri2FloatArray(uri: Uri): FloatArray? {
|
// fun uri2FloatArray(uri: Uri): FloatArray? {
|
||||||
//// return MyApp.instance?.let { context ->
|
// return MyApp.instance?.let { context ->
|
||||||
//// ImageUtil.uriToBitmap(context, uri)?.let {
|
// ImageUtil.uriToBitmap(context, uri)?.let {
|
||||||
//// bitmap2FloatArray(it)
|
// bitmap2FloatArray(it)
|
||||||
//// }
|
|
||||||
//// }
|
|
||||||
//// }
|
|
||||||
//
|
|
||||||
// fun bitmap2FloatArray(originBitmap: Bitmap, isRecycle: Boolean): FloatArray? {
|
|
||||||
// var rgb565Bitmap: Bitmap? = null
|
|
||||||
// try {
|
|
||||||
// val scaledBitmap = originBitmap.scale(MODEL_INPUT_WIDTH, MODEL_INPUT_HEIGHT)
|
|
||||||
// if (isRecycle) {
|
|
||||||
// originBitmap.recycle()
|
|
||||||
// }
|
// }
|
||||||
// rgb565Bitmap = scaledBitmap.copy(Bitmap.Config.RGB_565, false)
|
|
||||||
// scaledBitmap.recycle()
|
|
||||||
// val inputTensor = TensorImageUtils.bitmapToFloat32Tensor(
|
|
||||||
// rgb565Bitmap,
|
|
||||||
// NO_MEAN_RGB, // [0.485, 0.456, 0.406] TORCHVISION_NORM_MEAN_RGB
|
|
||||||
// NO_STD_RGB // [0.229, 0.224, 0.225] TORCHVISION_NORM_STD_RGB
|
|
||||||
// )
|
|
||||||
// val outputTensor = module_mobile.forward(IValue.from(inputTensor)).toTensor()
|
|
||||||
// return outputTensor.dataAsFloatArray
|
|
||||||
// } catch (e: OutOfMemoryError) {
|
|
||||||
// e.printStackTrace()
|
|
||||||
// logInfo("bitmap2FloatArray异常:${e.message}")
|
|
||||||
// } finally {
|
|
||||||
// if (rgb565Bitmap != null && rgb565Bitmap.isRecycled.not()) {
|
|
||||||
// rgb565Bitmap.recycle()
|
|
||||||
// }
|
|
||||||
// //System.gc()
|
|
||||||
// //System.runFinalization()
|
|
||||||
// }
|
|
||||||
// return null
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//// fun queryFood(uri: Uri, queryCount: Int = DEFAULT_QUERY_COUNT): List<String>? {
|
|
||||||
//// return uri2FloatArray(uri)?.let {
|
|
||||||
//// queryFood(it, queryCount)
|
|
||||||
//// }
|
|
||||||
//// }
|
|
||||||
//
|
|
||||||
//// /**
|
|
||||||
//// * 返回识别物品名称列表
|
|
||||||
//// */
|
|
||||||
//// fun queryFood(bitmap: Bitmap, queryCount: Int = DEFAULT_QUERY_COUNT): List<String> {
|
|
||||||
//// val floatArray = bitmap2FloatArray(bitmap)
|
|
||||||
//// return queryFood(floatArray, queryCount)
|
|
||||||
//// }
|
|
||||||
//
|
|
||||||
//// /**
|
|
||||||
//// * 返回识别物品IdNameScore对象列表
|
|
||||||
//// */
|
|
||||||
//// fun queryFoodNameScore(bitmap: Bitmap, queryCount: Int = DEFAULT_QUERY_COUNT): List<IdNameScore> {
|
|
||||||
//// val floatArray = bitmap2FloatArray(bitmap)
|
|
||||||
//// return queryFoodNameScore(floatArray, queryCount)
|
|
||||||
//// }
|
|
||||||
//
|
|
||||||
// suspend fun queryFoodNameScore(
|
|
||||||
// floatArray: FloatArray?,
|
|
||||||
// queryCount: Int = DEFAULT_QUERY_COUNT
|
|
||||||
// ): List<IdNameScore> {
|
|
||||||
// if (floatArray == null) return emptyList()
|
|
||||||
//// val query: Query<Food> = box.query(Food_.foodVector.nearestNeighbors(floatArray, queryCount)).build()
|
|
||||||
//// //查询比较分数
|
|
||||||
////// val tempList = query.findWithScores().sortedBy { it.score }.map { "${it.get().name}|${it.get().foodIdx}|${it.score}" }
|
|
||||||
//// var idScoreList: List<IdWithScore>
|
|
||||||
//// try {
|
|
||||||
//// idScoreList = query.findIdsWithScores();
|
|
||||||
//// } finally {
|
|
||||||
//// // 先关闭Query,释放Cursor
|
|
||||||
//// query.close()
|
|
||||||
//// }
|
|
||||||
//
|
|
||||||
// logInfo("queryFood向量:${floatArray.slice(0 until 50).toJsonString()}")
|
|
||||||
// val idScoreList = ObjectBox.query(floatArray, queryCount)
|
|
||||||
// // 批量加载所有实体,一次DB操作替代循环单条查询
|
|
||||||
// val foodMap = ObjectBox.getByIds(idScoreList.map { it.id }).associateBy { it.id }
|
|
||||||
// val nameScoreList = mutableListOf<IdNameScore>()
|
|
||||||
// idScoreList.forEach {
|
|
||||||
// val name = foodMap[it.id]?.name ?: ""
|
|
||||||
// nameScoreList.add(IdNameScore(id = it.id, name = name, score = it.score))
|
|
||||||
// }
|
|
||||||
// logInfo("queryFood数据:${nameScoreList.toJsonString()}")
|
|
||||||
// return nameScoreList
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// suspend fun getFoodScoreList(bitmap: Bitmap, queryCount: Int = DEFAULT_QUERY_COUNT): List<IdNameScore> {
|
|
||||||
// val floatArray = bitmap2FloatArray(bitmap, false) ?: return emptyList()
|
|
||||||
// logInfo("getFoodScoreList向量:${floatArray.toJsonString()}")
|
|
||||||
// val nameScoreList = queryFoodNameScore(floatArray, queryCount)
|
|
||||||
// if (nameScoreList.isEmpty()) {
|
|
||||||
// return emptyList()
|
|
||||||
// }
|
|
||||||
// //暂时不过滤
|
|
||||||
// val maxScoreList = nameScoreList
|
|
||||||
// //.filter { it.score < 0.15 }
|
|
||||||
// .groupBy { it.name }
|
|
||||||
// .map { (_, value) -> value.minByOrNull { it.score }!! }
|
|
||||||
// .toMutableList()
|
|
||||||
//// val map = mutableMapOf<String, Int>()
|
|
||||||
//// nameScoreList.forEach {
|
|
||||||
//// val key = it.name
|
|
||||||
//// val count = map[key] ?: 0
|
|
||||||
//// map[key] = count + 1
|
|
||||||
//// }
|
|
||||||
//// val orderList = map.entries.sortedByDescending { it.value }.map { it.key }.toMutableList()
|
|
||||||
//// val firstFood = nameScoreList[0].name
|
|
||||||
//// orderList.remove(firstFood)
|
|
||||||
//// orderList.add(0, firstFood)
|
|
||||||
////
|
|
||||||
//// val sortedScoreList = maxScoreList.sortedWith(compareBy {
|
|
||||||
//// orderList.indexOf(it.name)
|
|
||||||
//// })
|
|
||||||
// val sortedScoreList = maxScoreList.sortedBy { it.score }
|
|
||||||
// logInfo("getFoodScoreList数据:${sortedScoreList.toJsonString()}")
|
|
||||||
// return sortedScoreList
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//// suspend fun queryFood(floatArray: FloatArray, queryCount: Int = DEFAULT_QUERY_COUNT): List<String> {
|
|
||||||
//// val map = mutableMapOf<String, Int>()
|
|
||||||
//// val nameScoreList = queryFoodNameScore(floatArray, queryCount)
|
|
||||||
//// nameScoreList.filter { it.score < 0.05 }.forEach {
|
|
||||||
//// val count = map[it.name] ?: 0
|
|
||||||
//// map[it.name] = count + 1
|
|
||||||
//// }
|
|
||||||
//// val list = map.entries.sortedByDescending { it.value }.map { it.key }
|
|
||||||
//// return list
|
|
||||||
//// }
|
|
||||||
//
|
|
||||||
// data class IdNameScore(
|
|
||||||
// val id: Long,
|
|
||||||
// var name: String,
|
|
||||||
// val score: Double
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// suspend fun initDefFoodData(context: Context, action: () -> Unit = {}) {
|
|
||||||
// try {
|
|
||||||
// val count = ObjectBox.countIndexField(DEFAULT_FOOD_INDEX)
|
|
||||||
// if (count > 0) {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// val embeddingsJson = AssetsTool.readAssetsFile(context, "data/embeddings.json")
|
|
||||||
// val labelsJson = AssetsTool.readAssetsFile(context, "data/labels.json")
|
|
||||||
// val classInfoJson = AssetsTool.readAssetsFile(context, "data/class_info.json")
|
|
||||||
//
|
|
||||||
// val embeddingsList: List<List<Float>> =
|
|
||||||
// Gson().fromJson(embeddingsJson, object : TypeToken<List<List<Float>>>() {}.type)
|
|
||||||
// val labelsList: IntArray = Gson().fromJson(labelsJson, IntArray::class.java)
|
|
||||||
// val classInfo: FoodClassInfo =
|
|
||||||
// Gson().fromJson(classInfoJson, FoodClassInfo::class.java)
|
|
||||||
//
|
|
||||||
// val foodMap = classInfo.idx_to_class
|
|
||||||
// val list = mutableListOf<Food>()
|
|
||||||
// val size = embeddingsList.size
|
|
||||||
// val size2 = labelsList.size
|
|
||||||
// val size3 = foodMap.size
|
|
||||||
// logInfo("initDefFoodData: $size,$size2,$size3")
|
|
||||||
// embeddingsList.forEachIndexed { index, floatList ->
|
|
||||||
// val classIdx = labelsList[index]
|
|
||||||
// val foodName = foodMap["$classIdx"]
|
|
||||||
// val array = floatList.toFloatArray()
|
|
||||||
// // ObjectBox.boxStore.runInTx {
|
|
||||||
// // box.put(Food(name = foodName, foodVector = array, foodIdx = DEFAULT_FOOD_INDEX))
|
|
||||||
// // }
|
|
||||||
// val food = Food(name = foodName, foodVector = array, foodIdx = DEFAULT_FOOD_INDEX)
|
|
||||||
// list.add(food)
|
|
||||||
// }
|
|
||||||
//// val tempList = list.chunked(10)
|
|
||||||
// ObjectBox.putAll(list)
|
|
||||||
// action()
|
|
||||||
// } catch (e: Exception) {
|
|
||||||
// e.printStackTrace()
|
|
||||||
// logInfo("initDefFoodData: ---${e.message}--")
|
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//
|
|
||||||
//
|
fun bitmap2FloatArray(originBitmap: Bitmap, isRecycle: Boolean): FloatArray? {
|
||||||
|
var rgb565Bitmap: Bitmap? = null
|
||||||
|
try {
|
||||||
|
val scaledBitmap = originBitmap.scale(MODEL_INPUT_WIDTH, MODEL_INPUT_HEIGHT)
|
||||||
|
if (isRecycle) {
|
||||||
|
originBitmap.recycle()
|
||||||
|
}
|
||||||
|
rgb565Bitmap = scaledBitmap.copy(Bitmap.Config.RGB_565, false)
|
||||||
|
scaledBitmap.recycle()
|
||||||
|
val inputTensor = TensorImageUtils.bitmapToFloat32Tensor(
|
||||||
|
rgb565Bitmap,
|
||||||
|
NO_MEAN_RGB, // [0.485, 0.456, 0.406] TORCHVISION_NORM_MEAN_RGB
|
||||||
|
NO_STD_RGB // [0.229, 0.224, 0.225] TORCHVISION_NORM_STD_RGB
|
||||||
|
)
|
||||||
|
val outputTensor = module_mobile.forward(IValue.from(inputTensor)).toTensor()
|
||||||
|
return outputTensor.dataAsFloatArray
|
||||||
|
} catch (e: OutOfMemoryError) {
|
||||||
|
e.printStackTrace()
|
||||||
|
logInfo("bitmap2FloatArray异常:${e.message}")
|
||||||
|
} finally {
|
||||||
|
if (rgb565Bitmap != null && rgb565Bitmap.isRecycled.not()) {
|
||||||
|
rgb565Bitmap.recycle()
|
||||||
|
}
|
||||||
|
//System.gc()
|
||||||
|
//System.runFinalization()
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// fun queryFood(uri: Uri, queryCount: Int = DEFAULT_QUERY_COUNT): List<String>? {
|
||||||
|
// return uri2FloatArray(uri)?.let {
|
||||||
|
// queryFood(it, queryCount)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// /**
|
// /**
|
||||||
// * ,此方法的主要目的是:从assets 拷贝到 app的cache目录
|
// * 返回识别物品名称列表
|
||||||
// * @param context
|
|
||||||
// * @param fileName
|
|
||||||
// * @return 例如是这样:/data/user/0/com.frizzle.pluginhookandroid9/cache/plugin-debug.apk
|
|
||||||
// *
|
|
||||||
// * 不可能反正SD
|
|
||||||
// */
|
// */
|
||||||
//// fun copyAssetToCache(context: Context, fileName: String): String? {
|
// fun queryFood(bitmap: Bitmap, queryCount: Int = DEFAULT_QUERY_COUNT): List<String> {
|
||||||
//// // 此app的缓存目录 --> 会默认在 cache目录...,可以自己去看看哦
|
// val floatArray = bitmap2FloatArray(bitmap)
|
||||||
//// val cacheDir = context.getCacheDir()
|
// return queryFood(floatArray, queryCount)
|
||||||
//// if (!cacheDir.exists()) {
|
// }
|
||||||
//// cacheDir.mkdirs() // TODO 如果没有缓存目录,就创建
|
|
||||||
//// }
|
// /**
|
||||||
//// val outPath = File(cacheDir, fileName) // TODO 创建输出的文件位置
|
// * 返回识别物品IdNameScore对象列表
|
||||||
//// if (outPath.exists()) {
|
// */
|
||||||
//// outPath.delete() // TODO 如果该文件已经存在,就删掉
|
// fun queryFoodNameScore(bitmap: Bitmap, queryCount: Int = DEFAULT_QUERY_COUNT): List<IdNameScore> {
|
||||||
//// }
|
// val floatArray = bitmap2FloatArray(bitmap)
|
||||||
//// var `is`: InputStream? = null // 读取
|
// return queryFoodNameScore(floatArray, queryCount)
|
||||||
//// var fos: FileOutputStream? = null // 写入
|
// }
|
||||||
//// try {
|
|
||||||
//// // 创建文件,如果创建成功,就返回true
|
suspend fun queryFoodNameScore(
|
||||||
//// val res = outPath.createNewFile()
|
floatArray: FloatArray?,
|
||||||
//// if (res) {
|
queryCount: Int = DEFAULT_QUERY_COUNT
|
||||||
//// `is` = context.getAssets().open(fileName) // 拿到main/assets目录的输入流,用于读取字节
|
): List<IdNameScore> {
|
||||||
//// fos = FileOutputStream(outPath) // 读取出来的字节最终写到outPath
|
if (floatArray == null) return emptyList()
|
||||||
//// val buf = ByteArray(`is`.available()) // 缓存区
|
// val query: Query<Food> = box.query(Food_.foodVector.nearestNeighbors(floatArray, queryCount)).build()
|
||||||
//// var byteCount: Int
|
// //查询比较分数
|
||||||
////
|
//// val tempList = query.findWithScores().sortedBy { it.score }.map { "${it.get().name}|${it.get().foodIdx}|${it.score}" }
|
||||||
//// // 开始循环读取
|
// var idScoreList: List<IdWithScore>
|
||||||
//// while ((`is`.read(buf).also { byteCount = it }) != -1) {
|
|
||||||
//// fos.write(buf, 0, byteCount)
|
|
||||||
//// }
|
|
||||||
//// return outPath.getAbsolutePath()
|
|
||||||
//// }
|
|
||||||
//// } catch (e: IOException) {
|
|
||||||
//// e.printStackTrace()
|
|
||||||
//// } finally {
|
|
||||||
//// try {
|
|
||||||
//// // TODO 一定要记得关闭资源,为了不去性能的磨损
|
|
||||||
//// fos?.flush()
|
|
||||||
//// `is`?.close()
|
|
||||||
//// fos?.close()
|
|
||||||
//// } catch (e: IOException) {
|
|
||||||
//// e.printStackTrace()
|
|
||||||
//// }
|
|
||||||
//// }
|
|
||||||
//// return null
|
|
||||||
//// }
|
|
||||||
//
|
|
||||||
// fun copyAssetToCache(context: Context, fileName: String): String? {
|
|
||||||
// val cacheFile = File(context.cacheDir, fileName)
|
|
||||||
// val buffer = ByteArray(8 * 1024)
|
|
||||||
// var inputStream: InputStream? = null
|
|
||||||
// var outputStream: FileOutputStream? = null
|
|
||||||
//
|
|
||||||
// try {
|
// try {
|
||||||
// inputStream = context.assets.open(fileName)
|
// idScoreList = query.findIdsWithScores();
|
||||||
// outputStream = FileOutputStream(cacheFile)
|
// } finally {
|
||||||
// var byteCount: Int
|
// // 先关闭Query,释放Cursor
|
||||||
// while (inputStream.read(buffer).also { byteCount = it } != -1) {
|
// query.close()
|
||||||
// outputStream.write(buffer, 0, byteCount)
|
// }
|
||||||
|
|
||||||
|
logInfo("queryFood向量:${floatArray.slice(0 until 50).toJsonString()}")
|
||||||
|
val idScoreList = ObjectBox.query(floatArray, queryCount)
|
||||||
|
// 批量加载所有实体,一次DB操作替代循环单条查询
|
||||||
|
val foodMap = ObjectBox.getByIds(idScoreList.map { it.id }).associateBy { it.id }
|
||||||
|
val nameScoreList = mutableListOf<IdNameScore>()
|
||||||
|
idScoreList.forEach {
|
||||||
|
val name = foodMap[it.id]?.name ?: ""
|
||||||
|
nameScoreList.add(IdNameScore(id = it.id, name = name, score = it.score))
|
||||||
|
}
|
||||||
|
logInfo("queryFood数据:${nameScoreList.toJsonString()}")
|
||||||
|
return nameScoreList
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getFoodScoreList(bitmap: Bitmap, queryCount: Int = DEFAULT_QUERY_COUNT): List<IdNameScore> {
|
||||||
|
val floatArray = bitmap2FloatArray(bitmap, false) ?: return emptyList()
|
||||||
|
logInfo("getFoodScoreList向量:${floatArray.toJsonString()}")
|
||||||
|
val nameScoreList = queryFoodNameScore(floatArray, queryCount)
|
||||||
|
if (nameScoreList.isEmpty()) {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
//暂时不过滤
|
||||||
|
val maxScoreList = nameScoreList
|
||||||
|
//.filter { it.score < 0.15 }
|
||||||
|
.groupBy { it.name }
|
||||||
|
.map { (_, value) -> value.minByOrNull { it.score }!! }
|
||||||
|
.toMutableList()
|
||||||
|
// val map = mutableMapOf<String, Int>()
|
||||||
|
// nameScoreList.forEach {
|
||||||
|
// val key = it.name
|
||||||
|
// val count = map[key] ?: 0
|
||||||
|
// map[key] = count + 1
|
||||||
|
// }
|
||||||
|
// val orderList = map.entries.sortedByDescending { it.value }.map { it.key }.toMutableList()
|
||||||
|
// val firstFood = nameScoreList[0].name
|
||||||
|
// orderList.remove(firstFood)
|
||||||
|
// orderList.add(0, firstFood)
|
||||||
|
//
|
||||||
|
// val sortedScoreList = maxScoreList.sortedWith(compareBy {
|
||||||
|
// orderList.indexOf(it.name)
|
||||||
|
// })
|
||||||
|
val sortedScoreList = maxScoreList.sortedBy { it.score }
|
||||||
|
logInfo("getFoodScoreList数据:${sortedScoreList.toJsonString()}")
|
||||||
|
return sortedScoreList
|
||||||
|
}
|
||||||
|
|
||||||
|
// suspend fun queryFood(floatArray: FloatArray, queryCount: Int = DEFAULT_QUERY_COUNT): List<String> {
|
||||||
|
// val map = mutableMapOf<String, Int>()
|
||||||
|
// val nameScoreList = queryFoodNameScore(floatArray, queryCount)
|
||||||
|
// nameScoreList.filter { it.score < 0.05 }.forEach {
|
||||||
|
// val count = map[it.name] ?: 0
|
||||||
|
// map[it.name] = count + 1
|
||||||
|
// }
|
||||||
|
// val list = map.entries.sortedByDescending { it.value }.map { it.key }
|
||||||
|
// return list
|
||||||
|
// }
|
||||||
|
|
||||||
|
data class IdNameScore(
|
||||||
|
val id: Long,
|
||||||
|
var name: String,
|
||||||
|
val score: Double
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun initDefFoodData(context: Context, action: () -> Unit = {}) {
|
||||||
|
try {
|
||||||
|
val count = ObjectBox.countIndexField(DEFAULT_FOOD_INDEX)
|
||||||
|
if (count > 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val embeddingsJson = AssetsTool.readAssetsFile(context, "data/embeddings.json")
|
||||||
|
val labelsJson = AssetsTool.readAssetsFile(context, "data/labels.json")
|
||||||
|
val classInfoJson = AssetsTool.readAssetsFile(context, "data/class_info.json")
|
||||||
|
|
||||||
|
val embeddingsList: List<List<Float>> =
|
||||||
|
Gson().fromJson(embeddingsJson, object : TypeToken<List<List<Float>>>() {}.type)
|
||||||
|
val labelsList: IntArray = Gson().fromJson(labelsJson, IntArray::class.java)
|
||||||
|
val classInfo: FoodClassInfo =
|
||||||
|
Gson().fromJson(classInfoJson, FoodClassInfo::class.java)
|
||||||
|
|
||||||
|
val foodMap = classInfo.idx_to_class
|
||||||
|
val list = mutableListOf<Food>()
|
||||||
|
val size = embeddingsList.size
|
||||||
|
val size2 = labelsList.size
|
||||||
|
val size3 = foodMap.size
|
||||||
|
logInfo("initDefFoodData: $size,$size2,$size3")
|
||||||
|
embeddingsList.forEachIndexed { index, floatList ->
|
||||||
|
val classIdx = labelsList[index]
|
||||||
|
val foodName = foodMap["$classIdx"]
|
||||||
|
val array = floatList.toFloatArray()
|
||||||
|
// ObjectBox.boxStore.runInTx {
|
||||||
|
// box.put(Food(name = foodName, foodVector = array, foodIdx = DEFAULT_FOOD_INDEX))
|
||||||
|
// }
|
||||||
|
val food = Food(name = foodName, foodVector = array, foodIdx = DEFAULT_FOOD_INDEX)
|
||||||
|
list.add(food)
|
||||||
|
}
|
||||||
|
// val tempList = list.chunked(10)
|
||||||
|
ObjectBox.putAll(list)
|
||||||
|
action()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
logInfo("initDefFoodData: ---${e.message}--")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ,此方法的主要目的是:从assets 拷贝到 app的cache目录
|
||||||
|
* @param context
|
||||||
|
* @param fileName
|
||||||
|
* @return 例如是这样:/data/user/0/com.frizzle.pluginhookandroid9/cache/plugin-debug.apk
|
||||||
|
*
|
||||||
|
* 不可能反正SD
|
||||||
|
*/
|
||||||
|
// fun copyAssetToCache(context: Context, fileName: String): String? {
|
||||||
|
// // 此app的缓存目录 --> 会默认在 cache目录...,可以自己去看看哦
|
||||||
|
// val cacheDir = context.getCacheDir()
|
||||||
|
// if (!cacheDir.exists()) {
|
||||||
|
// cacheDir.mkdirs() // TODO 如果没有缓存目录,就创建
|
||||||
|
// }
|
||||||
|
// val outPath = File(cacheDir, fileName) // TODO 创建输出的文件位置
|
||||||
|
// if (outPath.exists()) {
|
||||||
|
// outPath.delete() // TODO 如果该文件已经存在,就删掉
|
||||||
|
// }
|
||||||
|
// var `is`: InputStream? = null // 读取
|
||||||
|
// var fos: FileOutputStream? = null // 写入
|
||||||
|
// try {
|
||||||
|
// // 创建文件,如果创建成功,就返回true
|
||||||
|
// val res = outPath.createNewFile()
|
||||||
|
// if (res) {
|
||||||
|
// `is` = context.getAssets().open(fileName) // 拿到main/assets目录的输入流,用于读取字节
|
||||||
|
// fos = FileOutputStream(outPath) // 读取出来的字节最终写到outPath
|
||||||
|
// val buf = ByteArray(`is`.available()) // 缓存区
|
||||||
|
// var byteCount: Int
|
||||||
|
//
|
||||||
|
// // 开始循环读取
|
||||||
|
// while ((`is`.read(buf).also { byteCount = it }) != -1) {
|
||||||
|
// fos.write(buf, 0, byteCount)
|
||||||
|
// }
|
||||||
|
// return outPath.getAbsolutePath()
|
||||||
// }
|
// }
|
||||||
// outputStream.channel.force(true) // 强制物理落盘,比flush更彻底
|
|
||||||
// return cacheFile.absolutePath
|
|
||||||
// } catch (e: IOException) {
|
// } catch (e: IOException) {
|
||||||
// e.printStackTrace()
|
// e.printStackTrace()
|
||||||
// logInfo("文件拷贝失败 fileName=$fileName, error=${e.message}")
|
|
||||||
// // 拷贝失败时删除残缺文件,避免下次读取到损坏文件
|
|
||||||
// if (cacheFile.exists()) {
|
|
||||||
// cacheFile.delete()
|
|
||||||
// }
|
|
||||||
// } finally {
|
// } finally {
|
||||||
// outputStream?.close()
|
// try {
|
||||||
// inputStream?.close()
|
// // TODO 一定要记得关闭资源,为了不去性能的磨损
|
||||||
|
// fos?.flush()
|
||||||
|
// `is`?.close()
|
||||||
|
// fos?.close()
|
||||||
|
// } catch (e: IOException) {
|
||||||
|
// e.printStackTrace()
|
||||||
|
// }
|
||||||
// }
|
// }
|
||||||
// return null
|
// return null
|
||||||
// }
|
// }
|
||||||
//
|
|
||||||
// private fun logInfo(msg:String) {
|
fun copyAssetToCache(context: Context, fileName: String): String? {
|
||||||
// Log.d("FoodModule", msg)
|
val cacheFile = File(context.cacheDir, fileName)
|
||||||
// LogSaveUtil.saveLogFile(msg)
|
val buffer = ByteArray(8 * 1024)
|
||||||
// }
|
var inputStream: InputStream? = null
|
||||||
//
|
var outputStream: FileOutputStream? = null
|
||||||
//}
|
|
||||||
|
try {
|
||||||
|
inputStream = context.assets.open(fileName)
|
||||||
|
outputStream = FileOutputStream(cacheFile)
|
||||||
|
var byteCount: Int
|
||||||
|
while (inputStream.read(buffer).also { byteCount = it } != -1) {
|
||||||
|
outputStream.write(buffer, 0, byteCount)
|
||||||
|
}
|
||||||
|
outputStream.channel.force(true) // 强制物理落盘,比flush更彻底
|
||||||
|
return cacheFile.absolutePath
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
logInfo("文件拷贝失败 fileName=$fileName, error=${e.message}")
|
||||||
|
// 拷贝失败时删除残缺文件,避免下次读取到损坏文件
|
||||||
|
if (cacheFile.exists()) {
|
||||||
|
cacheFile.delete()
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
outputStream?.close()
|
||||||
|
inputStream?.close()
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun logInfo(msg:String) {
|
||||||
|
Log.d("FoodModule", msg)
|
||||||
|
LogSaveUtil.saveLogFile(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@ package com.shuwei.dish.match.ui
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.util.Log
|
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
@@ -18,12 +17,15 @@ import com.shuwei.dish.match.base.DeviceRole
|
|||||||
import com.shuwei.dish.match.base.GlobalData
|
import com.shuwei.dish.match.base.GlobalData
|
||||||
import com.shuwei.dish.match.databinding.ActivityInitBinding
|
import com.shuwei.dish.match.databinding.ActivityInitBinding
|
||||||
import com.shuwei.dish.match.db.AppRepository
|
import com.shuwei.dish.match.db.AppRepository
|
||||||
|
import com.shuwei.dish.match.objbox.FoodModule
|
||||||
import com.shuwei.dish.match.utils.AppUtil
|
import com.shuwei.dish.match.utils.AppUtil
|
||||||
import com.shuwei.dish.match.utils.NetworkUtils
|
import com.shuwei.dish.match.utils.NetworkUtils
|
||||||
import com.shuwei.dish.match.utils.SpTool
|
import com.shuwei.dish.match.utils.SpTool
|
||||||
import com.shuwei.dish.match.utils.Weigher2
|
import com.shuwei.dish.match.utils.Weigher2
|
||||||
import com.shuwei.dish.match.utils.WeightUtil
|
import com.shuwei.dish.match.utils.WeightUtil
|
||||||
|
import com.shuwei.dish.match.utils.ext.gone
|
||||||
import com.shuwei.dish.match.utils.ext.startActivity
|
import com.shuwei.dish.match.utils.ext.startActivity
|
||||||
|
import com.shuwei.dish.match.utils.ext.visible
|
||||||
import com.shuwei.dish.match.viewmodel.AppViewModel
|
import com.shuwei.dish.match.viewmodel.AppViewModel
|
||||||
import com.shuwei.dish.match.viewmodel.factory.AppFactory
|
import com.shuwei.dish.match.viewmodel.factory.AppFactory
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@@ -47,9 +49,9 @@ class InitActivity : BaseActivity() {
|
|||||||
// useBackground(false)
|
// useBackground(false)
|
||||||
binding = ActivityInitBinding.inflate(layoutInflater)
|
binding = ActivityInitBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
// lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
// FoodModule.init(this@InitActivity)
|
FoodModule.init(this@InitActivity)
|
||||||
// }
|
}
|
||||||
// setHeaderBackground(isHomePage = true)
|
// setHeaderBackground(isHomePage = true)
|
||||||
setHeaderBgVisible(false)
|
setHeaderBgVisible(false)
|
||||||
BaseApp.appVersion = AppUtil.getAppVersionCode(this).toString()
|
BaseApp.appVersion = AppUtil.getAppVersionCode(this).toString()
|
||||||
@@ -63,9 +65,6 @@ class InitActivity : BaseActivity() {
|
|||||||
WeightUtil.startContinuousRead()
|
WeightUtil.startContinuousRead()
|
||||||
|
|
||||||
initViews()
|
initViews()
|
||||||
val dpi = resources.displayMetrics.density
|
|
||||||
val metrics = resources.displayMetrics.toString()
|
|
||||||
Log.d(TAG, "dpi=$dpi, $metrics")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initViewModel() {
|
private fun initViewModel() {
|
||||||
@@ -74,9 +73,9 @@ class InitActivity : BaseActivity() {
|
|||||||
appViewModel = ViewModelProvider(this, factory)[AppViewModel::class.java]
|
appViewModel = ViewModelProvider(this, factory)[AppViewModel::class.java]
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startNextPage() {
|
private fun startNextPage(withDelay: Boolean = false) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
delay(2000)
|
if (withDelay) delay(2000)
|
||||||
// 子设备:直接进入小屏专属页面,不走原有大屏业务流程
|
// 子设备:直接进入小屏专属页面,不走原有大屏业务流程
|
||||||
if (GlobalData.deviceRole == DeviceRole.SLAVE) {
|
if (GlobalData.deviceRole == DeviceRole.SLAVE) {
|
||||||
startActivity<SlaveActivity>()
|
startActivity<SlaveActivity>()
|
||||||
@@ -135,9 +134,6 @@ class InitActivity : BaseActivity() {
|
|||||||
binding.btnConnectNetwork.setOnClickListener {
|
binding.btnConnectNetwork.setOnClickListener {
|
||||||
openNetworkSettings()
|
openNetworkSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 启动加载动画 - 持续旋转
|
|
||||||
startLoadingAnimation()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -159,9 +155,12 @@ class InitActivity : BaseActivity() {
|
|||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
if (NetworkUtils.isNetworkConnected(this)) {
|
if (NetworkUtils.isNetworkConnected(this)) {
|
||||||
startNextPage()
|
// 有网络时隐藏网络检测区域,直接跳转
|
||||||
|
binding.llNetwork.gone()
|
||||||
|
startNextPage(withDelay = false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
binding.llNetwork.visible()
|
||||||
startCountdown()
|
startCountdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,9 +177,11 @@ class InitActivity : BaseActivity() {
|
|||||||
startTime = SystemClock.elapsedRealtime()
|
startTime = SystemClock.elapsedRealtime()
|
||||||
lastNetworkCheckTime = 0
|
lastNetworkCheckTime = 0
|
||||||
// binding.llNetwork.setBackgroundColor(Color.TRANSPARENT)
|
// binding.llNetwork.setBackgroundColor(Color.TRANSPARENT)
|
||||||
binding.llLoading.visibility = android.view.View.VISIBLE
|
binding.llLoading.visible()
|
||||||
binding.llNetworkButton.visibility = android.view.View.GONE
|
binding.llNetworkButton.gone()
|
||||||
binding.tvCountdown.text = "60秒"
|
binding.tvCountdown.text = "60秒"
|
||||||
|
// 无网络时才启动加载动画
|
||||||
|
startLoadingAnimation()
|
||||||
scheduleCountdown()
|
scheduleCountdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,8 +234,8 @@ class InitActivity : BaseActivity() {
|
|||||||
*/
|
*/
|
||||||
private fun showNetworkButton() {
|
private fun showNetworkButton() {
|
||||||
// binding.llNetwork.setBackgroundColor("#5C77F7".toColorInt())
|
// binding.llNetwork.setBackgroundColor("#5C77F7".toColorInt())
|
||||||
binding.llLoading.visibility = android.view.View.GONE
|
binding.llLoading.gone()
|
||||||
binding.llNetworkButton.visibility = android.view.View.VISIBLE
|
binding.llNetworkButton.visible()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ class MasterScaleActivity : BaseActivity() {
|
|||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
setHeaderBackground()
|
setHeaderBackground()
|
||||||
|
|
||||||
|
binding.ivMasterBack.setOnClickListener { finish() }
|
||||||
binding.rvScaleList.layoutManager = LinearLayoutManager(this)
|
binding.rvScaleList.layoutManager = LinearLayoutManager(this)
|
||||||
binding.rvScaleList.itemAnimator = null // 关闭默认动画,避免数据频繁更新时出现闪烁错乱
|
binding.rvScaleList.itemAnimator = null // 关闭默认动画,避免数据频繁更新时出现闪烁错乱
|
||||||
binding.rvScaleList.adapter = adapter
|
binding.rvScaleList.adapter = adapter
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
package com.shuwei.dish.match.ui
|
package com.shuwei.dish.match.ui
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.activity.viewModels
|
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.shuwei.dish.match.R
|
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.base.BaseActivity
|
||||||
import com.shuwei.dish.match.databinding.ActivityPrepareCookBinding
|
import com.shuwei.dish.match.databinding.ActivityPrepareCookBinding
|
||||||
import com.shuwei.dish.match.databinding.LayoutFoodRemindBinding
|
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.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.CookFoodEntity
|
||||||
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
|
import com.shuwei.dish.match.entity.CookFoodGoodsEntity
|
||||||
import com.shuwei.dish.match.entity.FoodRecord
|
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.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.SwipeCallback
|
||||||
import com.shuwei.dish.match.utils.WeightUtil
|
import com.shuwei.dish.match.utils.WeightUtil
|
||||||
import com.shuwei.dish.match.utils.ext.clickWithDebounce
|
import com.shuwei.dish.match.utils.ext.clickWithDebounce
|
||||||
import com.shuwei.dish.match.utils.ext.gone
|
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.startActivity
|
||||||
import com.shuwei.dish.match.utils.ext.toJsonString
|
import com.shuwei.dish.match.utils.ext.toJsonString
|
||||||
import com.shuwei.dish.match.utils.ext.toast
|
import com.shuwei.dish.match.utils.ext.toast
|
||||||
import com.shuwei.dish.match.utils.ext.visible
|
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 java.io.Serializable
|
||||||
import kotlin.getValue
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
class PrepareCookActivity : BaseActivity() {
|
class PrepareCookActivity : BaseActivity() {
|
||||||
@@ -40,6 +52,8 @@ class PrepareCookActivity : BaseActivity() {
|
|||||||
|
|
||||||
private lateinit var binding: ActivityPrepareCookBinding
|
private lateinit var binding: ActivityPrepareCookBinding
|
||||||
|
|
||||||
|
private val cameraUtils: CameraUtils by lazy { CameraUtils(this) }
|
||||||
|
|
||||||
private var food: FoodRecord? = null
|
private var food: FoodRecord? = null
|
||||||
|
|
||||||
private var goodsList: MutableList<CookFoodGoodsEntity>? = null
|
private var goodsList: MutableList<CookFoodGoodsEntity>? = null
|
||||||
@@ -69,6 +83,7 @@ class PrepareCookActivity : BaseActivity() {
|
|||||||
binding.tvDishName.text = food?.foodName ?: ""
|
binding.tvDishName.text = food?.foodName ?: ""
|
||||||
addViewClickListener()
|
addViewClickListener()
|
||||||
|
|
||||||
|
initCamera(binding.flCameraContainer)
|
||||||
initRecyclerView()
|
initRecyclerView()
|
||||||
|
|
||||||
getDishDetail()
|
getDishDetail()
|
||||||
@@ -81,6 +96,9 @@ class PrepareCookActivity : BaseActivity() {
|
|||||||
//Log.d(TAG, "addViewClickListener: address=$address,state=$state,weight=$weight")
|
//Log.d(TAG, "addViewClickListener: address=$address,state=$state,weight=$weight")
|
||||||
if (address == AddressUtil.ONE) {
|
if (address == AddressUtil.ONE) {
|
||||||
binding.tvDishPartWeight.text = "${weight}"
|
binding.tvDishPartWeight.text = "${weight}"
|
||||||
|
if (state == WeightUtil.STATE_STABLE) {
|
||||||
|
recognizeFood(weight)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
binding.ivWeightClear.setOnClickListener {
|
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() {
|
private fun addFood() {
|
||||||
FoodSearchDialog().show(this) { item ->
|
FoodSearchDialog().show(this) { item ->
|
||||||
val filterResult = list.firstOrNull { it.goodsId == item.goodsId }
|
val filterResult = list.firstOrNull { it.goodsId == item.goodsId }
|
||||||
@@ -251,6 +288,16 @@ class PrepareCookActivity : BaseActivity() {
|
|||||||
|
|
||||||
private var firstReqSize = 0
|
private var firstReqSize = 0
|
||||||
private val list = mutableListOf<CookFoodGoodsEntity>()
|
private val list = mutableListOf<CookFoodGoodsEntity>()
|
||||||
|
private val recognizeDialog by lazy {
|
||||||
|
FoodRecognizeDialog(this) { item ->
|
||||||
|
// TODO: 处理识别结果选中
|
||||||
|
}
|
||||||
|
.apply {
|
||||||
|
setOnDismissListener {
|
||||||
|
isTakingPhoto.set(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
private val dishPartAdapter by lazy {
|
private val dishPartAdapter by lazy {
|
||||||
DishPartAdapter(list).apply {
|
DishPartAdapter(list).apply {
|
||||||
setOnItemClickListener { _, _, positon ->
|
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")
|
@Suppress("DEPRECATION")
|
||||||
@SuppressLint("GestureBackNavigation")
|
@SuppressLint("GestureBackNavigation")
|
||||||
@Deprecated("Deprecated in Java")
|
@Deprecated("Deprecated in Java")
|
||||||
@@ -367,4 +434,49 @@ class PrepareCookActivity : BaseActivity() {
|
|||||||
}.show()
|
}.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<FoodModule.IdNameScore>) {
|
||||||
|
Log.d(TAG, "takePhoto queryFood, list=${list.toString()}")
|
||||||
|
//调用接口成功,返回食材列表设置isTakingPhoto.set(false),暂时写死数据
|
||||||
|
val foodList = mutableListOf<CookFoodGoodsEntity>()
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -82,18 +82,19 @@ class SelectDishActivity : BaseActivity() {
|
|||||||
private var configFinished = false
|
private var configFinished = false
|
||||||
|
|
||||||
fun judgeDeviceConfig(block: () -> Unit) {
|
fun judgeDeviceConfig(block: () -> Unit) {
|
||||||
if (configFinished) {
|
// if (configFinished) {
|
||||||
block()
|
// block()
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
appViewModel.loadSeasoning {
|
// appViewModel.loadSeasoning {
|
||||||
if (it.isEmpty()) {
|
// if (it.isEmpty()) {
|
||||||
showDeviceConfigDialog()
|
// showDeviceConfigDialog()
|
||||||
return@loadSeasoning
|
// return@loadSeasoning
|
||||||
}
|
// }
|
||||||
configFinished = true
|
// configFinished = true
|
||||||
block()
|
// block()
|
||||||
}
|
// }
|
||||||
|
block()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showDeviceConfigDialog() {
|
private fun showDeviceConfigDialog() {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import com.shuwei.dish.match.base.BaseFragment
|
|||||||
import com.shuwei.dish.match.databinding.FragmentCollectBinding
|
import com.shuwei.dish.match.databinding.FragmentCollectBinding
|
||||||
import com.shuwei.dish.match.dialog.Loading
|
import com.shuwei.dish.match.dialog.Loading
|
||||||
import com.shuwei.dish.match.objbox.FoodCollectionBean
|
import com.shuwei.dish.match.objbox.FoodCollectionBean
|
||||||
|
import com.shuwei.dish.match.objbox.FoodModule
|
||||||
import com.shuwei.dish.match.ui.SettingActivity
|
import com.shuwei.dish.match.ui.SettingActivity
|
||||||
import com.shuwei.dish.match.utils.BitmapSaver
|
import com.shuwei.dish.match.utils.BitmapSaver
|
||||||
import com.shuwei.dish.match.utils.Debouncer
|
import com.shuwei.dish.match.utils.Debouncer
|
||||||
@@ -119,38 +120,38 @@ class CollectFragment : BaseFragment<FragmentCollectBinding>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getImageVector(index: Int, bitmap: Bitmap) {
|
private fun getImageVector(index: Int, bitmap: Bitmap) {
|
||||||
//// val bitmap = BitmapCropper.cropCenter(
|
// val bitmap = BitmapCropper.cropCenter(
|
||||||
//// original = srcBmp,
|
// original = srcBmp,
|
||||||
//// targetWidth = 900, targetHeight = 900,
|
// targetWidth = 900, targetHeight = 900,
|
||||||
////// offsetX = 30, offsetY = 100
|
//// offsetX = 30, offsetY = 100
|
||||||
//// )
|
|
||||||
// val imageVector = try {
|
|
||||||
// FoodModule.bitmap2FloatArray(bitmap, false)
|
|
||||||
// } catch (e: Exception) {
|
|
||||||
// e.printStackTrace()
|
|
||||||
// toast("操作失败")
|
|
||||||
// log("操作失败:${e.message}")
|
|
||||||
// hideWaitingDialog()
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// val file = BitmapSaver.saveToAppFilesDir(
|
|
||||||
// bitmap, requireActivity(), "IMG_CROP_${System.currentTimeMillis()}.jpg"
|
|
||||||
// )
|
// )
|
||||||
// log("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${file?.absolutePath}")
|
val imageVector = try {
|
||||||
//
|
FoodModule.bitmap2FloatArray(bitmap, false)
|
||||||
// activity?.runOnUiThread {
|
} catch (e: Exception) {
|
||||||
// foodCollectionList[index].let {
|
e.printStackTrace()
|
||||||
// it.imageVector = imageVector
|
toast("操作失败")
|
||||||
// it.bitmap = null
|
log("操作失败:${e.message}")
|
||||||
// it.isShowCamera = false
|
hideWaitingDialog()
|
||||||
// it.imageFile = file
|
return
|
||||||
// }
|
}
|
||||||
// collectionAdapter.notifyItemChanged(index)
|
val file = BitmapSaver.saveToAppFilesDir(
|
||||||
// }
|
bitmap, requireActivity(), "IMG_CROP_${System.currentTimeMillis()}.jpg"
|
||||||
// if (bitmap.isRecycled.not()) {
|
)
|
||||||
// bitmap.recycle()
|
log("${this.javaClass.simpleName}-cameraCallback-裁剪bitmap保存文件路径:${file?.absolutePath}")
|
||||||
// }
|
|
||||||
// hideWaitingDialog()
|
activity?.runOnUiThread {
|
||||||
|
foodCollectionList[index].let {
|
||||||
|
it.imageVector = imageVector
|
||||||
|
it.bitmap = null
|
||||||
|
it.isShowCamera = false
|
||||||
|
it.imageFile = file
|
||||||
|
}
|
||||||
|
collectionAdapter.notifyItemChanged(index)
|
||||||
|
}
|
||||||
|
if (bitmap.isRecycled.not()) {
|
||||||
|
bitmap.recycle()
|
||||||
|
}
|
||||||
|
hideWaitingDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -59,7 +59,8 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:gravity="center">
|
android:gravity="center"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
<!-- Loading 动画容器 -->
|
<!-- Loading 动画容器 -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|||||||
@@ -1,19 +1,23 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout 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="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
tools:background="@color/bg_color">
|
tools:background="@color/bg_color">
|
||||||
|
|
||||||
<!-- 顶部标题 -->
|
<!-- 顶部标题 -->
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/tvMasterTitle"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_horizontal"
|
android:layout_gravity="center_horizontal"
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
android:layout_marginBottom="10dp"
|
android:layout_marginBottom="10dp"
|
||||||
android:text="全设备秤数据监控"
|
android:text="全设备秤数据监控"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
android:textColor="@color/home_title"
|
android:textColor="@color/home_title"
|
||||||
android:textSize="36sp"
|
android:textSize="36sp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
@@ -23,20 +27,36 @@
|
|||||||
android:id="@+id/tvConnectionStatus"
|
android:id="@+id/tvConnectionStatus"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_horizontal"
|
app:layout_constraintTop_toBottomOf="@id/tvMasterTitle"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
android:layout_marginBottom="16dp"
|
android:layout_marginBottom="16dp"
|
||||||
android:textColor="@color/home_sub_title"
|
android:textColor="@color/home_sub_title"
|
||||||
android:textSize="24sp"
|
android:textSize="24sp"
|
||||||
tools:text="已发现设备:3 台" />
|
tools:text="已发现设备:3 台" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivMasterBack"
|
||||||
|
android:layout_width="70dp"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:layout_gravity="start"
|
||||||
|
android:paddingStart="30dp"
|
||||||
|
android:paddingEnd="10dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/tvMasterTitle"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/tvMasterTitle"
|
||||||
|
android:src="@drawable/ic_back"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
<!-- 秤数据列表 -->
|
<!-- 秤数据列表 -->
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/rvScaleList"
|
android:id="@+id/rvScaleList"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1"
|
app:layout_constraintTop_toBottomOf="@id/tvConnectionStatus"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
android:overScrollMode="never"
|
android:overScrollMode="never"
|
||||||
android:padding="16dp"
|
android:padding="16dp"
|
||||||
tools:listitem="@layout/list_item_scale_data"/>
|
tools:listitem="@layout/list_item_scale_data"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|||||||
@@ -262,6 +262,11 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/flCameraContainer"
|
||||||
|
android:layout_width="1dp"
|
||||||
|
android:layout_height="1dp" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatButton
|
<androidx.appcompat.widget.AppCompatButton
|
||||||
android:id="@+id/btnCook"
|
android:id="@+id/btnCook"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:orientation="vertical"
|
||||||
|
tools:background="@color/white">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/viewLine"
|
||||||
|
android:layout_width="90dp"
|
||||||
|
android:layout_height="10dp"
|
||||||
|
android:layout_marginTop="30dp"
|
||||||
|
android:background="@drawable/shape_gray_dc_5" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSheetName"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="50dp"
|
||||||
|
android:text="菜品识别结果"
|
||||||
|
android:textColor="@color/black333"
|
||||||
|
android:textSize="36sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginStart="45dp"
|
||||||
|
android:layout_marginTop="15dp"
|
||||||
|
android:layout_marginEnd="45dp"
|
||||||
|
android:layout_marginBottom="15dp"
|
||||||
|
android:minHeight="500dp"
|
||||||
|
android:nestedScrollingEnabled="true"
|
||||||
|
android:overScrollMode="never"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||||
|
app:spanCount="2"
|
||||||
|
tools:itemCount="10"
|
||||||
|
tools:listitem="@layout/list_item_search_goods_info" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
Reference in New Issue
Block a user