更新
This commit is contained in:
@@ -67,6 +67,13 @@
|
|||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<!-- <intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="taihecare" android:host="share" />
|
||||||
|
</intent-filter> -->
|
||||||
|
|
||||||
</activity>
|
</activity>
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 63 KiB |
1
assets/img/icon/explain.svg
Normal file
1
assets/img/icon/explain.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21.73 21.73"><defs><style>.cls-1{opacity:0.6;}.cls-2{fill:#fff;}</style></defs><title>资源 236 </title><g id="图层_2" data-name="图层 2"><g id="图层_1-2" data-name="图层 1"><g class="cls-1"><path class="cls-2" d="M10.86,14.93a.91.91,0,1,0,.91.91A.91.91,0,0,0,10.86,14.93Z"/><path class="cls-2" d="M10.86,5a.91.91,0,0,0-.91.91v7a.91.91,0,1,0,1.82,0v-7A.9.9,0,0,0,10.86,5Z"/><path class="cls-2" d="M10.86,0A10.87,10.87,0,1,0,21.73,10.86,10.87,10.87,0,0,0,10.86,0Zm9,10.86a9,9,0,1,1-9-9A9,9,0,0,1,19.9,10.86Z"/></g></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 589 B |
@@ -353,5 +353,11 @@
|
|||||||
"版本":"版本:",
|
"版本":"版本:",
|
||||||
"日报":"日报",
|
"日报":"日报",
|
||||||
"月报":"月报",
|
"月报":"月报",
|
||||||
"周报":"周报"
|
"周报":"周报",
|
||||||
|
"4g设备配置wifi提示":"该设备为4G设备,无需配置wifi",
|
||||||
|
"微信客服提示":"请先安装微信APP,再联系客服",
|
||||||
|
"打开微信客服提示":"正在打开微信客服...",
|
||||||
|
"身高":"身高",
|
||||||
|
"身高输入提示":"请输入身高"
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -3,9 +3,9 @@ class CommonVariables {
|
|||||||
|
|
||||||
|
|
||||||
// 企业微信客服拉起的url地址
|
// 企业微信客服拉起的url地址
|
||||||
static String wxKfUrl = "https://work.weixin.qq.com/kfid/kfc7d2337b9c07b1269";
|
static String wxKfUrl = "https://work.weixin.qq.com/kfid/kfcab6a07e8aac68945";
|
||||||
// 企业微信ID
|
// 企业微信ID
|
||||||
static String wxCorpId = "ww51feda6026280cd0";
|
static String wxCorpId = "wwc17348c75dbde1dc";
|
||||||
//ICP备案号
|
//ICP备案号
|
||||||
static String ICPRightCode = "皖ICP备2024068219号-1A";
|
static String ICPRightCode = "皖ICP备2024068219号-1A";
|
||||||
//公司名称
|
//公司名称
|
||||||
|
|||||||
@@ -163,16 +163,20 @@ class MyUtils {
|
|||||||
return '${twoDigits(date.month)}/${twoDigits(date.day)}';
|
return '${twoDigits(date.month)}/${twoDigits(date.day)}';
|
||||||
}
|
}
|
||||||
|
|
||||||
static String getFormatChineseTime(date) {
|
static String getFormatChineseTime(int date, {bool showWeekday = true}) {
|
||||||
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(date);
|
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(date);
|
||||||
// 格式化年月日
|
// 格式化年月日
|
||||||
String dateStr = DateFormat('yyyy年MM月dd日').format(dateTime);
|
String dateStr = DateFormat('yyyy年MM月dd日').format(dateTime);
|
||||||
// 获取星期
|
|
||||||
const weekDays = ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'];
|
if (!showWeekday) return dateStr;
|
||||||
String weekStr = weekDays[dateTime.weekday - 1];
|
|
||||||
|
// 获取星期
|
||||||
|
const weekDays = ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'];
|
||||||
|
String weekStr = weekDays[dateTime.weekday - 1];
|
||||||
|
|
||||||
|
return '$dateStr $weekStr';
|
||||||
|
}
|
||||||
|
|
||||||
return '$dateStr $weekStr';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Color stringToColor(String hexColor) {
|
Color stringToColor(String hexColor) {
|
||||||
|
|||||||
@@ -10,11 +10,15 @@ import 'SleepdateWidget.dart';
|
|||||||
class SleepCalendarWidget extends StatefulWidget {
|
class SleepCalendarWidget extends StatefulWidget {
|
||||||
final int? timestamp;
|
final int? timestamp;
|
||||||
final ValueChanged<DateTime>? onDateSelected;
|
final ValueChanged<DateTime>? onDateSelected;
|
||||||
|
final int? type; // 新增参数,默认日历类型为日
|
||||||
|
final Color highlightColor; // ✅ 新增
|
||||||
|
|
||||||
const SleepCalendarWidget({
|
const SleepCalendarWidget({
|
||||||
super.key,
|
super.key,
|
||||||
this.timestamp,
|
this.timestamp,
|
||||||
this.onDateSelected,
|
this.onDateSelected,
|
||||||
|
this.type = 1,
|
||||||
|
this.highlightColor = Colors.black, // ✅ 默认值
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -55,172 +59,233 @@ class _SleepCalendarWidgetState extends State<SleepCalendarWidget> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
_buildHeader(),
|
||||||
width: double.infinity,
|
_buildCalendarBody(showLabel),
|
||||||
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:
|
|
||||||
EdgeInsetsDirectional.fromSTEB(65.rpx, 0.rpx, 65.rpx, 0.rpx),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
ClickableContainer(
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
highlightColor: Colors.grey,
|
|
||||||
padding: EdgeInsetsDirectional.fromSTEB(
|
|
||||||
8.rpx, 8.rpx, 8.rpx, 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,
|
|
||||||
letterSpacing: 0.0,
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
ClickableContainer(
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
highlightColor: Colors.grey,
|
|
||||||
padding: EdgeInsetsDirectional.fromSTEB(
|
|
||||||
8.rpx, 8.rpx, 8.rpx, 8.rpx),
|
|
||||||
onTap: () => calendarController.nextMonth(),
|
|
||||||
child: Icon(
|
|
||||||
Icons.arrow_forward_ios,
|
|
||||||
color: const Color(0xFF6D6F73),
|
|
||||||
size: 24.rpx,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
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),
|
|
||||||
child: Obx(() {
|
|
||||||
final daysInMonth = calendarController.getDaysInMonth();
|
|
||||||
final calendarRows =
|
|
||||||
calendarController.getCalendarRows(daysInMonth);
|
|
||||||
final selectedDate = calendarController.selectedDate.value;
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
children: [
|
|
||||||
// Weekdays Header
|
|
||||||
Container(
|
|
||||||
constraints: BoxConstraints(minHeight: 90.rpx),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
for (var day in ["一", "二", "三", "四", "五", "六", "日"])
|
|
||||||
Expanded(
|
|
||||||
child: Center(
|
|
||||||
child: Text(
|
|
||||||
day,
|
|
||||||
style: TextStyle(
|
|
||||||
color: stringToColor("#FFFFFF"),
|
|
||||||
fontSize:
|
|
||||||
AppConstants().normal_text_fontSize,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// Calendar days
|
|
||||||
Column(
|
|
||||||
children: calendarRows.map((week) {
|
|
||||||
return Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: week.map((date) {
|
|
||||||
if (date.year == 0) {
|
|
||||||
return Expanded(
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.all(0.rpx),
|
|
||||||
child: SizedBox.shrink(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
final isSelected = selectedDate != null &&
|
|
||||||
date.year == selectedDate.year &&
|
|
||||||
date.month == selectedDate.month &&
|
|
||||||
date.day == selectedDate.day;
|
|
||||||
return Expanded(
|
|
||||||
child: SleepdateWidget(
|
|
||||||
date: date,
|
|
||||||
isSelected: isSelected,
|
|
||||||
onTap: () {
|
|
||||||
calendarController.selectDate(date);
|
|
||||||
if (widget.onDateSelected != null) {
|
|
||||||
widget.onDateSelected!(date);
|
|
||||||
}
|
|
||||||
print(date);
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
SizedBox(height: 55.rpx),
|
|
||||||
Wrap(
|
|
||||||
spacing: 20.rpx,
|
|
||||||
runSpacing: 20.rpx,
|
|
||||||
children: showLabel.map<Widget>((item) {
|
|
||||||
return Container(
|
|
||||||
padding: EdgeInsets.all(5.rpx),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 20.rpx,
|
|
||||||
height: 20.rpx,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: item["color"],
|
|
||||||
borderRadius: BorderRadius.circular(10.rpx),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 8.rpx),
|
|
||||||
Text(
|
|
||||||
item["name"],
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 24.rpx,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<Map<String, 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),
|
||||||
|
child: Obx(() {
|
||||||
|
final daysInMonth = calendarController.getDaysInMonth();
|
||||||
|
final calendarRows = calendarController.getCalendarRows(daysInMonth);
|
||||||
|
final selectedDate = calendarController.selectedDate.value;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
// 仅当为日历模式显示周标题
|
||||||
|
_buildWeekdayHeader(),
|
||||||
|
// 日历日期格子,支持日和周的高亮逻辑
|
||||||
|
|
||||||
|
_buildCalendarGrid(calendarRows, selectedDate),
|
||||||
|
// 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) {
|
||||||
|
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, // ✅ 传入高亮颜色
|
||||||
|
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<Map<String, dynamic>> 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: item["color"],
|
||||||
|
borderRadius: BorderRadius.circular(10.rpx),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 8.rpx),
|
||||||
|
Text(
|
||||||
|
item["name"],
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 24.rpx,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ class SleepdateWidget extends StatelessWidget {
|
|||||||
final DateTime date;
|
final DateTime date;
|
||||||
final bool isSelected;
|
final bool isSelected;
|
||||||
final VoidCallback onTap;
|
final VoidCallback onTap;
|
||||||
|
final Color highlightColor; // 新增
|
||||||
|
|
||||||
const SleepdateWidget({
|
const SleepdateWidget({
|
||||||
super.key,
|
super.key,
|
||||||
required this.date,
|
required this.date,
|
||||||
required this.isSelected,
|
required this.isSelected,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
|
this.highlightColor = Colors.black, // 默认值黑色
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -25,8 +27,8 @@ class SleepdateWidget extends StatelessWidget {
|
|||||||
width: 90.rpx,
|
width: 90.rpx,
|
||||||
height: 90.rpx,
|
height: 90.rpx,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(30.rpx),
|
borderRadius: BorderRadius.circular(30.rpx),
|
||||||
color: isSelected ? Colors.black : Colors.transparent,
|
color: isSelected ? highlightColor : Colors.transparent, // 使用传入的颜色
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsetsDirectional.fromSTEB(10.rpx, 10.rpx, 10.rpx, 10.rpx),
|
padding: EdgeInsetsDirectional.fromSTEB(10.rpx, 10.rpx, 10.rpx, 10.rpx),
|
||||||
@@ -35,15 +37,13 @@ class SleepdateWidget extends StatelessWidget {
|
|||||||
color: Color(0xFFDC1C1C),
|
color: Color(0xFFDC1C1C),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
child: Align(
|
alignment: Alignment.center,
|
||||||
alignment: AlignmentDirectional(0, 0),
|
child: Text(
|
||||||
child: Text(
|
'${date.day}',
|
||||||
'${date.day}',
|
style: TextStyle(
|
||||||
style: TextStyle(
|
color: Colors.white,
|
||||||
color: Colors.white,
|
fontSize: 26.rpx,
|
||||||
fontSize: 26.rpx,
|
letterSpacing: 0.0,
|
||||||
letterSpacing: 0.0,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
47
lib/component/tool/ToggleColorContainer.dart
Normal file
47
lib/component/tool/ToggleColorContainer.dart
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ToggleColorContainer extends StatelessWidget {
|
||||||
|
final Color initialColor;
|
||||||
|
final Color toggledColor;
|
||||||
|
final EdgeInsetsGeometry padding;
|
||||||
|
final Widget child;
|
||||||
|
final double borderRadius;
|
||||||
|
final VoidCallback onToggle;
|
||||||
|
final bool toggled; // 新增,外部传入是否高亮
|
||||||
|
|
||||||
|
const ToggleColorContainer({
|
||||||
|
Key? key,
|
||||||
|
required this.initialColor,
|
||||||
|
required this.toggledColor,
|
||||||
|
required this.padding,
|
||||||
|
required this.child,
|
||||||
|
required this.onToggle,
|
||||||
|
required this.toggled,
|
||||||
|
this.borderRadius = 0,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final Color currentColor = toggled ? toggledColor : initialColor;
|
||||||
|
|
||||||
|
return Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Ink(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: currentColor,
|
||||||
|
borderRadius: BorderRadius.circular(borderRadius),
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(borderRadius),
|
||||||
|
onTap: onToggle,
|
||||||
|
splashColor: Colors.transparent,
|
||||||
|
highlightColor: Colors.transparent,
|
||||||
|
child: Padding(
|
||||||
|
padding: padding,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
//蓝牙指令
|
//蓝牙指令
|
||||||
|
|
||||||
// wifi列表指令
|
|
||||||
import 'package:EasyDartModule/EasyDartModule.dart' as edm;
|
import 'package:EasyDartModule/EasyDartModule.dart' as edm;
|
||||||
import 'package:easydevice/src/app/thapp.dart';
|
import 'package:easydevice/src/app/thapp.dart';
|
||||||
import 'package:vbvs_app/common/util/DailyLogUtils.dart';
|
import 'package:vbvs_app/common/util/DailyLogUtils.dart';
|
||||||
|
|
||||||
|
// wifi列表指令
|
||||||
getWifiList(THapp tHapp) async {
|
getWifiList(THapp tHapp) async {
|
||||||
try {
|
try {
|
||||||
|
print("wscan scan");
|
||||||
edm.EasyDartModule.logger.info("发送请求网络列表指令");
|
edm.EasyDartModule.logger.info("发送请求网络列表指令");
|
||||||
DailyLogUtils.writeLog("发送请求网络列表指令");
|
DailyLogUtils.writeLog("发送请求网络列表指令");
|
||||||
List data = [];
|
List data = [];
|
||||||
var wifilist = await tHapp.send("wscan scan", true, (log) {
|
var wifilist = await tHapp.send("wscan scan", true, (log) {
|
||||||
print("[bles]${log.log}");
|
print("[aaaaaa]${log.log}");
|
||||||
if (log.log.contains("SCAN RESULT OVER!")) {
|
if (log.log.contains("SCAN RESULT OVER!")) {
|
||||||
final wifiList = <Map<String, dynamic>>[];
|
final wifiList = <Map<String, dynamic>>[];
|
||||||
final items = log.log.split('[wifi]: SCAN RESULT ITEM:');
|
final items = log.log.split('[wifi]: SCAN RESULT ITEM:');
|
||||||
@@ -39,7 +40,7 @@ getWifiList(THapp tHapp) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}, 10);
|
}, 2);
|
||||||
|
|
||||||
return wifilist;
|
return wifilist;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -93,7 +94,7 @@ Future<bool> sendWifiSetting(wifiItem, String password, THapp tHapp) async {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}, 10);
|
}, 1);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
edm.EasyDartModule.logger.error("WiFi配置超时或失败");
|
edm.EasyDartModule.logger.error("WiFi配置超时或失败");
|
||||||
@@ -107,13 +108,71 @@ Future<bool> sendWifiSetting(wifiItem, String password, THapp tHapp) async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getDeviceWifiStatus(THapp tHapp, int times) async {
|
||||||
|
// edm.EasyDartModule.logger.info("发送请求设备已配置网络状态指令");
|
||||||
|
// DailyLogUtils.writeLog("发送请求设备已配置网络状态指令");
|
||||||
|
// try {
|
||||||
|
// var result = await tHapp.send("at+system info", true, (ss) {
|
||||||
|
// var log = ss.log;
|
||||||
|
|
||||||
|
// // 匹配设备状态
|
||||||
|
// final statusMatch = RegExp(r'Status=([^\s]+)').firstMatch(log);
|
||||||
|
// final status = statusMatch?.group(1);
|
||||||
|
// if (status != null) {
|
||||||
|
// print('提取到的 status: $status');
|
||||||
|
|
||||||
|
// // 如果设备连接状态是 "connect",继续检测
|
||||||
|
// if (status.contains('connect')) {
|
||||||
|
// ss.result = true;
|
||||||
|
// ss.over = true;
|
||||||
|
|
||||||
|
// // 匹配 Wi-Fi 连接信息
|
||||||
|
// final wifiInfoMatch = RegExp(
|
||||||
|
// r'WIFI CONNECTED INFO:SSID=([^\s]+),RSSI=([-0-9]+),AUTH=([0-9]+),CH=([0-9]+),BSSID=([A-F0-9]+)')
|
||||||
|
// .firstMatch(log);
|
||||||
|
// if (wifiInfoMatch != null) {
|
||||||
|
// final ssid = wifiInfoMatch.group(1);
|
||||||
|
// final rssi = wifiInfoMatch.group(2);
|
||||||
|
// final auth = wifiInfoMatch.group(3);
|
||||||
|
// final ch = wifiInfoMatch.group(4);
|
||||||
|
// final bssid = wifiInfoMatch.group(5);
|
||||||
|
|
||||||
|
// // 打印并返回 Wi-Fi 信息
|
||||||
|
// print(
|
||||||
|
// 'Wi-Fi 信息: SSID=$ssid, RSSI=$rssi, AUTH=$auth, CH=$ch, BSSID=$bssid');
|
||||||
|
|
||||||
|
// // 停止监听并返回信息
|
||||||
|
// ss.result = {
|
||||||
|
// 'ssid': ssid,
|
||||||
|
// 'rssi': rssi,
|
||||||
|
// 'auth': auth,
|
||||||
|
// 'ch': ch,
|
||||||
|
// 'bssid': bssid,
|
||||||
|
// };
|
||||||
|
// ss.over = true;
|
||||||
|
// return ss.result;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 未找到状态或Wi-Fi信息时,返回 false
|
||||||
|
// return false;
|
||||||
|
// }, times);
|
||||||
|
|
||||||
|
// return result;
|
||||||
|
// } catch (e) {
|
||||||
|
// print(e);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
getDeviceWifiStatus(THapp tHapp, int times) async {
|
getDeviceWifiStatus(THapp tHapp, int times) async {
|
||||||
edm.EasyDartModule.logger.info("发送请求设备已配置网络状态指令");
|
edm.EasyDartModule.logger.info("发送请求设备已配置网络状态指令");
|
||||||
DailyLogUtils.writeLog("发送请求设备已配置网络状态指令");
|
DailyLogUtils.writeLog("发送请求设备已配置网络状态指令");
|
||||||
|
print("at+system info");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var result = await tHapp.send("at+system info", true, (ss) {
|
var result = await tHapp.send("at+system info", true, (ss) {
|
||||||
var log = ss.log;
|
var log = ss.log;
|
||||||
|
|
||||||
// 匹配设备状态
|
// 匹配设备状态
|
||||||
final statusMatch = RegExp(r'Status=([^\s]+)').firstMatch(log);
|
final statusMatch = RegExp(r'Status=([^\s]+)').firstMatch(log);
|
||||||
final status = statusMatch?.group(1);
|
final status = statusMatch?.group(1);
|
||||||
@@ -122,9 +181,6 @@ getDeviceWifiStatus(THapp tHapp, int times) async {
|
|||||||
|
|
||||||
// 如果设备连接状态是 "connect",继续检测
|
// 如果设备连接状态是 "connect",继续检测
|
||||||
if (status.contains('connect')) {
|
if (status.contains('connect')) {
|
||||||
ss.result = true;
|
|
||||||
ss.over = true;
|
|
||||||
|
|
||||||
// 匹配 Wi-Fi 连接信息
|
// 匹配 Wi-Fi 连接信息
|
||||||
final wifiInfoMatch = RegExp(
|
final wifiInfoMatch = RegExp(
|
||||||
r'WIFI CONNECTED INFO:SSID=([^\s]+),RSSI=([-0-9]+),AUTH=([0-9]+),CH=([0-9]+),BSSID=([A-F0-9]+)')
|
r'WIFI CONNECTED INFO:SSID=([^\s]+),RSSI=([-0-9]+),AUTH=([0-9]+),CH=([0-9]+),BSSID=([A-F0-9]+)')
|
||||||
@@ -149,12 +205,12 @@ getDeviceWifiStatus(THapp tHapp, int times) async {
|
|||||||
'bssid': bssid,
|
'bssid': bssid,
|
||||||
};
|
};
|
||||||
ss.over = true;
|
ss.over = true;
|
||||||
return ss.result;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 未找到状态或Wi-Fi信息时,返回 false
|
// 继续监听
|
||||||
return false;
|
return false;
|
||||||
}, times);
|
}, times);
|
||||||
|
|
||||||
@@ -163,3 +219,37 @@ getDeviceWifiStatus(THapp tHapp, int times) async {
|
|||||||
print(e);
|
print(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String> getDeviceNetVersion(THapp tHapp, int times) async {
|
||||||
|
edm.EasyDartModule.logger.info("发送请求设备的网络信息");
|
||||||
|
DailyLogUtils.writeLog("发送请求设备的网络信息");
|
||||||
|
print("ls /root/mnt");
|
||||||
|
|
||||||
|
String netType = "unknown";
|
||||||
|
|
||||||
|
try {
|
||||||
|
var result = await tHapp.send("ls /root/mnt", true, (ss) {
|
||||||
|
var log = ss.log;
|
||||||
|
print("[获取]$log");
|
||||||
|
|
||||||
|
if (log.contains("wifi.json") || log.contains("a76xx.json")) {
|
||||||
|
if (log.contains("wifi.json")) {
|
||||||
|
netType = "wifi";
|
||||||
|
} else if (log.contains("a76xx.json")) {
|
||||||
|
netType = "4g";
|
||||||
|
}
|
||||||
|
ss.over = true;
|
||||||
|
ss.result = netType;
|
||||||
|
// 继续监听
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
ss.over = false;
|
||||||
|
return false;
|
||||||
|
}, times, 15);
|
||||||
|
|
||||||
|
return netType;
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
return netType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -66,8 +66,14 @@ class BlueteethBindController extends GetControllerEx<BlueteethBindModel> {
|
|||||||
RxMap connect_wifi = {}.obs;
|
RxMap connect_wifi = {}.obs;
|
||||||
RxString scanMac = "".obs;
|
RxString scanMac = "".obs;
|
||||||
|
|
||||||
String? currentDeviceMac;
|
RxString? currentDeviceMac = "".obs;
|
||||||
RxString? cid = "".obs ;//校准id
|
RxString? cid = "".obs ;
|
||||||
|
|
||||||
|
RxString search = "".obs;//搜索关键字
|
||||||
|
|
||||||
|
RxInt connectStatus = 0.obs;
|
||||||
|
|
||||||
|
RxMap selectWifi = {}.obs; //正在连接wifi信息
|
||||||
|
|
||||||
// 安全展示 TopSlideNotification
|
// 安全展示 TopSlideNotification
|
||||||
void safeShowNotification(String msg) {
|
void safeShowNotification(String msg) {
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ class BodyDeviceController extends GetControllerEx<BodyDeviceModel> {
|
|||||||
|
|
||||||
RxString keyWord = "".obs;
|
RxString keyWord = "".obs;
|
||||||
|
|
||||||
|
String wifiMac = "";
|
||||||
|
|
||||||
Future<ApiResponse> getDeviceNum() async {
|
Future<ApiResponse> getDeviceNum() async {
|
||||||
try {
|
try {
|
||||||
ApiResponse apiResponse = ApiResponse(code: -1, msg: "设备.设备列表请求失败".tr);
|
ApiResponse apiResponse = ApiResponse(code: -1, msg: "设备.设备列表请求失败".tr);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import 'package:get_storage/get_storage.dart';
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:vbvs_app/common/color/ServiceConstant.dart';
|
import 'package:vbvs_app/common/color/ServiceConstant.dart';
|
||||||
import 'package:vbvs_app/common/color/app_uri_status.dart';
|
import 'package:vbvs_app/common/color/app_uri_status.dart';
|
||||||
|
import 'package:vbvs_app/common/util/CommonVariables.dart';
|
||||||
import 'package:vbvs_app/common/util/MyUtils.dart';
|
import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||||
import 'package:vbvs_app/common/util/requestWithLog.dart';
|
import 'package:vbvs_app/common/util/requestWithLog.dart';
|
||||||
import 'package:vbvs_app/component/tool/TopSlideNotification.dart';
|
import 'package:vbvs_app/component/tool/TopSlideNotification.dart';
|
||||||
@@ -231,6 +232,21 @@ class LoginController extends GetControllerEx<LoginModel> {
|
|||||||
return apiResponse;
|
return apiResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> openWeChatCustomerService(BuildContext context) async {
|
||||||
|
bool isWeChatInstalled = await fluwx.isWeChatInstalled;
|
||||||
|
if (!isWeChatInstalled) {
|
||||||
|
TopSlideNotification.show(context,
|
||||||
|
text: "微信客服提示".tr,
|
||||||
|
textColor: themeController.currentColor.sc9);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TopSlideNotification.show(context,
|
||||||
|
text: "打开微信客服提示".tr, textColor: themeController.currentColor.sc1);
|
||||||
|
await fluwx.open(
|
||||||
|
target: CustomerServiceChat(
|
||||||
|
corpId: CommonVariables.wxCorpId, url: CommonVariables.wxKfUrl));
|
||||||
|
}
|
||||||
|
|
||||||
//退出登录
|
//退出登录
|
||||||
// Future<void> logout() async {
|
// Future<void> logout() async {
|
||||||
// await repository.logout();
|
// await repository.logout();
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ class PersonController extends GetControllerEx<PersonModel> {
|
|||||||
RxInt gender = 1.obs;
|
RxInt gender = 1.obs;
|
||||||
RxString birthday = "".obs;
|
RxString birthday = "".obs;
|
||||||
RxInt weight = 65.obs;
|
RxInt weight = 65.obs;
|
||||||
|
RxInt height = 175.obs;
|
||||||
DateTime? dateTime = DateTime.now(); //选择时间
|
DateTime? dateTime = DateTime.now(); //选择时间
|
||||||
RxList diseaseList = [].obs;
|
RxList diseaseList = [].obs;
|
||||||
|
|
||||||
@@ -89,6 +90,10 @@ class PersonController extends GetControllerEx<PersonModel> {
|
|||||||
apiResponse.msg = "请输入体重".tr;
|
apiResponse.msg = "请输入体重".tr;
|
||||||
return apiResponse;
|
return apiResponse;
|
||||||
}
|
}
|
||||||
|
if (height.value == 0) {
|
||||||
|
apiResponse.msg = "请输入身高".tr;
|
||||||
|
return apiResponse;
|
||||||
|
}
|
||||||
|
|
||||||
var data = {
|
var data = {
|
||||||
"id": currentPersonId.value,
|
"id": currentPersonId.value,
|
||||||
@@ -98,6 +103,7 @@ class PersonController extends GetControllerEx<PersonModel> {
|
|||||||
? MyUtils.formatBindTime(dateTime!)
|
? MyUtils.formatBindTime(dateTime!)
|
||||||
: birthday.value,
|
: birthday.value,
|
||||||
"weight": weight.value,
|
"weight": weight.value,
|
||||||
|
"height": height.value,
|
||||||
"disease": selectedDiseaseIds.value,
|
"disease": selectedDiseaseIds.value,
|
||||||
};
|
};
|
||||||
var response =
|
var response =
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import 'package:vbvs_app/controller/time/countdown_controller.dart';
|
|||||||
import 'package:vbvs_app/model/api_response.dart';
|
import 'package:vbvs_app/model/api_response.dart';
|
||||||
import 'package:vbvs_app/model/user_data.dart';
|
import 'package:vbvs_app/model/user_data.dart';
|
||||||
|
|
||||||
|
|
||||||
part 'user_info_controller.g.dart';
|
part 'user_info_controller.g.dart';
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
@@ -134,16 +135,16 @@ class UserInfoController extends GetControllerEx<UserInfoModel> {
|
|||||||
String serviceApi = ServiceConstant.user_info;
|
String serviceApi = ServiceConstant.user_info;
|
||||||
String queryUrl = "${serviceAddress}${serviceName}${serviceApi}";
|
String queryUrl = "${serviceAddress}${serviceName}${serviceApi}";
|
||||||
String? language = "";
|
String? language = "";
|
||||||
if (languageController.selectLanguage != null) {
|
if (languageController.selectLanguage != null) {
|
||||||
language = languageController.selectLanguage.value!.language_code;
|
language = languageController.selectLanguage.value!.language_code;
|
||||||
}
|
}
|
||||||
if (language != null && language.isNotEmpty) {
|
if (language != null && language.isNotEmpty) {
|
||||||
if (queryUrl.contains("?")) {
|
if (queryUrl.contains("?")) {
|
||||||
queryUrl += "&lang=$language";
|
queryUrl += "&lang=$language";
|
||||||
} else {
|
} else {
|
||||||
queryUrl += "?lang=$language";
|
queryUrl += "?lang=$language";
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
final data = {
|
final data = {
|
||||||
"nickName": user.tmpNickName,
|
"nickName": user.tmpNickName,
|
||||||
if (user.tmpHead != null && user.tmpHead!.isNotEmpty)
|
if (user.tmpHead != null && user.tmpHead!.isNotEmpty)
|
||||||
@@ -180,16 +181,16 @@ class UserInfoController extends GetControllerEx<UserInfoModel> {
|
|||||||
String serviceApi = ServiceConstant.user_info;
|
String serviceApi = ServiceConstant.user_info;
|
||||||
String queryUrl = "${serviceAddress}${serviceName}${serviceApi}";
|
String queryUrl = "${serviceAddress}${serviceName}${serviceApi}";
|
||||||
String? language = "";
|
String? language = "";
|
||||||
if (languageController.selectLanguage != null) {
|
if (languageController.selectLanguage != null) {
|
||||||
language = languageController.selectLanguage.value!.language_code;
|
language = languageController.selectLanguage.value!.language_code;
|
||||||
}
|
}
|
||||||
if (language != null && language.isNotEmpty) {
|
if (language != null && language.isNotEmpty) {
|
||||||
if (queryUrl.contains("?")) {
|
if (queryUrl.contains("?")) {
|
||||||
queryUrl += "&lang=$language";
|
queryUrl += "&lang=$language";
|
||||||
} else {
|
} else {
|
||||||
queryUrl += "?lang=$language";
|
queryUrl += "?lang=$language";
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
var response = await EasyDartModule.dio.get(queryUrl);
|
var response = await EasyDartModule.dio.get(queryUrl);
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
var responseData =
|
var responseData =
|
||||||
@@ -238,4 +239,6 @@ class UserInfoController extends GetControllerEx<UserInfoModel> {
|
|||||||
countdownController.countdown.value = 0;
|
countdownController.countdown.value = 0;
|
||||||
return apiResponse;
|
return apiResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
10
lib/enum/CalendarType.dart
Normal file
10
lib/enum/CalendarType.dart
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
enum CalendarType {
|
||||||
|
day(1, '日'),
|
||||||
|
week(2, '周'),
|
||||||
|
month(3, '月');
|
||||||
|
|
||||||
|
final int code;
|
||||||
|
final String description;
|
||||||
|
|
||||||
|
const CalendarType(this.code, this.description);
|
||||||
|
}
|
||||||
@@ -5,9 +5,9 @@ import 'package:vbvs_app/common/color/appConstants.dart';
|
|||||||
import 'package:vbvs_app/common/util/FitTool.dart';
|
import 'package:vbvs_app/common/util/FitTool.dart';
|
||||||
import 'package:vbvs_app/common/util/MyUtils.dart';
|
import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||||
import 'package:vbvs_app/component/base/SleepCalendarWidget.dart';
|
import 'package:vbvs_app/component/base/SleepCalendarWidget.dart';
|
||||||
import 'package:vbvs_app/component/tool/TopSlideNotification.dart';
|
|
||||||
import 'package:vbvs_app/controller/device/device_calibration_controller.dart';
|
import 'package:vbvs_app/controller/device/device_calibration_controller.dart';
|
||||||
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
||||||
|
import 'package:vbvs_app/enum/CalendarType.dart';
|
||||||
|
|
||||||
getOnePicker(context, List arr, int checkIndex, Function onSelectedItemChanged,
|
getOnePicker(context, List arr, int checkIndex, Function onSelectedItemChanged,
|
||||||
{bool looping = false}) {
|
{bool looping = false}) {
|
||||||
@@ -924,6 +924,7 @@ void showProgressDialog(
|
|||||||
void showSleepCalendarBottomSheet({
|
void showSleepCalendarBottomSheet({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
int? timestamp,
|
int? timestamp,
|
||||||
|
int? type = 1, // 新增参数,默认值为1
|
||||||
required void Function(DateTime selectedDate) onDateSelected,
|
required void Function(DateTime selectedDate) onDateSelected,
|
||||||
}) {
|
}) {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
@@ -941,7 +942,9 @@ void showSleepCalendarBottomSheet({
|
|||||||
),
|
),
|
||||||
child: SleepCalendarWidget(
|
child: SleepCalendarWidget(
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
onDateSelected: onDateSelected, // 传递回调下去
|
type: type, // 传递类型给子组件
|
||||||
|
onDateSelected: onDateSelected,
|
||||||
|
// highlightColor: Colors.green,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ import 'package:vbvs_app/common/util/FitTool.dart';
|
|||||||
import 'package:vbvs_app/common/util/MyUtils.dart';
|
import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||||
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
||||||
import 'package:vbvs_app/component/tool/CustomCard.dart';
|
import 'package:vbvs_app/component/tool/CustomCard.dart';
|
||||||
|
import 'package:vbvs_app/component/tool/ToggleColorContainer.dart';
|
||||||
import 'package:vbvs_app/component/tool/TopSlideNotification.dart';
|
import 'package:vbvs_app/component/tool/TopSlideNotification.dart';
|
||||||
|
import 'package:vbvs_app/component/tool/cmd.dart';
|
||||||
import 'package:vbvs_app/controller/device/blueteeth_bind_controller.dart';
|
import 'package:vbvs_app/controller/device/blueteeth_bind_controller.dart';
|
||||||
import 'package:vbvs_app/controller/device/body_device_controller.dart';
|
import 'package:vbvs_app/controller/device/body_device_controller.dart';
|
||||||
import 'package:vbvs_app/controller/person/person_controller.dart';
|
import 'package:vbvs_app/controller/person/person_controller.dart';
|
||||||
@@ -41,6 +43,9 @@ class _DeviceDataComponentWidgetState extends State<DeviceDataComponentWidget> {
|
|||||||
OverlayEntry? _popupEntry;
|
OverlayEntry? _popupEntry;
|
||||||
BodyDeviceController bodyDeviceController = Get.find();
|
BodyDeviceController bodyDeviceController = Get.find();
|
||||||
PersonController personController = Get.find();
|
PersonController personController = Get.find();
|
||||||
|
bool _isPopupOpen = false;
|
||||||
|
|
||||||
|
var lisObj;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@@ -48,7 +53,15 @@ class _DeviceDataComponentWidgetState extends State<DeviceDataComponentWidget> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
void _showPopup() {
|
void _showPopup() {
|
||||||
|
setState(() {
|
||||||
|
_isPopupOpen = true;
|
||||||
|
});
|
||||||
final RenderBox? renderBox =
|
final RenderBox? renderBox =
|
||||||
_arrowKey.currentContext?.findRenderObject() as RenderBox?;
|
_arrowKey.currentContext?.findRenderObject() as RenderBox?;
|
||||||
if (renderBox == null) return;
|
if (renderBox == null) return;
|
||||||
@@ -67,7 +80,7 @@ class _DeviceDataComponentWidgetState extends State<DeviceDataComponentWidget> {
|
|||||||
|
|
||||||
final textStyle = TextStyle(
|
final textStyle = TextStyle(
|
||||||
fontSize: AppConstants().normal_text_fontSize,
|
fontSize: AppConstants().normal_text_fontSize,
|
||||||
color: themeController.currentColor.sc3, // 你也可以传 sc2
|
color: themeController.currentColor.sc3,
|
||||||
);
|
);
|
||||||
|
|
||||||
final allTexts = [
|
final allTexts = [
|
||||||
@@ -126,11 +139,17 @@ class _DeviceDataComponentWidgetState extends State<DeviceDataComponentWidget> {
|
|||||||
popupLeft = screenWidth - popupWidth - paddingOffset;
|
popupLeft = screenWidth - popupWidth - paddingOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setState(() {
|
||||||
|
// _isPopupOpen = false;
|
||||||
|
// });
|
||||||
_popupEntry?.remove();
|
_popupEntry?.remove();
|
||||||
_popupEntry = OverlayEntry(
|
_popupEntry = OverlayEntry(
|
||||||
builder: (context) => GestureDetector(
|
builder: (context) => GestureDetector(
|
||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_isPopupOpen = false;
|
||||||
|
});
|
||||||
_popupEntry?.remove();
|
_popupEntry?.remove();
|
||||||
_popupEntry = null;
|
_popupEntry = null;
|
||||||
},
|
},
|
||||||
@@ -222,6 +241,9 @@ class _DeviceDataComponentWidgetState extends State<DeviceDataComponentWidget> {
|
|||||||
: null,
|
: null,
|
||||||
text: "体征检测设备.首页展示".tr,
|
text: "体征检测设备.首页展示".tr,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
setState(() {
|
||||||
|
_isPopupOpen = false;
|
||||||
|
});
|
||||||
_popupEntry?.remove();
|
_popupEntry?.remove();
|
||||||
_popupEntry = null;
|
_popupEntry = null;
|
||||||
ApiResponse apiResponse =
|
ApiResponse apiResponse =
|
||||||
@@ -245,6 +267,9 @@ class _DeviceDataComponentWidgetState extends State<DeviceDataComponentWidget> {
|
|||||||
_buildMenuItem(
|
_buildMenuItem(
|
||||||
text: "体征检测设备.设备详情".tr,
|
text: "体征检测设备.设备详情".tr,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_isPopupOpen = false;
|
||||||
|
});
|
||||||
_popupEntry?.remove();
|
_popupEntry?.remove();
|
||||||
_popupEntry = null;
|
_popupEntry = null;
|
||||||
Get.toNamed("/deviceDetail", arguments: widget.device);
|
Get.toNamed("/deviceDetail", arguments: widget.device);
|
||||||
@@ -254,18 +279,12 @@ class _DeviceDataComponentWidgetState extends State<DeviceDataComponentWidget> {
|
|||||||
|
|
||||||
if (widget.device['bind_type'] == BindType.active.code) {
|
if (widget.device['bind_type'] == BindType.active.code) {
|
||||||
items.addAll([
|
items.addAll([
|
||||||
// _buildMenuItem(
|
|
||||||
// text: "WIFI配置".tr,
|
|
||||||
// onTap: () {
|
|
||||||
// _popupEntry?.remove();
|
|
||||||
// _popupEntry = null;
|
|
||||||
// // Get.toNamed("/deviceDetail", arguments: widget.device);
|
|
||||||
// dealWifi(widget.device['mac']);
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
_buildMenuItem(
|
_buildMenuItem(
|
||||||
text: "WIFI配置".tr,
|
text: "WIFI配置".tr,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_isPopupOpen = false;
|
||||||
|
});
|
||||||
_popupEntry?.remove();
|
_popupEntry?.remove();
|
||||||
_popupEntry = null;
|
_popupEntry = null;
|
||||||
dealWifi(widget.device['mac']);
|
dealWifi(widget.device['mac']);
|
||||||
@@ -274,6 +293,9 @@ class _DeviceDataComponentWidgetState extends State<DeviceDataComponentWidget> {
|
|||||||
_buildMenuItem(
|
_buildMenuItem(
|
||||||
text: "设备校准".tr,
|
text: "设备校准".tr,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_isPopupOpen = false;
|
||||||
|
});
|
||||||
_popupEntry?.remove();
|
_popupEntry?.remove();
|
||||||
_popupEntry = null;
|
_popupEntry = null;
|
||||||
BlueteethBindController blueteethBindController = Get.find();
|
BlueteethBindController blueteethBindController = Get.find();
|
||||||
@@ -284,6 +306,9 @@ class _DeviceDataComponentWidgetState extends State<DeviceDataComponentWidget> {
|
|||||||
_buildMenuItem(
|
_buildMenuItem(
|
||||||
text: "分享设备".tr,
|
text: "分享设备".tr,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_isPopupOpen = false;
|
||||||
|
});
|
||||||
_popupEntry?.remove();
|
_popupEntry?.remove();
|
||||||
_popupEntry = null;
|
_popupEntry = null;
|
||||||
Get.toNamed("/deviceSharePage", arguments: widget.device);
|
Get.toNamed("/deviceSharePage", arguments: widget.device);
|
||||||
@@ -292,6 +317,9 @@ class _DeviceDataComponentWidgetState extends State<DeviceDataComponentWidget> {
|
|||||||
_buildMenuItem(
|
_buildMenuItem(
|
||||||
text: "消息设置".tr,
|
text: "消息设置".tr,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_isPopupOpen = false;
|
||||||
|
});
|
||||||
_popupEntry?.remove();
|
_popupEntry?.remove();
|
||||||
_popupEntry = null;
|
_popupEntry = null;
|
||||||
// Get.toNamed("/deviceDetail", arguments: widget.device);
|
// Get.toNamed("/deviceDetail", arguments: widget.device);
|
||||||
@@ -305,6 +333,9 @@ class _DeviceDataComponentWidgetState extends State<DeviceDataComponentWidget> {
|
|||||||
_buildMenuItem(
|
_buildMenuItem(
|
||||||
text: "体征检测设备.重命名".tr,
|
text: "体征检测设备.重命名".tr,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_isPopupOpen = false;
|
||||||
|
});
|
||||||
_popupEntry?.remove();
|
_popupEntry?.remove();
|
||||||
_popupEntry = null;
|
_popupEntry = null;
|
||||||
_showRenameDialog();
|
_showRenameDialog();
|
||||||
@@ -313,6 +344,9 @@ class _DeviceDataComponentWidgetState extends State<DeviceDataComponentWidget> {
|
|||||||
_buildMenuItem(
|
_buildMenuItem(
|
||||||
text: "体征检测设备.删除".tr,
|
text: "体征检测设备.删除".tr,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_isPopupOpen = false;
|
||||||
|
});
|
||||||
_popupEntry?.remove();
|
_popupEntry?.remove();
|
||||||
_popupEntry = null;
|
_popupEntry = null;
|
||||||
_showUnbindConfirmDialog();
|
_showUnbindConfirmDialog();
|
||||||
@@ -494,14 +528,37 @@ class _DeviceDataComponentWidgetState extends State<DeviceDataComponentWidget> {
|
|||||||
color: themeController.currentColor.sc3,
|
color: themeController.currentColor.sc3,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ClickableContainer(
|
// ClickableContainer(
|
||||||
|
// key: _arrowKey,
|
||||||
|
// padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
// 16.rpx, 16.rpx, 14.rpx, 16.rpx),
|
||||||
|
// backgroundColor: Colors.transparent,
|
||||||
|
// highlightColor: Colors.black.withOpacity(0.1),
|
||||||
|
// borderRadius: 8.rpx,
|
||||||
|
// onTap: _showPopup,
|
||||||
|
// child: Container(
|
||||||
|
// width: 15.rpx,
|
||||||
|
// height: 8.rpx,
|
||||||
|
// child: SvgPicture.asset(
|
||||||
|
// 'assets/img/icon/arrow_down.svg',
|
||||||
|
// fit: BoxFit.cover,
|
||||||
|
// color: Colors.white,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
ToggleColorContainer(
|
||||||
key: _arrowKey,
|
key: _arrowKey,
|
||||||
padding: EdgeInsetsDirectional.fromSTEB(
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
16.rpx, 16.rpx, 14.rpx, 16.rpx),
|
16.rpx,
|
||||||
backgroundColor: Colors.transparent,
|
16.rpx,
|
||||||
highlightColor: Colors.black.withOpacity(0.1),
|
14.rpx,
|
||||||
|
16.rpx,
|
||||||
|
),
|
||||||
|
initialColor: Colors.transparent,
|
||||||
|
toggledColor: Colors.black.withOpacity(0.5),
|
||||||
borderRadius: 8.rpx,
|
borderRadius: 8.rpx,
|
||||||
onTap: _showPopup,
|
onToggle: _showPopup,
|
||||||
|
toggled: _isPopupOpen,
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 15.rpx,
|
width: 15.rpx,
|
||||||
height: 8.rpx,
|
height: 8.rpx,
|
||||||
@@ -1167,6 +1224,9 @@ class _DeviceDataComponentWidgetState extends State<DeviceDataComponentWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> dealWifi(String mac) async {
|
Future<void> dealWifi(String mac) async {
|
||||||
|
bodyDeviceController.wifiMac = mac;
|
||||||
|
Get.toNamed("/wifiPage", arguments: mac);
|
||||||
|
return;
|
||||||
final blueteethBindController = Get.find<BlueteethBindController>();
|
final blueteethBindController = Get.find<BlueteethBindController>();
|
||||||
final themeController = Get.find<ThemeController>();
|
final themeController = Get.find<ThemeController>();
|
||||||
|
|
||||||
@@ -1179,18 +1239,24 @@ class _DeviceDataComponentWidgetState extends State<DeviceDataComponentWidget> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// 开始扫描蓝牙设备
|
// 开始扫描蓝牙设备
|
||||||
await FlutterBluePlus.startScan(timeout: Duration(seconds: 20));
|
await FlutterBluePlus.startScan(timeout: Duration(seconds: 10));
|
||||||
|
|
||||||
// 设置超时(20秒)
|
// 设置超时(20秒)
|
||||||
timeoutTimer = Timer(Duration(seconds: 20), () {
|
timeoutTimer = Timer(Duration(seconds: 20), () {
|
||||||
if (!isConnected) {
|
try {
|
||||||
Navigator.of(Get.context!).pop(); // 关闭加载对话框
|
if (!isConnected) {
|
||||||
TopSlideNotification.show(
|
Navigator.of(context).pop(); // 先关闭 dialog
|
||||||
Get.context!,
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
text: "设备连接超时,请重试".tr,
|
TopSlideNotification.show(
|
||||||
textColor: themeController.currentColor.sc9,
|
context,
|
||||||
);
|
text: "设备连接超时,请重试".tr,
|
||||||
FlutterBluePlus.stopScan();
|
textColor: themeController.currentColor.sc9,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
FlutterBluePlus.stopScan();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1198,23 +1264,20 @@ class _DeviceDataComponentWidgetState extends State<DeviceDataComponentWidget> {
|
|||||||
StreamSubscription<List<ScanResult>>? scanSubscription;
|
StreamSubscription<List<ScanResult>>? scanSubscription;
|
||||||
scanSubscription = FlutterBluePlus.scanResults.listen((results) async {
|
scanSubscription = FlutterBluePlus.scanResults.listen((results) async {
|
||||||
// 过滤出符合条件的设备
|
// 过滤出符合条件的设备
|
||||||
final targetDevice = results.firstWhere(
|
ScanResult? targetDevice;
|
||||||
(r) {
|
|
||||||
try {
|
for (var r in results) {
|
||||||
if (r.advertisementData.manufacturerData.containsKey(0xFFED)) {
|
if (r.advertisementData.manufacturerData.containsKey(0xFFED)) {
|
||||||
List<int> rawData =
|
List<int> rawData = r.advertisementData.manufacturerData[0xFFED]!;
|
||||||
r.advertisementData.manufacturerData[0xFFED]!;
|
BleDeviceData deviceData = parseBleData(rawData);
|
||||||
BleDeviceData deviceData = parseBleData(rawData);
|
String deviceMac =
|
||||||
String deviceMac =
|
deviceData.deviceId.replaceAll(':', '').toLowerCase();
|
||||||
deviceData.deviceId.replaceAll(':', '').toLowerCase();
|
if (deviceMac == mac.toLowerCase()) {
|
||||||
return deviceMac == mac.toLowerCase();
|
targetDevice = r;
|
||||||
}
|
break;
|
||||||
return false;
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
if (targetDevice != null && !isConnected) {
|
if (targetDevice != null && !isConnected) {
|
||||||
isConnected = true;
|
isConnected = true;
|
||||||
@@ -1236,8 +1299,40 @@ class _DeviceDataComponentWidgetState extends State<DeviceDataComponentWidget> {
|
|||||||
textColor: themeController.currentColor.sc2,
|
textColor: themeController.currentColor.sc2,
|
||||||
);
|
);
|
||||||
blueteethBindController.currentDevice = bledevice;
|
blueteethBindController.currentDevice = bledevice;
|
||||||
|
if (lisObj != null) {
|
||||||
|
lisObj!.cancel();
|
||||||
|
}
|
||||||
|
var aa;
|
||||||
|
lisObj = blueteethBindController.currentDevice!.statusStream
|
||||||
|
.listen((onData) async {
|
||||||
|
if (onData.status == BleEventType.recvLineLog) {
|
||||||
|
final line = onData.val;
|
||||||
|
print("[bleee]:" + line);
|
||||||
|
}
|
||||||
|
if (onData.status == BleEventType.ready) {
|
||||||
|
aa = await getDeviceNetVersion(
|
||||||
|
blueteethBindController.currentDevice!, 1);
|
||||||
|
if (aa == "4g") {
|
||||||
|
// TopSlideNotification.show(
|
||||||
|
// Get.context!,
|
||||||
|
// text: "4g设备配置wifi提示".tr,
|
||||||
|
// textColor: themeController.currentColor.sc9,
|
||||||
|
// );
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
TopSlideNotification.show(
|
||||||
|
context,
|
||||||
|
text: "4g设备配置wifi提示".tr,
|
||||||
|
textColor: themeController.currentColor.sc9,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
Get.toNamed("/wifiPage", arguments: 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Get.toNamed("/wifiPage", arguments: {bledevice});
|
// Get.toNamed("/wifiPage", arguments: {bledevice});
|
||||||
Get.toNamed("/wifiPage", arguments: 1);
|
|
||||||
} else {
|
} else {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
TopSlideNotification.show(
|
TopSlideNotification.show(
|
||||||
@@ -1256,6 +1351,7 @@ class _DeviceDataComponentWidgetState extends State<DeviceDataComponentWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 等待扫描完成
|
// 等待扫描完成
|
||||||
await Future.delayed(Duration(seconds: 20));
|
await Future.delayed(Duration(seconds: 20));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -360,13 +360,7 @@ class _InstantBodyPageState extends State<InstantBodyPage> {
|
|||||||
padding: EdgeInsetsDirectional.fromSTEB(
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
66.rpx, 0, 66.rpx, 0),
|
66.rpx, 0, 66.rpx, 0),
|
||||||
child: Container(
|
child: Container(
|
||||||
// decoration: BoxDecoration(
|
|
||||||
// image: DecorationImage(
|
|
||||||
// image: AssetImage(
|
|
||||||
// 'assets/img/body_black.gif'), // 本地图片
|
|
||||||
// fit: BoxFit.cover,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
image: DecorationImage(
|
image: DecorationImage(
|
||||||
image: AssetImage(
|
image: AssetImage(
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:flutterflow_ui/flutterflow_ui.dart';
|
|||||||
import 'package:vbvs_app/common/color/appConstants.dart';
|
import 'package:vbvs_app/common/color/appConstants.dart';
|
||||||
import 'package:vbvs_app/common/util/FitTool.dart';
|
import 'package:vbvs_app/common/util/FitTool.dart';
|
||||||
import 'package:vbvs_app/common/util/MyUtils.dart';
|
import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||||
|
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
||||||
import 'package:vbvs_app/component/tool/CustomCard.dart';
|
import 'package:vbvs_app/component/tool/CustomCard.dart';
|
||||||
import 'package:vbvs_app/controller/device/blueteeth_bind_controller.dart';
|
import 'package:vbvs_app/controller/device/blueteeth_bind_controller.dart';
|
||||||
import 'package:vbvs_app/controller/device/body_device_controller.dart';
|
import 'package:vbvs_app/controller/device/body_device_controller.dart';
|
||||||
@@ -71,9 +72,28 @@ class _EPageState extends State<BindDeviceSuccess> {
|
|||||||
),
|
),
|
||||||
|
|
||||||
/// 左边返回按钮
|
/// 左边返回按钮
|
||||||
|
// Positioned(
|
||||||
|
// left: 0,
|
||||||
|
// child: returnIconButtom,
|
||||||
|
// ),
|
||||||
Positioned(
|
Positioned(
|
||||||
left: 0,
|
left: 40.rpx,
|
||||||
child: returnIconButtom,
|
child: ClickableContainer(
|
||||||
|
onTap: () {
|
||||||
|
Get.offAllNamed("/mianPageBottomChange");
|
||||||
|
},
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
highlightColor: Colors
|
||||||
|
.grey, // 可以设置为 themeController.currentColor.sc3 之类
|
||||||
|
borderRadius: 8.rpx,
|
||||||
|
padding: EdgeInsets.all(0.rpx), // 增加可点击区域
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
'assets/img/icon/close.svg',
|
||||||
|
width: 25.rpx,
|
||||||
|
height: 25.rpx,
|
||||||
|
color: themeController.currentColor.sc3,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'package:flutterflow_ui/flutterflow_ui.dart';
|
|||||||
import 'package:permission_handler/permission_handler.dart'; // 引入permission_handler
|
import 'package:permission_handler/permission_handler.dart'; // 引入permission_handler
|
||||||
import 'package:vbvs_app/common/util/FitTool.dart';
|
import 'package:vbvs_app/common/util/FitTool.dart';
|
||||||
import 'package:vbvs_app/common/util/MyUtils.dart';
|
import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||||
|
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
||||||
import 'package:vbvs_app/controller/device/blueteeth_bind_controller.dart';
|
import 'package:vbvs_app/controller/device/blueteeth_bind_controller.dart';
|
||||||
import 'package:vbvs_app/controller/main_bottom/global_controller.dart';
|
import 'package:vbvs_app/controller/main_bottom/global_controller.dart';
|
||||||
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
||||||
@@ -51,6 +52,8 @@ class _BlueteethDevicePageState extends State<BlueteethDevicePage> {
|
|||||||
flutterBlue = FlutterBluePlus(); // 初始化flutterBlue实例
|
flutterBlue = FlutterBluePlus(); // 初始化flutterBlue实例
|
||||||
_checkBluetoothPermission(); // 检查蓝牙权限
|
_checkBluetoothPermission(); // 检查蓝牙权限
|
||||||
Get.find<BlueteethBindController>().startStatusPolling();
|
Get.find<BlueteethBindController>().startStatusPolling();
|
||||||
|
blueteethBindController.search.value = "";
|
||||||
|
blueteethBindController.currentDeviceMac?.value = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查蓝牙权限
|
// 检查蓝牙权限
|
||||||
@@ -131,15 +134,94 @@ class _BlueteethDevicePageState extends State<BlueteethDevicePage> {
|
|||||||
await FlutterBluePlus.startScan(timeout: Duration(seconds: 10));
|
await FlutterBluePlus.startScan(timeout: Duration(seconds: 10));
|
||||||
// await FlutterBluePlus.startScan(timeout: Duration(minutes: 30));
|
// await FlutterBluePlus.startScan(timeout: Duration(minutes: 30));
|
||||||
|
|
||||||
|
// _scanSubscription = FlutterBluePlus.scanResults.listen((results) {
|
||||||
|
// if (!mounted) return; // 确保页面未销毁
|
||||||
|
// final signalThreshold = blueteethBindController.model.singal!;
|
||||||
|
// final filteredResults = results
|
||||||
|
// .where((r) =>
|
||||||
|
// r.rssi > signalThreshold &&
|
||||||
|
// r.advertisementData.localName == "AITH-V2" &&
|
||||||
|
// r.advertisementData.manufacturerData.containsKey(0xFFED))
|
||||||
|
// .toList();
|
||||||
|
|
||||||
|
// // 解析数据
|
||||||
|
// final parsedDeviceList = <BleDeviceData>[];
|
||||||
|
|
||||||
|
// for (var r in filteredResults) {
|
||||||
|
// try {
|
||||||
|
// List<int> rawData = r.advertisementData.manufacturerData[0xFFED]!;
|
||||||
|
// BleDeviceData deviceData = parseBleData(rawData);
|
||||||
|
// deviceData.name = r.advertisementData.advName;
|
||||||
|
// deviceData.rssi = r.rssi;
|
||||||
|
// deviceData.mac = deviceData.deviceId.replaceAll(':', '');
|
||||||
|
// parsedDeviceList.add(deviceData);
|
||||||
|
// if (deviceData.mac!.toLowerCase() == 'b43a45c3dfa0') {
|
||||||
|
// print('匹配设备数据: ${deviceData.mac}-->sn:${deviceData.sn}');
|
||||||
|
// }
|
||||||
|
// } catch (e) {
|
||||||
|
// print("设备数据解析失败: $e");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 使用一个临时变量 lastDeviceList 来比较是否有变化
|
||||||
|
// setState(() {
|
||||||
|
// bool hasChanges = false;
|
||||||
|
|
||||||
|
// // 如果 devicelist 长度不同或内容有差异,认为有变化
|
||||||
|
// if (blueteethBindController.model.devicelist?.length !=
|
||||||
|
// parsedDeviceList.length) {
|
||||||
|
// hasChanges = true;
|
||||||
|
// } else {
|
||||||
|
// // 深度比较每个设备的属性(比如 mac, rssi)
|
||||||
|
// for (int i = 0;
|
||||||
|
// i < blueteethBindController.model.devicelist!.length;
|
||||||
|
// i++) {
|
||||||
|
// if (blueteethBindController.model.devicelist![i].mac !=
|
||||||
|
// parsedDeviceList[i].mac ||
|
||||||
|
// blueteethBindController.model.devicelist![i].rssi !=
|
||||||
|
// parsedDeviceList[i].rssi) {
|
||||||
|
// hasChanges = true;
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 如果有变化,更新 devicelist 和 blelist
|
||||||
|
// if (hasChanges) {
|
||||||
|
// blueteethBindController.model.devicelist = parsedDeviceList;
|
||||||
|
// blueteethBindController.model.blelist = filteredResults;
|
||||||
|
// // blueteethBindController.updateDeviceStatus();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
_scanSubscription = FlutterBluePlus.scanResults.listen((results) {
|
_scanSubscription = FlutterBluePlus.scanResults.listen((results) {
|
||||||
if (!mounted) return; // 确保页面未销毁
|
if (!mounted) return;
|
||||||
|
|
||||||
final signalThreshold = blueteethBindController.model.singal!;
|
final signalThreshold = blueteethBindController.model.singal!;
|
||||||
final filteredResults = results
|
final searchKey =
|
||||||
.where((r) =>
|
blueteethBindController.search.value.trim().toLowerCase();
|
||||||
r.rssi > signalThreshold &&
|
|
||||||
r.advertisementData.localName == "AITH-V2" &&
|
final filteredResults = results.where((r) {
|
||||||
r.advertisementData.manufacturerData.containsKey(0xFFED))
|
final isTarget = r.rssi > signalThreshold &&
|
||||||
.toList();
|
r.advertisementData.localName == "AITH-V2" &&
|
||||||
|
r.advertisementData.manufacturerData.containsKey(0xFFED);
|
||||||
|
|
||||||
|
if (!isTarget) return false;
|
||||||
|
|
||||||
|
// 搜索关键字过滤(根据名称和 MAC 地址模糊匹配)
|
||||||
|
final name = r.advertisementData.advName.toLowerCase();
|
||||||
|
final mac = r.device.remoteId.str.replaceAll(':', '').toLowerCase();
|
||||||
|
final search = searchKey.trim().replaceAll(':', '').toLowerCase();
|
||||||
|
|
||||||
|
if (search.isNotEmpty &&
|
||||||
|
!name.contains(search) &&
|
||||||
|
!mac.contains(search)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}).toList();
|
||||||
|
|
||||||
// 解析数据
|
// 解析数据
|
||||||
final parsedDeviceList = <BleDeviceData>[];
|
final parsedDeviceList = <BleDeviceData>[];
|
||||||
@@ -152,24 +234,23 @@ class _BlueteethDevicePageState extends State<BlueteethDevicePage> {
|
|||||||
deviceData.rssi = r.rssi;
|
deviceData.rssi = r.rssi;
|
||||||
deviceData.mac = deviceData.deviceId.replaceAll(':', '');
|
deviceData.mac = deviceData.deviceId.replaceAll(':', '');
|
||||||
parsedDeviceList.add(deviceData);
|
parsedDeviceList.add(deviceData);
|
||||||
if (deviceData.mac!.toLowerCase() == 'b43a45c3dfa0') {
|
|
||||||
print('匹配设备数据: ${deviceData.mac}-->sn:${deviceData.sn}');
|
// if (deviceData.mac!.toLowerCase() == 'b43a45c3dfa0') {
|
||||||
}
|
// print('匹配设备数据: ${deviceData.mac}-->sn:${deviceData.sn}');
|
||||||
|
// }
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print("设备数据解析失败: $e");
|
print("设备数据解析失败: $e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用一个临时变量 lastDeviceList 来比较是否有变化
|
// 判断是否更新
|
||||||
setState(() {
|
setState(() {
|
||||||
bool hasChanges = false;
|
bool hasChanges = false;
|
||||||
|
|
||||||
// 如果 devicelist 长度不同或内容有差异,认为有变化
|
|
||||||
if (blueteethBindController.model.devicelist?.length !=
|
if (blueteethBindController.model.devicelist?.length !=
|
||||||
parsedDeviceList.length) {
|
parsedDeviceList.length) {
|
||||||
hasChanges = true;
|
hasChanges = true;
|
||||||
} else {
|
} else {
|
||||||
// 深度比较每个设备的属性(比如 mac, rssi)
|
|
||||||
for (int i = 0;
|
for (int i = 0;
|
||||||
i < blueteethBindController.model.devicelist!.length;
|
i < blueteethBindController.model.devicelist!.length;
|
||||||
i++) {
|
i++) {
|
||||||
@@ -183,11 +264,9 @@ class _BlueteethDevicePageState extends State<BlueteethDevicePage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果有变化,更新 devicelist 和 blelist
|
|
||||||
if (hasChanges) {
|
if (hasChanges) {
|
||||||
blueteethBindController.model.devicelist = parsedDeviceList;
|
blueteethBindController.model.devicelist = parsedDeviceList;
|
||||||
blueteethBindController.model.blelist = filteredResults;
|
blueteethBindController.model.blelist = filteredResults;
|
||||||
// blueteethBindController.updateDeviceStatus();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -360,6 +439,7 @@ class _BlueteethDevicePageState extends State<BlueteethDevicePage> {
|
|||||||
newValue.toStringAsFixed(0));
|
newValue.toStringAsFixed(0));
|
||||||
blueteethBindController.model.singal =
|
blueteethBindController.model.singal =
|
||||||
newValue;
|
newValue;
|
||||||
|
_startScanning();
|
||||||
blueteethBindController.updateAll();
|
blueteethBindController.updateAll();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -425,6 +505,12 @@ class _BlueteethDevicePageState extends State<BlueteethDevicePage> {
|
|||||||
child: Align(
|
child: Align(
|
||||||
alignment: AlignmentDirectional(-1, 0),
|
alignment: AlignmentDirectional(-1, 0),
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
|
initialValue: blueteethBindController
|
||||||
|
.search.value,
|
||||||
|
onChanged: (Value) {
|
||||||
|
blueteethBindController
|
||||||
|
.search.value = Value;
|
||||||
|
},
|
||||||
autofocus: false,
|
autofocus: false,
|
||||||
obscureText: false,
|
obscureText: false,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
@@ -494,11 +580,8 @@ class _BlueteethDevicePageState extends State<BlueteethDevicePage> {
|
|||||||
fontSize: 26.rpx,
|
fontSize: 26.rpx,
|
||||||
letterSpacing: 0.0,
|
letterSpacing: 0.0,
|
||||||
),
|
),
|
||||||
cursorColor: themeController
|
cursorColor:
|
||||||
.currentColor.sc3,
|
themeController.currentColor.sc3,
|
||||||
// validator: _model
|
|
||||||
// .textControllerValidator
|
|
||||||
// .asValidator(context),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -519,16 +602,60 @@ class _BlueteethDevicePageState extends State<BlueteethDevicePage> {
|
|||||||
color: stringToColor("#333333"), //固定
|
color: stringToColor("#333333"), //固定
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
ClickableContainer(
|
||||||
'搜索'.tr,
|
backgroundColor: Colors.transparent,
|
||||||
style: FlutterFlowTheme.of(context)
|
highlightColor:
|
||||||
.bodyMedium
|
themeController.currentColor.sc4,
|
||||||
.override(
|
borderRadius: 6.rpx,
|
||||||
fontFamily: 'Inter',
|
padding: EdgeInsets.zero,
|
||||||
fontSize: 30.rpx,
|
// onTap: () async {
|
||||||
letterSpacing: 0.0,
|
|
||||||
color: stringToColor("#333333"), //固定
|
// blueteethBindController
|
||||||
),
|
// .model.betDevicelist;
|
||||||
|
// blueteethBindController.search.value;
|
||||||
|
// blueteethBindController.updateAll();
|
||||||
|
// },
|
||||||
|
onTap: () async {
|
||||||
|
// final searchKey = blueteethBindController
|
||||||
|
// .search.value
|
||||||
|
// .trim();
|
||||||
|
// if (searchKey.isNotEmpty) {
|
||||||
|
// final filtered = blueteethBindController
|
||||||
|
// .model.betDevicelist!
|
||||||
|
// .where((device) {
|
||||||
|
// final name =
|
||||||
|
// device.name?.toLowerCase() ?? '';
|
||||||
|
// final mac =
|
||||||
|
// device.mac?.toLowerCase() ?? '';
|
||||||
|
// return name.contains(
|
||||||
|
// searchKey.toLowerCase()) ||
|
||||||
|
// mac.contains(
|
||||||
|
// searchKey.toLowerCase());
|
||||||
|
// }).toList();
|
||||||
|
|
||||||
|
// // 替换原始列表
|
||||||
|
// blueteethBindController
|
||||||
|
// .model.betDevicelist!
|
||||||
|
// ..clear()
|
||||||
|
// ..addAll(filtered);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// blueteethBindController.updateAll();
|
||||||
|
_startScanning();
|
||||||
|
},
|
||||||
|
|
||||||
|
child: Text(
|
||||||
|
'搜索'.tr,
|
||||||
|
style: FlutterFlowTheme.of(context)
|
||||||
|
.bodyMedium
|
||||||
|
.override(
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontSize: 30.rpx,
|
||||||
|
letterSpacing: 0.0,
|
||||||
|
color:
|
||||||
|
stringToColor("#333333"), //固定
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
].divide(SizedBox(width: 26.rpx)),
|
].divide(SizedBox(width: 26.rpx)),
|
||||||
),
|
),
|
||||||
@@ -548,8 +675,10 @@ class _BlueteethDevicePageState extends State<BlueteethDevicePage> {
|
|||||||
EdgeInsetsDirectional.fromSTEB(19.rpx, 0, 0, 0),
|
EdgeInsetsDirectional.fromSTEB(19.rpx, 0, 0, 0),
|
||||||
child: Obx(() {
|
child: Obx(() {
|
||||||
return Text(
|
return Text(
|
||||||
|
// '匹配出的外围设备'.tr +
|
||||||
|
// "(${blueteethBindController.model.betDevicelist!.length})",
|
||||||
'匹配出的外围设备'.tr +
|
'匹配出的外围设备'.tr +
|
||||||
"(${blueteethBindController.model.betDevicelist!.length})",
|
"(${blueteethBindController.model.blelist!.length})",
|
||||||
style: FlutterFlowTheme.of(context)
|
style: FlutterFlowTheme.of(context)
|
||||||
.bodyMedium
|
.bodyMedium
|
||||||
.override(
|
.override(
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'package:vbvs_app/common/util/DailyLogUtils.dart';
|
|||||||
import 'package:vbvs_app/common/util/FitTool.dart';
|
import 'package:vbvs_app/common/util/FitTool.dart';
|
||||||
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
||||||
import 'package:vbvs_app/component/tool/TopSlideNotification.dart';
|
import 'package:vbvs_app/component/tool/TopSlideNotification.dart';
|
||||||
|
import 'package:vbvs_app/component/tool/cmd.dart';
|
||||||
import 'package:vbvs_app/controller/device/blueteeth_bind_controller.dart';
|
import 'package:vbvs_app/controller/device/blueteeth_bind_controller.dart';
|
||||||
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
||||||
import 'package:vbvs_app/model/BleDeviceData.dart';
|
import 'package:vbvs_app/model/BleDeviceData.dart';
|
||||||
@@ -32,6 +33,8 @@ class _SingleBlueteethDeviceCompoentWidgetState
|
|||||||
extends State<SingleBlueteethDeviceCompoentWidget> {
|
extends State<SingleBlueteethDeviceCompoentWidget> {
|
||||||
ThemeController themeController = Get.find();
|
ThemeController themeController = Get.find();
|
||||||
BlueteethBindController blueteethBindController = Get.find();
|
BlueteethBindController blueteethBindController = Get.find();
|
||||||
|
var lisObj;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
List<int> rawData =
|
List<int> rawData =
|
||||||
@@ -41,12 +44,11 @@ class _SingleBlueteethDeviceCompoentWidgetState
|
|||||||
deviceData.rssi = widget.bleDevice.rssi;
|
deviceData.rssi = widget.bleDevice.rssi;
|
||||||
deviceData.mac = deviceData.deviceId.replaceAll(':', '');
|
deviceData.mac = deviceData.deviceId.replaceAll(':', '');
|
||||||
BleDeviceData device = deviceData;
|
BleDeviceData device = deviceData;
|
||||||
blueteethBindController.currentDeviceMac = device.mac;
|
// blueteethBindController.currentDeviceMac = device.mac;
|
||||||
device = blueteethBindController.model.betDevicelist!.firstWhere(
|
device = blueteethBindController.model.betDevicelist!.firstWhere(
|
||||||
(d) => d.mac == device.mac,
|
(d) => d.mac == device.mac,
|
||||||
orElse: () => device,
|
orElse: () => device,
|
||||||
);
|
);
|
||||||
|
|
||||||
return ClickableContainer(
|
return ClickableContainer(
|
||||||
backgroundColor: themeController.currentColor.sc5,
|
backgroundColor: themeController.currentColor.sc5,
|
||||||
highlightColor: themeController.currentColor.sc21,
|
highlightColor: themeController.currentColor.sc21,
|
||||||
@@ -54,9 +56,25 @@ class _SingleBlueteethDeviceCompoentWidgetState
|
|||||||
padding: EdgeInsetsDirectional.fromSTEB(30.rpx, 36.rpx, 0, 52.rpx),
|
padding: EdgeInsetsDirectional.fromSTEB(30.rpx, 36.rpx, 0, 52.rpx),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
try {
|
try {
|
||||||
|
//1.先判斷当前是否有别的设备处于连接中,没有处于连接,判断是否绑定;
|
||||||
|
//2.如果没有绑定,直接连接;如果绑定,弹出提示框,提示是否解绑;
|
||||||
|
if (blueteethBindController.currentDeviceMac?.value != null &&
|
||||||
|
blueteethBindController.currentDeviceMac!.value.isNotEmpty) {
|
||||||
|
if (blueteethBindController.currentDeviceMac?.value != device.mac) {
|
||||||
|
showConfirmDialog(
|
||||||
|
context, Container(), "其他设备正在绑定中,是否终止其他设备绑定?".tr,
|
||||||
|
onConfirm: () {
|
||||||
|
blueteethBindController.currentDeviceMac = null;
|
||||||
|
blueteethBindController.updateAll();
|
||||||
|
}, onCancel: () {});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
blueteethBindController.currentDeviceMac?.value = device.mac!;
|
||||||
|
blueteethBindController.updateAll();
|
||||||
|
}
|
||||||
|
|
||||||
if (device.bind == true) {
|
if (device.bind == true) {
|
||||||
showHaveBindDialog(context);
|
showHaveBindDialog(context);
|
||||||
// showBindDoubleDialog(context, []);
|
|
||||||
} else {
|
} else {
|
||||||
showConfirmDialog(
|
showConfirmDialog(
|
||||||
context,
|
context,
|
||||||
@@ -67,29 +85,52 @@ class _SingleBlueteethDeviceCompoentWidgetState
|
|||||||
await blueteethBindController.bindDeviceAndMAC(device);
|
await blueteethBindController.bindDeviceAndMAC(device);
|
||||||
TopSlideNotification.show(context, text: response.msg!);
|
TopSlideNotification.show(context, text: response.msg!);
|
||||||
if (response.code == HttpStatusCodes.ok) {
|
if (response.code == HttpStatusCodes.ok) {
|
||||||
showLoadingDialog(context); // 显示 loading
|
// showLoadingDialog(context); // 显示 loading
|
||||||
|
Get.toNamed("/personPage");
|
||||||
THapp bledevice = THapp(device: widget.bleDevice.device);
|
THapp bledevice = THapp(device: widget.bleDevice.device);
|
||||||
await bledevice.device.connect();
|
blueteethBindController.currentDevice = bledevice;
|
||||||
var res2 = bledevice.isConnected;
|
// await bledevice.device.connect();
|
||||||
if (res2) {
|
// var res2 = bledevice.isConnected;
|
||||||
Navigator.pop(context);
|
// if (res2) {
|
||||||
TopSlideNotification.show(
|
// // Navigator.pop(context);
|
||||||
context,
|
// TopSlideNotification.show(
|
||||||
text: "蓝牙绑定.连接成功".tr,
|
// context,
|
||||||
textColor: themeController.currentColor.sc2,
|
// text: "蓝牙绑定.连接成功".tr,
|
||||||
);
|
// textColor: themeController.currentColor.sc2,
|
||||||
blueteethBindController.currentDevice = bledevice;
|
// );
|
||||||
// Get.toNamed("/wifiPage", arguments: {bledevice});
|
// blueteethBindController.currentDevice = bledevice;
|
||||||
Get.toNamed("/wifiPage");
|
|
||||||
} else {
|
// if (lisObj != null) {
|
||||||
Navigator.pop(context);
|
// lisObj!.cancel();
|
||||||
TopSlideNotification.show(
|
// }
|
||||||
context,
|
// var aa;
|
||||||
text: "蓝牙绑定.连接失败".tr,
|
// lisObj = blueteethBindController.currentDevice!.statusStream
|
||||||
textColor: themeController.currentColor.sc9,
|
// .listen((onData) async {
|
||||||
);
|
// if (onData.status == BleEventType.recvLineLog) {
|
||||||
}
|
// final line = onData.val;
|
||||||
|
// print("[bleee]:" + line);
|
||||||
|
// }
|
||||||
|
// if (onData.status == BleEventType.ready) {
|
||||||
|
// aa = await getDeviceNetVersion(
|
||||||
|
// blueteethBindController.currentDevice!, 1);
|
||||||
|
// if (aa == "4g") {
|
||||||
|
// Get.toNamed("/calibrationPage", arguments: 1);
|
||||||
|
// } else {
|
||||||
|
// Get.toNamed("/wifiPage");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// } else {
|
||||||
|
// // Navigator.pop(context);
|
||||||
|
// TopSlideNotification.show(
|
||||||
|
// context,
|
||||||
|
// text: "蓝牙绑定.连接失败".tr,
|
||||||
|
// textColor: themeController.currentColor.sc9,
|
||||||
|
// );
|
||||||
|
// }
|
||||||
} else {
|
} else {
|
||||||
|
blueteethBindController.currentDeviceMac = null;
|
||||||
|
blueteethBindController.updateAll();
|
||||||
TopSlideNotification.show(
|
TopSlideNotification.show(
|
||||||
context,
|
context,
|
||||||
text: response.msg ?? "蓝牙绑定.连接异常".tr,
|
text: response.msg ?? "蓝牙绑定.连接异常".tr,
|
||||||
@@ -99,10 +140,10 @@ class _SingleBlueteethDeviceCompoentWidgetState
|
|||||||
},
|
},
|
||||||
onCancel: () {
|
onCancel: () {
|
||||||
print('用户点击了取消');
|
print('用户点击了取消');
|
||||||
// 执行取消后的处理逻辑
|
blueteethBindController.currentDeviceMac?.value = "";
|
||||||
|
blueteethBindController.updateAll();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
@@ -119,14 +160,36 @@ class _SingleBlueteethDeviceCompoentWidgetState
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Padding(
|
||||||
device.name ?? '蓝牙绑定.默认设备名称'.tr,
|
padding:
|
||||||
style: FlutterFlowTheme.of(context).bodyMedium.override(
|
EdgeInsetsDirectional.fromSTEB(0.rpx, 0.rpx, 30.rpx, 0.rpx),
|
||||||
fontFamily: 'Inter',
|
child: Row(
|
||||||
color: const Color(0xFFF6FAFD),
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
fontSize: 30.rpx,
|
children: [
|
||||||
letterSpacing: 0.0,
|
Text(
|
||||||
|
device.name ?? '蓝牙绑定.默认设备名称'.tr,
|
||||||
|
style: FlutterFlowTheme.of(context).bodyMedium.override(
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
color: const Color(0xFFF6FAFD),
|
||||||
|
fontSize: 30.rpx,
|
||||||
|
letterSpacing: 0.0,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
Obx(() {
|
||||||
|
if (blueteethBindController.currentDeviceMac == device.mac) {
|
||||||
|
return SizedBox(
|
||||||
|
width: 24.rpx,
|
||||||
|
height: 24.rpx,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 1,
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Container();
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -426,7 +426,7 @@ void showConfirmDialog(
|
|||||||
color: themeController.currentColor.sc17,
|
color: themeController.currentColor.sc17,
|
||||||
borderRadius: BorderRadius.circular(20.0),
|
borderRadius: BorderRadius.circular(20.0),
|
||||||
),
|
),
|
||||||
padding: EdgeInsetsDirectional.fromSTEB(64.rpx, 0, 64.rpx, 0),
|
padding: EdgeInsetsDirectional.fromSTEB(31.rpx, 0, 31.rpx, 0),
|
||||||
child: Container(
|
child: Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
@@ -446,10 +446,13 @@ void showConfirmDialog(
|
|||||||
padding: EdgeInsets.zero, // 这里去掉外部的 padding,避免影响点击范围
|
padding: EdgeInsets.zero, // 这里去掉外部的 padding,避免影响点击范围
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Get.back();
|
Get.back();
|
||||||
|
onCancel();
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding:
|
// padding:
|
||||||
EdgeInsetsDirectional.fromSTEB(0, 33.rpx, 0, 0.rpx),
|
// EdgeInsetsDirectional.fromSTEB(0, 33.rpx, 0, 0.rpx),
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
33.rpx, 33.rpx, 33.rpx, 33.rpx),
|
||||||
child: SvgPicture.asset(
|
child: SvgPicture.asset(
|
||||||
'assets/img/icon/close.svg',
|
'assets/img/icon/close.svg',
|
||||||
width: 25.rpx,
|
width: 25.rpx,
|
||||||
@@ -463,8 +466,8 @@ void showConfirmDialog(
|
|||||||
Align(
|
Align(
|
||||||
alignment: AlignmentDirectional(0, 0),
|
alignment: AlignmentDirectional(0, 0),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding:
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
EdgeInsetsDirectional.fromSTEB(0.rpx, 93.rpx, 0, 0),
|
33.rpx, 60.rpx, 33.rpx, 33.rpx),
|
||||||
child: Text(
|
child: Text(
|
||||||
title,
|
title,
|
||||||
style: FlutterFlowTheme.of(context).bodyMedium.override(
|
style: FlutterFlowTheme.of(context).bodyMedium.override(
|
||||||
@@ -476,18 +479,22 @@ void showConfirmDialog(
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
widget,
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsetsDirectional.fromSTEB(0, 58.rpx, 0, 60.rpx),
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
33.rpx, 0.rpx, 33.rpx, 0.rpx),
|
||||||
|
child: widget,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
33.rpx, 58.rpx, 33.rpx, 60.rpx),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
CustomCard(
|
CustomCard(
|
||||||
borderRadius: AppConstants().button_container_radius,
|
borderRadius: AppConstants().button_container_radius,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
onConfirm();
|
|
||||||
// await Future.delayed(Duration(milliseconds: 300));
|
|
||||||
Get.back();
|
Get.back();
|
||||||
|
onCancel();
|
||||||
},
|
},
|
||||||
colors: [
|
colors: [
|
||||||
themeController.currentColor.sc1,
|
themeController.currentColor.sc1,
|
||||||
@@ -505,7 +512,7 @@ void showConfirmDialog(
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"蓝牙绑定.是".tr,
|
"蓝牙绑定.否".tr,
|
||||||
style: FlutterFlowTheme.of(context)
|
style: FlutterFlowTheme.of(context)
|
||||||
.bodyMedium
|
.bodyMedium
|
||||||
.override(
|
.override(
|
||||||
@@ -523,8 +530,9 @@ void showConfirmDialog(
|
|||||||
CustomCard(
|
CustomCard(
|
||||||
borderRadius: AppConstants().button_container_radius,
|
borderRadius: AppConstants().button_container_radius,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
onConfirm();
|
||||||
|
// await Future.delayed(Duration(milliseconds: 300));
|
||||||
Get.back();
|
Get.back();
|
||||||
onCancel();
|
|
||||||
},
|
},
|
||||||
colors: [
|
colors: [
|
||||||
themeController.currentColor.sc1,
|
themeController.currentColor.sc1,
|
||||||
@@ -542,7 +550,7 @@ void showConfirmDialog(
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"蓝牙绑定.否".tr,
|
"蓝牙绑定.是".tr,
|
||||||
style: FlutterFlowTheme.of(context)
|
style: FlutterFlowTheme.of(context)
|
||||||
.bodyMedium
|
.bodyMedium
|
||||||
.override(
|
.override(
|
||||||
@@ -660,8 +668,8 @@ void showWifiDialog(
|
|||||||
textColor: themeController.currentColor.sc9,
|
textColor: themeController.currentColor.sc9,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
Get.back();
|
|
||||||
onConfirm();
|
onConfirm();
|
||||||
|
Get.back();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
colors: [
|
colors: [
|
||||||
@@ -788,7 +796,6 @@ void showTipDialog(
|
|||||||
child: widget,
|
child: widget,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// widget,
|
|
||||||
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsetsDirectional.fromSTEB(0, 19.rpx, 0, 60.rpx),
|
padding: EdgeInsetsDirectional.fromSTEB(0, 19.rpx, 0, 60.rpx),
|
||||||
|
|||||||
@@ -89,7 +89,8 @@ class _CalibrationPageState extends State<CalibrationPage> {
|
|||||||
child: CustomCard(
|
child: CustomCard(
|
||||||
borderRadius: 20.rpx,
|
borderRadius: 20.rpx,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
Get.toNamed("/personPage");
|
// Get.toNamed("/personPage");
|
||||||
|
Get.toNamed("/bindDeviceSuccess");
|
||||||
},
|
},
|
||||||
colors: [
|
colors: [
|
||||||
themeController.currentColor.sc1,
|
themeController.currentColor.sc1,
|
||||||
|
|||||||
@@ -383,7 +383,7 @@ class _DeviceSharePageState extends State<DeviceSharePage> {
|
|||||||
// "太和e护分享链接",
|
// "太和e护分享链接",
|
||||||
// scene: WeChatScene.session));
|
// scene: WeChatScene.session));
|
||||||
final Uint8List data = await rootBundle.load('assets/img/camera.png').then((bd) => bd.buffer.asUint8List());
|
final Uint8List data = await rootBundle.load('assets/img/camera.png').then((bd) => bd.buffer.asUint8List());
|
||||||
loginController.fluwx.share(WeChatShareWebPageModel("https://www.baidu.com",title: "标题",description: "描述",thumbData: data));
|
loginController.fluwx.share(WeChatShareWebPageModel("taihecare://goods?id=123",title: "标题",description: "描述",thumbData: data));
|
||||||
},
|
},
|
||||||
colors: [
|
colors: [
|
||||||
// 渐变色
|
// 渐变色
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:easydevice/easydevice.dart';
|
import 'package:easydevice/easydevice.dart';
|
||||||
import 'package:ef/ef.dart';
|
import 'package:ef/ef.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:flutterflow_ui/flutterflow_ui.dart';
|
import 'package:flutterflow_ui/flutterflow_ui.dart';
|
||||||
import 'package:vbvs_app/common/color/appConstants.dart';
|
import 'package:vbvs_app/common/color/appConstants.dart';
|
||||||
@@ -15,6 +18,8 @@ import 'package:vbvs_app/controller/main_bottom/global_controller.dart';
|
|||||||
import 'package:vbvs_app/controller/person/person_controller.dart';
|
import 'package:vbvs_app/controller/person/person_controller.dart';
|
||||||
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
||||||
import 'package:vbvs_app/controller/user_info_controller.dart';
|
import 'package:vbvs_app/controller/user_info_controller.dart';
|
||||||
|
import 'package:vbvs_app/model/BleDeviceData.dart';
|
||||||
|
import 'package:vbvs_app/pages/device_bind/blueteeth_device_page.dart';
|
||||||
import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
|
import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
|
||||||
|
|
||||||
class WifiPage extends StatefulWidget {
|
class WifiPage extends StatefulWidget {
|
||||||
@@ -36,10 +41,69 @@ class _WifiPageState extends State<WifiPage> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
blueteethBindController.connectStatus.value = 0;
|
||||||
blueteethBindController.wifiList = [].obs;
|
blueteethBindController.wifiList = [].obs;
|
||||||
blueteethBindController.wifiStatus = 0.obs;
|
blueteethBindController.wifiStatus = 0.obs;
|
||||||
blueteethBindController.connect_wifi.value = {};
|
blueteethBindController.connect_wifi.value = {};
|
||||||
initWifiStatusAndWifiList();
|
blueteethBindController.selectWifi.value = {};
|
||||||
|
|
||||||
|
if (widget.type == null) {
|
||||||
|
THapp bledevice = blueteethBindController.currentDevice!;
|
||||||
|
bledevice.device.connect().then((Value) {
|
||||||
|
var res2 = bledevice.isConnected;
|
||||||
|
if (res2) {
|
||||||
|
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
// TopSlideNotification.show(
|
||||||
|
// context,
|
||||||
|
// text: "蓝牙绑定.连接成功".tr,
|
||||||
|
// textColor: themeController.currentColor.sc2,
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
blueteethBindController.currentDevice = bledevice;
|
||||||
|
if (lisObj != null) {
|
||||||
|
lisObj!.cancel();
|
||||||
|
}
|
||||||
|
var aa;
|
||||||
|
lisObj = blueteethBindController.currentDevice!.statusStream
|
||||||
|
.listen((onData) async {
|
||||||
|
if (onData.status == BleEventType.recvLineLog) {
|
||||||
|
final line = onData.val;
|
||||||
|
print("[bleee]:" + line);
|
||||||
|
}
|
||||||
|
if (onData.status == BleEventType.ready) {
|
||||||
|
aa = await getDeviceNetVersion(
|
||||||
|
blueteethBindController.currentDevice!, 1);
|
||||||
|
if (aa == "4g") {
|
||||||
|
TopSlideNotification.show(
|
||||||
|
context,
|
||||||
|
text: "4g设备配置wifi提示".tr,
|
||||||
|
textColor: themeController.currentColor.sc2,
|
||||||
|
);
|
||||||
|
blueteethBindController.connectStatus.value = 1;
|
||||||
|
blueteethBindController.updateAll();
|
||||||
|
Future.delayed(const Duration(seconds: 1), () {
|
||||||
|
Get.toNamed("/calibrationPage", arguments: 1);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await initWifiStatusAndWifiList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
TopSlideNotification.show(
|
||||||
|
context,
|
||||||
|
text: "连接失败".tr,
|
||||||
|
textColor: themeController.currentColor.sc9,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
dealWifi(widget.type).then((aa) {
|
||||||
|
print("object");
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -79,14 +143,38 @@ class _WifiPageState extends State<WifiPage> {
|
|||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
children: [
|
children: [
|
||||||
/// 居中标题
|
/// 居中标题
|
||||||
Text(
|
Row(
|
||||||
'wifi页.标题'.tr,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
style: FlutterFlowTheme.of(context).bodyMedium.override(
|
children: [
|
||||||
fontFamily: 'Readex Pro',
|
Text(
|
||||||
color: themeController.currentColor.sc3,
|
'wifi页.标题'.tr,
|
||||||
letterSpacing: 0,
|
style:
|
||||||
fontSize: 30.rpx,
|
FlutterFlowTheme.of(context).bodyMedium.override(
|
||||||
),
|
fontFamily: 'Readex Pro',
|
||||||
|
color: themeController.currentColor.sc3,
|
||||||
|
letterSpacing: 0,
|
||||||
|
fontSize: 30.rpx,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 14.rpx,
|
||||||
|
),
|
||||||
|
Obx(() {
|
||||||
|
if (blueteethBindController.connectStatus.value ==
|
||||||
|
0) {
|
||||||
|
return SizedBox(
|
||||||
|
width: 24.rpx,
|
||||||
|
height: 24.rpx,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 1,
|
||||||
|
valueColor:
|
||||||
|
AlwaysStoppedAnimation<Color>(Colors.white),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Container();
|
||||||
|
}),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
/// 左边返回按钮
|
/// 左边返回按钮
|
||||||
@@ -100,15 +188,16 @@ class _WifiPageState extends State<WifiPage> {
|
|||||||
child: CustomCard(
|
child: CustomCard(
|
||||||
borderRadius: 20.rpx,
|
borderRadius: 20.rpx,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (blueteethBindController.wifiStatus.value != 1) {
|
// if (blueteethBindController.wifiStatus.value != 1) {
|
||||||
TopSlideNotification.show(
|
// TopSlideNotification.show(
|
||||||
context,
|
// context,
|
||||||
text: "wifi页.需配网".tr,
|
// text: "wifi页.需配网".tr,
|
||||||
textColor: themeController.currentColor.sc9,
|
// textColor: themeController.currentColor.sc9,
|
||||||
);
|
// );
|
||||||
} else {
|
// } else {
|
||||||
Get.toNamed("/calibrationPage", arguments: 1);
|
// Get.toNamed("/calibrationPage", arguments: 1);
|
||||||
}
|
// }
|
||||||
|
Get.toNamed("/calibrationPage", arguments: 1);
|
||||||
},
|
},
|
||||||
colors: [
|
colors: [
|
||||||
themeController.currentColor.sc1,
|
themeController.currentColor.sc1,
|
||||||
@@ -460,8 +549,11 @@ class _WifiPageState extends State<WifiPage> {
|
|||||||
wifiItem['ssid'] ??
|
wifiItem['ssid'] ??
|
||||||
'未命名'.tr,
|
'未命名'.tr,
|
||||||
onConfirm: () async {
|
onConfirm: () async {
|
||||||
showLoadingDialog(
|
// showLoadingDialog(
|
||||||
context); // 显示 loading
|
// context); // 显示 loading
|
||||||
|
blueteethBindController
|
||||||
|
.selectWifi
|
||||||
|
.value = wifiItem;
|
||||||
bool flag = await sendWifiSetting(
|
bool flag = await sendWifiSetting(
|
||||||
wifiItem,
|
wifiItem,
|
||||||
blueteethBindController
|
blueteethBindController
|
||||||
@@ -478,7 +570,10 @@ class _WifiPageState extends State<WifiPage> {
|
|||||||
var aa = await getDeviceWifiStatus(
|
var aa = await getDeviceWifiStatus(
|
||||||
blueteethBindController
|
blueteethBindController
|
||||||
.currentDevice!,
|
.currentDevice!,
|
||||||
20);
|
1);
|
||||||
|
blueteethBindController
|
||||||
|
.selectWifi
|
||||||
|
.value = {};
|
||||||
if (aa != null &&
|
if (aa != null &&
|
||||||
aa is Map) {
|
aa is Map) {
|
||||||
wifiStatus = true;
|
wifiStatus = true;
|
||||||
@@ -503,7 +598,7 @@ class _WifiPageState extends State<WifiPage> {
|
|||||||
? 1
|
? 1
|
||||||
: 0;
|
: 0;
|
||||||
if (wifiStatus) {
|
if (wifiStatus) {
|
||||||
Navigator.pop(context);
|
// Navigator.pop(context);
|
||||||
TopSlideNotification
|
TopSlideNotification
|
||||||
.show(
|
.show(
|
||||||
context,
|
context,
|
||||||
@@ -519,7 +614,7 @@ class _WifiPageState extends State<WifiPage> {
|
|||||||
blueteethBindController
|
blueteethBindController
|
||||||
.updateAll();
|
.updateAll();
|
||||||
} else {
|
} else {
|
||||||
Navigator.pop(context);
|
// Navigator.pop(context);
|
||||||
TopSlideNotification
|
TopSlideNotification
|
||||||
.show(
|
.show(
|
||||||
context,
|
context,
|
||||||
@@ -536,7 +631,7 @@ class _WifiPageState extends State<WifiPage> {
|
|||||||
.updateAll();
|
.updateAll();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Navigator.pop(context);
|
// Navigator.pop(context);
|
||||||
TopSlideNotification.show(
|
TopSlideNotification.show(
|
||||||
context,
|
context,
|
||||||
text: "wifi页.配网失败".tr,
|
text: "wifi页.配网失败".tr,
|
||||||
@@ -574,7 +669,24 @@ class _WifiPageState extends State<WifiPage> {
|
|||||||
.sc3,
|
.sc3,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
getWifiIconByRsso(wifiItem),
|
if (blueteethBindController
|
||||||
|
.selectWifi.value ==
|
||||||
|
wifiItem)
|
||||||
|
SizedBox(
|
||||||
|
width: 32.rpx,
|
||||||
|
height: 32.rpx,
|
||||||
|
child:
|
||||||
|
CircularProgressIndicator(
|
||||||
|
strokeWidth: 1,
|
||||||
|
valueColor:
|
||||||
|
AlwaysStoppedAnimation<
|
||||||
|
Color>(
|
||||||
|
Colors.white),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
getWifiIconByRsso(
|
||||||
|
wifiItem),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
@@ -589,6 +701,9 @@ class _WifiPageState extends State<WifiPage> {
|
|||||||
horizontal: 20.rpx, vertical: 10.rpx),
|
horizontal: 20.rpx, vertical: 10.rpx),
|
||||||
borderRadius: 20.rpx,
|
borderRadius: 20.rpx,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
blueteethBindController
|
||||||
|
.connectStatus.value = 0;
|
||||||
|
blueteethBindController.updateAll();
|
||||||
print("点击刷新");
|
print("点击刷新");
|
||||||
await initWifiList();
|
await initWifiList();
|
||||||
TopSlideNotification.show(
|
TopSlideNotification.show(
|
||||||
@@ -645,93 +760,92 @@ class _WifiPageState extends State<WifiPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDeviceCard(BuildContext context,
|
Future<void> initWifiStatusAndWifiList() async {
|
||||||
{required String title, required String imageUrl, required String type}) {
|
|
||||||
return CustomCard(
|
|
||||||
borderRadius: 20.rpx, // 圆角大小
|
|
||||||
onTap: () {
|
|
||||||
if (type != null) {
|
|
||||||
if (type == '1') {
|
|
||||||
Get.toNamed("/blueteethDevice");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
colors: [themeController.currentColor.sc17], // 背景色
|
|
||||||
|
|
||||||
child: Container(
|
|
||||||
width: double.infinity,
|
|
||||||
height: MediaQuery.sizeOf(context).height * 0.135,
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
minHeight: 220.rpx,
|
|
||||||
),
|
|
||||||
padding: EdgeInsetsDirectional.fromSTEB(77.rpx, 0, 21.rpx, 0),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: FlutterFlowTheme.of(context).bodyMedium.override(
|
|
||||||
fontFamily: 'Inter',
|
|
||||||
color: const Color(0xFFC2CED7),
|
|
||||||
fontSize: 30.rpx,
|
|
||||||
letterSpacing: 0.0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(8.rpx),
|
|
||||||
child: Image.asset(
|
|
||||||
imageUrl,
|
|
||||||
width: 212.rpx,
|
|
||||||
height: 168.rpx,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void initWifiStatusAndWifiList() {
|
|
||||||
if (lisObj != null) {
|
if (lisObj != null) {
|
||||||
lisObj!.cancel();
|
lisObj!.cancel();
|
||||||
}
|
}
|
||||||
|
bool wifiStatus = false;
|
||||||
|
var aa =
|
||||||
|
await getDeviceWifiStatus(blueteethBindController.currentDevice!, 1);
|
||||||
|
if (aa != null && aa is Map) {
|
||||||
|
wifiStatus = true;
|
||||||
|
blueteethBindController.connect_wifi.value = aa;
|
||||||
|
}
|
||||||
|
blueteethBindController.wifiStatus.value = wifiStatus == true ? 1 : 0;
|
||||||
|
List wifiList = [];
|
||||||
|
try {
|
||||||
|
final result = await getWifiList(blueteethBindController.currentDevice!);
|
||||||
|
if (result is List) {
|
||||||
|
wifiList = result;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("异常: $e");
|
||||||
|
}
|
||||||
|
if (wifiList.length > 0) {
|
||||||
|
blueteethBindController.connectStatus.value = 1;
|
||||||
|
blueteethBindController.updateAll();
|
||||||
|
// Navigator.pop(context);
|
||||||
|
TopSlideNotification.show(
|
||||||
|
context,
|
||||||
|
text: "获取wifi列表成功".tr,
|
||||||
|
textColor: themeController.currentColor.sc2,
|
||||||
|
);
|
||||||
|
blueteethBindController.wifiList.value = wifiList;
|
||||||
|
blueteethBindController.updateAll();
|
||||||
|
} else {
|
||||||
|
// Navigator.pop(context);
|
||||||
|
TopSlideNotification.show(
|
||||||
|
context,
|
||||||
|
text: "获取wifi列表失败".tr,
|
||||||
|
textColor: themeController.currentColor.sc9,
|
||||||
|
);
|
||||||
|
}
|
||||||
lisObj = blueteethBindController.currentDevice!.statusStream
|
lisObj = blueteethBindController.currentDevice!.statusStream
|
||||||
.listen((onData) async {
|
.listen((onData) async {
|
||||||
if (onData.status == BleEventType.recvLineLog) {
|
if (onData.status == BleEventType.recvLineLog) {
|
||||||
final line = onData.val;
|
final line = onData.val;
|
||||||
print("[bleee]:" + line);
|
print("[bleee]:" + line);
|
||||||
}
|
}
|
||||||
if (onData.status == BleEventType.ready) {
|
// if (onData.status == BleEventType.ready) {
|
||||||
showLoadingDialog(context, title: "获取wifi列表中...".tr);
|
// // showLoadingDialog(context, title: "获取wifi列表中...".tr);
|
||||||
bool wifiStatus = false;
|
// bool wifiStatus = false;
|
||||||
var aa = await getDeviceWifiStatus(
|
// var aa = await getDeviceWifiStatus(
|
||||||
blueteethBindController.currentDevice!, 10);
|
// blueteethBindController.currentDevice!, 1);
|
||||||
if (aa != null && aa is Map) {
|
// if (aa != null && aa is Map) {
|
||||||
wifiStatus = true;
|
// wifiStatus = true;
|
||||||
blueteethBindController.connect_wifi.value = aa;
|
// blueteethBindController.connect_wifi.value = aa;
|
||||||
}
|
// }
|
||||||
blueteethBindController.wifiStatus.value = wifiStatus == true ? 1 : 0;
|
// blueteethBindController.wifiStatus.value = wifiStatus == true ? 1 : 0;
|
||||||
List wifiList =
|
// List wifiList = [];
|
||||||
await getWifiList(blueteethBindController.currentDevice!);
|
// try {
|
||||||
if (wifiList.length > 0) {
|
// final result =
|
||||||
Navigator.pop(context);
|
// await getWifiList(blueteethBindController.currentDevice!);
|
||||||
TopSlideNotification.show(
|
// if (result is List) {
|
||||||
context,
|
// wifiList = result;
|
||||||
text: "获取wifi列表成功".tr,
|
// }
|
||||||
textColor: themeController.currentColor.sc2,
|
// } catch (e) {
|
||||||
);
|
// print("异常: $e");
|
||||||
blueteethBindController.wifiList.value = wifiList;
|
// }
|
||||||
blueteethBindController.updateAll();
|
// if (wifiList.length > 0) {
|
||||||
} else {
|
// blueteethBindController.connectStatus.value = 1;
|
||||||
Navigator.pop(context);
|
// blueteethBindController.updateAll();
|
||||||
TopSlideNotification.show(
|
// // Navigator.pop(context);
|
||||||
context,
|
// TopSlideNotification.show(
|
||||||
text: "获取wifi列表失败".tr,
|
// context,
|
||||||
textColor: themeController.currentColor.sc9,
|
// text: "获取wifi列表成功".tr,
|
||||||
);
|
// textColor: themeController.currentColor.sc2,
|
||||||
}
|
// );
|
||||||
}
|
// blueteethBindController.wifiList.value = wifiList;
|
||||||
|
// blueteethBindController.updateAll();
|
||||||
|
// } else {
|
||||||
|
// Navigator.pop(context);
|
||||||
|
// TopSlideNotification.show(
|
||||||
|
// context,
|
||||||
|
// text: "获取wifi列表失败".tr,
|
||||||
|
// textColor: themeController.currentColor.sc9,
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -740,6 +854,8 @@ class _WifiPageState extends State<WifiPage> {
|
|||||||
var wifiList = await getWifiList(blueteethBindController.currentDevice!);
|
var wifiList = await getWifiList(blueteethBindController.currentDevice!);
|
||||||
print(wifiList);
|
print(wifiList);
|
||||||
if (wifiList.length > 0) {
|
if (wifiList.length > 0) {
|
||||||
|
blueteethBindController.connectStatus.value = 1;
|
||||||
|
|
||||||
blueteethBindController.wifiList.value = wifiList;
|
blueteethBindController.wifiList.value = wifiList;
|
||||||
blueteethBindController.updateAll();
|
blueteethBindController.updateAll();
|
||||||
}
|
}
|
||||||
@@ -802,4 +918,150 @@ class _WifiPageState extends State<WifiPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> dealWifi(String mac) async {
|
||||||
|
// bodyDeviceController.wifiMac = mac;
|
||||||
|
// Get.toNamed("/wifiPage", arguments: 2);
|
||||||
|
// return;
|
||||||
|
final blueteethBindController = Get.find<BlueteethBindController>();
|
||||||
|
final themeController = Get.find<ThemeController>();
|
||||||
|
|
||||||
|
// 显示加载对话框
|
||||||
|
// showLoadingDialog(Get.context!, title: "连接中...".tr);
|
||||||
|
|
||||||
|
// 设置超时定时器
|
||||||
|
Timer? timeoutTimer;
|
||||||
|
bool isConnected = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 开始扫描蓝牙设备
|
||||||
|
await FlutterBluePlus.startScan(timeout: Duration(seconds: 10));
|
||||||
|
|
||||||
|
// 设置超时(20秒)
|
||||||
|
timeoutTimer = Timer(Duration(seconds: 20), () {
|
||||||
|
try {
|
||||||
|
if (!isConnected) {
|
||||||
|
// Navigator.of(context).pop(); // 先关闭 dialog
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
TopSlideNotification.show(
|
||||||
|
context,
|
||||||
|
text: "设备连接超时,请重试".tr,
|
||||||
|
textColor: themeController.currentColor.sc9,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
FlutterBluePlus.stopScan();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听扫描结果
|
||||||
|
StreamSubscription<List<ScanResult>>? scanSubscription;
|
||||||
|
scanSubscription = FlutterBluePlus.scanResults.listen((results) async {
|
||||||
|
// 过滤出符合条件的设备
|
||||||
|
ScanResult? targetDevice;
|
||||||
|
|
||||||
|
for (var r in results) {
|
||||||
|
if (r.advertisementData.manufacturerData.containsKey(0xFFED)) {
|
||||||
|
List<int> rawData = r.advertisementData.manufacturerData[0xFFED]!;
|
||||||
|
BleDeviceData deviceData = parseBleData(rawData);
|
||||||
|
String deviceMac =
|
||||||
|
deviceData.deviceId.replaceAll(':', '').toLowerCase();
|
||||||
|
if (deviceMac == mac.toLowerCase()) {
|
||||||
|
targetDevice = r;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetDevice != null && !isConnected) {
|
||||||
|
isConnected = true;
|
||||||
|
FlutterBluePlus.stopScan();
|
||||||
|
scanSubscription?.cancel();
|
||||||
|
timeoutTimer?.cancel();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 连接设备
|
||||||
|
// await targetDevice.device.connect();
|
||||||
|
THapp bledevice = THapp(device: targetDevice.device);
|
||||||
|
await bledevice.device.connect();
|
||||||
|
var res2 = bledevice.isConnected;
|
||||||
|
if (res2) {
|
||||||
|
// Navigator.pop(context);
|
||||||
|
TopSlideNotification.show(
|
||||||
|
context,
|
||||||
|
text: "蓝牙绑定.连接成功".tr,
|
||||||
|
textColor: themeController.currentColor.sc2,
|
||||||
|
);
|
||||||
|
blueteethBindController.currentDevice = bledevice;
|
||||||
|
if (lisObj != null) {
|
||||||
|
lisObj!.cancel();
|
||||||
|
}
|
||||||
|
var aa;
|
||||||
|
lisObj = blueteethBindController.currentDevice!.statusStream
|
||||||
|
.listen((onData) async {
|
||||||
|
if (onData.status == BleEventType.recvLineLog) {
|
||||||
|
final line = onData.val;
|
||||||
|
print("[bleee]:" + line);
|
||||||
|
}
|
||||||
|
if (onData.status == BleEventType.ready) {
|
||||||
|
aa = await getDeviceNetVersion(
|
||||||
|
blueteethBindController.currentDevice!, 1);
|
||||||
|
if (aa == "4g") {
|
||||||
|
// TopSlideNotification.show(
|
||||||
|
// Get.context!,
|
||||||
|
// text: "4g设备配置wifi提示".tr,
|
||||||
|
// textColor: themeController.currentColor.sc9,
|
||||||
|
// );
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
TopSlideNotification.show(
|
||||||
|
context,
|
||||||
|
text: "4g设备配置wifi提示".tr,
|
||||||
|
textColor: themeController.currentColor.sc9,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// Get.toNamed("/wifiPage", arguments: 2);
|
||||||
|
await initWifiStatusAndWifiList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get.toNamed("/wifiPage", arguments: {bledevice});
|
||||||
|
} else {
|
||||||
|
// Navigator.pop(context);
|
||||||
|
TopSlideNotification.show(
|
||||||
|
context,
|
||||||
|
text: "蓝牙绑定.连接失败".tr,
|
||||||
|
textColor: themeController.currentColor.sc9,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Navigator.of(Get.context!).pop(); // 关闭加载对话框
|
||||||
|
TopSlideNotification.show(
|
||||||
|
Get.context!,
|
||||||
|
text: "设备连接失败".tr,
|
||||||
|
textColor: themeController.currentColor.sc9,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 等待扫描完成
|
||||||
|
await Future.delayed(Duration(seconds: 20));
|
||||||
|
} catch (e) {
|
||||||
|
timeoutTimer?.cancel();
|
||||||
|
Navigator.of(Get.context!).pop(); // 关闭加载对话框
|
||||||
|
TopSlideNotification.show(
|
||||||
|
Get.context!,
|
||||||
|
text: "扫描过程中发生错误".tr,
|
||||||
|
textColor: themeController.currentColor.sc9,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
timeoutTimer?.cancel();
|
||||||
|
await FlutterBluePlus.stopScan();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
|||||||
import 'package:vbvs_app/component/tool/CustomCard.dart';
|
import 'package:vbvs_app/component/tool/CustomCard.dart';
|
||||||
import 'package:vbvs_app/component/tool/TopSlideNotification.dart';
|
import 'package:vbvs_app/component/tool/TopSlideNotification.dart';
|
||||||
import 'package:vbvs_app/controller/date/CalendarController.dart';
|
import 'package:vbvs_app/controller/date/CalendarController.dart';
|
||||||
|
import 'package:vbvs_app/controller/login/login_controller.dart';
|
||||||
import 'package:vbvs_app/controller/main_bottom/global_controller.dart';
|
import 'package:vbvs_app/controller/main_bottom/global_controller.dart';
|
||||||
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
||||||
import 'package:vbvs_app/controller/user_info_controller.dart';
|
import 'package:vbvs_app/controller/user_info_controller.dart';
|
||||||
@@ -27,6 +28,7 @@ class _MinePageState extends State<MinePage> {
|
|||||||
UserInfoController userInfoController = Get.find();
|
UserInfoController userInfoController = Get.find();
|
||||||
ThemeController themeController = Get.find();
|
ThemeController themeController = Get.find();
|
||||||
CalendarController calendarController = Get.find();
|
CalendarController calendarController = Get.find();
|
||||||
|
LoginController loginController = Get.find();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -80,41 +82,43 @@ class _MinePageState extends State<MinePage> {
|
|||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
ClickableContainer(
|
// ClickableContainer(
|
||||||
backgroundColor:
|
// backgroundColor:
|
||||||
Colors.transparent, // 容器背景色
|
// Colors.transparent, // 容器背景色
|
||||||
highlightColor: themeController
|
// highlightColor: themeController
|
||||||
.currentColor.sc21, // 点击时的背景色
|
// .currentColor.sc21, // 点击时的背景色
|
||||||
padding: EdgeInsets
|
// padding: EdgeInsets
|
||||||
.zero, // 这里去掉外部的 padding,避免影响点击范围
|
// .zero, // 这里去掉外部的 padding,避免影响点击范围
|
||||||
onTap: () {
|
// onTap: () async {
|
||||||
if (userInfoController.model.login ==
|
// if (userInfoController.model.login ==
|
||||||
LoginStatus.LOGIN.code) {
|
// LoginStatus.LOGIN.code) {
|
||||||
TopSlideNotification.show(
|
// // TopSlideNotification.show(
|
||||||
context,
|
// // context,
|
||||||
text: "待开发功能".tr,
|
// // text: "待开发功能".tr,
|
||||||
);
|
// // );
|
||||||
} else {
|
// await loginController.openWeChatCustomerService(context);
|
||||||
TopSlideNotification.show(
|
// } else {
|
||||||
context,
|
// TopSlideNotification.show(
|
||||||
text: "必须登录提示".tr,
|
// context,
|
||||||
textColor:
|
// text: "必须登录提示".tr,
|
||||||
themeController.currentColor.sc9,
|
// textColor:
|
||||||
);
|
// themeController.currentColor.sc9,
|
||||||
Get.toNamed("/loginPage");
|
// );
|
||||||
}
|
// Get.toNamed("/loginPage");
|
||||||
},
|
// }
|
||||||
child: Padding(
|
// },
|
||||||
padding: EdgeInsetsDirectional.fromSTEB(
|
// child: Padding(
|
||||||
0.rpx, 0.rpx, 0.rpx, 0.rpx),
|
// padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
child: SvgPicture.asset(
|
// 0.rpx, 0.rpx, 0.rpx, 0.rpx),
|
||||||
'assets/img/icon/earphone.svg',
|
// child: SvgPicture.asset(
|
||||||
width: 29.rpx,
|
// 'assets/img/icon/earphone.svg',
|
||||||
height: 29.rpx, // 如果 SVG 中没有固定颜色,可以这样设置
|
// width: 29.rpx,
|
||||||
color: themeController.currentColor.sc3,
|
// height: 29.rpx, // 如果 SVG 中没有固定颜色,可以这样设置
|
||||||
),
|
// color: themeController.currentColor.sc3,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
|
// ),
|
||||||
|
|
||||||
ClickableContainer(
|
ClickableContainer(
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
Colors.transparent, // 容器背景色
|
Colors.transparent, // 容器背景色
|
||||||
@@ -639,13 +643,15 @@ class _MinePageState extends State<MinePage> {
|
|||||||
);
|
);
|
||||||
Get.toNamed("/loginPage");
|
Get.toNamed("/loginPage");
|
||||||
} else {
|
} else {
|
||||||
TopSlideNotification.show(
|
// TopSlideNotification.show(
|
||||||
context,
|
// context,
|
||||||
text: "待开发.提示".tr,
|
// text: "待开发.提示".tr,
|
||||||
textColor:
|
// textColor:
|
||||||
themeController.currentColor.sc2,
|
// themeController.currentColor.sc2,
|
||||||
);
|
// );
|
||||||
// Get.toNamed("/newSleepReportPage",arguments: DateTime.now().millisecondsSinceEpoch);
|
Get.toNamed("/newSleepReportPage",
|
||||||
|
arguments: DateTime.now()
|
||||||
|
.millisecondsSinceEpoch);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
408
lib/pages/sleep_report/chart/DotBarChart.dart
Normal file
408
lib/pages/sleep_report/chart/DotBarChart.dart
Normal file
@@ -0,0 +1,408 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:vbvs_app/common/util/FitTool.dart';
|
||||||
|
import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||||
|
|
||||||
|
class DotBarChart extends StatefulWidget {
|
||||||
|
final List<Map<String, dynamic>> showLabel;
|
||||||
|
final int threshold;
|
||||||
|
final int startTime;
|
||||||
|
final int endTime;
|
||||||
|
|
||||||
|
const DotBarChart({
|
||||||
|
Key? key,
|
||||||
|
required this.showLabel,
|
||||||
|
required this.threshold,
|
||||||
|
required this.startTime,
|
||||||
|
required this.endTime,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_DotBarChartState createState() => _DotBarChartState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DotBarChartState extends State<DotBarChart> {
|
||||||
|
int? selectedIndex;
|
||||||
|
OverlayEntry? _overlayEntry;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_removeOverlay();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _removeOverlay() {
|
||||||
|
_overlayEntry?.remove();
|
||||||
|
_overlayEntry = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showTooltip(BuildContext context, Map<String, dynamic> data,
|
||||||
|
Offset position, double dotY) {
|
||||||
|
_removeOverlay();
|
||||||
|
|
||||||
|
final RenderBox renderBox = context.findRenderObject() as RenderBox;
|
||||||
|
final Offset globalPosition = renderBox.localToGlobal(position);
|
||||||
|
|
||||||
|
_overlayEntry = OverlayEntry(
|
||||||
|
builder: (context) => Positioned(
|
||||||
|
left: globalPosition.dx - 60.rpx,
|
||||||
|
top: globalPosition.dy - 80.rpx,
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(12.rpx),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: themeController.currentColor.sc5,
|
||||||
|
borderRadius: BorderRadius.circular(8.rpx),
|
||||||
|
boxShadow: [
|
||||||
|
// BoxShadow(
|
||||||
|
// color: Colors.black.withOpacity(0.6),
|
||||||
|
// blurRadius: 10.rpx,
|
||||||
|
// offset: Offset(0, 4.rpx),
|
||||||
|
// ),
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.5),
|
||||||
|
blurRadius: 12.rpx,
|
||||||
|
spreadRadius: 2.rpx,
|
||||||
|
offset: Offset(0, 6.rpx),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'时间: ${DateFormat('HH:mm').format(DateTime.fromMillisecondsSinceEpoch(data['time']))}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20.rpx,
|
||||||
|
color: themeController.currentColor.sc3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 4.rpx),
|
||||||
|
Text(
|
||||||
|
'时长: ${data['times']}秒',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20.rpx,
|
||||||
|
color: themeController.currentColor.sc3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Overlay.of(context)?.insert(_overlayEntry!);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (widget.showLabel.isEmpty) return const SizedBox();
|
||||||
|
|
||||||
|
int maxTimes = widget.showLabel
|
||||||
|
.map((e) => e['times'] ?? 0)
|
||||||
|
.reduce((a, b) => a > b ? a : b);
|
||||||
|
|
||||||
|
int yMax = (maxTimes / 10).ceil() * 10;
|
||||||
|
if (yMax == 0) yMax = 10;
|
||||||
|
|
||||||
|
int maxSteps = 6;
|
||||||
|
int step = (yMax / maxSteps).ceil();
|
||||||
|
step = ((step + 9) ~/ 10) * 10;
|
||||||
|
int displayMax = step * maxSteps;
|
||||||
|
List<int> yLabels = List.generate(maxSteps + 1, (index) => step * index);
|
||||||
|
|
||||||
|
DateFormat fullFormat = DateFormat('HH:mm');
|
||||||
|
DateFormat hourFormat = DateFormat('H');
|
||||||
|
|
||||||
|
int maxXLabels = 11;
|
||||||
|
int totalPoints = widget.showLabel.length;
|
||||||
|
List<int> xLabelIndices = [];
|
||||||
|
|
||||||
|
if (totalPoints <= maxXLabels) {
|
||||||
|
xLabelIndices = List.generate(totalPoints, (i) => i);
|
||||||
|
} else {
|
||||||
|
xLabelIndices.add(0);
|
||||||
|
int middleCount = maxXLabels - 2;
|
||||||
|
double stepX = (totalPoints - 1) / (middleCount + 1);
|
||||||
|
for (int i = 1; i <= middleCount; i++) {
|
||||||
|
xLabelIndices.add((stepX * i).round());
|
||||||
|
}
|
||||||
|
xLabelIndices.add(totalPoints - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
double yAxisWidth = 36.rpx;
|
||||||
|
double xAxisHeight = 40.rpx;
|
||||||
|
double chartHeight = 491.rpx;
|
||||||
|
double bottomPadding = 10.rpx;
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
selectedIndex = null;
|
||||||
|
_removeOverlay();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: chartHeight,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: yAxisWidth,
|
||||||
|
height: chartHeight + bottomPadding,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Positioned(
|
||||||
|
top: 0,
|
||||||
|
right: 6.rpx,
|
||||||
|
child: Text(
|
||||||
|
'秒',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18.rpx,
|
||||||
|
color: themeController.currentColor.sc4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
...List.generate(yLabels.length, (index) {
|
||||||
|
double yLabelsAreaHeight =
|
||||||
|
chartHeight - (30.rpx + 18.rpx) - bottomPadding;
|
||||||
|
double y = (30.rpx + 18.rpx) +
|
||||||
|
index * (yLabelsAreaHeight / (yLabels.length - 1));
|
||||||
|
double textHeight = 18.rpx;
|
||||||
|
double topPos = y - textHeight / 2;
|
||||||
|
if (index == yLabels.length - 1) {
|
||||||
|
topPos -= bottomPadding / 2;
|
||||||
|
}
|
||||||
|
return Positioned(
|
||||||
|
right: 6.rpx,
|
||||||
|
top: topPos,
|
||||||
|
child: Text(
|
||||||
|
'${yLabels.reversed.toList()[index]}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18.rpx,
|
||||||
|
color: themeController.currentColor.sc4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: CustomPaint(
|
||||||
|
size: Size(double.infinity, chartHeight),
|
||||||
|
painter: _DotBarChartPainter(
|
||||||
|
data: widget.showLabel,
|
||||||
|
yMax: displayMax,
|
||||||
|
threshold: widget.threshold,
|
||||||
|
yLabelsCount: yLabels.length,
|
||||||
|
yAxisTopPadding: 30.rpx + 18.rpx,
|
||||||
|
horizontalPadding: 20.rpx,
|
||||||
|
selectedIndex: selectedIndex,
|
||||||
|
),
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
final double chartWidth =
|
||||||
|
constraints.maxWidth - 2 * 20.rpx;
|
||||||
|
final double xStep = widget.showLabel.length > 1
|
||||||
|
? chartWidth / (widget.showLabel.length - 1)
|
||||||
|
: 0;
|
||||||
|
final double drawableHeight =
|
||||||
|
chartHeight - (30.rpx + 18.rpx);
|
||||||
|
|
||||||
|
return Stack(
|
||||||
|
children:
|
||||||
|
widget.showLabel.asMap().entries.map((entry) {
|
||||||
|
int index = entry.key;
|
||||||
|
Map<String, dynamic> data = entry.value;
|
||||||
|
int times = data['times'] ?? 0;
|
||||||
|
double x = 20.rpx + index * xStep;
|
||||||
|
double y = (30.rpx + 18.rpx) +
|
||||||
|
drawableHeight * (1 - times / displayMax);
|
||||||
|
|
||||||
|
return Positioned(
|
||||||
|
left: x - 20.rpx, // Increase touch area
|
||||||
|
top: y - 20.rpx, // Increase touch area
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
selectedIndex = index;
|
||||||
|
});
|
||||||
|
_showTooltip(
|
||||||
|
context,
|
||||||
|
data,
|
||||||
|
Offset(x, y),
|
||||||
|
y,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
width: 40.rpx,
|
||||||
|
height: 40.rpx,
|
||||||
|
color: Colors.transparent,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(left: yAxisWidth),
|
||||||
|
child: SizedBox(
|
||||||
|
height: xAxisHeight,
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: List.generate(widget.showLabel.length, (index) {
|
||||||
|
String label = '';
|
||||||
|
if (xLabelIndices.contains(index)) {
|
||||||
|
DateTime dt = DateTime.fromMillisecondsSinceEpoch(
|
||||||
|
widget.showLabel[index]['time']);
|
||||||
|
if (index == 0 || index == totalPoints - 1) {
|
||||||
|
label = fullFormat.format(dt);
|
||||||
|
} else {
|
||||||
|
label = dt.hour.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(top: 14.rpx),
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18.rpx,
|
||||||
|
color: themeController.currentColor.sc4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DotBarChartPainter extends CustomPainter {
|
||||||
|
final List<Map<String, dynamic>> data;
|
||||||
|
final int yMax;
|
||||||
|
final int threshold;
|
||||||
|
final int yLabelsCount;
|
||||||
|
final double yAxisTopPadding;
|
||||||
|
final double horizontalPadding;
|
||||||
|
final int? selectedIndex;
|
||||||
|
|
||||||
|
_DotBarChartPainter({
|
||||||
|
required this.data,
|
||||||
|
required this.yMax,
|
||||||
|
required this.threshold,
|
||||||
|
required this.yLabelsCount,
|
||||||
|
required this.yAxisTopPadding,
|
||||||
|
required this.horizontalPadding,
|
||||||
|
this.selectedIndex,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final double chartWidth = size.width - 2 * horizontalPadding;
|
||||||
|
final double xStep = data.length > 1 ? chartWidth / (data.length - 1) : 0;
|
||||||
|
final double chartHeight = size.height;
|
||||||
|
final double drawableHeight = chartHeight - yAxisTopPadding;
|
||||||
|
|
||||||
|
final Paint thresholdPaint = Paint()
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..color = stringToColor("#FF7159")
|
||||||
|
..strokeWidth = 1.rpx;
|
||||||
|
|
||||||
|
double thresholdY =
|
||||||
|
yAxisTopPadding + drawableHeight * (1 - threshold / yMax);
|
||||||
|
|
||||||
|
drawDashedLine(
|
||||||
|
canvas,
|
||||||
|
Offset(0, thresholdY),
|
||||||
|
Offset(size.width, thresholdY),
|
||||||
|
thresholdPaint,
|
||||||
|
8.rpx,
|
||||||
|
6.rpx,
|
||||||
|
);
|
||||||
|
|
||||||
|
final double dotRadius = 13.rpx;
|
||||||
|
|
||||||
|
for (int i = 0; i < data.length; i++) {
|
||||||
|
int times = data[i]['times'] ?? 0;
|
||||||
|
double x = horizontalPadding + i * xStep;
|
||||||
|
double y = yAxisTopPadding + drawableHeight * (1 - times / yMax);
|
||||||
|
|
||||||
|
Paint dotPaint = Paint()
|
||||||
|
..style = PaintingStyle.fill
|
||||||
|
..color = times >= threshold
|
||||||
|
? stringToColor("#FF7159")
|
||||||
|
: stringToColor("#00C1AA");
|
||||||
|
|
||||||
|
// Draw a larger circle for selected dot
|
||||||
|
if (i == selectedIndex) {
|
||||||
|
Paint borderPaint = Paint()
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..color = Colors.white
|
||||||
|
..strokeWidth = 3.rpx;
|
||||||
|
|
||||||
|
canvas.drawCircle(Offset(x, y), dotRadius + 1.rpx, borderPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.drawCircle(Offset(x, y), dotRadius, dotPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Paint solidLinePaint = Paint()
|
||||||
|
..color = Colors.grey.withOpacity(0.7)
|
||||||
|
..strokeWidth = 1.rpx
|
||||||
|
..style = PaintingStyle.stroke;
|
||||||
|
|
||||||
|
final Paint dashedLinePaint = Paint()
|
||||||
|
..color = Colors.grey.withOpacity(0.4)
|
||||||
|
..strokeWidth = 1.rpx
|
||||||
|
..style = PaintingStyle.stroke;
|
||||||
|
|
||||||
|
for (int i = 0; i < yLabelsCount; i++) {
|
||||||
|
double y = yAxisTopPadding + i * (drawableHeight / (yLabelsCount - 1));
|
||||||
|
|
||||||
|
if (i == yLabelsCount - 1) {
|
||||||
|
canvas.drawLine(Offset(0, y), Offset(size.width, y), solidLinePaint);
|
||||||
|
} else {
|
||||||
|
drawDashedLine(
|
||||||
|
canvas,
|
||||||
|
Offset(0, y),
|
||||||
|
Offset(size.width, y),
|
||||||
|
dashedLinePaint,
|
||||||
|
8.rpx,
|
||||||
|
6.rpx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawDashedLine(Canvas canvas, Offset start, Offset end, Paint paint,
|
||||||
|
double dashWidth, double gapWidth) {
|
||||||
|
double dx = start.dx;
|
||||||
|
final double y = start.dy;
|
||||||
|
while (dx < end.dx) {
|
||||||
|
final double nextDx = (dx + dashWidth).clamp(start.dx, end.dx);
|
||||||
|
canvas.drawLine(Offset(dx, y), Offset(nextDx, y), paint);
|
||||||
|
dx = nextDx + gapWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
|
||||||
|
}
|
||||||
147
lib/pages/sleep_report/chart/FatigueCircleIndicator.dart
Normal file
147
lib/pages/sleep_report/chart/FatigueCircleIndicator.dart
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
import 'package:flutter/material.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';
|
||||||
|
|
||||||
|
class FatigueCircleIndicator extends StatelessWidget {
|
||||||
|
final Map<String, dynamic> data;
|
||||||
|
|
||||||
|
const FatigueCircleIndicator({super.key, required this.data});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final double screenWidth = MediaQuery.of(context).size.width;
|
||||||
|
final double radius = (screenWidth * 0.127).clamp(95.rpx, double.infinity);
|
||||||
|
|
||||||
|
final double strokeWidth = 14.rpx;
|
||||||
|
final double backgroundStrokeWidth = 8.rpx;
|
||||||
|
|
||||||
|
final String name = data["name"];
|
||||||
|
final Color color = data["color"];
|
||||||
|
final int percent = data["percent"];
|
||||||
|
final String explain = data["explain"];
|
||||||
|
final Color bottomColor = data["bottomColor"];
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: radius * 2,
|
||||||
|
height: radius * 2,
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
// 合并绘制背景与进度
|
||||||
|
CustomPaint(
|
||||||
|
size: Size(radius * 2, radius * 2),
|
||||||
|
painter: _CirclePainter(
|
||||||
|
percent: percent,
|
||||||
|
color: color,
|
||||||
|
bottomColor: bottomColor,
|
||||||
|
progressStrokeWidth: strokeWidth,
|
||||||
|
backgroundStrokeWidth: backgroundStrokeWidth,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// 中心文本
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'$percent%',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: AppConstants().normal_text_fontSize,
|
||||||
|
color: percent > 60
|
||||||
|
? themeController.currentColor.sc9
|
||||||
|
: themeController.currentColor.sc3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 4.rpx),
|
||||||
|
Text(
|
||||||
|
explain,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: AppConstants().normal_text_fontSize,
|
||||||
|
color: percent > 60
|
||||||
|
? themeController.currentColor.sc9
|
||||||
|
: themeController.currentColor.sc3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 40.rpx),
|
||||||
|
Text(
|
||||||
|
name,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: AppConstants().normal_text_fontSize,
|
||||||
|
color: themeController.currentColor.sc3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CirclePainter extends CustomPainter {
|
||||||
|
final int percent;
|
||||||
|
final Color color;
|
||||||
|
final Color bottomColor;
|
||||||
|
final double progressStrokeWidth;
|
||||||
|
final double backgroundStrokeWidth;
|
||||||
|
|
||||||
|
_CirclePainter({
|
||||||
|
required this.percent,
|
||||||
|
required this.color,
|
||||||
|
required this.bottomColor,
|
||||||
|
required this.progressStrokeWidth,
|
||||||
|
required this.backgroundStrokeWidth,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final center = Offset(size.width / 2, size.height / 2);
|
||||||
|
final radius = (size.width - progressStrokeWidth) / 2;
|
||||||
|
|
||||||
|
// 背景环(底色,细)
|
||||||
|
final backgroundPaint = Paint()
|
||||||
|
..color = bottomColor
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = backgroundStrokeWidth
|
||||||
|
..strokeCap = StrokeCap.round;
|
||||||
|
|
||||||
|
canvas.drawArc(
|
||||||
|
Rect.fromCircle(center: center, radius: radius),
|
||||||
|
90 * 3.1415926 / 180,
|
||||||
|
2 * 3.1415926,
|
||||||
|
false,
|
||||||
|
backgroundPaint,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 进度环(进度色,粗)
|
||||||
|
final progressPaint = Paint()
|
||||||
|
..color = color
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = progressStrokeWidth
|
||||||
|
..strokeCap = StrokeCap.butt;
|
||||||
|
|
||||||
|
final sweepAngle = 2 * 3.1415926 * (percent / 100);
|
||||||
|
|
||||||
|
canvas.drawArc(
|
||||||
|
Rect.fromCircle(center: center, radius: radius),
|
||||||
|
90 * 3.1415926 / 180,
|
||||||
|
sweepAngle,
|
||||||
|
false,
|
||||||
|
progressPaint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(_CirclePainter oldDelegate) {
|
||||||
|
return oldDelegate.percent != percent ||
|
||||||
|
oldDelegate.color != color ||
|
||||||
|
oldDelegate.bottomColor != bottomColor ||
|
||||||
|
oldDelegate.progressStrokeWidth != progressStrokeWidth ||
|
||||||
|
oldDelegate.backgroundStrokeWidth != backgroundStrokeWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
281
lib/pages/sleep_report/chart/HorizontalBarChart.dart
Normal file
281
lib/pages/sleep_report/chart/HorizontalBarChart.dart
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:vbvs_app/common/util/FitTool.dart';
|
||||||
|
import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||||
|
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
||||||
|
import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
|
||||||
|
|
||||||
|
class HorizontalBarChart extends StatelessWidget {
|
||||||
|
final List<Map<String, dynamic>> showLabel;
|
||||||
|
final bool showPercent;
|
||||||
|
|
||||||
|
const HorizontalBarChart({
|
||||||
|
super.key,
|
||||||
|
required this.showLabel,
|
||||||
|
this.showPercent = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final data = showLabel.map((item) {
|
||||||
|
return BarData(
|
||||||
|
label: item['name'],
|
||||||
|
value: (item['percent'] ?? 0).toDouble(),
|
||||||
|
color: item['color'] ?? Colors.grey,
|
||||||
|
explain: item['explain'] ?? '',
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
final double labelWidth = (MediaQuery.of(context).size.width * 0.5).clamp(
|
||||||
|
MediaQuery.of(context).size.width * 0.08,
|
||||||
|
MediaQuery.of(context).size.width * 0.22);
|
||||||
|
|
||||||
|
final double barHeight = 24.0.rpx;
|
||||||
|
final double barSpacing = 40.0.rpx;
|
||||||
|
final totalHeight = data.length * (barHeight + barSpacing) + 30.rpx;
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
height: totalHeight,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// 左侧标签列
|
||||||
|
SizedBox(
|
||||||
|
width: labelWidth,
|
||||||
|
child: ListView.builder(
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemCount: data.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final bar = data[index];
|
||||||
|
return Container(
|
||||||
|
height: barHeight + barSpacing,
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: LabelWithSvg(
|
||||||
|
label: bar.label,
|
||||||
|
explain: bar.explain,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 16.rpx,
|
||||||
|
),
|
||||||
|
// 右侧柱状图区
|
||||||
|
Expanded(
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
// 网格线背景层
|
||||||
|
CustomPaint(
|
||||||
|
size: Size(double.infinity, totalHeight),
|
||||||
|
painter: GridPainter(totalHeight: totalHeight),
|
||||||
|
),
|
||||||
|
|
||||||
|
// 柱状图列表
|
||||||
|
ListView.builder(
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemCount: data.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final bar = data[index];
|
||||||
|
return SizedBox(
|
||||||
|
height: barHeight + barSpacing,
|
||||||
|
child: CustomPaint(
|
||||||
|
painter: SingleBarPainter(
|
||||||
|
value: bar.value,
|
||||||
|
color: bar.color,
|
||||||
|
showPercent: showPercent,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BarData {
|
||||||
|
final String label;
|
||||||
|
final double value;
|
||||||
|
final Color color;
|
||||||
|
final String explain;
|
||||||
|
|
||||||
|
BarData({
|
||||||
|
required this.label,
|
||||||
|
required this.value,
|
||||||
|
required this.color,
|
||||||
|
required this.explain,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class LabelWithSvg extends StatelessWidget {
|
||||||
|
final String label;
|
||||||
|
final String explain;
|
||||||
|
|
||||||
|
const LabelWithSvg({
|
||||||
|
super.key,
|
||||||
|
required this.label,
|
||||||
|
required this.explain,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final textStyle =
|
||||||
|
TextStyle(color: themeController.currentColor.sc3, fontSize: 26.rpx);
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: textStyle,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// SizedBox(width: 8.rpx),
|
||||||
|
ClickableContainer(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
highlightColor: Colors.white,
|
||||||
|
padding:
|
||||||
|
EdgeInsetsDirectional.fromSTEB(14.rpx, 14.rpx, 14.rpx, 14.rpx),
|
||||||
|
borderRadius: 0.rpx,
|
||||||
|
onTap: () {
|
||||||
|
// Get.toNamed("/deviceShareListPage", arguments: explain);
|
||||||
|
showTipDialog(
|
||||||
|
context,
|
||||||
|
Container(
|
||||||
|
child: Text(
|
||||||
|
explain,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 26.rpx,
|
||||||
|
color: themeController.currentColor.sc3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: SizedBox(
|
||||||
|
width: 17.rpx,
|
||||||
|
height: 17.rpx,
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
'assets/img/icon/explain.svg',
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SingleBarPainter extends CustomPainter {
|
||||||
|
final double value;
|
||||||
|
final Color color;
|
||||||
|
final bool showPercent;
|
||||||
|
final double maxValue = 100;
|
||||||
|
|
||||||
|
SingleBarPainter({
|
||||||
|
required this.value,
|
||||||
|
required this.color,
|
||||||
|
required this.showPercent,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final barHeight = 24.0.rpx;
|
||||||
|
final rightPadding = 20.0.rpx;
|
||||||
|
final chartWidth = size.width - rightPadding;
|
||||||
|
|
||||||
|
final left = 0.0;
|
||||||
|
final right = left + (value / maxValue) * chartWidth;
|
||||||
|
final rect = Rect.fromLTWH(
|
||||||
|
left, (size.height - barHeight) / 2, right - left, barHeight);
|
||||||
|
final paint = Paint()..color = color;
|
||||||
|
canvas.drawRect(rect, paint);
|
||||||
|
|
||||||
|
if (showPercent) {
|
||||||
|
final textStyle =
|
||||||
|
TextStyle(color: themeController.currentColor.sc3, fontSize: 26.rpx);
|
||||||
|
final textPainter = TextPainter(textDirection: TextDirection.ltr);
|
||||||
|
textPainter.text =
|
||||||
|
TextSpan(text: '${value.toStringAsFixed(0)}%', style: textStyle);
|
||||||
|
textPainter.layout();
|
||||||
|
canvas.save();
|
||||||
|
canvas.clipRect(Rect.fromLTWH(
|
||||||
|
left, (size.height - barHeight) / 2, chartWidth, barHeight));
|
||||||
|
textPainter.paint(
|
||||||
|
canvas,
|
||||||
|
Offset(right + 4.0.rpx, (size.height - textPainter.height) / 2),
|
||||||
|
);
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class GridPainter extends CustomPainter {
|
||||||
|
final double totalHeight;
|
||||||
|
final int gridCount = 5;
|
||||||
|
final double maxValue = 100;
|
||||||
|
final double bottomPadding = 30.0.rpx;
|
||||||
|
|
||||||
|
GridPainter({required this.totalHeight});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final Paint gridPaint = Paint()
|
||||||
|
..color = Colors.grey.withOpacity(0.3)
|
||||||
|
..strokeWidth = 1.0.rpx;
|
||||||
|
|
||||||
|
final double rightPadding = 20.0.rpx;
|
||||||
|
final double chartWidth = size.width - rightPadding;
|
||||||
|
final double chartHeight = totalHeight - bottomPadding;
|
||||||
|
|
||||||
|
for (int i = 0; i <= gridCount; i++) {
|
||||||
|
double dx = (i / gridCount) * chartWidth;
|
||||||
|
_drawDashedLine(
|
||||||
|
canvas, Offset(dx, 0), Offset(dx, chartHeight), gridPaint);
|
||||||
|
|
||||||
|
final percent = (i / gridCount) * maxValue;
|
||||||
|
final TextPainter textPainter = TextPainter(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
text: TextSpan(
|
||||||
|
text: '${percent.toInt()}',
|
||||||
|
style: TextStyle(color: Colors.grey, fontSize: 18.rpx),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
textPainter.layout();
|
||||||
|
textPainter.paint(
|
||||||
|
canvas, Offset(dx - textPainter.width / 2, chartHeight + 4.0.rpx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _drawDashedLine(Canvas canvas, Offset start, Offset end, Paint paint) {
|
||||||
|
const double dashWidth = 6.0;
|
||||||
|
const double dashSpace = 4.0;
|
||||||
|
|
||||||
|
final double totalHeight = (end.dy - start.dy).abs();
|
||||||
|
double currentY = start.dy;
|
||||||
|
|
||||||
|
while (currentY < end.dy) {
|
||||||
|
final double nextY = currentY + dashWidth;
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(start.dx, currentY),
|
||||||
|
Offset(start.dx, nextY > end.dy ? end.dy : nextY),
|
||||||
|
paint,
|
||||||
|
);
|
||||||
|
currentY = nextY + dashSpace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||||
|
}
|
||||||
252
lib/pages/sleep_report/chart/LineChartByRange.dart
Normal file
252
lib/pages/sleep_report/chart/LineChartByRange.dart
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:vbvs_app/common/util/FitTool.dart';
|
||||||
|
import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
class LineChartByRange extends StatelessWidget {
|
||||||
|
final List<Map<String, dynamic>> showLabel;
|
||||||
|
final int startTime;
|
||||||
|
final int endTime;
|
||||||
|
|
||||||
|
const LineChartByRange({
|
||||||
|
Key? key,
|
||||||
|
required this.showLabel,
|
||||||
|
required this.startTime,
|
||||||
|
required this.endTime,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (showLabel.isEmpty) return const SizedBox();
|
||||||
|
|
||||||
|
int maxTimes =
|
||||||
|
showLabel.map((e) => e['times'] ?? 0).reduce((a, b) => a > b ? a : b);
|
||||||
|
int yMax = (maxTimes / 10).ceil() * 10;
|
||||||
|
if (yMax == 0) yMax = 10;
|
||||||
|
|
||||||
|
DateTime minTime = DateTime.fromMillisecondsSinceEpoch(startTime);
|
||||||
|
DateTime maxTime = DateTime.fromMillisecondsSinceEpoch(endTime);
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 500.rpx,
|
||||||
|
child: CustomPaint(
|
||||||
|
size: Size(double.infinity, 500.rpx),
|
||||||
|
painter: _LineChartByRangePainter(
|
||||||
|
data: showLabel,
|
||||||
|
yMax: yMax,
|
||||||
|
minTime: minTime,
|
||||||
|
maxTime: maxTime,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LineChartByRangePainter extends CustomPainter {
|
||||||
|
final List<Map<String, dynamic>> data;
|
||||||
|
final int yMax;
|
||||||
|
final DateTime minTime;
|
||||||
|
final DateTime maxTime;
|
||||||
|
|
||||||
|
_LineChartByRangePainter({
|
||||||
|
required this.data,
|
||||||
|
required this.yMax,
|
||||||
|
required this.minTime,
|
||||||
|
required this.maxTime,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
double padding = 20.rpx;
|
||||||
|
double labelInset = 12.rpx; // X轴标签缩进距离
|
||||||
|
|
||||||
|
// 绘图X轴起止点,考虑内缩labelInset
|
||||||
|
final double xStart = padding + labelInset;
|
||||||
|
final double xEnd = size.width - padding - labelInset;
|
||||||
|
final double chartWidth = xEnd - xStart;
|
||||||
|
|
||||||
|
double chartHeight = size.height - 30.rpx;
|
||||||
|
|
||||||
|
int totalDuration =
|
||||||
|
maxTime.millisecondsSinceEpoch - minTime.millisecondsSinceEpoch;
|
||||||
|
if (totalDuration <= 0) return;
|
||||||
|
|
||||||
|
Paint linePaint = Paint()
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 3.rpx
|
||||||
|
..color = stringToColor("#00C1AA")
|
||||||
|
..strokeCap = StrokeCap.round;
|
||||||
|
|
||||||
|
Paint fillCirclePaint = Paint()
|
||||||
|
..style = PaintingStyle.fill
|
||||||
|
..color = stringToColor("#00C1AA");
|
||||||
|
|
||||||
|
// 1. 先绘制数据线段及起止点圆点
|
||||||
|
for (var item in data) {
|
||||||
|
int start = item['startTime'];
|
||||||
|
int end = item['endTime'];
|
||||||
|
int times = item['times'];
|
||||||
|
|
||||||
|
double startX = xStart +
|
||||||
|
chartWidth * (start - minTime.millisecondsSinceEpoch) / totalDuration;
|
||||||
|
double endX = xStart +
|
||||||
|
chartWidth * (end - minTime.millisecondsSinceEpoch) / totalDuration;
|
||||||
|
double y = chartHeight * (1 - times / yMax);
|
||||||
|
|
||||||
|
// 画线段
|
||||||
|
canvas.drawLine(Offset(startX, y), Offset(endX, y), linePaint);
|
||||||
|
|
||||||
|
// 画起点圆点
|
||||||
|
canvas.drawCircle(Offset(startX, y), 4.rpx, fillCirclePaint);
|
||||||
|
|
||||||
|
// 画终点圆点
|
||||||
|
canvas.drawCircle(Offset(endX, y), 4.rpx, fillCirclePaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Y轴辅助线及文字
|
||||||
|
Paint axisPaint = Paint()
|
||||||
|
..color = Colors.grey.withOpacity(0.4)
|
||||||
|
..strokeWidth = 1.rpx;
|
||||||
|
|
||||||
|
for (int i = 0; i <= 6; i++) {
|
||||||
|
double y = chartHeight * i / 6;
|
||||||
|
|
||||||
|
if (i == 6) {
|
||||||
|
// 实线
|
||||||
|
canvas.drawLine(Offset(xStart, y), Offset(xEnd, y), axisPaint);
|
||||||
|
} else {
|
||||||
|
// 虚线
|
||||||
|
drawDashedLine(
|
||||||
|
canvas,
|
||||||
|
Offset(xStart, y),
|
||||||
|
Offset(xEnd, y),
|
||||||
|
axisPaint,
|
||||||
|
dashWidth: 8.rpx,
|
||||||
|
dashSpace: 6.rpx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Y轴文字
|
||||||
|
TextPainter tp = TextPainter(
|
||||||
|
text: TextSpan(
|
||||||
|
text: '${yMax - (yMax * i / 6).round()}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18.rpx, color: themeController.currentColor.sc4),
|
||||||
|
),
|
||||||
|
textDirection: ui.TextDirection.ltr,
|
||||||
|
);
|
||||||
|
tp.layout();
|
||||||
|
tp.paint(canvas, Offset(0, y - tp.height / 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. X轴线
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(xStart, chartHeight), Offset(xEnd, chartHeight), axisPaint);
|
||||||
|
|
||||||
|
// 4. 画X轴时间点对应的垂直虚线辅助线
|
||||||
|
int totalHours = maxTime.difference(minTime).inHours;
|
||||||
|
int startHour = minTime.hour;
|
||||||
|
|
||||||
|
for (int i = 1; i < totalHours; i++) {
|
||||||
|
double x = xStart + chartWidth * i / totalHours;
|
||||||
|
|
||||||
|
// 垂直虚线
|
||||||
|
drawDashedLine(
|
||||||
|
canvas,
|
||||||
|
Offset(x, 0),
|
||||||
|
Offset(x, chartHeight),
|
||||||
|
axisPaint,
|
||||||
|
dashWidth: 4.rpx,
|
||||||
|
dashSpace: 4.rpx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 画左侧完整时分 (HH:mm),往内缩 labelInset
|
||||||
|
String leftLabel = DateFormat('HH:mm').format(minTime);
|
||||||
|
TextPainter leftTp = TextPainter(
|
||||||
|
text: TextSpan(
|
||||||
|
text: leftLabel,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18.rpx,
|
||||||
|
color: themeController.currentColor.sc4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
textDirection: ui.TextDirection.ltr,
|
||||||
|
);
|
||||||
|
leftTp.layout();
|
||||||
|
leftTp.paint(canvas,
|
||||||
|
Offset(padding + labelInset - leftTp.width / 2, chartHeight + 8.rpx));
|
||||||
|
|
||||||
|
// 6. 画右侧完整时分 (HH:mm),往内缩 labelInset
|
||||||
|
String rightLabel = DateFormat('HH:mm').format(maxTime);
|
||||||
|
TextPainter rightTp = TextPainter(
|
||||||
|
text: TextSpan(
|
||||||
|
text: rightLabel,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18.rpx,
|
||||||
|
color: themeController.currentColor.sc4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
textDirection: ui.TextDirection.ltr,
|
||||||
|
);
|
||||||
|
rightTp.layout();
|
||||||
|
rightTp.paint(
|
||||||
|
canvas,
|
||||||
|
Offset(size.width - padding - labelInset - rightTp.width / 2,
|
||||||
|
chartHeight + 8.rpx));
|
||||||
|
|
||||||
|
// 7. 中间小时数字(23, 0, 1, 2, ...)
|
||||||
|
for (int i = 1; i < totalHours; i++) {
|
||||||
|
double x = xStart + chartWidth * i / totalHours;
|
||||||
|
|
||||||
|
int hourLabelNum = (startHour + i) % 24;
|
||||||
|
String hourLabel = '$hourLabelNum';
|
||||||
|
|
||||||
|
TextPainter tp = TextPainter(
|
||||||
|
text: TextSpan(
|
||||||
|
text: hourLabel,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18.rpx,
|
||||||
|
color: themeController.currentColor.sc4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
textDirection: ui.TextDirection.ltr,
|
||||||
|
);
|
||||||
|
tp.layout();
|
||||||
|
|
||||||
|
tp.paint(canvas, Offset(x - tp.width / 2, chartHeight + 8.rpx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
|
||||||
|
|
||||||
|
void drawDashedLine(
|
||||||
|
Canvas canvas,
|
||||||
|
Offset start,
|
||||||
|
Offset end,
|
||||||
|
Paint paint, {
|
||||||
|
required double dashWidth,
|
||||||
|
required double dashSpace,
|
||||||
|
}) {
|
||||||
|
final dx = end.dx - start.dx;
|
||||||
|
final dy = end.dy - start.dy;
|
||||||
|
final distance = sqrt(dx * dx + dy * dy);
|
||||||
|
final direction = Offset(dx / distance, dy / distance);
|
||||||
|
|
||||||
|
double drawn = 0;
|
||||||
|
while (drawn < distance) {
|
||||||
|
final from = start + direction * drawn;
|
||||||
|
final to = start + direction * (drawn + dashWidth).clamp(0, distance);
|
||||||
|
canvas.drawLine(from, to, paint);
|
||||||
|
drawn += dashWidth + dashSpace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
112
lib/pages/sleep_report/chart/StatusBarWithIndicator.dart
Normal file
112
lib/pages/sleep_report/chart/StatusBarWithIndicator.dart
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:vbvs_app/common/util/FitTool.dart';
|
||||||
|
|
||||||
|
class StatusBarWithIndicator extends StatelessWidget {
|
||||||
|
final int selectKey;
|
||||||
|
final List<Map<String, dynamic>> showLabel;
|
||||||
|
final IconData icon;
|
||||||
|
final double gap; // 每段之间的间距
|
||||||
|
|
||||||
|
const StatusBarWithIndicator({
|
||||||
|
super.key,
|
||||||
|
required this.selectKey,
|
||||||
|
required this.showLabel,
|
||||||
|
this.icon = Icons.favorite,
|
||||||
|
this.gap = 8.0, // 默认 8.rpx 间距
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return LayoutBuilder(builder: (context, constraints) {
|
||||||
|
final totalWidth = constraints.maxWidth;
|
||||||
|
final itemCount = showLabel.length;
|
||||||
|
|
||||||
|
// 每条线的宽度 = (总宽度 - 总间隔)/ 项数
|
||||||
|
final totalGap = (itemCount - 1) * gap.rpx;
|
||||||
|
final itemWidth = (totalWidth - totalGap) / itemCount;
|
||||||
|
|
||||||
|
// 找到选中项的 index
|
||||||
|
final selectedIndex = showLabel.indexWhere((e) => e['key'] == selectKey);
|
||||||
|
final iconLeft = selectedIndex >= 0
|
||||||
|
? selectedIndex * (itemWidth + gap.rpx) + itemWidth / 2
|
||||||
|
: 0.0;
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: Stack(
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
children: [
|
||||||
|
if (selectedIndex >= 0)
|
||||||
|
Positioned(
|
||||||
|
left: iconLeft,
|
||||||
|
top: -20.rpx,
|
||||||
|
child: Transform.translate(
|
||||||
|
offset: Offset(-22.5.rpx, 0), // 图片宽度 45.rpx,居中偏移
|
||||||
|
child: Container(
|
||||||
|
width: 45.rpx,
|
||||||
|
height: 76.rpx,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
image: DecorationImage(
|
||||||
|
image: AssetImage('assets/img/tip_arrow.gif'),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(top: 50.rpx),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// 条形段(带间距)
|
||||||
|
Row(
|
||||||
|
children: showLabel.asMap().entries.map((entry) {
|
||||||
|
int index = entry.key;
|
||||||
|
var item = entry.value;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
width: itemWidth,
|
||||||
|
height: 15.rpx,
|
||||||
|
margin: EdgeInsets.only(
|
||||||
|
left: index == 0 ? 0 : gap.rpx,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: item['color'],
|
||||||
|
borderRadius: BorderRadius.circular(0.rpx),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
SizedBox(height: 12.rpx),
|
||||||
|
// 名称文字
|
||||||
|
Row(
|
||||||
|
children: showLabel.asMap().entries.map((entry) {
|
||||||
|
int index = entry.key;
|
||||||
|
var item = entry.value;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
width: itemWidth,
|
||||||
|
margin: EdgeInsets.only(
|
||||||
|
left: index == 0 ? 0 : gap.rpx,
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text(
|
||||||
|
item['name'],
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 24.rpx,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
57
lib/pages/sleep_report/chart/VerticalBarList.dart
Normal file
57
lib/pages/sleep_report/chart/VerticalBarList.dart
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:vbvs_app/common/util/FitTool.dart';
|
||||||
|
|
||||||
|
class VerticalBarList extends StatelessWidget {
|
||||||
|
final List<Map<String, dynamic>> showLabel;
|
||||||
|
final double maxBarHeight;
|
||||||
|
final double barWidth;
|
||||||
|
|
||||||
|
const VerticalBarList({
|
||||||
|
super.key,
|
||||||
|
required this.showLabel,
|
||||||
|
this.maxBarHeight = 100.0, // 柱子的最大高度
|
||||||
|
this.barWidth = 20.0, // 每根柱子的宽度
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: showLabel.map((item) {
|
||||||
|
final percent = item['percent'] ?? 0;
|
||||||
|
final color = item['color'] ?? Colors.grey;
|
||||||
|
final name = item['name'] ?? '';
|
||||||
|
|
||||||
|
final barHeight = maxBarHeight * (percent / 100.0);
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 8.rpx),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
// 柱子
|
||||||
|
Container(
|
||||||
|
width: barWidth.rpx,
|
||||||
|
height: barHeight.rpx,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color,
|
||||||
|
borderRadius: BorderRadius.circular(4.rpx),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.rpx),
|
||||||
|
// 文字竖排
|
||||||
|
Text(
|
||||||
|
name,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 24.rpx,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
141
lib/pages/sleep_report/component/BreathPauseWidget.dart
Normal file
141
lib/pages/sleep_report/component/BreathPauseWidget.dart
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
import 'package:ef/ef.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/svg.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/component/tool/ClickableContainer.dart';
|
||||||
|
import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
|
||||||
|
import 'package:vbvs_app/pages/sleep_report/chart/DotBarChart.dart';
|
||||||
|
|
||||||
|
class BreathPauseWidget extends StatefulWidget {
|
||||||
|
BreathPauseWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BreathPauseWidget> createState() => _BreathPauseWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BreathPauseWidgetState extends State<BreathPauseWidget> {
|
||||||
|
@override
|
||||||
|
void setState(VoidCallback callback) {
|
||||||
|
super.setState(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// var showLabel = [
|
||||||
|
// {"time": 1744547251000, "times": 20},
|
||||||
|
// {"time": 1744550851000, "times": 50},
|
||||||
|
// {"time": 1744554451000, "times": 20},
|
||||||
|
// {"time": 1744558051000, "times": 30},
|
||||||
|
// {"time": 1744561651000, "times": 20},
|
||||||
|
// {"time": 1744565251000, "times": 10},
|
||||||
|
// {"time": 1744568851000, "times": 20},
|
||||||
|
// {"time": 1744572451000, "times": 20},
|
||||||
|
// {"time": 1744583251000, "times": 100},
|
||||||
|
// {"time": 1744586851000, "times": 20},
|
||||||
|
// ];
|
||||||
|
var showLabel = [
|
||||||
|
{"time": 1744547251000, "times": 25},
|
||||||
|
{"time": 1744550851000, "times": 27},
|
||||||
|
{"time": 1744554451000, "times": 40},
|
||||||
|
{"time": 1744558051000, "times": 28},
|
||||||
|
{"time": 1744561651000, "times": 15},
|
||||||
|
{"time": 1744565251000, "times": 48},
|
||||||
|
{"time": 1744568851000, "times": 25},
|
||||||
|
{"time": 1744572451000, "times": 17},
|
||||||
|
{"time": 1744583251000, "times": 35},
|
||||||
|
{"time": 1744586851000, "times": 40},
|
||||||
|
];
|
||||||
|
var threshold = 40;
|
||||||
|
var startTime = 1744641151000;
|
||||||
|
var endTime = 1744677151000;
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: themeController.currentColor.sc5,
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
AppConstants().normal_container_radius), // 你可以按需调整圆角半径
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(26.rpx, 29.rpx, 26.rpx, 0.rpx),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"呼吸暂停监测".tr,
|
||||||
|
style: TextStyle(
|
||||||
|
color: themeController.currentColor.sc3,
|
||||||
|
fontSize: AppConstants().title_text_fontSize),
|
||||||
|
),
|
||||||
|
ClickableContainer(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
highlightColor: Colors.white, // 或设置为你需要的水波纹颜色
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
14.rpx, 0.rpx, 14.rpx, 0), //
|
||||||
|
borderRadius: 0.rpx, // 圆形点击区域
|
||||||
|
onTap: () {
|
||||||
|
showTipDialog(
|
||||||
|
context,
|
||||||
|
Container(
|
||||||
|
child: Text(
|
||||||
|
"呼吸暂停监测介绍。",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 26.rpx,
|
||||||
|
color: themeController.currentColor.sc3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||||
|
width: 28.rpx,
|
||||||
|
height: 28.rpx,
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
'assets/img/icon/explain.svg',
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
color: themeController.currentColor.sc4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 32.rpx,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding:
|
||||||
|
EdgeInsetsDirectional.fromSTEB(0.rpx, 0.rpx, 0.rpx, 0.rpx),
|
||||||
|
child: DotBarChart(
|
||||||
|
showLabel: showLabel,
|
||||||
|
threshold: threshold,
|
||||||
|
startTime: startTime,
|
||||||
|
endTime: endTime,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 52.rpx,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
152
lib/pages/sleep_report/component/DiseasePercentsWidget.dart
Normal file
152
lib/pages/sleep_report/component/DiseasePercentsWidget.dart
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
import 'package:ef/ef.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/svg.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/component/tool/ClickableContainer.dart';
|
||||||
|
import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
|
||||||
|
import 'package:vbvs_app/pages/sleep_report/chart/HorizontalBarChart.dart';
|
||||||
|
|
||||||
|
class DiseasePercentsWidget extends StatefulWidget {
|
||||||
|
DiseasePercentsWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DiseasePercentsWidget> createState() => _DiseasePercentsWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DiseasePercentsWidgetState extends State<DiseasePercentsWidget> {
|
||||||
|
var showLabel = [
|
||||||
|
{
|
||||||
|
"key": 1,
|
||||||
|
"name": "心脏病",
|
||||||
|
"color": stringToColor("#00C1AA"),
|
||||||
|
"percent": 45,
|
||||||
|
"explain": "心脏病是指心脏的结构或功能异常,可能导致心脏无法有效地泵血。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": 2,
|
||||||
|
"name": "高血压",
|
||||||
|
"color": stringToColor("#00C1AA"),
|
||||||
|
"percent": 32,
|
||||||
|
"explain": "高血压是指血液在动脉中流动时对血管壁施加的压力过高。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": 3,
|
||||||
|
"name": "糖尿病",
|
||||||
|
"color": stringToColor("#00C1AA"),
|
||||||
|
"percent": 50,
|
||||||
|
"explain": "糖尿病是一种代谢性疾病,导致血糖水平异常升高。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": 4,
|
||||||
|
"name": "甲亢",
|
||||||
|
"color": stringToColor("#FF7159"),
|
||||||
|
"percent": 80,
|
||||||
|
"explain": "甲亢是指甲状腺分泌过多的甲状腺激素,导致新陈代谢加速。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": 5,
|
||||||
|
"name": "消化系统",
|
||||||
|
"color": stringToColor("#00C1AA"),
|
||||||
|
"percent": 12,
|
||||||
|
"explain": "消化系统是身体中处理食物的机构,是造成疾病和疾病症状的来源。",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": 6,
|
||||||
|
"name": "呼吸系统",
|
||||||
|
"color": stringToColor("#00C1AA"),
|
||||||
|
"percent": 62,
|
||||||
|
"explain": "呼吸系统是负责气体交换的器官系统,包括鼻、喉、气管和肺等。",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
@override
|
||||||
|
void setState(VoidCallback callback) {
|
||||||
|
super.setState(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: themeController.currentColor.sc5,
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
AppConstants().normal_container_radius), // 你可以按需调整圆角半径
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(26.rpx, 29.rpx, 26.rpx, 45.rpx),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"慢性病风险指数".tr,
|
||||||
|
style: TextStyle(
|
||||||
|
color: themeController.currentColor.sc3,
|
||||||
|
fontSize: AppConstants().title_text_fontSize),
|
||||||
|
),
|
||||||
|
ClickableContainer(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
highlightColor: Colors.white, // 或设置为你需要的水波纹颜色
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
14.rpx, 0.rpx, 14.rpx, 0), //
|
||||||
|
borderRadius: 0.rpx, // 圆形点击区域
|
||||||
|
onTap: () {
|
||||||
|
// 你的点击逻辑
|
||||||
|
showTipDialog(
|
||||||
|
context,
|
||||||
|
Container(
|
||||||
|
child: Text(
|
||||||
|
"慢性病风险指数介绍。",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 26.rpx,
|
||||||
|
color: themeController.currentColor.sc3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||||
|
width: 28.rpx,
|
||||||
|
height: 28.rpx,
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
'assets/img/icon/explain.svg',
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
color: themeController.currentColor.sc4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 34.rpx,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
child: HorizontalBarChart(
|
||||||
|
showLabel: showLabel,
|
||||||
|
showPercent: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
148
lib/pages/sleep_report/component/HeartHealthWidget.dart
Normal file
148
lib/pages/sleep_report/component/HeartHealthWidget.dart
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
import 'package:ef/ef.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:flutterflow_ui/flutterflow_ui.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/component/tool/ClickableContainer.dart';
|
||||||
|
import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
|
||||||
|
import 'package:vbvs_app/pages/sleep_report/chart/FatigueCircleIndicator.dart';
|
||||||
|
|
||||||
|
class HeartHealthWidget extends StatefulWidget {
|
||||||
|
HeartHealthWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HeartHealthWidget> createState() => _HeartHealthWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HeartHealthWidgetState extends State<HeartHealthWidget> {
|
||||||
|
@override
|
||||||
|
void setState(VoidCallback callback) {
|
||||||
|
super.setState(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var showLabel = [
|
||||||
|
{
|
||||||
|
"name": "焦虑抑郁",
|
||||||
|
"color": Color(0xFF4CAF50),
|
||||||
|
"percent": "7%",
|
||||||
|
"explain": "低风险"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "过度疲劳",
|
||||||
|
"color": stringToColor("#FF7159"),
|
||||||
|
"percent": "69%",
|
||||||
|
"explain": "高风险"
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: themeController.currentColor.sc5,
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
AppConstants().normal_container_radius), // 你可以按需调整圆角半径
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(26.rpx, 29.rpx, 26.rpx, 0.rpx),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"心理健康评估".tr,
|
||||||
|
style: TextStyle(
|
||||||
|
color: themeController.currentColor.sc3,
|
||||||
|
fontSize: AppConstants().title_text_fontSize),
|
||||||
|
),
|
||||||
|
ClickableContainer(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
highlightColor: Colors.white, // 或设置为你需要的水波纹颜色
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
14.rpx, 0.rpx, 14.rpx, 0), //
|
||||||
|
borderRadius: 0.rpx, // 圆形点击区域
|
||||||
|
onTap: () {
|
||||||
|
showTipDialog(
|
||||||
|
context,
|
||||||
|
Container(
|
||||||
|
child: Text(
|
||||||
|
"心理健康评估介绍。",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 26.rpx,
|
||||||
|
color: themeController.currentColor.sc3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||||
|
width: 28.rpx,
|
||||||
|
height: 28.rpx,
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
'assets/img/icon/explain.svg',
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
color: themeController.currentColor.sc4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 104.rpx,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding:
|
||||||
|
EdgeInsetsDirectional.fromSTEB(30.rpx, 0.rpx, 30.rpx, 0.rpx),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
FatigueCircleIndicator(
|
||||||
|
data: {
|
||||||
|
"name": "焦虑抑郁",
|
||||||
|
"color": stringToColor("#00C1AA"),
|
||||||
|
"percent": 7,
|
||||||
|
"explain": "低风险",
|
||||||
|
"bottomColor": Colors.grey,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
FatigueCircleIndicator(
|
||||||
|
data: {
|
||||||
|
"name": "过度疲劳",
|
||||||
|
"color": stringToColor("#FF7159"),
|
||||||
|
"percent": 69,
|
||||||
|
"explain": "高风险",
|
||||||
|
"bottomColor": Colors.grey,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
].divide(SizedBox(
|
||||||
|
width: 110.rpx,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 72.rpx,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
117
lib/pages/sleep_report/component/HeartPointWidget.dart
Normal file
117
lib/pages/sleep_report/component/HeartPointWidget.dart
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import 'package:ef/ef.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/svg.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/component/tool/ClickableContainer.dart';
|
||||||
|
import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
|
||||||
|
import 'package:vbvs_app/pages/sleep_report/chart/StatusBarWithIndicator.dart';
|
||||||
|
|
||||||
|
class HeartPointWidget extends StatefulWidget {
|
||||||
|
HeartPointWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HeartPointWidget> createState() => _HeartPointWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HeartPointWidgetState extends State<HeartPointWidget> {
|
||||||
|
@override
|
||||||
|
void setState(VoidCallback callback) {
|
||||||
|
super.setState(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: themeController.currentColor.sc5,
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
AppConstants().normal_container_radius), // 你可以按需调整圆角半径
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(26.rpx, 29.rpx, 26.rpx, 45.rpx),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"心率散点图".tr,
|
||||||
|
style: TextStyle(
|
||||||
|
color: themeController.currentColor.sc3,
|
||||||
|
fontSize: AppConstants().title_text_fontSize),
|
||||||
|
),
|
||||||
|
ClickableContainer(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
highlightColor: Colors.white, // 或设置为你需要的水波纹颜色
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
14.rpx, 0.rpx, 14.rpx, 0), //
|
||||||
|
borderRadius: 0.rpx, // 圆形点击区域
|
||||||
|
onTap: () {
|
||||||
|
showTipDialog(
|
||||||
|
context,
|
||||||
|
Container(
|
||||||
|
child: Text(
|
||||||
|
"心率散点图介绍。",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 26.rpx,
|
||||||
|
color: themeController.currentColor.sc3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||||
|
width: 28.rpx,
|
||||||
|
height: 28.rpx,
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
'assets/img/icon/explain.svg',
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
color: themeController.currentColor.sc4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 83.rpx,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding:
|
||||||
|
EdgeInsetsDirectional.fromSTEB(30.rpx, 0.rpx, 30.rpx, 0.rpx),
|
||||||
|
child: StatusBarWithIndicator(
|
||||||
|
selectKey: 2,
|
||||||
|
showLabel: [
|
||||||
|
{"key": 1, "name": "正常", "color": Color(0xFF4CAF50)},
|
||||||
|
{"key": 2, "name": "一般", "color": Color(0xFF8BC34A)},
|
||||||
|
{"key": 3, "name": "注意", "color": Color(0xFFFFC107)},
|
||||||
|
{"key": 4, "name": "警告", "color": Color(0xFFF44336)},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 56.rpx,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
117
lib/pages/sleep_report/component/HrvWidget.dart
Normal file
117
lib/pages/sleep_report/component/HrvWidget.dart
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import 'package:ef/ef.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/svg.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/component/tool/ClickableContainer.dart';
|
||||||
|
import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
|
||||||
|
import 'package:vbvs_app/pages/sleep_report/chart/StatusBarWithIndicator.dart';
|
||||||
|
|
||||||
|
class HrvWidget extends StatefulWidget {
|
||||||
|
HrvWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HrvWidget> createState() => _HrvWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HrvWidgetState extends State<HrvWidget> {
|
||||||
|
@override
|
||||||
|
void setState(VoidCallback callback) {
|
||||||
|
super.setState(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: themeController.currentColor.sc5,
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
AppConstants().normal_container_radius), // 你可以按需调整圆角半径
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(26.rpx, 29.rpx, 26.rpx, 45.rpx),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"心率变异性(HRV)".tr,
|
||||||
|
style: TextStyle(
|
||||||
|
color: themeController.currentColor.sc3,
|
||||||
|
fontSize: AppConstants().title_text_fontSize),
|
||||||
|
),
|
||||||
|
ClickableContainer(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
highlightColor: Colors.white, // 或设置为你需要的水波纹颜色
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
14.rpx, 0.rpx, 14.rpx, 0), //
|
||||||
|
borderRadius: 0.rpx, // 圆形点击区域
|
||||||
|
onTap: () {
|
||||||
|
showTipDialog(
|
||||||
|
context,
|
||||||
|
Container(
|
||||||
|
child: Text(
|
||||||
|
"心率变异性(HRV)介绍。",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 26.rpx,
|
||||||
|
color: themeController.currentColor.sc3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||||
|
width: 28.rpx,
|
||||||
|
height: 28.rpx,
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
'assets/img/icon/explain.svg',
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
color: themeController.currentColor.sc4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 83.rpx,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding:
|
||||||
|
EdgeInsetsDirectional.fromSTEB(30.rpx, 0.rpx, 30.rpx, 0.rpx),
|
||||||
|
child: StatusBarWithIndicator(
|
||||||
|
selectKey: 2,
|
||||||
|
showLabel: [
|
||||||
|
{"key": 1, "name": "正常", "color": Color(0xFF4CAF50)},
|
||||||
|
{"key": 2, "name": "一般", "color": Color(0xFF8BC34A)},
|
||||||
|
{"key": 3, "name": "注意", "color": Color(0xFFFFC107)},
|
||||||
|
{"key": 4, "name": "警告", "color": Color(0xFFF44336)},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 56.rpx,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
117
lib/pages/sleep_report/component/SkinPercentWidget.dart
Normal file
117
lib/pages/sleep_report/component/SkinPercentWidget.dart
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import 'package:ef/ef.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/svg.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/component/tool/ClickableContainer.dart';
|
||||||
|
import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
|
||||||
|
import 'package:vbvs_app/pages/sleep_report/chart/StatusBarWithIndicator.dart';
|
||||||
|
|
||||||
|
class SkinPercentWidget extends StatefulWidget {
|
||||||
|
SkinPercentWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SkinPercentWidget> createState() => _SkinPercentWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SkinPercentWidgetState extends State<SkinPercentWidget> {
|
||||||
|
@override
|
||||||
|
void setState(VoidCallback callback) {
|
||||||
|
super.setState(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: themeController.currentColor.sc5,
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
AppConstants().normal_container_radius), // 你可以按需调整圆角半径
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(26.rpx, 29.rpx, 26.rpx, 45.rpx),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"皮肤指数".tr,
|
||||||
|
style: TextStyle(
|
||||||
|
color: themeController.currentColor.sc3,
|
||||||
|
fontSize: AppConstants().title_text_fontSize),
|
||||||
|
),
|
||||||
|
ClickableContainer(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
highlightColor: Colors.white, // 或设置为你需要的水波纹颜色
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
14.rpx, 0.rpx, 14.rpx, 0), //
|
||||||
|
borderRadius: 0.rpx, // 圆形点击区域
|
||||||
|
onTap: () {
|
||||||
|
showTipDialog(
|
||||||
|
context,
|
||||||
|
Container(
|
||||||
|
child: Text(
|
||||||
|
"皮肤指数介绍。",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 26.rpx,
|
||||||
|
color: themeController.currentColor.sc3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||||
|
width: 28.rpx,
|
||||||
|
height: 28.rpx,
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
'assets/img/icon/explain.svg',
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
color: themeController.currentColor.sc4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 83.rpx,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding:
|
||||||
|
EdgeInsetsDirectional.fromSTEB(30.rpx, 0.rpx, 30.rpx, 0.rpx),
|
||||||
|
child: StatusBarWithIndicator(
|
||||||
|
selectKey: 2,
|
||||||
|
showLabel: [
|
||||||
|
{"key": 1, "name": "正常", "color": Color(0xFF4CAF50)},
|
||||||
|
{"key": 2, "name": "一般", "color": Color(0xFF8BC34A)},
|
||||||
|
{"key": 3, "name": "注意", "color": Color(0xFFFFC107)},
|
||||||
|
{"key": 4, "name": "警告", "color": Color(0xFFF44336)},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 56.rpx,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ import 'package:flutterflow_ui/flutterflow_ui.dart';
|
|||||||
import 'package:vbvs_app/common/color/appConstants.dart';
|
import 'package:vbvs_app/common/color/appConstants.dart';
|
||||||
import 'package:vbvs_app/common/util/FitTool.dart';
|
import 'package:vbvs_app/common/util/FitTool.dart';
|
||||||
import 'package:vbvs_app/common/util/MyUtils.dart';
|
import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||||
import 'package:vbvs_app/pages/sleep_report/component/SegmentedCirclePainter.dart';
|
import 'package:vbvs_app/pages/sleep_report/chart/SegmentedCirclePainter.dart';
|
||||||
|
|
||||||
class SleepScoreWidget extends StatefulWidget {
|
class SleepScoreWidget extends StatefulWidget {
|
||||||
const SleepScoreWidget({super.key});
|
const SleepScoreWidget({super.key});
|
||||||
|
|||||||
124
lib/pages/sleep_report/component/SnoreViewWidget.dart
Normal file
124
lib/pages/sleep_report/component/SnoreViewWidget.dart
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
import 'package:ef/ef.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/svg.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/component/tool/ClickableContainer.dart';
|
||||||
|
import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
|
||||||
|
import 'package:vbvs_app/pages/sleep_report/chart/LineChartByRange.dart';
|
||||||
|
|
||||||
|
class SnoreViewWidgetWidget extends StatefulWidget {
|
||||||
|
SnoreViewWidgetWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SnoreViewWidgetWidget> createState() => _SnoreViewWidgetWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SnoreViewWidgetWidgetState extends State<SnoreViewWidgetWidget> {
|
||||||
|
@override
|
||||||
|
void setState(VoidCallback callback) {
|
||||||
|
super.setState(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var showLabel = [
|
||||||
|
{"startTime": 1744644751000, "endTime": 1744648351000, "times": 25},
|
||||||
|
{"startTime": 1744650031000, "endTime": 1744653631000, "times": 27},
|
||||||
|
{"startTime": 1744655011000, "endTime": 1744662211000, "times": 60},
|
||||||
|
// {"startTime": 1744657291000, "endTime": 1744657411000, "times": 28},
|
||||||
|
// {"startTime": 1744661011000, "endTime": 1744661131000, "times": 15},
|
||||||
|
// {"startTime": 1744668331000, "endTime": 1744668511000, "times": 48},
|
||||||
|
// {"startTime": 1744673431000, "endTime": 1744673551000, "times": 25},
|
||||||
|
];
|
||||||
|
var startTime = 1744641151000;
|
||||||
|
var endTime = 1744677151000;
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: themeController.currentColor.sc5,
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
AppConstants().normal_container_radius), // 你可以按需调整圆角半径
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(26.rpx, 29.rpx, 26.rpx, 0.rpx),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"呼吸暂停监测".tr,
|
||||||
|
style: TextStyle(
|
||||||
|
color: themeController.currentColor.sc3,
|
||||||
|
fontSize: AppConstants().title_text_fontSize),
|
||||||
|
),
|
||||||
|
ClickableContainer(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
highlightColor: Colors.white, // 或设置为你需要的水波纹颜色
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
14.rpx, 0.rpx, 14.rpx, 0), //
|
||||||
|
borderRadius: 0.rpx, // 圆形点击区域
|
||||||
|
onTap: () {
|
||||||
|
showTipDialog(
|
||||||
|
context,
|
||||||
|
Container(
|
||||||
|
child: Text(
|
||||||
|
"呼吸暂停监测介绍。",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 26.rpx,
|
||||||
|
color: themeController.currentColor.sc3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||||
|
width: 28.rpx,
|
||||||
|
height: 28.rpx,
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
'assets/img/icon/explain.svg',
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
color: themeController.currentColor.sc4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 32.rpx,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding:
|
||||||
|
EdgeInsetsDirectional.fromSTEB(0.rpx, 0.rpx, 0.rpx, 0.rpx),
|
||||||
|
child: LineChartByRange(
|
||||||
|
showLabel: showLabel,
|
||||||
|
startTime: startTime,
|
||||||
|
endTime: endTime,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 52.rpx,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
119
lib/pages/sleep_report/component/ZiZhuShenJingPercentWidget.dart
Normal file
119
lib/pages/sleep_report/component/ZiZhuShenJingPercentWidget.dart
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
import 'package:ef/ef.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/svg.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/component/tool/ClickableContainer.dart';
|
||||||
|
import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
|
||||||
|
import 'package:vbvs_app/pages/sleep_report/chart/StatusBarWithIndicator.dart';
|
||||||
|
|
||||||
|
class ZiZhuShenJingPercentWidget extends StatefulWidget {
|
||||||
|
ZiZhuShenJingPercentWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ZiZhuShenJingPercentWidget> createState() =>
|
||||||
|
_ZiZhuShenJingPercentWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ZiZhuShenJingPercentWidgetState
|
||||||
|
extends State<ZiZhuShenJingPercentWidget> {
|
||||||
|
@override
|
||||||
|
void setState(VoidCallback callback) {
|
||||||
|
super.setState(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: themeController.currentColor.sc5,
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
AppConstants().normal_container_radius), // 你可以按需调整圆角半径
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(26.rpx, 29.rpx, 26.rpx, 45.rpx),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"自主神经平衡指数".tr,
|
||||||
|
style: TextStyle(
|
||||||
|
color: themeController.currentColor.sc3,
|
||||||
|
fontSize: AppConstants().title_text_fontSize),
|
||||||
|
),
|
||||||
|
ClickableContainer(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
highlightColor: Colors.white, // 或设置为你需要的水波纹颜色
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
14.rpx, 0.rpx, 14.rpx, 0), //
|
||||||
|
borderRadius: 0.rpx, // 圆形点击区域
|
||||||
|
onTap: () {
|
||||||
|
showTipDialog(
|
||||||
|
context,
|
||||||
|
Container(
|
||||||
|
child: Text(
|
||||||
|
"自主神经平衡指数监测介绍。",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 26.rpx,
|
||||||
|
color: themeController.currentColor.sc3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部
|
||||||
|
width: 28.rpx,
|
||||||
|
height: 28.rpx,
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
'assets/img/icon/explain.svg',
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
color: themeController.currentColor.sc4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 83.rpx,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding:
|
||||||
|
EdgeInsetsDirectional.fromSTEB(30.rpx, 0.rpx, 30.rpx, 0.rpx),
|
||||||
|
child: StatusBarWithIndicator(
|
||||||
|
selectKey: 3,
|
||||||
|
showLabel: [
|
||||||
|
{"key": 1, "name": "正常", "color": Color(0xFF4CAF50)},
|
||||||
|
{"key": 2, "name": "一般", "color": Color(0xFF8BC34A)},
|
||||||
|
{"key": 3, "name": "注意", "color": Color(0xFFFFC107)},
|
||||||
|
{"key": 4, "name": "警告", "color": Color(0xFFF44336)},
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 56.rpx,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,14 @@ import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
|||||||
import 'package:vbvs_app/controller/date/CalendarController.dart';
|
import 'package:vbvs_app/controller/date/CalendarController.dart';
|
||||||
import 'package:vbvs_app/controller/sleep/sleep_report_controller.dart';
|
import 'package:vbvs_app/controller/sleep/sleep_report_controller.dart';
|
||||||
import 'package:vbvs_app/pages/common/selectDialog.dart';
|
import 'package:vbvs_app/pages/common/selectDialog.dart';
|
||||||
|
import 'package:vbvs_app/pages/sleep_report/component/BreathPauseWidget.dart';
|
||||||
|
import 'package:vbvs_app/pages/sleep_report/component/DiseasePercentsWidget.dart';
|
||||||
|
import 'package:vbvs_app/pages/sleep_report/component/HeartHealthWidget.dart';
|
||||||
|
import 'package:vbvs_app/pages/sleep_report/component/HeartPointWidget.dart';
|
||||||
|
import 'package:vbvs_app/pages/sleep_report/component/SkinPercentWidget.dart';
|
||||||
import 'package:vbvs_app/pages/sleep_report/component/SleepScoreWidget.dart';
|
import 'package:vbvs_app/pages/sleep_report/component/SleepScoreWidget.dart';
|
||||||
|
import 'package:vbvs_app/pages/sleep_report/component/SnoreViewWidget.dart';
|
||||||
|
import 'package:vbvs_app/pages/sleep_report/component/ZiZhuShenJingPercentWidget.dart';
|
||||||
|
|
||||||
class NewSleepReportPage extends StatefulWidget {
|
class NewSleepReportPage extends StatefulWidget {
|
||||||
var date;
|
var date;
|
||||||
@@ -32,6 +39,7 @@ class _NewSleepReportPageState extends State<NewSleepReportPage> {
|
|||||||
DateTime.fromMillisecondsSinceEpoch(widget.date);
|
DateTime.fromMillisecondsSinceEpoch(widget.date);
|
||||||
sleepReportController.selectedDate.value =
|
sleepReportController.selectedDate.value =
|
||||||
DateTime.fromMillisecondsSinceEpoch(widget.date);
|
DateTime.fromMillisecondsSinceEpoch(widget.date);
|
||||||
|
sleepReportController.model.type = 1;
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,7 +317,7 @@ class _NewSleepReportPageState extends State<NewSleepReportPage> {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsetsDirectional.fromSTEB(
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
30.rpx, 57.rpx, 30.rpx, 57.rpx),
|
30.rpx, 32.rpx, 30.rpx, 32.rpx),
|
||||||
child: Obx(() {
|
child: Obx(() {
|
||||||
var date = sleepReportController.selectedDate;
|
var date = sleepReportController.selectedDate;
|
||||||
return getTimeWidget();
|
return getTimeWidget();
|
||||||
@@ -497,7 +505,65 @@ class _NewSleepReportPageState extends State<NewSleepReportPage> {
|
|||||||
child: SleepScoreWidget(),
|
child: SleepScoreWidget(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
Padding(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
30.rpx, 0.rpx, 30.rpx, 0),
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
child: HeartPointWidget(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
30.rpx, 0.rpx, 30.rpx, 0),
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
child: SnoreViewWidgetWidget(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
30.rpx, 0.rpx, 30.rpx, 0),
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
child: BreathPauseWidget(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
30.rpx, 0.rpx, 30.rpx, 0),
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
child: HeartHealthWidget(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
30.rpx, 0.rpx, 30.rpx, 0),
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
child: DiseasePercentsWidget(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
30.rpx, 0.rpx, 30.rpx, 0),
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
child: SkinPercentWidget(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
30.rpx, 0.rpx, 30.rpx, 0),
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
child: ZiZhuShenJingPercentWidget(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
].divide(SizedBox(
|
||||||
|
height: 25.rpx,
|
||||||
|
)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -508,124 +574,144 @@ class _NewSleepReportPageState extends State<NewSleepReportPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget getTimeWidget() {
|
Widget getTimeWidget() {
|
||||||
if (sleepReportController.model.type == 1) {
|
final selectedDate = sleepReportController.selectedDate.value!;
|
||||||
//日报
|
final type = sleepReportController.model.type;
|
||||||
return Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
String displayText = '';
|
||||||
children: [
|
if (type == 1) {
|
||||||
Container(
|
// 日报
|
||||||
width: 28.rpx,
|
displayText =
|
||||||
height: 28.rpx,
|
MyUtils.getFormatChineseTime(selectedDate.millisecondsSinceEpoch);
|
||||||
// width: double.infinity,
|
} else if (type == 2) {
|
||||||
decoration: BoxDecoration(),
|
// 周报
|
||||||
),
|
final startOfWeek =
|
||||||
Container(
|
selectedDate.subtract(Duration(days: selectedDate.weekday - 1));
|
||||||
child: Row(
|
final endOfWeek = startOfWeek.add(const Duration(days: 6));
|
||||||
children: [
|
displayText =
|
||||||
ClickableContainer(
|
'${MyUtils.getFormatChineseTime(startOfWeek.millisecondsSinceEpoch, showWeekday: false)}-${MyUtils.getFormatChineseTime(endOfWeek.millisecondsSinceEpoch, showWeekday: false)}';
|
||||||
backgroundColor: Colors.transparent,
|
} else if (type == 3) {
|
||||||
highlightColor: themeController.currentColor.sc3,
|
// 月报
|
||||||
padding: EdgeInsets.all(10.rpx), // 增加点击热区
|
displayText =
|
||||||
borderRadius: 8.rpx,
|
'${selectedDate.year}年${selectedDate.month.toString().padLeft(2, '0')}月';
|
||||||
onTap: () {
|
}
|
||||||
sleepReportController.selectedDate.value =
|
|
||||||
sleepReportController.selectedDate.value!
|
void onLeftArrowTap() {
|
||||||
.subtract(const Duration(days: 1));
|
if (type == 1) {
|
||||||
calendarController.selectedDate.value =
|
sleepReportController.selectedDate.value =
|
||||||
sleepReportController.selectedDate.value;
|
selectedDate.subtract(const Duration(days: 1));
|
||||||
sleepReportController.updateAll();
|
} else if (type == 2) {
|
||||||
calendarController.updateAll();
|
sleepReportController.selectedDate.value =
|
||||||
},
|
selectedDate.subtract(const Duration(days: 7));
|
||||||
child: SizedBox(
|
} else if (type == 3) {
|
||||||
width: 9.rpx,
|
sleepReportController.selectedDate.value = DateTime(
|
||||||
height: 14.rpx,
|
selectedDate.year,
|
||||||
child: SvgPicture.asset(
|
selectedDate.month - 1,
|
||||||
'assets/img/icon/arrow_left.svg',
|
selectedDate.day,
|
||||||
color: themeController.currentColor.sc3,
|
);
|
||||||
),
|
}
|
||||||
),
|
calendarController.selectedDate.value =
|
||||||
|
sleepReportController.selectedDate.value;
|
||||||
|
sleepReportController.updateAll();
|
||||||
|
calendarController.updateAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onRightArrowTap() {
|
||||||
|
if (type == 1) {
|
||||||
|
sleepReportController.selectedDate.value =
|
||||||
|
selectedDate.add(const Duration(days: 1));
|
||||||
|
} else if (type == 2) {
|
||||||
|
sleepReportController.selectedDate.value =
|
||||||
|
selectedDate.add(const Duration(days: 7));
|
||||||
|
} else if (type == 3) {
|
||||||
|
sleepReportController.selectedDate.value = DateTime(
|
||||||
|
selectedDate.year,
|
||||||
|
selectedDate.month + 1,
|
||||||
|
selectedDate.day,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
calendarController.selectedDate.value =
|
||||||
|
sleepReportController.selectedDate.value;
|
||||||
|
sleepReportController.updateAll();
|
||||||
|
calendarController.updateAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
SizedBox(width: 28.rpx, height: 28.rpx), // 占位
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
ClickableContainer(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
highlightColor: themeController.currentColor.sc3,
|
||||||
|
padding: EdgeInsets.all(10.rpx),
|
||||||
|
borderRadius: 8.rpx,
|
||||||
|
onTap: onLeftArrowTap,
|
||||||
|
child: SizedBox(
|
||||||
|
width: 9.rpx,
|
||||||
|
height: 14.rpx,
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
'assets/img/icon/arrow_left.svg',
|
||||||
|
color: themeController.currentColor.sc3,
|
||||||
),
|
),
|
||||||
Container(
|
|
||||||
child: Text(
|
|
||||||
MyUtils.getFormatChineseTime(sleepReportController
|
|
||||||
.selectedDate.value!.millisecondsSinceEpoch),
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: AppConstants().normal_text_fontSize,
|
|
||||||
color: themeController.currentColor.sc3,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ClickableContainer(
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
highlightColor: themeController.currentColor.sc3,
|
|
||||||
padding: EdgeInsets.all(10.rpx), // 增加点击热区
|
|
||||||
borderRadius: 8.rpx,
|
|
||||||
onTap: () {
|
|
||||||
sleepReportController.selectedDate.value =
|
|
||||||
sleepReportController.selectedDate.value!
|
|
||||||
.subtract(const Duration(days: -1));
|
|
||||||
calendarController.selectedDate.value =
|
|
||||||
sleepReportController.selectedDate.value;
|
|
||||||
sleepReportController.updateAll();
|
|
||||||
calendarController.updateAll();
|
|
||||||
},
|
|
||||||
child: SizedBox(
|
|
||||||
width: 9.rpx,
|
|
||||||
height: 14.rpx,
|
|
||||||
child: SvgPicture.asset(
|
|
||||||
'assets/img/icon/arrow_right.svg',
|
|
||||||
color: themeController.currentColor.sc3,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
].divide(SizedBox(
|
|
||||||
width: 26.rpx,
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ClickableContainer(
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
highlightColor: themeController.currentColor.sc3,
|
|
||||||
padding: EdgeInsetsDirectional.fromSTEB(
|
|
||||||
0.rpx,
|
|
||||||
0.rpx,
|
|
||||||
0.rpx,
|
|
||||||
0.rpx,
|
|
||||||
),
|
|
||||||
borderRadius: 0,
|
|
||||||
onTap: () {
|
|
||||||
showSleepCalendarBottomSheet(
|
|
||||||
timestamp: sleepReportController
|
|
||||||
.selectedDate.value!.millisecondsSinceEpoch,
|
|
||||||
context: context,
|
|
||||||
onDateSelected: (selectedDate) {
|
|
||||||
print("选中日期:");
|
|
||||||
print(selectedDate);
|
|
||||||
sleepReportController.selectedDate.value = selectedDate;
|
|
||||||
calendarController.selectedDate.value = selectedDate;
|
|
||||||
sleepReportController.updateAll();
|
|
||||||
calendarController.updateAll();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: SizedBox(
|
|
||||||
width: 28.rpx,
|
|
||||||
height: 28.rpx,
|
|
||||||
child: SvgPicture.asset(
|
|
||||||
'assets/img/icon/share.svg',
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
color: themeController.currentColor.sc3,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
Padding(
|
||||||
],
|
padding: EdgeInsets.symmetric(horizontal: 26.rpx),
|
||||||
);
|
child: Text(
|
||||||
}
|
displayText,
|
||||||
if (sleepReportController.model.type == 2) {
|
style: TextStyle(
|
||||||
//周报
|
fontSize: AppConstants().normal_text_fontSize,
|
||||||
}
|
color: themeController.currentColor.sc3,
|
||||||
if (sleepReportController.model.type == 3) {
|
),
|
||||||
//月报
|
),
|
||||||
}
|
),
|
||||||
return Container();
|
ClickableContainer(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
highlightColor: themeController.currentColor.sc3,
|
||||||
|
padding: EdgeInsets.all(10.rpx),
|
||||||
|
borderRadius: 8.rpx,
|
||||||
|
onTap: onRightArrowTap,
|
||||||
|
child: SizedBox(
|
||||||
|
width: 9.rpx,
|
||||||
|
height: 14.rpx,
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
'assets/img/icon/arrow_right.svg',
|
||||||
|
color: themeController.currentColor.sc3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
ClickableContainer(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
highlightColor: themeController.currentColor.sc3,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
borderRadius: 0,
|
||||||
|
onTap: () {
|
||||||
|
showSleepCalendarBottomSheet(
|
||||||
|
type: sleepReportController.model.type,
|
||||||
|
timestamp: selectedDate.millisecondsSinceEpoch,
|
||||||
|
context: context,
|
||||||
|
onDateSelected: (newDate) {
|
||||||
|
sleepReportController.selectedDate.value = newDate;
|
||||||
|
calendarController.selectedDate.value = newDate;
|
||||||
|
sleepReportController.updateAll();
|
||||||
|
calendarController.updateAll();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: SizedBox(
|
||||||
|
width: 28.rpx,
|
||||||
|
height: 28.rpx,
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
'assets/img/icon/share.svg',
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
color: themeController.currentColor.sc3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsetsDirectional.fromSTEB(
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
30.rpx, 25.rpx, 30.rpx, 0),
|
0.rpx, 25.rpx, 0.rpx, 0),
|
||||||
child: Container(
|
child: Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
|||||||
Reference in New Issue
Block a user