Преглед на файлове

refactor(更新) :
- 卡片录入和rfid标签录入完成

周文健 преди 5 месеца
родител
ревизия
3659cc3bf9
променени са 39 файла, в които са добавени 1372 реда и са изтрити 16 реда
  1. 5 0
      app/build.gradle
  2. 8 0
      app/src/main/java/com/grkj/iscs/enums/DeviceInputTypeEnum.kt
  3. 18 0
      app/src/main/java/com/grkj/iscs/modbus/ModBusController.kt
  4. 4 5
      app/src/main/java/com/grkj/iscs/model/ISCSDomainData.kt
  5. 20 0
      app/src/main/java/com/grkj/iscs/model/UrlConsts.kt
  6. 8 0
      app/src/main/java/com/grkj/iscs/model/bo/DockStatusBO.kt
  7. 83 2
      app/src/main/java/com/grkj/iscs/util/NetApi.kt
  8. 11 0
      app/src/main/java/com/grkj/iscs/view/activity/HomeActivity.kt
  9. 83 0
      app/src/main/java/com/grkj/iscs/view/base/BaseNavFragment.kt
  10. 12 0
      app/src/main/java/com/grkj/iscs/view/base/BaseViewModel.kt
  11. 21 0
      app/src/main/java/com/grkj/iscs/view/fragment/DeviceInputHomeFragment.kt
  12. 293 0
      app/src/main/java/com/grkj/iscs/view/fragment/DeviceInputKeyAndLockFragment.kt
  13. 81 0
      app/src/main/java/com/grkj/iscs/view/fragment/DeviceInputScanFragment.kt
  14. 31 0
      app/src/main/java/com/grkj/iscs/view/fragment/DeviceInputTypeSelectFragment.kt
  15. 1 8
      app/src/main/java/com/grkj/iscs/view/fragment/DeviceStatusFragment.kt
  16. 1 0
      app/src/main/java/com/grkj/iscs/view/fragment/JobExecutionFragment.kt
  17. 9 0
      app/src/main/java/com/grkj/iscs/view/iview/IDeviceInputHomeView.kt
  18. 7 0
      app/src/main/java/com/grkj/iscs/view/presenter/DeviceInputHomePresenter.kt
  19. 141 0
      app/src/main/java/com/grkj/iscs/view/presenter/DeviceInputKeyAndLockViewModel.kt
  20. 1 1
      app/src/main/java/com/grkj/iscs/view/presenter/DeviceStatusPresenter.kt
  21. 53 0
      app/src/main/java/com/grkj/iscs/view/viewmodel/DeviceInputViewModel.kt
  22. 8 0
      app/src/main/res/drawable/bg_device_input_type_select.xml
  23. 7 0
      app/src/main/res/drawable/divider_horizontal.xml
  24. 13 0
      app/src/main/res/layout/fragment_device_input_home.xml
  25. 50 0
      app/src/main/res/layout/fragment_device_input_key_and_lock.xml
  26. 27 0
      app/src/main/res/layout/fragment_device_input_scan.xml
  27. 85 0
      app/src/main/res/layout/fragment_device_input_type_select.xml
  28. 154 0
      app/src/main/res/layout/item_rv_key_dock_device_input.xml
  29. 52 0
      app/src/main/res/layout/item_rv_lock_dock_child_device_input.xml
  30. BIN
      app/src/main/res/mipmap-xhdpi/icon_card_input.png
  31. BIN
      app/src/main/res/mipmap-xhdpi/icon_key_lock_scan.png
  32. BIN
      app/src/main/res/mipmap-xhdpi/icon_rfid_input.png
  33. BIN
      app/src/main/res/mipmap-xhdpi/menu_icon_device_input.png
  34. 26 0
      app/src/main/res/navigation/nav_device_input.xml
  35. 17 0
      app/src/main/res/values-en/strings.xml
  36. 17 0
      app/src/main/res/values-zh/strings.xml
  37. 1 0
      app/src/main/res/values/colors.xml
  38. 17 0
      app/src/main/res/values/strings.xml
  39. 7 0
      gradle/libs.versions.toml

+ 5 - 0
app/build.gradle

@@ -95,6 +95,11 @@ dependencies {
     implementation(libs.room.testing)
     ksp(libs.room.compiler)
 
+    implementation(libs.android.navigation.fragment)
+    implementation(libs.android.navigation.ui)
+    implementation(libs.android.navigation.dynamic.features.fragment)
+    implementation(libs.kotlinx.serialization.json)
+
     implementation fileTree(dir: "libs", include: ["*.jar", "*.aar"])
 
     // RV通用Adapter  https://github.com/hongyangAndroid/base-adapter

+ 8 - 0
app/src/main/java/com/grkj/iscs/enums/DeviceInputTypeEnum.kt

@@ -0,0 +1,8 @@
+package com.grkj.iscs.enums
+
+/**
+ * 设备录入类型
+ */
+enum class DeviceInputTypeEnum {
+    CARD, RFID, NONE;
+}

+ 18 - 0
app/src/main/java/com/grkj/iscs/modbus/ModBusController.kt

@@ -786,6 +786,15 @@ object ModBusController {
             ?.find { it.isLeft == isLeft && it.isExist } != null
     }
 
+    /**
+     * 钥匙是否是新设备
+     */
+    fun isKeyNewHardware(dockAddr: Byte?, isLeft: Boolean): Boolean {
+        dockAddr ?: return false
+        return dockList.find { it.addr == dockAddr }?.getKeyList()
+            ?.find { it.isLeft == isLeft && it.newHardware } != null
+    }
+
     /**
      * 获取钥匙锁仓的锁定状态
      */
@@ -813,6 +822,15 @@ object ModBusController {
             ?.find { it.idx == lockIdx && it.isExist } != null
     }
 
+    /**
+     * 获取挂锁是否是新设备
+     */
+    fun isLockNewHardware(dockAddr: Byte?, lockIdx: Int): Boolean {
+        dockAddr ?: return false
+        return dockList.find { it.addr == dockAddr }?.getLockList()
+            ?.find { it.idx == lockIdx && it.newHardware } != null
+    }
+
     /**
      * 获取钥匙状态
      *

+ 4 - 5
app/src/main/java/com/grkj/iscs/model/ISCSDomainData.kt

@@ -1,8 +1,6 @@
 package com.grkj.iscs.model
 
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.liveData
+import com.grkj.iscs.enums.DeviceInputTypeEnum
 
 /**
  * 业务数据
@@ -11,6 +9,7 @@ object ISCSDomainData {
     /**
      * 设备是否初始化完成
      */
-    val isDeviceInit: MutableLiveData<Boolean> = MutableLiveData(false)
-    val deviceInitProgress: MutableLiveData<Int> = MutableLiveData(0)
+    var deviceInputType: DeviceInputTypeEnum = DeviceInputTypeEnum.NONE
+
+
 }

+ 20 - 0
app/src/main/java/com/grkj/iscs/model/UrlConsts.kt

@@ -332,4 +332,24 @@ object UrlConsts {
      * 获取锁定站列表
      */
     const val GET_IS_LOTO_STATION_PAGE = "/iscs/station/getIsLotoStationPage"
+
+    /**
+     * 机柜录入工卡(只要cardNfc参数)
+     */
+    const val INSERT_IS_JOB_CARD_BY_CABINET = "/iscs/card/insertIsJobCardByCabinet"
+
+    /**
+     * 机柜录入钥匙(需要参数keyNfc,macAddress)
+     */
+    const val INSERT_IS_KEY_BY_CABINET = "/iscs/key/insertIsKeyByCabinet"
+
+    /**
+     * 机柜录入挂锁(需要参数lockNfc)
+     */
+    const val INSERT_IS_LOCK_BY_CABINET = "/iscs/lock/insertIsLockByCabinet"
+
+    /**
+     * 机柜录入rfid标签(需要参数rfid)
+     */
+    const val INSERT_IS_RFID_BY_CABINET = "/iscs/token/insertIsRfidTokenByCabinet"
 }

