浏览代码

refactor(国际化):
- 启动页默认设置语言为英文
- 初始化欢迎页增加语言切换功能
- 登录页语言切换弹窗路径调整

feat(导航栏):
- `CustomNavBar`新增`clearSelected`方法用于清空选中状态
- `CustomNavBar`支持通过设置`selectedItemId = View.NO_ID`来取消所有选中项

fix(硬件管理):
- 修复钥匙和锁信息保存时编码生成规则错误的问题

周文健 2 月之前
父节点
当前提交
d037603707

+ 2 - 2
app/src/main/java/com/grkj/iscs/features/login/dialog/ChangeLangDialog.kt → app/src/main/java/com/grkj/iscs/features/common/dialog/ChangeLangDialog.kt

@@ -1,4 +1,4 @@
-package com.grkj.iscs.features.login.dialog
+package com.grkj.iscs.features.common.dialog
 
 import android.view.View
 import com.drake.brv.utils.linear
@@ -53,4 +53,4 @@ class ChangeLangDialog(
             CustomDialog.show(ChangeLangDialog(canChangeLang, targetRegion, onConfirm))
         }
     }
-}
+}

+ 24 - 1
app/src/main/java/com/grkj/iscs/features/init/fragment/InitWelcomeFragment.kt

@@ -4,13 +4,19 @@ import androidx.fragment.app.viewModels
 import com.grkj.data.data.MMKVConstants
 import com.grkj.iscs.R
 import com.grkj.iscs.databinding.FragmentInitWelcomeBinding
+import com.grkj.iscs.features.common.dialog.ChangeLangDialog
 import com.grkj.iscs.features.init.viewmodel.InitDeviceRegistrationKeyAndLockViewModel
+import com.grkj.shared.utils.i18n.I18nManager
+import com.grkj.shared.utils.i18n.LanguageEntry
+import com.grkj.shared.utils.i18n.LanguageRegistry
+import com.grkj.shared.utils.i18n.LanguageStore
 import com.grkj.ui_base.base.BaseFragment
 import com.grkj.ui_base.utils.CommonUtils
 import com.kongzue.dialogx.dialogs.PopTip
 import com.sik.sikcore.extension.getMMKVData
 import com.sik.sikcore.extension.setDebouncedClickListener
 import dagger.hilt.android.AndroidEntryPoint
