From 42cd7cb06a1076749d366580afd5c6205f0a3c89 Mon Sep 17 00:00:00 2001
From: cloudroam <cloudroam>
Date: 星期三, 02 四月 2025 10:38:07 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'

---
 app/src/main/java/com/example/firstapp/ui/dashboard/DashboardFragment.kt |  612 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 600 insertions(+), 12 deletions(-)

diff --git a/app/src/main/java/com/example/firstapp/ui/dashboard/DashboardFragment.kt b/app/src/main/java/com/example/firstapp/ui/dashboard/DashboardFragment.kt
index 0be390f..21cb233 100644
--- a/app/src/main/java/com/example/firstapp/ui/dashboard/DashboardFragment.kt
+++ b/app/src/main/java/com/example/firstapp/ui/dashboard/DashboardFragment.kt
@@ -1,42 +1,630 @@
 package com.example.firstapp.ui.dashboard
 
+import com.example.firstapp.R
 import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import android.widget.TextView
 import androidx.fragment.app.Fragment
-import androidx.lifecycle.ViewModelProvider
+import androidx.fragment.app.viewModels
 import com.example.firstapp.databinding.FragmentDashboardBinding
+import com.google.android.material.tabs.TabLayout
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.example.firstapp.adapter.PackageAdapter
+import com.github.mikephil.charting.charts.BarChart
+import com.github.mikephil.charting.charts.PieChart
+import com.github.mikephil.charting.components.Legend
+import com.github.mikephil.charting.components.XAxis
+import com.github.mikephil.charting.data.*
+import com.github.mikephil.charting.formatter.ValueFormatter
+import java.util.*
+import java.text.SimpleDateFormat
+import android.graphics.Color
+import android.widget.Button
+import android.widget.GridLayout
+import android.widget.Toast
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.lifecycleScope
+import com.bumptech.glide.Glide
+import com.example.firstapp.database.response.UserInfo
+import com.example.firstapp.database.service.RetrofitClient
+import com.example.firstapp.model.DailyStat
+import com.example.firstapp.utils.PreferencesManager
+import com.github.mikephil.charting.components.YAxis
+import kotlinx.coroutines.launch
 
 class DashboardFragment : Fragment() {
 
     private var _binding: FragmentDashboardBinding? = null
-
-    // This property is only valid between onCreateView and
-    // onDestroyView.
     private val binding get() = _binding!!
+    private val packageAdapter = PackageAdapter()
+    private var currentDate = Calendar.getInstance()
+    private var currentDateType = DateType.DAY
+    private lateinit var barChart: BarChart
+    private lateinit var pieChart: PieChart
+    private lateinit var heatmapView: View
+    private var currentUserInfo: UserInfo? = null // 确保使用你的实际数据类
+
+
+    enum class DateType {
+        DAY, WEEK, MONTH, YEAR
+    }
+    private val viewModel: DashboardViewModel by viewModels()
 
     override fun onCreateView(
         inflater: LayoutInflater,
         container: ViewGroup?,
         savedInstanceState: Bundle?
     ): View {
-        val dashboardViewModel =
-            ViewModelProvider(this).get(DashboardViewModel::class.java)
-
         _binding = FragmentDashboardBinding.inflate(inflater, container, false)
-        val root: View = binding.root
+        return binding.root
+    }
 
-        val textView: TextView = binding.textDashboard
-        dashboardViewModel.text.observe(viewLifecycleOwner) {
-            textView.text = it
+    override fun onResume() {
+        super.onResume()
+        // 重新加载用户信息
+        loadUserInfo()
+    }
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+
+        //渲染包裹列表
+        setupRecyclerView()
+        //初始化tab内容和数据
+        setupTabLayout()
+        //日期调整
+        setupDatePicker()
+        setupView(view)
+        updateDateDisplay()
+        loadPackages()
+
+        // 遮罩层
+        loadUserInfo()
+
+        // 遮罩层点击时间
+        binding.overlayContent.setOnClickListener {
+            // 跳转到vipActivity
+            val intent = android.content.Intent(requireContext(), com.example.firstapp.activity.VipActivity::class.java)
+            startActivity(intent)
         }
-        return root
+    }
+
+    private fun showOverlay() {
+        binding.viewOverlay.visibility = View.VISIBLE
+        binding.overlayContent.visibility = View.VISIBLE
+
+    }
+
+    private fun hiddleOverlay() {
+        binding.viewOverlay.visibility = View.GONE
+        binding.overlayContent.visibility = View.GONE
+
+
+    }
+
+
+    private fun setupRecyclerView() {
+        binding.recyclerPackages.apply {
+            layoutManager = LinearLayoutManager(context)
+            adapter = packageAdapter
+        }
+    }
+
+    private fun setupTabLayout() {
+        binding.tabDateRange.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
+            override fun onTabSelected(tab: TabLayout.Tab?) {
+                currentDateType = when(tab?.position) {
+                    0 -> DateType.DAY
+                    1 -> DateType.WEEK
+                    2 -> DateType.MONTH
+                    3 -> DateType.YEAR
+                    else -> DateType.DAY
+                }
+                updateDateDisplay()
+                updateCharts()
+                loadPackages()
+            }
+            override fun onTabUnselected(tab: TabLayout.Tab?) {}
+            override fun onTabReselected(tab: TabLayout.Tab?) {}
+        })
+    }
+
+    private fun setupDatePicker() {
+        binding.btnPreviousDate.setOnClickListener {
+            adjustDate(-1)
+        }
+        binding.btnNextDate.setOnClickListener {
+            adjustDate(1)
+        }
+    }
+
+    private fun adjustDate(amount: Int) {
+        when (currentDateType) {
+            DateType.DAY -> currentDate.add(Calendar.DAY_OF_MONTH, amount)
+            DateType.WEEK -> currentDate.add(Calendar.WEEK_OF_YEAR, amount)
+            DateType.MONTH -> currentDate.add(Calendar.MONTH, amount)
+            DateType.YEAR -> currentDate.add(Calendar.YEAR, amount)
+        }
+        updateDateDisplay()
+        updateCharts()
+        loadPackages()
+    }
+
+    private fun updateDateDisplay() {
+        val dateFormat = when (currentDateType) {
+            DateType.DAY -> "yyyy年MM月dd日"
+            DateType.WEEK -> {
+                // 获取本周的起始和结束日期
+                val calendar = currentDate.clone() as Calendar
+                calendar.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY)
+                val startDate = SimpleDateFormat("MM月dd日", Locale.getDefault()).format(calendar.time)
+                
+                calendar.add(Calendar.DAY_OF_WEEK, 6)
+                val endDate = SimpleDateFormat("MM月dd日", Locale.getDefault()).format(calendar.time)
+                
+                "$startDate-$endDate"
+            }
+            DateType.MONTH -> "yyyy年MM月"
+            DateType.YEAR -> "yyyy年"
+        }
+        
+        if (currentDateType == DateType.WEEK) {
+            binding.textCurrentDate.text = dateFormat
+        } else {
+            binding.textCurrentDate.text = SimpleDateFormat(dateFormat, Locale.getDefault())
+                .format(currentDate.time)
+        }
+    }
+    private fun setupView(view: View) {
+        val weekStatsView = binding.layoutWeekStats.root
+        barChart = weekStatsView.findViewById(R.id.chart_daily_packages)
+        pieChart = weekStatsView.findViewById(R.id.chart_courier_distribution)
+        heatmapView = weekStatsView.findViewById(R.id.heatmap_yearly)
+        
+        // 初始化时隐藏统计视图
+        weekStatsView.visibility = View.GONE
+        
+        setupBarChart()
+        setupPieChart()
+        setupHeatmap()
+        updateCharts()
+    }
+    private fun setupBarChart() {
+        barChart.apply {
+            description.isEnabled = false
+            setDrawGridBackground(false)
+            legend.isEnabled = false
+            
+            // 增大图表高度
+            minimumHeight = (resources.displayMetrics.density * 300).toInt()
+
+            // X轴设置
+            xAxis.apply {
+                position = XAxis.XAxisPosition.BOTTOM
+                setDrawGridLines(false)
+                granularity = 1f
+                labelRotationAngle = 0f
+                textSize = 10f  //标签字体
+                setExtraLeftOffset(5f)  // 减少左侧留白
+                setExtraBottomOffset(15f)
+            }
+
+            // Y轴设置
+            axisLeft.apply {
+                setDrawGridLines(true)
+                axisMinimum = 0f
+                granularity = 1f
+                valueFormatter = object : ValueFormatter() {
+                    override fun getFormattedValue(value: Float): String {
+                        return if (value > 0) value.toInt().toString() else ""
+                    }
+                }
+            }
+            axisRight.isEnabled = false
+            
+            // 设置图表交互
+            setTouchEnabled(true)
+            isDragEnabled = true
+            setScaleEnabled(true)
+            
+            // 设置边距
+            setExtraOffsets(10f, 10f, 10f, 20f)
+        }
+
+
+        
+        updateBarChartData()
+    }
+    private fun updateBarChartData() {
+        val statsFlow = when (currentDateType) {
+            DateType.WEEK -> {
+                viewModel.getWeeklyStats(currentDate.timeInMillis, 6)
+            }
+            DateType.MONTH -> {
+                viewModel.getYearMonthlyStats(currentDate.timeInMillis)
+            }
+            else -> return
+        }
+
+        statsFlow.observe(viewLifecycleOwner) { stats ->
+            if (stats.isEmpty()) return@observe
+            
+            val entries = stats.mapIndexed { index, stat ->
+                val entry = BarEntry(index.toFloat(), stat.count.toFloat())
+                if (stat.count == 0) {
+                    entry.data = "hide_label"
+                }
+                entry
+            }
+
+            val dataSet = BarDataSet(entries, "包裹数量")
+            dataSet.apply {
+                color = resources.getColor(R.color.purple_500)
+                valueTextSize = 12f
+                valueFormatter = object : ValueFormatter() {
+                    override fun getFormattedValue(value: Float): String {
+                        return if (value > 0) value.toInt().toString() else ""
+                    }
+                }
+            }
+
+            val barData = BarData(dataSet)
+            barChart.data = barData
+            
+            // 修改X轴标签显示
+            barChart.xAxis.apply {
+                valueFormatter = object : ValueFormatter() {
+                    override fun getFormattedValue(value: Float): String {
+                        val position = value.toInt()
+                        if (position >= 0 && position < stats.size) {
+                            return when(currentDateType) {
+                                DateType.WEEK -> {
+                                    val weekStat = stats[position]
+                                    try {
+                                        // 解析日期字符串
+                                        val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
+                                        val date = sdf.parse(weekStat.date)
+                                        val calendar = Calendar.getInstance()
+                                        calendar.time = date
+                                        
+                                        // 获取年份和周数
+                                        val weekOfYear = calendar.get(Calendar.WEEK_OF_YEAR)
+                                        val year = calendar.get(Calendar.YEAR)
+                                        
+                                        // 显示格式:第X周
+//                                        "第${weekOfYear}周"
+                                        "${weekOfYear}"
+                                    } catch (e: Exception) {
+                                        weekStat.date
+                                    }
+                                }
+//                                DateType.MONTH -> "${stats[position].date}月"
+                                DateType.MONTH -> {
+                                    stats[position].date.replaceFirst("^0+".toRegex(), "")
+                                }
+                                else -> ""
+                            }
+                        }
+                        return ""
+                    }
+                }
+                labelCount = stats.size
+                setAvoidFirstLastClipping(true)
+                labelRotationAngle = 0f
+                
+                // 增加标签间距
+//                setExtraBottomOffset(20f)
+                textSize = 11f  // 稍微调小字体
+            }
+            
+            // 高亮显示
+            if (currentDateType == DateType.MONTH) {
+                val currentMonth = currentDate.get(Calendar.MONTH)
+                dataSet.setColors(List(stats.size) { index ->
+                    if (index == currentMonth) resources.getColor(R.color.purple_500)
+                    else resources.getColor(R.color.purple_200)
+                })
+            } else if (currentDateType == DateType.WEEK) {
+                val highlightIndex = 3 // 当前周在第4个位置
+                dataSet.setColors(List(stats.size) { index ->
+                    if (index == highlightIndex) resources.getColor(R.color.purple_500)
+                    else resources.getColor(R.color.purple_200)
+                })
+            }
+            
+            // 刷新图表
+            barChart.notifyDataSetChanged()
+            barChart.invalidate()
+        }
+    }
+    private fun setupPieChart() {
+        pieChart.apply {
+            // 隐藏图表描述(右下角默认文字)
+            description.isEnabled = false
+            // 显示实际值而非百分比
+            setUsePercentValues(false)
+            // 隐藏饼图区块上的标签(如数值或名称)
+            setDrawEntryLabels(false)
+
+            // 设置饼图与容器的边距,参数顺序:左、上、右、下
+            // 右侧留出 80f 空间,可能为图例腾出位置
+            setExtraOffsets(20f, 20f, 20f, 20f)
+
+
+
+            // 配置图例
+            legend.apply {
+                isEnabled = true // 启用图例
+                verticalAlignment = Legend.LegendVerticalAlignment.CENTER // 垂直居中
+                horizontalAlignment = Legend.LegendHorizontalAlignment.RIGHT // 水平靠右对齐
+                orientation = Legend.LegendOrientation.VERTICAL // 图例项垂直排列
+                setDrawInside(false)  // 图例绘制在图表外部(而非覆盖在图上)
+                xEntrySpace = 10f  // 图例项水平间距
+                yEntrySpace = 5f  // 图例项垂直间距
+                yOffset = 0f      // 图例整体 Y 轴无偏移
+                textSize = 12f   // 图例文字大小
+            }
+
+            // 调整中心空白区域大小
+            holeRadius = 20f  // 中间空心圆的半径(占饼图比例)
+            transparentCircleRadius = 25f  // 透明圆圈的半径(可能用于边框效果)
+
+        }
+
+        updatePieChartData()
+    }
+    private fun updatePieChartData() {
+        viewModel.getCourierStats(
+            currentDate.timeInMillis,
+            currentDateType.name
+        ).observe(viewLifecycleOwner) { stats ->
+            val entries = stats.map { stat ->
+                PieEntry(stat.count.toFloat(), "${stat.courierName}(${stat.count})")
+            }
+
+            val dataSet = PieDataSet(entries, "快递公司分布")
+            dataSet.colors = listOf(
+                resources.getColor(R.color.purple_500),
+                resources.getColor(R.color.teal_200),
+                resources.getColor(R.color.purple_200),
+                resources.getColor(R.color.teal_700)
+            )
+            dataSet.valueTextSize = 14f // 增大数值文字大小
+
+            val pieData = PieData(dataSet)
+            pieData.setValueFormatter(object : ValueFormatter() {
+                override fun getFormattedValue(value: Float): String {
+                    return value.toInt().toString()
+                }
+            })
+
+            pieChart.data = pieData
+            pieChart.invalidate()
+        }
+    }
+    private fun getDayLabels(): Array<String> {
+        return arrayOf("周一", "周二", "周三", "周四", "周五", "周六", "周日")
+    }
+
+    private fun loadPackages() {
+        viewModel.getPackages(
+            currentDate.timeInMillis,
+            currentDateType.name
+        ).observe(viewLifecycleOwner) { packages ->
+            when (currentDateType) {
+                DateType.DAY -> {
+                    binding.textPackageCount.text = "${packages.size}个"
+                }
+                DateType.WEEK -> {
+                    // 获取本周统计
+                    viewModel.getCurrentWeekStats(currentDate.timeInMillis)
+                        .observe(viewLifecycleOwner) { stats ->
+                            val weekTotal = stats.sumOf { it.count }
+                            binding.textPackageCount.text = "${weekTotal}个"
+                        }
+                }
+                DateType.MONTH -> {
+                    // 获取本月统计
+                    viewModel.getMonthlyStats(currentDate.timeInMillis)
+                        .observe(viewLifecycleOwner) { stats ->
+                            val monthTotal = stats.sumOf { it.count }
+                            binding.textPackageCount.text = "${monthTotal}个"
+                        }
+                }
+                DateType.YEAR -> {
+                    // 获取本年统计
+                    viewModel.getCurrentYearStats(currentDate.timeInMillis)
+                        .observe(viewLifecycleOwner) { stats ->
+                            val yearTotal = stats.sumOf { it.count }
+                            binding.textPackageCount.text = "${yearTotal}个"
+                        }
+                }
+            }
+            packageAdapter.updatePackages(packages)
+            packageAdapter.updatePackages(packages)
+//            binding.textPackageCount.text = "${packages.size}个"
+        }
+    }
+    private fun setupHeatmap() {
+        heatmapView.visibility = View.GONE
+    }
+
+    private fun updateHeatmapData() {
+        viewModel.getYearlyHeatmap(currentDate.timeInMillis).observe(viewLifecycleOwner) { stats ->
+            if (stats.isEmpty()) return@observe
+
+            // 创建52周x7天的数据矩阵
+            val heatmapMatrix = Array(7) { IntArray(52) }
+            
+            // 填充数据
+            stats.forEach { stat ->
+                val week = stat.weekOfYear - 1 // 0-51
+                val dayOfWeek = stat.dayOfWeek - 1 // 0-6
+                if (week in 0..51 && dayOfWeek in 0..6) {
+                    heatmapMatrix[dayOfWeek][week] = stat.count
+                }
+            }
+
+            // 更新UI
+            binding.layoutWeekStats.heatmapYearly.apply {
+                removeAllViews()
+                
+                val gridLayout = GridLayout(context).apply {
+                    rowCount = 8 // 增加一行用于显示月份
+                    columnCount = 53 // 增加一列用于显示星期标签
+                }
+
+                // 添加月份标签
+                val months = arrayOf("1月", "2月", "3月", "4月", "5月", "6月", 
+                                   "7月", "8月", "9月", "10月", "11月", "12月")
+                months.forEachIndexed { index, month ->
+                    val label = TextView(context).apply {
+                        text = month
+                        textSize = 10f
+                        setPadding(0, 0, 8, 4)
+                        // 计算每个月份标签的位置
+                        val weekPosition = (index * 4.3).toInt()
+                        val params = GridLayout.LayoutParams()
+                        params.columnSpec = GridLayout.spec(weekPosition + 1)
+                        params.rowSpec = GridLayout.spec(0)
+                        layoutParams = params
+                    }
+                    gridLayout.addView(label)
+                }
+
+                // 添加星期标签
+                val dayLabels = arrayOf("周一", "周二", "周三", "周四", "周五", "周六", "周日")
+                dayLabels.forEachIndexed { index, label ->
+                    val textView = TextView(context).apply {
+                        text = label
+                        textSize = 10f
+                        setPadding(4, 0, 8, 0)
+                        val params = GridLayout.LayoutParams()
+                        params.columnSpec = GridLayout.spec(0)
+                        params.rowSpec = GridLayout.spec(index + 1)
+                        layoutParams = params
+                    }
+                    gridLayout.addView(textView)
+                }
+
+                // 添加热力图单元格
+                for (day in 0..6) {
+                    for (week in 0..51) {
+                        val count = heatmapMatrix[day][week]
+                        val cell = View(context).apply {
+                            val params = GridLayout.LayoutParams().apply {
+                                width = resources.getDimensionPixelSize(R.dimen.heatmap_cell_size)
+                                height = resources.getDimensionPixelSize(R.dimen.heatmap_cell_size)
+                                columnSpec = GridLayout.spec(week + 1)
+                                rowSpec = GridLayout.spec(day + 1)
+                                setMargins(1, 1, 1, 1)
+                            }
+                            layoutParams = params
+                            setBackgroundColor(getHeatmapColor(count))
+                        }
+                        gridLayout.addView(cell)
+                    }
+                }
+
+                addView(gridLayout)
+            }
+        }
+    }
+
+    private fun getHeatmapColor(count: Int): Int {
+        return when {
+            count == 0 -> Color.parseColor("#EBEDF0")
+            count == 1 -> Color.parseColor("#9BE9A8")
+            count == 2 -> Color.parseColor("#40C463")
+            count <= 4 -> Color.parseColor("#30A14E")
+            else -> Color.parseColor("#216E39")
+        }
+    }
+
+    private fun updateCharts() {
+        when (currentDateType) {
+            DateType.DAY -> {
+                // 日视图显示包裹列表,隐藏统计图表
+                binding.recyclerPackages.visibility = View.VISIBLE
+                binding.layoutWeekStats.root.visibility = View.GONE
+                binding.layoutYearStats.root.visibility = View.GONE
+                binding.cardPackageStats.visibility = View.VISIBLE
+            }
+            DateType.WEEK, DateType.MONTH -> {
+                // 周和月视图显示柱状图和饼图,隐藏包裹列表
+                binding.recyclerPackages.visibility = View.GONE
+                binding.layoutWeekStats.root.visibility = View.VISIBLE
+                binding.layoutYearStats.root.visibility = View.GONE
+                binding.layoutWeekStats.chartDailyPackages.visibility = View.VISIBLE
+                (binding.layoutWeekStats.chartDailyPackages.parent as? View)?.visibility = View.VISIBLE
+                binding.layoutWeekStats.heatmapYearly.visibility = View.GONE
+                binding.cardPackageStats.visibility = View.VISIBLE
+                updateBarChartData()
+                updatePieChartData()
+            }
+            DateType.YEAR -> {
+                // 年视图显示热力图和饼图,隐藏包裹列表和柱状图
+                binding.recyclerPackages.visibility = View.GONE
+                binding.layoutWeekStats.root.visibility = View.VISIBLE
+                binding.layoutYearStats.root.visibility = View.VISIBLE
+                (binding.layoutWeekStats.chartDailyPackages.parent as? View)?.visibility = View.GONE
+                binding.layoutWeekStats.heatmapYearly.visibility = View.VISIBLE
+                binding.cardPackageStats.visibility = View.GONE
+                updateHeatmapData()
+                updatePieChartData()
+                updateYearlyStats()
+            }
+        }
+    }
+
+    private fun updateYearlyStats() {
+        viewModel.getYearlyStats(currentDate.timeInMillis).observe(viewLifecycleOwner) { stats: List<DailyStat> ->
+            if (stats.isEmpty()) return@observe
+            
+            // 更新年度包裹总数
+            binding.layoutYearStats.textTotalPackages.text = "${stats.sumOf { it.count }}个"
+            
+            // 更新平均每天包裹数
+            val avgDaily = stats.sumOf { it.count }.toFloat() / 365
+            binding.layoutYearStats.textDailyAverage.text = String.format("%.2f", avgDaily)
+        }
     }
 
     override fun onDestroyView() {
         super.onDestroyView()
         _binding = null
     }
+    private fun loadUserInfo() {
+
+        lifecycleScope.launch {
+            try {
+                // 从本地获取保存的手机号
+                val savedPhone = PreferencesManager.getPhone()
+                if (savedPhone.isNullOrEmpty()) {
+                    Toast.makeText(context, "用户未登录", Toast.LENGTH_SHORT).show()
+                    return@launch
+                }
+
+                val response = RetrofitClient.apiService.getUserInfo(savedPhone)
+                if (response.code == "0" && response.data != null) {
+                    // 保存用户信息
+                    currentUserInfo = response.data
+                    val userInfo = response.data
+                    if(userInfo.isMember){
+                        hiddleOverlay()
+                    }else{
+                        // 显示遮罩层
+                        showOverlay()
+                    }
+
+                }
+            } catch (e: Exception) {
+                e.printStackTrace()
+                Toast.makeText(context, "获取用户信息失败", Toast.LENGTH_SHORT).show()
+            }
+        }
+
+    }
 }
\ No newline at end of file

--
Gitblit v1.9.3