+ 8 - 0
app/src/main/java/com/grkj/iscs/model/bo/DockStatusBO.kt

@@ -0,0 +1,8 @@
+package com.grkj.iscs.model.bo
+
+import com.grkj.iscs.view.fragment.DockTestFragment.DockTestBean
+
+data class DockStatusBO(
+    val row: Int,
+    val dockList: MutableList<DockTestBean>
+)

+ 83 - 2
app/src/main/java/com/grkj/iscs/util/NetApi.kt

@@ -559,14 +559,14 @@ object NetApi {
             ),
             { res, msg, code ->
                 res?.let {
-                    callBack(true, msg.toString(),code)
+                    callBack(true, msg.toString(), code)
                 } ?: let {
                     if (retryCount > 0) {
                         Executor.delayOnIO(500) {
                             updateLockPointBatch(list, retryCount - 1, callBack)
                         }
                     } else {
-                        callBack.invoke(false, msg.toString(),code)
+                        callBack.invoke(false, msg.toString(), code)
                     }
                 }
             }, isGet = false, isAuth = true
@@ -1443,4 +1443,85 @@ object NetApi {
             }, isGet = true, isAuth = true
         )
     }
+
+    /**
+     * 设备录入工卡
+     */
+    fun deviceInputCard(deviceNfc: String, callBack: (Boolean) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.INSERT_IS_JOB_CARD_BY_CABINET,
+            false,
+            mapOf(
+                "cardNfc" to deviceNfc
+            ),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(true)
+                } ?: run {
+                    callBack.invoke(false)
+                }
+            }, isGet = false, isAuth = true
+        )
+    }
+
+    /**
+     * 设备录入RFID
+     */
+    fun deviceInputRFID(deviceNfc: String, callBack: (Boolean) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.INSERT_IS_RFID_BY_CABINET,
+            false,
+            mapOf(
+                "rfid" to deviceNfc
+            ),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(true)
+                } ?: run {
+                    callBack.invoke(false)
+                }
+            }, isGet = false, isAuth = true
+        )
+    }
+
+    /**
+     * 设备录入钥匙
+     */
+    fun deviceInputKey(keyNfc: String, keyMacAddress: String, callBack: (Boolean) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.INSERT_IS_KEY_BY_CABINET,
+            false,
+            mapOf(
+                "keyNfc" to keyNfc,
+                "macAddress" to keyMacAddress,
+            ),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(true)
+                } ?: run {
+                    callBack.invoke(false)
+                }
+            }, isGet = false, isAuth = true
+        )
+    }
+
+    /**
+     * 设备录入挂锁
+     */
+    fun deviceInputLock(lockNfc: String, callBack: (Boolean) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.INSERT_IS_LOCK_BY_CABINET,
+            false,
+            mapOf(
+                "lockNfc" to lockNfc
+            ),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(true)
+                } ?: run {
+                    callBack.invoke(false)
+                }
+            }, isGet = false, isAuth = true
+        )
+    }
 }

+ 11 - 0
app/src/main/java/com/grkj/iscs/view/activity/HomeActivity.kt

@@ -28,6 +28,7 @@ import com.grkj.iscs.util.log.LogUtil
 import com.grkj.iscs.view.adapter.MenuAdapter
 import com.grkj.iscs.view.base.BaseFragment
 import com.grkj.iscs.view.base.BaseMvpActivity
+import com.grkj.iscs.view.fragment.DeviceInputHomeFragment
 import com.grkj.iscs.view.fragment.DeviceStatusFragment
 import com.grkj.iscs.view.fragment.DockTestFragment
 import com.grkj.iscs.view.fragment.ExceptionReportFragment
@@ -82,6 +83,16 @@ class HomeActivity : BaseMvpActivity<IHomeView, HomePresenter, ActivityHomeBindi
                     )
                 )
             }
