diff --git a/app/src/main/java/com/sw/dualscreen/GlobalData.kt b/app/src/main/java/com/sw/dualscreen/GlobalData.kt index 43aefb4..8a63be6 100644 --- a/app/src/main/java/com/sw/dualscreen/GlobalData.kt +++ b/app/src/main/java/com/sw/dualscreen/GlobalData.kt @@ -39,7 +39,7 @@ object GlobalData { var appId = "Hkz1rBk6PZXbS8KwKr67K2eZtsz8bRoMHLg64bUdgCZj" var sdkKey = "AabXs3sHM8UhhE7oCGf4LVMLrdkGb1nJNksbjjTdVt7k" - var activeKey = "085F-118G-Q3LH-HPGZ"//2号楼"085F-118G-Q3AB-1WVJ" 5号楼"085F-118G-Q3LH-HPGZ" + var activeKey = "085F-118G-Q3NU-134V"//2号楼"085F-118G-Q3AB-1WVJ" 5号楼"085F-118G-Q3LH-HPGZ" } /** diff --git a/app/src/main/java/com/sw/dualscreen/activity/BaseActivity.kt b/app/src/main/java/com/sw/dualscreen/activity/BaseActivity.kt index a9773ad..bbd4ae2 100644 --- a/app/src/main/java/com/sw/dualscreen/activity/BaseActivity.kt +++ b/app/src/main/java/com/sw/dualscreen/activity/BaseActivity.kt @@ -2,6 +2,8 @@ package com.sw.dualscreen.activity import android.app.Dialog import android.content.Context +import android.content.res.Configuration +import android.content.res.Resources import android.os.Bundle import android.text.TextUtils import android.view.View @@ -19,6 +21,7 @@ import com.sw.dualscreen.viewmodel.BaseViewModel import kotlinx.coroutines.launch import timber.log.Timber + abstract class BaseActivity : AppCompatActivity() { protected lateinit var binding: VB protected lateinit var context: Context diff --git a/app/src/main/java/com/sw/dualscreen/activity/CollectedFoodActivity.kt b/app/src/main/java/com/sw/dualscreen/activity/CollectedFoodActivity.kt index 32687ff..f54cc28 100644 --- a/app/src/main/java/com/sw/dualscreen/activity/CollectedFoodActivity.kt +++ b/app/src/main/java/com/sw/dualscreen/activity/CollectedFoodActivity.kt @@ -85,12 +85,21 @@ class CollectedFoodActivity : BaseActivity() { box = ObjectBox.boxStore.boxFor(Food::class) } list.clear() - var queryList = box?.all?.distinctBy { it.name } + val totalList = box?.all + ?.filter { it.name!=null } + ?.groupBy { it.name!! } + ?.map { CollectedFoodBean(foodName = it.key, count = it.value.size) } + //var queryMap:Map> ?= null if (searchName.isNullOrBlank().not()) { - queryList = queryList?.filter { it.name?.contains(searchName) == true } - } - queryList?.forEach { - list.add(CollectedFoodBean(foodName = it.name)) + //queryMap = totalMap?.filter { it.key.contains(searchName) } + val temp = totalList?.filter { it.foodName?.contains(searchName) == true} + if (temp.isNullOrEmpty().not()) { + list.addAll(temp) + } + } else { + if (totalList.isNullOrEmpty().not()) { + list.addAll(totalList) + } } adapter.notifyDataSetChanged() if (list.isEmpty()) { diff --git a/app/src/main/java/com/sw/dualscreen/activity/MainActivity.kt b/app/src/main/java/com/sw/dualscreen/activity/MainActivity.kt index ed9cae1..e15e81a 100644 --- a/app/src/main/java/com/sw/dualscreen/activity/MainActivity.kt +++ b/app/src/main/java/com/sw/dualscreen/activity/MainActivity.kt @@ -4,15 +4,13 @@ import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.graphics.Outline -import android.graphics.Typeface import android.hardware.display.DisplayManager import android.net.Uri +import android.os.Handler +import android.os.Looper import android.text.TextUtils import android.view.View import android.view.ViewOutlineProvider -import android.widget.AdapterView -import android.widget.ArrayAdapter -import android.widget.Toast import androidx.activity.viewModels import androidx.camera.core.CameraSelector import androidx.camera.core.ImageAnalysis @@ -21,28 +19,19 @@ import androidx.camera.core.ImageCaptureException import androidx.camera.core.Preview import androidx.camera.lifecycle.ProcessCameraProvider import androidx.core.content.ContextCompat -import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.GridLayoutManager import com.google.common.util.concurrent.ListenableFuture -import com.sw.dualscreen.GlobalData import com.sw.dualscreen.GlobalKey -import com.sw.dualscreen.R -import com.sw.dualscreen.adapter.GenericItemAdapter -import com.sw.dualscreen.adapter.GridSpacingItemDecoration import com.sw.dualscreen.adapter.SearchFoodAdapter -import com.sw.dualscreen.adapter.dpToPx import com.sw.dualscreen.databinding.ActivityMainBinding -import com.sw.dualscreen.databinding.ItemFoodInfoBinding import com.sw.dualscreen.ext.dp -import com.sw.dualscreen.ext.format2String +import com.sw.dualscreen.ext.gone import com.sw.dualscreen.ext.visible import com.sw.dualscreen.model.response.FoodInfo import com.sw.dualscreen.objbox.FoodModule import com.sw.dualscreen.objbox.FoodModule.IdNameScore -import com.sw.dualscreen.presentation.SecondaryScreenPresentation +import com.sw.dualscreen.presentation.MainScreenPresentation import com.sw.dualscreen.sdk.SensorScaleUtils -import com.sw.dualscreen.utils.AssetsTool -import com.sw.dualscreen.utils.BitmapCropper import com.sw.dualscreen.utils.BitmapSaver import com.sw.dualscreen.utils.Debouncer import com.sw.dualscreen.utils.GlideUtils @@ -55,7 +44,6 @@ import com.sw.dualscreen.viewmodel.UserViewModel import com.sw.plate.utils.LightManager import com.sw.plate.utils.ToastUtils import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel -import kotlinx.coroutines.launch import timber.log.Timber import java.io.File import java.time.LocalDateTime @@ -68,12 +56,20 @@ import kotlin.math.roundToInt /** * 服务员显示界面 */ +@SuppressLint("NotifyDataSetChanged") class MainActivity : BaseActivity() { + + companion object { + const val TIME_OUT = 5 * 60 * 1000L + } + private val viewModel by viewModels() private var imageCapture: ImageCapture? = null private var isAnalyzing = true // 控制是否进行图像分析 - private var presentation: SecondaryScreenPresentation? = null +// private var presentation: SecondaryScreenPresentation? = null + private var presentation: MainScreenPresentation? = null private val executor = Executors.newSingleThreadExecutor() + private val handler by lazy { Handler(Looper.getMainLooper()) } private val searchFoodList = mutableListOf() private val adapter by lazy { SearchFoodAdapter(searchFoodList).apply { @@ -86,8 +82,7 @@ class MainActivity : BaseActivity() { item.photoUri = null checkedItem = item notifyDataSetChanged() - // TODO 暂时注释--------------- - //updateCurrentFood(item) + updateCurrentFood(item) } } } @@ -111,19 +106,27 @@ class MainActivity : BaseActivity() { override fun initialize() { super.initialize() + FoodModule.init(this) initView() setupSecondaryDisplay() setupCamera() initData() - FoodModule.init(this) } private fun initData() { viewModel.getEquipmentToken() - presentation?.step1() +// presentation?.step1() + presentation?.step1FoodRecognizing() } private fun initView() { +// // 在Activity中获取DPI +// val metrics = getResources()?.displayMetrics +// val screenDPI = metrics?.densityDpi +// val density = metrics?.density +// +// Log.d("ScreenInfo", "DPI: $screenDPI, density: $density"); + viewModel.activeEngine() updateDateTime() binding.ivSetting.setOnClickListener { @@ -143,8 +146,13 @@ class MainActivity : BaseActivity() { binding.previewView.clipToOutline = true binding.tvToSearch.setOnClickListener { bottomSheetDialog = CustomBottomSheetDialog.newInstance(viewModel) { + checkedItem = it it.photoUri = null updateCurrentFood(it) + searchFoodList.forEachIndexed { index, info -> + searchFoodList[index].isChecked = false + } + adapter.notifyDataSetChanged() } bottomSheetDialog!!.show(supportFragmentManager, "CustomBottomSheetDialog") } @@ -155,9 +163,9 @@ class MainActivity : BaseActivity() { // arrayOf("即放即取", "余量取餐") //) //binding.spinner.adapter = spinnerAdapter - val mode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) - //binding.spinner.setSelection(mode ?: 0) - presentation?.updateMealPickupMode(mode ?: 0) +// val mode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) +// //binding.spinner.setSelection(mode ?: 0) +// presentation?.updateMealPickupMode(mode ?: 0) //binding.spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { // override fun onItemSelected( // parent: AdapterView<*>?, @@ -196,6 +204,7 @@ class MainActivity : BaseActivity() { debouncer.debounce { recognizeFood() } } binding.btnPay.setOnClickListener { + presentation?.dismiss() startActivity(Intent(this, PayActivity::class.java).apply { putExtra(PayActivity.FOOD_INFO, checkedItem) }) @@ -233,38 +242,26 @@ class MainActivity : BaseActivity() { override fun registerDataChange() { super.registerDataChange() +//// lifecycleScope.launch { +//// viewModel.identifiedFoodInfoList2.collect { list -> +//// updateFoodInfo(list) +//// } +//// } // lifecycleScope.launch { -// viewModel.identifiedFoodInfoList2.collect { list -> -// updateFoodInfo(list) +// viewModel.identifiedFoodInfoList.collect { list -> +// //adapter.updateData(list) +// searchFoodList.clear() +// searchFoodList.addAll(list) +// adapter.notifyDataSetChanged() +// +// if (list.isNotEmpty()) { +// checkedItem = list[0] +// checkedItem!!.photoUri = lastPhotoUri +// updateCurrentFood(checkedItem) +// lastPhotoUri = null +// } // } // } - lifecycleScope.launch { - viewModel.identifiedFoodInfoList.collect { list -> - //adapter.updateData(list) - searchFoodList.clear() - searchFoodList.addAll(list) - adapter.notifyDataSetChanged() - - //TODO 暂时写死数据测试--------------------------- - binding.flPay.visible() - binding.recyclerview.visible() - searchFoodList.clear() - searchFoodList.add(FoodInfo(foodName = "岐山臊子面")) - searchFoodList.add(FoodInfo(foodName = "肉末干拌面")) - searchFoodList.add(FoodInfo(foodName = "雪菜肉丝面")) - searchFoodList.add(FoodInfo(foodName = "康帅博方便面")) - searchFoodList.add(FoodInfo(foodName = "担担面")) - searchFoodList.add(FoodInfo(foodName = "四合一干拌面")) - adapter.notifyDataSetChanged() - - if (list.isNotEmpty()) { - checkedItem = list[0] - checkedItem!!.photoUri = lastPhotoUri - updateCurrentFood(checkedItem) - lastPhotoUri = null - } - } - } SensorScaleUtils.addWeightListener { weight -> Timber.d("registerDataChange weight = $weight") @@ -286,7 +283,7 @@ class MainActivity : BaseActivity() { } } - private fun recognizeFood() { + fun recognizeFood() { LightManager.openRedLight() LightManager.openGreenLight() takePhoto { photoUri -> @@ -325,7 +322,7 @@ class MainActivity : BaseActivity() { binding.layoutRescan.visibility = View.GONE viewModel.getFoodInfo(foodName) { list -> runOnUiThread { - updateFoodInfo(list, scoreList) + updateFoodInfo(list.toMutableList(), scoreList) } } } @@ -335,8 +332,22 @@ class MainActivity : BaseActivity() { } @SuppressLint("NotifyDataSetChanged") - private fun updateFoodInfo(list: List, scoreList: List) { - Timber.d("registerDataChange识别后查询接口数据:${GsonUtils.toJson(list)}") + private fun updateFoodInfo(list: MutableList, scoreList: List) { + Timber.d("registerDataChange识别后查询接口数据:${GsonUtils.toJson(list)},识别数据:${GsonUtils.toJson(scoreList)}") + // TODO: 临时测试数据,后续删除------------------- + //if (list.isEmpty()) { + // //binding.flPay.visible() + // //binding.recyclerview.visible() + // //list.add(FoodInfo(foodName = "豆角茄子")) + // //list.add(FoodInfo(foodName = "豆芽炒粉条")) + // //list.add(FoodInfo(foodName = "岐山臊子面")) + // //list.add(FoodInfo(foodName = "肉末干拌面")) + // //list.add(FoodInfo(foodName = "雪菜肉丝面")) + // //list.add(FoodInfo(foodName = "康帅博方便面")) + // //list.add(FoodInfo(foodName = "担担面")) + // //list.add(FoodInfo(foodName = "四合一干拌面")) + //} + // TODO: 临时测试数据,后续删除------------------- list.forEach { foodInfo -> val scoreItem = scoreList.firstOrNull { it.name == foodInfo.foodName } val score = scoreItem?.score ?: 0.0 @@ -349,9 +360,17 @@ class MainActivity : BaseActivity() { if (list.isNotEmpty()) { checkedItem = list[0] checkedItem!!.photoUri = lastPhotoUri + checkedItem!!.isChecked = true + adapter.notifyItemChanged(0) updateCurrentFood(checkedItem) lastPhotoUri = null LightManager.closeRedLight() + + binding.flPay.visible() + binding.tvToSearch.visible() + } else { + binding.flPay.gone() + binding.tvToSearch.gone() } } @@ -532,21 +551,34 @@ class MainActivity : BaseActivity() { if (displays.size > 1) { val secondaryDisplay = displays[1] // 创建副屏的Presentation - presentation = - SecondaryScreenPresentation( - activity = this, - this, - display = secondaryDisplay, - viewModel = viewModel, - recognizeViewModel = viewModels().value - ) - presentation!!.setStepChangeCallback { step -> - if (step == 1) { - Timber.d("setStepChangeCallback${step}") - updateCurrentFood(null) - bottomSheetDialog?.dismiss() + presentation = MainScreenPresentation( + activity = this, + display = secondaryDisplay, + userViewModel = viewModel, + recognizeViewModel = viewModels().value + ).apply { + setStepChangeCallback { step -> + if (step == 1) { + Timber.d("setStepChangeCallback${step}") + updateCurrentFood(null) + bottomSheetDialog?.dismiss() + } } } + //SecondaryScreenPresentation( + // activity = this, + // this, + // display = secondaryDisplay, + // viewModel = viewModel, + // recognizeViewModel = viewModels().value + //) +// presentation!!.setStepChangeCallback { step -> +// if (step == 1) { +// Timber.d("setStepChangeCallback${step}") +// updateCurrentFood(null) +// bottomSheetDialog?.dismiss() +// } +// } presentation!!.show() } else { ToastUtils.showToast("获取副屏失败") @@ -568,9 +600,49 @@ class MainActivity : BaseActivity() { } private fun updateDateTime() { - val formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 EEEE", Locale.CHINA) + val formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 E", Locale.CHINA) val dateTime = formatter.format(LocalDateTime.now(ZoneId.of("Asia/Shanghai"))) binding.tvTitleTime.text = dateTime } + override fun onResume() { + super.onResume() + // 启动定时器 + handler.postDelayed(timeoutRunnable, TIME_OUT) + + if (presentation?.isShowing == false) { + setupSecondaryDisplay() + } + val mode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) + presentation?.updateMealPickupMode(mode ?: 0) + } + override fun onPause() { + super.onPause() + // 取消定时器 + handler.removeCallbacks(timeoutRunnable) + } + + private var lastTouchTime = 0L + private val timeoutRunnable = Runnable { + //显示待机页面 + presentation?.loadStandbyScreen() + } + + override fun onUserInteraction() { + super.onUserInteraction() + if (System.currentTimeMillis() - lastTouchTime >= TIME_OUT) { + //隐藏待机页面 + presentation?.hideStandbyScreen() + } + // 重置定时器 + handler.removeCallbacks(timeoutRunnable) + handler.postDelayed(timeoutRunnable, TIME_OUT) + lastTouchTime = System.currentTimeMillis() + } + + fun resetTouchTime() { + handler.removeCallbacks(timeoutRunnable) + handler.postDelayed(timeoutRunnable, TIME_OUT) + lastTouchTime = System.currentTimeMillis() + } } \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/activity/PayActivity.kt b/app/src/main/java/com/sw/dualscreen/activity/PayActivity.kt index ed19bae..4b3fe1e 100644 --- a/app/src/main/java/com/sw/dualscreen/activity/PayActivity.kt +++ b/app/src/main/java/com/sw/dualscreen/activity/PayActivity.kt @@ -4,15 +4,16 @@ import android.view.View import androidx.activity.viewModels import androidx.fragment.app.Fragment import com.sw.dualscreen.R -import com.sw.dualscreen.activity.fragment.CashPayFragment -import com.sw.dualscreen.activity.fragment.FacePayFragment -import com.sw.dualscreen.activity.fragment.NumberPayFragment -import com.sw.dualscreen.activity.fragment.PayResultFragment -import com.sw.dualscreen.activity.fragment.QrCodePayFragment +import com.sw.dualscreen.activity.fragment.pay.CashPayFragment +import com.sw.dualscreen.activity.fragment.pay.FacePayFragment +import com.sw.dualscreen.activity.fragment.pay.NumberPayFragment +import com.sw.dualscreen.activity.fragment.pay.PayResultFragment +import com.sw.dualscreen.activity.fragment.pay.QrCodePayFragment import com.sw.dualscreen.databinding.ActivityPayBinding import com.sw.dualscreen.model.response.FoodInfo import com.sw.dualscreen.viewmodel.BaseViewModel import com.sw.dualscreen.viewmodel.UserViewModel +import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel class PayActivity : BaseActivity() { @@ -26,10 +27,11 @@ class PayActivity : BaseActivity() { const val TAG_PAY_RESULT = "tagPayResult" } - private val viewModel by viewModels() + val userViewModel by viewModels() + val recognizeViewModel by viewModels() override fun getViewModel(): BaseViewModel { - return viewModel + return userViewModel } override fun inflateViewBinding(): ActivityPayBinding { @@ -125,4 +127,12 @@ class PayActivity : BaseActivity() { addFragment(payResultFragment!!, TAG_PAY_RESULT) } + override fun onDestroy() { + qrCodePayFragment?.presentation?.dismiss() + cashPayFragment?.presentation?.dismiss() + numberPayFragment?.presentation?.dismiss() + facePayFragment?.presentation?.dismiss() + super.onDestroy() + } + } \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/activity/fragment/BaseFragment.kt b/app/src/main/java/com/sw/dualscreen/activity/fragment/BaseFragment.kt index 4559989..377f9ac 100644 --- a/app/src/main/java/com/sw/dualscreen/activity/fragment/BaseFragment.kt +++ b/app/src/main/java/com/sw/dualscreen/activity/fragment/BaseFragment.kt @@ -1,6 +1,9 @@ package com.sw.dualscreen.activity.fragment +import android.content.Context +import android.hardware.display.DisplayManager import android.os.Bundle +import android.view.Display import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -9,6 +12,10 @@ import androidx.viewbinding.ViewBinding abstract class BaseFragment: Fragment() { + val displays: Array by lazy { + val displayManager = requireActivity().getSystemService(Context.DISPLAY_SERVICE) as DisplayManager + displayManager.displays + } protected lateinit var binding: VB protected abstract fun inflateViewBinding(): VB diff --git a/app/src/main/java/com/sw/dualscreen/activity/fragment/BusinessFragment.kt b/app/src/main/java/com/sw/dualscreen/activity/fragment/BusinessFragment.kt index 1144834..73310fd 100644 --- a/app/src/main/java/com/sw/dualscreen/activity/fragment/BusinessFragment.kt +++ b/app/src/main/java/com/sw/dualscreen/activity/fragment/BusinessFragment.kt @@ -6,6 +6,7 @@ import com.sw.dualscreen.R import com.sw.dualscreen.databinding.FragmentBusinessBinding import com.sw.dualscreen.sdk.SensorScaleUtils import com.sw.dualscreen.utils.SPUtil +import com.sw.plate.utils.ToastUtils import kotlin.math.roundToInt class BusinessFragment: BaseFragment() { @@ -34,6 +35,21 @@ class BusinessFragment: BaseFragment() { binding.btnClearZero.setOnClickListener { SensorScaleUtils.tare() } + binding.btnConfirm.setOnClickListener { + if (binding.rgCharge.checkedRadioButtonId == -1) { + ToastUtils.showToast("请设置计费模式") + return@setOnClickListener + } + if (binding.rgTakeFood.checkedRadioButtonId == -1) { + ToastUtils.showToast("请设置取餐模式") + return@setOnClickListener + } + var mode = if (binding.rgCharge.checkedRadioButtonId == R.id.rbChargeYes) 0 else 1 + SPUtil.getInstance().put(GlobalKey.KEY_CHARGE_MODE, mode) + + mode = if (binding.rgTakeFood.checkedRadioButtonId == R.id.rbPickAndPlace) 0 else 1 + SPUtil.getInstance().put(GlobalKey.KEY_PICKUP_MODE, mode) + } SensorScaleUtils.addWeightListener { value -> val realWeight = (value * 1000).roundToInt() binding.tvFoodWeight.text = "$realWeight" diff --git a/app/src/main/java/com/sw/dualscreen/activity/fragment/CashPayFragment.kt b/app/src/main/java/com/sw/dualscreen/activity/fragment/CashPayFragment.kt deleted file mode 100644 index 64e5b32..0000000 --- a/app/src/main/java/com/sw/dualscreen/activity/fragment/CashPayFragment.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.sw.dualscreen.activity.fragment - -import android.text.Spanned -import android.text.SpannedString -import android.text.style.AbsoluteSizeSpan -import androidx.core.text.buildSpannedString -import com.sw.dualscreen.databinding.FragmentCashPayBinding - -class CashPayFragment: BaseFragment() { - - override fun inflateViewBinding(): FragmentCashPayBinding { - return FragmentCashPayBinding.inflate(layoutInflater) - } - - override fun initialize() { - binding.tvRealAmount.text = getAmountText("36.8") - } - - private fun getAmountText(amount:String): SpannedString { - return buildSpannedString { - append("¥", AbsoluteSizeSpan(36, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - append(amount, AbsoluteSizeSpan(60, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/activity/fragment/CollectFragment.kt b/app/src/main/java/com/sw/dualscreen/activity/fragment/CollectFragment.kt index 4db19cd..12c909b 100644 --- a/app/src/main/java/com/sw/dualscreen/activity/fragment/CollectFragment.kt +++ b/app/src/main/java/com/sw/dualscreen/activity/fragment/CollectFragment.kt @@ -60,6 +60,7 @@ class CollectFragment : BaseFragment() { setOnItemClickListener { adapter, view, position -> checkedItem = searchFoodList[position] selectedFoodId = checkedItem!!.id + selectedFoodName = checkedItem!!.foodName val isChecked = checkedItem!!.isChecked searchFoodList[position].isChecked = isChecked.not() notifyItemChanged(position) diff --git a/app/src/main/java/com/sw/dualscreen/activity/fragment/FacePayFragment.kt b/app/src/main/java/com/sw/dualscreen/activity/fragment/FacePayFragment.kt deleted file mode 100644 index f89df12..0000000 --- a/app/src/main/java/com/sw/dualscreen/activity/fragment/FacePayFragment.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.sw.dualscreen.activity.fragment - -import com.sw.dualscreen.activity.PayActivity -import com.sw.dualscreen.databinding.FragmentFacePayBinding - -class FacePayFragment : BaseFragment() { - - override fun inflateViewBinding(): FragmentFacePayBinding { - return FragmentFacePayBinding.inflate(layoutInflater) - } - - private var payActivity: PayActivity? = null - override fun initialize() { - payActivity = activity as PayActivity - binding.tvFaceState.setOnClickListener { - payActivity?.showPayResult() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/activity/fragment/NumberPayFragment.kt b/app/src/main/java/com/sw/dualscreen/activity/fragment/NumberPayFragment.kt deleted file mode 100644 index a8ab754..0000000 --- a/app/src/main/java/com/sw/dualscreen/activity/fragment/NumberPayFragment.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.sw.dualscreen.activity.fragment - -import com.sw.dualscreen.databinding.FragmentNumberPayBinding -import com.sw.dualscreen.ext.hideKeyboard - -class NumberPayFragment: BaseFragment() { - - override fun inflateViewBinding(): FragmentNumberPayBinding { - return FragmentNumberPayBinding.inflate(layoutInflater) - } - - override fun initialize() { - binding.root.hideKeyboard() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/activity/fragment/QrCodePayFragment.kt b/app/src/main/java/com/sw/dualscreen/activity/fragment/QrCodePayFragment.kt deleted file mode 100644 index f19b823..0000000 --- a/app/src/main/java/com/sw/dualscreen/activity/fragment/QrCodePayFragment.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.sw.dualscreen.activity.fragment - -import android.text.Spanned -import android.text.SpannedString -import android.text.style.AbsoluteSizeSpan -import androidx.core.text.buildSpannedString -import com.sw.dualscreen.R -import com.sw.dualscreen.databinding.FragmentQrcodePayBinding - -class QrCodePayFragment: BaseFragment() { - - override fun inflateViewBinding(): FragmentQrcodePayBinding { - return FragmentQrcodePayBinding.inflate(layoutInflater) - } - - override fun initialize() { - binding.tvRealAmount.text = getAmountText("36.8") - binding.ivPayQrCode.setImageResource(R.drawable.ic_qrcode) - binding.tvPayResult.text = "待支付(60s)..." - } - - private fun getAmountText(amount:String): SpannedString { - return buildSpannedString { - append("¥", AbsoluteSizeSpan(36, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - append(amount, AbsoluteSizeSpan(60, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/activity/fragment/pay/CashPayFragment.kt b/app/src/main/java/com/sw/dualscreen/activity/fragment/pay/CashPayFragment.kt new file mode 100644 index 0000000..bb2d57e --- /dev/null +++ b/app/src/main/java/com/sw/dualscreen/activity/fragment/pay/CashPayFragment.kt @@ -0,0 +1,63 @@ +package com.sw.dualscreen.activity.fragment.pay + +import android.text.Spanned +import android.text.SpannedString +import android.text.style.AbsoluteSizeSpan +import androidx.core.text.buildSpannedString +import com.sw.dualscreen.activity.PayActivity +import com.sw.dualscreen.activity.fragment.BaseFragment +import com.sw.dualscreen.databinding.FragmentCashPayBinding +import com.sw.dualscreen.presentation.pay.CashPayPresentation + +class CashPayFragment : BaseFragment() { + + private lateinit var payActivity: PayActivity + private var foodName: String? = null + private var payAmount: String? = null + + var presentation: CashPayPresentation? = null + + override fun inflateViewBinding(): FragmentCashPayBinding { + return FragmentCashPayBinding.inflate(layoutInflater) + } + + override fun initialize() { + payActivity = requireActivity() as PayActivity + + foodName = payActivity.foodInfo?.foodName + payAmount = "36.80" + binding.tvRealAmount.text = getAmountText(payAmount?:"") + + showSubScreen() + } + override fun onHiddenChanged(hidden: Boolean) { + super.onHiddenChanged(hidden) + if (hidden) { + binding.root.postDelayed({ + presentation?.dismiss() + }, 500) + return + } + showSubScreen() + } + + private fun showSubScreen() { + // 查找副屏(通常索引为1) + if (displays.size > 1) { + presentation = CashPayPresentation(activity = payActivity, display = displays[1]) { + presentation?.dismiss() + }.also { + it.foodName = foodName + it.payAmount = payAmount + } + presentation?.show() + } + } + + private fun getAmountText(amount: String): SpannedString { + return buildSpannedString { + append("¥", AbsoluteSizeSpan(36, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + append(amount, AbsoluteSizeSpan(60, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/activity/fragment/pay/FacePayFragment.kt b/app/src/main/java/com/sw/dualscreen/activity/fragment/pay/FacePayFragment.kt new file mode 100644 index 0000000..8785926 --- /dev/null +++ b/app/src/main/java/com/sw/dualscreen/activity/fragment/pay/FacePayFragment.kt @@ -0,0 +1,57 @@ +package com.sw.dualscreen.activity.fragment.pay + +import com.sw.dualscreen.activity.PayActivity +import com.sw.dualscreen.activity.fragment.BaseFragment +import com.sw.dualscreen.databinding.FragmentFacePayBinding +import com.sw.dualscreen.presentation.pay.CashPayPresentation +import com.sw.dualscreen.presentation.pay.FacePayPresentation + +class FacePayFragment : BaseFragment() { + + private var payActivity: PayActivity? = null + private var foodName: String? = null + private var payAmount: String? = null + var presentation: FacePayPresentation? = null + override fun inflateViewBinding(): FragmentFacePayBinding { + return FragmentFacePayBinding.inflate(layoutInflater) + } + + override fun initialize() { + payActivity = activity as PayActivity + binding.tvFaceState.setOnClickListener { + payActivity?.showPayResult() + } + foodName = payActivity?.foodInfo?.foodName + payAmount = "36.80" + showSubScreen() + } + + override fun onHiddenChanged(hidden: Boolean) { + super.onHiddenChanged(hidden) + if (hidden) { + binding.root.postDelayed({ + presentation?.dismiss() + }, 500) + return + } + showSubScreen() + } + + private fun showSubScreen() { + // 查找副屏(通常索引为1) + if (displays.size > 1) { + presentation = FacePayPresentation( + activity = payActivity!!, + display = displays[1], + userViewModel = payActivity!!.userViewModel, + recognizeViewModel = payActivity!!.recognizeViewModel, + ) { + presentation?.dismiss() + }.also { + it.foodName = foodName + it.payAmount = payAmount + } + presentation?.show() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/activity/fragment/pay/NumberPayFragment.kt b/app/src/main/java/com/sw/dualscreen/activity/fragment/pay/NumberPayFragment.kt new file mode 100644 index 0000000..077901a --- /dev/null +++ b/app/src/main/java/com/sw/dualscreen/activity/fragment/pay/NumberPayFragment.kt @@ -0,0 +1,52 @@ +package com.sw.dualscreen.activity.fragment.pay + +import com.sw.dualscreen.activity.PayActivity +import com.sw.dualscreen.activity.fragment.BaseFragment +import com.sw.dualscreen.databinding.FragmentNumberPayBinding +import com.sw.dualscreen.ext.hideKeyboard +import com.sw.dualscreen.presentation.pay.CashPayPresentation + +class NumberPayFragment: BaseFragment() { + + private lateinit var payActivity: PayActivity + private var foodName: String? = null + private var payAmount: String? = null + var presentation: CashPayPresentation? = null + override fun inflateViewBinding(): FragmentNumberPayBinding { + return FragmentNumberPayBinding.inflate(layoutInflater) + } + + override fun initialize() { + binding.root.hideKeyboard() + payActivity = requireActivity() as PayActivity + + foodName = payActivity.foodInfo?.foodName + payAmount = "36.80" + + showSubScreen() + } + + override fun onHiddenChanged(hidden: Boolean) { + super.onHiddenChanged(hidden) + if (hidden) { + binding.root.postDelayed({ + presentation?.dismiss() + }, 500) + return + } + showSubScreen() + } + + private fun showSubScreen() { + // 查找副屏(通常索引为1) + if (displays.size > 1) { + presentation = CashPayPresentation(activity = payActivity, display = displays[1]) { + presentation?.dismiss() + }.also { + it.foodName = foodName + it.payAmount = payAmount + } + presentation?.show() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/activity/fragment/PayResultFragment.kt b/app/src/main/java/com/sw/dualscreen/activity/fragment/pay/PayResultFragment.kt similarity index 87% rename from app/src/main/java/com/sw/dualscreen/activity/fragment/PayResultFragment.kt rename to app/src/main/java/com/sw/dualscreen/activity/fragment/pay/PayResultFragment.kt index 9cdb82b..ad984a6 100644 --- a/app/src/main/java/com/sw/dualscreen/activity/fragment/PayResultFragment.kt +++ b/app/src/main/java/com/sw/dualscreen/activity/fragment/pay/PayResultFragment.kt @@ -1,6 +1,7 @@ -package com.sw.dualscreen.activity.fragment +package com.sw.dualscreen.activity.fragment.pay import com.sw.dualscreen.activity.PayActivity +import com.sw.dualscreen.activity.fragment.BaseFragment import com.sw.dualscreen.databinding.FragmentPayResultBinding class PayResultFragment: BaseFragment() { diff --git a/app/src/main/java/com/sw/dualscreen/activity/fragment/pay/QrCodePayFragment.kt b/app/src/main/java/com/sw/dualscreen/activity/fragment/pay/QrCodePayFragment.kt new file mode 100644 index 0000000..2ee978e --- /dev/null +++ b/app/src/main/java/com/sw/dualscreen/activity/fragment/pay/QrCodePayFragment.kt @@ -0,0 +1,95 @@ +package com.sw.dualscreen.activity.fragment.pay + +import android.text.Spanned +import android.text.SpannedString +import android.text.style.AbsoluteSizeSpan +import androidx.core.text.buildSpannedString +import androidx.lifecycle.lifecycleScope +import com.sw.dualscreen.R +import com.sw.dualscreen.activity.PayActivity +import com.sw.dualscreen.activity.fragment.BaseFragment +import com.sw.dualscreen.databinding.FragmentQrcodePayBinding +import com.sw.dualscreen.ext.load +import com.sw.dualscreen.presentation.pay.QrCodePayPresentation +import com.sw.dualscreen.utils.countDownByFlow +import kotlinx.coroutines.Job + +class QrCodePayFragment: BaseFragment() { + + + private lateinit var payActivity: PayActivity + private var foodName: String? = null + private var payAmount: String? = null + private var payQrCodePic: Any? = null + var presentation: QrCodePayPresentation?=null + override fun inflateViewBinding(): FragmentQrcodePayBinding { + return FragmentQrcodePayBinding.inflate(layoutInflater) + } + + override fun initialize() { + payActivity = requireActivity() as PayActivity + + foodName = payActivity.foodInfo?.foodName + payAmount = "36.80" + payQrCodePic = R.drawable.ic_qrcode + binding.tvRealAmount.text = getAmountText(payAmount?:"") + binding.ivPayQrCode.load(payQrCodePic) + binding.tvPayResult.text = "待支付(60s)..." + + startCountDown() + + showSubScreen() + } + + override fun onHiddenChanged(hidden: Boolean) { + super.onHiddenChanged(hidden) + if (hidden) { + binding.root.postDelayed({ + presentation?.dismiss() + }, 500) + return + } + showSubScreen() + } + + private fun showSubScreen() { + // 查找副屏(通常索引为1) + if (displays.size > 1) { + presentation = QrCodePayPresentation(activity = payActivity, display = displays[1]) { + presentation?.dismiss() + }.also { + it.foodName = foodName + it.payAmount = payAmount + it.payQrCodePic = payQrCodePic + } + presentation?.show() + } + } + + private fun getAmountText(amount:String): SpannedString { + return buildSpannedString { + append("¥", AbsoluteSizeSpan(36, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + append(amount, AbsoluteSizeSpan(60, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + } + } + private fun startCountDown() { + countDownJob = countDownByFlow( + total = 60, + scope = lifecycleScope, + onStart = { + binding.tvPayResult.text = "待支付(60s)..." + }, + onTick = { seconds -> + binding.tvPayResult.text = "待支付(${seconds}s)..." + }, + onFinish = { + binding.tvPayResult.text = "支付倒计时结束" + } + ) + } + private var countDownJob: Job? = null + override fun onDestroy() { + super.onDestroy() + countDownJob?.cancel() // 自动取消订阅,防止内存泄漏 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/adapter/CollectedFoodNewAdapter.kt b/app/src/main/java/com/sw/dualscreen/adapter/CollectedFoodNewAdapter.kt index 17901ab..11f291f 100644 --- a/app/src/main/java/com/sw/dualscreen/adapter/CollectedFoodNewAdapter.kt +++ b/app/src/main/java/com/sw/dualscreen/adapter/CollectedFoodNewAdapter.kt @@ -21,7 +21,7 @@ class CollectedFoodNewAdapter(var list: MutableList) : override fun onBindViewHolder(holder: VH, position: Int, item: CollectedFoodBean?) { holder.binding.tvFoodName.text = item?.foodName - holder.binding.tvCollectedNum.text = "已采集153" + holder.binding.tvCollectedNum.text = "已采集${item?.count}" } } \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/adapter/SearchFoodAdapter.kt b/app/src/main/java/com/sw/dualscreen/adapter/SearchFoodAdapter.kt index f2f0078..bc08783 100644 --- a/app/src/main/java/com/sw/dualscreen/adapter/SearchFoodAdapter.kt +++ b/app/src/main/java/com/sw/dualscreen/adapter/SearchFoodAdapter.kt @@ -26,7 +26,7 @@ class SearchFoodAdapter(var list: MutableList) : text = item.foodName } else { val score = (item.score / 100.0).format2String(2) - text = "${item.foodName}\n${score}%" + text = item.foodName + if (item.score != 0) "-${score}%" else "" } isChecked = item.isChecked } diff --git a/app/src/main/java/com/sw/dualscreen/ext/ViewExt.kt b/app/src/main/java/com/sw/dualscreen/ext/ViewExt.kt index d2d9c00..bfa0e7c 100644 --- a/app/src/main/java/com/sw/dualscreen/ext/ViewExt.kt +++ b/app/src/main/java/com/sw/dualscreen/ext/ViewExt.kt @@ -2,8 +2,10 @@ package com.sw.dualscreen.ext import android.graphics.drawable.GradientDrawable import android.view.View +import android.widget.ImageView import androidx.annotation.ColorInt import androidx.core.graphics.toColorInt +import com.sw.dualscreen.utils.GlideUtils /** * 设置圆角边框 @@ -27,4 +29,8 @@ fun View.setRoundedBorder( private fun View.dpToPx(dp: Float): Float { return dp * context.resources.displayMetrics.density +} + +fun ImageView.load(url: Any?) { + GlideUtils.loadImage(context, url, this) } \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/network/ApiClient.kt b/app/src/main/java/com/sw/dualscreen/network/ApiClient.kt index 6aa70d1..bf2f877 100644 --- a/app/src/main/java/com/sw/dualscreen/network/ApiClient.kt +++ b/app/src/main/java/com/sw/dualscreen/network/ApiClient.kt @@ -13,7 +13,9 @@ import java.util.concurrent.TimeUnit object ApiClient { private const val BASE_URL = "http://device.shuziweidao.com:8889/" - private const val TIME_OUT = 30L // 超时时间(秒) + //private const val TIME_OUT = 30L // 超时时间(秒) + //todo 临时测试改为5秒 + private const val TIME_OUT = 5L // 超时时间(秒) private val okHttpClient = OkHttpClient.Builder() .connectTimeout(TIME_OUT, TimeUnit.SECONDS) diff --git a/app/src/main/java/com/sw/dualscreen/objbox/DataBean.kt b/app/src/main/java/com/sw/dualscreen/objbox/DataBean.kt index 1d6e350..67f36e9 100644 --- a/app/src/main/java/com/sw/dualscreen/objbox/DataBean.kt +++ b/app/src/main/java/com/sw/dualscreen/objbox/DataBean.kt @@ -26,4 +26,5 @@ data class FoodCollectionBean( data class CollectedFoodBean( var foodName: String?, + var count: Int = 0 ) diff --git a/app/src/main/java/com/sw/dualscreen/objbox/FoodModule.kt b/app/src/main/java/com/sw/dualscreen/objbox/FoodModule.kt index b3d29f1..18608f8 100644 --- a/app/src/main/java/com/sw/dualscreen/objbox/FoodModule.kt +++ b/app/src/main/java/com/sw/dualscreen/objbox/FoodModule.kt @@ -10,6 +10,7 @@ import com.sw.dualscreen.MyApp import com.sw.dualscreen.utils.AssetsTool import com.sw.dualscreen.utils.GsonUtils import com.sw.dualscreen.utils.ImageUtil +import com.sw.plate.App import io.objectbox.Box import io.objectbox.kotlin.boxFor import io.objectbox.query.Query @@ -25,7 +26,7 @@ import java.io.InputStream object FoodModule { private const val THRESHOLD = 0.8 - private lateinit var module_mobile: Module + private var module_mobile: Module?=null private lateinit var box: Box private lateinit var embeddingsList: List> private lateinit var labelsList: IntArray @@ -61,7 +62,10 @@ object FoodModule { 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() + if (module_mobile == null) { + init(App.getContext()) + } + val outputTensor = module_mobile!!.forward(IValue.from(inputTensor)).toTensor() return outputTensor.dataAsFloatArray } @@ -103,6 +107,9 @@ object FoodModule { fun getFoodScoreList(bitmap: Bitmap, queryCount: Int = 15): List { val floatArray = bitmap2FloatArray(bitmap) val nameScoreList = queryFoodNameScore(floatArray, queryCount) + if (nameScoreList.isEmpty()) { + return emptyList() + } val maxScoreList = nameScoreList .filter { it.score < 1 - THRESHOLD } .groupBy { it.name } diff --git a/app/src/main/java/com/sw/dualscreen/presentation/MainScreenPresentation.kt b/app/src/main/java/com/sw/dualscreen/presentation/MainScreenPresentation.kt new file mode 100644 index 0000000..6fc1345 --- /dev/null +++ b/app/src/main/java/com/sw/dualscreen/presentation/MainScreenPresentation.kt @@ -0,0 +1,962 @@ +package com.sw.dualscreen.presentation + +import android.Manifest +import android.app.Presentation +import android.content.pm.PackageManager +import android.graphics.Bitmap +import android.graphics.Outline +import android.graphics.Point +import android.hardware.Camera +import android.os.Build +import android.os.Bundle +import android.view.Display +import android.view.View +import android.view.ViewGroup +import android.view.ViewOutlineProvider +import android.view.ViewTreeObserver +import androidx.annotation.RequiresApi +import androidx.core.content.ContextCompat +import androidx.lifecycle.Observer +import androidx.lifecycle.lifecycleScope +import com.arcsoft.face.ErrorInfo +import com.sw.dualscreen.GlobalKey +import com.sw.dualscreen.R +import com.sw.dualscreen.activity.MainActivity +import com.sw.dualscreen.databinding.PresentationMainScreenBinding +import com.sw.dualscreen.ext.dp +import com.sw.dualscreen.ext.format2String +import com.sw.dualscreen.ext.gone +import com.sw.dualscreen.ext.maskName +import com.sw.dualscreen.ext.visible +import com.sw.dualscreen.model.request.UserNutritionParam +import com.sw.dualscreen.model.response.DinnerTypeInfo +import com.sw.dualscreen.model.response.FoodInfo +import com.sw.dualscreen.model.response.UserNutritionData +import com.sw.dualscreen.utils.Debouncer +import com.sw.dualscreen.utils.GlideUtils +import com.sw.dualscreen.utils.GsonUtils +import com.sw.dualscreen.utils.SPUtil +import com.sw.dualscreen.viewmodel.UserViewModel +import com.sw.plate.utils.LightManager +import com.sw.plate.utils.ToastUtils +import com.sw.plate.utils.arcface.ConfigUtil +import com.sw.plate.utils.arcface.ErrorCodeUtil +import com.sw.plate.utils.arcface.FaceRectTransformer +import com.sw.plate.utils.arcface.FaceRectView +import com.sw.plate.utils.arcface.FaceRectView.DrawInfo +import com.sw.plate.utils.arcface.PreviewConfig +import com.sw.plate.utils.arcface.camera.CameraListener +import com.sw.plate.utils.arcface.camera.DualCameraHelper +import com.sw.plate.utils.arcface.face.constants.LivenessType +import com.sw.plate.utils.arcface.face.model.CompareResult +import com.sw.plate.utils.arcface.face.model.FacePreviewInfo +import com.sw.plate.utils.arcface.face.model.RecognizeConfiguration +import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel +import kotlinx.coroutines.flow.drop +import kotlinx.coroutines.launch +import timber.log.Timber +import kotlin.math.roundToInt + +class MainScreenPresentation( + val activity: MainActivity, + display: Display, + val userViewModel: UserViewModel, + val recognizeViewModel: RecognizeViewModel, + private val onDismissListener: () -> Unit = {} +) : Presentation(activity, display), ViewTreeObserver.OnGlobalLayoutListener { + + companion object { + const val TAG = "SubScreenPresentation" + } + + private lateinit var binding: PresentationMainScreenBinding + private lateinit var stepChangeCallback: (Int) -> Unit + // 当前步骤 + var currentStep: Int = 0 + + private var isGoStep1 = false;//防止持续调用 + + // 虹软人脸配置 ⬇ + private var isRecognition = false + private var rgbCameraHelper: DualCameraHelper? = null + private var rgbFaceRectTransformer: FaceRectTransformer? = null + private val livenessType = LivenessType.IR + private var openRectInfoDraw = false + + private var irCameraHelper: DualCameraHelper? = null + private var irFaceRectTransformer: FaceRectTransformer? = null + + private var detectWeight = 0.0 //识别菜品时的重量 + private var lastWeight = 0.0 // 上一次的计算热量结果 + private var userNutritionData: UserNutritionData? = null + private var dinnerTypeInfo: DinnerTypeInfo? = null + private val debouncer = Debouncer(500) + private var recognitionTime: Long = 0L // 人脸识别时的时间 + private var recognitionWeight: Double = 0.0 // 人脸识别时的重量 + + private var lastFaceTrackId: Int = -1 // 上一次的人脸信息 + var mealPickupMode: Int = 0 // 取餐模式 0 即放即取 1 余量取餐 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = PresentationMainScreenBinding.inflate(layoutInflater) + setContentView(binding.root) + mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0 + window?.setBackgroundDrawableResource(android.R.color.transparent) + initView() + setupArcCamera() + registerDataChange() + } + + fun updateWeight(weight: Double) { + lastWeight = weight * 1000 // 将千克转成克 + Timber.tag(TAG).d("updateWeight lastWeight = $weight, currentStep = $currentStep, isGoStep1 = $isGoStep1") + if ((currentStep == 2) && + lastWeight <= 5 && + isGoStep1 + ) {//人脸识别过程中,秤上物品拿走 + isGoStep1 = false + activity.runOnUiThread { + Timber.tag(TAG).e("切回step1") + step1FoodRecognizing() + } + } + +// if (currentStep != 3) { +// return +// } + if (currentFood == null) { + Timber.tag(TAG).e("updateWeight currentFood is null") + return + } + + if (userNutritionData == null) { + Timber.tag(TAG).e("updateWeight userNutritionData is null") + return + } + if (dinnerTypeInfo == null) { + Timber.tag(TAG).e("updateWeight dinnerType is null") + return + } + calculateNutrition(recognitionWeight - lastWeight) + } + fun setStepChangeCallback(callback: (Int) -> Unit) { + stepChangeCallback = callback + } + + override fun onGlobalLayout() { + Timber.tag(TAG).d("onGlobalLayout") + binding.dualCameraTexturePreviewRgb.getViewTreeObserver().removeOnGlobalLayoutListener(this) + openCamera() + } + + private fun openCamera() { + Timber.tag(TAG).d("openCamera") + if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) != + PackageManager.PERMISSION_GRANTED + ) { + ToastUtils.showToast("无摄像头权限") + return + } + try { + val cameraCount = Camera.getNumberOfCameras() + if (cameraCount < 3) { + ToastUtils.showToast("摄像头数量异常") + return + } + recognizeViewModel.init( + PreviewConfig( + 2, 1, 90, 90 + ) + ) + initRgbCamera() + if (DualCameraHelper.hasDualCamera() && livenessType === LivenessType.IR) { + initIrCamera() + } + } catch (e: Exception) { + Timber.tag(TAG).e(e, "打开摄像头失败") + } + } + + /** + * 启用虹软人脸失败 + */ + private fun setupArcCamera() { + initArcViewModel() + initArcView() + openRectInfoDraw = true + } + + private fun registerDataChange() { + activity.lifecycleScope.launch { + userViewModel.loadFaceResult.collect { needUpdate -> + if (needUpdate) { + recognizeViewModel.refreshFaceList() + } + } + } + activity.lifecycleScope.launch { + userViewModel.nutritionData.collect { + Timber.tag(TAG).d("registerDataChange nutritionData = $it") + userNutritionData = it + if (it == null) return@collect + step3ShowRecognizeResult() + //binding.nutritionInclude.tvUserName.text = it.userName.maskName() + //binding.nutritionInclude.tvRecommendHeat.text = "推荐热量:${it.recommendMin}-${it.recommendMax}" + //updateWeight(lastWeight / 1000) + } + } + activity.lifecycleScope.launch { + userViewModel.dinnerTypeInfo.drop(1).collect { + Timber.tag(TAG).d("registerDataChange dinnerTypeInfo = $it") + dinnerTypeInfo = it + } + } + } + + /** + * 显示识别出的菜品信息 + */ + fun step3ShowRecognizeResult() { + Timber.tag(TAG).d("step3") + LightManager.closeRedLight() + LightManager.openGreenLight() + currentStep = 3 + stepChangeCallback(currentStep) + if (currentFood == null) { + ToastUtils.showToast("选中物品为空") + return + } + updateFoodInfo(currentFood!!) +// binding.flPreview.visibility = View.VISIBLE +// binding.ivRecImage.visibility = View.VISIBLE +// binding.flFace.visibility = View.INVISIBLE +//// binding.ivFaceBg.visibility = View.GONE +// binding.tvFaceTip.visibility = View.GONE +// +//// binding.tvBottomTip.visibility = View.GONE +// binding.nutritionInclude.clNutritionData.visibility = View.VISIBLE + + + foodRecSuccess(currentFood!!) + } + private fun initView() { + binding.ivRecImage.let { + it.outlineProvider = object : ViewOutlineProvider() { + override fun getOutline(view: View, outline: Outline) { + outline.setRoundRect(0, 0, view.width, view.height, 12f.dp) + } + } + it.clipToOutline = true + } + } + override fun onDisplayRemoved() { + super.onDisplayRemoved() + onDismissListener() + } + + + /** + * 加载待机页面 + */ + fun loadStandbyScreen() { + binding.llStandbyPage.visible() + } + + fun hideStandbyScreen() { + binding.llStandbyPage.gone() + } + + /** + * 餐品识别成功,计费模式 + */ + fun loadBilledMode(foodInfo: FoodInfo) { + binding.calorieInclude.root.visible() + binding.nutritionInclude.root.gone() + + binding.flCameraView.visible() + binding.ivFaceRecMask.gone() + binding.tvFaceTip.gone() + binding.tvFoodRecPrompt.gone() + binding.llPriceInfo.visible() + binding.ivRecImage.gone() + + //updateFoodInfo(foodInfo) + + userNutritionData?.let { + Timber.tag(TAG).d("loadBilledMode:${GsonUtils.toJson(it)}") + } + } + + /** + * 餐品识别成功,不计费模式 + */ + fun loadUnbilledMode(foodInfo: FoodInfo) { + binding.calorieInclude.root.gone() + binding.nutritionInclude.root.visible() + + binding.flCameraView.visible() + binding.ivFaceRecMask.gone() + binding.tvFaceTip.gone() + binding.tvFoodRecPrompt.gone() + binding.llPriceInfo.gone() + binding.ivRecImage.gone() + + //updateFoodInfo(foodInfo) + userNutritionData?.let { + binding.nutritionInclude.tvUserName.text = it.userName.maskName() + binding.nutritionInclude.tvRecommendHeat.text = "推荐热量:${it.recommendMin}-${it.recommendMax}kcal" + updateWeight(lastWeight / 1000) + } + } + + /** + * 餐品识别中 + */ + fun step1FoodRecognizing() { + hideStandbyScreen() + activity.resetTouchTime() + + Timber.tag(TAG).d("step1") + //if (abs(lastWeight.toInt()) <= 5) { + // activity.runOnUiThread { + // ToastUtils.showToast("秤上重量几乎为0,请确认") + // } + // return + //} + currentStep = 1 + LightManager.closeGreenLight() + LightManager.closeRedLight() + + dinnerTypeInfo = null + currentFood = null + userNutritionData = null + recognitionWeight = 0.0 + + stepChangeCallback(currentStep) + + binding.calorieInclude.root.gone() + binding.nutritionInclude.root.gone() + + binding.tvFoodName.text = "餐品识别中..." + binding.tvFoodRecPrompt.visible() + binding.flCameraView.visible() + binding.ivPreviewImage.visible() + binding.ivRecImage.gone() + binding.ivFaceRecMask.gone() + binding.tvFaceTip.gone() + binding.llPriceInfo.gone() + + pauseCamera() + activity.clearFoodList() + } + + fun foodRecSuccess(foodInfo: FoodInfo) { + //识别逻辑完成根据是否计费显示不同页面 + val mode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0) + if (mode == 0) { + //计费 + loadBilledMode(foodInfo) + } else { + //不计费 + loadUnbilledMode(foodInfo) + } + } + + /** + * 用户人脸识别中 + */ + fun step2FaceRecognizing(foodInfo: FoodInfo) { + isGoStep1 = true + Timber.tag(TAG).d("step2") + currentStep = 2 + LightManager.closeRedLight() + + binding.calorieInclude.root.gone() + binding.nutritionInclude.root.gone() + + binding.tvFoodName.text = foodInfo.foodName + binding.flFace.visible() + binding.flCameraView.visible() + binding.ivFaceRecMask.visible() + binding.tvFaceTip.visible() + binding.tvFoodRecPrompt.visible() + binding.llPriceInfo.gone() + + binding.ivRecImage.gone() + binding.ivPreviewImage.gone() + + dinnerTypeInfo = null + userNutritionData = null + recognitionWeight = 0.0 + + stepChangeCallback(currentStep) + currentFood = foodInfo + userViewModel.getDinnerType() + resumeCamera() + } + + // 当前食物信息 + private var currentFood: FoodInfo? = null + fun updateFood(foodInfo: FoodInfo?) { + Timber.tag(TAG).d("updateFood") + currentFood = foodInfo + if (foodInfo != null) { + if (currentStep == 1) { + step2FaceRecognizing(foodInfo) + } else { + updateFoodInfo(foodInfo) + } + } + } + + private fun updateFoodInfo(foodInfo: FoodInfo) { + binding.tvFoodName.text = foodInfo.foodName + binding.ivPreviewImage.gone() + binding.ivRecImage.visible() + if (foodInfo.photoUri != null) { + GlideUtils.loadRoundCornerImage( + context, + url = foodInfo.photoUri, + imageView = binding.ivRecImage, + radius = 12 + ) + } else { + GlideUtils.loadRoundCornerImage( + context, + url = foodInfo.imgUrl, + imageView = binding.ivRecImage, + radius = 12 + ) + } + } + + private fun calculateNutrition(weight1: Double) { + var weight = weight1 + if (weight < 0) weight = 0.0 + debouncer.debounce { + Timber.tag(TAG).d("calculateNutrition weight = $weight, recognitionWeight = $recognitionWeight") + val dinnerType = dinnerTypeInfo!!.dinnerType!!.dinnerType!! + Timber.tag(TAG).d("calculateNutrition foodName = ${currentFood!!.foodName}, dinnerType = $dinnerType, userId = ${userNutritionData!!.userId}") + val calcResultInfo = UserNutritionUtils.calculateNutrition( + currentFood!!, + userNutritionData!!, + weight, + dinnerType = dinnerType + ) + Timber.tag(TAG).d("calculateNutrition calcResultInfo = $calcResultInfo") + val columnMax = UserNutritionUtils.getMaxInt(calcResultInfo) + + activity.runOnUiThread { + val totalKcal = calcResultInfo.totalKcal + val grain = calcResultInfo.grain + val fruits = calcResultInfo.fruits + val meat = calcResultInfo.meat + // 设置热量 + binding.nutritionInclude.customKcalColumn.setImageDrawable( + true, + totalKcal.max < totalKcal.current + ) + var maxKcal = totalKcal.max + Timber.tag(TAG).d("maxKcal = $maxKcal") + // 避免0作为被除数 + if (maxKcal <= 0.0) { + maxKcal = 1.0 + } + Timber.tag(TAG).d("maxKcal 1 = $maxKcal") + binding.nutritionInclude.totalKcalTv.text = totalKcal.current.format2String() + var totalKcalHeight = (totalKcal.current / maxKcal).toFloat() + if (totalKcalHeight == 0F) { + binding.nutritionInclude.divKcalView.visibility = View.VISIBLE + } else { + binding.nutritionInclude.divKcalView.visibility = View.GONE + totalKcalHeight += 0.1F + } + binding.nutritionInclude.customKcalColumn.setCustomHeightPercent( + totalKcalHeight, + true + ) + + // 设置主食 + binding.nutritionInclude.customFoodColumn.setImageDrawable( + false, + grain.max < grain.current + ) + binding.nutritionInclude.totalFoodTv.text = grain.current.format2String() + var grainHeight = (grain.current / columnMax).toFloat() + if (grainHeight == 0F) { + binding.nutritionInclude.divTotalFoodView.visibility = View.VISIBLE + } else { + binding.nutritionInclude.divTotalFoodView.visibility = View.GONE + grainHeight += 0.1F + } + binding.nutritionInclude.customFoodColumn.setCustomHeightPercent( + grainHeight, + true + ) + + // 设置果蔬 + binding.nutritionInclude.customVegetableColumn.setImageDrawable( + false, + fruits.max < fruits.current + ) + binding.nutritionInclude.totalVegetableTv.text = fruits.current.format2String() + var fruitsHeight = (fruits.current / columnMax).toFloat() + + if (fruitsHeight == 0F) { + binding.nutritionInclude.divVegetableView.visibility = View.VISIBLE + } else { + binding.nutritionInclude.divVegetableView.visibility = View.GONE + fruitsHeight += 0.1F + } + binding.nutritionInclude.customVegetableColumn.setCustomHeightPercent( + fruitsHeight, + true + ) + + // 设置肉蛋 + binding.nutritionInclude.customMeatColumn.setImageDrawable( + false, + meat.max < meat.current + ) + binding.nutritionInclude.totalMeatTv.text = meat.current.format2String() + var meatHeight = (meat.current / columnMax).toFloat() + if (meatHeight == 0F) { + binding.nutritionInclude.divMeatView.visibility = View.VISIBLE + } else { + binding.nutritionInclude.divMeatView.visibility = View.GONE + meatHeight += 0.1F + } + binding.nutritionInclude.customMeatColumn.setCustomHeightPercent( + meatHeight, + true + ) + } + } + } + fun updateImage(bitmap: Bitmap) { + activity.runOnUiThread { + binding.ivPreviewImage.setImageBitmap(bitmap) + } + } + + /** + * 余量取餐 + */ + fun updateMealPickupMode(mode: Int) { + Timber.tag(TAG).d("updateMealPickupMode mode = $mode") + mealPickupMode = mode + step1FoodRecognizing() + } + override fun onStop() { + if (rgbCameraHelper != null) { + rgbCameraHelper!!.release() + rgbCameraHelper = null + } + if (irCameraHelper != null) { + irCameraHelper!!.release() + irCameraHelper = null + } + recognizeViewModel.destroy() + super.onStop() + } + + fun resumeCamera() { + Timber.tag(TAG).d("resumeCamera isRecognition = $isRecognition") + isRecognition = true + if (rgbCameraHelper != null && rgbCameraHelper!!.isStopped) { + rgbCameraHelper!!.start() + } + } + + fun pauseCamera() { + Timber.tag(TAG).d("pauseCamera isRecognition = $isRecognition") + isRecognition = false + + recognizeViewModel.onPreviewFrame(ByteArray(1382400), true) + } + + private fun initArcViewModel() { + recognizeViewModel.setLiveType(livenessType) + recognizeViewModel.ftInitCode.observe(activity, Observer { ftInitCode: Int? -> + if (ftInitCode != ErrorInfo.MOK) { + val error: String? = context.getString( + R.string.specific_engine_init_failed, "ftEngine", + ftInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(ftInitCode!!) + ) + Timber.tag(TAG).e("ftInitCode observe = $error") + ToastUtils.showToast(error) + } + }) + recognizeViewModel.frInitCode.observe(activity, Observer { frInitCode: Int? -> + if (frInitCode != ErrorInfo.MOK) { + val error: String? = context.getString( + R.string.specific_engine_init_failed, "frEngine", + frInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(frInitCode!!) + ) + Timber.tag(TAG).e("frInitCode observe = $error") + ToastUtils.showToast(error) + } + }) + recognizeViewModel.flInitCode.observe(activity, Observer { flInitCode: Int? -> + if (flInitCode != ErrorInfo.MOK) { + val error: String? = context.getString( + R.string.specific_engine_init_failed, "flEngine", + flInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(flInitCode!!) + ) + Timber.tag(TAG).e("flInitCode observe = $error") + ToastUtils.showToast(error) + } + }) + + recognizeViewModel.recognizeConfiguration + .observe(activity, Observer { recognizeConfiguration: RecognizeConfiguration? -> + Timber.tag(TAG).i("recognizeConfiguration: observe = ${recognizeConfiguration.toString()}") + }) + recognizeViewModel.recognizeNotice.observe(activity, Observer { notice: String? -> + Timber.tag(TAG).i("recognizeNotice observe notice = $notice") + }) + + recognizeViewModel.recognizeUserId.observe( + activity, + Observer { compareResult: CompareResult -> + Timber.tag(TAG).i("recognizeUserId observe compareResult = ${compareResult.trackId}, userId = ${compareResult.faceEntity.userName}") + recognitionTime = System.currentTimeMillis() + recognitionWeight = lastWeight + lastFaceTrackId = compareResult.trackId + val faceEntity = compareResult.faceEntity + val userId = faceEntity.userName + if (userId == null ) return@Observer + if (currentFood == null) { + //重新识别 + activity.runOnUiThread { + ToastUtils.showToast("currentFood == null") + } + //activity.recognizeFood() + //step1FoodRecognizing() + return@Observer + } + userViewModel.getUserNutritionData( + userId = userId, + foodId = currentFood!!.id!! + ) + }) + + recognizeViewModel.drawRectInfoText.observe(activity, Observer { info -> + Timber.tag(TAG).i("drawRectInfoText observe info = $info") + }) + } + + private fun initArcView() { + //在布局结束后才做初始化操作 + binding.dualCameraTexturePreviewRgb.getViewTreeObserver().addOnGlobalLayoutListener(this) + recognizeViewModel.getCompareResultList().getValue() + } + + /** + * 调整View的宽高,使预览显示正常且采集框固定为 + * + * @param rgbPreview RGB预览View + * @param previewView 显示预览数据的view + * @param faceRectView 画框的view + * @param previewSize 预览大小 + * @param displayOrientation 相机旋转角度 + * @param scale 缩放比例 + * @return 调整后的LayoutParams + */ + private fun adjustPreviewViewSize( + rgbPreview: View, + previewView: View, + faceRectView: FaceRectView, + previewSize: Camera.Size, + displayOrientation: Int, + scale: Float + ): ViewGroup.LayoutParams { +// val layoutParams = previewView.layoutParams +// val measuredWidth = previewView.measuredWidth +// val measuredHeight = previewView.measuredHeight +// var ratio = (previewSize.height.toFloat()) / previewSize.width.toFloat() +// if (ratio > 1) { +// ratio = 1 / ratio +// } +// if (displayOrientation % 180 == 0) { +// layoutParams.width = measuredWidth +// layoutParams.height = (measuredWidth * ratio).toInt() +// } else { +// layoutParams.height = measuredHeight +// layoutParams.width = (measuredHeight * ratio).toInt() +// } +// if (scale < 1f) { +// val rgbParam = rgbPreview.getLayoutParams() +// layoutParams.width = (rgbParam.width * scale).toInt() +// layoutParams.height = (rgbParam.height * scale).toInt() +// } else { +// layoutParams.width = (layoutParams.width * scale).toInt() +// layoutParams.height = (layoutParams.height * scale).toInt() +// } +// +// val metrics = DisplayMetrics() +// activity.windowManager.defaultDisplay.getMetrics(metrics) +// +// if (layoutParams.width >= metrics.widthPixels) { +// val viewRatio = layoutParams.width / (metrics.widthPixels.toFloat()) +// layoutParams.width = (layoutParams.width / viewRatio).toInt() +// layoutParams.height = (layoutParams.height / viewRatio).toInt() +// } +// if (layoutParams.height >= metrics.heightPixels) { +// val viewRatio = layoutParams.height / (metrics.heightPixels.toFloat()) +// layoutParams.width = (layoutParams.width / viewRatio).toInt() +// layoutParams.height = (layoutParams.height / viewRatio).toInt() +// } + + + val layoutParams = previewView.layoutParams +// layoutParams.width = (608.dp * 1.5).roundToInt() +// layoutParams.height = (456.dp * 1.5).roundToInt() + layoutParams.width = (600.dp * 1.5).roundToInt() + layoutParams.height = (400.dp * 1.5).roundToInt() + previewView.setLayoutParams(layoutParams) + faceRectView.setLayoutParams(layoutParams) + return layoutParams + } + + private fun initRgbCamera() { + val cameraListener: CameraListener = object : CameraListener { + override fun onCameraOpened( + camera: Camera, + cameraId: Int, + displayOrientation: Int, + isMirror: Boolean + ) { + Timber.tag(TAG).d("initRgbCamera Rgb onCameraOpened: cameraId = $cameraId, displayOrientation = $displayOrientation") + activity.runOnUiThread({ + val previewSizeRgb = camera.getParameters().getPreviewSize() + val layoutParams = adjustPreviewViewSize( + binding.dualCameraTexturePreviewRgb, + binding.dualCameraTexturePreviewRgb, binding.dualCameraFaceRectView, + previewSizeRgb, displayOrientation, 0.6F + ) + + Timber.tag(TAG).d("initRgbCamera previewSizeRgb = ${previewSizeRgb.width}-${previewSizeRgb.height}") + Timber.tag(TAG).d("initRgbCamera layoutParams = ${layoutParams.width}-${layoutParams.height}") + Timber.tag(TAG).d( + "initRgbCamera isMirror = ${isMirror}, isDrawRgbRectHorizontalMirror = ${ + ConfigUtil.isDrawRgbRectHorizontalMirror( + context + ) + }, isDrawRgbRectVerticalMirror = ${ + ConfigUtil.isDrawRgbRectVerticalMirror( + context + ) + }" + ) + // 调整识别窗口位置 + rgbFaceRectTransformer = FaceRectTransformer( +// previewSizeRgb.width, +// previewSizeRgb.height, + layoutParams.width, + layoutParams.height, + layoutParams.width, + layoutParams.height, + 90, + cameraId, + isMirror, + true, + true + ) + + recognizeViewModel.onRgbCameraOpened(camera) + recognizeViewModel.setRgbFaceRectTransformer(rgbFaceRectTransformer) + }) + } + + @RequiresApi(api = Build.VERSION_CODES.Q) + override fun onPreview(nv21: ByteArray?, camera: Camera?) { + if (!isRecognition) { + return + } + binding.dualCameraFaceRectView.clearFaceInfo() + val facePreviewInfoList: MutableList? = + recognizeViewModel.onPreviewFrame(nv21, true) + if (facePreviewInfoList != null && rgbFaceRectTransformer != null) { + drawPreviewInfo(facePreviewInfoList) + } + recognizeViewModel.clearLeftFace(facePreviewInfoList) + } + + override fun onCameraClosed() { + Timber.tag(TAG).i("initRgbCamera onCameraClosed: ") + } + + override fun onCameraError(e: java.lang.Exception) { + Timber.tag(TAG).i("initRgbCamera onCameraError: %s", e.message) + e.printStackTrace() + } + + override fun onCameraConfigurationChanged(cameraID: Int, displayOrientation: Int) { + Timber.tag(TAG).i("initRgbCamera onCameraConfigurationChanged: threadName = ${Thread.currentThread().name}") + if (rgbFaceRectTransformer != null) { + rgbFaceRectTransformer!!.cameraDisplayOrientation = displayOrientation + } + Timber.tag(TAG).i("initRgbCamera onCameraConfigurationChanged: $cameraID $displayOrientation") + } + } + val measuredWidth = binding.dualCameraTexturePreviewRgb.measuredWidth + val measuredHeight = binding.dualCameraTexturePreviewRgb.measuredHeight + Timber.tag(TAG).i("initRgbCamera measuredWidth=$measuredWidth,measuredHeight=$measuredHeight") + + val previewConfig: PreviewConfig = recognizeViewModel.previewConfig + rgbCameraHelper = DualCameraHelper.Builder() + .previewViewSize(Point(measuredWidth, measuredHeight)) + .rotation(activity.windowManager.defaultDisplay.rotation) + .additionalRotation(previewConfig.rgbAdditionalDisplayOrientation) // 角度 + .previewSize(recognizeViewModel.loadPreviewSize()) + .specificCameraId(previewConfig.rgbCameraId) + .isMirror(true) + .previewOn(binding.dualCameraTexturePreviewRgb) + .cameraListener(cameraListener) + .build() + rgbCameraHelper!!.init() +// rgbCameraHelper!!.start() + } + + /** + * 初始化红外相机,若活体检测类型是可见光活体检测或不启用活体,则不需要启用 + */ + private fun initIrCamera() { + Timber.tag(TAG).d("initIrCamera: livenessType = $livenessType") + if (livenessType === LivenessType.RGB) { + return + } + val irCameraListener: CameraListener = object : CameraListener { + override fun onCameraOpened( + camera: Camera, + cameraId: Int, + displayOrientation: Int, + isMirror: Boolean + ) { + Timber.tag(TAG).d("initIrCamera IR onCameraOpened: cameraId = $cameraId, displayOrientation = $displayOrientation") + val previewSizeIr = camera.getParameters().getPreviewSize() + val layoutParams = adjustPreviewViewSize( + binding.dualCameraTexturePreviewRgb, + binding.dualCameraTexturePreviewIr, binding.dualCameraFaceRectViewIr, + previewSizeIr, displayOrientation, 0.25f + ) + + irFaceRectTransformer = FaceRectTransformer( +// previewSizeIr.width, previewSizeIr.height, + layoutParams.width, layoutParams.height, + layoutParams.width, layoutParams.height, displayOrientation, cameraId, isMirror, + ConfigUtil.isDrawIrRectHorizontalMirror(context), + ConfigUtil.isDrawIrRectVerticalMirror(context) + ) + + recognizeViewModel.onIrCameraOpened(camera) + recognizeViewModel.setIrFaceRectTransformer(irFaceRectTransformer) + } + + + override fun onPreview(nv21: ByteArray?, camera: Camera?) { + recognizeViewModel.refreshIrPreviewData(nv21) + } + + override fun onCameraClosed() { + Timber.tag(TAG).i("initIrCamera onCameraClosed: ") + } + + override fun onCameraError(e: java.lang.Exception) { + Timber.tag(TAG).i("initIrCamera onCameraError: ${e.message}") + e.printStackTrace() + } + + override fun onCameraConfigurationChanged( + cameraID: Int, + displayOrientation: Int + ) { + if (irFaceRectTransformer != null) { + irFaceRectTransformer!!.cameraDisplayOrientation = displayOrientation + } + Timber.tag(TAG).i("initIrCamera onCameraConfigurationChanged: cameraID = $cameraID, displayOrientation = $displayOrientation") + } + } + + val previewConfig = recognizeViewModel.previewConfig + irCameraHelper = DualCameraHelper.Builder() + .previewViewSize( + Point( + binding.dualCameraTexturePreviewIr.measuredWidth, + binding.dualCameraTexturePreviewIr.measuredHeight + ) + ) + .rotation(activity.windowManager.defaultDisplay.rotation) + .specificCameraId(previewConfig.irCameraId) + .previewOn(binding.dualCameraTexturePreviewIr) + .cameraListener(irCameraListener) + .isMirror(true) + .previewSize(recognizeViewModel.loadPreviewSize()) //相机预览大小设置,RGB与IR需使用相同大小 + .additionalRotation(previewConfig.irAdditionalDisplayOrientation) //额外旋转角度 + .build() + irCameraHelper!!.init() + try { + irCameraHelper!!.start() + } catch (e: RuntimeException) { + ToastUtils.showToast(e.message + context.getString(R.string.camera_error_notice)) + } + } + + /** + * 绘制RGB、IR画面的实时人脸信息 + * + * @param facePreviewInfoList RGB画面的实时人脸信息 + */ + private fun drawPreviewInfo(facePreviewInfoList: MutableList) { +// Timber.tag(TAG).d("drawPreviewInfo facePreviewInfoList = ${facePreviewInfoList.size}, rgbFaceRectTransformer = ${rgbFaceRectTransformer != null}") + if (rgbFaceRectTransformer != null) { + val rgbDrawInfoList: MutableList? = recognizeViewModel.getDrawInfo( + facePreviewInfoList, + LivenessType.RGB, + openRectInfoDraw + ) + // 识别成功 + binding.dualCameraFaceRectView.drawRealtimeFaceInfo(rgbDrawInfoList) + } + + if (facePreviewInfoList.isEmpty() || (lastFaceTrackId != facePreviewInfoList[0]!!.trackId)) { + if (lastFaceTrackId != -1) { + mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0 + Timber.tag(TAG).i("$lastFaceTrackId 用户离开") + lastFaceTrackId = -1 + postUserData() + if (mealPickupMode == 0) { + step1FoodRecognizing() + } else { + step2FaceRecognizing(currentFood!!) + } + } + } + } + + fun postUserData() { + Timber.tag(TAG).d("postUserData") + if (userNutritionData == null || currentFood == null) { + Timber.tag(TAG).d("postUserData userNutritionData = ${userNutritionData == null}, currentFood = ${currentFood == null}") + return + } + var eatWeight = 0.0 + eatWeight = if (mealPickupMode == 0) { + //即放即取 + recognitionWeight - 0 + } else { + recognitionWeight - lastWeight + } + val userNutritionParam = UserNutritionParam( + userId = userNutritionData?.userId!!, + foodId = currentFood?.id!!, + faceTime = recognitionTime, + faceEndTime = System.currentTimeMillis(), + eatWeight = eatWeight,//lastWeight + foodWeight = lastWeight + ) + userViewModel.postUserNutritionData(listOf(userNutritionParam)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/presentation/pay/CashPayPresentation.kt b/app/src/main/java/com/sw/dualscreen/presentation/pay/CashPayPresentation.kt new file mode 100644 index 0000000..466afa8 --- /dev/null +++ b/app/src/main/java/com/sw/dualscreen/presentation/pay/CashPayPresentation.kt @@ -0,0 +1,44 @@ +package com.sw.dualscreen.presentation.pay + +import android.R +import android.app.Presentation +import android.os.Bundle +import android.text.Spanned +import android.text.SpannedString +import android.text.style.AbsoluteSizeSpan +import android.view.Display +import androidx.core.text.buildSpannedString +import com.sw.dualscreen.activity.PayActivity +import com.sw.dualscreen.databinding.PresentationCashPayBinding +import com.sw.dualscreen.ext.load + +class CashPayPresentation( + val activity: PayActivity, + display: Display, + private val onDismissListener: () -> Unit = {} +) : Presentation(activity, display) { + + private lateinit var binding: PresentationCashPayBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = PresentationCashPayBinding.inflate(layoutInflater) + setContentView(binding.root) + window?.setBackgroundDrawableResource(R.color.transparent) + initView() + } + + var foodName: String? = null + var payAmount: String? = null + private fun initView() { + binding.tvFoodName.text = foodName + binding.tvRealAmount.text = getAmountText(payAmount ?: "") + } + + private fun getAmountText(amount: String): SpannedString { + return buildSpannedString { + append("¥", AbsoluteSizeSpan(32, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + append(amount, AbsoluteSizeSpan(48, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/presentation/pay/FacePayPresentation.kt b/app/src/main/java/com/sw/dualscreen/presentation/pay/FacePayPresentation.kt new file mode 100644 index 0000000..e059017 --- /dev/null +++ b/app/src/main/java/com/sw/dualscreen/presentation/pay/FacePayPresentation.kt @@ -0,0 +1,571 @@ +package com.sw.dualscreen.presentation.pay + +import android.Manifest +import android.app.Presentation +import android.content.pm.PackageManager +import android.graphics.Bitmap +import android.graphics.Point +import android.hardware.Camera +import android.os.Build +import android.os.Bundle +import android.text.Spanned +import android.text.SpannedString +import android.text.style.AbsoluteSizeSpan +import android.view.Display +import android.view.View +import android.view.ViewGroup +import android.view.ViewTreeObserver +import androidx.annotation.RequiresApi +import androidx.core.content.ContextCompat +import androidx.core.text.buildSpannedString +import androidx.lifecycle.Observer +import androidx.lifecycle.lifecycleScope +import com.arcsoft.face.ErrorInfo +import com.sw.dualscreen.GlobalKey +import com.sw.dualscreen.R +import com.sw.dualscreen.activity.PayActivity +import com.sw.dualscreen.databinding.PresentationFacePayBinding +import com.sw.dualscreen.ext.dp +import com.sw.dualscreen.presentation.MainScreenPresentation +import com.sw.dualscreen.utils.SPUtil +import com.sw.dualscreen.viewmodel.UserViewModel +import com.sw.plate.utils.ToastUtils +import com.sw.plate.utils.arcface.ConfigUtil +import com.sw.plate.utils.arcface.ErrorCodeUtil +import com.sw.plate.utils.arcface.FaceRectTransformer +import com.sw.plate.utils.arcface.FaceRectView +import com.sw.plate.utils.arcface.FaceRectView.DrawInfo +import com.sw.plate.utils.arcface.PreviewConfig +import com.sw.plate.utils.arcface.camera.CameraListener +import com.sw.plate.utils.arcface.camera.DualCameraHelper +import com.sw.plate.utils.arcface.face.constants.LivenessType +import com.sw.plate.utils.arcface.face.model.CompareResult +import com.sw.plate.utils.arcface.face.model.FacePreviewInfo +import com.sw.plate.utils.arcface.face.model.RecognizeConfiguration +import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel +import kotlinx.coroutines.launch +import timber.log.Timber +import kotlin.math.roundToInt + +class FacePayPresentation( + val activity: PayActivity, + display: Display, + val userViewModel: UserViewModel, + val recognizeViewModel: RecognizeViewModel, + private val onDismissListener: () -> Unit = {} +) : Presentation(activity, display) , ViewTreeObserver.OnGlobalLayoutListener { + + companion object { + private const val TAG = "FacePayPresentation" + } + + private lateinit var binding: PresentationFacePayBinding + + // 虹软人脸配置 ⬇ + private var isRecognition = false + private var rgbCameraHelper: DualCameraHelper? = null + private var rgbFaceRectTransformer: FaceRectTransformer? = null + private val livenessType = LivenessType.IR + private var openRectInfoDraw = false + + private var irCameraHelper: DualCameraHelper? = null + private var irFaceRectTransformer: FaceRectTransformer? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = PresentationFacePayBinding.inflate(layoutInflater) + setContentView(binding.root) + window?.setBackgroundDrawableResource(R.color.transparent) + initView() + setupArcCamera() + registerDataChange() + } + + override fun show() { + super.show() + + resumeCamera() + } + + var foodName: String? = null + var payAmount: String? = null + private fun initView() { + binding.tvFoodName.text = foodName + binding.tvRealAmount.text = getAmountText(payAmount ?: "") + } + + private fun getAmountText(amount: String): SpannedString { + return buildSpannedString { + append("¥", AbsoluteSizeSpan(32, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + append(amount, AbsoluteSizeSpan(48, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + } + } + + override fun onGlobalLayout() { + Timber.tag(TAG).d("onGlobalLayout") + binding.dualCameraTexturePreviewRgb.getViewTreeObserver().removeOnGlobalLayoutListener(this) + openCamera() + } + private fun openCamera() { + Timber.tag(TAG).d("openCamera") + if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) != + PackageManager.PERMISSION_GRANTED + ) { + ToastUtils.showToast("无摄像头权限") + return + } + try { + val cameraCount = Camera.getNumberOfCameras() + if (cameraCount < 3) { + ToastUtils.showToast("摄像头数量异常") + return + } + recognizeViewModel.init( + PreviewConfig( + 2, 1, 90, 90 + ) + ) + initRgbCamera() + if (DualCameraHelper.hasDualCamera() && livenessType === LivenessType.IR) { + initIrCamera() + } + } catch (e: Exception) { + Timber.tag(TAG).e(e, "打开摄像头失败") + } + } + + /** + * 启用虹软人脸失败 + */ + private fun setupArcCamera() { + initArcViewModel() + initArcView() + openRectInfoDraw = true + } + + private fun registerDataChange() { + activity.lifecycleScope.launch { + userViewModel.loadFaceResult.collect { needUpdate -> + if (needUpdate) { + recognizeViewModel.refreshFaceList() + } + } + } +// activity.lifecycleScope.launch { +// userViewModel.nutritionData.collect { +// Timber.tag(TAG).d("registerDataChange nutritionData = $it") +// userNutritionData = it +// if (it == null) return@collect +// step3ShowRecognizeResult() +// //binding.nutritionInclude.tvUserName.text = it.userName.maskName() +// //binding.nutritionInclude.tvRecommendHeat.text = "推荐热量:${it.recommendMin}-${it.recommendMax}" +// //updateWeight(lastWeight / 1000) +// } +// } +// activity.lifecycleScope.launch { +// userViewModel.dinnerTypeInfo.drop(1).collect { +// Timber.tag(TAG).d("registerDataChange dinnerTypeInfo = $it") +// dinnerTypeInfo = it +// } +// } + } + override fun onStop() { + if (rgbCameraHelper != null) { + rgbCameraHelper!!.release() + rgbCameraHelper = null + } + if (irCameraHelper != null) { + irCameraHelper!!.release() + irCameraHelper = null + } + recognizeViewModel.destroy() + super.onStop() + } + + fun resumeCamera() { + Timber.tag(TAG).d("resumeCamera isRecognition = $isRecognition") + isRecognition = true + if (rgbCameraHelper != null && rgbCameraHelper!!.isStopped) { + rgbCameraHelper!!.start() + } + } + + fun pauseCamera() { + Timber.tag(TAG).d("pauseCamera isRecognition = $isRecognition") + isRecognition = false + + recognizeViewModel.onPreviewFrame(ByteArray(1382400), true) + } + + private fun initArcViewModel() { + recognizeViewModel.setLiveType(livenessType) + recognizeViewModel.ftInitCode.observe(activity, Observer { ftInitCode: Int? -> + if (ftInitCode != ErrorInfo.MOK) { + val error: String? = context.getString( + R.string.specific_engine_init_failed, "ftEngine", + ftInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(ftInitCode!!) + ) + Timber.tag(TAG).e("ftInitCode observe = $error") + ToastUtils.showToast(error) + } + }) + recognizeViewModel.frInitCode.observe(activity, Observer { frInitCode: Int? -> + if (frInitCode != ErrorInfo.MOK) { + val error: String? = context.getString( + R.string.specific_engine_init_failed, "frEngine", + frInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(frInitCode!!) + ) + Timber.tag(TAG).e("frInitCode observe = $error") + ToastUtils.showToast(error) + } + }) + recognizeViewModel.flInitCode.observe(activity, Observer { flInitCode: Int? -> + if (flInitCode != ErrorInfo.MOK) { + val error: String? = context.getString( + R.string.specific_engine_init_failed, "flEngine", + flInitCode, ErrorCodeUtil.arcFaceErrorCodeToFieldName(flInitCode!!) + ) + Timber.tag(TAG).e("flInitCode observe = $error") + ToastUtils.showToast(error) + } + }) + + recognizeViewModel.recognizeConfiguration + .observe(activity, Observer { recognizeConfiguration: RecognizeConfiguration? -> + Timber.tag(TAG).i("recognizeConfiguration: observe = ${recognizeConfiguration.toString()}") + }) + recognizeViewModel.recognizeNotice.observe(activity, Observer { notice: String? -> + Timber.tag(TAG).i("recognizeNotice observe notice = $notice") + }) + + recognizeViewModel.recognizeUserId.observe( + activity, + Observer { compareResult: CompareResult -> + Timber.tag(TAG).i("recognizeUserId observe compareResult = ${compareResult.trackId}, userId = ${compareResult.faceEntity.userName}") +// recognitionTime = System.currentTimeMillis() +// recognitionWeight = lastWeight + lastFaceTrackId = compareResult.trackId + val faceEntity = compareResult.faceEntity + val userId = faceEntity.userName + if (userId == null ) return@Observer + activity.runOnUiThread { + ToastUtils.showToast("获取用户id成功") + } +// if (currentFood == null) { +// //重新识别 +// activity.runOnUiThread { +// ToastUtils.showToast("currentFood == null") +// } +// //activity.recognizeFood() +// //step1FoodRecognizing() +// return@Observer +// } +// userViewModel.getUserNutritionData( +// userId = userId, +// foodId = currentFood!!.id!! +// ) + }) + + recognizeViewModel.drawRectInfoText.observe(activity, Observer { info -> + Timber.tag(TAG).i("drawRectInfoText observe info = $info") + }) + } + + private fun initArcView() { + //在布局结束后才做初始化操作 + binding.dualCameraTexturePreviewRgb.getViewTreeObserver().addOnGlobalLayoutListener(this) + recognizeViewModel.getCompareResultList().getValue() + } + + /** + * 调整View的宽高,使预览显示正常且采集框固定为 + * + * @param rgbPreview RGB预览View + * @param previewView 显示预览数据的view + * @param faceRectView 画框的view + * @param previewSize 预览大小 + * @param displayOrientation 相机旋转角度 + * @param scale 缩放比例 + * @return 调整后的LayoutParams + */ + private fun adjustPreviewViewSize( + rgbPreview: View, + previewView: View, + faceRectView: FaceRectView, + previewSize: Camera.Size, + displayOrientation: Int, + scale: Float + ): ViewGroup.LayoutParams { +// val layoutParams = previewView.layoutParams +// val measuredWidth = previewView.measuredWidth +// val measuredHeight = previewView.measuredHeight +// var ratio = (previewSize.height.toFloat()) / previewSize.width.toFloat() +// if (ratio > 1) { +// ratio = 1 / ratio +// } +// if (displayOrientation % 180 == 0) { +// layoutParams.width = measuredWidth +// layoutParams.height = (measuredWidth * ratio).toInt() +// } else { +// layoutParams.height = measuredHeight +// layoutParams.width = (measuredHeight * ratio).toInt() +// } +// if (scale < 1f) { +// val rgbParam = rgbPreview.getLayoutParams() +// layoutParams.width = (rgbParam.width * scale).toInt() +// layoutParams.height = (rgbParam.height * scale).toInt() +// } else { +// layoutParams.width = (layoutParams.width * scale).toInt() +// layoutParams.height = (layoutParams.height * scale).toInt() +// } +// +// val metrics = DisplayMetrics() +// activity.windowManager.defaultDisplay.getMetrics(metrics) +// +// if (layoutParams.width >= metrics.widthPixels) { +// val viewRatio = layoutParams.width / (metrics.widthPixels.toFloat()) +// layoutParams.width = (layoutParams.width / viewRatio).toInt() +// layoutParams.height = (layoutParams.height / viewRatio).toInt() +// } +// if (layoutParams.height >= metrics.heightPixels) { +// val viewRatio = layoutParams.height / (metrics.heightPixels.toFloat()) +// layoutParams.width = (layoutParams.width / viewRatio).toInt() +// layoutParams.height = (layoutParams.height / viewRatio).toInt() +// } + + + val layoutParams = previewView.layoutParams +// layoutParams.width = (608.dp * 1.5).roundToInt() +// layoutParams.height = (456.dp * 1.5).roundToInt() + layoutParams.width = (600.dp * 1.5).roundToInt() + layoutParams.height = (400.dp * 1.5).roundToInt() + previewView.setLayoutParams(layoutParams) + faceRectView.setLayoutParams(layoutParams) + return layoutParams + } + + private fun initRgbCamera() { + val cameraListener: CameraListener = object : CameraListener { + override fun onCameraOpened( + camera: Camera, + cameraId: Int, + displayOrientation: Int, + isMirror: Boolean + ) { + Timber.tag(TAG).d("initRgbCamera Rgb onCameraOpened: cameraId = $cameraId, displayOrientation = $displayOrientation") + activity.runOnUiThread({ + val previewSizeRgb = camera.getParameters().getPreviewSize() + val layoutParams = adjustPreviewViewSize( + binding.dualCameraTexturePreviewRgb, + binding.dualCameraTexturePreviewRgb, binding.dualCameraFaceRectView, + previewSizeRgb, displayOrientation, 0.6F + ) + + Timber.tag(TAG).d("initRgbCamera previewSizeRgb = ${previewSizeRgb.width}-${previewSizeRgb.height}") + Timber.tag(TAG).d("initRgbCamera layoutParams = ${layoutParams.width}-${layoutParams.height}") + Timber.tag(TAG).d( + "initRgbCamera isMirror = ${isMirror}, isDrawRgbRectHorizontalMirror = ${ + ConfigUtil.isDrawRgbRectHorizontalMirror( + context + ) + }, isDrawRgbRectVerticalMirror = ${ + ConfigUtil.isDrawRgbRectVerticalMirror( + context + ) + }" + ) + // 调整识别窗口位置 + rgbFaceRectTransformer = FaceRectTransformer( +// previewSizeRgb.width, +// previewSizeRgb.height, + layoutParams.width, + layoutParams.height, + layoutParams.width, + layoutParams.height, + 90, + cameraId, + isMirror, + true, + true + ) + + recognizeViewModel.onRgbCameraOpened(camera) + recognizeViewModel.setRgbFaceRectTransformer(rgbFaceRectTransformer) + }) + } + + @RequiresApi(api = Build.VERSION_CODES.Q) + override fun onPreview(nv21: ByteArray?, camera: Camera?) { + if (!isRecognition) { + return + } + binding.dualCameraFaceRectView.clearFaceInfo() + val facePreviewInfoList: MutableList? = + recognizeViewModel.onPreviewFrame(nv21, true) + if (facePreviewInfoList != null && rgbFaceRectTransformer != null) { + drawPreviewInfo(facePreviewInfoList) + } + recognizeViewModel.clearLeftFace(facePreviewInfoList) + } + + override fun onCameraClosed() { + Timber.tag(TAG).i("initRgbCamera onCameraClosed: ") + } + + override fun onCameraError(e: java.lang.Exception) { + Timber.tag(TAG).i("initRgbCamera onCameraError: %s", e.message) + e.printStackTrace() + } + + override fun onCameraConfigurationChanged(cameraID: Int, displayOrientation: Int) { + Timber.tag(TAG).i("initRgbCamera onCameraConfigurationChanged: threadName = ${Thread.currentThread().name}") + if (rgbFaceRectTransformer != null) { + rgbFaceRectTransformer!!.cameraDisplayOrientation = displayOrientation + } + Timber.tag(TAG).i("initRgbCamera onCameraConfigurationChanged: $cameraID $displayOrientation") + } + } + val measuredWidth = binding.dualCameraTexturePreviewRgb.measuredWidth + val measuredHeight = binding.dualCameraTexturePreviewRgb.measuredHeight + Timber.tag(TAG).i("initRgbCamera measuredWidth=$measuredWidth,measuredHeight=$measuredHeight") + + val previewConfig: PreviewConfig = recognizeViewModel.previewConfig + rgbCameraHelper = DualCameraHelper.Builder() + .previewViewSize(Point(measuredWidth, measuredHeight)) + .rotation(activity.windowManager.defaultDisplay.rotation) + .additionalRotation(previewConfig.rgbAdditionalDisplayOrientation) // 角度 + .previewSize(recognizeViewModel.loadPreviewSize()) + .specificCameraId(previewConfig.rgbCameraId) + .isMirror(true) + .previewOn(binding.dualCameraTexturePreviewRgb) + .cameraListener(cameraListener) + .build() + rgbCameraHelper!!.init() +// rgbCameraHelper!!.start() + } + + /** + * 初始化红外相机,若活体检测类型是可见光活体检测或不启用活体,则不需要启用 + */ + private fun initIrCamera() { + Timber.tag(TAG).d("initIrCamera: livenessType = $livenessType") + if (livenessType === LivenessType.RGB) { + return + } + val irCameraListener: CameraListener = object : CameraListener { + override fun onCameraOpened( + camera: Camera, + cameraId: Int, + displayOrientation: Int, + isMirror: Boolean + ) { + Timber.tag(TAG).d("initIrCamera IR onCameraOpened: cameraId = $cameraId, displayOrientation = $displayOrientation") + val previewSizeIr = camera.getParameters().getPreviewSize() + val layoutParams = adjustPreviewViewSize( + binding.dualCameraTexturePreviewRgb, + binding.dualCameraTexturePreviewIr, binding.dualCameraFaceRectViewIr, + previewSizeIr, displayOrientation, 0.25f + ) + + irFaceRectTransformer = FaceRectTransformer( +// previewSizeIr.width, previewSizeIr.height, + layoutParams.width, layoutParams.height, + layoutParams.width, layoutParams.height, displayOrientation, cameraId, isMirror, + ConfigUtil.isDrawIrRectHorizontalMirror(context), + ConfigUtil.isDrawIrRectVerticalMirror(context) + ) + + recognizeViewModel.onIrCameraOpened(camera) + recognizeViewModel.setIrFaceRectTransformer(irFaceRectTransformer) + } + + + override fun onPreview(nv21: ByteArray?, camera: Camera?) { + recognizeViewModel.refreshIrPreviewData(nv21) + } + + override fun onCameraClosed() { + Timber.tag(TAG).i("initIrCamera onCameraClosed: ") + } + + override fun onCameraError(e: java.lang.Exception) { + Timber.tag(TAG).i("initIrCamera onCameraError: ${e.message}") + e.printStackTrace() + } + + override fun onCameraConfigurationChanged( + cameraID: Int, + displayOrientation: Int + ) { + if (irFaceRectTransformer != null) { + irFaceRectTransformer!!.cameraDisplayOrientation = displayOrientation + } + Timber.tag(TAG).i("initIrCamera onCameraConfigurationChanged: cameraID = $cameraID, displayOrientation = $displayOrientation") + } + } + + val previewConfig = recognizeViewModel.previewConfig + irCameraHelper = DualCameraHelper.Builder() + .previewViewSize( + Point( + binding.dualCameraTexturePreviewIr.measuredWidth, + binding.dualCameraTexturePreviewIr.measuredHeight + ) + ) + .rotation(activity.windowManager.defaultDisplay.rotation) + .specificCameraId(previewConfig.irCameraId) + .previewOn(binding.dualCameraTexturePreviewIr) + .cameraListener(irCameraListener) + .isMirror(true) + .previewSize(recognizeViewModel.loadPreviewSize()) //相机预览大小设置,RGB与IR需使用相同大小 + .additionalRotation(previewConfig.irAdditionalDisplayOrientation) //额外旋转角度 + .build() + irCameraHelper!!.init() + try { + irCameraHelper!!.start() + } catch (e: RuntimeException) { + ToastUtils.showToast(e.message + context.getString(R.string.camera_error_notice)) + } + } + + /** + * 绘制RGB、IR画面的实时人脸信息 + * + * @param facePreviewInfoList RGB画面的实时人脸信息 + */ + private fun drawPreviewInfo(facePreviewInfoList: MutableList) { +// Timber.tag(TAG).d("drawPreviewInfo facePreviewInfoList = ${facePreviewInfoList.size}, rgbFaceRectTransformer = ${rgbFaceRectTransformer != null}") + if (rgbFaceRectTransformer != null) { + val rgbDrawInfoList: MutableList? = recognizeViewModel.getDrawInfo( + facePreviewInfoList, + LivenessType.RGB, + openRectInfoDraw + ) + // 识别成功 + binding.dualCameraFaceRectView.drawRealtimeFaceInfo(rgbDrawInfoList) + ToastUtils.showToast("识别到人脸信息") + } + + if (facePreviewInfoList.isEmpty() || (lastFaceTrackId != facePreviewInfoList[0]!!.trackId)) { + if (lastFaceTrackId != -1) { + // mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0 + // Timber.tag(TAG).i("$lastFaceTrackId 用户离开") + // lastFaceTrackId = -1 + // postUserData() + // if (mealPickupMode == 0) { + // step1FoodRecognizing() + // } else { + // step2FaceRecognizing(currentFood!!) + // } + } + } + } + private var lastFaceTrackId: Int = -1 // 上一次的人脸信息 + + fun updateImage(bitmap: Bitmap) { + activity.runOnUiThread { + // binding.ivPreviewImage.setImageBitmap(bitmap) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/presentation/pay/QrCodePayPresentation.kt b/app/src/main/java/com/sw/dualscreen/presentation/pay/QrCodePayPresentation.kt new file mode 100644 index 0000000..1b5ea1f --- /dev/null +++ b/app/src/main/java/com/sw/dualscreen/presentation/pay/QrCodePayPresentation.kt @@ -0,0 +1,45 @@ +package com.sw.dualscreen.presentation.pay + +import android.app.Presentation +import android.os.Bundle +import android.text.Spanned +import android.text.SpannedString +import android.text.style.AbsoluteSizeSpan +import android.view.Display +import androidx.core.text.buildSpannedString +import com.sw.dualscreen.activity.PayActivity +import com.sw.dualscreen.databinding.PresentationQrcodePayBinding +import com.sw.dualscreen.ext.load + +class QrCodePayPresentation( + val activity: PayActivity, + display: Display, + private val onDismissListener: () -> Unit = {} +) : Presentation(activity, display) { + + private lateinit var binding: PresentationQrcodePayBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = PresentationQrcodePayBinding.inflate(layoutInflater) + setContentView(binding.root) + window?.setBackgroundDrawableResource(android.R.color.transparent) + initView() + } + + var foodName: String? = null + var payAmount: String? = null + var payQrCodePic: Any? = null + private fun initView() { + binding.tvFoodName.text = foodName + binding.tvRealAmount.text = getAmountText(payAmount ?: "") + binding.ivPayQrCode.load(payQrCodePic) + } + + private fun getAmountText(amount: String): SpannedString { + return buildSpannedString { + append("¥", AbsoluteSizeSpan(32, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + append(amount, AbsoluteSizeSpan(48, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/utils/FlowCountDown.kt b/app/src/main/java/com/sw/dualscreen/utils/FlowCountDown.kt new file mode 100644 index 0000000..33a8626 --- /dev/null +++ b/app/src/main/java/com/sw/dualscreen/utils/FlowCountDown.kt @@ -0,0 +1,32 @@ +package com.sw.dualscreen.utils + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onCompletion +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.onStart + +// 倒计时Flow扩展函数 +fun countDownByFlow( + total: Int, + scope: CoroutineScope, + onTick: (Int) -> Unit, + onStart: (() -> Unit)? = null, + onFinish: (() -> Unit)? = null +): Job { + return flow { + for (i in total downTo 0) { + emit(i) + if (i != 0) delay(1000) + } + }.flowOn(Dispatchers.Main) + .onStart { onStart?.invoke() } + .onCompletion { onFinish?.invoke() } + .onEach { onTick.invoke(it) } + .launchIn(scope) +} diff --git a/app/src/main/java/com/sw/dualscreen/view/CustomBottomSheetDialog.kt b/app/src/main/java/com/sw/dualscreen/view/CustomBottomSheetDialog.kt index 95345d8..dc842df 100644 --- a/app/src/main/java/com/sw/dualscreen/view/CustomBottomSheetDialog.kt +++ b/app/src/main/java/com/sw/dualscreen/view/CustomBottomSheetDialog.kt @@ -1,5 +1,6 @@ package com.sw.dualscreen.view +import android.annotation.SuppressLint import android.content.DialogInterface import android.graphics.Color import android.graphics.Typeface @@ -14,6 +15,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.sw.dualscreen.R import com.sw.dualscreen.adapter.GenericItemAdapter import com.sw.dualscreen.adapter.GridSpacingItemDecoration +import com.sw.dualscreen.adapter.SearchFoodAdapter import com.sw.dualscreen.adapter.dpToPx import com.sw.dualscreen.databinding.BottomSheetDialogBinding import com.sw.dualscreen.databinding.ItemSearchFoodInfoBinding @@ -27,12 +29,29 @@ import timber.log.Timber /** * 底部搜索结果弹窗 */ +@SuppressLint("NotifyDataSetChanged") class CustomBottomSheetDialog( val viewModel: UserViewModel, val itemClickCallback: (FoodInfo) -> Unit ) : BottomSheetDialogFragment() { private lateinit var binding: BottomSheetDialogBinding - private lateinit var adapter: GenericItemAdapter + // lateinit var adapter: GenericItemAdapter + private val searchFoodList = mutableListOf() + private val adapter by lazy { + SearchFoodAdapter(searchFoodList).apply { + setOnItemClickListener { adapter, view, position -> + searchFoodList.forEachIndexed { index, info -> + searchFoodList[index].isChecked = index == position + } + val item = searchFoodList[position] + Timber.d("itemClick ${item.foodName}, position = $position") + itemClickCallback(item) + checkedItem = item + notifyDataSetChanged() + dismissAllowingStateLoss() + } + } + } private var checkedItem: FoodInfo? = null private val debouncer = Debouncer(2000) @@ -83,18 +102,21 @@ class CustomBottomSheetDialog( } private fun initView() { - binding.viewClose.setOnClickListener { dismiss() } - adapter = createAdapter() - binding.recyclerview.layoutManager = GridLayoutManager(context, 2) // 添加间距装饰(12dp) - binding.recyclerview.addItemDecoration( - GridSpacingItemDecoration( - spanCount = 2, - spacing = requireContext().dpToPx(30), - includeEdge = false // 包含边缘间距 - ) - ) - binding.recyclerview.adapter = adapter + //binding.recyclerview.addItemDecoration( + // GridSpacingItemDecoration( + // spanCount = 2, + // spacing = requireContext().dpToPx(30), + // includeEdge = false // 包含边缘间距 + // ) + //) + //adapter = createAdapter() + //binding.recyclerview.adapter = adapter + binding.viewClose.setOnClickListener { dismiss() } + binding.recyclerview.let { + it.layoutManager = GridLayoutManager(context, 2) + it.adapter = adapter + } binding.ivSearch.setOnClickListener { searchInfo() } @@ -110,40 +132,43 @@ class CustomBottomSheetDialog( } } + @SuppressLint("NotifyDataSetChanged") private fun registerDataChange() { lifecycleScope.launch { viewModel.searchFoodInfoList.collect { - adapter.updateData(it) + searchFoodList.clear() + searchFoodList.addAll(it) + adapter.notifyDataSetChanged() } } } - private fun createAdapter(): GenericItemAdapter { - return GenericItemAdapter( - items = emptyList(), - bindingInflater = ItemSearchFoodInfoBinding::inflate, - bindCallback = { item, position -> - this.tvName.text = item.foodName - if (item.id != checkedItem?.id) { - this.tvName.typeface = Typeface.defaultFromStyle(Typeface.NORMAL) - this.tvName.setTextColor(resources.getColor(R.color.search_normal)) - this.llRoot.setBackgroundResource(R.drawable.grid_search_item_normal) - } else { - this.tvName.typeface = Typeface.defaultFromStyle(Typeface.BOLD) - this.tvName.setTextColor(resources.getColor(R.color.search_checked)) - this.llRoot.setBackgroundResource(R.drawable.grid_search_item_checked) - } - - this.llRoot.setOnClickListener { - Timber.d("itemClick ${item.foodName}, position = $position") - checkedItem = item -// viewModel.updateCurrentItem(item) - itemClickCallback(item) - adapter.notifyDataSetChanged() - } - } - ) - } +// private fun createAdapter(): GenericItemAdapter { +// return GenericItemAdapter( +// items = emptyList(), +// bindingInflater = ItemSearchFoodInfoBinding::inflate, +// bindCallback = { item, position -> +// this.tvName.text = item.foodName +// if (item.id != checkedItem?.id) { +// this.tvName.typeface = Typeface.defaultFromStyle(Typeface.NORMAL) +// this.tvName.setTextColor(resources.getColor(R.color.search_normal)) +// this.llRoot.setBackgroundResource(R.drawable.grid_search_item_normal) +// } else { +// this.tvName.typeface = Typeface.defaultFromStyle(Typeface.BOLD) +// this.tvName.setTextColor(resources.getColor(R.color.search_checked)) +// this.llRoot.setBackgroundResource(R.drawable.grid_search_item_checked) +// } +// +// this.llRoot.setOnClickListener { +// Timber.d("itemClick ${item.foodName}, position = $position") +// checkedItem = item +//// viewModel.updateCurrentItem(item) +// itemClickCallback(item) +// adapter.notifyDataSetChanged() +// } +// } +// ) +// } override fun onCancel(dialog: DialogInterface) { Timber.d("onCancel") diff --git a/app/src/main/java/com/sw/dualscreen/view/CustomImageView.java b/app/src/main/java/com/sw/dualscreen/view/CustomImageView.java index f09c132..b4a2b87 100644 --- a/app/src/main/java/com/sw/dualscreen/view/CustomImageView.java +++ b/app/src/main/java/com/sw/dualscreen/view/CustomImageView.java @@ -29,7 +29,8 @@ public class CustomImageView extends androidx.appcompat.widget.AppCompatImageVie /** * 最大高度 */ - private static final int MAX_HEIGHT = 280; +// private static final int MAX_HEIGHT = 280; + private static final int MAX_HEIGHT = 250; private boolean isDrawText = false; private Drawable bigRedDrawable; @@ -100,6 +101,14 @@ public class CustomImageView extends androidx.appcompat.widget.AppCompatImageVie } } + public void setImageDrawable2(boolean isBig, Boolean isRed) { + if (isBig) { + setImageDrawable(isRed ? bigRedDrawable : bigGreenDrawable); + } else { + setImageDrawable(isRed ? redDrawable : greenDrawable); + } + } + public void setCustomHeightPercent(float percent, boolean isAnimation) { Timber.d("setCustomHeightPercent percent = %s", percent); if (percent<= 0){ diff --git a/app/src/main/java/com/sw/dualscreen/viewmodel/BaseViewModel.kt b/app/src/main/java/com/sw/dualscreen/viewmodel/BaseViewModel.kt index 6ea394f..cdc763f 100644 --- a/app/src/main/java/com/sw/dualscreen/viewmodel/BaseViewModel.kt +++ b/app/src/main/java/com/sw/dualscreen/viewmodel/BaseViewModel.kt @@ -65,7 +65,7 @@ abstract class BaseViewModel() : ViewModel() { fun parseEquipmentInfo(equipmentInfo: EquipmentInfo) { GlobalData.appBaseUrl = equipmentInfo.appPackageUrl!! - GlobalData.appBaseUrl="http://192.168.1.210/gateway/local" + //GlobalData.appBaseUrl="http://192.168.1.210/gateway/local" GlobalData.sdkKey = equipmentInfo.arcsoftSdkKey!! GlobalData.appId = equipmentInfo.arcsoftAppId!! // GlobalData.activeKey = equipmentInfo.arcsoftActiveKey!! diff --git a/app/src/main/java/com/sw/dualscreen/viewmodel/UserViewModel.kt b/app/src/main/java/com/sw/dualscreen/viewmodel/UserViewModel.kt index 724eb91..d8dd2c5 100644 --- a/app/src/main/java/com/sw/dualscreen/viewmodel/UserViewModel.kt +++ b/app/src/main/java/com/sw/dualscreen/viewmodel/UserViewModel.kt @@ -86,6 +86,8 @@ class UserViewModel : BaseViewModel() { SPUtil.getInstance().put(GlobalKey.KEY_FIRST_RUN, true) withContext(Dispatchers.Default) { val list: List = response.result?.data ?: emptyList() + val item = list.firstOrNull { it.userId == "1951105919342936066" } + Timber.d("getUserFaceCache userId = ${item?.userId}") val faceEntity = list.map { FaceEntity(it.userId, null, Base64.decode(it.faceFeatureString)) } @@ -221,6 +223,8 @@ class UserViewModel : BaseViewModel() { val list = response.result ?: emptyList() _identifiedFoodInfoList2.value = list action(list) + } else { + action(emptyList()) } } } diff --git a/app/src/main/res/drawable-xxhdpi/ic_camera_rect.png b/app/src/main/res/drawable-xxhdpi/ic_camera_rect.png new file mode 100644 index 0000000..0e5dcdb Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_camera_rect.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_qrcode.png b/app/src/main/res/drawable-xxhdpi/ic_qrcode.png deleted file mode 100644 index 95920a3..0000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_qrcode.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/placeholder_waiting.png b/app/src/main/res/drawable-xxhdpi/placeholder_waiting.png new file mode 100644 index 0000000..edf7116 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/placeholder_waiting.png differ diff --git a/app/src/main/res/drawable/bg_nutrition_rate.xml b/app/src/main/res/drawable/bg_nutrition_rate.xml new file mode 100644 index 0000000..99bb923 --- /dev/null +++ b/app/src/main/res/drawable/bg_nutrition_rate.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_search.xml b/app/src/main/res/drawable/bg_search.xml index b3945d5..b09b31d 100644 --- a/app/src/main/res/drawable/bg_search.xml +++ b/app/src/main/res/drawable/bg_search.xml @@ -1,10 +1,7 @@ - + + android:radius="43dp" /> \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_qrcode.png b/app/src/main/res/drawable/ic_qrcode.png new file mode 100644 index 0000000..735fe0a Binary files /dev/null and b/app/src/main/res/drawable/ic_qrcode.png differ diff --git a/app/src/main/res/drawable/ic_search.png b/app/src/main/res/drawable/ic_search.png deleted file mode 100644 index 0172a02..0000000 Binary files a/app/src/main/res/drawable/ic_search.png and /dev/null differ diff --git a/app/src/main/res/drawable/rate_orange.xml b/app/src/main/res/drawable/rate_orange.xml new file mode 100644 index 0000000..0492659 --- /dev/null +++ b/app/src/main/res/drawable/rate_orange.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rate_pink.xml b/app/src/main/res/drawable/rate_pink.xml new file mode 100644 index 0000000..ad8da87 --- /dev/null +++ b/app/src/main/res/drawable/rate_pink.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rate_sky_blue.xml b/app/src/main/res/drawable/rate_sky_blue.xml new file mode 100644 index 0000000..800a3db --- /dev/null +++ b/app/src/main/res/drawable/rate_sky_blue.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/setting_border_gray3.xml b/app/src/main/res/drawable/setting_border_gray3.xml index 49ae121..f9942d8 100644 --- a/app/src/main/res/drawable/setting_border_gray3.xml +++ b/app/src/main/res/drawable/setting_border_gray3.xml @@ -3,5 +3,5 @@ android:shape="rectangle"> - + \ No newline at end of file diff --git a/app/src/main/res/drawable/view_shape_point.xml b/app/src/main/res/drawable/view_shape_point.xml index a2451e8..30bf8b5 100644 --- a/app/src/main/res/drawable/view_shape_point.xml +++ b/app/src/main/res/drawable/view_shape_point.xml @@ -1,10 +1,7 @@ - + + android:radius="4dp" /> \ No newline at end of file diff --git a/app/src/main/res/drawable/vip_level.xml b/app/src/main/res/drawable/vip_level.xml index ac99d9e..f417b03 100644 --- a/app/src/main/res/drawable/vip_level.xml +++ b/app/src/main/res/drawable/vip_level.xml @@ -1,13 +1,13 @@ - + - + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 0f7dc33..fa9f16c 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,6 +1,7 @@ @@ -55,19 +56,20 @@ @@ -94,7 +96,12 @@ android:id="@+id/recyclerview" android:layout_width="match_parent" android:layout_height="0dp" - android:layout_marginHorizontal="60dp" + app:spanCount="2" + tools:itemCount="8" + tools:listitem="@layout/list_item_search_food" + app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" + android:layout_marginHorizontal="14dp" + android:layout_marginTop="42dp" android:layout_weight="1" /> + android:textSize="31sp" + android:visibility="gone"/> diff --git a/app/src/main/res/layout/bottom_sheet_dialog.xml b/app/src/main/res/layout/bottom_sheet_dialog.xml index 19996a7..b6f7eec 100644 --- a/app/src/main/res/layout/bottom_sheet_dialog.xml +++ b/app/src/main/res/layout/bottom_sheet_dialog.xml @@ -1,63 +1,74 @@ + android:orientation="vertical"> + android:textStyle="bold" + android:textColor="#FF0A1428" + android:textSize="31sp" /> - + android:textColor="#FF0A1428" + android:textColorHint="#FFB1B1C0" + android:textSize="31sp" /> - + android:layout_width="100dp" + android:layout_height="85dp" + android:paddingStart="25dp" + android:paddingEnd="40dp" + android:layout_gravity="end|center_vertical" + android:src="@drawable/ic_search2" /> + + android:layout_height="match_parent" + android:minHeight="500dp" + android:layout_marginTop="42dp" + android:layout_marginBottom="20dp" + android:layout_marginHorizontal="14dp" + app:spanCount="2" + tools:itemCount="12" + tools:listitem="@layout/list_item_search_food" + app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"/> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_business.xml b/app/src/main/res/layout/fragment_business.xml index 47b06a5..c5adae5 100644 --- a/app/src/main/res/layout/fragment_business.xml +++ b/app/src/main/res/layout/fragment_business.xml @@ -11,6 +11,7 @@ android:layout_height="wrap_content" android:background="@drawable/setting_border" android:orientation="vertical" + android:layout_marginHorizontal="32dp" android:paddingHorizontal="32dp" android:paddingTop="35dp" android:paddingBottom="42dp"> @@ -73,6 +74,7 @@ android:background="@drawable/setting_border" android:orientation="vertical" android:paddingHorizontal="32dp" + android:layout_marginHorizontal="32dp" android:paddingTop="35dp" android:paddingBottom="42dp"> @@ -177,4 +179,20 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_collect.xml b/app/src/main/res/layout/fragment_collect.xml index 22aa04f..bc43ce2 100644 --- a/app/src/main/res/layout/fragment_collect.xml +++ b/app/src/main/res/layout/fragment_collect.xml @@ -22,7 +22,7 @@ android:layout_marginStart="32dp" android:layout_marginTop="35dp" android:drawablePadding="17dp" - android:text="计费设置" + android:text="餐品采集" android:textColor="@color/black" android:textSize="36sp" android:textStyle="bold" @@ -139,6 +139,7 @@ android:background="@drawable/setting_border_gray3" android:gravity="center_vertical" android:hint="输入菜品名称" + android:inputType="text" android:imeOptions="actionSearch" android:maxLines="1" android:paddingStart="47dp" diff --git a/app/src/main/res/layout/fragment_face_pay.xml b/app/src/main/res/layout/fragment_face_pay.xml index d7e4a67..3875d2b 100644 --- a/app/src/main/res/layout/fragment_face_pay.xml +++ b/app/src/main/res/layout/fragment_face_pay.xml @@ -30,16 +30,16 @@ android:id="@+id/ivFacePhoto" android:layout_width="match_parent" android:layout_height="510dp" - android:src="@mipmap/ic_launcher" android:scaleType="centerCrop" - tools:ignore="ContentDescription" /> + tools:ignore="ContentDescription" + tools:src="@mipmap/ic_launcher" /> - - - - - - - + tools:ignore="ContentDescription" + tools:src="@drawable/ic_qrcode" /> + android:textStyle="bold" + tools:text="待支付(60s)..." /> \ No newline at end of file diff --git a/app/src/main/res/layout/item_user_nutrition_new.xml b/app/src/main/res/layout/item_user_nutrition_new.xml new file mode 100644 index 0000000..56d2dc7 --- /dev/null +++ b/app/src/main/res/layout/item_user_nutrition_new.xml @@ -0,0 +1,300 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_user_calorie.xml b/app/src/main/res/layout/layout_user_calorie.xml new file mode 100644 index 0000000..11911e5 --- /dev/null +++ b/app/src/main/res/layout/layout_user_calorie.xml @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_collected_data.xml b/app/src/main/res/layout/list_item_collected_data.xml index da58edf..ab81daa 100644 --- a/app/src/main/res/layout/list_item_collected_data.xml +++ b/app/src/main/res/layout/list_item_collected_data.xml @@ -33,12 +33,12 @@ app:layout_constraintStart_toEndOf="@id/tvFoodName" android:src="@drawable/ic_close" /> - + android:background="#FFE6E6E6"/> \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_search_food.xml b/app/src/main/res/layout/list_item_search_food.xml index 72d9bfb..2cbc539 100644 --- a/app/src/main/res/layout/list_item_search_food.xml +++ b/app/src/main/res/layout/list_item_search_food.xml @@ -8,8 +8,8 @@ android:ellipsize="end" android:gravity="center" android:maxLines="1" - android:layout_marginHorizontal="16dp" - android:layout_marginVertical="8dp" + android:layout_marginHorizontal="14dp" + android:layout_marginVertical="14dp" android:paddingHorizontal="15dp" android:textAlignment="center" android:textColor="@color/color_search_food" diff --git a/app/src/main/res/layout/presentation_cash_pay.xml b/app/src/main/res/layout/presentation_cash_pay.xml new file mode 100644 index 0000000..aa58ee6 --- /dev/null +++ b/app/src/main/res/layout/presentation_cash_pay.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/presentation_face_pay.xml b/app/src/main/res/layout/presentation_face_pay.xml new file mode 100644 index 0000000..1b864fc --- /dev/null +++ b/app/src/main/res/layout/presentation_face_pay.xml @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/presentation_main_screen.xml b/app/src/main/res/layout/presentation_main_screen.xml new file mode 100644 index 0000000..b9e2711 --- /dev/null +++ b/app/src/main/res/layout/presentation_main_screen.xml @@ -0,0 +1,249 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/presentation_qrcode_pay.xml b/app/src/main/res/layout/presentation_qrcode_pay.xml new file mode 100644 index 0000000..5a75c15 --- /dev/null +++ b/app/src/main/res/layout/presentation_qrcode_pay.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index b37dd84..24070b2 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -2,11 +2,16 @@ 1000dp 11sp + 36sp + 18sp 20sp + 30sp 30dp + 10dp 80dp 87dp 107dp 44dp 53dp + 130dp \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 3bd895f..10c9faa 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -74,6 +74,13 @@ 1 + + + + + diff --git a/lib_face/src/main/java/com/sw/plate/utils/arcface/ConfigUtil.java b/lib_face/src/main/java/com/sw/plate/utils/arcface/ConfigUtil.java index 2b94ae2..4b20d0b 100644 --- a/lib_face/src/main/java/com/sw/plate/utils/arcface/ConfigUtil.java +++ b/lib_face/src/main/java/com/sw/plate/utils/arcface/ConfigUtil.java @@ -87,7 +87,8 @@ public class ConfigUtil { /** * 默认相机分辨率 */ - private static final String DEFAULT_PREVIEW_SIZE = "1280x720"; +// private static final String DEFAULT_PREVIEW_SIZE = "1280x720"; + private static final String DEFAULT_PREVIEW_SIZE = "600x400"; // private static final String DEFAULT_PREVIEW_SIZE = "400x640";