添加了副屏显示逻辑控制
This commit is contained in:
Generated
-1
@@ -1,4 +1,3 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||||
|
|||||||
@@ -102,4 +102,6 @@ dependencies {
|
|||||||
|
|
||||||
implementation(libs.androidx.activity.ktx)
|
implementation(libs.androidx.activity.ktx)
|
||||||
implementation(libs.androidx.fragment.ktx)
|
implementation(libs.androidx.fragment.ktx)
|
||||||
|
// 图片加载
|
||||||
|
implementation(libs.glide)
|
||||||
}
|
}
|
||||||
@@ -2,11 +2,13 @@ package com.sw.dualscreen.activity
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Outline
|
import android.graphics.Outline
|
||||||
|
import android.graphics.Typeface
|
||||||
import android.hardware.display.DisplayManager
|
import android.hardware.display.DisplayManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewOutlineProvider
|
import android.view.ViewOutlineProvider
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
@@ -17,27 +19,41 @@ import androidx.camera.core.ImageCaptureException
|
|||||||
import androidx.camera.core.Preview
|
import androidx.camera.core.Preview
|
||||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
|
import com.sw.dualscreen.R
|
||||||
|
import com.sw.dualscreen.adapter.GenericItemAdapter
|
||||||
|
import com.sw.dualscreen.adapter.GridSpacingItemDecoration
|
||||||
|
import com.sw.dualscreen.adapter.dpToPx
|
||||||
import com.sw.dualscreen.databinding.ActivityMainBinding
|
import com.sw.dualscreen.databinding.ActivityMainBinding
|
||||||
|
import com.sw.dualscreen.databinding.ItemFoodInfoBinding
|
||||||
import com.sw.dualscreen.ext.dp
|
import com.sw.dualscreen.ext.dp
|
||||||
|
import com.sw.dualscreen.model.response.FoodInfo
|
||||||
import com.sw.dualscreen.presentation.SecondaryScreenPresentation
|
import com.sw.dualscreen.presentation.SecondaryScreenPresentation
|
||||||
|
import com.sw.dualscreen.sdk.SensorScaleUtils
|
||||||
|
import com.sw.dualscreen.utils.GlideUtils
|
||||||
import com.sw.dualscreen.view.CustomBottomSheetDialog
|
import com.sw.dualscreen.view.CustomBottomSheetDialog
|
||||||
import com.sw.dualscreen.viewmodel.UserViewModel
|
import com.sw.dualscreen.viewmodel.UserViewModel
|
||||||
import com.sw.plate.utils.AppUtil
|
import com.sw.plate.utils.AppUtil
|
||||||
import com.sw.plate.utils.ToastUtils
|
import com.sw.plate.utils.ToastUtils
|
||||||
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
|
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
private lateinit var binding: ActivityMainBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
private lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>
|
|
||||||
private val viewModel by viewModels<UserViewModel>()
|
private val viewModel by viewModels<UserViewModel>()
|
||||||
private lateinit var imageCapture: ImageCapture
|
private var imageCapture: ImageCapture? = null
|
||||||
private var isAnalyzing = true // 控制是否进行图像分析
|
private var isAnalyzing = true // 控制是否进行图像分析
|
||||||
private lateinit var presentation: SecondaryScreenPresentation
|
private lateinit var presentation: SecondaryScreenPresentation
|
||||||
private val executor = Executors.newSingleThreadExecutor()
|
private val executor = Executors.newSingleThreadExecutor()
|
||||||
|
private lateinit var adapter: GenericItemAdapter<FoodInfo, ItemFoodInfoBinding>
|
||||||
|
private var checkedItem: FoodInfo? = null
|
||||||
|
private var imageAnalysis: ImageAnalysis? = null
|
||||||
|
private var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>? = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -54,6 +70,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private fun initData() {
|
private fun initData() {
|
||||||
viewModel.getEquipmentToken()
|
viewModel.getEquipmentToken()
|
||||||
|
presentation.step1()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initView() {
|
private fun initView() {
|
||||||
@@ -64,12 +81,20 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
binding.previewView.clipToOutline = true
|
binding.previewView.clipToOutline = true
|
||||||
|
|
||||||
binding.tvToSearch.setOnClickListener {
|
binding.tvToSearch.setOnClickListener {
|
||||||
val bottomSheetDialog = CustomBottomSheetDialog.newInstance()
|
val bottomSheetDialog = CustomBottomSheetDialog.newInstance(viewModel) {
|
||||||
|
updateCurrentFood(it)
|
||||||
|
}
|
||||||
bottomSheetDialog.show(supportFragmentManager, "CustomBottomSheetDialog")
|
bottomSheetDialog.show(supportFragmentManager, "CustomBottomSheetDialog")
|
||||||
}
|
}
|
||||||
binding.spinner.setItems(arrayOf("即放即取", "持续取餐"))
|
|
||||||
|
val spinnerAdapter = ArrayAdapter<String>(
|
||||||
|
this,
|
||||||
|
R.layout.item_for_custom_spinner,
|
||||||
|
arrayOf("即放即取", "持续取餐")
|
||||||
|
)
|
||||||
|
binding.spinner.adapter = spinnerAdapter
|
||||||
|
binding.spinner.setSelection(0)
|
||||||
binding.spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
binding.spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
override fun onItemSelected(
|
override fun onItemSelected(
|
||||||
parent: AdapterView<*>?,
|
parent: AdapterView<*>?,
|
||||||
@@ -84,13 +109,90 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
adapter = createAdapter()
|
||||||
|
binding.recyclerview.layoutManager = GridLayoutManager(this, 2)
|
||||||
|
// 添加间距装饰(12dp)
|
||||||
|
binding.recyclerview.addItemDecoration(
|
||||||
|
GridSpacingItemDecoration(
|
||||||
|
spanCount = 2,
|
||||||
|
spacing = dpToPx(30),
|
||||||
|
includeEdge = false // 包含边缘间距
|
||||||
|
)
|
||||||
|
)
|
||||||
|
binding.recyclerview.adapter = adapter
|
||||||
|
binding.tvFoodName.setOnClickListener {
|
||||||
|
presentation.step3()
|
||||||
|
}
|
||||||
|
registerDataChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateCurrentFood(foodInfo: FoodInfo?) {
|
||||||
|
presentation.updateFood(foodInfo)
|
||||||
|
if (foodInfo != null) {
|
||||||
|
binding.tvFoodName.text = foodInfo.foodName
|
||||||
|
binding.previewView.visibility = View.GONE
|
||||||
|
binding.ivImg.visibility = View.VISIBLE
|
||||||
|
GlideUtils.loadRoundCornerImage(this, foodInfo.imgUrl, binding.ivImg, 12)
|
||||||
|
pauseAnalysis()
|
||||||
|
} else {
|
||||||
|
binding.tvFoodName.text = "-"
|
||||||
|
binding.previewView.visibility = View.VISIBLE
|
||||||
|
binding.ivImg.visibility = View.GONE
|
||||||
|
resumeAnalysis()
|
||||||
|
viewModel.cleanIdentifiedFoodInfoList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun registerDataChange() {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
viewModel.identifiedFoodInfoList.collect { list ->
|
||||||
|
binding.recyclerview
|
||||||
|
adapter.updateData(list)
|
||||||
|
// presentation.step2()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
viewModel.getIdentifiedFoodList()
|
||||||
|
// SensorScaleUtils.startScale { weight ->
|
||||||
|
// Timber.d("weight = $weight")
|
||||||
|
// if (weight > 0.0) {
|
||||||
|
// if (canIndentify) {
|
||||||
|
// viewModel.getIdentifiedFoodList()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createAdapter(): GenericItemAdapter<FoodInfo, ItemFoodInfoBinding> {
|
||||||
|
return GenericItemAdapter(
|
||||||
|
items = emptyList(),
|
||||||
|
bindingInflater = ItemFoodInfoBinding::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.identify_normal))
|
||||||
|
this.llRoot.setBackgroundResource(R.drawable.grid_item_normal)
|
||||||
|
} else {
|
||||||
|
this.tvName.typeface = Typeface.defaultFromStyle(Typeface.BOLD)
|
||||||
|
this.tvName.setTextColor(resources.getColor(R.color.identify_checked))
|
||||||
|
this.llRoot.setBackgroundResource(R.drawable.grid_item_checked)
|
||||||
|
}
|
||||||
|
//
|
||||||
|
this.llRoot.setOnClickListener {
|
||||||
|
Timber.d("itemClick ${item.foodName}, position = $position")
|
||||||
|
checkedItem = item
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
updateCurrentFood(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupCamera() {
|
private fun setupCamera() {
|
||||||
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
|
Timber.d("setupCamera")
|
||||||
cameraProviderFuture.addListener({
|
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
|
||||||
val cameraProvider = cameraProviderFuture.get()
|
cameraProviderFuture!!.addListener({
|
||||||
|
val cameraProvider = cameraProviderFuture!!.get()
|
||||||
|
|
||||||
// 1. 创建预览用例
|
// 1. 创建预览用例
|
||||||
val preview = Preview.Builder()
|
val preview = Preview.Builder()
|
||||||
@@ -100,18 +202,17 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. 创建图像分析用例(用于副屏显示)
|
// 2. 创建图像分析用例(用于副屏显示)
|
||||||
val imageAnalysis = ImageAnalysis.Builder()
|
imageAnalysis = ImageAnalysis.Builder()
|
||||||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||||
// .setTargetResolution(Size(640, 480)) // 降低分辨率减轻负担
|
// .setTargetResolution(Size(640, 480)) // 降低分辨率减轻负担
|
||||||
.build()
|
.build()
|
||||||
.also { analysis ->
|
.also { analysis ->
|
||||||
analysis.setAnalyzer(executor) { imageProxy ->
|
analysis.setAnalyzer(executor) { imageProxy ->
|
||||||
|
// 仅在步骤1时传递
|
||||||
if (isAnalyzing) {
|
if (isAnalyzing) {
|
||||||
val bitmap = imageProxy.toBitmap()
|
val bitmap = imageProxy.toBitmap()
|
||||||
runOnUiThread {
|
|
||||||
presentation.updateImage(bitmap) // 更新副屏
|
presentation.updateImage(bitmap) // 更新副屏
|
||||||
}
|
}
|
||||||
}
|
|
||||||
imageProxy.close() // 必须关闭以释放资源
|
imageProxy.close() // 必须关闭以释放资源
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,6 +240,48 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}, ContextCompat.getMainExecutor(this))
|
}, ContextCompat.getMainExecutor(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun pauseAnalysis() {
|
||||||
|
Timber.d("pauseAnalysis")
|
||||||
|
isAnalyzing = false
|
||||||
|
imageAnalysis?.clearAnalyzer()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resumeAnalysis() {
|
||||||
|
Timber.d("resumeAnalysis isAnalyzing = $isAnalyzing")
|
||||||
|
if (isAnalyzing) return
|
||||||
|
isAnalyzing = true
|
||||||
|
imageAnalysis?.setAnalyzer(executor) { imageProxy ->
|
||||||
|
if (presentation.currentStep == 1) {
|
||||||
|
val bitmap = imageProxy.toBitmap()
|
||||||
|
presentation.updateImage(bitmap)
|
||||||
|
}
|
||||||
|
imageProxy.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 停止相机识别
|
||||||
|
*/
|
||||||
|
private fun shutdownCamera() {
|
||||||
|
Timber.d("shutdownCamera")
|
||||||
|
try {
|
||||||
|
// 1. 首先停止图像分析
|
||||||
|
imageAnalysis?.clearAnalyzer()
|
||||||
|
|
||||||
|
// 2. 解除所有绑定
|
||||||
|
cameraProviderFuture?.get()?.unbindAll()
|
||||||
|
|
||||||
|
// 3. 关闭相关资源
|
||||||
|
imageCapture = null
|
||||||
|
imageAnalysis = null
|
||||||
|
|
||||||
|
// 4. 停止副屏更新
|
||||||
|
isAnalyzing = false
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "Error shutting down camera")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 拍照功能
|
// 拍照功能
|
||||||
private fun takePhoto() {
|
private fun takePhoto() {
|
||||||
// 临时停止分析以避免干扰
|
// 临时停止分析以避免干扰
|
||||||
@@ -151,7 +294,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
)
|
)
|
||||||
).build()
|
).build()
|
||||||
// 执行拍照
|
// 执行拍照
|
||||||
imageCapture.takePicture(
|
imageCapture?.takePicture(
|
||||||
outputFileOptions,
|
outputFileOptions,
|
||||||
executor,
|
executor,
|
||||||
object : ImageCapture.OnImageSavedCallback {
|
object : ImageCapture.OnImageSavedCallback {
|
||||||
@@ -191,8 +334,18 @@ class MainActivity : AppCompatActivity() {
|
|||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
recognizeViewModel = viewModels<RecognizeViewModel>().value
|
recognizeViewModel = viewModels<RecognizeViewModel>().value
|
||||||
)
|
)
|
||||||
|
presentation.setStepChangeCallback { step ->
|
||||||
|
if (step == 1) {
|
||||||
|
updateCurrentFood(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
presentation.show()
|
presentation.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
SensorScaleUtils.closeScale()
|
||||||
|
presentation.dismiss()
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ val Float.sp: Float
|
|||||||
)
|
)
|
||||||
|
|
||||||
// 添加扩展函数
|
// 添加扩展函数
|
||||||
fun ImageProxy.toBitmap(): Bitmap {
|
fun ImageProxy.toSafeBitmap(): Bitmap {
|
||||||
val yBuffer = planes[0].buffer // Y
|
val yBuffer = planes[0].buffer // Y
|
||||||
val uBuffer = planes[1].buffer // U
|
val uBuffer = planes[1].buffer // U
|
||||||
val vBuffer = planes[2].buffer // V
|
val vBuffer = planes[2].buffer // V
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.sw.dualscreen.ext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 姓名脱敏处理
|
||||||
|
* 2个字:张*
|
||||||
|
* 多于2个字:张*某
|
||||||
|
*/
|
||||||
|
fun String?.maskName(): String {
|
||||||
|
if (this == null || this.isEmpty()) return ""
|
||||||
|
|
||||||
|
return when {
|
||||||
|
length == 2 -> "${this[0]}*"
|
||||||
|
length > 2 -> "${this[0]}*${this[length - 1]}"
|
||||||
|
else -> this // 1个字的情况原样返回
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手机号脱敏处理
|
||||||
|
* 将第4-7位替换为*
|
||||||
|
* 例如:138****1234
|
||||||
|
*/
|
||||||
|
fun String?.maskPhone(): String {
|
||||||
|
if (this == null || this.isEmpty()) return ""
|
||||||
|
if (this.length < 11) {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
val start = 3 // 第4位(索引从0开始)
|
||||||
|
val end = 6 // 第7位
|
||||||
|
|
||||||
|
val sb = StringBuilder(this)
|
||||||
|
for (i in start..end) {
|
||||||
|
sb.setCharAt(i, '*')
|
||||||
|
}
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
@@ -5,11 +5,11 @@ import android.app.Presentation
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Outline
|
||||||
import android.graphics.Point
|
import android.graphics.Point
|
||||||
import android.hardware.Camera
|
import android.hardware.Camera
|
||||||
import android.hardware.camera2.CameraDevice
|
import android.hardware.camera2.CameraDevice
|
||||||
import android.hardware.camera2.CameraManager
|
import android.hardware.camera2.CameraManager
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
@@ -17,16 +17,22 @@ import android.util.Log
|
|||||||
import android.view.Display
|
import android.view.Display
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.view.ViewOutlineProvider
|
||||||
import android.view.ViewTreeObserver
|
import android.view.ViewTreeObserver
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.camera.view.PreviewView
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.arcsoft.face.ErrorInfo
|
import com.arcsoft.face.ErrorInfo
|
||||||
import com.sw.dualscreen.R
|
import com.sw.dualscreen.R
|
||||||
import com.sw.dualscreen.activity.MainActivity
|
import com.sw.dualscreen.activity.MainActivity
|
||||||
|
import com.sw.dualscreen.databinding.ItemUserNutritionBinding
|
||||||
import com.sw.dualscreen.databinding.PresentationSecondaryScreenBinding
|
import com.sw.dualscreen.databinding.PresentationSecondaryScreenBinding
|
||||||
|
import com.sw.dualscreen.ext.dp
|
||||||
|
import com.sw.dualscreen.ext.maskName
|
||||||
|
import com.sw.dualscreen.model.response.FoodInfo
|
||||||
|
import com.sw.dualscreen.utils.GlideUtils
|
||||||
import com.sw.dualscreen.viewmodel.UserViewModel
|
import com.sw.dualscreen.viewmodel.UserViewModel
|
||||||
import com.sw.plate.utils.ToastUtils
|
import com.sw.plate.utils.ToastUtils
|
||||||
import com.sw.plate.utils.arcface.ConfigUtil
|
import com.sw.plate.utils.arcface.ConfigUtil
|
||||||
@@ -41,6 +47,7 @@ import com.sw.plate.utils.arcface.face.constants.LivenessType
|
|||||||
import com.sw.plate.utils.arcface.face.model.FacePreviewInfo
|
import com.sw.plate.utils.arcface.face.model.FacePreviewInfo
|
||||||
import com.sw.plate.utils.arcface.face.model.RecognizeConfiguration
|
import com.sw.plate.utils.arcface.face.model.RecognizeConfiguration
|
||||||
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
|
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,7 +57,7 @@ class SecondaryScreenPresentation(
|
|||||||
val activity: MainActivity,
|
val activity: MainActivity,
|
||||||
context: Context,
|
context: Context,
|
||||||
display: Display,
|
display: Display,
|
||||||
viewModel: UserViewModel,
|
val viewModel: UserViewModel,
|
||||||
private val onDismissListener: () -> Unit = {},
|
private val onDismissListener: () -> Unit = {},
|
||||||
val recognizeViewModel: RecognizeViewModel
|
val recognizeViewModel: RecognizeViewModel
|
||||||
) : Presentation(context, display), ViewTreeObserver.OnGlobalLayoutListener {
|
) : Presentation(context, display), ViewTreeObserver.OnGlobalLayoutListener {
|
||||||
@@ -60,7 +67,14 @@ class SecondaryScreenPresentation(
|
|||||||
// private var cameraCaptureSession: CameraCaptureSession? = null
|
// private var cameraCaptureSession: CameraCaptureSession? = null
|
||||||
private lateinit var cameraManager: CameraManager
|
private lateinit var cameraManager: CameraManager
|
||||||
private var cameraIdList: Array<String> = arrayOf()
|
private var cameraIdList: Array<String> = arrayOf()
|
||||||
lateinit var previewView: PreviewView
|
|
||||||
|
// 当前食物信息
|
||||||
|
private var currentFood: FoodInfo? = null
|
||||||
|
private lateinit var itemUserNutritionBinding: ItemUserNutritionBinding
|
||||||
|
private lateinit var stepChangeCallback: (Int) -> Unit
|
||||||
|
|
||||||
|
// 当前步骤
|
||||||
|
var currentStep: Int = 0
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -68,9 +82,48 @@ class SecondaryScreenPresentation(
|
|||||||
binding = PresentationSecondaryScreenBinding.inflate(layoutInflater)
|
binding = PresentationSecondaryScreenBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
window?.setBackgroundDrawableResource(android.R.color.transparent)
|
window?.setBackgroundDrawableResource(android.R.color.transparent)
|
||||||
|
initView()
|
||||||
|
// binding.includeUserHeat.ivHeat.showText("1070千卡")
|
||||||
|
registerDataChange()
|
||||||
|
}
|
||||||
|
|
||||||
previewView = binding.previewView
|
private fun registerDataChange() {
|
||||||
|
activity.lifecycleScope.launch {
|
||||||
|
viewModel.nutritionData.collect {
|
||||||
|
Timber.d("registerDataChange nutritionData = $it")
|
||||||
|
if (it == null) return@collect
|
||||||
|
itemUserNutritionBinding.tvUserName.text = it.userName.maskName()
|
||||||
|
itemUserNutritionBinding.tvRecommendHeat.text =
|
||||||
|
"推荐热量:${it.recommendMin}-${it.recommendMax}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initView() {
|
||||||
|
binding.ivImg.outlineProvider = object : ViewOutlineProvider() {
|
||||||
|
override fun getOutline(view: View, outline: Outline) {
|
||||||
|
outline.setRoundRect(0, 0, view.width, view.height, 12f.dp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.ivImg.clipToOutline = true
|
||||||
|
itemUserNutritionBinding = binding.includeUserNutrition
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDisplayRemoved() {
|
||||||
|
super.onDisplayRemoved()
|
||||||
|
onDismissListener()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateImage(bitmap: Bitmap) {
|
||||||
|
activity.runOnUiThread {
|
||||||
|
binding.ivImg.setImageBitmap(bitmap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用虹软人脸失败
|
||||||
|
*/
|
||||||
|
private fun setupArcCamera() {
|
||||||
cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
|
cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
|
||||||
// 获取摄像头列表
|
// 获取摄像头列表
|
||||||
cameraIdList = cameraManager.cameraIdList
|
cameraIdList = cameraManager.cameraIdList
|
||||||
@@ -87,41 +140,94 @@ class SecondaryScreenPresentation(
|
|||||||
initArcView()
|
initArcView()
|
||||||
openRectInfoDraw = true
|
openRectInfoDraw = true
|
||||||
openCamera(cameraIdList.last())
|
openCamera(cameraIdList.last())
|
||||||
// binding.includeUserHeat.ivHeat.showText("1070千卡")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDisplayRemoved() {
|
|
||||||
super.onDisplayRemoved()
|
|
||||||
onDismissListener()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateImage(bitmap: Bitmap) {
|
|
||||||
activity.runOnUiThread {
|
|
||||||
binding.ivImg.setImageBitmap(bitmap)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户1 显示相机预览
|
* 用户1 显示相机预览
|
||||||
*/
|
*/
|
||||||
fun step1(foodImg: Uri) {
|
fun step1() {
|
||||||
|
Timber.d("step1")
|
||||||
|
currentStep = 1
|
||||||
|
stepChangeCallback(currentStep)
|
||||||
|
binding.tvFoodInfo.text = "菜品识别中"
|
||||||
|
binding.flPreview.visibility = View.VISIBLE
|
||||||
|
// binding.previewView.visibility = View.VISIBLE
|
||||||
|
binding.ivImg.visibility = View.VISIBLE
|
||||||
|
binding.flFace.visibility = View.GONE
|
||||||
|
pauseCamera()
|
||||||
|
|
||||||
|
binding.tvBottomTip.visibility = View.VISIBLE
|
||||||
|
itemUserNutritionBinding.clNutritionData.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 显示人脸识别
|
* 显示人脸识别
|
||||||
*/
|
*/
|
||||||
fun step2() {
|
fun step2(foodInfo: FoodInfo) {
|
||||||
|
Timber.d("step2")
|
||||||
|
currentStep = 2
|
||||||
|
stepChangeCallback(currentStep)
|
||||||
|
currentFood = foodInfo
|
||||||
|
resumeCamera()
|
||||||
|
binding.tvFoodInfo.text = foodInfo.foodName
|
||||||
|
|
||||||
|
binding.flPreview.visibility = View.GONE
|
||||||
|
|
||||||
|
binding.flFace.visibility = View.VISIBLE
|
||||||
|
binding.ivFaceBg.visibility = View.VISIBLE
|
||||||
|
binding.tvFaceTip.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
setupArcCamera()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateFood(foodInfo: FoodInfo?) {
|
||||||
|
Timber.d("step2 updateFood")
|
||||||
|
currentFood = foodInfo
|
||||||
|
if (foodInfo != null) {
|
||||||
|
if (currentStep != 2) {
|
||||||
|
step2(foodInfo)
|
||||||
|
} else {
|
||||||
|
binding.tvFoodInfo.text = foodInfo.foodName
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 显示识别出的菜品信息
|
* 显示识别出的菜品信息
|
||||||
*/
|
*/
|
||||||
fun step3() {
|
fun step3() {
|
||||||
|
Timber.d("step3")
|
||||||
|
currentStep = 3
|
||||||
|
stepChangeCallback(currentStep)
|
||||||
|
if (currentFood == null) {
|
||||||
|
ToastUtils.showToast("选中物品为空3")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
binding.tvFoodInfo.text = currentFood!!.foodName
|
||||||
|
|
||||||
|
binding.flPreview.visibility = View.VISIBLE
|
||||||
|
// binding.previewView.visibility = View.GONE
|
||||||
|
binding.ivImg.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
GlideUtils.loadRoundCornerImage(
|
||||||
|
context,
|
||||||
|
url = currentFood!!.imgUrl,
|
||||||
|
imageView = binding.ivImg,
|
||||||
|
radius = 12
|
||||||
|
)
|
||||||
|
|
||||||
|
binding.flFace.visibility = View.GONE
|
||||||
|
binding.ivFaceBg.visibility = View.GONE
|
||||||
|
binding.tvFaceTip.visibility = View.GONE
|
||||||
|
|
||||||
|
binding.tvBottomTip.visibility = View.GONE
|
||||||
|
itemUserNutritionBinding.clNutritionData.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
viewModel.getUserNutritionData(
|
||||||
|
userId = "187c96f7bf294c08bc1d1e57b90a7210",
|
||||||
|
foodId = currentFood!!.id!!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun openCamera(cameraId: String) {
|
private fun openCamera(cameraId: String) {
|
||||||
if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) !=
|
if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) !=
|
||||||
@@ -158,6 +264,10 @@ class SecondaryScreenPresentation(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setStepChangeCallback(callback: (Int) -> Unit) {
|
||||||
|
stepChangeCallback = callback
|
||||||
|
}
|
||||||
|
|
||||||
// 虹软人脸配置 ⬇
|
// 虹软人脸配置 ⬇
|
||||||
private var isRecognition = false
|
private var isRecognition = false
|
||||||
private var rgbCameraHelper: DualCameraHelper? = null
|
private var rgbCameraHelper: DualCameraHelper? = null
|
||||||
@@ -206,6 +316,9 @@ class SecondaryScreenPresentation(
|
|||||||
})
|
})
|
||||||
recognizeViewModel.recognizeNotice.observe(activity, Observer { notice: String? ->
|
recognizeViewModel.recognizeNotice.observe(activity, Observer { notice: String? ->
|
||||||
Timber.i("recognizeNotice observe notice = $notice")
|
Timber.i("recognizeNotice observe notice = $notice")
|
||||||
|
if (currentStep == 3 && notice == "leave") {
|
||||||
|
step1()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
recognizeViewModel.recognizeUserId.observe(activity, Observer { userId: String? ->
|
recognizeViewModel.recognizeUserId.observe(activity, Observer { userId: String? ->
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class RemoteRepository constructor(
|
|||||||
*/
|
*/
|
||||||
suspend fun getRestInfoFoodsByType(
|
suspend fun getRestInfoFoodsByType(
|
||||||
restId: String = GlobalData.restId,
|
restId: String = GlobalData.restId,
|
||||||
type: Int = 1,
|
type: Int = 0,
|
||||||
foodName: String,
|
foodName: String,
|
||||||
): ApiResponse<List<FoodInfo>> {
|
): ApiResponse<List<FoodInfo>> {
|
||||||
return safeApiCall {
|
return safeApiCall {
|
||||||
|
|||||||
+9
-7
@@ -1,11 +1,10 @@
|
|||||||
package com.sw.plate.sdk
|
package com.sw.dualscreen.sdk
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.sw.plate.utils.ThreadUtils
|
|
||||||
import com.sw.plate.utils.ToastUtils
|
import com.sw.plate.utils.ToastUtils
|
||||||
import com.wabon.wbintelligenthardwaresdk.api.SensorScale
|
import com.wabon.wbintelligenthardwaresdk.api.SensorScale
|
||||||
import com.wabon.wbintelligenthardwaresdk.api.SensorScale.OnScaleResult
|
import com.wabon.wbintelligenthardwaresdk.api.SensorScale.OnScaleResult
|
||||||
import kotlinx.coroutines.delay
|
import timber.log.Timber
|
||||||
|
|
||||||
typealias Callback = (Double) -> Unit
|
typealias Callback = (Double) -> Unit
|
||||||
|
|
||||||
@@ -65,16 +64,19 @@ object SensorScaleUtils {
|
|||||||
}
|
}
|
||||||
this.callback = callback
|
this.callback = callback
|
||||||
init()
|
init()
|
||||||
|
mSensorScale?.zero {
|
||||||
|
Timber.d("startScale: zero")
|
||||||
|
}
|
||||||
mSensorScale?.openScale(serialPort, baudRate) { open ->
|
mSensorScale?.openScale(serialPort, baudRate) { open ->
|
||||||
isOpened = open
|
isOpened = open
|
||||||
Log.d(TAG, "isOpened = $isOpened")
|
Timber.d("isOpened = $isOpened")
|
||||||
if (open) {
|
if (open) {
|
||||||
if (autoScale) {
|
if (autoScale) {
|
||||||
ThreadUtils.launchOnIo {
|
Thread({
|
||||||
delay(1000)
|
Thread.sleep(1000)
|
||||||
// 打开后需要等待后才能调用,否则会 1001 SDK未初始化
|
// 打开后需要等待后才能调用,否则会 1001 SDK未初始化
|
||||||
mSensorScale?.startContinuousRead()
|
mSensorScale?.startContinuousRead()
|
||||||
}
|
}).start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.sw.dualscreen.utils
|
||||||
|
|
||||||
|
class Debouncer(private val delayMillis: Long) {
|
||||||
|
private var lastActionTime = 0L
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行防抖操作
|
||||||
|
* @param action 要执行的操作
|
||||||
|
* @return Boolean 是否执行了操作 (true=已执行, false=被防抖)
|
||||||
|
*/
|
||||||
|
fun debounce(action: () -> Unit): Boolean {
|
||||||
|
val currentTime = System.currentTimeMillis()
|
||||||
|
if (currentTime - lastActionTime >= delayMillis) {
|
||||||
|
lastActionTime = currentTime
|
||||||
|
action()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置防抖计时
|
||||||
|
fun reset() {
|
||||||
|
lastActionTime = 0L
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
package com.sw.dualscreen.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.widget.ImageView
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.RequestBuilder
|
||||||
|
import com.bumptech.glide.load.DataSource
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
|
import com.bumptech.glide.load.engine.GlideException
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.CircleCrop
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||||
|
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
||||||
|
import com.bumptech.glide.request.RequestListener
|
||||||
|
import com.bumptech.glide.request.RequestOptions
|
||||||
|
import com.bumptech.glide.request.target.Target
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Glide图片加载工具类
|
||||||
|
*/
|
||||||
|
object GlideUtils {
|
||||||
|
|
||||||
|
// 默认配置
|
||||||
|
private val defaultOptions = RequestOptions()
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
|
||||||
|
.skipMemoryCache(false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 普通加载图片
|
||||||
|
* @param context 上下文
|
||||||
|
* @param url 图片地址
|
||||||
|
* @param imageView 目标ImageView
|
||||||
|
* @param placeholderResId 占位图资源ID
|
||||||
|
* @param errorResId 错误图资源ID
|
||||||
|
*/
|
||||||
|
fun loadImage(
|
||||||
|
context: Context,
|
||||||
|
url: Any?,
|
||||||
|
imageView: ImageView,
|
||||||
|
@DrawableRes placeholderResId: Int = 0,
|
||||||
|
@DrawableRes errorResId: Int = 0
|
||||||
|
) {
|
||||||
|
val requestBuilder = Glide.with(context)
|
||||||
|
.load(url)
|
||||||
|
.apply(defaultOptions)
|
||||||
|
|
||||||
|
applyPlaceholder(requestBuilder, placeholderResId, errorResId)
|
||||||
|
.transition(DrawableTransitionOptions.withCrossFade())
|
||||||
|
.into(imageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载圆形图片
|
||||||
|
* @param context 上下文
|
||||||
|
* @param url 图片地址
|
||||||
|
* @param imageView 目标ImageView
|
||||||
|
* @param placeholderResId 占位图资源ID
|
||||||
|
* @param errorResId 错误图资源ID
|
||||||
|
*/
|
||||||
|
fun loadCircleImage(
|
||||||
|
context: Context,
|
||||||
|
url: Any?,
|
||||||
|
imageView: ImageView,
|
||||||
|
@DrawableRes placeholderResId: Int = 0,
|
||||||
|
@DrawableRes errorResId: Int = 0
|
||||||
|
) {
|
||||||
|
val requestBuilder = Glide.with(context)
|
||||||
|
.load(url)
|
||||||
|
.apply(defaultOptions)
|
||||||
|
.transform(CircleCrop())
|
||||||
|
|
||||||
|
applyPlaceholder(requestBuilder, placeholderResId, errorResId)
|
||||||
|
.into(imageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载圆角图片
|
||||||
|
* @param context 上下文
|
||||||
|
* @param url 图片地址
|
||||||
|
* @param imageView 目标ImageView
|
||||||
|
* @param radius 圆角半径(px)
|
||||||
|
* @param placeholderResId 占位图资源ID
|
||||||
|
* @param errorResId 错误图资源ID
|
||||||
|
*/
|
||||||
|
fun loadRoundCornerImage(
|
||||||
|
context: Context,
|
||||||
|
url: Any?,
|
||||||
|
imageView: ImageView,
|
||||||
|
radius: Int,
|
||||||
|
@DrawableRes placeholderResId: Int = 0,
|
||||||
|
@DrawableRes errorResId: Int = 0
|
||||||
|
) {
|
||||||
|
val requestBuilder = Glide.with(context)
|
||||||
|
.load(url)
|
||||||
|
.apply(defaultOptions)
|
||||||
|
.transform(RoundedCorners(radius))
|
||||||
|
|
||||||
|
applyPlaceholder(requestBuilder, placeholderResId, errorResId)
|
||||||
|
.into(imageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载图片并获取Bitmap
|
||||||
|
* @param context 上下文
|
||||||
|
* @param url 图片地址
|
||||||
|
* @param callback 加载回调
|
||||||
|
*/
|
||||||
|
fun loadImageAsBitmap(
|
||||||
|
context: Context,
|
||||||
|
url: Any?,
|
||||||
|
callback: (Bitmap?) -> Unit
|
||||||
|
) {
|
||||||
|
Glide.with(context)
|
||||||
|
.asBitmap()
|
||||||
|
.load(url)
|
||||||
|
.apply(defaultOptions)
|
||||||
|
.addListener(object : RequestListener<Bitmap> {
|
||||||
|
|
||||||
|
override fun onLoadFailed(
|
||||||
|
e: GlideException?,
|
||||||
|
model: Any?,
|
||||||
|
target: Target<Bitmap?>,
|
||||||
|
isFirstResource: Boolean
|
||||||
|
): Boolean {
|
||||||
|
callback(null)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResourceReady(
|
||||||
|
resource: Bitmap,
|
||||||
|
model: Any,
|
||||||
|
target: Target<Bitmap?>?,
|
||||||
|
dataSource: DataSource,
|
||||||
|
isFirstResource: Boolean
|
||||||
|
): Boolean {
|
||||||
|
callback(resource)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.submit()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除内存缓存
|
||||||
|
* @param context 上下文
|
||||||
|
*/
|
||||||
|
fun clearMemoryCache(context: Context) {
|
||||||
|
Glide.get(context).clearMemory()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除磁盘缓存
|
||||||
|
* @param context 上下文
|
||||||
|
*/
|
||||||
|
fun clearDiskCache(context: Context) {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
Glide.get(context).clearDiskCache()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取缓存文件
|
||||||
|
* @param context 上下文
|
||||||
|
* @param url 图片地址
|
||||||
|
* @param callback 回调
|
||||||
|
*/
|
||||||
|
fun getImageCacheFile(
|
||||||
|
context: Context,
|
||||||
|
url: String,
|
||||||
|
callback: (File?) -> Unit
|
||||||
|
) {
|
||||||
|
Glide.with(context)
|
||||||
|
.downloadOnly()
|
||||||
|
.load(url)
|
||||||
|
.addListener(object : RequestListener<File> {
|
||||||
|
override fun onLoadFailed(
|
||||||
|
e: GlideException?,
|
||||||
|
model: Any?,
|
||||||
|
target: Target<File?>,
|
||||||
|
isFirstResource: Boolean
|
||||||
|
): Boolean {
|
||||||
|
callback(null)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResourceReady(
|
||||||
|
resource: File,
|
||||||
|
model: Any,
|
||||||
|
target: Target<File?>?,
|
||||||
|
dataSource: DataSource,
|
||||||
|
isFirstResource: Boolean
|
||||||
|
): Boolean {
|
||||||
|
callback(resource)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.submit()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 暂停请求
|
||||||
|
* @param context 上下文
|
||||||
|
*/
|
||||||
|
fun pauseRequests(context: Context) {
|
||||||
|
Glide.with(context).pauseRequests()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 恢复请求
|
||||||
|
* @param context 上下文
|
||||||
|
*/
|
||||||
|
fun resumeRequests(context: Context) {
|
||||||
|
Glide.with(context).resumeRequests()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 私有方法:应用占位图和错误图
|
||||||
|
private fun <T> applyPlaceholder(
|
||||||
|
requestBuilder: RequestBuilder<T>,
|
||||||
|
@DrawableRes placeholderResId: Int,
|
||||||
|
@DrawableRes errorResId: Int
|
||||||
|
): RequestBuilder<T> {
|
||||||
|
return requestBuilder.apply {
|
||||||
|
if (placeholderResId != 0) {
|
||||||
|
placeholder(placeholderResId)
|
||||||
|
}
|
||||||
|
if (errorResId != 0) {
|
||||||
|
error(errorResId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
package com.sw.dualscreen.view
|
package com.sw.dualscreen.view
|
||||||
|
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
import android.graphics.Typeface
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
import com.sw.dualscreen.R
|
import com.sw.dualscreen.R
|
||||||
import com.sw.dualscreen.adapter.GenericItemAdapter
|
import com.sw.dualscreen.adapter.GenericItemAdapter
|
||||||
@@ -17,8 +17,15 @@ import com.sw.dualscreen.adapter.dpToPx
|
|||||||
import com.sw.dualscreen.databinding.BottomSheetDialogBinding
|
import com.sw.dualscreen.databinding.BottomSheetDialogBinding
|
||||||
import com.sw.dualscreen.databinding.ItemSearchFoodInfoBinding
|
import com.sw.dualscreen.databinding.ItemSearchFoodInfoBinding
|
||||||
import com.sw.dualscreen.model.response.FoodInfo
|
import com.sw.dualscreen.model.response.FoodInfo
|
||||||
|
import com.sw.dualscreen.utils.Debouncer
|
||||||
|
import com.sw.dualscreen.viewmodel.UserViewModel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
class CustomBottomSheetDialog : BottomSheetDialogFragment() {
|
class CustomBottomSheetDialog(
|
||||||
|
val viewModel: UserViewModel,
|
||||||
|
val itemClickCallback: (FoodInfo) -> Unit
|
||||||
|
) : BottomSheetDialogFragment() {
|
||||||
private lateinit var binding: BottomSheetDialogBinding
|
private lateinit var binding: BottomSheetDialogBinding
|
||||||
private lateinit var adapter: GenericItemAdapter<FoodInfo, ItemSearchFoodInfoBinding>
|
private lateinit var adapter: GenericItemAdapter<FoodInfo, ItemSearchFoodInfoBinding>
|
||||||
private var checkedItem: FoodInfo? = null
|
private var checkedItem: FoodInfo? = null
|
||||||
@@ -34,9 +41,12 @@ class CustomBottomSheetDialog : BottomSheetDialogFragment() {
|
|||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
val debouncer = Debouncer(3000)
|
||||||
|
initView()
|
||||||
binding.ivSearch.setOnClickListener {
|
binding.ivSearch.setOnClickListener {
|
||||||
|
debouncer.debounce {
|
||||||
|
viewModel.searchByFoodName(binding.etSearch.text.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,8 +73,11 @@ class CustomBottomSheetDialog : BottomSheetDialogFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun newInstance(): CustomBottomSheetDialog {
|
fun newInstance(
|
||||||
return CustomBottomSheetDialog()
|
viewModel: UserViewModel,
|
||||||
|
itemClickCallback: (FoodInfo) -> Unit
|
||||||
|
): CustomBottomSheetDialog {
|
||||||
|
return CustomBottomSheetDialog(viewModel, itemClickCallback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,28 +88,20 @@ class CustomBottomSheetDialog : BottomSheetDialogFragment() {
|
|||||||
binding.recyclerview.addItemDecoration(
|
binding.recyclerview.addItemDecoration(
|
||||||
GridSpacingItemDecoration(
|
GridSpacingItemDecoration(
|
||||||
spanCount = 2,
|
spanCount = 2,
|
||||||
spacing = requireContext().dpToPx(6),
|
spacing = requireContext().dpToPx(30),
|
||||||
includeEdge = false // 包含边缘间距
|
includeEdge = false // 包含边缘间距
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
binding.recyclerview.adapter = adapter
|
binding.recyclerview.adapter = adapter
|
||||||
binding.recyclerview.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
registerDataChange()
|
||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
}
|
||||||
super.onScrolled(recyclerView, dx, dy)
|
|
||||||
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
|
|
||||||
val lastVisibleItem = layoutManager.findLastVisibleItemPosition()
|
|
||||||
val totalItems = layoutManager.itemCount
|
|
||||||
|
|
||||||
// if (!viewModel.isLoading && viewModel.canLoadMore
|
private fun registerDataChange() {
|
||||||
// && lastVisibleItem >= totalItems - 3
|
lifecycleScope.launch {
|
||||||
// ) {
|
viewModel.searchFoodInfoList.collect {
|
||||||
// viewModel.getSearchMemberList(
|
adapter.updateData(it)
|
||||||
// param = lastText,
|
}
|
||||||
// pageNum = viewModel.currentPage
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createAdapter(): GenericItemAdapter<FoodInfo, ItemSearchFoodInfoBinding> {
|
private fun createAdapter(): GenericItemAdapter<FoodInfo, ItemSearchFoodInfoBinding> {
|
||||||
@@ -104,20 +109,30 @@ class CustomBottomSheetDialog : BottomSheetDialogFragment() {
|
|||||||
items = emptyList(),
|
items = emptyList(),
|
||||||
bindingInflater = ItemSearchFoodInfoBinding::inflate,
|
bindingInflater = ItemSearchFoodInfoBinding::inflate,
|
||||||
bindCallback = { item, position ->
|
bindCallback = { item, position ->
|
||||||
// this.tvName.text = item.name.maskName()
|
this.tvName.text = item.foodName
|
||||||
// this.tvPhone.text = item.phone.maskPhone()
|
if (item.id != checkedItem?.id) {
|
||||||
// if (item.id != checkedItem?.id) {
|
this.tvName.typeface = Typeface.defaultFromStyle(Typeface.NORMAL)
|
||||||
// this.llRoot.setBackgroundResource(R.drawable.grid_item_bind)
|
this.tvName.setTextColor(resources.getColor(R.color.search_normal))
|
||||||
// } else {
|
this.llRoot.setBackgroundResource(R.drawable.grid_search_item_normal)
|
||||||
// this.llRoot.setBackgroundResource(R.drawable.grid_item_unbind)
|
} else {
|
||||||
// }
|
this.tvName.typeface = Typeface.defaultFromStyle(Typeface.BOLD)
|
||||||
//
|
this.tvName.setTextColor(resources.getColor(R.color.search_checked))
|
||||||
// this.llRoot.setOnClickListener {
|
this.llRoot.setBackgroundResource(R.drawable.grid_search_item_checked)
|
||||||
// Timber.d("itemClick ${item.name}, position = $position")
|
}
|
||||||
// checkedItem = item
|
|
||||||
// adapter.notifyDataSetChanged()
|
this.llRoot.setOnClickListener {
|
||||||
// }
|
Timber.d("itemClick ${item.foodName}, position = $position")
|
||||||
|
checkedItem = item
|
||||||
|
// viewModel.updateCurrentItem(item)
|
||||||
|
itemClickCallback(item)
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun dismiss() {
|
||||||
|
viewModel.cleanSearchFoodInfoList()
|
||||||
|
super.dismiss()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -27,16 +27,12 @@ class UserViewModel : BaseViewModel() {
|
|||||||
private val faceApi: FaceApi = FaceApi()
|
private val faceApi: FaceApi = FaceApi()
|
||||||
|
|
||||||
// 识别出的列表
|
// 识别出的列表
|
||||||
private val _identifiedFoodInfoList = MutableStateFlow<List<FoodInfo>?>(null)
|
private val _identifiedFoodInfoList = MutableStateFlow<List<FoodInfo>>(emptyList())
|
||||||
val identifiedFoodInfoList: StateFlow<List<FoodInfo>?> = _identifiedFoodInfoList
|
val identifiedFoodInfoList: StateFlow<List<FoodInfo>> = _identifiedFoodInfoList
|
||||||
|
|
||||||
// 搜索出的食物列表
|
// 搜索出的食物列表
|
||||||
private val _foodInfoList = MutableStateFlow<List<FoodInfo>?>(null)
|
private val _searchFoodInfoList = MutableStateFlow<List<FoodInfo>>(emptyList())
|
||||||
val foodInfoList: StateFlow<List<FoodInfo>?> = _foodInfoList
|
val searchFoodInfoList: StateFlow<List<FoodInfo>> = _searchFoodInfoList
|
||||||
|
|
||||||
// 当前食物信息
|
|
||||||
private val _currentFoodInfo = MutableStateFlow<FoodInfo?>(null)
|
|
||||||
val currentFoodInfo: StateFlow<FoodInfo?> = _currentFoodInfo
|
|
||||||
|
|
||||||
// 就餐数据
|
// 就餐数据
|
||||||
private val _nutritionData = MutableStateFlow<NutritionData?>(null)
|
private val _nutritionData = MutableStateFlow<NutritionData?>(null)
|
||||||
@@ -46,6 +42,7 @@ class UserViewModel : BaseViewModel() {
|
|||||||
* 获取token
|
* 获取token
|
||||||
*/
|
*/
|
||||||
fun getEquipmentToken(qrcodeId: String = "3ea47dc0-3cf0-3c2f-909c-265a9a65572e") {
|
fun getEquipmentToken(qrcodeId: String = "3ea47dc0-3cf0-3c2f-909c-265a9a65572e") {
|
||||||
|
Timber.d("getEquipmentToken")
|
||||||
launchWithLoading {
|
launchWithLoading {
|
||||||
val response = repository.getEquipmentToken(qrcodeId)
|
val response = repository.getEquipmentToken(qrcodeId)
|
||||||
if (parseResponse(response)) {
|
if (parseResponse(response)) {
|
||||||
@@ -131,35 +128,49 @@ class UserViewModel : BaseViewModel() {
|
|||||||
Timber.d("resetUserInfo")
|
Timber.d("resetUserInfo")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun identifiedFoodList() {
|
fun getIdentifiedFoodList() {
|
||||||
|
Timber.d("identifiedFoodList")
|
||||||
launchWithLoading {
|
launchWithLoading {
|
||||||
delay(5000)
|
delay(5000)
|
||||||
_identifiedFoodInfoList.value = List(6) {
|
_identifiedFoodInfoList.value = List(6) {
|
||||||
FoodInfo(
|
FoodInfo(
|
||||||
id = "185225109358109491${it}",
|
id = "185225109358109491${it}",
|
||||||
foodName = "豆芽炒粉条$it",
|
foodName = "豆芽炒粉条$it",
|
||||||
imgUrl = "http://101.201.149.156:9002/temp/20221119/62dc2d17-6853-4277-96ed-a4ef4c8654ea_1668840050258.png"
|
imgUrl = "http://101.201.149.156:9002/temp/20230912/f54b2d76-45e1-4c6b-a7a0-5fd1eace99d1_1694484601879.png"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
// _currentFoodInfo.value = _identifiedFoodInfoList.value[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun cleanIdentifiedFoodInfoList() {
|
||||||
|
Timber.d("cleanIdentifiedFoodInfoList")
|
||||||
|
_identifiedFoodInfoList.value = emptyList<FoodInfo>()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 搜索食物
|
* 搜索食物
|
||||||
*/
|
*/
|
||||||
fun searchByFoodName(foodName: String) {
|
fun searchByFoodName(foodName: String) {
|
||||||
|
Timber.d("searchByFoodName foodName = $foodName")
|
||||||
launchWithLoading {
|
launchWithLoading {
|
||||||
val response = repository.getRestInfoFoodsByType(foodName = foodName)
|
val response = repository.getRestInfoFoodsByType(foodName = foodName)
|
||||||
if (parseResponse(response)) {
|
if (parseResponse(response)) {
|
||||||
_foodInfoList.value = response.result
|
_searchFoodInfoList.value = response.result ?: emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun cleanSearchFoodInfoList() {
|
||||||
|
Timber.d("cleanSearchFoodInfoList")
|
||||||
|
_searchFoodInfoList.value = emptyList<FoodInfo>()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户就餐数据
|
* 获取用户就餐数据
|
||||||
*/
|
*/
|
||||||
fun getUserNutritionData(userId: String, foodId: String) {
|
fun getUserNutritionData(userId: String, foodId: String) {
|
||||||
|
Timber.d("getUserNutritionData userId = ${userId}, foodId = $foodId")
|
||||||
launchWithLoading {
|
launchWithLoading {
|
||||||
val response = repository.getUserNutritionData(userId = userId, foodId = foodId)
|
val response = repository.getUserNutritionData(userId = userId, foodId = foodId)
|
||||||
if (parseResponse(response)) {
|
if (parseResponse(response)) {
|
||||||
@@ -172,6 +183,7 @@ class UserViewModel : BaseViewModel() {
|
|||||||
* 提交就餐数据
|
* 提交就餐数据
|
||||||
*/
|
*/
|
||||||
fun postUserNutritionData(param: UserNutritionParam) {
|
fun postUserNutritionData(param: UserNutritionParam) {
|
||||||
|
Timber.d("postUserNutritionData param = $param")
|
||||||
launchWithLoading {
|
launchWithLoading {
|
||||||
val response = repository.postUserNutritionData(param)
|
val response = repository.postUserNutritionData(param)
|
||||||
if (parseResponse(response)) {
|
if (parseResponse(response)) {
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 952 KiB |
@@ -8,5 +8,5 @@
|
|||||||
android:topRightRadius="12dp" />
|
android:topRightRadius="12dp" />
|
||||||
<stroke
|
<stroke
|
||||||
android:width="2dp"
|
android:width="2dp"
|
||||||
android:color="#ffece7d0" />
|
android:color="#33343B" />
|
||||||
</shape>
|
</shape>
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 1.0 MiB |
@@ -5,4 +5,7 @@
|
|||||||
android:bottomRightRadius="12dp"
|
android:bottomRightRadius="12dp"
|
||||||
android:topLeftRadius="12dp"
|
android:topLeftRadius="12dp"
|
||||||
android:topRightRadius="12dp" />
|
android:topRightRadius="12dp" />
|
||||||
|
<stroke
|
||||||
|
android:color="#ECE7D0"
|
||||||
|
android:width="2dp" />
|
||||||
</shape>
|
</shape>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- 填充颜色 -->
|
||||||
|
<solid android:color="@color/black" />
|
||||||
|
<stroke
|
||||||
|
android:width="2dp"
|
||||||
|
android:color="#33343B" />
|
||||||
|
<!-- 矩形的圆角半径 -->
|
||||||
|
<corners android:radius="12dp" />
|
||||||
|
</shape>
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/main"
|
android:id="@+id/main"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -21,21 +20,22 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
android:padding="28dp"
|
|
||||||
android:background="@drawable/bg_detection_selector">
|
android:background="@drawable/bg_detection_selector">
|
||||||
|
|
||||||
<com.sw.dualscreen.view.CustomSpinner
|
<Spinner
|
||||||
android:id="@+id/spinner"
|
android:id="@+id/spinner"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:spinnerTextColor="#ffece7d0"
|
style="@style/customSpinnerStyle"
|
||||||
app:spinnerTextSize="26sp" />
|
android:layout_gravity="end"
|
||||||
|
android:padding="0dp"
|
||||||
|
android:popupBackground="@drawable/shape_for_custom_spinner" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tv_goods"
|
android:id="@+id/tv_food_name"
|
||||||
style="@style/text_title_blob"
|
style="@style/text_title_blob"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="531dp"
|
android:layout_height="531dp"
|
||||||
android:layout_marginHorizontal="60dp"
|
android:layout_marginHorizontal="60dp"
|
||||||
android:layout_marginTop="120dp"
|
android:layout_marginTop="110dp"
|
||||||
android:layout_marginBottom="90dp"
|
android:layout_marginBottom="90dp"
|
||||||
android:visibility="visible">
|
android:visibility="visible">
|
||||||
|
|
||||||
@@ -55,6 +55,13 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_img"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:contentDescription="食物图预览"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
android:background="@drawable/grid_item_checked"
|
android:background="@drawable/grid_item_checked"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingVertical="42dp">
|
android:paddingVertical="40dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tv_name"
|
android:id="@+id/tv_name"
|
||||||
@@ -16,7 +16,6 @@
|
|||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:textColor="#ff031127"
|
android:textColor="#ff031127"
|
||||||
android:textSize="36sp"
|
android:textSize="36sp" />
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="marquee"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingHorizontal="40dp"
|
||||||
|
android:paddingVertical="24dp"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:text="下拉列表项样式"
|
||||||
|
android:textColor="#ECE7D0"
|
||||||
|
android:textSize="26dp" />
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
android:background="@drawable/grid_search_item_checked"
|
android:background="@drawable/grid_search_item_checked"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingVertical="42dp">
|
android:paddingVertical="35dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tv_name"
|
android:id="@+id/tv_name"
|
||||||
@@ -16,7 +16,6 @@
|
|||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:textColor="#FF9900"
|
android:textColor="#FF9900"
|
||||||
android:textSize="30sp"
|
android:textSize="30sp" />
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
+12
-10
@@ -1,26 +1,28 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/cl_nutritionData"
|
||||||
android:layout_width="540dp"
|
android:layout_width="540dp"
|
||||||
android:layout_height="487dp"
|
android:layout_height="487dp"
|
||||||
android:background="@drawable/rounded_border_transparent"
|
android:visibility="visible"
|
||||||
android:padding="28dp">
|
android:padding="28dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tv_user_name"
|
android:id="@+id/tv_user_name"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="夏*秋"
|
tools:text="夏*秋"
|
||||||
android:textColor="#ffece7d0"
|
android:textColor="#ffece7d0"
|
||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tv_heat"
|
android:id="@+id/tv_recommend_heat"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="推荐热量:680kcal"
|
tools:text="推荐热量:680kcal"
|
||||||
android:textColor="#ffece7d0"
|
android:textColor="#ffece7d0"
|
||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
@@ -87,9 +89,9 @@
|
|||||||
<com.sw.dualscreen.view.CustomImageView
|
<com.sw.dualscreen.view.CustomImageView
|
||||||
android:id="@+id/iv_staple_food"
|
android:id="@+id/iv_staple_food"
|
||||||
android:layout_width="87dp"
|
android:layout_width="87dp"
|
||||||
android:src="#99CC99"
|
android:layout_height="100dp"
|
||||||
android:layout_gravity="bottom|center"
|
android:layout_gravity="bottom|center"
|
||||||
android:layout_height="100dp" />
|
android:src="#99CC99" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@@ -106,8 +108,8 @@
|
|||||||
<com.sw.dualscreen.view.CustomImageView
|
<com.sw.dualscreen.view.CustomImageView
|
||||||
android:id="@+id/iv_fruits_vegetables"
|
android:id="@+id/iv_fruits_vegetables"
|
||||||
android:layout_width="87dp"
|
android:layout_width="87dp"
|
||||||
android:layout_gravity="bottom|center"
|
android:layout_height="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_gravity="bottom|center" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@@ -124,8 +126,8 @@
|
|||||||
<com.sw.dualscreen.view.CustomImageView
|
<com.sw.dualscreen.view.CustomImageView
|
||||||
android:id="@+id/iv_meat_eggs"
|
android:id="@+id/iv_meat_eggs"
|
||||||
android:layout_width="87dp"
|
android:layout_width="87dp"
|
||||||
android:layout_gravity="bottom|center"
|
android:layout_height="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_gravity="bottom|center" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/main"
|
android:id="@+id/main"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -9,35 +9,7 @@
|
|||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
tools:context=".activity.MainActivity">
|
tools:context=".activity.MainActivity">
|
||||||
|
|
||||||
<LinearLayout
|
<!--人脸识别-->
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingHorizontal="80dp"
|
|
||||||
android:paddingTop="80dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="133dp"
|
|
||||||
android:background="@drawable/rounded_border_transparent"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:visibility="visible">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tv_goods"
|
|
||||||
style="@style/text_title_blob_2"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="羊肉臊子荞面饸饹" />
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginTop="40dp"
|
|
||||||
android:visibility="visible">
|
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/fl_face"
|
android:id="@+id/fl_face"
|
||||||
android:layout_width="376dp"
|
android:layout_width="376dp"
|
||||||
@@ -64,15 +36,38 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:background="@drawable/bg_avatar"
|
android:background="@drawable/bg_face"
|
||||||
android:visibility="gone" />
|
android:contentDescription="相机遮盖层"
|
||||||
|
android:visibility="visible" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="80dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="133dp"
|
||||||
|
android:background="@drawable/rounded_border_transparent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="visible">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_food_info"
|
||||||
|
style="@style/text_title_blob_2"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:text="羊肉臊子荞面饸饹" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tv_face_tip"
|
android:id="@+id/tv_face_tip"
|
||||||
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="376dp"
|
android:layout_marginTop="566dp"
|
||||||
android:text="请正视屏幕"
|
android:text="请正视屏幕"
|
||||||
android:textColor="#ffece7d0"
|
android:textColor="#ffece7d0"
|
||||||
android:textSize="24sp"
|
android:textSize="24sp"
|
||||||
@@ -82,21 +77,21 @@
|
|||||||
android:id="@+id/fl_preview"
|
android:id="@+id/fl_preview"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="420dp"
|
android:layout_height="420dp"
|
||||||
android:visibility="visible"
|
android:layout_marginTop="174dp"
|
||||||
android:paddingHorizontal="80dp">
|
android:visibility="visible">
|
||||||
|
|
||||||
<androidx.camera.view.PreviewView
|
<!-- <androidx.camera.view.PreviewView-->
|
||||||
android:id="@+id/previewView"
|
<!-- android:id="@+id/previewView"-->
|
||||||
android:visibility="gone"
|
<!-- android:layout_width="match_parent"-->
|
||||||
android:layout_width="match_parent"
|
<!-- android:layout_height="match_parent"-->
|
||||||
android:layout_height="match_parent" />
|
<!-- android:visibility="gone" />-->
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/iv_img"
|
android:id="@+id/iv_img"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:visibility="visible"
|
android:contentDescription="img"
|
||||||
android:contentDescription="img" />
|
android:visibility="visible" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -105,16 +100,10 @@
|
|||||||
android:background="@drawable/preview_border" />
|
android:background="@drawable/preview_border" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="bottom"
|
|
||||||
android:paddingHorizontal="80dp"
|
|
||||||
android:paddingBottom="80dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="486dp"
|
android:layout_height="486dp"
|
||||||
|
android:layout_marginTop="633dp"
|
||||||
android:background="@drawable/rounded_border_transparent"
|
android:background="@drawable/rounded_border_transparent"
|
||||||
android:gravity="center">
|
android:gravity="center">
|
||||||
|
|
||||||
@@ -124,13 +113,11 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="先识别 后取餐"
|
android:text="先识别 后取餐"
|
||||||
android:visibility="gone" />
|
android:visibility="visible" />
|
||||||
|
|
||||||
<include
|
<include
|
||||||
android:id="@+id/include_user_heat"
|
android:id="@+id/include_user_nutrition"
|
||||||
layout="@layout/item_user_heat"
|
layout="@layout/item_user_nutrition" />
|
||||||
android:visibility="visible" />
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
</LinearLayout>
|
</FrameLayout>
|
||||||
@@ -7,4 +7,9 @@
|
|||||||
<color name="beige_ECE7D0">#ECE7D0</color>
|
<color name="beige_ECE7D0">#ECE7D0</color>
|
||||||
<color name="blue_6498DD">#6498DD</color>
|
<color name="blue_6498DD">#6498DD</color>
|
||||||
<color name="black_031127">#031127</color>
|
<color name="black_031127">#031127</color>
|
||||||
|
|
||||||
|
<color name="identify_checked">#031127</color>
|
||||||
|
<color name="identify_normal">#6498DD</color>
|
||||||
|
<color name="search_checked">#FF9900</color>
|
||||||
|
<color name="search_normal">#666666</color>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -60,4 +60,10 @@
|
|||||||
<item name="android:textSize">26sp</item>
|
<item name="android:textSize">26sp</item>
|
||||||
<item name="android:textColor">#ffece7d0</item>
|
<item name="android:textColor">#ffece7d0</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="customSpinnerStyle" parent="android:Widget.ListView.DropDown">
|
||||||
|
<item name="android:textAlignment">center</item>
|
||||||
|
<item name="divider">@color/white</item>
|
||||||
|
<item name="android:dividerHeight">1dp</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "8.10.1"
|
agp = "8.10.1"
|
||||||
|
glide = "4.16.0"
|
||||||
kotlin = "2.0.21"
|
kotlin = "2.0.21"
|
||||||
coreKtx = "1.10.1"
|
coreKtx = "1.10.1"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
@@ -24,6 +25,7 @@ lifecycleViewmodelKtx = "2.8.3"
|
|||||||
lifecycleRuntimeKtx = "2.8.3"
|
lifecycleRuntimeKtx = "2.8.3"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
|
||||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||||
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
||||||
|
|||||||
@@ -422,7 +422,7 @@ public class FaceHelper implements FaceListener {
|
|||||||
if (!contained) {
|
if (!contained) {
|
||||||
RecognizeInfo recognizeInfo = recognizeInfoMap.remove(key);
|
RecognizeInfo recognizeInfo = recognizeInfoMap.remove(key);
|
||||||
if (recognizeInfo != null) {
|
if (recognizeInfo != null) {
|
||||||
recognizeCallback.onNoticeChanged("");
|
recognizeCallback.onNoticeChanged("leave");
|
||||||
// 人脸离开时,通知特征提取线程,避免一直等待活体结果
|
// 人脸离开时,通知特征提取线程,避免一直等待活体结果
|
||||||
synchronized (recognizeInfo.getWaitLock()) {
|
synchronized (recognizeInfo.getWaitLock()) {
|
||||||
recognizeInfo.getWaitLock().notifyAll();
|
recognizeInfo.getWaitLock().notifyAll();
|
||||||
|
|||||||
Reference in New Issue
Block a user