+            if (userInfo.roles?.any { it == USER_ROLE_ADMHDWTESTER } == true) {
+                BusinessManager.isTestMode = true
+                mMenuList.add(
+                    Menu(
+                        getString(R.string.device_input),
+                        R.mipmap.menu_icon_device_input,
+                        DeviceInputHomeFragment()
+                    )
+                )
+            }
             if (userInfo.roles?.any { it == USER_ROLE_ADMHDWSETTER } == true) {
                 mMenuList.add(
                     Menu(

+ 83 - 0
app/src/main/java/com/grkj/iscs/view/base/BaseNavFragment.kt

@@ -0,0 +1,83 @@
+package com.grkj.iscs.view.base
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.annotation.LayoutRes
+import androidx.databinding.DataBindingUtil
+import androidx.databinding.ViewDataBinding
+import androidx.fragment.app.Fragment
+import androidx.navigation.NavController
+import androidx.navigation.fragment.findNavController
+import com.grkj.iscs.model.eventmsg.MsgEvent
+import org.greenrobot.eventbus.EventBus
+import org.greenrobot.eventbus.Subscribe
+import org.greenrobot.eventbus.ThreadMode
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+/**
+ * BaseFragment: 支持 ViewBinding, EventBus, 权限管理, 串口 & 蓝牙, 以及 Navigation 切换
+ */
+abstract class BaseNavFragment<V : ViewDataBinding> : Fragment() {
+    protected val logger: Logger = LoggerFactory.getLogger(this::class.java)
+    protected lateinit var binding: V
+    private var isFirstLoad: Boolean = true
+    protected val navController: NavController get() = findNavController()
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        if (!::binding.isInitialized) {
+            binding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false)
+            binding.lifecycleOwner = viewLifecycleOwner
+        }
+        return binding.root
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        // EventBus 注册
+        if (!EventBus.getDefault().isRegistered(this)) {
+            EventBus.getDefault().register(this)
+        }
+        if (isFirstLoad) {
+            initView()
+            initData()
+            initListeners()
+            initObservers()
+            isFirstLoad = false
+        }
+    }
+
+    override fun onDestroyView() {
+        if (EventBus.getDefault().isRegistered(this)) {
+            EventBus.getDefault().unregister(this)
+        }
+        super.onDestroyView()
+    }
+
+    @Subscribe(threadMode = ThreadMode.MAIN)
+    open fun onEvent(event: MsgEvent) {
+
+    }
+
+    /** 获取布局资源 ID */
+    @LayoutRes
+    protected abstract fun getLayoutId(): Int
+
+    /** 初始化视图 */
+    protected abstract fun initView()
+
+    /** 可选:初始化数据 */
+    protected open fun initData() {}
+
+    /** 可选:初始化监听 */
+    protected open fun initListeners() {}
+
+    /** 可选:初始化 LiveData/事件观察 */
+    protected open fun initObservers() {}
+}

+ 12 - 0
app/src/main/java/com/grkj/iscs/view/base/BaseViewModel.kt

@@ -0,0 +1,12 @@
+package com.grkj.iscs.view.base
+
+import androidx.lifecycle.ViewModel
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+/**
+ * ViewModel基类
+ */
+abstract class BaseViewModel : ViewModel() {
+    protected val logger: Logger = LoggerFactory.getLogger(this::class.java)
+}

+ 21 - 0
app/src/main/java/com/grkj/iscs/view/fragment/DeviceInputHomeFragment.kt

@@ -0,0 +1,21 @@
+package com.grkj.iscs.view.fragment
+
+import com.grkj.iscs.databinding.FragmentDeviceInputHomeBinding
+import com.grkj.iscs.view.base.BaseMvpFragment
+import com.grkj.iscs.view.iview.IDeviceInputHomeView
+import com.grkj.iscs.view.presenter.DeviceInputHomePresenter
+
+class DeviceInputHomeFragment :
+    BaseMvpFragment<IDeviceInputHomeView, DeviceInputHomePresenter, FragmentDeviceInputHomeBinding>() {
+
+    override val viewBinding: FragmentDeviceInputHomeBinding
+        get() = FragmentDeviceInputHomeBinding.inflate(layoutInflater)
+
+    override fun initPresenter(): DeviceInputHomePresenter {
+        return DeviceInputHomePresenter()
+    }
+
+    override fun initView() {
+
+    }
+}

+ 293 - 0
app/src/main/java/com/grkj/iscs/view/fragment/DeviceInputKeyAndLockFragment.kt

@@ -0,0 +1,293 @@
+package com.grkj.iscs.view.fragment
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.view.View
+import android.widget.TextView
+import androidx.core.content.ContextCompat
+import androidx.core.view.isVisible
+import androidx.lifecycle.ViewModelProvider
+import androidx.recyclerview.widget.RecyclerView
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.FragmentDeviceInputKeyAndLockBinding
+import com.grkj.iscs.extentions.setSelected
+import com.grkj.iscs.extentions.setVisibleWithHolder
+import com.grkj.iscs.modbus.ModBusController
+import com.grkj.iscs.model.DeviceConst.DOCK_TYPE_KEY
+import com.grkj.iscs.model.DeviceConst.DOCK_TYPE_LOCK
+import com.grkj.iscs.model.bo.DockStatusBO
+import com.grkj.iscs.util.CommonUtils
+import com.grkj.iscs.util.SPUtils
+import com.grkj.iscs.view.base.BaseNavFragment
+import com.grkj.iscs.view.dialog.TipDialog
+import com.grkj.iscs.view.presenter.DeviceInputKeyAndLockViewModel
+import com.sik.sikcore.extension.setDebouncedClickListener
+import com.sik.sikcore.thread.ThreadUtils
+import com.zhy.adapter.recyclerview.CommonAdapter
+import com.zhy.adapter.recyclerview.MultiItemTypeAdapter
+import com.zhy.adapter.recyclerview.base.ItemViewDelegate
+import com.zhy.adapter.recyclerview.base.ViewHolder
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.withContext
+
+/**
+ * 新硬件录入页
+ */
+class DeviceInputKeyAndLockFragment :
+    BaseNavFragment<FragmentDeviceInputKeyAndLockBinding>() {
+    private val viewModel: DeviceInputKeyAndLockViewModel by lazy { ViewModelProvider(this)[DeviceInputKeyAndLockViewModel::class] }
+    private val tipDialog: TipDialog by lazy { TipDialog(requireContext()) }
+    private var mRowList = mutableListOf<DockStatusBO>()
+    private var isAlreadyInput: Boolean = false
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_device_input_key_and_lock
+    }
+
+    override fun initView() {
+        viewModel.loginUser = SPUtils.getLoginUser(requireContext())
+        viewModel.initData(mRowList)
+        binding.cbBack.setDebouncedClickListener {
+            navController.popBackStack()
+        }
+        binding.cbRescanOrInput.setDebouncedClickListener {
+            if (isAlreadyInput) {
+                mRowList.clear()
+                viewModel.initData(mRowList)
+                binding.scanTip.isVisible = mRowList.isEmpty()
+                binding.rvDock.isVisible = mRowList.isNotEmpty()
+                binding.cbRescanOrInput.isVisible = binding.rvDock.isVisible
+                isAlreadyInput = false
+                binding.cbRescanOrInput.setText(
+                    if (isAlreadyInput) CommonUtils.getStr(R.string.rescan)
+                        .toString() else CommonUtils.getStr(R.string.input_to_system).toString()
+                )
+            } else {
+                viewModel.deviceInputData(mRowList).observe(this) {
+                    if (it.first) {
+                        isAlreadyInput = true
+                        binding.cbRescanOrInput.setText(
+                            if (isAlreadyInput) CommonUtils.getStr(R.string.rescan)
+                                .toString() else CommonUtils.getStr(R.string.input_to_system)
+                                .toString()
+                        )
+                        tipDialog.setType(TipDialog.TYPE_CONFIRM)
+                        tipDialog.setTip(
+                            requireContext().getString(
+                                R.string.already_input_device_tip,
+                                it.second,
+                                it.third
+                            )
+                        )
+                        tipDialog.show()
+                    } else {
+                        tipDialog.setType(TipDialog.TYPE_ALL)
+                        tipDialog.setTip(
+                            requireContext().getString(R.string.input_device_error)
+                        )
+                        tipDialog.showCancelCountdown(10)
+                    }
+                }
+
+            }
+        }
+        val adapter = MultiItemTypeAdapter(requireContext(), mRowList)
+        adapter.addItemViewDelegate(KeyDockItemDelegate(requireContext(), viewModel))
+        adapter.addItemViewDelegate(
+            LockDockItemDelegate(
+                requireContext(),
+                viewModel,
+                requireContext()
+            )
+        )
+        adapter.addItemViewDelegate(EmptyItemDelegate())
+        binding.rvDock?.adapter = adapter
+    }
+
+    override fun onResume() {
+        super.onResume()
+        fun refreshAdapter() {
+            binding.scanTip.isVisible = mRowList.isEmpty()
+            binding.rvDock.isVisible = mRowList.isNotEmpty()
+            binding.cbRescanOrInput.isVisible = binding.rvDock.isVisible
+            binding.cbRescanOrInput.setText(
+                if (isAlreadyInput) CommonUtils.getStr(R.string.rescan)
+                    .toString() else CommonUtils.getStr(R.string.input_to_system).toString()
+            )
+            ThreadUtils.runOnIO {
+                if (isResumed) {
+                    withContext(Dispatchers.Main) {
+                        binding.rvDock?.adapter?.notifyDataSetChanged()
+                    }
+                    delay(1000)
+                    refreshAdapter()
+                }
+            }
+
+        }
+        refreshAdapter()
+    }
+
+    class KeyDockItemDelegate(
+        var context: Context,
+        var presenter: DeviceInputKeyAndLockViewModel?
+    ) :
+        ItemViewDelegate<DockStatusBO> {
+        private val statusCloseTintColor =
+            ContextCompat.getColor(context, R.color.common_status_red)
+        private val statusOpenTintColor =
+            ContextCompat.getColor(context, R.color.common_status_green)
+
+        override fun getItemViewLayoutId(): Int {
+            return R.layout.item_rv_key_dock_device_input
+        }
+
+        override fun convert(holder: ViewHolder?, row: DockStatusBO, position: Int) {
+            holder?.setVisibleWithHolder(R.id.ll_left, row.dockList.any { it.column == "1" })
+            holder?.setVisibleWithHolder(R.id.ll_right, row.dockList.any { it.column == "2" })
+            holder?.setSelected(
+                R.id.iv_key_1,
+                ModBusController.isKeyExist(row.dockList.find { it.column == "1" }?.address, true)
+            )
+            holder?.setSelected(
+                R.id.iv_key_2,
+                ModBusController.isKeyExist(row.dockList.find { it.column == "1" }?.address, false)
+            )
+            holder?.setSelected(
+                R.id.iv_key_3,
+                ModBusController.isKeyExist(row.dockList.find { it.column == "2" }?.address, true)
+            )
+            holder?.setSelected(
+                R.id.iv_key_4,
+                ModBusController.isKeyExist(row.dockList.find { it.column == "2" }?.address, false)
+            )
+//            holder?.getView<View>(R.id.v_buckle_status_1)?.backgroundTintList =
+//                if (presenter?.getKeyBuckleLockEnabled(
+//                        row.dockList.find { it.column == "1" }?.address,
+//                        true
+//                    ) == true
+//                ) ColorStateList.valueOf(statusCloseTintColor) else ColorStateList.valueOf(
+//                    statusOpenTintColor
+//                )
+//            holder?.getView<View>(R.id.v_buckle_status_2)?.backgroundTintList =
+//                if (presenter?.getKeyBuckleLockEnabled(
+//                        row.dockList.find { it.column == "1" }?.address,
+//                        false
+//                    ) == true
+//                ) ColorStateList.valueOf(statusCloseTintColor) else ColorStateList.valueOf(
+//                    statusOpenTintColor
+//                )
+//            holder?.getView<View>(R.id.v_buckle_status_3)?.backgroundTintList =
+//                if (presenter?.getKeyBuckleLockEnabled(
+//                        row.dockList.find { it.column == "2" }?.address,
+//                        true
+//                    ) == true
+//                ) ColorStateList.valueOf(statusCloseTintColor) else ColorStateList.valueOf(
+//                    statusOpenTintColor
+//                )
+//            holder?.getView<View>(R.id.v_buckle_status_4)?.backgroundTintList =
+//                if (presenter?.getKeyBuckleLockEnabled(
+//                        row.dockList.find { it.column == "2" }?.address,
+//                        false
+//                    ) == true
+//                ) ColorStateList.valueOf(statusCloseTintColor) else ColorStateList.valueOf(
+//                    statusOpenTintColor
+//                )
+            holder?.getView<TextView>(R.id.tv_new_device_1)?.isVisible =
+                ModBusController.isKeyNewHardware(
+                    row.dockList.find { it.column == "1" }?.address,
+                    true
+                )
+            holder?.getView<TextView>(R.id.tv_new_device_2)?.isVisible =
+                ModBusController.isKeyNewHardware(
+                    row.dockList.find { it.column == "1" }?.address,
+                    false
+                )
+            holder?.getView<TextView>(R.id.tv_new_device_3)?.isVisible =
+                ModBusController.isKeyNewHardware(
+                    row.dockList.find { it.column == "2" }?.address,
+                    true
+                )
+            holder?.getView<TextView>(R.id.tv_new_device_4)?.isVisible =
+                ModBusController.isKeyNewHardware(
+                    row.dockList.find { it.column == "2" }?.address,
+                    false
+                )
+        }
+
+        override fun isForViewType(item: DockStatusBO?, position: Int): Boolean {
+            return item?.dockList?.all { it.type == DOCK_TYPE_KEY } == true
+        }
+    }
+
+    class LockDockItemDelegate(
+        var context: Context,
+        var presenter: DeviceInputKeyAndLockViewModel?,
+        var ctx: Context
+    ) : ItemViewDelegate<DockStatusBO> {
+        private val statusCloseTintColor =
+            ContextCompat.getColor(context, R.color.common_status_red)
+        private val statusOpenTintColor =
+            ContextCompat.getColor(context, R.color.common_status_green)
+        private val statusNotLightTintColor =
+            ContextCompat.getColor(context, R.color.common_status_not_light)
+
+        override fun getItemViewLayoutId(): Int {
+            return R.layout.item_rv_lock_dock_status
+        }
+
+        override fun convert(holder: ViewHolder?, row: DockStatusBO, position: Int) {
+            val rv = holder?.getView<RecyclerView>(R.id.rv_root)
+            rv?.adapter = object :
+                CommonAdapter<Int>(
+                    ctx,
+                    R.layout.item_rv_lock_dock_child_device_input,
+                    row.dockList[0].deviceList
+                ) {
+                override fun convert(holder: ViewHolder?, lockIdx: Int, position: Int) {
+                    holder?.setSelected(
+                        R.id.root,
+                        ModBusController.isLockExist(row.dockList[0].address, lockIdx)
+                    )
+                    holder?.getView<TextView>(R.id.tv_new_device)?.isVisible =
+                        ModBusController.isLockNewHardware(
+                            row.dockList[0].address, lockIdx
+                        )
+                    ColorStateList.valueOf(statusNotLightTintColor).let {
+                        holder?.getView<View>(R.id.v_buckle_status_close)?.backgroundTintList = it
+                        holder?.getView<View>(R.id.v_buckle_status_open)?.backgroundTintList = it
+                    }
+//                    (presenter?.getLockBuckleLockEnabled(
+//                        row.dockList[0].address,
+//                        lockIdx
+//                    ) == true).let {
+//                        if (it) {
+//                            holder?.getView<View>(R.id.v_buckle_status_close)?.backgroundTintList =
+//                                ColorStateList.valueOf(statusCloseTintColor)
+//                        } else {
+//                            holder?.getView<View>(R.id.v_buckle_status_open)?.backgroundTintList =
+//                                ColorStateList.valueOf(statusOpenTintColor)
+//                        }
+//                    }
+                }
+            }
+        }
+
+        override fun isForViewType(item: DockStatusBO?, position: Int): Boolean {
+            return item?.dockList?.all { it.type == DOCK_TYPE_LOCK } == true
+        }
+    }
+
+    class EmptyItemDelegate : ItemViewDelegate<DockStatusBO> {
+        override fun getItemViewLayoutId(): Int {
+            return R.layout.item_rv_empty_dock_status
+        }
+
+        override fun convert(holder: ViewHolder?, row: DockStatusBO, position: Int) {
+        }
+
+        override fun isForViewType(item: DockStatusBO?, position: Int): Boolean {
+            return item?.dockList?.isEmpty() == true || item?.dockList?.none { it.type == DOCK_TYPE_KEY || it.type == DOCK_TYPE_LOCK } == true
+        }
+    }
+}

