From acb5be78c07b8499d0a38515b05a4982207c1c9a Mon Sep 17 00:00:00 2001 From: zhujie <leon.zhu@cloudroam.com.cn> Date: 星期五, 18 四月 2025 15:24:16 +0800 Subject: [PATCH] Merge branch 'master' of http://47.96.225.205:8888/r/FirstApp2 --- app/src/main/java/com/example/firstapp/activity/SettingActivity.kt | 57 ++++ app/src/main/res/layout/activity_phone_login.xml | 2 app/src/main/res/layout/activity_setting.xml | 20 + app/src/main/java/com/example/firstapp/ui/dashboard/DashboardFragment.kt | 433 +++++++++++++++++++++++++++++- app/src/main/java/com/example/firstapp/MainActivity.kt | 155 +++++++++- app/src/main/res/layout/layout_week_stats.xml | 9 app/src/main/assets/calendar-heatmap.html | 124 ++++++++ app/src/main/res/values/dimens.xml | 2 8 files changed, 755 insertions(+), 47 deletions(-) diff --git a/app/src/main/assets/calendar-heatmap.html b/app/src/main/assets/calendar-heatmap.html new file mode 100644 index 0000000..31b6d96 --- /dev/null +++ b/app/src/main/assets/calendar-heatmap.html @@ -0,0 +1,124 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width,height=device-height"> + <title>水平日历色块图</title> + <style>::-webkit-scrollbar{display:none;}html,body{overflow:hidden;height:100%;margin:0;}</style> +</head> +<body> +<div id="mountNode"></div> +<script>/*Fixing iframe window.innerHeight 0 issue in Safari*/document.body.clientHeight;</script> +<script src="https://gw.alipayobjects.com/os/antv/pkg/_antv.g2-3.5.1/dist/g2.min.js"></script> +<script src="https://gw.alipayobjects.com/os/antv/pkg/_antv.data-set-0.10.1/dist/data-set.min.js"></script> +<script src="https://gw.alipayobjects.com/os/antv/assets/lib/jquery-3.2.1.min.js"></script> +<script> + var Shape = G2.Shape; + var Util = G2.Util; + Shape.registerShape('polygon', 'boundary-polygon', { + draw: function draw(cfg, container) { + if (!Util.isEmpty(cfg.points)) { + var attrs = { + stroke: '#fff', + lineWidth: 1, + fill: cfg.color, + fillOpacity: cfg.opacity + }; + var points = cfg.points; + var path = [['M', points[0].x, points[0].y], ['L', points[1].x, points[1].y], ['L', points[2].x, points[2].y], ['L', points[3].x, points[3].y], ['Z']]; + attrs.path = this.parsePath(path); + var polygon = container.addShape('path', { + attrs: attrs + }); + + if (cfg.origin._origin.lastWeek) { + var linePath = [['M', points[2].x, points[2].y], ['L', points[3].x, points[3].y]]; + // 最后一周的多边形添加右侧边框 + container.addShape('path', { + zIndex: 1, + attrs: { + path: this.parsePath(linePath), + lineWidth: 1, + stroke: '#404040' + } + }); + if (cfg.origin._origin.lastDay) { + container.addShape('path', { + zIndex: 1, + attrs: { + path: this.parsePath([['M', points[1].x, points[1].y], ['L', points[2].x, points[2].y]]), + lineWidth: 1, + stroke: '#404040' + } + }); + } + } + container.sort(); + return polygon; + } + } + }); + + $.getJSON('https://gw.alipayobjects.com/os/antvdemo/assets/data/github-commit.json', function(data) { + var chart = new G2.Chart({ + container: 'mountNode', + forceFit: true, + height: window.innerHeight, + padding: [window.innerHeight / 3, 20, window.innerHeight / 3, 80] + }); + chart.source(data, { + day: { + type: 'cat', + values: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'] + }, + week: { + type: 'cat' + }, + commits: { + sync: true + } + }); + + chart.axis('week', { + position: 'top', + tickLine: null, + line: null, + label: { + offset: 12, + textStyle: { + fontSize: 12, + fill: '#666', + textBaseline: 'top' + }, + formatter: function formatter(val) { + if (val === '2') { + return 'MAY'; + } else if (val === '6') { + return 'JUN'; + } else if (val === '10') { + return 'JUL'; + } else if (val === '15') { + return 'AUG'; + } else if (val === '19') { + return 'SEP'; + } else if (val === '24') { + return 'OCT'; + } + return ''; + } + } + }); + chart.axis('day', { + grid: null + }); + chart.legend(false); + chart.tooltip({ + title: 'date' + }); + chart.coord().reflect('y'); + chart.polygon().position('week*day*date').color('commits', '#BAE7FF-#1890FF-#0050B3').shape('boundary-polygon'); + chart.render(); + }); +</script> +</body> +</html> diff --git a/app/src/main/java/com/example/firstapp/MainActivity.kt b/app/src/main/java/com/example/firstapp/MainActivity.kt index 735c567..585212b 100644 --- a/app/src/main/java/com/example/firstapp/MainActivity.kt +++ b/app/src/main/java/com/example/firstapp/MainActivity.kt @@ -14,6 +14,7 @@ import android.Manifest import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import android.net.Uri import android.os.Build import androidx.annotation.RequiresApi @@ -31,6 +32,7 @@ import java.util.Calendar import java.util.Date import java.util.Locale +import android.app.AlertDialog class MainActivity : AppCompatActivity() { // 安全防护关键词数组 @@ -40,23 +42,41 @@ private var smsReceiver: SmsReceiver? = null - private val multiplePermissionRequest = + // 短信权限请求 + private val smsPermissionRequest = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions -> when { permissions.getOrDefault( Manifest.permission.RECEIVE_SMS, false ) && permissions.getOrDefault(Manifest.permission.READ_SMS, false) -> { // 两个权限都获得授权 + Toast.makeText(this, "短信权限已授予", Toast.LENGTH_SHORT).show() registerSmsReceiver() + // 在Android O及以上版本同步最近短信 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + syncRecentSms() + } } else -> { // 有权限被拒绝 - Toast.makeText( - this, "需要短信读取和接收权限才能正常使用功能", Toast.LENGTH_SHORT - ).show() + showSmsPermissionExplanationDialog() } } + } + + // 通知权限请求 + private val notificationPermissionRequest = + registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> + if (isGranted) { + // 权限已授予 + Toast.makeText(this, "通知权限已授予,您将能收到重要提醒", Toast.LENGTH_SHORT).show() + } else { + // 权限被拒绝 + Toast.makeText(this, "通知权限被拒绝,应用将无法发送提醒通知", Toast.LENGTH_SHORT).show() + } + // 无论通知权限是否授予,都继续请求短信权限 + requestSmsPermissions() } private val syncLock = Object() @@ -76,39 +96,122 @@ // 重置提醒计划并检查是否有错过的提醒 resetReminders() + + // 开始权限请求流程 + startPermissionsFlow() + } - // 检查权限 - if (ContextCompat.checkSelfPermission( - this, Manifest.permission.RECEIVE_SMS - ) != android.content.pm.PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission( - this, Manifest.permission.READ_SMS - ) != android.content.pm.PackageManager.PERMISSION_GRANTED - ) { - // 同时请求两个权限 - multiplePermissionRequest.launch( + override fun onResume() { + super.onResume() + + // 每次恢复活动时检查短信权限 + checkAndHandleSmsPermissions(showDialog = false) + } + + // 权限请求主流程 + private fun startPermissionsFlow() { + // 先请求通知权限,再请求短信权限 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + // Android 13+ 需要通知权限 + if (ContextCompat.checkSelfPermission( + this, + Manifest.permission.POST_NOTIFICATIONS + ) != PackageManager.PERMISSION_GRANTED + ) { + notificationPermissionRequest.launch(Manifest.permission.POST_NOTIFICATIONS) + } else { + // 已有通知权限,直接请求短信权限 + requestSmsPermissions() + } + } else { + // 低版本Android无需请求通知权限,直接请求短信权限 + requestSmsPermissions() + } + } + + // 请求短信权限 + private fun requestSmsPermissions() { + if (!hasSmsPermissions()) { + // 没有短信权限,请求权限 + smsPermissionRequest.launch( arrayOf( Manifest.permission.RECEIVE_SMS, Manifest.permission.READ_SMS ) ) } else { - // 权限已经授予,继续执行相关操作 + // 已有短信权限,直接初始化短信处理 registerSmsReceiver() - syncRecentSms() + // 在Android O及以上版本同步最近短信 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + syncRecentSms() + } + } + } + + // 检查并处理短信权限(可选是否显示对话框) + private fun checkAndHandleSmsPermissions(showDialog: Boolean = true) { + if (!hasSmsPermissions()) { + if (showDialog) { + showSmsPermissionExplanationDialog() + } + } else { + // 已有短信权限,确保接收器已注册 + if (smsReceiver == null) { + registerSmsReceiver() + } + } + } + + // 检查是否有短信权限 + private fun hasSmsPermissions(): Boolean { + return ContextCompat.checkSelfPermission( + this, Manifest.permission.RECEIVE_SMS + ) == PackageManager.PERMISSION_GRANTED && + ContextCompat.checkSelfPermission( + this, Manifest.permission.READ_SMS + ) == PackageManager.PERMISSION_GRANTED + } + + // 显示短信权限解释对话框 + private fun showSmsPermissionExplanationDialog() { + AlertDialog.Builder(this) + .setTitle("需要短信权限") + .setMessage("应用需要短信权限来自动处理和分类您的短信信息,包括快递、付款等内容。没有此权限,应用核心功能将无法正常工作。") + .setPositiveButton("授予权限") { _, _ -> + requestSmsPermissions() + } + .setNegativeButton("暂时不要") { dialog, _ -> + dialog.dismiss() + Toast.makeText(this, "您可以稍后在设置中开启短信权限", Toast.LENGTH_LONG).show() + } + .setCancelable(false) + .show() + } + + // 提供给外部调用的请求通知权限方法 + fun requestNotificationPermission() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + // 判断是否已经有通知权限 + if (ContextCompat.checkSelfPermission( + this, + Manifest.permission.POST_NOTIFICATIONS + ) != PackageManager.PERMISSION_GRANTED + ) { + notificationPermissionRequest.launch(Manifest.permission.POST_NOTIFICATIONS) + } else { + Toast.makeText(this, "已经拥有通知权限", Toast.LENGTH_SHORT).show() + } + } else { + // Android 13以下的版本不需要请求权限 + Toast.makeText(this, "当前系统版本无需单独请求通知权限", Toast.LENGTH_SHORT).show() } } private fun registerSmsReceiver() { -// 应用启动时执行 registerSmsReceiver() -// 创建 SmsReceiver 实例 -// 注册广播接收器,开始监听短信 -// 等待新短信到达 -// 新短信到达时,系统发送广播 -// SmsReceiver 的 onReceive 方法被调用 -// 处理短信内容 -// 发送数据更新广播 -// MainActivity 接收到更新广播 -// 更新 UI - Log.d("SMS_DEBUG", "MainActivity收到数据更新广播") + // 确保不重复注册 + if (smsReceiver != null) return + + Log.d("SMS_DEBUG", "MainActivity注册短信接收器") smsReceiver = SmsReceiver() val filter = IntentFilter(Telephony.Sms.Intents.SMS_RECEIVED_ACTION) registerReceiver(smsReceiver, filter) diff --git a/app/src/main/java/com/example/firstapp/activity/SettingActivity.kt b/app/src/main/java/com/example/firstapp/activity/SettingActivity.kt index f20a9c1..1d2bca2 100644 --- a/app/src/main/java/com/example/firstapp/activity/SettingActivity.kt +++ b/app/src/main/java/com/example/firstapp/activity/SettingActivity.kt @@ -10,6 +10,12 @@ import android.widget.Button import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope +import android.os.Build +import android.Manifest +import android.content.Context +import android.content.pm.PackageManager +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.content.ContextCompat import com.example.firstapp.databinding.ActivitySettingBinding import com.example.firstapp.R @@ -30,6 +36,26 @@ ViewModelProvider(this).get(HomeViewModel::class.java) } private var isFullscreen: Boolean = false + + // 添加通知权限请求 + private val notificationPermissionRequest = + registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> + if (isGranted) { + // 权限已授予,可以发送通知 + AlertDialog.Builder(this) + .setTitle("通知权限") + .setMessage("通知权限已开启,您将能收到重要提醒") + .setPositiveButton("确定", null) + .show() + } else { + // 权限被拒绝 + AlertDialog.Builder(this) + .setTitle("通知权限") + .setMessage("通知权限被拒绝,应用将无法发送提醒通知。您可以在系统设置中手动开启权限。") + .setPositiveButton("确定", null) + .show() + } + } @SuppressLint("ClickableViewAccessibility") override fun onCreate(savedInstanceState: Bundle?) { @@ -65,6 +91,8 @@ // }) + // 通知权限 + setupNotificationPermission() // 退出登录 logout() // 账号注销 @@ -76,6 +104,35 @@ } + private fun setupNotificationPermission() { + binding.notificationPermission.setOnClickListener { + // 请求通知权限(在Android 13及以上需要) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + if (ContextCompat.checkSelfPermission( + this, + Manifest.permission.POST_NOTIFICATIONS + ) != PackageManager.PERMISSION_GRANTED + ) { + notificationPermissionRequest.launch(Manifest.permission.POST_NOTIFICATIONS) + } else { + // 已经有权限 + AlertDialog.Builder(this) + .setTitle("通知权限") + .setMessage("您已经开启了通知权限") + .setPositiveButton("确定", null) + .show() + } + } else { + // Android 13以下版本不需要请求权限 + AlertDialog.Builder(this) + .setTitle("通知权限") + .setMessage("当前系统版本无需单独请求通知权限") + .setPositiveButton("确定", null) + .show() + } + } + } + private fun aboutCompany(){ binding.aboutCompany.setOnClickListener { 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 b689d58..5aa4f28 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,37 +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.view.Gravity -import android.widget.GridLayout -import android.widget.LinearLayout -import android.widget.Toast -import androidx.cardview.widget.CardView -import androidx.lifecycle.lifecycleScope -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.google.android.material.tabs.TabLayout import kotlinx.coroutines.launch +import java.text.SimpleDateFormat +import java.util.* + class DashboardFragment : Fragment() { @@ -57,6 +60,7 @@ } private val viewModel: DashboardViewModel by viewModels() + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -74,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() @@ -763,7 +775,242 @@ 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_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_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 @@ -789,8 +1036,10 @@ } // 添加月份标签 - val months = arrayOf("1月", "2月", "3月", "4月", "5月", "6月", - "7月", "8月", "9月", "10月", "11月", "12月") + 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 @@ -898,6 +1147,154 @@ } } + 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") // 最淡 diff --git a/app/src/main/res/layout/activity_phone_login.xml b/app/src/main/res/layout/activity_phone_login.xml index c0adc81..1328aea 100644 --- a/app/src/main/res/layout/activity_phone_login.xml +++ b/app/src/main/res/layout/activity_phone_login.xml @@ -66,7 +66,7 @@ android:layout_weight="1" android:background="@null" android:hint="请输入验证码" - android:text="888888" + android:text="" android:inputType="number" android:maxLength="6" android:textSize="16sp" diff --git a/app/src/main/res/layout/activity_setting.xml b/app/src/main/res/layout/activity_setting.xml index b66c6ac..3710dc8 100644 --- a/app/src/main/res/layout/activity_setting.xml +++ b/app/src/main/res/layout/activity_setting.xml @@ -35,7 +35,7 @@ </androidx.appcompat.widget.Toolbar> <LinearLayout - android:id="@+id/about_company" + android:id="@+id/notification_permission" android:layout_width="match_parent" android:layout_height="50dp" android:gravity="center" @@ -48,6 +48,24 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="5dp" + android:text="开启通知权限" + android:textColor="#000000" /> + </LinearLayout> + + <LinearLayout + android:id="@+id/about_company" + android:layout_width="match_parent" + android:layout_height="50dp" + android:gravity="center" + android:orientation="vertical" + android:background="#F9F9F9" + android:layout_marginTop="1dp" + > + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="5dp" android:text="关于我们" android:textColor="#000000" /> </LinearLayout> diff --git a/app/src/main/res/layout/layout_week_stats.xml b/app/src/main/res/layout/layout_week_stats.xml index a328499..641e13d 100644 --- a/app/src/main/res/layout/layout_week_stats.xml +++ b/app/src/main/res/layout/layout_week_stats.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> @@ -62,6 +63,14 @@ </androidx.cardview.widget.CardView> +<!-- <WebView--> +<!-- android:id="@+id/webView"--> +<!-- android:layout_width="match_parent"--> +<!-- android:layout_height="300dp"--> +<!-- tools:ignore="WebViewLayout" />--> + + + <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 8c1e7b6..d0e0c75 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -4,7 +4,7 @@ <dimen name="activity_vertical_margin">16dp</dimen> <dimen name="list_item_spacing">16dp</dimen> <dimen name="list_item_spacing_half">8dp</dimen> - <dimen name="heatmap_cell_size">12dp</dimen> + <dimen name="heatmap_cell_size">25dp</dimen> <dimen name="fab_margin">16dp</dimen> <!-- 徽章尺寸 --> -- Gitblit v1.9.3