Author SHA1 Message Date
lvmeng 67f5d07f25 优化 2026-02-04 18:01:15 +08:00
lvmeng 79e089363d 优化重复注册 2026-02-04 10:20:34 +08:00
lvmeng 16c9e5f61a 优化重复注册 2026-02-04 09:23:19 +08:00
lvmeng a8bbf68b9b 优化 2026-02-03 18:51:13 +08:00
mazengfei 0024ea2775 优化 2026-02-03 16:19:42 +08:00
lvmeng 9fea23303c 打开app不再清除本地数据,不再发送数据到结算终端,改为新增人脸采集接口,全量增量接口优化 2026-02-03 14:59:22 +08:00
mazengfei c5325f6ad6 优化流程,测试不再验证活体 2026-02-02 16:36:24 +08:00
mazengfei e1e754be9f 调整提示音 2026-01-29 14:31:23 +08:00
mazengfei 1d0e6d4bca 隐藏toast 2026-01-26 17:32:50 +08:00
lvmeng 8f788529d2 增加日志 2026-01-26 15:16:35 +08:00
mazengfei 5bc244dde5 增加无餐盘提醒 2026-01-22 15:49:36 +08:00
lvmeng d4f9ace67b 新增识别提示 2026-01-22 15:10:12 +08:00
lvmeng 855a84684c 人脸识别小于阈值时增加重试次数,降低识别错误情况 2026-01-22 10:30:57 +08:00
mazengfei f2bedb7777 优化 2026-01-21 17:58:55 +08:00
mazengfei aad4f299aa 优化 2026-01-21 15:53:53 +08:00
lvmeng 176c555860 采集优化 2026-01-21 14:27:16 +08:00
mazengfei de4e5d57e2 优化 2026-01-21 13:00:58 +08:00
mazengfei 8326ff0a03 优化 2026-01-20 19:04:17 +08:00
mazengfei b7408dc6c4 优化 2026-01-20 14:00:28 +08:00
mazengfei ec12017fd4 优化 2026-01-20 12:23:38 +08:00
lvmeng 662e2c63e1 优化--- 2026-01-20 08:32:14 +08:00
mazengfei f473d1f174 优化 2026-01-19 17:55:16 +08:00
lvmeng 3b51e6f3f2 日志按钮优化 2026-01-19 14:35:00 +08:00
lvmeng 40d41d1afb 增加实时日志查询 2026-01-19 14:32:48 +08:00
mazengfei b23045194a 增加日志 2026-01-19 13:08:28 +08:00
mazengfei 1b4762916a 餐盘柜优化-不绑定用户可多次取盘 2026-01-16 16:45:13 +08:00
mazengfei cf2d2db42f 餐盘柜优化-绑定临时用户 2026-01-16 16:14:38 +08:00
mazengfei 8337b3fabb 餐盘柜优化 2026-01-16 11:53:44 +08:00
mazengfei ab58651733 取盘优化 2026-01-15 11:39:13 +08:00
mazengfei aadba7efd5 增加人脸采集功能;
识别人脸成功后打开有餐盘的第一个柜子;
扫码打开已绑定的柜子,无则打开第一个空柜子并放入餐盘;
设置页面问题优化;
2026-01-14 19:00:20 +08:00
mazengfei 11a0a41e3c Rename .java to .kt 2026-01-14 19:00:20 +08:00
马增飞 582be4e9b7 绑定解绑功能调试;其它优化 2026-01-05 18:38:10 +08:00
马增飞 17d47a876f 增加配置接口;调试人脸识别;验证手机号密码登录和人脸登录功能; 2025-12-29 18:41:48 +08:00
马增飞 6c28260069 Merge remote-tracking branch 'refs/remotes/origin/main_新版_lvmeng_1222' into main_新版_lvmeng_1229 2025-12-29 09:57:05 +08:00
马增飞 7097bbf907 优化 2025-12-29 09:55:49 +08:00
lvmeng c046441d4e 优化 2025-12-26 14:16:01 +08:00
lvmeng 7a6c4012d1 餐盘柜接口改造 2025-12-26 14:05:41 +08:00
mazengfei 5e4dc4baa4 处理人脸数据不同步 2025-11-07 20:37:50 +08:00
mazengfei 4231c68cdc 添加方法 2025-11-07 15:18:15 +08:00
mazengfei 55ba11bf34 修改人脸同步逻辑 2025-11-07 10:55:48 +08:00
马增飞 abe594fb66 设备联调 2025-11-06 19:34:42 +08:00
lvmeng fb074c1aaa 修改开机初始化页面 2025-09-26 16:11:00 +08:00
lvmeng 73456046c5 增加初始化页面 2025-09-17 17:42:38 +08:00
lvmeng 7f56d4d1a1 修改待机也按钮名称 2025-09-17 15:45:00 +08:00
lvmeng 1658b6fe6f logo修改 2025-09-12 13:32:10 +08:00
lvmeng e9a366b52a 增加心跳处理人脸数据增减 2025-09-09 15:59:29 +08:00
lvmeng 3b492cb710 待机页面优化 2025-09-05 17:59:30 +08:00
lvmeng eaafeab1d8 增加余额不足判断 2025-09-05 15:29:43 +08:00
zxj 578a9aaced 添加注释 2025-08-22 16:28:20 +08:00
zxj cd375bfaf8 修复了登录成功后跳转界面错误问题 2025-08-21 15:50:05 +08:00
zxj 7a832ec40d 调整了初始化界面逻辑 2025-08-14 13:36:45 +08:00
zxj b83a442fe3 修改了配色,添加了解除绑定提示 2025-08-08 17:26:09 +08:00
zxj 2c10731b8d 修复了识别追踪信息功能 2025-08-08 10:50:11 +08:00
122 changed files with 6810 additions and 1213 deletions
Generated
+1
View File
@@ -0,0 +1 @@
SmartPlateCabinet
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" />
</component>
</project>
+3 -6
View File
@@ -2,20 +2,17 @@
<project version="4"> <project version="4">
<component name="deploymentTargetSelector"> <component name="deploymentTargetSelector">
<selectionStates> <selectionStates>
<SelectionState runConfigName="SmartPlateCabinet.app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-07-25T08:39:03.160022800Z"> <DropdownSelection timestamp="2026-01-22T07:28:00.026342Z">
<Target type="DEFAULT_BOOT"> <Target type="DEFAULT_BOOT">
<handle> <handle>
<DeviceId pluginId="Default" identifier="serial=?;connection=93a2ccce" /> <DeviceId pluginId="Default" identifier="serial=192.168.1.128:5555;connection=c168ec92" />
</handle> </handle>
</Target> </Target>
</DropdownSelection> </DropdownSelection>
<DialogSelection /> <DialogSelection />
</SelectionState> </SelectionState>
<SelectionState runConfigName="LoginByFaceActivity">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
</selectionStates> </selectionStates>
</component> </component>
</project> </project>
+1 -1
View File
@@ -6,7 +6,7 @@
<GradleProjectSettings> <GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" /> <option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="17" /> <option name="gradleJvm" value="azul-17" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />
+1 -2
View File
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="zulu-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">
+12 -5
View File
@@ -22,14 +22,14 @@ android {
minSdk = 24 minSdk = 24
targetSdk = 35 targetSdk = 35
versionCode = 1 versionCode = 1
versionName = "1.0" versionName = "1.1"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
// ndk { ndk {
// abiFilters.addAll(listOf("armeabi-v7a"/*, "arm64-v8a"*/)) abiFilters.addAll(listOf("armeabi-v7a"/*, "arm64-v8a"*/))
// } }
setProperty("archivesBaseName", "智能餐盘柜_${versionName}") setProperty("archivesBaseName", "zncpg_${versionName}")
} }
buildTypes { buildTypes {
@@ -93,4 +93,11 @@ dependencies {
implementation(libs.androidx.activity.ktx) implementation(libs.androidx.activity.ktx)
implementation(libs.androidx.fragment.ktx) implementation(libs.androidx.fragment.ktx)
// implementation(libs.adapter.coroutines) // implementation(libs.adapter.coroutines)
implementation("androidx.work:work-runtime-ktx:2.8.1")
implementation(libs.core)
implementation(libs.android.core)
implementation("org.greenrobot:eventbus:3.3.1")
} }
+34 -36
View File
@@ -14,62 +14,60 @@
<uses-permission <uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE" android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" /> android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application <application
android:name=".MyApp" android:name=".MyApp"
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_logo512"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_logo512"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.SmartPlateCabinet" android:theme="@style/Theme.SmartPlateCabinet"
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="true"
tools:targetApi="31"> tools:targetApi="31">
<!-- 注册BootReceiver,监听开机完成广播 -->
<receiver
android:name=".receiver.BootReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<activity
android:name=".activity.UnBindDialogActivity"
android:exported="false"
android:theme="@style/DialogActivity" />
<activity <activity
android:name=".activity.InitActivity" android:name=".activity.InitActivity"
android:exported="true"> android:exported="false"
android:launchMode="singleTask">
</activity>
<activity
android:name=".activity.LoginByPwdActivity"
android:exported="false"
android:launchMode="singleTask"/>
<activity
android:name=".activity.LoginByFaceActivity"
android:exported="true"
android:launchMode="singleTask"/>
<activity
android:name=".activity.DeviceInitActivity"
android:exported="true"
android:launchMode="singleTask">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".activity.TestActivity"
android:exported="true" />
<activity
android:name=".activity.LoginByPwdActivity"
android:exported="true"
android:launchMode="singleTask">
<!-- <intent-filter> -->
<!-- <action android:name="android.intent.action.MAIN" /> -->
<!-- <category android:name="android.intent.category.LAUNCHER" /> -->
<!-- </intent-filter> -->
</activity>
<activity
android:name=".activity.LoginByFaceActivity"
android:exported="true"
android:launchMode="singleTask">
<!-- <intent-filter>-->
<!-- <action android:name="android.intent.action.MAIN" />-->
<!-- <category android:name="android.intent.category.LAUNCHER" />-->
<!-- </intent-filter>-->
</activity>
<activity <activity
android:name=".activity.MainActivity" android:name=".activity.MainActivity"
android:exported="true"> android:exported="false">
<!-- <intent-filter> -->
<!-- <action android:name="android.intent.action.MAIN" /> -->
<!-- <category android:name="android.intent.category.LAUNCHER" /> -->
<!-- </intent-filter> -->
</activity> </activity>
</application> </application>
@@ -28,7 +28,18 @@ object GlobalData {
var appId = "Hkz1rBk6PZXbS8KwKr67K2eZtsz8bRoMHLg64bUdgCZj" var appId = "Hkz1rBk6PZXbS8KwKr67K2eZtsz8bRoMHLg64bUdgCZj"
var sdkKey = "AabXs3sHM8UhhE7oCGf4LVMLrdkGb1nJNksbjjTdVt7k" var sdkKey = "AabXs3sHM8UhhE7oCGf4LVMLrdkGb1nJNksbjjTdVt7k"
var activeKey = "085F-118G-Q391-53YL" var activeKey = "85Q1-1216-X3DH-QYTJ"//"085F-118G-Q3J6-35UX" //"085F-118G-Q391-53YL"
/**
* 具体业务baseurl
*/
var appBaseUrl: String = "https://dev.yixiong-tech.com:8081"
/**
* 设备id
*/
var deviceId: String = ""
var restId: String = ""
} }
/** /**
@@ -45,7 +56,9 @@ object Constants {
* key * key
*/ */
object GlobalKey { object GlobalKey {
const val KEY_EQUIPMENT_INFO = "equipmentInfo"
const val KEY_TOKEN = "tokenKey" const val KEY_TOKEN = "tokenKey"
const val KEY_FIRST_RUN = "firstRun" const val KEY_FIRST_RUN = "firstRun"
const val KEY_USER_INFO = "userInfoKey" const val KEY_USER_INFO = "userInfoKey"
const val PARAM_EQUIPMENT_INFO = "equipmentUserInfo"
} }
@@ -1,26 +1,47 @@
package com.sw.platecabinet package com.sw.platecabinet
import android.util.Log
import com.sw.plate.App import com.sw.plate.App
import com.sw.plate.utils.AppUtil import com.sw.plate.utils.AppUtil
import com.sw.platecabinet.model.response.EquipmentUserInfo
import com.sw.platecabinet.utils.CrashHandler
import timber.log.Timber import timber.log.Timber
class MyApp : App() { class MyApp : App() {
companion object { companion object {
const val DEBUG: Boolean = true const val DEBUG: Boolean = true
var instance: MyApp? = null
var plateList: List<EquipmentUserInfo>? = null
} }
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
instance = this
Timber.plant(Timber.DebugTree()) Timber.plant(Timber.DebugTree())
Timber.d("初始化") Timber.d("初始化")
initGlobalData() initGlobalData()
// 初始化崩溃处理器
CrashHandler.init(this)
} }
/** /**
* 初始化全局数据 * 初始化全局数据
*/ */
private fun initGlobalData() { private fun initGlobalData() {
var deviceId = AppUtil.getUDID(this)
// deviceId = "be154831-3466-3ba2-a2ea-57652c919fed"
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.appVersion = AppUtil.getAppVersionCode(this).toString()
GlobalData.globalEquipmentCode = "202501171634" GlobalData.globalEquipmentCode = "202501171634"
// GlobalData.appBaseUrl = "http://192.168.1.201:14801"
} }
} }
@@ -1,10 +1,10 @@
package com.sw.platecabinet.activity package com.sw.platecabinet.activity
import android.app.Dialog
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.text.TextUtils
import android.util.Log
import android.view.KeyEvent import android.view.KeyEvent
import android.view.View import android.view.View
import android.view.WindowInsetsController import android.view.WindowInsetsController
@@ -14,25 +14,31 @@ import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import com.google.gson.Gson
import com.sw.inbound.utils.DateTimeUtils 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.ScanGunKeyEventHelper
import com.sw.plate.utils.ToastUtils import com.sw.plate.utils.arcface.facedb.FaceDatabase
import com.sw.plate.utils.comn.SerialApi import com.sw.plate.utils.arcface.facedb.entity.FaceEntity
import com.sw.plate.utils.comn.SerialPortManager import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel
import com.sw.platecabinet.MyApp
import com.sw.platecabinet.R import com.sw.platecabinet.R
import com.sw.platecabinet.databinding.ItemTitleTimeBinding import com.sw.platecabinet.databinding.ItemTitleTimeBinding
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.EquipmentUserInfo
import com.sw.platecabinet.model.response.UserFaceModel
import com.sw.platecabinet.utils.IntervalExecutor
import com.sw.platecabinet.utils.PermissionHelper import com.sw.platecabinet.utils.PermissionHelper
import com.sw.platecabinet.view.CustomDialog import com.sw.platecabinet.utils.PlateUtils
import com.sw.platecabinet.utils.SpTool
import com.sw.platecabinet.view.CustomLoadingDialog
import com.sw.platecabinet.viewmodel.SettingViewModel import com.sw.platecabinet.viewmodel.SettingViewModel
import com.sw.platecabinet.viewmodel.UserViewModel import com.sw.platecabinet.viewmodel.UserViewModel
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
@@ -44,15 +50,15 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
private var headerBinding: ItemTitleTimeBinding? = null private var headerBinding: ItemTitleTimeBinding? = null
protected lateinit var context: Context protected lateinit var context: Context
private var timeJob: Job? = null private var timeJob: Job? = null
private var mDialogWaiting: CustomDialog? = null private var mDialogWaiting: CustomLoadingDialog? = null
private val permissionHelpers = mutableMapOf<Int, PermissionHelper>() private val permissionHelpers = mutableMapOf<Int, PermissionHelper>()
protected var keyEventHelper: ScanGunKeyEventHelper? = null protected var keyEventHelper: ScanGunKeyEventHelper? = null
// 管理员对应的viewmodel // 管理员对应的viewmodel
private val settingViewModel by viewModels<SettingViewModel>() val settingViewModel by viewModels<SettingViewModel>()
// 用户对应的viewModel // 用户对应的viewModel
protected val viewModel by viewModels<UserViewModel>() protected val userViewModel by viewModels<UserViewModel>()
private var startTime: Long = 0 private var startTime: Long = 0
@@ -97,7 +103,28 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
* 处理扫描枪数据 * 处理扫描枪数据
*/ */
protected open fun handleScanKeyInfo(scanInfo: String) { protected open fun handleScanKeyInfo(scanInfo: String) {
settingViewModel.findByPlateNumber(plateNumber = scanInfo) // settingViewModel.findByPlateNumber(plateNumber = scanInfo)
var plateNumber = scanInfo
if (scanInfo.length > 6) {
plateNumber = scanInfo.take(6)
}
Log.d("TAG", "handleScanKeyInfo: " + Gson().toJson(MyApp.plateList))
if (MyApp.plateList == null) {
initPlateList()
}
//柜子未关联该餐盘,直接找空位置放入盘子
val item = MyApp.plateList?.firstOrNull { it.plateNumber.isNullOrBlank() }
if (item == null) {
//柜子已满
MainActivity.start(context = context, pageType = PageType.PLATE_CABINET_FULL)
return
}
//找到位置,放入餐盘,清除已有id和时间
item.plateNumber = plateNumber
item.faceId = null
item.updateTime = null
PlateUtils.open(this, item.equipmentBoxCode, true)
SpTool.savePlateData(MyApp.plateList!!)
} }
override fun dispatchKeyEvent(event: KeyEvent): Boolean { override fun dispatchKeyEvent(event: KeyEvent): Boolean {
@@ -112,12 +139,12 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
fun updateTime() { fun updateTime() {
headerBinding?.let { headerBinding?.let {
it.tvLeftDate.setClickListeners( it.tvLeftDate.clickWithCoroutines(
onDoubleClick = { onDoubleClick = {
onLeftDoubleClick() onLeftDoubleClick()
} }
) )
it.tvRightTime.setClickListeners( it.tvRightTime.clickWithCoroutines(
onDoubleClick = { onDoubleClick = {
onRightDoubleClick() onRightDoubleClick()
} }
@@ -181,7 +208,15 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
enforceImmersiveMode() enforceImmersiveMode()
} }
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus) {
disableSystemUICompletely()
}
}
private fun disableSystemUICompletely() { private fun disableSystemUICompletely() {
Timber.d("disableSystemUICompletely")
// 禁用系统手势(Android 10+ // 禁用系统手势(Android 10+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.insetsController?.systemBarsBehavior = window.insetsController?.systemBarsBehavior =
@@ -206,6 +241,7 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
private fun enforceImmersiveMode() { private fun enforceImmersiveMode() {
Timber.d("enforceImmersiveMode")
// 持续强制隐藏系统栏(防止手势触发) // 持续强制隐藏系统栏(防止手势触发)
window.decorView.postDelayed({ window.decorView.postDelayed({
window.decorView.systemUiVisibility = ( window.decorView.systemUiVisibility = (
@@ -219,23 +255,28 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
/** /**
* 显示等待提示框 * 显示等待提示框
*/ */
fun showWaitingDialog(tip: String?): Dialog? { fun showWaitingDialog(tip: String?) {
runOnUiThread {
hideWaitingDialog() hideWaitingDialog()
val view = View.inflate(this, R.layout.dialog_waiting, null) val view = View.inflate(this, R.layout.dialog_waiting, null)
if (!TextUtils.isEmpty(tip)) (view.findViewById<View?>(R.id.tvTip) as TextView).setText(tip) if (!TextUtils.isEmpty(tip)) (view.findViewById<View?>(R.id.tvTip) as TextView).setText(
mDialogWaiting = CustomDialog(this, view, R.style.MyDialog) tip
)
mDialogWaiting = CustomLoadingDialog(this, view, R.style.MyDialog)
mDialogWaiting!!.show() mDialogWaiting!!.show()
mDialogWaiting!!.setCancelable(true) mDialogWaiting!!.setCancelable(true)
return mDialogWaiting }
} }
/** /**
* 隐藏等待提示框 * 隐藏等待提示框
*/ */
fun hideWaitingDialog() { fun hideWaitingDialog() {
runOnUiThread {
mDialogWaiting?.dismiss() mDialogWaiting?.dismiss()
mDialogWaiting = null mDialogWaiting = null
} }
}
protected abstract fun inflateViewBinding(): VB protected abstract fun inflateViewBinding(): VB
@@ -248,104 +289,237 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
*/ */
protected open fun registerDataChange() { protected open fun registerDataChange() {
Timber.d("registerDataChange") Timber.d("registerDataChange")
lifecycleScope.launch { // lifecycleScope.launch {
viewModel.showLoading.collect { // viewModel.showLoading.collect {
Timber.d("registerDataChange 用户 showLoading = $it") // Timber.d("registerDataChange 用户 showLoading = $it")
if (it) { // if (it) {
showWaitingDialog("") // showWaitingDialog("")
} else { // } else {
hideWaitingDialog() // hideWaitingDialog()
} // }
} // }
} // }
lifecycleScope.launch { // lifecycleScope.launch {
viewModel.currentUserInfo.drop(1).collect { // viewModel.currentUserInfo.collect {
if (it == null) return@collect // if (it == null) return@collect
handleLoginSuccess(it, false) //// it.showBalanceNotEnoughDialog = true
} // handleLoginSuccess(it, false)
} // }
lifecycleScope.launch { // }
settingViewModel.showLoading.collect { // lifecycleScope.launch {
Timber.d("registerDataChange 管理员 showLoading = $it") // settingViewModel.showLoading.collect {
if (it) { // Timber.d("registerDataChange 管理员 showLoading = $it")
showWaitingDialog("") // if (it) {
} else { // showWaitingDialog("")
hideWaitingDialog() // } else {
} // hideWaitingDialog()
} // }
} // }
lifecycleScope.launch { // }
settingViewModel.searchUserInfo.drop(1).collect { // lifecycleScope.launch {
Timber.d("registerDataChange 管理员 currentUserInfo = $it") // settingViewModel.searchUserInfo.collect {
if (it == null) return@collect // Timber.d("registerDataChange 管理员 currentUserInfo = $it")
handleLoginSuccess(it, true) // if (it == null) return@collect
} // handleLoginSuccess(it, true)
} // }
lifecycleScope.launch { // }
settingViewModel.equipmentList.drop(1).collect { // lifecycleScope.launch {
if (it.isEmpty()) return@collect // settingViewModel.equipmentList.collect {
val unbindList = it.filter { !it.isBound() } // loadEquipmentList(it)
if (unbindList.isEmpty()) { // }
MainActivity.start(context = context, pageType = PageType.PLATE_CABINET_FULL) // }
} else {
val firstInfo = unbindList[0]
val currentUserInfo = settingViewModel.searchUserInfo.value
firstInfo.plateNumber = currentUserInfo?.plateNumber ?: ""
MainActivity.start(
context = context,
pageType = PageType.BIND_PLATE,
equipmentUserInfo = firstInfo
)
}
}
}
} }
// private fun loadEquipmentList(items: List<EquipmentUserInfo>) {
// if (items.isEmpty()) return
// val unbindList = items.filter { !it.isBound() }
// if (unbindList.isEmpty()) {
// MainActivity.start(context = context, pageType = PageType.PLATE_CABINET_FULL)
// } else {
// val firstInfo = unbindList[0]
// val currentUserInfo = settingViewModel.searchUserInfo.value
// firstInfo.plateNumber = currentUserInfo?.plateNumber ?: ""
// MainActivity.start(
// context = context,
// pageType = PageType.BIND_PLATE,
// equipmentUserInfo = firstInfo
// )
// }
// }
/** /**
* 处理登录成功操作 * 处理登录成功操作
* @param isAdmin true 管理员 执行绑盘此操作 false 用户,提示错误 * @param isAdmin true 管理员 执行绑盘此操作 false 用户,提示错误
*/ */
open fun handleLoginSuccess(equipmentUserInfo: EquipmentUserInfo, isAdmin: Boolean) { open fun handleLoginSuccess(equipmentUserInfo: EquipmentUserInfo, isAdmin: Boolean) {
Timber.d("handleLoginSuccess isAdmin = $isAdmin") // Timber.d("handleLoginSuccess isAdmin = $isAdmin")
viewModel.resetUserInfo() // equipmentUserInfo.isIntercept = false
if (equipmentUserInfo.isOtherEquipment()) { // val cardBalance = equipmentUserInfo.cardBalance ?: 0.toDouble()
MainActivity.start( // val balanceIsNotEnough = cardBalance <= 0.toDouble()
context, // if (balanceIsNotEnough && equipmentUserInfo.showBalanceNotEnoughDialog) {
pageType = PageType.PLATE_TIP, // //提示余额不足弹窗
equipmentUserInfo = equipmentUserInfo // equipmentUserInfo.isIntercept = true
) // BalanceNotEnoughDialog(this).show()
return // return
} // }
if (equipmentUserInfo.equipmentBoxCode?.isNotEmpty() == true) { // viewModel.resetUserInfo()
SerialApi.openPlate( //// if (equipmentUserInfo.isOtherEquipment()) {
equipmentUserInfo.equipmentBoxCode!!.toInt(), //// MainActivity.start(
object : SerialPortManager.SendCallback { //// context,
override fun onSuccess() { //// pageType = PageType.PLATE_TIP,
MainActivity.start(context, pageType = PageType.PLATE_TIP) //// equipmentUserInfo = equipmentUserInfo,
} //// isAdmin
//// )
override fun onFail(e: Exception?) { //// return
ToastUtils.showToast("柜门打开失败") //// }
} // if (equipmentUserInfo.equipmentBoxCode?.isNotEmpty() == true) {
}) // SerialApi.openPlate(
} else { // equipmentUserInfo.equipmentBoxCode!!.toInt(),
if (isAdmin) { // object : SerialPortManager.SendCallback {
settingViewModel.getEquipmentList() // override fun onSuccess() {
} else { // MainActivity.start(
Timber.d("无餐盘信息") // context,
MainActivity.start( // pageType = PageType.PLATE_TIP,
context, // isAdmin = isAdmin
pageType = PageType.PLATE_TIP, // )
equipmentUserInfo = equipmentUserInfo // }
) //
} // override fun onFail(e: Exception?) {
} // ToastUtils.showToast("柜门打开失败")
// }
// })
// } else {
// if (isAdmin) {
// settingViewModel.getEquipmentList {
// loadEquipmentList(it)
// }
// } else {
// Timber.d("无餐盘信息")
// MainActivity.start(
// context,
// pageType = PageType.PLATE_TIP,
// equipmentUserInfo = equipmentUserInfo,
// isAdmin = isAdmin
// )
// }
// }
} }
override fun onDestroy() { override fun onDestroy() {
SerialApi.close()
timeJob?.cancel() timeJob?.cancel()
keyEventHelper?.onDestroy() keyEventHelper?.onDestroy()
super.onDestroy() super.onDestroy()
} }
private val intervalExecutor by lazy { IntervalExecutor() }
private var faceTaskJob: Job? = null
// private val initialDelay = 5 * 60 * 1000L
// private val dealyMillis = 10 * 60 * 1000L
private val initialDelay = 5 * 60 * 1000L
private val dealyMillis = 5 * 60 * 1000L
fun startFaceTask(block: (List<String?>) -> Unit) {
faceTaskJob =
intervalExecutor.startIntervalTaskWithInitialDelay(initialDelay, dealyMillis) {
getFaceIncrementList(action = block)
}
}
private var faceTimestamp = 0L
//private var taskPageNo = 1
private fun getFaceIncrementList(pageNo: Int = 1, action:(List<String?>)-> Unit) {
val timestamp = SpTool.lastFaceTimestamp
if (timestamp == 0L) {
return
}
settingViewModel.getFaceIncrementList(
pageNo = pageNo,
timestamp = timestamp
) { list ->
runOnUiThread {
if (pageNo == 1 && list.isEmpty()) {
//未查询到增量数据
return@runOnUiThread
}
updateFaceData(list)
val userIdList = list.map { it.userId }
action(userIdList)
//if (list.size >= settingViewModel.PAGE_SIZE) {
// faceTimestamp = list.last().faceUpdateTimestamp ?: 0
// taskPageNo++
// getFaceIncrementList(taskPageNo)
// return@runOnUiThread
//}
if (list.isNotEmpty()) {
faceTimestamp = list.last().faceUpdateTimestamp ?: 0
}
SpTool.lastFaceTimestamp = faceTimestamp
}
}
}
public val recognizeViewModel by viewModels<RecognizeViewModel>()
private fun updateFaceData(list: List<UserFaceModel>) {
Thread {
val faceList = mutableListOf<FaceEntity>()
val faceDao = FaceDatabase.getInstance(this).faceDao()
try {
list.forEach { model ->
if (model.faceDeleted == true) {
//删除数据
faceDao.deleteFaceById(model.userId)
} else {
val faceData = faceDao.queryByUserName(model.userId)
if (faceData == null) {
//保存数据
val faceEntity = FaceEntity(
model.userId,
null,
Base64.decode(model.faceFeatureStr)
).also {
//1-会员、2-临时用户
it.userType = if (model.isMember) "1" else "2"
it.cardNo = model.cardNo
}
faceList.add(faceEntity)
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
try {
if (faceList.isNotEmpty()) {
FaceDatabase.getInstance(this).faceDao().insert(faceList)
recognizeViewModel.refreshFaceList()
}
} catch (e: Exception) {
e.printStackTrace()
}
}.start()
}
fun initPlateList() {
// SpTool.putString(SpTool.PLATE_BOX_DATA, "")
// var list = SpTool.getPlateData()
val list: MutableList<EquipmentUserInfo> = mutableListOf()
repeat(22) { index ->
list.add(
EquipmentUserInfo(equipmentBoxCode = "${index + 1}")
)
}
MyApp.plateList = list
}
fun clearAllFace(block: () -> Unit) {
Thread {
val faceDao = FaceDatabase.getInstance(App.getContext()).faceDao()
faceDao.deleteAll()
faceDao.resetId()
runOnUiThread { block() }
}.start()
}
} }
@@ -0,0 +1,105 @@
package com.sw.platecabinet.activity
import android.content.Intent
import androidx.lifecycle.lifecycleScope
import com.sw.plate.utils.ToastUtils
import com.sw.plate.utils.arcface.FaceApi
import com.sw.platecabinet.GlobalData
import com.sw.platecabinet.databinding.ActivityDeviceInitBinding
import com.sw.platecabinet.databinding.ItemTitleTimeBinding
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/**
* 设备初始化界面
*/
class DeviceInitActivity : BaseActivity<ActivityDeviceInitBinding>() {
override fun inflateViewBinding(): ActivityDeviceInitBinding {
return ActivityDeviceInitBinding.inflate(layoutInflater)
}
override fun inflateTitleBinding(): ItemTitleTimeBinding? {
return null
}
override fun initialize() {
// val isSuccess = deviceViewModel.checkEquipmentInfo()
// if (isSuccess) {
// goLoginActivity()
// return
// }
// binding.ivQrCode.setImageBitmap(
// QRCodeUtil.generateQRCode(
// content = GlobalData.deviceId,
// size = 200
// )
// )
// binding.btnInit.setOnClickListener {
// deviceViewModel.getDeviceToken()
// }
// binding.btnInit.gone()
// binding.ivQrCode.invisible()
// binding.root.postDelayed({
// goLoginActivity()
// }, 1000)
initPlateList()
showWaitingDialog("加载中……")
lifecycleScope.launch {
withContext(Dispatchers.Default) {
val count = FaceApi().queryFaceCount()
if (count == 0) {
userViewModel.getUserFaceCache { status, msg ->
getDeviceConfig()
}
} else {
getDeviceConfig()
}
}
}
}
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() {
super.registerDataChange()
// lifecycleScope.launch {
// deviceViewModel.deviceInfoResult.collect { it ->
// if (it == true) {
// goLoginActivity()
// }
// }
// }
}
private fun goLoginActivity() {
val intent = Intent(this, LoginByFaceActivity::class.java)
startActivity(intent)
finish()
}
override fun onLeftDoubleClick() {
}
}
@@ -1,9 +1,17 @@
package com.sw.platecabinet.activity package com.sw.platecabinet.activity
import android.content.Intent import android.util.Log
import androidx.lifecycle.lifecycleScope
import com.sw.plate.utils.arcface.FaceApi
import com.sw.plate.utils.arcface.faceserver.FaceServer
import com.sw.platecabinet.databinding.ActivityInitBinding import com.sw.platecabinet.databinding.ActivityInitBinding
import com.sw.platecabinet.databinding.ItemTitleTimeBinding import com.sw.platecabinet.databinding.ItemTitleTimeBinding
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
/**
* 待机界面
*/
class InitActivity : BaseActivity<ActivityInitBinding>() { class InitActivity : BaseActivity<ActivityInitBinding>() {
override fun inflateViewBinding(): ActivityInitBinding { override fun inflateViewBinding(): ActivityInitBinding {
return ActivityInitBinding.inflate(layoutInflater) return ActivityInitBinding.inflate(layoutInflater)
@@ -14,10 +22,18 @@ class InitActivity : BaseActivity<ActivityInitBinding>() {
} }
override fun initialize() { override fun initialize() {
binding.main.setOnClickListener { Log.d("InitActivity", "collectFace,initialize: ")
val intent = Intent(this, LoginByFaceActivity::class.java) binding.takeButton.setOnClickListener {
startActivity(intent) finish()
} }
// binding.takeButton.setOnClickListener {
//
// lifecycleScope.launch(Dispatchers.IO) {
// FaceApi().deleteByUserName("288")
// }
//
// finish()
// }
} }
@@ -2,6 +2,7 @@ package com.sw.platecabinet.activity
import android.Manifest import android.Manifest
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Point import android.graphics.Point
import android.hardware.Camera import android.hardware.Camera
import android.os.Build import android.os.Build
@@ -10,10 +11,12 @@ import android.util.DisplayMetrics
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewTreeObserver import android.view.ViewTreeObserver
import androidx.activity.viewModels
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import com.arcsoft.face.ErrorInfo import com.arcsoft.face.ErrorInfo
import com.google.gson.Gson
import com.sw.inbound.utils.DateTimeUtils
import com.sw.plate.utils.Base64
import com.sw.plate.utils.ToastUtils import com.sw.plate.utils.ToastUtils
import com.sw.plate.utils.arcface.ConfigUtil import com.sw.plate.utils.arcface.ConfigUtil
import com.sw.plate.utils.arcface.ErrorCodeUtil import com.sw.plate.utils.arcface.ErrorCodeUtil
@@ -24,22 +27,41 @@ import com.sw.plate.utils.arcface.PreviewConfig
import com.sw.plate.utils.arcface.camera.CameraListener import com.sw.plate.utils.arcface.camera.CameraListener
import com.sw.plate.utils.arcface.camera.DualCameraHelper import com.sw.plate.utils.arcface.camera.DualCameraHelper
import com.sw.plate.utils.arcface.face.constants.LivenessType 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.FacePreviewInfo
import com.sw.plate.utils.arcface.face.model.RecognizeConfiguration import com.sw.plate.utils.arcface.face.model.RecognizeConfiguration
import com.sw.plate.utils.arcface.facedb.FaceDatabase
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
import com.sw.plate.utils.arcface.viewmodel.RecognizeViewModel.REGISTER_STATUS_READY
import com.sw.platecabinet.MyApp
import com.sw.platecabinet.R import com.sw.platecabinet.R
import com.sw.platecabinet.databinding.ActivityLoginFaceBinding import com.sw.platecabinet.databinding.ActivityLoginFaceBinding
import com.sw.platecabinet.databinding.ItemTitleTimeBinding import com.sw.platecabinet.databinding.ItemTitleTimeBinding
import com.sw.platecabinet.model.response.EquipmentUserInfo import com.sw.platecabinet.model.response.EquipmentUserInfo
import com.sw.platecabinet.model.response.UserFaceModel
import com.sw.platecabinet.utils.BitmapSaver
import com.sw.platecabinet.utils.Debouncer
import com.sw.platecabinet.utils.LogFileUtil
import com.sw.platecabinet.utils.PermissionHelper import com.sw.platecabinet.utils.PermissionHelper
import com.sw.platecabinet.utils.PlateUtils
import com.sw.platecabinet.utils.RegisterCallbackHandler
import com.sw.platecabinet.utils.SoundPoolUtil
import com.sw.platecabinet.utils.SpTool
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import timber.log.Timber import timber.log.Timber
import java.io.File
import java.util.concurrent.atomic.AtomicBoolean
/** /**
* 人脸识别 * 人脸识别
*/ */
class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(), class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
ViewTreeObserver.OnGlobalLayoutListener { ViewTreeObserver.OnGlobalLayoutListener {
private val recognizeViewModel by viewModels<RecognizeViewModel>() // private val recognizeViewModel by viewModels<RecognizeViewModel>()
private var countDownTimer: CountDownTimer? = null private var countDownTimer: CountDownTimer? = null
private val CAMERA_PERMISSION_REQUEST_CODE = 100 private val CAMERA_PERMISSION_REQUEST_CODE = 100
@@ -58,35 +80,84 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
} }
companion object { companion object {
private const val TAG = "LoginByFaceActivity"
private var instance: LoginByFaceActivity? = null private var instance: LoginByFaceActivity? = null
fun finishActivity() { private const val COLLECT_SUCCESS = "collect_success"
instance?.finish() private const val RECOGNIZE_SUCCESS = "recognize_success"
private const val NO_PLATE_REMIND = "no_plate_remind";
fun goInitActivity() {
instance?.goInitActivity()
} }
} }
// private var logList: MutableList<String> = mutableListOf()
// private val logAdapter by lazy { LogAdapter(logList) }
override fun initialize() { override fun initialize() {
instance = this instance = this
viewModel.activeEngine() userViewModel.activeEngine()
// addSocketListener()
initCountTime() initCountTime()
initArcViewModel() initArcViewModel()
initArcView() initArcView()
openRectInfoDraw = true openRectInfoDraw = true
recognizeViewModel.setDrawRectInfoTextValue(true) recognizeViewModel.setDrawRectInfoTextValue(true)
viewModel.generateToken() //viewModel.generateToken()
binding.llToPwd.setOnClickListener {
val intent = Intent(this, LoginByPwdActivity::class.java) // binding.llToPwd.setOnClickListener {
startActivity(intent) // val intent = Intent(this, LoginByPwdActivity::class.java)
// startActivity(intent)
// }
// TaskManager.startTask()
EventBus.getDefault().register(this)
// binding.btnCollectFace.setOnClickListener { collectFace() }
// binding.rvLogInfo.let {
// it.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true)
// it.adapter = logAdapter
// }
//
// binding.btnLogState.let {
// it.tag = false
// it.setImageResource(R.drawable.ic_log_show)
// it.setOnClickListener { v ->
// binding.rvLogInfo.run {
// val state = it.tag.toString().toBooleanStrictOrNull() ?: false
// if (state) visible() else gone()
// }
// val state = it.tag.toString().toBooleanStrictOrNull() ?: false
// it.tag = state
// it.setImageResource(
// if (state) R.drawable.ic_log_hide else R.drawable.ic_log_show
// )
// }
// }
//开启人脸增量数据定时任务
startFaceTask {
loadLogInfo("collectFace增量接口数据用户id:${Gson().toJson(it)}")
} }
val soundMap = hashMapOf<String, Int>()
soundMap[COLLECT_SUCCESS] = R.raw.collect_success
soundMap[RECOGNIZE_SUCCESS] = R.raw.recognize_success
soundMap[NO_PLATE_REMIND] = R.raw.no_plate_remind
SoundPoolUtil.getInstance().loadR(this, soundMap);
} }
private fun checkCameraPermission() { private fun checkCameraPermission() {
Timber.i("checkCameraPermission") Timber.i("checkCameraPermission")
val permissionHelper = val permissionHelper =
PermissionHelper.with(this, REQUIRED_PERMISSIONS, CAMERA_PERMISSION_REQUEST_CODE) PermissionHelper.with(this, REQUIRED_PERMISSIONS, CAMERA_PERMISSION_REQUEST_CODE)
.onGranted { .onGranted {
Timber.d("checkCameraPermission 权限已允许") loadLogInfo("checkCameraPermission 权限已允许")
// 权限已授予 // 权限已授予
recognizeViewModel.init() recognizeViewModel.init()
initRgbCamera() initRgbCamera()
@@ -97,6 +168,13 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
permissionHelper.checkAndRequest() permissionHelper.checkAndRequest()
} }
@Subscribe(threadMode = ThreadMode.MAIN)
fun getFaceEntity(insertEntity: FaceEntity) {
Timber.tag("performSync").e("-time=%s", insertEntity.registerTime)
recognizeViewModel.addFace(insertEntity)
Timber.tag("performSync over").e("-time=%s", insertEntity.registerTime)
}
override fun onLeftDoubleClick() { override fun onLeftDoubleClick() {
finish() finish()
} }
@@ -106,11 +184,28 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
private var rgbFaceRectTransformer: FaceRectTransformer? = null private var rgbFaceRectTransformer: FaceRectTransformer? = null
private val livenessType = LivenessType.RGB private val livenessType = LivenessType.RGB
private var openRectInfoDraw = false private var openRectInfoDraw = false
private val regDebouncer = Debouncer(3000)
private var registerHandler: RegisterCallbackHandler? = null
private fun initArcViewModel() { private fun initArcViewModel() {
recognizeViewModel.setLiveType(livenessType) recognizeViewModel.setLiveType(livenessType)
recognizeViewModel.prepareRegister()
recognizeViewModel.setOnRegisterFinishedCallback { facePreviewInfo, userFaceInfo ->
registerHandler?.setCallback {
if (!isCollectFace) {
return@setCallback
}
if (this.userFaceInfo == null && userFaceInfo != null) {
recognizeViewModel.updateRegisterStatus(RecognizeViewModel.REGISTER_STATUS_DONE)
this.userFaceInfo = userFaceInfo
getFaceDataBlock { faceData, headBmp ->
isCollecting.set(false)
isCollectFace = false
sendFaceData(faceData, headBmp)
}
}
}
}
recognizeViewModel.ftInitCode.observe(this, Observer { ftInitCode: Int? -> recognizeViewModel.ftInitCode.observe(this, Observer { ftInitCode: Int? ->
if (ftInitCode != ErrorInfo.MOK) { if (ftInitCode != ErrorInfo.MOK) {
val error: String? = getString( val error: String? = getString(
@@ -150,9 +245,13 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
Timber.i("recognizeNotice observe notice = $notice") Timber.i("recognizeNotice observe notice = $notice")
}) })
recognizeViewModel.recognizeUserId.observe(this, Observer { userId: String? -> recognizeViewModel.recognizeUserId.observe(this, Observer { result: CompareResult? ->
Timber.i("recognizeUserId observe userId = $userId") loadLogInfo("collectFace,recognizeUserId observe userId = ${result?.faceEntity?.userName}")
viewModel.getUserInfoById(memberId = userId?.toInt() ?: 0) if (faceUseSwitch) {
loadFaceRecognizeResult2(result)
return@Observer
}
loadFaceRecognizeResult(result)
}) })
recognizeViewModel.drawRectInfoText.observe(this, Observer { info -> recognizeViewModel.drawRectInfoText.observe(this, Observer { info ->
@@ -160,6 +259,205 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
}) })
} }
/**
* 人脸识别开关,true-loadFaceRecognizeResult2false-loadFaceRecognizeResult
*/
private var faceUseSwitch = true
/**
* 识别失败重试次数
*/
private var retryFailCount = 0
/**
* 识别失败最大次数
*/
private val maxFailCount = 5
/**
* 人脸失败阈值
*/
private val faceFailThreshold = 0.3
/**
* 配置阈值,默认值为0.8
*/
private val faceSuccessThreshold by lazy { ConfigUtil.getRecognizeThreshold(this) }
private val openPlateDebouncer = Debouncer(5000)
private val isCollecting = AtomicBoolean(false)
/**
* 根据相似度similar>设定阈值0.8作为识别成功判断,
* 如果similar<0.3作为识别失败判断,
* 如果similar>=0.3&&similar<=0.8时,暂时不作为失败情况,再识别5次如果similar<0.8则作为识别失败处理
*/
private fun loadFaceRecognizeResult2(result: CompareResult?) {
// 无有效结果直接忽略
val similar = result?.similar ?: return
binding.tvSimilarValue.text = "$similar${retryFailCount}"
loadLogInfo(
"collectFace,recognizeUserId 阈值 success=$faceSuccessThreshold, " +
"fail=$faceFailThreshold, similar=$similar, retry=$retryFailCount"
)
if (similar >= faceSuccessThreshold) {
val userId = result.faceEntity?.userName
if (userId.isNullOrBlank()) {
loadLogInfo("collectFace,recognizeUserId 识别成功但 userId 为空,忽略")
return
}
loadLogInfo(
"collectFace,recognizeUserId 识别成功 userId=$userId, time=${DateTimeUtils.getDateTimeString()}"
)
// 成功后必须重置状态
retryFailCount = 0
isCollecting.set(false)
isCollectFace = false
currentUserId = userId
// 防抖,防止重复开闸
openPlateDebouncer.debounce {
openPlate(userId)
}
return
}
if (isCollectFace) {
loadLogInfo("collectFace,recognizeUserId 正在采集中,忽略失败结果 similar=$similar")
return
}
if (similar <= faceFailThreshold || retryFailCount >= maxFailCount) {
loadLogInfo(
"collectFace,recognizeUserId 识别失败,开始采集 similar=$similar, " +
"retry=$retryFailCount, time=${DateTimeUtils.getDateTimeString()}"
)
if (!isCollecting.compareAndSet(false, true)) {
loadLogInfo("collectFace 已在采集中,忽略重复触发")
return
}
retryFailCount = 0
isCollectFace = true
collectFace()//采集人脸
return
}
retryFailCount++
loadLogInfo("collectFace,recognizeUserId 中间态重试,retryFailCount=$retryFailCount")
}
// private fun loadFaceRecognizeResult2(result: CompareResult?) {
//
// if(isCollectFace){
// return
// }
//
// val similar = result?.similar ?: 0f
// binding.tvSimilarValue.text = "$similar${retryFailCount}次"
// loadLogInfo("collectFace,recognizeUserId 人脸识别配置阈值为:${faceSuccessThreshold},失败阈值为:${faceFailThreshold},相似度为:${similar},失败次数为:${retryFailCount}")
// if (similar > faceSuccessThreshold) {
// loadLogInfo("collectFace,recognizeUserId 相似度为:${similar},识别成功时间:${DateTimeUtils.getDateTimeString()}")
// //大 于识别阈值,作为识别成功判断
// currentUserId = result?.faceEntity?.userName
// Debouncer(5000).debounce { openPlate(currentUserId) }
// return
// }
// loadLogInfo("collectFace,recognizeUserId 人脸识别相似度为:${similar},失败次数为:${retryFailCount}")
// if (similar < faceFailThreshold || retryFailCount >= maxFailCount) {
// //相似度低于识别错误阈值进行采集、识别失败次数大于等于最大失败次数,作为识别失败判断
// if (isCollectFace.not()) {
// //非采集中
// loadLogInfo("collectFace,recognizeUserId 未识别到人脸信息,相似度为:${similar},开始采集时间:${DateTimeUtils.getDateTimeString()}")
// isCollectFace = true
// collectFace()
// return
// }
// }
// //满足similar >= faceFailThreshold && similar <= successThreshold时进行重试
// retryFailCount++
// loadLogInfo("collectFace,recognizeUserId 人脸识别失败次数为:${retryFailCount}")
// }
/**
* 根据相似度similar>设定阈值0.8作为识别是否成功来加载人脸识别结果
*/
private fun loadFaceRecognizeResult(result: CompareResult?) {
val similar = result?.similar ?: 0f
binding.tvSimilarValue.text = "$similar"
val similarPass = result?.isSimilarPass ?: false
if (similarPass.not()) {
loadLogInfo("collectFace,recognizeUserId 未识别到人脸信息,开始采集")
isCollectFace = true
collectFace()
return
}
currentUserId = result?.faceEntity?.userName
if (tempUserId != currentUserId) {
SoundPoolUtil.getInstance().play("success", 0)
}
// plateEnable = true
Debouncer(5000).debounce { openPlate(currentUserId) }
}
/**
* 查询餐盘数据
*/
private fun getPlateData(block: (List<EquipmentUserInfo>) -> Unit) {
MyApp.plateList?.let {
block(it)
}
}
private var currentPlateInfo: EquipmentUserInfo? = null
private var plateEnable = false
/**
* 打开餐盘柜
*/
private fun openPlate(userId: String?) {
if (plateEnable.not()) return
loadLogInfo("collectFace,openPlate: userId=$userId")
getPlateData { items ->
loadLogInfo("collectFace,openPlate: items:${Gson().toJson(items)}")
// 未查询到当前用户,查询第一个未绑定的餐盘
val item = items.firstOrNull { it.plateNumber.isNullOrEmpty().not() }
loadLogInfo("collectFace,openPlate: item2:${item}")
if (item == null) {
//暂无餐盘
loadLogInfo("collectFace,openPlate: 暂无餐盘,请联系管理人员,isCollectFace=$isCollectFace")
//ToastUtils.showToast("暂无餐盘,请联系管理人员")
SoundPoolUtil.getInstance().play(NO_PLATE_REMIND, 0)
goInitActivity()
return@getPlateData
}
// if (tempUserId != currentUserId) {
SoundPoolUtil.getInstance().play(RECOGNIZE_SUCCESS, 0)
// } else {
// SoundPoolUtil.getInstance().play(COLLECT_SUCCESS, 0)
// }
loadLogInfo("collectFace,openPlate: item3:${item}")
item.faceId = userId
item.updateTime = DateTimeUtils.getDateTimeString()
item.plateNumber = null
plateEnable = false
Debouncer(5000).debounce {
//打开柜子
PlateUtils.open(this, item.equipmentBoxCode, false)
}
currentPlateInfo = item
MyApp.plateList = items
SpTool.savePlateData(items)
}
}
// private val debouncer by lazy { Debouncer(3000) }
private fun initArcView() { private fun initArcView() {
//在布局结束后才做初始化操作 //在布局结束后才做初始化操作
binding.dualCameraTexturePreviewRgb.getViewTreeObserver().addOnGlobalLayoutListener(this) binding.dualCameraTexturePreviewRgb.getViewTreeObserver().addOnGlobalLayoutListener(this)
@@ -167,6 +465,7 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
} }
override fun onDestroy() { override fun onDestroy() {
loadLogInfo("onDestroy")
instance = null instance = null
if (rgbCameraHelper != null) { if (rgbCameraHelper != null) {
rgbCameraHelper!!.release() rgbCameraHelper!!.release()
@@ -175,6 +474,7 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
recognizeViewModel.destroy() recognizeViewModel.destroy()
EventBus.getDefault().unregister(this)
super.onDestroy() super.onDestroy()
} }
@@ -248,25 +548,37 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
displayOrientation: Int, displayOrientation: Int,
isMirror: Boolean isMirror: Boolean
) { ) {
loadLogInfo("initRgbCamera Rgb onCameraOpened: cameraId = $cameraId, displayOrientation = $displayOrientation")
runOnUiThread({ runOnUiThread({
val previewSizeRgb = camera.getParameters().getPreviewSize() val previewSizeRgb = camera.getParameters().getPreviewSize()
val layoutParams = adjustPreviewViewSize( val layoutParams = adjustPreviewViewSize(
binding.dualCameraTexturePreviewRgb, binding.dualCameraTexturePreviewRgb,
binding.dualCameraTexturePreviewRgb, binding.dualCameraFaceRectView, binding.dualCameraTexturePreviewRgb, binding.dualCameraFaceRectView,
previewSizeRgb, displayOrientation, 1f previewSizeRgb, displayOrientation, 0.6f
)
loadLogInfo("initRgbCamera previewSizeRgb = ${previewSizeRgb.width}-${previewSizeRgb.height}")
loadLogInfo("initRgbCamera layoutParams = ${layoutParams.width}-${layoutParams.height}")
loadLogInfo(
"initRgbCamera isMirror = ${isMirror}, isDrawRgbRectHorizontalMirror = ${
ConfigUtil.isDrawRgbRectHorizontalMirror(
context
)
}, isDrawRgbRectVerticalMirror = ${
ConfigUtil.isDrawRgbRectVerticalMirror(
context
)
}"
) )
Timber.d("initRgbCamera previewSizeRgb = ${previewSizeRgb.width}-${previewSizeRgb.height}")
Timber.d("initRgbCamera layoutParams = ${layoutParams.width}-${layoutParams.height}")
rgbFaceRectTransformer = FaceRectTransformer( rgbFaceRectTransformer = FaceRectTransformer(
previewSizeRgb.width, previewSizeRgb.width,
previewSizeRgb.height, previewSizeRgb.height,
layoutParams.width, layoutParams.width,
layoutParams.height, layoutParams.height,
displayOrientation, 90,
cameraId, cameraId,
isMirror, isMirror,
ConfigUtil.isDrawRgbRectHorizontalMirror(context), true,
ConfigUtil.isDrawRgbRectVerticalMirror(context) true
) )
recognizeViewModel.onRgbCameraOpened(camera) recognizeViewModel.onRgbCameraOpened(camera)
@@ -280,29 +592,33 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
return return
} }
binding.dualCameraFaceRectView.clearFaceInfo() binding.dualCameraFaceRectView.clearFaceInfo()
val facePreviewInfoList: MutableList<FacePreviewInfo?>? = facePreviewInfoList = recognizeViewModel.onPreviewFrame(nv21, true)
recognizeViewModel.onPreviewFrame(nv21, true)
if (facePreviewInfoList != null && rgbFaceRectTransformer != null) { if (facePreviewInfoList != null && rgbFaceRectTransformer != null) {
drawPreviewInfo(facePreviewInfoList) drawPreviewInfo(facePreviewInfoList)
} }
if (facePreviewInfoList.isNullOrEmpty()) {
currentPlateInfo = null
currentUserId = null
userFaceInfo = null
}
recognizeViewModel.clearLeftFace(facePreviewInfoList) recognizeViewModel.clearLeftFace(facePreviewInfoList)
} }
override fun onCameraClosed() { override fun onCameraClosed() {
Timber.i("onCameraClosed: ") Timber.i("initRgbCamera onCameraClosed: ")
} }
override fun onCameraError(e: java.lang.Exception) { override fun onCameraError(e: java.lang.Exception) {
Timber.i("onCameraError: %s", e.message) Timber.i("initRgbCamera onCameraError: %s", e.message)
e.printStackTrace() e.printStackTrace()
} }
override fun onCameraConfigurationChanged(cameraID: Int, displayOrientation: Int) { override fun onCameraConfigurationChanged(cameraID: Int, displayOrientation: Int) {
Timber.i("onCameraConfigurationChanged: threadName = ${Thread.currentThread().name}") Timber.i("initRgbCamera onCameraConfigurationChanged: threadName = ${Thread.currentThread().name}")
if (rgbFaceRectTransformer != null) { if (rgbFaceRectTransformer != null) {
rgbFaceRectTransformer!!.cameraDisplayOrientation = displayOrientation rgbFaceRectTransformer!!.cameraDisplayOrientation = displayOrientation
} }
Timber.i("onCameraConfigurationChanged: $cameraID $displayOrientation") Timber.i("initRgbCamera onCameraConfigurationChanged: $cameraID $displayOrientation")
} }
} }
@@ -331,12 +647,10 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
* *
* @param facePreviewInfoList RGB画面的实时人脸信息 * @param facePreviewInfoList RGB画面的实时人脸信息
*/ */
private fun drawPreviewInfo(facePreviewInfoList: MutableList<FacePreviewInfo?>) { private fun drawPreviewInfo(facePreviewInfoList: MutableList<FacePreviewInfo>?) {
if (rgbFaceRectTransformer != null) { if (rgbFaceRectTransformer != null) {
val rgbDrawInfoList: MutableList<DrawInfo?>? = recognizeViewModel.getDrawInfo( val rgbDrawInfoList: MutableList<DrawInfo>? = recognizeViewModel.getDrawInfo(
facePreviewInfoList, facePreviewInfoList, LivenessType.RGB, openRectInfoDraw
LivenessType.RGB,
openRectInfoDraw
) )
// 识别成功 // 识别成功
binding.dualCameraFaceRectView.drawRealtimeFaceInfo(rgbDrawInfoList) binding.dualCameraFaceRectView.drawRealtimeFaceInfo(rgbDrawInfoList)
@@ -350,7 +664,7 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
override fun onGlobalLayout() { override fun onGlobalLayout() {
Timber.d("onGlobalLayout") loadLogInfo("onGlobalLayout")
binding.dualCameraTexturePreviewRgb.getViewTreeObserver().removeOnGlobalLayoutListener(this) binding.dualCameraTexturePreviewRgb.getViewTreeObserver().removeOnGlobalLayoutListener(this)
// 请求权限 // 请求权限
checkCameraPermission() checkCameraPermission()
@@ -359,46 +673,274 @@ class LoginByFaceActivity : BaseActivity<ActivityLoginFaceBinding>(),
private fun resumeCamera() { private fun resumeCamera() {
isRecognition = true isRecognition = true
if (rgbCameraHelper != null && rgbCameraHelper!!.isStopped) { if (rgbCameraHelper != null && rgbCameraHelper!!.isStopped) {
loadLogInfo("collectFace,resumeCamera: ")
rgbCameraHelper!!.start() rgbCameraHelper!!.start()
} else {
recognizeViewModel.onPreviewFrame(ByteArray(1382400), true)
} }
} }
/**
* 临时用户id
*/
private var tempUserId: String = ""
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
registerHandler = RegisterCallbackHandler()
try {
if (isFirstOpen) {
goInitActivity()
binding.root.postDelayed({
isFirstOpen = false
}, 1000)
return
}
retryFailCount = 0
binding.tvSimilarValue.text = ""
isCollecting.set(false)
isCollectFace = false
plateEnable = true
collectCount = 0
tempUserId = "${System.currentTimeMillis()}"
resumeCamera() resumeCamera()
viewModel.resetUserInfo() userViewModel.resetUserInfo()
countDownTimer?.let { countDownTimer?.let {
it.cancel() it.cancel()
it.start() it.start()
} }
// initFaceScheduler()
// checkFaceState()
} catch (e: Exception) {
e.printStackTrace()
} }
}
private var isFirstOpen = true
protected override fun onPause() { protected override fun onPause() {
pauseCamera() pauseCamera()
super.onPause() super.onPause()
countDownTimer?.cancel()
} }
private fun pauseCamera() { private fun pauseCamera() {
isRecognition = false isRecognition = false
recognizeViewModel.onPreviewFrame(ByteArray(1382400), true) recognizeViewModel.onPreviewFrame(ByteArray(1382400), true)
} }
override fun handleLoginSuccess(equipmentUserInfo: EquipmentUserInfo, isAdmin: Boolean) { override fun handleLoginSuccess(equipmentUserInfo: EquipmentUserInfo, isAdmin: Boolean) {
super.handleLoginSuccess(equipmentUserInfo, isAdmin) super.handleLoginSuccess(equipmentUserInfo, isAdmin)
finish() // goInitActivity()
} }
private val totalTimeInMillis = 15 * 1000L
fun initCountTime() { fun initCountTime() {
val totalTimeInMillis = 30L * 1000
countDownTimer = object : CountDownTimer(totalTimeInMillis, 1000) { countDownTimer = object : CountDownTimer(totalTimeInMillis, 1000) {
override fun onTick(millisUntilFinished: Long) { override fun onTick(millisUntilFinished: Long) {
loadLogInfo("initCountTime onTick = ${(millisUntilFinished / 1000).toInt()}")
binding.tvToInit.text = "${(millisUntilFinished / 1000).toInt() + 1}" binding.tvToInit.text = "${(millisUntilFinished / 1000).toInt() + 1}"
} }
override fun onFinish() { override fun onFinish() {
finish() goInitActivity()
} }
}
}
fun goInitActivity() {
val intent = Intent(context, InitActivity::class.java)
startActivity(intent)
}
/**
* 采集人脸
*/
private fun collectFace() {
loadLogInfo("collectFace: -----------1,开始采集人脸,开始时间:${DateTimeUtils.getDateTimeString()}----------")
if (isFirstOpen) return
loadLogInfo("collectFace: ----------2,isFirstOpen=false-----------")
loadLogInfo("collectFace: ----------4,检测到人脸信息-----------")
currentUserId = null
userFaceInfo = null
recognizeViewModel.updateRegisterStatus(REGISTER_STATUS_READY)
loadLogInfo("collectFace: ----------5,人脸采集中-----------")
}
private val uploadFaceDebouncer = Debouncer(10000)
/**
* 改为上传人脸数据,不需要发生人脸数据到收银端
*/
private fun sendFaceData(faceData: String, headBmp: Bitmap) {
loadLogInfo("collectFace,sendFaceData: ----------11,发送人脸数据-----------")
val imageFile = BitmapSaver.saveToAppFilesDir(
headBmp, this, "IMG_CROP_${System.currentTimeMillis()}.jpg"
)
loadLogInfo("collectFace,sendFaceData:${imageFile?.absolutePath}")
if (imageFile == null) {
ToastUtils.showToast("获取人脸照片失败")
return
}
uploadFaceDebouncer.debounce {
addUserFace(faceData, imageFile)
}
// // 给某个客户端发送
// val jsonObject = JSONObject().also {
// it.put("type", "faceFeature")
// it.put("content", faceData)
// }
// tcpClient?.send(jsonObject)
}
private fun addUserFace(faceData: String, imageFile: File) {
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
}
Thread {
saveFaceInfo(faceModel)
uploadFaceDebouncer.reset()
}.start() }.start()
} }
} }
private fun getFaceDataBlock(block: (String, Bitmap) -> Unit) {
if (userFaceInfo == null || userFaceInfo?.faceFeature?.featureData == null) {
return
}
val faceData: String? = Base64.encode(userFaceInfo?.faceFeature?.featureData)
if (faceData.isNullOrBlank()) {
return
}
loadLogInfo("collectFace,getFaceData: ----------7,拿到人脸提取特征数据时间:${DateTimeUtils.getDateTimeString()}-----------")
block(faceData, userFaceInfo!!.headBmp)
}
/**
* 加载日志
* @param msg 日志内容
*/
private fun loadLogInfo(msg: String) {
Timber.tag(TAG).d(msg)
LogFileUtil.saveLog(msg)
}
private var facePreviewInfoList: MutableList<FacePreviewInfo>? = null
private var userFaceInfo: UserFaceInfo? = null
private var currentUserId: String? = null
private fun saveFaceInfo(faceModel: UserFaceModel) {
try {
loadLogInfo("collectFace,saveFaceInfo: ----------8,保存人脸取特征数据-----------")
val faceDao = FaceDatabase.getInstance(this).faceDao()
val faceEntity = FaceEntity(
faceModel.userId,
null,
Base64.decode(faceModel.faceFeatureStr)
).also {
//1-会员、2-临时用户
it.userType = if (faceModel.isMember) "1" else "2"
}
faceDao.insert(faceEntity)
loadLogInfo("collectFace,saveFaceInfo: ----------10,人脸数据保存完成-----------")
recognizeViewModel.refreshFaceList()
loadLogInfo("collectFace,saveFaceInfo: ----------11,刷新人脸数据-----------")
runOnUiThread { recognizeViewModel.onPreviewFrame(ByteArray(1382400), true) }
loadLogInfo("---------------------------------")
collectCount = 0
} catch (e: Exception) {
e.printStackTrace()
loadLogInfo("collectFace,saveFaceInfo: ----------13,人脸采集异常:${e.message}-----------")
collectCount++
loadLogInfo("collectFace,saveFaceInfo: ----------14,${collectCount}次人脸采集异常:${e.message}-----------")
ToastUtils.showToast("人脸采集异常:${e.message}")
}
}
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 fun clearFaceData() {
Thread {
val faceDao = FaceDatabase.getInstance(this).faceDao()
faceDao.deleteTempUserFaceData()
recognizeViewModel.refreshFaceList()
loadLogInfo("collectFace,TcpClientListener收到消息,已删除并刷新人脸数据")
}.start()
}
private var isCollectFace = false
}
@@ -1,47 +1,62 @@
package com.sw.platecabinet.activity //package com.sw.platecabinet.activity
//
import android.content.Intent //import android.content.Intent
import com.sw.plate.utils.ToastUtils //import com.sw.plate.utils.ToastUtils
import com.sw.platecabinet.databinding.ActivityLoginByPwdBinding //import com.sw.platecabinet.databinding.ActivityLoginByPwdBinding
import com.sw.platecabinet.databinding.ItemTitleTimeBinding //import com.sw.platecabinet.databinding.ItemTitleTimeBinding
import com.sw.platecabinet.model.response.EquipmentUserInfo //import com.sw.platecabinet.dialog.BalanceNotEnoughDialog
import com.sw.platecabinet.utils.KeyboardUtils //import com.sw.platecabinet.model.request.LoginParam
//import com.sw.platecabinet.model.response.EquipmentUserInfo
class LoginByPwdActivity : BaseActivity<ActivityLoginByPwdBinding>() { //import com.sw.platecabinet.utils.KeyboardUtils
//
override fun inflateViewBinding(): ActivityLoginByPwdBinding { ///**
return ActivityLoginByPwdBinding.inflate(layoutInflater) // * 密码登录
} // */
//class LoginByPwdActivity : BaseActivity<ActivityLoginByPwdBinding>() {
override fun inflateTitleBinding(): ItemTitleTimeBinding { //
return binding.includeHeader // override fun inflateViewBinding(): ActivityLoginByPwdBinding {
} // return ActivityLoginByPwdBinding.inflate(layoutInflater)
// }
override fun initialize() { //
binding.btnLogin.setOnClickListener { // override fun inflateTitleBinding(): ItemTitleTimeBinding {
val phone = binding.etPhone.text.toString() // return binding.includeHeader
val pwd = binding.etPwd.text.toString() // }
if (phone.isEmpty() || pwd.isEmpty()) { //
ToastUtils.showToast("手机或校验码不能为空") // override fun initialize() {
return@setOnClickListener // binding.btnLogin.setOnClickListener {
} // val phone = binding.etPhone.text.toString()
viewModel.loginWithPwd(phone = phone, password = pwd) // val pwd = binding.etPwd.text.toString()
KeyboardUtils.hideKeyboard(this) // if (phone.isEmpty() || pwd.isEmpty()) {
} // ToastUtils.showToast("手机或校验码不能为空")
binding.tvFaceRec.setOnClickListener { // return@setOnClickListener
val intent = Intent(this, LoginByFaceActivity::class.java) // }
startActivity(intent) // val loginParam = LoginParam(
} // //equipmentId = equipmentId,
} // phone = phone,
// password = pwd
override fun onResume() { // )
super.onResume() // viewModel.loginWithPwd(loginParam)
viewModel.resetUserInfo() // KeyboardUtils.hideKeyboard(this)
} // }
// binding.tvFaceRec.setOnClickListener {
override fun handleLoginSuccess(equipmentUserInfo: EquipmentUserInfo, isAdmin: Boolean) { // val intent = Intent(this, LoginByFaceActivity::class.java)
super.handleLoginSuccess(equipmentUserInfo, isAdmin) // startActivity(intent)
LoginByFaceActivity.finishActivity() // }
finish() // }
} //
} // override fun onResume() {
// super.onResume()
// viewModel.resetUserInfo()
// }
//
// override fun handleLoginSuccess(equipmentUserInfo: EquipmentUserInfo, isAdmin: Boolean) {
// super.handleLoginSuccess(equipmentUserInfo, isAdmin)
//// val cardBalance = equipmentUserInfo.cardBalance?:0.toDouble()
//// val balanceIsNotEnough = cardBalance <= 0.toDouble()
// if (equipmentUserInfo.isIntercept) {
// return
// }
// LoginByFaceActivity.goInitActivity()
// finish()
// }
//}
@@ -2,19 +2,21 @@ package com.sw.platecabinet.activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import com.sw.plate.utils.ToastUtils
import com.sw.platecabinet.R import com.sw.platecabinet.R
import com.sw.platecabinet.databinding.ActivityMainBinding import com.sw.platecabinet.databinding.ActivityMainBinding
import com.sw.platecabinet.databinding.ItemTitleTimeBinding import com.sw.platecabinet.databinding.ItemTitleTimeBinding
import com.sw.platecabinet.fragment.BindPlateFragment import com.sw.platecabinet.dialog.CustomDialog
import com.sw.platecabinet.fragment.PlateCabinetFullFragment import com.sw.platecabinet.fragment.PlateCabinetFullFragment
import com.sw.platecabinet.fragment.PlateOpenFragment import com.sw.platecabinet.fragment.PlateOpenFragment
import com.sw.platecabinet.fragment.SettingListFragment import com.sw.platecabinet.fragment.SettingListFragment
import com.sw.platecabinet.fragment.UnBindPlateFragment
import com.sw.platecabinet.model.response.EquipmentUserInfo
import com.sw.platecabinet.utils.FragmentHelper import com.sw.platecabinet.utils.FragmentHelper
typealias Callback = (String) -> Unit typealias Callback = (String) -> Unit
/**
* 主界面,内部嵌套framgnet使用
*/
class MainActivity : BaseActivity<ActivityMainBinding>() { class MainActivity : BaseActivity<ActivityMainBinding>() {
private lateinit var fragmentHelper: FragmentHelper private lateinit var fragmentHelper: FragmentHelper
private var pageType = PageType.SETTING_LIST private var pageType = PageType.SETTING_LIST
@@ -31,32 +33,49 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
// 静态启动方法 // 静态启动方法
companion object { companion object {
private const val PARAM_PAGE_TYPE = "pageType" private const val PARAM_PAGE_TYPE = "pageType"
private const val PARAM_EQUIPMENT_INFO = "equipmentUserInfo" // private const val PARAM_EQUIPMENT_INFO = "equipmentUserInfo"
fun start( // private const val PARAM_IS_ADMIN = "isAdmin"
context: Context, // fun start(
pageType: PageType, // context: Context,
equipmentUserInfo: EquipmentUserInfo? = null // pageType: PageType,
) { // equipmentUserInfo: EquipmentUserInfo? = null,
// isAdmin: Boolean = false,
// block:()->Unit={}
// ) {
// val intent = Intent(context, MainActivity::class.java)
// intent.putExtra(PARAM_PAGE_TYPE, pageType.name)
// intent.putExtra(PARAM_EQUIPMENT_INFO, equipmentUserInfo)
// intent.putExtra(PARAM_IS_ADMIN, isAdmin)
// context.startActivity(intent)
// }
fun start(context: Context, pageType: PageType, isScanCode: Boolean = false) {
val intent = Intent(context, MainActivity::class.java) val intent = Intent(context, MainActivity::class.java)
intent.putExtra(PARAM_PAGE_TYPE, pageType.name) intent.putExtra(PARAM_PAGE_TYPE, pageType.name)
intent.putExtra(PARAM_EQUIPMENT_INFO, equipmentUserInfo) intent.putExtra(PlateOpenFragment.IS_SCAN_CODE, isScanCode)
context.startActivity(intent) context.startActivity(intent)
} }
} }
override fun initialize() { override fun initialize() {
val param = intent.getStringExtra(PARAM_PAGE_TYPE) val param = intent.getStringExtra(PARAM_PAGE_TYPE)
val equipmentUserInfo = intent.getParcelableExtra<EquipmentUserInfo>(PARAM_EQUIPMENT_INFO) // val isAdmin = intent.getBooleanExtra(PARAM_IS_ADMIN, false)
val isScanCode = intent.getBooleanExtra(PlateOpenFragment.IS_SCAN_CODE, false)
// val equipmentUserInfo = intent.getParcelableExtra<EquipmentUserInfo>(PARAM_EQUIPMENT_INFO)
pageType = param?.let { PageType.valueOf(param) } ?: PageType.SETTING_LIST pageType = param?.let { PageType.valueOf(param) } ?: PageType.SETTING_LIST
fragmentHelper = FragmentHelper(supportFragmentManager, R.id.fragment_container) fragmentHelper = FragmentHelper(supportFragmentManager, R.id.fragment_container)
val page = when (pageType) { val page = when (pageType) {
PageType.SETTING_LIST -> SettingListFragment() PageType.SETTING_LIST -> SettingListFragment()
PageType.BIND_PLATE -> BindPlateFragment.newInstance(equipmentUserInfo) // PageType.BIND_PLATE -> BindPlateFragment.newInstance(equipmentUserInfo)
PageType.PLATE_TIP -> PlateOpenFragment.newInstance(equipmentUserInfo) // PageType.PLATE_TIP -> PlateOpenFragment.newInstance(equipmentUserInfo, isAdmin)
PageType.UNBIND_PLATE -> UnBindPlateFragment.newInstance(equipmentUserInfo) PageType.PLATE_TIP -> PlateOpenFragment.newInstance(isScanCode)
// PageType.UNBIND_PLATE -> UnBindPlateFragment.newInstance(equipmentUserInfo)
PageType.PLATE_CABINET_FULL -> PlateCabinetFullFragment() PageType.PLATE_CABINET_FULL -> PlateCabinetFullFragment()
else -> null
}
page?.let {
fragmentHelper.addFragment(it)
} }
fragmentHelper.addFragment(page)
} }
override fun onLeftDoubleClick() { override fun onLeftDoubleClick() {
@@ -66,7 +85,9 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
} }
override fun onRightDoubleClick() { override fun onRightDoubleClick() {
if (pageType == PageType.SETTING_LIST) {
loadClearDialog()
}
} }
override fun handleScanKeyInfo(scanInfo: String) { override fun handleScanKeyInfo(scanInfo: String) {
@@ -82,8 +103,27 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun registerDataChange() { override fun registerDataChange() {
super.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,57 @@
//package com.sw.platecabinet.activity
//
//import android.graphics.Color
//import android.view.Gravity
//import android.view.ViewGroup
//import androidx.core.graphics.drawable.toDrawable
//import androidx.core.view.WindowCompat
//import com.sw.platecabinet.GlobalKey
//import com.sw.platecabinet.databinding.ActivityUnbindDialogBinding
//import com.sw.platecabinet.databinding.ItemTitleTimeBinding
//import com.sw.platecabinet.ext.dp
//import com.sw.platecabinet.model.response.EquipmentUserInfo
//
///**
// * 解绑确认弹窗界面
// */
//class UnBindDialogActivity : BaseActivity<ActivityUnbindDialogBinding>() {
// private var equipmentUserInfo: EquipmentUserInfo? = null
//
// override fun inflateViewBinding(): ActivityUnbindDialogBinding {
// return ActivityUnbindDialogBinding.inflate(layoutInflater)
// }
//
// override fun inflateTitleBinding(): ItemTitleTimeBinding? {
// return null
// }
//
// override fun initialize() {
// window?.apply {
// WindowCompat.setDecorFitsSystemWindows(this, false)
// setBackgroundDrawable(Color.TRANSPARENT.toDrawable())
//
// attributes = attributes.apply {
// y = 314.dp
// width = (context.resources.displayMetrics.widthPixels * 0.8).toInt()
// height = ViewGroup.LayoutParams.MATCH_PARENT
// gravity = Gravity.CENTER
// }
// }
// equipmentUserInfo =
// intent.getParcelableExtra<EquipmentUserInfo>(GlobalKey.PARAM_EQUIPMENT_INFO)
// if (equipmentUserInfo == null) return
// binding.tvTipInfo.text = "即将解除【${equipmentUserInfo!!.name}】的餐盘"
// binding.tvCancel.setOnClickListener {
// finish()
// }
// binding.tvConfirm.setOnClickListener {
// setResult(RESULT_OK)
// finish()
// }
// }
//
// override fun finish() {
// super.finish()
// overridePendingTransition(0, 0) // 确保退出也无动画
// }
//}
@@ -0,0 +1,34 @@
package com.sw.platecabinet.adapter
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.sw.platecabinet.databinding.ListItemLogInfoBinding
class LogAdapter(var list: MutableList<String>) : RecyclerView.Adapter<LogAdapter.ViewHolder>() {
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.binding.tvLogText.text = list[position]
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding =
ListItemLogInfoBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
}
override fun getItemCount() = list.size
inner class ViewHolder(var binding: ListItemLogInfoBinding) :
RecyclerView.ViewHolder(binding.root)
fun addData(logInfo: String) {
Handler(Looper.getMainLooper()).post {
// list.add(logInfo)
// notifyDataSetChanged()
}
}
}
@@ -0,0 +1,36 @@
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.DialogBalanceNotEnoughBinding
import com.sw.platecabinet.ext.dp
open class BalanceNotEnoughDialog(
private var activity: FragmentActivity
) : Dialog(activity) {
private lateinit var binding: DialogBalanceNotEnoughBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestWindowFeature(Window.FEATURE_NO_TITLE)
binding = DialogBalanceNotEnoughBinding.inflate(layoutInflater)
setContentView(binding.root)
window?.run {
attributes = attributes.apply {
width = 480.dp
}
setBackgroundDrawable(ColorDrawable())
setCancelable(false)
}
addListener()
}
private fun addListener() {
binding.tvConfirm.setOnClickListener { dismiss() }
}
}
@@ -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()
}
}
}
@@ -0,0 +1,38 @@
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.DialogUserBindRemindBinding
import com.sw.platecabinet.ext.dp
open class UserBindRemindDialog(
private var activity: FragmentActivity,
var content:String
) : Dialog(activity) {
private lateinit var binding: DialogUserBindRemindBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestWindowFeature(Window.FEATURE_NO_TITLE)
binding = DialogUserBindRemindBinding.inflate(layoutInflater)
setContentView(binding.root)
window?.run {
attributes = attributes.apply {
width = 480.dp
}
setBackgroundDrawable(ColorDrawable())
setCancelable(false)
}
binding.tvBindContent.text = content
addListener()
}
private fun addListener() {
binding.tvConfirm.setOnClickListener { dismiss() }
}
}
@@ -2,6 +2,7 @@ package com.sw.platecabinet.ext
import android.content.res.Resources import android.content.res.Resources
import android.util.TypedValue import android.util.TypedValue
import android.view.View
import androidx.annotation.Dimension import androidx.annotation.Dimension
/** /**
@@ -51,3 +52,15 @@ val Float.sp: Float
fun formatNumber(num: String): String { fun formatNumber(num: String): String {
return num.padStart(2, '0') return num.padStart(2, '0')
} }
fun View.visible() {
visibility = View.VISIBLE
}
fun View.invisible() {
visibility = View.INVISIBLE
}
fun View.gone() {
visibility = View.GONE
}
@@ -4,6 +4,11 @@ import android.os.Handler
import android.os.Looper import android.os.Looper
import android.view.View import android.view.View
import android.widget.TextView 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 的双击和单击事件监听器 * 设置 TextView 的双击和单击事件监听器
@@ -31,3 +36,37 @@ fun TextView.setClickListeners(
}, doubleClickInterval) }, 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
}
}
}
@@ -12,7 +12,7 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import com.sw.platecabinet.R import com.sw.platecabinet.R
import com.sw.platecabinet.view.CustomDialog import com.sw.platecabinet.view.CustomLoadingDialog
import com.sw.platecabinet.viewmodel.SettingViewModel import com.sw.platecabinet.viewmodel.SettingViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -21,7 +21,7 @@ abstract class BaseFragment<VB : ViewBinding>(
) : Fragment() { ) : Fragment() {
private var _binding: VB? = null private var _binding: VB? = null
protected val binding get() = _binding!! protected val binding get() = _binding!!
private var mDialogWaiting: CustomDialog? = null private var mDialogWaiting: CustomLoadingDialog? = null
protected val viewModel by viewModels<SettingViewModel>() protected val viewModel by viewModels<SettingViewModel>()
override fun onCreateView( override fun onCreateView(
@@ -59,13 +59,12 @@ abstract class BaseFragment<VB : ViewBinding>(
hideWaitingDialog() hideWaitingDialog()
val view = View.inflate(requireContext(), R.layout.dialog_waiting, null) val view = View.inflate(requireContext(), R.layout.dialog_waiting, null)
if (!TextUtils.isEmpty(tip)) (view.findViewById<View?>(R.id.tvTip) as TextView).text = tip if (!TextUtils.isEmpty(tip)) (view.findViewById<View?>(R.id.tvTip) as TextView).text = tip
mDialogWaiting = CustomDialog(requireContext(), view, R.style.MyDialog) mDialogWaiting = CustomLoadingDialog(requireContext(), view, R.style.MyDialog)
mDialogWaiting!!.show() mDialogWaiting!!.show()
mDialogWaiting!!.setCancelable(true) mDialogWaiting!!.setCancelable(true)
return mDialogWaiting return mDialogWaiting
} }
/** /**
* 隐藏等待提示框 * 隐藏等待提示框
*/ */
@@ -1,250 +1,317 @@
package com.sw.platecabinet.fragment //package com.sw.platecabinet.fragment
//
import android.os.Bundle //import android.annotation.SuppressLint
import android.text.Editable //import android.os.Bundle
import android.text.TextUtils //import android.text.Editable
import android.text.TextWatcher //import android.text.TextUtils
import android.view.inputmethod.EditorInfo //import android.text.TextWatcher
import androidx.lifecycle.lifecycleScope //import android.view.inputmethod.EditorInfo
import androidx.recyclerview.widget.GridLayoutManager //import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager //import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView //import androidx.recyclerview.widget.GridLayoutManager
import com.sw.plate.utils.ToastUtils //import androidx.recyclerview.widget.LinearLayoutManager
import com.sw.plate.utils.comn.SerialApi //import androidx.recyclerview.widget.RecyclerView
import com.sw.plate.utils.comn.SerialPortManager //import com.sw.plate.utils.ToastUtils
import com.sw.platecabinet.R //import com.sw.plate.utils.comn.SerialApi
import com.sw.platecabinet.activity.MainActivity //import com.sw.plate.utils.comn.SerialPortManager
import com.sw.platecabinet.activity.PageType //import com.sw.platecabinet.R
import com.sw.platecabinet.adapter.GenericItemAdapter //import com.sw.platecabinet.activity.MainActivity
import com.sw.platecabinet.adapter.GridSpacingItemDecoration //import com.sw.platecabinet.activity.PageType
import com.sw.platecabinet.adapter.dpToPx //import com.sw.platecabinet.adapter.GenericItemAdapter
import com.sw.platecabinet.databinding.FragmentBindPlateBinding //import com.sw.platecabinet.adapter.GridSpacingItemDecoration
import com.sw.platecabinet.databinding.ItemSearchUserInfoBinding //import com.sw.platecabinet.adapter.dpToPx
import com.sw.platecabinet.ext.maskName //import com.sw.platecabinet.databinding.FragmentBindPlateBinding
import com.sw.platecabinet.ext.maskPhone //import com.sw.platecabinet.databinding.ItemSearchUserInfoBinding
import com.sw.platecabinet.model.response.EquipmentUserInfo //import com.sw.platecabinet.dialog.UserBindRemindDialog
import com.sw.platecabinet.model.response.SearchResult //import com.sw.platecabinet.ext.maskName
import com.sw.platecabinet.utils.Debouncer //import com.sw.platecabinet.ext.maskPhone
import com.sw.platecabinet.utils.KeyboardUtils //import com.sw.platecabinet.model.ErrorInfo
import kotlinx.coroutines.flow.drop //import com.sw.platecabinet.model.request.BindParam
import kotlinx.coroutines.launch //import com.sw.platecabinet.model.response.EquipmentUserInfo
import timber.log.Timber //import com.sw.platecabinet.model.response.SearchResult
//import com.sw.platecabinet.utils.Debouncer
/** //import com.sw.platecabinet.utils.KeyboardUtils
* 餐盘柜绑定 //import com.sw.platecabinet.viewmodel.SettingViewModel
*/ //import com.sw.platecabinet.viewmodel.UserViewModel
class BindPlateFragment : BaseFragment<FragmentBindPlateBinding>( //import kotlinx.coroutines.launch
FragmentBindPlateBinding::inflate //import timber.log.Timber
) { //
private lateinit var adapter: GenericItemAdapter<SearchResult.Member, ItemSearchUserInfoBinding> ///**
internal val KEY_ACTION_TYPE = "actionType" // * 餐盘柜绑定
private var info: EquipmentUserInfo? = null // */
private var checkedItem: SearchResult.Member? = null //class BindPlateFragment : BaseFragment<FragmentBindPlateBinding>(
private var lastText = "" // FragmentBindPlateBinding::inflate
private val debouncer = Debouncer(2000) //) {
// private lateinit var adapter: GenericItemAdapter<SearchResult.Member, ItemSearchUserInfoBinding>
companion object { // internal val KEY_ACTION_TYPE = "actionType"
@JvmStatic // private var info: EquipmentUserInfo? = null
fun newInstance(userInfo: EquipmentUserInfo?) = // private var checkedItem: SearchResult.Member? = null
BindPlateFragment().apply { // private var lastText = ""
arguments = Bundle().apply { // private val debouncer = Debouncer(2000)
putParcelable(KEY_ACTION_TYPE, userInfo) //
} // private val userViewModel by viewModels<UserViewModel>()
} //
} // companion object {
// @JvmStatic
override fun initialize() { // fun newInstance(userInfo: EquipmentUserInfo?) =
arguments?.let { // BindPlateFragment().apply {
info = it.getParcelable(KEY_ACTION_TYPE) // arguments = Bundle().apply {
info?.let { // putParcelable(KEY_ACTION_TYPE, userInfo)
binding.tvNum.text = "${it.equipmentName}-${it.equipmentBoxCode}" // }
if (!TextUtils.isEmpty(it.plateNumber)) { // }
binding.etCode.text = Editable.Factory.getInstance().newEditable(it.plateNumber) // }
} //
} // @SuppressLint("SetTextI18n")
} // override fun initialize() {
initView() // arguments?.let { args ->
initListener() // info = args.getParcelable(KEY_ACTION_TYPE)
} // info?.let {
// binding.tvNum.text = "${it.equipmentName}-${it.equipmentBoxCode}"
private fun initView() { // if (!TextUtils.isEmpty(it.plateNumber)) {
adapter = createAdapter() //// binding.etCode.text = Editable.Factory.getInstance().newEditable(it.plateNumber)
binding.recyclerview.layoutManager = GridLayoutManager(context, 2) // binding.etCode.apply {
// 添加间距装饰(12dp) // setText(it.plateNumber)
binding.recyclerview.addItemDecoration( // setSelection(length())
GridSpacingItemDecoration( // }
spanCount = 2, // }
spacing = requireContext().dpToPx(6), // }
includeEdge = false // 包含边缘间距 // }
) // initView()
) // initListener()
binding.recyclerview.adapter = adapter // }
binding.recyclerview.addOnScrollListener(object : RecyclerView.OnScrollListener() { //
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { // private fun initView() {
super.onScrolled(recyclerView, dx, dy) // adapter = createAdapter()
val layoutManager = recyclerView.layoutManager as LinearLayoutManager // binding.recyclerview.layoutManager = GridLayoutManager(context, 2)
val lastVisibleItem = layoutManager.findLastVisibleItemPosition() // // 添加间距装饰(12dp)
val totalItems = layoutManager.itemCount // binding.recyclerview.addItemDecoration(
// GridSpacingItemDecoration(
if (!viewModel.isLoading && viewModel.canLoadMore // spanCount = 2,
&& lastVisibleItem >= totalItems - 3 // spacing = requireContext().dpToPx(6),
) { // includeEdge = false // 包含边缘间距
viewModel.getSearchMemberList( // )
param = lastText, // )
pageNum = viewModel.currentPage // binding.recyclerview.adapter = adapter
) // binding.recyclerview.addOnScrollListener(object : RecyclerView.OnScrollListener() {
} // override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
} // super.onScrolled(recyclerView, dx, dy)
}) // val layoutManager = recyclerView.layoutManager as LinearLayoutManager
(activity as MainActivity).registerKeyEventInfo { // val lastVisibleItem = layoutManager.findLastVisibleItemPosition()
if (binding.etUserInfo.isFocused) return@registerKeyEventInfo // val totalItems = layoutManager.itemCount
binding.etCode.text.clear() //
binding.etCode.postDelayed({ // if (!viewModel.isLoading && viewModel.canLoadMore
binding.etCode.text.clear() // && lastVisibleItem >= totalItems - 3
binding.etCode.text = Editable.Factory.getInstance().newEditable(it) // ) {
binding.etUserInfo.requestFocus() // viewModel.getSearchMemberList(
}, 50) // param = lastText,
} // pageNum = viewModel.currentPage
} // )
// }
private fun initListener() { // }
binding.etCode.addTextChangedListener(object : TextWatcher { // })
override fun beforeTextChanged( // (activity as MainActivity).registerKeyEventInfo {
s: CharSequence?, //// binding.etCode.text.clear()
start: Int, //// binding.etCode.postDelayed({
count: Int, //// binding.etCode.text.clear()
after: Int //// binding.etCode.text = Editable.Factory.getInstance().newEditable(it)
) { // activity?.runOnUiThread {
Timber.d("beforeTextChanged s = ${s.toString()}, start = $start, count = $count, after = $after") // if (binding.etUserInfo.isFocused) {
} // return@runOnUiThread
// }
override fun onTextChanged( // binding.etCode.apply {
s: CharSequence?, // if (it.length > 6) {
start: Int, // setText(it.substring(0, 6))
before: Int, // } else {
count: Int // setText(it)
) { // }
Timber.d("onTextChanged s = ${s.toString()}, start = $start, count = $count, before = $before") // if (length() > 0) {
} // setSelection(length())
// }
override fun afterTextChanged(s: Editable?) { // }
Timber.d("afterTextChanged s = ${s.toString()}") // binding.etUserInfo.requestFocus()
} // }
}) //// }, 50)
binding.etUserInfo.setOnEditorActionListener { _, actionId, keyEvent -> // }
if (actionId == EditorInfo.IME_ACTION_SEARCH) { // }
val currentText = binding.etUserInfo.text.toString() //
val containsNewlines = currentText.contains('\n') || currentText.contains('\r') // private fun initListener() {
if (containsNewlines) { // binding.etCode.addTextChangedListener(object : TextWatcher {
Timber.d("非用户主动搜索") // override fun beforeTextChanged(
true // s: CharSequence?,
} // start: Int,
debouncer.debounce { // count: Int,
// 执行搜索操作 // after: Int
viewModel.getSearchMemberList(param = currentText, pageNum = 1) // ) {
// 隐藏键盘 // Timber.d("beforeTextChanged s = ${s.toString()}, start = $start, count = $count, after = $after")
KeyboardUtils.hideKeyboard(requireActivity()) // }
} //
true // 表示已处理该事件 // override fun onTextChanged(
} else { // s: CharSequence?,
false // 未处理其他动作 // start: Int,
} // before: Int,
} // count: Int
binding.llBind.setOnClickListener { // ) {
if (info == null || info!!.equipmentCode?.isEmpty() == true || info!!.equipmentBoxCode?.isEmpty() == true) { // Timber.d("onTextChanged s = ${s.toString()}, start = $start, count = $count, before = $before")
ToastUtils.showToast("传入的设备信息异常") // }
return@setOnClickListener //
} // override fun afterTextChanged(s: Editable?) {
val plateNumber = binding.etCode.text.toString() // Timber.d("afterTextChanged s = ${s.toString()}")
if (plateNumber.isEmpty()) { // }
ToastUtils.showToast("请输入餐盘号") // })
return@setOnClickListener // binding.etUserInfo.setOnEditorActionListener { _, actionId, keyEvent ->
} // if (actionId == EditorInfo.IME_ACTION_SEARCH) {
if (checkedItem == null) { // val currentText = binding.etUserInfo.text.toString()
ToastUtils.showToast("请选择要绑定的会员") // val containsNewlines = currentText.contains('\n') || currentText.contains('\r')
return@setOnClickListener // if (containsNewlines) {
} // Timber.d("非用户主动搜索")
if (checkedItem!!.id == null) { // true
ToastUtils.showToast("选中的会员信息异常") // }
return@setOnClickListener // debouncer.debounce {
} // // 执行搜索操作
// viewModel.getSearchMemberList(param = currentText, pageNum = 1)
viewModel.bindPlate( // // 隐藏键盘
equipmentCode = info!!.equipmentCode!!, // KeyboardUtils.hideKeyboard(requireActivity())
equipmentBoxCode = info!!.equipmentBoxCode!!, // }
memberId = checkedItem!!.id!!, // true // 表示已处理该事件
plateNumber = plateNumber // } else {
) // false // 未处理其他动作
KeyboardUtils.hideKeyboard(requireActivity()) // }
} // }
binding.tvBack.setOnClickListener { activity?.finish() } // binding.ivSearch.setOnClickListener {
} // debouncer.debounce {
// // 执行搜索操作
/** // viewModel.getSearchMemberList(
* 监听数据变化 // param = binding.etUserInfo.text.toString(),
*/ // pageNum = 1
override fun registerDataChange() { // )
super.registerDataChange() // // 隐藏键盘
lifecycleScope.launch { // KeyboardUtils.hideKeyboard(requireActivity())
viewModel.searchMemberList.drop(1).collect { // }
adapter.updateData(it) // }
} // binding.llBind.setOnClickListener {
} // if (equipmentBoxCode.isNullOrBlank().not()) {
lifecycleScope.launch { // showBindDialog()
viewModel.bindStateChange.drop(1).collect { // return@setOnClickListener
if (it.first == null) return@collect // 解绑状态过滤 // }
val errorInfo = it.second // if (info == null || info!!.equipmentCode?.isEmpty() == true || info!!.equipmentBoxCode?.isEmpty() == true) {
if (errorInfo.isSuccess()) { // ToastUtils.showToast("传入的设备信息异常")
if (errorInfo.equipmentUserInfo == null) { // return@setOnClickListener
ToastUtils.showToast("数据异常") // }
return@collect // val plateNumber = binding.etCode.text.toString()
} // if (plateNumber.isEmpty()) {
if (errorInfo.equipmentUserInfo.isOtherEquipment() == true) { // ToastUtils.showToast("请输入餐盘号")
MainActivity.start( // return@setOnClickListener
requireContext(), // }
pageType = PageType.PLATE_TIP, // if (checkedItem == null) {
equipmentUserInfo = errorInfo.equipmentUserInfo // ToastUtils.showToast("请选择要绑定的会员")
) // return@setOnClickListener
} else { // }
SerialApi.openPlate( // if (checkedItem!!.id == null) {
errorInfo.equipmentUserInfo.equipmentBoxCode!!.toInt(), // ToastUtils.showToast("选中的会员信息异常")
object : SerialPortManager.SendCallback { // return@setOnClickListener
override fun onSuccess() { // }
ToastUtils.showToast("绑定成功") // val bindParam = BindParam(
activity?.finish() // equipmentId = info?.equipmentId,
} // equipmentCode = info?.equipmentCode,
// equipmentBoxCode = info?.equipmentBoxCode,
override fun onFail(e: Exception?) { // faceId = checkedItem?.faceId,
ToastUtils.showToast("绑定失败") // plateNumber = plateNumber
} // )
}) // viewModel.bindPlate(bindParam) { pair ->
} // updateBindState(pair)
} else { // }
ToastUtils.showToast(errorInfo.msg) // KeyboardUtils.hideKeyboard(requireActivity())
} // }
} // binding.tvBack.setOnClickListener { activity?.finish() }
} // }
} //
// /**
private fun createAdapter(): GenericItemAdapter<SearchResult.Member, ItemSearchUserInfoBinding> { // * 监听数据变化
return GenericItemAdapter( // */
items = emptyList(), // override fun registerDataChange() {
bindingInflater = ItemSearchUserInfoBinding::inflate, // super.registerDataChange()
bindCallback = { item, position -> // lifecycleScope.launch {
this.tvName.text = item.name.maskName() // viewModel.searchMemberList.collect {
this.tvPhone.text = item.phone.maskPhone() // adapter.updateData(it)
if (item.id != checkedItem?.id) { // }
this.llRoot.setBackgroundResource(R.drawable.grid_item_bind) // }
} else { //// lifecycleScope.launch {
this.llRoot.setBackgroundResource(R.drawable.grid_item_unbind) //// viewModel.bindStateChange.collect {
} //// updateBindState(it)
//// }
this.llRoot.setOnClickListener { //// }
Timber.d("itemClick ${item.name}, position = $position") // }
checkedItem = item //
adapter.notifyDataSetChanged() // private fun createAdapter(): GenericItemAdapter<SearchResult.Member, ItemSearchUserInfoBinding> {
} // return GenericItemAdapter(
} // items = emptyList(),
) // bindingInflater = ItemSearchUserInfoBinding::inflate,
} // bindCallback = { item, position ->
} // this.tvName.text = item.name.maskName()
// this.tvPhone.text = item.phone.maskPhone()
// if (item.id != checkedItem?.id) {
// this.llRoot.setBackgroundResource(R.drawable.grid_item_bind)
// } else {
// this.llRoot.setBackgroundResource(R.drawable.grid_item_unbind)
// }
//
// this.llRoot.setOnClickListener {
// Timber.d("itemClick ${item.name}, position = $position")
// checkedItem = item
// adapter.notifyDataSetChanged()
// //TODO 调用接口查询用户是否已绑盘
// this@BindPlateFragment.equipmentBoxCode = null
// (activity as? MainActivity)?.showWaitingDialog("查询中,请稍后……")
// userViewModel.getUserInfoById(memberId = item.faceId) {
// (activity as? MainActivity)?.hideWaitingDialog()
// this@BindPlateFragment.equipmentBoxCode = it?.equipmentBoxCode
// }
// }
// }
// )
// }
//
// private var equipmentBoxCode:String? = null
//
// private fun showBindDialog() {
// UserBindRemindDialog(
// activity = requireActivity(),
// content = "您已绑定编号为${equipmentBoxCode}的柜子,请解绑后重试"
// )
// .show()
// }
//
// private fun updateBindState(pair: Pair<Boolean?, ErrorInfo>){
// if (pair.first == null) return // 解绑状态过滤
// val errorInfo = pair.second
// if (errorInfo.isSuccess()) {
// if (errorInfo.equipmentUserInfo == null) {
// ToastUtils.showToast("数据异常")
// return
// }
//// if (errorInfo.equipmentUserInfo.isOtherEquipment()) {
//// MainActivity.start(
//// requireContext(),
//// pageType = PageType.PLATE_TIP,
//// equipmentUserInfo = errorInfo.equipmentUserInfo,
//// isAdmin = true
//// )
//// } else {
// SerialApi.openPlate(
// errorInfo.equipmentUserInfo.equipmentBoxCode!!.toInt(),
// object : SerialPortManager.SendCallback {
// override fun onSuccess() {
// ToastUtils.showToast("绑定成功")
// activity?.finish()
// }
//
// override fun onFail(e: Exception?) {
// ToastUtils.showToast("绑定失败")
// }
// })
//// }
// } else {
// ToastUtils.showToast(errorInfo.msg)
// }
// }
//}
@@ -1,9 +1,11 @@
package com.sw.platecabinet.fragment package com.sw.platecabinet.fragment
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.os.CountDownTimer import android.os.CountDownTimer
import com.sw.platecabinet.Constants import com.sw.platecabinet.Constants
import com.sw.platecabinet.R import com.sw.platecabinet.R
import com.sw.platecabinet.activity.InitActivity
import com.sw.platecabinet.databinding.FragmentPlateOpenBinding import com.sw.platecabinet.databinding.FragmentPlateOpenBinding
import com.sw.platecabinet.model.response.EquipmentUserInfo import com.sw.platecabinet.model.response.EquipmentUserInfo
@@ -13,41 +15,62 @@ import com.sw.platecabinet.model.response.EquipmentUserInfo
class PlateOpenFragment : class PlateOpenFragment :
BaseFragment<FragmentPlateOpenBinding>(FragmentPlateOpenBinding::inflate) { BaseFragment<FragmentPlateOpenBinding>(FragmentPlateOpenBinding::inflate) {
private var totalTimeInMillis: Long = Constants.AUTO_CLOSE_TIME * 1000 private var totalTimeInMillis: Long = Constants.AUTO_CLOSE_TIME * 1000
internal val KEY_ACTION_TYPE = "actionType" // internal val KEY_ACTION_TYPE = "actionType"
private var info: EquipmentUserInfo? = null // internal val PARAM_IS_ADMIN = "isAdmin"
// private var info: EquipmentUserInfo? = null
// private var isAdmin: Boolean = false
private var isScanCode = false
companion object { companion object {
const val IS_SCAN_CODE = "isScanCode"
// @JvmStatic
// fun newInstance(userInfo: EquipmentUserInfo?, isAdmin: Boolean) =
// PlateOpenFragment().apply {
// arguments = Bundle().apply {
//// putParcelable(KEY_ACTION_TYPE, userInfo)
//// putBoolean(PARAM_IS_ADMIN, isAdmin)
// }
// }
@JvmStatic @JvmStatic
fun newInstance(userInfo: EquipmentUserInfo?) = fun newInstance(isScanCode: Boolean) =
PlateOpenFragment().apply { PlateOpenFragment().apply {
arguments = Bundle().apply { arguments = Bundle().apply {
putParcelable(KEY_ACTION_TYPE, userInfo) putBoolean(IS_SCAN_CODE, isScanCode)
} }
} }
} }
override fun initialize() { override fun initialize() {
arguments?.let { arguments?.let {
info = it.getParcelable(KEY_ACTION_TYPE) // info = it.getParcelable(KEY_ACTION_TYPE)
info?.let { // isAdmin = it.getBoolean(PARAM_IS_ADMIN)
if (!it.isBound()) { // info?.let {
binding.ivType.setImageResource(R.drawable.ic_tip_warn) // if (!it.hasEquipmentBox()) {
binding.tvTitle.text = "暂无餐盘信息" // binding.ivType.setImageResource(R.drawable.ic_tip_warn)
binding.tvSubTitle.text = "请联系管理员" // binding.tvTitle.text = "暂无餐盘信息"
binding.tvTitle.setTextColor(requireContext().getColor(R.color.tip_title_fail)) // binding.tvSubTitle.text = "请联系管理员"
} else if (it.isOtherEquipment()) { // binding.tvTitle.setTextColor(requireContext().getColor(R.color.tip_title_fail))
binding.ivType.setImageResource(R.drawable.ic_tip_warn) // }
binding.tvTitle.text = "暂无餐盘信息" //// else if (it.isOtherEquipment()) {
binding.tvSubTitle.text = "您的餐盘绑定在${it.equipmentName ?: " "}号柜上" //// binding.ivType.setImageResource(R.drawable.ic_tip_warn)
binding.tvTitle.setTextColor(requireContext().getColor(R.color.tip_title_fail)) //// binding.tvTitle.text = "暂无餐盘信息"
} else { //// binding.tvSubTitle.text = "您的餐盘绑定在${it.equipmentName ?: " "}号柜上"
//// binding.tvTitle.setTextColor(requireContext().getColor(R.color.tip_title_fail))
//// }
// else {
// binding.ivType.setImageResource(R.drawable.ic_tip_success)
// binding.tvTitle.text = "柜门开启"
// binding.tvSubTitle.text = "餐盘取出后请关闭柜门"
// binding.tvTitle.setTextColor(requireContext().getColor(R.color.tip_title_success))
// }
// }
isScanCode = it.getBoolean(IS_SCAN_CODE, false)
binding.ivType.setImageResource(R.drawable.ic_tip_success) binding.ivType.setImageResource(R.drawable.ic_tip_success)
binding.tvTitle.text = "柜门开启" binding.tvTitle.text = "柜门开启"
binding.tvSubTitle.text = "餐盘取出后请关闭柜门" binding.tvSubTitle.text = if (isScanCode) "餐盘放入后请关闭柜门" else "餐盘取出后请关闭柜门"
binding.tvTitle.setTextColor(requireContext().getColor(R.color.tip_title_success)) binding.tvTitle.setTextColor(requireContext().getColor(R.color.tip_title_success))
} }
}
}
initCountTime() initCountTime()
} }
@@ -60,6 +83,11 @@ class PlateOpenFragment :
override fun onFinish() { override fun onFinish() {
activity?.finish() activity?.finish()
// if (!isAdmin) {
val intent = Intent(context, InitActivity::class.java)
startActivity(intent)
// }
} }
}.start() }.start()
} }
@@ -11,7 +11,9 @@ import com.sw.plate.utils.ToastUtils
import com.sw.plate.utils.comn.SerialApi import com.sw.plate.utils.comn.SerialApi
import com.sw.plate.utils.comn.SerialPortManager import com.sw.plate.utils.comn.SerialPortManager
import com.sw.platecabinet.GlobalData import com.sw.platecabinet.GlobalData
import com.sw.platecabinet.MyApp
import com.sw.platecabinet.R import com.sw.platecabinet.R
import com.sw.platecabinet.activity.BaseActivity
import com.sw.platecabinet.activity.MainActivity import com.sw.platecabinet.activity.MainActivity
import com.sw.platecabinet.activity.PageType import com.sw.platecabinet.activity.PageType
import com.sw.platecabinet.adapter.GenericItemAdapter import com.sw.platecabinet.adapter.GenericItemAdapter
@@ -24,8 +26,9 @@ import com.sw.platecabinet.ext.formatNumber
import com.sw.platecabinet.ext.maskName import com.sw.platecabinet.ext.maskName
import com.sw.platecabinet.model.response.EquipmentUserInfo import com.sw.platecabinet.model.response.EquipmentUserInfo
import com.sw.platecabinet.utils.ListArrangementUtil import com.sw.platecabinet.utils.ListArrangementUtil
import com.sw.platecabinet.utils.PlateUtils
import com.sw.platecabinet.utils.SpTool
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
@@ -48,12 +51,12 @@ class SettingListFragment :
override fun registerDataChange() { override fun registerDataChange() {
super.registerDataChange() super.registerDataChange()
lifecycleScope.launch { // lifecycleScope.launch {
viewModel.equipmentList.drop(1).collect { it -> // viewModel.equipmentList.collect { it ->
Timber.d("registerDateChange updateAdapter it = $it") // Timber.d("registerDateChange updateAdapter it = $it")
updateAdapter(it) // updateAdapter(it)
} // }
} // }
} }
override fun initialize() { override fun initialize() {
@@ -81,8 +84,22 @@ class SettingListFragment :
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
viewModel.getEquipmentList() // viewModel.getEquipmentList{
// updateAdapter(it)
// }
MyApp.plateList?.let {
updateAdapter(it)
} }
}
// private fun getPlateData(block: (List<EquipmentUserInfo>) -> Unit) {
// MyApp.plateList?.let {
// block(it)
// }
//// viewModel.getEquipmentList { items ->
//// block(items)
//// }
// }
private fun createItemAdapter(): GenericItemAdapter<EquipmentUserInfo, ItemBindViewBinding> { private fun createItemAdapter(): GenericItemAdapter<EquipmentUserInfo, ItemBindViewBinding> {
return GenericItemAdapter( return GenericItemAdapter(
@@ -109,74 +126,66 @@ class SettingListFragment :
item: EquipmentUserInfo, item: EquipmentUserInfo,
position: Int position: Int
) { ) {
if (item.isPlaceholder() == true) { // if (item.isPlaceholder()) {
this.llRoot.isVisible = false // this.llRoot.isVisible = false
return // return
} // }
this.llRoot.isVisible = true this.llRoot.isVisible = true
this.tvNum.text = formatNumber(item.equipmentBoxCode ?: "") this.tvNum.text = formatNumber(item.equipmentBoxCode ?: "")
if (item.isBound()) { if (item.plateNumber.isNullOrBlank().not()) {
val date = DateTimeUtils.parseDateTime(item.updateTime) // val date = DateTimeUtils.parseDateTime(item.updateTime)
val timeInMillis = date?.time ?: 0L // val timeInMillis = date?.time ?: 0L
val isOldTime = DateTimeUtils.isMoreThanHoursFromNow(timeInMillis) // val isOldTime = DateTimeUtils.isMoreThanHoursFromNow(timeInMillis)
this.llRoot.setBackgroundResource(R.drawable.grid_item_bind) this.llRoot.setBackgroundResource(R.drawable.grid_item_bind)
this.tvNum.setTextColor(resources.getColor(R.color.bind_4E535D)) this.tvNum.setTextColor(resources.getColor(R.color.bind_4E535D))
this.tvLastTime.setTextColor( // this.tvLastTime.setTextColor(
if (isOldTime) resources.getColor(R.color.bind_time_old) else resources.getColor( // if (isOldTime) resources.getColor(R.color.bind_time_old) else resources.getColor(
R.color.bind_585868 // R.color.bind_585868
) // )
) // )
this.llOpen.setBackgroundResource(R.drawable.grid_button_bind) this.llOpen.setBackgroundResource(R.drawable.grid_button_bind)
this.tvOpen.setTextColor(resources.getColor(R.color.bind_F0C8B4)) this.tvOpen.setTextColor(resources.getColor(R.color.bind_FFCC99))
this.llBindInfo.visibility = View.VISIBLE this.llBindInfo.visibility = View.VISIBLE
this.tvUnbind.visibility = View.GONE this.tvUnbind.visibility = View.GONE
this.tvName.text = item.name.maskName() // this.tvName.text = item.name.maskName()
this.tvLastTime.text = DateTimeUtils.getTimeAgo(date) this.tvName.text = item.plateNumber
// this.tvLastTime.text = DateTimeUtils.getTimeAgo(date)
} else { } else {
this.llRoot.setBackgroundResource(R.drawable.grid_item_unbind) this.llRoot.setBackgroundResource(R.drawable.grid_item_unbind)
this.tvNum.setTextColor(resources.getColor(R.color.bind_F0C8B4)) this.tvNum.setTextColor(resources.getColor(R.color.bind_FFCC99))
this.llOpen.setBackgroundResource(R.drawable.grid_button_unbind) this.llOpen.setBackgroundResource(R.drawable.grid_button_unbind)
this.tvOpen.setTextColor(resources.getColor(R.color.unbind_32283C)) this.tvOpen.setTextColor(resources.getColor(R.color.unbind_32283C))
this.llBindInfo.visibility = View.GONE this.llBindInfo.visibility = View.GONE
this.tvUnbind.visibility = View.VISIBLE this.tvUnbind.visibility = View.VISIBLE
// this.tvUnbind.text = "未绑定"
} }
this.llRoot.setOnClickListener { // this.llRoot.setOnClickListener {
Timber.d("itemClick llRoot ${item.name}, position = $position") //// Timber.d("itemClick llRoot ${item.name}, position = $position")
if (item.isBound()) { // if (item.isBound()) {
MainActivity.start( // MainActivity.start(
requireContext(), // requireContext(),
pageType = PageType.UNBIND_PLATE, // pageType = PageType.UNBIND_PLATE,
equipmentUserInfo = item // equipmentUserInfo = item
) // )
} else { // } else {
MainActivity.start( // MainActivity.start(
requireContext(), // requireContext(),
pageType = PageType.BIND_PLATE, // pageType = PageType.BIND_PLATE,
equipmentUserInfo = item // equipmentUserInfo = item
) // )
} // }
} // }
this.llOpen.setOnClickListener { this.llOpen.setOnClickListener {
Timber.d("itemClick llOpen equipmentBoxCode = ${item.equipmentBoxCode}") // item.faceId = null
val equipmentBoxCode: Int = item.equipmentBoxCode?.toInt() ?: -1 PlateUtils.open(
if (equipmentBoxCode < 0) { fragmentActivity = requireActivity(),
ToastUtils.showToast("餐盘柜编号错误") code = item.equipmentBoxCode,
return@setOnClickListener isScanCode = false,
} isSetting = true
showWaitingDialog("请稍等") )
SerialApi.openPlate(equipmentBoxCode, object : SerialPortManager.SendCallback {
override fun onSuccess() {
hideWaitingDialog()
MainActivity.start(requireContext(), pageType = PageType.PLATE_TIP)
}
override fun onFail(e: Exception?) {
hideWaitingDialog()
ToastUtils.showToast("柜门打开失败")
}
})
} }
} }
@@ -1,73 +1,95 @@
package com.sw.platecabinet.fragment //package com.sw.platecabinet.fragment
//
import android.os.Bundle //import android.app.Activity
import androidx.lifecycle.lifecycleScope //import android.content.Intent
import com.sw.inbound.utils.DateTimeUtils //import android.os.Bundle
import com.sw.plate.utils.ToastUtils //import androidx.activity.result.contract.ActivityResultContracts
import com.sw.platecabinet.databinding.FragmentUnbindPlateBinding //import androidx.lifecycle.lifecycleScope
import com.sw.platecabinet.ext.maskName //import com.sw.inbound.utils.DateTimeUtils
import com.sw.platecabinet.ext.maskPhone //import com.sw.plate.utils.ToastUtils
import com.sw.platecabinet.model.response.EquipmentUserInfo //import com.sw.platecabinet.GlobalKey
import kotlinx.coroutines.flow.drop //import com.sw.platecabinet.activity.UnBindDialogActivity
import kotlinx.coroutines.launch //import com.sw.platecabinet.databinding.FragmentUnbindPlateBinding
//import com.sw.platecabinet.ext.maskName
/** //import com.sw.platecabinet.ext.maskPhone
* 餐盘柜绑定 //import com.sw.platecabinet.model.ErrorInfo
*/ //import com.sw.platecabinet.model.response.EquipmentUserInfo
class UnBindPlateFragment private constructor() : BaseFragment<FragmentUnbindPlateBinding>( //import kotlinx.coroutines.launch
FragmentUnbindPlateBinding::inflate //
) { ///**
internal val ARG_PARAM1 = "param1" // * 餐盘柜绑定
private var info: EquipmentUserInfo? = null // */
//class UnBindPlateFragment private constructor() : BaseFragment<FragmentUnbindPlateBinding>(
companion object { // FragmentUnbindPlateBinding::inflate
@JvmStatic //) {
fun newInstance(param1: EquipmentUserInfo?) = // internal val ARG_PARAM1 = "param1"
UnBindPlateFragment().apply { // private var info: EquipmentUserInfo? = null
arguments = Bundle().apply { //
putParcelable(ARG_PARAM1, param1) // companion object {
} // @JvmStatic
} // fun newInstance(param1: EquipmentUserInfo?) =
} // UnBindPlateFragment().apply {
// arguments = Bundle().apply {
override fun initialize() { // putParcelable(ARG_PARAM1, param1)
arguments?.let { // }
info = it.getParcelable(ARG_PARAM1) // }
info?.let { // }
binding.tvNum.text = "${it.equipmentName}-${it.equipmentBoxCode}" //
binding.tvPlateNumberValue.text = it.plateNumber // private val dialogLauncher = registerForActivityResult(
binding.tvNameValue.text = it.name.maskName() // ActivityResultContracts.StartActivityForResult()
binding.tvPhoneValue.text = it.phone.maskPhone() // ) { result ->
val timeInfo = DateTimeUtils.parseDateTime(it.updateTime) // if (result.resultCode == Activity.RESULT_OK) {
val timeAgo = DateTimeUtils.getTimeAgo(timeInfo) // viewModel.unbindPlate(info?.id) { pair ->
binding.tvUpdateTimeValue.text = timeAgo // updateBindState(pair)
} // }
} // }
binding.llUnbind.setOnClickListener { // }
if (info == null) return@setOnClickListener //
viewModel.unbindPlate( // override fun initialize() {
equipmentBoxCode = info!!.equipmentBoxCode!!, // arguments?.let { args ->
equipmentCode = info!!.equipmentCode!! // info = args.getParcelable(ARG_PARAM1)
) // info?.let {
} // binding.tvNum.text = "${it.equipmentName}-${it.equipmentBoxCode}"
binding.tvBack.setOnClickListener { // binding.tvPlateNumberValue.text = it.plateNumber
activity?.finish() // binding.tvNameValue.text = it.name.maskName()
} // binding.tvPhoneValue.text = it.phone.maskPhone()
} // val timeInfo = DateTimeUtils.parseDateTime(it.updateTime)
// val timeAgo = DateTimeUtils.getTimeAgo(timeInfo)
override fun registerDataChange() { // binding.tvUpdateTimeValue.text = timeAgo
super.registerDataChange() // }
lifecycleScope.launch { // }
viewModel.bindStateChange.drop(1).collect { // binding.llUnbind.setOnClickListener {
if (it.first == null) return@collect // 绑定状态过滤 // if (info == null) return@setOnClickListener
val errorInfo = it.second // val intent = Intent(requireContext(), UnBindDialogActivity::class.java).apply {
if (errorInfo.isSuccess()) { // putExtra(GlobalKey.PARAM_EQUIPMENT_INFO, info)
ToastUtils.showToast("解绑成功") // }
activity?.finish() // dialogLauncher.launch(intent)
} else { // requireActivity().overridePendingTransition(0, 0)
ToastUtils.showToast(errorInfo.msg) // }
} // binding.tvBack.setOnClickListener {
} // activity?.finish()
} // }
} // }
} //
// override fun registerDataChange() {
// super.registerDataChange()
//// lifecycleScope.launch {
//// viewModel.bindStateChange.collect {
//// updateBindState(it)
//// }
//// }
// }
// private fun updateBindState(pair: Pair<Boolean?, ErrorInfo>){
// if (pair.first == null) return // 绑定状态过滤
// val errorInfo = pair.second
// if (errorInfo.isSuccess()) {
// ToastUtils.showToast("解绑成功")
//// activity?.finish()
// // 关闭宿主 Activity
// requireActivity().finish()
// requireActivity().overridePendingTransition(0, 0)
// } else {
// ToastUtils.showToast(errorInfo.msg)
// }
// }
//}
@@ -6,11 +6,22 @@ import com.sw.platecabinet.model.response.EquipmentUserInfo
* 错误信息 code == 200 成功 * 错误信息 code == 200 成功
*/ */
data class ErrorInfo( data class ErrorInfo(
val code: Int = 200, val code: String = "00000",
val msg: String = "", val msg: String = "",
val equipmentUserInfo: EquipmentUserInfo? = null val equipmentUserInfo: EquipmentUserInfo? = null
) { ) {
fun isSuccess(): Boolean { fun isSuccess(): Boolean {
return code == 200 return code == "00000"
} }
} }
data class DeviceConfig(
var arcsoftAppId: String? = null,
var arcsoftSdkKey: String? = null,
var arcsoftActiveKey: String? = null
)
data class CodeMsg(
val code: String? = "",
val msg: String? = ""
)
@@ -12,28 +12,25 @@ import kotlinx.parcelize.Parcelize
@Parcelize @Parcelize
data class BindParam( data class BindParam(
/** /**
* app版本号 * 设备Id
*/ */
var appVersion: String = "", val equipmentId: String? = null,
/** /**
* 设备ID * 设备编号
*/ */
@SerializedName("equipmentCode") var equipmentCode: String? = "",
var equipmentCode: String = "",
/** /**
*设备盒子编号 *设备盒子编号
*/ */
@SerializedName("equipmentBoxCode")
val equipmentBoxCode: String? = null, val equipmentBoxCode: String? = null,
/** /**
* *
* 会员Id * 会员Id
*/ */
@SerializedName("memberId") val memberId: String? = null,
val memberId: Int? = null, val faceId: String? = null,
/** /**
*餐盘编号 *餐盘编号
*/ */
@SerializedName("plateNumber")
val plateNumber: String? = null val plateNumber: String? = null
) : Parcelable ) : Parcelable
@@ -11,27 +11,20 @@ import kotlinx.parcelize.Parcelize
@Parcelize @Parcelize
data class LoginParam( data class LoginParam(
/** /**
* app版本号 * 设备Id
*/ */
var appVersion: String = "", val equipmentId: Int? = null,
/**
* 设备ID
*/
@SerializedName("equipmentCode")
var equipmentCode: String = "",
/** /**
* 会员信息 * 会员信息
*/ */
@SerializedName("memberId") // val memberId: String? = null,
val memberId: Int? = null, val faceId: String? = null,
/** /**
* 密码 * 密码
*/ */
@SerializedName("password")
val password: String? = null, val password: String? = null,
/** /**
* 手机号 * 手机号
*/ */
@SerializedName("phone")
val phone: String? = null val phone: String? = null
) : Parcelable ) : Parcelable
@@ -10,25 +10,27 @@ import kotlinx.parcelize.Parcelize
*/ */
@Parcelize @Parcelize
data class SearchParam( data class SearchParam(
/** // /**
* app版本号 // * app版本号
*/ // */
var appVersion: String = "", // var appVersion: String = "",
/** // /**
* 设备ID // * 设备ID
*/ // */
@SerializedName("equipmentCode") // @SerializedName("equipmentCode")
var equipmentCode: String = "", // var equipmentCode: String = "",
@SerializedName("consumptionTime") // @SerializedName("consumptionTime")
val consumptionTime: List<String?>? = listOf(), // val consumptionTime: List<String?>? = listOf(),
@SerializedName("createTime") // @SerializedName("createTime")
val createTime: List<String?>? = listOf(), // val createTime: List<String?>? = listOf(),
@SerializedName("memberFrom") // @SerializedName("memberFrom")
val memberFrom: Int? = 0, // val memberFrom: Int? = 0,
@SerializedName("pageNum") // @SerializedName("pageNum")
val pageNum: Int? = 0, var pageNum: Int? = 0,
@SerializedName("pageSize") // @SerializedName("pageSize")
val pageSize: Int? = 0, var pageSize: Int? = 0,
@SerializedName("param") // @SerializedName("param")
val `param`: String? = "" // val `param`: String? = "",
var name:String?=null,
var phone:String?=null
) : Parcelable ) : Parcelable
@@ -1,10 +1,12 @@
package com.sw.platecabinet.model.response package com.sw.platecabinet.model.response
data class ApiResponse<T>( data class ApiResponse<T>(
val code: Int, val code: String,
val success: Boolean? = false, // val success: Boolean? = false,
val msg: String? = "", val msg: String? = "",
val data: T? = null val data: T? = null,
// val result: T? = null,
) { ) {
fun isSuccess(): Boolean = code == 200 // fun isSuccess(): Boolean = code == 200
fun isSuccess(): Boolean = code == "00000"
} }
@@ -0,0 +1,72 @@
package com.sw.platecabinet.model.response
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@Parcelize
data class EquipmentInfo(
@SerializedName("appPackageLocalUrl")
val appPackageLocalUrl: String? = "",
@SerializedName("appPackageUrl")
val appPackageUrl: String? = "",
@SerializedName("arcsoftActiveKey")
val arcsoftActiveKey: String? = "",
@SerializedName("arcsoftAppId")
val arcsoftAppId: String? = "",
@SerializedName("arcsoftSdkKey")
val arcsoftSdkKey: String? = "",
@SerializedName("arrayCross")
val arrayCross: Int? = 0,
@SerializedName("arrayMode")
val arrayMode: String? = "",
@SerializedName("arrayVertical")
val arrayVertical: Int? = 0,
@SerializedName("canteenId")
val canteenId: String? = "",
@SerializedName("canteenName")
val canteenName: String? = "",
@SerializedName("clientServerIp")
val clientServerIp: String? = "",
@SerializedName("createBy")
val createBy: String? = "",
@SerializedName("createTime")
val createTime: String? = "",
@SerializedName("customerName")
val customerName: String? = "",
@SerializedName("equipmentCode")
val equipmentCode: String? = "",
@SerializedName("equipmentName")
val equipmentName: String? = "",
@SerializedName("equipmentName_dictText")
val equipmentNameDictText: String? = "",
@SerializedName("id")
val id: String? = "",
@SerializedName("isSync")
val isSync: Int? = 0,
@SerializedName("mqName")
val mqName: String? = "",
@SerializedName("mqPassword")
val mqPassword: String? = "",
@SerializedName("orgCode")
val orgCode: String? = "",
@SerializedName("owningTrack")
val owningTrack: Int? = 0,
@SerializedName("owningTrackOrder")
val owningTrackOrder: Int? = 0,
@SerializedName("screenHtml")
val screenHtml: String? = "",
@SerializedName("serviceRequestAddress")
val serviceRequestAddress: String? = "",
@SerializedName("status")
val status: Int? = 0,
@SerializedName("sysOrgCode")
val sysOrgCode: String? = "",
@SerializedName("updateBy")
val updateBy: String? = "",
@SerializedName("updateTime")
val updateTime: String? = "",
@SerializedName("zhstServerIp")
val zhstServerIp: String? = ""
) : Parcelable
@@ -1,8 +1,8 @@
package com.sw.platecabinet.model.response package com.sw.platecabinet.model.response
import android.os.Parcelable import android.os.Parcelable
import com.google.gson.annotations.SerializedName import android.text.TextUtils
//import com.google.gson.annotations.SerializedName
import com.sw.platecabinet.GlobalData import com.sw.platecabinet.GlobalData
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@@ -11,70 +11,80 @@ import kotlinx.parcelize.Parcelize
*/ */
@Parcelize @Parcelize
data class EquipmentUserInfo( data class EquipmentUserInfo(
// /**
// * 主键
// */
// val id: Long? = 0,
// /**
// *
// * 设备Id
// */
// val equipmentId: String? = "",
// /**
// * 设备编号
// */
// var equipmentCode: String? = "",
/** /**
* 设备盒子编号 * 设备盒子编号
*/ */
@SerializedName("equipmentBoxCode")
var equipmentBoxCode: String? = "", var equipmentBoxCode: String? = "",
/**
* 设备编号
*/
@SerializedName("equipmentCode")
var equipmentCode: String? = "",
/**
*
* 设备Id
*/
@SerializedName("equipmentId")
val equipmentId: String? = "",
/**
* 设备名称
*/
@SerializedName("equipmentName")
val equipmentName: String? = "",
/**
* 主键
*/
@SerializedName("id")
val id: Int? = 0,
/** /**
* 会员Id * 会员Id
*/ */
@SerializedName("memberId") var faceId: String? = "",
val memberId: Int? = 0,
@SerializedName("name")
val name: String? = "",
@SerializedName("phone")
val phone: String? = "",
/** /**
* 餐盘编号 * 餐盘编号
*/ */
@SerializedName("plateNumber")
var plateNumber: String? = "", var plateNumber: String? = "",
// /**
// * 设备名称
// */
// val equipmentName: String? = "",
// val orderNo: String? = "",
/** /**
* *
* 更新时间 * 更新时间
*/ */
@SerializedName("updateTime") var updateTime: String? = "",
val updateTime: String? = ""
// val name: String? = "",
// val phone: String? = "",
// val faceUrl: String? = "",
// val mealTime: String? = "",
// val mealTimeInterval: String? = "",
// val openTime: String? = "",
// val openTimeInterval: String? = "",
// val eatCount: Int? = 0,
//
// var cardBalance: Double? = 0.toDouble(),
// /**
// * 用于cardBalance后续拦截判断
// */
// var showBalanceNotEnoughDialog: Boolean = false,
// var isIntercept: Boolean = false
) : Parcelable { ) : Parcelable {
/** // /**
* 是否已绑定 // * 是否已绑定
*/ // */
fun isBound(): Boolean { // fun isBound(): Boolean {
return memberId != null // return faceId.isNullOrBlank().not()
} // }
/** // /**
* 是否是其他设备 // * 是否是其他设备
*/ // */
fun isOtherEquipment(): Boolean { // fun isOtherEquipment(): Boolean {
return equipmentCode != null && equipmentCode != "" && GlobalData.globalEquipmentCode != equipmentCode // return equipmentCode != null && equipmentCode != "" && GlobalData.globalEquipmentCode != equipmentCode
} // }
fun isPlaceholder(): Boolean { // fun hasEquipmentBox(): Boolean {
return equipmentBoxCode == null || equipmentBoxCode == "" // return !TextUtils.isEmpty(equipmentBoxCode)
} // }
//
// fun isPlaceholder(): Boolean {
// return equipmentBoxCode.isNullOrBlank()
// }
} }
@@ -49,33 +49,37 @@ data class SearchResult(
) : Parcelable { ) : Parcelable {
@Parcelize @Parcelize
data class Member( data class Member(
@SerializedName("cardBalance") // @SerializedName("cardBalance")
val cardBalance: String? = "", // val cardBalance: String? = "",
@SerializedName("cardCode") // @SerializedName("cardCode")
val cardCode: String? = "", // val cardCode: String? = "",
@SerializedName("consumptionCount") // @SerializedName("consumptionCount")
val consumptionCount: Int? = 0, // val consumptionCount: Int? = 0,
@SerializedName("consumptionTotal") // @SerializedName("consumptionTotal")
val consumptionTotal: String? = "", // val consumptionTotal: String? = "",
@SerializedName("createTime") // @SerializedName("createTime")
val createTime: String? = "", // val createTime: String? = "",
@SerializedName("firstTopUpTime") // @SerializedName("firstTopUpTime")
val firstTopUpTime: String? = "", // val firstTopUpTime: String? = "",
@SerializedName("id") // @SerializedName("id")
val id: Int? = 0, // val id: Int? = 0,
@SerializedName("integralBalance") // @SerializedName("integralBalance")
val integralBalance: Int? = 0, // val integralBalance: Int? = 0,
@SerializedName("lastConsumptionTime") // @SerializedName("lastConsumptionTime")
val lastConsumptionTime: String? = "", // val lastConsumptionTime: String? = "",
@SerializedName("memberFrom") // @SerializedName("memberFrom")
val memberFrom: Int? = 0, // val memberFrom: Int? = 0,
@SerializedName("name") // @SerializedName("name")
// val name: String? = "",
// @SerializedName("phone")
// val phone: String? = "",
// @SerializedName("rewardBalance")
// val rewardBalance: String? = "",
// @SerializedName("topUpBalance")
// val topUpBalance: String? = ""
val id: String? = "",
val faceId: String? = "",
val name: String? = "", val name: String? = "",
@SerializedName("phone")
val phone: String? = "", val phone: String? = "",
@SerializedName("rewardBalance")
val rewardBalance: String? = "",
@SerializedName("topUpBalance")
val topUpBalance: String? = ""
) : Parcelable ) : Parcelable
} }
@@ -10,14 +10,33 @@ import kotlinx.parcelize.Parcelize
*/ */
@Parcelize @Parcelize
data class UserFaceModel( data class UserFaceModel(
@SerializedName("faceFeature") // @SerializedName("faceFeature")
val faceFeature: String? = "", // val faceFeature: String? = "",
@SerializedName("faceFeatureString") // @SerializedName("faceFeatureString")
val faceFeatureString: String? = "", // val faceFeatureString: String? = "",
@SerializedName("faceType") // @SerializedName("faceType")
val faceType: String? = "", // val faceType: String? = "",
@SerializedName("userFaceId") // @SerializedName("userFaceId")
val userFaceId: String? = "", // val userFaceId: String? = "",
@SerializedName("userId") // @SerializedName("userId")
val userId: String? = "" // val userId: String? = "",
// @SerializedName("updateTimeStamp")
// val updateTimeStamp: Long = 0,
val userId: String? = "",
val faceFeatureStr: String? = "",
val faceUpdateTimestamp: Long? = null,
/**
* 人脸删除标识
*/
val faceDeleted: Boolean? = false,
/**
* 会员编号
*/
val cardNo: String,
/**
* 是否会员
*/
val isMember: Boolean
) : Parcelable ) : Parcelable
@@ -1,5 +1,6 @@
package com.sw.platecabinet.network package com.sw.platecabinet.network
import com.sw.platecabinet.GlobalData
import com.sw.platecabinet.MyApp import com.sw.platecabinet.MyApp
import com.sw.platecabinet.network.api.ApiService import com.sw.platecabinet.network.api.ApiService
import com.sw.platecabinet.network.interceptor.RequestInterceptor import com.sw.platecabinet.network.interceptor.RequestInterceptor
@@ -11,12 +12,14 @@ import timber.log.Timber
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
object ApiClient { object ApiClient {
private const val BASE_URL = "https://vip.shuziweidao.com" //private const val BASE_URL = "https://vip.shuziweidao.com"
// 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 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 // 超时时间(秒)
private val okHttpClient = OkHttpClient.Builder() val okHttpClient = OkHttpClient.Builder()
.connectTimeout(TIME_OUT, TimeUnit.SECONDS) .connectTimeout(TIME_OUT, TimeUnit.SECONDS)
.readTimeout(TIME_OUT, TimeUnit.SECONDS) .readTimeout(TIME_OUT, TimeUnit.SECONDS)
.writeTimeout(TIME_OUT, TimeUnit.SECONDS) .writeTimeout(TIME_OUT, TimeUnit.SECONDS)
@@ -33,7 +36,7 @@ object ApiClient {
.build() .build()
private val retrofit = Retrofit.Builder() private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL) .baseUrl(DEVICE_BASE_URL)
.client(okHttpClient) .client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create())
// .addCallAdapterFactory(CoroutineCallAdapterFactory()) // 协程适配器 // .addCallAdapterFactory(CoroutineCallAdapterFactory()) // 协程适配器
@@ -1,66 +1,207 @@
package com.sw.platecabinet.network.api package com.sw.platecabinet.network.api
import com.sw.platecabinet.GlobalData
import com.sw.platecabinet.model.DeviceConfig
import com.sw.platecabinet.model.request.BindParam import com.sw.platecabinet.model.request.BindParam
import com.sw.platecabinet.model.request.EquipmentParam import com.sw.platecabinet.model.request.EquipmentParam
import com.sw.platecabinet.model.request.LoginParam import com.sw.platecabinet.model.request.LoginParam
import com.sw.platecabinet.model.request.SearchParam import com.sw.platecabinet.model.request.SearchParam
import com.sw.platecabinet.model.response.ApiResponse 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.EquipmentUserInfo
import com.sw.platecabinet.model.response.SearchResult import com.sw.platecabinet.model.response.SearchResult
import com.sw.platecabinet.model.response.UserFaceModel import com.sw.platecabinet.model.response.UserFaceModel
import okhttp3.MultipartBody
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Multipart
import retrofit2.http.POST import retrofit2.http.POST
import retrofit2.http.Part
import retrofit2.http.Query import retrofit2.http.Query
import retrofit2.http.Url
interface ApiService { interface ApiService {
// /**
/** // * device获取token
* 生成token // */
*/ // @GET("sys/getEquipmentToken")
@GET("/shuwei-zhct/scales/generateToken") // suspend fun getDeviceToken(
suspend fun generateToken( // @Query("qrcodeId") qrcodeId: String,
@Query("deviceId") deviceId: String, // @Query("appVersion") appVersion: String = GlobalData.appVersion
@Query("appVersion") appVersion: String, // ): ApiResponse<String>
@Query("equipmentCode") equipmentCode: String //
): ApiResponse<String> // /**
// *获取配置信息
// */
// @GET("equipment/stEquipment/queryByEquipmentCode")
// suspend fun getDeviceInfo(
// @Query("equipmentCode") equipmentCode: String,
// @Query("appVersion") appVersion: String = GlobalData.appVersion,
// @Header("X-Access-Token") token: String
// ): ApiResponse<EquipmentInfo>
//
// /**
// * 生成token
// */
// @GET//("/shuwei-zhct/scales/generateToken")
// suspend fun generateToken(
// @Url url: String = "${GlobalData.appBaseUrl}/shuwei-zhct/scales/generateToken",
// @Query("deviceId") deviceId: String,
// @Query("appVersion") appVersion: String,
// @Query("equipmentCode") equipmentCode: String
// ): ApiResponse<String>
/** /**
* 获取人脸数据 * 获取人脸数据
*/ */
@GET("/shuwei-zhct/scales/getUserFaceCache") // @GET//("/shuwei-zhct/scales/getUserFaceCache")
// suspend fun getUserFaceCache(
// @Url url: String = "${GlobalData.appBaseUrl}/shuwei-zhct/scales/getUserFaceCache",
// @Query("appVersion") appVersion: String,
// @Query("equipmentCode") equipmentCode: String
// ): ApiResponse<List<UserFaceModel>>
@POST
suspend fun getUserFaceCache( suspend fun getUserFaceCache(
@Query("appVersion") appVersion: String, @Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/common/app/faceFeature/list",
@Query("equipmentCode") equipmentCode: String @Body param: Map<String, Int>
): ApiResponse<List<UserFaceModel>> ): ApiResponse<List<UserFaceModel>>
// /**
// * 餐盘用户信息获取
// */
// @POST//("/shuwei-zhct/swEquipmentRelUser/equipmentBoxLogin")
// suspend fun equipmentBoxLogin(
// @Url url: String = "${GlobalData.appBaseUrl}/shuwei-zhct/swEquipmentRelUser/equipmentBoxLogin",
// @Body param: LoginParam
// ): ApiResponse<EquipmentUserInfo>
/** /**
* 餐盘用户信息获取 * 餐盘用户信息获取
*/ */
@POST("/shuwei-zhct/swEquipmentRelUser/equipmentBoxLogin") @POST//("/shuwei-zhct/swEquipmentRelUser/equipmentBoxLogin")
suspend fun equipmentBoxLogin(@Body param: LoginParam): ApiResponse<EquipmentUserInfo> suspend fun equipmentBoxLogin(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/sideboard/app/getPlateBoxUserInfo",
@Body param: LoginParam
): ApiResponse<EquipmentUserInfo>
/** /**
* 餐盘用户信息列表查询 * 餐盘用户信息列表查询
*/ */
@POST("/shuwei-zhct/swEquipmentRelUser/list") // @POST//("/shuwei-zhct/swEquipmentRelUser/list")
suspend fun getEquipmentList(@Body param: EquipmentParam): ApiResponse<List<EquipmentUserInfo>> // suspend fun getEquipmentList(
// @Url url: String = "${GlobalData.appBaseUrl}/shuwei-zhct/swEquipmentRelUser/list",
// @Body param: EquipmentParam
// ): ApiResponse<List<EquipmentUserInfo>>
@GET
suspend fun getEquipmentList(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/sideboard/app/getYxMemberRefPlateByEquipmentCode",
): ApiResponse<List<EquipmentUserInfo>>
// /**
// * 餐盘用户信息绑定解绑
// */
// @POST//("/shuwei-zhct/swEquipmentRelUser/addOrEdit")
// suspend fun bindEquipment(
// @Url url: String = "${GlobalData.appBaseUrl}/shuwei-zhct/swEquipmentRelUser/addOrEdit",
// @Body param: BindParam
// ): ApiResponse<EquipmentUserInfo?>
/** /**
* 餐盘用户信息绑定解 * 餐盘绑
*/ */
@POST("/shuwei-zhct/swEquipmentRelUser/addOrEdit") @POST
suspend fun bindEquipment(@Body param: BindParam): ApiResponse<EquipmentUserInfo?> suspend fun plateBind(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/sideboard/app/plateBinding",
@Body param: BindParam
): ApiResponse<EquipmentUserInfo?>
/**
* 餐盘解绑
*/
@POST
@FormUrlEncoded
suspend fun plateUnbind(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/sideboard/app/plateUnbind",
@Field("id") id: Long?
): ApiResponse<Any?>
/** /**
* 用户信息模糊搜索 * 用户信息模糊搜索
*/ */
@POST("/shuwei-user/swclientUserInfoShop/selectList") @POST//("/shuwei-user/swclientUserInfoShop/selectList")
suspend fun searchUser(@Body param: SearchParam): ApiResponse<SearchResult> suspend fun searchUser(
// @Url url: String = "${GlobalData.appBaseUrl}/shuwei-user/swclientUserInfoShop/selectList",
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/common/app/getUserInfoByNameOrPhone",
@Body param: SearchParam
): ApiResponse<List<SearchResult.Member>?>
// /**
// * 通过餐盘号获取信息
// */
// @POST//("/shuwei-zhct/swEquipmentRelUser/findByPlateNumber")
// suspend fun findByPlateNumber(
// @Url url: String = "${GlobalData.appBaseUrl}/shuwei-zhct/swEquipmentRelUser/findByPlateNumber",
// @Body param: BindParam
// ): ApiResponse<EquipmentUserInfo>
/** /**
* 通过餐盘号获取信息 * 通过餐盘号获取信息
*/ */
@POST("/shuwei-zhct/swEquipmentRelUser/findByPlateNumber") @GET
suspend fun findByPlateNumber(@Body param: BindParam): ApiResponse<EquipmentUserInfo> suspend fun findByPlateNumber(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/sideboard/app/getMemberRefPlateByPlateNumber",
@Query("plateNumber") plateNumber: String
): ApiResponse<EquipmentUserInfo>
/**
* 获取人脸增量数据
*
* @param timeDate 时间戳(毫秒级)
*/
// @GET//("/shuwei-zhct/scales/getHeartbeatInterface")
// suspend fun getHeartbeatInterface(
// @Url url: String = "${GlobalData.appBaseUrl}/shuwei-zhct/scales/getHeartbeatInterface",
// @Query("timeDate") timeDate: Long
// ): ApiResponse<List<UserFaceModel>>
/**
* 获取人脸增量数据
*/
@POST
suspend fun getFaceIncrementList(
@Url url: String = "${GlobalData.appBaseUrl}/terminal/neglect/common/app/faceFeature/increment/list",
@Body param: Map<String, Long>
): ApiResponse<List<UserFaceModel>?>
/**
* 获取设备配置数据
*/
@GET
suspend fun getDeviceConfig(
@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?>
} }
@@ -2,6 +2,7 @@ package com.sw.platecabinet.network.interceptor
import com.sw.inbound.utils.SPUtil import com.sw.inbound.utils.SPUtil
import com.sw.plate.App import com.sw.plate.App
import com.sw.platecabinet.GlobalData
import com.sw.platecabinet.GlobalKey import com.sw.platecabinet.GlobalKey
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.Response import okhttp3.Response
@@ -16,7 +17,10 @@ class RequestInterceptor : Interceptor {
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
.header("Accept", "application/json") .header("Accept", "application/json")
// .header("Authorization", "Bearer ${getToken()}") // .header("Authorization", "Bearer ${getToken()}")
.header("Authorization", getToken()) // .header("Authorization", getToken())
.header("X-Access-Token", "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjYW50ZWVuSWQiOiJiZTE1NDgzMS0zNDY2LTNiYTItYTJlYS01NzY1MmM5MTlmZWQiLCJ0eXBlIjoiNCIsInVzZXJJZCI6IjAwMDAwMDAwMDAwMDAwMDAwMDEifQ.sN40cOC-O5WQFrF4IDUs8fFlkNdUKLbJt_rHyTsgYYM")
.header("X-DEVICE-CODE", GlobalData.deviceId)
.header("authorization", "57ee87183f2a4fa59683ec9ef41c8f5d")
val newRequest = requestBuilder.build() val newRequest = requestBuilder.build()
@@ -0,0 +1,104 @@
//package com.sw.platecabinet.network.task
//
//import android.content.Context
//import android.util.Log
//import androidx.work.CoroutineWorker
//import androidx.work.WorkerParameters
//import com.arcsoft.face.FaceEngine
//import com.sw.inbound.utils.GsonUtils
//import com.sw.plate.App
//import com.sw.plate.utils.Base64
//import com.sw.plate.utils.PrefUtils
//import com.sw.plate.utils.arcface.FaceApi
//import com.sw.plate.utils.arcface.facedb.entity.FaceEntity
//import com.sw.plate.utils.arcface.faceserver.FaceServer
//import com.sw.platecabinet.network.ApiClient
//import com.sw.platecabinet.repository.RemoteRepository
//import kotlinx.coroutines.Dispatchers
//import kotlinx.coroutines.withContext
//import org.greenrobot.eventbus.EventBus
//import timber.log.Timber
//
//class HeartBeatTask(appContext: Context, workerParams: WorkerParameters) :
// CoroutineWorker(appContext, workerParams) {
//
// companion object {
// private const val TAG = "HeartBeatTask"
// }
//
// override suspend fun doWork(): Result {
// return try {
// // 执行后台任务逻辑
// performSync()
//
// TaskManager.startTask()
//
// Result.success()
// } catch (e: Exception) {
// e.printStackTrace()
// Result.retry()
// }
// }
//
// private val repository: RemoteRepository by lazy {
// RemoteRepository(ApiClient.apiService)
// }
//
// private val faceApi: FaceApi = FaceApi()
//
// private suspend fun performSync() {
//// withContext(Dispatchers.IO) {
//// val faceUpdateTimeStamp = PrefUtils.getLong(App.getContext(), "faceUpdateTimeStamp", 0)
//// if (faceUpdateTimeStamp == 0L) {
//// val timestamp = System.currentTimeMillis()
//// PrefUtils.setLong(App.getContext(), "faceUpdateTimeStamp", timestamp)
//// }
//// val resp = repository.getHeartbeatInterface(
//// PrefUtils.getLong(
//// App.getContext(),
//// "faceUpdateTimeStamp",
//// 0
//// )
//// )
//// Timber.tag(TAG).d("performSync: ${GsonUtils.toJson(resp)}")
//// val list = resp.data
//// if (list.isNullOrEmpty()) {
//// return@withContext
//// }
//// val faceServer = FaceServer.getInstance()
//// val faceEngine: FaceEngine? = faceServer.faceEngine
////
//// list.forEachIndexed { index, it ->
//// try {
//// if (index == list.size - 1) {
//// PrefUtils.setLong(
//// App.getContext(),
//// "faceUpdateTimeStamp",
//// it.updateTimeStamp
//// )
//// }
//// if (it.faceType == "1") {
//// if (it.userId.isNullOrBlank().not()) {
//// //userId非空,根据userId删除人脸数据
//// faceApi.deleteByUserName(it.userId)
//// faceEngine?.removeFaceFeature(it.userId.toInt())
//// }
//// } else if (it.faceType == "0") {
//// //新增人脸数据
//// val featureData = Base64.decode(it.faceFeature)
//// val insertEntity = FaceEntity(it.userId, null, featureData)
//// val faceId = faceApi.insert(insertEntity)
//// insertEntity.faceId = faceId
////// faceServer.registerFaceFeatureInfoFromDb(insertEntity, faceEngine)
////
//// EventBus.getDefault().post(insertEntity)
////// callback(insertEntity)
//// }
//// } catch (e: Exception) {
//// e.printStackTrace()
//// }
//// }
//// }
// }
//
//}
@@ -0,0 +1,40 @@
//package com.sw.platecabinet.network.task
//
//import androidx.lifecycle.LifecycleOwner
//import androidx.work.ExistingWorkPolicy
//import androidx.work.OneTimeWorkRequestBuilder
//import androidx.work.WorkInfo
//import androidx.work.WorkManager
//import com.sw.plate.App
//import java.util.concurrent.TimeUnit
//
//object TaskManager {
// private const val TASK_NAME = "SmartPlateCabinetTask"
//// private var owner: LifecycleOwner?=null
//// fun setOwner(owner: LifecycleOwner) {
//// this.owner = owner
//// }
// fun startTask() {
// val nextRequest = OneTimeWorkRequestBuilder<HeartBeatTask>()
// .setInitialDelay(1, TimeUnit.MINUTES)
//// .setInputData(inputData)
// .build()
// WorkManager.getInstance(App.getContext())
//// .apply{
//// getWorkInfoByIdLiveData(nextRequest.id)
//// .observe(owner) { workInfo ->
//// if (workInfo?.state == WorkInfo.State.SUCCEEDED) {
//// val result = workInfo.outputData.getString("RESULT_KEY")
//// // 使用返回的结果
//// }
//// }
//// }
// .enqueueUniqueWork(TASK_NAME, ExistingWorkPolicy.REPLACE, nextRequest)
// }
//
// fun cancelTask() {
// WorkManager.getInstance(App.getContext())
// .cancelUniqueWork(TASK_NAME)
// }
//
//}
@@ -0,0 +1,21 @@
package com.sw.platecabinet.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.sw.platecabinet.activity.DeviceInitActivity
import com.sw.platecabinet.activity.LoginByFaceActivity
import timber.log.Timber
class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (Intent.ACTION_BOOT_COMPLETED == intent.action) {
Timber.d("设备启动完成,开始执行自启动逻辑")
// val intent = Intent(context, LoginByFaceActivity::class.java)
val intent = Intent(context, DeviceInitActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
}
}
}
@@ -1,6 +1,8 @@
package com.sw.platecabinet.repository package com.sw.platecabinet.repository
import com.google.gson.JsonParseException import com.google.gson.JsonParseException
import com.sw.inbound.utils.GsonUtils
import com.sw.platecabinet.model.CodeMsg
import com.sw.platecabinet.model.response.ApiResponse import com.sw.platecabinet.model.response.ApiResponse
import retrofit2.HttpException import retrofit2.HttpException
import timber.log.Timber import timber.log.Timber
@@ -18,31 +20,37 @@ abstract class BaseRepository {
when (e) { when (e) {
is HttpException -> { is HttpException -> {
ApiResponse(code = e.code(), msg = e.message()) val jsonData = e.response()?.errorBody()?.string()
val codeMsg = GsonUtils.fromJson(jsonData, CodeMsg::class.java)
if (codeMsg?.msg.isNullOrBlank()) {
ApiResponse(code = "" + e.code(), msg = e.message())
} else {
ApiResponse(code = codeMsg?.code?:"", msg = codeMsg?.msg?:"")
}
} }
is SocketTimeoutException -> { is SocketTimeoutException -> {
ApiResponse(code = -2, msg = "请求超时: ${e.message}") ApiResponse(code = "-2", msg = "请求超时: ${e.message}")
} }
is ConnectException -> { is ConnectException -> {
ApiResponse(code = -3, msg = "连接失败: ${e.message}") ApiResponse(code = "-3", msg = "连接失败: ${e.message}")
} }
is SSLHandshakeException -> { is SSLHandshakeException -> {
ApiResponse(code = -4, msg = "SSL握手失败: ${e.message}") ApiResponse(code = "-4", msg = "SSL握手失败: ${e.message}")
} }
is JsonParseException -> { is JsonParseException -> {
ApiResponse(code = -5, msg = "JSON解析错误: ${e.message}") ApiResponse(code = "-5", msg = "JSON解析错误: ${e.message}")
} }
is IOException -> { is IOException -> {
ApiResponse(code = -6, msg = "网络IO错误: ${e.message}") ApiResponse(code = "-6", msg = "网络IO错误: ${e.message}")
} }
else -> { else -> {
ApiResponse(code = -1, msg = "未知错误: ${e.message ?: "无错误信息"}") ApiResponse(code = "-1", msg = "未知错误: ${e.message ?: "无错误信息"}")
} }
} }
} }
@@ -1,15 +1,14 @@
package com.sw.platecabinet.repository 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.request.SearchParam
import com.sw.platecabinet.model.response.ApiResponse import com.sw.platecabinet.model.response.ApiResponse
import com.sw.platecabinet.model.response.EquipmentUserInfo import com.sw.platecabinet.model.response.EquipmentUserInfo
import com.sw.platecabinet.model.response.SearchResult import com.sw.platecabinet.model.response.SearchResult
import com.sw.platecabinet.model.response.UserFaceModel import com.sw.platecabinet.model.response.UserFaceModel
import com.sw.platecabinet.network.api.ApiService import com.sw.platecabinet.network.api.ApiService
import com.sw.platecabinet.utils.FileUtils
import java.io.File
/** /**
* 远程数据处理 * 远程数据处理
@@ -17,63 +16,144 @@ import com.sw.platecabinet.network.api.ApiService
class RemoteRepository constructor( class RemoteRepository constructor(
private val apiService: ApiService private val apiService: ApiService
) : BaseRepository() { ) : BaseRepository() {
/**
* 生成token // /**
*/ // * 生成token
suspend fun generateToken(deviceId: String): ApiResponse<String> { // */
return safeApiCall { // suspend fun getDeviceToken(qrcodeId: String): ApiResponse<String> {
apiService.generateToken( // return safeApiCall {
deviceId, // apiService.getDeviceToken(
GlobalData.appVersion, // qrcodeId
GlobalData.globalEquipmentCode // )
) // }
} // }
} //
// /**
// * 获取设备信息
// */
// suspend fun getDeviceInfo(equipmentCode: String, token: String): ApiResponse<EquipmentInfo> {
// return safeApiCall {
// apiService.getDeviceInfo(
// equipmentCode,
// token = token
// )
// }
// }
//
// /**
// * 生成token
// */
// suspend fun generateToken(deviceId: String): ApiResponse<String> {
// return safeApiCall {
// apiService.generateToken(
// deviceId = deviceId,
// appVersion = GlobalData.appVersion,
// equipmentCode = GlobalData.globalEquipmentCode
// )
// }
// }
/** /**
* 获取人脸数据 * 获取人脸数据
*/ */
suspend fun getUserFaceCache(): ApiResponse<List<UserFaceModel>> { suspend fun getUserFaceCache(
pageNum: Int,
pageSize: Int = 100,
): ApiResponse<List<UserFaceModel>> {
return safeApiCall { return safeApiCall {
apiService.getUserFaceCache( apiService.getUserFaceCache(
GlobalData.appVersion, param = mapOf(
GlobalData.globalEquipmentCode "pageNum" to pageNum,
"pageSize" to pageSize
)
) )
} }
} }
/** // /**
* 登录 // * 登录
*/ // */
suspend fun equipmentBoxLogin(param: LoginParam): ApiResponse<EquipmentUserInfo> { // suspend fun equipmentBoxLogin(param: LoginParam): ApiResponse<EquipmentUserInfo> {
return safeApiCall { apiService.equipmentBoxLogin(param) } // return safeApiCall { apiService.equipmentBoxLogin(param = param) }
} // }
/** // /**
* 获取设备绑定用户列表 // * 获取设备绑定用户列表
*/ // */
suspend fun getEquipmentList(param: EquipmentParam): ApiResponse<List<EquipmentUserInfo>> { // suspend fun getEquipmentList(): ApiResponse<List<EquipmentUserInfo>> {
return safeApiCall { apiService.getEquipmentList(param) } // return safeApiCall { apiService.getEquipmentList() }
} // }
/** // /**
* 餐盘用户信息绑定解绑 // * 餐盘用户信息绑定解绑
*/ // */
suspend fun bindEquipment(param: BindParam): ApiResponse<EquipmentUserInfo?> { // suspend fun bindEquipment(param: BindParam): ApiResponse<EquipmentUserInfo?> {
return safeApiCall { apiService.bindEquipment(param) } // return safeApiCall { apiService.bindEquipment(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 searchUser(param: SearchParam): ApiResponse<SearchResult> { suspend fun searchUser(param: SearchParam): ApiResponse<List<SearchResult.Member>?> {
return safeApiCall { apiService.searchUser(param) } return safeApiCall { apiService.searchUser(param = param) }
} }
/** /**
* 通过餐盘编号获取绑定信息 * 通过餐盘编号获取绑定信息
*/ */
suspend fun findByPlateNumber(param: BindParam): ApiResponse<EquipmentUserInfo> { suspend fun findByPlateNumber(plateNumber: String): ApiResponse<EquipmentUserInfo> {
return safeApiCall { apiService.findByPlateNumber(param) } return safeApiCall { apiService.findByPlateNumber(plateNumber = plateNumber) }
}
// /**
// * 获取人脸增量数据
// *
// * @param timeDate 时间戳(毫秒级)
// */
// suspend fun getHeartbeatInterface(timeDate: Long): ApiResponse<List<UserFaceModel>> {
// return safeApiCall { apiService.getHeartbeatInterface(timeDate = timeDate) }
// }
suspend fun getFaceIncrementList(
pageNum: Long,
pageSize: Long = 100L,
timestamp: Long
): ApiResponse<List<UserFaceModel>?> {
return safeApiCall {
apiService.getFaceIncrementList(
param = mapOf(
"pageNum" to pageNum,
"pageSize" to pageSize,
"timestamp" to timestamp
)
)
}
}
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,300 @@
package com.sw.platecabinet.socket;
import android.annotation.SuppressLint;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 局域网多客户端通信管理器:支持并发、心跳、认证、广播、点对点发送
* 可用于 Android 和 JVM 程序。
*/
public class LanCommunicationManager {
// ============ 监听配置 ============
private final int port;
private final long HEARTBEAT_TIMEOUT_MS;
private final int MAX_CLIENT_THREADS;
// ============ 状态 ============
private volatile boolean running = false;
private ServerSocket serverSocket;
// ============ 线程池 ============
private final ExecutorService acceptExecutor = Executors.newSingleThreadExecutor();
private final ExecutorService clientExecutor;
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
// ============ 客户端会话 ============
private final ConcurrentHashMap<String, ClientSession> clients = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Socket, ClientSession> unAuthSessions = new ConcurrentHashMap<>();
// ============ 回调接口 ============
public interface Listener {
// 新客户端完成 AUTH / 认证
void onClientConnected(String clientId);
// 客户端断开
void onClientDisconnected(String clientId);
// 收到业务消息(type != heartbeat/auth)
void onMessageReceived(String clientId, JSONObject message);
}
private Listener listener;
// ============ 构造 ============
public LanCommunicationManager(int port, int maxClientThreads, long heartbeatTimeoutMs) {
this.port = port;
this.MAX_CLIENT_THREADS = maxClientThreads;
this.HEARTBEAT_TIMEOUT_MS = heartbeatTimeoutMs;
this.clientExecutor = Executors.newFixedThreadPool(Math.max(2, maxClientThreads));
}
public void setListener(Listener listener) {
this.listener = listener;
}
// ============ 启动服务端 ============
@SuppressLint("DiscouragedApi")
public void start() throws IOException {
if (running) return;
running = true;
serverSocket = new ServerSocket(port);
serverSocket.setSoTimeout(2000);
acceptExecutor.execute(this::acceptLoop);
scheduler.scheduleAtFixedRate(this::heartbeatCheck,
HEARTBEAT_TIMEOUT_MS,
HEARTBEAT_TIMEOUT_MS,
TimeUnit.MILLISECONDS);
System.out.println("LanCommunicationManager started on port " + port);
}
private void acceptLoop() {
while (running) {
try {
Socket socket = serverSocket.accept();
socket.setSoTimeout((int) HEARTBEAT_TIMEOUT_MS * 2);
ClientSession session = new ClientSession(socket);
unAuthSessions.put(socket, session);
clientExecutor.execute(() -> clientReadLoop(session));
} catch (SocketTimeoutException ignore) {
} catch (Exception e) {
if (running) e.printStackTrace();
}
}
}
// ============ 处理客户端数据读取 ============
private void clientReadLoop(ClientSession session) {
Socket socket = session.socket;
try (DataInputStream in = new DataInputStream(socket.getInputStream())) {
while (running && !socket.isClosed()) {
int len;
try {
len = in.readInt();
} catch (SocketTimeoutException ste) {
continue;
}
if (len <= 0 || len > 10 * 1024 * 1024) break;
byte[] buf = new byte[len];
in.readFully(buf);
session.updateLastSeen();
JSONObject msg = new JSONObject(new String(buf));
handleMessage(session, msg);
}
} catch (Exception ignored) {
} finally {
closeSession(session);
}
}
private void handleMessage(ClientSession session, JSONObject msg) {
String type = msg.optString("type", "");
switch (type) {
case "auth":
handleAuth(session, msg);
break;
case "heartbeat":
session.updateLastSeen();
break;
default:
if (listener != null && session.clientId != null) {
listener.onMessageReceived(session.clientId, msg);
}
break;
}
}
private void handleAuth(ClientSession session, JSONObject msg) {
String clientId = msg.optString("clientId", null);
if (clientId == null) return;
session.clientId = clientId;
// 移动到已认证 map
unAuthSessions.remove(session.socket);
clients.put(clientId, session);
if (listener != null) listener.onClientConnected(clientId);
sendToSession(session, ack("auth_ok"));
}
// ============ 心跳超时 ============
private void heartbeatCheck() {
long now = System.currentTimeMillis();
for (Map.Entry<String, ClientSession> e : clients.entrySet()) {
ClientSession s = e.getValue();
if (now - s.lastSeen > HEARTBEAT_TIMEOUT_MS) {
closeSession(s);
}
}
for (ClientSession s : unAuthSessions.values()) {
if (now - s.lastSeen > HEARTBEAT_TIMEOUT_MS * 2) {
closeSession(s);
}
}
}
// ============ 发送 ============
public boolean sendToClient(String clientId, JSONObject json) {
ClientSession s = clients.get(clientId);
return s != null && sendToSession(s, json);
}
public void broadcast(JSONObject json) {
for (ClientSession s : clients.values()) {
sendToSession(s, json);
}
}
private boolean sendToSession(ClientSession s, JSONObject json) {
try {
DataOutputStream out = s.out;
synchronized (out) {
byte[] data = json.toString().getBytes();
out.writeInt(data.length);
out.write(data);
out.flush();
}
return true;
} catch (Exception e) {
closeSession(s);
return false;
}
}
// ============ ACK ============
private JSONObject ack(String type) {
JSONObject j = new JSONObject();
try {
j.put("type", "ack");
j.put("ack", type);
} catch (JSONException e) {
throw new RuntimeException(e);
}
return j;
}
// ============ 停止 ============
public void stop() {
running = false;
try {
serverSocket.close();
} catch (Exception ignored) {
}
for (ClientSession s : clients.values()) closeSession(s);
for (ClientSession s : unAuthSessions.values()) closeSession(s);
acceptExecutor.shutdownNow();
clientExecutor.shutdownNow();
scheduler.shutdownNow();
System.out.println("LanCommunicationManager stopped");
}
// ============ 会话类 ============
public static class ClientSession {
public final Socket socket;
public final DataOutputStream out;
public volatile long lastSeen = System.currentTimeMillis();
public volatile String clientId;
public ClientSession(Socket socket) throws IOException {
this.socket = socket;
this.out = new DataOutputStream(socket.getOutputStream());
}
public void updateLastSeen() {
lastSeen = System.currentTimeMillis();
}
}
public void closeSession(ClientSession session) {
if (session == null) return;
try {
Socket socket = session.socket;
// 1. 从已认证表移除
if (session.clientId != null) {
ClientSession removed = clients.remove(session.clientId);
if (removed != null && listener != null) {
listener.onClientDisconnected(session.clientId);
}
}
// 2. 从未认证表移除
unAuthSessions.remove(socket);
// 3. 关闭输出流
try {
session.out.close();
} catch (Exception ignored) {
}
// 4. 关闭 socket
try {
if (!socket.isClosed()) socket.close();
} catch (Exception ignored) {
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
@@ -0,0 +1,35 @@
package com.sw.platecabinet.socket;
public class LanServer {
public static final int TYPE_ADD_FACE_DATA = 1;
public static final int TYPE_CLEAR_FACE_DATA = 2;
private static volatile LanCommunicationManager instance;
public static LanCommunicationManager getInstance() {
if (instance == null) {
synchronized (LanServer.class) {
if (instance == null) {
instance = new LanCommunicationManager(
5000, // 监听端口
20, // 最大客户端数
10_000 // 心跳超时时间 10 秒
);
}
}
}
return instance;
}
void a() {
//HashMap<String, Object> data = new HashMap<>();
//data.put("type", LanServer.TYPE_ADD_FACE_DATA);
//data.put("faceId", tempUserId);//收到采集信息,每次生成新的ID
//data.put("faceFeature", faceFeatureString);
//lanServer.broadcast(new JSONObject(data));
//
//HashMap<String, Object> data = new HashMap<>();
//data.put("type", LanServer.TYPE_CLEAR_FACE_DATA);
//lanServer.broadcast(new JSONObject(data));
}
}
@@ -0,0 +1,21 @@
package com.sw.platecabinet.socket
import android.util.Log
import org.json.JSONObject
open class LanServerListenerImpl: LanCommunicationManager.Listener {
companion object {
private const val TAG = "LanServerListenerImpl"
}
override fun onClientConnected(clientId: String?) {
Log.d(TAG, "addSocketListener,onClientConnected: clientId = $clientId")
}
override fun onClientDisconnected(clientId: String?) {
Log.d(TAG, "addSocketListener,onClientDisconnected: clientId = $clientId")
}
override fun onMessageReceived(clientId: String?, message: JSONObject?) {
Log.d(TAG, "addSocketListener,onMessageReceived: clientId = $clientIdmessage = $message")
}
}
@@ -0,0 +1,330 @@
package com.sw.platecabinet.socket;
import android.util.Log;
import org.json.JSONObject;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
* TcpClient - Android ready
* <p>
* Features:
* - length-prefix protocol (int length + bytes)
* - separate read thread and write queue & writer thread
* - auto-reconnect with exponential backoff
* - heartbeat scheduler
* - send queue with optional callback for send result
* - auto send "auth" JSON after connection
*/
public class TcpClient {
private static final String TAG = "TcpClient";
// configuration
private final String serverIp;
private final int serverPort;
private final String clientId; // will be sent in auth message
private final int connectTimeoutMs;
private final long heartbeatIntervalMs;
private final long heartbeatTimeoutMs;
// socket + streams
private Socket socket;
private DataOutputStream out;
private DataInputStream in;
// threads & executors
private final ExecutorService writerExecutor = Executors.newSingleThreadExecutor(r -> new Thread(r, "TcpClient-Writer"));
private final ExecutorService readerExecutor = Executors.newSingleThreadExecutor(r -> new Thread(r, "TcpClient-Reader"));
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(r -> new Thread(r, "TcpClient-Scheduler"));
private final ExecutorService connectExecutor = Executors.newSingleThreadExecutor(r -> new Thread(r, "TcpClient-Connect"));
// send queue
private final BlockingQueue<JSONObject> sendQueue = new LinkedBlockingQueue<>();
// state
private final AtomicBoolean running = new AtomicBoolean(false);
private final AtomicBoolean connected = new AtomicBoolean(false);
private final AtomicBoolean authSent = new AtomicBoolean(false);
// reconnection/backoff
private final long baseReconnectDelayMs = 1000; // 1s
private final long maxReconnectDelayMs = 30_000; // 30s
private final AtomicInteger reconnectAttempt = new AtomicInteger(0);
// heartbeat task future
private ScheduledFuture<?> heartbeatFuture;
// listener
public interface Listener {
void onConnected();
void onDisconnected(Exception e);
void onMessage(JSONObject json);
void onSendSuccess(JSONObject json);
void onSendFailed(JSONObject json, Exception e);
}
private Listener listener;
public void setListener(Listener l) {
this.listener = l;
}
// ctor
public TcpClient(String serverIp, int serverPort, String clientId,
int connectTimeoutMs, long heartbeatIntervalMs, long heartbeatTimeoutMs) {
this.serverIp = serverIp;
this.serverPort = serverPort;
this.clientId = clientId;
this.connectTimeoutMs = connectTimeoutMs;
this.heartbeatIntervalMs = heartbeatIntervalMs;
this.heartbeatTimeoutMs = heartbeatTimeoutMs;
}
// start client (will attempt connect)
public void start() {
if (running.getAndSet(true)) return;
scheduleConnect(0);
// writer thread drains sendQueue
writerExecutor.execute(this::writerLoop);
}
// stop client and cleanup
public void stop() {
running.set(false);
cancelHeartbeat();
closeSocketQuiet();
writerExecutor.shutdownNow();
readerExecutor.shutdownNow();
scheduler.shutdownNow();
connectExecutor.shutdownNow();
sendQueue.clear();
}
// send JSON (queued). Non-blocking.
public void send(JSONObject json) {
if (!running.get()) return;
sendQueue.offer(json);
}
// AUTH shortcut (immediately send auth JSON)
private void sendAuth() {
try {
JSONObject auth = new JSONObject();
auth.put("type", "auth");
auth.put("clientId", clientId);
sendQueue.offer(auth);
authSent.set(true);
} catch (Exception ignored) {
}
}
// writer thread loop (serializes sends)
private void writerLoop() {
while (running.get()) {
try {
JSONObject json = sendQueue.take(); // blocks
if (connected.get() && out != null) {
try {
byte[] data = json.toString().getBytes();
synchronized (out) {
out.writeInt(data.length);
out.write(data);
out.flush();
}
if (listener != null) listener.onSendSuccess(json);
} catch (Exception e) {
if (listener != null) listener.onSendFailed(json, e);
// on write failure, attempt reconnect
safeCloseAndScheduleReconnect(e);
}
} else {
// not connected: requeue it and wait for connection
sendQueue.offer(json);
Thread.sleep(500); // avoid busy loop
}
} catch (InterruptedException ignored) {
break;
}
}
}
// reader loop (runs in readerExecutor)
private void startReaderLoop() {
readerExecutor.execute(() -> {
try {
while (running.get() && connected.get() && in != null) {
int length;
try {
length = in.readInt(); // will throw SocketTimeoutException if set
} catch (SocketTimeoutException ste) {
// used to detect socket liveness; continue loop
continue;
}
if (length <= 0 || length > 10 * 1024 * 1024) {
// invalid length, break
throw new RuntimeException("Invalid message length: " + length);
}
byte[] buf = new byte[length];
in.readFully(buf);
String s = new String(buf);
try {
JSONObject json = new JSONObject(s);
// update last seen time via heartbeat ack if needed
if ("heartbeat".equals(json.optString("type"))) {
// optionally respond or update time
} else if ("auth_ok".equals(json.optString("type")) || "ack".equals(json.optString("type"))) {
// ignore or process ack
} else {
if (listener != null) listener.onMessage(json);
}
} catch (Exception je) {
Log.w(TAG, "Invalid JSON from server: " + s, je);
}
}
} catch (Exception e) {
if (running.get()) {
safeCloseAndScheduleReconnect(e);
}
}
});
}
// schedule connect attempt with delay (ms)
private void scheduleConnect(long delayMs) {
connectExecutor.execute(() -> {
try {
if (delayMs > 0) Thread.sleep(delayMs);
} catch (InterruptedException ignored) {
}
if (!running.get()) return;
tryConnect();
});
}
// connect logic
private void tryConnect() {
if (!running.get()) return;
closeSocketQuiet(); // ensure closed
try {
Socket s = new Socket();
s.connect(new InetSocketAddress(serverIp, serverPort), connectTimeoutMs);
s.setSoTimeout((int) Math.max(heartbeatTimeoutMs, 5_000));
socket = s;
out = new DataOutputStream(socket.getOutputStream());
in = new DataInputStream(socket.getInputStream());
connected.set(true);
reconnectAttempt.set(0);
authSent.set(false);
// start reader
startReaderLoop();
// send auth immediately
sendAuth();
// start heartbeat
startHeartbeat();
if (listener != null) listener.onConnected();
Log.i(TAG, "Connected to " + serverIp + ":" + serverPort);
} catch (Exception e) {
Log.w(TAG, "Connect failed: " + e.getMessage());
scheduleReconnectWithBackoff();
}
}
// start heartbeat scheduler
private void startHeartbeat() {
cancelHeartbeat();
heartbeatFuture = scheduler.scheduleAtFixedRate(() -> {
if (!running.get() || !connected.get()) return;
try {
JSONObject hb = new JSONObject();
hb.put("type", "heartbeat");
hb.put("time", System.currentTimeMillis());
sendQueue.offer(hb);
} catch (Exception ignored) {
}
}, 0, heartbeatIntervalMs, TimeUnit.MILLISECONDS);
}
private void cancelHeartbeat() {
if (heartbeatFuture != null && !heartbeatFuture.isCancelled()) {
heartbeatFuture.cancel(true);
heartbeatFuture = null;
}
}
// close socket quietly and notify listener
private void safeCloseAndScheduleReconnect(Exception cause) {
closeSocketQuiet();
if (listener != null) listener.onDisconnected(cause);
scheduleReconnectWithBackoff();
}
private void scheduleReconnectWithBackoff() {
int attempt = reconnectAttempt.incrementAndGet();
long delay = Math.min(maxReconnectDelayMsFromAttempt(attempt), maxReconnectDelayMs);
Log.i(TAG, "Scheduling reconnect attempt " + attempt + " after " + delay + "ms");
scheduleConnect(delay);
}
// compute exponential backoff
private long maxReconnectDelayMsFromAttempt(int attempt) {
long d = baseReconnectDelayMs * (1L << Math.min(attempt, 30));
if (d < 0) d = maxReconnectDelayMs;
return Math.min(d, maxReconnectDelayMs);
}
// close socket and streams
private void closeSocketQuiet() {
connected.set(false);
cancelHeartbeat();
try {
if (out != null) {
out.close();
}
} catch (Exception ignored) {
}
try {
if (in != null) {
in.close();
}
} catch (Exception ignored) {
}
try {
if (socket != null && !socket.isClosed()) {
socket.close();
}
} catch (Exception ignored) {
}
out = null;
in = null;
socket = null;
}
// helper: when manually call reconnect (immediately)
public void reconnectNow() {
scheduleConnect(0);
}
// helper to set immediate send of a JSON and wait (blocking) until it is queued (not until delivered)
public boolean sendBlocking(JSONObject json, long timeoutMs) throws InterruptedException {
return sendQueue.offer(json, timeoutMs, TimeUnit.MILLISECONDS);
}
}
@@ -0,0 +1,33 @@
package com.sw.platecabinet.socket
import android.util.Log
import com.sw.platecabinet.socket.TcpClient.*
import org.json.JSONObject
import java.lang.Exception
open class TcpClientListenerImpl : Listener {
companion object {
private const val TAG = "TcpClientListenerImpl"
}
override fun onConnected() {
Log.d(TAG, "addSocketListener,onConnected: ")
}
override fun onDisconnected(e: Exception?) {
Log.d(TAG, "addSocketListener,onDisconnected: ${e?.toString()}")
}
override fun onMessage(json: JSONObject?) {
Log.d(TAG, "addSocketListener,onMessage: $json")
}
override fun onSendSuccess(json: JSONObject?) {
Log.d(TAG, "addSocketListener,onSendSuccess: $json")
}
override fun onSendFailed(json: JSONObject?, e: Exception?) {
Log.d(TAG, "addSocketListener,onSendFailed: $json,e:${e?.toString()}")
}
}
@@ -0,0 +1,47 @@
package com.sw.platecabinet.socket;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* Simple UDP discovery client:
* Sends "DISCOVER_SERVER" broadcast and waits for first reply "SERVER_FOUND:serverName:ip"
*/
public class UdpDiscoveryClient {
public interface Listener {
void onFound(String ip, String serverName);
void onError(Exception e);
}
public void discover(int discoveryPort, int timeoutMs, Listener listener) {
new Thread(() -> {
try (DatagramSocket socket = new DatagramSocket()) {
socket.setBroadcast(true);
byte[] data = "DISCOVER_SERVER".getBytes();
DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName("255.255.255.255"), discoveryPort);
socket.send(packet);
socket.setSoTimeout(timeoutMs);
byte[] buf = new byte[512];
DatagramPacket resp = new DatagramPacket(buf, buf.length);
socket.receive(resp);
String msg = new String(resp.getData(), 0, resp.getLength());
if (msg.startsWith("SERVER_FOUND")) {
// format: SERVER_FOUND:serverName:ip
String[] parts = msg.split(":", 3);
if (parts.length >= 3) {
listener.onFound(parts[2], parts[1]);
return;
}
}
listener.onError(new Exception("Invalid response"));
} catch (Exception e) {
listener.onError(e);
}
}).start();
}
}
@@ -0,0 +1,113 @@
package com.sw.platecabinet.socket;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
public class test {
LanCommunicationManager manager = new LanCommunicationManager(
9999, // 端口
20, // 最大客户端线程
30_000 // 心跳超时 30 秒
);
public void test() {
manager.setListener(new LanCommunicationManager.Listener() {
@Override
public void onClientConnected(String clientId) {
System.out.println("新的客户端上线:" + clientId);
}
@Override
public void onClientDisconnected(String clientId) {
System.out.println("客户端离线:" + clientId);
}
@Override
public void onMessageReceived(String clientId, JSONObject msg) {
System.out.println("收到 " + clientId + " 的消息:" + msg);
}
});
// 启动
try {
manager.start();
} catch (IOException e) {
throw new RuntimeException(e);
}
// 给某个客户端发送
JSONObject j = new JSONObject();
try {
j.put("type", "cmd");
j.put("content", "hello");
} catch (JSONException e) {
throw new RuntimeException(e);
}
manager.sendToClient("device123", j);
// 广播
manager.broadcast(j);
}
public void clientTets() {
// 1) discover server (optional)
UdpDiscoveryClient disc = new UdpDiscoveryClient();
disc.discover(9876, 3000, new UdpDiscoveryClient.Listener() {
@Override
public void onFound(String ip, String serverName) {
startClient(ip);
}
@Override
public void onError(Exception e) { /* fallback to manual IP */ }
});
}
// 2) start client
private TcpClient client;
private void startClient(String serverIp) {
client = new TcpClient(
serverIp,
9999,
"device123", // clientId
5000, // connectTimeoutMs
10_000, // heartbeatIntervalMs
30_000 // heartbeatTimeoutMs
);
client.setListener(new TcpClient.Listener() {
@Override
public void onConnected() {
Log.i("APP", "connected");
}
@Override
public void onDisconnected(Exception e) {
Log.i("APP", "disconnected", e);
}
@Override
public void onMessage(JSONObject json) {
Log.i("APP", "msg:" + json);
}
@Override
public void onSendSuccess(JSONObject json) {
}
@Override
public void onSendFailed(JSONObject json, Exception e) {
}
});
client.start();
}
}
@@ -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,241 @@
package com.sw.platecabinet.utils
import android.app.ActivityManager
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.os.Process
import com.sw.platecabinet.activity.InitActivity
import timber.log.Timber
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.PrintWriter
import java.io.StringWriter
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import kotlin.system.exitProcess
/**
* 崩溃处理
*/
class CrashHandler private constructor(private val context: Context) :
Thread.UncaughtExceptionHandler {
companion object {
private const val TAG = "CrashHandler"
private const val CRASH_REPORTS_DIR = "crash_reports"
private const val LOG_LINES = 500 // 收集最近500行日志
@Volatile
private var instance: CrashHandler? = null
fun init(context: Context) {
if (instance == null) {
synchronized(CrashHandler::class.java) {
if (instance == null) {
instance = CrashHandler(context.applicationContext)
}
}
}
}
fun getCrashReportFiles(context: Context): Array<File> {
val crashDir = File(context.getExternalFilesDir(null), CRASH_REPORTS_DIR)
return if (crashDir.exists() && crashDir.isDirectory) {
crashDir.listFiles { _, name -> name.endsWith(".log") } ?: emptyArray()
} else {
emptyArray()
}
}
fun clearCrashReports(context: Context) {
getCrashReportFiles(context).forEach { it.delete() }
}
}
private val defaultHandler = Thread.getDefaultUncaughtExceptionHandler()
init {
Thread.setDefaultUncaughtExceptionHandler(this)
}
override fun uncaughtException(thread: Thread, ex: Throwable) {
handleException(thread, ex)
// 如果系统提供了默认的异常处理器,则交给系统去结束程序
// 否则自己结束程序
defaultHandler?.uncaughtException(thread, ex) ?: run {
Process.killProcess(Process.myPid())
exitProcess(1)
}
}
/**
* 自动重启app
*/
private fun restartApp() {
// 延迟1秒后重启应用
Handler(Looper.getMainLooper()).postDelayed({
val intent = Intent(context, InitActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
val pendingIntent = PendingIntent.getActivity(
context, 0, intent,
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
)
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmManager.set(AlarmManager.RTC, System.currentTimeMillis() + 100, pendingIntent)
Process.killProcess(Process.myPid())
exitProcess(1)
}, 1000)
}
private fun handleException(thread: Thread, ex: Throwable) {
// 收集设备信息和异常信息
val crashInfo = collectCrashInfo(thread, ex)
// 保存日志文件
saveCrashInfoToFile(crashInfo)
// 这里可以添加其他处理逻辑,比如上传到服务器等
}
private fun collectCrashInfo(thread: Thread, ex: Throwable): String {
return buildString {
// 收集设备信息
collectDeviceInfo(this)
// 收集应用日志
try {
append("\n\n").append(collectLogs())
} catch (e: OutOfMemoryError) {
append("collectLogs发生OutOfMemoryError:${e.message}")
}
// 收集线程和异常信息
append("\n\n========== Thread & Exception Info ==========\n")
append("Thread: ${thread.name}\n")
append("Stack Trace:\n")
val sw = StringWriter()
val pw = PrintWriter(sw)
ex.printStackTrace(pw)
var cause: Throwable? = ex.cause
while (cause != null) {
cause.printStackTrace(pw)
cause = cause.cause
}
pw.close()
append(sw.toString())
}
}
private fun collectDeviceInfo(sb: StringBuilder) {
sb.append("========== Device Info ==========\n")
try {
// 应用信息
val pm = context.packageManager
val pi = pm.getPackageInfo(context.packageName, 0)
sb.append("App Version: ${pi.versionName}_${pi.versionCode}\n")
// Android 版本信息
sb.append("OS Version: ${Build.VERSION.RELEASE}_${Build.VERSION.SDK_INT}\n")
// 设备信息
sb.append("Vendor: ${Build.MANUFACTURER}\n")
sb.append("Model: ${Build.MODEL}\n")
sb.append("CPU ABI: ${Build.SUPPORTED_ABIS[0]}\n")
// 其他信息
sb.append("Locale: ${Locale.getDefault()}\n")
sb.append(
"Current Time: ${
SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss",
Locale.getDefault()
).format(Date())
}\n"
)
// 内存信息
val memoryInfo = ActivityManager.MemoryInfo()
val activityManager =
context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
activityManager.getMemoryInfo(memoryInfo)
sb.append("Available Memory: ${memoryInfo.availMem / (1024 * 1024)}MB\n")
sb.append("Total Memory: ${memoryInfo.totalMem / (1024 * 1024)}MB\n")
sb.append("Low Memory: ${memoryInfo.lowMemory}\n")
} catch (e: Exception) {
Timber.e(e, "Error while collecting device info")
sb.append("Error while collecting device info: ${e.message}\n")
}
}
private fun collectLogs(): String {
return buildString {
append("========== Application Logs ==========\n")
try {
val process = Runtime.getRuntime().exec("logcat -d -v threadtime")
val reader = process.inputStream.bufferedReader()
val logLines = reader.readLines()
val start = kotlin.comparisons.maxOf(0, logLines.size - LOG_LINES)
logLines.subList(start, logLines.size).forEach {
append(it).append("\n")
}
} catch (e: IOException) {
Timber.e(e, "Error collecting logs")
append("Error collecting logs: ${e.message}\n")
}
}
}
private fun saveCrashInfoToFile(crashInfo: String) {
try {
val time = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.getDefault()).format(Date())
val fileName = "crash_$time.log"
val crashDir = File(context.getExternalFilesDir(null), CRASH_REPORTS_DIR)
if (!crashDir.exists() && !crashDir.mkdirs()) {
Timber.tag(TAG).e("Failed to create crash report directory")
return
}
val crashFile = File(crashDir, fileName)
FileOutputStream(crashFile).use { it.write(crashInfo.toByteArray()) }
Timber.tag(TAG).d("Crash info saved to: ${crashFile.absolutePath}")
} catch (e: Exception) {
Timber.tag(TAG).e(e, "Error saving crash info to file")
}
}
/**
* 清理旧的崩溃日志
*/
fun cleanupOldCrashReports(maxAgeDays: Int = 7) {
val crashDir = File(context.getExternalFilesDir(null), CRASH_REPORTS_DIR)
if (!crashDir.exists() || !crashDir.isDirectory) return
val now = System.currentTimeMillis()
val maxAgeMillis = maxAgeDays * 24 * 60 * 60 * 1000L
crashDir.listFiles()?.forEach { file ->
if (file.lastModified() < now - maxAgeMillis) {
file.delete()
}
}
}
}
@@ -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
}
}
}
@@ -0,0 +1,33 @@
package com.sw.platecabinet.utils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlin.ranges.downTo
// 倒计时Flow扩展函数
fun countDownByFlow(
total: Int,
scope: CoroutineScope,
onTick: (Int) -> Unit,
onStart: (() -> Unit)? = null,
onFinish: (() -> Unit)? = null
): Job {
return flow {
for (i in total downTo 0) {
emit(i)
if (i != 0) delay(1000)
}
}.flowOn(Dispatchers.Main)
.onStart { onStart?.invoke() }
.onCompletion { onFinish?.invoke() }
.onEach { onTick.invoke(it) }
.launchIn(scope)
}
@@ -0,0 +1,68 @@
package com.sw.platecabinet.utils
import kotlinx.coroutines.*
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
class IntervalExecutor {
/**
* 启动定时任务
* @param delayMillis 延迟时间(毫秒)
* @param action 要执行的方法
* @return Job 可用于取消任务
*/
fun startIntervalTask(delayMillis: Long, action: suspend () -> Unit): Job {
return CoroutineScope(Dispatchers.Default).launch {
while (isActive) {
ensureActive()
action()
delay(delayMillis)
}
}
}
/**
* 启动定时任务(带初始延迟)
* @param initialDelay 初始延迟时间(毫秒)
* @param delayMillis 后续执行间隔(毫秒)
* @param action 要执行的方法
* @return Job 可用于取消任务
*/
fun startIntervalTaskWithInitialDelay(
initialDelay: Long,
delayMillis: Long,
action: suspend () -> Unit
): Job {
return CoroutineScope(Dispatchers.Default).launch {
delay(initialDelay)
while (isActive) {
ensureActive()
action()
delay(delayMillis)
}
}
}
}
// 使用示例
//fun main() = runBlocking {
// val executor = IntervalExecutor()
//
// // 示例1:每隔10秒执行一次
// val job1 = executor.startIntervalTask(10000) {
// println("定时任务执行: ${System.currentTimeMillis()}")
// // 这里可以执行你的业务逻辑
// }
//
// // 示例2:先延迟5秒,然后每隔3秒执行一次
// val job2 = executor.startIntervalTaskWithInitialDelay(5000, 3000) {
// println("带初始延迟的定时任务: ${System.currentTimeMillis()}")
// }
//
// // 运行30秒后取消任务
// delay(30000)
// job1.cancel()
// job2.cancel()
// println("所有定时任务已取消")
//}
@@ -0,0 +1,57 @@
package com.sw.platecabinet.utils;
/**
* Log统一管理类
*/
public class L {
private L() {
/* cannot be instantiated */
throw new UnsupportedOperationException("cannot be instantiated");
}
public static boolean isDebug = true;// 是否需要打印bug,可以在application的onCreate函数里面初始化
private static final String TAG = "mzf";
// 下面四个是默认tag的函数
public static void i(String msg) {
if (isDebug)
android.util.Log.i(TAG, msg);
}
public static void d(String msg) {
if (isDebug)
android.util.Log.d(TAG, msg);
}
public static void e(String msg) {
if (isDebug)
android.util.Log.e(TAG, msg);
}
public static void v(String msg) {
if (isDebug)
android.util.Log.v(TAG, msg);
}
// 下面是传入自定义tag的函数
public static void i(String tag, String msg) {
if (isDebug)
android.util.Log.i(tag, msg);
}
public static void d(String tag, String msg) {
if (isDebug)
android.util.Log.d(tag, msg);
}
public static void e(String tag, String msg) {
if (isDebug)
android.util.Log.e(tag, msg);
}
public static void v(String tag, String msg) {
if (isDebug)
android.util.Log.v(tag, msg);
}
}
@@ -0,0 +1,697 @@
package com.sw.platecabinet.utils;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import com.sw.plate.App;
import com.sw.plate.utils.AppUtil;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringReader;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
public class LogFileUtil {
private static boolean isSaveLog = true;
public static final String FILE_STR_PATH = App.getContext().getExternalCacheDir().getAbsolutePath();
public static final int SIZETYPE_B = 1;//获取文件大小单位为B的double值
public static final int SIZETYPE_KB = 2;//获取文件大小单位为KB的double值
public static final int SIZETYPE_MB = 3;//获取文件大小单位为MB的double值
public static final int SIZETYPE_GB = 4;//获取文件大小单位为GB的double值
private static String urlNull = "原文件路径不存在";
private static String isFile = "原文件不是文件";
private static String canRead = "原文件不能读";
private static String copyFalse = "备份失败!";
private static String cFromFile = "创建原文件出错:";
private static String ctoFile = "创建备份文件出错:";
/**
* 写入文件
* FileUtil.byteWriteFile(getExternalFilesDir("123"),"test.jpg",nv21);
*
* @param filePath
* @param fileName
* @param bytes
*/
public static void byteWriteFile(File filePath, String fileName, byte[] bytes) {
BufferedOutputStream bout = null;
try {
File file = new File(filePath, fileName);
if (file.exists() == false) {
if (file.getParentFile().exists() == false) {
file.getParentFile().mkdirs();
}
file.createNewFile();
}
bout = new BufferedOutputStream(new FileOutputStream(file, false));
bout.write(bytes);
bout.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bout != null) {
try {
bout.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 获取文件名
*
* @param path 路径
* @return 文件名
*/
public static String getFileName(String path) {
int index = path.lastIndexOf("/");
return path.substring(index + 1);
}
/**
* 根据文件路径获取文件
*
* @param path 路径
* @return
*/
public static File getFileByPath(String path) {
File file = new File(path);
if (file.exists()) {
return new File(path);
} else {
return null;
}
}
/**
* 重命名文件
*
* @param filePath 文件路径
* @param newName 新名称
* @return {@code true}: 重命名成功<br>{@code false}: 重命名失败
*/
public static boolean rename(String filePath, String newName) {
return rename(getFileByPath(filePath), newName);
}
/**
* 重命名文件
*
* @param file 文件
* @param newName 新名称
* @return {@code true}: 重命名成功<br>{@code false}: 重命名失败
*/
public static boolean rename(File file, String newName) {
// 文件为空返回false
if (file == null) return false;
// 文件不存在返回false
if (!file.exists()) return false;
// 新的文件名为空返回false
if (AppUtil.isEmpty(newName)) return false;
// 如果文件名没有改变返回true
if (newName.equals(file.getName())) return true;
File newFile = new File(file.getParent() + File.separator + newName);
// 如果重命名的文件已存在返回false
return !newFile.exists()
&& file.renameTo(newFile);
}
/**
* 判断文件是否存在
*
* @param path 文件的路径,含文件后缀和文件名
* @return 是否存在
*/
public static boolean isFileExists(String path) {
File file = new File(path);
if (file.exists()) {
return true;
} else {
return false;
}
}
/**
* 判断文件是否存在
*
* @return 是否存在
*/
public static boolean isFileExists(File file) {
if (file.exists()) {
return true;
} else {
return false;
}
}
/**
* 删除文件夹及文件夹下所有内容
*
* @param path 文件夹路径
* @return 返回是否删除成功
*/
public static boolean deleteFiles(String path) {
if (getFileByPath(path) != null) {
return deleteFiles(getFileByPath(path));
} else {
return false;
}
}
/**
* 删除文件夹及文件夹下所有内容
*
* @param file 文件
* @return 返回是否删除成功
*/
public static boolean deleteFiles(File file) {
try {
if (file.exists()) { // 判断文件是否存在
if (file.isFile()) { // 判断是否是文件
file.delete(); // delete()方法
} else if (file.isDirectory()) { // 否则如果它是一个目录
File files[] = file.listFiles(); // 声明目录下所有的文件 files[];
for (int i = 0; i < files.length; i++) { // 遍历目录下所有的文件
deleteFiles(files[i].getPath()); // 把每个文件 用这个方法进行迭代
}
file.delete();//删除目录
}
//file.delete();
return true;
} else {
return false;
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 获取文件的Uri
*
* @param path 文件的路径
* @return 文件的Uri
*/
public static Uri getUriFromFile(String path) {
File file = new File(path);
return Uri.fromFile(file);
}
/**
* 获取文件指定文件的指定单位的大小
*
* @param filePath 文件路径
* @param sizeType 获取大小的类型1为B、2为KB、3为MB、4为GB
* @return double值的大小
*/
public static double getFileOrFilesSize(String filePath, int sizeType) {
File file = new File(filePath);
long blockSize = 0;
try {
if (file.isDirectory()) {
blockSize = getFileSizes(file);
} else {
blockSize = getFileSize(file);
}
} catch (Exception e) {
e.printStackTrace();
L.i("获取文件大小", "获取失败!");
}
return FormetFileSize(blockSize, sizeType);
}
/**
* 调用此方法自动计算指定文件或指定文件夹的大小
*
* @param filePath 文件路径
* @return 计算好的带B、KB、MB、GB的字符串
*/
public static String getAutoFileOrFilesSize(String filePath) {
File file = new File(filePath);
long blockSize = 0;
try {
if (file.isDirectory()) {
blockSize = getFileSizes(file);
} else {
blockSize = getFileSize(file);
}
} catch (Exception e) {
e.printStackTrace();
L.i("获取文件大小", "获取失败!");
}
return FormetFileSize(blockSize);
}
/**
* 获取指定文件大小
*
* @param file
* @return
* @throws Exception
*/
private static long getFileSize(File file) throws Exception {
long size = 0;
if (file.exists()) {
FileInputStream fis = null;
fis = new FileInputStream(file);
size = fis.available();
} else {
// file.createNewFile();
L.i("获取文件大小", "文件不存在!");
}
return size;
}
/**
* 获取目录下指定文件名的文件包括子目录
* <p>大小写忽略</p>
*
* @param dirPath 目录路径
* @param fileName 文件名
* @return 文件链表
*/
public static List<File> searchFileInDir(String dirPath, String fileName) {
return searchFileInDir(getFileByPath(dirPath), fileName);
}
/**
* 获取目录下指定文件名的文件包括子目录
* <p>大小写忽略</p>
*
* @param dir 目录
* @param fileName 文件名
* @return 文件链表
*/
public static List<File> searchFileInDir(File dir, String fileName) {
if (dir == null || !dir.isDirectory()) return null;
List<File> list = new ArrayList<>();
File[] files = dir.listFiles();
if (files != null && files.length != 0) {
for (File file : files) {
if (file.getName().toUpperCase().equals(fileName.toUpperCase())) {
list.add(file);
}
if (file.isDirectory()) {
list.addAll(searchFileInDir(file, fileName));
}
}
}
return list;
}
/**
* 获取指定文件夹
*
* @param f
* @return
* @throws Exception
*/
private static long getFileSizes(File f) throws Exception {
long size = 0;
File flist[] = f.listFiles();
for (int i = 0; i < flist.length; i++) {
if (flist[i].isDirectory()) {
size = size + getFileSizes(flist[i]);
} else {
size = size + getFileSize(flist[i]);
}
}
return size;
}
/**
* 转换文件大小
*
* @param fileS
* @return
*/
private static String FormetFileSize(long fileS) {
DecimalFormat df = new DecimalFormat("#.00");
String fileSizeString = "";
String wrongSize = "0B";
if (fileS == 0) {
return wrongSize;
}
if (fileS < 1024) {
fileSizeString = df.format((double) fileS) + "B";
} else if (fileS < 1048576) {
fileSizeString = df.format((double) fileS / 1024) + "KB";
} else if (fileS < 1073741824) {
fileSizeString = df.format((double) fileS / 1048576) + "MB";
} else {
fileSizeString = df.format((double) fileS / 1073741824) + "GB";
}
return fileSizeString;
}
/**
* 转换文件大小,指定转换的类型
*
* @param fileS
* @param sizeType
* @return
*/
private static double FormetFileSize(long fileS, int sizeType) {
DecimalFormat df = new DecimalFormat("#.00");
double fileSizeLong = 0;
switch (sizeType) {
case SIZETYPE_B:
fileSizeLong = Double.valueOf(df.format((double) fileS));
break;
case SIZETYPE_KB:
fileSizeLong = Double.valueOf(df.format((double) fileS / 1024));
break;
case SIZETYPE_MB:
fileSizeLong = Double.valueOf(df.format((double) fileS / 1048576));
break;
case SIZETYPE_GB:
fileSizeLong = Double.valueOf(df.format((double) fileS / 1073741824));
break;
default:
break;
}
return fileSizeLong;
}
/**
* 复制文件
*
* @param fromFilePath 旧文件地址和名称
* @param toFilePath 新文件地址和名称
* @return 返回备份文件的信息,ok是成功,其它就是错误
*/
public static File copyFile(String fromFilePath, String toFilePath) {
File fromFile = null;
File toFile = null;
try {
fromFile = new File(fromFilePath);
} catch (Exception e) {
L.i(cFromFile + e.getMessage());
return null;
}
try {
toFile = new File(toFilePath);
if (toFile.isDirectory()) {
toFile = new File(toFilePath.endsWith("/") ? toFilePath + fromFile.getName() : toFilePath + "/" + fromFile.getName());
}
} catch (Exception e) {
L.i(ctoFile + e.getMessage());
return null;
}
if (!fromFile.exists()) {
L.i(urlNull);
return null;
}
if (!fromFile.isFile()) {
L.i(isFile);
return null;
}
if (!fromFile.canRead()) {
L.i(canRead);
return null;
}
// 复制到的路径如果不存在就创建
if (!toFile.getParentFile().exists()) {
toFile.getParentFile().mkdirs();
}
if (toFile.exists()) {
toFile.delete();
}
if (!toFile.canWrite()) {
//return notWrite;
}
try {
FileInputStream fosfrom = new FileInputStream(
fromFile);
FileOutputStream fosto = new FileOutputStream(toFile);
byte bt[] = new byte[1024];
int c;
while ((c = fosfrom.read(bt)) > 0) {
fosto.write(bt, 0, c); // 将内容写到新文件当中
}
//关闭数据流
fosfrom.close();
fosto.close();
} catch (Exception e) {
e.printStackTrace();
L.i(copyFalse + e.getMessage());
return null;
}
return toFile;
}
/**
* 创建文件
*
* @param path 文件路径
* @return 创建的文件
*/
public static synchronized File createNewFile(String path) {
File file = new File(path);
String parentPath = path.substring(0, path.lastIndexOf("/") + 1);
File parentFile = new File(parentPath);
if (!parentFile.exists()) {
parentFile.mkdirs();
}
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
return file;
}
/***
* 根据文件后缀回去MIME类型
****/
private static String getMIMEType(File file) {
String type = "*/*";
String fName = file.getName();
//获取后缀名前的分隔符"."在fName中的位置。
int dotIndex = fName.lastIndexOf(".");
if (dotIndex < 0) {
return type;
}
/* 获取文件的后缀名*/
String end = fName.substring(dotIndex, fName.length()).toLowerCase();
if (end == "") return type;
//在MIME和文件类型的匹配表中找到对应的MIME类型。
for (int i = 0; i < MIME_MapTable.length; i++) { //MIME_MapTable??在这里你一定有疑问,这个MIME_MapTable是什么?
if (end.equals(MIME_MapTable[i][0]))
type = MIME_MapTable[i][1];
}
return type;
}
/**
* 获取全路径中的文件拓展名
*
* @param file 文件
* @return 文件拓展名
*/
public static String getFileExtension(File file) {
if (file == null) return null;
return getFileExtension(file.getPath());
}
/**
* 获取全路径中的文件拓展名
*
* @param filePath 文件路径
* @return 文件拓展名
*/
public static String getFileExtension(String filePath) {
if (AppUtil.isEmpty(filePath)) return filePath;
int lastPoi = filePath.lastIndexOf('.');
int lastSep = filePath.lastIndexOf(File.separator);
if (lastPoi == -1 || lastSep >= lastPoi) return "";
return filePath.substring(lastPoi + 1);
}
/**
* 调用系统应用打开文件
*
* @param context 上下文
* @param file 文件
*/
public static void openFile(Context context, File file) {
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//设置intent的Action属性
intent.setAction(Intent.ACTION_VIEW);
//获取文件file的MIME类型
String type = getMIMEType(file);
//设置intent的data和Type属性。
intent.setDataAndType(Uri.fromFile(file), type);
//跳转
try {
context.startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
L.i("找不到打开此文件的应用!");
}
}
/**
* 保存文本文件
* FileUtil.saveStrFile("测试", "log.txt", FILE_STR_PATH, true);
*
* @param res 写入内容
* @param fileName 文件名
* @param filePath 路径
* @param append true新增 false替换
* @return
*/
public static boolean saveStrFile(String res, String fileName, String filePath, boolean append) {
boolean flag = true;
BufferedReader bufferedReader = null;
BufferedWriter bufferedWriter = null;
try {
File file = new File(filePath, fileName);
if (!file.exists()) {
file.getParentFile().mkdirs();
file.createNewFile();
}
bufferedReader = new BufferedReader(new StringReader(res));
bufferedWriter = new BufferedWriter(new FileWriter(file, append));
char buf[] = new char[1024]; //字符缓冲区
int len;
while ((len = bufferedReader.read(buf)) != -1) {
bufferedWriter.write(buf, 0, len);
}
bufferedWriter.flush();
bufferedReader.close();
bufferedWriter.close();
} catch (IOException e) {
e.printStackTrace();
flag = false;
return flag;
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return flag;
}
public static void saveLog(String... logs) {
if (!isSaveLog) {
return;
}
new Thread() {
@Override
public void run() {
super.run();
StringBuilder sb = new StringBuilder();
for (String log : logs) {
sb.append("===")
.append((AppUtil.formatDateGetCurrentTime()))
.append("===")
.append("\n")
.append(log)
.append("\n");
}
String fileName = "log" + AppUtil.formatDateGetDay(System.currentTimeMillis()) + ".txt";
LogFileUtil.saveStrFile(sb.toString(), fileName, FILE_STR_PATH, true);
}
}.start();
}
private static final String[][] MIME_MapTable = {
// {后缀名,MIME类型}
{".3gp", "video/3gpp"},
{".apk", "application/vnd.android.package-archive"},
{".asf", "video/x-ms-asf"},
{".avi", "video/x-msvideo"},
{".bin", "application/octet-stream"},
{".bmp", "image/bmp"},
{".c", "text/plain"},
{".class", "application/octet-stream"},
{".conf", "text/plain"},
{".cpp", "text/plain"},
{".doc", "application/msword"},
{".docx",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
{".xls", "application/vnd.ms-excel"},
{".xlsx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
{".exe", "application/octet-stream"},
{".gif", "image/gif"},
{".gtar", "application/x-gtar"},
{".gz", "application/x-gzip"},
{".h", "text/plain"},
{".htm", "text/html"},
{".html", "text/html"},
{".jar", "application/java-archive"},
{".java", "text/plain"},
{".jpeg", "image/jpeg"},
{".jpg", "image/jpeg"},
{".js", "application/x-javascript"},
{".log", "text/plain"},
{".m3u", "audio/x-mpegurl"},
{".m4a", "audio/mp4a-latm"},
{".m4b", "audio/mp4a-latm"},
{".m4p", "audio/mp4a-latm"},
{".m4u", "video/vnd.mpegurl"},
{".m4v", "video/x-m4v"},
{".mov", "video/quicktime"},
{".mp2", "audio/x-mpeg"},
{".mp3", "audio/x-mpeg"},
{".mp4", "video/mp4"},
{".mpc", "application/vnd.mpohun.certificate"},
{".mpe", "video/mpeg"},
{".mpeg", "video/mpeg"},
{".mpg", "video/mpeg"},
{".mpg4", "video/mp4"},
{".mpga", "audio/mpeg"},
{".msg", "application/vnd.ms-outlook"},
{".ogg", "audio/ogg"},
{".pdf", "application/pdf"},
{".png", "image/png"},
{".pps", "application/vnd.ms-powerpoint"},
{".ppt", "application/vnd.ms-powerpoint"},
{".pptx",
"application/vnd.openxmlformats-officedocument.presentationml.presentation"},
{".prop", "text/plain"}, {".rc", "text/plain"},
{".rmvb", "audio/x-pn-realaudio"}, {".rtf", "application/rtf"},
{".sh", "text/plain"}, {".tar", "application/x-tar"},
{".tgz", "application/x-compressed"}, {".txt", "text/plain"},
{".wav", "audio/x-wav"}, {".wma", "audio/x-ms-wma"},
{".wmv", "audio/x-ms-wmv"},
{".wps", "application/vnd.ms-works"}, {".xml", "text/plain"},
{".z", "application/x-compress"},
{".zip", "application/x-zip-compressed"}, {"", "*/*"}};
}
@@ -0,0 +1,50 @@
package com.sw.platecabinet.utils
import androidx.fragment.app.FragmentActivity
import com.sw.plate.utils.ToastUtils
import com.sw.plate.utils.comn.SerialApi
import com.sw.plate.utils.comn.SerialPortManager
import com.sw.platecabinet.activity.BaseActivity
import com.sw.platecabinet.activity.MainActivity
import com.sw.platecabinet.activity.PageType
import timber.log.Timber
object PlateUtils {
fun open(
fragmentActivity: FragmentActivity,
code: String?,
isScanCode: Boolean,
isSetting: Boolean = false
) {
val activity = fragmentActivity as BaseActivity<*>
Timber.d("itemClick llOpen equipmentBoxCode = ${code}")
val equipmentBoxCode: Int = code?.toInt() ?: -1
if (equipmentBoxCode < 0) {
ToastUtils.showToast("餐盘柜编号错误")
return
}
activity.showWaitingDialog("请稍等")
SerialApi.openPlate(equipmentBoxCode, object : SerialPortManager.SendCallback {
override fun onSuccess() {
activity.hideWaitingDialog()
if (isSetting.not()) {
MainActivity.start(
activity,
pageType = PageType.PLATE_TIP,
isScanCode = isScanCode
)
} else {
ToastUtils.showToast("柜门打开成功")
}
}
override fun onFail(e: Exception?) {
activity.hideWaitingDialog()
ToastUtils.showToast("柜门打开失败")
}
})
}
}
@@ -0,0 +1,103 @@
package com.sw.platecabinet.utils
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.google.zxing.WriterException
import com.google.zxing.common.BitMatrix
import com.google.zxing.qrcode.QRCodeWriter
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
import kotlin.apply
import kotlin.ranges.until
import kotlin.text.isEmpty
/**
* 二维码生成工具类
*/
object QRCodeUtil {
/**
* 生成二维码(默认大小)
* @param content 二维码内容
* @return 生成的二维码Bitmap
*/
@JvmOverloads
fun generateQRCode(content: String, size: Int = 500): Bitmap? {
return generateQRCode(content, size, Color.BLACK, Color.WHITE)
}
/**
* 生成二维码(自定义颜色)
* @param content 二维码内容
* @param size 二维码边长(像素)
* @param colorCode 二维码颜色
* @param backgroundColor 背景颜色
* @return 生成的二维码Bitmap
*/
fun generateQRCode(
content: String,
size: Int,
colorCode: Int,
backgroundColor: Int
): Bitmap? {
if (content.isEmpty()) {
return null
}
return try {
val hints = mutableMapOf<EncodeHintType, Any>().apply {
put(EncodeHintType.CHARACTER_SET, "UTF-8")
put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H) // 纠错级别
put(EncodeHintType.MARGIN, 1) // 边距
}
val bitMatrix = QRCodeWriter().encode(
content,
BarcodeFormat.QR_CODE,
size,
size,
hints
)
val pixels = IntArray(size * size).apply {
for (y in 0 until size) {
for (x in 0 until size) {
this[y * size + x] = if (bitMatrix.get(x, y)) colorCode else backgroundColor
}
}
}
Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888).apply {
setPixels(pixels, 0, size, 0, 0, size, size)
}
} catch (e: WriterException) {
e.printStackTrace()
null
}
}
/**
* 生成带Logo的二维码
* @param content 二维码内容
* @param size 二维码边长(像素)
* @param logo Logo Bitmap
* @return 带Logo的二维码Bitmap
*/
fun generateQRCodeWithLogo(content: String, size: Int, logo: Bitmap?): Bitmap? {
val qrCode = generateQRCode(content, size) ?: return null
logo ?: return qrCode
val logoSize = size / 5 // Logo大小约为二维码的1/5
val scaledLogo = Bitmap.createScaledBitmap(logo, logoSize, logoSize, false)
val offset = (size - logoSize) / 2
return Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888).apply {
val canvas = Canvas(this)
canvas.drawBitmap(qrCode, 0f, 0f, null)
canvas.drawBitmap(scaledLogo, offset.toFloat(), offset.toFloat(), null)
}
}
}
@@ -0,0 +1,15 @@
package com.sw.platecabinet.utils
class RegisterCallbackHandler {
private var isCallbackCalled = false
private val lock = Any()
fun setCallback(callback: () -> Unit) {
synchronized(lock) {
if (!isCallbackCalled) {
isCallbackCalled = true
callback()
}
}
}
}
@@ -0,0 +1,427 @@
package com.sw.platecabinet.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();
}
}
@@ -0,0 +1,42 @@
package com.sw.platecabinet.utils
import com.google.gson.Gson
import com.sw.inbound.utils.GsonUtils
import com.sw.inbound.utils.SPUtil
import com.sw.inbound.utils.SPUtil.Companion.getInstance
import com.sw.platecabinet.MyApp
import com.sw.platecabinet.model.response.EquipmentUserInfo
object SpTool {
const val LAST_FACE_TIMESTAMP: String = "faceTimestamp"
const val PLATE_BOX_DATA: String = "plateBoxBind"
private var spUtil: SPUtil? = null
init {
spUtil = getInstance(MyApp.instance!!, "default_sp")
}
fun putString(key:String, value:String) {
spUtil?.put(key, value)
}
var lastFaceTimestamp: Long
get() = spUtil?.get<Long?>(SpTool.LAST_FACE_TIMESTAMP, 0L) ?: 0
set(timestamp) {
spUtil?.put(LAST_FACE_TIMESTAMP, timestamp)
}
fun savePlateData(items: List<EquipmentUserInfo>) {
val jsonData = Gson().toJson(items)
putString(PLATE_BOX_DATA, jsonData)
}
fun getPlateData(): List<EquipmentUserInfo>? {
val jsonData = spUtil?.get(PLATE_BOX_DATA, "")
if (jsonData.isNullOrBlank()) {
return null
}
return GsonUtils.fromJsonList(jsonData, EquipmentUserInfo::class.java)
}
}
@@ -10,13 +10,16 @@ import android.view.View;
import android.view.Window; import android.view.Window;
import android.view.WindowManager; import android.view.WindowManager;
public class CustomDialog extends Dialog { /**
* 加载进度弹窗
*/
public class CustomLoadingDialog extends Dialog {
/** /**
* 宽高由布局文件中指定但是最底层的宽度无效可以多嵌套一层解决 * 宽高由布局文件中指定但是最底层的宽度无效可以多嵌套一层解决
*/ */
public CustomDialog(Context context, View layout, int style) { public CustomLoadingDialog(Context context, View layout, int style) {
super(context, style); super(context, style);
@@ -35,7 +38,7 @@ public class CustomDialog extends Dialog {
/** /**
* 宽高由该方法的参数设置 * 宽高由该方法的参数设置
*/ */
public CustomDialog(Context context, int width, int height, View layout, public CustomLoadingDialog(Context context, int width, int height, View layout,
int style) { int style) {
super(context, style); super(context, style);
// 设置内容 // 设置内容
@@ -3,7 +3,9 @@ package com.sw.platecabinet.viewmodel
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.sw.plate.utils.ToastUtils import com.sw.plate.utils.ToastUtils
import com.sw.platecabinet.GlobalData
import com.sw.platecabinet.model.response.ApiResponse import com.sw.platecabinet.model.response.ApiResponse
import com.sw.platecabinet.model.response.EquipmentInfo
import com.sw.platecabinet.network.ApiClient import com.sw.platecabinet.network.ApiClient
import com.sw.platecabinet.repository.RemoteRepository import com.sw.platecabinet.repository.RemoteRepository
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@@ -62,4 +64,13 @@ abstract class BaseViewModel() : ViewModel() {
fun handleError(exception: Exception) { fun handleError(exception: Exception) {
Timber.d("handleError ${exception.message}") Timber.d("handleError ${exception.message}")
} }
fun parseEquipmentInfo(equipmentInfo: EquipmentInfo) {
// GlobalData.appBaseUrl = equipmentInfo.appPackageUrl!!
// GlobalData.appBaseUrl="https://vip.shuziweidao.com"
// GlobalData.sdkKey = equipmentInfo.arcsoftSdkKey!!
// GlobalData.appId = equipmentInfo.arcsoftAppId!!
// GlobalData.activeKey = equipmentInfo.arcsoftActiveKey!!
// GlobalData.restId = equipmentInfo.canteenId!!
}
} }
@@ -0,0 +1,68 @@
//package com.sw.platecabinet.viewmodel
//
//import com.sw.inbound.utils.GsonUtils
//import com.sw.inbound.utils.SPUtil
//import com.sw.platecabinet.GlobalData
//import com.sw.platecabinet.GlobalKey
//import com.sw.platecabinet.model.response.EquipmentInfo
//import kotlinx.coroutines.flow.MutableStateFlow
//import kotlinx.coroutines.flow.StateFlow
//import timber.log.Timber
//import kotlin.jvm.java
//
///**
// * 初始化的viewmodel
// */
//class DeviceViewModel : BaseViewModel() {
// private val _deviceInfoResult = MutableStateFlow<Boolean?>(null)
// val deviceInfoResult: StateFlow<Boolean?> = _deviceInfoResult
//
// /**
// * 获取token
// */
// fun getDeviceToken(deviceId: String = GlobalData.deviceId) {
// launchWithLoading {
// val response = repository.getDeviceToken(deviceId)
// if (parseResponse(response)) {
// val response1 = repository.getDeviceInfo(deviceId, response.result!!)
//
// if (parseResponse(response1)) {
// val equipmentInfo = response1.result
// if (equipmentInfo == null) return@launchWithLoading
// try {
// parseEquipmentInfo(equipmentInfo)
// SPUtil.getInstance()
// .put(GlobalKey.KEY_EQUIPMENT_INFO, GsonUtils.toJson(equipmentInfo))
// _deviceInfoResult.value = true
// } catch (e: Exception) {
// Timber.e(e)
// }
// }
// }
// }
// }
//
// /**
// * 检查缓存数据
// */
// fun checkEquipmentInfo(): Boolean {
// val equipmentInfoStr = SPUtil.getInstance().get(GlobalKey.KEY_EQUIPMENT_INFO, "")
// if (equipmentInfoStr == null) {
// Timber.e("获取缓存设备信息失败")
// return false
// }
// val equipmentInfo =
// GsonUtils.fromJson<EquipmentInfo>(equipmentInfoStr, EquipmentInfo::class.java)
// if (equipmentInfo == null) {
// Timber.e("解析缓存设备信息失败")
// return false
// }
// try {
// parseEquipmentInfo(equipmentInfo)
// return true
// } catch (e: Exception) {
// Timber.e(e)
// return false
// }
// }
//}
@@ -1,21 +1,22 @@
package com.sw.platecabinet.viewmodel package com.sw.platecabinet.viewmodel
import com.sw.inbound.utils.DateTimeUtils
import com.sw.platecabinet.GlobalData
import com.sw.platecabinet.GlobalData.globalEquipmentCode 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.EquipmentUserInfo
import com.sw.platecabinet.model.response.SearchResult 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.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.withContext
import java.io.File
/**
* 管理员操作viewmodel
*/
class SettingViewModel : BaseViewModel() { class SettingViewModel : BaseViewModel() {
// 设备绑定列表 // 设备绑定列表
private val _equipmentList = MutableStateFlow<List<EquipmentUserInfo>>(emptyList()) // private val _equipmentList = MutableStateFlow<List<EquipmentUserInfo>>(emptyList())
val equipmentList: StateFlow<List<EquipmentUserInfo>> = _equipmentList // val equipmentList: StateFlow<List<EquipmentUserInfo>> = _equipmentList
private val _searchMemberList = MutableStateFlow<List<SearchResult.Member>>(emptyList()) private val _searchMemberList = MutableStateFlow<List<SearchResult.Member>>(emptyList())
val searchMemberList: StateFlow<List<SearchResult.Member>> = _searchMemberList val searchMemberList: StateFlow<List<SearchResult.Member>> = _searchMemberList
@@ -30,128 +31,220 @@ class SettingViewModel : BaseViewModel() {
/** /**
* 绑定/解绑状态 <绑定/解绑, 错误信息,> * 绑定/解绑状态 <绑定/解绑, 错误信息,>
*/ */
private val _bindStateChange = MutableStateFlow<Pair<Boolean?, ErrorInfo>>(null to ErrorInfo()) // private val _bindStateChange = MutableStateFlow<Pair<Boolean?, ErrorInfo>>(null to ErrorInfo())
val bindStateChange: StateFlow<Pair<Boolean?, ErrorInfo>> = _bindStateChange // val bindStateChange: StateFlow<Pair<Boolean?, ErrorInfo>> = _bindStateChange
/** /**
* 获取设备绑定用户信息列表 * 获取设备绑定用户信息列表
*/ */
fun getEquipmentList(equipmentCode: String = globalEquipmentCode) { // fun getEquipmentList(equipmentCode: String = globalEquipmentCode) {
launchWithLoading { // launchWithLoading {
val param = // val param =
EquipmentParam(appVersion = GlobalData.appVersion, equipmentCode = equipmentCode) // EquipmentParam(appVersion = GlobalData.appVersion, equipmentCode = equipmentCode)
val response = repository.getEquipmentList(param) // val response = repository.getEquipmentList(param)
if (parseResponse(response)) { // if (parseResponse(response)) {
_equipmentList.value = response.data ?: emptyList() // _equipmentList.value = response.data ?: emptyList()
} // }
} // }
// val list = mutableListOf<EquipmentUserInfo>() //// val list = mutableListOf<EquipmentUserInfo>()
// for (i in 1 .. 30){ //// for (i in 1 .. 30){
// list.add(EquipmentUserInfo(id = i, equipmentBoxCode = i.toString())) //// list.add(EquipmentUserInfo(id = i, equipmentBoxCode = i.toString()))
//// }
//// _equipmentList.value = list
// }
// fun getEquipmentList(block: (List<EquipmentUserInfo>) -> Unit) {
// launchWithLoading {
// val response = repository.getEquipmentList()
// if (parseResponse(response)) {
// val list = response.data ?: emptyList()
// block(list)
// }
// }
// } // }
// _equipmentList.value = list
}
/** // /**
* 搜索会员信息列表 // * 搜索会员信息列表
*/ // */
fun getSearchMemberList(param: String, pageNum: Int = 1, pageSize: Int = 50) { // fun getSearchMemberList(param:String, pageNum: Int = 1, pageSize: Int = 50) {
currentPage = pageNum // if (TextUtils.isEmpty(param)) return
launch { // currentPage = pageNum
val param = SearchParam( // var name =""
appVersion = GlobalData.appVersion, // var phone =""
param = param, // val num = param.toIntOrNull()
pageNum = pageNum, // if (num != null) {
pageSize = pageSize // phone = param
) // } else {
isLoading = true // name = param
val response = repository.searchUser(param) // }
isLoading = false // launch {
if (parseResponse(response)) { // val searchParam = SearchParam(
response.data?.let { //// appVersion = GlobalData.appVersion,
canLoadMore = it.hasNextPage != false // name = name,
val newList = it.list ?: emptyList() // phone = phone,
if (pageNum == 1) { // pageNum = pageNum,
_searchMemberList.value = newList // pageSize = pageSize
} else { // )
_searchMemberList.value = _searchMemberList.value.plus(newList) // isLoading = true
} // val response = repository.searchUser(searchParam)
currentPage++ // 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( // fun bindPlate(
equipmentBoxCode: String, // equipmentBoxCode: String,
equipmentCode: String = globalEquipmentCode, // equipmentCode: String = globalEquipmentCode,
memberId: Int, // memberId: Int,
plateNumber: String // plateNumber: String
) { // ) {
launchWithLoading { // _bindStateChange.value = null to ErrorInfo()
val bindParam = BindParam( // launchWithLoading {
appVersion = GlobalData.appVersion, // val bindParam = BindParam(
equipmentBoxCode = equipmentBoxCode, // appVersion = GlobalData.appVersion,
equipmentCode = equipmentCode, // equipmentBoxCode = equipmentBoxCode,
memberId = memberId, // equipmentCode = equipmentCode,
plateNumber = plateNumber // memberId = memberId,
) // plateNumber = plateNumber
val response = repository.bindEquipment(bindParam) // )
if (parseResponse(response)) { // val response = repository.bindEquipment(bindParam)
var userInfo = response.data // if (parseResponse(response)) {
if (userInfo == null) { // var userInfo = response.data
userInfo = EquipmentUserInfo() // if (userInfo == null) {
userInfo.equipmentBoxCode = equipmentBoxCode // userInfo = EquipmentUserInfo()
userInfo.plateNumber = plateNumber // userInfo.equipmentBoxCode = equipmentBoxCode
userInfo.equipmentCode = equipmentCode // userInfo.plateNumber = plateNumber
} // userInfo.equipmentCode = equipmentCode
_bindStateChange.value = true to ErrorInfo(equipmentUserInfo = userInfo) // }
} else { // _bindStateChange.value = true to ErrorInfo(equipmentUserInfo = userInfo)
_bindStateChange.value = true to ErrorInfo(response.code, response.msg.toString()) // } else {
} // _bindStateChange.value = true to ErrorInfo(response.code, response.msg.toString())
} // }
} // }
// }
// fun unbindPlate(
// equipmentBoxCode: String,
// equipmentCode: String = globalEquipmentCode
// ) {
// launchWithLoading {
// val bindParam = BindParam(
// appVersion = GlobalData.appVersion,
// equipmentBoxCode = equipmentBoxCode,
// equipmentCode = equipmentCode
// )
// val response = repository.bindEquipment(bindParam)
// if (parseResponse(response)) {
// _bindStateChange.value = false to ErrorInfo()
// } else {
// _bindStateChange.value = false to ErrorInfo(response.code, response.msg.toString())
// }
// }
// }
fun unbindPlate( // fun unbindPlate(id: Long?, block:(Pair<Boolean?, ErrorInfo>)-> Unit) {
equipmentBoxCode: String, // block(null to ErrorInfo())
equipmentCode: String = globalEquipmentCode // launchWithLoading {
) { // val response = repository.plateUnbind(id)
launchWithLoading { // if (parseResponse(response)) {
val bindParam = BindParam( // block(false to ErrorInfo())
appVersion = GlobalData.appVersion, // } else {
equipmentBoxCode = equipmentBoxCode, // block(false to ErrorInfo(response.code, response.msg.toString()))
equipmentCode = equipmentCode // }
) // }
val response = repository.bindEquipment(bindParam) // }
if (parseResponse(response)) {
_bindStateChange.value = false to ErrorInfo() // fun bindPlate(bindParam: BindParam, block:(Pair<Boolean?, ErrorInfo>)-> Unit) {
} else { // launchWithLoading {
_bindStateChange.value = false to ErrorInfo(response.code, response.msg.toString()) //// 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( fun findByPlateNumber(
plateNumber: String, plateNumber: String,
equipmentCode: String = globalEquipmentCode equipmentCode: String = globalEquipmentCode
) { ) {
launchWithLoading { // _searchUserInfo.value = null
val bindParam = BindParam( // launchWithLoading {
appVersion = GlobalData.appVersion, //// val bindParam = BindParam(plateNumber = plateNumber)
plateNumber = plateNumber, // val response = repository.findByPlateNumber(plateNumber)
equipmentCode = equipmentCode // if (response.code == "00000") {
) // _searchUserInfo.value = response.data
val response = repository.findByPlateNumber(bindParam) // } else {
if (response.code == 200) { // _searchUserInfo.value = EquipmentUserInfo(
_searchUserInfo.value = response.data // plateNumber = plateNumber,
} else { // equipmentCode = equipmentCode,
_searchUserInfo.value = EquipmentUserInfo( // updateTime = DateTimeUtils.getDateTimeString()
plateNumber = plateNumber, // )
equipmentCode = equipmentCode, // }
updateTime = DateTimeUtils.getDateTimeString() // }
}
val PAGE_SIZE = 100
/**
* 获取人脸数据
*/
fun getFaceIncrementList(
pageNo: Int = 1,
pageSize: Int = PAGE_SIZE,
timestamp: Long,
block: (List<UserFaceModel>) -> Unit
) {
launch {
val response = repository.getFaceIncrementList(
pageNum = pageNo.toLong(),
pageSize = pageSize.toLong(),
timestamp = timestamp
) )
if (parseResponse(response)) {
withContext(Dispatchers.Default) {
val list: List<UserFaceModel> = response.data ?: emptyList()
block(list)
} }
} }
} }
} }
/**
* 添加人脸数据
*/
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)
}
}
}
@@ -11,9 +11,11 @@ import com.sw.plate.utils.arcface.facedb.entity.FaceEntity
import com.sw.platecabinet.GlobalData import com.sw.platecabinet.GlobalData
import com.sw.platecabinet.GlobalData.globalEquipmentCode import com.sw.platecabinet.GlobalData.globalEquipmentCode
import com.sw.platecabinet.GlobalKey import com.sw.platecabinet.GlobalKey
import com.sw.platecabinet.model.DeviceConfig
import com.sw.platecabinet.model.request.LoginParam import com.sw.platecabinet.model.request.LoginParam
import com.sw.platecabinet.model.response.EquipmentUserInfo import com.sw.platecabinet.model.response.EquipmentUserInfo
import com.sw.platecabinet.model.response.UserFaceModel import com.sw.platecabinet.model.response.UserFaceModel
import com.sw.platecabinet.utils.SpTool
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@@ -21,51 +23,86 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
/**
* 用户viewmodel
*/
class UserViewModel : BaseViewModel() { class UserViewModel : BaseViewModel() {
companion object {
const val PAGE_SIZE = 100
}
// 通过人脸获取到的用户信息 // 通过人脸获取到的用户信息
private val _currentUserInfo = MutableStateFlow<EquipmentUserInfo?>(null) // private val _currentUserInfo = MutableStateFlow<EquipmentUserInfo?>(null)
val currentUserInfo: StateFlow<EquipmentUserInfo?> = _currentUserInfo // val currentUserInfo: StateFlow<EquipmentUserInfo?> = _currentUserInfo
private val faceApi: FaceApi = FaceApi() private val faceApi: FaceApi = FaceApi()
/** /**
* 获取token * 获取token
*/ */
fun generateToken(deviceId: String = "SWSN:88:12:AC:4E:D9:CC") { // fun generateToken(deviceId: String = "SWSN:88:12:AC:4E:D9:CC") {
launchWithLoading { // launchWithLoading {
val response = repository.generateToken(deviceId) // val response = repository.generateToken(deviceId)
if (parseResponse(response)) { // if (parseResponse(response)) {
// 缓存token // // 缓存token
SPUtil.getInstance().put(GlobalKey.KEY_TOKEN, response.data) // SPUtil.getInstance().put(GlobalKey.KEY_TOKEN, response.data)
// 首次运行获取人脸数据 // // 首次运行获取人脸数据
if (SPUtil.getInstance().get(GlobalKey.KEY_FIRST_RUN, false) != true) { // if (SPUtil.getInstance().get(GlobalKey.KEY_FIRST_RUN, false) != true) {
getUserFaceCache() // getUserFaceCache()
} // }
} // }
} // }
} // }
/** /**
* 获取人脸数据 * 获取人脸数据
*/ */
fun getUserFaceCache(index: Int = 0) { fun getUserFaceCache(
pageNo: Int = 1,
pageSize: Int = PAGE_SIZE,
callback: (Boolean, String?) -> Unit = { statue, msg -> }
) {
var currentPageNo = pageNo
launchWithLoading { launchWithLoading {
val response = repository.getUserFaceCache() val response = repository.getUserFaceCache(pageNum = pageNo, pageSize = pageSize)
if (parseResponse(response)) { val status = parseResponse(response)
if (status.not()) {
callback(false, response.msg)
return@launchWithLoading
}
// 获取成功一次后缓存状态 // 获取成功一次后缓存状态
SPUtil.getInstance().put(GlobalKey.KEY_FIRST_RUN, true) SPUtil.getInstance().put(GlobalKey.KEY_FIRST_RUN, true)
val list: List<UserFaceModel> = response.data ?: emptyList() val list: List<UserFaceModel> = response.data ?: emptyList()
val faceEntity = list.map { if (pageNo == 1 && list.isEmpty()) {
FaceEntity(it.userId, null, Base64.decode(it.faceFeatureString)) callback(true, "暂无数据")
return@launchWithLoading
}
val faceEntityList = list.map {
FaceEntity(it.userId, null, Base64.decode(it.faceFeatureStr))
} }
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
faceApi.updateFaceData(index, faceEntity) faceApi.updateFaceData(pageNo, faceEntityList)
activeEngine() if (list.size >= pageSize) {
faceTimestamp = list.last().faceUpdateTimestamp ?: 0
currentPageNo++
getUserFaceCache(
pageNo = currentPageNo,
callback = callback
)
return@withContext
} }
if (list.isNotEmpty()) {
faceTimestamp = list.last().faceUpdateTimestamp ?: 0
}
SpTool.lastFaceTimestamp = faceTimestamp
callback(true, "查询完成")
} }
} }
} }
private var faceTimestamp = 0L
/** /**
* 激活人脸识别引擎 * 激活人脸识别引擎
*/ */
@@ -109,44 +146,48 @@ class UserViewModel : BaseViewModel() {
/** /**
* 校验码登录 * 校验码登录
*/ */
fun loginWithPwd(equipmentCode: String = globalEquipmentCode, phone: String, password: String) { fun loginWithPwd(loginParam: LoginParam) {
launchWithLoading { // launchWithLoading {
val loginParam = LoginParam( // val response = repository.equipmentBoxLogin(loginParam)
appVersion = GlobalData.appVersion, // if (parseResponse(response)) {
equipmentCode = equipmentCode, // _currentUserInfo.value = response.data
phone = phone, // }
password = password // }
)
val response = repository.equipmentBoxLogin(loginParam)
if (parseResponse(response)) {
_currentUserInfo.value = response.data
}
}
} }
/** /**
* 通过用户id获取用户信息 * 通过用户id获取用户信息
*/ */
fun getUserInfoById(equipmentCode: String = globalEquipmentCode, memberId: Int) { // fun getUserInfoById(memberId: String?, action: (EquipmentUserInfo?) -> Unit = {}) {
launchWithLoading { // _currentUserInfo.value = null
val loginParam = LoginParam( // launchWithLoading {
appVersion = GlobalData.appVersion, // val loginParam = LoginParam(faceId = memberId)
equipmentCode = equipmentCode, // val response = repository.equipmentBoxLogin(loginParam)
memberId = memberId // if (parseResponse(response)) {
) // _currentUserInfo.value = response.data
val response = repository.equipmentBoxLogin(loginParam) // action(response.data)
if (parseResponse(response)) { // }
_currentUserInfo.value = response.data // }
} // }
}
}
/** /**
* 重置用户信息 * 重置用户信息
*/ */
fun resetUserInfo() { fun resetUserInfo() {
Timber.d("resetUserInfo") Timber.d("resetUserInfo")
_currentUserInfo.value = null // _currentUserInfo.value = null
}
fun getDeviceConfig(block: (DeviceConfig?) -> Unit) {
// Timber.tag(TAG).d("getDeviceConfig")
launch {
val response = repository.getDeviceConfig()
if (parseResponse(response)) {
block(response.data)
} else {
block(null)
}
}
} }
} }
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#000000" /> <!-- 整体黑色背景 -->
<stroke
android:width="2dp"
android:color="#333333" /> <!-- 边框颜色和宽度 -->
<corners android:radius="12dp" /> <!-- 圆角半径 -->
</shape>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#30000000">
<item>
<shape android:shape="rectangle">
<solid android:color="#ff08C6DC" />
<corners android:radius="8dp" />
</shape>
</item>
</ripple>
+35
View File
@@ -0,0 +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" />
</item>
<item
android:width="337dp"
android:height="322dp"
android:gravity="top|center_horizontal"
android:top="112dp">
<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>
@@ -4,5 +4,5 @@
<corners android:radius="12dp" /> <corners android:radius="12dp" />
<stroke <stroke
android:width="2dp" android:width="2dp"
android:color="#887872" /> android:color="#FFCC99" />
</shape> </shape>
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"> <shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="6dp" /> <corners android:radius="6dp" />
<solid android:color="#F0C8B4" /> <solid android:color="#FFCC99" />
</shape> </shape>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 999 KiB

+1 -4
View File
@@ -1,8 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"> <shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="12dp" /> <corners android:radius="12dp" />
<!-- <solid android:color="#F3D2BD"/>--> <solid android:color="#FFCC99" />
<gradient
android:endColor="#E5B79F"
android:startColor="#F9DDC9" />
</shape> </shape>
+1 -1
View File
@@ -3,5 +3,5 @@
<corners android:radius="12dp" /> <corners android:radius="12dp" />
<stroke <stroke
android:width="2dp" android:width="2dp"
android:color="#F0C8B4" /> android:color="#FFCC99" />
</shape> </shape>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/white"/>
<corners android:radius="30dp"/>
</shape>
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
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"/>-->
<TextView
android:id="@+id/btnInit"
android:layout_width="225dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="75dp"
android:background="@drawable/bg_init_button"
android:gravity="center"
android:padding="10dp"
android:text="设备初始化"
android:textColor="@color/white"
android:textSize="22sp"
android:textStyle="bold"
android:visibility="invisible"
tools:ignore="HardcodedText" />
<Space
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1" />
<ImageView
android:id="@+id/ivQrCode"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_gravity="center_horizontal|bottom"
android:layout_marginBottom="37dp"
tools:ignore="ContentDescription"
tools:src="@mipmap/ic_logo512"
android:visibility="invisible"/>
</LinearLayout>
+40 -9
View File
@@ -12,29 +12,60 @@
android:id="@+id/includeHeader" android:id="@+id/includeHeader"
layout="@layout/item_title_time" /> layout="@layout/item_title_time" />
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"/>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="260dp" android:layout_marginTop="0dp"
android:gravity="center_horizontal" android:gravity="center"
android:orientation="vertical"> android:orientation="vertical">
<ImageView <ImageView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="142dp"
android:src="@drawable/ic_init_text" /> android:src="@drawable/ic_init_text"
android:adjustViewBounds="true"/>
<ImageView <ImageView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="345dp"
android:layout_marginTop="90dp" android:layout_marginTop="90dp"
android:src="@drawable/ic_init_img" /> android:src="@drawable/ic_init_img"
android:adjustViewBounds="true" />
<ImageView <!-- <ImageView-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_marginTop="20dp"-->
<!-- android:src="@drawable/ic_init_press" />-->
<FrameLayout
android:id="@+id/takeButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="20dp" android:layout_marginTop="-35dp">
android:src="@drawable/ic_init_press" /> <TextView
android:layout_width="wrap_content"
android:layout_height="60dp"
android:text="点击取盘"
android:textColor="#FFCC99"
android:textSize="24sp"
android:layout_marginHorizontal="60dp"
android:layout_marginVertical="50dp"
android:paddingHorizontal="45dp"
android:gravity="center"
android:background="@drawable/btn_outline"
tools:ignore="HardcodedText" />
</FrameLayout>
</LinearLayout> </LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
</LinearLayout> </LinearLayout>
@@ -21,7 +21,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="手机后四位" android:text="手机后四位"
android:textColor="#F0C8B4" android:textColor="#FFCC99"
android:textSize="26sp" android:textSize="26sp"
android:textStyle="bold" /> android:textStyle="bold" />
@@ -62,7 +62,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="25dp" android:layout_marginTop="25dp"
android:text="校验码" android:text="校验码"
android:textColor="#F0C8B4" android:textColor="#FFCC99"
android:textSize="26sp" android:textSize="26sp"
android:textStyle="bold" /> android:textStyle="bold" />
@@ -115,7 +115,7 @@
android:layout_marginTop="40dp" android:layout_marginTop="40dp"
android:padding="8dp" android:padding="8dp"
android:text="人脸识别" android:text="人脸识别"
android:textColor="#887872" android:textColor="#FFCC99"
android:textSize="26sp" android:textSize="26sp"
android:textStyle="bold" /> android:textStyle="bold" />
</LinearLayout> </LinearLayout>
+110 -30
View File
@@ -21,12 +21,12 @@
<FrameLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:gravity="center_vertical" android:gravity="center_vertical"
android:visibility="visible"> android:visibility="visible">
<FrameLayout <FrameLayout
android:layout_width="380dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_marginTop="0dp" android:layout_marginTop="0dp"
@@ -34,22 +34,23 @@
<TextureView <TextureView
android:id="@+id/dual_camera_texture_preview_rgb" android:id="@+id/dual_camera_texture_preview_rgb"
android:layout_width="380dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:visibility="visible" /> android:visibility="visible" />
<com.sw.plate.utils.arcface.FaceRectView <com.sw.plate.utils.arcface.FaceRectView
android:id="@+id/dual_camera_face_rect_view" android:id="@+id/dual_camera_face_rect_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="380dp" android:layout_height="match_parent"
android:visibility="gone" /> android:visibility="visible" />
</FrameLayout> </FrameLayout>
<ImageView <ImageView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginTop="0dp" android:layout_marginTop="0dp"
android:background="@drawable/bg_face" android:scaleType="centerCrop"
android:src="@drawable/bg_face"
android:visibility="visible" /> android:visibility="visible" />
<TextView <TextView
@@ -59,39 +60,118 @@
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
android:layout_marginTop="129dp" android:layout_marginTop="129dp"
android:text="请正视屏幕" android:text="请正视屏幕"
android:textStyle="bold" android:textColor="#FFCC99"
android:textColor="#fff0c8b4" android:textSize="36sp"
android:textSize="36sp" /> android:textStyle="bold" />
<LinearLayout
android:id="@+id/ll_to_pwd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="208dp"
android:layout_marginTop="730dp"
android:background="@drawable/btn_outline_887872"
android:gravity="center"
android:paddingHorizontal="45dp"
android:paddingVertical="18dp">
<TextView <TextView
android:id="@+id/tvSimilarValue"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="密码登录" android:textSize="26sp"
android:textColor="#ff887872" android:layout_marginTop="80dp"
android:textSize="24sp" /> tools:text="0.6183次"
</LinearLayout> android:layout_gravity="center_horizontal"
android:textColor="@color/white"/>
<!-- <LinearLayout-->
<!-- android:id="@+id/ll_to_pwd"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_marginStart="208dp"-->
<!-- android:layout_marginTop="730dp"-->
<!-- android:background="@drawable/btn_outline"-->
<!-- android:gravity="center"-->
<!-- android:paddingHorizontal="45dp"-->
<!-- android:paddingVertical="18dp"-->
<!-- android:visibility="gone">-->
<!-- <TextView-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:text="密码登录"-->
<!-- android:textColor="#FFCC99"-->
<!-- android:textSize="24sp" />-->
<!-- </LinearLayout>-->
<TextView <TextView
android:id="@+id/tv_to_init" android:id="@+id/tv_to_init"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:text="30"
android:padding="12dp"
android:gravity="center"
android:layout_marginTop="185dp" android:layout_marginTop="185dp"
android:textColor="#F0C8B4" android:gravity="center"
android:textSize="36sp" /> android:padding="12dp"
android:textColor="#FFCC99"
android:textSize="36sp"
tools:text="30" />
<!-- <LinearLayout-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:gravity="center_horizontal"-->
<!-- android:layout_gravity="bottom|center_horizontal"-->
<!-- android:layout_marginBottom="130dp"-->
<!-- android:orientation="vertical">-->
<!-- <LinearLayout-->
<!-- android:id="@+id/layoutState"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="50dp"-->
<!-- android:gravity="center_vertical"-->
<!-- android:orientation="horizontal"-->
<!-- android:visibility="invisible">-->
<!-- <ProgressBar-->
<!-- android:id="@+id/pbCollectLoading"-->
<!-- android:layout_width="32dp"-->
<!-- android:layout_height="32dp"-->
<!-- android:indeterminateTint="@color/white" />-->
<!-- <TextView-->
<!-- android:id="@+id/tvCollectState"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_marginStart="10dp"-->
<!-- android:text="采集中......"-->
<!-- android:textColor="@color/white"-->
<!-- android:textSize="24sp" />-->
<!-- </LinearLayout>-->
<!-- <TextView-->
<!-- android:id="@+id/btnCollectFace"-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="60dp"-->
<!-- android:background="@drawable/btn_outline"-->
<!-- android:gravity="center"-->
<!-- android:layout_marginTop="10dp"-->
<!-- android:paddingHorizontal="45dp"-->
<!-- android:text="开始采集"-->
<!-- android:textColor="#FFCC99"-->
<!-- android:textSize="24sp"-->
<!-- tools:ignore="HardcodedText" />-->
<!-- </LinearLayout>-->
<!-- <androidx.recyclerview.widget.RecyclerView-->
<!-- android:id="@+id/rvLogInfo"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="match_parent"-->
<!-- android:layout_gravity="bottom"-->
<!-- android:overScrollMode="never"-->
<!-- android:visibility="gone"-->
<!-- android:background="@color/black"-->
<!-- tools:listitem="@layout/list_item_log_info" />-->
<!-- <ImageView-->
<!-- android:id="@+id/btnLogState"-->
<!-- android:layout_width="60dp"-->
<!-- android:layout_height="60dp"-->
<!-- android:layout_gravity="bottom|end"-->
<!-- android:background="@drawable/shape_white_circle"-->
<!-- android:padding="10dp"-->
<!-- android:layout_marginEnd="10dp"-->
<!-- android:layout_marginBottom="10dp"-->
<!-- android:src="@drawable/ic_log_show"-->
<!-- android:textSize="16sp" />-->
</FrameLayout> </FrameLayout>
</LinearLayout> </LinearLayout>
@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="1024dp"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:id="@+id/dialogContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_marginTop="0dp"
android:background="@drawable/bg_dialog"
android:gravity="center"
android:orientation="vertical"
android:paddingTop="30dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_tip_warn_unbind" />
<TextView
android:id="@+id/tv_tip_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
tools:text="即将解除【曹德磊】的餐盘"
android:textColor="#ffffffff"
android:textSize="28sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android: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/tv_cancel"
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/tv_confirm"
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>
</LinearLayout>
@@ -0,0 +1,53 @@
<?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:id="@+id/dialogContainer"
android:layout_width="480dp"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_marginTop="0dp"
android:background="@drawable/bg_dialog"
android:gravity="center"
android:orientation="vertical"
tools:ignore="HardcodedText">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:src="@drawable/ic_tip_warn_unbind" />
<TextView
android:id="@+id/tv_tip_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="余额不足"
android:textColor="#ffffffff"
android:textSize="28sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="请通过小程序或到柜台充值后使用"
android:textColor="#ff999999"
android:textSize="24sp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="30dp"
android:background="#ff333333" />
<TextView
android:id="@+id/tv_confirm"
android:layout_width="match_parent"
android:layout_height="90dp"
android:gravity="center"
android:text="知道了"
android:textColor="#ffffcc99"
android:textSize="30sp"
android:textStyle="bold" />
</LinearLayout>
+77
View File
@@ -0,0 +1,77 @@
<?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="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:layout_marginHorizontal="50dp"
android:gravity="center"
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>
@@ -0,0 +1,54 @@
<?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:id="@+id/dialogContainer"
android:layout_width="480dp"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_marginTop="0dp"
android:background="@drawable/bg_dialog"
android:gravity="center"
android:orientation="vertical"
tools:ignore="HardcodedText">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:src="@drawable/ic_tip_warn_unbind" />
<TextView
android:id="@+id/tv_tip_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="温馨提示"
android:textColor="#ffffffff"
android:textSize="28sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvBindContent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="请通过小程序或到柜台充值后使用"
android:textColor="#ff999999"
android:textSize="24sp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="30dp"
android:background="#ff333333" />
<TextView
android:id="@+id/tv_confirm"
android:layout_width="match_parent"
android:layout_height="90dp"
android:gravity="center"
android:text="知道了"
android:textColor="#ffffcc99"
android:textSize="30sp"
android:textStyle="bold" />
</LinearLayout>
+12 -14
View File
@@ -1,31 +1,29 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="match_parent"> android:layout_height="wrap_content">
<RelativeLayout <LinearLayout
android:layout_width="240dp" android:layout_width="240dp"
android:layout_height="80dp" android:layout_height="80dp"
android:gravity="center_vertical"
android:background="@drawable/dialog_waiting"> android:background="@drawable/dialog_waiting">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:indeterminateTint="@color/teal_200"
android:indeterminateTintMode="src_atop" />
<TextView <TextView
android:id="@+id/tvTip" android:id="@+id/tvTip"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerInParent="true" android:layout_marginStart="30dp"
android:textColor="@color/white" android:textColor="@color/white"
android:text="请稍等" android:text="请稍等"
android:textSize="22sp" /> android:textSize="22sp" />
<ProgressBar </LinearLayout>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginRight="30dp"
android:layout_toLeftOf="@id/tvTip"
android:indeterminateTint="@color/teal_200"
android:indeterminateTintMode="src_atop" />
</RelativeLayout>
</FrameLayout> </FrameLayout>
+24 -15
View File
@@ -7,14 +7,15 @@
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:orientation="vertical" android:orientation="vertical"
android:paddingHorizontal="60dp" android:paddingHorizontal="60dp"
tools:context=".fragment.BindPlateFragment"> tools:context=".fragment.BindPlateFragment"
tools:background="@color/black">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="60dp" android:layout_marginTop="60dp"
android:text="柜体编号" android:text="柜体编号"
android:textColor="#ff7c6d6a" android:textColor="#999999"
android:textSize="20sp" /> android:textSize="20sp" />
<TextView <TextView
@@ -22,7 +23,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="20dp" android:layout_marginTop="20dp"
android:textColor="#fff0c8b4" android:textColor="#FFCC99"
android:textSize="36sp" android:textSize="36sp"
android:textStyle="bold" android:textStyle="bold"
tools:text="1-12" /> tools:text="1-12" />
@@ -43,7 +44,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="餐盘码" android:text="餐盘码"
android:textColor="#fff0c8b4" android:textColor="#FFCC99"
android:textSize="26sp" /> android:textSize="26sp" />
<Space <Space
@@ -55,7 +56,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="确认餐盘码是否有误" android:text="确认餐盘码是否有误"
android:textColor="#ff7c6d6a" android:textColor="#666666"
android:textSize="20sp" /> android:textSize="20sp" />
</LinearLayout> </LinearLayout>
@@ -72,11 +73,11 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/transparent" android:background="@color/transparent"
android:gravity="center" android:gravity="center"
android:maxLines="1"
android:hint="请录入餐盘码" android:hint="请录入餐盘码"
android:inputType="number" android:inputType="number"
android:textColor="#fff0c8b4" android:maxLines="1"
android:textColorHint="#ff7c6d6a" android:textColor="#FFCC99"
android:textColorHint="#999999"
android:textSize="26sp" android:textSize="26sp"
android:textStyle="bold" android:textStyle="bold"
tools:text="" /> tools:text="" />
@@ -92,7 +93,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="会员" android:text="会员"
android:textColor="#fff0c8b4" android:textColor="#FFCC99"
android:textSize="26sp" /> android:textSize="26sp" />
<Space <Space
@@ -104,7 +105,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="仅限VIP会员绑定,非VIP暂不支持" android:text="仅限VIP会员绑定,非VIP暂不支持"
android:textColor="#ff7c6d6a" android:textColor="#666666"
android:textSize="20sp" /> android:textSize="20sp" />
</LinearLayout> </LinearLayout>
@@ -117,17 +118,25 @@
<EditText <EditText
android:id="@+id/et_user_info" android:id="@+id/et_user_info"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/transparent" android:background="@color/transparent"
android:gravity="center" android:gravity="center"
android:hint="请输入会员姓名或手机号" android:hint="请输入会员姓名或手机号"
android:imeOptions="actionSearch" android:imeOptions="actionSearch"
android:inputType="text" android:inputType="text"
android:maxLines="1" android:maxLines="1"
android:textColor="#F0C8B4" android:textColor="#FFCC99"
android:textColorHint="#ff7c6d6a" android:textColorHint="#999999"
android:textSize="26sp" /> android:textSize="26sp" />
<ImageView
android:id="@+id/iv_search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="24dp"
android:src="@drawable/ic_search" />
</LinearLayout> </LinearLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
@@ -150,7 +159,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="绑定开门" android:text="绑定开门"
android:textColor="#ff11111c" android:textColor="#11111C"
android:textSize="30sp" android:textSize="30sp"
android:textStyle="bold" /> android:textStyle="bold" />
</LinearLayout> </LinearLayout>
@@ -162,6 +171,6 @@
android:layout_marginTop="46dp" android:layout_marginTop="46dp"
android:paddingHorizontal="20dp" android:paddingHorizontal="20dp"
android:text="返回" android:text="返回"
android:textColor="#ff887872" android:textColor="#FFCC99"
android:textSize="26sp" /> android:textSize="26sp" />
</LinearLayout> </LinearLayout>
@@ -18,7 +18,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="48dp" android:layout_marginTop="48dp"
android:text="餐盘柜已满" android:text="餐盘柜已满"
android:textColor="#fff0c8b4" android:textColor="#FFCC99"
android:textSize="36sp" /> android:textSize="36sp" />
<TextView <TextView
@@ -27,7 +27,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="32dp" android:layout_marginTop="32dp"
android:text="3秒后返回主屏" android:text="3秒后返回主屏"
android:textColor="#ff887872" android:textColor="#FFCC99"
android:textSize="20sp" /> android:textSize="20sp" />
@@ -51,7 +51,7 @@
android:layout_marginHorizontal="20dp" android:layout_marginHorizontal="20dp"
android:maxLines="1" android:maxLines="1"
android:ellipsize="end" android:ellipsize="end"
android:textColor="#ff887872" android:textColor="#FFCC99"
android:textSize="24sp" /> android:textSize="24sp" />
<TextView <TextView
@@ -60,7 +60,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="34dp" android:layout_marginTop="34dp"
android:text="3" android:text="3"
android:textColor="#fff0c8b4" android:textColor="#FFCC99"
android:textSize="36sp" /> android:textSize="36sp" />
</LinearLayout> </LinearLayout>
</FrameLayout> </FrameLayout>
@@ -1,16 +1,23 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main" android:id="@+id/main"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
tools:context=".fragment.SettingListFragment"> tools:context=".fragment.SettingListFragment"
tools:background="@color/black">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/mainRecyclerView" android:id="@+id/mainRecyclerView"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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"/>
</LinearLayout> </LinearLayout>
@@ -13,14 +13,15 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="80dp" android:layout_marginTop="80dp"
android:src="@drawable/ic_unbind_img" /> android:rotation="180"
android:src="@drawable/ic_bind_img" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="25dp" android:layout_marginTop="25dp"
android:text="柜体编号" android:text="柜体编号"
android:textColor="#ff7c6d6a" android:textColor="#999999"
android:textSize="20sp" /> android:textSize="20sp" />
<TextView <TextView
@@ -30,7 +31,7 @@
android:layout_marginTop="23dp" android:layout_marginTop="23dp"
tools:text="1-12" tools:text="1-12"
android:textStyle="bold" android:textStyle="bold"
android:textColor="#fff0c8b4" android:textColor="#FFCC99"
android:textSize="36sp" /> android:textSize="36sp" />
@@ -53,7 +54,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="餐盘码" android:text="餐盘码"
android:textColor="#ff7c6d6a" android:textColor="#999999"
android:textSize="24sp" /> android:textSize="24sp" />
<Space <Space
@@ -66,7 +67,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:text="100888" tools:text="100888"
android:textColor="#fff0c8b4" android:textColor="#FFCC99"
android:textSize="24sp" android:textSize="24sp"
android:textStyle="bold" /> android:textStyle="bold" />
</LinearLayout> </LinearLayout>
@@ -88,7 +89,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="姓名" android:text="姓名"
android:textColor="#ff7c6d6a" android:textColor="#999999"
android:textSize="24sp" /> android:textSize="24sp" />
<Space <Space
@@ -101,7 +102,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:text="曹*磊" tools:text="曹*磊"
android:textColor="#fff0c8b4" android:textColor="#FFCC99"
android:textSize="24sp" android:textSize="24sp"
android:textStyle="bold" /> android:textStyle="bold" />
</LinearLayout> </LinearLayout>
@@ -123,7 +124,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="手机号" android:text="手机号"
android:textColor="#ff7c6d6a" android:textColor="#999999"
android:textSize="24sp" /> android:textSize="24sp" />
<Space <Space
@@ -136,7 +137,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:text="138****8000" tools:text="138****8000"
android:textColor="#fff0c8b4" android:textColor="#FFCC99"
android:textSize="24sp" android:textSize="24sp"
android:textStyle="bold" /> android:textStyle="bold" />
</LinearLayout> </LinearLayout>
@@ -157,7 +158,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="最近使用" android:text="最近使用"
android:textColor="#ff7c6d6a" android:textColor="#999999"
android:textSize="24sp" /> android:textSize="24sp" />
<Space <Space
@@ -170,7 +171,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:text="14天前使用" tools:text="14天前使用"
android:textColor="#fff0c8b4" android:textColor="#FFCC99"
android:textSize="24sp" android:textSize="24sp"
android:textStyle="bold" /> android:textStyle="bold" />
</LinearLayout> </LinearLayout>
@@ -188,7 +189,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="解除绑定" android:text="解除绑定"
android:textColor="#ff11111c" android:textColor="#11111C"
android:textSize="30sp" android:textSize="30sp"
android:textStyle="bold" /> android:textStyle="bold" />
</LinearLayout> </LinearLayout>
@@ -200,6 +201,6 @@
android:layout_marginTop="48dp" android:layout_marginTop="48dp"
android:paddingHorizontal="20dp" android:paddingHorizontal="20dp"
android:text="返回" android:text="返回"
android:textColor="#ff887872" android:textColor="#FFCC99"
android:textSize="26sp" /> android:textSize="26sp" />
</LinearLayout> </LinearLayout>
+8 -7
View File
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/ll_root" android:id="@+id/ll_root"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -15,8 +16,8 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="24dp" android:layout_marginStart="24dp"
android:text="02" tools:text="02"
android:textColor="#fff0c8b4" android:textColor="#FFCC99"
android:textSize="24sp" android:textSize="24sp"
android:textStyle="bold" /> android:textStyle="bold" />
@@ -27,7 +28,7 @@
android:layout_marginStart="24dp" android:layout_marginStart="24dp"
android:layout_weight="1" android:layout_weight="1"
android:text="未绑定" android:text="未绑定"
android:textColor="#fff0c8b4" android:textColor="#FFCC99"
android:textSize="18sp" android:textSize="18sp"
android:textStyle="bold" android:textStyle="bold"
android:visibility="gone" /> android:visibility="gone" />
@@ -44,11 +45,10 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="24dp" android:layout_marginStart="24dp"
android:text="马*飞(8000)"
android:textColor="#585868" android:textColor="#585868"
android:textSize="18sp" android:textSize="18sp"
android:textStyle="bold" /> android:textStyle="bold"
tools:text="马*飞(8000)" />
<TextView <TextView
android:id="@+id/tv_last_time" android:id="@+id/tv_last_time"
@@ -58,7 +58,8 @@
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:text="9小时前使用" android:text="9小时前使用"
android:textColor="#585868" android:textColor="#585868"
android:textSize="14sp" /> android:textSize="14sp"
android:visibility="gone"/>
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
@@ -12,7 +12,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="马*飞(8000)" android:text="马*飞(8000)"
android:textColor="#fff0c8b4" android:textColor="#FFCC99"
android:textSize="26sp" android:textSize="26sp"
android:textStyle="bold" /> android:textStyle="bold" />
@@ -22,7 +22,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="138****8000" android:text="138****8000"
android:textColor="#ff7c6d6a" android:textColor="#999999"
android:textSize="20sp" android:textSize="20sp"
android:textStyle="bold" /> android:textStyle="bold" />
</LinearLayout> </LinearLayout>
+5 -6
View File
@@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="40dp"
android:paddingHorizontal="24dp"> android:paddingHorizontal="24dp">
<TextView <TextView
@@ -11,11 +11,10 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
android:paddingTop="20dp" android:textColor="#FFCC99"
android:textColor="#F0C8B4"
android:textSize="18sp" android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"
tools:text="2025年6月18日 星期三" /> tools:text="2025年6月18日 星期三" />
<TextView <TextView
@@ -23,11 +22,11 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
android:paddingTop="20dp" android:textColor="#FFCC99"
android:textColor="#F0C8B4"
android:textSize="18sp" android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/tvLeftDate" app:layout_constraintTop_toTopOf="@id/tvLeftDate"
app:layout_constraintBottom_toBottomOf="@id/tvLeftDate"
tools:text="14:02:39" /> tools:text="14:02:39" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,23 @@
<?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:orientation="vertical">
<TextView
android:id="@+id/tvLogText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="20dp"
android:layout_marginVertical="10dp"
android:textColor="@color/white"
android:textSize="20sp"
tools:text="log" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginHorizontal="20dp"
android:background="#30FFFFFF" />
</LinearLayout>
Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Some files were not shown because too many files have changed in this diff Show More