+ 81 - 0
app/src/main/java/com/grkj/iscs/view/fragment/DeviceInputScanFragment.kt

@@ -0,0 +1,81 @@
+package com.grkj.iscs.view.fragment
+
+import androidx.lifecycle.ViewModelProvider
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.FragmentDeviceInputScanBinding
+import com.grkj.iscs.enums.DeviceInputTypeEnum.*
+import com.grkj.iscs.model.ISCSDomainData
+import com.grkj.iscs.util.CommonUtils
+import com.grkj.iscs.view.activity.HomeActivity
+import com.grkj.iscs.view.base.BaseNavFragment
+import com.grkj.iscs.view.dialog.TipDialog
+import com.grkj.iscs.view.viewmodel.DeviceInputViewModel
+import com.sik.sikcore.extension.setDebouncedClickListener
+
+/**
+ * 设备录入扫描界面(卡片录入、RFID标签录入)
+ */
+class DeviceInputScanFragment : BaseNavFragment<FragmentDeviceInputScanBinding>() {
+    private val viewModel: DeviceInputViewModel by lazy { ViewModelProvider(this)[DeviceInputViewModel::class] }
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_device_input_scan
+    }
+
+    override fun initView() {
+        binding.cbBack.setDebouncedClickListener {
+            navController.popBackStack()
+        }
+        when (ISCSDomainData.deviceInputType) {
+            CARD -> binding.inputTip.text = CommonUtils.getStr(R.string.device_input_scan_card_tip)
+            RFID -> binding.inputTip.text = CommonUtils.getStr(R.string.device_input_scan_rfid_tip)
+            NONE -> binding.inputTip.text = ""
+        }
+
+        (activity as HomeActivity).cardNoLiveData.observeForever {deviceNfc->
+            if (isVisible && deviceNfc.isNotEmpty()) {
+                val tipDialog = TipDialog(requireContext())
+                tipDialog.setTip(
+                    requireContext().getString(
+                        R.string.device_input_recognize_tip,
+                        getTipTypeStr(), deviceNfc ?: ""
+                    )
+                )
+                tipDialog.setConfirmListener {
+                    viewModel.deviceInputScan(deviceNfc).observe(this) {
+                        (activity as HomeActivity).cardNoLiveData.postValue("")
+                        if (it) {
+                            tipDialog.setTip(
+                                requireContext().getString(
+                                    R.string.device_input_success_tip,
+                                    getTipTypeStr(), deviceNfc ?: ""
+                                )
+                            )
+                            tipDialog.setConfirmListener{}
+                            tipDialog.setType(TipDialog.TYPE_CONFIRM)
+                            tipDialog.show()
+                        } else {
+                            tipDialog.setTip(
+                                requireContext().getString(
+                                    R.string.device_input_fail_tip,
+                                    getTipTypeStr()
+                                )
+                            )
+                            tipDialog.setConfirmListener{}
+                            tipDialog.setType(TipDialog.TYPE_CONFIRM)
+                            tipDialog.show()
+                        }
+                    }
+                }
+                tipDialog.showCancelCountdown(10)
+            }
+        }
+    }
+
+    private fun getTipTypeStr(): String {
+        return when (ISCSDomainData.deviceInputType) {
+            CARD -> requireContext().getString(R.string.card)
+            RFID -> requireContext().getString(R.string.rfid)
+            else -> ""
+        }
+    }
+}

