支付页面统一副屏逻辑

This commit is contained in:
2026-03-09 19:56:09 +08:00
parent 59b3ad72fe
commit ec4a5801c2
11 changed files with 2243 additions and 977 deletions
@@ -14,11 +14,10 @@ import com.sw.dualscreen.ext.gone
import com.sw.dualscreen.model.response.FoodInfo import com.sw.dualscreen.model.response.FoodInfo
import com.sw.dualscreen.model.response.MemberInfo import com.sw.dualscreen.model.response.MemberInfo
import com.sw.dualscreen.model.response.PaySuccessEvent import com.sw.dualscreen.model.response.PaySuccessEvent
import com.sw.dualscreen.presentation.pay.ScanQrCodePayPresentation import com.sw.dualscreen.presentation.pay.PayPresentation
import com.sw.dualscreen.utils.SPUtil import com.sw.dualscreen.utils.SPUtil
import com.sw.dualscreen.viewmodel.BaseViewModel import com.sw.dualscreen.viewmodel.BaseViewModel
import com.sw.dualscreen.viewmodel.UserViewModel import com.sw.dualscreen.viewmodel.UserViewModel
import com.sw.plate.utils.ToastUtils
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
@@ -62,9 +61,19 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
var foodOrderId: String = "" var foodOrderId: String = ""
var eatNum: Int = 1 var eatNum: Int = 1
val presentation by lazy {
PayPresentation(
activity = this@PayActivity,
userViewModel = userViewModel,
recognizeViewModel = recognizeViewModel,
display = displays[1]
)
}
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
override fun initialize() { override fun initialize() {
super.initialize() super.initialize()
presentation.show()
foodInfo = intent.getParcelableExtra(FOOD_INFO) foodInfo = intent.getParcelableExtra(FOOD_INFO)
foodOrderId = intent.getStringExtra(FOOD_ORDER_ID) ?: "" foodOrderId = intent.getStringExtra(FOOD_ORDER_ID) ?: ""
eatNum = intent.getIntExtra(FOOD_EAT_NUM, 1) eatNum = intent.getIntExtra(FOOD_EAT_NUM, 1)
@@ -101,7 +110,6 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
} }
showFacePay() showFacePay()
} }
showScanQrCodePay() showScanQrCodePay()
} }
@@ -136,39 +144,43 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
} }
override fun onDestroy() { override fun onDestroy() {
qrCodePayFragment?.presentation?.dismiss() presentation?.dismiss()
cashPayFragment?.presentation?.dismiss()
numberPayFragment?.presentation?.dismiss()
facePayFragment?.presentation?.dismiss()
scanQrCodePayPresentation?.dismiss()
super.onDestroy() super.onDestroy()
} }
private var scanQrCodePayPresentation: ScanQrCodePayPresentation? = null
var memberInfo: MemberInfo? = null var memberInfo: MemberInfo? = null
fun showPayInfo(type: Int = 1, isVip: Boolean = false, memberInfo: MemberInfo? = null) { fun showPayInfo(type: Int = 1, isVip: Boolean = false, memberInfo: MemberInfo? = null) {
this.memberInfo = memberInfo this.memberInfo = memberInfo
payResultFragment = PayResultFragment.instance(type, memberInfo) payResultFragment = PayResultFragment.instance(type, memberInfo)
showFragment(payResultFragment!!, TAG_PAY_RESULT) showFragment(payResultFragment!!, TAG_PAY_RESULT)
if (displays.size > 1) { // if (displays.size > 1) {
scanQrCodePayPresentation = ScanQrCodePayPresentation( // scanQrCodePayPresentation = ScanQrCodePayPresentation(
activity = this, // activity = this,
display = displays[1], // display = displays[1],
type = type // type = type
) { // ) {
scanQrCodePayPresentation?.dismiss() // scanQrCodePayPresentation?.dismiss()
}.also { // }.also {
// it.foodName = foodInfo?.foodName
// //确认使用vipPrice 还是 specPrice
// val onePrice = if (isVip) foodInfo?.vipPrice else foodInfo?.specPrice
// it.totalPrice = (onePrice ?: 0.0) * eatNum
// }
// scanQrCodePayPresentation?.show()
// }
presentation.let {
it.type = type
it.foodName = foodInfo?.foodName it.foodName = foodInfo?.foodName
//确认使用vipPrice 还是 specPrice //确认使用vipPrice 还是 specPrice
val onePrice = if (isVip) foodInfo?.vipPrice else foodInfo?.specPrice val onePrice = if (isVip) foodInfo?.vipPrice else foodInfo?.specPrice
it.totalPrice = (onePrice ?: 0.0) * eatNum it.totalPrice = (onePrice ?: 0.0) * eatNum
} presentation.initView()
scanQrCodePayPresentation?.show()
} }
} }
fun updateQrCodeImage(qrCodeUrl: String?) { fun updateQrCodeImage(qrCodeUrl: String?) {
scanQrCodePayPresentation?.loadQrCodeImage(qrCodeUrl) // scanQrCodePayPresentation?.loadQrCodeImage(qrCodeUrl)
presentation?.loadQrCodeImage(qrCodeUrl)
} }
fun showPaySuccess(isVip: Boolean) { fun showPaySuccess(isVip: Boolean) {
@@ -8,7 +8,7 @@ import com.sw.dualscreen.activity.PayActivity
import com.sw.dualscreen.activity.fragment.BaseFragment import com.sw.dualscreen.activity.fragment.BaseFragment
import com.sw.dualscreen.databinding.FragmentCashPayBinding import com.sw.dualscreen.databinding.FragmentCashPayBinding
import com.sw.dualscreen.ext.format2String import com.sw.dualscreen.ext.format2String
import com.sw.dualscreen.presentation.pay.CashPayPresentation import com.sw.dualscreen.presentation.pay.PayPresentation
import com.sw.plate.utils.ToastUtils import com.sw.plate.utils.ToastUtils
class CashPayFragment : BaseFragment<FragmentCashPayBinding>() { class CashPayFragment : BaseFragment<FragmentCashPayBinding>() {
@@ -17,8 +17,6 @@ class CashPayFragment : BaseFragment<FragmentCashPayBinding>() {
private var foodName: String? = null private var foodName: String? = null
private var payAmount: Double? = null private var payAmount: Double? = null
var presentation: CashPayPresentation? = null
override fun inflateViewBinding(): FragmentCashPayBinding { override fun inflateViewBinding(): FragmentCashPayBinding {
return FragmentCashPayBinding.inflate(layoutInflater) return FragmentCashPayBinding.inflate(layoutInflater)
} }
@@ -32,7 +30,8 @@ class CashPayFragment : BaseFragment<FragmentCashPayBinding>() {
binding.tvRealAmount.text = getAmountText(payAmount.format2String(2)) binding.tvRealAmount.text = getAmountText(payAmount.format2String(2))
binding.btnConfirmPayFinish.setOnClickListener { binding.btnConfirmPayFinish.setOnClickListener {
payActivity.cashPay(hashMapOf( payActivity.cashPay(
hashMapOf(
//标价金额(单位为元) //标价金额(单位为元)
"totalFee" to payAmount.format2String(2), "totalFee" to payAmount.format2String(2),
//订单类型(0付款1会员充值) //订单类型(0付款1会员充值)
@@ -41,7 +40,8 @@ class CashPayFragment : BaseFragment<FragmentCashPayBinding>() {
"orderNo" to payActivity.foodOrderId, "orderNo" to payActivity.foodOrderId,
//支付来源:0线下收款 、3:线上纯会员支付 101:支付宝主动、102:支付宝被动、201:微信主动、202:微信被动 //支付来源:0线下收款 、3:线上纯会员支付 101:支付宝主动、102:支付宝被动、201:微信主动、202:微信被动
"paySource" to "0" "paySource" to "0"
)) { paySuccess -> )
) { paySuccess ->
if (paySuccess.not()) { if (paySuccess.not()) {
ToastUtils.showToast("接口调用失败") ToastUtils.showToast("接口调用失败")
return@cashPay return@cashPay
@@ -51,39 +51,47 @@ class CashPayFragment : BaseFragment<FragmentCashPayBinding>() {
} }
showSubScreen() showSubScreen()
} }
override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden)
if (hidden) {
dismissSubScreen()
return
}
//showSubScreen()
}
override fun onDestroy() { // override fun onHiddenChanged(hidden: Boolean) {
dismissSubScreen() // super.onHiddenChanged(hidden)
super.onDestroy() // if (hidden) {
} // dismissSubScreen()
// return
private fun dismissSubScreen() { // }
presentation?.let { // //showSubScreen()
if (it.isShowing) { // }
it.dismiss() //
} // override fun onDestroy() {
} // dismissSubScreen()
} // super.onDestroy()
// }
//
// private fun dismissSubScreen() {
// }
private fun showSubScreen() { private fun showSubScreen() {
// 查找副屏(通常索引为1 // // 查找副屏(通常索引为1)
if (displays.size > 1) { // if (displays.size > 1) {
dismissSubScreen() // dismissSubScreen()
presentation = CashPayPresentation(activity = payActivity, display = displays[1]) { // presentation = PayPresentation(
presentation?.dismiss() // activity = payActivity,
}.also { // pageType = PayPresentation.CASH_PAY,
// userViewModel = payActivity.userViewModel,
// recognizeViewModel = payActivity.recognizeViewModel,
// display = displays[1]
// ) {
// presentation?.dismiss()
// }.also {
// it.foodName = foodName
// it.payAmount = payAmount.format2String(2)
// }
// presentation?.show()
// }
payActivity.presentation.let {
it.pageType = PayPresentation.CASH_PAY
it.foodName = foodName it.foodName = foodName
it.payAmount = payAmount.format2String(2) it.payAmount = payAmount.format2String(2)
} it.initView()
presentation?.show()
} }
} }
@@ -5,14 +5,13 @@ import com.sw.dualscreen.activity.PayActivity
import com.sw.dualscreen.activity.fragment.BaseFragment import com.sw.dualscreen.activity.fragment.BaseFragment
import com.sw.dualscreen.databinding.FragmentFacePayBinding import com.sw.dualscreen.databinding.FragmentFacePayBinding
import com.sw.dualscreen.ext.format2String import com.sw.dualscreen.ext.format2String
import com.sw.dualscreen.presentation.pay.FacePayPresentation import com.sw.dualscreen.presentation.pay.PayPresentation
class FacePayFragment : BaseFragment<FragmentFacePayBinding>() { class FacePayFragment : BaseFragment<FragmentFacePayBinding>() {
private lateinit var payActivity: PayActivity private lateinit var payActivity: PayActivity
private var foodName: String? = null private var foodName: String? = null
private var payAmount: Double? = null private var payAmount: Double? = null
var presentation: FacePayPresentation? = null
override fun inflateViewBinding(): FragmentFacePayBinding { override fun inflateViewBinding(): FragmentFacePayBinding {
return FragmentFacePayBinding.inflate(layoutInflater) return FragmentFacePayBinding.inflate(layoutInflater)
} }
@@ -32,24 +31,26 @@ class FacePayFragment : BaseFragment<FragmentFacePayBinding>() {
override fun onHiddenChanged(hidden: Boolean) { override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden) super.onHiddenChanged(hidden)
if (hidden) { if (hidden) {
dismissSubScreen() // dismissSubScreen()
payActivity.presentation.pauseCamera()
return return
} }
payActivity.presentation.resumeCamera()
//showSubScreen() //showSubScreen()
} }
override fun onDestroy() { // override fun onDestroy() {
dismissSubScreen() // dismissSubScreen()
super.onDestroy() // super.onDestroy()
} // }
private fun dismissSubScreen() { // private fun dismissSubScreen() {
presentation?.let { // presentation?.let {
if (it.isShowing) { // if (it.isShowing) {
it.dismiss() // it.dismiss()
} // }
} // }
} // }
fun loadBitmap(frame: Bitmap) { fun loadBitmap(frame: Bitmap) {
binding.ivFaceImage.setImageBitmap(frame) binding.ivFaceImage.setImageBitmap(frame)
@@ -57,21 +58,28 @@ class FacePayFragment : BaseFragment<FragmentFacePayBinding>() {
private fun showSubScreen() { private fun showSubScreen() {
// 查找副屏(通常索引为1 // 查找副屏(通常索引为1
if (displays.size > 1) { // if (displays.size > 1) {
dismissSubScreen() // dismissSubScreen()
presentation = FacePayPresentation( // presentation = PayPresentation(
activity = payActivity, // activity = payActivity,
display = displays[1], // pageType = PayPresentation.FACE_PAY,
userViewModel = payActivity.userViewModel, // userViewModel = payActivity.userViewModel,
recognizeViewModel = payActivity.recognizeViewModel, // recognizeViewModel = payActivity.recognizeViewModel,
// parentTextureView = binding.parentTextureView, // display = displays[1]
) { // ) {
presentation?.dismiss() // presentation?.dismiss()
}.also { // }.also {
// it.foodName = foodName
// it.payAmount = payAmount.format2String(2)
// }
// presentation?.show()
// }
payActivity.presentation.let {
it.pageType = PayPresentation.FACE_PAY
it.foodName = foodName it.foodName = foodName
it.payAmount = payAmount.format2String(2) it.payAmount = payAmount.format2String(2)
it.initView()
} }
presentation?.show()
}
} }
} }
@@ -5,7 +5,7 @@ import com.sw.dualscreen.activity.fragment.BaseFragment
import com.sw.dualscreen.databinding.FragmentNumberPayBinding import com.sw.dualscreen.databinding.FragmentNumberPayBinding
import com.sw.dualscreen.ext.format2String import com.sw.dualscreen.ext.format2String
import com.sw.dualscreen.ext.hideKeyboard import com.sw.dualscreen.ext.hideKeyboard
import com.sw.dualscreen.presentation.pay.CashPayPresentation import com.sw.dualscreen.presentation.pay.PayPresentation
import com.sw.plate.utils.ToastUtils import com.sw.plate.utils.ToastUtils
class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() { class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
@@ -13,7 +13,6 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
private lateinit var payActivity: PayActivity private lateinit var payActivity: PayActivity
private var foodName: String? = null private var foodName: String? = null
private var payAmount: Double? = null private var payAmount: Double? = null
var presentation: CashPayPresentation? = null
override fun inflateViewBinding(): FragmentNumberPayBinding { override fun inflateViewBinding(): FragmentNumberPayBinding {
return FragmentNumberPayBinding.inflate(layoutInflater) return FragmentNumberPayBinding.inflate(layoutInflater)
} }
@@ -79,39 +78,49 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
showSubScreen() showSubScreen()
} }
override fun onHiddenChanged(hidden: Boolean) { // override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden) // super.onHiddenChanged(hidden)
if (hidden) { // if (hidden) {
dismissSubScreen() // dismissSubScreen()
return // return
} // }
//showSubScreen() // //showSubScreen()
} // }
//
// override fun onDestroy() {
// dismissSubScreen()
// super.onDestroy()
// }
override fun onDestroy() { // private fun dismissSubScreen() {
dismissSubScreen() // presentation?.let {
super.onDestroy() // if (it.isShowing) {
} // it.dismiss()
// }
private fun dismissSubScreen() { // }
presentation?.let { // }
if (it.isShowing) {
it.dismiss()
}
}
}
private fun showSubScreen() { private fun showSubScreen() {
// 查找副屏(通常索引为1 // 查找副屏(通常索引为1
if (displays.size > 1) { // if (displays.size > 1) {
dismissSubScreen() // dismissSubScreen()
presentation = CashPayPresentation(activity = payActivity, display = displays[1]) { // presentation = PayPresentation(
presentation?.dismiss() // activity = payActivity,
}.also { // pageType = PayPresentation.CASH_PAY,
// userViewModel = payActivity.userViewModel,
// recognizeViewModel = payActivity.recognizeViewModel,
// display = displays[1]
// ).also {
// it.foodName = foodName
// it.payAmount = payAmount.format2String(2)
// }
// presentation?.show()
// }
payActivity.presentation.let {
it.pageType = PayPresentation.CASH_PAY
it.foodName = foodName it.foodName = foodName
it.payAmount = payAmount.format2String(2) it.payAmount = payAmount.format2String(2)
} it.initView()
presentation?.show()
} }
} }
@@ -8,11 +8,11 @@ import androidx.lifecycle.lifecycleScope
import com.sw.dualscreen.activity.PayActivity import com.sw.dualscreen.activity.PayActivity
import com.sw.dualscreen.activity.fragment.BaseFragment import com.sw.dualscreen.activity.fragment.BaseFragment
import com.sw.dualscreen.ext.load import com.sw.dualscreen.ext.load
import com.sw.dualscreen.presentation.pay.ScanQrCodePayPresentation
import com.sw.dualscreen.utils.countDownByFlow import com.sw.dualscreen.utils.countDownByFlow
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import com.sw.dualscreen.databinding.FragmentScanQrcodePayBinding import com.sw.dualscreen.databinding.FragmentScanQrcodePayBinding
import com.sw.dualscreen.ext.format2String import com.sw.dualscreen.ext.format2String
import com.sw.dualscreen.presentation.pay.PayPresentation
import com.sw.dualscreen.utils.IntervalExecutor import com.sw.dualscreen.utils.IntervalExecutor
class ScanQrCodePayFragment : BaseFragment<FragmentScanQrcodePayBinding>() { class ScanQrCodePayFragment : BaseFragment<FragmentScanQrcodePayBinding>() {
@@ -25,7 +25,6 @@ class ScanQrCodePayFragment : BaseFragment<FragmentScanQrcodePayBinding>() {
private var foodName: String? = null private var foodName: String? = null
private var payAmount: Double? = null private var payAmount: Double? = null
private var payQrCodePic: String? = null private var payQrCodePic: String? = null
var presentation: ScanQrCodePayPresentation? = null
private var countDownJob: Job? = null private var countDownJob: Job? = null
override fun inflateViewBinding(): FragmentScanQrcodePayBinding { override fun inflateViewBinding(): FragmentScanQrcodePayBinding {
@@ -60,41 +59,48 @@ class ScanQrCodePayFragment : BaseFragment<FragmentScanQrcodePayBinding>() {
override fun onHiddenChanged(hidden: Boolean) { override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden) super.onHiddenChanged(hidden)
if (hidden) { // if (hidden) {
dismissSubScreen() //// dismissSubScreen()
return // return
} // }
//showSubScreen() //showSubScreen()
} }
override fun onDestroy() { override fun onDestroy() {
dismissSubScreen() // dismissSubScreen()
countDownJob?.cancel() // 自动取消订阅,防止内存泄漏 countDownJob?.cancel() // 自动取消订阅,防止内存泄漏
payTaskJob?.cancel() payTaskJob?.cancel()
super.onDestroy() super.onDestroy()
} }
private fun dismissSubScreen() { private fun dismissSubScreen() {
presentation?.let { // presentation?.let {
if (it.isShowing) { // if (it.isShowing) {
it.dismiss() // it.dismiss()
} // }
} // }
} }
private fun showSubScreen() { private fun showSubScreen() {
// 查找副屏(通常索引为1 // // 查找副屏(通常索引为1)
if (displays.size > 1) { // if (displays.size > 1) {
dismissSubScreen() // dismissSubScreen()
presentation = // presentation =
ScanQrCodePayPresentation(activity = payActivity, type = 0, display = displays[1]) { // ScanQrCodePayPresentation(activity = payActivity, type = 0, display = displays[1]) {
presentation?.dismiss() // presentation?.dismiss()
}.also { // }.also {
// it.foodName = foodName
// it.totalPrice = payAmount ?: 0.0
// it.payQrCodePic = payQrCodePic
// }
// presentation?.show()
// }
payActivity.presentation.let {
it.pageType = PayPresentation.QR_CODE_PAY
it.foodName = foodName it.foodName = foodName
it.totalPrice = payAmount ?: 0.0 it.totalPrice = payAmount ?: 0.0
it.payQrCodePic = payQrCodePic it.payQrCodePic = payQrCodePic
} it.initView()
presentation?.show()
} }
} }
@@ -54,7 +54,8 @@ data class MemberInfo(
//赠送余额 //赠送余额
val rewardBalance: Double?, val rewardBalance: Double?,
//积分余额 //积分余额
val integralBalance: Int? val integralBalance: Int?,
val member: Boolean
) : Parcelable ) : Parcelable
data class TextBean( data class TextBean(
@@ -1,49 +1,49 @@
package com.sw.dualscreen.presentation.pay //package com.sw.dualscreen.presentation.pay
//
import android.R //import android.R
import android.app.Presentation //import android.app.Presentation
import android.os.Bundle //import android.os.Bundle
import android.text.Spanned //import android.text.Spanned
import android.text.SpannedString //import android.text.SpannedString
import android.text.style.AbsoluteSizeSpan //import android.text.style.AbsoluteSizeSpan
import android.view.Display //import android.view.Display
import androidx.core.text.buildSpannedString //import androidx.core.text.buildSpannedString
import com.sw.dualscreen.activity.PayActivity //import com.sw.dualscreen.activity.PayActivity
import com.sw.dualscreen.databinding.PresentationCashPayBinding //import com.sw.dualscreen.databinding.PresentationCashPayBinding
import com.sw.dualscreen.ext.load //import com.sw.dualscreen.ext.load
//
class CashPayPresentation( //class CashPayPresentation(
val activity: PayActivity, // val activity: PayActivity,
display: Display, // display: Display,
private val onDismissListener: () -> Unit = {} // private val onDismissListener: () -> Unit = {}
) : Presentation(activity, display) { //) : Presentation(activity, display) {
//
private lateinit var binding: PresentationCashPayBinding // private lateinit var binding: PresentationCashPayBinding
//
override fun onCreate(savedInstanceState: Bundle?) { // override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) // super.onCreate(savedInstanceState)
binding = PresentationCashPayBinding.inflate(layoutInflater) // binding = PresentationCashPayBinding.inflate(layoutInflater)
setContentView(binding.root) // setContentView(binding.root)
window?.setBackgroundDrawableResource(R.color.transparent) // window?.setBackgroundDrawableResource(R.color.transparent)
initView() // initView()
} // }
//
var foodName: String? = null // var foodName: String? = null
var payAmount: String? = null // var payAmount: String? = null
private fun initView() { // private fun initView() {
binding.tvFoodName.text = foodName // binding.tvFoodName.text = foodName
binding.tvRealAmount.text = getAmountText(payAmount ?: "") // binding.tvRealAmount.text = getAmountText(payAmount ?: "")
} // }
//
private fun getAmountText(amount: String): SpannedString { // private fun getAmountText(amount: String): SpannedString {
return buildSpannedString { // return buildSpannedString {
append("¥", AbsoluteSizeSpan(32, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) // append("¥", AbsoluteSizeSpan(32, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
append(amount, AbsoluteSizeSpan(48, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) // append(amount, AbsoluteSizeSpan(48, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
} // }
} // }
//
override fun onDisplayRemoved() { // override fun onDisplayRemoved() {
super.onDisplayRemoved() // super.onDisplayRemoved()
onDismissListener() // onDismissListener()
} // }
} //}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,804 @@
package com.sw.dualscreen.presentation.pay
import android.Manifest
import android.annotation.SuppressLint
import android.app.Dialog
import android.app.Presentation
import android.content.pm.PackageManager
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.TextUtils
import android.text.style.AbsoluteSizeSpan
import android.view.Display
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.widget.FrameLayout
import android.widget.TextView
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.databinding.PresentationPayBinding
import com.sw.dualscreen.ext.dp
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.SPUtil
import com.sw.dualscreen.utils.SpannedUtils
import com.sw.dualscreen.view.CustomDialog
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
class PayPresentation(
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 = "PayPresentation"
const val CASH_PAY = 100
const val FACE_PAY = 200
const val QR_CODE_PAY = 300
}
private lateinit var binding: PresentationPayBinding
// 虹软人脸配置 ⬇
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 = PresentationPayBinding.inflate(layoutInflater)
setContentView(binding.root)
window?.setBackgroundDrawableResource(android.R.color.white)
setupArcCamera()
registerDataChange()
initView()
}
override fun show() {
super.show()
pauseCamera()
}
var pageType: Int = 0
var type: Int = 0
// fun updatePage(pageType:Int, type:Int = -1) {
// this.pageType == pageType
// this.type = type
// if (pageType == PayPresentation.CASH_PAY) {
//
// return
// }
// if (pageType == PayPresentation.FACE_PAY) {
//
// return
// }
// if (pageType == PayPresentation.QR_CODE_PAY) {
//
// return
// }
// }
var foodName: String? = null
var payAmount: String? = null
fun initView() {
when (pageType) {
FACE_PAY -> {
//人脸识别
binding.flCashPay.gone()
binding.flQrCodePay.gone()
binding.flFacePay.visible()
binding.tvFoodName.text = foodName
binding.tvRealAmount.text = SpannedUtils.getAmountText(
listOf(
TextBean(text = "¥", textSize = 32),
TextBean(text = payAmount ?: "", textSize = 48),
)
)
//pauseCamera()
resumeCamera()
// binding.root.postDelayed({
// resumeCamera()
// }, 1000)
}
QR_CODE_PAY -> {
//二维码
binding.flCashPay.gone()
binding.flQrCodePay.visible()
binding.flFacePay.gone()
initQrCodeView()
}
CASH_PAY -> {
binding.flCashPay.visible()
binding.flQrCodePay.gone()
binding.flFacePay.gone()
binding.tvFoodName2.text = foodName
binding.tvRealAmount2.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)
//parentTextureView.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()
}
}
}
}
override fun onStop() {
rgbCameraHelper?.release()
rgbCameraHelper = null
irCameraHelper?.release()
irCameraHelper = null
recognizeViewModel.destroy()
super.onStop()
}
fun resumeCamera() {
Timber.tag(TAG).d("resumeCamera isRecognition = $isRecognition")
isRecognition = true
if (rgbCameraHelper?.isStopped == true) {
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
// TODO: 测试支付用户id
// userId = "1987710988425662466"
faceRecSuccess(userId)
})
recognizeViewModel.drawRectInfoText.observe(activity, Observer { info ->
Timber.tag(TAG).i("drawRectInfoText observe info = $info")
})
}
private fun initArcView() {
//在布局结束后才做初始化操作
binding.dualCameraTexturePreviewRgb.getViewTreeObserver().addOnGlobalLayoutListener(this)
//parentTextureView.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 w = (600.dp * 1.5).toInt()
val h = 1068.dp
val layoutParams = FrameLayout.LayoutParams(w, h)
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<FacePreviewInfo?>? =
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=$measuredWidthmeasuredHeight=$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!!.setSurfaceFrameCallback { frame ->
activity.facePayFragment?.loadBitmap(frame)
}
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<FacePreviewInfo?>) {
// Timber.tag(TAG).d("drawPreviewInfo facePreviewInfoList = ${facePreviewInfoList.size}, rgbFaceRectTransformer = ${rgbFaceRectTransformer != null}")
if (rgbFaceRectTransformer != null) {
val rgbDrawInfoList: MutableList<DrawInfo?>? = 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!!)
// }
resumeCamera()
}
}
}
private var lastFaceTrackId: Int = -1 // 上一次的人脸信息
override fun onDisplayRemoved() {
super.onDisplayRemoved()
onDismissListener()
}
private fun faceRecSuccess(userId: String) {
activity.runOnUiThread {
activity.showWaitingDialog("加载中,请稍后……")
}
activity.getMemberInfoById(userId) { memberInfo ->
if (memberInfo == null) {
activity.hideWaitingDialog()
//ToastUtils.showToast("查询会员信息失败,请稍后重试")
return@getMemberInfoById
}
activity.bindOrder(memberInfo.faceUserId ?: "") { bindResult ->
if (bindResult.not()) {
activity.hideWaitingDialog()
//ToastUtils.showToast("订单绑定失败")
return@bindOrder
}
binding.root.postDelayed({
activity.hideWaitingDialog()
// TODO: 测试 memberInfo.member是否返回正常数据---------------
activity.showPayInfo(type = 1, isVip = memberInfo.member, memberInfo = memberInfo)
activity.hidePayTab()
binding.root.postDelayed({
dismiss()
}, 500)
}, 1000)
}
}
}
// private fun bindOrder(userId: String, block: () -> Unit) {
// val pickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0)
// val mode = if (pickupMode == 1) 1 else 2
// userViewModel.bindOrder(userId, activity.foodOrderId, mode = mode) { bindResult ->
// activity.runOnUiThread {
// if (bindResult.not()) {
// activity.hideWaitingDialog()
// //ToastUtils.showToast("订单绑定失败")
// return@runOnUiThread
// }
// block()
// }
// }
// }
// private fun getMemberInfo(userId: String, block:(MemberInfo)-> Unit) {
// userViewModel.getMemberInfoById(memberId = userId) { memberInfo ->
// activity.runOnUiThread {
// if (memberInfo == null) {
// activity.hideWaitingDialog()
// //ToastUtils.showToast("查询会员信息失败,请稍后重试")
// return@runOnUiThread
// }
// block(memberInfo)
// }
// }
// }
var payQrCodePic: Any? = null
//总价格
var totalPrice = 0.0
//余额
private var balance = 0.0
//实际支付金额
private var realPayPrice = 0.0
//扣除余额
private var expensesBalance = 0.0
@SuppressLint("SetTextI18n")
private fun initQrCodeView() {
when (type) {
0 -> {
binding.layoutVip.gone()
binding.layoutPaySuccess.gone()
binding.layoutScanQrCode.visible()
binding.tvPayTip.text = "请扫码支付或出示付款码"
binding.tvFoodName3.text = foodName
binding.tvRealAmount3.text = SpannedUtils.getAmountText(
listOf(
TextBean(text = "¥", textSize = 32),
TextBean(text = totalPrice.format2String(2), textSize = 48),
)
)
binding.ivPayQrCode.load(payQrCodePic)
}
1 -> {
val memberInfo = activity.memberInfo
binding.layoutVip.run {
if (memberInfo != null) visible() else gone()
}
binding.layoutPaySuccess.gone()
binding.layoutScanQrCode.visible()
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.tvFoodName3.text = foodName
binding.tvRealAmount3.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.tvPayTip.text = SpannedUtils.getAmountText(getAmountList())
}
2 -> {
val memberInfo = activity.memberInfo
binding.layoutVip.run {
if (memberInfo != null) visible() else gone()
}
binding.layoutPaySuccess.visible()
binding.layoutScanQrCode.gone()
balance = if (memberInfo != null) (memberInfo.topUpBalance
?: 0.0) + (memberInfo.rewardBalance ?: 0.0) else 0.0
realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance
expensesBalance = if (balance >= totalPrice) totalPrice else balance
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),
)
)
val showBalance = expensesBalance.format2String(2)
val showTotal = totalPrice.format2String(2)
binding.tvPayInfo.text = "余额扣除 $showBalance 元,在线支付 $showTotal"
}
}
}
fun loadQrCodeImage(qrCodeUrl: String?) {
binding.ivPayQrCode.load(qrCodeUrl)
}
private fun loadUserInfo(item: MemberInfo) {
binding.ivHeadPic.load(
if (item.faceUrl.isNullOrBlank()) R.drawable.ic_avatar_default
else item.faceUrl
)
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 getAmountList(): List<TextBean> {
val list: MutableList<TextBean> = mutableListOf()
list.add(TextBean(text = "余额扣除 ", textSize = 30, textColor = "#FF5E7585"))
list.add(
TextBean(
text = "-${expensesBalance.format2String(2)}",
textSize = 30,
textColor = "#FF0A1428",
isBold = true
)
)
if (balance >= totalPrice) {
//余额大于等于总价格,使用余额支付
list.add(TextBean(text = " 元,扣除后可用余额 ", textSize = 30, textColor = "#FF5E7585"))
val remainingBalance = balance - totalPrice
list.add(
TextBean(
text = remainingBalance.format2String(2),
textSize = 30,
textColor = "#FF0A1428",
isBold = true
)
)
} else {
//余额小于总价格,使用余额+扫码支付
//实际支付金额
list.add(TextBean(text = " 元,还需支付 ", textSize = 30, textColor = "#FF5E7585"))
list.add(
TextBean(
text = realPayPrice.format2String(2),
textSize = 30,
textColor = "#FF0A1428",
isBold = true
)
)
}
list.add(TextBean(text = "", textSize = 30, textColor = "#FF5E7585"))
return list
}
}
@@ -1,202 +1,202 @@
package com.sw.dualscreen.presentation.pay //package com.sw.dualscreen.presentation.pay
//
import android.annotation.SuppressLint //import android.annotation.SuppressLint
import android.app.Presentation //import android.app.Presentation
import android.os.Bundle //import android.os.Bundle
import android.view.Display //import android.view.Display
import com.sw.dualscreen.R //import com.sw.dualscreen.R
import com.sw.dualscreen.activity.PayActivity //import com.sw.dualscreen.activity.PayActivity
import com.sw.dualscreen.databinding.PresentationScanQrcodePayBinding //import com.sw.dualscreen.databinding.PresentationScanQrcodePayBinding
import com.sw.dualscreen.ext.format2String //import com.sw.dualscreen.ext.format2String
import com.sw.dualscreen.ext.gone //import com.sw.dualscreen.ext.gone
import com.sw.dualscreen.ext.invisible //import com.sw.dualscreen.ext.invisible
import com.sw.dualscreen.ext.load //import com.sw.dualscreen.ext.load
import com.sw.dualscreen.ext.visible //import com.sw.dualscreen.ext.visible
import com.sw.dualscreen.model.response.MemberInfo //import com.sw.dualscreen.model.response.MemberInfo
import com.sw.dualscreen.model.response.TextBean //import com.sw.dualscreen.model.response.TextBean
import com.sw.dualscreen.utils.SpannedUtils //import com.sw.dualscreen.utils.SpannedUtils
//
class ScanQrCodePayPresentation( //class ScanQrCodePayPresentation(
val activity: PayActivity, // val activity: PayActivity,
display: Display, // display: Display,
val type: Int, // val type: Int,
private val onDismissListener: () -> Unit = {} // private val onDismissListener: () -> Unit = {}
) : Presentation(activity, display) { //) : Presentation(activity, display) {
//
//type = 0,扫码支付默认显示二维码 // //type = 0,扫码支付默认显示二维码
//type = 1,会员结算显示二维码,是会员则显示名字、头像、手机号、可用余额 // //type = 1,会员结算显示二维码,是会员则显示名字、头像、手机号、可用余额
//type = 2,支付成功,是会员则显示名字、头像、手机号、可用余额 // //type = 2,支付成功,是会员则显示名字、头像、手机号、可用余额
//
private lateinit var binding: PresentationScanQrcodePayBinding // private lateinit var binding: PresentationScanQrcodePayBinding
//
override fun onCreate(savedInstanceState: Bundle?) { // override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) // super.onCreate(savedInstanceState)
binding = PresentationScanQrcodePayBinding.inflate(layoutInflater) // binding = PresentationScanQrcodePayBinding.inflate(layoutInflater)
setContentView(binding.root) // setContentView(binding.root)
window?.setBackgroundDrawableResource(android.R.color.transparent) // window?.setBackgroundDrawableResource(android.R.color.transparent)
initView() // initView()
} // }
//
var foodName: String? = null // var foodName: String? = null
var payQrCodePic: Any? = null // var payQrCodePic: Any? = null
//
//总价格 // //总价格
var totalPrice = 0.0 // var totalPrice = 0.0
//
//余额 // //余额
private var balance = 0.0 // private var balance = 0.0
//
//实际支付金额 // //实际支付金额
private var realPayPrice = 0.0 // private var realPayPrice = 0.0
//
//扣除余额 // //扣除余额
private var expensesBalance = 0.0 // private var expensesBalance = 0.0
//
@SuppressLint("SetTextI18n") // @SuppressLint("SetTextI18n")
private fun initView() { // private fun initView() {
when (type) { // when (type) {
0 -> { // 0 -> {
binding.layoutVip.gone() // binding.layoutVip.gone()
binding.layoutPaySuccess.gone() // binding.layoutPaySuccess.gone()
binding.layoutScanQrCode.visible() // binding.layoutScanQrCode.visible()
//
binding.tvPayTip.text = "请扫码支付或出示付款码" // binding.tvPayTip.text = "请扫码支付或出示付款码"
binding.tvFoodName.text = foodName // binding.tvFoodName.text = foodName
binding.tvRealAmount.text = SpannedUtils.getAmountText( // binding.tvRealAmount.text = SpannedUtils.getAmountText(
listOf( // listOf(
TextBean(text = "¥", textSize = 32), // TextBean(text = "¥", textSize = 32),
TextBean(text = totalPrice.format2String(2), textSize = 48), // TextBean(text = totalPrice.format2String(2), textSize = 48),
) // )
) // )
binding.ivPayQrCode.load(payQrCodePic) // binding.ivPayQrCode.load(payQrCodePic)
} // }
//
1 -> { // 1 -> {
val memberInfo = activity.memberInfo // val memberInfo = activity.memberInfo
binding.layoutVip.run { // binding.layoutVip.run {
if (memberInfo != null) visible() else gone() // if (memberInfo != null) visible() else gone()
} // }
binding.layoutPaySuccess.gone() // binding.layoutPaySuccess.gone()
binding.layoutScanQrCode.visible() // binding.layoutScanQrCode.visible()
//
balance = (memberInfo?.topUpBalance ?: 0.0) + (memberInfo?.rewardBalance ?: 0.0) // balance = (memberInfo?.topUpBalance ?: 0.0) + (memberInfo?.rewardBalance ?: 0.0)
realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance // realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance
expensesBalance = if (balance >= totalPrice) totalPrice else balance // expensesBalance = if (balance >= totalPrice) totalPrice else balance
//
memberInfo?.let { loadUserInfo(it) } // memberInfo?.let { loadUserInfo(it) }
binding.tvUserBalance.text = SpannedUtils.getAmountText( // binding.tvUserBalance.text = SpannedUtils.getAmountText(
listOf( // listOf(
TextBean(text = "¥", textSize = 20), // TextBean(text = "¥", textSize = 20),
TextBean(text = balance.format2String(2), textSize = 32), // TextBean(text = balance.format2String(2), textSize = 32),
) // )
) // )
//
binding.tvFoodName.text = foodName // binding.tvFoodName.text = foodName
binding.tvRealAmount.text = SpannedUtils.getAmountText( // binding.tvRealAmount.text = SpannedUtils.getAmountText(
listOf( // listOf(
TextBean(text = "¥", textSize = 32), // TextBean(text = "¥", textSize = 32),
TextBean(text = totalPrice.format2String(2), textSize = 48), // TextBean(text = totalPrice.format2String(2), textSize = 48),
) // )
) // )
binding.ivPayQrCode.run { // binding.ivPayQrCode.run {
// load(payQrCodePic) //// load(payQrCodePic)
if (balance >= totalPrice) invisible() else visible() // if (balance >= totalPrice) invisible() else visible()
} // }
//
binding.tvPayTip.text = SpannedUtils.getAmountText(getAmountList()) // binding.tvPayTip.text = SpannedUtils.getAmountText(getAmountList())
} // }
//
2 -> { // 2 -> {
val memberInfo = activity.memberInfo // val memberInfo = activity.memberInfo
binding.layoutVip.run { // binding.layoutVip.run {
if (memberInfo != null) visible() else gone() // if (memberInfo != null) visible() else gone()
} // }
binding.layoutPaySuccess.visible() // binding.layoutPaySuccess.visible()
binding.layoutScanQrCode.gone() // binding.layoutScanQrCode.gone()
//
balance = if (memberInfo != null) (memberInfo.topUpBalance // balance = if (memberInfo != null) (memberInfo.topUpBalance
?: 0.0) + (memberInfo.rewardBalance ?: 0.0) else 0.0 // ?: 0.0) + (memberInfo.rewardBalance ?: 0.0) else 0.0
realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance // realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance
expensesBalance = if (balance >= totalPrice) totalPrice else balance // expensesBalance = if (balance >= totalPrice) totalPrice else balance
//
memberInfo?.let { loadUserInfo(it) } // memberInfo?.let { loadUserInfo(it) }
val remainBalance = if (balance >= totalPrice) balance - totalPrice else 0.0 // val remainBalance = if (balance >= totalPrice) balance - totalPrice else 0.0
binding.tvUserBalance.text = SpannedUtils.getAmountText( // binding.tvUserBalance.text = SpannedUtils.getAmountText(
listOf( // listOf(
TextBean(text = "¥", textSize = 24), // TextBean(text = "¥", textSize = 24),
TextBean(text = remainBalance.format2String(2), textSize = 36), // TextBean(text = remainBalance.format2String(2), textSize = 36),
) // )
) // )
//
val showBalance = expensesBalance.format2String(2) // val showBalance = expensesBalance.format2String(2)
val showTotal = totalPrice.format2String(2) // val showTotal = totalPrice.format2String(2)
binding.tvPayInfo.text = "余额扣除 $showBalance 元,在线支付 $showTotal" // binding.tvPayInfo.text = "余额扣除 $showBalance 元,在线支付 $showTotal 元"
} // }
} // }
} // }
//
fun loadQrCodeImage(qrCodeUrl: String?) { // fun loadQrCodeImage(qrCodeUrl: String?) {
binding.ivPayQrCode.load(qrCodeUrl) // binding.ivPayQrCode.load(qrCodeUrl)
} // }
//
private fun loadUserInfo(item: MemberInfo) { // private fun loadUserInfo(item: MemberInfo) {
binding.ivHeadPic.load( // binding.ivHeadPic.load(
if(item.faceUrl.isNullOrBlank()) R.drawable.ic_avatar_default // if(item.faceUrl.isNullOrBlank()) R.drawable.ic_avatar_default
else item.faceUrl // else item.faceUrl
) // )
binding.tvUserName.text = item.name // binding.tvUserName.text = item.name
//
val phone = item.phone ?: "" // val phone = item.phone ?: ""
binding.tvUserPhone.text = // binding.tvUserPhone.text =
if (phone.length == 11) phone.replace(phone.substring(3, 7), "****") // if (phone.length == 11) phone.replace(phone.substring(3, 7), "****")
else phone // else phone
} // }
//
private fun getAmountList(): List<TextBean> { // private fun getAmountList(): List<TextBean> {
val list: MutableList<TextBean> = mutableListOf() // val list: MutableList<TextBean> = mutableListOf()
//
list.add(TextBean(text = "余额扣除 ", textSize = 30, textColor = "#FF5E7585")) // list.add(TextBean(text = "余额扣除 ", textSize = 30, textColor = "#FF5E7585"))
list.add( // list.add(
TextBean( // TextBean(
text = "-${expensesBalance.format2String(2)}", // text = "-${expensesBalance.format2String(2)}",
textSize = 30, // textSize = 30,
textColor = "#FF0A1428", // textColor = "#FF0A1428",
isBold = true // isBold = true
) // )
) // )
//
if (balance >= totalPrice) { // if (balance >= totalPrice) {
//余额大于等于总价格,使用余额支付 // //余额大于等于总价格,使用余额支付
list.add(TextBean(text = " 元,扣除后可用余额 ", textSize = 30, textColor = "#FF5E7585")) // list.add(TextBean(text = " 元,扣除后可用余额 ", textSize = 30, textColor = "#FF5E7585"))
val remainingBalance = balance - totalPrice // val remainingBalance = balance - totalPrice
list.add( // list.add(
TextBean( // TextBean(
text = remainingBalance.format2String(2), // text = remainingBalance.format2String(2),
textSize = 30, // textSize = 30,
textColor = "#FF0A1428", // textColor = "#FF0A1428",
isBold = true // isBold = true
) // )
) // )
} else { // } else {
//余额小于总价格,使用余额+扫码支付 // //余额小于总价格,使用余额+扫码支付
//实际支付金额 // //实际支付金额
list.add(TextBean(text = " 元,还需支付 ", textSize = 30, textColor = "#FF5E7585")) // list.add(TextBean(text = " 元,还需支付 ", textSize = 30, textColor = "#FF5E7585"))
list.add( // list.add(
TextBean( // TextBean(
text = realPayPrice.format2String(2), // text = realPayPrice.format2String(2),
textSize = 30, // textSize = 30,
textColor = "#FF0A1428", // textColor = "#FF0A1428",
isBold = true // isBold = true
) // )
) // )
} // }
list.add(TextBean(text = "", textSize = 30, textColor = "#FF5E7585")) // list.add(TextBean(text = " 元", textSize = 30, textColor = "#FF5E7585"))
//
return list // return list
} // }
//
override fun onDisplayRemoved() { // override fun onDisplayRemoved() {
super.onDisplayRemoved() // super.onDisplayRemoved()
onDismissListener() // onDismissListener()
} // }
} //}
@@ -0,0 +1,418 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFE7EFF8">
<LinearLayout
android:id="@+id/flFacePay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFE7EFF8"
android:orientation="vertical"
android:visibility="gone">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="28dp"
android:layout_marginTop="28dp"
android:background="@drawable/setting_border"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingVertical="56dp">
<TextView
android:id="@+id/tvFoodName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FF0A1428"
android:textSize="40sp"
android:textStyle="bold"
tools:text="岐山臊子面" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="55dp"
android:text="应付金额"
android:textColor="#FF5E7585"
android:textSize="24sp"
android:textStyle="bold"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/tvRealAmount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
android:textColor="#FFFF3232"
android:textSize="32sp"
android:textStyle="bold"
tools:text="¥36.80" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="28dp"
android:background="@drawable/setting_border"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="79dp"
android:text="会员验证"
android:textColor="#ff0a1428"
android:textSize="27sp"
android:textStyle="bold"
tools:ignore="HardcodedText" />
<!-- android:layout_width="300dp"-->
<!-- android:layout_height="510dp"-->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="510dp">
<FrameLayout
android:id="@+id/flFace"
android:layout_width="400dp"
android:layout_height="680dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="-85dp">
<TextureView
android:id="@+id/dual_camera_texture_preview_rgb"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible" />
<com.sw.plate.utils.arcface.FaceRectView
android:id="@+id/dual_camera_face_rect_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<FrameLayout
android:id="@+id/flRecognizeIr"
android:layout_width="1.33dp"
android:layout_height="1dp"
android:layout_gravity="bottom"
android:visibility="visible">
<TextureView
android:id="@+id/dual_camera_texture_preview_ir"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.sw.plate.utils.arcface.FaceRectView
android:id="@+id/dual_camera_face_rect_view_ir"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
</FrameLayout>
<FrameLayout
android:id="@+id/llCircleArea"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#FFF4FAFF" />
<com.sw.dualscreen.view.HollowOvalInRectView
android:layout_width="400dp"
android:layout_height="510dp"
android:layout_gravity="center_horizontal" />
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#FFF4FAFF" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="请正视屏幕进行面部识别..."
android:textColor="#ff5e7585"
android:textSize="22sp"
android:textStyle="bold"
android:layout_gravity="bottom|center_horizontal"
tools:ignore="HardcodedText" />
</FrameLayout>
<!-- <View-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="85dp"-->
<!-- android:layout_gravity="bottom"-->
<!-- android:background="#FFF4FAFF"/>-->
</FrameLayout>
</LinearLayout>
</LinearLayout>
<FrameLayout
android:id="@+id/flCashPay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFE7EFF8"
android:visibility="gone">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:layout_margin="28dp"
android:background="@drawable/setting_border"
android:orientation="vertical">
<TextView
android:id="@+id/tvFoodName2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FF0A1428"
android:layout_marginTop="210dp"
android:textSize="40sp"
android:textStyle="bold"
tools:text="岐山臊子面" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="55dp"
android:text="应付金额"
android:textColor="#FF5E7585"
android:textSize="24sp"
android:textStyle="bold"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/tvRealAmount2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
android:textColor="#FFFF3232"
android:textSize="32sp"
android:textStyle="bold"
tools:text="¥36.80" />
<!-- <TextView-->
<!-- android:id="@+id/tvPayTip"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_marginTop="55dp"-->
<!-- android:text="请扫码支付或出示付款码"-->
<!-- android:textColor="#FF5E7585"-->
<!-- android:textSize="24sp"-->
<!-- android:textStyle="bold"-->
<!-- tools:ignore="HardcodedText" />-->
</LinearLayout>
</FrameLayout>
<FrameLayout
android:id="@+id/flQrCodePay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFE7EFF8"
android:visibility="gone">
<LinearLayout
android:id="@+id/layoutScanQrCode"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:layout_margin="28dp"
android:background="@drawable/setting_border"
android:orientation="vertical"
android:visibility="visible">
<TextView
android:id="@+id/tvFoodName3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="327dp"
android:textColor="#FF0A1428"
android:textSize="40sp"
android:textStyle="bold"
tools:text="岐山臊子面" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="55dp"
android:text="应付金额"
android:textColor="#FF5E7585"
android:textSize="24sp"
android:textStyle="bold"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/tvRealAmount3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
android:textColor="#FFFF3232"
android:textSize="48sp"
android:textStyle="bold"
tools:text="¥36.80" />
<ImageView
android:id="@+id/ivPayQrCode"
android:layout_width="333dp"
android:layout_height="333dp"
android:layout_marginTop="56dp"
tools:ignore="ContentDescription"
tools:src="@drawable/ic_qrcode" />
<TextView
android:id="@+id/tvPayTip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="55dp"
android:text="请扫码支付或出示付款码"
android:textColor="#FF5E7585"
android:textSize="24sp"
tools:ignore="HardcodedText" />
<!-- android:textStyle="bold"-->
</LinearLayout>
<LinearLayout
android:id="@+id/layoutPaySuccess"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:layout_margin="28dp"
android:background="@drawable/setting_border"
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginTop="471dp"
android:src="@drawable/ic_pay_success" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
android:text="支付成功"
android:textColor="#ff00c864"
android:textSize="48sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvPayInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="55dp"
android:textColor="#FF5E7585"
android:textSize="24sp"
tools:text="余额扣除 16.80 元,在线支付 20.00 元" />
</LinearLayout>
<androidx.cardview.widget.CardView
android:id="@+id/layoutVip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="@color/white"
android:layout_marginTop="56dp"
android:layout_marginHorizontal="56dp"
app:cardCornerRadius="5dp"
app:cardElevation="0dp"
android:visibility="visible">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="28dp">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/ivHeadPic"
android:layout_width="84dp"
android:layout_height="84dp"
android:src="@drawable/ic_avatar_default"
android:layout_marginTop="28dp"
app:shapeAppearance="@style/CircleStyle"
app:strokeColor="@android:color/transparent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvUserName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="27dp"
android:textColor="#ff0a1428"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/tvUserPhone"
app:layout_constraintStart_toEndOf="@id/ivHeadPic"
app:layout_constraintTop_toTopOf="@id/ivHeadPic"
app:layout_constraintVertical_chainStyle="packed"
tools:text="DAMIHU" />
<TextView
android:id="@+id/tvUserPhone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:textColor="#ff889ac2"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="@id/ivHeadPic"
app:layout_constraintStart_toStartOf="@id/tvUserName"
app:layout_constraintTop_toBottomOf="@id/tvUserName"
tools:text="138****8000" />
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="28dp"
android:background="#FFDBE5FB"
app:layout_constraintTop_toBottomOf="@id/ivHeadPic" />
<TextView
android:id="@+id/tvBalanceName"
android:layout_width="wrap_content"
android:layout_height="79dp"
android:text="可用余额"
android:gravity="center_vertical"
android:textColor="#ff889ac2"
android:textSize="24sp"
app:layout_constraintStart_toStartOf="@id/ivHeadPic"
app:layout_constraintTop_toBottomOf="@id/divider" />
<TextView
android:id="@+id/tvUserBalance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#FF0A1428"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="@id/tvBalanceName"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tvBalanceName"
tools:text="¥16.80" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</FrameLayout>
</FrameLayout>