Compare commits
20
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6d2441704 | ||
|
|
e7d11530a6 | ||
|
|
c56769769b | ||
|
|
8673cec545 | ||
|
|
8ec7ea6a79 | ||
|
|
a80e3d1ba6 | ||
|
|
6d825d7235 | ||
|
|
4f1860eb37 | ||
|
|
45a7bf8369 | ||
|
|
f0b50ef82f | ||
|
|
824dc5f867 | ||
|
|
7232735073 | ||
|
|
8a1630f2a4 | ||
|
|
39c0ca7d72 | ||
|
|
dfde8c0fad | ||
|
|
aca84f1e94 | ||
|
|
7bcb74d9e0 | ||
|
|
6c857dd1f7 | ||
|
|
fd0f9977b1 | ||
|
|
ec4a5801c2 |
Generated
+1
@@ -0,0 +1 @@
|
||||
DualScreen
|
||||
Generated
+2
-2
@@ -4,10 +4,10 @@
|
||||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DropdownSelection timestamp="2026-02-06T02:13:05.230327500Z">
|
||||
<DropdownSelection timestamp="2026-03-10T03:02:12.156794100Z">
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="Default" identifier="serial=192.168.1.170:5555;connection=1ec19d1e" />
|
||||
<DeviceId pluginId="Default" identifier="serial=192.168.1.211:5555;connection=7435869e" />
|
||||
</handle>
|
||||
</Target>
|
||||
</DropdownSelection>
|
||||
|
||||
Generated
+13
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DeviceTable">
|
||||
<option name="columnSorters">
|
||||
<list>
|
||||
<ColumnSorterState>
|
||||
<option name="column" value="Name" />
|
||||
<option name="order" value="ASCENDING" />
|
||||
</ColumnSorterState>
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="UsePropertyAccessSyntax" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
Generated
+10
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MaterialThemeProjectNewConfig">
|
||||
<option name="metadata">
|
||||
<MTProjectMetadataState>
|
||||
<option name="userId" value="29face46:19ce5e8d9d0:-7fe0" />
|
||||
</MTProjectMetadataState>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,4 @@
|
||||
kotlin version: 2.0.21
|
||||
error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
|
||||
1. Kotlin compile daemon is ready
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"version": 3,
|
||||
"artifactType": {
|
||||
"type": "APK",
|
||||
"kind": "Directory"
|
||||
},
|
||||
"applicationId": "com.sw.dualscreen",
|
||||
"variantName": "debug",
|
||||
"elements": [
|
||||
{
|
||||
"type": "SINGLE",
|
||||
"filters": [],
|
||||
"attributes": [],
|
||||
"versionCode": 1,
|
||||
"versionName": "1.0",
|
||||
"outputFile": "档口双屏_1.0-debug.apk"
|
||||
}
|
||||
],
|
||||
"elementType": "File",
|
||||
"minSdkVersionForDexing": 29
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.",
|
||||
"_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.",
|
||||
"_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.",
|
||||
"entities": [
|
||||
{
|
||||
"id": "1:594331511073099531",
|
||||
"lastPropertyId": "11:7949771231314213889",
|
||||
"name": "Food",
|
||||
"properties": [
|
||||
{
|
||||
"id": "1:2317727855243736226",
|
||||
"name": "id",
|
||||
"type": 6,
|
||||
"flags": 1
|
||||
},
|
||||
{
|
||||
"id": "4:1528837175750321569",
|
||||
"name": "foodVector",
|
||||
"indexId": "1:6010838397086628487",
|
||||
"type": 28,
|
||||
"flags": 8
|
||||
},
|
||||
{
|
||||
"id": "5:5561277398349179032",
|
||||
"name": "collectId",
|
||||
"type": 9
|
||||
},
|
||||
{
|
||||
"id": "6:8313847979709019172",
|
||||
"name": "foodId",
|
||||
"type": 9
|
||||
},
|
||||
{
|
||||
"id": "7:1375536603452792462",
|
||||
"name": "foodName",
|
||||
"type": 9
|
||||
},
|
||||
{
|
||||
"id": "8:7942065542192169896",
|
||||
"name": "version",
|
||||
"type": 9
|
||||
},
|
||||
{
|
||||
"id": "9:4394472487596452023",
|
||||
"name": "otherField",
|
||||
"type": 9
|
||||
},
|
||||
{
|
||||
"id": "10:5704890122356491756",
|
||||
"name": "createTime",
|
||||
"type": 9
|
||||
},
|
||||
{
|
||||
"id": "11:7949771231314213889",
|
||||
"name": "isDel",
|
||||
"type": 5
|
||||
}
|
||||
],
|
||||
"relations": []
|
||||
}
|
||||
],
|
||||
"lastEntityId": "1:594331511073099531",
|
||||
"lastIndexId": "1:6010838397086628487",
|
||||
"lastRelationId": "0:0",
|
||||
"lastSequenceId": "0:0",
|
||||
"modelVersion": 5,
|
||||
"modelVersionParserMinimum": 5,
|
||||
"retiredEntityUids": [],
|
||||
"retiredIndexUids": [],
|
||||
"retiredPropertyUids": [
|
||||
340951913928211738,
|
||||
5484846939472684412
|
||||
],
|
||||
"retiredRelationUids": [],
|
||||
"version": 1
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"version": 3,
|
||||
"artifactType": {
|
||||
"type": "APK",
|
||||
"kind": "Directory"
|
||||
},
|
||||
"applicationId": "com.sw.dualscreen",
|
||||
"variantName": "release",
|
||||
"elements": [
|
||||
{
|
||||
"type": "SINGLE",
|
||||
"filters": [],
|
||||
"attributes": [],
|
||||
"versionCode": 1,
|
||||
"versionName": "1.0",
|
||||
"outputFile": "档口双屏_1.0-release.apk"
|
||||
}
|
||||
],
|
||||
"elementType": "File",
|
||||
"baselineProfiles": [
|
||||
{
|
||||
"minApi": 28,
|
||||
"maxApi": 30,
|
||||
"baselineProfiles": [
|
||||
"baselineProfiles/1/档口双屏_1.0-release.dm"
|
||||
]
|
||||
},
|
||||
{
|
||||
"minApi": 31,
|
||||
"maxApi": 2147483647,
|
||||
"baselineProfiles": [
|
||||
"baselineProfiles/0/档口双屏_1.0-release.dm"
|
||||
]
|
||||
}
|
||||
],
|
||||
"minSdkVersionForDexing": 29
|
||||
}
|
||||
@@ -65,7 +65,8 @@
|
||||
<activity android:name="com.sw.dualscreen.activity.CollectedFoodActivity"
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
|
||||
<activity android:name="com.sw.dualscreen.activity.PayActivity"
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
||||
android:launchMode="singleTask"/>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
|
||||
@@ -59,7 +59,7 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
keyEventHelper?.onDestroy()
|
||||
keyEventHelper?.release()
|
||||
EventBus.getDefault().unregister(this)
|
||||
super.onDestroy()
|
||||
}
|
||||
@@ -153,11 +153,11 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||
*/
|
||||
fun registerKeyEvent() {
|
||||
keyEventHelper =
|
||||
ScanGunKeyEventHelper(context, object : ScanGunKeyEventHelper.OnScanSuccessListener {
|
||||
ScanGunKeyEventHelper( object : ScanGunKeyEventHelper.OnScanSuccessListener {
|
||||
override fun onScanSuccess(barcode: String?) {
|
||||
Timber.d("onScanSuccess barcode = $barcode")
|
||||
if (barcode == null) return
|
||||
handleScanKeyInfo(barcode)
|
||||
scanGunKeyEventCallback(barcode)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -166,14 +166,13 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||
/**
|
||||
* 处理扫描枪数据
|
||||
*/
|
||||
protected open fun handleScanKeyInfo(scanInfo: String) {
|
||||
var payCode = scanInfo
|
||||
//if (scanInfo.length > 6) {
|
||||
// payCode = scanInfo.take(6)
|
||||
//}
|
||||
Log.d("TAG", "handleScanKeyInfo: " + payCode)
|
||||
fun scanGunKeyEventCallback(scanInfo: String){
|
||||
if (this is PayActivity) {
|
||||
qrCodePay(scanInfo)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
if (keyEventHelper != null) {
|
||||
if (keyEventHelper!!.isScanGunEvent(event)) {
|
||||
|
||||
@@ -48,6 +48,7 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
|
||||
|
||||
override fun initialize() {
|
||||
super.initialize()
|
||||
registerKeyEvent()
|
||||
binding.root.setOnClickListener { it.hideKeyboard() }
|
||||
binding.rvFoodList.let {
|
||||
it.layoutManager = LinearLayoutManager(this)
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package com.sw.dualscreen.activity
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.os.CountDownTimer
|
||||
import android.provider.Settings
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.sw.dualscreen.GlobalData
|
||||
import com.sw.dualscreen.databinding.ActivityInitBinding
|
||||
@@ -27,6 +31,27 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
|
||||
private val userViewModel by viewModels<UserViewModel>()
|
||||
private var pageNum = 1
|
||||
|
||||
// 相机权限请求回调
|
||||
private val requestCameraPermissionLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.RequestPermission()
|
||||
) { isGranted ->
|
||||
if (isGranted) {
|
||||
// 权限请求成功,打开 MainActivity
|
||||
goMainActivity()
|
||||
} else {
|
||||
// 权限请求失败,显示弹窗
|
||||
showPermissionDeniedDialog()
|
||||
}
|
||||
}
|
||||
|
||||
// 设置页面返回的回调
|
||||
private val startSettingsLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) {
|
||||
// 从设置页面返回,重新检查权限
|
||||
checkCameraPermissionAfterSettings()
|
||||
}
|
||||
|
||||
override fun getViewModel(): BaseViewModel {
|
||||
return userViewModel
|
||||
}
|
||||
@@ -85,6 +110,7 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
|
||||
|
||||
override fun initialize() {
|
||||
super.initialize()
|
||||
registerKeyEvent()
|
||||
initNetworkTimer()
|
||||
binding.okButton.clickWithDebounce(500) {
|
||||
checkNetworkTimer?.cancel()
|
||||
@@ -127,7 +153,7 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
|
||||
runOnUiThread {
|
||||
hideWaitingDialog()
|
||||
binding.root.postDelayed({
|
||||
goMainActivity()
|
||||
checkCameraPermissionAndGo()
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
@@ -141,6 +167,53 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
|
||||
finish()
|
||||
}
|
||||
|
||||
// 检查相机权限
|
||||
private fun checkCameraPermissionAndGo() {
|
||||
val hasCameraPermission = ContextCompat.checkSelfPermission(
|
||||
this,
|
||||
Manifest.permission.CAMERA
|
||||
) == android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||
|
||||
if (hasCameraPermission) {
|
||||
// 有权限,直接打开 MainActivity
|
||||
goMainActivity()
|
||||
} else {
|
||||
// 无权限,请求相机权限
|
||||
requestCameraPermissionLauncher.launch(Manifest.permission.CAMERA)
|
||||
}
|
||||
}
|
||||
|
||||
// 从设置页面返回后检查权限
|
||||
private fun checkCameraPermissionAfterSettings() {
|
||||
val hasCameraPermission = ContextCompat.checkSelfPermission(
|
||||
this,
|
||||
Manifest.permission.CAMERA
|
||||
) == android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||
|
||||
if (hasCameraPermission) {
|
||||
// 权限已授予,打开 MainActivity
|
||||
goMainActivity()
|
||||
} else {
|
||||
// 权限仍未授予,再次显示弹窗
|
||||
showPermissionDeniedDialog()
|
||||
}
|
||||
}
|
||||
|
||||
// 显示权限被拒绝的弹窗
|
||||
private fun showPermissionDeniedDialog() {
|
||||
RemindDialog(
|
||||
context = context,
|
||||
content = "暂无相机权限,请授权后重试",
|
||||
confirmBlock = {
|
||||
// 打开 App 设置页面
|
||||
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
||||
data = android.net.Uri.fromParts("package", packageName, null)
|
||||
}
|
||||
startSettingsLauncher.launch(intent)
|
||||
}
|
||||
).show()
|
||||
}
|
||||
|
||||
private var checkNetworkTimer: CountDownTimer? = null
|
||||
|
||||
private fun initNetworkTimer() {
|
||||
|
||||
@@ -151,12 +151,15 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
||||
override fun initialize() {
|
||||
super.initialize()
|
||||
isPageVisible = true
|
||||
// 拦截扫码枪按键事件,防止触发页面按钮点击
|
||||
registerKeyEvent()
|
||||
addBackEventListener()
|
||||
initView()
|
||||
viewModel.getUserFaceCache(pageNo = 1)
|
||||
if (SpTool.firstGetFace) {
|
||||
viewModel.getUserFaceCache(pageNo = 1)
|
||||
}
|
||||
setupCamera()
|
||||
initData()
|
||||
registerKeyEvent()
|
||||
}
|
||||
|
||||
private fun initData() {
|
||||
@@ -456,6 +459,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
||||
// 3. 关闭相关资源
|
||||
imageCapture = null
|
||||
imageAnalysis = null
|
||||
cameraProviderFuture = null
|
||||
|
||||
// 4. 停止副屏更新
|
||||
isAnalyzing = false
|
||||
@@ -554,6 +558,10 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
isPageVisible = true
|
||||
// shutdownCamera 后 cameraProviderFuture 置为 null,需重新初始化相机
|
||||
if (cameraProviderFuture == null) {
|
||||
setupCamera()
|
||||
}
|
||||
// 启动定时器
|
||||
handler.postDelayed(timeoutRunnable, TIME_OUT)
|
||||
// ToastUtils.showToast("isPageVisible=$isPageVisible")
|
||||
@@ -796,7 +804,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
||||
userId = null,
|
||||
) {
|
||||
isRefreshPage = false
|
||||
//presentation?.dismiss()
|
||||
shutdownCamera()
|
||||
startActivity(Intent(this, PayActivity::class.java).apply {
|
||||
putExtra(PayActivity.FOOD_INFO, checkedItem!!)
|
||||
putExtra(PayActivity.FOOD_EAT_NUM, eatNum)
|
||||
@@ -915,16 +923,16 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
||||
|
||||
// private val initialDelay = 5 * 60 * 1000L
|
||||
// private val dealyMillis = 10 * 60 * 1000L
|
||||
private val initialDelay = 30 * 1000L
|
||||
private val dealyMillis = 3 * 60 * 1000L
|
||||
private val initialDelay = 10 * 1000L
|
||||
private val dealyMillis = 5 * 1000L
|
||||
// private var taskPageNo = 1
|
||||
fun startFaceTask() {
|
||||
faceTaskJob =
|
||||
intervalExecutor.startIntervalTaskWithInitialDelay(initialDelay, dealyMillis) {
|
||||
val timestamp = SpTool.getLastFaceTimestamp()
|
||||
if (timestamp == 0L) {
|
||||
return@startIntervalTaskWithInitialDelay
|
||||
}
|
||||
val timestamp = SpTool.lastFaceTimestamp
|
||||
//if (timestamp == 0L) {
|
||||
// return@startIntervalTaskWithInitialDelay
|
||||
//}
|
||||
viewModel.getFaceIncrementList(
|
||||
// pageNo = taskPageNo,
|
||||
timestamp = timestamp,
|
||||
@@ -956,7 +964,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
||||
model.userId, null, Base64.decode(model.faceFeatureStr)
|
||||
).also {
|
||||
//1-会员、2-临时用户
|
||||
it.userType = if (model.isMember) "1" else "2"
|
||||
it.userType = if (model.member) "1" else "2"
|
||||
it.cardNo = model.cardNo
|
||||
}
|
||||
faceList.add(faceEntity)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.sw.dualscreen.activity
|
||||
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.viewModels
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.sw.dualscreen.GlobalKey
|
||||
@@ -14,13 +15,15 @@ import com.sw.dualscreen.ext.gone
|
||||
import com.sw.dualscreen.model.response.FoodInfo
|
||||
import com.sw.dualscreen.model.response.MemberInfo
|
||||
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.Debouncer
|
||||
import com.sw.dualscreen.utils.SPUtil
|
||||
import com.sw.dualscreen.utils.SoundPoolUtil
|
||||
import com.sw.dualscreen.viewmodel.BaseViewModel
|
||||
import com.sw.dualscreen.viewmodel.UserViewModel
|
||||
import com.sw.plate.utils.ToastUtils
|
||||
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import timber.log.Timber
|
||||
|
||||
class PayActivity : BaseActivity<ActivityPayBinding>() {
|
||||
|
||||
@@ -37,6 +40,7 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
||||
const val TAG_PAY_NUMBER = "tagPayNumber"
|
||||
const val TAG_PAY_FACE = "tagPayFace"
|
||||
const val TAG_PAY_RESULT = "tagPayResult"
|
||||
const val PAY_SUCCESS = "pay_success"
|
||||
}
|
||||
|
||||
val userViewModel by viewModels<UserViewModel>()
|
||||
@@ -61,10 +65,31 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
||||
|
||||
var foodOrderId: String = ""
|
||||
var eatNum: Int = 1
|
||||
var isMember: Boolean = false
|
||||
|
||||
val presentation by lazy {
|
||||
PayPresentation(
|
||||
activity = this@PayActivity,
|
||||
userViewModel = userViewModel,
|
||||
recognizeViewModel = recognizeViewModel,
|
||||
display = displays[1]
|
||||
)
|
||||
}
|
||||
|
||||
fun playSoundOnPaySuccess() {
|
||||
SoundPoolUtil.getInstance().play(PAY_SUCCESS, 0)
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun initialize() {
|
||||
super.initialize()
|
||||
presentation.show()
|
||||
registerKeyEvent()
|
||||
addBackEventListener()
|
||||
val soundMap = hashMapOf<String, Int>()
|
||||
soundMap[PAY_SUCCESS] = R.raw.pay_success
|
||||
SoundPoolUtil.getInstance().loadR(this, soundMap)
|
||||
|
||||
foodInfo = intent.getParcelableExtra(FOOD_INFO)
|
||||
foodOrderId = intent.getStringExtra(FOOD_ORDER_ID) ?: ""
|
||||
eatNum = intent.getIntExtra(FOOD_EAT_NUM, 1)
|
||||
@@ -74,12 +99,14 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
||||
binding.include.ivPageBack.setOnClickListener { finish() }
|
||||
binding.include.tvPageTitle.text = "下单结算"
|
||||
binding.btnPayQrCode.setOnClickListener {
|
||||
isMember = false
|
||||
if (binding.btnPayQrCode.isChecked) {
|
||||
return@setOnClickListener
|
||||
}
|
||||
showScanQrCodePay()
|
||||
}
|
||||
binding.btnPayCash.setOnClickListener {
|
||||
isMember = false
|
||||
if (binding.btnPayCash.isChecked) {
|
||||
return@setOnClickListener
|
||||
}
|
||||
@@ -101,7 +128,6 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
||||
}
|
||||
showFacePay()
|
||||
}
|
||||
|
||||
showScanQrCodePay()
|
||||
}
|
||||
|
||||
@@ -136,45 +162,55 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
qrCodePayFragment?.presentation?.dismiss()
|
||||
cashPayFragment?.presentation?.dismiss()
|
||||
numberPayFragment?.presentation?.dismiss()
|
||||
facePayFragment?.presentation?.dismiss()
|
||||
scanQrCodePayPresentation?.dismiss()
|
||||
presentation.dismiss()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private var scanQrCodePayPresentation: ScanQrCodePayPresentation? = null
|
||||
var memberInfo: MemberInfo? = null
|
||||
fun showPayInfo(type: Int = 1, isVip: Boolean = false, memberInfo: MemberInfo? = null) {
|
||||
this.memberInfo = memberInfo
|
||||
payResultFragment = PayResultFragment.instance(type, memberInfo)
|
||||
showFragment(payResultFragment!!, TAG_PAY_RESULT)
|
||||
if (displays.size > 1) {
|
||||
scanQrCodePayPresentation = ScanQrCodePayPresentation(
|
||||
activity = this,
|
||||
display = displays[1],
|
||||
type = type
|
||||
) {
|
||||
scanQrCodePayPresentation?.dismiss()
|
||||
}.also {
|
||||
it.foodName = foodInfo?.foodName
|
||||
//确认使用vipPrice 还是 specPrice
|
||||
val onePrice = if (isVip) foodInfo?.vipPrice else foodInfo?.specPrice
|
||||
it.totalPrice = (onePrice ?: 0.0) * eatNum
|
||||
}
|
||||
scanQrCodePayPresentation?.show()
|
||||
// if (displays.size > 1) {
|
||||
// scanQrCodePayPresentation = ScanQrCodePayPresentation(
|
||||
// activity = this,
|
||||
// display = displays[1],
|
||||
// type = type
|
||||
// ) {
|
||||
// scanQrCodePaypresentation.dismiss()
|
||||
// }.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.pageType = PayPresentation.QR_CODE_PAY
|
||||
it.type = type
|
||||
it.foodName = foodInfo?.foodName
|
||||
//确认使用vipPrice 还是 specPrice
|
||||
val onePrice = if (isVip) foodInfo?.vipPrice else foodInfo?.specPrice
|
||||
it.totalPrice = (onePrice ?: 0.0) * eatNum
|
||||
presentation.initView()
|
||||
}
|
||||
}
|
||||
|
||||
fun updateQrCodeImage(qrCodeUrl: String?) {
|
||||
scanQrCodePayPresentation?.loadQrCodeImage(qrCodeUrl)
|
||||
// scanQrCodePaypresentation.loadQrCodeImage(qrCodeUrl)
|
||||
presentation.loadQrCodeImage(qrCodeUrl)
|
||||
}
|
||||
|
||||
private var debouncer = Debouncer(5000)
|
||||
|
||||
fun showPaySuccess(isVip: Boolean) {
|
||||
EventBus.getDefault().post(PaySuccessEvent())
|
||||
showPayInfo(type = 2, isVip = isVip, memberInfo = memberInfo)
|
||||
hidePayTab()
|
||||
debouncer.debounce {
|
||||
playSoundOnPaySuccess()
|
||||
EventBus.getDefault().post(PaySuccessEvent())
|
||||
showPayInfo(type = 2, isVip = isVip, memberInfo = memberInfo)
|
||||
hidePayTab()
|
||||
}
|
||||
}
|
||||
|
||||
fun getQrCodeImg(
|
||||
@@ -206,7 +242,7 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
fun memberPay(param: HashMap<String, String>, block: () -> Unit) {
|
||||
fun memberPay(param: HashMap<String, String?>, block: () -> Unit) {
|
||||
userViewModel.memberPay(param) {
|
||||
runOnUiThread {
|
||||
if (it.not()) {
|
||||
@@ -260,6 +296,18 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
fun qrCodePay(scanInfo: String) {
|
||||
showWaitingDialog("支付中……")
|
||||
userViewModel.qrCodePay(scanInfo, foodOrderId, memberInfo?.id) { payState, backData ->
|
||||
Timber.d("qrCodePay支付返回数据:state=$payState,backData=$backData")
|
||||
hideWaitingDialog()
|
||||
if (payState) {
|
||||
showPaySuccess(isMember)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// fun bindOrder(userId: String, block: () -> Unit) {
|
||||
// val pickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0)
|
||||
// val mode = if (pickupMode == 1) 1 else 2
|
||||
@@ -303,4 +351,12 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
fun addBackEventListener() {
|
||||
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ class SettingActivity : BaseActivity<ActivitySettingBinding>() {
|
||||
|
||||
override fun initialize() {
|
||||
super.initialize()
|
||||
registerKeyEvent()
|
||||
initBackDispatcher()
|
||||
binding.include.let {
|
||||
it.ivPageBack.setOnClickListener {
|
||||
|
||||
@@ -8,7 +8,7 @@ import com.sw.dualscreen.activity.PayActivity
|
||||
import com.sw.dualscreen.activity.fragment.BaseFragment
|
||||
import com.sw.dualscreen.databinding.FragmentCashPayBinding
|
||||
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
|
||||
|
||||
class CashPayFragment : BaseFragment<FragmentCashPayBinding>() {
|
||||
@@ -17,8 +17,6 @@ class CashPayFragment : BaseFragment<FragmentCashPayBinding>() {
|
||||
private var foodName: String? = null
|
||||
private var payAmount: Double? = null
|
||||
|
||||
var presentation: CashPayPresentation? = null
|
||||
|
||||
override fun inflateViewBinding(): FragmentCashPayBinding {
|
||||
return FragmentCashPayBinding.inflate(layoutInflater)
|
||||
}
|
||||
@@ -27,63 +25,73 @@ class CashPayFragment : BaseFragment<FragmentCashPayBinding>() {
|
||||
payActivity = requireActivity() as PayActivity
|
||||
payActivity.foodInfo?.let {
|
||||
foodName = it.foodName
|
||||
payAmount = (it.specPrice?:0.0) * payActivity.eatNum
|
||||
payAmount = (it.specPrice ?: 0.0) * payActivity.eatNum
|
||||
}
|
||||
binding.tvRealAmount.text = getAmountText(payAmount.format2String(2))
|
||||
|
||||
binding.btnConfirmPayFinish.setOnClickListener {
|
||||
payActivity.cashPay(hashMapOf(
|
||||
//标价金额(单位为元)
|
||||
"totalFee" to payAmount.format2String(2),
|
||||
//订单类型(0付款1会员充值)
|
||||
"orderType" to "0",
|
||||
//商户订单号
|
||||
"orderNo" to payActivity.foodOrderId,
|
||||
//支付来源:0线下收款 、3:线上纯会员支付 101:支付宝主动、102:支付宝被动、201:微信主动、202:微信被动
|
||||
"paySource" to "0"
|
||||
)) { paySuccess ->
|
||||
payActivity.cashPay(
|
||||
hashMapOf(
|
||||
//标价金额(单位为元)
|
||||
"totalFee" to payAmount.format2String(2),
|
||||
//订单类型(0付款1会员充值)
|
||||
"orderType" to "0",
|
||||
//商户订单号
|
||||
"orderNo" to payActivity.foodOrderId,
|
||||
//支付来源:0线下收款 、3:线上纯会员支付 101:支付宝主动、102:支付宝被动、201:微信主动、202:微信被动
|
||||
"paySource" to "0"
|
||||
)
|
||||
) { paySuccess ->
|
||||
if (paySuccess.not()) {
|
||||
ToastUtils.showToast("接口调用失败")
|
||||
return@cashPay
|
||||
}
|
||||
payActivity.showPaySuccess( false)
|
||||
payActivity.showPaySuccess(false)
|
||||
}
|
||||
}
|
||||
showSubScreen()
|
||||
}
|
||||
override fun onHiddenChanged(hidden: Boolean) {
|
||||
super.onHiddenChanged(hidden)
|
||||
if (hidden) {
|
||||
dismissSubScreen()
|
||||
return
|
||||
}
|
||||
//showSubScreen()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
dismissSubScreen()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun dismissSubScreen() {
|
||||
presentation?.let {
|
||||
if (it.isShowing) {
|
||||
it.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
// override fun onHiddenChanged(hidden: Boolean) {
|
||||
// super.onHiddenChanged(hidden)
|
||||
// if (hidden) {
|
||||
// dismissSubScreen()
|
||||
// return
|
||||
// }
|
||||
// //showSubScreen()
|
||||
// }
|
||||
//
|
||||
// override fun onDestroy() {
|
||||
// dismissSubScreen()
|
||||
// super.onDestroy()
|
||||
// }
|
||||
//
|
||||
// private fun dismissSubScreen() {
|
||||
// }
|
||||
|
||||
private fun showSubScreen() {
|
||||
// 查找副屏(通常索引为1)
|
||||
if (displays.size > 1) {
|
||||
dismissSubScreen()
|
||||
presentation = CashPayPresentation(activity = payActivity, display = displays[1]) {
|
||||
presentation?.dismiss()
|
||||
}.also {
|
||||
it.foodName = foodName
|
||||
it.payAmount = payAmount.format2String(2)
|
||||
}
|
||||
presentation?.show()
|
||||
// // 查找副屏(通常索引为1)
|
||||
// if (displays.size > 1) {
|
||||
// dismissSubScreen()
|
||||
// presentation = PayPresentation(
|
||||
// activity = payActivity,
|
||||
// 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.payAmount = payAmount.format2String(2)
|
||||
it.initView()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,14 +5,13 @@ import com.sw.dualscreen.activity.PayActivity
|
||||
import com.sw.dualscreen.activity.fragment.BaseFragment
|
||||
import com.sw.dualscreen.databinding.FragmentFacePayBinding
|
||||
import com.sw.dualscreen.ext.format2String
|
||||
import com.sw.dualscreen.presentation.pay.FacePayPresentation
|
||||
import com.sw.dualscreen.presentation.pay.PayPresentation
|
||||
|
||||
class FacePayFragment : BaseFragment<FragmentFacePayBinding>() {
|
||||
|
||||
private lateinit var payActivity: PayActivity
|
||||
private var foodName: String? = null
|
||||
private var payAmount: Double? = null
|
||||
var presentation: FacePayPresentation? = null
|
||||
override fun inflateViewBinding(): FragmentFacePayBinding {
|
||||
return FragmentFacePayBinding.inflate(layoutInflater)
|
||||
}
|
||||
@@ -28,28 +27,30 @@ class FacePayFragment : BaseFragment<FragmentFacePayBinding>() {
|
||||
}
|
||||
showSubScreen()
|
||||
}
|
||||
//
|
||||
// override fun onHiddenChanged(hidden: Boolean) {
|
||||
// super.onHiddenChanged(hidden)
|
||||
// if (hidden) {
|
||||
//// dismissSubScreen()
|
||||
// payActivity.presentation.pauseCamera()
|
||||
// return
|
||||
// }
|
||||
// payActivity.presentation.resumeCamera()
|
||||
// //showSubScreen()
|
||||
// }
|
||||
|
||||
override fun onHiddenChanged(hidden: Boolean) {
|
||||
super.onHiddenChanged(hidden)
|
||||
if (hidden) {
|
||||
dismissSubScreen()
|
||||
return
|
||||
}
|
||||
//showSubScreen()
|
||||
}
|
||||
// override fun onDestroy() {
|
||||
// dismissSubScreen()
|
||||
// super.onDestroy()
|
||||
// }
|
||||
|
||||
override fun onDestroy() {
|
||||
dismissSubScreen()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun dismissSubScreen() {
|
||||
presentation?.let {
|
||||
if (it.isShowing) {
|
||||
it.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
// private fun dismissSubScreen() {
|
||||
// presentation?.let {
|
||||
// if (it.isShowing) {
|
||||
// it.dismiss()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
fun loadBitmap(frame: Bitmap) {
|
||||
binding.ivFaceImage.setImageBitmap(frame)
|
||||
@@ -57,21 +58,44 @@ class FacePayFragment : BaseFragment<FragmentFacePayBinding>() {
|
||||
|
||||
private fun showSubScreen() {
|
||||
// 查找副屏(通常索引为1)
|
||||
if (displays.size > 1) {
|
||||
dismissSubScreen()
|
||||
presentation = FacePayPresentation(
|
||||
activity = payActivity,
|
||||
display = displays[1],
|
||||
userViewModel = payActivity.userViewModel,
|
||||
recognizeViewModel = payActivity.recognizeViewModel,
|
||||
// parentTextureView = binding.parentTextureView,
|
||||
) {
|
||||
presentation?.dismiss()
|
||||
}.also {
|
||||
it.foodName = foodName
|
||||
it.payAmount = payAmount.format2String(2)
|
||||
}
|
||||
presentation?.show()
|
||||
// if (displays.size > 1) {
|
||||
// dismissSubScreen()
|
||||
// presentation = PayPresentation(
|
||||
// activity = payActivity,
|
||||
// pageType = PayPresentation.FACE_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.FACE_PAY
|
||||
it.foodName = foodName
|
||||
it.payAmount = payAmount.format2String(2)
|
||||
isResumeCamera = true
|
||||
it.initView()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
payActivity.presentation.pauseCamera()
|
||||
}
|
||||
|
||||
private var isResumeCamera = false
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (isResumeCamera) {
|
||||
payActivity.presentation.resumeCamera()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
package com.sw.dualscreen.activity.fragment.pay
|
||||
|
||||
import android.text.InputType
|
||||
import com.sw.dualscreen.R
|
||||
import com.sw.dualscreen.activity.PayActivity
|
||||
import com.sw.dualscreen.activity.fragment.BaseFragment
|
||||
import com.sw.dualscreen.databinding.FragmentNumberPayBinding
|
||||
import com.sw.dualscreen.ext.format2String
|
||||
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
|
||||
|
||||
class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
|
||||
@@ -13,7 +15,8 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
|
||||
private lateinit var payActivity: PayActivity
|
||||
private var foodName: String? = null
|
||||
private var payAmount: Double? = null
|
||||
var presentation: CashPayPresentation? = null
|
||||
// 密码是否显示的标志
|
||||
private var isPasswordVisible = false
|
||||
override fun inflateViewBinding(): FragmentNumberPayBinding {
|
||||
return FragmentNumberPayBinding.inflate(layoutInflater)
|
||||
}
|
||||
@@ -28,6 +31,12 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
|
||||
foodName = it.foodName
|
||||
payAmount = (it.specPrice ?: 0.0) * payActivity.eatNum
|
||||
}
|
||||
|
||||
// 密码显示/隐藏切换
|
||||
binding.ivTogglePassword.setOnClickListener {
|
||||
togglePasswordVisibility()
|
||||
}
|
||||
|
||||
binding.btnConfirm.setOnClickListener { v ->
|
||||
val phone = binding.etInputPhone.text.toString().trim()
|
||||
val key = binding.etInputNumber.text.toString().trim()
|
||||
@@ -42,7 +51,7 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
|
||||
// ToastUtils.showToast("会员验证成功")
|
||||
// }
|
||||
// }
|
||||
|
||||
payActivity.isMember = false
|
||||
payActivity.getMemberInfoByPhone(phone, key) { memberInfo ->
|
||||
if (memberInfo == null) {
|
||||
binding.root.postDelayed({
|
||||
@@ -51,6 +60,9 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
|
||||
}, 1000)
|
||||
return@getMemberInfoByPhone
|
||||
}
|
||||
//肯定是会员
|
||||
memberInfo.member = true
|
||||
payActivity.isMember = true
|
||||
//绑定订单使用faceUserId
|
||||
payActivity.bindOrder(memberInfo.faceUserId ?: "") { bindResult ->
|
||||
if (bindResult.not()) {
|
||||
@@ -60,7 +72,7 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
|
||||
}
|
||||
binding.root.postDelayed({
|
||||
payActivity.hideWaitingDialog()
|
||||
payActivity.showPayInfo(type = 1, isVip = true, memberInfo = memberInfo)
|
||||
payActivity.showPayInfo(type = 1, isVip = memberInfo.member, memberInfo = memberInfo)
|
||||
payActivity.hidePayTab()
|
||||
}, 1000)
|
||||
}
|
||||
@@ -79,42 +91,76 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
|
||||
showSubScreen()
|
||||
}
|
||||
|
||||
override fun onHiddenChanged(hidden: Boolean) {
|
||||
super.onHiddenChanged(hidden)
|
||||
if (hidden) {
|
||||
dismissSubScreen()
|
||||
return
|
||||
}
|
||||
//showSubScreen()
|
||||
}
|
||||
// override fun onHiddenChanged(hidden: Boolean) {
|
||||
// super.onHiddenChanged(hidden)
|
||||
// if (hidden) {
|
||||
// dismissSubScreen()
|
||||
// return
|
||||
// }
|
||||
// //showSubScreen()
|
||||
// }
|
||||
//
|
||||
// override fun onDestroy() {
|
||||
// dismissSubScreen()
|
||||
// super.onDestroy()
|
||||
// }
|
||||
|
||||
override fun onDestroy() {
|
||||
dismissSubScreen()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun dismissSubScreen() {
|
||||
presentation?.let {
|
||||
if (it.isShowing) {
|
||||
it.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
// private fun dismissSubScreen() {
|
||||
// presentation?.let {
|
||||
// if (it.isShowing) {
|
||||
// it.dismiss()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
private fun showSubScreen() {
|
||||
// 查找副屏(通常索引为1)
|
||||
if (displays.size > 1) {
|
||||
dismissSubScreen()
|
||||
presentation = CashPayPresentation(activity = payActivity, display = displays[1]) {
|
||||
presentation?.dismiss()
|
||||
}.also {
|
||||
it.foodName = foodName
|
||||
it.payAmount = payAmount.format2String(2)
|
||||
}
|
||||
presentation?.show()
|
||||
// if (displays.size > 1) {
|
||||
// dismissSubScreen()
|
||||
// presentation = PayPresentation(
|
||||
// activity = payActivity,
|
||||
// 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.payAmount = payAmount.format2String(2)
|
||||
it.initView()
|
||||
}
|
||||
}
|
||||
|
||||
// 切换密码显示/隐藏
|
||||
private fun togglePasswordVisibility() {
|
||||
val editText = binding.etInputNumber
|
||||
val imageView = binding.ivTogglePassword
|
||||
|
||||
// 保存当前光标位置
|
||||
val cursorPosition = editText.selectionStart
|
||||
|
||||
if (isPasswordVisible) {
|
||||
// 当前显示,改为隐藏
|
||||
editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
imageView.setImageResource(R.drawable.ic_eye_close)
|
||||
isPasswordVisible = false
|
||||
} else {
|
||||
// 当前隐藏,改为显示
|
||||
editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
||||
imageView.setImageResource(R.drawable.ic_eye_open)
|
||||
isPasswordVisible = true
|
||||
}
|
||||
|
||||
// 恢复光标位置
|
||||
editText.setSelection(cursorPosition)
|
||||
}
|
||||
|
||||
// private fun bindOrder(userId: String, block: () -> Unit) {
|
||||
// payActivity.bindOrder(userId) { bindResult ->
|
||||
// if (bindResult.not()) {
|
||||
|
||||
@@ -74,7 +74,7 @@ class PayResultFragment : BaseFragment<FragmentPayResultBinding>() {
|
||||
memberInfo = it.getParcelable(MEMBER_INFO)
|
||||
}
|
||||
|
||||
if (memberInfo != null) {
|
||||
if (memberInfo != null && memberInfo!!.member) {
|
||||
totalPrice = (payActivity.foodInfo?.vipPrice ?: 0.0) * payActivity.eatNum
|
||||
binding.layoutVip.visible()
|
||||
balance = (memberInfo!!.topUpBalance ?: 0.0) + (memberInfo!!.rewardBalance ?: 0.0)
|
||||
@@ -112,9 +112,15 @@ class PayResultFragment : BaseFragment<FragmentPayResultBinding>() {
|
||||
)
|
||||
)
|
||||
|
||||
binding.tvAccountInfo.text = SpannedUtils.getAmountText(
|
||||
getAmountList()
|
||||
)
|
||||
if (memberInfo != null && memberInfo!!.member) {
|
||||
binding.tvAccountInfo.text = SpannedUtils.getAmountText(
|
||||
getAmountList()
|
||||
)
|
||||
} else {
|
||||
binding.tvAccountInfo.text = SpannedUtils.getAmountText(
|
||||
getAmountListNotMember()
|
||||
)
|
||||
}
|
||||
if (balance >= totalPrice) {
|
||||
binding.ivPayQrCode.invisible()
|
||||
binding.btnConfirmPay.run {
|
||||
@@ -201,6 +207,21 @@ class PayResultFragment : BaseFragment<FragmentPayResultBinding>() {
|
||||
//扣除余额
|
||||
private var expensesBalance = 0.0
|
||||
|
||||
private fun getAmountListNotMember(): List<TextBean> {
|
||||
val list: MutableList<TextBean> = mutableListOf()
|
||||
|
||||
list.add(TextBean(text = "应付金额 ", textSize = 30, textColor = "#FF889AC2"))
|
||||
list.add(
|
||||
TextBean(
|
||||
text = "-${totalPrice.format2String(2)}",
|
||||
textSize = 30,
|
||||
textColor = "#FF0A1428",
|
||||
isBold = true
|
||||
)
|
||||
)
|
||||
list.add(TextBean(text = " 元", textSize = 30, textColor = "#FF889AC2"))
|
||||
return list
|
||||
}
|
||||
private fun getAmountList(): List<TextBean> {
|
||||
val list: MutableList<TextBean> = mutableListOf()
|
||||
|
||||
@@ -268,7 +289,7 @@ class PayResultFragment : BaseFragment<FragmentPayResultBinding>() {
|
||||
//订单号
|
||||
"orderNo" to payActivity.foodOrderId,
|
||||
//用户id
|
||||
"memberId" to memberInfo!!.id!!
|
||||
"memberId" to memberInfo?.id
|
||||
),
|
||||
block = callback
|
||||
)
|
||||
|
||||
+31
-25
@@ -8,11 +8,11 @@ import androidx.lifecycle.lifecycleScope
|
||||
import com.sw.dualscreen.activity.PayActivity
|
||||
import com.sw.dualscreen.activity.fragment.BaseFragment
|
||||
import com.sw.dualscreen.ext.load
|
||||
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
|
||||
import com.sw.dualscreen.presentation.pay.PayPresentation
|
||||
import com.sw.dualscreen.utils.IntervalExecutor
|
||||
|
||||
class ScanQrCodePayFragment : BaseFragment<FragmentScanQrcodePayBinding>() {
|
||||
@@ -25,7 +25,6 @@ class ScanQrCodePayFragment : BaseFragment<FragmentScanQrcodePayBinding>() {
|
||||
private var foodName: String? = null
|
||||
private var payAmount: Double? = null
|
||||
private var payQrCodePic: String? = null
|
||||
var presentation: ScanQrCodePayPresentation? = null
|
||||
private var countDownJob: Job? = null
|
||||
|
||||
override fun inflateViewBinding(): FragmentScanQrcodePayBinding {
|
||||
@@ -37,7 +36,7 @@ class ScanQrCodePayFragment : BaseFragment<FragmentScanQrcodePayBinding>() {
|
||||
|
||||
payActivity.foodInfo?.let {
|
||||
foodName = it.foodName
|
||||
payAmount = (it.specPrice?:0.0)*payActivity.eatNum
|
||||
payAmount = (it.specPrice ?: 0.0) * payActivity.eatNum
|
||||
}
|
||||
binding.tvRealAmount.text = getAmountText(payAmount.format2String(2))
|
||||
//不需要绑定会员id
|
||||
@@ -60,41 +59,48 @@ class ScanQrCodePayFragment : BaseFragment<FragmentScanQrcodePayBinding>() {
|
||||
|
||||
override fun onHiddenChanged(hidden: Boolean) {
|
||||
super.onHiddenChanged(hidden)
|
||||
if (hidden) {
|
||||
dismissSubScreen()
|
||||
return
|
||||
}
|
||||
// if (hidden) {
|
||||
//// dismissSubScreen()
|
||||
// return
|
||||
// }
|
||||
//showSubScreen()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
dismissSubScreen()
|
||||
// dismissSubScreen()
|
||||
countDownJob?.cancel() // 自动取消订阅,防止内存泄漏
|
||||
payTaskJob?.cancel()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun dismissSubScreen() {
|
||||
presentation?.let {
|
||||
if (it.isShowing) {
|
||||
it.dismiss()
|
||||
}
|
||||
}
|
||||
// presentation?.let {
|
||||
// if (it.isShowing) {
|
||||
// it.dismiss()
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
private fun showSubScreen() {
|
||||
// 查找副屏(通常索引为1)
|
||||
if (displays.size > 1) {
|
||||
dismissSubScreen()
|
||||
presentation =
|
||||
ScanQrCodePayPresentation(activity = payActivity, type = 0, display = displays[1]) {
|
||||
presentation?.dismiss()
|
||||
}.also {
|
||||
it.foodName = foodName
|
||||
it.totalPrice = payAmount ?: 0.0
|
||||
it.payQrCodePic = payQrCodePic
|
||||
}
|
||||
presentation?.show()
|
||||
// // 查找副屏(通常索引为1)
|
||||
// if (displays.size > 1) {
|
||||
// dismissSubScreen()
|
||||
// presentation =
|
||||
// ScanQrCodePayPresentation(activity = payActivity, type = 0, display = displays[1]) {
|
||||
// presentation?.dismiss()
|
||||
// }.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.totalPrice = payAmount ?: 0.0
|
||||
it.payQrCodePic = payQrCodePic
|
||||
it.initView()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,8 @@ data class MemberInfo(
|
||||
//赠送余额
|
||||
val rewardBalance: Double?,
|
||||
//积分余额
|
||||
val integralBalance: Int?
|
||||
val integralBalance: Int?,
|
||||
var member: Boolean = false
|
||||
) : Parcelable
|
||||
|
||||
data class TextBean(
|
||||
|
||||
@@ -32,7 +32,7 @@ data class UserFaceModel(
|
||||
/**
|
||||
* 是否会员
|
||||
*/
|
||||
val isMember: Boolean
|
||||
val member: Boolean
|
||||
) : Parcelable
|
||||
|
||||
@Parcelize
|
||||
|
||||
@@ -202,7 +202,7 @@ interface ApiService {
|
||||
@POST
|
||||
suspend fun memberPay(
|
||||
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/pay/app/memberPay",
|
||||
@Body param: HashMap<String, String>
|
||||
@Body param: HashMap<String, String?>
|
||||
): ApiResponse<Any?>
|
||||
|
||||
/**
|
||||
@@ -216,6 +216,16 @@ interface ApiService {
|
||||
@Query("totalFee") totalFee: String? = null
|
||||
): ApiResponse<String?>
|
||||
|
||||
/**
|
||||
* 二维码支付
|
||||
* @param orderType 0付款 1会员充值
|
||||
*/
|
||||
@POST
|
||||
suspend fun qrCodePay(
|
||||
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/pay/app/qrCodePay",
|
||||
@Body param: HashMap<String, String?>
|
||||
): ApiResponse<String?>
|
||||
|
||||
/**
|
||||
* 开餐-生成订单
|
||||
*/
|
||||
|
||||
@@ -143,6 +143,9 @@ object ObjectBox {
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
null
|
||||
} finally {
|
||||
// 线程资源清理
|
||||
boxStore?.closeThreadResources()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -177,16 +180,13 @@ object ObjectBox {
|
||||
getBox<Food>()?.all
|
||||
} ?: emptyList()
|
||||
|
||||
suspend fun filter(name: String?, isDistinct: Boolean = false) = safeDbOp {
|
||||
val list = if (isDistinct) {
|
||||
getBox<Food>()?.all?.distinctBy { it.foodName }
|
||||
suspend fun filter(name: String?) = safeDbOp {
|
||||
if (!name.isNullOrBlank()) {
|
||||
getBox<Food>()?.query(Food_.foodName.contains(name))?.build()?.find()
|
||||
?.distinctBy { it.foodName }
|
||||
} else {
|
||||
getBox<Food>()?.all
|
||||
}
|
||||
if (name.isNullOrBlank().not()) {
|
||||
list?.filter { it.foodName?.contains(name) == true }
|
||||
}
|
||||
list
|
||||
getBox<Food>()?.all?.distinctBy { it.foodName }
|
||||
} ?: emptyList()
|
||||
} ?: emptyList()
|
||||
|
||||
suspend fun removeAll() = safeDbOp {
|
||||
@@ -194,10 +194,7 @@ object ObjectBox {
|
||||
}
|
||||
|
||||
suspend fun remove(name: String) = safeDbOp {
|
||||
getBox<Food>()?.run {
|
||||
val filterIdList = all.filter { it.foodName == name }.map { it.id }
|
||||
removeByIds(filterIdList)
|
||||
}
|
||||
getBox<Food>()?.query(Food_.foodName.equal(name))?.build()?.remove()
|
||||
}
|
||||
|
||||
private val dbMutex = Mutex() // 协程并发锁,保证写入操作原子性
|
||||
@@ -207,9 +204,7 @@ object ObjectBox {
|
||||
// 检查存储是否可读写(操作数据库前调用)
|
||||
fun isStorageAvailable(context: Context): Boolean {
|
||||
return try {
|
||||
val state = Environment.getExternalStorageState()
|
||||
val innerDir = context.filesDir
|
||||
Environment.MEDIA_MOUNTED == state && innerDir.canRead() && innerDir.canWrite()
|
||||
context.filesDir.canRead() && context.filesDir.canWrite()
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -1,49 +1,49 @@
|
||||
package com.sw.dualscreen.presentation.pay
|
||||
|
||||
import android.R
|
||||
import android.app.Presentation
|
||||
import android.os.Bundle
|
||||
import android.text.Spanned
|
||||
import android.text.SpannedString
|
||||
import android.text.style.AbsoluteSizeSpan
|
||||
import android.view.Display
|
||||
import androidx.core.text.buildSpannedString
|
||||
import com.sw.dualscreen.activity.PayActivity
|
||||
import com.sw.dualscreen.databinding.PresentationCashPayBinding
|
||||
import com.sw.dualscreen.ext.load
|
||||
|
||||
class CashPayPresentation(
|
||||
val activity: PayActivity,
|
||||
display: Display,
|
||||
private val onDismissListener: () -> Unit = {}
|
||||
) : Presentation(activity, display) {
|
||||
|
||||
private lateinit var binding: PresentationCashPayBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = PresentationCashPayBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
window?.setBackgroundDrawableResource(R.color.transparent)
|
||||
initView()
|
||||
}
|
||||
|
||||
var foodName: String? = null
|
||||
var payAmount: String? = null
|
||||
private fun initView() {
|
||||
binding.tvFoodName.text = foodName
|
||||
binding.tvRealAmount.text = getAmountText(payAmount ?: "")
|
||||
}
|
||||
|
||||
private fun getAmountText(amount: String): SpannedString {
|
||||
return buildSpannedString {
|
||||
append("¥", AbsoluteSizeSpan(32, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
append(amount, AbsoluteSizeSpan(48, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDisplayRemoved() {
|
||||
super.onDisplayRemoved()
|
||||
onDismissListener()
|
||||
}
|
||||
}
|
||||
//package com.sw.dualscreen.presentation.pay
|
||||
//
|
||||
//import android.R
|
||||
//import android.app.Presentation
|
||||
//import android.os.Bundle
|
||||
//import android.text.Spanned
|
||||
//import android.text.SpannedString
|
||||
//import android.text.style.AbsoluteSizeSpan
|
||||
//import android.view.Display
|
||||
//import androidx.core.text.buildSpannedString
|
||||
//import com.sw.dualscreen.activity.PayActivity
|
||||
//import com.sw.dualscreen.databinding.PresentationCashPayBinding
|
||||
//import com.sw.dualscreen.ext.load
|
||||
//
|
||||
//class CashPayPresentation(
|
||||
// val activity: PayActivity,
|
||||
// display: Display,
|
||||
// private val onDismissListener: () -> Unit = {}
|
||||
//) : Presentation(activity, display) {
|
||||
//
|
||||
// private lateinit var binding: PresentationCashPayBinding
|
||||
//
|
||||
// override fun onCreate(savedInstanceState: Bundle?) {
|
||||
// super.onCreate(savedInstanceState)
|
||||
// binding = PresentationCashPayBinding.inflate(layoutInflater)
|
||||
// setContentView(binding.root)
|
||||
// window?.setBackgroundDrawableResource(R.color.transparent)
|
||||
// initView()
|
||||
// }
|
||||
//
|
||||
// var foodName: String? = null
|
||||
// var payAmount: String? = null
|
||||
// private fun initView() {
|
||||
// binding.tvFoodName.text = foodName
|
||||
// binding.tvRealAmount.text = getAmountText(payAmount ?: "")
|
||||
// }
|
||||
//
|
||||
// private fun getAmountText(amount: String): SpannedString {
|
||||
// return buildSpannedString {
|
||||
// append("¥", AbsoluteSizeSpan(32, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
// append(amount, AbsoluteSizeSpan(48, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// override fun onDisplayRemoved() {
|
||||
// super.onDisplayRemoved()
|
||||
// onDismissListener()
|
||||
// }
|
||||
//}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,826 @@
|
||||
package com.sw.dualscreen.presentation.pay
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
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.style.AbsoluteSizeSpan
|
||||
import android.view.Display
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewTreeObserver
|
||||
import android.widget.FrameLayout
|
||||
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.R
|
||||
import com.sw.dualscreen.activity.PayActivity
|
||||
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.SpannedUtils
|
||||
import com.sw.dualscreen.viewmodel.UserViewModel
|
||||
import com.sw.plate.utils.ToastUtils
|
||||
import com.sw.plate.utils.arcface.ConfigUtil
|
||||
import com.sw.plate.utils.arcface.ErrorCodeUtil
|
||||
import com.sw.plate.utils.arcface.FaceRectTransformer
|
||||
import com.sw.plate.utils.arcface.FaceRectView
|
||||
import com.sw.plate.utils.arcface.FaceRectView.DrawInfo
|
||||
import com.sw.plate.utils.arcface.PreviewConfig
|
||||
import com.sw.plate.utils.arcface.camera.CameraListener
|
||||
import com.sw.plate.utils.arcface.camera.DualCameraHelper
|
||||
import com.sw.plate.utils.arcface.face.constants.LivenessType
|
||||
import com.sw.plate.utils.arcface.face.model.CompareResult
|
||||
import com.sw.plate.utils.arcface.face.model.FacePreviewInfo
|
||||
import com.sw.plate.utils.arcface.face.model.RecognizeConfiguration
|
||||
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
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
|
||||
if (isExistNotPayOrder.get()) return@Observer
|
||||
lastFaceTrackId = compareResult.trackId
|
||||
val faceEntity = compareResult.faceEntity
|
||||
val userId = faceEntity.userName
|
||||
val isMember = faceEntity.userType == "1"
|
||||
activity.isMember = isMember
|
||||
if (userId.isNullOrBlank()) return@Observer
|
||||
// TODO: 测试支付用户id
|
||||
// userId = "1987710988425662466"
|
||||
isExistNotPayOrder.set(true)
|
||||
faceRecSuccess(userId, isMember)
|
||||
})
|
||||
|
||||
recognizeViewModel.drawRectInfoText.observe(activity, Observer { info ->
|
||||
Timber.tag(TAG).i("drawRectInfoText observe info = $info")
|
||||
})
|
||||
}
|
||||
|
||||
private var isExistNotPayOrder = AtomicBoolean(false)
|
||||
|
||||
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=$measuredWidth,measuredHeight=$measuredHeight")
|
||||
|
||||
val previewConfig: PreviewConfig = recognizeViewModel.previewConfig
|
||||
rgbCameraHelper = DualCameraHelper.Builder()
|
||||
.previewViewSize(Point(measuredWidth, measuredHeight))
|
||||
.rotation(activity.windowManager.defaultDisplay.rotation)
|
||||
.additionalRotation(previewConfig.rgbAdditionalDisplayOrientation) // 角度
|
||||
.previewSize(recognizeViewModel.loadPreviewSize())
|
||||
.specificCameraId(previewConfig.rgbCameraId)
|
||||
.isMirror(true)
|
||||
.previewOn(binding.dualCameraTexturePreviewRgb)
|
||||
.cameraListener(cameraListener)
|
||||
.build()
|
||||
rgbCameraHelper!!.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, isMember: Boolean) {
|
||||
activity.runOnUiThread {
|
||||
activity.showWaitingDialog("加载中,请稍后……")
|
||||
}
|
||||
activity.getMemberInfoById(userId) { memberInfo ->
|
||||
if (memberInfo == null) {
|
||||
activity.hideWaitingDialog()
|
||||
//ToastUtils.showToast("查询会员信息失败,请稍后重试")
|
||||
return@getMemberInfoById
|
||||
}
|
||||
memberInfo.member = isMember
|
||||
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 && memberInfo.member) 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()
|
||||
}
|
||||
|
||||
if (memberInfo !=null && memberInfo!!.member) {
|
||||
binding.tvPayTip.text = SpannedUtils.getAmountText(getAmountList())
|
||||
} else {
|
||||
binding.tvPayTip.text = SpannedUtils.getAmountText(getAmountListNotMember())
|
||||
}
|
||||
}
|
||||
|
||||
2 -> {
|
||||
val memberInfo = activity.memberInfo
|
||||
binding.layoutVip.run {
|
||||
if (memberInfo != null && memberInfo.member) visible() else gone()
|
||||
}
|
||||
binding.layoutPaySuccess.visible()
|
||||
binding.layoutScanQrCode.gone()
|
||||
|
||||
balance = if (memberInfo != null && memberInfo.member) (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 getAmountListNotMember(): List<TextBean> {
|
||||
val list: MutableList<TextBean> = mutableListOf()
|
||||
|
||||
list.add(TextBean(text = "应付金额 ", textSize = 30, textColor = "#FF889AC2"))
|
||||
list.add(
|
||||
TextBean(
|
||||
text = "-${totalPrice.format2String(2)}",
|
||||
textSize = 30,
|
||||
textColor = "#FF0A1428",
|
||||
isBold = true
|
||||
)
|
||||
)
|
||||
list.add(TextBean(text = " 元", textSize = 30, textColor = "#FF889AC2"))
|
||||
return list
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
+202
-202
@@ -1,202 +1,202 @@
|
||||
package com.sw.dualscreen.presentation.pay
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Presentation
|
||||
import android.os.Bundle
|
||||
import android.view.Display
|
||||
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,
|
||||
private val onDismissListener: () -> Unit = {}
|
||||
) : Presentation(activity, display) {
|
||||
|
||||
//type = 0,扫码支付默认显示二维码
|
||||
//type = 1,会员结算显示二维码,是会员则显示名字、头像、手机号、可用余额
|
||||
//type = 2,支付成功,是会员则显示名字、头像、手机号、可用余额
|
||||
|
||||
private lateinit var binding: PresentationScanQrcodePayBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = PresentationScanQrcodePayBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
window?.setBackgroundDrawableResource(android.R.color.transparent)
|
||||
initView()
|
||||
}
|
||||
|
||||
var foodName: 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
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun initView() {
|
||||
when (type) {
|
||||
0 -> {
|
||||
binding.layoutVip.gone()
|
||||
binding.layoutPaySuccess.gone()
|
||||
binding.layoutScanQrCode.visible()
|
||||
|
||||
binding.tvPayTip.text = "请扫码支付或出示付款码"
|
||||
binding.tvFoodName.text = foodName
|
||||
binding.tvRealAmount.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.tvFoodName.text = foodName
|
||||
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.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
|
||||
}
|
||||
|
||||
override fun onDisplayRemoved() {
|
||||
super.onDisplayRemoved()
|
||||
onDismissListener()
|
||||
}
|
||||
}
|
||||
//package com.sw.dualscreen.presentation.pay
|
||||
//
|
||||
//import android.annotation.SuppressLint
|
||||
//import android.app.Presentation
|
||||
//import android.os.Bundle
|
||||
//import android.view.Display
|
||||
//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,
|
||||
// private val onDismissListener: () -> Unit = {}
|
||||
//) : Presentation(activity, display) {
|
||||
//
|
||||
// //type = 0,扫码支付默认显示二维码
|
||||
// //type = 1,会员结算显示二维码,是会员则显示名字、头像、手机号、可用余额
|
||||
// //type = 2,支付成功,是会员则显示名字、头像、手机号、可用余额
|
||||
//
|
||||
// private lateinit var binding: PresentationScanQrcodePayBinding
|
||||
//
|
||||
// override fun onCreate(savedInstanceState: Bundle?) {
|
||||
// super.onCreate(savedInstanceState)
|
||||
// binding = PresentationScanQrcodePayBinding.inflate(layoutInflater)
|
||||
// setContentView(binding.root)
|
||||
// window?.setBackgroundDrawableResource(android.R.color.transparent)
|
||||
// initView()
|
||||
// }
|
||||
//
|
||||
// var foodName: 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
|
||||
//
|
||||
// @SuppressLint("SetTextI18n")
|
||||
// private fun initView() {
|
||||
// when (type) {
|
||||
// 0 -> {
|
||||
// binding.layoutVip.gone()
|
||||
// binding.layoutPaySuccess.gone()
|
||||
// binding.layoutScanQrCode.visible()
|
||||
//
|
||||
// binding.tvPayTip.text = "请扫码支付或出示付款码"
|
||||
// binding.tvFoodName.text = foodName
|
||||
// binding.tvRealAmount.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.tvFoodName.text = foodName
|
||||
// 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.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
|
||||
// }
|
||||
//
|
||||
// override fun onDisplayRemoved() {
|
||||
// super.onDisplayRemoved()
|
||||
// onDismissListener()
|
||||
// }
|
||||
//}
|
||||
@@ -76,7 +76,8 @@ class RemoteRepository constructor(
|
||||
)
|
||||
}
|
||||
}
|
||||
/**
|
||||
|
||||
/**
|
||||
* 获取人脸数据
|
||||
*/
|
||||
suspend fun getFaceIncrementList(
|
||||
@@ -134,16 +135,18 @@ class RemoteRepository constructor(
|
||||
suspend fun getRestInfoFoodsByType(
|
||||
foodName: String,
|
||||
pageNum: Int = 1,
|
||||
pageSize:Int = 50
|
||||
pageSize: Int = 50
|
||||
): ApiResponse<List<FoodInfo>> {
|
||||
return safeApiCall {
|
||||
apiService.getRestInfoFoodsByType(param = hashMapOf(
|
||||
"name" to foodName,
|
||||
"pageNum" to "$pageNum",
|
||||
"pageSize" to "$pageSize",
|
||||
//"订单来源权举:1-营养秤,2-档口
|
||||
"deviceType" to "2"
|
||||
))
|
||||
apiService.getRestInfoFoodsByType(
|
||||
param = hashMapOf(
|
||||
"name" to foodName,
|
||||
"pageNum" to "$pageNum",
|
||||
"pageSize" to "$pageSize",
|
||||
//"订单来源权举:1-营养秤,2-档口
|
||||
"deviceType" to "2"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,6 +189,26 @@ class RemoteRepository constructor(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 二维码支付
|
||||
*/
|
||||
suspend fun qrCodePay(
|
||||
authCode: String,
|
||||
orderNo: String,
|
||||
memberId: String?
|
||||
): ApiResponse<String?> {
|
||||
return safeApiCall {
|
||||
apiService.qrCodePay(
|
||||
param = hashMapOf(
|
||||
"authCode" to authCode,
|
||||
"outTradeNo" to orderNo,
|
||||
"memberId" to memberId,
|
||||
"orderType" to "0"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getDinnerType(
|
||||
// restId: String = GlobalData.restId,
|
||||
): ApiResponse<DinnerType> {
|
||||
@@ -247,7 +270,7 @@ class RemoteRepository constructor(
|
||||
/**
|
||||
* 会员支付
|
||||
*/
|
||||
suspend fun memberPay(param: HashMap<String, String>): ApiResponse<Any?> {
|
||||
suspend fun memberPay(param: HashMap<String, String?>): ApiResponse<Any?> {
|
||||
return safeApiCall {
|
||||
apiService.memberPay(param = param)
|
||||
}
|
||||
@@ -276,10 +299,12 @@ class RemoteRepository constructor(
|
||||
*/
|
||||
suspend fun getMemberInfoByPhone(phone: String, key: String): ApiResponse<MemberInfo?> {
|
||||
return safeApiCall {
|
||||
apiService.getMemberInfoByPhone(param = hashMapOf(
|
||||
"phone" to phone,
|
||||
"password" to key
|
||||
))
|
||||
apiService.getMemberInfoByPhone(
|
||||
param = hashMapOf(
|
||||
"phone" to phone,
|
||||
"password" to key
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,12 +342,14 @@ class RemoteRepository constructor(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteCollectFood(foodId:String?, version: String?): ApiResponse<Any?> {
|
||||
suspend fun deleteCollectFood(foodId: String?, version: String?): ApiResponse<Any?> {
|
||||
return safeApiCall {
|
||||
apiService.deleteCollectFood(param = mutableMapOf(
|
||||
"foodId" to foodId,
|
||||
"version" to version
|
||||
))
|
||||
apiService.deleteCollectFood(
|
||||
param = mutableMapOf(
|
||||
"foodId" to foodId,
|
||||
"version" to version
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,427 @@
|
||||
package com.sw.dualscreen.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.media.AudioAttributes;
|
||||
import android.media.AudioManager;
|
||||
import android.media.SoundPool;
|
||||
import android.os.Build;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class SoundPoolUtil {
|
||||
|
||||
private static final String TAG = SoundPoolUtil.class.getSimpleName();
|
||||
private static SoundPoolUtil mSound;
|
||||
private SoundPool mSoundPool;
|
||||
private boolean isLoadC = false;
|
||||
private Map<String, Integer> idCache;
|
||||
private List<Integer> sidCache;
|
||||
|
||||
public SoundPoolUtil() {
|
||||
idCache = new HashMap<>();
|
||||
sidCache = new ArrayList<>();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
AudioAttributes aab = new AudioAttributes.Builder()
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||
.build();
|
||||
mSoundPool = new SoundPool.Builder()
|
||||
.setMaxStreams(10)
|
||||
.setAudioAttributes(aab)
|
||||
.build();
|
||||
} else {
|
||||
mSoundPool = new SoundPool(10, AudioManager.STREAM_MUSIC, 8);
|
||||
}
|
||||
// mSoundPool = new SoundPool(60, AudioManager.USE_DEFAULT_STREAM_TYPE, 7);
|
||||
mSoundPool.setOnLoadCompleteListener(new MyOnLoadCompleteListener());
|
||||
}
|
||||
|
||||
public static SoundPoolUtil getInstance() {
|
||||
|
||||
synchronized (SoundPoolUtil.class) {
|
||||
if (mSound == null) {
|
||||
mSound = new SoundPoolUtil();
|
||||
}
|
||||
}
|
||||
return mSound;
|
||||
}
|
||||
|
||||
private int loadCompleteSize = 0;
|
||||
private LoadCompletion completionListener;
|
||||
|
||||
public void setCompletionListener(LoadCompletion listener) {
|
||||
this.completionListener = listener;
|
||||
}
|
||||
|
||||
private class MyOnLoadCompleteListener implements SoundPool.OnLoadCompleteListener {
|
||||
|
||||
@Override
|
||||
public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
|
||||
loadCompleteSize++;
|
||||
|
||||
L.e("loadSize" + loadSize + "===" + loadCompleteSize);
|
||||
if (loadSize == loadCompleteSize) {
|
||||
isLoadC = true;
|
||||
if (completionListener != null) {
|
||||
completionListener.onCompletion();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 加载指定资源
|
||||
*
|
||||
* @param name
|
||||
* @param path
|
||||
*/
|
||||
public void loadR(String name, String path) {
|
||||
if (checkSoundPool()) {
|
||||
if (!idCache.containsKey(name)) {
|
||||
idCache.put(name, mSoundPool.load(path, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int loadSize = 0;
|
||||
|
||||
/**
|
||||
* 加载指定路径列表的资源
|
||||
*
|
||||
* @param map
|
||||
*/
|
||||
public void loadR(Map<String, String> map) {
|
||||
loadSize = map.size();
|
||||
Set<Map.Entry<String, String>> entries = map.entrySet();
|
||||
for (Map.Entry<String, String> entry : entries) {
|
||||
String key = entry.getKey();
|
||||
if (checkSoundPool()) {
|
||||
if (!idCache.containsKey(key)) {
|
||||
idCache.put(key, mSoundPool.load(entry.getValue(), 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载指定AssetFileDescriptor的资源
|
||||
*
|
||||
* @param name
|
||||
* @param afd
|
||||
*/
|
||||
public void loadRF(String name, AssetFileDescriptor afd) {
|
||||
if (checkSoundPool()) {
|
||||
if (!idCache.containsKey(name)) {
|
||||
idCache.put(name, mSoundPool.load(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength(), 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载指定AssetFileDescriptor列表的资源
|
||||
*
|
||||
* @param map
|
||||
*/
|
||||
public void loadRF(Map<String, AssetFileDescriptor> map) {
|
||||
Set<Map.Entry<String, AssetFileDescriptor>> entries = map.entrySet();
|
||||
for (Map.Entry<String, AssetFileDescriptor> entry : entries) {
|
||||
String key = entry.getKey();
|
||||
if (checkSoundPool()) {
|
||||
if (!idCache.containsKey(key)) {
|
||||
idCache.put(key, mSoundPool.load(entry.getValue().getFileDescriptor(), entry.getValue().getStartOffset(), entry.getValue().getLength(), 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载指定列表资源
|
||||
*
|
||||
* @param context
|
||||
* @param map
|
||||
*/
|
||||
public void loadR(Context context, Map<String, Integer> map) {
|
||||
loadSize = map.size();
|
||||
Set<Map.Entry<String, Integer>> entries = map.entrySet();
|
||||
for (Map.Entry<String, Integer> entry : entries) {
|
||||
String key = entry.getKey();
|
||||
if (checkSoundPool()) {
|
||||
|
||||
if (!idCache.containsKey(key)) {
|
||||
idCache.put(key, mSoundPool.load(context, entry.getValue(), 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载单个音频
|
||||
*
|
||||
* @param context
|
||||
* @param name
|
||||
* @param res
|
||||
*/
|
||||
public void loadR(Context context, String name, int res) {
|
||||
if (checkSoundPool()) {
|
||||
if (!idCache.containsKey(name)) {
|
||||
idCache.put(name, mSoundPool.load(context, res, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放指定音频,并返用于停止、暂停、恢复的StreamId
|
||||
*
|
||||
* @param name
|
||||
* @param times
|
||||
* @return
|
||||
*/
|
||||
public int play(String name, int times) {
|
||||
L.e(String.format("play %s times=%s", name, times));
|
||||
return this.play(name, 1, 1, 1, times, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放指定音频,并指定播放次数和频率
|
||||
*
|
||||
* @param name
|
||||
* @param times
|
||||
* @param rate
|
||||
* @return
|
||||
*/
|
||||
public int play(String name, int times, int rate) {
|
||||
return this.play(name, 1, 1, 1, times, rate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放指定音频,并指定优先级和播放频率
|
||||
*
|
||||
* @param name
|
||||
* @param property
|
||||
* @param times
|
||||
* @param rate
|
||||
* @return
|
||||
*/
|
||||
public int play(String name, int property, int times, int rate) {
|
||||
return this.play(name, 1, 1, property, times, rate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放指定音频,并指定左右声道、优先级、播放次数、播放频率
|
||||
*
|
||||
* @param name
|
||||
* @param leftVolume
|
||||
* @param rightVolume
|
||||
* @param property
|
||||
* @param times
|
||||
* @param rate
|
||||
* @return
|
||||
*/
|
||||
public int play(String name, float leftVolume, float rightVolume, int property, int times, int rate) {
|
||||
int streamId = -1;
|
||||
if (checkSoundPool()) {
|
||||
L.d(TAG, "play: " + name);
|
||||
if (idCache.containsKey(name) && isLoadC) {
|
||||
L.d(TAG, "name:" + idCache.get(name));
|
||||
streamId = mSoundPool.play(idCache.get(name), leftVolume, rightVolume, property, times, rate);
|
||||
L.d(TAG, "streadmId:" + streamId);
|
||||
sidCache.add(streamId);
|
||||
}
|
||||
}
|
||||
return streamId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放指定列表的音频,并返回并返用于停止、暂停、恢复的StreamId列表
|
||||
*
|
||||
* @param names
|
||||
* @param times
|
||||
* @return
|
||||
*/
|
||||
public List<Integer> play(List<String> names, int times) {
|
||||
|
||||
return this.play(names, 1, 1, 1, times, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放指定列表的音频,并返回并返用于停止、暂停、恢复的StreamId列表,指定次数和频率
|
||||
*
|
||||
* @param names
|
||||
* @param times
|
||||
* @param rate
|
||||
* @return
|
||||
*/
|
||||
public List<Integer> play(List<String> names, int times, int rate) {
|
||||
|
||||
return this.play(names, 1, 1, 1, times, rate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放指定列表的音频,并返回并返用于停止、暂停、恢复的StreamId列表,指定所有参数
|
||||
*
|
||||
* @param names
|
||||
* @param leftVolume
|
||||
* @param rightVolume
|
||||
* @param property
|
||||
* @param times
|
||||
* @param rate
|
||||
* @return
|
||||
*/
|
||||
public List<Integer> play(List<String> names, int leftVolume, int rightVolume, int property, int times, int rate) {
|
||||
List<Integer> streamIds = new ArrayList<>();
|
||||
if (checkSoundPool()) {
|
||||
for (String name : names) {
|
||||
if (idCache.containsKey(name) && isLoadC) {
|
||||
int a = mSoundPool.play(idCache.get(name), leftVolume, rightVolume, property, times, rate);
|
||||
streamIds.add(a);
|
||||
sidCache.add(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
return streamIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止指定id音频
|
||||
*/
|
||||
public void stop(int r) {
|
||||
if (checkSoundPool()) {
|
||||
mSoundPool.stop(r);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止指定列表音频
|
||||
*/
|
||||
public void stopAll() {
|
||||
if (checkSoundPool()) {
|
||||
for (int r : sidCache) {
|
||||
mSoundPool.stop(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停指定音效
|
||||
*
|
||||
* @param r
|
||||
*/
|
||||
public void pause(int r) {
|
||||
if (checkSoundPool()) {
|
||||
mSoundPool.pause(r);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停指定列表音频
|
||||
*
|
||||
* @param list
|
||||
*/
|
||||
public void pause(List<Integer> list) {
|
||||
if (checkSoundPool()) {
|
||||
for (int r : list) {
|
||||
mSoundPool.pause(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停所有音效
|
||||
*/
|
||||
public void pauseAll() {
|
||||
mSoundPool.autoPause();
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复指定音频播放
|
||||
*
|
||||
* @param r
|
||||
*/
|
||||
public void resume(int r) {
|
||||
if (checkSoundPool()) {
|
||||
mSoundPool.resume(r);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复指定列表的音频
|
||||
*
|
||||
* @param list
|
||||
*/
|
||||
public void resume(List<Integer> list) {
|
||||
if (checkSoundPool()) {
|
||||
for (int r : list) {
|
||||
mSoundPool.resume(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复所有暂停的音频
|
||||
*/
|
||||
public void resumeAll() {
|
||||
if (checkSoundPool()) {
|
||||
mSoundPool.autoResume();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 卸载指定音频
|
||||
*
|
||||
* @param name
|
||||
*/
|
||||
public void unLoad(String name) {
|
||||
if (checkSoundPool()) {
|
||||
if (idCache.containsKey(name)) {
|
||||
mSoundPool.unload(idCache.get(name));
|
||||
idCache.remove(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 卸载指定列表的音频
|
||||
*
|
||||
* @param names
|
||||
*/
|
||||
public void unLoad(List<String> names) {
|
||||
if (checkSoundPool()) {
|
||||
for (String name : names) {
|
||||
if (idCache.containsKey(name)) {
|
||||
mSoundPool.unload(idCache.get(name));
|
||||
idCache.remove(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 释放所有资源,如果想继续播放,需要重新加载资源
|
||||
*/
|
||||
public void release() {
|
||||
if (checkSoundPool()) {
|
||||
mSound = null;
|
||||
mSoundPool.release();
|
||||
idCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkSoundPool() {
|
||||
if (mSoundPool != null) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public interface LoadCompletion {
|
||||
void onCompletion();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.sw.dualscreen.utils;
|
||||
|
||||
import com.sw.dualscreen.MyApp;
|
||||
|
||||
public class SpTool {
|
||||
|
||||
public static final String LAST_FACE_TIMESTAMP = "faceTimestamp";
|
||||
|
||||
public static long getLastFaceTimestamp() {
|
||||
return SPUtil.Companion.getInstance(MyApp.getContext(), "default_sp").get(LAST_FACE_TIMESTAMP, 0L);
|
||||
}
|
||||
|
||||
public static void setLastFaceTimestamp(long timestamp) {
|
||||
SPUtil.Companion.getInstance(MyApp.getContext(), "default_sp").put(LAST_FACE_TIMESTAMP, timestamp);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.sw.dualscreen.utils
|
||||
|
||||
import com.sw.dualscreen.MyApp
|
||||
import com.sw.dualscreen.utils.SPUtil.Companion.getInstance
|
||||
|
||||
object SpTool {
|
||||
const val LAST_FACE_TIMESTAMP: String = "faceTimestamp"
|
||||
|
||||
const val IS_FIRST_GET_FACE = "isFirstGetFace"
|
||||
|
||||
private var spUtil: SPUtil? = null
|
||||
|
||||
init {
|
||||
spUtil = getInstance(MyApp.instance!!, "default_sp")
|
||||
}
|
||||
|
||||
|
||||
var lastFaceTimestamp: Long
|
||||
get() = spUtil?.get(LAST_FACE_TIMESTAMP, 0L)?:0L
|
||||
set(timestamp) {
|
||||
spUtil?.put(LAST_FACE_TIMESTAMP, timestamp)
|
||||
}
|
||||
|
||||
var firstGetFace: Boolean
|
||||
get() = spUtil?.get(IS_FIRST_GET_FACE, true)?:true
|
||||
set(value) {
|
||||
spUtil?.boolean(IS_FIRST_GET_FACE, value)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,7 +21,7 @@ class HollowOvalInRectView @JvmOverloads constructor(
|
||||
private val rectWidthDp = 400f // 矩形宽度
|
||||
private val rectHeightDp = 510f // 矩形高度
|
||||
private val ovalWidthDp = 400f // 椭圆宽度
|
||||
private val ovalHeightDp = 436f // 椭圆高度
|
||||
private val ovalHeightDp = 416f // 椭圆高度
|
||||
|
||||
// 转换后的像素尺寸
|
||||
private val rectWidth: Float
|
||||
|
||||
@@ -65,7 +65,9 @@ abstract class BaseViewModel() : ViewModel() {
|
||||
}
|
||||
val message = response.msg
|
||||
Timber.d("msg = ${message}, code = $code")
|
||||
ToastUtils.showToast("${message}(${code})")
|
||||
if (message!=null && !message.startsWith("未知错误")) {
|
||||
ToastUtils.showToast("${message}(${code})")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ 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.DeviceConfig
|
||||
import com.sw.dualscreen.model.response.DinnerType
|
||||
import com.sw.dualscreen.model.response.FoodInfo
|
||||
@@ -95,7 +96,7 @@ class UserViewModel : BaseViewModel() {
|
||||
launch {
|
||||
_loadFaceResult.value = false
|
||||
//获取全量数据时,时间戳改为0
|
||||
SpTool.setLastFaceTimestamp(0)
|
||||
SpTool.lastFaceTimestamp = 0
|
||||
val response = repository.getUserFaceCache(currentPageNo)
|
||||
if (parseResponse(response)) {
|
||||
// 获取成功一次后缓存状态
|
||||
@@ -103,13 +104,16 @@ class UserViewModel : BaseViewModel() {
|
||||
withContext(Dispatchers.Default) {
|
||||
val list: List<UserFaceModel> = response.data ?: emptyList()
|
||||
if (pageNo == 1 && list.isEmpty()) {
|
||||
ToastUtils.showToast("查询人脸数据为空")
|
||||
//ToastUtils.showToast("查询人脸数据为空")
|
||||
return@withContext
|
||||
}
|
||||
// val item = list.firstOrNull { it.userId == "1951105919342936066" }
|
||||
// Timber.tag(TAG).d("getUserFaceCache userId = ${item?.userId}")
|
||||
//val item = list.map { it.member }
|
||||
//Timber.tag(TAG).d("getUserFaceCache item = $item")
|
||||
val faceEntity = list.map {
|
||||
FaceEntity(it.userId, null, Base64.decode(it.faceFeatureStr))
|
||||
FaceEntity(it.userId, null, Base64.decode(it.faceFeatureStr)).also { item ->
|
||||
item.userType = if (it.member) "1" else "2"
|
||||
item.cardNo = it.cardNo
|
||||
}
|
||||
}
|
||||
faceApi.updateFaceData(currentPageNo, faceEntity)
|
||||
if (list.size >= pageSize) {
|
||||
@@ -123,7 +127,7 @@ class UserViewModel : BaseViewModel() {
|
||||
//非空最后一条数据的是时间戳,空使用上次list最后一条时间戳
|
||||
lastFaceTimestamp = list.last().faceUpdateTimestamp ?: 0L
|
||||
}
|
||||
SpTool.setLastFaceTimestamp(lastFaceTimestamp)
|
||||
SpTool.lastFaceTimestamp = lastFaceTimestamp
|
||||
_loadFaceResult.value = true
|
||||
}
|
||||
|
||||
@@ -201,7 +205,7 @@ class UserViewModel : BaseViewModel() {
|
||||
//非空实现最后一条数据的是时间戳,空使用上次list最后一条时间戳
|
||||
lastFaceTimestamp = list.last().faceUpdateTimestamp ?: 0L
|
||||
}
|
||||
SpTool.setLastFaceTimestamp(lastFaceTimestamp)
|
||||
SpTool.lastFaceTimestamp = lastFaceTimestamp
|
||||
onAllQueryFinished()
|
||||
}
|
||||
}
|
||||
@@ -356,6 +360,25 @@ class UserViewModel : BaseViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 二维码支付
|
||||
*/
|
||||
fun qrCodePay(
|
||||
authCode: String,
|
||||
orderNo: String,
|
||||
memberId: String?,
|
||||
block: (Boolean, String?) -> Unit
|
||||
) {
|
||||
launch {
|
||||
val response = repository.qrCodePay(authCode, orderNo, memberId)
|
||||
if (parseResponse(response)) {
|
||||
block(true, response.data)
|
||||
} else {
|
||||
block(false, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getDinnerType() {
|
||||
Timber.tag(TAG).d("getDinnerType")
|
||||
_dinnerTypeInfo.value = null
|
||||
@@ -474,7 +497,7 @@ class UserViewModel : BaseViewModel() {
|
||||
/**
|
||||
* 会员支付
|
||||
*/
|
||||
fun memberPay(param: HashMap<String, String>, block: (Boolean) -> Unit) {
|
||||
fun memberPay(param: HashMap<String, String?>, block: (Boolean) -> Unit) {
|
||||
Timber.tag(TAG).d("memberPay")
|
||||
launchWithLoading {
|
||||
val response = repository.memberPay(param)
|
||||
@@ -571,19 +594,22 @@ class UserViewModel : BaseViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun getCollectedFoodVector(param: MutableMap<String, String>, block: (List<FoodVector>?) -> Unit) {
|
||||
fun getCollectedFoodVector(
|
||||
param: MutableMap<String, String>,
|
||||
block: (List<FoodVector>?) -> Unit
|
||||
) {
|
||||
Timber.tag(TAG).d("getCollectedFoodVector")
|
||||
launchWithLoading {
|
||||
val response = repository.getCollectedFoodVector(param)
|
||||
if (parseResponse(response)) {
|
||||
block(response.data?:emptyList())
|
||||
block(response.data ?: emptyList())
|
||||
} else {
|
||||
block(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteCollectFood(foodId:String?, version: String?, block: (Boolean) -> Unit) {
|
||||
fun deleteCollectFood(foodId: String?, version: String?, block: (Boolean) -> Unit) {
|
||||
Timber.tag(TAG).d("deleteCollectFood")
|
||||
launchWithLoading {
|
||||
val response = repository.deleteCollectFood(foodId, version)
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<!-- 眼睛轮廓 -->
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:strokeColor="@android:color/black"
|
||||
android:strokeWidth="1.5"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"
|
||||
android:pathData="M 2 12 C 2 12 5 6 12 6 C 19 6 22 12 22 12 C 22 12 19 18 12 18 C 5 18 2 12 2 12 Z" />
|
||||
<!-- 瞳孔(path绘制圆形) -->
|
||||
<path
|
||||
android:fillColor="@android:color/black"
|
||||
android:pathData="M 12 9 C 13.66 9 15 10.34 15 12 C 15 13.66 13.66 15 12 15 C 10.34 15 9 13.66 9 12 C 9 10.34 10.34 9 12 9 Z" />
|
||||
<!-- 斜线表示隐藏 -->
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:strokeColor="@android:color/black"
|
||||
android:strokeWidth="1.5"
|
||||
android:strokeLineCap="round"
|
||||
android:pathData="M 4 4 L 20 20" />
|
||||
</vector>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<!-- 眼睛轮廓 -->
|
||||
<path
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:strokeColor="@android:color/black"
|
||||
android:strokeWidth="1.5"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"
|
||||
android:pathData="M 2 12 C 2 12 5 6 12 6 C 19 6 22 12 22 12 C 22 12 19 18 12 18 C 5 18 2 12 2 12 Z" />
|
||||
<!-- 瞳孔(path绘制圆形) -->
|
||||
<path
|
||||
android:fillColor="@android:color/black"
|
||||
android:pathData="M 12 9 C 13.66 9 15 10.34 15 12 C 15 13.66 13.66 15 12 15 C 10.34 15 9 13.66 9 12 C 9 10.34 10.34 9 12 9 Z" />
|
||||
</vector>
|
||||
@@ -11,16 +11,14 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="32dp"
|
||||
android:background="@drawable/setting_border"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvChargeSetting"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="35dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:drawablePadding="17dp"
|
||||
android:text="餐品采集"
|
||||
android:textColor="@color/black"
|
||||
@@ -34,7 +32,6 @@
|
||||
android:id="@+id/btnCollectedFood"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:gravity="center_vertical"
|
||||
android:includeFontPadding="false"
|
||||
@@ -51,7 +48,7 @@
|
||||
android:id="@+id/flPreview"
|
||||
android:layout_width="456dp"
|
||||
android:layout_height="342dp"
|
||||
android:layout_marginTop="41dp"
|
||||
android:layout_marginTop="30dp"
|
||||
app:layout_constraintStart_toStartOf="@id/tvChargeSetting"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvChargeSetting">
|
||||
|
||||
|
||||
@@ -34,24 +34,41 @@
|
||||
android:textStyle="bold"
|
||||
tools:ignore="Autofill,HardcodedText,LabelFor,TextFields" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etInputNumber"
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp"
|
||||
android:layout_marginHorizontal="64dp"
|
||||
android:inputType="textVisiblePassword"
|
||||
android:background="@drawable/setting_border_gray3"
|
||||
android:hint="输入消费码"
|
||||
android:textColorHint="#ffc4cfda"
|
||||
android:textColor="#ff0a1428"
|
||||
android:layout_marginTop="32dp"
|
||||
android:textSize="36sp"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:text=""
|
||||
android:paddingHorizontal="50dp"
|
||||
android:textStyle="bold"
|
||||
tools:ignore="Autofill,HardcodedText,LabelFor,TextFields" />
|
||||
android:layout_marginTop="32dp">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etInputNumber"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:inputType="textPassword"
|
||||
android:background="@drawable/setting_border_gray3"
|
||||
android:hint="输入消费码"
|
||||
android:textColorHint="#ffc4cfda"
|
||||
android:textColor="#ff0a1428"
|
||||
android:textSize="36sp"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:text=""
|
||||
android:paddingHorizontal="50dp"
|
||||
android:textStyle="bold"
|
||||
tools:ignore="Autofill,HardcodedText,LabelFor,TextFields" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivTogglePassword"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:paddingEnd="16dp"
|
||||
android:contentDescription="@string/app_name"
|
||||
android:src="@drawable/ic_eye_close"
|
||||
android:scaleType="centerInside"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatButton
|
||||
android:id="@+id/btnConfirm"
|
||||
|
||||
@@ -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>
|
||||
Binary file not shown.
@@ -13,7 +13,7 @@ material = "1.10.0"
|
||||
activity = "1.8.0"
|
||||
constraintlayout = "2.1.4"
|
||||
accompanistPermissions = "0.37.3"
|
||||
cameraCore = "1.3.0"
|
||||
cameraCore = "1.4.1"
|
||||
retrofit = "3.0.0"
|
||||
okhttp = "4.12.0"
|
||||
timber = "5.0.1"
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
#Mon Jul 14 11:28:37 CST 2025
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.11.1-all.zip
|
||||
#distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
||||
#distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.11.1-all.zip
|
||||
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.14.3-all.zip
|
||||
#distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.14.3-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -5,7 +5,10 @@ import static android.content.Context.INPUT_SERVICE;
|
||||
import android.content.Context;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.TextUtils;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyCharacterMap;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -14,172 +17,185 @@ import java.util.List;
|
||||
/**
|
||||
* 扫描枪事件处理
|
||||
*/
|
||||
|
||||
public class ScanGunKeyEventHelper {
|
||||
private final static long MESSAGE_DELAY = 500; //延迟500ms,判断扫码是否完成。
|
||||
private final StringBuffer mStringBufferResult; //扫码内容
|
||||
private boolean mCaps; //大小写区分
|
||||
private final Handler mHandler;
|
||||
private final Runnable mScanningFishedRunnable;
|
||||
private OnScanSuccessListener mOnScanSuccessListener;
|
||||
private final Context mContext;
|
||||
|
||||
// private String mDeviceName = "TMC HIDKeyBoard";
|
||||
//private String mDeviceName = "Linux 3.4.35 with ak-hsudc Composite Gadget (ACM + HID)";
|
||||
//private String mDeviceName1 = "USBKey Chip USBKey Module";
|
||||
private static final long SCAN_DELAY = 200; // 扫码结束判断时间
|
||||
|
||||
private String[] deviceNameArray = new String[]{
|
||||
"CROWN CROWN Barcode KeyBorad FS",
|
||||
"Linux 3.4.35 with ak-hsudc Composite Gadget (ACM + HID)",
|
||||
"USBKey Chip USBKey Module"
|
||||
};
|
||||
private long lastTime = 0L;
|
||||
private final StringBuilder resultBuffer = new StringBuilder();
|
||||
private boolean caps;
|
||||
|
||||
public ScanGunKeyEventHelper(Context context, OnScanSuccessListener onScanSuccessListener) {
|
||||
mContext = context;
|
||||
mOnScanSuccessListener = onScanSuccessListener;
|
||||
mStringBufferResult = new StringBuffer();
|
||||
mHandler = new Handler();
|
||||
mScanningFishedRunnable = this::performScanSuccess;
|
||||
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||
private final Runnable scanFinishRunnable;
|
||||
|
||||
private OnScanSuccessListener listener;
|
||||
|
||||
private long lastScanTime = 0;
|
||||
|
||||
public ScanGunKeyEventHelper(OnScanSuccessListener listener) {
|
||||
this.listener = listener;
|
||||
scanFinishRunnable = this::handleScanFinish;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回扫码成功后的结果
|
||||
* 扫码结束
|
||||
*/
|
||||
private void performScanSuccess() {
|
||||
String barcode = mStringBufferResult.toString();
|
||||
L.d("performScanSuccess barcode = " + barcode);
|
||||
long currentTime = System.currentTimeMillis();
|
||||
if (currentTime - lastTime < 5 * 1000) {
|
||||
L.d("performScanSuccess 操作太频繁");
|
||||
mStringBufferResult.setLength(0);
|
||||
private void handleScanFinish() {
|
||||
|
||||
String barcode = resultBuffer.toString();
|
||||
|
||||
if (barcode.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
lastTime = currentTime;
|
||||
if (mOnScanSuccessListener != null && !TextUtils.isEmpty(barcode))
|
||||
mOnScanSuccessListener.onScanSuccess(barcode);
|
||||
mStringBufferResult.setLength(0);
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
// 防止重复扫描
|
||||
if (now - lastScanTime < 1000) {
|
||||
resultBuffer.setLength(0);
|
||||
return;
|
||||
}
|
||||
|
||||
lastScanTime = now;
|
||||
|
||||
if (listener != null) {
|
||||
listener.onScanSuccess(barcode);
|
||||
}
|
||||
|
||||
resultBuffer.setLength(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫码枪事件解析
|
||||
*
|
||||
* @param event
|
||||
* 解析 KeyEvent
|
||||
*/
|
||||
public void analysisKeyEvent(KeyEvent event) {
|
||||
int keyCode = event.getKeyCode();
|
||||
//字母大小写判断
|
||||
checkLetterStatus(event);
|
||||
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||
char aChar = getInputCode(event);
|
||||
if (aChar != 0) {
|
||||
mStringBufferResult.append(aChar);
|
||||
}
|
||||
if (keyCode == KeyEvent.KEYCODE_ENTER) {
|
||||
//若为回车键,直接返回
|
||||
mHandler.removeCallbacks(mScanningFishedRunnable);
|
||||
mHandler.post(mScanningFishedRunnable);
|
||||
} else {
|
||||
//延迟post,若500ms内,有其他事件
|
||||
mHandler.removeCallbacks(mScanningFishedRunnable);
|
||||
mHandler.postDelayed(mScanningFishedRunnable, MESSAGE_DELAY);
|
||||
}
|
||||
|
||||
if (event.getAction() != KeyEvent.ACTION_DOWN) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkCaps(event);
|
||||
|
||||
int keyCode = event.getKeyCode();
|
||||
|
||||
if (keyCode == KeyEvent.KEYCODE_ENTER) {
|
||||
|
||||
handler.removeCallbacks(scanFinishRunnable);
|
||||
handler.post(scanFinishRunnable);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
char c = getInputCode(event);
|
||||
|
||||
if (c != 0) {
|
||||
resultBuffer.append(c);
|
||||
}
|
||||
|
||||
handler.removeCallbacks(scanFinishRunnable);
|
||||
handler.postDelayed(scanFinishRunnable, SCAN_DELAY);
|
||||
}
|
||||
|
||||
//检查shift键
|
||||
private void checkLetterStatus(KeyEvent event) {
|
||||
/**
|
||||
* 判断是否为扫码枪
|
||||
*/
|
||||
public boolean isScanGunEvent(KeyEvent event) {
|
||||
|
||||
if (event == null) return false;
|
||||
|
||||
InputDevice device = event.getDevice();
|
||||
|
||||
if (device == null) return false;
|
||||
|
||||
int sources = device.getSources();
|
||||
|
||||
// HID keyboard
|
||||
boolean isKeyboard =
|
||||
(sources & InputDevice.SOURCE_KEYBOARD) == InputDevice.SOURCE_KEYBOARD;
|
||||
|
||||
// 排除虚拟键盘
|
||||
boolean notVirtual =
|
||||
event.getDeviceId() != KeyCharacterMap.VIRTUAL_KEYBOARD;
|
||||
|
||||
return isKeyboard && notVirtual;
|
||||
}
|
||||
|
||||
/**
|
||||
* SHIFT 状态
|
||||
*/
|
||||
private void checkCaps(KeyEvent event) {
|
||||
|
||||
int keyCode = event.getKeyCode();
|
||||
if (keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT || keyCode == KeyEvent.KEYCODE_SHIFT_LEFT) {
|
||||
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||
//按着shift键,表示大写
|
||||
mCaps = true;
|
||||
} else {
|
||||
//松开shift键,表示小写
|
||||
mCaps = false;
|
||||
}
|
||||
|
||||
if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
|
||||
|| keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
|
||||
|
||||
caps = event.getAction() == KeyEvent.ACTION_DOWN;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取扫描内容
|
||||
*
|
||||
* @param event
|
||||
* @return
|
||||
* KeyEvent 转字符
|
||||
*/
|
||||
private char getInputCode(KeyEvent event) {
|
||||
|
||||
int keyCode = event.getKeyCode();
|
||||
char aChar;
|
||||
|
||||
if (keyCode >= KeyEvent.KEYCODE_A && keyCode <= KeyEvent.KEYCODE_Z) {
|
||||
//字母
|
||||
aChar = (char) ((mCaps ? 'A' : 'a') + keyCode - KeyEvent.KEYCODE_A);
|
||||
|
||||
return (char) ((caps ? 'A' : 'a') + keyCode - KeyEvent.KEYCODE_A);
|
||||
|
||||
} else if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
|
||||
//数字
|
||||
aChar = (char) ('0' + keyCode - KeyEvent.KEYCODE_0);
|
||||
} else {
|
||||
//其他符号
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_PERIOD:
|
||||
aChar = '.';
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MINUS:
|
||||
aChar = mCaps ? '_' : '-';
|
||||
break;
|
||||
case KeyEvent.KEYCODE_SLASH:
|
||||
aChar = '/';
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BACKSLASH:
|
||||
aChar = mCaps ? '|' : '\\';
|
||||
break;
|
||||
default:
|
||||
aChar = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return (char) ('0' + keyCode - KeyEvent.KEYCODE_0);
|
||||
}
|
||||
return aChar;
|
||||
|
||||
switch (keyCode) {
|
||||
|
||||
case KeyEvent.KEYCODE_MINUS:
|
||||
return caps ? '_' : '-';
|
||||
|
||||
case KeyEvent.KEYCODE_EQUALS:
|
||||
return caps ? '+' : '=';
|
||||
|
||||
case KeyEvent.KEYCODE_LEFT_BRACKET:
|
||||
return caps ? '{' : '[';
|
||||
|
||||
case KeyEvent.KEYCODE_RIGHT_BRACKET:
|
||||
return caps ? '}' : ']';
|
||||
|
||||
case KeyEvent.KEYCODE_BACKSLASH:
|
||||
return caps ? '|' : '\\';
|
||||
|
||||
case KeyEvent.KEYCODE_SEMICOLON:
|
||||
return caps ? ':' : ';';
|
||||
|
||||
case KeyEvent.KEYCODE_APOSTROPHE:
|
||||
return caps ? '"' : '\'';
|
||||
|
||||
case KeyEvent.KEYCODE_COMMA:
|
||||
return caps ? '<' : ',';
|
||||
|
||||
case KeyEvent.KEYCODE_PERIOD:
|
||||
return caps ? '>' : '.';
|
||||
|
||||
case KeyEvent.KEYCODE_SLASH:
|
||||
return caps ? '?' : '/';
|
||||
|
||||
case KeyEvent.KEYCODE_SPACE:
|
||||
return ' ';
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void release() {
|
||||
handler.removeCallbacks(scanFinishRunnable);
|
||||
listener = null;
|
||||
}
|
||||
|
||||
public interface OnScanSuccessListener {
|
||||
void onScanSuccess(String barcode);
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
mHandler.removeCallbacks(mScanningFishedRunnable);
|
||||
mOnScanSuccessListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 输入设备是否存在
|
||||
*
|
||||
* @param deviceName
|
||||
* @return
|
||||
*/
|
||||
public boolean isInputDeviceExist(String deviceName) {
|
||||
|
||||
InputManager inputManager = (InputManager) mContext.getSystemService(INPUT_SERVICE);
|
||||
int[] deviceIds = inputManager.getInputDeviceIds();
|
||||
for (int id : deviceIds) {
|
||||
if (inputManager.getInputDevice(id).getName().equals(deviceName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为扫码枪事件(部分机型KeyEvent获取的名字错误)
|
||||
*
|
||||
* @param event
|
||||
* @return
|
||||
*/
|
||||
public boolean isScanGunEvent(KeyEvent event) {
|
||||
if (event == null || event.getDevice() == null) return false;
|
||||
String deviceName = event.getDevice().getName();
|
||||
L.e("event===" + deviceName +
|
||||
"===Char===" + event.getCharacters() +
|
||||
"===Action===" + event.getAction());
|
||||
//return deviceName.equals(mDeviceName) || deviceName.equals(mDeviceName1);
|
||||
List<String> devices = Arrays.asList(deviceNameArray);
|
||||
return devices.contains(deviceName);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user