+ 31 - 0
app/src/main/java/com/grkj/iscs/view/fragment/DeviceInputTypeSelectFragment.kt

@@ -0,0 +1,31 @@
+package com.grkj.iscs.view.fragment
+
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.FragmentDeviceInputTypeSelectBinding
+import com.grkj.iscs.enums.DeviceInputTypeEnum
+import com.grkj.iscs.model.ISCSDomainData
+import com.grkj.iscs.view.base.BaseNavFragment
+import com.sik.sikcore.extension.setDebouncedClickListener
+
+/**
+ * 设备录入类型选择界面
+ */
+class DeviceInputTypeSelectFragment : BaseNavFragment<FragmentDeviceInputTypeSelectBinding>() {
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_device_input_type_select
+    }
+
+    override fun initView() {
+        binding.keyLockScanLayout.setDebouncedClickListener {
+            navController.navigate(R.id.action_deviceInputTypeSelectFragment_to_deviceInputKeyAndLockFragment)
+        }
+        binding.cardInputLayout.setDebouncedClickListener {
+            ISCSDomainData.deviceInputType = DeviceInputTypeEnum.CARD
+            navController.navigate(R.id.action_deviceInputTypeSelectFragment_to_deviceInputScanFragment)
+        }
+        binding.rfidInputLayout.setDebouncedClickListener {
+            ISCSDomainData.deviceInputType = DeviceInputTypeEnum.RFID
+            navController.navigate(R.id.action_deviceInputTypeSelectFragment_to_deviceInputScanFragment)
+        }
+    }
+}

+ 1 - 8
app/src/main/java/com/grkj/iscs/view/fragment/DeviceStatusFragment.kt

@@ -6,21 +6,19 @@ import android.view.View
 import android.widget.ImageView
 import androidx.core.content.ContextCompat
 import androidx.recyclerview.widget.RecyclerView
-import com.grkj.iscs.MyApplication
 import com.grkj.iscs.R
 import com.grkj.iscs.databinding.FragmentDeviceStatusBinding
 import com.grkj.iscs.extentions.setSelected
 import com.grkj.iscs.extentions.setVisibleWithHolder
 import com.grkj.iscs.modbus.ModBusController
-import com.grkj.iscs.model.Constants.USER_ROLE_ADMHDWTESTER
 import com.grkj.iscs.model.DeviceConst.DOCK_TYPE_KEY
 import com.grkj.iscs.model.DeviceConst.DOCK_TYPE_LOCK
+import com.grkj.iscs.model.bo.DockStatusBO
 import com.grkj.iscs.util.SPUtils
 import com.grkj.iscs.util.ToastUtils
 import com.grkj.iscs.view.base.BaseMvpFragment
 import com.grkj.iscs.view.dialog.SlotExceptionDialog
 import com.grkj.iscs.view.dialog.TipDialog
-import com.grkj.iscs.view.fragment.DockTestFragment.DockTestBean
 import com.grkj.iscs.view.iview.IDeviceStatusView
 import com.grkj.iscs.view.presenter.DeviceStatusPresenter
 import com.sik.sikcore.thread.ThreadUtils
@@ -157,11 +155,6 @@ class DeviceStatusFragment :
         return DeviceStatusPresenter()
     }
 
-    data class DockStatusBO(
-        val row: Int,
-        val dockList: MutableList<DockTestBean>
-    )
-
     class KeyDockItemDelegate(var context: Context, var presenter: DeviceStatusPresenter?) :
         ItemViewDelegate<DockStatusBO> {
         private val statusCloseTintColor =

+ 1 - 0
app/src/main/java/com/grkj/iscs/view/fragment/JobExecutionFragment.kt

@@ -43,6 +43,7 @@ class JobExecutionFragment(val changePageCallback: (PageChangeBO) -> Unit) :
         (activity as HomeActivity).cardNoLiveData.observeForever {
             if (mBinding?.vp?.currentItem == 2) {
                 (mMenuList[2].fragment as JobProgressFragment).getCardNo(it)
+                (activity as HomeActivity).cardNoLiveData.postValue("")
             }
         }
     }

+ 9 - 0
app/src/main/java/com/grkj/iscs/view/iview/IDeviceInputHomeView.kt

@@ -0,0 +1,9 @@
+package com.grkj.iscs.view.iview
+
+import com.grkj.iscs.view.base.IView
+
+/**
+ * 设备录入界面接口
+ */
+interface IDeviceInputHomeView : IView {
+}

+ 7 - 0
app/src/main/java/com/grkj/iscs/view/presenter/DeviceInputHomePresenter.kt

@@ -0,0 +1,7 @@
+package com.grkj.iscs.view.presenter
+
+import com.grkj.iscs.view.base.BasePresenter
+import com.grkj.iscs.view.iview.IDeviceInputHomeView
+
+class DeviceInputHomePresenter : BasePresenter<IDeviceInputHomeView>() {
+}

+ 141 - 0
app/src/main/java/com/grkj/iscs/view/presenter/DeviceInputKeyAndLockViewModel.kt

@@ -0,0 +1,141 @@
+package com.grkj.iscs.view.presenter
+
+import android.graphics.Bitmap
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.liveData
+import com.clj.fastble.BleManager
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+import com.grkj.iscs.BusinessManager
+import com.grkj.iscs.BusinessManager.getCurrentStatus
+import com.grkj.iscs.MyApplication
+import com.grkj.iscs.R
+import com.grkj.iscs.extentions.removeLeadingZeros
+import com.grkj.iscs.extentions.toHexStrings
+import com.grkj.iscs.modbus.DockBean
+import com.grkj.iscs.modbus.ModBusController
+import com.grkj.iscs.model.DeviceConst
+import com.grkj.iscs.model.DeviceConst.DOCK_TYPE_LOCK
+import com.grkj.iscs.model.DictAndSystemConstants
+import com.grkj.iscs.model.bo.DockStatusBO
+import com.grkj.iscs.model.bo.LoginUserBO
+import com.grkj.iscs.model.vo.dict.CommonDictRespVO
+import com.grkj.iscs.model.vo.hardware.CabinetSlotsRecord
+import com.grkj.iscs.model.vo.hardware.SlotExDTO
+import com.grkj.iscs.util.CommonUtils
+import com.grkj.iscs.util.Executor
+import com.grkj.iscs.util.NetApi
+import com.grkj.iscs.util.SPUtils
+import com.grkj.iscs.util.ToastUtils
+import com.grkj.iscs.util.log.LogUtil
+import com.grkj.iscs.view.base.BaseViewModel
+import com.grkj.iscs.view.fragment.DockTestFragment.DockTestBean
+import com.sik.sikcore.thread.ThreadUtils
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlin.coroutines.resume
+
+class DeviceInputKeyAndLockViewModel : BaseViewModel() {
+    var loginUser: LoginUserBO? = null
+
+    fun initData(rowList: MutableList<DockStatusBO>) {
+        val dockConfigJson = SPUtils.getDockConfig(MyApplication.instance?.applicationContext!!)
+        if (!dockConfigJson.isNullOrEmpty()) {
+            val tempList: MutableList<DockTestBean> = Gson().fromJson(
+                dockConfigJson, object : TypeToken<MutableList<DockTestBean>>() {}.type
+            )
+            if (tempList.isNotEmpty()) {
+                tempList.forEach { dock ->
+                    try {
+                        if (dock.type == DOCK_TYPE_LOCK) {
+                            dock.deviceList = mutableListOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
+                        }
+                        rowList.find { it.row == dock.row.toInt() }?.let {
+                            it.dockList.add(dock)
+                        } ?: let {
+                            rowList.add(DockStatusBO(dock.row.toInt(), mutableListOf(dock)))
+                        }
+                    } catch (e: Exception) {
+                        LogUtil.e("Device status data error : ${e.message}")
+                    }
+                }
+                // 添加空行
+                val maxRow = rowList.maxBy { it.row }.row
+                for (i in 1..maxRow) {
+                    if (rowList.find { it.row == i } == null) {
+                        rowList.add(DockStatusBO(i, mutableListOf()))
+                    }
+                }
+                rowList.sortBy { it.row }
+            }
+        }
+    }
+
+    /**
+     * 设备录入挂锁
+     */
+    fun deviceInputLock(lockNfc: String): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            emit(deviceInputLockSuspend(lockNfc))
+        }
+    }
+
+    /**
+     * 设备录入钥匙
+     */
+    fun deviceInputKey(keyNfc: String, keyMacAddress: String): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            emit(deviceInputKeySuspend(keyNfc, keyMacAddress))
+        }
+    }
+
+    /**
+     * 设备录入挂起任务
+     */
+    private suspend fun deviceInputKeySuspend(keyNfc: String, keyMacAddress: String): Boolean {
+        return suspendCancellableCoroutine<Boolean> { cont ->
+            NetApi.deviceInputKey(keyNfc, keyMacAddress) {
+                cont.resume(it)
+                cont.cancel()
+            }
+        }
+    }
+
+    /**
+     * 设备录入挂起任务
+     */
+    private suspend fun deviceInputLockSuspend(cardNo: String): Boolean {
+        return suspendCancellableCoroutine<Boolean> { cont ->
+            NetApi.deviceInputLock(cardNo) {
+                cont.resume(it)
+                cont.cancel()
+            }
+        }
+    }
+
+    /**
+     * 设备录入(钥匙和挂锁)
+     * //todo 录入待完成
+     */
+    fun deviceInputData(dockData: MutableList<DockStatusBO>): LiveData<Triple<Boolean, Int, Int>> {
+        return liveData(Dispatchers.IO) {
+            val dockList = dockData.map { it.dockList }.flatten()
+            val deviceList =
+                ModBusController.dockList.filter { it.addr in dockList.map { it.address } }
+                    .map { it.deviceList }.flatten()
+            val lockDevice = deviceList.filter { it.type == DeviceConst.DEVICE_TYPE_LOCK }
+                .filterIsInstance<DockBean.LockBean>()
+            val keyDevice = deviceList.filter { it.type == DeviceConst.DEVICE_TYPE_KEY }
+                .filterIsInstance<DockBean.KeyBean>()
+            lockDevice.filter { it.rfid?.isNotEmpty() == true }.map { it.rfid }.forEach { rfid ->
+                deviceInputLockSuspend(rfid.toString())
+            }
+            emit(
+                Triple(
+                    true,
+                    lockDevice.count { it.rfid?.isNotEmpty() == true },
+                    keyDevice.count { it.rfid?.isNotEmpty() == true })
+            )
+        }
+    }
+}

