增加初始化页面

This commit is contained in:
2025-09-17 17:42:38 +08:00
parent 7f56d4d1a1
commit 73456046c5
24 changed files with 549 additions and 35 deletions
+8
View File
@@ -4,6 +4,14 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-09-17T07:41:36.397341700Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="Default" identifier="serial=192.168.1.80:5555;connection=28be19dc" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState> </SelectionState>
</selectionStates> </selectionStates>
</component> </component>
-1
View File
@@ -1,4 +1,3 @@
<?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="zulu-17" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="zulu-17" project-jdk-type="JavaSDK">
+3
View File
@@ -95,4 +95,7 @@ dependencies {
// implementation(libs.adapter.coroutines) // implementation(libs.adapter.coroutines)
implementation("androidx.work:work-runtime-ktx:2.8.1") implementation("androidx.work:work-runtime-ktx:2.8.1")
implementation(libs.core)
implementation(libs.android.core)
} }
+9 -3
View File
@@ -21,11 +21,13 @@
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_launcher512" android:icon="@mipmap/ic_logo512"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher512" 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,监听开机完成广播 --> <!-- 注册BootReceiver,监听开机完成广播 -->
<receiver <receiver
@@ -48,10 +50,14 @@
<activity <activity
android:name=".activity.LoginByPwdActivity" android:name=".activity.LoginByPwdActivity"
android:exported="false" android:exported="false"
android:launchMode="singleTask"></activity> android:launchMode="singleTask"/>
<activity <activity
android:name=".activity.LoginByFaceActivity" android:name=".activity.LoginByFaceActivity"
android:exported="true" android:exported="true"
android:launchMode="singleTask"/>
<activity
android:name=".activity.DeviceInitActivity"
android:exported="true"
android:launchMode="singleTask"> android:launchMode="singleTask">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@@ -29,6 +29,17 @@ object GlobalData {
var appId = "Hkz1rBk6PZXbS8KwKr67K2eZtsz8bRoMHLg64bUdgCZj" var appId = "Hkz1rBk6PZXbS8KwKr67K2eZtsz8bRoMHLg64bUdgCZj"
var sdkKey = "AabXs3sHM8UhhE7oCGf4LVMLrdkGb1nJNksbjjTdVt7k" var sdkKey = "AabXs3sHM8UhhE7oCGf4LVMLrdkGb1nJNksbjjTdVt7k"
var activeKey = "085F-118G-Q391-53YL" var activeKey = "085F-118G-Q391-53YL"
/**
* 具体业务baseurl
*/
var appBaseUrl: String = ""
/**
* 设备id
*/
var deviceId: String = ""
var restId: String = ""
} }
/** /**
@@ -45,6 +56,7 @@ 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"
@@ -0,0 +1,72 @@
package com.sw.platecabinet.activity
import android.content.Intent
import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope
import com.sw.plate.utils.AppUtil
import com.sw.platecabinet.GlobalData
import com.sw.platecabinet.databinding.ActivityDeviceInitBinding
import com.sw.platecabinet.databinding.ItemTitleTimeBinding
import com.sw.platecabinet.utils.QRCodeUtil
import com.sw.platecabinet.viewmodel.DeviceViewModel
import kotlinx.coroutines.launch
import timber.log.Timber
import kotlin.getValue
/**
* 设备初始化界面
*/
class DeviceInitActivity : BaseActivity<ActivityDeviceInitBinding>() {
private val deviceViewModel by viewModels<DeviceViewModel>()
override fun inflateViewBinding(): ActivityDeviceInitBinding {
return ActivityDeviceInitBinding.inflate(layoutInflater)
}
override fun inflateTitleBinding(): ItemTitleTimeBinding? {
return null
}
override fun initialize() {
var deviceId = AppUtil.getUDID(this)
deviceId = "6ce7cd59-b875-38b2-b73d-88a570be3212"
GlobalData.deviceId = deviceId
val isSuccess = deviceViewModel.checkEquipmentInfo()
if (isSuccess) {
goLoginActivity()
return
}
binding.ivQrCode.setImageBitmap(
QRCodeUtil.generateQRCode(
content = GlobalData.deviceId,
size = 200
)
)
binding.btnInit.setOnClickListener {
deviceViewModel.getDeviceToken()
}
}
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() {
}
}
@@ -4,7 +4,8 @@ data class ApiResponse<T>(
val code: Int, val code: Int,
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
} }
@@ -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
@@ -12,6 +12,7 @@ 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 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 // 超时时间(秒)
@@ -33,7 +34,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,25 +1,48 @@
package com.sw.platecabinet.network.api package com.sw.platecabinet.network.api
import com.sw.platecabinet.GlobalData
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 retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.POST import retrofit2.http.POST
import retrofit2.http.Query import retrofit2.http.Query
import retrofit2.http.Url
interface ApiService { interface ApiService {
/**
* device获取token
*/
@GET("sys/getEquipmentToken")
suspend fun getDeviceToken(
@Query("qrcodeId") qrcodeId: String,
@Query("appVersion") appVersion: String = GlobalData.appVersion
): 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 * 生成token
*/ */
@GET("/shuwei-zhct/scales/generateToken") @GET//("/shuwei-zhct/scales/generateToken")
suspend fun generateToken( suspend fun generateToken(
@Url url: String = "${GlobalData.appBaseUrl}/shuwei-zhct/scales/generateToken",
@Query("deviceId") deviceId: String, @Query("deviceId") deviceId: String,
@Query("appVersion") appVersion: String, @Query("appVersion") appVersion: String,
@Query("equipmentCode") equipmentCode: String @Query("equipmentCode") equipmentCode: String
@@ -28,8 +51,9 @@ interface ApiService {
/** /**
* 获取人脸数据 * 获取人脸数据
*/ */
@GET("/shuwei-zhct/scales/getUserFaceCache") @GET//("/shuwei-zhct/scales/getUserFaceCache")
suspend fun getUserFaceCache( suspend fun getUserFaceCache(
@Url url: String = "${GlobalData.appBaseUrl}/shuwei-zhct/scales/getUserFaceCache",
@Query("appVersion") appVersion: String, @Query("appVersion") appVersion: String,
@Query("equipmentCode") equipmentCode: String @Query("equipmentCode") equipmentCode: String
): ApiResponse<List<UserFaceModel>> ): ApiResponse<List<UserFaceModel>>
@@ -37,38 +61,56 @@ interface ApiService {
/** /**
* 餐盘用户信息获取 * 餐盘用户信息获取
*/ */
@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}/shuwei-zhct/swEquipmentRelUser/equipmentBoxLogin",
@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>>
/** /**
* 餐盘用户信息绑定解绑 * 餐盘用户信息绑定解绑
*/ */
@POST("/shuwei-zhct/swEquipmentRelUser/addOrEdit") @POST//("/shuwei-zhct/swEquipmentRelUser/addOrEdit")
suspend fun bindEquipment(@Body param: BindParam): ApiResponse<EquipmentUserInfo?> suspend fun bindEquipment(
@Url url: String = "${GlobalData.appBaseUrl}/shuwei-zhct/swEquipmentRelUser/addOrEdit",
@Body param: BindParam
): ApiResponse<EquipmentUserInfo?>
/** /**
* 用户信息模糊搜索 * 用户信息模糊搜索
*/ */
@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",
@Body param: SearchParam
): ApiResponse<SearchResult>
/** /**
* 通过餐盘号获取信息 * 通过餐盘号获取信息
*/ */
@POST("/shuwei-zhct/swEquipmentRelUser/findByPlateNumber") @POST//("/shuwei-zhct/swEquipmentRelUser/findByPlateNumber")
suspend fun findByPlateNumber(@Body param: BindParam): ApiResponse<EquipmentUserInfo> suspend fun findByPlateNumber(
@Url url: String = "${GlobalData.appBaseUrl}/shuwei-zhct/swEquipmentRelUser/findByPlateNumber",
@Body param: BindParam
): ApiResponse<EquipmentUserInfo>
/** /**
* 获取人脸增量数据 * 获取人脸增量数据
* *
* @param timeDate 时间戳(毫秒级) * @param timeDate 时间戳(毫秒级)
*/ */
@GET("/shuwei-zhct/scales/getHeartbeatInterface") @GET//("/shuwei-zhct/scales/getHeartbeatInterface")
suspend fun getHeartbeatInterface(@Query("timeDate") timeDate: Long): ApiResponse<List<UserFaceModel>> suspend fun getHeartbeatInterface(
@Url url: String = "${GlobalData.appBaseUrl}/shuwei-zhct/scales/getHeartbeatInterface",
@Query("timeDate") timeDate: Long
): ApiResponse<List<UserFaceModel>>
} }
@@ -6,6 +6,7 @@ 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
@@ -17,15 +18,39 @@ import com.sw.platecabinet.network.api.ApiService
class RemoteRepository constructor( class RemoteRepository constructor(
private val apiService: ApiService private val apiService: ApiService
) : BaseRepository() { ) : BaseRepository() {
/**
* 生成token
*/
suspend fun getDeviceToken(qrcodeId: String): ApiResponse<String> {
return safeApiCall {
apiService.getDeviceToken(
qrcodeId
)
}
}
/**
* 获取设备信息
*/
suspend fun getDeviceInfo(equipmentCode: String, token: String): ApiResponse<EquipmentInfo> {
return safeApiCall {
apiService.getDeviceInfo(
equipmentCode,
token = token
)
}
}
/** /**
* 生成token * 生成token
*/ */
suspend fun generateToken(deviceId: String): ApiResponse<String> { suspend fun generateToken(deviceId: String): ApiResponse<String> {
return safeApiCall { return safeApiCall {
apiService.generateToken( apiService.generateToken(
deviceId, deviceId = deviceId,
GlobalData.appVersion, appVersion = GlobalData.appVersion,
GlobalData.globalEquipmentCode equipmentCode = GlobalData.globalEquipmentCode
) )
} }
} }
@@ -36,8 +61,8 @@ class RemoteRepository constructor(
suspend fun getUserFaceCache(): ApiResponse<List<UserFaceModel>> { suspend fun getUserFaceCache(): ApiResponse<List<UserFaceModel>> {
return safeApiCall { return safeApiCall {
apiService.getUserFaceCache( apiService.getUserFaceCache(
GlobalData.appVersion, appVersion = GlobalData.appVersion,
GlobalData.globalEquipmentCode equipmentCode = GlobalData.globalEquipmentCode
) )
} }
} }
@@ -46,35 +71,35 @@ class RemoteRepository constructor(
* 登录 * 登录
*/ */
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(param: EquipmentParam): ApiResponse<List<EquipmentUserInfo>> {
return safeApiCall { apiService.getEquipmentList(param) } return safeApiCall { apiService.getEquipmentList(param = param) }
} }
/** /**
* 餐盘用户信息绑定解绑 * 餐盘用户信息绑定解绑
*/ */
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 searchUser(param: SearchParam): ApiResponse<SearchResult> { suspend fun searchUser(param: SearchParam): ApiResponse<SearchResult> {
return safeApiCall { apiService.searchUser(param) } return safeApiCall { apiService.searchUser(param = param) }
} }
/** /**
* 通过餐盘编号获取绑定信息 * 通过餐盘编号获取绑定信息
*/ */
suspend fun findByPlateNumber(param: BindParam): ApiResponse<EquipmentUserInfo> { suspend fun findByPlateNumber(param: BindParam): ApiResponse<EquipmentUserInfo> {
return safeApiCall { apiService.findByPlateNumber(param) } return safeApiCall { apiService.findByPlateNumber(param = param) }
} }
/** /**
@@ -83,6 +108,6 @@ class RemoteRepository constructor(
* @param timeDate 时间戳(毫秒级) * @param timeDate 时间戳(毫秒级)
*/ */
suspend fun getHeartbeatInterface(timeDate: Long): ApiResponse<List<UserFaceModel>> { suspend fun getHeartbeatInterface(timeDate: Long): ApiResponse<List<UserFaceModel>> {
return safeApiCall { apiService.getHeartbeatInterface(timeDate) } return safeApiCall { apiService.getHeartbeatInterface(timeDate = timeDate) }
} }
} }
@@ -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)
}
}
}
@@ -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,12 @@ 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.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
}
}
}
@@ -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>
+18
View File
@@ -0,0 +1,18 @@
<?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:src="@drawable/img_init" android:scaleType="fitCenter"/>
</item>
</layer-list>
Binary file not shown.

