diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e1ef242..7dfd089 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -108,6 +108,9 @@ dependencies { implementation(libs.pytorch.android) implementation(libs.pytorch.android.torchvision) + implementation("io.github.scwang90:refresh-layout-kernel:3.0.0-alpha") + implementation("io.github.scwang90:refresh-header-classics:3.0.0-alpha") + val objectboxVersion = "5.0.1" debugImplementation("io.objectbox:objectbox-android-objectbrowser:$objectboxVersion") // releaseImplementation("io.objectbox:objectbox-android:$objectboxVersion") 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 74a6202..fcfa140 100644 --- a/app/src/main/java/com/sw/dualscreen/activity/BaseActivity.kt +++ b/app/src/main/java/com/sw/dualscreen/activity/BaseActivity.kt @@ -2,13 +2,12 @@ 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.hardware.display.DisplayManager import android.os.Bundle import android.text.TextUtils import android.view.Display import android.view.View +import android.view.ViewGroup import android.view.WindowManager import android.widget.TextView import androidx.activity.enableEdgeToEdge @@ -93,6 +92,19 @@ abstract class BaseActivity : AppCompatActivity() { mDialogWaiting = null } + fun showWaitingDialog2(tip: String?) { + if (mDialogWaiting == null) { + hideWaitingDialog() + val view = View.inflate(this, R.layout.dialog_waiting, null) + mDialogWaiting = CustomDialog(this, view, R.style.MyDialog) + mDialogWaiting!!.show() + mDialogWaiting!!.setCancelable(true) + } + val contentView = mDialogWaiting?.findViewById(android.R.id.content) + val tvTip = contentView?.findViewById(R.id.tvTip) + tvTip?.text = tip + } + private var launchPermissionCallback: IActivityResult.RequestPermissionCallback? = null fun requestSinglePermissionResult( 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 f4f3a05..64bfc5b 100644 --- a/app/src/main/java/com/sw/dualscreen/activity/CollectedFoodActivity.kt +++ b/app/src/main/java/com/sw/dualscreen/activity/CollectedFoodActivity.kt @@ -10,7 +10,9 @@ import com.sw.dualscreen.databinding.ActivityCollectedFoodBinding import com.sw.dualscreen.databinding.LayoutEmptySearchBinding import com.sw.dualscreen.dialog.WarnDialog import com.sw.dualscreen.ext.addOnActionSearchListener +import com.sw.dualscreen.ext.gone import com.sw.dualscreen.ext.hideKeyboard +import com.sw.dualscreen.ext.visible import com.sw.dualscreen.objbox.CollectedFoodInfo import com.sw.dualscreen.objbox.Food import com.sw.dualscreen.objbox.ObjectBox @@ -21,6 +23,11 @@ import io.objectbox.Box import io.objectbox.kotlin.boxFor class CollectedFoodActivity : BaseActivity() { + + companion object { + private const val PAGE_SIZE = 100 + } + private val viewModel by viewModels() override fun getViewModel(): BaseViewModel { return viewModel @@ -33,7 +40,7 @@ class CollectedFoodActivity : BaseActivity() { private val list: MutableList = mutableListOf() private val adapter by lazy { CollectedFoodNewAdapter(list).apply { - isStateViewEnable = true +// isStateViewEnable = true addOnItemChildClickListener(R.id.ivDeleteFood) { _, _, position -> deleteGoods(position) } @@ -42,6 +49,7 @@ class CollectedFoodActivity : BaseActivity() { override fun initialize() { super.initialize() + //binding.root.setOnClickListener { it.hideKeyboard() } binding.rvFoodList.let { it.layoutManager = LinearLayoutManager(this) it.adapter = adapter @@ -65,14 +73,30 @@ class CollectedFoodActivity : BaseActivity() { ToastUtils.showToast("请输入物品名称") return@addOnActionSearchListener } + pageNo = 1 getCollectGoods(searchName) } v.addTextChangedListener { if (it.isNullOrBlank()) { + pageNo = 1 getCollectGoods() } } } + binding.refreshLayout.let { + it.setEnableRefresh(true) + it.setEnableLoadMore(false) + it.setOnRefreshListener { + pageNo = 1 + getCollectGoods() + } + it.setOnLoadMoreListener { + getCollectGoods() + } + } + binding.emptyInclude.root.setOnClickListener { + it.hideKeyboard() + } getCollectGoods() } @@ -81,54 +105,81 @@ class CollectedFoodActivity : BaseActivity() { @SuppressLint("NotifyDataSetChanged") private fun getCollectGoods(searchName: String? = null) { - if (box == null) { - box = ObjectBox.boxStore.boxFor(Food::class) - } - list.clear() - val totalList = box?.all - ?.filter { it.name!=null } - ?.groupBy { it.name!! } - ?.map { CollectedFoodInfo(foodName = it.key, foodCount = it.value.size) } - //var queryMap:Map> ?= null - if (searchName.isNullOrBlank().not()) { - //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()) { - loadEmptyView() - } - binding.root.hideKeyboard() +// if (box == null) { +// box = ObjectBox.boxStore.boxFor(Food::class) +// } +// list.clear() +// val totalList = box?.all +// ?.filter { it.name!=null } +// ?.groupBy { it.name!! } +// ?.map { CollectedFoodInfo(foodName = it.key, foodCount = it.value.size) } +// //var queryMap:Map> ?= null +// if (searchName.isNullOrBlank().not()) { +// //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()) { +// loadEmptyView() +// } - viewModel.getCollectedFoodList(pageNo = pageNo) { - if (it.isNotEmpty()) { - list.addAll(it) - runOnUiThread { - adapter.notifyDataSetChanged() - } + viewModel.getCollectedFoodList( + pageNo = pageNo, + pageSize = PAGE_SIZE, + foodName = searchName ?: "" + ) { items -> + runOnUiThread { + loadFoodList(items) } } } + @SuppressLint("NotifyDataSetChanged") + private fun loadFoodList(items: List) { + finishRefresh() + if (pageNo == 1 && items.isEmpty()) { + list.clear() + adapter.notifyDataSetChanged() + loadEmptyView() + binding.refreshLayout.setEnableLoadMore(false) + return + } + binding.refreshLayout.visible() + binding.emptyInclude.root.gone() + if (pageNo == 1) { + list.clear() + } + list.addAll(items) + val isEnableLoadMore = items.size >= PAGE_SIZE + if (isEnableLoadMore) { + pageNo++ + } + binding.refreshLayout.setEnableLoadMore(isEnableLoadMore) + adapter.notifyDataSetChanged() + binding.root.hideKeyboard() + } + private var pageNo = 1 - private var emptyBinding: LayoutEmptySearchBinding? = null + // private var emptyBinding: LayoutEmptySearchBinding? = null private fun loadEmptyView() { - if (emptyBinding == null) { - emptyBinding = - LayoutEmptySearchBinding.inflate(layoutInflater, binding.rvFoodList, false) - } - emptyBinding?.root?.let { layout -> - layout.setOnClickListener { layout.hideKeyboard() } - adapter.stateView = layout - } + binding.refreshLayout.gone() + binding.emptyInclude.root.visible() + //if (emptyBinding == null) { + // emptyBinding = + // LayoutEmptySearchBinding.inflate(layoutInflater, binding.rvFoodList, false) + //} + //emptyBinding?.root?.let { layout -> + // layout.setOnClickListener { layout.hideKeyboard() } + // adapter.stateView = layout + //} } private fun deleteGoods(position: Int) { @@ -154,4 +205,16 @@ class CollectedFoodActivity : BaseActivity() { }.start() }).show() } + + + private fun finishRefresh() { + binding.refreshLayout.let { + if (pageNo == 1) { + it.finishRefresh(500) + } else { + it.finishLoadMore(500) + } + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/activity/MainActivity.kt b/app/src/main/java/com/sw/dualscreen/activity/MainActivity.kt index 07638db..ad395b8 100644 --- a/app/src/main/java/com/sw/dualscreen/activity/MainActivity.kt +++ b/app/src/main/java/com/sw/dualscreen/activity/MainActivity.kt @@ -11,6 +11,7 @@ import android.os.Looper import android.text.TextUtils import android.view.View import android.view.ViewOutlineProvider +import androidx.activity.OnBackPressedCallback import androidx.activity.viewModels import androidx.camera.core.CameraSelector import androidx.camera.core.ImageAnalysis @@ -21,20 +22,22 @@ import androidx.camera.lifecycle.ProcessCameraProvider import androidx.core.content.ContextCompat 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.adapter.SearchFoodAdapter import com.sw.dualscreen.databinding.ActivityMainBinding import com.sw.dualscreen.ext.dp import com.sw.dualscreen.ext.gone +import com.sw.dualscreen.ext.load import com.sw.dualscreen.ext.visible import com.sw.dualscreen.model.response.FoodInfo +import com.sw.dualscreen.model.response.FoodOrder import com.sw.dualscreen.objbox.FoodModule import com.sw.dualscreen.objbox.FoodModule.IdNameScore import com.sw.dualscreen.presentation.MainScreenPresentation import com.sw.dualscreen.sdk.SensorScaleUtils import com.sw.dualscreen.utils.BitmapSaver import com.sw.dualscreen.utils.Debouncer -import com.sw.dualscreen.utils.GlideUtils import com.sw.dualscreen.utils.GsonUtils import com.sw.dualscreen.utils.ImageUtil import com.sw.dualscreen.utils.SPUtil @@ -65,8 +68,9 @@ class MainActivity : BaseActivity() { private val viewModel by viewModels() private var imageCapture: ImageCapture? = null - private var isAnalyzing = true // 控制是否进行图像分析 -// private var presentation: SecondaryScreenPresentation? = null + var isAnalyzing = true // 控制是否进行图像分析 + + // private var presentation: SecondaryScreenPresentation? = null private var presentation: MainScreenPresentation? = null private val executor = Executors.newSingleThreadExecutor() private val handler by lazy { Handler(Looper.getMainLooper()) } @@ -86,7 +90,7 @@ class MainActivity : BaseActivity() { } } } - private var checkedItem: FoodInfo? = null + var checkedItem: FoodInfo? = null private var imageAnalysis: ImageAnalysis? = null private var cameraProviderFuture: ListenableFuture? = null private var canIdentify: Boolean = false @@ -106,6 +110,7 @@ class MainActivity : BaseActivity() { override fun initialize() { super.initialize() + addBackEventListener() FoodModule.init(this) initView() setupSecondaryDisplay() @@ -204,45 +209,44 @@ class MainActivity : BaseActivity() { // binding.tvRescan.setOnClickListener { // debouncer.debounce { recognizeFood() } // } - binding.btnPay.setOnClickListener { - if (checkedItem == null) { - ToastUtils.showToast("暂无识别数据,请搜索选择") - return@setOnClickListener + binding.btnPay.let { + it.run { + val mode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0) + if (mode == 0) visible() else gone() + } + it.setOnClickListener { v -> + if (checkedItem == null) { + ToastUtils.showToast("暂无识别数据,请搜索选择") + return@setOnClickListener + } + createOrder(checkedItem!!) } - presentation?.dismiss() - startActivity(Intent(this, PayActivity::class.java).apply { - putExtra(PayActivity.FOOD_INFO, checkedItem) - }) } } fun updateCurrentFood(foodInfo: FoodInfo?) { - if (foodInfo != null) { - presentation?.updateFood(foodInfo) - binding.tvFoodName.text = foodInfo.foodName - binding.previewView.visibility = View.GONE - binding.ivImg.visibility = View.VISIBLE - val imgUrl = foodInfo.imgUrl - if (TextUtils.isEmpty(imgUrl)) { - takePhoto { imgUri -> - GlideUtils.loadRoundCornerImage(this, imgUri, binding.ivImg, 12) - presentation?.updateFood(foodInfo.copy(imgUrl = imgUri.toString())) - } - } else { - if (foodInfo.photoUri != null) { - GlideUtils.loadRoundCornerImage(this, foodInfo.photoUri, binding.ivImg, 12) - } else { - GlideUtils.loadRoundCornerImage(this, foodInfo.imgUrl, binding.ivImg, 12) - } - } - pauseAnalysis() - } else { + if (foodInfo == null) { binding.tvFoodName.text = "-" binding.previewView.visibility = View.VISIBLE binding.ivImg.visibility = View.GONE resumeAnalysis() viewModel.cleanIdentifiedFoodInfoList() + return } + pauseAnalysis() + presentation?.updateFood(foodInfo) + binding.tvFoodName.text = foodInfo.foodName + binding.previewView.visibility = View.GONE + binding.ivImg.visibility = View.VISIBLE + if (foodInfo.foodImg.isNullOrBlank()) { + takePhoto { imgUri -> + binding.ivImg.load(imgUri) + presentation?.updateFood(foodInfo.copy(foodImg = imgUri.toString())) + } + } else { + binding.ivImg.load(foodInfo.photoUri ?: foodInfo.foodImg) + } + //pauseAnalysis() } override fun registerDataChange() { @@ -343,21 +347,13 @@ class MainActivity : BaseActivity() { @SuppressLint("NotifyDataSetChanged") 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: 临时测试数据,后续删除------------------- + Timber.d( + "registerDataChange识别后查询接口数据:${GsonUtils.toJson(list)},识别数据:${ + GsonUtils.toJson( + scoreList + ) + }" + ) list.forEach { foodInfo -> val scoreItem = scoreList.firstOrNull { it.name == foodInfo.foodName } val score = scoreItem?.score ?: 0.0 @@ -476,7 +472,7 @@ class MainActivity : BaseActivity() { fun resumeAnalysis() { Timber.d("resumeAnalysis isAnalyzing = $isAnalyzing") if (isAnalyzing) return - isAnalyzing = true +// isAnalyzing = true imageAnalysis?.setAnalyzer(executor) { imageProxy -> if (presentation?.currentStep == 1) { val bitmap = imageProxy.toBitmap() @@ -578,13 +574,13 @@ class MainActivity : BaseActivity() { } } } - //SecondaryScreenPresentation( - // activity = this, - // this, - // display = secondaryDisplay, - // viewModel = viewModel, - // recognizeViewModel = viewModels().value - //) + //SecondaryScreenPresentation( + // activity = this, + // this, + // display = secondaryDisplay, + // viewModel = viewModel, + // recognizeViewModel = viewModels().value + //) // presentation!!.setStepChangeCallback { step -> // if (step == 1) { // Timber.d("setStepChangeCallback${step}") @@ -629,6 +625,7 @@ class MainActivity : BaseActivity() { val mode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) presentation?.updateMealPickupMode(mode ?: 0) } + override fun onPause() { super.onPause() // 取消定时器 @@ -658,4 +655,61 @@ class MainActivity : BaseActivity() { handler.postDelayed(timeoutRunnable, TIME_OUT) lastTouchTime = System.currentTimeMillis() } + + private fun createOrder(foodInfo: FoodInfo) { + val mode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0) + val realWeight = (lastWeight * 1000).roundToInt() + val order = FoodOrder( + deviceId = GlobalData.deviceId, + foodId = foodInfo.foodId, + foodName = foodInfo.foodName ?: "", + foodMaterialId = foodInfo.foodMaterialId ?: "", + specId = foodInfo.specId ?: "", + foodWeight = realWeight, + eatWeight = realWeight, + //根据specId对应规格重量计算 + eatNum = getEatNum(realWeight, foodInfo.specWeight ?: 0.0), + userId = null, + notPay = mode != 0, + ) + viewModel.createOrder(order) { orderId -> + runOnUiThread { + if (orderId.isBlank()) { + ToastUtils.showToast("订单id为空") + return@runOnUiThread + } + foodOrderId = orderId + presentation?.dismiss() + startActivity(Intent(this, PayActivity::class.java).apply { + putExtra(PayActivity.FOOD_INFO, foodInfo) + putExtra(PayActivity.FOOD_ORDER_ID, orderId) + }) +// ToastUtils.showToast("订单已生成") + } + } + } + + private fun getEatNum(realWeight: Int, specWeight: Double): Int { + if (realWeight == 0 || specWeight == 0.0) return 0 + return (realWeight / specWeight).roundToInt() + } + + private var foodOrderId: String = "" + private var isExist = false + fun addBackEventListener() { + onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + if (isExist.not()) { + ToastUtils.showToast("再按一次退出") + isExist = true + binding.root.postDelayed({ + isExist = false + }, 2000) + return + } + finish() + } + }) + } + } \ 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 5f4a0e3..b3fb237 100644 --- a/app/src/main/java/com/sw/dualscreen/activity/PayActivity.kt +++ b/app/src/main/java/com/sw/dualscreen/activity/PayActivity.kt @@ -1,6 +1,5 @@ package com.sw.dualscreen.activity -import android.view.View import androidx.activity.viewModels import androidx.fragment.app.Fragment import com.sw.dualscreen.R @@ -11,7 +10,7 @@ import com.sw.dualscreen.activity.fragment.pay.PayResultFragment import com.sw.dualscreen.activity.fragment.pay.ScanQrCodePayFragment import com.sw.dualscreen.databinding.ActivityPayBinding import com.sw.dualscreen.model.response.FoodInfo -import com.sw.dualscreen.presentation.pay.FacePayPresentation +import com.sw.dualscreen.model.response.MemberInfo import com.sw.dualscreen.presentation.pay.ScanQrCodePayPresentation import com.sw.dualscreen.viewmodel.BaseViewModel import com.sw.dualscreen.viewmodel.UserViewModel @@ -22,6 +21,10 @@ class PayActivity : BaseActivity() { companion object { const val FOOD_INFO = "foodInfo" + const val TOTAL_AMOUNT = "totalAmount" + const val FOOD_ORDER_ID = "foodOrderId" + + // const val MEMBER_INFO = "memberInfo" const val TAG_PAY_QR_CODE = "tagPayQrCode" const val TAG_PAY_CASH = "tagPayCash" const val TAG_PAY_NUMBER = "tagPayNumber" @@ -42,16 +45,22 @@ class PayActivity : BaseActivity() { var foodInfo: FoodInfo? = null + // var totalAmount:Double = 0.0 private var qrCodePayFragment: ScanQrCodePayFragment? = null private var cashPayFragment: CashPayFragment? = null private var numberPayFragment: NumberPayFragment? = null var facePayFragment: FacePayFragment? = null private var payResultFragment: PayResultFragment? = null + var foodOrderId:String = "" + @Suppress("DEPRECATION") override fun initialize() { super.initialize() foodInfo = intent.getParcelableExtra(FOOD_INFO) + foodOrderId = intent.getStringExtra(FOOD_ORDER_ID)?:"" +// totalAmount = intent.getDoubleExtra(TOTAL_AMOUNT,0.0) +// memberInfo = intent.getParcelableExtra(MEMBER_INFO) binding.tvFoodName.text = foodInfo?.foodName binding.include.ivPageBack.setOnClickListener { finish() } binding.include.tvPageTitle.text = "下单结算" @@ -140,10 +149,10 @@ class PayActivity : BaseActivity() { // } // } - fun showPayResult() { - payResultFragment = PayResultFragment() - showFragment(payResultFragment!!, TAG_PAY_RESULT) - } +// fun showPayResult() { +// payResultFragment = PayResultFragment.instance(1, memberInfo) +// showFragment(payResultFragment!!, TAG_PAY_RESULT) +// } override fun onDestroy() { qrCodePayFragment?.presentation?.dismiss() @@ -154,8 +163,29 @@ class PayActivity : BaseActivity() { } private var scanQrCodePayPresentation: ScanQrCodePayPresentation? = null - fun paySuccess() { - showFragment(PayResultFragment(), "") + var memberInfo: MemberInfo? = null + fun showPayInfo(memberInfo: MemberInfo) { + this.memberInfo = memberInfo + payResultFragment = PayResultFragment.instance(1, memberInfo) + showFragment(payResultFragment!!, TAG_PAY_RESULT) + if (displays.size > 1) { + scanQrCodePayPresentation = ScanQrCodePayPresentation( + activity = this, + display = displays[1], + type = 1 + ) { + scanQrCodePayPresentation?.dismiss() + }.also { + it.foodName = foodInfo?.foodName + it.totalPrice = foodInfo?.vipPrice?:0.0 + } + scanQrCodePayPresentation?.show() + } + } + + fun showPaySuccess() { + payResultFragment = PayResultFragment.instance(2, memberInfo!!) + showFragment(payResultFragment!!, TAG_PAY_RESULT) if (displays.size > 1) { scanQrCodePayPresentation = ScanQrCodePayPresentation( activity = this, @@ -164,8 +194,8 @@ class PayActivity : BaseActivity() { ) { scanQrCodePayPresentation?.dismiss() }.also { -// it.foodName = foodName -// it.payAmount = payAmount + it.foodName = foodInfo?.foodName + it.totalPrice = foodInfo?.vipPrice?:0.0 } scanQrCodePayPresentation?.show() } 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 73310fd..bfe8540 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 @@ -17,18 +17,18 @@ class BusinessFragment: BaseFragment() { override fun initialize() { binding.rgCharge.let { - it.setOnCheckedChangeListener { group, checkedId -> - val mode = if (checkedId == R.id.rbChargeYes) 0 else 1 - SPUtil.getInstance().put(GlobalKey.KEY_CHARGE_MODE, mode) - } +// it.setOnCheckedChangeListener { group, checkedId -> +// val mode = if (checkedId == R.id.rbChargeYes) 0 else 1 +// SPUtil.getInstance().put(GlobalKey.KEY_CHARGE_MODE, mode) +// } val mode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0) it.check(if (mode == 0) R.id.rbChargeYes else R.id.rbChargeNot) } binding.rgTakeFood.let { - it.setOnCheckedChangeListener { group, checkedId -> - val mode = if (checkedId == R.id.rbPickAndPlace) 0 else 1 - SPUtil.getInstance().put(GlobalKey.KEY_PICKUP_MODE, mode) - } +// it.setOnCheckedChangeListener { group, checkedId -> +// val mode = if (checkedId == R.id.rbPickAndPlace) 0 else 1 +// SPUtil.getInstance().put(GlobalKey.KEY_PICKUP_MODE, mode) +// } val mode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) it.check(if (mode == 0) R.id.rbPickAndPlace else R.id.rbSurplusCalculate) } @@ -49,6 +49,8 @@ class BusinessFragment: BaseFragment() { mode = if (binding.rgTakeFood.checkedRadioButtonId == R.id.rbPickAndPlace) 0 else 1 SPUtil.getInstance().put(GlobalKey.KEY_PICKUP_MODE, mode) + + ToastUtils.showToast("设置已保存") } SensorScaleUtils.addWeightListener { value -> val realWeight = (value * 1000).roundToInt() 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 24d28c7..989a991 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 @@ -12,6 +12,7 @@ import android.widget.Toast import androidx.camera.view.PreviewView import androidx.core.net.toUri import androidx.core.view.updateLayoutParams +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.GridLayoutManager import com.sw.dualscreen.R import com.sw.dualscreen.activity.CollectedFoodActivity @@ -30,10 +31,12 @@ import com.sw.dualscreen.objbox.ObjectBox import com.sw.dualscreen.utils.BitmapSaver import com.sw.dualscreen.utils.CameraUtils import com.sw.dualscreen.utils.Debouncer +import com.sw.dualscreen.utils.ImageUploader import com.sw.dualscreen.utils.ImageUtil import com.sw.plate.utils.ToastUtils import io.objectbox.Box import io.objectbox.kotlin.boxFor +import kotlinx.coroutines.launch import okhttp3.RequestBody import okhttp3.RequestBody.Companion.toRequestBody import timber.log.Timber @@ -44,7 +47,7 @@ class CollectFragment : BaseFragment() { companion object { private const val TAG = "CollectFragment" - const val MAX_COUNT = 5 + const val MAX_COUNT = 100 } private var selectedFoodId: String? = "" private var selectedFoodName: String? = "" @@ -181,9 +184,7 @@ class CollectFragment : BaseFragment() { Toast.makeText(requireActivity(), "请拍摄菜品照片", Toast.LENGTH_SHORT).show() return@setOnClickListener } - vectorThread(){ - upload() - } + upload() } binding.editFoodName.setOnEditorActionListener { v, actionId, event -> @@ -216,42 +217,71 @@ class CollectFragment : BaseFragment() { } } - private fun uploadCollectFoodPics(files: List, params: HashMap, callback: (Boolean) -> Unit) { - (requireActivity() as SettingActivity).viewModel.uploadCollectFoodPics(files,params, callback) - } private fun upload() { - val params = HashMap() - //params["placeId"] = restId.toRequestBody() - params["foodId"] = checkedItem!!.foodId.toRequestBody() - params["foodName"] = checkedItem!!.foodName!!.toRequestBody() - //params["foodVector"] = foodVector.toRequestBody() - for (index in foodCollectionList.indices step 5) { - val end = if(index + 5 < foodCollectionList.size - 1) index + 5 else foodCollectionList.size - 1 - val subList = foodCollectionList.subList(index, end) - val subFiles = subList.map { it.imageFile } - uploadCollectFoodPics(subFiles, params) { isSuccess-> - Timber.tag(TAG).d("uploadMultipleImages: ${isSuccess}") - subList.filter { it.imageFile!=null }.forEach { it.uploadSuccess = isSuccess } - -// val count = collectList.count { it.imageFile!=null && it.uploadSuccess.not() } -// runOnUiThread { -// binding.btnUploadImage.text = "待上传图片${count}张" -// if (count == 0) { -// Loading.dismiss() -// } -// } - } + lifecycleScope.launch { + val totalFileCount = foodCollectionList.count { it.imageFile != null } + settingActivity?.showWaitingDialog("图片上传中0/$totalFileCount") + val params = HashMap() + //params["placeId"] = restId.toRequestBody() + params["foodId"] = checkedItem!!.foodId.toRequestBody() + params["foodName"] = checkedItem!!.foodName!!.toRequestBody() + //params["foodVector"] = foodVector.toRequestBody() + ImageUploader( + totalList = foodCollectionList, + uploadImage = { batch-> + val files = batch.map { it.imageFile } + (requireActivity() as SettingActivity).viewModel.uploadCollectFoodPics(files,params) + }, + onProgress = { count, batch -> + activity?.runOnUiThread { + settingActivity?.showWaitingDialog2("图片上传中$count/$totalFileCount") + batch.forEach { + it.uploadSuccess = true + } + } + }, + onError = { + activity?.runOnUiThread { + ToastUtils.showToast("上传失败,请稍后重试") + } + }, + onComplete = { + vectorThread() + } + ).processUploads() } + + +// for (index in foodCollectionList.indices step 5) { +// val end = if(index + 5 < foodCollectionList.size - 1) index + 5 else foodCollectionList.size - 1 +// val subList = foodCollectionList.subList(index, end) +// val subFiles = subList.map { it.imageFile } +// uploadCollectFoodPics(subFiles, params) { isSuccess-> +// Timber.tag(TAG).d("uploadMultipleImages: ${isSuccess}") +// subList.filter { it.imageFile!=null }.forEach { it.uploadSuccess = isSuccess } +// +//// val count = collectList.count { it.imageFile!=null && it.uploadSuccess.not() } +//// runOnUiThread { +//// binding.btnUploadImage.text = "待上传图片${count}张" +//// if (count == 0) { +//// Loading.dismiss() +//// } +//// } +// } +// } } - private fun vectorThread(block:()-> Unit) { + private fun vectorThread() { settingActivity?.showWaitingDialog("加载中……") Thread { foodCollectionList // .filter { it.bitmap != null } - .filter { it.imageVector != null } +// .filter { it.imageVector != null } .forEachIndexed { index, it -> + if (it.imageVector == null) { + return@forEachIndexed + } image2VectorTask(imageVector = it.imageVector!!, index) } activity?.runOnUiThread { 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 index ab9b2a4..a933e2f 100644 --- 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 @@ -18,9 +18,9 @@ class FacePayFragment : BaseFragment() { override fun initialize() { payActivity = activity as PayActivity - binding.tvFaceState.setOnClickListener { - payActivity?.showPayResult() - } +// binding.tvFaceState.setOnClickListener { +// payActivity?.showPayResult() +// } foodName = payActivity?.foodInfo?.foodName payAmount = "36.80" showSubScreen() diff --git a/app/src/main/java/com/sw/dualscreen/activity/fragment/pay/PayResultFragment.kt b/app/src/main/java/com/sw/dualscreen/activity/fragment/pay/PayResultFragment.kt index 320a088..ff7f644 100644 --- a/app/src/main/java/com/sw/dualscreen/activity/fragment/pay/PayResultFragment.kt +++ b/app/src/main/java/com/sw/dualscreen/activity/fragment/pay/PayResultFragment.kt @@ -1,16 +1,47 @@ package com.sw.dualscreen.activity.fragment.pay +import android.os.Bundle import android.text.Spanned import android.text.SpannedString import android.text.style.AbsoluteSizeSpan +import android.text.style.ForegroundColorSpan +import androidx.core.graphics.toColorInt import androidx.core.text.buildSpannedString import com.sw.dualscreen.R import com.sw.dualscreen.activity.PayActivity import com.sw.dualscreen.activity.fragment.BaseFragment import com.sw.dualscreen.databinding.FragmentPayResultBinding +import com.sw.dualscreen.ext.format2String +import com.sw.dualscreen.ext.gone +import com.sw.dualscreen.ext.invisible import com.sw.dualscreen.ext.load +import com.sw.dualscreen.ext.visible +import com.sw.dualscreen.model.response.FoodInfo +import com.sw.dualscreen.model.response.MemberInfo +import com.sw.dualscreen.model.response.TextBean +import com.sw.dualscreen.utils.SpannedUtils -class PayResultFragment: BaseFragment() { +class PayResultFragment : BaseFragment() { + + //type = 0,扫码支付默认显示二维码 + //type = 1,会员结算显示二维码,是会员则显示名字、头像、手机号、可用余额 + //type = 2,支付成功,是会员则显示名字、头像、手机号、可用余额 + + companion object { + const val PAGE_TYPE = "pageType" + const val MEMBER_INFO = "memberInfo" + fun instance(pageType: Int, memberInfo: MemberInfo): PayResultFragment { + return PayResultFragment().apply { + arguments = Bundle().also { + it.putInt(PAGE_TYPE, pageType) + it.putParcelable(MEMBER_INFO, memberInfo) + } + } + } + } + + private var pageType: Int = 1 + private var memberInfo: MemberInfo? = null override fun inflateViewBinding(): FragmentPayResultBinding { return FragmentPayResultBinding.inflate(layoutInflater) @@ -22,19 +53,121 @@ class PayResultFragment: BaseFragment() { binding.btnBack.setOnClickListener { //payActivity?.showFacePay() } + arguments?.let { + pageType = it.getInt(PAGE_TYPE, 0) + memberInfo = it.getParcelable(MEMBER_INFO) + } - binding.ivHeadPic.load(R.mipmap.ic_launcher) - binding.tvUserName.text = "张三" - binding.tvUserPhone.text = "132****5678" - binding.tvUserBalance.text = getBalanceText("0.00") - binding.tvPayAmount.text = "收款金额 20.00 元" - binding.tvPayInfo.text = "应收 38.00 元,余额扣除 16.80 元" - } + totalPrice = payActivity?.foodInfo?.vipPrice ?: 0.0 + balance = (memberInfo?.topUpBalance ?: 0.0) + (memberInfo?.rewardBalance ?: 0.0) + realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance + expensesBalance = if (balance >= totalPrice) totalPrice else balance + memberInfo?.let { loadUserInfo(it) } - private fun getBalanceText(amount: String): SpannedString { - return buildSpannedString { - append("¥", AbsoluteSizeSpan(20, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - append(amount, AbsoluteSizeSpan(32, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + when (pageType) { + 0 -> { + + } + + 1 -> { + binding.layoutPayInfo.visible() + binding.layoutPaySuccess.gone() + + binding.tvRealAmount.text = SpannedUtils.getAmountText( + listOf( + TextBean(text = "¥", textSize = 36), + TextBean(text = realPayPrice.format2String(2), textSize = 60), + ) + ) + + //余额扣除 -36.80 元,扣除后可用余额 10.00 元 + //余额扣除 -16.80 元,还需支付 20.00 元 + + binding.tvUserBalance.text = SpannedUtils.getAmountText( + listOf( + TextBean(text = "¥", textSize = 24), + TextBean(text = balance.format2String(2), textSize = 36), + ) + ) + + binding.tvAccountInfo.text = SpannedUtils.getAmountText( + getAmountList() + ) + if (balance >= totalPrice) { + binding.ivPayQrCode.invisible() + binding.btnConfirmPay.isEnabled = true + } else { + binding.ivPayQrCode.visible() +// binding.ivPayQrCode.load(payQrCodePic) + binding.btnConfirmPay.isEnabled = false + } + //点击支付接口成功打开成功页面,或者扫码手动成功回调打开成功页面 + binding.btnConfirmPay.setOnClickListener { + payActivity?.showWaitingDialog("支付中,请稍后……") + binding.root.postDelayed({ + payActivity?.hideWaitingDialog() + payActivity?.showPaySuccess() + },1000) + } + } + + 2 -> { + binding.layoutPayInfo.gone() + binding.layoutPaySuccess.visible() + + binding.tvPayInfo.text = + "应收 ${totalPrice.format2String(2)} 元,余额扣除 ${expensesBalance.format2String(2)} 元" + binding.tvPayAmount.text = "收款金额 ${realPayPrice.format2String(2)} 元" + } + + else -> {} } } + + //总价格 + private var totalPrice = 0.0 + + //余额 + private var balance = 0.0 + //实际支付金额 + private var realPayPrice = 0.0 + //扣除余额 + private var expensesBalance = 0.0 + + private fun getAmountList(): List { + val list: MutableList = mutableListOf() + + list.add(TextBean(text = "余额扣除 ", textSize = 30, textColor = "#FF889AC2")) + list.add(TextBean(text = "-${expensesBalance.format2String(2)}", textSize = 30, textColor = "#FF0A1428")) + + if (balance >= totalPrice) { + //余额大于等于总价格,使用余额支付 + TextBean(text = " 元,扣除后可用余额 ", textSize = 30, textColor = "#FF889AC2") + val remainingBalance = balance - totalPrice + TextBean(text = remainingBalance.format2String(2), textSize = 30, textColor = "#FF0A1428") + } else { + //余额小于总价格,使用余额+扫码支付 + //实际支付金额 + TextBean(text = " 元,还需支付 ", textSize = 30, textColor = "#FF889AC2") + TextBean(text = realPayPrice.format2String(2), textSize = 30, textColor = "#FF0A1428") + TextBean(text = " 元", textSize = 30, textColor = "#FF889AC2") + } + + return list + } + + private fun loadUserInfo(item: MemberInfo) { + binding.ivHeadPic.load(R.mipmap.ic_launcher) + binding.tvUserName.text = item.name + + val phone = item.phone ?: "" + binding.tvUserPhone.text = + if (phone.length == 11) + phone.replace(phone.substring(3, 7), "****") + else + phone + } + + + } \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/activity/fragment/pay/ScanQrCodePayFragment.kt b/app/src/main/java/com/sw/dualscreen/activity/fragment/pay/ScanQrCodePayFragment.kt index a583051..1879b9d 100644 --- a/app/src/main/java/com/sw/dualscreen/activity/fragment/pay/ScanQrCodePayFragment.kt +++ b/app/src/main/java/com/sw/dualscreen/activity/fragment/pay/ScanQrCodePayFragment.kt @@ -13,6 +13,7 @@ import com.sw.dualscreen.presentation.pay.ScanQrCodePayPresentation import com.sw.dualscreen.utils.countDownByFlow import kotlinx.coroutines.Job import com.sw.dualscreen.databinding.FragmentScanQrcodePayBinding +import com.sw.dualscreen.ext.format2String class ScanQrCodePayFragment: BaseFragment() { @@ -35,7 +36,7 @@ class ScanQrCodePayFragment: BaseFragment() { payActivity = requireActivity() as PayActivity foodName = payActivity.foodInfo?.foodName - payAmount = "36.80" + payAmount = payActivity.foodInfo?.specPrice.format2String(2) payQrCodePic = R.drawable.ic_qrcode binding.tvRealAmount.text = getAmountText(payAmount?:"") binding.ivPayQrCode.load(payQrCodePic) @@ -75,7 +76,7 @@ class ScanQrCodePayFragment: BaseFragment() { presentation?.dismiss() }.also { it.foodName = foodName - it.payAmount = payAmount + it.totalPrice = payActivity.foodInfo?.specPrice?:0.0 it.payQrCodePic = payQrCodePic } presentation?.show() diff --git a/app/src/main/java/com/sw/dualscreen/adapter/FoodCollectionAdapter.kt b/app/src/main/java/com/sw/dualscreen/adapter/FoodCollectionAdapter.kt index ea86512..4ef0242 100644 --- a/app/src/main/java/com/sw/dualscreen/adapter/FoodCollectionAdapter.kt +++ b/app/src/main/java/com/sw/dualscreen/adapter/FoodCollectionAdapter.kt @@ -10,6 +10,7 @@ import com.chad.library.adapter4.viewholder.QuickViewHolder import com.sw.dualscreen.R import com.sw.dualscreen.databinding.ListItemFoodCollectionBinding import com.sw.dualscreen.ext.dp +import com.sw.dualscreen.ext.load import com.sw.dualscreen.objbox.FoodCollectionBean import com.sw.dualscreen.utils.GlideUtils @@ -38,14 +39,15 @@ class FoodCollectionAdapter (var list: MutableList) : scaleType = ImageView.ScaleType.CENTER_CROP //setImageURI(it.imageUri) //setImageBitmap(it.bitmap) - it.bitmap?.let { bitmap -> - GlideUtils.loadRoundCornerWitBitmap( - context, - url = bitmap, - imageView = this, - radius = 8.dp - ) - } + load(it.imageFile) +// it.bitmap?.let { bitmap -> +// GlideUtils.loadRoundCornerWitBitmap( +// context, +// url = bitmap, +// imageView = this, +// radius = 8.dp +// ) +// } } } } diff --git a/app/src/main/java/com/sw/dualscreen/ext/CommonExt.kt b/app/src/main/java/com/sw/dualscreen/ext/CommonExt.kt index d3529c7..89f09b2 100644 --- a/app/src/main/java/com/sw/dualscreen/ext/CommonExt.kt +++ b/app/src/main/java/com/sw/dualscreen/ext/CommonExt.kt @@ -65,8 +65,8 @@ val Float.sp: Float Resources.getSystem().displayMetrics ) -fun Double.format2String(): String = "%.1f".format(this) -fun Double.format2String(num:Int): String = "%.${num}f".format(this) +fun Double?.format2String(): String = this.format2String(1) +fun Double?.format2String(num:Int): String = "%.${num}f".format(this?:0.0) // 添加扩展函数 fun ImageProxy.toSafeBitmap(): Bitmap { diff --git a/app/src/main/java/com/sw/dualscreen/model/response/DataBean.kt b/app/src/main/java/com/sw/dualscreen/model/response/DataBean.kt index d50dab9..a1b6798 100644 --- a/app/src/main/java/com/sw/dualscreen/model/response/DataBean.kt +++ b/app/src/main/java/com/sw/dualscreen/model/response/DataBean.kt @@ -1,5 +1,55 @@ package com.sw.dualscreen.model.response +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + data class FoodSearchReq( - var nameList:List + var nameList: List +) + +data class FoodOrder( + //设备id + var deviceId: String, + //菜品id + var foodId: String, + //菜品名称 + var foodName: String, + //菜品营养表id + var foodMaterialId: String, + //餐品规格ID + var specId: String, + //用户id(档口机,内部员工直接取餐:获取的用户ID/memberId) + var userId: String? = null, + //食物重量 + var foodWeight: Int, + //食用重量 + var eatWeight: Int, + //份数 + var eatNum: Int, + //是否需要付款 + var notPay: Boolean, + //备注 + var remark: String? = "", +) + +@Parcelize +data class MemberInfo( + //会员id + val id: String?, + //手机号 + val phone: String?, + //姓名 + val name: String?, + //充值余额 + val topUpBalance: Double?, + //赠送余额 + val rewardBalance: Double?, + //积分余额 + val integralBalance: Int? +) : Parcelable + +data class TextBean( + var text: String, + var textSize: Int, + var textColor: String? = null ) \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/model/response/FoodInfo.kt b/app/src/main/java/com/sw/dualscreen/model/response/FoodInfo.kt index 58d4c74..3be4e88 100644 --- a/app/src/main/java/com/sw/dualscreen/model/response/FoodInfo.kt +++ b/app/src/main/java/com/sw/dualscreen/model/response/FoodInfo.kt @@ -24,13 +24,21 @@ data class FoodInfo( //碳水化合物 val carbohydrate:String? = null, //规格售卖价格(元) - val specPrice:String? = null, + val specPrice: Double? = null, //VIP售卖价(元) - val vipPrice:String? = null, + val vipPrice:Double? = null, + //餐品营养id + val foodMaterialId:String? = null, + //菜品规格id + val specId:String? = null, + //规格重量(g) + val specWeight:Double? = null, + + //----------------------------------- var score: Int = 0, var isChecked: Boolean = false, var photoUri: Uri? = null, - var imgUrl: String? = "", + var foodImg: String? = "", // @SerializedName("foodTypeAndRealIntakeVoList") // val foodTypeAndRealIntakeVoList: List? = listOf(), diff --git a/app/src/main/java/com/sw/dualscreen/network/api/ApiService.kt b/app/src/main/java/com/sw/dualscreen/network/api/ApiService.kt index 7099c89..133d908 100644 --- a/app/src/main/java/com/sw/dualscreen/network/api/ApiService.kt +++ b/app/src/main/java/com/sw/dualscreen/network/api/ApiService.kt @@ -6,6 +6,8 @@ import com.sw.dualscreen.model.response.ApiResponse import com.sw.dualscreen.model.response.DinnerType import com.sw.dualscreen.model.response.FoodInfo import com.sw.dualscreen.model.response.FoodSearchReq +import com.sw.dualscreen.model.response.FoodOrder +import com.sw.dualscreen.model.response.MemberInfo import com.sw.dualscreen.model.response.UserFaceModel import com.sw.dualscreen.model.response.UserNutritionData import com.sw.dualscreen.objbox.CollectedFoodInfo @@ -71,7 +73,7 @@ interface ApiService { @POST suspend fun getCollectedFoodList( @Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/boothMachine/app/dishPage", - @Body param: Map + @Body param: Map ): ApiResponse> @@ -108,7 +110,7 @@ interface ApiService { @GET suspend fun getDinnerType( // @Url url: String = "${GlobalData.appBaseUrl}/zhstapi/zhst/getCanteenDinnerType", - @Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/boothMachine/app/getRegionRule", + @Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/common/app/getRegionRule", // @Query("appVersion") appVersion: String = GlobalData.appVersion, // @Query("canteenId") restId: String, ): ApiResponse @@ -134,6 +136,34 @@ interface ApiService { @Body req: FoodSearchReq ): ApiResponse> + /** + * 开餐-生成订单 + */ + @POST + suspend fun createOrder( + @Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/boothMachine/app/userEatFood/doubleScale", + @Body order: FoodOrder + ): ApiResponse + + /** + * APP根据id获取会员或员工信息及余额 + */ + @GET + suspend fun getMemberInfo( + @Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/common/app/getUserInfoBalanceById", + @Query("id") memberId: String, + ): ApiResponse + + /** + * 绑定用户与订单号 + */ + @GET + suspend fun bindOrder( + @Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/boothMachine/app/bingOrder", + @Query("userId") userId: String, + @Query("orderId") orderId: String + ): ApiResponse + /** * 提交采集图片数据 diff --git a/app/src/main/java/com/sw/dualscreen/presentation/MainScreenPresentation.kt b/app/src/main/java/com/sw/dualscreen/presentation/MainScreenPresentation.kt index 95bba69..4c8b092 100644 --- a/app/src/main/java/com/sw/dualscreen/presentation/MainScreenPresentation.kt +++ b/app/src/main/java/com/sw/dualscreen/presentation/MainScreenPresentation.kt @@ -16,6 +16,7 @@ import android.view.ViewOutlineProvider import android.view.ViewTreeObserver import androidx.annotation.RequiresApi import androidx.core.content.ContextCompat +import androidx.core.view.updateLayoutParams import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope import com.arcsoft.face.ErrorInfo @@ -26,6 +27,7 @@ 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.load import com.sw.dualscreen.ext.maskName import com.sw.dualscreen.ext.visible import com.sw.dualscreen.model.request.UserNutritionParam @@ -33,8 +35,6 @@ import com.sw.dualscreen.model.response.DinnerType 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 @@ -55,6 +55,7 @@ import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel import kotlinx.coroutines.flow.drop import kotlinx.coroutines.launch import timber.log.Timber +import kotlin.math.max import kotlin.math.roundToInt class MainScreenPresentation( @@ -63,7 +64,7 @@ class MainScreenPresentation( val userViewModel: UserViewModel, val recognizeViewModel: RecognizeViewModel, private val onDismissListener: () -> Unit = {} -) : Presentation(activity, display), ViewTreeObserver.OnGlobalLayoutListener { +) : Presentation(activity, display), ViewTreeObserver.OnGlobalLayoutListener { companion object { const val TAG = "SubScreenPresentation" @@ -71,6 +72,7 @@ class MainScreenPresentation( private lateinit var binding: PresentationMainScreenBinding private lateinit var stepChangeCallback: (Int) -> Unit + // 当前步骤 var currentStep: Int = 0 @@ -110,11 +112,19 @@ class MainScreenPresentation( fun updateWeight(weight: Double) { lastWeight = weight * 1000 // 将千克转成克 - Timber.tag(TAG).d("updateWeight lastWeight = $weight, currentStep = $currentStep, isGoStep1 = $isGoStep1") - if ((currentStep == 2) && - lastWeight <= 5 && - isGoStep1 - ) {//人脸识别过程中,秤上物品拿走 + Timber.tag(TAG) + .d("updateWeight lastWeight = $weight, currentStep = $currentStep, isGoStep1 = $isGoStep1") +// if (lastWeight <= 5) { +// activity.isAnalyzing = false +// } + if (currentStep == 2 && lastWeight <= 5 && isGoStep1) { + //人脸识别过程中,秤上物品拿走 + isGoStep1 = false + activity.runOnUiThread { + Timber.tag(TAG).e("切回step1") + step1FoodRecognizing() + } + } else if (lastWeight <= 5) { isGoStep1 = false activity.runOnUiThread { Timber.tag(TAG).e("切回step1") @@ -140,6 +150,7 @@ class MainScreenPresentation( } calculateNutrition(recognitionWeight - lastWeight) } + fun setStepChangeCallback(callback: (Int) -> Unit) { stepChangeCallback = callback } @@ -200,10 +211,10 @@ class MainScreenPresentation( 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) +// step3ShowRecognizeResult() +// //binding.nutritionInclude.tvUserName.text = it.userName.maskName() +// //binding.nutritionInclude.tvRecommendHeat.text = "推荐热量:${it.recommendMin}-${it.recommendMax}" +// //updateWeight(lastWeight / 1000) } } activity.lifecycleScope.launch { @@ -227,20 +238,22 @@ class MainScreenPresentation( ToastUtils.showToast("选中物品为空") return } - updateFoodInfo(currentFood!!) +// updateFoodInfo(currentFood!!) - foodRecSuccess(currentFood!!) + //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 -// } + 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() @@ -274,11 +287,46 @@ class MainScreenPresentation( //updateFoodInfo(foodInfo) - userNutritionData?.let { - Timber.tag(TAG).d("loadBilledMode:${GsonUtils.toJson(it)}") +// userNutritionData?.let { +// Timber.tag(TAG).d("loadBilledMode:${GsonUtils.toJson(it)}") +// } + currentFood?.let { + val specPrice = it.specPrice?:0.0 + val vipPrice = it.vipPrice?:0.0 + binding.tvNormalPrice.text = "${specPrice.format2String(2)} 元/份" + binding.tvVipPrice.text = "${vipPrice.format2String(2)} 元/份" + + var calorie = getIntNutritionValue(it.calorie) + calorie = max(calorie, 0) + val fat = getIntNutritionValue(it.fat) + val protein = getIntNutritionValue(it.protein) + val carbohydrate = getIntNutritionValue(it.carbohydrate) + + val fatRate = getNutritionRate(fat, calorie) + val proteinRate = getNutritionRate(protein, calorie) + val carbohydrateRate = getNutritionRate(carbohydrate, calorie) + binding.calorieInclude.run { + tvCalorie.text = "$calorie" + tvFatRate.text = "$fatRate%" + tvProteinRate.text = "$proteinRate%" + tvCarbohydrateRate.text = "$carbohydrateRate%" + + viewFatLine.updateLayoutParams { height = (fatRate/100.0*84).roundToInt()*1.dp } + viewProteinLine.updateLayoutParams { height = (proteinRate/100.0*84).roundToInt()*1.dp } + viewCarbohydrateLine.updateLayoutParams { height = (carbohydrateRate/100.0*84).roundToInt()*1.dp } + } } + } + private fun getNutritionRate(value: Int, total: Int): Int{ + if (value == 0 || total == 0) return 0 + return (100.0 * value / total).roundToInt() + } + + private fun getIntNutritionValue(text: String?): Int { + return if (text.isNullOrBlank()) 0 else text.toFloat().roundToInt() } + /** * 餐品识别成功,不计费模式 */ @@ -296,7 +344,8 @@ class MainScreenPresentation( //updateFoodInfo(foodInfo) userNutritionData?.let { binding.nutritionInclude.tvUserName.text = it.userName.maskName() - binding.nutritionInclude.tvRecommendHeat.text = "推荐热量:${it.recommendMin}-${it.recommendMax}kcal" + binding.nutritionInclude.tvRecommendHeat.text = + "推荐热量:${it.recommendMin}-${it.recommendMax}kcal" updateWeight(lastWeight / 1000) } } @@ -307,7 +356,7 @@ class MainScreenPresentation( fun step1FoodRecognizing() { hideStandbyScreen() activity.resetTouchTime() - + activity.resumeAnalysis() Timber.tag(TAG).d("step1") //if (abs(lastWeight.toInt()) <= 5) { // activity.runOnUiThread { @@ -332,8 +381,8 @@ class MainScreenPresentation( binding.tvFoodName.text = "餐品识别中..." binding.tvFoodRecPrompt.visible() binding.flCameraView.visible() + binding.ivRecImage.gone() binding.ivPreviewImage.visible() - //binding.ivRecImage.gone() binding.ivFaceRecMask.gone() binding.tvFaceTip.gone() binding.llPriceInfo.gone() @@ -344,17 +393,21 @@ class MainScreenPresentation( activity.clearFoodList() } - fun foodRecSuccess(foodInfo: FoodInfo) { - //识别逻辑完成根据是否计费显示不同页面 - val mode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0) - if (mode == 0) { - //计费 - loadBilledMode() - } else { - //不计费 - loadUnbilledMode() - } - } +// fun foodRecSuccess(foodInfo: FoodInfo) { +// //识别逻辑完成根据是否计费显示不同页面 +// val mode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0) +// if (mode == 0) { +// //计费 +// loadBilledMode() +// } else { +// //不计费 +// //userViewModel.getUserNutritionData( +// // userId = userId, +// // foodId = currentFood!!.foodId!! +// //) +// loadUnbilledMode() +// } +// } /** * 用户人脸识别中 @@ -377,7 +430,7 @@ class MainScreenPresentation( binding.tvFoodRecPrompt.visible() binding.llPriceInfo.gone() - //binding.ivRecImage.gone() + binding.ivRecImage.gone() binding.ivPreviewImage.gone() dinnerTypeInfo = null @@ -386,7 +439,9 @@ class MainScreenPresentation( stepChangeCallback(currentStep) currentFood = foodInfo + userViewModel.getDinnerType() + resumeCamera() } @@ -395,18 +450,37 @@ class MainScreenPresentation( fun updateFood(foodInfo: FoodInfo?) { Timber.tag(TAG).d("updateFood") currentFood = foodInfo - if (foodInfo != null) { + if (foodInfo == null) { + return + } + binding.tvFoodName.text = foodInfo.foodName + binding.ivPreviewImage.gone() + binding.ivRecImage.let { + it.visible() + val imageUrl = if (foodInfo.foodImg.isNullOrBlank()) foodInfo.photoUri else foodInfo.foodImg + it.load(imageUrl) + } + val mode = SPUtil.getInstance().get(GlobalKey.KEY_CHARGE_MODE, 0) + if (mode == 0) { + //计费,查询营养信息、价格信息------------------------ + loadBilledMode() + } else { + //不计费 + //开启人脸识别 if (currentStep == 1) { step2FaceRecognizing(foodInfo) - } else { - updateFoodInfo(foodInfo) } } + //if (currentStep == 1) { + // step2FaceRecognizing(foodInfo) + //} else { + // updateFoodInfo(foodInfo) + //} } private fun updateFoodInfo(foodInfo: FoodInfo) { - binding.tvFoodName.text = foodInfo.foodName - binding.ivPreviewImage.gone() +// binding.tvFoodName.text = foodInfo.foodName +// binding.ivPreviewImage.gone() //binding.ivRecImage.visible() // if (foodInfo.photoUri != null) { // GlideUtils.loadRoundCornerImage( @@ -429,9 +503,11 @@ class MainScreenPresentation( var weight = weight1 if (weight < 0) weight = 0.0 debouncer.debounce { - Timber.tag(TAG).d("calculateNutrition weight = $weight, recognitionWeight = $recognitionWeight") + Timber.tag(TAG) + .d("calculateNutrition weight = $weight, recognitionWeight = $recognitionWeight") val dinnerType = dinnerTypeInfo!!.dinnerType!! - Timber.tag(TAG).d("calculateNutrition foodName = ${currentFood!!.foodName}, dinnerType = $dinnerType, userId = ${userNutritionData!!.userId}") + Timber.tag(TAG) + .d("calculateNutrition foodName = ${currentFood!!.foodName}, dinnerType = $dinnerType, userId = ${userNutritionData!!.userId}") val calcResultInfo = UserNutritionUtils.calculateNutrition( currentFood!!, userNutritionData!!, @@ -528,6 +604,7 @@ class MainScreenPresentation( } } } + fun updateImage(bitmap: Bitmap) { activity.runOnUiThread { binding.ivPreviewImage.setImageBitmap(bitmap) @@ -538,10 +615,11 @@ class MainScreenPresentation( * 余量取餐 */ fun updateMealPickupMode(mode: Int) { - Timber.tag(TAG).d("updateMealPickupMode mode = $mode") - mealPickupMode = mode - step1FoodRecognizing() + Timber.tag(TAG).d("updateMealPickupMode mode = $mode") + mealPickupMode = mode + step1FoodRecognizing() } + override fun onStop() { if (rgbCameraHelper != null) { rgbCameraHelper!!.release() @@ -605,7 +683,8 @@ class MainScreenPresentation( recognizeViewModel.recognizeConfiguration .observe(activity, Observer { recognizeConfiguration: RecognizeConfiguration? -> - Timber.tag(TAG).i("recognizeConfiguration: observe = ${recognizeConfiguration.toString()}") + Timber.tag(TAG) + .i("recognizeConfiguration: observe = ${recognizeConfiguration.toString()}") }) recognizeViewModel.recognizeNotice.observe(activity, Observer { notice: String? -> Timber.tag(TAG).i("recognizeNotice observe notice = $notice") @@ -614,26 +693,34 @@ class MainScreenPresentation( recognizeViewModel.recognizeUserId.observe( activity, Observer { compareResult: CompareResult -> - Timber.tag(TAG).i("recognizeUserId observe compareResult = ${compareResult.trackId}, userId = ${compareResult.faceEntity.userName}") + 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 (userId == null) return@Observer if (currentFood == null) { - //重新识别 - activity.runOnUiThread { - ToastUtils.showToast("currentFood == null") - } - //activity.recognizeFood() - //step1FoodRecognizing() - return@Observer + currentFood = activity.checkedItem } - userViewModel.getUserNutritionData( - userId = userId, - foodId = currentFood!!.foodId!! - ) + if (currentFood == null) { + activity.runOnUiThread { + ToastUtils.showToast("请选择菜品") + return@runOnUiThread + } + } + // TODO: ------------------人脸识别成功 + //------------------------------------------------- + // TODO: ------------------人脸识别成功 + + step3ShowRecognizeResult() + //根据接口数据更新热量数据-------------------------- + loadUnbilledMode() +// userViewModel.getUserNutritionData( +// userId = userId, +// foodId = currentFood!!.foodId!! +// ) }) recognizeViewModel.drawRectInfoText.observe(activity, Observer { info -> @@ -724,7 +811,8 @@ class MainScreenPresentation( displayOrientation: Int, isMirror: Boolean ) { - Timber.tag(TAG).d("initRgbCamera Rgb onCameraOpened: cameraId = $cameraId, displayOrientation = $displayOrientation") + Timber.tag(TAG) + .d("initRgbCamera Rgb onCameraOpened: cameraId = $cameraId, displayOrientation = $displayOrientation") activity.runOnUiThread({ val previewSizeRgb = camera.getParameters().getPreviewSize() val layoutParams = adjustPreviewViewSize( @@ -733,8 +821,10 @@ class MainScreenPresentation( 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 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( @@ -790,16 +880,19 @@ class MainScreenPresentation( } override fun onCameraConfigurationChanged(cameraID: Int, displayOrientation: Int) { - Timber.tag(TAG).i("initRgbCamera onCameraConfigurationChanged: threadName = ${Thread.currentThread().name}") + Timber.tag(TAG) + .i("initRgbCamera onCameraConfigurationChanged: threadName = ${Thread.currentThread().name}") if (rgbFaceRectTransformer != null) { rgbFaceRectTransformer!!.cameraDisplayOrientation = displayOrientation } - Timber.tag(TAG).i("initRgbCamera onCameraConfigurationChanged: $cameraID $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 measuredHeight = binding.dualCameraTexturePreviewRgb.measuredHeight + Timber.tag(TAG) + .i("initRgbCamera measuredWidth=$measuredWidth,measuredHeight=$measuredHeight") val previewConfig: PreviewConfig = recognizeViewModel.previewConfig rgbCameraHelper = DualCameraHelper.Builder() @@ -831,7 +924,8 @@ class MainScreenPresentation( displayOrientation: Int, isMirror: Boolean ) { - Timber.tag(TAG).d("initIrCamera IR onCameraOpened: cameraId = $cameraId, displayOrientation = $displayOrientation") + Timber.tag(TAG) + .d("initIrCamera IR onCameraOpened: cameraId = $cameraId, displayOrientation = $displayOrientation") val previewSizeIr = camera.getParameters().getPreviewSize() val layoutParams = adjustPreviewViewSize( binding.dualCameraTexturePreviewRgb, @@ -872,7 +966,8 @@ class MainScreenPresentation( if (irFaceRectTransformer != null) { irFaceRectTransformer!!.cameraDisplayOrientation = displayOrientation } - Timber.tag(TAG).i("initIrCamera onCameraConfigurationChanged: cameraID = $cameraID, displayOrientation = $displayOrientation") + Timber.tag(TAG) + .i("initIrCamera onCameraConfigurationChanged: cameraID = $cameraID, displayOrientation = $displayOrientation") } } @@ -916,14 +1011,17 @@ class MainScreenPresentation( // 识别成功 binding.dualCameraFaceRectView.drawRealtimeFaceInfo(rgbDrawInfoList) } - - if (facePreviewInfoList.isEmpty() || (lastFaceTrackId != facePreviewInfoList[0]!!.trackId)) { + val listIsEmpty = facePreviewInfoList.isEmpty() + val listFirstTrackId = if (listIsEmpty.not()) facePreviewInfoList[0]!!.trackId else null + Timber.tag(TAG).d("listIsEmpty=$listIsEmpty,lastFaceTrackId=$lastFaceTrackId,listFirstTrackId=$listFirstTrackId") + if (listIsEmpty || (lastFaceTrackId != listFirstTrackId)) { if (lastFaceTrackId != -1) { mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0 Timber.tag(TAG).i("$lastFaceTrackId 用户离开") lastFaceTrackId = -1 postUserData() if (mealPickupMode == 0) { + activity.isAnalyzing = false step1FoodRecognizing() } else { step2FaceRecognizing(currentFood!!) @@ -935,7 +1033,8 @@ class MainScreenPresentation( fun postUserData() { Timber.tag(TAG).d("postUserData") if (userNutritionData == null || currentFood == null) { - Timber.tag(TAG).d("postUserData userNutritionData = ${userNutritionData == null}, currentFood = ${currentFood == null}") + Timber.tag(TAG) + .d("postUserData userNutritionData = ${userNutritionData == null}, currentFood = ${currentFood == null}") return } var eatWeight = 0.0 @@ -947,7 +1046,7 @@ class MainScreenPresentation( } val userNutritionParam = UserNutritionParam( userId = userNutritionData?.userId!!, - foodId = currentFood?.foodId !!, + foodId = currentFood?.foodId!!, faceTime = recognitionTime, faceEndTime = System.currentTimeMillis(), eatWeight = eatWeight,//lastWeight 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 index ceeecd9..b720d3e 100644 --- a/app/src/main/java/com/sw/dualscreen/presentation/pay/FacePayPresentation.kt +++ b/app/src/main/java/com/sw/dualscreen/presentation/pay/FacePayPresentation.kt @@ -30,6 +30,7 @@ import com.sw.dualscreen.R import com.sw.dualscreen.activity.PayActivity import com.sw.dualscreen.databinding.PresentationFacePayBinding import com.sw.dualscreen.ext.load +import com.sw.dualscreen.utils.GsonUtils import com.sw.dualscreen.view.CustomDialog import com.sw.dualscreen.viewmodel.UserViewModel import com.sw.plate.utils.ToastUtils @@ -267,13 +268,30 @@ class FacePayPresentation( activity.runOnUiThread { //ToastUtils.showToast("用户人脸识别成功,挑战支付页面") showWaitingDialog("刷脸支付中,请稍后……") - binding.root.postDelayed({ - hideWaitingDialog() - activity.paySuccess() - binding.root.postDelayed({ - dismiss() - },500) - },1500) + userViewModel.getMemberInfo(memberId = userId) { memberInfo -> + activity.runOnUiThread { + if (memberInfo == null) { + ToastUtils.showToast("查询会员信息失败,请稍后重试") + return@runOnUiThread + } + userViewModel.bindOrder(userId, activity.foodOrderId) { bindResult -> + activity.runOnUiThread { + if (bindResult.not()) { + hideWaitingDialog() + ToastUtils.showToast("订单绑定失败") + return@runOnUiThread + } + binding.root.postDelayed({ + hideWaitingDialog() + activity.showPayInfo(memberInfo) + binding.root.postDelayed({ + dismiss() + },500) + },1500) + } + } + } + } } // userViewModel.getUserNutritionData( // userId = userId, @@ -577,6 +595,8 @@ class FacePayPresentation( // } else { // step2FaceRecognizing(currentFood!!) // } + + resumeCamera() } } } diff --git a/app/src/main/java/com/sw/dualscreen/presentation/pay/ScanQrCodePayPresentation.kt b/app/src/main/java/com/sw/dualscreen/presentation/pay/ScanQrCodePayPresentation.kt index ecbf0f1..9192d9c 100644 --- a/app/src/main/java/com/sw/dualscreen/presentation/pay/ScanQrCodePayPresentation.kt +++ b/app/src/main/java/com/sw/dualscreen/presentation/pay/ScanQrCodePayPresentation.kt @@ -2,24 +2,23 @@ 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.text.style.ForegroundColorSpan import android.view.Display -import androidx.core.graphics.toColorInt -import androidx.core.text.buildSpannedString import com.sw.dualscreen.R import com.sw.dualscreen.activity.PayActivity import com.sw.dualscreen.databinding.PresentationScanQrcodePayBinding +import com.sw.dualscreen.ext.format2String import com.sw.dualscreen.ext.gone +import com.sw.dualscreen.ext.invisible import com.sw.dualscreen.ext.load import com.sw.dualscreen.ext.visible +import com.sw.dualscreen.model.response.MemberInfo +import com.sw.dualscreen.model.response.TextBean +import com.sw.dualscreen.utils.SpannedUtils class ScanQrCodePayPresentation( val activity: PayActivity, display: Display, - val type:Int, + val type: Int, private val onDismissListener: () -> Unit = {} ) : Presentation(activity, display) { @@ -38,74 +37,139 @@ class ScanQrCodePayPresentation( } var foodName: String? = null - var payAmount: String? = null var payQrCodePic: Any? = null + + //总价格 + var totalPrice = 0.0 + + //余额 + private var balance = 0.0 + + //实际支付金额 + private var realPayPrice = 0.0 + //扣除余额 + private var expensesBalance = 0.0 + private fun initView() { - when(type) { - 0->{ + when (type) { + 0 -> { binding.layoutVip.gone() binding.layoutPaySuccess.gone() binding.layoutScanQrCode.visible() binding.tvPayTip.text = "请扫码支付或出示付款码" binding.tvFoodName.text = foodName - binding.tvRealAmount.text = getAmountText(payAmount ?: "") + binding.tvRealAmount.text = SpannedUtils.getAmountText( + listOf( + TextBean(text = "¥", textSize = 32), + TextBean(text = totalPrice.format2String(2), textSize = 48), + ) + ) binding.ivPayQrCode.load(payQrCodePic) } - 1->{ + + 1 -> { binding.layoutVip.visible() binding.layoutPaySuccess.gone() binding.layoutScanQrCode.visible() + val memberInfo = activity.memberInfo + totalPrice = activity.foodInfo?.vipPrice ?: 0.0 + balance = (memberInfo?.topUpBalance ?: 0.0) + (memberInfo?.rewardBalance ?: 0.0) + realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance + expensesBalance = if (balance >= totalPrice) totalPrice else balance + + memberInfo?.let { loadUserInfo(it) } + binding.tvUserBalance.text = SpannedUtils.getAmountText( + listOf( + TextBean(text = "¥", textSize = 20), + TextBean(text = balance.format2String(2), textSize = 32), + ) + ) + binding.tvFoodName.text = foodName - binding.tvRealAmount.text = getAmountText(payAmount ?: "") - binding.ivPayQrCode.load(payQrCodePic) + binding.tvRealAmount.text = SpannedUtils.getAmountText( + listOf( + TextBean(text = "¥", textSize = 32), + TextBean(text = totalPrice.format2String(2), textSize = 48), + ) + ) + binding.ivPayQrCode.run { +// load(payQrCodePic) + if (balance >= totalPrice) invisible() else visible() + } - binding.ivHeadPic.load(R.mipmap.ic_launcher) - binding.tvUserName.text = "张三" - binding.tvUserPhone.text = "132****5678" - binding.tvUserBalance.text = getBalanceText("0.00") - - binding.tvPayTip.text = getPayTip("-16.80", "22.00") + binding.tvPayTip.text = SpannedUtils.getAmountText(getAmountList()) } - 2->{ + + 2 -> { binding.layoutVip.visible() binding.layoutPaySuccess.visible() binding.layoutScanQrCode.gone() - binding.ivHeadPic.load(R.mipmap.ic_launcher) - binding.tvUserName.text = "张三" - binding.tvUserPhone.text = "132****5678" - binding.tvUserBalance.text = getBalanceText("0.00") + val memberInfo = activity.memberInfo + totalPrice = activity.foodInfo?.vipPrice ?: 0.0 + balance = (memberInfo?.topUpBalance ?: 0.0) + (memberInfo?.rewardBalance ?: 0.0) + realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance + expensesBalance = if (balance >= totalPrice) totalPrice else balance - binding.tvPayInfo.text = "余额扣除 16.80 元,在线支付 20.00 元" + memberInfo?.let { loadUserInfo(it) } + val remainBalance = if (balance >= totalPrice) balance - totalPrice else 0.0 + binding.tvUserBalance.text = SpannedUtils.getAmountText( + listOf( + TextBean(text = "¥", textSize = 24), + TextBean(text = remainBalance.format2String(2), textSize = 36), + ) + ) + binding.tvPayInfo.text = + "余额扣除 ${expensesBalance.format2String(2)} 元,在线支付 ${totalPrice.format2String(2)} 元" } } } - private fun getPayTip(startAmount:String, endAmount: String): SpannedString { - return buildSpannedString { - append("余额扣除 ", ForegroundColorSpan("#FF5E7585".toColorInt()), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - append(startAmount, ForegroundColorSpan("#FF0A1428".toColorInt()), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - append(" 元,还需支付 ", ForegroundColorSpan("#FF5E7585".toColorInt()), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - append(endAmount, ForegroundColorSpan("#FF0A1428".toColorInt()), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - append(" 元", ForegroundColorSpan("#FF5E7585".toColorInt()), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - } + private fun loadUserInfo(item: MemberInfo) { + binding.ivHeadPic.load(R.mipmap.ic_launcher) + binding.tvUserName.text = item.name + + val phone = item.phone ?: "" + binding.tvUserPhone.text = + if (phone.length == 11) + phone.replace(phone.substring(3, 7), "****") + else + phone } - private fun getBalanceText(amount: String): SpannedString { - return buildSpannedString { - append("¥", AbsoluteSizeSpan(20, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - append(amount, AbsoluteSizeSpan(32, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - } - } + private fun getAmountList(): List { + val list: MutableList = mutableListOf() - 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) + list.add(TextBean(text = "余额扣除 ", textSize = 30, textColor = "#FF5E7585")) + list.add( + TextBean( + text = "-${expensesBalance.format2String(2)}", + textSize = 30, + textColor = "#FF0A1428" + ) + ) + + if (balance >= totalPrice) { + //余额大于等于总价格,使用余额支付 + TextBean(text = " 元,扣除后可用余额 ", textSize = 30, textColor = "#FF5E7585") + val remainingBalance = balance - totalPrice + TextBean( + text = remainingBalance.format2String(2), + textSize = 30, + textColor = "#FF0A1428" + ) + } else { + //余额小于总价格,使用余额+扫码支付 + //实际支付金额 + TextBean(text = " 元,还需支付 ", textSize = 30, textColor = "#FF5E7585") + TextBean(text = realPayPrice.format2String(2), textSize = 30, textColor = "#FF0A1428") + TextBean(text = " 元", textSize = 30, textColor = "#FF5E7585") } + + return list } override fun onDisplayRemoved() { diff --git a/app/src/main/java/com/sw/dualscreen/presentation/pay/TransmitScreen.kt b/app/src/main/java/com/sw/dualscreen/presentation/pay/TransmitScreen.kt new file mode 100644 index 0000000..1fa4263 --- /dev/null +++ b/app/src/main/java/com/sw/dualscreen/presentation/pay/TransmitScreen.kt @@ -0,0 +1,12 @@ +package com.sw.dualscreen.presentation.pay + +import android.view.TextureView +import androidx.camera.lifecycle.ProcessCameraProvider +import java.util.concurrent.ExecutorService + + + +class TransmitScreen { + + +} \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/repository/RemoteRepository.kt b/app/src/main/java/com/sw/dualscreen/repository/RemoteRepository.kt index a61bc47..5483578 100644 --- a/app/src/main/java/com/sw/dualscreen/repository/RemoteRepository.kt +++ b/app/src/main/java/com/sw/dualscreen/repository/RemoteRepository.kt @@ -1,23 +1,21 @@ package com.sw.dualscreen.repository -import android.content.Context -import android.net.Uri import com.sw.dualscreen.GlobalData import com.sw.dualscreen.model.request.UserNutritionParam import com.sw.dualscreen.model.response.ApiResponse import com.sw.dualscreen.model.response.DinnerType import com.sw.dualscreen.model.response.FoodInfo import com.sw.dualscreen.model.response.FoodSearchReq +import com.sw.dualscreen.model.response.FoodOrder +import com.sw.dualscreen.model.response.MemberInfo import com.sw.dualscreen.model.response.UserFaceModel import com.sw.dualscreen.model.response.UserNutritionData import com.sw.dualscreen.network.api.ApiService import com.sw.dualscreen.objbox.CollectedFoodInfo -import com.sw.dualscreen.utils.ImageUtil import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody import okhttp3.RequestBody import okhttp3.RequestBody.Companion.asRequestBody -import okhttp3.RequestBody.Companion.toRequestBody import java.io.File /** @@ -74,18 +72,21 @@ class RemoteRepository constructor( ) } } + /** * 获取人脸数据 */ suspend fun getCollectedFoodList( pageNum: Int, - pageSize: Int = 50, + pageSize: Int = 100, + foodName: String, ): ApiResponse> { return safeApiCall { apiService.getCollectedFoodList( param = mapOf( - "pageNum" to pageNum, - "pageSize" to pageSize + "pageNum" to "$pageNum", + "pageSize" to "$pageSize", + "foodName" to foodName, ) ) } @@ -141,10 +142,7 @@ class RemoteRepository constructor( * @param restId 从device服务获取的canteenId字段 * @param foodName 菜品名称,多个使用逗号拼接 */ - suspend fun getFoodInfo( - restId: String = GlobalData.restId, - foodName: String, - ): ApiResponse> { + suspend fun getFoodInfo(foodName: String): ApiResponse> { return safeApiCall { // val map = mutableMapOf>() // map["nameList"] = foodName.split(",") @@ -152,10 +150,31 @@ class RemoteRepository constructor( } } + suspend fun createOrder(order: FoodOrder): ApiResponse { + return safeApiCall { + apiService.createOrder(order = order) + } + } + + suspend fun bindOrder(userId: String, orderId: String): ApiResponse { + return safeApiCall { + apiService.bindOrder(userId = userId, orderId = orderId) + } + } + + suspend fun getMemberInfo(memberId: String): ApiResponse { + return safeApiCall { + apiService.getMemberInfo(memberId = memberId) + } + } + /** * 上传采集菜品信息 */ - suspend fun uploadCollectFoodPics(fileList: List, params: HashMap): ApiResponse { + suspend fun uploadCollectFoodPics( + fileList: List, + params: HashMap + ): ApiResponse { // 准备文件参数 val fileParts = mutableListOf() fileList.forEachIndexed { index, file -> diff --git a/app/src/main/java/com/sw/dualscreen/utils/ImageUploader.kt b/app/src/main/java/com/sw/dualscreen/utils/ImageUploader.kt new file mode 100644 index 0000000..965a54f --- /dev/null +++ b/app/src/main/java/com/sw/dualscreen/utils/ImageUploader.kt @@ -0,0 +1,38 @@ +package com.sw.dualscreen.utils + +import com.sw.dualscreen.objbox.FoodCollectionBean +import java.util.concurrent.atomic.AtomicInteger + +class ImageUploader( + private val totalList: List, + private val uploadImage: suspend (List)-> Boolean, + private val onProgress: (Int, List) -> Unit, + private val onError:(List) -> Unit, + private val onComplete:() -> Unit +) { + companion object { + private const val BATCH_SIZE = 5 + } + private val uploadedCount = AtomicInteger(0) + + suspend fun processUploads() { + val batches = totalList.chunked(BATCH_SIZE) + + for (batch in batches) { + val success = uploadImage(batch) + if (!success) { + //println("上传失败,终止流程") + onError(batch) + return + } + val fileCount = batch.count { it.imageFile!=null } + uploadedCount.addAndGet(fileCount) + onProgress(uploadedCount.get(), batch) + //println("已上传 ${uploadedCount.get()}/$totalImages") + } + + onComplete() + //println("流程完成,总计上传 ${uploadedCount.get()} 张图片") + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/sw/dualscreen/utils/SpannedUtils.kt b/app/src/main/java/com/sw/dualscreen/utils/SpannedUtils.kt new file mode 100644 index 0000000..1f2523f --- /dev/null +++ b/app/src/main/java/com/sw/dualscreen/utils/SpannedUtils.kt @@ -0,0 +1,32 @@ +package com.sw.dualscreen.utils + +import android.text.Spanned +import android.text.SpannedString +import android.text.style.AbsoluteSizeSpan +import android.text.style.ForegroundColorSpan +import androidx.core.graphics.toColorInt +import androidx.core.text.buildSpannedString +import com.sw.dualscreen.model.response.TextBean + +object SpannedUtils { + + public fun getAmountText(list: List): SpannedString { + return buildSpannedString { + list.forEach { + append( + it.text, + AbsoluteSizeSpan(it.textSize, true), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ) + if (it.textColor.isNullOrBlank().not()) { + append( + it.text, + ForegroundColorSpan(it.textColor!!.toColorInt()), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } + } + } + } + +} \ No newline at end of file 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 dc842df..f8a0c1d 100644 --- a/app/src/main/java/com/sw/dualscreen/view/CustomBottomSheetDialog.kt +++ b/app/src/main/java/com/sw/dualscreen/view/CustomBottomSheetDialog.kt @@ -128,7 +128,11 @@ class CustomBottomSheetDialog( private fun searchInfo(){ debouncer.debounce { - viewModel.searchByFoodName(binding.etSearch.text.toString()) + viewModel.searchByFoodName(binding.etSearch.text.toString()) { + searchFoodList.clear() + searchFoodList.addAll(it) + adapter.notifyDataSetChanged() + } } } diff --git a/app/src/main/java/com/sw/dualscreen/view/CustomDialog.java b/app/src/main/java/com/sw/dualscreen/view/CustomDialog.java index 131bdec..fb8a0c9 100644 --- a/app/src/main/java/com/sw/dualscreen/view/CustomDialog.java +++ b/app/src/main/java/com/sw/dualscreen/view/CustomDialog.java @@ -9,6 +9,9 @@ import android.view.Gravity; import android.view.View; import android.view.Window; import android.view.WindowManager; +import android.widget.TextView; + +import com.sw.dualscreen.R; /** * 加载进度条弹窗 @@ -88,4 +91,5 @@ public class CustomDialog extends Dialog { // fullScreenImmersive(getWindow().getDecorView()); // this.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); } + } \ No newline at end of file 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 1feb718..75a9ad4 100644 --- a/app/src/main/java/com/sw/dualscreen/viewmodel/UserViewModel.kt +++ b/app/src/main/java/com/sw/dualscreen/viewmodel/UserViewModel.kt @@ -5,11 +5,15 @@ import com.arcsoft.face.ErrorInfo import com.sw.dualscreen.GlobalData import com.sw.dualscreen.GlobalKey import com.sw.dualscreen.model.request.UserNutritionParam +import com.sw.dualscreen.model.response.ApiResponse import com.sw.dualscreen.model.response.DinnerType import com.sw.dualscreen.model.response.FoodInfo +import com.sw.dualscreen.model.response.FoodOrder +import com.sw.dualscreen.model.response.MemberInfo import com.sw.dualscreen.model.response.UserFaceModel import com.sw.dualscreen.model.response.UserNutritionData import com.sw.dualscreen.objbox.CollectedFoodInfo +import com.sw.dualscreen.utils.GsonUtils import com.sw.dualscreen.utils.SPUtil import com.sw.plate.App import com.sw.plate.utils.Base64 @@ -115,10 +119,15 @@ class UserViewModel : BaseViewModel() { } } - fun getCollectedFoodList(pageNo: Int = 1, pageSize: Int = 100, block:(List)-> Unit) { + fun getCollectedFoodList( + pageNo: Int = 1, + pageSize: Int = 100, + foodName: String, + block: (List) -> Unit + ) { Timber.tag(TAG).d("getCollectedFoodList index = $pageNo") launch { - val response = repository.getCollectedFoodList(pageNo) + val response = repository.getCollectedFoodList(pageNum = pageNo, pageSize = pageSize, foodName = foodName) if (parseResponse(response)) { withContext(Dispatchers.Default) { val list: List = response.result ?: emptyList() @@ -254,22 +263,73 @@ class UserViewModel : BaseViewModel() { } } - - - fun uploadCollectFoodPics(files: List, params: HashMap, callback: (Boolean) -> Unit) { - Timber.tag(TAG).d("uploadCollectFoodPics") + fun createOrder(order: FoodOrder, block:(String)-> Unit) { + Timber.tag(TAG).d("createOrder") launchWithLoading { - val fileList = files.filterNotNull() - if (fileList.isEmpty()) { - callback(false) - return@launchWithLoading + val response = repository.createOrder(order) + if (parseResponse(response)) { + block(response.data.toString()) + } else { + block("") } - val resp = repository.uploadCollectFoodPics(fileList, params) - if (!parseResponse(resp)) { - callback(false) - return@launchWithLoading - } - callback(resp.code == "00000") } } + + fun bindOrder(userId: String, orderId: String, block:(Boolean)-> Unit){ + Timber.tag(TAG).d("bindOrder") + launchWithLoading { + val response = repository.bindOrder(userId = userId, orderId = orderId) + if (parseResponse(response)) { + block(true) + } else { + block(false) + } + } + } + + fun getMemberInfo(memberId: String, block:(MemberInfo?)-> Unit) { + Timber.tag(TAG).d("getMemberInfo") + launchWithLoading { + val response = repository.getMemberInfo(memberId) + if (parseResponse(response)) { + block(response.data) + } else { + block(null) + } + } + } + + +// fun uploadCollectFoodPics(files: List, params: HashMap, callback: (Boolean) -> Unit) { +// Timber.tag(TAG).d("uploadCollectFoodPics") +// launchWithLoading { +// val fileList = files.filterNotNull() +// if (fileList.isEmpty()) { +// callback(false) +// return@launchWithLoading +// } +// val resp = repository.uploadCollectFoodPics(fileList, params) +// if (!parseResponse(resp)) { +// callback(false) +// return@launchWithLoading +// } +// callback(resp.code == "00000") +// } +// } + + suspend fun uploadCollectFoodPics( + files: List, + params: HashMap + ): Boolean { + Timber.tag(TAG).d("uploadCollectFoodPics") + val fileList = files.filterNotNull() + if (fileList.isEmpty()) { + return true + } + val resp = repository.uploadCollectFoodPics(fileList, params) +// if (!parseResponse(resp)) { +// return false +// } + return resp.code == "00000" + } } \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_btn_save2.xml b/app/src/main/res/drawable/bg_btn_save2.xml index 771f6c6..01391f0 100644 --- a/app/src/main/res/drawable/bg_btn_save2.xml +++ b/app/src/main/res/drawable/bg_btn_save2.xml @@ -1,6 +1,15 @@ - - - - \ No newline at end of file + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_collected_food.xml b/app/src/main/res/layout/activity_collected_food.xml index bcdb30d..d6aca4c 100644 --- a/app/src/main/res/layout/activity_collected_food.xml +++ b/app/src/main/res/layout/activity_collected_food.xml @@ -44,33 +44,39 @@ - - - - - - - - - + - - - - - + android:layout_margin="10dp"> - - - - + + + + + + + + + + \ 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 17b9cb2..0a3716e 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -77,12 +77,13 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> - diff --git a/app/src/main/res/layout/fragment_pay_result.xml b/app/src/main/res/layout/fragment_pay_result.xml index 2d07764..5664e3b 100644 --- a/app/src/main/res/layout/fragment_pay_result.xml +++ b/app/src/main/res/layout/fragment_pay_result.xml @@ -126,15 +126,10 @@ android:id="@+id/ivPayQrCode" android:layout_width="300dp" android:layout_height="300dp" - android:layout_marginTop="56dp" + android:layout_marginVertical="50dp" android:src="@drawable/ic_qrcode" android:visibility="visible" /> - - + + @@ -18,17 +19,17 @@ android:layout_height="wrap_content" android:layout_marginTop="30dp" android:layout_marginBottom="39dp" - android:text="暂无该物品" + android:text="暂无采集记录" android:textColor="#ff000000" - android:textSize="30sp" + android:textSize="36sp" android:textStyle="bold" /> + android:textSize="30sp" /> \ 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 index 11911e5..4b8763a 100644 --- a/app/src/main/res/layout/layout_user_calorie.xml +++ b/app/src/main/res/layout/layout_user_calorie.xml @@ -3,7 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="match_parent" android:gravity="center_horizontal" android:layout_gravity="center" android:orientation="vertical"> @@ -12,15 +12,16 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="热量" - android:layout_marginTop="65dp" + android:layout_marginTop="50dp" android:textColor="#ff5e7585" android:textSize="24sp" android:textStyle="bold" /> + android:text="kcal/100g" /> @@ -51,6 +52,7 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="vertical" + android:gravity="center_horizontal" app:layout_constraintEnd_toStartOf="@+id/llProtein" app:layout_constraintHorizontal_chainStyle="spread" app:layout_constraintStart_toStartOf="parent"> @@ -59,7 +61,7 @@ android:id="@+id/tvFatRate" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="13%" + tools:text="13%" android:textColor="#ff5e7585" android:textSize="16sp" /> @@ -91,6 +93,7 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="vertical" + android:gravity="center_horizontal" app:layout_constraintEnd_toStartOf="@+id/llCarbohydrate" app:layout_constraintStart_toEndOf="@id/llFat"> @@ -98,7 +101,7 @@ android:id="@+id/tvProteinRate" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="47%" + tools:text="47%" android:textColor="#ff5e7585" android:textSize="16sp" /> @@ -130,6 +133,7 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="vertical" + android:gravity="center_horizontal" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/llProtein"> @@ -137,7 +141,7 @@ android:id="@+id/tvCarbohydrateRate" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="40%" + tools:text="40%" android:textColor="#ff5e7585" android:textSize="16sp" /> @@ -168,11 +172,12 @@ \ 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 index 2fb8668..11b85b8 100644 --- a/app/src/main/res/layout/presentation_main_screen.xml +++ b/app/src/main/res/layout/presentation_main_screen.xml @@ -2,7 +2,6 @@ @@ -18,14 +17,14 @@ android:layout_height="wrap_content" android:background="@drawable/setting_border" android:orientation="vertical" - android:paddingBottom="28dp"> + android:paddingBottom="26dp"> - - - - - - + 50% + \ No newline at end of file