Files
SecurityDefenseDevice/app/src/main/java/com/example/defenseapplication/home/HomeMenuFragment.kt
2026-05-18 17:39:30 +08:00

523 lines
20 KiB
Kotlin

package com.example.defenseapplication.home
import android.Manifest
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Color
import android.os.Build
import android.graphics.Rect
import android.graphics.drawable.GradientDrawable
import android.os.Bundle
import android.util.Log
import android.util.TypedValue
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.defenseapplication.R
import com.example.defenseapplication.adapter.DeviceAdapter
import com.example.defenseapplication.adapter.DeviceUi
import com.example.defenseapplication.adapter.FamilyAdapter
import com.example.defenseapplication.bean.DeviceBean
import com.example.defenseapplication.bean.FamilyBean
import com.example.defenseapplication.bean.SwitchFamilyRequest
import com.example.defenseapplication.databinding.HomeMenuFragmentBinding
import com.example.defenseapplication.utils.DeviceIdEvent
import com.example.defenseapplication.utils.FamilySwitchEvent
import com.example.defenseapplication.utils.MessageStatusManager
import com.example.defenseapplication.utils.RetrofitNetwork
import com.example.defenseapplication.utils.UserinfoUtil
import com.example.defenseapplication.view.DefenseDialogs
import com.example.defenseapplication.view.showDefenseDialog
import com.gyf.immersionbar.ImmersionBar
import com.maning.mlkitscanner.scan.MNScanManager
import com.maning.mlkitscanner.scan.callback.act.MNScanCallback
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
//主页碎片
class HomeMenuFragment : Fragment() {
// ViewBinding
private var _binding: HomeMenuFragmentBinding? = null
private val binding get() = _binding!!
private val familyAdapter by lazy { FamilyAdapter(::onFamilySelected) }
private val deviceAdapter by lazy { DeviceAdapter(::onDeviceClicked) }
private var isFamilyExpanded = false
private lateinit var currentFamily: String
private var currentPage = 1
private val pageSize = 10
private var hasMore = true
private var isLoading = false
private val requestPermissionsLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
val cameraGranted = permissions[Manifest.permission.CAMERA] ?: false
val storageGranted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
permissions[Manifest.permission.READ_MEDIA_IMAGES] ?: false
} else {
permissions[Manifest.permission.READ_EXTERNAL_STORAGE] ?: false
}
if (cameraGranted && storageGranted) {
startScan()
} else {
Toast.makeText(requireContext(), R.string.permission_denied, Toast.LENGTH_SHORT).show()
}
}
private fun checkPermissionsAndScan() {
val cameraPermission = ContextCompat.checkSelfPermission(
requireContext(),
Manifest.permission.CAMERA
)
val storagePermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
ContextCompat.checkSelfPermission(
requireContext(),
Manifest.permission.READ_MEDIA_IMAGES
)
} else {
ContextCompat.checkSelfPermission(
requireContext(),
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
}
if (cameraPermission == PackageManager.PERMISSION_GRANTED &&
storagePermission == PackageManager.PERMISSION_GRANTED) {
startScan()
} else {
val permissionsToRequest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.READ_MEDIA_IMAGES
)
} else {
arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
}
requestPermissionsLauncher.launch(permissionsToRequest)
}
}
private fun startScan() {
MNScanManager.startScan(requireContext() as Activity, object: MNScanCallback {
override fun onActivityResult(resultCode: Int, data: Intent?) {
when (resultCode) {
MNScanManager.RESULT_SUCCESS -> {
val result = data?.getStringArrayListExtra(MNScanManager.INTENT_KEY_RESULT_SUCCESS)
if (!result.isNullOrEmpty()) {
ToastUtils.showToast(requireContext(), "扫码成功: $result")
} else {
ToastUtils.showToast(requireContext(), "扫码结果为空")
}
}
MNScanManager.RESULT_FAIL -> {
val error = data?.getStringExtra(MNScanManager.INTENT_KEY_RESULT_ERROR)
ToastUtils.showToast(requireContext(), "扫码失败: $error")
}
MNScanManager.RESULT_CANCLE -> {
ToastUtils.showToast(requireContext(), "扫码取消")
}
}
}
})
}
private val unreadCountChangeListener = object : MessageStatusManager.UnreadCountChangeListener {
override fun onUnreadCountChanged(count: Int) {
updateUnreadCountUI(count)
}
}
private val familySwitchListener = object : FamilySwitchEvent.FamilySwitchListener {
override fun onFamilySwitched(familyName: String) {
if (familyName != currentFamily) {
currentFamily = familyName
updateFamilyDisplayText(familyName)
familyAdapter.submitList(familyBeanList, currentFamily)
switchToFamily(familyName)
}
}
}
private fun updateFamilyDisplayText(familyName: String) {
val familyBean = familyBeanList.find { it.parentName == familyName }
val isAdmin = familyBean?.role == "appadmin"
binding.tvCurrentFamily.text = if (isAdmin) {
getString(R.string.my_family)
} else {
"${familyName}${getString(R.string.family_with_normal)}"
}
}
private var familyList: List<String> = emptyList()
private var familyBeanList: List<FamilyBean> = emptyList()
private var deviceBeanList: List<DeviceBean> = emptyList()
private var canViewDevice = false
private val currentFamilyBean: FamilyBean?
get() = familyBeanList.find { it.parentName == currentFamily }
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = HomeMenuFragmentBinding.inflate(inflater, container, false)
ImmersionBar.with(this)
.statusBarDarkFont(true)
.titleBar(binding.main)
.init()
initView()
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 为 LinearLayout 设置渐变背景
setGradientBackground(
view = binding.gradientLayout,
startColor = Color.parseColor("#EBDCED"),
endColor = Color.parseColor("#CAF4FF")
)
binding.message.setOnClickListener {
val intent = Intent(requireContext(), MessageActivity::class.java)
startActivity(intent)
}
binding.add.setOnClickListener {
val intent = Intent(requireContext(), AddDeviceActivity::class.java)
startActivity(intent)
}
binding.addFamily.setOnClickListener {
checkPermissionsAndScan()
}
fetchFamilyList()
fetchUnreadMessageCount()
MessageStatusManager.addListener(unreadCountChangeListener)
FamilySwitchEvent.addListener(familySwitchListener)
updateUnreadCountUI(MessageStatusManager.getUnreadCount())
}
private fun initView() {
binding.rvFamilyList.layoutManager = LinearLayoutManager(requireContext())
binding.rvFamilyList.adapter = familyAdapter
binding.rvDeviceList.layoutManager = GridLayoutManager(requireContext(), 2)
binding.rvDeviceList.adapter = deviceAdapter
if (binding.rvDeviceList.itemDecorationCount == 0) {
binding.rvDeviceList.addItemDecoration(
GridHorizontalSpaceItemDecoration(horizontalSpace = dpToPx(8))
)
}
binding.swipeRefreshLayout.setOnRefreshListener {
refreshDeviceList()
}
binding.rvDeviceList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val layoutManager = recyclerView.layoutManager as GridLayoutManager
val lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition()
val totalItemCount = layoutManager.itemCount
if (!isLoading && hasMore && lastVisibleItemPosition >= totalItemCount - 1 && dy > 0) {
loadMoreDevices()
}
}
})
binding.familyHeaderLayout.setOnClickListener {
toggleFamilyList()
}
binding.familyPopupMask.setOnClickListener {
if (isFamilyExpanded) {
toggleFamilyList()
}
}
}
private fun fetchUnreadMessageCount() {
CoroutineScope(Dispatchers.IO).launch {
try {
val response = RetrofitNetwork.getNetworkService().getMessage()
withContext(Dispatchers.Main) {
if (response.code == 200 && response.data != null) {
val messageGroups = response.data as List<*>
var unreadCount = 0
messageGroups.forEach { group ->
if (group is Map<*, *>) {
val list = group["list"] as? List<*>
list?.let { unreadCount += it.size }
}
}
MessageStatusManager.setUnreadCount(unreadCount)
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
private fun updateUnreadCountUI(count: Int) {
if (count > 0) {
binding.tvUnreadCount.visibility = View.VISIBLE
binding.tvUnreadCount.text = if (count > 99) "99+" else count.toString()
} else {
binding.tvUnreadCount.visibility = View.GONE
}
}
private fun fetchFamilyList() {
CoroutineScope(Dispatchers.IO).launch {
try {
val response = RetrofitNetwork.getNetworkService().getFamilyList()
withContext(Dispatchers.Main) {
if (response.code == 200 && response.data != null) {
familyBeanList = response.data.reversed()
familyList = response.data.map { it.parentName }.reversed()
if (familyList.isNotEmpty()) {
currentFamily = familyList.first()
updateFamilyDisplayText(currentFamily)
familyAdapter.submitList(familyBeanList, currentFamily)
val userInfo = UserinfoUtil.getUserInfo()
val currentFamilyData = familyBeanList.firstOrNull()
canViewDevice = userInfo?.role == "appadmin" && currentFamilyData?.role != "member"
updateAdminUI()
switchToFamily(currentFamily)
}
} else {
Toast.makeText(requireContext(), response.msg, Toast.LENGTH_SHORT).show()
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
Toast.makeText(requireContext(), R.string.network_error, Toast.LENGTH_SHORT).show()
}
}
}
}
private fun updateAdminUI() {
Log.d("HomeMenuFragment", "canViewDevice = $canViewDevice")
if (canViewDevice) {
binding.tvDeviceTitle.visibility = View.VISIBLE
binding.rvDeviceList.visibility = View.VISIBLE
binding.message.visibility = View.VISIBLE
binding.addFamily.visibility = View.VISIBLE
binding.add.visibility = View.VISIBLE
binding.layoutNoPermission.visibility = View.GONE
} else {
binding.tvDeviceTitle.visibility = View.GONE
binding.rvDeviceList.visibility = View.GONE
binding.message.visibility = View.GONE
binding.addFamily.visibility = View.GONE
binding.add.visibility = View.GONE
binding.layoutNoPermission.visibility = View.VISIBLE
}
}
private fun switchToFamily(familyName: String) {
val familyBean = familyBeanList.find { it.parentName == familyName } ?: return
CoroutineScope(Dispatchers.IO).launch {
try {
val userInfo = UserinfoUtil.getUserInfo()
val request = SwitchFamilyRequest(
clientId = "428a8310cd442757ae699df5d894f051",
grantType = "switch",
id = familyBean.id,
userType = "app_user",
role = userInfo?.role ?: ""
)
val response = RetrofitNetwork.getNetworkService().switchFamily(request)
withContext(Dispatchers.Main) {
if (response.code == 200) {
val userInfo = UserinfoUtil.getUserInfo()
canViewDevice = userInfo?.role == "appadmin" && familyBean.role != "member"
updateAdminUI()
updateFamilyDisplayText(familyName)
fetchDeviceList()
FamilySwitchEvent.notifyFamilySwitched(familyName)
} else {
Toast.makeText(requireContext(), response.msg, Toast.LENGTH_SHORT).show()
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
Toast.makeText(requireContext(), R.string.network_error, Toast.LENGTH_SHORT).show()
}
}
}
}
private fun fetchDeviceList(page: Int = 1, isLoadMore: Boolean = false) {
if (!canViewDevice || isLoading) return
isLoading = true
CoroutineScope(Dispatchers.IO).launch {
try {
val response = RetrofitNetwork.getNetworkService().getDeviceList()
withContext(Dispatchers.Main) {
Log.d("DeviceList", "code = ${response.code}, data = ${response.data}")
if (response.code == 200 && response.data != null) {
val newDevices = response.data
if (isLoadMore) {
deviceBeanList = deviceBeanList + newDevices
hasMore = newDevices.size >= pageSize
} else {
deviceBeanList = newDevices
hasMore = newDevices.size >= pageSize
}
Log.d("DeviceList", "deviceBeanList size = ${deviceBeanList.size}")
updateDeviceList()
} else {
Toast.makeText(requireContext(), response.msg, Toast.LENGTH_SHORT).show()
if (isLoadMore) {
hasMore = false
}
}
isLoading = false
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
Toast.makeText(requireContext(), R.string.network_error, Toast.LENGTH_SHORT).show()
isLoading = false
if (isLoadMore) {
hasMore = false
}
}
}
}
}
private fun refreshDeviceList() {
currentPage = 1
hasMore = true
fetchDeviceList(currentPage, false)
binding.swipeRefreshLayout.isRefreshing = false
}
private fun loadMoreDevices() {
if (isLoading || !hasMore) return
currentPage++
fetchDeviceList(currentPage, true)
}
private fun toggleFamilyList() {
isFamilyExpanded = !isFamilyExpanded
val visibility = if (isFamilyExpanded) View.VISIBLE else View.GONE
binding.familyPopupMask.visibility = visibility
binding.familyPopupCard.visibility = visibility
binding.ivFamilyArrow.animate()
.rotation(if (isFamilyExpanded) 180f else 0f)
.setDuration(200L)
.start()
}
private fun onFamilySelected(familyName: String) {
currentFamily = familyName
binding.tvCurrentFamily.text = familyName
familyAdapter.submitList(familyBeanList, currentFamily)
switchToFamily(familyName)
if (isFamilyExpanded) {
toggleFamilyList()
}
}
private fun updateDeviceList() {
val deviceUiList = deviceBeanList.map {
DeviceUi(it.deviceName, it.status, it.id)
}
if (deviceUiList.isEmpty()) {
deviceAdapter.submitList(listOf(
DeviceUi(getString(R.string.no_device), "0")
))
} else {
deviceAdapter.submitList(deviceUiList)
}
}
private fun onDeviceClicked(device: DeviceUi) {
if (device.isOnline) {
DeviceIdEvent.setDeviceId(device.deviceId)
val intent = Intent(requireContext(), com.example.defenseapplication.liveVideo.LiveVideoActivity::class.java)
intent.putExtra("device_name", device.name)
startActivity(intent)
} else {
showDefenseDialog(
config = DefenseDialogs.deviceOfflineConfig(requireContext())
)
}
}
private fun dpToPx(dp: Int): Int {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dp.toFloat(),
resources.displayMetrics
).toInt()
}
private class GridHorizontalSpaceItemDecoration(
private val horizontalSpace: Int
) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val position = parent.getChildAdapterPosition(view)
if (position == RecyclerView.NO_POSITION) return
if (position % 2 == 0) {
outRect.right = horizontalSpace / 2
} else {
outRect.left = horizontalSpace / 2
}
}
}
private fun setGradientBackground(
view: View,
startColor: Int,
endColor: Int,
orientation: GradientDrawable.Orientation = GradientDrawable.Orientation.TL_BR
) {
val gradientDrawable = GradientDrawable(orientation, intArrayOf(startColor, endColor))
gradientDrawable.gradientType = GradientDrawable.LINEAR_GRADIENT
view.background = gradientDrawable
}
override fun onDestroyView() {
super.onDestroyView()
binding.rvFamilyList.adapter = null
binding.rvDeviceList.adapter = null
MessageStatusManager.removeListener(unreadCountChangeListener)
FamilySwitchEvent.removeListener(familySwitchListener)
_binding = null // 避免内存泄漏
}
}