列表,个人中心

This commit is contained in:
fengjignqi
2026-09-22 09:28:21 +08:00
parent f232e223bd
commit 062a16c5a2
161 changed files with 2370 additions and 294 deletions

View File

@@ -11,7 +11,6 @@ import com.juhabi.birds.device.entity.DeviceEntity
import com.juhabi.birds.device.mvp.DeviceContract
import com.juhabi.birds.device.mvp.DeviceImpl
import com.juhabi.birds.device.mvp.DevicePresenter
import kotlin.random.Random
/**
* 绑定设备
@@ -51,16 +50,33 @@ class BindDeviceActivity: BaseActivity<BindDeviceLayoutBinding, DevicePresenter>
finish()
}
R.id.tv_bind ->{//绑定设备
// val imei = "${System.currentTimeMillis()}${Random.nextInt(100).toString().padStart(2, '0')}"
// val name = "device001"
val imei = "860000000000005"
val name = "BM202609160005"
presenter?.bindDevice(imei, name)
R.id.tv_bind -> {
bindDevice()
}
}
}
/** 校验设备编码和名称非空后,发起绑定请求。 */
private fun bindDevice() {
val imei = binding.etDeviceCode.text
?.toString()
?.trim()
.orEmpty()
val name = binding.etDeviceName.text
?.toString()
?.trim()
.orEmpty()
if (imei.isBlank()) {
showMessage("请输入设备编码")
return
}
if (name.isBlank()) {
showMessage("请输入设备名称")
return
}
presenter?.bindDevice(imei, name)
}
override fun showError(message: String) {
showMessage(message)
}

View File

@@ -1,22 +0,0 @@
package com.juhabi.birds.device
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.viewholder.BaseViewHolder
import com.juhabi.birds.R
import com.juhabi.birds.device.entity.DeviceEntity
/** 临时设备列表适配器,后续可根据设计稿替换 item 展示。 */
class DeviceAdapter : BaseQuickAdapter<DeviceEntity, BaseViewHolder>(R.layout.device_item) {
init {
addChildClickViewIds(R.id.img_tips)
}
override fun convert(holder: BaseViewHolder, item: DeviceEntity) {
holder.setText(R.id.tv_device_name, item.name.ifBlank { "未命名设备" })
holder.setText(R.id.tv_device_status, item.signalText.ifBlank { "离线" })
holder.setText(R.id.tv_device_no, "设备编号:${item.deviceNo}")
holder.setText(R.id.tv_device_imei, "IMEI:${item.imei}")
holder.setText(R.id.tv_device_detail, "型号:${item.model ?: "-"} 信号:${item.signalLevel} 头部电量:${item.hoodBattery ?: "-"} 戒指电量:${item.ringBattery ?: "-"}")
holder.setText(R.id.tv_device_location, "连接:${item.hoodConnectedText} 地址:${item.address ?: "暂无地址"}")
}
}

View File

