From 88b00f3fc74446a1727c93722c7b64179b45a9db Mon Sep 17 00:00:00 2001 From: zhujie <leon.zhu@cloudroam.com.cn> Date: 星期五, 18 四月 2025 17:40:45 +0800 Subject: [PATCH] Merge branch 'master' of http://47.96.225.205:8888/r/FirstApp2 --- app/src/main/java/com/example/firstapp/ui/dashboard/DashboardFragment.kt | 953 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 files changed, 905 insertions(+), 48 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 5d10632..89515e4 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,38 +1,40 @@ package com.example.firstapp.ui.dashboard -import com.example.firstapp.R +import android.graphics.Color import android.os.Bundle +import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.webkit.WebView +import android.widget.GridLayout +import android.widget.HorizontalScrollView +import android.widget.LinearLayout import android.widget.TextView +import android.widget.Toast +import androidx.cardview.widget.CardView import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels -import com.example.firstapp.databinding.FragmentDashboardBinding -import com.google.android.material.tabs.TabLayout +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager +import com.example.firstapp.R import com.example.firstapp.adapter.PackageAdapter +import com.example.firstapp.database.response.UserInfo +import com.example.firstapp.database.service.RetrofitClient +import com.example.firstapp.databinding.FragmentDashboardBinding +import com.example.firstapp.model.DailyStat +import com.example.firstapp.utils.PreferencesManager 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 com.google.android.material.tabs.TabLayout import kotlinx.coroutines.launch +import java.text.SimpleDateFormat +import java.util.* + class DashboardFragment : Fragment() { @@ -43,14 +45,21 @@ private var currentDateType = DateType.DAY private lateinit var barChart: BarChart private lateinit var pieChart: PieChart + private lateinit var chartCourierDistriBution:CardView private lateinit var heatmapView: View private var currentUserInfo: UserInfo? = null // 确保使用你的实际数据类 + + private var startDateCur:String = "" + private var endDateCur:String = "" + private lateinit var bar_title:TextView + private lateinit var pie_title:TextView enum class DateType { DAY, WEEK, MONTH, YEAR } private val viewModel: DashboardViewModel by viewModels() + override fun onCreateView( inflater: LayoutInflater, @@ -69,6 +78,14 @@ override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + +// val webView: WebView = binding.layoutWeekStats.webView +// val webSettings = webView.settings +// webSettings.javaScriptEnabled = true // 启用 JavaScript +// +// +// // 加载本地的 HTML 文件 +// webView.loadUrl("file:///android_asset/calendar-heatmap.html") //渲染包裹列表 setupRecyclerView() @@ -93,12 +110,13 @@ private fun showOverlay() { binding.viewOverlay.visibility = View.VISIBLE - binding.viewOverlay.visibility = View.VISIBLE + binding.overlayContent.visibility = View.VISIBLE + } private fun hiddleOverlay() { binding.viewOverlay.visibility = View.GONE - binding.viewOverlay.visibility = View.GONE + binding.overlayContent.visibility = View.GONE } @@ -112,8 +130,14 @@ } private fun setupTabLayout() { + val weekStatsView = binding.layoutWeekStats.root + bar_title = weekStatsView.findViewById(R.id.bar_title) + pie_title = weekStatsView.findViewById(R.id.pie_title) + binding.tabDateRange.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { override fun onTabSelected(tab: TabLayout.Tab?) { + currentDate = Calendar.getInstance() + currentDateType = when(tab?.position) { 0 -> DateType.DAY 1 -> DateType.WEEK @@ -121,6 +145,40 @@ 3 -> DateType.YEAR else -> DateType.DAY } + + when (currentDateType) { + DateType.DAY -> { + binding.cardPackageStatsTitleText.text = "本日收到包裹总数" + binding.layoutYearStatsTitleText.text = "包裹取件码记录" + + binding.cardPackageStatsTitleText.visibility = View.VISIBLE + binding.layoutYearStatsTitleText.visibility = View.VISIBLE + } + DateType.WEEK -> { + binding.cardPackageStatsTitleText.text = "本周收到包裹总数" + bar_title.text = "本周收到包裹数分布 ->" + pie_title.text = "本周包裹物流公司分布 ->" + binding.cardPackageStatsTitleText.visibility = View.VISIBLE + binding.layoutYearStatsTitleText.visibility = View.GONE + + } + DateType.MONTH -> { + binding.cardPackageStatsTitleText.text = "本月收到包裹总数" + bar_title.text = "本月收到包裹数分布 ->" + pie_title.text = "本月包裹物流公司分布 ->" + binding.cardPackageStatsTitleText.visibility = View.VISIBLE + binding.layoutYearStatsTitleText.visibility = View.GONE + } + DateType.YEAR -> { + bar_title.text = "本年收到包裹数分布 ->" + pie_title.text = "本年包裹物流公司分布 ->" + binding.cardPackageStatsTitleText.visibility = View.GONE + binding.layoutYearStatsTitleText.visibility = View.GONE + } + } + + + updateDateDisplay() updateCharts() loadPackages() @@ -153,16 +211,94 @@ private fun updateDateDisplay() { val dateFormat = when (currentDateType) { + DateType.DAY -> { + // 获取当天的起始和结束时间 + val calendar = currentDate.clone() as Calendar + // 设置为当天的 00:00:00 + calendar.set(Calendar.HOUR_OF_DAY, 0) + calendar.set(Calendar.MINUTE, 0) + calendar.set(Calendar.SECOND, 0) + calendar.set(Calendar.MILLISECOND, 0) + startDateCur = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.time) + + // 设置为当天的 23:59:59 + calendar.set(Calendar.HOUR_OF_DAY, 23) + calendar.set(Calendar.MINUTE, 59) + calendar.set(Calendar.SECOND, 59) + calendar.set(Calendar.MILLISECOND, 999) + endDateCur = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.time) + + // 返回当天的日期格式 + SimpleDateFormat("yyyy年MM月dd日", Locale.getDefault()).format(currentDate.time) + } + 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) + startDateCur = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.time) + + calendar.add(Calendar.DAY_OF_WEEK, 6) + val endDate = SimpleDateFormat("MM月dd日", Locale.getDefault()).format(calendar.time) + endDateCur = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.time) + + "$startDate-$endDate" + } + DateType.MONTH -> { + // 获取本月的起始和结束日期 + val calendar = currentDate.clone() as Calendar + calendar.set(Calendar.DAY_OF_MONTH, 1) + startDateCur = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.time) + + calendar.add(Calendar.MONTH, 1) + calendar.set(Calendar.DAY_OF_MONTH, 0) + endDateCur = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.time) + + "yyyy年MM月" + } + DateType.YEAR -> { + // 获取当前年的起始和结束日期 + val calendar = currentDate.clone() as Calendar + + // 设置为当前年份的 1 月 1 日 + calendar.set(Calendar.MONTH, Calendar.JANUARY) + calendar.set(Calendar.DAY_OF_MONTH, 1) + startDateCur = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.time) + + // 设置为当前年份的 12 月 31 日 + calendar.set(Calendar.MONTH, Calendar.DECEMBER) + calendar.set(Calendar.DAY_OF_MONTH, 31) + endDateCur = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.time) + + + "yyyy年" + } + } + + // 更新界面显示 + if (currentDateType == DateType.WEEK) { + binding.textCurrentDate.text = dateFormat + } else { + binding.textCurrentDate.text = SimpleDateFormat(dateFormat, Locale.getDefault()) + .format(currentDate.time) + } + } + + private fun updateDateDisplay_bak() { + 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) - + startDateCur=SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.time) + calendar.add(Calendar.DAY_OF_WEEK, 6) val endDate = SimpleDateFormat("MM月dd日", Locale.getDefault()).format(calendar.time) - + endDateCur=SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(calendar.time) + + "$startDate-$endDate" } DateType.MONTH -> "yyyy年MM月" @@ -177,10 +313,16 @@ } } private fun setupView(view: View) { + val weekStatsView = binding.layoutWeekStats.root barChart = weekStatsView.findViewById(R.id.chart_daily_packages) + chartCourierDistriBution=weekStatsView.findViewById(R.id.chart_courier_card_view) pieChart = weekStatsView.findViewById(R.id.chart_courier_distribution) heatmapView = weekStatsView.findViewById(R.id.heatmap_yearly) + + barChart.setViewPortOffsets(100f, 100f, 100f, 200f) + +// barChart.invalidate() // 初始化时隐藏统计视图 weekStatsView.visibility = View.GONE @@ -189,6 +331,7 @@ setupPieChart() setupHeatmap() updateCharts() + } private fun setupBarChart() { barChart.apply { @@ -239,7 +382,8 @@ private fun updateBarChartData() { val statsFlow = when (currentDateType) { DateType.WEEK -> { - viewModel.getWeeklyStats(currentDate.timeInMillis, 6) +// viewModel.getWeeklyStats(currentDate.timeInMillis, 6) + viewModel.getWeeklyStatsChart(startDateCur,endDateCur) } DateType.MONTH -> { viewModel.getYearMonthlyStats(currentDate.timeInMillis) @@ -383,31 +527,200 @@ 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 // 增大数值文字大小 + if (entries.isNotEmpty()) { + val dataSet = PieDataSet(entries, "快递公司分布") + dataSet.colors = listOf( + resources.getColor(R.color.light_blue_600_1), + resources.getColor(R.color.sunflower), + resources.getColor(R.color.light_blue), + resources.getColor(R.color.vermillion), + resources.getColor(R.color.fish_belly_white), + resources.getColor(R.color.light_green), + resources.getColor(R.color.crimson), + resources.getColor(R.color.sky_blue), + resources.getColor(R.color.gold), + resources.getColor(R.color.light_purple), + resources.getColor(R.color.yellow), + resources.getColor(R.color.canary_yellow), + resources.getColor(R.color.red_purple), + resources.getColor(R.color.light_cyan), + resources.getColor(R.color.orange), + resources.getColor(R.color.magenta), + resources.getColor(R.color.light_purple_2), + resources.getColor(R.color.bright_yellow), + resources.getColor(R.color.emerald_green), + resources.getColor(R.color.turmeric), + resources.getColor(R.color.red_gold), + resources.getColor(R.color.off_white), + resources.getColor(R.color.tangerine), + resources.getColor(R.color.aqua_blue), + resources.getColor(R.color.frost), + resources.getColor(R.color.wisteria), + resources.getColor(R.color.cyan) + ) + dataSet.valueTextSize = 14f // 增大数值文字大小 - val pieData = PieData(dataSet) - pieData.setValueFormatter(object : ValueFormatter() { - override fun getFormattedValue(value: Float): String { - return value.toInt().toString() - } - }) + val pieData = PieData(dataSet) + pieData.setValueFormatter(object : ValueFormatter() { + override fun getFormattedValue(value: Float): String { + return value.toInt().toString() + } + }) - pieChart.data = pieData - pieChart.invalidate() + pieChart.data = pieData + pieChart.invalidate() + + pieChart.visibility = View.VISIBLE // 例如:隐藏 PieChart + + pie_title.visibility = View.VISIBLE + + chartCourierDistriBution.visibility =View.VISIBLE + + } else { + // 如果 entries 为空,可以选择隐藏图表或设置一个默认显示 + pieChart.visibility = View.GONE // 例如:隐藏 PieChart + + pie_title.visibility = View.GONE + + chartCourierDistriBution.visibility =View.GONE + + } + + +// 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() { + private fun loadPackages(){ + + + +// 根据日、周、月、年 获取统计数字 + when (currentDateType) { + DateType.DAY -> { + + viewModel.getCurrentDayStatsByType(startDateCur,endDateCur,"快递") .observe(viewLifecycleOwner) { stats -> + binding.textPackageCount.text = "${stats}个" + } + + // 获取取件记录 + viewModel.getPackagesReaded(currentDate.timeInMillis, + currentDateType.name) + .observe(viewLifecycleOwner) { unpackages-> + // 只读取未取件的包裹 + packageAdapter.updatePackages(unpackages) + } + + + } + DateType.WEEK -> { + + viewModel.getCurrentDayStatsByType(startDateCur,endDateCur,"快递") .observe(viewLifecycleOwner) { stats -> + binding.textPackageCount.text = "${stats}个" + } + } + DateType.MONTH -> { + + viewModel.getCurrentDayStatsByType(startDateCur,endDateCur,"快递") .observe(viewLifecycleOwner) { stats -> + binding.textPackageCount.text = "${stats}个" + } + } + DateType.YEAR -> { + + viewModel.getCurrentDayStatsByType(startDateCur,endDateCur,"快递") .observe(viewLifecycleOwner) { stats -> + binding.layoutYearStats.textTotalPackages.text = "${stats}个" + } + } + } + } + + private fun loadPackages_bak2(){ + + val formatter = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) + +// 根据日、周、月、年 获取统计数字 + when (currentDateType) { + DateType.DAY -> { + val today = Calendar.getInstance() + val tmpCurDateStart = formatter.format(currentDate.time) + val tmpCurDateEnd = formatter.format(currentDate.time) + viewModel.getCurrentDayStatsByType(tmpCurDateStart,tmpCurDateEnd,"快递") .observe(viewLifecycleOwner) { stats -> + binding.textPackageCount.text = "${stats}个" + } + + // 获取本周统计 + viewModel.getPackagesReaded(currentDate.timeInMillis, + currentDateType.name) + .observe(viewLifecycleOwner) { unpackages-> + // 只读取未取件的包裹 + packageAdapter.updatePackages(unpackages) + } + + + } + DateType.WEEK -> { + val today = Calendar.getInstance() + + // 获取本周的周一(第一天) + today.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY) + val firstDayOfWeek = formatter.format(today.time) + // 获取本周的周日(最后一天) + today.add(Calendar.DATE, 6) // 加6天 + val lastDayOfWeek = formatter.format(today.time) + viewModel.getCurrentDayStatsByType(firstDayOfWeek,lastDayOfWeek,"快递") .observe(viewLifecycleOwner) { stats -> + binding.textPackageCount.text = "${stats}个" + } + } + DateType.MONTH -> { + val today = Calendar.getInstance() + today.set(Calendar.DAY_OF_MONTH, 1) // 设置为本月第一天 + val firstDayOfMonth = formatter.format(today.time) + + today.add(Calendar.MONTH, 1) // 移动到下个月 + today.set(Calendar.DAY_OF_MONTH, 0) // 设置为下个月的最后一天 + val lastDayOfMonth = formatter.format(today.time) + viewModel.getCurrentDayStatsByType(firstDayOfMonth,lastDayOfMonth,"快递") .observe(viewLifecycleOwner) { stats -> + binding.textPackageCount.text = "${stats}个" + } + } + DateType.YEAR -> { + val today = Calendar.getInstance() + today.set(Calendar.MONTH, Calendar.JANUARY) // 设置为第一月 + today.set(Calendar.DAY_OF_MONTH, 1) // 设置为第一天 + val firstDayOfYear = formatter.format(today.time) + + today.add(Calendar.YEAR, 1) // 移动到下一年 + today.set(Calendar.MONTH, Calendar.DECEMBER) // 设置为最后一月 + today.set(Calendar.DAY_OF_MONTH, 31) // 设置为最后一天 + val lastDayOfYear = formatter.format(today.time) + viewModel.getCurrentDayStatsByType(firstDayOfYear,lastDayOfYear,"快递") .observe(viewLifecycleOwner) { stats -> +// binding.textPackageCount.text = "${stats}个" + binding.layoutYearStats.textTotalPackages.text = "${stats}个" + } + } + } + } + private fun loadPackages_bak() { viewModel.getPackages( currentDate.timeInMillis, currentDateType.name @@ -415,14 +728,26 @@ when (currentDateType) { DateType.DAY -> { binding.textPackageCount.text = "${packages.size}个" + + // 获取本周统计 + viewModel.getPackagesReaded(currentDate.timeInMillis, + currentDateType.name) + .observe(viewLifecycleOwner) { unpackages-> + // 只读取未取件的包裹 + packageAdapter.updatePackages(unpackages) + } + } DateType.WEEK -> { // 获取本周统计 - viewModel.getCurrentWeekStats(currentDate.timeInMillis) - .observe(viewLifecycleOwner) { stats -> - val weekTotal = stats.sumOf { it.count } - binding.textPackageCount.text = "${weekTotal}个" - } + viewModel.getCurrentWeekStats2(startDateCur,endDateCur).observe(viewLifecycleOwner) { stats -> + binding.textPackageCount.text = "${stats}个" + } +// viewModel.getCurrentWeekStats(currentDate.timeInMillis) +// .observe(viewLifecycleOwner) { stats -> +// val weekTotal = stats.sumOf { it.count } +// binding.textPackageCount.text = "${weekTotal}个" +// } } DateType.MONTH -> { // 获取本月统计 @@ -441,8 +766,8 @@ } } } - packageAdapter.updatePackages(packages) - packageAdapter.updatePackages(packages) + +// packageAdapter.updatePackages(packages) // binding.textPackageCount.text = "${packages.size}个" } } @@ -450,7 +775,539 @@ heatmapView.visibility = View.GONE } + + + // 用于创建图例 + private fun createLegend(): LinearLayout { + val legendLayout = LinearLayout(context).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.END or Gravity.CENTER_VERTICAL + setPadding(8, 16, 8, 0) + } + + val labelLow = TextView(context).apply { + text = "少" + textSize = 10f + setPadding(0, 0, 8, 0) + } + legendLayout.addView(labelLow) + + val legendLevels = listOf(0, 5, 10, 15, 20) + legendLevels.forEach { level -> + val colorBox = View(context).apply { + setBackgroundColor(getHeatmapColor(level)) + val size = resources.getDimensionPixelSize(R.dimen.heatmap_cell_size) + layoutParams = LinearLayout.LayoutParams(size, size).apply { + marginEnd = 4 + } + } + + val label = TextView(context).apply { + textSize = 10f + setPadding(0, 0, 8, 0) + } + + legendLayout.addView(colorBox) + legendLayout.addView(label) + } + + val labelHigh = TextView(context).apply { + text = "多" + textSize = 10f + setPadding(8, 0, 0, 0) + } + legendLayout.addView(labelHigh) + + return legendLayout + } + private fun updateHeatmapData() { + viewModel.getYearlyHeatmap(currentDate.timeInMillis).observe(viewLifecycleOwner) { stats -> + if (stats.isEmpty()) return@observe + + val heatmapMatrix = Array(7) { IntArray(52) } + stats.forEach { stat -> + val week = stat.weekOfYear - 1 + val dayOfWeek = stat.dayOfWeek - 1 + if (week in 0..51 && dayOfWeek in 0..6) { + heatmapMatrix[dayOfWeek][week] = stat.count + } + } + + binding.layoutWeekStats.heatmapYearly.apply { + removeAllViews() + + // 外层父布局:水平 LinearLayout,左固定周标签,右为横向滚动热力图 + val outerLayout = LinearLayout(context).apply { + orientation = LinearLayout.HORIZONTAL + } + + // 左侧星期标签列 + val dayLabelLayout = LinearLayout(context).apply { + orientation = LinearLayout.VERTICAL + val dayLabels = arrayOf("周一", "周二", "周三", "周四", "周五", "周六", "周日") + // 顶部空白占位 + addView(TextView(context).apply { + text = "" + textSize = 20f + height = resources.getDimensionPixelSize(R.dimen.heatmap_cell_week_size) + }) + dayLabels.forEach { label -> + val textView = TextView(context).apply { + text = label + textSize = 10f + height = resources.getDimensionPixelSize(R.dimen.heatmap_cell_size) + setPadding(4, 0, 4, 0) + } + addView(textView) + } + } + + // 右侧滚动部分 + val scrollView = HorizontalScrollView(context).apply { + isHorizontalScrollBarEnabled = false + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + } + + val scrollContentLayout = LinearLayout(context).apply { + orientation = LinearLayout.VERTICAL + } + + val gridLayout = GridLayout(context).apply { + rowCount = 8 + columnCount = 52 + } + + // 添加月份标签(估算) + 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(10, 0, 8, 4) + val weekPosition = (index * 4.3).toInt() + layoutParams = GridLayout.LayoutParams().apply { + columnSpec = GridLayout.spec(weekPosition) + rowSpec = GridLayout.spec(0) + } + } + gridLayout.addView(label) + }*/ + months.forEachIndexed { index, month -> + val label = TextView(context).apply { + text = month + textSize = 10f + setPadding(10, 0, 8, 4) + + val weekPosition = (index * 4.3).toInt() + val span = 4 // 控制跨列范围 + layoutParams = GridLayout.LayoutParams().apply { + columnSpec = GridLayout.spec(weekPosition, span, GridLayout.CENTER) + rowSpec = GridLayout.spec(0) + } + } + gridLayout.addView(label) + } + + // 添加热力格子 + for (day in 0..6) { + for (week in 0..51) { + val count = heatmapMatrix[day][week] + val cell = View(context).apply { + layoutParams = GridLayout.LayoutParams().apply { + width = resources.getDimensionPixelSize(R.dimen.heatmap_cell_size) + height = resources.getDimensionPixelSize(R.dimen.heatmap_cell_size) + columnSpec = GridLayout.spec(week) + rowSpec = GridLayout.spec(day + 1) + setMargins(1, 1, 1, 1) + } + setBackgroundColor(getHeatmapColor(count)) + } + gridLayout.addView(cell) + } + } + + scrollContentLayout.addView(gridLayout) + scrollView.addView(scrollContentLayout) + + // 添加两个主要部分到外层布局 + outerLayout.addView(dayLabelLayout) + outerLayout.addView(scrollView) + + // 图例 + val legendLayout = LinearLayout(context).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.END or Gravity.CENTER_VERTICAL + setPadding(8, 16, 8, 0) + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + } + + val labelLow = TextView(context).apply { + text = "少" + textSize = 10f + setPadding(0, 0, 8, 0) + } + legendLayout.addView(labelLow) + + val legendLevels = listOf(0, 5, 10, 15, 20) + legendLevels.forEach { level -> + val colorBox = View(context).apply { + setBackgroundColor(getHeatmapColor(level)) + val size = resources.getDimensionPixelSize(R.dimen.heatmap_cell_legend_size) + layoutParams = LinearLayout.LayoutParams(size, size).apply { + marginEnd = 4 + } + } + + val label = TextView(context).apply { + textSize = 10f + setPadding(0, 0, 8, 0) + } + + legendLayout.addView(colorBox) + legendLayout.addView(label) + } + + val labelHigh = TextView(context).apply { + text = "多" + textSize = 10f + setPadding(8, 0, 0, 0) + } + legendLayout.addView(labelHigh) + + // 总容器垂直布局:热力图 + 图例 + val container = LinearLayout(context).apply { + orientation = LinearLayout.VERTICAL + addView(outerLayout) + addView(legendLayout) + } + +// addView(container) + + // 设置外部 margin + val params = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ).apply { + setMargins(16, 16, 16, 16) // 左、上、右、下的 margin,单位是像素 + } + + // 添加到父布局并应用 margin + addView(container, params) + + } + } + } + + + + private fun updateHeatmapData_a() { + 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 // 1行月份 + 7行星期 + columnCount = 53 // 1列星期 + 52列周数 + } + + // 添加月份标签 + 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() // 估算月份起始位置 + layoutParams = GridLayout.LayoutParams().apply { + columnSpec = GridLayout.spec(weekPosition + 1) + rowSpec = GridLayout.spec(0) + } + } + gridLayout.addView(label) + } + + // 添加星期标签 + val dayLabels = arrayOf("周一", "周二", "周三", "周四", "周五", "周六", "周日") + dayLabels.forEachIndexed { index, label -> + val textView = TextView(context).apply { + text = label + textSize = 10f + setPadding(4, 0, 8, 0) + layoutParams = GridLayout.LayoutParams().apply { + columnSpec = GridLayout.spec(0) + rowSpec = GridLayout.spec(index + 1) + } + } + gridLayout.addView(textView) + } + + // 添加热力图单元格 + for (day in 0..6) { + for (week in 0..51) { + val count = heatmapMatrix[day][week] + val cell = View(context).apply { + layoutParams = 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) + } + setBackgroundColor(getHeatmapColor(count)) + } + gridLayout.addView(cell) + } + } + + // 创建图例(Legend) + val legendLayout = LinearLayout(context).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.END or Gravity.CENTER_VERTICAL // 靠右对齐 + setPadding(8, 16, 8, 0) + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + } + + // 左侧“少”标签 + val labelLow = TextView(context).apply { + text = "少" + textSize = 10f + setPadding(0, 0, 8, 0) + } + legendLayout.addView(labelLow) + + // 渐变色块 + 数值 + val legendLevels = listOf(0, 5, 10, 15, 20) + legendLevels.forEach { level -> + val colorBox = View(context).apply { + setBackgroundColor(getHeatmapColor(level)) + val size = resources.getDimensionPixelSize(R.dimen.heatmap_cell_size) + layoutParams = LinearLayout.LayoutParams(size, size).apply { + marginEnd = 4 + } + } + + val label = TextView(context).apply { +// text = "$level" + textSize = 10f + setPadding(0, 0, 8, 0) + } + + legendLayout.addView(colorBox) + legendLayout.addView(label) + } + + // 右侧“多”标签 + val labelHigh = TextView(context).apply { + text = "多" + textSize = 10f + setPadding(8, 0, 0, 0) + } + legendLayout.addView(labelHigh) + + // 垂直组合 gridLayout + legendLayout + val container = LinearLayout(context).apply { + orientation = LinearLayout.VERTICAL + addView(gridLayout) + addView(legendLayout) + } + + addView(container) + } + } + } + + private fun updateHeatmapData_aaa() { + 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 // 1行月份 + 7行星期 + columnCount = 53 // 1列星期 + 52列周数 + } + + // 添加月份标签 + 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() // 估算月份起始位置 + layoutParams = GridLayout.LayoutParams().apply { + columnSpec = GridLayout.spec(weekPosition + 1) + rowSpec = GridLayout.spec(0) + } + } + gridLayout.addView(label) + } + + // 添加星期标签 + val dayLabels = arrayOf("周一", "周二", "周三", "周四", "周五", "周六", "周日") + dayLabels.forEachIndexed { index, label -> + val textView = TextView(context).apply { + text = label + textSize = 10f + setPadding(4, 0, 8, 0) + layoutParams = GridLayout.LayoutParams().apply { + columnSpec = GridLayout.spec(0) + rowSpec = GridLayout.spec(index + 1) + } + } + gridLayout.addView(textView) + } + + // 添加热力图单元格 + for (day in 0..6) { + for (week in 0..51) { + val count = heatmapMatrix[day][week] + val cell = View(context).apply { + layoutParams = 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) + } + setBackgroundColor(getHeatmapColor(count)) + } + gridLayout.addView(cell) + } + } + + // ✅ 把 gridLayout 包裹进 HorizontalScrollView + val scrollView = HorizontalScrollView(context).apply { + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + isHorizontalScrollBarEnabled = true + } + scrollView.addView(gridLayout) + + + + + // 创建图例(Legend) + val legendLayout = LinearLayout(context).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.END or Gravity.CENTER_VERTICAL // 靠右对齐 + setPadding(8, 16, 8, 0) + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + } + + // 左侧“少”标签 + val labelLow = TextView(context).apply { + text = "少" + textSize = 10f + setPadding(0, 0, 8, 0) + } + legendLayout.addView(labelLow) + + // 渐变色块 + 数值 + val legendLevels = listOf(0, 5, 10, 15, 20) + legendLevels.forEach { level -> + val colorBox = View(context).apply { + setBackgroundColor(getHeatmapColor(level)) + val size = resources.getDimensionPixelSize(R.dimen.heatmap_cell_size) + layoutParams = LinearLayout.LayoutParams(size, size).apply { + marginEnd = 4 + } + } + + val label = TextView(context).apply { +// text = "$level" + textSize = 10f + setPadding(0, 0, 8, 0) + } + + legendLayout.addView(colorBox) + legendLayout.addView(label) + } + + // 右侧“多”标签 + val labelHigh = TextView(context).apply { + text = "多" + textSize = 10f + setPadding(8, 0, 0, 0) + } + legendLayout.addView(labelHigh) + + // 垂直组合 gridLayout + legendLayout + val container = LinearLayout(context).apply { + orientation = LinearLayout.VERTICAL + addView(scrollView) + addView(legendLayout) + } + + addView(container) + } + } + } + + private fun getHeatmapColor(count: Int): Int { + return when { + count == 0 -> Color.parseColor("#EBECF1") // 最淡 + count < 5 -> Color.parseColor("#ACE7B1") + count < 10 -> Color.parseColor("#68C16F") + count < 15 -> Color.parseColor("#529F57") + else -> Color.parseColor("#356D40") // 最深 + } + } + + + + private fun updateHeatmapData_bak() { viewModel.getYearlyHeatmap(currentDate.timeInMillis).observe(viewLifecycleOwner) { stats -> if (stats.isEmpty()) return@observe @@ -532,7 +1389,7 @@ } } - private fun getHeatmapColor(count: Int): Int { + private fun getHeatmapColor_bak(count: Int): Int { return when { count == 0 -> Color.parseColor("#EBEDF0") count == 1 -> Color.parseColor("#9BE9A8") @@ -583,7 +1440,7 @@ if (stats.isEmpty()) return@observe // 更新年度包裹总数 - binding.layoutYearStats.textTotalPackages.text = "${stats.sumOf { it.count }}个" +// binding.layoutYearStats.textTotalPackages.text = "${stats.sumOf { it.count }}个" // 更新平均每天包裹数 val avgDaily = stats.sumOf { it.count }.toFloat() / 365 -- Gitblit v1.9.3