+ 1 - 1
app/src/main/java/com/grkj/iscs/view/presenter/DeviceStatusPresenter.kt

@@ -12,6 +12,7 @@ import com.grkj.iscs.extentions.toHexStrings
 import com.grkj.iscs.modbus.ModBusController
 import com.grkj.iscs.model.DeviceConst.DOCK_TYPE_LOCK
 import com.grkj.iscs.model.DictAndSystemConstants
+import com.grkj.iscs.model.bo.DockStatusBO
 import com.grkj.iscs.model.bo.LoginUserBO
 import com.grkj.iscs.model.vo.dict.CommonDictRespVO
 import com.grkj.iscs.model.vo.hardware.CabinetSlotsRecord
@@ -22,7 +23,6 @@ import com.grkj.iscs.util.SPUtils
 import com.grkj.iscs.util.ToastUtils
 import com.grkj.iscs.util.log.LogUtil
 import com.grkj.iscs.view.base.BasePresenter
-import com.grkj.iscs.view.fragment.DeviceStatusFragment.DockStatusBO
 import com.grkj.iscs.view.fragment.DockTestFragment.DockTestBean
 import com.grkj.iscs.view.iview.IDeviceStatusView
 import com.sik.sikcore.thread.ThreadUtils

+ 53 - 0
app/src/main/java/com/grkj/iscs/view/viewmodel/DeviceInputViewModel.kt

@@ -0,0 +1,53 @@
+package com.grkj.iscs.view.viewmodel
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.liveData
+import com.grkj.iscs.enums.DeviceInputTypeEnum.*
+import com.grkj.iscs.model.ISCSDomainData
+import com.grkj.iscs.util.NetApi
+import com.grkj.iscs.view.base.BaseViewModel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlin.coroutines.resume
+
+/**
+ * 设备录入
+ */
+class DeviceInputViewModel : BaseViewModel() {
+    /**
+     * 设备录入
+     */
+    fun deviceInputScan(cardNo: String): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            emit(deviceInputScanSuspend(cardNo))
+        }
+    }
+
+    /**
+     * 设备录入挂起任务
+     */
+    private suspend fun deviceInputScanSuspend(cardNo: String): Boolean {
+        return suspendCancellableCoroutine<Boolean> { cont ->
+            when (ISCSDomainData.deviceInputType) {
+                CARD -> {
+                    NetApi.deviceInputCard(cardNo) {
+                        cont.resume(it)
+                        cont.cancel()
+                    }
+                }
+
+                RFID -> {
+                    NetApi.deviceInputRFID(cardNo) {
+                        cont.resume(it)
+                        cont.cancel()
+                    }
+                }
+
+                else -> {
+                    cont.resume(false)
+                    cont.cancel()
+                }
+            }
+        }
+    }
+}

+ 8 - 0
app/src/main/res/drawable/bg_device_input_type_select.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <stroke
+        android:width="1dp"
+        android:color="@color/color_d7d2d2" />
+    <corners android:radius="6dp" />
+</shape>

+ 7 - 0
app/src/main/res/drawable/divider_horizontal.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="line">
+    <size
+        android:width="10dp"
+        android:height="1dp" />
+</shape>

+ 13 - 0
app/src/main/res/layout/fragment_device_input_home.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <androidx.fragment.app.FragmentContainerView
+        android:id="@+id/nav_host_fragment"
+        android:name="androidx.navigation.fragment.NavHostFragment"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:defaultNavHost="true"
+        app:navGraph="@navigation/nav_device_input" />
+
+</layout>

+ 50 - 0
app/src/main/res/layout/fragment_device_input_key_and_lock.xml

@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout 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">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@drawable/item_rv_technology_sop_bg_normal"
+        android:padding="@dimen/common_spacing"
+        tools:context=".view.fragment.DeviceStatusFragment">
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/rv_dock"
+            style="@style/CommonRecyclerView"
+            android:layout_above="@+id/cb_back"
+            android:layout_marginBottom="10dp"
+            android:visibility="gone"/>
+
+        <TextView
+            android:id="@+id/scan_tip"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_centerInParent="true"
+            android:gravity="center"
+            android:textColor="@color/color_d7d2d2"
+            android:textSize="18sp"
+            android:text="@string/device_input_scan_key_and_lock_tip"/>
+
+        <com.grkj.iscs.view.widget.CommonBtn
+            android:id="@+id/cb_rescan_or_input"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true"
+            android:layout_marginRight="10dp"
+            android:layout_toLeftOf="@+id/cb_back"
+            android:visibility="gone"
+            app:btn_bg="@drawable/common_btn_blue_bg"
+            app:btn_name="@string/rescan" />
+
+        <com.grkj.iscs.view.widget.CommonBtn
+            android:id="@+id/cb_back"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentRight="true"
+            android:layout_alignParentBottom="true"
+            app:btn_bg="@drawable/common_btn_red_bg"
+            app:btn_name="@string/back" />
+    </RelativeLayout>
+</layout>

+ 27 - 0
app/src/main/res/layout/fragment_device_input_scan.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <TextView
+            android:id="@+id/input_tip"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_centerInParent="true"
+            android:gravity="center"
+            android:textColor="@color/color_d7d2d2"
+            android:textSize="18sp" />
+
+        <com.grkj.iscs.view.widget.CommonBtn
+            android:id="@+id/cb_back"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentRight="true"
+            android:layout_alignParentBottom="true"
+            app:btn_bg="@drawable/common_btn_red_bg"
+            app:btn_name="@string/back" />
+    </RelativeLayout>
+</layout>