@@ -2,16 +2,17 @@ package com.juhabi.birds.device
import android.Manifest
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.juhabi.birds.R
import com.juhabi.birds.base.BaseFragment
import com.juhabi.birds.custom.AddDevicePopupWindow
import com.juhabi.birds.custom.DeviceActionPopupWindow
import com.juhabi.birds.custom.CustomRefreshHeader
import com.juhabi.birds.databinding.FragmentHomeBinding
import com.juhabi.birds.device.adapter.DeviceAdapter
import com.juhabi.birds.device.entity.DeviceEntity
import com.juhabi.birds.device.mvp.DeviceContract
import com.juhabi.birds.device.mvp.DeviceImpl
@@ -19,6 +20,7 @@ import com.juhabi.birds.device.mvp.DevicePresenter
import com.juhabi.birds.util.ToastUtil
import com.maning.mlkitscanner.scan.MNScanManager
import com.maning.mlkitscanner.scan.callback.act.MNScanCallback
import com.maning.mlkitscanner.scan.model.MNScanConfig
import com.scwang.smart.refresh.footer.ClassicsFooter
class DeviceFragment : BaseFragment<FragmentHomeBinding, DevicePresenter>(),
@@ -38,7 +40,6 @@ class DeviceFragment : BaseFragment<FragmentHomeBinding, DevicePresenter>(),
override fun initView(savedInstanceState: Bundle?) {
listOf(
binding.tvIndex,
binding.imgScan,
).forEach { button ->
button.setOnClickListener(this)
@@ -50,23 +51,29 @@ class DeviceFragment : BaseFragment<FragmentHomeBinding, DevicePresenter>(),
putSerializable(DeviceDetailActivity.EXTRA_DEVICE, device)
})
}
deviceAdapter.setOnItemChildClickListener { adapter, view, position ->
deviceAdapter.setOnItemChildClickListener { _, view, position ->
when (view.id) {
R.id.img_tips -> {
R.id.img_more -> {
val device = deviceAdapter.data[position]
DeviceActionPopupWindow(requireContext()).show(
view,
onRename = {//重命名
onRename = {
showEditDialog(
"设备重命名", device.name, click = object : OnDialogClick {
title = "重命名设备",
label = "设备名称",
hint = device.name,
tip = "最多支持20个字符,支持中英文、数字和字符。",
confirmText = "确认修改",
click = object : OnDialogClick {
override fun onCancelClick() {
}
override fun onCancelClick() {}
override fun onConfirmClick(content: String) {
presenter?.renameDevice(device.deviceNo, content)
}
}
)
},
},
onUnbind = {//解绑设备
showDialogtext(
"提示",
@@ -87,6 +94,15 @@ class DeviceFragment : BaseFragment<FragmentHomeBinding, DevicePresenter>(),
}
)
}
R.id.tv_show_map -> {
val device = deviceAdapter.data[position]
// 没有经纬度时只展示空态,不要把空坐标传给地图
if (device.lat.isNullOrBlank() || device.lng.isNullOrBlank()) {
showMessage("暂无定位")
return@setOnItemChildClickListener
}
// 地图页后续接入,这里先预留点击入口
}
}
}
}
@@ -108,17 +124,23 @@ class DeviceFragment : BaseFragment<FragmentHomeBinding, DevicePresenter>(),
override fun onClick(view: View) {
when(view.id){
R.id.tv_index ->{
goTo(BindDeviceActivity::class.java,null)
}
//扫一扫:先申请相机和相册权限,授权通过后再拉起扫码页。
// 扫码页仅使用相机,隐藏旧库内部的相册入口。
R.id.img_scan -> {
getMorePermissionRequst(
scanPermissions(),
"相机和相册权限",
"扫一扫功能需要使用相机及访问相册权限",
REQUEST_CAMERA_AND_ALBUM
AddDevicePopupWindow(requireContext()).show(view,
onManualAdd = {
// 手动添加:进入绑定设备页
goTo(BindDeviceActivity::class.java, null)
},
onScan = {
// 扫一扫:先申请相机权限,授权后再打开扫码页
getMorePermissionRequst(
scanPermissions(),
"相机权限",
"扫一扫功能需要使用相机权限",
REQUEST_CAMERA_AND_ALBUM
)
}
)
}
@@ -206,7 +228,7 @@ class DeviceFragment : BaseFragment<FragmentHomeBinding, DevicePresenter>(),
}
/** 相机和相册均已授权后,自动打开扫一扫。 */
/** 相机授权后打开扫码页,库内相册按钮已关闭。 */
override fun onPermissionsGranted(permissions: Array<String>) {
startDeviceScan()
}
@@ -215,36 +237,50 @@ class DeviceFragment : BaseFragment<FragmentHomeBinding, DevicePresenter>(),
super.onPermissionsDenied(permissions, permanentlyDenied)
}
/** Android 13 及以上申请读图权限,更低版本申请存储读取权限。 */
/** 相册入口已隐藏,因此扫码流程只需要相机权限。 */
private fun scanPermissions(): Array<String> {
val albumPermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Manifest.permission.READ_MEDIA_IMAGES
} else {
Manifest.permission.READ_EXTERNAL_STORAGE
}
return arrayOf(Manifest.permission.CAMERA, albumPermission)
return arrayOf(Manifest.permission.CAMERA)
}
/** 使用 MNMLKitScanner 默认扫码页识别二维码或条形码。 */
private fun startDeviceScan() {
MNScanManager.startScan(mContext, object : MNScanCallback {
override fun onActivityResult(resultCode: Int, data: Intent) {
when (resultCode) {
MNScanManager.RESULT_SUCCESS -> {
val results = data.getStringArrayListExtra(MNScanManager.INTENT_KEY_RESULT_SUCCESS)
val content = results?.filter { it.isNotBlank() }?.joinToString("\n").orEmpty()
val config = MNScanConfig.Builder()
.isShowPhotoAlbum(false)
.builder()
MNScanManager.startScan(mContext, config, object : MNScanCallback {
override fun onActivityResult(resultCode: Int, data: Intent?) {
if (!isAdded) {
return
}
// 库把 RESULT_SUCCESS 定义为 0,和系统返回键的 RESULT_CANCELED 相同。
// 点关闭会走 RESULT_CANCLE=2 并带回空 Intent;按返回键 data 为 null。
when {
resultCode == MNScanManager.RESULT_SUCCESS && data != null -> {
val results = data.getStringArrayListExtra(
MNScanManager.INTENT_KEY_RESULT_SUCCESS
)
val content = results
?.filter { it.isNotBlank() }
?.joinToString("\n")
.orEmpty()
if (content.isBlank()) {
ToastUtil.showShort(mContext, "未识别到内容")
} else {
ToastUtil.showShort(mContext, content)
}
}
MNScanManager.RESULT_FAIL -> {
val resultError = data.getStringExtra(MNScanManager.INTENT_KEY_RESULT_ERROR)
ToastUtil.showShort(mContext, resultError.takeUnless { it.isNullOrBlank() } ?: "扫码失败")
resultCode == MNScanManager.RESULT_FAIL -> {
val resultError = data?.getStringExtra(
MNScanManager.INTENT_KEY_RESULT_ERROR
)
ToastUtil.showShort(
mContext,
resultError.takeUnless { it.isNullOrBlank() } ?: "扫码失败"
)
}
MNScanManager.RESULT_CANCLE -> {
ToastUtil.showShort(mContext, getString(R.string.cancel_scan))
else -> {
// 关闭按钮或系统返回,按取消处理
}
}
}

View File

@@ -0,0 +1,165 @@
package com.juhabi.birds.device.adapter
import android.widget.ProgressBar
import androidx.core.content.ContextCompat
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.viewholder.BaseViewHolder
import com.juhabi.birds.R
import com.juhabi.birds.device.entity.DeviceEntity
/** 临时设备列表适配器,后续可根据设计稿替换 item 展示。 */
class DeviceAdapter : BaseQuickAdapter<DeviceEntity, BaseViewHolder>(R.layout.device_item) {
init {
addChildClickViewIds(R.id.img_more)
}
override fun convert(holder: BaseViewHolder, item: DeviceEntity) {
//设备名字
holder.setText(R.id.tv_dev_name, item.name.ifBlank { "未命名设备" })
//在线状态
if (item.status == "1") {
holder.setText(R.id.tv_dev_online_status, "在线")
} else {
holder.setText(R.id.tv_dev_online_status, "离线")
}
val statusRes = if (item.status == "1") {
R.drawable.shape_device_online_bg
} else {
R.drawable.shape_device_offline_bg
}
holder.setBackgroundResource(
R.id.tv_dev_online_status,
statusRes
)
holder.setText(R.id.tv_dev_number, "IMEI:${item.deviceNo}")
if (item.status == "1") {
// 信号:0 离线、1 弱、2 一般、3 强。资源 id 必须赋给变量才会生效。
val imgResource: Int
val signalText: String
val wifiTextColor: Int
if (item.signalLevel == 0) {
imgResource = R.mipmap.icon_dev_wifi_offline
signalText = "离线"
wifiTextColor = R.color.color_999999
} else if (item.signalLevel == 1) {
imgResource = R.mipmap.icon_dev_wifi_low
signalText = "弱"
wifiTextColor = R.color.color_e34d59
} else if (item.signalLevel == 2) {
imgResource = R.mipmap.icon_dev_wifi_mid
signalText = "一般"
wifiTextColor = R.color.color_ff942a
} else {
imgResource = R.mipmap.icon_dev_wifi_hight
signalText = "强"
wifiTextColor = R.color.color_zhu
}
holder.setImageResource(R.id.img_signal, imgResource)
holder.setText(R.id.tv_signal_text, signalText)
holder.setTextColor(R.id.tv_signal_text,wifiTextColor)
holder.setText(
R.id.tv_head_foot_connect_status,
if (item.hoodConnected == "0") "断开" else "已连接"
)
}else{
holder.setImageResource(R.id.img_signal, R.mipmap.icon_dev_wifi_offline)
holder.setText(R.id.tv_signal_text, "离线")
holder.setTextColor(R.id.tv_signal_text, R.color.color_999999)
holder.setText(
R.id.tv_head_foot_connect_status,
if (item.hoodConnected == "0") "断开" else "已连接"
)
}
holder.setText(R.id.tv_head_power_value, "${item.hoodBattery}%")
holder.setText(R.id.tv_foot_power_value, "${item.ringBattery}%")
// 电量三档:0-30 低,31-70 中,71-100 高
val headBattery = (item.hoodBattery ?: 0).coerceIn(0, 100)
val footBattery = (item.ringBattery ?: 0).coerceIn(0, 100)
val isOnline = item.status == "1"
if (isOnline) {
holder.setImageResource(
R.id.img_head_power_icon,
getBatteryIcon(
battery = headBattery,
highIcon = R.mipmap.icon_power_head_high,
midIcon = R.mipmap.icon_power_head_mid
)
)
holder.setImageResource(
R.id.img_foot_power_icon,
getBatteryIcon(
battery = footBattery,
highIcon = R.mipmap.icon_power_foot_high,
midIcon = R.mipmap.icon_power_foot_mid
)
)
} else {
// 离线时头套、脚环电量图标统一显示离线态
holder.setImageResource(
R.id.img_head_power_icon,
R.mipmap.icon_dev_power_offline
)
holder.setImageResource(
R.id.img_foot_power_icon,
R.mipmap.icon_dev_power_offline
)
}
val progressHead = holder.getView<ProgressBar>(R.id.progress_head)
val progressFoot = holder.getView<ProgressBar>(R.id.progress_foot)
val headProgressRes = if (isOnline) {
R.drawable.progress_head_bg
} else {
R.drawable.progress_offline_bg
}
val footProgressRes = if (isOnline) {
R.drawable.progress_foot_bg
} else {
R.drawable.progress_offline_bg
}
// mutate 避免两条进度条共用同一份离线 drawable 状态
progressHead.progressDrawable = ContextCompat.getDrawable(
context,
headProgressRes
)?.mutate()
progressFoot.progressDrawable = ContextCompat.getDrawable(
context,
footProgressRes
)?.mutate()
// 替换 drawable 后进度会被重置,需要重新写入
progressHead.max = 100
progressHead.progress = headBattery
progressFoot.max = 100
progressFoot.progress = footBattery
//tv_location_detail 显示地址
holder.setText(R.id.tv_location_detail, item.address)
}
/**
* 按电量返回对应档位图标。
* 低电量头套、脚环共用 icon_power_low。
*/
private fun getBatteryIcon(
battery: Int,
highIcon: Int,
midIcon: Int
): Int {
return when {
battery <= BATTERY_LOW_MAX -> R.mipmap.icon_power_low
battery <= BATTERY_MID_MAX -> midIcon
else -> highIcon
}
}
private companion object {
const val BATTERY_LOW_MAX = 30
const val BATTERY_MID_MAX = 70
}
}

View File

@@ -3,19 +3,19 @@ package com.juhabi.birds.device.entity
import java.io.Serializable
data class DeviceEntity(
val address: String,
val deviceNo: String,
val hoodBattery: Int?,
val hoodConnected: String,
val hoodConnectedText: String,
val imei: String,
val lat: Double?,
val lng: Double?,
val model: String?,
val name: String,
val ringBattery: Int?,
val signalLevel: Int,
val signalText: String,
val status: String,
val hoodCoverOn: String
val address: String = "",
val deviceNo: String = "",
val hoodBattery: Int? = null,
val hoodConnected: String = "",
val hoodConnectedText: String = "",
val imei: String = "",
val lat: String? = null,
val lng: String? = null,
val model: String? = null,
val name: String = "",
val ringBattery: Int? = null,
val signalLevel: Int = 0,
val signalText: String = "",
val status: String = "",
val hoodCoverOn: String = ""
) : Serializable