diff --git a/app/src/main/java/com/sw/platecabinet/GlobalData.kt b/app/src/main/java/com/sw/platecabinet/GlobalData.kt index 1212717..91e2c13 100644 --- a/app/src/main/java/com/sw/platecabinet/GlobalData.kt +++ b/app/src/main/java/com/sw/platecabinet/GlobalData.kt @@ -21,6 +21,7 @@ object GlobalData { * 竖排数量 */ var arrayVertical: Int = 11 + /** * 排列方式 0 垂直 1 水平 */ @@ -38,9 +39,9 @@ object GlobalData { /** * 具体业务 BaseUrl */ - const val LOCAL_BASE_URL: String = "http://192.168.10.101:24801" - const val TEST_BASE_URL: String = "https://dev.yixiong-tech.com:8081" - const val PROD_BASE_URL: String = "https://platform-api.uat.shuziweidao.com" + const val LOCAL_BASE_URL: String = "http://192.168.10.101:24801"//开发环境 + const val TEST_BASE_URL: String = "https://dev.yixiong-tech.com:8081"//测试环境 + const val PROD_BASE_URL: String = "https://platform-api.uat.shuziweidao.com"//生产环境 /** * 设备id @@ -69,4 +70,23 @@ object GlobalKey { const val KEY_USER_INFO = "userInfoKey" const val PARAM_EQUIPMENT_INFO = "equipmentUserInfo" const val KEY_BASE_URL = "baseUrlKey" -} \ No newline at end of file +} + +/** + * 业务环境配置项 + * @param name 环境名称(用于弹窗展示) + * @param url 环境 BaseUrl(不带尾斜杠) + */ +data class Environment( + val name: String, + val url: String +) + +/** + * 预设环境列表(本地 / 测试 / 生产) + */ +val ENVIRONMENTS: List = listOf( + Environment("本地", GlobalData.LOCAL_BASE_URL), + Environment("测试", GlobalData.TEST_BASE_URL), + Environment("生产", GlobalData.PROD_BASE_URL) +) \ No newline at end of file diff --git a/app/src/main/java/com/sw/platecabinet/MyApp.kt b/app/src/main/java/com/sw/platecabinet/MyApp.kt index bf7bb09..525ae06 100644 --- a/app/src/main/java/com/sw/platecabinet/MyApp.kt +++ b/app/src/main/java/com/sw/platecabinet/MyApp.kt @@ -4,6 +4,7 @@ import android.util.Log import com.sw.plate.App import com.sw.plate.utils.AppUtil import com.sw.platecabinet.utils.CrashHandler +import com.sw.platecabinet.utils.SpTool import timber.log.Timber class MyApp : App() { @@ -33,6 +34,10 @@ class MyApp : App() { GlobalData.appVersion = AppUtil.getAppVersionCode(this).toString() GlobalData.globalEquipmentCode = "202501171634" + // 从持久化读取业务 BaseUrl,首次无值兜底测试环境 + GlobalData.appBaseUrl = SpTool.getBaseUrl().ifBlank { GlobalData.TEST_BASE_URL } + Timber.d("initGlobalData appBaseUrl=${GlobalData.appBaseUrl}") + // GlobalData.appBaseUrl = "http://192.168.1.201:14801" } } \ No newline at end of file diff --git a/app/src/main/java/com/sw/platecabinet/activity/DeviceInitActivity.kt b/app/src/main/java/com/sw/platecabinet/activity/DeviceInitActivity.kt index a0769d2..ca60dc5 100644 --- a/app/src/main/java/com/sw/platecabinet/activity/DeviceInitActivity.kt +++ b/app/src/main/java/com/sw/platecabinet/activity/DeviceInitActivity.kt @@ -1,17 +1,34 @@ package com.sw.platecabinet.activity import android.content.Intent +import android.os.Handler +import android.os.Looper +import android.view.MotionEvent +import android.view.WindowManager +import androidx.lifecycle.lifecycleScope import com.sw.plate.utils.ToastUtils +import com.sw.plate.utils.arcface.facedb.FaceDatabase +import com.sw.platecabinet.Environment import com.sw.platecabinet.GlobalData +import com.sw.platecabinet.dialog.EnvironmentSelectDialog import com.sw.platecabinet.member.databinding.ActivityDeviceInitBinding import com.sw.platecabinet.member.databinding.ItemTitleTimeBinding import com.sw.platecabinet.utils.SpTool +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import timber.log.Timber /** * 设备初始化界面 */ class DeviceInitActivity : BaseActivity() { + // 连点切换环境的计数与复位 + private var tapCount = 0 + private val tapHandler = Handler(Looper.getMainLooper()) + private val resetTapRunnable = Runnable { tapCount = 0 } + override fun inflateViewBinding(): ActivityDeviceInitBinding { return ActivityDeviceInitBinding.inflate(layoutInflater) } @@ -21,7 +38,8 @@ class DeviceInitActivity : BaseActivity() { } override fun initialize() { - showWaitingDialog("加载中……") + // 等待框设为不拦截触摸,确保连点手势可达 + showWaitingDialog("加载中……")?.window?.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) // 首次启动先全量拉取人脸缓存,后续走增量同步 if (SpTool.getFirstGetFace()) { netViewModelV2.getUserFaceCache { status, msg -> @@ -33,6 +51,50 @@ class DeviceInitActivity : BaseActivity() { } } + /** + * 连续点击 3 次(500ms 内)触发环境切换弹窗 + */ + override fun dispatchTouchEvent(ev: MotionEvent): Boolean { + if (ev.action == MotionEvent.ACTION_DOWN) { + tapCount++ + tapHandler.removeCallbacks(resetTapRunnable) + tapHandler.postDelayed(resetTapRunnable, 500) + if (tapCount >= 3) { + tapCount = 0 + tapHandler.removeCallbacks(resetTapRunnable) + showEnvironmentDialog() + return true + } + } + return super.dispatchTouchEvent(ev) + } + + /** + * 弹出环境选择弹窗 + */ + private fun showEnvironmentDialog() { + EnvironmentSelectDialog(this) { env -> + switchEnvironment(env) + }.show() + } + + /** + * 切换环境:持久化 + 清空本地人脸库 + 重置增量同步状态 + */ + private fun switchEnvironment(env: Environment) { + SpTool.setBaseUrl(env.url) + lifecycleScope.launch(Dispatchers.IO) { + val faceDao = FaceDatabase.getInstance(this@DeviceInitActivity).faceDao() + faceDao.deleteAll() + faceDao.resetId() + SpTool.setLastFaceTimestamp(0L) + SpTool.setFirstGetFace(true) + withContext(Dispatchers.Main) { + ToastUtils.showToast("已切换到${env.name},重启后生效") + } + } + } + /** * 获取设备配置(V2),并初始化虹软 SDK 激活参数 */ @@ -41,8 +103,8 @@ class DeviceInitActivity : BaseActivity() { runOnUiThread { if (deviceConfig == null) { hideWaitingDialog() - ToastUtils.showToast("获取设备配置数据失败") - goLoginActivity() + ToastUtils.showToast("获取设备配置失败,请连点屏幕3次切换环境") +// goLoginActivity() return@runOnUiThread } GlobalData.appId = deviceConfig.arcsoftAppId ?: "" @@ -56,8 +118,9 @@ class DeviceInitActivity : BaseActivity() { }, onFailure = { errMsg -> runOnUiThread { hideWaitingDialog() - ToastUtils.showToast(errMsg ?: "接口异常") - goLoginActivity() + Timber.e("getDeviceConfig onFailure: $errMsg") + ToastUtils.showToast("服务连接失败,请连点屏幕3次切换环境") +// goLoginActivity() } }) } diff --git a/app/src/main/java/com/sw/platecabinet/dialog/EnvironmentSelectDialog.kt b/app/src/main/java/com/sw/platecabinet/dialog/EnvironmentSelectDialog.kt new file mode 100644 index 0000000..11cffcd --- /dev/null +++ b/app/src/main/java/com/sw/platecabinet/dialog/EnvironmentSelectDialog.kt @@ -0,0 +1,81 @@ +package com.sw.platecabinet.dialog + +import android.app.Dialog +import android.content.res.ColorStateList +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.os.Bundle +import android.view.Gravity +import android.view.View +import android.view.Window +import android.widget.LinearLayout +import android.widget.RadioButton +import androidx.fragment.app.FragmentActivity +import com.sw.platecabinet.ENVIRONMENTS +import com.sw.platecabinet.Environment +import com.sw.platecabinet.GlobalData +import com.sw.platecabinet.ext.dp +import com.sw.platecabinet.member.databinding.DialogEnvironmentSelectBinding + +/** + * 业务环境选择弹窗 + */ +class EnvironmentSelectDialog( + private val activity: FragmentActivity, + private val onConfirm: (Environment) -> Unit +) : Dialog(activity) { + + private lateinit var binding: DialogEnvironmentSelectBinding + private var selected: Environment? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + requestWindowFeature(Window.FEATURE_NO_TITLE) + binding = DialogEnvironmentSelectBinding.inflate(layoutInflater) + setContentView(binding.root) + window?.run { + attributes = attributes.apply { width = 480.dp } + setBackgroundDrawable(ColorDrawable()) + setCancelable(true) + } + renderEnvironments() + addListener() + } + + /** + * 渲染环境选项,高亮当前环境 + */ + private fun renderEnvironments() { + val current = GlobalData.appBaseUrl + ENVIRONMENTS.forEach { env -> + val rb = RadioButton(activity).apply { + id = View.generateViewId() + text = env.name + gravity = Gravity.CENTER_VERTICAL + textSize = 18f + setTextColor(Color.WHITE) + buttonTintList = ColorStateList.valueOf(Color.WHITE) + isChecked = env.url == current + setOnClickListener { selected = env } + } + binding.rgEnvironments.addView( + rb, + LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ).apply { topMargin = 8.dp } + ) + } + selected = ENVIRONMENTS.firstOrNull { it.url == current } + } + + /** + * 确认按钮:回调所选环境并关闭弹窗 + */ + private fun addListener() { + binding.tvConfirm.setOnClickListener { + selected?.let(onConfirm) + dismiss() + } + } +} diff --git a/app/src/main/java/com/sw/platecabinet/network/ApiClient.kt b/app/src/main/java/com/sw/platecabinet/network/ApiClient.kt index 4d1beba..62cb9c7 100644 --- a/app/src/main/java/com/sw/platecabinet/network/ApiClient.kt +++ b/app/src/main/java/com/sw/platecabinet/network/ApiClient.kt @@ -29,7 +29,7 @@ object ApiClient { private val retrofit by lazy { Retrofit.Builder() - .baseUrl(GlobalData.appBaseUrl) + .baseUrl(resolveBaseUrl()) .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .build() @@ -42,4 +42,13 @@ object ApiClient { val apiServiceV2: ApiServiceV2 by lazy { retrofit.create(ApiServiceV2::class.java) } + + /** + * 规范化 Retrofit 要求的 baseUrl:非空且以 / 结尾。 + * 所有接口走 @Url 全路径,此值不参与实际拼接,只需合法。 + */ + private fun resolveBaseUrl(): String { + val url = GlobalData.appBaseUrl.ifBlank { "http://localhost/" } + return if (url.endsWith("/")) url else "$url/" + } } diff --git a/app/src/main/java/com/sw/platecabinet/repository/RemoteRepositoryV2.kt b/app/src/main/java/com/sw/platecabinet/repository/RemoteRepositoryV2.kt index facd288..e36f97a 100644 --- a/app/src/main/java/com/sw/platecabinet/repository/RemoteRepositoryV2.kt +++ b/app/src/main/java/com/sw/platecabinet/repository/RemoteRepositoryV2.kt @@ -22,7 +22,7 @@ class RemoteRepositoryV2 constructor( */ suspend fun getUserFaceCache( pageNum: Int, - pageSize: Int = 100, + pageSize: Int = 500, ): ApiResponse> { return safeApiCall { apiService.getUserFaceCache( diff --git a/app/src/main/res/layout/activity_login_face.xml b/app/src/main/res/layout/activity_login_face.xml index f38396a..554db33 100644 --- a/app/src/main/res/layout/activity_login_face.xml +++ b/app/src/main/res/layout/activity_login_face.xml @@ -59,9 +59,9 @@ android:layout_gravity="center_horizontal" android:layout_marginTop="129dp" android:text="请正视屏幕" - android:textStyle="bold" android:textColor="#FFCC99" - android:textSize="36sp" /> + android:textSize="36sp" + android:textStyle="bold" /> + android:paddingVertical="18dp" + android:visibility="gone"> + android:textSize="36sp" + tools:text="30" />