+ 85 - 0
app/src/main/res/layout/fragment_device_input_type_select.xml

@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:divider="@drawable/divider_horizontal"
+        android:gravity="center_vertical"
+        android:orientation="horizontal"
+        android:paddingHorizontal="28dp"
+        android:showDividers="middle">
+
+        <LinearLayout
+            android:id="@+id/key_lock_scan_layout"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:background="@drawable/bg_device_input_type_select"
+            android:gravity="center"
+            android:orientation="vertical"
+            android:padding="5dp">
+
+            <ImageView
+                android:layout_width="111dp"
+                android:layout_height="49dp"
+                android:src="@mipmap/icon_key_lock_scan" />
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="5dp"
+                android:text="@string/scan_key_and_lock"
+                android:textColor="@color/color_d7d2d2"
+                android:textSize="12sp" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/card_input_layout"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:background="@drawable/bg_device_input_type_select"
+            android:gravity="center"
+            android:orientation="vertical"
+            android:padding="5dp">
+
+            <ImageView
+                android:layout_width="111dp"
+                android:layout_height="49dp"
+                android:src="@mipmap/icon_card_input" />
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="5dp"
+                android:text="@string/card_input"
+                android:textColor="@color/color_d7d2d2"
+                android:textSize="12sp" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/rfid_input_layout"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:background="@drawable/bg_device_input_type_select"
+            android:gravity="center"
+            android:orientation="vertical"
+            android:padding="5dp">
+
+            <ImageView
+                android:layout_width="111dp"
+                android:layout_height="49dp"
+                android:src="@mipmap/icon_rfid_input" />
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="5dp"
+                android:text="@string/rfid_tag_input"
+                android:textColor="@color/color_d7d2d2"
+                android:textSize="12sp" />
+        </LinearLayout>
+    </LinearLayout>
+</layout>

+ 154 - 0
app/src/main/res/layout/item_rv_key_dock_device_input.xml

@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/root"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginHorizontal="@dimen/common_spacing_small"
+    android:layout_marginVertical="@dimen/common_spacing_smallest"
+    android:orientation="horizontal"
+    android:padding="@dimen/common_spacing_small">
+
+    <LinearLayout
+        android:id="@+id/ll_left"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1">
+
+        <RelativeLayout
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1">
+
+            <ImageView
+                android:id="@+id/iv_key_1"
+                android:layout_width="50dp"
+                android:layout_height="35dp"
+                android:layout_centerHorizontal="true"
+                android:background="@drawable/dock_key_selector" />
+
+            <TextView
+                android:id="@+id/tv_new_device_1"
+                style="@style/CommonTextView"
+                android:layout_below="@+id/iv_key_1"
+                android:layout_alignLeft="@+id/iv_key_1"
+                android:layout_alignRight="@+id/iv_key_1"
+                android:layout_marginTop="5dp"
+                android:background="@drawable/common_btn_red_bg"
+                android:text="@string/new_device"
+                android:visibility="invisible" />
+
+            <View
+                android:id="@+id/v_buckle_status_1"
+                android:layout_width="@dimen/common_status_circle_small"
+                android:layout_height="@dimen/common_status_circle_small"
+                android:layout_toRightOf="@+id/iv_key_1"
+                android:background="@drawable/common_status_circle"
+                android:visibility="invisible" />
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1">
+
+            <ImageView
+                android:id="@+id/iv_key_2"
+                android:layout_width="50dp"
+                android:layout_height="35dp"
+                android:layout_centerHorizontal="true"
+                android:background="@drawable/dock_key_selector" />
+
+            <TextView
+                android:id="@+id/tv_new_device_2"
+                style="@style/CommonTextView"
+                android:layout_below="@+id/iv_key_2"
+                android:layout_alignLeft="@+id/iv_key_2"
+                android:layout_alignRight="@+id/iv_key_2"
+                android:layout_marginTop="5dp"
+                android:background="@drawable/common_btn_red_bg"
+                android:text="@string/new_device"
+                android:visibility="invisible" />
+
+            <View
+                android:id="@+id/v_buckle_status_2"
+                android:layout_width="@dimen/common_status_circle_small"
+                android:layout_height="@dimen/common_status_circle_small"
+                android:layout_toRightOf="@+id/iv_key_2"
+                android:background="@drawable/common_status_circle"
+                android:visibility="invisible" />
+        </RelativeLayout>
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/ll_right"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:orientation="horizontal">
+
+        <RelativeLayout
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1">
+
+            <ImageView
+                android:id="@+id/iv_key_3"
+                android:layout_width="50dp"
+                android:layout_height="35dp"
+                android:layout_centerHorizontal="true"
+                android:background="@drawable/dock_key_selector" />
+
+            <TextView
+                android:id="@+id/tv_new_device_3"
+                style="@style/CommonTextView"
+                android:layout_below="@+id/iv_key_3"
+                android:layout_alignLeft="@+id/iv_key_3"
+                android:layout_alignRight="@+id/iv_key_3"
+                android:layout_marginTop="5dp"
+                android:background="@drawable/common_btn_red_bg"
+                android:text="@string/new_device"
+                android:visibility="invisible" />
+
+            <View
+                android:id="@+id/v_buckle_status_3"
+                android:layout_width="@dimen/common_status_circle_small"
+                android:layout_height="@dimen/common_status_circle_small"
+                android:layout_toRightOf="@+id/iv_key_3"
+                android:background="@drawable/common_status_circle"
+                android:visibility="invisible" />
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1">
+
+            <ImageView
+                android:id="@+id/iv_key_4"
+                android:layout_width="50dp"
+                android:layout_height="35dp"
+                android:layout_centerHorizontal="true"
+                android:background="@drawable/dock_key_selector" />
+
+            <TextView
+                android:id="@+id/tv_new_device_4"
+                style="@style/CommonTextView"
+                android:layout_below="@+id/iv_key_4"
+                android:layout_alignLeft="@+id/iv_key_4"
+                android:layout_alignRight="@+id/iv_key_4"
+                android:layout_marginTop="5dp"
+                android:background="@drawable/common_btn_red_bg"
+                android:text="@string/new_device"
+                android:visibility="invisible" />
+
+            <View
+                android:id="@+id/v_buckle_status_4"
+                android:layout_width="@dimen/common_status_circle_small"
+                android:layout_height="@dimen/common_status_circle_small"
+                android:layout_toRightOf="@+id/iv_key_4"
+                android:background="@drawable/common_status_circle"
+                android:visibility="invisible" />
+
+        </RelativeLayout>
+    </LinearLayout>
+</LinearLayout>

+ 52 - 0
app/src/main/res/layout/item_rv_lock_dock_child_device_input.xml

@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_marginHorizontal="@dimen/common_spacing_small"
+    android:orientation="horizontal">
+
+    <FrameLayout
+        android:id="@+id/root"
+        android:layout_width="20dp"
+        android:layout_height="70dp"
+        android:background="@drawable/dock_lock_selector" />
+
+    <LinearLayout
+        android:id="@+id/ll_lock_status"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_alignTop="@+id/root"
+        android:layout_alignBottom="@+id/root"
+        android:layout_marginLeft="@dimen/divider_line_margin"
+        android:layout_toRightOf="@+id/root"
+        android:divider="@drawable/divider_dock_lock_status"
+        android:gravity="center"
+        android:orientation="vertical"
+        android:showDividers="middle"
+        android:visibility="invisible">
+
+        <View
+            android:id="@+id/v_buckle_status_close"
+            android:layout_width="@dimen/common_status_circle_medium"
+            android:layout_height="@dimen/common_status_circle_medium"
+            android:background="@drawable/common_status_circle" />
+
+        <View
+            android:id="@+id/v_buckle_status_open"
+            android:layout_width="@dimen/common_status_circle_medium"
+            android:layout_height="@dimen/common_status_circle_medium"
+            android:background="@drawable/common_status_circle" />
+    </LinearLayout>
+
+    <TextView
+        android:id="@+id/tv_new_device"
+        style="@style/CommonTextView"
+        android:layout_below="@+id/root"
+        android:layout_alignLeft="@+id/root"
+        android:layout_alignRight="@+id/root"
+        android:layout_marginTop="5dp"
+        android:background="@drawable/common_btn_red_bg"
+        android:text="@string/new_device"
+        android:visibility="invisible" />
+</RelativeLayout>
+

