Files
tuiche/lib/component/base/SleepCalendarWidget.dart
2025-07-10 11:47:27 +08:00

348 lines
11 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:vbvs_app/common/color/appConstants.dart';
import 'package:vbvs_app/common/util/FitTool.dart';
import 'package:vbvs_app/common/util/MyUtils.dart';
import 'package:vbvs_app/common/util/requestWithLog.dart';
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
import 'package:vbvs_app/component/tool/TopSlideNotification.dart';
import 'package:vbvs_app/controller/date/CalendarController.dart';
import 'package:vbvs_app/pages/main_bottom/component/main_page_b_bottom_change.dart';
import 'SleepdateWidget.dart';
class SleepCalendarWidget extends StatefulWidget {
final int? timestamp;
final ValueChanged<DateTime>? onDateSelected;
final int? type; // 新增参数,默认日历类型为日
final Color highlightColor; // ✅ 新增
final String? mac;
const SleepCalendarWidget({
super.key,
this.mac,
this.timestamp,
this.onDateSelected,
this.type = 1,
this.highlightColor = Colors.black, // ✅ 默认值
});
@override
State<SleepCalendarWidget> createState() => _SleepCalendarWidgetState();
}
class _SleepCalendarWidgetState extends State<SleepCalendarWidget> {
CalendarController calendarController = Get.find();
RxMap sleepDate = <String, dynamic>{}.obs;
RxList showLabel = <dynamic>[
{"level": 5, "name": "无报告", "color": "#9E9E9E"}
].obs;
// @override
// void initState() {
// super.initState();
// final initialDate = widget.timestamp != null
// ? DateTime.fromMillisecondsSinceEpoch(widget.timestamp!)
// : DateTime.now();
// calendarController.displayedMonth.value = initialDate;
// calendarController.selectedDate.value = initialDate;
// }
@override
void initState() {
super.initState();
final initialDate = widget.timestamp != null
? DateTime.fromMillisecondsSinceEpoch(widget.timestamp!)
: DateTime.now();
calendarController.displayedMonth.value = initialDate;
calendarController.selectedDate.value = initialDate;
// 初始化请求
fetchDate(initialDate);
fetchSleepColor();
// 每当月份变化时,重新请求数据
ever(calendarController.displayedMonth, (DateTime newMonth) {
fetchDate(newMonth);
});
}
Future<void> fetchDate(DateTime timeStamp) async {
final dateStr = timeStamp.toString().split(' ')[0];
await requestWithLog(
logTitle: "查询睡眠报告",
method: MyHttpMethod.get,
queryUrl:
"https://sleepdata.he-info.com/api/analysis/sleep/analysis?mac=${widget.mac}&time=$dateStr&type=3",
onSuccess: (res) {
sleepDate.value = res.data;
},
onFailure: (res) {
sleepDate.value = {};
},
);
}
Future<void> fetchSleepColor() async {
await requestWithLog(
logTitle: "查询睡眠报告",
method: MyHttpMethod.get,
queryUrl: "https://sleepdata.he-info.com/api/analysis/sleep/score/type",
onSuccess: (res) {
showLabel.value = [
...res.data,
{"level": 5, "name": "无报告", "color": "#9E9E9E"},
// ✅ 注意拼写是 scoreList
];
},
onFailure: (res) {
showLabel.value = [
{"level": 5, "name": "无报告", "color": "#9E9E9E"},
];
},
);
}
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20.rpx),
topRight: Radius.circular(20.rpx),
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildHeader(),
_buildCalendarBody(showLabel),
],
),
);
}
Widget _buildHeader() {
return Container(
width: double.infinity,
constraints: BoxConstraints(minHeight: 90.rpx),
decoration: BoxDecoration(
color: const Color(0xFF313541),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20.rpx),
topRight: Radius.circular(20.rpx),
),
),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 65.rpx),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ClickableContainer(
backgroundColor: Colors.transparent,
highlightColor: Colors.grey,
padding: EdgeInsets.all(8.rpx),
onTap: () => calendarController.previousMonth(),
child: Icon(
Icons.arrow_back_ios_new,
color: const Color(0xFF6D6F73),
size: 24.rpx,
),
),
Obx(() => Text(
'${calendarController.displayedMonth.value.year}${calendarController.displayedMonth.value.month}',
style: TextStyle(
color: Colors.white,
fontSize: 30.rpx,
),
)),
ClickableContainer(
backgroundColor: Colors.transparent,
highlightColor: Colors.grey,
padding: EdgeInsets.all(8.rpx),
onTap: () => calendarController.nextMonth(),
child: Icon(
Icons.arrow_forward_ios,
color: const Color(0xFF6D6F73),
size: 24.rpx,
),
),
],
),
),
);
}
Widget _buildCalendarBody(List<dynamic> showLabel) {
return Container(
width: double.infinity,
constraints: BoxConstraints(minHeight: 720.rpx),
decoration: const BoxDecoration(color: Color(0xFF242835)),
child: Padding(
// padding: EdgeInsetsDirectional.fromSTEB(65.rpx, 13.rpx, 65.rpx, 38.rpx),
padding: EdgeInsetsDirectional.fromSTEB(65.rpx, 0.rpx, 65.rpx, 0.rpx),
child: Obx(() {
final daysInMonth = calendarController.getDaysInMonth();
final calendarRows = calendarController.getCalendarRows(daysInMonth);
final selectedDate = calendarController.selectedDate.value;
return Column(
mainAxisSize: MainAxisSize.max,
children: [
// 仅当为日历模式显示周标题
_buildWeekdayHeader(),
// 日历日期格子支持日和周的高亮逻辑sleep
_buildCalendarGrid(calendarRows, selectedDate, sleepDate.value),
// TODO: 你可以扩展 month 类型的展示
SizedBox(height: 55.rpx),
_buildLegend(showLabel),
],
);
}),
),
);
}
Widget _buildWeekdayHeader() {
return Container(
constraints: BoxConstraints(minHeight: 90.rpx),
child: Row(
children: ["", "", "", "", "", "", ""].map((day) {
return Expanded(
child: Center(
child: Text(
day,
style: TextStyle(
color: stringToColor("#FFFFFF"),
fontSize: AppConstants().normal_text_fontSize,
),
),
),
);
}).toList(),
),
);
}
Widget _buildCalendarGrid(List<List<DateTime>> calendarRows,
DateTime? selectedDate, Map sleepDate) {
final isMonthSelected = widget.type == 3;
Widget content = Column(
children: calendarRows.map((week) {
final isWeekSelected = widget.type == 2 &&
selectedDate != null &&
week.any((d) =>
d.year == selectedDate.year &&
d.month == selectedDate.month &&
d.day == selectedDate.day);
final row = Row(
children: week.map((date) {
if (date.year == 0) {
return const Expanded(child: SizedBox.shrink());
}
final isSelected = widget.type == 1 &&
selectedDate != null &&
date.year == selectedDate.year &&
date.month == selectedDate.month &&
date.day == selectedDate.day;
return Expanded(
child: SleepdateWidget(
highlightColor: widget.highlightColor, // ✅ 传入高亮颜色
sleepDate: sleepDate,
date: date,
isSelected: isSelected,
onTap: () {
calendarController.selectDate(date);
widget.onDateSelected?.call(date);
Get.back();
},
),
);
}).toList(),
);
if (isWeekSelected) {
return Container(
decoration: BoxDecoration(
color: widget.highlightColor,
borderRadius:
BorderRadius.circular(AppConstants().button_container_radius),
),
padding: EdgeInsets.symmetric(vertical: 6.rpx),
margin: EdgeInsets.symmetric(vertical: 6.rpx),
child: row,
);
} else {
return row;
}
}).toList(),
);
// 如果是月份模式,包一层黑色背景容器
if (isMonthSelected && selectedDate != null) {
final displayedMonth = calendarController.displayedMonth.value;
final isSameMonth = displayedMonth.year == selectedDate.year &&
displayedMonth.month == selectedDate.month;
if (isSameMonth) {
// ✅ 当前显示的月 == 当前选中的月 → 加高亮外框
return Container(
decoration: BoxDecoration(
color: widget.highlightColor, // 背景淡色
borderRadius:
BorderRadius.circular(AppConstants().normal_container_radius),
// border: Border.all(
// color: widget.highlightColor,
// width: 2.rpx,
// ),
),
// padding: EdgeInsets.symmetric(vertical: 0.rpx, horizontal: 0.rpx),
// margin: EdgeInsets.symmetric(vertical: 0.rpx),
child: content,
);
} else {
return content;
}
} else {
return content;
}
}
Widget _buildLegend(List showLabel) {
return Wrap(
spacing: 20.rpx,
runSpacing: 20.rpx,
children: showLabel.map((item) {
return Container(
padding: EdgeInsets.all(5.rpx),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 20.rpx,
height: 20.rpx,
decoration: BoxDecoration(
color: stringToColor(item["color"]),
borderRadius: BorderRadius.circular(10.rpx),
),
),
SizedBox(width: 8.rpx),
Text(
item["name"],
style: TextStyle(
color: Colors.white,
fontSize: 24.rpx,
),
),
],
),
);
}).toList(),
);
}
}