From 7bcb74d9e0c8a78029856206c36c90069d659696 Mon Sep 17 00:00:00 2001 From: lvmeng <848755140@qq.com> Date: Tue, 10 Mar 2026 09:41:34 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=94=AF=E4=BB=98=E6=88=90?= =?UTF-8?q?=E5=8A=9F=E8=AF=AD=E9=9F=B3=E6=8F=90=E7=A4=BA=EF=BC=9B=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E6=89=AB=E7=A0=81=E6=94=AF=E4=BB=98=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sw/dualscreen/activity/BaseActivity.kt | 10 +- .../com/sw/dualscreen/activity/PayActivity.kt | 24 + .../sw/dualscreen/network/api/ApiService.kt | 12 + .../dualscreen/repository/RemoteRepository.kt | 9 + .../sw/dualscreen/utils/SoundPoolUtil.java | 427 ++++++++++++++++++ .../sw/dualscreen/viewmodel/UserViewModel.kt | 14 + app/src/main/res/raw/pay_success.mp3 | Bin 0 -> 14205 bytes 7 files changed, 489 insertions(+), 7 deletions(-) create mode 100644 app/src/main/java/com/sw/dualscreen/utils/SoundPoolUtil.java create mode 100644 app/src/main/res/raw/pay_success.mp3 diff --git a/app/src/main/java/com/sw/dualscreen/activity/BaseActivity.kt b/app/src/main/java/com/sw/dualscreen/activity/BaseActivity.kt index 670e16a..4ec7296 100644 --- a/app/src/main/java/com/sw/dualscreen/activity/BaseActivity.kt +++ b/app/src/main/java/com/sw/dualscreen/activity/BaseActivity.kt @@ -157,7 +157,7 @@ abstract class BaseActivity : AppCompatActivity() { override fun onScanSuccess(barcode: String?) { Timber.d("onScanSuccess barcode = $barcode") if (barcode == null) return - handleScanKeyInfo(barcode) + scanGunKeyEventCallback(barcode) } }) } @@ -166,12 +166,8 @@ abstract class BaseActivity : AppCompatActivity() { /** * 处理扫描枪数据 */ - protected open fun handleScanKeyInfo(scanInfo: String) { - var payCode = scanInfo - //if (scanInfo.length > 6) { - // payCode = scanInfo.take(6) - //} - Log.d("TAG", "handleScanKeyInfo: " + payCode) + open fun scanGunKeyEventCallback(scanInfo: String) { + Timber.tag("TAG").d("handleScanKeyInfo: $scanInfo") } override fun dispatchKeyEvent(event: KeyEvent): Boolean { diff --git a/app/src/main/java/com/sw/dualscreen/activity/PayActivity.kt b/app/src/main/java/com/sw/dualscreen/activity/PayActivity.kt index 30100f0..91f076b 100644 --- a/app/src/main/java/com/sw/dualscreen/activity/PayActivity.kt +++ b/app/src/main/java/com/sw/dualscreen/activity/PayActivity.kt @@ -16,10 +16,12 @@ import com.sw.dualscreen.model.response.MemberInfo import com.sw.dualscreen.model.response.PaySuccessEvent import com.sw.dualscreen.presentation.pay.PayPresentation import com.sw.dualscreen.utils.SPUtil +import com.sw.dualscreen.utils.SoundPoolUtil import com.sw.dualscreen.viewmodel.BaseViewModel import com.sw.dualscreen.viewmodel.UserViewModel import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel import org.greenrobot.eventbus.EventBus +import timber.log.Timber class PayActivity : BaseActivity() { @@ -36,6 +38,7 @@ class PayActivity : BaseActivity() { const val TAG_PAY_NUMBER = "tagPayNumber" const val TAG_PAY_FACE = "tagPayFace" const val TAG_PAY_RESULT = "tagPayResult" + const val PAY_SUCCESS = "pay_success" } val userViewModel by viewModels() @@ -70,10 +73,19 @@ class PayActivity : BaseActivity() { ) } + fun playSoundOnPaySuccess() { + SoundPoolUtil.getInstance().play(PAY_SUCCESS, 0) + } + @Suppress("DEPRECATION") override fun initialize() { super.initialize() presentation.show() + + val soundMap = hashMapOf() + soundMap[PAY_SUCCESS] = R.raw.pay_success + SoundPoolUtil.getInstance().loadR(this, soundMap) + foodInfo = intent.getParcelableExtra(FOOD_INFO) foodOrderId = intent.getStringExtra(FOOD_ORDER_ID) ?: "" eatNum = intent.getIntExtra(FOOD_EAT_NUM, 1) @@ -185,6 +197,7 @@ class PayActivity : BaseActivity() { } fun showPaySuccess(isVip: Boolean) { + playSoundOnPaySuccess() EventBus.getDefault().post(PaySuccessEvent()) showPayInfo(type = 2, isVip = isVip, memberInfo = memberInfo) hidePayTab() @@ -273,6 +286,17 @@ class PayActivity : BaseActivity() { } } + override fun scanGunKeyEventCallback(scanInfo: String) { + super.scanGunKeyEventCallback(scanInfo) + userViewModel.qrCodePay(scanInfo, foodOrderId) { payState, backData -> + Timber.d("qrCodePay支付返回数据:state=$payState,backData=$backData") + if (payState) { + showPaySuccess(false) + } + } + + } + // fun bindOrder(userId: String, block: () -> Unit) { // val pickupMode = SPUtil.getInstance().get(GlobalKey.KEY_PICKUP_MODE, 0) // val mode = if (pickupMode == 1) 1 else 2 diff --git a/app/src/main/java/com/sw/dualscreen/network/api/ApiService.kt b/app/src/main/java/com/sw/dualscreen/network/api/ApiService.kt index f8a186b..3962d50 100644 --- a/app/src/main/java/com/sw/dualscreen/network/api/ApiService.kt +++ b/app/src/main/java/com/sw/dualscreen/network/api/ApiService.kt @@ -216,6 +216,18 @@ interface ApiService { @Query("totalFee") totalFee: String? = null ): ApiResponse + /** + * 二维码支付 + * @param orderType 0付款 1会员充值 + */ + @GET + suspend fun qrCodePay( + @Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/pay/app/qrCodePay", + @Query("authCode") authCode: String, + @Query("outTradeNo") orderNo: String, + @Query("orderType") orderType:Int + ): ApiResponse + /** * 开餐-生成订单 */ diff --git a/app/src/main/java/com/sw/dualscreen/repository/RemoteRepository.kt b/app/src/main/java/com/sw/dualscreen/repository/RemoteRepository.kt index 2d188e4..94dd563 100644 --- a/app/src/main/java/com/sw/dualscreen/repository/RemoteRepository.kt +++ b/app/src/main/java/com/sw/dualscreen/repository/RemoteRepository.kt @@ -186,6 +186,15 @@ class RemoteRepository constructor( } } + /** + * 二维码支付 + */ + suspend fun qrCodePay(authCode: String,orderNo: String): ApiResponse { + return safeApiCall { + apiService.qrCodePay(authCode = authCode, orderNo=orderNo, orderType = 0) + } + } + suspend fun getDinnerType( // restId: String = GlobalData.restId, ): ApiResponse { diff --git a/app/src/main/java/com/sw/dualscreen/utils/SoundPoolUtil.java b/app/src/main/java/com/sw/dualscreen/utils/SoundPoolUtil.java new file mode 100644 index 0000000..4ace7ff --- /dev/null +++ b/app/src/main/java/com/sw/dualscreen/utils/SoundPoolUtil.java @@ -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 idCache; + private List 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 map) { + loadSize = map.size(); + Set> entries = map.entrySet(); + for (Map.Entry 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 map) { + Set> entries = map.entrySet(); + for (Map.Entry 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 map) { + loadSize = map.size(); + Set> entries = map.entrySet(); + for (Map.Entry 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 play(List names, int times) { + + return this.play(names, 1, 1, 1, times, 1); + } + + /** + * 播放指定列表的音频,并返回并返用于停止、暂停、恢复的StreamId列表,指定次数和频率 + * + * @param names + * @param times + * @param rate + * @return + */ + public List play(List 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 play(List names, int leftVolume, int rightVolume, int property, int times, int rate) { + List 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 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 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 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(); + } +} + diff --git a/app/src/main/java/com/sw/dualscreen/viewmodel/UserViewModel.kt b/app/src/main/java/com/sw/dualscreen/viewmodel/UserViewModel.kt index a1dd0e1..ed548f4 100644 --- a/app/src/main/java/com/sw/dualscreen/viewmodel/UserViewModel.kt +++ b/app/src/main/java/com/sw/dualscreen/viewmodel/UserViewModel.kt @@ -5,6 +5,7 @@ import com.arcsoft.face.ErrorInfo import com.sw.dualscreen.GlobalData import com.sw.dualscreen.GlobalKey import com.sw.dualscreen.model.request.UserNutritionParam +import com.sw.dualscreen.model.response.ApiResponse import com.sw.dualscreen.model.response.DeviceConfig import com.sw.dualscreen.model.response.DinnerType import com.sw.dualscreen.model.response.FoodInfo @@ -359,6 +360,19 @@ class UserViewModel : BaseViewModel() { } } + /** + * 二维码支付 + */ + fun qrCodePay(authCode: String,orderNo: String, block:(Boolean, String?)-> Unit){ + launch { + val response = repository.qrCodePay(authCode, orderNo) + if (parseResponse(response)) { + block(true, response.data) + } else { + block(false, null) + } + } + } fun getDinnerType() { Timber.tag(TAG).d("getDinnerType") _dinnerTypeInfo.value = null diff --git a/app/src/main/res/raw/pay_success.mp3 b/app/src/main/res/raw/pay_success.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..2415638308454411db992c40abff5aea364c2cfa GIT binary patch literal 14205 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