BIN
app/src/main/res/mipmap-xhdpi/icon_card_input.png


BIN
app/src/main/res/mipmap-xhdpi/icon_key_lock_scan.png


BIN
app/src/main/res/mipmap-xhdpi/icon_rfid_input.png


BIN
app/src/main/res/mipmap-xhdpi/menu_icon_device_input.png


+ 26 - 0
app/src/main/res/navigation/nav_device_input.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/nav_device_input"
+    app:startDestination="@id/deviceInputTypeSelectFragment">
+
+    <fragment
+        android:id="@+id/deviceInputTypeSelectFragment"
+        android:name="com.grkj.iscs.view.fragment.DeviceInputTypeSelectFragment"
+        android:label="DeviceInputTypeSelectFragment" >
+        <action
+            android:id="@+id/action_deviceInputTypeSelectFragment_to_deviceInputKeyAndLockFragment"
+            app:destination="@id/deviceInputKeyAndLockFragment" />
+        <action
+            android:id="@+id/action_deviceInputTypeSelectFragment_to_deviceInputScanFragment"
+            app:destination="@id/deviceInputScanFragment" />
+    </fragment>
+    <fragment
+        android:id="@+id/deviceInputKeyAndLockFragment"
+        android:name="com.grkj.iscs.view.fragment.DeviceInputKeyAndLockFragment"
+        android:label="DeviceInputKeyAndLockFragment" />
+    <fragment
+        android:id="@+id/deviceInputScanFragment"
+        android:name="com.grkj.iscs.view.fragment.DeviceInputScanFragment"
+        android:label="DeviceInputScanFragment" />
+</navigation>

+ 17 - 0
app/src/main/res/values-en/strings.xml

@@ -364,4 +364,21 @@
     <string name="lock_nfc_not_correct">该nfc无对应的挂锁信息</string>
     <string name="only_one_person_allowed">Only one person allowed</string>
     <string name="real_person_verification_required">Real-person verification required</string>
+    <string name="device_input">Device enter</string>
+    <string name="scan_key_and_lock">scan key and lock</string>
+    <string name="card_input">card enter</string>
+    <string name="rfid_tag_input">rfid tag enter</string>
+    <string name="device_input_scan_key_and_lock_tip">wait to scan new key and lock...</string>
+    <string name="device_input_scan_card_tip">please read card on card reader</string>
+    <string name="device_input_scan_rfid_tip">please read RFID on card reader</string>
+    <string name="device_input_recognize_tip">recognize %1$s\"%2$s\",do you want to entered system?</string>
+    <string name="card">Card</string>
+    <string name="rfid">RFID</string>
+    <string name="device_input_success_tip">%1$s\"%2$s\"has been entered into the system.</string>
+    <string name="device_input_fail_tip">The %1$s is already in the system and does not need to be entered.</string>
+    <string name="new_device">New</string>
+    <string name="rescan">Rescan</string>
+    <string name="input_to_system">Enter to system</string>
+    <string name="already_input_device_tip">%1$d keys and %2$d padlocks have been entered</string>
+    <string name="input_device_error">entered error</string>
 </resources>

+ 17 - 0
app/src/main/res/values-zh/strings.xml

@@ -364,4 +364,21 @@
     <string name="lock_nfc_not_correct">该nfc无对应的挂锁信息</string>
     <string name="only_one_person_allowed">请保持单人入镜</string>
     <string name="real_person_verification_required">请保持真人操作</string>
+    <string name="device_input">设备录入</string>
+    <string name="scan_key_and_lock">扫描钥匙挂锁</string>
+    <string name="card_input">录入卡片</string>
+    <string name="rfid_tag_input">录入RFID标签</string>
+    <string name="device_input_scan_key_and_lock_tip">等待扫描新的钥匙和挂锁...</string>
+    <string name="device_input_scan_card_tip">请在读卡器上刷卡</string>
+    <string name="device_input_scan_rfid_tip">请在读卡器上读取RFID</string>
+    <string name="device_input_recognize_tip">已识别%1$s\"%2$s\",确定要录入系统吗?</string>
+    <string name="card">卡片</string>
+    <string name="rfid">RFID</string>
+    <string name="device_input_success_tip">%1$s\"%2$s\"已录入系统。</string>
+    <string name="device_input_fail_tip">该%1$s已在系统中,无需录入。</string>
+    <string name="new_device">New</string>
+    <string name="rescan">重新扫描</string>
+    <string name="input_to_system">录入系统</string>
+    <string name="already_input_device_tip">已录入%1$d把钥匙与%2$d把挂锁</string>
+    <string name="input_device_error">录入失败</string>
 </resources>

+ 1 - 0
app/src/main/res/values/colors.xml

@@ -43,4 +43,5 @@
     <color name="item_rv_step_bg_ready">#B3FFFFFF</color>
     <color name="common_switch_enable">#91ce93</color>
     <color name="common_switch_disable">#f0f0f0</color>
+    <color name="color_d7d2d2">#d7d2d2</color>
 </resources>

+ 17 - 0
app/src/main/res/values/strings.xml

@@ -364,4 +364,21 @@
     <string name="lock_nfc_not_correct">该nfc无对应的挂锁信息</string>
     <string name="only_one_person_allowed">请保持单人入镜</string>
     <string name="real_person_verification_required">请保持真人操作</string>
+    <string name="device_input">设备录入</string>
+    <string name="scan_key_and_lock">扫描钥匙挂锁</string>
+    <string name="card_input">录入卡片</string>
+    <string name="rfid_tag_input">录入RFID标签</string>
+    <string name="device_input_scan_key_and_lock_tip">等待扫描新的钥匙和挂锁...</string>
+    <string name="device_input_scan_card_tip">请在读卡器上刷卡</string>
+    <string name="device_input_scan_rfid_tip">请在读卡器上读取RFID</string>
+    <string name="device_input_recognize_tip">已识别%1$s\"%2$s\",确定要录入系统吗?</string>
+    <string name="card">卡片</string>
+    <string name="rfid">RFID</string>
+    <string name="device_input_success_tip">%1$s\"%2$s\"已录入系统。</string>
+    <string name="device_input_fail_tip">该%1$s已在系统中,无需录入。</string>
+    <string name="new_device">New</string>
+    <string name="rescan">重新扫描</string>
+    <string name="input_to_system">录入系统</string>
+    <string name="already_input_device_tip">已录入%1$d把钥匙与%2$d把挂锁</string>
+    <string name="input_device_error">录入失败</string>
 </resources>

+ 7 - 0
gradle/libs.versions.toml

@@ -15,6 +15,8 @@ log-interceptor = "3.14.9"
 autosize = "v1.2.1"
 fastble = "2.4.0"
 lifecycle-version = "2.9.0"
+nav_version = "2.9.0"
+kotlin_serialization_json = "1.7.3"
 room-version = "2.7.1"
 
 [libraries]
@@ -43,6 +45,11 @@ room-compiler = { group = "androidx.room",name = "room-compiler",version.ref = "
 room-ktx = { group = "androidx.room",name = "room-ktx",version.ref = "room-version" }
 room-testing = { group = "androidx.room",name = "room-testing",version.ref = "room-version" }
 
+android-navigation-fragment = { group = "androidx.navigation", name = "navigation-fragment", version.ref = "nav_version" }
+android-navigation-ui = { group = "androidx.navigation", name = "navigation-ui", version.ref = "nav_version" }
+android-navigation-dynamic-features-fragment = { group = "androidx.navigation", name = "navigation-dynamic-features-fragment", version.ref = "nav_version" }
+kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlin_serialization_json" }
+
 [plugins]
 android-application = { id = "com.android.application", version.ref = "agp" }
 jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }