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