Commit c6dbb993 by pye52

QRCodeVIewModel重构Kotlin完毕

parent bc5f9705
...@@ -243,6 +243,10 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe ...@@ -243,6 +243,10 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe
@Override @Override
public void onChanged(Command command) { public void onChanged(Command command) {
if (command == null) {
reset();
return;
}
// 若为debug模式切换命令,则直接执行即可 // 若为debug模式切换命令,则直接执行即可
if (CommandHelper.isLogConfig(command)) { if (CommandHelper.isLogConfig(command)) {
toggleDebugLayout(); toggleDebugLayout();
......
...@@ -15,35 +15,24 @@ import com.google.gson.Gson ...@@ -15,35 +15,24 @@ import com.google.gson.Gson
import com.google.gson.JsonObject import com.google.gson.JsonObject
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/**
* 监听本地扫码、服务器下发的设置指令 <br/>
* 监听Command数据库的变动,按如下流程进行处理: <br/>
* 从数据库读取 -> 解析执行 -> 更新到数据库
*/
class CommandViewModel( class CommandViewModel(
private var commandRepository: CommandRepository, private var commandRepository: CommandRepository,
private var gson: Gson, private var gson: Gson,
private var deviceSN: String private var deviceSN: String
) : ViewModel() { ) : ViewModel() {
val commandWorker = MutableLiveData<Command>() val commandTask = Transformations.map(
private val dataLiveData: LiveData<List<Command>> = commandRepository.queryUndoneCommand() commandRepository.queryUndoneCommand()
) { it?.firstOrNull() }
private val dataObserver = Observer<List<Command>> { commands: List<Command>? ->
if (commands == null || commands.isEmpty()) {
return@Observer
}
commandWorker.postValue(commands.first())
}
init {
// 监听数据库的变动,并执行未完成的指令
dataLiveData.observeForever(dataObserver)
}
fun initialize() { fun initialize() {
SCWebSocketClient.getInstance().addListener(listener) SCWebSocketClient.getInstance().addListener(listener)
} }
override fun onCleared() {
super.onCleared()
dataLiveData.removeObserver(dataObserver)
}
fun getCommandWorker(command: Command?): OneTimeWorkRequest? { fun getCommandWorker(command: Command?): OneTimeWorkRequest? {
return CommandHelper.createWorker(gson, command, deviceSN) return CommandHelper.createWorker(gson, command, deviceSN)
} }
......
package com.bgycc.smartcanteen.viewModel;
import android.text.TextUtils;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.bgycc.smartcanteen.entity.Command;
import com.bgycc.smartcanteen.entity.PayData;
import com.bgycc.smartcanteen.qrcode.IQRCodeScan;
import com.bgycc.smartcanteen.state.QRCodeState;
import com.bgycc.smartcanteen.executor.SCTaskExecutor;
import com.bgycc.smartcanteen.repository.CommandRepository;
import com.bgycc.smartcanteen.repository.PayDataRepository;
import com.bgycc.smartcanteen.utils.DeviceProxy;
import com.bgycc.smartcanteen.utils.SmartCanteenUtils;
import com.blankj.utilcode.util.LogUtils;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import static com.bgycc.smartcanteen.entity.PayData.TYPE_COMMAND;
import static com.bgycc.smartcanteen.utils.SmartCanteenUtils.TAG;
/**
* 负责读取串口数据,并将串口数据识别为设备指令或支付订单,保存到本地数据库中。<br/><br/>
* 串口状态(空闲、读取中、读取成功、读取失败)都会通过{@link QRCodeState}发出通知 <br/><br/>
* 调用{@link #initialize()}进行初始化,初始化完毕后,将在子线程中执行串口读取 <br/><br/>
* 当串口数据读取异常后,会在{@link #DELAY}毫秒后重新进入读取状态,若读取成功后,需要手动调用{@link #scan()}方法进入读取状态 <br/><br/>
*/
public class QRCodeViewModel extends ViewModel {
private static final String RESPONSE_ACTION = "action";
private static final long DELAY = 1500;
private PayDataRepository payDataRepository;
private CommandRepository commandRepository;
private String deviceSN;
private ScheduledFuture<?> scanFuture;
private IQRCodeScan scan;
private MutableLiveData<QRCodeState> qrCodeState = new MutableLiveData<>();
public QRCodeViewModel(PayDataRepository payDataRepository,
CommandRepository commandRepository,
String deviceSN) {
this.payDataRepository = payDataRepository;
this.commandRepository = commandRepository;
this.deviceSN = deviceSN;
}
public LiveData<QRCodeState> getQRCodeStateEvent() {
return qrCodeState;
}
public void initialize() {
try {
scan = DeviceProxy.createQRCodeScan();
scan();
} catch (Exception e) {
LogUtils.e(TAG, "串口初始化失败: " + e.getMessage(), e);
scan = null;
qrCodeState.postValue(new QRCodeState(QRCodeState.INIT_FAILED));
}
}
public void scan() {
if (scan == null) return;
if (scanFuture == null || scanFuture.isDone()) {
scanFuture = SCTaskExecutor.getInstance().schedule(scanRunnable, DELAY, TimeUnit.MILLISECONDS);
}
}
private Runnable scanRunnable = () -> {
qrCodeState.postValue(new QRCodeState(QRCodeState.IDLE));
LogUtils.i(TAG, "开始扫描二维码");
while (!scan.available()) {}
qrCodeState.postValue(new QRCodeState(QRCodeState.SCANNING));
// 读取串口扫描的二维码数据
String scanData;
try {
scanData = scan.scan();
} catch (Exception e) {
LogUtils.e(TAG, "读取串口数据失败: " + e.getMessage(), e);
qrCodeState.postValue(new QRCodeState(QRCodeState.FAILED));
return;
}
// 若存在测试数据,则使用测试数据覆盖
if (!TextUtils.isEmpty(SmartCanteenUtils.testQRCode)) {
scanData = SmartCanteenUtils.testQRCode;
}
// 需要识别二维码的类型
String terminalType = PayData.matchTerminalType(scanData);
if (terminalType.equals(TYPE_COMMAND)) {
// 设备指令需要单独处理(由CommandViewModel处理)
JsonObject jsonObject;
try {
jsonObject = JsonParser.parseString(scanData).getAsJsonObject();
} catch (Exception e) {
LogUtils.e(TAG, "非法的指令格式: " + scanData, e);
qrCodeState.postValue(new QRCodeState(QRCodeState.FAILED));
return;
}
if (jsonObject.has(RESPONSE_ACTION)) {
String action = jsonObject.get(RESPONSE_ACTION).getAsString();
Command command = new Command(scanData, action);
commandRepository.insertCommand(command);
LogUtils.d(TAG, "扫码结果: " + command.toString());
// 任务写入数据库成功后,主动发送一次IDLE,以重置界面提示语
qrCodeState.postValue(new QRCodeState(QRCodeState.SUCCESS, scanData));
againDelay();
} else {
qrCodeState.postValue(new QRCodeState(QRCodeState.FAILED));
}
return;
}
PayData newPay = new PayData(deviceSN, scanData, terminalType);
// 扫描到的二维码,先检查该订单是否已存在
PayData existsPay = payDataRepository.queryPayDataByPayCode(scanData);
if (existsPay != null) {
LogUtils.w(TAG, "该订单已存在: " + scanData);
qrCodeState.postValue(new QRCodeState(QRCodeState.SCAN_REPEAT));
return;
}
// 扫描到的二维码保存到数据库
long lastInsertId = payDataRepository.insertPayData(newPay);
if (lastInsertId == -1) {
LogUtils.e(TAG, "数据库插入失败: " + newPay.toString());
}
LogUtils.d(TAG, "扫码结果: " + newPay.toString());
qrCodeState.postValue(new QRCodeState(QRCodeState.SUCCESS, newPay, newPay.getPayCode()));
};
private void againDelay() {
SCTaskExecutor.getInstance().schedule(scanRunnable, DELAY, TimeUnit.MILLISECONDS);
}
@Override
protected void onCleared() {
super.onCleared();
if (scanFuture != null) {
scanFuture.cancel(true);
scanFuture = null;
}
if (scan != null) {
scan.close();
}
}
}
package com.bgycc.smartcanteen.viewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.bgycc.smartcanteen.entity.Command
import com.bgycc.smartcanteen.entity.PayData
import com.bgycc.smartcanteen.executor.coroutinesDispatcher
import com.bgycc.smartcanteen.qrcode.IQRCodeScan
import com.bgycc.smartcanteen.repository.CommandRepository
import com.bgycc.smartcanteen.repository.PayDataRepository
import com.bgycc.smartcanteen.state.QRCodeState
import com.bgycc.smartcanteen.utils.DeviceProxy
import com.bgycc.smartcanteen.utils.SmartCanteenUtils
import com.bgycc.smartcanteen.utils.SmartCanteenUtils.TAG
import com.blankj.utilcode.util.LogUtils
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import kotlinx.coroutines.*
/**
* 负责读取串口数据,并将串口数据识别为设备指令或支付订单,保存到本地数据库中。
*
* 串口状态(空闲、读取中、读取成功、读取失败)都会通过[QRCodeState]发出通知
*
* 调用[.initialize]进行初始化,初始化完毕后,将在子线程中执行串口读取
*
* 当串口数据读取异常后,会在[.DELAY]毫秒后重新进入读取状态,若读取成功后,需要手动调用[.scan]方法进入读取状态
*/
class QRCodeViewModel(
private val payDataRepository: PayDataRepository,
private val commandRepository: CommandRepository,
private val deviceSN: String
) : ViewModel() {
companion object {
private const val RESPONSE_ACTION = "action"
private const val SCAN_CHECK_DELAY = 100L
private const val DELAY = 1500L
}
private var scanFuture: Job? = null
private var scan: IQRCodeScan? = null
val qrCodeState = MutableLiveData<QRCodeState>()
fun initialize() {
try {
scan = DeviceProxy.createQRCodeScan()
scan()
} catch (e: Exception) {
LogUtils.e(TAG, "串口初始化失败: " + e.message, e)
scan = null
qrCodeState.postValue(QRCodeState.INIT_FAILED.instance())
}
}
fun scan() {
val scan = scan ?: return
scanFuture?.cancel()
scanFuture = viewModelScope.launch(coroutinesDispatcher) {
delay(DELAY)
qrCodeState.postValue(QRCodeState(QRCodeState.IDLE))
LogUtils.i(TAG, "开始扫描二维码")
while (!scan.available()) {
delay(SCAN_CHECK_DELAY)
qrCodeState.postValue(QRCodeState.SCANNING.instance())
}
// 读取串口扫描的二维码数据
var scanData: String
try {
scanData = withContext(Dispatchers.IO) {
scan.scan()
}
} catch (e: Exception) {
LogUtils.e(TAG, "读取串口数据失败: " + e.message, e)
qrCodeState.postValue(QRCodeState.FAILED.instance())
return@launch
}
// 若存在测试数据,则使用测试数据覆盖
if (SmartCanteenUtils.testQRCode.isNotBlank()) {
scanData = SmartCanteenUtils.testQRCode
}
// 若二维码是设备指令,则需要单独处理
val terminalType = PayData.matchTerminalType(scanData)
if (terminalType == PayData.TYPE_COMMAND) {
parseAndGetAction(scanData)
return@launch
}
val newPay = PayData(deviceSN, scanData, terminalType)
// 扫描到的二维码,先检查该订单是否已存在
payDataRepository.queryPayDataByPayCode(scanData)
?.let {
// 扫描到的二维码保存到数据库
if (payDataRepository.insertPayData(newPay) == -1L) {
LogUtils.e(TAG, "数据库插入失败: $newPay")
}
LogUtils.d(TAG, "扫码结果: $newPay")
qrCodeState.postValue(QRCodeState.SUCCESS.instance(newPay, newPay.payCode))
}
?: let {
LogUtils.w(TAG, "该订单已存在: $scanData")
qrCodeState.postValue(QRCodeState.SCAN_REPEAT.instance())
}
}
}
private suspend fun parseAndGetAction(scanData: String) {
// 设备指令需要单独处理(由CommandViewModel处理)
val jsonObject: JsonObject
try {
jsonObject = JsonParser.parseString(scanData).asJsonObject
} catch (e: Exception) {
LogUtils.e(TAG, "非法的指令格式: $scanData", e)
qrCodeState.postValue(QRCodeState.FAILED.instance())
return
}
jsonObject[RESPONSE_ACTION]
?.asString?.let { action ->
val command = Command(scanData, action)
commandRepository.insertCommand(command)
LogUtils.d(TAG, "扫码结果: $command")
// 任务写入数据库成功后,主动发送一次IDLE,以重置界面提示语
qrCodeState.postValue(QRCodeState.SUCCESS.instance(scanData))
delay(DELAY)
scan()
}
?: qrCodeState.postValue(QRCodeState.FAILED.instance())
}
override fun onCleared() {
super.onCleared()
scanFuture?.cancel()
scan?.close()
}
private fun Int.instance() = QRCodeState(this)
private fun Int.instance(message: String) = QRCodeState(this, message)
private fun Int.instance(data: PayData, message: String) = QRCodeState(this, data, message)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment