列表,个人中心

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

@@ -2,7 +2,7 @@ package com.juhabi.birds
object AppConfig {
// const val BASE_URL = "http://47.97.217.123:8081"
const val BASE_URL = "http://192.168.1.217:8081/"
const val BASE_URL = "http://192.168.0.212:8081/"
const val APP_CLIENT_ID = "app"
const val APP_USER_TYPE = "app_user"
const val CONTENT_LANGUAGE = "zh_CN"

View File

@@ -10,6 +10,7 @@ import android.os.Bundle
import android.provider.Settings
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager
import android.widget.EditText
import android.widget.ImageView
@@ -279,10 +280,12 @@ abstract class BaseActivity<VB : ViewBinding, P : IPresenter<*>> :
dialog.show()
}
/** 显示带输入框的弹窗,输入为空时保留弹窗并提示。 */
/** 显示带输入框的弹窗,标题、标签、提示文案由调用方传入。 */
fun showEditDialog(
title: String,
hint: String,
label: String = "",
hint: String = "",
tip: String = "",
content: String = "",
touchOutsideCancel: Boolean = true,
cancelText: String = "取消",
@@ -291,6 +294,8 @@ abstract class BaseActivity<VB : ViewBinding, P : IPresenter<*>> :
) {
val view = layoutInflater.inflate(R.layout.dialog_input_text, null)
view.findViewById<TextView>(R.id.dialogTitle).text = title
bindOptionalText(view.findViewById(R.id.dialogLabel), label)
bindOptionalText(view.findViewById(R.id.dialogTip), tip)
val input = view.findViewById<EditText>(R.id.dialogInput).apply {
this.hint = hint
setText(content)
@@ -309,7 +314,7 @@ abstract class BaseActivity<VB : ViewBinding, P : IPresenter<*>> :
setOnClickListener {
val value = input.text.toString().trim()
if (value.isEmpty()) {
input.error = hint
input.error = hint.ifBlank { "请输入内容" }
return@setOnClickListener
}
dialog.dismiss()
@@ -321,6 +326,16 @@ abstract class BaseActivity<VB : ViewBinding, P : IPresenter<*>> :
dialog.show()
}
/** 文案为空时隐藏对应控件,避免留下空白行。 */
private fun bindOptionalText(textView: TextView, text: String) {
if (text.isBlank()) {
textView.visibility = View.GONE
} else {
textView.visibility = View.VISIBLE
textView.text = text
}
}
/** 在屏幕顶部显示权限用途提示弹窗。 */
fun showPermissionDialog(title: String, content: String) {
permissionDialog?.dismiss()

View File

@@ -280,10 +280,12 @@ abstract class BaseFragment<VB : ViewBinding, P : IPresenter<*>> : Fragment(), I
dialog.show()
}
/** 显示带输入框的弹窗,输入为空时保留弹窗并提示。 */
/** 显示带输入框的弹窗,标题、标签、提示文案由调用方传入。 */
fun showEditDialog(
title: String,
hint: String,
label: String = "",
hint: String = "",
tip: String = "",
content: String = "",
touchOutsideCancel: Boolean = true,
cancelText: String = "取消",
@@ -292,6 +294,8 @@ abstract class BaseFragment<VB : ViewBinding, P : IPresenter<*>> : Fragment(), I
) {
val view = layoutInflater.inflate(R.layout.dialog_input_text, null)
view.findViewById<TextView>(R.id.dialogTitle).text = title
bindOptionalText(view.findViewById(R.id.dialogLabel), label)
bindOptionalText(view.findViewById(R.id.dialogTip), tip)
val input = view.findViewById<EditText>(R.id.dialogInput).apply {
this.hint = hint
setText(content)
@@ -310,7 +314,7 @@ abstract class BaseFragment<VB : ViewBinding, P : IPresenter<*>> : Fragment(), I
setOnClickListener {
val value = input.text.toString().trim()
if (value.isEmpty()) {
input.error = hint
input.error = hint.ifBlank { "请输入内容" }
return@setOnClickListener
}
dialog.dismiss()
@@ -322,6 +326,16 @@ abstract class BaseFragment<VB : ViewBinding, P : IPresenter<*>> : Fragment(), I
dialog.show()
}
/** 文案为空时隐藏对应控件,避免留下空白行。 */
private fun bindOptionalText(textView: TextView, text: String) {
if (text.isBlank()) {
textView.visibility = View.GONE
} else {
textView.visibility = View.VISIBLE
textView.text = text
}
}
/** 在屏幕顶部显示权限用途提示弹窗。 */
fun showPermissionDialog(title: String, content: String) {
permissionDialog?.dismiss()

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

View File

@@ -4,11 +4,19 @@ import android.os.Bundle
import android.os.CountDownTimer
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.gyf.immersionbar.ImmersionBar
import com.juhabi.birds.AppConfig
import com.juhabi.birds.R
import com.juhabi.birds.base.BaseActivity
import com.juhabi.birds.databinding.RegisterLayoutBinding
import com.juhabi.birds.databinding.DialogCountryCodeBinding
import com.juhabi.birds.login.adapter.CountryCallingCode
import com.juhabi.birds.login.adapter.CountryCodeAdapter
import com.juhabi.birds.login.model.UserEntity
import com.juhabi.birds.login.mvp.LoginContract
import com.juhabi.birds.login.mvp.LoginImpl
@@ -47,6 +55,7 @@ class RegisterActivity : BaseActivity<RegisterLayoutBinding, LoginPresenter>(),
binding.getCodeButton,
binding.confirmButton,
binding.includedTitle.imgBack,
binding.linearCountry,
).forEach { button ->
button.setOnClickListener(this)
}
@@ -59,6 +68,8 @@ class RegisterActivity : BaseActivity<RegisterLayoutBinding, LoginPresenter>(),
finish()
}
R.id.linear_country -> showCountryCodeDialog()
R.id.getCodeButton -> {//获取验证码
if (binding.phoneEditText.text.toString().isBlank()) {
ToastUtil.showShort(mContext, "手机号不能为空")
@@ -109,6 +120,44 @@ class RegisterActivity : BaseActivity<RegisterLayoutBinding, LoginPresenter>(),
}
}
private fun showCountryCodeDialog() {
val dialogBinding = DialogCountryCodeBinding.inflate(layoutInflater)
val dialog = AlertDialog.Builder(this)
.setView(dialogBinding.root)
.create()
dialogBinding.countryRecyclerView.apply {
layoutManager = LinearLayoutManager(this@RegisterActivity)
adapter = CountryCodeAdapter(loadCountryCallingCodes()) { country ->
binding.areaCodeButton.text = country.dial_code
dialog.dismiss()
}
}
dialog.setOnShowListener {
dialog.window?.apply {
setBackgroundDrawableResource(android.R.color.transparent)
setLayout(
(resources.displayMetrics.widthPixels * 0.88f).toInt(),
ViewGroup.LayoutParams.WRAP_CONTENT
)
}
}
dialog.show()
}
private fun loadCountryCallingCodes(): List<CountryCallingCode> {
val language = resources.configuration.locales[0].language
val fileName = when (language) {
"ar" -> "country_calling_codes_ar.json"
"en" -> "country_calling_codes_en.json"
else -> "country_calling_codes.json"
}
val json = assets.open(fileName).bufferedReader().use { it.readText() }
val type = object : TypeToken<List<CountryCallingCode>>() {}.type
return Gson().fromJson(json, type)
}
override fun showError(message: String) {
hideLoading()
ToastUtil.showShort(mContext, message)

View File

@@ -0,0 +1,41 @@
package com.juhabi.birds.login.adapter
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.juhabi.birds.databinding.ItemCountryCodeBinding
data class CountryCallingCode(
val code: String,
val name: String,
val dial_code: String
)
class CountryCodeAdapter(
private val countries: List<CountryCallingCode>,
private val onItemClick: (CountryCallingCode) -> Unit
) : RecyclerView.Adapter<CountryCodeAdapter.CountryViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CountryViewHolder {
return CountryViewHolder(
ItemCountryCodeBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
}
override fun onBindViewHolder(holder: CountryViewHolder, position: Int) {
holder.bind(countries[position])
}
override fun getItemCount(): Int = countries.size
inner class CountryViewHolder(
private val binding: ItemCountryCodeBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(country: CountryCallingCode) {
binding.countryNameTextView.text = country.name
binding.countryDialCodeTextView.text = country.dial_code
binding.root.setOnClickListener { onItemClick(country) }
}
}
}

View File

@@ -2,7 +2,6 @@ package com.juhabi.birds.main
import android.os.Bundle
import android.view.LayoutInflater
import androidx.annotation.ColorRes
import androidx.core.content.ContextCompat
import com.juhabi.birds.R
import com.juhabi.birds.base.BaseActivity
@@ -80,12 +79,9 @@ class MainActivity : BaseActivity<ActivityMainBinding, MainPresenter>(),
}
private fun updateTopBarColor(position: Int) {
@ColorRes val colorRes = when (position) {
PAGE_HOME -> R.color.color_zhu
PAGE_MINE -> R.color.color_B85CE6
else -> R.color.color_zhu
}
binding.statusBarView.setBackgroundColor(ContextCompat.getColor(this, colorRes))
binding.statusBarView.setBackgroundColor(
ContextCompat.getColor(this, R.color.color_zhu)
)
}
override fun initData(savedInstanceState: Bundle?) {

View File

@@ -123,8 +123,8 @@ class MineCenterActivity : BaseActivity<ActivityMineCenterBinding, MinePresenter
R.id.inputDialogButton -> {
showEditDialog(
"请输入内容",
"请输入测试内容",
title = "请输入内容",
hint = "请输入测试内容",
click = object : OnDialogClick {
override fun onCancelClick() {
showMessage("已取消输入")
@@ -190,13 +190,12 @@ class MineCenterActivity : BaseActivity<ActivityMineCenterBinding, MinePresenter
}
override fun getUserInfoSuccess(userInfo: MineUserInfo) {
val user = userInfo.user ?: return showError("个人信息为空")
binding.nicknameTextView.text = user.nickName.ifBlank { "未设置昵称" }
binding.phoneTextView.text = "手机号:${user.phonenumber.ifBlank { "未绑定" }}"
binding.emailTextView.text = "邮箱:${user.email.ifBlank { "未绑定" }}"
binding.userIdTextView.text = "用户 ID:${user.userId}"
binding.userTypeTextView.text = "用户类型:${user.userType}"
binding.loginDateTextView.text = "最近登录:${user.loginDate.ifBlank { "暂无" }}"
binding.nicknameTextView.text = userInfo.nickName.ifBlank { "未设置昵称" }
binding.phoneTextView.text = "手机号:${userInfo.phoneNumber?.ifBlank { null } ?: "未绑定"}"
binding.emailTextView.text = "邮箱:${userInfo.email?.ifBlank { null } ?: "未绑定"}"
binding.userIdTextView.text = "账号:${userInfo.userName.ifBlank { "暂无" }}"
binding.userTypeTextView.text = "用户类型:${userInfo.userType.ifBlank { "暂无" }}"
binding.loginDateTextView.text = "注册时间:${userInfo.createTime.ifBlank { "暂无" }}"
}
override fun showError(message: String) {

View File

@@ -2,23 +2,27 @@ package com.juhabi.birds.mine
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.juhabi.birds.base.BaseFragment
import com.juhabi.birds.databinding.FragmentMineBinding
import com.juhabi.birds.mine.model.MineUserInfo
import com.juhabi.birds.R
import com.juhabi.birds.databinding.MineCenterLayoutBinding
import com.juhabi.birds.mine.ModifyPsdActivity
import com.juhabi.birds.main.model.AppVersionInfo
import com.juhabi.birds.mine.mvp.MineContract
import com.juhabi.birds.mine.mvp.MineImpl
import com.juhabi.birds.mine.mvp.MinePresenter
import com.juhabi.birds.util.manager.UserInfoManager
/**
* 个人中心
*/
class MineFragment : BaseFragment<FragmentMineBinding, MinePresenter>(),
MineContract.MineView {
class MineFragment : BaseFragment<MineCenterLayoutBinding, MinePresenter>(),
MineContract.MineView, View.OnClickListener {
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentMineBinding {
return FragmentMineBinding.inflate(inflater, container, false)
override fun createBinding(inflater: LayoutInflater, container: ViewGroup?): MineCenterLayoutBinding {
return MineCenterLayoutBinding.inflate(inflater, container, false)
}
override fun createPresenter(): MinePresenter {
@@ -32,11 +36,68 @@ class MineFragment : BaseFragment<FragmentMineBinding, MinePresenter>(),
}
override fun initView(savedInstanceState: Bundle?) {
listOf(
binding.linearXgmm,
binding.linearGhsj,
binding.linearQchc,
binding.linearBbxx,
binding.linearYsxy,
binding.linearYhxy,
binding.linearQhyy,
binding.linearZxzh,
binding.tvLogout,
).forEach { button ->
button.setOnClickListener(this)
}
}
override fun onClick(view: View) {
when (view.id) {
//修改密码
R.id.linear_xgmm -> {
goTo(ModifyPsdActivity::class.java, null)
}
R.id.linear_ghsj -> {
goTo(UpdateUserAccount::class.java, null)
}
//退出登录
R.id.tv_logout -> {
showDialogtext(
"退出登录",
"确定要退出当前账号吗?",
"确定",
"取消",
object : OnDialogClick {
override fun onCancelClick() {
}
override fun onConfirmClick() {
// 退出登录接口后续接入
}
}
)
}
}
}
/** 将个人信息请求结果更新到个人中心页面。 */
override fun getUserInfoSuccess(userInfo: MineUserInfo) {
showMessage("个人信息获取成功")
val displayName = userInfo.userName.ifBlank { "未设置用户名" }
val displayAccount = userInfo.phoneNumber
?.ifBlank { null }
?: "未绑定手机号"
binding.tvUserName.text = displayName
binding.tvUserAccount.text = displayAccount
if (userInfo.avatar.isNullOrBlank()) {
binding.imgAvatar.setImageResource(R.mipmap.icon_mine_user_head_defaul)
}
//存一下个人信息
UserInfoManager.setUserInfo(userInfo)
}
/** 版本检测回调,个人中心暂不处理版本信息。 */

View File

@@ -39,6 +39,7 @@ class ModifyPsdActivity : BaseActivity<ModifyPsdLayoutBinding, MinePresenter>(),
override fun initView(savedInstanceState: Bundle?) {
binding.includedTitle.tvTitle.text = "修改密码"
listOf(
binding.oldPsdVisibilityButton,
binding.passwordVisibilityButton,

View File

@@ -0,0 +1,87 @@
package com.juhabi.birds.mine
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import com.gyf.immersionbar.ImmersionBar
import com.juhabi.birds.R
import com.juhabi.birds.base.BaseActivity
import com.juhabi.birds.databinding.ModifyPsdLayoutBinding
import com.juhabi.birds.databinding.UpdateUserPhoneLayoutBinding
import com.juhabi.birds.main.model.AppVersionInfo
import com.juhabi.birds.mine.model.MineUserInfo
import com.juhabi.birds.mine.mvp.MineContract
import com.juhabi.birds.mine.mvp.MineImpl
import com.juhabi.birds.mine.mvp.MinePresenter
import com.juhabi.birds.util.manager.UserInfoManager
/**
* 修改手机号
*/
class UpdateUserAccount : BaseActivity<UpdateUserPhoneLayoutBinding, MinePresenter>(),
MineContract.MineView, View.OnClickListener {
override fun createBinding(inflater: LayoutInflater): UpdateUserPhoneLayoutBinding {
return UpdateUserPhoneLayoutBinding.inflate(inflater)
}
override fun createPresenter(): MinePresenter? {
return MinePresenter(MineImpl())
}
override fun initWindows() {
ImmersionBar.with(this)
.statusBarView(binding.statusBarView)
.statusBarDarkFont(true)
.navigationBarColor(R.color.color_zhu)
.navigationBarDarkIcon(true)
.init()
}
override fun initView(savedInstanceState: Bundle?) {
binding.includedTitle.tvTitle.text = "修改密码"
var userinfo: MineUserInfo? = UserInfoManager.getUserInfo(mContext);
binding.tvUserAccount.text = userinfo?.nickName
listOf(
binding.tvConfirmGetcode,
binding.tvConfirmAccount,
binding.tvNewPhoneGetcode,
binding.tvSubmitNewAccount,
binding.includedTitle.imgBack,
).forEach { button ->
button.setOnClickListener(this)
}
}
override fun onClick(view: View) {
when(view.id){
R.id.img_back ->{
finish()
}
R.id.tv_confirm_getcode ->{
}
R.id.tv_confirmAccount ->{
}
R.id.tv_new_phone_getcode ->{
}
R.id.tv_submit_new_account ->{
}
}
}
override fun showError(message: String) {
}
override fun getUserInfoSuccess(userInfo: MineUserInfo) {
}
override fun onVersionChecked(versionInfo: AppVersionInfo) {
}
}

View File

@@ -1,24 +1,15 @@
package com.juhabi.birds.mine.model
/**
* 3.1 获取当前用户资料接口返回项。
*/
data class MineUserInfo(
val user: User? = null,
val permissions: List<Any> = emptyList(),
val roles: List<Any> = emptyList(),
val url: String? = null
) {
data class User(
val userId: String = "",
val tenantId: String = "",
val userName: String? = null,
val nickName: String = "",
val userType: String = "",
val email: String = "",
val phonenumber: String = "",
val sex: String = "",
val avatar: String? = null,
val status: String = "",
val loginIp: String = "",
val loginDate: String = "",
val createTime: String = ""
)
}
val userName: String = "",
val nickName: String = "",
val phoneNumber: String? = null,
val email: String? = null,
val phoneBound: Boolean = false,
val userType: String = "",
val createTime: String = "",
val avatar: String? = null
)

View File

@@ -2,14 +2,14 @@ package com.juhabi.birds.util.manager
import android.content.Context
import com.juhabi.birds.MyApplication
import com.juhabi.birds.mine.model.MineUserInfo
import com.juhabi.birds.network.TokenManager
import com.juhabi.birds.user.model.UserEntity
import com.juhabi.birds.util.SharedPreferencesUtil
/** 管理当前用户的内存缓存和轻量级本地持久化数据。 */
object UserInfoManager {
@Volatile
private var cachedUser: UserEntity? = null
private var cachedUser: MineUserInfo? = null
@JvmStatic
fun getInstance(): UserInfoManager = this
@@ -21,42 +21,42 @@ object UserInfoManager {
cachedUser = SharedPreferencesUtil.getObject(
context,
KEY_USER_INFO,
UserEntity::class.java
MineUserInfo::class.java
)
}
@JvmStatic
fun getUserInfo(): UserEntity? = getUserInfo(MyApplication.appContext)
fun getUserInfo(): MineUserInfo? = getUserInfo(MyApplication.appContext)
@JvmStatic
fun getUserInfo(context: Context): UserEntity? {
fun getUserInfo(context: Context): MineUserInfo? {
cachedUser?.let { return it }
return SharedPreferencesUtil.getObject(
context,
KEY_USER_INFO,
UserEntity::class.java
MineUserInfo::class.java
)?.also { cachedUser = it }
}
@JvmStatic
fun setUserInfo(userInfo: UserEntity) {
fun setUserInfo(userInfo: MineUserInfo) {
setUserInfo(MyApplication.appContext, userInfo)
}
@JvmStatic
fun setUserInfo(context: Context, userInfo: UserEntity) {
fun setUserInfo(context: Context, userInfo: MineUserInfo) {
cachedUser = userInfo
SharedPreferencesUtil.putObject(context, KEY_USER_INFO, userInfo)
}
/** 登录成功后统一保存用户信息和 token。 */
@JvmStatic
fun saveLogin(userInfo: UserEntity, token: String) {
fun saveLogin(userInfo: MineUserInfo, token: String) {
saveLogin(MyApplication.appContext, userInfo, token)
}
@JvmStatic
fun saveLogin(context: Context, userInfo: UserEntity, token: String) {
fun saveLogin(context: Context, userInfo: MineUserInfo, token: String) {
setUserInfo(context, userInfo)
TokenManager.save(context, token)
SharedPreferencesUtil.putBoolean(context, KEY_USER_LOGIN, token.isNotBlank())