After

Width:  |  Height:  |  Size: 999 KiB

@@ -0,0 +1,48 @@
<?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="visible"
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" />
</LinearLayout>
+3 -4
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="#FFCC99"
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="#FFCC99"
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>

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

+1
View File
@@ -3,5 +3,6 @@
<style name="Base.Theme.SmartPlateCabinet" parent="Theme.Material3.DayNight.NoActionBar"> <style name="Base.Theme.SmartPlateCabinet" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your dark theme here. --> <!-- Customize your dark theme here. -->
<!-- <item name="colorPrimary">@color/my_dark_primary</item> --> <!-- <item name="colorPrimary">@color/my_dark_primary</item> -->
<item name="android:windowBackground">@drawable/bg_splash</item>
</style> </style>
</resources> </resources>
+1
View File
@@ -4,6 +4,7 @@
<item name="android:windowFullscreen">true</item> <item name="android:windowFullscreen">true</item>
<item name="android:windowTranslucentStatus">false</item> <item name="android:windowTranslucentStatus">false</item>
<item name="android:windowTranslucentNavigation">false</item> <item name="android:windowTranslucentNavigation">false</item>
<item name="android:windowBackground">@drawable/bg_splash</item>
</style> </style>
<style name="Theme.SmartPlateCabinet" parent="Base.Theme.SmartPlateCabinet" /> <style name="Theme.SmartPlateCabinet" parent="Base.Theme.SmartPlateCabinet" />
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<!-- <domain-config cleartextTrafficPermitted="true">-->
<!-- <domain includeSubdomains="true">vip.shuziweidao.com</domain>-->
<!-- <domain includeSubdomains="true">192.168.1.207</domain>-->
<!-- <domain includeSubdomains="true">device.shuziweidao.com</domain>-->
<!-- </domain-config>-->
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
+5
View File
@@ -23,7 +23,12 @@ timber = "5.0.1"
gson = "2.13.1" gson = "2.13.1"
hiltAndroid = "2.56.2" hiltAndroid = "2.56.2"
core = "3.4.1"
androidCore = "3.3.0"
[libraries] [libraries]
android-core = { module = "com.google.zxing:android-core", version.ref = "androidCore" }
core = { module = "com.google.zxing:core", version.ref = "core" }
androidx-activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activityKtx" } androidx-activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activityKtx" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "fragmentKtx" } androidx-fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "fragmentKtx" }