+import java.util.Locale
 
 /**
  * 初始化欢迎界面
@@ -36,7 +42,7 @@ class InitWelcomeFragment : BaseFragment<FragmentInitWelcomeBinding>() {
         if (MMKVConstants.KEY_PORT_CONFIG.getMMKVData("").isEmpty()) {
             binding.startBtn.text = CommonUtils.getStr("detect_port")
         } else {
-            viewModel.createLockCabinetData().observe(this){
+            viewModel.createLockCabinetData().observe(this) {
 
             }
             binding.startBtn.text = CommonUtils.getStr("start")
@@ -44,5 +50,22 @@ class InitWelcomeFragment : BaseFragment<FragmentInitWelcomeBinding>() {
                 navController.navigate(R.id.action_initWelcomeFragment_to_initSetAdminAccountFragment)
             }
         }
+        binding.chipLang.text = I18nManager.locale.value.toLanguageTag()
+        binding.chipLang.setOnClickListener {
+            val targetRegion = "US" // 自己决定用什么;也可以是 "CN" / 配置项 / 服务器下发
+            val entries = LanguageRegistry.entriesFromSources(targetRegion)
+
+            ChangeLangDialog.show(
+                canChangeLang = entries,
+                targetRegion = targetRegion
+            ) { selected: LanguageEntry ->
+                // 切换并持久化(MMKV),I18nManager 会热更新
+                LanguageStore.setExplicit(Locale.forLanguageTag(selected.tag))
+
+                // 芯片文本友好化(可选)
+                binding.chipLang.text = I18nManager.locale.value.toLanguageTag()
+                requireActivity().recreate()
+            }
+        }
     }
 }

+ 1 - 1
app/src/main/java/com/grkj/iscs/features/login/activity/LoginActivity.kt

@@ -24,7 +24,7 @@ import com.grkj.iscs.R
 import com.grkj.iscs.databinding.ActivityLoginBinding
 import com.grkj.iscs.databinding.ItemLoginMethodBinding
 import com.grkj.iscs.features.init.activity.SetRemoteServerActivity
-import com.grkj.iscs.features.login.dialog.ChangeLangDialog
+import com.grkj.iscs.features.common.dialog.ChangeLangDialog
 import com.grkj.iscs.features.login.dialog.LoginDialog
 import com.grkj.iscs.features.login.viewmodel.LoginViewModel
 import com.grkj.iscs.features.main.activity.MainActivity

+ 1 - 0
app/src/main/java/com/grkj/iscs/features/main/activity/MainActivity.kt

@@ -143,6 +143,7 @@ class MainActivity() : BaseActivity<ActivityMainBinding>() {
                 binding.navBar.selectedItemId = firstId
                 MainDomainData.fromQuickEntry = false
             }
+            binding.navBar.clearSelected()
             replaceNavGraph(R.navigation.nav_user_info)
         }
         navController.addOnDestinationChangedListener { _, destination, _ ->

+ 13 - 1
app/src/main/java/com/grkj/iscs/features/splash/activity/SplashActivity.kt

@@ -16,6 +16,8 @@ import com.grkj.iscs.features.login.activity.LoginActivity
 import com.grkj.iscs.features.splash.viewmodel.SplashViewModel
 import com.grkj.shared.config.Constants
 import com.grkj.shared.utils.i18n.I18nManager
+import com.grkj.shared.utils.i18n.LanguageRegistry
+import com.grkj.shared.utils.i18n.LanguageStore
 import com.grkj.ui_base.base.BaseActivity
 import com.grkj.ui_base.utils.event.StartModbusEvent
 import com.kongzue.dialogx.DialogX
@@ -28,6 +30,7 @@ import dagger.hilt.android.AndroidEntryPoint
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
+import java.util.Locale
 
 @AndroidEntryPoint
 class SplashActivity : BaseActivity<ActivitySplashBinding>() {
@@ -72,7 +75,7 @@ class SplashActivity : BaseActivity<ActivitySplashBinding>() {
             BackupScheduler.applySaved(this@SplashActivity)
             DbReadyGate.await()
             StartModbusEvent.sendStartModbusEvent()
-            viewModel.checkPresetData().observe(this@SplashActivity){
+            viewModel.checkPresetData().observe(this@SplashActivity) {
                 viewModel.checkSysMenuAndRole().observe(this@SplashActivity) {
                     val isAppInit = MMKVConstants.APP_INIT.getMMKVData(false)
                     if (isAppInit) {
@@ -88,6 +91,15 @@ class SplashActivity : BaseActivity<ActivitySplashBinding>() {
     }
 
     fun initConfig() {
+        if (LanguageStore.currentMode() == LanguageStore.Mode.FOLLOW_SYSTEM) {
+            val targetRegion = "US" // 自己决定用什么;也可以是 "CN" / 配置项 / 服务器下发
+            val entries = LanguageRegistry.entriesFromSources(targetRegion)
+            LanguageStore.setExplicit(
+                Locale.forLanguageTag(
+                    entries.find { it.region == targetRegion }?.tag ?: entries[0].tag
+                )
+            )
+        }
         val dialogXTextInfo = TextInfo()
         val dialogXTitleTextInfo = TextInfo().apply {
             setBold(true)

+ 23 - 9
app/src/main/res/layout/fragment_init_welcome.xml

@@ -13,10 +13,10 @@
             android:layout_gravity="center_horizontal"
             android:layout_marginLeft="@dimen/init_margin_space"
             android:layout_marginTop="@dimen/init_margin_space"
-            app:i18nKey='@{"welcome_tip"}'
             android:textColor="?attr/colorTextPrimary"
             android:textSize="@dimen/iscs_text_h2"
-            android:textStyle="bold" />
+            android:textStyle="bold"
+            app:i18nKey='@{"welcome_tip"}' />
 
         <TextView
             android:id="@+id/title_cn"
@@ -26,10 +26,10 @@
             android:layout_alignLeft="@+id/welcome_tip"
             android:layout_gravity="center_horizontal"
             android:layout_marginTop="@dimen/iscs_space_5"
-            app:i18nKey='@{"loto"}'
             android:textColor="?attr/colorTextPrimary"
             android:textSize="@dimen/iscs_text_h3"
-            android:textStyle="bold" />
+            android:textStyle="bold"
+            app:i18nKey='@{"loto"}' />
 
         <TextView
             android:id="@+id/title_en"
@@ -39,11 +39,25 @@
             android:layout_alignLeft="@+id/title_cn"
             android:layout_gravity="center_horizontal"
             android:layout_marginTop="@dimen/login_sub_title_margin_top"
-            app:i18nKey='@{"loto_en"}'
             android:textColor="?attr/colorTextPrimary"
             android:textSize="@dimen/iscs_text_h4"
-            android:textStyle="bold" />
+            android:textStyle="bold"
+            app:i18nKey='@{"loto_en"}' />
 
+        <TextView
+            android:id="@+id/chipLang"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignTop="@+id/welcome_tip"
+            android:layout_alignParentRight="true"
+            android:layout_gravity="right"
+            android:layout_marginRight="@dimen/init_margin_space"
+            android:background="@drawable/common_btn_white_board"
+            android:paddingHorizontal="@dimen/iscs_space_2"
+            android:paddingVertical="@dimen/iscs_space_1"
+            android:text="EN"
+            android:textAllCaps="true"
+            android:textSize="@dimen/iscs_text_md" />
 
         <TextView
             android:id="@+id/start_tip"
@@ -52,8 +66,8 @@
             android:layout_above="@+id/start_btn"
             android:layout_centerHorizontal="true"
             android:layout_marginBottom="@dimen/iscs_space_4"
-            android:textSize="@dimen/iscs_text_md"
             android:textColor="?attr/colorTextPrimary"
+            android:textSize="@dimen/iscs_text_md"
             app:i18nKey='@{"start_tip"}' />
 
         <TextView
@@ -65,8 +79,8 @@
             android:layout_marginBottom="50dp"
             android:background="@drawable/common_btn_secondary"
             android:paddingHorizontal="@dimen/iscs_space_4"
-            app:i18nKey='@{"start"}'
             android:textColor="?attr/colorTextPrimary"
-            android:textSize="@dimen/iscs_text_md" />
+            android:textSize="@dimen/iscs_text_md"
+            app:i18nKey='@{"start"}' />
     </RelativeLayout>
 </layout>

+ 1 - 2
data/src/main/java/com/grkj/data/logic/impl/standard/HardwareLogic.kt

@@ -309,7 +309,6 @@ class HardwareLogic @Inject constructor(
     override fun saveKeyInfo(keyNfc: String, keyMacAddress: String) {
         val isKey = IsKey()
         val defaultKeyCodeSize = hardwareDao.getLastKeyId()
-        hardwareDao.getAllKeyData().size
         isKey.keyCode = "KEY_${defaultKeyCodeSize + 1}"
         isKey.keyNfc = keyNfc
         isKey.macAddress = keyMacAddress
@@ -324,7 +323,7 @@ class HardwareLogic @Inject constructor(
 
     override fun saveLockInfo(lockNfc: String) {
         val isLock = IsLock()
-        var defaultLockCodeSize = hardwareDao.getLastLockId()
+        val defaultLockCodeSize = hardwareDao.getLastLockId()
         isLock.lockCode = "LOCK_${defaultLockCodeSize + 1}"
         isLock.lockNfc = lockNfc
         isLock.exStatus =

+ 55 - 37
ui-base/src/main/java/com/grkj/ui_base/widget/CustomNavBar.kt

@@ -42,7 +42,9 @@ class CustomNavBar @JvmOverloads constructor(
     context: Context, attrs: AttributeSet? = null, defStyle: Int = 0
 ) : LinearLayout(context, attrs, defStyle) {
     private val logger: Logger = LoggerFactory.getLogger(this::class.java)
-
+    private companion object {
+        const val NO_SELECTION = -1
+    }
     @SuppressLint("RestrictedApi")
     private val menuBuilder = MenuBuilder(context)
     val menu: Menu get() = menuBuilder
@@ -197,51 +199,48 @@ class CustomNavBar @JvmOverloads constructor(
                 container.getChildAt(c).refreshDrawableState() // ★ 子也刷一下
             }
         }
-        if (length > 0) selectIndex(selectedIdx.coerceIn(0, length - 1))
+        if (length > 0) {
+            if (selectedIdx in 0 until length) {
+                selectIndex(selectedIdx)
+            } else {
+                selectIndex(NO_SELECTION) // 全部不选
+            }
+        }
     }
 
     private fun selectIndex(idx: Int) {
-        if (idx == selectedIdx) {
-            invalidate(); return
-        }
         selectedIdx = idx
+
         for (i in 0 until childCount) {
-            val v = getChildAt(i)
-            v.isSelected = (i == selectedIdx)
-            v.refreshDrawableState()                   // ★
-            for (c in 0 until (v as ViewGroup).childCount) {
-                val childView = v.getChildAt(c)
-                when (childView) {
+            val container = getChildAt(i) as ViewGroup
+            val selected = (selectedIdx >= 0 && i == selectedIdx)
+
+            container.isSelected = selected
+            container.refreshDrawableState()
+
+            for (c in 0 until container.childCount) {
+                val child = container.getChildAt(c)
+                when (child) {
                     is ImageView -> {
-                        if (v.isSelected) {
-                            childView.layoutParams = childView.layoutParams.apply {
-                                width = iconSelectedSize.toInt()
-                                height = iconSelectedSize.toInt()
-                            }
-                        } else {
-                            childView.layoutParams = childView.layoutParams.apply {
-                                width = iconSize.toInt()
-                                height = iconSize.toInt()
-                            }
+                        child.layoutParams = child.layoutParams.apply {
+                            val s = if (selected) iconSelectedSize else iconSize
+                            width = s; height = s
                         }
                     }
-
                     is TextView -> {
-                        if (v.isSelected) {
-                            childView.setTextSize(
-                                TypedValue.COMPLEX_UNIT_PX, textSelectedSizePx.toFloat()
-                            )
-                        } else {
-                            childView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizePx.toFloat())
-                        }
+                        child.setTextSize(
+                            TypedValue.COMPLEX_UNIT_PX,
+                            (if (selected) textSelectedSizePx else textSizePx).toFloat()
+                        )
                     }
                 }
-                childView.refreshDrawableState() // ★
+                child.refreshDrawableState()
             }
         }
         invalidate()
     }
 
+
     @SuppressLint("RestrictedApi")
     override fun onDraw(canvas: Canvas) {
         // 菜单发生变化(比如运行时 add/remove)时自动重建
@@ -256,17 +255,29 @@ class CustomNavBar @JvmOverloads constructor(
     }
 
     var selectedItemId: Int
-        @SuppressLint("RestrictedApi") get() = menuBuilder.getItemOrNull(selectedIdx)?.itemId
-            ?: View.NO_ID
-        @SuppressLint("RestrictedApi") set(id) {
-            if (isEmpty() && menuBuilder.size() > 0) buildItems()  // ★
-            val idx =
-                (0 until menuBuilder.size()).firstOrNull { menuBuilder.getItem(it).itemId == id }
-                    ?: return
+        @SuppressLint("RestrictedApi")
+        get() = if (selectedIdx in 0 until menuBuilder.size())
+            menuBuilder.getItem(selectedIdx).itemId
+        else View.NO_ID
+        @SuppressLint("RestrictedApi")
+        set(id) {
+            if (isEmpty() && menuBuilder.size() > 0) buildItems()
+
+            // ★ 支持“清空选中”
+            if (id == View.NO_ID) {
+                selectIndex(NO_SELECTION)
+                return
+            }
+
+            val idx = (0 until menuBuilder.size())
+                .firstOrNull { menuBuilder.getItem(it).itemId == id }
+                ?: return
+
             selectIndex(idx)
             onItemSelected?.invoke(menuBuilder.getItem(idx))
         }
 
+
     // —— 工具 —— //
     private fun dp(dp: Float): Int = AutoSizeUtils.dp2px(context, dp)
     private fun sp(sp: Float): Int = AutoSizeUtils.sp2px(context, sp)
@@ -286,4 +297,11 @@ class CustomNavBar @JvmOverloads constructor(
     @SuppressLint("RestrictedApi")
     private fun MenuBuilder.getItemOrNull(i: Int): MenuItem? =
         if (i in 0 until size()) getItem(i) else null
+
+    /**
+     * 取消选择
+     */
+    fun clearSelected() {
+        selectIndex(NO_SELECTION)  // 直接调 index 逻辑,别再走 selectedItemId
+    }
 }