打开app不再清除本地数据,不再发送数据到结算终端,改为新增人脸采集接口,全量增量接口优化

This commit is contained in:
2026-02-03 14:59:22 +08:00
parent c5325f6ad6
commit 9fea23303c
23 changed files with 857 additions and 363 deletions
@@ -34,6 +34,11 @@ class MyApp : App() {
Log.d("MyApp", "initialize: deviceId=$deviceId")
GlobalData.deviceId = deviceId
// TODO: 临时测试----------------
// if ("2987f0c5-5754-33e9-b00a-251db5e2e55f" == deviceId) {
// GlobalData.deviceId = "79814f20-f253-397c-9da7-8430ffa5ef00"
// }
GlobalData.appVersion = AppUtil.getAppVersionCode(this).toString()
GlobalData.globalEquipmentCode = "202501171634"
@@ -1,6 +1,5 @@
package com.sw.platecabinet.activity
import android.app.Dialog
import android.content.Context
import android.os.Build
import android.os.Bundle
@@ -15,23 +14,19 @@ import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding
import com.google.gson.Gson
import com.sw.inbound.utils.DateTimeUtils
import com.sw.plate.App
import com.sw.plate.utils.Base64
import com.sw.plate.utils.ScanGunKeyEventHelper
import com.sw.plate.utils.ToastUtils
import com.sw.plate.utils.arcface.facedb.FaceDatabase
import com.sw.plate.utils.arcface.facedb.entity.FaceEntity
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
import com.sw.plate.utils.comn.SerialApi
import com.sw.plate.utils.comn.SerialPortManager
import com.sw.platecabinet.MyApp
import com.sw.platecabinet.R
import com.sw.platecabinet.databinding.ItemTitleTimeBinding
import com.sw.platecabinet.dialog.BalanceNotEnoughDialog
import com.sw.platecabinet.ext.setClickListeners
import com.sw.platecabinet.ext.clickWithCoroutines
import com.sw.platecabinet.model.response.EquipmentUserInfo
import com.sw.platecabinet.model.response.UserFaceModel
import com.sw.platecabinet.utils.IntervalExecutor
@@ -60,10 +55,10 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
protected var keyEventHelper: ScanGunKeyEventHelper? = null
// 管理员对应的viewmodel
private val settingViewModel by viewModels<SettingViewModel>()
val settingViewModel by viewModels<SettingViewModel>()
// 用户对应的viewModel
protected val viewModel by viewModels<UserViewModel>()
protected val userViewModel by viewModels<UserViewModel>()
private var startTime: Long = 0
@@ -88,8 +83,6 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
registerKeyEvent()
val durationTime = System.currentTimeMillis() - startTime
Timber.d("启动时间:$durationTime")
startFaceTask()
}
/**
@@ -115,7 +108,7 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
if (scanInfo.length > 6) {
plateNumber = scanInfo.take(6)
}
Log.d("TAG", "handleScanKeyInfo: "+ Gson().toJson(MyApp.plateList))
Log.d("TAG", "handleScanKeyInfo: " + Gson().toJson(MyApp.plateList))
if (MyApp.plateList == null) {
initPlateList()
}
@@ -146,12 +139,12 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
fun updateTime() {
headerBinding?.let {
it.tvLeftDate.setClickListeners(
it.tvLeftDate.clickWithCoroutines(
onDoubleClick = {
onLeftDoubleClick()
}
)
it.tvRightTime.setClickListeners(
it.tvRightTime.clickWithCoroutines(
onDoubleClick = {
onRightDoubleClick()
}
@@ -426,7 +419,6 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
// private val dealyMillis = 10 * 60 * 1000L
private val initialDelay = 5 * 60 * 1000L
private val dealyMillis = 5 * 60 * 1000L
private var taskPageNo = 1
fun startFaceTask() {
faceTaskJob =
intervalExecutor.startIntervalTaskWithInitialDelay(initialDelay, dealyMillis) {
@@ -435,28 +427,28 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
}
private var faceTimestamp = 0L
private fun getFaceIncrementList() {
//private var taskPageNo = 1
private fun getFaceIncrementList(pageNo: Int = 1) {
val timestamp = SpTool.lastFaceTimestamp
if (timestamp == 0L) {
return
}
settingViewModel.getFaceIncrementList(
pageNo = taskPageNo,
pageNo = pageNo,
timestamp = timestamp
) { list ->
runOnUiThread {
if (taskPageNo == 1 && list.isEmpty()) {
if (pageNo == 1 && list.isEmpty()) {
//未查询到增量数据
return@runOnUiThread
}
updateFaceData(list)
if (list.size >= settingViewModel.PAGE_SIZE) {
faceTimestamp = list.last().faceUpdateTimestamp ?: 0
taskPageNo++
getFaceIncrementList()
return@runOnUiThread
}
//if (list.size >= settingViewModel.PAGE_SIZE) {
// faceTimestamp = list.last().faceUpdateTimestamp ?: 0
// taskPageNo++
// getFaceIncrementList(taskPageNo)
// return@runOnUiThread
//}
if (list.isNotEmpty()) {
faceTimestamp = list.last().faceUpdateTimestamp ?: 0
}
@@ -481,7 +473,8 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
null,
Base64.decode(model.faceFeatureStr)
).also {
it.userType = "1"
//1-会员、2-临时用户
it.userType = if (model.isMember) "1" else "2"
}
faceList.add(faceEntity)
}
@@ -512,4 +505,13 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
MyApp.plateList = list
}
fun clearAllFace(block: () -> Unit) {
Thread {
val faceDao = FaceDatabase.getInstance(App.getContext()).faceDao()
faceDao.deleteAll()
faceDao.resetId()
runOnUiThread { block() }
}.start()
}
}
@@ -1,22 +1,15 @@
package com.sw.platecabinet.activity
import android.content.Intent
import android.util.Log
import androidx.activity.viewModels
import com.sw.inbound.utils.DateTimeUtils
import com.sw.plate.App
import com.sw.plate.utils.AppUtil
import androidx.lifecycle.lifecycleScope
import com.sw.plate.utils.ToastUtils
import com.sw.plate.utils.arcface.facedb.FaceDatabase
import com.sw.plate.utils.arcface.FaceApi
import com.sw.platecabinet.GlobalData
import com.sw.platecabinet.MyApp
import com.sw.platecabinet.databinding.ActivityDeviceInitBinding
import com.sw.platecabinet.databinding.ItemTitleTimeBinding
import com.sw.platecabinet.ext.gone
import com.sw.platecabinet.ext.invisible
import com.sw.platecabinet.model.response.EquipmentUserInfo
import com.sw.platecabinet.utils.SpTool
import com.sw.platecabinet.viewmodel.SettingViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/**
* 设备初始化界面
@@ -53,37 +46,38 @@ class DeviceInitActivity : BaseActivity<ActivityDeviceInitBinding>() {
// }, 1000)
initPlateList()
showWaitingDialog("加载中……")
clearAllFace {
viewModel.getUserFaceCache()
viewModel.getDeviceConfig { deviceConfig ->
runOnUiThread {
if (deviceConfig == null) {
hideWaitingDialog()
ToastUtils.showToast("获取设备配置数据失败")
hideWaitingDialog()
goLoginActivity()
return@runOnUiThread
lifecycleScope.launch {
withContext(Dispatchers.Default) {
val count = FaceApi().queryFaceCount()
if (count == 0) {
userViewModel.getUserFaceCache { status, msg ->
getDeviceConfig()
}
GlobalData.appId = deviceConfig.arcsoftAppId ?: ""
GlobalData.sdkKey = deviceConfig.arcsoftSdkKey ?: ""
GlobalData.activeKey = deviceConfig.arcsoftActiveKey ?: ""
binding.root.postDelayed({
hideWaitingDialog()
goLoginActivity()
}, 500)
}
}
}
// goLoginActivity()
getDeviceConfig()
}
private fun clearAllFace(block: () -> Unit) {
Thread {
val faceDao = FaceDatabase.getInstance(App.getContext()).faceDao()
faceDao.deleteAll()
faceDao.resetId()
runOnUiThread { block() }
}.start()
private fun getDeviceConfig() {
userViewModel.getDeviceConfig { deviceConfig ->
runOnUiThread {
if (deviceConfig == null) {
hideWaitingDialog()
ToastUtils.showToast("获取设备配置数据失败")
hideWaitingDialog()
goLoginActivity()
return@runOnUiThread
}
GlobalData.appId = deviceConfig.arcsoftAppId ?: ""
GlobalData.sdkKey = deviceConfig.arcsoftSdkKey ?: ""
GlobalData.activeKey = deviceConfig.arcsoftActiveKey ?: ""
binding.root.postDelayed({
hideWaitingDialog()
goLoginActivity()
}, 500)
}
}
}
override fun registerDataChange() {
@@ -2,6 +2,7 @@ package com.sw.platecabinet.activity
import android.Manifest
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Point
import android.hardware.Camera
import android.os.Build
@@ -14,7 +15,6 @@ import androidx.annotation.RequiresApi
import androidx.lifecycle.Observer
import com.arcsoft.face.ErrorInfo
import com.google.gson.Gson
import com.sw.platecabinet.socket.TcpClientListenerImpl
import com.sw.inbound.utils.DateTimeUtils
import com.sw.plate.utils.Base64
import com.sw.plate.utils.ToastUtils
@@ -35,17 +35,15 @@ import com.sw.plate.utils.arcface.facedb.entity.FaceEntity
import com.sw.plate.utils.arcface.model.UserFaceInfo
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel.REGISTER_STATUS_READY
import com.sw.platecabinet.GlobalData
import com.sw.platecabinet.MyApp
import com.sw.platecabinet.R
import com.sw.platecabinet.databinding.ActivityLoginFaceBinding
import com.sw.platecabinet.databinding.ItemTitleTimeBinding
import com.sw.platecabinet.model.response.EquipmentUserInfo
import com.sw.platecabinet.socket.LanServer
import com.sw.platecabinet.socket.LanServerListenerImpl
import com.sw.platecabinet.socket.TcpClient
import com.sw.platecabinet.model.response.UserFaceModel
import com.sw.platecabinet.utils.BitmapSaver
import com.sw.platecabinet.utils.Debouncer
import com.sw.platecabinet.utils.FileUtil
import com.sw.platecabinet.utils.LogFileUtil
import com.sw.platecabinet.utils.PermissionHelper
import com.sw.platecabinet.utils.PlateUtils
import com.sw.platecabinet.utils.SoundPoolUtil
@@ -53,7 +51,6 @@ import com.sw.platecabinet.utils.SpTool
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.json.JSONObject
import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean
@@ -98,15 +95,14 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
override fun initialize() {
instance = this
viewModel.activeEngine()
addSocketListener()
userViewModel.activeEngine()
// addSocketListener()
initCountTime()
initArcViewModel()
initArcView()
openRectInfoDraw = true
recognizeViewModel.setDrawRectInfoTextValue(true)
//viewModel.generateToken()
// viewModel.activeEngine()
// binding.llToPwd.setOnClickListener {
// val intent = Intent(this, LoginByPwdActivity::class.java)
@@ -141,6 +137,8 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
// }
// }
//开启人脸增量数据定时任务
startFaceTask()
val soundMap = hashMapOf<String, Int>()
soundMap[COLLECT_SUCCESS] = R.raw.collect_success
@@ -193,11 +191,11 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
if (this.userFaceInfo == null && userFaceInfo != null) {
recognizeViewModel.updateRegisterStatus(RecognizeViewModel.REGISTER_STATUS_DONE)
this.userFaceInfo = userFaceInfo
getFaceDataBlock { faceData ->
getFaceDataBlock { faceData, headBmp ->
isCollecting.set(false)
isCollectFace = false
Thread {
saveFaceInfo(faceData)
sendFaceData(faceData, headBmp)
}.start()
}
return@setOnRegisterFinishedCallback
@@ -476,8 +474,6 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
recognizeViewModel.destroy()
EventBus.getDefault().unregister(this)
// getFaceScheduler?.shutdown()
// checkFaceScheduler?.shutdown()
super.onDestroy()
}
@@ -705,7 +701,7 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
collectCount = 0
tempUserId = "${System.currentTimeMillis()}"
resumeCamera()
viewModel.resetUserInfo()
userViewModel.resetUserInfo()
countDownTimer?.let {
it.cancel()
it.start()
@@ -754,6 +750,9 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
startActivity(intent)
}
/**
* 采集人脸
*/
private fun collectFace() {
loadLogInfo("collectFace: -----------1,开始采集人脸,开始时间:${DateTimeUtils.getDateTimeString()}----------")
if (isFirstOpen) return
@@ -765,25 +764,43 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
loadLogInfo("collectFace: ----------5,人脸采集中-----------")
}
private fun sendFaceData(faceData: String) {
/**
* 改为上传人脸数据,不需要发生人脸数据到收银端
*/
private fun sendFaceData(faceData: String, headBmp: Bitmap) {
loadLogInfo("collectFace,sendFaceData: ----------11,发送人脸数据-----------")
// 给某个客户端发送
val jsonObject = JSONObject().also {
it.put("type", "faceFeature")
it.put("content", faceData)
val imageFile = BitmapSaver.saveToAppFilesDir(
headBmp, this, "IMG_CROP_${System.currentTimeMillis()}.jpg"
)
loadLogInfo("collectFace,sendFaceData:${imageFile?.absolutePath}")
if (imageFile == null) {
ToastUtils.showToast("获取人脸照片失败")
return
}
tcpClient?.send(jsonObject)
settingViewModel.addUserFace(faceData, imageFile) { status, faceModel ->
loadLogInfo("collectFace,saveFaceInfo: ----------12,人脸数据发送完成时间:${DateTimeUtils.getDateTimeString()},结果:$status-----------")
if (status.not()) {
ToastUtils.showToast("新增数据失败")
loadLogInfo("collectFace,sendFaceData: ----------12,发送人脸数据失败,faceData=${faceData}")
return@addUserFace
}
if (faceModel == null) {
ToastUtils.showToast("新增数据成功,但未拿到接口数据")
loadLogInfo("collectFace,sendFaceData: ----------12,发送人脸数据成功但接口返回为null,faceData=${faceData}")
return@addUserFace
}
saveFaceInfo(faceModel)
}
// // 给某个客户端发送
// val jsonObject = JSONObject().also {
// it.put("type", "faceFeature")
// it.put("content", faceData)
// }
// tcpClient?.send(jsonObject)
}
// private var getFaceScheduler: ScheduledExecutorService? = null
// private var checkFaceScheduler: ScheduledExecutorService? = null
//
// private fun initFaceScheduler() {
//// getFaceScheduler = Executors.newScheduledThreadPool(1)
// checkFaceScheduler = Executors.newScheduledThreadPool(1)
// }
private fun getFaceDataBlock(block: (String) -> Unit) {
private fun getFaceDataBlock(block: (String, Bitmap) -> Unit) {
if (userFaceInfo == null || userFaceInfo?.faceFeature?.featureData == null) {
return
}
@@ -792,7 +809,7 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
return
}
loadLogInfo("collectFace,getFaceData: ----------7,拿到人脸提取特征数据时间:${DateTimeUtils.getDateTimeString()}-----------")
block(faceData)
block(faceData, userFaceInfo!!.headBmp)
}
/**
@@ -801,7 +818,7 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
*/
private fun loadLogInfo(msg: String) {
Timber.tag(TAG).d(msg)
FileUtil.saveLog(msg)
LogFileUtil.saveLog(msg)
}
private var facePreviewInfoList: MutableList<FacePreviewInfo>? = null
@@ -809,23 +826,17 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
private var currentUserId: String? = null
private fun saveFaceInfo(faceData: String) {
private fun saveFaceInfo(faceModel: UserFaceModel) {
try {
sendFaceData(faceData)
loadLogInfo("collectFace,saveFaceInfo: ----------12,人脸数据发送完成时间:${DateTimeUtils.getDateTimeString()}-----------")
// runOnUiThread {
// openPlate(tempUserId)
// }
loadLogInfo("collectFace,saveFaceInfo: ----------8,保存人脸取特征数据-----------")
val faceDao = FaceDatabase.getInstance(this).faceDao()
val faceEntity = FaceEntity(
tempUserId,
faceModel.userId,
null,
Base64.decode(faceData)
Base64.decode(faceModel.faceFeatureStr)
).also {
it.userType = "2"
//1-会员、2-临时用户
it.userType = if (faceModel.isMember) "1" else "2"
}
faceDao.insert(faceEntity)
loadLogInfo("collectFace,saveFaceInfo: ----------10,人脸数据保存完成-----------")
@@ -843,70 +854,70 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
}
}
private var collectCount = 0;
private var collectCount = 0
private var tcpClient: TcpClient? = null
private fun addSocketListener() {
// val ipAddress = NetworkUtils.getIPAddress(true)
// loadLogInfo( "addSocketListener,ipAddress: $ipAddress");
tcpClient = TcpClient(
"192.168.1.95",
5000,
GlobalData.deviceId, // clientId
5000, // connectTimeoutMs
10 * 1000, // heartbeatIntervalMs
30 * 1000 // heartbeatTimeoutMs
)
tcpClient?.setListener(object : TcpClientListenerImpl() {
override fun onSendSuccess(json: JSONObject?) {
super.onSendSuccess(json)
loadLogInfo("collectFace,TcpClientListener发送成功:$json")
// toast("发送成功")
}
override fun onSendFailed(json: JSONObject?, e: java.lang.Exception?) {
super.onSendFailed(json, e)
ToastUtils.showToast("发送失败${e?.message}")
loadLogInfo("collectFace,TcpClientListener人脸数据发送失败:${e?.message}")
}
override fun onMessage(json: JSONObject?) {
super.onMessage(json)
loadLogInfo("collectFace,TcpClientListener收到消息:$json")
val type = json?.getInt("type")
// 根据服务端消息类型处理删除新增临时用户人脸数据---------------------
when (type) {
LanServer.TYPE_ADD_FACE_DATA -> {
//采集设备id自己生成,不接收服务端的,在本地只判断是否存在同一人脸数据
loadLogInfo("collectFace,TcpClientListener收到消息,新增用户:采集设备id自己生成,不接收服务端的,在本地只判断是否存在同一人脸数据")
}
LanServer.TYPE_CLEAR_FACE_DATA -> {
clearFaceData()
}
else -> {}
}
}
})
tcpClient?.start()
try {
LanServer.getInstance().let {
it.setListener(object : LanServerListenerImpl() {
override fun onMessageReceived(clientId: String?, message: JSONObject?) {
super.onMessageReceived(clientId, message)
ToastUtils.showToast("收到消息clientId=$clientId")
}
})
it.start()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
// private var tcpClient: TcpClient? = null
//
// private fun addSocketListener() {
//// val ipAddress = NetworkUtils.getIPAddress(true)
//// loadLogInfo( "addSocketListener,ipAddress: $ipAddress");
// tcpClient = TcpClient(
// "192.168.1.95",
// 5000,
// GlobalData.deviceId, // clientId
// 5000, // connectTimeoutMs
// 10 * 1000, // heartbeatIntervalMs
// 30 * 1000 // heartbeatTimeoutMs
// )
//
// tcpClient?.setListener(object : TcpClientListenerImpl() {
// override fun onSendSuccess(json: JSONObject?) {
// super.onSendSuccess(json)
// loadLogInfo("collectFace,TcpClientListener发送成功:$json")
//// toast("发送成功")
// }
//
// override fun onSendFailed(json: JSONObject?, e: java.lang.Exception?) {
// super.onSendFailed(json, e)
// ToastUtils.showToast("发送失败${e?.message}")
// loadLogInfo("collectFace,TcpClientListener人脸数据发送失败:${e?.message}")
// }
//
// override fun onMessage(json: JSONObject?) {
// super.onMessage(json)
// loadLogInfo("collectFace,TcpClientListener收到消息:$json")
// val type = json?.getInt("type")
// // 根据服务端消息类型处理删除新增临时用户人脸数据---------------------
// when (type) {
// LanServer.TYPE_ADD_FACE_DATA -> {
// //采集设备id自己生成,不接收服务端的,在本地只判断是否存在同一人脸数据
// loadLogInfo("collectFace,TcpClientListener收到消息,新增用户:采集设备id自己生成,不接收服务端的,在本地只判断是否存在同一人脸数据")
// }
//
// LanServer.TYPE_CLEAR_FACE_DATA -> {
// clearFaceData()
// }
//
// else -> {}
// }
// }
// })
// tcpClient?.start()
//
// try {
// LanServer.getInstance().let {
// it.setListener(object : LanServerListenerImpl() {
// override fun onMessageReceived(clientId: String?, message: JSONObject?) {
// super.onMessageReceived(clientId, message)
// ToastUtils.showToast("收到消息clientId=$clientId")
// }
// })
// it.start()
// }
// } catch (e: Exception) {
// e.printStackTrace()
// }
// }
private fun clearFaceData() {
Thread {
@@ -917,34 +928,6 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
}.start()
}
// private val WAITING_TIME = 3 * 1000L
// private var checkStartTime = 0L
private fun checkFaceState() {
// checkStartTime = System.currentTimeMillis()
//
// checkFaceScheduler?.scheduleWithFixedDelay(
// {
// checkFaceBlock()
// }, 5L, 5L, TimeUnit.MILLISECONDS
// )
}
private var isCollectFace = false
// private fun checkFaceBlock() {
// loadLogInfo("collectFace,currentUserId=${currentUserId}")
// if (currentUserId.isNullOrBlank().not()) {
// isCollectFace = false
//
// checkFaceScheduler?.shutdown()
// return
// }
//// if (System.currentTimeMillis() - checkStartTime >= WAITING_TIME) {
//// //超过1秒采集人脸并识别
//// checkFaceScheduler?.shutdown()
//// isCollectFace = true
//// collectFace()
//// }
// }
}
@@ -2,14 +2,14 @@ package com.sw.platecabinet.activity
import android.content.Context
import android.content.Intent
import com.sw.platecabinet.MyApp
import com.sw.plate.utils.ToastUtils
import com.sw.platecabinet.R
import com.sw.platecabinet.databinding.ActivityMainBinding
import com.sw.platecabinet.databinding.ItemTitleTimeBinding
import com.sw.platecabinet.dialog.CustomDialog
import com.sw.platecabinet.fragment.PlateCabinetFullFragment
import com.sw.platecabinet.fragment.PlateOpenFragment
import com.sw.platecabinet.fragment.SettingListFragment
import com.sw.platecabinet.model.response.EquipmentUserInfo
import com.sw.platecabinet.utils.FragmentHelper
typealias Callback = (String) -> Unit
@@ -49,7 +49,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
// context.startActivity(intent)
// }
fun start(context: Context,pageType: PageType, isScanCode: Boolean = false) {
fun start(context: Context, pageType: PageType, isScanCode: Boolean = false) {
val intent = Intent(context, MainActivity::class.java)
intent.putExtra(PARAM_PAGE_TYPE, pageType.name)
intent.putExtra(PlateOpenFragment.IS_SCAN_CODE, isScanCode)
@@ -85,7 +85,9 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
}
override fun onRightDoubleClick() {
if (pageType == PageType.SETTING_LIST) {
loadClearDialog()
}
}
override fun handleScanKeyInfo(scanInfo: String) {
@@ -101,8 +103,27 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun registerDataChange() {
super.registerDataChange()
}
}
/**
* 加载清除数据弹窗
*/
private fun loadClearDialog() {
CustomDialog(
activity = this,
content = "请确认是否清除本地所有数据,并重新拉取全量人脸数据?",
onConfirm = {
showWaitingDialog("加载中……")
clearAllFace {
userViewModel.getUserFaceCache { status, msg ->
hideWaitingDialog()
ToastUtils.showToast(
if (status) "请求成功,$msg" else "请求失败,$msg"
)
}
}
}).show()
}
}
/**
* 界面类型
@@ -0,0 +1,48 @@
package com.sw.platecabinet.dialog
import android.app.Dialog
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.Window
import androidx.fragment.app.FragmentActivity
import com.sw.platecabinet.databinding.DialogCustomBinding
import com.sw.platecabinet.databinding.DialogUserBindRemindBinding
import com.sw.platecabinet.ext.dp
open class CustomDialog(
var activity: FragmentActivity,
var content: String,
var onCancel: () -> Unit = {},
var onConfirm: () -> Unit = {},
) : Dialog(activity) {
private lateinit var binding: DialogCustomBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestWindowFeature(Window.FEATURE_NO_TITLE)
binding = DialogCustomBinding.inflate(layoutInflater)
setContentView(binding.root)
window?.run {
attributes = attributes.apply {
width = 480.dp
}
setBackgroundDrawable(ColorDrawable())
setCancelable(false)
}
binding.tvContent.text = content
addClickListener()
}
private fun addClickListener() {
binding.btnCancel.setOnClickListener {
onCancel()
dismiss()
}
binding.btnConfirm.setOnClickListener {
onConfirm()
dismiss()
}
}
}
@@ -4,6 +4,11 @@ import android.os.Handler
import android.os.Looper
import android.view.View
import android.widget.TextView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
/**
* 设置 TextView 的双击和单击事件监听器
@@ -31,3 +36,37 @@ fun TextView.setClickListeners(
}, doubleClickInterval)
}
}
fun View.clickWithCoroutines(
doubleClickInterval: Long = 300,
onDoubleClick: (View) -> Unit = {},
onSingleClick: (View) -> Unit = {}
) {
var pendingItem: View? = null
var lastClickTime: Long = 0
var clickJob: Job? = null
setOnClickListener { view ->
val currentTime = System.currentTimeMillis()
// 如果是同一控件且在双击间隔内,则判定为双击
if (pendingItem === view && currentTime - lastClickTime < doubleClickInterval) {
// 取消之前的单击任务
clickJob?.cancel()
onDoubleClick(view)
pendingItem = null
} else {
// 取消之前的单击任务
clickJob?.cancel()
// 启动新的单击任务
clickJob = CoroutineScope(Dispatchers.Main).launch {
delay(doubleClickInterval)
onSingleClick(view)
}
pendingItem = view
lastClickTime = currentTime
}
}
}
@@ -25,6 +25,18 @@ data class UserFaceModel(
val userId: String? = "",
val faceFeatureStr: String? = "",
val faceUpdateTimestamp:Long?=null,
val faceDeleted: Boolean?=false
val faceUpdateTimestamp: Long? = null,
/**
* 人脸删除标识
*/
val faceDeleted: Boolean? = false,
/**
* 会员编号
*/
val cardNo: String,
/**
* 是否会员
*/
val isMember: Boolean
) : Parcelable
@@ -16,7 +16,8 @@ object ApiClient {
// private const val DEVICE_BASE_URL = "http://device.shuziweidao.com:8889/"
private var DEVICE_BASE_URL = GlobalData.appBaseUrl
// private const val BASE_URL = "http://192.168.1.8:9092"
private const val TIME_OUT = 30L // 超时时间(秒)
// private const val TIME_OUT = 30L // 超时时间(秒)
private const val TIME_OUT = 60L // 超时时间(秒)
val okHttpClient = OkHttpClient.Builder()
.connectTimeout(TIME_OUT, TimeUnit.SECONDS)
@@ -11,12 +11,15 @@ import com.sw.platecabinet.model.response.EquipmentInfo
import com.sw.platecabinet.model.response.EquipmentUserInfo
import com.sw.platecabinet.model.response.SearchResult
import com.sw.platecabinet.model.response.UserFaceModel
import okhttp3.MultipartBody
import retrofit2.http.Body
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part
import retrofit2.http.Query
import retrofit2.http.Url
@@ -75,7 +78,7 @@ interface ApiService {
// @Body param: LoginParam
// ): ApiResponse<EquipmentUserInfo>
/**
/**
* 餐盘用户信息获取
*/
@POST//("/shuwei-zhct/swEquipmentRelUser/equipmentBoxLogin")
@@ -145,7 +148,7 @@ interface ApiService {
// ): ApiResponse<EquipmentUserInfo>
/**
/**
* 通过餐盘号获取信息
*/
@GET
@@ -155,8 +158,6 @@ interface ApiService {
): ApiResponse<EquipmentUserInfo>
/**
* 获取人脸增量数据
*
@@ -173,8 +174,8 @@ interface ApiService {
*/
@POST
suspend fun getFaceIncrementList(
@Url url:String = "${GlobalData.appBaseUrl}/terminal/neglect/common/app/faceFeature/increment/list",
@Body param:Map<String, Long>
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/common/app/faceFeature/increment/list",
@Body param: Map<String, Long>
): ApiResponse<List<UserFaceModel>?>
/**
@@ -185,5 +186,22 @@ interface ApiService {
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/common/app/getYxEquipmentByEquipmentCode"
): ApiResponse<DeviceConfig?>
/**
* 添加人脸数据
*/
@POST
@FormUrlEncoded
suspend fun addUserFace(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/common/app/addUserByFace",
@Field("featureChar") faceData:String,
@Field("url") imageUr:String
): ApiResponse<UserFaceModel?>
@Multipart
@POST
suspend fun uploadImage(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/upload",
@Part file: MultipartBody.Part,
): ApiResponse<String?>
}
@@ -1,17 +1,14 @@
package com.sw.platecabinet.repository
import com.sw.platecabinet.GlobalData
import com.sw.platecabinet.model.DeviceConfig
import com.sw.platecabinet.model.request.BindParam
import com.sw.platecabinet.model.request.EquipmentParam
import com.sw.platecabinet.model.request.LoginParam
import com.sw.platecabinet.model.request.SearchParam
import com.sw.platecabinet.model.response.ApiResponse
import com.sw.platecabinet.model.response.EquipmentInfo
import com.sw.platecabinet.model.response.EquipmentUserInfo
import com.sw.platecabinet.model.response.SearchResult
import com.sw.platecabinet.model.response.UserFaceModel
import com.sw.platecabinet.network.api.ApiService
import com.sw.platecabinet.utils.FileUtils
import java.io.File
/**
* 远程数据处理
@@ -73,19 +70,19 @@ class RemoteRepository constructor(
}
}
/**
* 登录
*/
suspend fun equipmentBoxLogin(param: LoginParam): ApiResponse<EquipmentUserInfo> {
return safeApiCall { apiService.equipmentBoxLogin(param = param) }
}
// /**
// * 登录
// */
// suspend fun equipmentBoxLogin(param: LoginParam): ApiResponse<EquipmentUserInfo> {
// return safeApiCall { apiService.equipmentBoxLogin(param = param) }
// }
/**
* 获取设备绑定用户列表
*/
suspend fun getEquipmentList(): ApiResponse<List<EquipmentUserInfo>> {
return safeApiCall { apiService.getEquipmentList() }
}
// /**
// * 获取设备绑定用户列表
// */
// suspend fun getEquipmentList(): ApiResponse<List<EquipmentUserInfo>> {
// return safeApiCall { apiService.getEquipmentList() }
// }
// /**
// * 餐盘用户信息绑定解绑
@@ -94,13 +91,13 @@ class RemoteRepository constructor(
// return safeApiCall { apiService.bindEquipment(param = param) }
// }
suspend fun plateBind(param: BindParam): ApiResponse<EquipmentUserInfo?> {
return safeApiCall { apiService.plateBind(param = param) }
}
// suspend fun plateBind(param: BindParam): ApiResponse<EquipmentUserInfo?> {
// return safeApiCall { apiService.plateBind(param = param) }
// }
suspend fun plateUnbind(id: Long?): ApiResponse<Any?> {
return safeApiCall { apiService.plateUnbind(id = id) }
}
// suspend fun plateUnbind(id: Long?): ApiResponse<Any?> {
// return safeApiCall { apiService.plateUnbind(id = id) }
// }
/**
* 用户信息模糊搜索
@@ -116,11 +113,11 @@ class RemoteRepository constructor(
return safeApiCall { apiService.findByPlateNumber(plateNumber = plateNumber) }
}
/**
* 获取人脸增量数据
*
* @param timeDate 时间戳(毫秒级)
*/
// /**
// * 获取人脸增量数据
// *
// * @param timeDate 时间戳(毫秒级)
// */
// suspend fun getHeartbeatInterface(timeDate: Long): ApiResponse<List<UserFaceModel>> {
// return safeApiCall { apiService.getHeartbeatInterface(timeDate = timeDate) }
// }
@@ -144,4 +141,19 @@ class RemoteRepository constructor(
suspend fun getDeviceConfig(): ApiResponse<DeviceConfig?> {
return safeApiCall { apiService.getDeviceConfig() }
}
suspend fun addUserFace(faceData: String, imageUr:String): ApiResponse<UserFaceModel?> {
return safeApiCall { apiService.addUserFace(faceData = faceData, imageUr = imageUr) }
}
suspend fun uploadImage(file: File): ApiResponse<String?> {
return safeApiCall {
val part = FileUtils.genRequestPart(file)
if (part == null) {
ApiResponse(code = "-1", msg = "解析图片失败")
} else {
apiService.uploadImage(file = part)
}
}
}
}
@@ -0,0 +1,59 @@
package com.sw.platecabinet.utils
import android.content.Context
import android.graphics.Bitmap
import android.os.Environment
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import kotlin.io.use
object BitmapSaver {
// 保存到公共目录(需WRITE_EXTERNAL_STORAGE权限)
fun saveToPublicDirectory(
bitmap: Bitmap,
folderName: String = Environment.DIRECTORY_PICTURES,
fileName: String,
format: Bitmap.CompressFormat = Bitmap.CompressFormat.PNG,
quality: Int = 100
): File? {
val dir = Environment.getExternalStoragePublicDirectory(folderName)
if (!dir.exists()) dir.mkdirs()
return saveBitmap(bitmap, File(dir, fileName), format, quality)
}
// 保存到应用私有目录(无需权限)
fun saveToAppFilesDir(
bitmap: Bitmap,
context: Context,
fileName: String,
format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG,
quality: Int = 100
): File? {
//val dir = context.getExternalFilesDir(null)
val dir = context.cacheDir
val cropFile = File(dir, "crop")
if (cropFile.exists().not()) {
cropFile.mkdirs()
}
return saveBitmap(bitmap, File(cropFile, fileName), format, quality)
}
private fun saveBitmap(
bitmap: Bitmap,
outputFile: File,
format: Bitmap.CompressFormat,
quality: Int
): File? {
return try {
FileOutputStream(outputFile).use { fos ->
bitmap.compress(format, quality, fos)
fos.flush()
}
outputFile
} catch (e: IOException) {
e.printStackTrace()
null
}
}
}
@@ -0,0 +1,163 @@
package com.sw.platecabinet.utils
import android.content.ContentUris
import android.content.Context
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import androidx.annotation.RequiresApi
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.asRequestBody
import timber.log.Timber
import java.io.File
import kotlin.io.copyTo
import kotlin.io.outputStream
import kotlin.io.use
import kotlin.text.equals
object FileUtils {
/**
* 从Uri获取File
* example: file:///data/user/0/com.sw.inbound/cache/IMG_17515262353556856678814444882273.jpg
*/
private fun getFileFromUri(context: Context, uri: Uri): File? {
Timber.d("getFileFromUri uri = ${uri.scheme}")
return when (uri.scheme) {
"file" -> File(uri.path ?: return null)
"content" -> {
try {
val inputStream = context.contentResolver.openInputStream(uri) ?: return null
val cacheDir = context.cacheDir
val file = File.createTempFile(
"upload_${System.currentTimeMillis()}",
".jpg",
cacheDir
)
file.outputStream().use { output ->
inputStream.copyTo(output)
}
file
} catch (e: Exception) {
Timber.e(e)
null
}
}
else -> null
}
}
/**
* 通过uri生成http请求体
*/
fun genRequestPart(context: Context, imageUri: Uri): MultipartBody.Part? {
Timber.d("genRequestPart imageUri = $imageUri")
// 1. 从Uri获取文件
val file = getFileFromUri(context, imageUri)
return genRequestPart(file)
}
fun genRequestPart(file: File?): MultipartBody.Part? {
if (file == null) {
Timber.e("getFileFromUri file is null")
return null
}
// 2. 创建请求体
val requestFile = file
.asRequestBody("application/octet-stream".toMediaTypeOrNull())
val imagePart = MultipartBody.Part.createFormData(
"file",
file.name,
requestFile
)
return imagePart
}
/**
* 通过Uri删除文件
* @param context 上下文
* @param uri 文件Uri
* @return Boolean 是否删除成功
*/
fun deleteFileWithUri(context: Context, uri: Uri): Boolean {
Timber.d("deleteFileWithUri uri = ${uri.scheme}")
return when {
// 1. 处理 content:// 类型的Uri (MediaStore)
uri.scheme.equals("content", ignoreCase = true) -> {
deleteContentUriFile(context, uri)
}
// 2. 处理 file:// 类型的Uri
uri.scheme.equals("file", ignoreCase = true) -> {
deleteFileUriFile(uri)
}
// 3. 其他情况尝试直接解析路径
else -> {
deleteFileFromPath(uri.path ?: return false)
}
}
}
// 删除Content Uri文件
private fun deleteContentUriFile(context: Context, uri: Uri): Boolean {
Timber.d("deleteContentUriFile uri = ${uri.scheme}")
return try {
context.contentResolver.delete(uri, null, null) > 0
} catch (e: SecurityException) {
// Android 10+需要特殊处理
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
deleteMediaStoreFile(context, uri)
} else {
false
}
} catch (e: Exception) {
Timber.e(e)
false
}
}
// Android 10+删除MediaStore文件
@RequiresApi(Build.VERSION_CODES.Q)
private fun deleteMediaStoreFile(context: Context, uri: Uri): Boolean {
Timber.d("deleteMediaStoreFile uri = ${uri.scheme}")
val contentResolver = context.contentResolver
val projection = arrayOf(MediaStore.MediaColumns._ID)
return try {
contentResolver.query(uri, projection, null, null, null)?.use { cursor ->
if (cursor.moveToFirst()) {
val id =
cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID))
val contentUri = ContentUris.withAppendedId(uri, id)
contentResolver.delete(contentUri, null, null) > 0
} else {
false
}
} ?: false
} catch (e: Exception) {
Timber.e(e)
false
}
}
// 删除File Uri文件
private fun deleteFileUriFile(uri: Uri): Boolean {
Timber.d("deleteFileUriFile uri = $uri")
return try {
File(uri.path ?: return false).delete()
} catch (e: Exception) {
Timber.e(e)
false
}
}
// 直接通过路径删除文件
private fun deleteFileFromPath(path: String): Boolean {
Timber.d("deleteFileFromPath path = $path")
return try {
File(path).delete()
} catch (e: Exception) {
Timber.e(e)
false
}
}
}
@@ -20,7 +20,7 @@ import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
public class FileUtil {
public class LogFileUtil {
private static boolean isSaveLog = true;
public static final String FILE_STR_PATH = App.getContext().getExternalCacheDir().getAbsolutePath();
@@ -624,7 +624,7 @@ public class FileUtil {
.append("\n");
}
String fileName = "log" + AppUtil.formatDateGetDay(System.currentTimeMillis()) + ".txt";
FileUtil.saveStrFile(sb.toString(), fileName, FILE_STR_PATH, true);
LogFileUtil.saveStrFile(sb.toString(), fileName, FILE_STR_PATH, true);
}
}.start();
@@ -1,22 +1,14 @@
package com.sw.platecabinet.viewmodel
import android.text.TextUtils
import com.sw.inbound.utils.DateTimeUtils
import com.sw.platecabinet.GlobalData
import com.sw.platecabinet.GlobalData.globalEquipmentCode
import com.sw.platecabinet.model.ErrorInfo
import com.sw.platecabinet.model.request.BindParam
import com.sw.platecabinet.model.request.EquipmentParam
import com.sw.platecabinet.model.request.SearchParam
import com.sw.platecabinet.model.response.EquipmentUserInfo
import com.sw.platecabinet.model.response.SearchResult
import com.sw.platecabinet.model.response.UserFaceModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.io.File
/**
* 管理员操作viewmodel
@@ -71,43 +63,43 @@ class SettingViewModel : BaseViewModel() {
// }
// }
/**
* 搜索会员信息列表
*/
fun getSearchMemberList(param:String, pageNum: Int = 1, pageSize: Int = 50) {
if (TextUtils.isEmpty(param)) return
currentPage = pageNum
var name =""
var phone =""
val num = param.toIntOrNull()
if (num != null) {
phone = param
} else {
name = param
}
launch {
val searchParam = SearchParam(
// appVersion = GlobalData.appVersion,
name = name,
phone = phone,
pageNum = pageNum,
pageSize = pageSize
)
isLoading = true
val response = repository.searchUser(searchParam)
isLoading = false
if (parseResponse(response)) {
response.data?.let { list ->
if (pageNum == 1) {
_searchMemberList.value = list
} else {
_searchMemberList.value = _searchMemberList.value.plus(list)
}
if (list.size >= pageSize) currentPage++
}
}
}
}
// /**
// * 搜索会员信息列表
// */
// fun getSearchMemberList(param:String, pageNum: Int = 1, pageSize: Int = 50) {
// if (TextUtils.isEmpty(param)) return
// currentPage = pageNum
// var name =""
// var phone =""
// val num = param.toIntOrNull()
// if (num != null) {
// phone = param
// } else {
// name = param
// }
// launch {
// val searchParam = SearchParam(
//// appVersion = GlobalData.appVersion,
// name = name,
// phone = phone,
// pageNum = pageNum,
// pageSize = pageSize
// )
// isLoading = true
// val response = repository.searchUser(searchParam)
// isLoading = false
// if (parseResponse(response)) {
// response.data?.let { list ->
// if (pageNum == 1) {
// _searchMemberList.value = list
// } else {
// _searchMemberList.value = _searchMemberList.value.plus(list)
// }
// if (list.size >= pageSize) currentPage++
// }
// }
// }
// }
// fun bindPlate(
// equipmentBoxCode: String,
@@ -158,35 +150,35 @@ class SettingViewModel : BaseViewModel() {
// }
// }
fun unbindPlate(id: Long?, block:(Pair<Boolean?, ErrorInfo>)-> Unit) {
block(null to ErrorInfo())
launchWithLoading {
val response = repository.plateUnbind(id)
if (parseResponse(response)) {
block(false to ErrorInfo())
} else {
block(false to ErrorInfo(response.code, response.msg.toString()))
}
}
}
fun bindPlate(bindParam: BindParam, block:(Pair<Boolean?, ErrorInfo>)-> Unit) {
launchWithLoading {
// val response = repository.plateBind(bindParam)
// fun unbindPlate(id: Long?, block:(Pair<Boolean?, ErrorInfo>)-> Unit) {
// block(null to ErrorInfo())
// launchWithLoading {
// val response = repository.plateUnbind(id)
// if (parseResponse(response)) {
// var userInfo = response.data
// if (userInfo == null) {
// userInfo = EquipmentUserInfo()
// userInfo.equipmentBoxCode = bindParam.equipmentBoxCode
// userInfo.plateNumber = bindParam.plateNumber
// userInfo.equipmentCode = bindParam.equipmentCode
// }
// block(true to ErrorInfo(equipmentUserInfo = userInfo))
// block(false to ErrorInfo())
// } else {
// block(true to ErrorInfo(response.code, response.msg.toString()))
// block(false to ErrorInfo(response.code, response.msg.toString()))
// }
}
}
// }
// }
// fun bindPlate(bindParam: BindParam, block:(Pair<Boolean?, ErrorInfo>)-> Unit) {
// launchWithLoading {
//// val response = repository.plateBind(bindParam)
//// if (parseResponse(response)) {
//// var userInfo = response.data
//// if (userInfo == null) {
//// userInfo = EquipmentUserInfo()
//// userInfo.equipmentBoxCode = bindParam.equipmentBoxCode
//// userInfo.plateNumber = bindParam.plateNumber
//// userInfo.equipmentCode = bindParam.equipmentCode
//// }
//// block(true to ErrorInfo(equipmentUserInfo = userInfo))
//// } else {
//// block(true to ErrorInfo(response.code, response.msg.toString()))
//// }
// }
// }
fun findByPlateNumber(
plateNumber: String,
@@ -234,4 +226,25 @@ class SettingViewModel : BaseViewModel() {
}
}
/**
* 添加人脸数据
*/
fun addUserFace(faceData: String, imageFile: File, block: (Boolean, UserFaceModel?) -> Unit) {
launch {
val imageResp = repository.uploadImage(imageFile)
val imageUrl = imageResp.data?:""
if (imageUrl.isBlank()) {
block(false, null)
return@launch
}
val faceResp = repository.addUserFace(faceData, imageUrl)
val status = parseResponse(faceResp)
if (status.not()) {
block(false, null)
return@launch
}
block(true, faceResp.data)
}
}
}
@@ -58,38 +58,45 @@ class UserViewModel : BaseViewModel() {
/**
* 获取人脸数据
*/
fun getUserFaceCache(pageNo: Int = 1, pageSize: Int = PAGE_SIZE, refreshFaceBlock: () -> Unit={}) {
fun getUserFaceCache(
pageNo: Int = 1,
pageSize: Int = PAGE_SIZE,
callback: (Boolean, String?) -> Unit = { statue, msg -> }
) {
var currentPageNo = pageNo
launchWithLoading {
val response = repository.getUserFaceCache(pageNum = pageNo, pageSize = pageSize)
if (parseResponse(response)) {
// 获取成功一次后缓存状态
SPUtil.getInstance().put(GlobalKey.KEY_FIRST_RUN, true)
val list: List<UserFaceModel> = response.data ?: emptyList()
if (pageNo == 1 && list.isEmpty()) {
return@launchWithLoading
val status = parseResponse(response)
if (status.not()) {
callback(false, response.msg)
return@launchWithLoading
}
// 获取成功一次后缓存状态
SPUtil.getInstance().put(GlobalKey.KEY_FIRST_RUN, true)
val list: List<UserFaceModel> = response.data ?: emptyList()
if (pageNo == 1 && list.isEmpty()) {
callback(true, "暂无数据")
return@launchWithLoading
}
val faceEntityList = list.map {
FaceEntity(it.userId, null, Base64.decode(it.faceFeatureStr))
}
withContext(Dispatchers.Default) {
faceApi.updateFaceData(pageNo, faceEntityList)
if (list.size >= pageSize) {
faceTimestamp = list.last().faceUpdateTimestamp ?: 0
currentPageNo++
getUserFaceCache(
pageNo = currentPageNo,
callback = callback
)
return@withContext
}
val faceEntityList = list.map {
FaceEntity(it.userId, null, Base64.decode(it.faceFeatureStr))
}
withContext(Dispatchers.Default) {
faceApi.updateFaceData(pageNo, faceEntityList)
refreshFaceBlock()
if (list.size >= pageSize) {
faceTimestamp = list.last().faceUpdateTimestamp ?: 0
currentPageNo++
getUserFaceCache(
pageNo = currentPageNo,
refreshFaceBlock = refreshFaceBlock
)
return@withContext
}
if (list.isNotEmpty()) {
faceTimestamp = list.last().faceUpdateTimestamp ?: 0
}
SpTool.lastFaceTimestamp = faceTimestamp
//activeEngine()
if (list.isNotEmpty()) {
faceTimestamp = list.last().faceUpdateTimestamp ?: 0
}
SpTool.lastFaceTimestamp = faceTimestamp
callback(true, "查询完成")
}
}
}
+20 -3
View File
@@ -1,18 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:width="600dp"
android:height="1024dp">
<bitmap android:src="@drawable/bg"/>
<bitmap android:src="@drawable/bg" />
</item>
<item
android:width="337dp"
android:height="322dp"
android:gravity="top|center_horizontal"
android:top="112dp">
<bitmap android:src="@drawable/img_init" android:scaleType="fitCenter"/>
<bitmap
android:scaleType="fitCenter"
android:src="@drawable/img_init" />
</item>
<!-- TODO 临时测试 -->
<!-- <item-->
<!-- android:width="828dp"-->
<!-- android:height="1768dp">-->
<!-- <bitmap android:src="@drawable/bg" />-->
<!-- </item>-->
<!-- <item-->
<!-- android:width="337dp"-->
<!-- android:height="322dp"-->
<!-- android:gravity="top|center_horizontal"-->
<!-- android:top="360dp">-->
<!-- <bitmap-->
<!-- android:scaleType="fitCenter"-->
<!-- android:src="@drawable/img_init" />-->
<!-- </item>-->
</layer-list>
@@ -6,14 +6,14 @@
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:layout_width="337dp"
android:layout_height="322dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="112dp"
android:src="@drawable/img_init"
tools:ignore="ContentDescription"
android:visibility="invisible"/>
<!-- <ImageView-->
<!-- android:layout_width="337dp"-->
<!-- android:layout_height="322dp"-->
<!-- android:layout_gravity="center_horizontal"-->
<!-- android:layout_marginTop="112dp"-->
<!-- android:src="@drawable/img_init"-->
<!-- tools:ignore="ContentDescription"-->
<!-- android:visibility="invisible"/>-->
<TextView
android:id="@+id/btnInit"
+3 -3
View File
@@ -47,15 +47,15 @@
android:id="@+id/takeButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="-20dp">
android:layout_marginTop="-35dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="60dp"
android:text="点击取盘"
android:textColor="#FFCC99"
android:textSize="24sp"
android:layout_marginHorizontal="30dp"
android:layout_marginVertical="20dp"
android:layout_marginHorizontal="60dp"
android:layout_marginVertical="50dp"
android:paddingHorizontal="45dp"
android:gravity="center"
android:background="@drawable/btn_outline"
+75
View File
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_dialog"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginTop="30dp"
android:src="@drawable/ic_tip_warn_unbind" />
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:textColor="#ffffffff"
android:textSize="28sp"
android:textStyle="bold"
tools:text="即将解除【曹德磊】的餐盘"
android:visibility="gone" />
<TextView
android:id="@+id/tvContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
tools:text="解绑后该用户将无法使用餐盘柜"
android:textColor="#ff999999"
android:textSize="24sp"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="30dp"
android:background="#ff333333" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="90dp"
android:orientation="horizontal">
<TextView
android:id="@+id/btnCancel"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="取消"
android:textColor="#ff999999"
android:textSize="30sp"
android:textStyle="bold" />
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="#333333" />
<TextView
android:id="@+id/btnConfirm"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="确定"
android:textColor="#ffffcc99"
android:textSize="30sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
@@ -15,6 +15,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
app:spanCount="2"
tools:itemCount="22"
android:overScrollMode="never"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
tools:listitem="@layout/item_bind_view"/>
@@ -33,10 +33,10 @@ public class FaceApi {
public void updateFaceData(int index, List<FaceEntity> list) {
Log.d(TAG, "updateFaceData: index = " + index + ", listSize = " + list.size());
FaceDao faceDao = FaceDatabase.getInstance(App.getContext()).faceDao();
if (index == 1) {
faceDao.deleteAll();
faceDao.resetId();
}
//if (index == 1) {
// faceDao.deleteAll();
// faceDao.resetId();
//}
faceDao.insert(list);
}
@@ -102,4 +102,9 @@ public class FaceApi {
return faceDao.queryByUserName(userName);
}
public int queryFaceCount() {
FaceDao faceDao = FaceDatabase.getInstance(App.getContext()).faceDao();
return faceDao.getFaceCount();
}
}
@@ -45,10 +45,15 @@ public class FaceEntity implements Parcelable {
@ColumnInfo(name = "register_time")
private long registerTime;
/**
* 用户类型:1-普通会员、2-临时用户、3-内部员工、或者其它待定类型
* 用户类型:1-普通会员、2-临时用户、或者其它待定类型
*/
@ColumnInfo(name = "user_type")
private String userType;
/**
* 会员编号
*/
@ColumnInfo(name = "cardNo")
private String cardNo;
@Ignore
private int trackId;//人脸追踪ID
@@ -66,6 +71,8 @@ public class FaceEntity implements Parcelable {
this.imagePath = faceEntity.imagePath;
this.featureData = faceEntity.featureData;
this.registerTime = faceEntity.registerTime;
this.userType = faceEntity.getUserType();
this.cardNo = faceEntity.getCardNo();
}
@@ -75,6 +82,8 @@ public class FaceEntity implements Parcelable {
userName = in.readString();
imagePath = in.readString();
featureData = in.createByteArray();
userType = in.readString();
cardNo = in.readString();
}
public static final Creator<FaceEntity> CREATOR = new Creator<FaceEntity>() {
@@ -145,6 +154,16 @@ public class FaceEntity implements Parcelable {
this.userType = userType;
}
public String getCardNo() {
return cardNo;
}
public void setCardNo(String cardNo) {
this.cardNo = cardNo;
}
@Override
public int describeContents() {
return 0;
@@ -158,10 +177,9 @@ public class FaceEntity implements Parcelable {
dest.writeString(imagePath);
dest.writeByteArray(featureData);
dest.writeString(userType);
dest.writeString(cardNo);
}
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -176,12 +194,13 @@ public class FaceEntity implements Parcelable {
TextUtils.equals(this.userName, that.userName) &&
TextUtils.equals(this.imagePath, that.imagePath) &&
Arrays.equals(featureData, that.featureData) &&
TextUtils.equals(this.userType, that.userType);
TextUtils.equals(this.userType, that.userType) &&
TextUtils.equals(this.cardNo, that.cardNo);
}
@Override
public int hashCode() {
int result = Objects.hash(faceId, registerTime, userName, imagePath, userType);
int result = Objects.hash(faceId, registerTime, userName, imagePath, userType, cardNo);
result = 31 * result + Arrays.hashCode(featureData);
return result;
}