优化扫码枪支付

This commit is contained in:
mazengfei
2026-03-10 14:44:34 +08:00
parent 39c0ca7d72
commit 8a1630f2a4
8 changed files with 193 additions and 154 deletions
@@ -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,7 +153,7 @@ 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
@@ -166,10 +166,13 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
/** /**
* 处理扫描枪数据 * 处理扫描枪数据
*/ */
open fun scanGunKeyEventCallback(scanInfo: String) { fun scanGunKeyEventCallback(scanInfo: String){
Timber.tag("TAG").d("handleScanKeyInfo: $scanInfo") if (this is PayActivity) {
qrCodePay(scanInfo)
}
} }
override fun dispatchKeyEvent(event: KeyEvent): Boolean { override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if (keyEventHelper != null) { if (keyEventHelper != null) {
if (keyEventHelper!!.isScanGunEvent(event)) { if (keyEventHelper!!.isScanGunEvent(event)) {
@@ -158,7 +158,6 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
} }
setupCamera() setupCamera()
initData() initData()
registerKeyEvent()
} }
private fun initData() { private fun initData() {
@@ -1,5 +1,7 @@
package com.sw.dualscreen.activity package com.sw.dualscreen.activity
import android.view.KeyEvent
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
@@ -15,6 +17,7 @@ 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.PayPresentation 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.utils.SoundPoolUtil
import com.sw.dualscreen.viewmodel.BaseViewModel import com.sw.dualscreen.viewmodel.BaseViewModel
@@ -81,7 +84,8 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
override fun initialize() { override fun initialize() {
super.initialize() super.initialize()
presentation.show() presentation.show()
registerKeyEvent()
addBackEventListener()
val soundMap = hashMapOf<String, Int>() val soundMap = hashMapOf<String, Int>()
soundMap[PAY_SUCCESS] = R.raw.pay_success soundMap[PAY_SUCCESS] = R.raw.pay_success
SoundPoolUtil.getInstance().loadR(this, soundMap) SoundPoolUtil.getInstance().loadR(this, soundMap)
@@ -196,11 +200,15 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
presentation?.loadQrCodeImage(qrCodeUrl) presentation?.loadQrCodeImage(qrCodeUrl)
} }
private var debouncer = Debouncer(5000)
fun showPaySuccess(isVip: Boolean) { fun showPaySuccess(isVip: Boolean) {
playSoundOnPaySuccess() debouncer.debounce {
EventBus.getDefault().post(PaySuccessEvent()) playSoundOnPaySuccess()
showPayInfo(type = 2, isVip = isVip, memberInfo = memberInfo) EventBus.getDefault().post(PaySuccessEvent())
hidePayTab() showPayInfo(type = 2, isVip = isVip, memberInfo = memberInfo)
hidePayTab()
}
} }
fun getQrCodeImg( fun getQrCodeImg(
@@ -286,10 +294,11 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
} }
} }
override fun scanGunKeyEventCallback(scanInfo: String) { fun qrCodePay(scanInfo: String) {
super.scanGunKeyEventCallback(scanInfo) showWaitingDialog("支付中……")
userViewModel.qrCodePay(scanInfo, foodOrderId) { payState, backData -> userViewModel.qrCodePay(scanInfo, foodOrderId) { payState, backData ->
Timber.d("qrCodePay支付返回数据:state=$payState,backData=$backData") Timber.d("qrCodePay支付返回数据:state=$payState,backData=$backData")
hideWaitingDialog()
if (payState) { if (payState) {
showPaySuccess(false) showPaySuccess(false)
} }
@@ -340,4 +349,12 @@ class PayActivity : BaseActivity<ActivityPayBinding>() {
// } // }
// } // }
// } // }
fun addBackEventListener() {
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
}
})
}
} }
@@ -220,12 +220,10 @@ interface ApiService {
* 二维码支付 * 二维码支付
* @param orderType 0付款 1会员充值 * @param orderType 0付款 1会员充值
*/ */
@GET @POST
suspend fun qrCodePay( suspend fun qrCodePay(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/pay/app/qrCodePay", @Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/pay/app/qrCodePay",
@Query("authCode") authCode: String, @Body param: HashMap<String, String>
@Query("outTradeNo") orderNo: String,
@Query("orderType") orderType:Int
): ApiResponse<String?> ): ApiResponse<String?>
/** /**
@@ -191,7 +191,13 @@ class RemoteRepository constructor(
*/ */
suspend fun qrCodePay(authCode: String,orderNo: String): ApiResponse<String?> { suspend fun qrCodePay(authCode: String,orderNo: String): ApiResponse<String?> {
return safeApiCall { return safeApiCall {
apiService.qrCodePay(authCode = authCode, orderNo=orderNo, orderType = 0) apiService.qrCodePay(
param = hashMapOf(
"authCode" to authCode,
"outTradeNo" to orderNo,
"orderType" to "0"
)
)
} }
} }
@@ -104,7 +104,7 @@ 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.map { it.member } //val item = list.map { it.member }
+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();
//字母大小写判断
checkLetterStatus(event);
if (event.getAction() == KeyEvent.ACTION_DOWN) {
char aChar = getInputCode(event);
if (aChar != 0) {
mStringBufferResult.append(aChar);
}
if (keyCode == KeyEvent.KEYCODE_ENTER) {
//若为回车键,直接返回
mHandler.removeCallbacks(mScanningFishedRunnable);
mHandler.post(mScanningFishedRunnable);
} else {
//延迟post,若500ms内,有其他事件
mHandler.removeCallbacks(mScanningFishedRunnable);
mHandler.postDelayed(mScanningFishedRunnable, MESSAGE_DELAY);
}
if (event.getAction() != KeyEvent.ACTION_DOWN) {
return;
} }
checkCaps(event);
int keyCode = event.getKeyCode();
if (keyCode == KeyEvent.KEYCODE_ENTER) {
handler.removeCallbacks(scanFinishRunnable);
handler.post(scanFinishRunnable);
return;
}
char c = getInputCode(event);
if (c != 0) {
resultBuffer.append(c);
}
handler.removeCallbacks(scanFinishRunnable);
handler.postDelayed(scanFinishRunnable, SCAN_DELAY);
} }
//检查shift键 /**
private void checkLetterStatus(KeyEvent event) { * 判断是否为扫码枪
*/
public boolean isScanGunEvent(KeyEvent event) {
if (event == null) return false;
InputDevice device = event.getDevice();
if (device == null) return false;
int sources = device.getSources();
// HID keyboard
boolean isKeyboard =
(sources & InputDevice.SOURCE_KEYBOARD) == InputDevice.SOURCE_KEYBOARD;
// 排除虚拟键盘
boolean notVirtual =
event.getDeviceId() != KeyCharacterMap.VIRTUAL_KEYBOARD;
return isKeyboard && notVirtual;
}
/**
* SHIFT 状态
*/
private void checkCaps(KeyEvent event) {
int keyCode = event.getKeyCode(); 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) {
case KeyEvent.KEYCODE_PERIOD:
aChar = '.';
break;
case KeyEvent.KEYCODE_MINUS:
aChar = mCaps ? '_' : '-';
break;
case KeyEvent.KEYCODE_SLASH:
aChar = '/';
break;
case KeyEvent.KEYCODE_BACKSLASH:
aChar = mCaps ? '|' : '\\';
break;
default:
aChar = 0;
break;
}
} }
return aChar;
switch (keyCode) {
case KeyEvent.KEYCODE_MINUS:
return caps ? '_' : '-';
case KeyEvent.KEYCODE_EQUALS:
return caps ? '+' : '=';
case KeyEvent.KEYCODE_LEFT_BRACKET:
return caps ? '{' : '[';
case KeyEvent.KEYCODE_RIGHT_BRACKET:
return caps ? '}' : ']';
case KeyEvent.KEYCODE_BACKSLASH:
return caps ? '|' : '\\';
case KeyEvent.KEYCODE_SEMICOLON:
return caps ? ':' : ';';
case KeyEvent.KEYCODE_APOSTROPHE:
return caps ? '"' : '\'';
case KeyEvent.KEYCODE_COMMA:
return caps ? '<' : ',';
case KeyEvent.KEYCODE_PERIOD:
return caps ? '>' : '.';
case KeyEvent.KEYCODE_SLASH:
return caps ? '?' : '/';
case KeyEvent.KEYCODE_SPACE:
return ' ';
default:
return 0;
}
}
public void release() {
handler.removeCallbacks(scanFinishRunnable);
listener = null;
} }
public interface OnScanSuccessListener { 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);
}
} }