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 = emptyList() private var familyBeanList: List = emptyList() private var deviceBeanList: List = 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 // 避免内存泄漏 } }