Author SHA1 Message Date
lvmeng a6d2441704 优化:修复眼睛图标显示效果 2026-03-18 16:14:47 +08:00
lvmeng e7d11530a6 优化:修改消费码输入框为密码模式,添加显示/隐藏密码切换功能 2026-03-18 15:55:06 +08:00
lvmeng c56769769b 优化:修改消费码输入框为密��模式,添加显示/隐藏密码切换功能 2026-03-18 15:42:50 +08:00
lvmeng 8673cec545 优化:修改人脸识别任务延迟参数和添加相机权限检查 2026-03-18 14:06:42 +08:00
lvmeng 8ec7ea6a79 优化 2026-03-13 15:42:37 +08:00
lvmeng a80e3d1ba6 ObjectBox数据库操作增加线程资源清理 2026-03-12 17:25:36 +08:00
mazengfei 6d825d7235 扫码支付增加会员id字段 2026-03-12 12:39:35 +08:00
lvmeng 4f1860eb37 存在待支付订单时停止识别 2026-03-12 11:43:20 +08:00
mazengfei 45a7bf8369 优化非支付页面扫码点击问题 2026-03-11 18:34:49 +08:00
lvmengandClaude Sonnet 4.6 f0b50ef82f 修复MainActivity中扫码枪按键事件触发页面按钮点击问题
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 14:54:52 +08:00
lvmeng 824dc5f867 优化扫码支付结果会员非会员显示 2026-03-11 13:55:29 +08:00
lvmengandClaude Sonnet 4.6 7232735073 修复MainActivity停止时CameraX NPE崩溃
1. 升级CameraX版本 1.3.0 → 1.4.1,从根本修复RequestWithCallback.abort() NPE
2. 跳转PayActivity前调用shutdownCamera(),防止in-flight拍照请求触发生命周期异常
3. onResume()顶部增加相机恢复逻辑,通过cameraProviderFuture==null判断是否需要重新初始化
4. shutdownCamera()中将cameraProviderFuture置null,作为相机已停止的可靠标志

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 19:05:28 +08:00
mazengfei 8a1630f2a4 优化扫码枪支付 2026-03-10 14:44:34 +08:00
lvmeng 39c0ca7d72 优化人脸识别区域效果 2026-03-10 11:10:23 +08:00
lvmeng dfde8c0fad 人脸全量接口只首次请求 2026-03-10 10:53:18 +08:00
lvmeng aca84f1e94 Rename .java to .kt 2026-03-10 10:53:18 +08:00
lvmeng 7bcb74d9e0 增加支付成功语音提示;增加扫码支付; 2026-03-10 09:41:34 +08:00
mazengfei 6c857dd1f7 优化 2026-03-09 21:59:04 +08:00
mazengfei fd0f9977b1 使用人脸接口中的member字段判断是否会员;逻辑优化; 2026-03-09 21:26:02 +08:00
lvmeng ec4a5801c2 支付页面统一副屏逻辑 2026-03-09 19:56:09 +08:00
48 changed files with 3476 additions and 1245 deletions
Generated
+1
View File
@@ -0,0 +1 @@
DualScreen
+2 -2
View File
@@ -4,10 +4,10 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <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"> <Target type="DEFAULT_BOOT">
<handle> <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> </handle>
</Target> </Target>
</DropdownSelection> </DropdownSelection>
+13
View File
@@ -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
View File
@@ -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>
+10
View File
@@ -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>
+4
View File
@@ -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
+21
View File
@@ -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.
+77
View File
@@ -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
}
+37
View File
@@ -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
}
+2 -1
View File
@@ -65,7 +65,8 @@
<activity android:name="com.sw.dualscreen.activity.CollectedFoodActivity" <activity android:name="com.sw.dualscreen.activity.CollectedFoodActivity"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" /> android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
<activity android:name="com.sw.dualscreen.activity.PayActivity" <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 <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
@@ -59,7 +59,7 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
} }
override fun onDestroy() { override fun onDestroy() {
keyEventHelper?.onDestroy() keyEventHelper?.release()
EventBus.getDefault().unregister(this) EventBus.getDefault().unregister(this)
super.onDestroy() super.onDestroy()
} }
@@ -153,11 +153,11 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
*/ */
fun registerKeyEvent() { fun registerKeyEvent() {
keyEventHelper = keyEventHelper =
ScanGunKeyEventHelper(context, object : ScanGunKeyEventHelper.OnScanSuccessListener { ScanGunKeyEventHelper( object : ScanGunKeyEventHelper.OnScanSuccessListener {
override fun onScanSuccess(barcode: String?) { override fun onScanSuccess(barcode: String?) {
Timber.d("onScanSuccess barcode = $barcode") Timber.d("onScanSuccess barcode = $barcode")
if (barcode == null) return if (barcode == null) return
handleScanKeyInfo(barcode) scanGunKeyEventCallback(barcode)
} }
}) })
} }
@@ -166,13 +166,12 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
/** /**
* 处理扫描枪数据 * 处理扫描枪数据
*/ */
protected open fun handleScanKeyInfo(scanInfo: String) { fun scanGunKeyEventCallback(scanInfo: String){
var payCode = scanInfo if (this is PayActivity) {
//if (scanInfo.length > 6) { qrCodePay(scanInfo)
// payCode = scanInfo.take(6)
//}
Log.d("TAG", "handleScanKeyInfo: " + payCode)
} }
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean { override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if (keyEventHelper != null) { if (keyEventHelper != null) {
@@ -48,6 +48,7 @@ class CollectedFoodActivity : BaseActivity<ActivityCollectedFoodBinding>() {
override fun initialize() { override fun initialize() {
super.initialize() super.initialize()
registerKeyEvent()
binding.root.setOnClickListener { it.hideKeyboard() } binding.root.setOnClickListener { it.hideKeyboard() }
binding.rvFoodList.let { binding.rvFoodList.let {
it.layoutManager = LinearLayoutManager(this) it.layoutManager = LinearLayoutManager(this)
@@ -1,8 +1,12 @@
package com.sw.dualscreen.activity package com.sw.dualscreen.activity
import android.Manifest
import android.content.Intent import android.content.Intent
import android.os.CountDownTimer import android.os.CountDownTimer
import android.provider.Settings
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.sw.dualscreen.GlobalData import com.sw.dualscreen.GlobalData
import com.sw.dualscreen.databinding.ActivityInitBinding import com.sw.dualscreen.databinding.ActivityInitBinding
@@ -27,6 +31,27 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
private val userViewModel by viewModels<UserViewModel>() private val userViewModel by viewModels<UserViewModel>()
private var pageNum = 1 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 { override fun getViewModel(): BaseViewModel {
return userViewModel return userViewModel
} }
@@ -85,6 +110,7 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
override fun initialize() { override fun initialize() {
super.initialize() super.initialize()
registerKeyEvent()
initNetworkTimer() initNetworkTimer()
binding.okButton.clickWithDebounce(500) { binding.okButton.clickWithDebounce(500) {
checkNetworkTimer?.cancel() checkNetworkTimer?.cancel()
@@ -127,7 +153,7 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
runOnUiThread { runOnUiThread {
hideWaitingDialog() hideWaitingDialog()
binding.root.postDelayed({ binding.root.postDelayed({
goMainActivity() checkCameraPermissionAndGo()
}, 1000) }, 1000)
} }
} }
@@ -141,6 +167,53 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
finish() 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 var checkNetworkTimer: CountDownTimer? = null
private fun initNetworkTimer() { private fun initNetworkTimer() {
@@ -151,12 +151,15 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun initialize() { override fun initialize() {
super.initialize() super.initialize()
isPageVisible = true isPageVisible = true
// 拦截扫码枪按键事件,防止触发页面按钮点击
registerKeyEvent()
addBackEventListener() addBackEventListener()
initView() initView()
if (SpTool.firstGetFace) {
viewModel.getUserFaceCache(pageNo = 1) viewModel.getUserFaceCache(pageNo = 1)
}
setupCamera() setupCamera()
initData() initData()
registerKeyEvent()
} }
private fun initData() { private fun initData() {
@@ -456,6 +459,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
// 3. 关闭相关资源 // 3. 关闭相关资源
imageCapture = null imageCapture = null
imageAnalysis = null imageAnalysis = null
cameraProviderFuture = null
// 4. 停止副屏更新 // 4. 停止副屏更新
isAnalyzing = false isAnalyzing = false
@@ -554,6 +558,10 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
isPageVisible = true isPageVisible = true
// shutdownCamera 后 cameraProviderFuture 置为 null,需重新初始化相机
if (cameraProviderFuture == null) {
setupCamera()
}
// 启动定时器 // 启动定时器
handler.postDelayed(timeoutRunnable, TIME_OUT) handler.postDelayed(timeoutRunnable, TIME_OUT)
// ToastUtils.showToast("isPageVisible=$isPageVisible") // ToastUtils.showToast("isPageVisible=$isPageVisible")
@@ -796,7 +804,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
userId = null, userId = null,
) { ) {
isRefreshPage = false isRefreshPage = false
//presentation?.dismiss() shutdownCamera()
startActivity(Intent(this, PayActivity::class.java).apply { startActivity(Intent(this, PayActivity::class.java).apply {
putExtra(PayActivity.FOOD_INFO, checkedItem!!) putExtra(PayActivity.FOOD_INFO, checkedItem!!)
putExtra(PayActivity.FOOD_EAT_NUM, eatNum) putExtra(PayActivity.FOOD_EAT_NUM, eatNum)
@@ -915,16 +923,16 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
// private val initialDelay = 5 * 60 * 1000L // private val initialDelay = 5 * 60 * 1000L
// private val dealyMillis = 10 * 60 * 1000L // private val dealyMillis = 10 * 60 * 1000L
private val initialDelay = 30 * 1000L private val initialDelay = 10 * 1000L
private val dealyMillis = 3 * 60 * 1000L private val dealyMillis = 5 * 1000L
// private var taskPageNo = 1 // private var taskPageNo = 1
fun startFaceTask() { fun startFaceTask() {
faceTaskJob = faceTaskJob =
intervalExecutor.startIntervalTaskWithInitialDelay(initialDelay, dealyMillis) { intervalExecutor.startIntervalTaskWithInitialDelay(initialDelay, dealyMillis) {
val timestamp = SpTool.getLastFaceTimestamp() val timestamp = SpTool.lastFaceTimestamp
if (timestamp == 0L) { //if (timestamp == 0L) {
return@startIntervalTaskWithInitialDelay // return@startIntervalTaskWithInitialDelay
} //}
viewModel.getFaceIncrementList( viewModel.getFaceIncrementList(
// pageNo = taskPageNo, // pageNo = taskPageNo,
timestamp = timestamp, timestamp = timestamp,
@@ -956,7 +964,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
model.userId, null, Base64.decode(model.faceFeatureStr) model.userId, null, Base64.decode(model.faceFeatureStr)
).also { ).also {
//1-会员、2-临时用户 //1-会员、2-临时用户
it.userType = if (model.isMember) "1" else "2" it.userType = if (model.member) "1" else "2"
it.cardNo = model.cardNo it.cardNo = model.cardNo
} }
faceList.add(faceEntity) faceList.add(faceEntity)
@@ -1,5 +1,6 @@
package com.sw.dualscreen.activity package com.sw.dualscreen.activity
import androidx.activity.OnBackPressedCallback
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.sw.dualscreen.GlobalKey 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.FoodInfo
import com.sw.dualscreen.model.response.MemberInfo import com.sw.dualscreen.model.response.MemberInfo
import com.sw.dualscreen.model.response.PaySuccessEvent import com.sw.dualscreen.model.response.PaySuccessEvent
import com.sw.dualscreen.presentation.pay.ScanQrCodePayPresentation import com.sw.dualscreen.presentation.pay.PayPresentation
import com.sw.dualscreen.utils.Debouncer
import com.sw.dualscreen.utils.SPUtil import com.sw.dualscreen.utils.SPUtil
import com.sw.dualscreen.utils.SoundPoolUtil
import com.sw.dualscreen.viewmodel.BaseViewModel import com.sw.dualscreen.viewmodel.BaseViewModel
import com.sw.dualscreen.viewmodel.UserViewModel import com.sw.dualscreen.viewmodel.UserViewModel
import com.sw.plate.utils.ToastUtils
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import timber.log.Timber
class PayActivity : BaseActivity<ActivityPayBinding>() { class PayActivity : BaseActivity<ActivityPayBinding>() {
@@ -37,6 +40,7 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
const val TAG_PAY_NUMBER = "tagPayNumber" const val TAG_PAY_NUMBER = "tagPayNumber"
const val TAG_PAY_FACE = "tagPayFace" const val TAG_PAY_FACE = "tagPayFace"
const val TAG_PAY_RESULT = "tagPayResult" const val TAG_PAY_RESULT = "tagPayResult"
const val PAY_SUCCESS = "pay_success"
} }
val userViewModel by viewModels<UserViewModel>() val userViewModel by viewModels<UserViewModel>()
@@ -61,10 +65,31 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
var foodOrderId: String = "" var foodOrderId: String = ""
var eatNum: Int = 1 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") @Suppress("DEPRECATION")
override fun initialize() { override fun initialize() {
super.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) foodInfo = intent.getParcelableExtra(FOOD_INFO)
foodOrderId = intent.getStringExtra(FOOD_ORDER_ID) ?: "" foodOrderId = intent.getStringExtra(FOOD_ORDER_ID) ?: ""
eatNum = intent.getIntExtra(FOOD_EAT_NUM, 1) eatNum = intent.getIntExtra(FOOD_EAT_NUM, 1)
@@ -74,12 +99,14 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
binding.include.ivPageBack.setOnClickListener { finish() } binding.include.ivPageBack.setOnClickListener { finish() }
binding.include.tvPageTitle.text = "下单结算" binding.include.tvPageTitle.text = "下单结算"
binding.btnPayQrCode.setOnClickListener { binding.btnPayQrCode.setOnClickListener {
isMember = false
if (binding.btnPayQrCode.isChecked) { if (binding.btnPayQrCode.isChecked) {
return@setOnClickListener return@setOnClickListener
} }
showScanQrCodePay() showScanQrCodePay()
} }
binding.btnPayCash.setOnClickListener { binding.btnPayCash.setOnClickListener {
isMember = false
if (binding.btnPayCash.isChecked) { if (binding.btnPayCash.isChecked) {
return@setOnClickListener return@setOnClickListener
} }
@@ -101,7 +128,6 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
} }
showFacePay() showFacePay()
} }
showScanQrCodePay() showScanQrCodePay()
} }
@@ -136,46 +162,56 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
} }
override fun onDestroy() { override fun onDestroy() {
qrCodePayFragment?.presentation?.dismiss() presentation.dismiss()
cashPayFragment?.presentation?.dismiss()
numberPayFragment?.presentation?.dismiss()
facePayFragment?.presentation?.dismiss()
scanQrCodePayPresentation?.dismiss()
super.onDestroy() super.onDestroy()
} }
private var scanQrCodePayPresentation: ScanQrCodePayPresentation? = null
var memberInfo: MemberInfo? = null var memberInfo: MemberInfo? = null
fun showPayInfo(type: Int = 1, isVip: Boolean = false, memberInfo: MemberInfo? = null) { fun showPayInfo(type: Int = 1, isVip: Boolean = false, memberInfo: MemberInfo? = null) {
this.memberInfo = memberInfo this.memberInfo = memberInfo
payResultFragment = PayResultFragment.instance(type, memberInfo) payResultFragment = PayResultFragment.instance(type, memberInfo)
showFragment(payResultFragment!!, TAG_PAY_RESULT) showFragment(payResultFragment!!, TAG_PAY_RESULT)
if (displays.size > 1) { // if (displays.size > 1) {
scanQrCodePayPresentation = ScanQrCodePayPresentation( // scanQrCodePayPresentation = ScanQrCodePayPresentation(
activity = this, // activity = this,
display = displays[1], // display = displays[1],
type = type // type = type
) { // ) {
scanQrCodePayPresentation?.dismiss() // scanQrCodePaypresentation.dismiss()
}.also { // }.also {
// it.foodName = foodInfo?.foodName
// //确认使用vipPrice 还是 specPrice
// val onePrice = if (isVip) foodInfo?.vipPrice else foodInfo?.specPrice
// it.totalPrice = (onePrice ?: 0.0) * eatNum
// }
// scanQrCodePaypresentation.show()
// }
presentation.let {
it.pageType = PayPresentation.QR_CODE_PAY
it.type = type
it.foodName = foodInfo?.foodName it.foodName = foodInfo?.foodName
//确认使用vipPrice 还是 specPrice //确认使用vipPrice 还是 specPrice
val onePrice = if (isVip) foodInfo?.vipPrice else foodInfo?.specPrice val onePrice = if (isVip) foodInfo?.vipPrice else foodInfo?.specPrice
it.totalPrice = (onePrice ?: 0.0) * eatNum it.totalPrice = (onePrice ?: 0.0) * eatNum
} presentation.initView()
scanQrCodePayPresentation?.show()
} }
} }
fun updateQrCodeImage(qrCodeUrl: String?) { fun updateQrCodeImage(qrCodeUrl: String?) {
scanQrCodePayPresentation?.loadQrCodeImage(qrCodeUrl) // scanQrCodePaypresentation.loadQrCodeImage(qrCodeUrl)
presentation.loadQrCodeImage(qrCodeUrl)
} }
private var debouncer = Debouncer(5000)
fun showPaySuccess(isVip: Boolean) { fun showPaySuccess(isVip: Boolean) {
debouncer.debounce {
playSoundOnPaySuccess()
EventBus.getDefault().post(PaySuccessEvent()) EventBus.getDefault().post(PaySuccessEvent())
showPayInfo(type = 2, isVip = isVip, memberInfo = memberInfo) showPayInfo(type = 2, isVip = isVip, memberInfo = memberInfo)
hidePayTab() hidePayTab()
} }
}
fun getQrCodeImg( fun getQrCodeImg(
orderId: String, orderId: String,
@@ -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) { userViewModel.memberPay(param) {
runOnUiThread { runOnUiThread {
if (it.not()) { 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) { // fun bindOrder(userId: String, block: () -> Unit) {
// val pickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) // val pickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0)
// val mode = if (pickupMode == 1) 1 else 2 // 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() { override fun initialize() {
super.initialize() super.initialize()
registerKeyEvent()
initBackDispatcher() initBackDispatcher()
binding.include.let { binding.include.let {
it.ivPageBack.setOnClickListener { it.ivPageBack.setOnClickListener {
@@ -8,7 +8,7 @@ import com.sw.dualscreen.activity.PayActivity
import com.sw.dualscreen.activity.fragment.BaseFragment import com.sw.dualscreen.activity.fragment.BaseFragment
import com.sw.dualscreen.databinding.FragmentCashPayBinding import com.sw.dualscreen.databinding.FragmentCashPayBinding
import com.sw.dualscreen.ext.format2String import com.sw.dualscreen.ext.format2String
import com.sw.dualscreen.presentation.pay.CashPayPresentation import com.sw.dualscreen.presentation.pay.PayPresentation
import com.sw.plate.utils.ToastUtils import com.sw.plate.utils.ToastUtils
class CashPayFragment : BaseFragment<FragmentCashPayBinding>() { class CashPayFragment : BaseFragment<FragmentCashPayBinding>() {
@@ -17,8 +17,6 @@ class CashPayFragment : BaseFragment<FragmentCashPayBinding>() {
private var foodName: String? = null private var foodName: String? = null
private var payAmount: Double? = null private var payAmount: Double? = null
var presentation: CashPayPresentation? = null
override fun inflateViewBinding(): FragmentCashPayBinding { override fun inflateViewBinding(): FragmentCashPayBinding {
return FragmentCashPayBinding.inflate(layoutInflater) return FragmentCashPayBinding.inflate(layoutInflater)
} }
@@ -27,12 +25,13 @@ class CashPayFragment : BaseFragment<FragmentCashPayBinding>() {
payActivity = requireActivity() as PayActivity payActivity = requireActivity() as PayActivity
payActivity.foodInfo?.let { payActivity.foodInfo?.let {
foodName = it.foodName 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.tvRealAmount.text = getAmountText(payAmount.format2String(2))
binding.btnConfirmPayFinish.setOnClickListener { binding.btnConfirmPayFinish.setOnClickListener {
payActivity.cashPay(hashMapOf( payActivity.cashPay(
hashMapOf(
//标价金额(单位为元) //标价金额(单位为元)
"totalFee" to payAmount.format2String(2), "totalFee" to payAmount.format2String(2),
//订单类型(0付款1会员充值) //订单类型(0付款1会员充值)
@@ -41,49 +40,58 @@ class CashPayFragment : BaseFragment<FragmentCashPayBinding>() {
"orderNo" to payActivity.foodOrderId, "orderNo" to payActivity.foodOrderId,
//支付来源:0线下收款 、3:线上纯会员支付 101:支付宝主动、102:支付宝被动、201:微信主动、202:微信被动 //支付来源:0线下收款 、3:线上纯会员支付 101:支付宝主动、102:支付宝被动、201:微信主动、202:微信被动
"paySource" to "0" "paySource" to "0"
)) { paySuccess -> )
) { paySuccess ->
if (paySuccess.not()) { if (paySuccess.not()) {
ToastUtils.showToast("接口调用失败") ToastUtils.showToast("接口调用失败")
return@cashPay return@cashPay
} }
payActivity.showPaySuccess( false) payActivity.showPaySuccess(false)
} }
} }
showSubScreen() showSubScreen()
} }
override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden)
if (hidden) {
dismissSubScreen()
return
}
//showSubScreen()
}
override fun onDestroy() { // override fun onHiddenChanged(hidden: Boolean) {
dismissSubScreen() // super.onHiddenChanged(hidden)
super.onDestroy() // if (hidden) {
} // dismissSubScreen()
// return
private fun dismissSubScreen() { // }
presentation?.let { // //showSubScreen()
if (it.isShowing) { // }
it.dismiss() //
} // override fun onDestroy() {
} // dismissSubScreen()
} // super.onDestroy()
// }
//
// private fun dismissSubScreen() {
// }
private fun showSubScreen() { private fun showSubScreen() {
// 查找副屏(通常索引为1 // // 查找副屏(通常索引为1)
if (displays.size > 1) { // if (displays.size > 1) {
dismissSubScreen() // dismissSubScreen()
presentation = CashPayPresentation(activity = payActivity, display = displays[1]) { // presentation = PayPresentation(
presentation?.dismiss() // activity = payActivity,
}.also { // pageType = PayPresentation.CASH_PAY,
// userViewModel = payActivity.userViewModel,
// recognizeViewModel = payActivity.recognizeViewModel,
// display = displays[1]
// ) {
// presentation?.dismiss()
// }.also {
// it.foodName = foodName
// it.payAmount = payAmount.format2String(2)
// }
// presentation?.show()
// }
payActivity.presentation.let {
it.pageType = PayPresentation.CASH_PAY
it.foodName = foodName it.foodName = foodName
it.payAmount = payAmount.format2String(2) it.payAmount = payAmount.format2String(2)
} it.initView()
presentation?.show()
} }
} }
@@ -5,14 +5,13 @@ import com.sw.dualscreen.activity.PayActivity
import com.sw.dualscreen.activity.fragment.BaseFragment import com.sw.dualscreen.activity.fragment.BaseFragment
import com.sw.dualscreen.databinding.FragmentFacePayBinding import com.sw.dualscreen.databinding.FragmentFacePayBinding
import com.sw.dualscreen.ext.format2String import com.sw.dualscreen.ext.format2String
import com.sw.dualscreen.presentation.pay.FacePayPresentation import com.sw.dualscreen.presentation.pay.PayPresentation
class FacePayFragment : BaseFragment<FragmentFacePayBinding>() { class FacePayFragment : BaseFragment<FragmentFacePayBinding>() {
private lateinit var payActivity: PayActivity private lateinit var payActivity: PayActivity
private var foodName: String? = null private var foodName: String? = null
private var payAmount: Double? = null private var payAmount: Double? = null
var presentation: FacePayPresentation? = null
override fun inflateViewBinding(): FragmentFacePayBinding { override fun inflateViewBinding(): FragmentFacePayBinding {
return FragmentFacePayBinding.inflate(layoutInflater) return FragmentFacePayBinding.inflate(layoutInflater)
} }
@@ -28,28 +27,30 @@ class FacePayFragment : BaseFragment<FragmentFacePayBinding>() {
} }
showSubScreen() 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) { // override fun onDestroy() {
super.onHiddenChanged(hidden) // dismissSubScreen()
if (hidden) { // super.onDestroy()
dismissSubScreen() // }
return
}
//showSubScreen()
}
override fun onDestroy() { // private fun dismissSubScreen() {
dismissSubScreen() // presentation?.let {
super.onDestroy() // if (it.isShowing) {
} // it.dismiss()
// }
private fun dismissSubScreen() { // }
presentation?.let { // }
if (it.isShowing) {
it.dismiss()
}
}
}
fun loadBitmap(frame: Bitmap) { fun loadBitmap(frame: Bitmap) {
binding.ivFaceImage.setImageBitmap(frame) binding.ivFaceImage.setImageBitmap(frame)
@@ -57,21 +58,44 @@ class FacePayFragment : BaseFragment<FragmentFacePayBinding>() {
private fun showSubScreen() { private fun showSubScreen() {
// 查找副屏(通常索引为1 // 查找副屏(通常索引为1
if (displays.size > 1) { // if (displays.size > 1) {
dismissSubScreen() // dismissSubScreen()
presentation = FacePayPresentation( // presentation = PayPresentation(
activity = payActivity, // activity = payActivity,
display = displays[1], // pageType = PayPresentation.FACE_PAY,
userViewModel = payActivity.userViewModel, // userViewModel = payActivity.userViewModel,
recognizeViewModel = payActivity.recognizeViewModel, // recognizeViewModel = payActivity.recognizeViewModel,
// parentTextureView = binding.parentTextureView, // display = displays[1]
) { // ) {
presentation?.dismiss() // presentation?.dismiss()
}.also { // }.also {
// it.foodName = foodName
// it.payAmount = payAmount.format2String(2)
// }
// presentation?.show()
// }
payActivity.presentation.let {
it.pageType = PayPresentation.FACE_PAY
it.foodName = foodName it.foodName = foodName
it.payAmount = payAmount.format2String(2) it.payAmount = payAmount.format2String(2)
isResumeCamera = true
it.initView()
} }
presentation?.show()
}
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 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.PayActivity
import com.sw.dualscreen.activity.fragment.BaseFragment import com.sw.dualscreen.activity.fragment.BaseFragment
import com.sw.dualscreen.databinding.FragmentNumberPayBinding import com.sw.dualscreen.databinding.FragmentNumberPayBinding
import com.sw.dualscreen.ext.format2String import com.sw.dualscreen.ext.format2String
import com.sw.dualscreen.ext.hideKeyboard import com.sw.dualscreen.ext.hideKeyboard
import com.sw.dualscreen.presentation.pay.CashPayPresentation import com.sw.dualscreen.presentation.pay.PayPresentation
import com.sw.plate.utils.ToastUtils import com.sw.plate.utils.ToastUtils
class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() { class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
@@ -13,7 +15,8 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
private lateinit var payActivity: PayActivity private lateinit var payActivity: PayActivity
private var foodName: String? = null private var foodName: String? = null
private var payAmount: Double? = null private var payAmount: Double? = null
var presentation: CashPayPresentation? = null // 密码是否显示的标志
private var isPasswordVisible = false
override fun inflateViewBinding(): FragmentNumberPayBinding { override fun inflateViewBinding(): FragmentNumberPayBinding {
return FragmentNumberPayBinding.inflate(layoutInflater) return FragmentNumberPayBinding.inflate(layoutInflater)
} }
@@ -28,6 +31,12 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
foodName = it.foodName foodName = it.foodName
payAmount = (it.specPrice ?: 0.0) * payActivity.eatNum payAmount = (it.specPrice ?: 0.0) * payActivity.eatNum
} }
// 密码显示/隐藏切换
binding.ivTogglePassword.setOnClickListener {
togglePasswordVisibility()
}
binding.btnConfirm.setOnClickListener { v -> binding.btnConfirm.setOnClickListener { v ->
val phone = binding.etInputPhone.text.toString().trim() val phone = binding.etInputPhone.text.toString().trim()
val key = binding.etInputNumber.text.toString().trim() val key = binding.etInputNumber.text.toString().trim()
@@ -42,7 +51,7 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
// ToastUtils.showToast("会员验证成功") // ToastUtils.showToast("会员验证成功")
// } // }
// } // }
payActivity.isMember = false
payActivity.getMemberInfoByPhone(phone, key) { memberInfo -> payActivity.getMemberInfoByPhone(phone, key) { memberInfo ->
if (memberInfo == null) { if (memberInfo == null) {
binding.root.postDelayed({ binding.root.postDelayed({
@@ -51,6 +60,9 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
}, 1000) }, 1000)
return@getMemberInfoByPhone return@getMemberInfoByPhone
} }
//肯定是会员
memberInfo.member = true
payActivity.isMember = true
//绑定订单使用faceUserId //绑定订单使用faceUserId
payActivity.bindOrder(memberInfo.faceUserId ?: "") { bindResult -> payActivity.bindOrder(memberInfo.faceUserId ?: "") { bindResult ->
if (bindResult.not()) { if (bindResult.not()) {
@@ -60,7 +72,7 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
} }
binding.root.postDelayed({ binding.root.postDelayed({
payActivity.hideWaitingDialog() payActivity.hideWaitingDialog()
payActivity.showPayInfo(type = 1, isVip = true, memberInfo = memberInfo) payActivity.showPayInfo(type = 1, isVip = memberInfo.member, memberInfo = memberInfo)
payActivity.hidePayTab() payActivity.hidePayTab()
}, 1000) }, 1000)
} }
@@ -79,40 +91,74 @@ class NumberPayFragment : BaseFragment<FragmentNumberPayBinding>() {
showSubScreen() showSubScreen()
} }
override fun onHiddenChanged(hidden: Boolean) { // override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden) // super.onHiddenChanged(hidden)
if (hidden) { // if (hidden) {
dismissSubScreen() // dismissSubScreen()
return // return
} // }
//showSubScreen() // //showSubScreen()
} // }
//
// override fun onDestroy() {
// dismissSubScreen()
// super.onDestroy()
// }
override fun onDestroy() { // private fun dismissSubScreen() {
dismissSubScreen() // presentation?.let {
super.onDestroy() // if (it.isShowing) {
} // it.dismiss()
// }
private fun dismissSubScreen() { // }
presentation?.let { // }
if (it.isShowing) {
it.dismiss()
}
}
}
private fun showSubScreen() { private fun showSubScreen() {
// 查找副屏(通常索引为1 // 查找副屏(通常索引为1
if (displays.size > 1) { // if (displays.size > 1) {
dismissSubScreen() // dismissSubScreen()
presentation = CashPayPresentation(activity = payActivity, display = displays[1]) { // presentation = PayPresentation(
presentation?.dismiss() // activity = payActivity,
}.also { // pageType = PayPresentation.CASH_PAY,
// userViewModel = payActivity.userViewModel,
// recognizeViewModel = payActivity.recognizeViewModel,
// display = displays[1]
// ).also {
// it.foodName = foodName
// it.payAmount = payAmount.format2String(2)
// }
// presentation?.show()
// }
payActivity.presentation.let {
it.pageType = PayPresentation.CASH_PAY
it.foodName = foodName it.foodName = foodName
it.payAmount = payAmount.format2String(2) it.payAmount = payAmount.format2String(2)
it.initView()
} }
presentation?.show()
} }
// 切换密码显示/隐藏
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) { // private fun bindOrder(userId: String, block: () -> Unit) {
@@ -74,7 +74,7 @@ class PayResultFragment : BaseFragment<FragmentPayResultBinding>() {
memberInfo = it.getParcelable(MEMBER_INFO) memberInfo = it.getParcelable(MEMBER_INFO)
} }
if (memberInfo != null) { if (memberInfo != null && memberInfo!!.member) {
totalPrice = (payActivity.foodInfo?.vipPrice ?: 0.0) * payActivity.eatNum totalPrice = (payActivity.foodInfo?.vipPrice ?: 0.0) * payActivity.eatNum
binding.layoutVip.visible() binding.layoutVip.visible()
balance = (memberInfo!!.topUpBalance ?: 0.0) + (memberInfo!!.rewardBalance ?: 0.0) balance = (memberInfo!!.topUpBalance ?: 0.0) + (memberInfo!!.rewardBalance ?: 0.0)
@@ -112,9 +112,15 @@ class PayResultFragment : BaseFragment<FragmentPayResultBinding>() {
) )
) )
if (memberInfo != null && memberInfo!!.member) {
binding.tvAccountInfo.text = SpannedUtils.getAmountText( binding.tvAccountInfo.text = SpannedUtils.getAmountText(
getAmountList() getAmountList()
) )
} else {
binding.tvAccountInfo.text = SpannedUtils.getAmountText(
getAmountListNotMember()
)
}
if (balance >= totalPrice) { if (balance >= totalPrice) {
binding.ivPayQrCode.invisible() binding.ivPayQrCode.invisible()
binding.btnConfirmPay.run { binding.btnConfirmPay.run {
@@ -201,6 +207,21 @@ class PayResultFragment : BaseFragment<FragmentPayResultBinding>() {
//扣除余额 //扣除余额
private var expensesBalance = 0.0 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> { private fun getAmountList(): List<TextBean> {
val list: MutableList<TextBean> = mutableListOf() val list: MutableList<TextBean> = mutableListOf()
@@ -268,7 +289,7 @@ class PayResultFragment : BaseFragment<FragmentPayResultBinding>() {
//订单号 //订单号
"orderNo" to payActivity.foodOrderId, "orderNo" to payActivity.foodOrderId,
//用户id //用户id
"memberId" to memberInfo!!.id!! "memberId" to memberInfo?.id
), ),
block = callback block = callback
) )
@@ -8,11 +8,11 @@ import androidx.lifecycle.lifecycleScope
import com.sw.dualscreen.activity.PayActivity import com.sw.dualscreen.activity.PayActivity
import com.sw.dualscreen.activity.fragment.BaseFragment import com.sw.dualscreen.activity.fragment.BaseFragment
import com.sw.dualscreen.ext.load import com.sw.dualscreen.ext.load
import com.sw.dualscreen.presentation.pay.ScanQrCodePayPresentation
import com.sw.dualscreen.utils.countDownByFlow import com.sw.dualscreen.utils.countDownByFlow
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import com.sw.dualscreen.databinding.FragmentScanQrcodePayBinding import com.sw.dualscreen.databinding.FragmentScanQrcodePayBinding
import com.sw.dualscreen.ext.format2String import com.sw.dualscreen.ext.format2String
import com.sw.dualscreen.presentation.pay.PayPresentation
import com.sw.dualscreen.utils.IntervalExecutor import com.sw.dualscreen.utils.IntervalExecutor
class ScanQrCodePayFragment : BaseFragment<FragmentScanQrcodePayBinding>() { class ScanQrCodePayFragment : BaseFragment<FragmentScanQrcodePayBinding>() {
@@ -25,7 +25,6 @@ class ScanQrCodePayFragment : BaseFragment<FragmentScanQrcodePayBinding>() {
private var foodName: String? = null private var foodName: String? = null
private var payAmount: Double? = null private var payAmount: Double? = null
private var payQrCodePic: String? = null private var payQrCodePic: String? = null
var presentation: ScanQrCodePayPresentation? = null
private var countDownJob: Job? = null private var countDownJob: Job? = null
override fun inflateViewBinding(): FragmentScanQrcodePayBinding { override fun inflateViewBinding(): FragmentScanQrcodePayBinding {
@@ -37,7 +36,7 @@ class ScanQrCodePayFragment : BaseFragment<FragmentScanQrcodePayBinding>() {
payActivity.foodInfo?.let { payActivity.foodInfo?.let {
foodName = it.foodName 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.tvRealAmount.text = getAmountText(payAmount.format2String(2))
//不需要绑定会员id //不需要绑定会员id
@@ -60,41 +59,48 @@ class ScanQrCodePayFragment : BaseFragment<FragmentScanQrcodePayBinding>() {
override fun onHiddenChanged(hidden: Boolean) { override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden) super.onHiddenChanged(hidden)
if (hidden) { // if (hidden) {
dismissSubScreen() //// dismissSubScreen()
return // return
} // }
//showSubScreen() //showSubScreen()
} }
override fun onDestroy() { override fun onDestroy() {
dismissSubScreen() // dismissSubScreen()
countDownJob?.cancel() // 自动取消订阅,防止内存泄漏 countDownJob?.cancel() // 自动取消订阅,防止内存泄漏
payTaskJob?.cancel() payTaskJob?.cancel()
super.onDestroy() super.onDestroy()
} }
private fun dismissSubScreen() { private fun dismissSubScreen() {
presentation?.let { // presentation?.let {
if (it.isShowing) { // if (it.isShowing) {
it.dismiss() // it.dismiss()
} // }
} // }
} }
private fun showSubScreen() { private fun showSubScreen() {
// 查找副屏(通常索引为1 // // 查找副屏(通常索引为1)
if (displays.size > 1) { // if (displays.size > 1) {
dismissSubScreen() // dismissSubScreen()
presentation = // presentation =
ScanQrCodePayPresentation(activity = payActivity, type = 0, display = displays[1]) { // ScanQrCodePayPresentation(activity = payActivity, type = 0, display = displays[1]) {
presentation?.dismiss() // presentation?.dismiss()
}.also { // }.also {
// it.foodName = foodName
// it.totalPrice = payAmount ?: 0.0
// it.payQrCodePic = payQrCodePic
// }
// presentation?.show()
// }
payActivity.presentation.let {
it.pageType = PayPresentation.QR_CODE_PAY
it.foodName = foodName it.foodName = foodName
it.totalPrice = payAmount ?: 0.0 it.totalPrice = payAmount ?: 0.0
it.payQrCodePic = payQrCodePic it.payQrCodePic = payQrCodePic
} it.initView()
presentation?.show()
} }
} }
@@ -54,7 +54,8 @@ data class MemberInfo(
//赠送余额 //赠送余额
val rewardBalance: Double?, val rewardBalance: Double?,
//积分余额 //积分余额
val integralBalance: Int? val integralBalance: Int?,
var member: Boolean = false
) : Parcelable ) : Parcelable
data class TextBean( data class TextBean(
@@ -32,7 +32,7 @@ data class UserFaceModel(
/** /**
* 是否会员 * 是否会员
*/ */
val isMember: Boolean val member: Boolean
) : Parcelable ) : Parcelable
@Parcelize @Parcelize
@@ -202,7 +202,7 @@ interface ApiService {
@POST @POST
suspend fun memberPay( suspend fun memberPay(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/pay/app/memberPay", @Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/pay/app/memberPay",
@Body param: HashMap<String, String> @Body param: HashMap<String, String?>
): ApiResponse<Any?> ): ApiResponse<Any?>
/** /**
@@ -216,6 +216,16 @@ interface ApiService {
@Query("totalFee") totalFee: String? = null @Query("totalFee") totalFee: String? = null
): ApiResponse<String?> ): 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) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
null null
} finally {
// 线程资源清理
boxStore?.closeThreadResources()
} }
} }
} }
@@ -177,16 +180,13 @@ object ObjectBox {
getBox<Food>()?.all getBox<Food>()?.all
} ?: emptyList() } ?: emptyList()
suspend fun filter(name: String?, isDistinct: Boolean = false) = safeDbOp { suspend fun filter(name: String?) = safeDbOp {
val list = if (isDistinct) { if (!name.isNullOrBlank()) {
getBox<Food>()?.all?.distinctBy { it.foodName } getBox<Food>()?.query(Food_.foodName.contains(name))?.build()?.find()
?.distinctBy { it.foodName }
} else { } else {
getBox<Food>()?.all getBox<Food>()?.all?.distinctBy { it.foodName }
} } ?: emptyList()
if (name.isNullOrBlank().not()) {
list?.filter { it.foodName?.contains(name) == true }
}
list
} ?: emptyList() } ?: emptyList()
suspend fun removeAll() = safeDbOp { suspend fun removeAll() = safeDbOp {
@@ -194,10 +194,7 @@ object ObjectBox {
} }
suspend fun remove(name: String) = safeDbOp { suspend fun remove(name: String) = safeDbOp {
getBox<Food>()?.run { getBox<Food>()?.query(Food_.foodName.equal(name))?.build()?.remove()
val filterIdList = all.filter { it.foodName == name }.map { it.id }
removeByIds(filterIdList)
}
} }
private val dbMutex = Mutex() // 协程并发锁,保证写入操作原子性 private val dbMutex = Mutex() // 协程并发锁,保证写入操作原子性
@@ -207,9 +204,7 @@ object ObjectBox {
// 检查存储是否可读写(操作数据库前调用) // 检查存储是否可读写(操作数据库前调用)
fun isStorageAvailable(context: Context): Boolean { fun isStorageAvailable(context: Context): Boolean {
return try { return try {
val state = Environment.getExternalStorageState() context.filesDir.canRead() && context.filesDir.canWrite()
val innerDir = context.filesDir
Environment.MEDIA_MOUNTED == state && innerDir.canRead() && innerDir.canWrite()
} catch (e: Exception) { } catch (e: Exception) {
false false
} }
@@ -1,49 +1,49 @@
package com.sw.dualscreen.presentation.pay //package com.sw.dualscreen.presentation.pay
//
import android.R //import android.R
import android.app.Presentation //import android.app.Presentation
import android.os.Bundle //import android.os.Bundle
import android.text.Spanned //import android.text.Spanned
import android.text.SpannedString //import android.text.SpannedString
import android.text.style.AbsoluteSizeSpan //import android.text.style.AbsoluteSizeSpan
import android.view.Display //import android.view.Display
import androidx.core.text.buildSpannedString //import androidx.core.text.buildSpannedString
import com.sw.dualscreen.activity.PayActivity //import com.sw.dualscreen.activity.PayActivity
import com.sw.dualscreen.databinding.PresentationCashPayBinding //import com.sw.dualscreen.databinding.PresentationCashPayBinding
import com.sw.dualscreen.ext.load //import com.sw.dualscreen.ext.load
//
class CashPayPresentation( //class CashPayPresentation(
val activity: PayActivity, // val activity: PayActivity,
display: Display, // display: Display,
private val onDismissListener: () -> Unit = {} // private val onDismissListener: () -> Unit = {}
) : Presentation(activity, display) { //) : Presentation(activity, display) {
//
private lateinit var binding: PresentationCashPayBinding // private lateinit var binding: PresentationCashPayBinding
//
override fun onCreate(savedInstanceState: Bundle?) { // override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) // super.onCreate(savedInstanceState)
binding = PresentationCashPayBinding.inflate(layoutInflater) // binding = PresentationCashPayBinding.inflate(layoutInflater)
setContentView(binding.root) // setContentView(binding.root)
window?.setBackgroundDrawableResource(R.color.transparent) // window?.setBackgroundDrawableResource(R.color.transparent)
initView() // initView()
} // }
//
var foodName: String? = null // var foodName: String? = null
var payAmount: String? = null // var payAmount: String? = null
private fun initView() { // private fun initView() {
binding.tvFoodName.text = foodName // binding.tvFoodName.text = foodName
binding.tvRealAmount.text = getAmountText(payAmount ?: "") // binding.tvRealAmount.text = getAmountText(payAmount ?: "")
} // }
//
private fun getAmountText(amount: String): SpannedString { // private fun getAmountText(amount: String): SpannedString {
return buildSpannedString { // return buildSpannedString {
append("¥", AbsoluteSizeSpan(32, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) // append("¥", AbsoluteSizeSpan(32, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
append(amount, AbsoluteSizeSpan(48, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) // append(amount, AbsoluteSizeSpan(48, true), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
} // }
} // }
//
override fun onDisplayRemoved() { // override fun onDisplayRemoved() {
super.onDisplayRemoved() // super.onDisplayRemoved()
onDismissListener() // onDismissListener()
} // }
} //}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,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=$measuredWidthmeasuredHeight=$measuredHeight")
val previewConfig: PreviewConfig = recognizeViewModel.previewConfig
rgbCameraHelper = DualCameraHelper.Builder()
.previewViewSize(Point(measuredWidth, measuredHeight))
.rotation(activity.windowManager.defaultDisplay.rotation)
.additionalRotation(previewConfig.rgbAdditionalDisplayOrientation) // 角度
.previewSize(recognizeViewModel.loadPreviewSize())
.specificCameraId(previewConfig.rgbCameraId)
.isMirror(true)
.previewOn(binding.dualCameraTexturePreviewRgb)
.cameraListener(cameraListener)
.build()
rgbCameraHelper!!.setSurfaceFrameCallback { frame ->
activity.facePayFragment?.loadBitmap(frame)
}
rgbCameraHelper!!.init()
// rgbCameraHelper!!.start()
}
/**
* 初始化红外相机,若活体检测类型是可见光活体检测或不启用活体,则不需要启用
*/
private fun initIrCamera() {
Timber.tag(TAG).d("initIrCamera: livenessType = $livenessType")
if (livenessType === LivenessType.RGB) {
return
}
val irCameraListener: CameraListener = object : CameraListener {
override fun onCameraOpened(
camera: Camera,
cameraId: Int,
displayOrientation: Int,
isMirror: Boolean
) {
Timber.tag(TAG)
.d("initIrCamera IR onCameraOpened: cameraId = $cameraId, displayOrientation = $displayOrientation")
val previewSizeIr = camera.getParameters().getPreviewSize()
val layoutParams = adjustPreviewViewSize(
binding.dualCameraTexturePreviewRgb,
binding.dualCameraTexturePreviewIr, binding.dualCameraFaceRectViewIr,
previewSizeIr, displayOrientation, 0.25f
)
irFaceRectTransformer = FaceRectTransformer(
previewSizeIr.width, previewSizeIr.height,
// layoutParams.width, layoutParams.height,
layoutParams.width, layoutParams.height,
displayOrientation, cameraId, isMirror,
ConfigUtil.isDrawIrRectHorizontalMirror(context),
ConfigUtil.isDrawIrRectVerticalMirror(context)
)
recognizeViewModel.onIrCameraOpened(camera)
recognizeViewModel.setIrFaceRectTransformer(irFaceRectTransformer)
}
override fun onPreview(nv21: ByteArray?, camera: Camera?) {
recognizeViewModel.refreshIrPreviewData(nv21)
}
override fun onCameraClosed() {
Timber.tag(TAG).i("initIrCamera onCameraClosed: ")
}
override fun onCameraError(e: java.lang.Exception) {
Timber.tag(TAG).i("initIrCamera onCameraError: ${e.message}")
e.printStackTrace()
}
override fun onCameraConfigurationChanged(
cameraID: Int,
displayOrientation: Int
) {
if (irFaceRectTransformer != null) {
irFaceRectTransformer!!.cameraDisplayOrientation = displayOrientation
}
Timber.tag(TAG)
.i("initIrCamera onCameraConfigurationChanged: cameraID = $cameraID, displayOrientation = $displayOrientation")
}
}
val previewConfig = recognizeViewModel.previewConfig
irCameraHelper = DualCameraHelper.Builder()
.previewViewSize(
Point(
binding.dualCameraTexturePreviewIr.measuredWidth,
binding.dualCameraTexturePreviewIr.measuredHeight
)
)
.rotation(activity.windowManager.defaultDisplay.rotation)
.specificCameraId(previewConfig.irCameraId)
.previewOn(binding.dualCameraTexturePreviewIr)
.cameraListener(irCameraListener)
.isMirror(true)
.previewSize(recognizeViewModel.loadPreviewSize()) //相机预览大小设置,RGB与IR需使用相同大小
.additionalRotation(previewConfig.irAdditionalDisplayOrientation) //额外旋转角度
.build()
irCameraHelper!!.init()
try {
irCameraHelper!!.start()
} catch (e: RuntimeException) {
ToastUtils.showToast(e.message + context.getString(R.string.camera_error_notice))
}
}
/**
* 绘制RGB、IR画面的实时人脸信息
*
* @param facePreviewInfoList RGB画面的实时人脸信息
*/
private fun drawPreviewInfo(facePreviewInfoList: MutableList<FacePreviewInfo?>) {
// Timber.tag(TAG).d("drawPreviewInfo facePreviewInfoList = ${facePreviewInfoList.size}, rgbFaceRectTransformer = ${rgbFaceRectTransformer != null}")
if (rgbFaceRectTransformer != null) {
val rgbDrawInfoList: MutableList<DrawInfo?>? = recognizeViewModel.getDrawInfo(
facePreviewInfoList,
LivenessType.RGB,
openRectInfoDraw
)
// 识别成功
binding.dualCameraFaceRectView.drawRealtimeFaceInfo(rgbDrawInfoList)
}
if (facePreviewInfoList.isEmpty() || (lastFaceTrackId != facePreviewInfoList[0]!!.trackId)) {
if (lastFaceTrackId != -1) {
// mealPickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) ?: 0
// Timber.tag(TAG).i("$lastFaceTrackId 用户离开")
// lastFaceTrackId = -1
// postUserData()
// if (mealPickupMode == 0) {
// step1FoodRecognizing()
// } else {
// step2FaceRecognizing(currentFood!!)
// }
resumeCamera()
}
}
}
private var lastFaceTrackId: Int = -1 // 上一次的人脸信息
override fun onDisplayRemoved() {
super.onDisplayRemoved()
onDismissListener()
}
private fun faceRecSuccess(userId: String, 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
}
}
@@ -1,202 +1,202 @@
package com.sw.dualscreen.presentation.pay //package com.sw.dualscreen.presentation.pay
//
import android.annotation.SuppressLint //import android.annotation.SuppressLint
import android.app.Presentation //import android.app.Presentation
import android.os.Bundle //import android.os.Bundle
import android.view.Display //import android.view.Display
import com.sw.dualscreen.R //import com.sw.dualscreen.R
import com.sw.dualscreen.activity.PayActivity //import com.sw.dualscreen.activity.PayActivity
import com.sw.dualscreen.databinding.PresentationScanQrcodePayBinding //import com.sw.dualscreen.databinding.PresentationScanQrcodePayBinding
import com.sw.dualscreen.ext.format2String //import com.sw.dualscreen.ext.format2String
import com.sw.dualscreen.ext.gone //import com.sw.dualscreen.ext.gone
import com.sw.dualscreen.ext.invisible //import com.sw.dualscreen.ext.invisible
import com.sw.dualscreen.ext.load //import com.sw.dualscreen.ext.load
import com.sw.dualscreen.ext.visible //import com.sw.dualscreen.ext.visible
import com.sw.dualscreen.model.response.MemberInfo //import com.sw.dualscreen.model.response.MemberInfo
import com.sw.dualscreen.model.response.TextBean //import com.sw.dualscreen.model.response.TextBean
import com.sw.dualscreen.utils.SpannedUtils //import com.sw.dualscreen.utils.SpannedUtils
//
class ScanQrCodePayPresentation( //class ScanQrCodePayPresentation(
val activity: PayActivity, // val activity: PayActivity,
display: Display, // display: Display,
val type: Int, // val type: Int,
private val onDismissListener: () -> Unit = {} // private val onDismissListener: () -> Unit = {}
) : Presentation(activity, display) { //) : Presentation(activity, display) {
//
//type = 0,扫码支付默认显示二维码 // //type = 0,扫码支付默认显示二维码
//type = 1,会员结算显示二维码,是会员则显示名字、头像、手机号、可用余额 // //type = 1,会员结算显示二维码,是会员则显示名字、头像、手机号、可用余额
//type = 2,支付成功,是会员则显示名字、头像、手机号、可用余额 // //type = 2,支付成功,是会员则显示名字、头像、手机号、可用余额
//
private lateinit var binding: PresentationScanQrcodePayBinding // private lateinit var binding: PresentationScanQrcodePayBinding
//
override fun onCreate(savedInstanceState: Bundle?) { // override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) // super.onCreate(savedInstanceState)
binding = PresentationScanQrcodePayBinding.inflate(layoutInflater) // binding = PresentationScanQrcodePayBinding.inflate(layoutInflater)
setContentView(binding.root) // setContentView(binding.root)
window?.setBackgroundDrawableResource(android.R.color.transparent) // window?.setBackgroundDrawableResource(android.R.color.transparent)
initView() // initView()
} // }
//
var foodName: String? = null // var foodName: String? = null
var payQrCodePic: Any? = null // var payQrCodePic: Any? = null
//
//总价格 // //总价格
var totalPrice = 0.0 // var totalPrice = 0.0
//
//余额 // //余额
private var balance = 0.0 // private var balance = 0.0
//
//实际支付金额 // //实际支付金额
private var realPayPrice = 0.0 // private var realPayPrice = 0.0
//
//扣除余额 // //扣除余额
private var expensesBalance = 0.0 // private var expensesBalance = 0.0
//
@SuppressLint("SetTextI18n") // @SuppressLint("SetTextI18n")
private fun initView() { // private fun initView() {
when (type) { // when (type) {
0 -> { // 0 -> {
binding.layoutVip.gone() // binding.layoutVip.gone()
binding.layoutPaySuccess.gone() // binding.layoutPaySuccess.gone()
binding.layoutScanQrCode.visible() // binding.layoutScanQrCode.visible()
//
binding.tvPayTip.text = "请扫码支付或出示付款码" // binding.tvPayTip.text = "请扫码支付或出示付款码"
binding.tvFoodName.text = foodName // binding.tvFoodName.text = foodName
binding.tvRealAmount.text = SpannedUtils.getAmountText( // binding.tvRealAmount.text = SpannedUtils.getAmountText(
listOf( // listOf(
TextBean(text = "¥", textSize = 32), // TextBean(text = "¥", textSize = 32),
TextBean(text = totalPrice.format2String(2), textSize = 48), // TextBean(text = totalPrice.format2String(2), textSize = 48),
) // )
) // )
binding.ivPayQrCode.load(payQrCodePic) // binding.ivPayQrCode.load(payQrCodePic)
} // }
//
1 -> { // 1 -> {
val memberInfo = activity.memberInfo // val memberInfo = activity.memberInfo
binding.layoutVip.run { // binding.layoutVip.run {
if (memberInfo != null) visible() else gone() // if (memberInfo != null) visible() else gone()
} // }
binding.layoutPaySuccess.gone() // binding.layoutPaySuccess.gone()
binding.layoutScanQrCode.visible() // binding.layoutScanQrCode.visible()
//
balance = (memberInfo?.topUpBalance ?: 0.0) + (memberInfo?.rewardBalance ?: 0.0) // balance = (memberInfo?.topUpBalance ?: 0.0) + (memberInfo?.rewardBalance ?: 0.0)
realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance // realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance
expensesBalance = if (balance >= totalPrice) totalPrice else balance // expensesBalance = if (balance >= totalPrice) totalPrice else balance
//
memberInfo?.let { loadUserInfo(it) } // memberInfo?.let { loadUserInfo(it) }
binding.tvUserBalance.text = SpannedUtils.getAmountText( // binding.tvUserBalance.text = SpannedUtils.getAmountText(
listOf( // listOf(
TextBean(text = "¥", textSize = 20), // TextBean(text = "¥", textSize = 20),
TextBean(text = balance.format2String(2), textSize = 32), // TextBean(text = balance.format2String(2), textSize = 32),
) // )
) // )
//
binding.tvFoodName.text = foodName // binding.tvFoodName.text = foodName
binding.tvRealAmount.text = SpannedUtils.getAmountText( // binding.tvRealAmount.text = SpannedUtils.getAmountText(
listOf( // listOf(
TextBean(text = "¥", textSize = 32), // TextBean(text = "¥", textSize = 32),
TextBean(text = totalPrice.format2String(2), textSize = 48), // TextBean(text = totalPrice.format2String(2), textSize = 48),
) // )
) // )
binding.ivPayQrCode.run { // binding.ivPayQrCode.run {
// load(payQrCodePic) //// load(payQrCodePic)
if (balance >= totalPrice) invisible() else visible() // if (balance >= totalPrice) invisible() else visible()
} // }
//
binding.tvPayTip.text = SpannedUtils.getAmountText(getAmountList()) // binding.tvPayTip.text = SpannedUtils.getAmountText(getAmountList())
} // }
//
2 -> { // 2 -> {
val memberInfo = activity.memberInfo // val memberInfo = activity.memberInfo
binding.layoutVip.run { // binding.layoutVip.run {
if (memberInfo != null) visible() else gone() // if (memberInfo != null) visible() else gone()
} // }
binding.layoutPaySuccess.visible() // binding.layoutPaySuccess.visible()
binding.layoutScanQrCode.gone() // binding.layoutScanQrCode.gone()
//
balance = if (memberInfo != null) (memberInfo.topUpBalance // balance = if (memberInfo != null) (memberInfo.topUpBalance
?: 0.0) + (memberInfo.rewardBalance ?: 0.0) else 0.0 // ?: 0.0) + (memberInfo.rewardBalance ?: 0.0) else 0.0
realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance // realPayPrice = if (balance >= totalPrice) 0.0 else totalPrice - balance
expensesBalance = if (balance >= totalPrice) totalPrice else balance // expensesBalance = if (balance >= totalPrice) totalPrice else balance
//
memberInfo?.let { loadUserInfo(it) } // memberInfo?.let { loadUserInfo(it) }
val remainBalance = if (balance >= totalPrice) balance - totalPrice else 0.0 // val remainBalance = if (balance >= totalPrice) balance - totalPrice else 0.0
binding.tvUserBalance.text = SpannedUtils.getAmountText( // binding.tvUserBalance.text = SpannedUtils.getAmountText(
listOf( // listOf(
TextBean(text = "¥", textSize = 24), // TextBean(text = "¥", textSize = 24),
TextBean(text = remainBalance.format2String(2), textSize = 36), // TextBean(text = remainBalance.format2String(2), textSize = 36),
) // )
) // )
//
val showBalance = expensesBalance.format2String(2) // val showBalance = expensesBalance.format2String(2)
val showTotal = totalPrice.format2String(2) // val showTotal = totalPrice.format2String(2)
binding.tvPayInfo.text = "余额扣除 $showBalance 元,在线支付 $showTotal" // binding.tvPayInfo.text = "余额扣除 $showBalance 元,在线支付 $showTotal 元"
} // }
} // }
} // }
//
fun loadQrCodeImage(qrCodeUrl: String?) { // fun loadQrCodeImage(qrCodeUrl: String?) {
binding.ivPayQrCode.load(qrCodeUrl) // binding.ivPayQrCode.load(qrCodeUrl)
} // }
//
private fun loadUserInfo(item: MemberInfo) { // private fun loadUserInfo(item: MemberInfo) {
binding.ivHeadPic.load( // binding.ivHeadPic.load(
if(item.faceUrl.isNullOrBlank()) R.drawable.ic_avatar_default // if(item.faceUrl.isNullOrBlank()) R.drawable.ic_avatar_default
else item.faceUrl // else item.faceUrl
) // )
binding.tvUserName.text = item.name // binding.tvUserName.text = item.name
//
val phone = item.phone ?: "" // val phone = item.phone ?: ""
binding.tvUserPhone.text = // binding.tvUserPhone.text =
if (phone.length == 11) phone.replace(phone.substring(3, 7), "****") // if (phone.length == 11) phone.replace(phone.substring(3, 7), "****")
else phone // else phone
} // }
//
private fun getAmountList(): List<TextBean> { // private fun getAmountList(): List<TextBean> {
val list: MutableList<TextBean> = mutableListOf() // val list: MutableList<TextBean> = mutableListOf()
//
list.add(TextBean(text = "余额扣除 ", textSize = 30, textColor = "#FF5E7585")) // list.add(TextBean(text = "余额扣除 ", textSize = 30, textColor = "#FF5E7585"))
list.add( // list.add(
TextBean( // TextBean(
text = "-${expensesBalance.format2String(2)}", // text = "-${expensesBalance.format2String(2)}",
textSize = 30, // textSize = 30,
textColor = "#FF0A1428", // textColor = "#FF0A1428",
isBold = true // isBold = true
) // )
) // )
//
if (balance >= totalPrice) { // if (balance >= totalPrice) {
//余额大于等于总价格,使用余额支付 // //余额大于等于总价格,使用余额支付
list.add(TextBean(text = " 元,扣除后可用余额 ", textSize = 30, textColor = "#FF5E7585")) // list.add(TextBean(text = " 元,扣除后可用余额 ", textSize = 30, textColor = "#FF5E7585"))
val remainingBalance = balance - totalPrice // val remainingBalance = balance - totalPrice
list.add( // list.add(
TextBean( // TextBean(
text = remainingBalance.format2String(2), // text = remainingBalance.format2String(2),
textSize = 30, // textSize = 30,
textColor = "#FF0A1428", // textColor = "#FF0A1428",
isBold = true // isBold = true
) // )
) // )
} else { // } else {
//余额小于总价格,使用余额+扫码支付 // //余额小于总价格,使用余额+扫码支付
//实际支付金额 // //实际支付金额
list.add(TextBean(text = " 元,还需支付 ", textSize = 30, textColor = "#FF5E7585")) // list.add(TextBean(text = " 元,还需支付 ", textSize = 30, textColor = "#FF5E7585"))
list.add( // list.add(
TextBean( // TextBean(
text = realPayPrice.format2String(2), // text = realPayPrice.format2String(2),
textSize = 30, // textSize = 30,
textColor = "#FF0A1428", // textColor = "#FF0A1428",
isBold = true // isBold = true
) // )
) // )
} // }
list.add(TextBean(text = "", textSize = 30, textColor = "#FF5E7585")) // list.add(TextBean(text = " 元", textSize = 30, textColor = "#FF5E7585"))
//
return list // return list
} // }
//
override fun onDisplayRemoved() { // override fun onDisplayRemoved() {
super.onDisplayRemoved() // super.onDisplayRemoved()
onDismissListener() // onDismissListener()
} // }
} //}
@@ -76,6 +76,7 @@ class RemoteRepository constructor(
) )
} }
} }
/** /**
* 获取人脸数据 * 获取人脸数据
*/ */
@@ -134,16 +135,18 @@ class RemoteRepository constructor(
suspend fun getRestInfoFoodsByType( suspend fun getRestInfoFoodsByType(
foodName: String, foodName: String,
pageNum: Int = 1, pageNum: Int = 1,
pageSize:Int = 50 pageSize: Int = 50
): ApiResponse<List<FoodInfo>> { ): ApiResponse<List<FoodInfo>> {
return safeApiCall { return safeApiCall {
apiService.getRestInfoFoodsByType(param = hashMapOf( apiService.getRestInfoFoodsByType(
param = hashMapOf(
"name" to foodName, "name" to foodName,
"pageNum" to "$pageNum", "pageNum" to "$pageNum",
"pageSize" to "$pageSize", "pageSize" to "$pageSize",
//"订单来源权举:1-营养秤,2-档口 //"订单来源权举:1-营养秤,2-档口
"deviceType" to "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( suspend fun getDinnerType(
// restId: String = GlobalData.restId, // restId: String = GlobalData.restId,
): ApiResponse<DinnerType> { ): 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 { return safeApiCall {
apiService.memberPay(param = param) apiService.memberPay(param = param)
} }
@@ -276,10 +299,12 @@ class RemoteRepository constructor(
*/ */
suspend fun getMemberInfoByPhone(phone: String, key: String): ApiResponse<MemberInfo?> { suspend fun getMemberInfoByPhone(phone: String, key: String): ApiResponse<MemberInfo?> {
return safeApiCall { return safeApiCall {
apiService.getMemberInfoByPhone(param = hashMapOf( apiService.getMemberInfoByPhone(
param = hashMapOf(
"phone" to phone, "phone" to phone,
"password" to key "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 { return safeApiCall {
apiService.deleteCollectFood(param = mutableMapOf( apiService.deleteCollectFood(
param = mutableMapOf(
"foodId" to foodId, "foodId" to foodId,
"version" to version "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 rectWidthDp = 400f // 矩形宽度
private val rectHeightDp = 510f // 矩形高度 private val rectHeightDp = 510f // 矩形高度
private val ovalWidthDp = 400f // 椭圆宽度 private val ovalWidthDp = 400f // 椭圆宽度
private val ovalHeightDp = 436f // 椭圆高度 private val ovalHeightDp = 416f // 椭圆高度
// 转换后的像素尺寸 // 转换后的像素尺寸
private val rectWidth: Float private val rectWidth: Float
@@ -65,7 +65,9 @@ abstract class BaseViewModel() : ViewModel() {
} }
val message = response.msg val message = response.msg
Timber.d("msg = ${message}, code = $code") Timber.d("msg = ${message}, code = $code")
if (message!=null && !message.startsWith("未知错误")) {
ToastUtils.showToast("${message}(${code})") ToastUtils.showToast("${message}(${code})")
}
return false return false
} }
@@ -5,6 +5,7 @@ import com.arcsoft.face.ErrorInfo
import com.sw.dualscreen.GlobalData import com.sw.dualscreen.GlobalData
import com.sw.dualscreen.GlobalKey import com.sw.dualscreen.GlobalKey
import com.sw.dualscreen.model.request.UserNutritionParam 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.DeviceConfig
import com.sw.dualscreen.model.response.DinnerType import com.sw.dualscreen.model.response.DinnerType
import com.sw.dualscreen.model.response.FoodInfo import com.sw.dualscreen.model.response.FoodInfo
@@ -95,7 +96,7 @@ class UserViewModel : BaseViewModel() {
launch { launch {
_loadFaceResult.value = false _loadFaceResult.value = false
//获取全量数据时,时间戳改为0 //获取全量数据时,时间戳改为0
SpTool.setLastFaceTimestamp(0) SpTool.lastFaceTimestamp = 0
val response = repository.getUserFaceCache(currentPageNo) val response = repository.getUserFaceCache(currentPageNo)
if (parseResponse(response)) { if (parseResponse(response)) {
// 获取成功一次后缓存状态 // 获取成功一次后缓存状态
@@ -103,13 +104,16 @@ class UserViewModel : BaseViewModel() {
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
val list: List<UserFaceModel> = response.data ?: emptyList() val list: List<UserFaceModel> = response.data ?: emptyList()
if (pageNo == 1 && list.isEmpty()) { if (pageNo == 1 && list.isEmpty()) {
ToastUtils.showToast("查询人脸数据为空") //ToastUtils.showToast("查询人脸数据为空")
return@withContext return@withContext
} }
// val item = list.firstOrNull { it.userId == "1951105919342936066" } //val item = list.map { it.member }
// Timber.tag(TAG).d("getUserFaceCache userId = ${item?.userId}") //Timber.tag(TAG).d("getUserFaceCache item = $item")
val faceEntity = list.map { 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) faceApi.updateFaceData(currentPageNo, faceEntity)
if (list.size >= pageSize) { if (list.size >= pageSize) {
@@ -123,7 +127,7 @@ class UserViewModel : BaseViewModel() {
//非空最后一条数据的是时间戳,空使用上次list最后一条时间戳 //非空最后一条数据的是时间戳,空使用上次list最后一条时间戳
lastFaceTimestamp = list.last().faceUpdateTimestamp ?: 0L lastFaceTimestamp = list.last().faceUpdateTimestamp ?: 0L
} }
SpTool.setLastFaceTimestamp(lastFaceTimestamp) SpTool.lastFaceTimestamp = lastFaceTimestamp
_loadFaceResult.value = true _loadFaceResult.value = true
} }
@@ -201,7 +205,7 @@ class UserViewModel : BaseViewModel() {
//非空实现最后一条数据的是时间戳,空使用上次list最后一条时间戳 //非空实现最后一条数据的是时间戳,空使用上次list最后一条时间戳
lastFaceTimestamp = list.last().faceUpdateTimestamp ?: 0L lastFaceTimestamp = list.last().faceUpdateTimestamp ?: 0L
} }
SpTool.setLastFaceTimestamp(lastFaceTimestamp) SpTool.lastFaceTimestamp = lastFaceTimestamp
onAllQueryFinished() 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() { fun getDinnerType() {
Timber.tag(TAG).d("getDinnerType") Timber.tag(TAG).d("getDinnerType")
_dinnerTypeInfo.value = null _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") Timber.tag(TAG).d("memberPay")
launchWithLoading { launchWithLoading {
val response = repository.memberPay(param) 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") Timber.tag(TAG).d("getCollectedFoodVector")
launchWithLoading { launchWithLoading {
val response = repository.getCollectedFoodVector(param) val response = repository.getCollectedFoodVector(param)
if (parseResponse(response)) { if (parseResponse(response)) {
block(response.data?:emptyList()) block(response.data ?: emptyList())
} else { } else {
block(null) 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") Timber.tag(TAG).d("deleteCollectFood")
launchWithLoading { launchWithLoading {
val response = repository.deleteCollectFood(foodId, version) 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>
+19
View File
@@ -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>
+2 -5
View File
@@ -11,16 +11,14 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="32dp" android:layout_marginHorizontal="32dp"
android:background="@drawable/setting_border" android:background="@drawable/setting_border"
android:orientation="vertical"
android:paddingBottom="16dp"> android:paddingBottom="16dp">
<TextView <TextView
android:id="@+id/tvChargeSetting" android:id="@+id/tvChargeSetting"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_marginStart="32dp" android:layout_marginStart="32dp"
android:layout_marginTop="35dp" android:layout_marginTop="30dp"
android:drawablePadding="17dp" android:drawablePadding="17dp"
android:text="餐品采集" android:text="餐品采集"
android:textColor="@color/black" android:textColor="@color/black"
@@ -34,7 +32,6 @@
android:id="@+id/btnCollectedFood" android:id="@+id/btnCollectedFood"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:layout_marginEnd="32dp" android:layout_marginEnd="32dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:includeFontPadding="false" android:includeFontPadding="false"
@@ -51,7 +48,7 @@
android:id="@+id/flPreview" android:id="@+id/flPreview"
android:layout_width="456dp" android:layout_width="456dp"
android:layout_height="342dp" android:layout_height="342dp"
android:layout_marginTop="41dp" android:layout_marginTop="30dp"
app:layout_constraintStart_toStartOf="@id/tvChargeSetting" app:layout_constraintStart_toStartOf="@id/tvChargeSetting"
app:layout_constraintTop_toBottomOf="@id/tvChargeSetting"> app:layout_constraintTop_toBottomOf="@id/tvChargeSetting">
@@ -34,17 +34,21 @@
android:textStyle="bold" android:textStyle="bold"
tools:ignore="Autofill,HardcodedText,LabelFor,TextFields" /> tools:ignore="Autofill,HardcodedText,LabelFor,TextFields" />
<EditText <FrameLayout
android:id="@+id/etInputNumber"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="100dp" android:layout_height="100dp"
android:layout_marginHorizontal="64dp" android:layout_marginHorizontal="64dp"
android:inputType="textVisiblePassword" 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:background="@drawable/setting_border_gray3"
android:hint="输入消费码" android:hint="输入消费码"
android:textColorHint="#ffc4cfda" android:textColorHint="#ffc4cfda"
android:textColor="#ff0a1428" android:textColor="#ff0a1428"
android:layout_marginTop="32dp"
android:textSize="36sp" android:textSize="36sp"
android:gravity="center" android:gravity="center"
android:maxLines="1" android:maxLines="1"
@@ -53,6 +57,19 @@
android:textStyle="bold" android:textStyle="bold"
tools:ignore="Autofill,HardcodedText,LabelFor,TextFields" /> 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 <androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnConfirm" android:id="@+id/btnConfirm"
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -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.
+1 -1
View File
@@ -13,7 +13,7 @@ material = "1.10.0"
activity = "1.8.0" activity = "1.8.0"
constraintlayout = "2.1.4" constraintlayout = "2.1.4"
accompanistPermissions = "0.37.3" accompanistPermissions = "0.37.3"
cameraCore = "1.3.0" cameraCore = "1.4.1"
retrofit = "3.0.0" retrofit = "3.0.0"
okhttp = "4.12.0" okhttp = "4.12.0"
timber = "5.0.1" timber = "5.0.1"
+2 -2
View File
@@ -1,8 +1,8 @@
#Mon Jul 14 11:28:37 CST 2025 #Mon Jul 14 11:28:37 CST 2025
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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\://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 zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
@@ -5,7 +5,10 @@ import static android.content.Context.INPUT_SERVICE;
import android.content.Context; import android.content.Context;
import android.hardware.input.InputManager; import android.hardware.input.InputManager;
import android.os.Handler; import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent; import android.view.KeyEvent;
import java.util.Arrays; import java.util.Arrays;
@@ -14,172 +17,185 @@ import java.util.List;
/** /**
* 扫描枪事件处理 * 扫描枪事件处理
*/ */
public class ScanGunKeyEventHelper { 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 static final long SCAN_DELAY = 200; // 扫码结束判断时间
//private String mDeviceName = "Linux 3.4.35 with ak-hsudc Composite Gadget (ACM + HID)";
//private String mDeviceName1 = "USBKey Chip USBKey Module";
private String[] deviceNameArray = new String[]{ private final StringBuilder resultBuffer = new StringBuilder();
"CROWN CROWN Barcode KeyBorad FS", private boolean caps;
"Linux 3.4.35 with ak-hsudc Composite Gadget (ACM + HID)",
"USBKey Chip USBKey Module"
};
private long lastTime = 0L;
public ScanGunKeyEventHelper(Context context, OnScanSuccessListener onScanSuccessListener) { private final Handler handler = new Handler(Looper.getMainLooper());
mContext = context; private final Runnable scanFinishRunnable;
mOnScanSuccessListener = onScanSuccessListener;
mStringBufferResult = new StringBuffer(); private OnScanSuccessListener listener;
mHandler = new Handler();
mScanningFishedRunnable = this::performScanSuccess; private long lastScanTime = 0;
public ScanGunKeyEventHelper(OnScanSuccessListener listener) {
this.listener = listener;
scanFinishRunnable = this::handleScanFinish;
} }
/** /**
* 返回扫码成功后的结果 * 扫码结束
*/ */
private void performScanSuccess() { private void handleScanFinish() {
String barcode = mStringBufferResult.toString();
L.d("performScanSuccess barcode = " + barcode); String barcode = resultBuffer.toString();
long currentTime = System.currentTimeMillis();
if (currentTime - lastTime < 5 * 1000) { if (barcode.isEmpty()) {
L.d("performScanSuccess 操作太频繁");
mStringBufferResult.setLength(0);
return; return;
} }
lastTime = currentTime;
if (mOnScanSuccessListener != null && !TextUtils.isEmpty(barcode)) long now = System.currentTimeMillis();
mOnScanSuccessListener.onScanSuccess(barcode);
mStringBufferResult.setLength(0); // 防止重复扫描
if (now - lastScanTime < 1000) {
resultBuffer.setLength(0);
return;
}
lastScanTime = now;
if (listener != null) {
listener.onScanSuccess(barcode);
}
resultBuffer.setLength(0);
} }
/** /**
* 扫码枪事件解析 * 解析 KeyEvent
*
* @param event
*/ */
public void analysisKeyEvent(KeyEvent event) { public void analysisKeyEvent(KeyEvent event) {
int keyCode = event.getKeyCode();
//字母大小写判断 if (event.getAction() != KeyEvent.ACTION_DOWN) {
checkLetterStatus(event); return;
if (event.getAction() == KeyEvent.ACTION_DOWN) {
char aChar = getInputCode(event);
if (aChar != 0) {
mStringBufferResult.append(aChar);
} }
checkCaps(event);
int keyCode = event.getKeyCode();
if (keyCode == KeyEvent.KEYCODE_ENTER) { if (keyCode == KeyEvent.KEYCODE_ENTER) {
//若为回车键,直接返回
mHandler.removeCallbacks(mScanningFishedRunnable); handler.removeCallbacks(scanFinishRunnable);
mHandler.post(mScanningFishedRunnable); handler.post(scanFinishRunnable);
} else {
//延迟post,若500ms内,有其他事件 return;
mHandler.removeCallbacks(mScanningFishedRunnable);
mHandler.postDelayed(mScanningFishedRunnable, MESSAGE_DELAY);
} }
} char c = getInputCode(event);
if (c != 0) {
resultBuffer.append(c);
} }
//检查shift键 handler.removeCallbacks(scanFinishRunnable);
private void checkLetterStatus(KeyEvent event) { handler.postDelayed(scanFinishRunnable, SCAN_DELAY);
}
/**
* 判断是否为扫码枪
*/
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(); int keyCode = event.getKeyCode();
if (keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT || keyCode == KeyEvent.KEYCODE_SHIFT_LEFT) {
if (event.getAction() == KeyEvent.ACTION_DOWN) { if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT
//按着shift键,表示大写 || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
mCaps = true;
} else { caps = event.getAction() == KeyEvent.ACTION_DOWN;
//松开shift键,表示小写
mCaps = false;
}
} }
} }
/** /**
* 获取扫描内容 * KeyEvent 转字符
*
* @param event
* @return
*/ */
private char getInputCode(KeyEvent event) { private char getInputCode(KeyEvent event) {
int keyCode = event.getKeyCode(); int keyCode = event.getKeyCode();
char aChar;
if (keyCode >= KeyEvent.KEYCODE_A && keyCode <= KeyEvent.KEYCODE_Z) { 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) { } else if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
//数字
aChar = (char) ('0' + keyCode - KeyEvent.KEYCODE_0); return (char) ('0' + keyCode - KeyEvent.KEYCODE_0);
} else { }
//其他符号
switch (keyCode) { switch (keyCode) {
case KeyEvent.KEYCODE_PERIOD:
aChar = '.';
break;
case KeyEvent.KEYCODE_MINUS: case KeyEvent.KEYCODE_MINUS:
aChar = mCaps ? '_' : '-'; return caps ? '_' : '-';
break;
case KeyEvent.KEYCODE_SLASH: case KeyEvent.KEYCODE_EQUALS:
aChar = '/'; return caps ? '+' : '=';
break;
case KeyEvent.KEYCODE_LEFT_BRACKET:
return caps ? '{' : '[';
case KeyEvent.KEYCODE_RIGHT_BRACKET:
return caps ? '}' : ']';
case KeyEvent.KEYCODE_BACKSLASH: case KeyEvent.KEYCODE_BACKSLASH:
aChar = mCaps ? '|' : '\\'; return caps ? '|' : '\\';
break;
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: default:
aChar = 0; return 0;
break;
} }
} }
return aChar;
public void release() {
handler.removeCallbacks(scanFinishRunnable);
listener = null;
} }
public interface OnScanSuccessListener { public interface OnScanSuccessListener {
void onScanSuccess(String barcode); 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);
}
} }