This commit is contained in:
wyf
2025-05-22 08:56:27 +08:00
parent 489e907e00
commit 8a418c9c98
39 changed files with 5964 additions and 144 deletions

1
assets/img/icon/ai.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1747822249470" class="icon" viewBox="0 0 1219 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3508" xmlns:xlink="http://www.w3.org/1999/xlink" width="238.0859375" height="200"><path d="M326.582238 206.473198a59.46507 59.46507 0 0 1 47.63963 24.32662 43.585194 43.585194 0 0 1 17.231356 18.582835v3.378697L642.828294 963.301367a46.288151 46.288151 0 0 1-33.786972 57.099983 47.9775 47.9775 0 0 1-59.80294-22.299402v-3.378697L484.367396 811.259994H161.026076l-64.533116 183.463257a47.301761 47.301761 0 0 1-61.830159 25.678099 46.288151 46.288151 0 0 1-33.786971-53.721286v-3.716566l249.347852-709.526409a43.923063 43.923063 0 0 1 30.408274-26.691707 58.451461 58.451461 0 0 1 45.950282-20.272184z m459.164947 202.721831c26.015968 0 47.301761 13.176919 49.328979 29.394666v549.714031c0 17.907095-21.961532 33.786972-49.328979 33.786972s-47.301761-12.839049-49.328979-29.394665V439.603304c0-17.569225 21.961532-32.097623 49.328979-32.097623z m-462.881514-58.451461L195.150918 713.953515h255.091637L322.865671 350.743568zM1038.135864 105.788022a15.204137 15.204137 0 0 1 10.136092 9.460352l38.517148 117.916532 120.957359 42.571585a14.866268 14.866268 0 0 1 8.784613 19.258574 14.528398 14.528398 0 0 1-9.798222 9.460352l-118.254401 33.786972-36.8278 115.889313a14.866268 14.866268 0 0 1-28.381056 0l-38.855018-118.254401-117.240792-37.16567a14.866268 14.866268 0 0 1-9.460352-18.920704 14.866268 14.866268 0 0 1 9.122482-9.122482l115.889314-38.855018 36.827799-115.889313a14.866268 14.866268 0 0 1 18.582834-9.798222zM772.570266 0.37267a9.798222 9.798222 0 0 1 6.419525 6.757395l21.961531 70.276901 72.64199 24.32662a10.136092 10.136092 0 0 1 6.419524 13.176919 10.473961 10.473961 0 0 1-6.757394 6.757394l-72.30412 21.285792L777.976181 214.919941a10.136092 10.136092 0 0 1-13.176919 6.419525 9.798222 9.798222 0 0 1-6.419524-6.757394l-21.961532-70.276902-70.276901-21.285792a10.473961 10.473961 0 0 1-7.095264-12.839049 10.136092 10.136092 0 0 1 6.757394-7.095264l70.614771-24.66449 23.313011-70.952641a10.136092 10.136092 0 0 1 12.501179-7.095264z" fill="#00C1AA" p-id="3509"></path></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1747827928589" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3806" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M532.733927 1013.26727l292.710814-421.991424a25.856122 25.856122 0 0 0-21.465459-40.491663H660.550982a25.856122 25.856122 0 0 1-27.319676-25.856122V25.856122A25.856122 25.856122 0 0 0 608.838738 0h-195.140543a25.856122 25.856122 0 0 0-24.392568 25.856122v499.071939a25.856122 25.856122 0 0 1-25.856122 25.856122H220.021206a25.856122 25.856122 0 0 0-21.465459 40.491663l292.710814 421.991424a26.343973 26.343973 0 0 0 41.467366 0z" fill="#F84B20" p-id="3807"></path></svg>

After

Width:  |  Height:  |  Size: 804 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1747827932650" class="icon" viewBox="0 0 3235 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3955" xmlns:xlink="http://www.w3.org/1999/xlink" width="631.8359375" height="200"><path d="M2468.93535421 234.66666663m0 40.508049l0 450.528542q0 40.508049-40.508049 40.508049l-1598.585923 0q-40.508049 0-40.508049-40.508049l0-450.528542q0-40.508049 40.508049-40.508049l1598.585923 0q40.508049 0 40.508049 40.508049Z" fill="#00C1AA" p-id="3956"></path></svg>

After

Width:  |  Height:  |  Size: 607 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1747827923553" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3657" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M491.356539 11.433663l-292.50833 421.211996a25.838236 25.838236 0 0 0 21.450611 40.463652h143.329082a25.838236 25.838236 0 0 1 25.838235 25.838236v498.726703a25.838236 25.838236 0 0 0 25.838236 26.32575h195.005554a25.838236 25.838236 0 0 0 24.375694-26.32575V498.947547a25.838236 25.838236 0 0 1 25.838236-25.838236h141.86654a25.838236 25.838236 0 0 0 21.450611-40.463652L534.257761 11.433663a25.838236 25.838236 0 0 0-42.901222 0z" fill="#00C1AA" p-id="3658"></path></svg>

After

Width:  |  Height:  |  Size: 806 B

1078
assets/img/服务协议.pdf Normal file

File diff suppressed because one or more lines are too long

1078
assets/img/隐私协议.pdf Normal file

File diff suppressed because one or more lines are too long

View File

@@ -360,6 +360,8 @@
"身高":"身高", "身高":"身高",
"身高输入提示":"请输入身高", "身高输入提示":"请输入身高",
"用户协议":"用户协议", "用户协议":"用户协议",
"隐私协议":"隐私协议" "隐私协议":"隐私协议",
"呼吸基准":"呼吸基准",
"呼吸基准介绍":"呼吸基准介绍。"
} }

View File

@@ -41,6 +41,9 @@ class DynamicReportDetailWidget extends StatelessWidget {
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
children: [ children: [
_buildHeader(context), _buildHeader(context),
SizedBox(
height: 33.rpx,
),
_buildSleepDateWidgets(), _buildSleepDateWidgets(),
SizedBox(height: 20.rpx), SizedBox(height: 20.rpx),
_buildSleepDataModuleWidgets(), _buildSleepDataModuleWidgets(),

View File

@@ -0,0 +1,40 @@
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:http/http.dart' as http;
class PrivacyPdfController extends GetxController {
var localPdfPath = Rx<String?>(null);
// 加载 PDF 文件
Future<void> loadPdf(int type, [String? url]) async {
final tempDir = await getTemporaryDirectory();
final filename = type == 1 ? 'service.pdf' : 'privacy.pdf';
final filePath = p.join(tempDir.path, filename);
final file = File(filePath);
try {
if (url == null || url.isEmpty) {
final byteData = await rootBundle
.load(type == 1 ? 'assets/img/服务协议.pdf' : 'assets/img/隐私协议.pdf');
await file.writeAsBytes(byteData.buffer.asUint8List());
} else {
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
await file.writeAsBytes(response.bodyBytes);
} else {
throw Exception('【PDF加载】类型$type:无法下载 PDF状态码 ${response.statusCode}');
}
}
} catch (e) {
final byteData = await rootBundle
.load(type == 1 ? 'assets/img/服务协议.pdf' : 'assets/img/隐私协议.pdf');
await file.writeAsBytes(byteData.buffer.asUint8List());
}
localPdfPath.value = filePath;
}
}

View File

@@ -0,0 +1,40 @@
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:http/http.dart' as http;
class UserPdfController extends GetxController {
var localPdfPath = Rx<String?>(null);
// 加载 PDF 文件
Future<void> loadPdf(int type, [String? url]) async {
final tempDir = await getTemporaryDirectory();
final filename = type == 1 ? 'service.pdf' : 'privacy.pdf';
final filePath = p.join(tempDir.path, filename);
final file = File(filePath);
try {
if (url == null || url.isEmpty) {
final byteData = await rootBundle
.load(type == 1 ? 'assets/img/服务协议.pdf' : 'assets/img/隐私协议.pdf');
await file.writeAsBytes(byteData.buffer.asUint8List());
} else {
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
await file.writeAsBytes(response.bodyBytes);
} else {
throw Exception('【PDF加载】类型$type:无法下载 PDF状态码 ${response.statusCode}');
}
}
} catch (e) {
final byteData = await rootBundle
.load(type == 1 ? 'assets/img/服务协议.pdf' : 'assets/img/隐私协议.pdf');
await file.writeAsBytes(byteData.buffer.asUint8List());
}
localPdfPath.value = filePath;
}
}

View File

@@ -0,0 +1,367 @@
// import 'dart:async';
// import 'package:EasyDartModule/EasyDartModule.dart';
// import 'package:ef/ef.dart';
// import 'package:geocoding/geocoding.dart';
// import 'package:geolocator/geolocator.dart';
// import 'package:json_annotation/json_annotation.dart';
// import 'package:vbvs_app/common/util/CommonVariables.dart';
// import 'package:vbvs_app/common/util/MyUtils.dart';
// import 'package:vbvs_app/controller/setting/language/language_controller.dart';
// import 'package:weather/weather.dart';
// part 'weather_controller.g.dart';
// @JsonSerializable()
// class WeatherModel {
// double? longitude; //经度
// double? latitude; //纬度
// String? weather_info = ''; //天气
// int? current_temperature; //温度
// int? min_temperature; //温度
// int? max_temperature; //温度
// String? wind_direction; //风向
// int? wind_speed; //风速等级
// String? cityName; // 新增城市名字段
// String? weatherIcon; // 新增天气图标字段
// String? weatherIconurl; // 新增天气图标字段
// WeatherModel();
// static WeatherModel fromJson(Map<String, dynamic> json) =>
// _$WeatherModelFromJson(json);
// Map<String, dynamic> toJson() => _$WeatherModelToJson(this);
// }
// class WeatherModelController extends GetControllerEx<WeatherModel> {
// LanguageController languageController = Get.find();
// WeatherModelController() {
// attr = GetModel(WeatherModel()).obs;
// weatherFactory = WeatherFactory(CommonVariables.weather_apiKey,
// language: Language.CHINESE_SIMPLIFIED);
// }
// Timer? _timer;
// late WeatherFactory weatherFactory;
// @override
// Future<void> onInit() async {
// super.onInit();
// await _getCurrentLocationAndWeather();
// // 启动定时器每5秒执行一次 getCurrentWeather 方法
// _timer = Timer.periodic(Duration(seconds: 5), (timer) {
// _getCurrentLocationAndWeather();
// });
// }
// @override
// void onClose() {
// // 取消定时器
// _timer?.cancel();
// super.onClose();
// }
// Future<void> _getCurrentLocationAndWeather() async {
// try {
// Position position = await _determinePosition();
// String? language = "zh_CN";
// if (languageController.selectLanguage != null) {
// language = languageController.selectLanguage.value!.language_code;
// }
// List<Placemark> placemarks = await placemarkFromCoordinates(
// position.latitude, position.longitude,
// localeIdentifier: "${language}");
// if (placemarks.isNotEmpty) {
// model.cityName = placemarks[0].locality ?? "未知数据".tr;
// }
// getCurrentWeather(position.latitude, position.longitude);
// } catch (e) {
// print(e);
// EasyDartModule.logger.error("获取位置和天气失败: $e");
// }
// }
// Future<Position> _determinePosition() async {
// bool serviceEnabled;
// LocationPermission permission;
// // 检查位置服务是否启用
// serviceEnabled = await Geolocator.isLocationServiceEnabled();
// if (!serviceEnabled) {
// // 位置服务未启用,返回默认位置
// return Future.error('Location services are disabled.');
// }
// permission = await Geolocator.checkPermission();
// if (permission == LocationPermission.denied) {
// permission = await Geolocator.requestPermission();
// if (permission == LocationPermission.denied) {
// // 权限被拒绝,返回默认位置
// return Future.error('Location permissions are denied');
// }
// }
// if (permission == LocationPermission.deniedForever) {
// // 权限被永久拒绝,返回默认位置
// return Future.error(
// 'Location permissions are permanently denied, we cannot request permissions.');
// }
// // 获取当前位置
// return await Geolocator.getCurrentPosition();
// }
// Future<Weather> getCurrentWeather(
// double latitude,
// double longitude,
// ) async {
// try {
// weatherFactory.language = Language.CHINESE_SIMPLIFIED;
// String? language = "zh_CN";
// if (languageController.selectLanguage != null) {
// language = languageController.selectLanguage.value!.language_code;
// }
// if (language == "zh_CN") {
// weatherFactory.language = Language.CHINESE_SIMPLIFIED;
// } else {
// weatherFactory.language = Language.ENGLISH;
// }
// Weather weather =
// await weatherFactory.currentWeatherByLocation(latitude, longitude);
// model.weather_info = weather.weatherDescription;
// model.min_temperature = weather.tempMin?.celsius?.toInt();
// model.max_temperature = weather.tempMax?.celsius?.toInt();
// model.current_temperature = weather.temperature?.celsius?.toInt();
// model.wind_speed = weather.windSpeed?.toInt();
// model.weatherIcon = weather.weatherIcon;
// if (model.weatherIcon != null) {
// model.weatherIconurl =
// "https://openweathermap.org/img/w/${model.weatherIcon}.png";
// }
// // model.wind_direction = getDirectionByDegree(weather.windDegree);
// updateAll();
// return weather;
// } catch (e) {
// print('Error: $e');
// rethrow;
// }
// }
// Future<List<Weather>> getWeatherForecast(
// double latitude, double longitude) async {
// try {
// return await weatherFactory.fiveDayForecastByLocation(
// latitude, longitude);
// } catch (e) {
// print('Error: $e');
// rethrow;
// }
// }
// String? getDirectionByDegree(double? windDegree) {
// if (windDegree == null) return null;
// if (windDegree >= 337.5 || windDegree < 22.5) {
// return '主页.天气.方向.北'.tr + '主页.天气.方向.单位'.tr;
// } else if (windDegree >= 22.5 && windDegree < 67.5) {
// return '主页.天气.方向.东北'.tr + '主页.天气.方向.单位'.tr;
// } else if (windDegree >= 67.5 && windDegree < 112.5) {
// return '主页.天气.方向.东'.tr + '主页.天气.方向.单位'.tr;
// } else if (windDegree >= 112.5 && windDegree < 157.5) {
// return '主页.天气.方向.东南'.tr + '主页.天气.方向.单位'.tr;
// } else if (windDegree >= 157.5 && windDegree < 202.5) {
// return '主页.天气.方向.南'.tr + '主页.天气.方向.单位'.tr;
// } else if (windDegree >= 202.5 && windDegree < 247.5) {
// return '主页.天气.方向.西南'.tr + '主页.天气.方向.单位'.tr;
// } else if (windDegree >= 247.5 && windDegree < 292.5) {
// return '主页.天气.方向.西'.tr + '主页.天气.方向.单位'.tr;
// } else if (windDegree >= 292.5 && windDegree < 337.5) {
// return '主页.天气.方向.西北'.tr + '主页.天气.方向.单位'.tr;
// } else {
// return null;
// }
// }
// }
import 'dart:async';
import 'package:EasyDartModule/EasyDartModule.dart';
import 'package:ef/ef.dart';
import 'package:geocoding/geocoding.dart';
import 'package:geolocator/geolocator.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:vbvs_app/common/util/CommonVariables.dart';
import 'package:vbvs_app/controller/setting/language/language_controller.dart';
import 'package:weather/weather.dart';
part 'weather_controller.g.dart';
@JsonSerializable()
class WeatherModel {
double? longitude; // 经度
double? latitude; // 纬度
String? weather_info = ''; // 天气
int? current_temperature; // 温度
int? min_temperature; // 最低温度
int? max_temperature; // 最高温度
String? wind_direction; // 风向
int? wind_speed; // 风速等级
String? cityName; // 城市名
String? weatherIcon; // 天气图标
String? weatherIconurl; // 天气图标url
WeatherModel();
static WeatherModel fromJson(Map<String, dynamic> json) =>
_$WeatherModelFromJson(json);
Map<String, dynamic> toJson() => _$WeatherModelToJson(this);
}
class WeatherModelController extends GetControllerEx<WeatherModel> {
LanguageController languageController = Get.find();
WeatherModelController() {
attr = GetModel(WeatherModel()).obs;
weatherFactory = WeatherFactory(CommonVariables.weather_apiKey,
language: Language.CHINESE_SIMPLIFIED);
}
Timer? _weatherTimer;
Timer? _locationTimer;
late WeatherFactory weatherFactory;
@override
Future<void> onInit() async {
super.onInit();
await _getCurrentLocation();
_weatherTimer = Timer.periodic(Duration(seconds: 5), (timer) {
_getCurrentWeather(); // 每 5 秒更新一次天气
});
_locationTimer = Timer.periodic(Duration(minutes: 10), (timer) {
_getCurrentLocation(); // 每 10 分钟更新一次位置
});
}
@override
void onClose() {
_weatherTimer?.cancel(); // 取消天气更新定时器
_locationTimer?.cancel(); // 取消位置更新定时器
super.onClose();
}
// 获取当前位置并存储到 model
Future<void> _getCurrentLocation() async {
try {
Position position = await _determinePosition();
String? language = "zh_CN";
if (languageController.selectLanguage != null) {
language = languageController.selectLanguage.value!.language_code;
}
List<Placemark> placemarks = await placemarkFromCoordinates(
position.latitude, position.longitude,
localeIdentifier: language);
if (placemarks.isNotEmpty) {
model.cityName = placemarks[0].locality ?? "未知数据".tr;
model.latitude = position.latitude;
model.longitude = position.longitude;
}
// 调用获取天气方法
_getCurrentWeather();
} catch (e) {
print(e);
EasyDartModule.logger.error("获取位置失败: $e");
}
}
// 获取当前位置
Future<Position> _determinePosition() async {
bool serviceEnabled;
LocationPermission permission;
serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
return Future.error('位置服务未启用');
}
permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
return Future.error('位置权限被拒绝');
}
}
if (permission == LocationPermission.deniedForever) {
return Future.error('位置权限被永久拒绝');
}
return await Geolocator.getCurrentPosition();
}
// 获取天气信息
Future<void> _getCurrentWeather() async {
if (model.latitude == null || model.longitude == null) {
return; // 如果位置数据没有获取到,则不更新天气
}
String? language = "zh_CN";
if (languageController.selectLanguage != null) {
language = languageController.selectLanguage.value!.language_code;
}
List<Placemark> placemarks = await placemarkFromCoordinates(
model.latitude!, model.longitude!,
localeIdentifier: language);
if (placemarks.isNotEmpty) {
model.cityName = placemarks[0].locality ?? "未知数据".tr;
}
try {
weatherFactory.language = Language.CHINESE_SIMPLIFIED;
String? language = "zh_CN";
if (languageController.selectLanguage != null) {
language = languageController.selectLanguage.value!.language_code;
}
if (language == "zh_CN") {
weatherFactory.language = Language.CHINESE_SIMPLIFIED;
} else {
weatherFactory.language = Language.ENGLISH;
}
Weather weather = await weatherFactory.currentWeatherByLocation(
model.latitude!, model.longitude!);
model.weather_info = weather.weatherDescription;
model.min_temperature = weather.tempMin?.celsius?.toInt();
model.max_temperature = weather.tempMax?.celsius?.toInt();
model.current_temperature = weather.temperature?.celsius?.toInt();
model.wind_speed = weather.windSpeed?.toInt();
model.weatherIcon = weather.weatherIcon;
if (model.weatherIcon != null) {
model.weatherIconurl =
"https://openweathermap.org/img/w/${model.weatherIcon}.png";
}
updateAll(); // 更新 UI
} catch (e) {
print('获取天气失败: $e');
}
}
// 获取 5 天天气预报
Future<List<Weather>> getWeatherForecast(
double latitude, double longitude) async {
try {
return await weatherFactory.fiveDayForecastByLocation(
latitude, longitude);
} catch (e) {
print('获取天气预报失败: $e');
rethrow;
}
}
}

View File

@@ -0,0 +1,31 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'weather_controller.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
WeatherModel _$WeatherModelFromJson(Map<String, dynamic> json) => WeatherModel()
..longitude = (json['longitude'] as num?)?.toDouble()
..latitude = (json['latitude'] as num?)?.toDouble()
..weather_info = json['weather_info'] as String?
..current_temperature = (json['current_temperature'] as num?)?.toInt()
..min_temperature = (json['min_temperature'] as num?)?.toInt()
..max_temperature = (json['max_temperature'] as num?)?.toInt()
..wind_direction = json['wind_direction'] as String?
..wind_speed = (json['wind_speed'] as num?)?.toInt()
..cityName = json['cityName'] as String?;
Map<String, dynamic> _$WeatherModelToJson(WeatherModel instance) =>
<String, dynamic>{
'longitude': instance.longitude,
'latitude': instance.latitude,
'weather_info': instance.weather_info,
'current_temperature': instance.current_temperature,
'min_temperature': instance.min_temperature,
'max_temperature': instance.max_temperature,
'wind_direction': instance.wind_direction,
'wind_speed': instance.wind_speed,
'cityName': instance.cityName,
};

View File

@@ -4,7 +4,6 @@ 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';
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/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/controller/device/blueteeth_bind_controller.dart'; import 'package:vbvs_app/controller/device/blueteeth_bind_controller.dart';

View File

@@ -304,15 +304,63 @@ class _HomePageState extends State<HomePage> {
), ),
), ),
Obx(() { Obx(() {
return Text( return Row(
"嘉兴 " + children: [
"${weatherModelController.model.weather_info ?? '未知数据'.tr}", Text(
"${weatherModelController.model.cityName??'未知数据'.tr}",
style: TextStyle( style: TextStyle(
color: themeController color: themeController
.currentColor.sc4, .currentColor.sc4,
fontSize: AppConstants() fontSize: AppConstants()
.normal_text_fontSize, .normal_text_fontSize,
), ),
),
Text(
"${(weatherModelController.model.current_temperature != null && weatherModelController.model.current_temperature! > 0) ? weatherModelController.model.current_temperature : '未知数据'.tr}" +
"°C",
style: TextStyle(
color: themeController
.currentColor.sc4,
fontSize: AppConstants()
.normal_text_fontSize,
),
),
Text(
"${(weatherModelController.model.weather_info?.isNotEmpty ?? false) ? weatherModelController.model.weather_info : '未知数据'.tr}",
style: TextStyle(
color: themeController
.currentColor.sc4,
fontSize: AppConstants()
.normal_text_fontSize,
),
),
if (weatherModelController
.model
.weatherIconurl !=
null &&
weatherModelController
.model
.weatherIconurl!
.isNotEmpty)
Container(
width: 35.rpx,
height: 26.rpx,
clipBehavior:
Clip.antiAlias,
decoration:
BoxDecoration(
shape: BoxShape
.circle),
child: Image.network(
weatherModelController
.model
.weatherIconurl!,
fit: BoxFit.cover,
),
),
].divide(SizedBox(
width: 20.rpx,
)),
); );
}), }),
], ],

View File

@@ -102,15 +102,162 @@ class _MessagePageState extends State<MessagePage> {
child: Column( child: Column(
children: [ children: [
SizedBox(height: 40.rpx), SizedBox(height: 40.rpx),
// Expanded(
// child: Stack(
// alignment: Alignment.bottomLeft,
// children: [
// Row(
// mainAxisAlignment: MainAxisAlignment.start,
// children: [
// Obx(() {
// return ClickableContainer(
// padding: EdgeInsets.all(0),
// backgroundColor: Colors.transparent,
// highlightColor:
// themeController.currentColor.sc21,
// borderRadius: 8.rpx,
// onTap: () => _onTabChanged(0),
// child: Container(
// width: 160.rpx,
// alignment: Alignment.center,
// child: Stack(
// alignment: Alignment.center,
// clipBehavior: Clip.none,
// children: [
// Text(
// '体征消息'.tr,
// style: FlutterFlowTheme.of(context)
// .bodyMedium
// .override(
// fontFamily: 'Inter',
// fontSize: AppConstants()
// .title_text_fontSize,
// color:
// messageController
// .model.type ==
// 2
// ? themeController
// .currentColor.sc3
// : themeController
// .currentColor.sc2,
// ),
// ),
// Obx(() {
// return messageController.model
// .body_message_read ==
// 1
// ? Positioned(
// top: -4,
// right: -14,
// child: Container(
// width: 8,
// height: 8,
// decoration:
// const BoxDecoration(
// color: Colors.red,
// shape: BoxShape.circle,
// ),
// ),
// )
// : const SizedBox.shrink();
// }),
// ],
// ),
// ),
// );
// }),
// Obx(() {
// return ClickableContainer(
// padding: EdgeInsets.all(0),
// backgroundColor: Colors.transparent,
// highlightColor:
// themeController.currentColor.sc21,
// borderRadius: 8.rpx,
// onTap: () => _onTabChanged(1),
// child: Container(
// width: 160.rpx,
// alignment: Alignment.center,
// child: Stack(
// alignment: Alignment.center,
// clipBehavior: Clip.none,
// children: [
// Text(
// '系统消息'.tr,
// style: FlutterFlowTheme.of(context)
// .bodyMedium
// .override(
// fontFamily: 'Inter',
// fontSize: AppConstants()
// .title_text_fontSize,
// color:
// messageController
// .model.type ==
// 1
// ? themeController
// .currentColor.sc3
// : themeController
// .currentColor.sc2,
// ),
// ),
// Obx(() {
// return messageController.model
// .system_message_read ==
// 1
// ? Positioned(
// top: -4,
// right: -14,
// child: Container(
// width: 8,
// height: 8,
// decoration:
// const BoxDecoration(
// color: Colors.red,
// shape: BoxShape.circle,
// ),
// ),
// )
// : const SizedBox.shrink();
// }),
// ],
// ),
// ),
// );
// }),
// ].divide(SizedBox(width: 10.rpx)),
// ),
// Obx(() {
// double lineWidth = 170.rpx;
// return AnimatedPositioned(
// duration: const Duration(milliseconds: 300),
// curve: Curves.easeInOut,
// bottom: 0,
// left: messageController.model.type == 1
// ? 0
// : 170.rpx,
// child: Container(
// width: lineWidth,
// height: 4.rpx,
// decoration: BoxDecoration(
// color: themeController.currentColor.sc2,
// borderRadius: BorderRadius.circular(2.rpx),
// ),
// ),
// );
// }),
// ],
// ),
// ),
Expanded( Expanded(
child: Stack( child: Stack(
alignment: Alignment.bottomLeft, alignment: Alignment.bottomLeft,
children: [ children: [
Row( Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
// 第一个容器,宽度占屏幕一半
Obx(() { Obx(() {
return ClickableContainer( return Expanded(
child: ClickableContainer(
padding: EdgeInsets.all(0), padding: EdgeInsets.all(0),
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
highlightColor: highlightColor:
@@ -118,7 +265,6 @@ class _MessagePageState extends State<MessagePage> {
borderRadius: 8.rpx, borderRadius: 8.rpx,
onTap: () => _onTabChanged(0), onTap: () => _onTabChanged(0),
child: Container( child: Container(
width: 160.rpx,
alignment: Alignment.center, alignment: Alignment.center,
child: Stack( child: Stack(
alignment: Alignment.center, alignment: Alignment.center,
@@ -126,20 +272,22 @@ class _MessagePageState extends State<MessagePage> {
children: [ children: [
Text( Text(
'体征消息'.tr, '体征消息'.tr,
style: FlutterFlowTheme.of(context) style:
FlutterFlowTheme.of(context)
.bodyMedium .bodyMedium
.override( .override(
fontFamily: 'Inter', fontFamily: 'Inter',
fontSize: AppConstants() fontSize: AppConstants()
.title_text_fontSize, .title_text_fontSize,
color: color: messageController
messageController
.model.type == .model.type ==
2 2
? themeController ? themeController
.currentColor.sc3 .currentColor
.sc3
: themeController : themeController
.currentColor.sc2, .currentColor
.sc2,
), ),
), ),
Obx(() { Obx(() {
@@ -155,7 +303,8 @@ class _MessagePageState extends State<MessagePage> {
decoration: decoration:
const BoxDecoration( const BoxDecoration(
color: Colors.red, color: Colors.red,
shape: BoxShape.circle, shape:
BoxShape.circle,
), ),
), ),
) )
@@ -164,10 +313,14 @@ class _MessagePageState extends State<MessagePage> {
], ],
), ),
), ),
),
); );
}), }),
SizedBox(width: 10.rpx),
// 第二个容器,宽度占屏幕一半
Obx(() { Obx(() {
return ClickableContainer( return Expanded(
child: ClickableContainer(
padding: EdgeInsets.all(0), padding: EdgeInsets.all(0),
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
highlightColor: highlightColor:
@@ -175,7 +328,6 @@ class _MessagePageState extends State<MessagePage> {
borderRadius: 8.rpx, borderRadius: 8.rpx,
onTap: () => _onTabChanged(1), onTap: () => _onTabChanged(1),
child: Container( child: Container(
width: 160.rpx,
alignment: Alignment.center, alignment: Alignment.center,
child: Stack( child: Stack(
alignment: Alignment.center, alignment: Alignment.center,
@@ -183,20 +335,22 @@ class _MessagePageState extends State<MessagePage> {
children: [ children: [
Text( Text(
'系统消息'.tr, '系统消息'.tr,
style: FlutterFlowTheme.of(context) style:
FlutterFlowTheme.of(context)
.bodyMedium .bodyMedium
.override( .override(
fontFamily: 'Inter', fontFamily: 'Inter',
fontSize: AppConstants() fontSize: AppConstants()
.title_text_fontSize, .title_text_fontSize,
color: color: messageController
messageController
.model.type == .model.type ==
1 1
? themeController ? themeController
.currentColor.sc3 .currentColor
.sc3
: themeController : themeController
.currentColor.sc2, .currentColor
.sc2,
), ),
), ),
Obx(() { Obx(() {
@@ -212,7 +366,8 @@ class _MessagePageState extends State<MessagePage> {
decoration: decoration:
const BoxDecoration( const BoxDecoration(
color: Colors.red, color: Colors.red,
shape: BoxShape.circle, shape:
BoxShape.circle,
), ),
), ),
) )
@@ -221,19 +376,24 @@ class _MessagePageState extends State<MessagePage> {
], ],
), ),
), ),
),
); );
}), }),
].divide(SizedBox(width: 10.rpx)), ],
), ),
// 动画线
Obx(() { Obx(() {
double lineWidth = 170.rpx; double lineWidth =
MediaQuery.sizeOf(context).width * 0.5 -
20.rpx; // 每个容器占宽度的一半
return AnimatedPositioned( return AnimatedPositioned(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut, curve: Curves.easeInOut,
bottom: 0, bottom: 0,
left: messageController.model.type == 1 left: messageController.model.type == 1
? 0 ? 0
: 170.rpx, : MediaQuery.sizeOf(context).width * 0.5 +
10.rpx, // 动态设置左侧位置
child: Container( child: Container(
width: lineWidth, width: lineWidth,
height: 4.rpx, height: 4.rpx,

View File

@@ -436,6 +436,7 @@ class _ApplyRepairPageState extends State<ApplyRepairPage> {
hintStyle: TextStyle( hintStyle: TextStyle(
letterSpacing: 0.0, letterSpacing: 0.0,
fontSize: AppConstants().normal_text_fontSize, fontSize: AppConstants().normal_text_fontSize,
color: themeController.currentColor.sc4,
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderSide: BorderSide( borderSide: BorderSide(
@@ -477,6 +478,7 @@ class _ApplyRepairPageState extends State<ApplyRepairPage> {
cursorColor: themeController.currentColor.sc3, cursorColor: themeController.currentColor.sc3,
onChanged: onChanged, onChanged: onChanged,
), ),
), ),
), ),
].divide(SizedBox(width: 24.rpx)), ].divide(SizedBox(width: 24.rpx)),

View File

@@ -0,0 +1,119 @@
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';
class AdviceComponnetWidget extends StatefulWidget {
final String title; // 建议标题
final String description; // 建议说明
const AdviceComponnetWidget({
super.key,
required this.title,
required this.description,
});
@override
State<AdviceComponnetWidget> createState() => _AdviceComponnetWidgetState();
}
class _AdviceComponnetWidgetState extends State<AdviceComponnetWidget> {
@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(),
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
// 显示标题
Container(
width: double.infinity,
decoration: BoxDecoration(
color: stringToColor("#313541"),
borderRadius: BorderRadius.circular(20.rpx),
),
child: Padding(
padding: EdgeInsetsDirectional.fromSTEB(
28.rpx, 30.rpx, 28.rpx, 30.rpx),
child: Container(
width: double.infinity,
decoration: BoxDecoration(),
child: Text(
widget.title, // 使用传入的标题
style: TextStyle(
color: themeController.currentColor.sc3,
),
),
),
),
),
// 显示描述
Container(
width: double.infinity,
decoration: BoxDecoration(),
child: Padding(
padding: EdgeInsetsDirectional.fromSTEB(
28.rpx, 30.rpx, 28.rpx, 30.rpx),
child: Container(
width: double.infinity,
decoration: BoxDecoration(),
child: RichText(
text: TextSpan(
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize: 26.rpx, // 设置文字大小
height: 1.3, // 设置行高,控制文字上下间距
),
children: [
WidgetSpan(
alignment: PlaceholderAlignment.middle, // 图标和文字垂直居中
child: Padding(
padding: EdgeInsets.only(
bottom: 4.rpx, // 调整底部间距
right: 10.rpx, // 适当调整图标右边距
top: 4.rpx, // 增加顶部间距
),
child: SvgPicture.asset(
'assets/img/icon/ai.svg', // 替换为你的 SVG 文件路径
width: 37.rpx, // 设置适中的 SVG 图标大小
height: 31.rpx, // 使图标和文字大小一致
color:
themeController.currentColor.sc2, // 设置 SVG 颜色
),
),
),
TextSpan(
text: widget.description, // 使用传入的描述
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize: AppConstants().normal_text_fontSize, // 文字大小
),
),
],
),
),
),
),
)
],
),
);
}
}

View File

@@ -0,0 +1,108 @@
import 'package:flutter/material.dart';
import 'package:vbvs_app/common/util/FitTool.dart';
class DataShowWidget extends StatefulWidget {
final Widget widget1; // 第一个传入的 widget
final Widget widget2; // 第二个传入的 widget
final Widget widget3; // 第三个传入的 widget
final Widget widget4; // 第四个传入的 widget
final MainAxisAlignment alignment; // 控制 Row 的对齐方式
const DataShowWidget({
super.key,
required this.widget1,
required this.widget2,
required this.widget3,
required this.widget4,
this.alignment = MainAxisAlignment.start, // 默认左对齐
});
@override
State<DataShowWidget> createState() => _DataShowWidgetState();
}
class _DataShowWidgetState extends State<DataShowWidget> {
@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,
height: 66.rpx,
decoration: BoxDecoration(),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal, // 设置横向滚动
child: Row(
mainAxisAlignment: widget.alignment, // 根据传入的 alignment 控制对齐方式
children: [
// 放入传入的 widget1
Container(
width: MediaQuery.sizeOf(context).width * 0.4, // 固定宽度
decoration: BoxDecoration(),
child: Align(
alignment: widget.alignment == MainAxisAlignment.start
? Alignment.centerLeft
: widget.alignment == MainAxisAlignment.center
? Alignment.center
: Alignment.centerRight, // 根据传入的 alignment 设置对齐
child: widget.widget1, // 显示传入的 widget1
),
),
// 放入传入的 widget2
Container(
width: MediaQuery.sizeOf(context).width * 0.15, // 固定宽度
decoration: BoxDecoration(),
child: Align(
alignment: widget.alignment == MainAxisAlignment.start
? Alignment.centerLeft
: widget.alignment == MainAxisAlignment.center
? Alignment.center
: Alignment.centerRight, // 同样设置对齐
child: widget.widget2, // 显示传入的 widget2
),
),
// 放入传入的 widget3
Container(
width: MediaQuery.sizeOf(context).width * 0.2, // 固定宽度
decoration: BoxDecoration(),
child: Align(
alignment: widget.alignment == MainAxisAlignment.start
? Alignment.centerLeft
: widget.alignment == MainAxisAlignment.center
? Alignment.center
: Alignment.centerRight, // 同样设置对齐
child: widget.widget3, // 显示传入的 widget3
),
),
// 放入传入的 widget4
Container(
width: MediaQuery.sizeOf(context).width * 0.15, // 固定宽度
decoration: BoxDecoration(),
child: Align(
alignment: widget.alignment == MainAxisAlignment.start
? Alignment.centerLeft
: widget.alignment == MainAxisAlignment.center
? Alignment.center
: Alignment.centerRight, // 同样设置对齐
child: widget.widget4, // 显示传入的 widget4
),
),
],
),
),
);
}
}

View File

@@ -204,7 +204,7 @@ class SingleBarPainter extends CustomPainter {
TextStyle(color: themeController.currentColor.sc3, fontSize: 26.rpx); TextStyle(color: themeController.currentColor.sc3, fontSize: 26.rpx);
final textPainter = TextPainter(textDirection: TextDirection.ltr); final textPainter = TextPainter(textDirection: TextDirection.ltr);
textPainter.text = textPainter.text =
TextSpan(text: '${value.toStringAsFixed(0)}%', style: textStyle); TextSpan(text: '${value.toStringAsFixed(0)}', style: textStyle);
textPainter.layout(); textPainter.layout();
canvas.save(); canvas.save();
canvas.clipRect(Rect.fromLTWH( canvas.clipRect(Rect.fromLTWH(

View File

@@ -0,0 +1,158 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'dart:ui' as ui;
class LineChart extends StatelessWidget {
final int startTime;
final int endTime;
final double minValue;
final double maxValue;
final List<Map<String, dynamic>> dataPoints;
LineChart({
required this.startTime,
required this.endTime,
required this.minValue,
required this.maxValue,
required this.dataPoints,
});
@override
Widget build(BuildContext context) {
return CustomPaint(
size: Size(double.infinity, 300),
painter: LineChartPainter(
startTime: startTime,
endTime: endTime,
minValue: minValue,
maxValue: maxValue,
dataPoints: dataPoints,
),
);
}
}
class LineChartPainter extends CustomPainter {
final int startTime;
final int endTime;
final double minValue;
final double maxValue;
final List<Map<String, dynamic>> dataPoints;
LineChartPainter({
required this.startTime,
required this.endTime,
required this.minValue,
required this.maxValue,
required this.dataPoints,
});
@override
void paint(Canvas canvas, Size size) {
Paint paint = Paint()..style = PaintingStyle.stroke;
double chartWidth = size.width;
double chartHeight = size.height;
// 时间轴刻度设置
DateFormat timeFormatStartEnd = DateFormat('HH:mm');
DateFormat timeFormatMiddle = DateFormat('h');
// 绘制Y轴刻度
double yAxisHeight = chartHeight - 40; // 留一些空间给X轴
double yAxisStep = yAxisHeight / 4;
paint.color = Colors.grey;
paint.strokeWidth = 1;
canvas.drawLine(Offset(30, 0), Offset(30, chartHeight), paint); // Y轴
// 绘制Y轴的刻度线
paint.color = Colors.grey;
for (int i = 0; i < 5; i++) {
double y = i * yAxisStep;
if (i == 0) {
paint.color = Colors.grey; // 0线
canvas.drawLine(Offset(25, y), Offset(35, y), paint);
} else if (i == 1) {
paint.color = Colors.red; // 最小值线
paint.style = PaintingStyle.stroke;
paint.strokeWidth = 1;
canvas.drawLine(Offset(25, y), Offset(35, y), paint);
} else if (i == 2) {
paint.color = Colors.grey; // 最大值与最小值中间线
paint.style = PaintingStyle.stroke;
paint.strokeWidth = 1;
_drawDashedLine(canvas, paint, 25, y, 35, y); // Custom dashed line
} else {
paint.color = Colors.red; // 最大值线
canvas.drawLine(Offset(25, y), Offset(35, y), paint);
}
}
// 绘制X轴时间刻度
DateTime startDate = DateTime.fromMillisecondsSinceEpoch(startTime);
DateTime endDate = DateTime.fromMillisecondsSinceEpoch(endTime);
double xAxisStep = (chartWidth - 60) /
(endDate.millisecondsSinceEpoch - startDate.millisecondsSinceEpoch);
for (DateTime date = startDate;
date.isBefore(endDate);
date = date.add(Duration(hours: 1))) {
String timeLabel = (date == startDate || date == endDate)
? timeFormatStartEnd.format(date)
: timeFormatMiddle.format(date);
paint.color = Colors.black;
// Draw text using TextPainter
_drawText(
canvas, timeLabel, Offset(30, yAxisHeight)); // Position dynamically
}
// 绘制折线图数据点
Path path = Path();
for (var i = 0; i < dataPoints.length; i++) {
var point = dataPoints[i];
DateTime pointTime = DateTime.fromMillisecondsSinceEpoch(point['time']);
double x = (pointTime.millisecondsSinceEpoch -
startDate.millisecondsSinceEpoch) *
xAxisStep +
30;
double y = chartHeight -
(point['value'] - minValue) * yAxisHeight / (maxValue - minValue);
path.lineTo(x, y);
}
paint.color = Colors.green; // Line color based on range
paint.strokeWidth = 2;
canvas.drawPath(path, paint);
}
// Custom method to draw dashed line
void _drawDashedLine(Canvas canvas, Paint paint, double startX, double startY,
double endX, double endY) {
double dashWidth = 5;
double dashSpace = 3;
double distance = (endX - startX).abs();
double dashCount = (distance / (dashWidth + dashSpace)).floorToDouble();
for (int i = 0; i < dashCount; i++) {
double startXDash = startX + (i * (dashWidth + dashSpace));
double endXDash = startXDash + dashWidth;
canvas.drawLine(
Offset(startXDash, startY), Offset(endXDash, endY), paint);
}
}
// Custom method to draw text
void _drawText(Canvas canvas, String text, Offset offset) {
TextPainter textPainter = TextPainter(
text: TextSpan(
text: text, style: TextStyle(color: Colors.black, fontSize: 12)),
textDirection: ui.TextDirection.ltr,
)..layout();
textPainter.paint(canvas, offset);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}

View File

@@ -154,19 +154,19 @@ class _LineChartByRangePainter extends CustomPainter {
int totalHours = maxTime.difference(minTime).inHours; int totalHours = maxTime.difference(minTime).inHours;
int startHour = minTime.hour; int startHour = minTime.hour;
for (int i = 1; i < totalHours; i++) { // for (int i = 1; i < totalHours; i++) {
double x = xStart + chartWidth * i / totalHours; // double x = xStart + chartWidth * i / totalHours;
// 垂直虚线 // // 垂直虚线
drawDashedLine( // drawDashedLine(
canvas, // canvas,
Offset(x, 0), // Offset(x, 0),
Offset(x, chartHeight), // Offset(x, chartHeight),
axisPaint, // axisPaint,
dashWidth: 4.rpx, // dashWidth: 4.rpx,
dashSpace: 4.rpx, // dashSpace: 4.rpx,
); // );
} // }
// 5. 画左侧完整时分 (HH:mm),往内缩 labelInset // 5. 画左侧完整时分 (HH:mm),往内缩 labelInset
String leftLabel = DateFormat('HH:mm').format(minTime); String leftLabel = DateFormat('HH:mm').format(minTime);

View File

@@ -0,0 +1,109 @@
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
import 'dart:math';
class RadarChart extends StatelessWidget {
final List<List<double>> data; // 存储多个数据集
final List<String> labels; // 每个角的标签
final double maxValue; // 数据的最大值,用来统一尺度
const RadarChart({
Key? key,
required this.data,
required this.labels,
this.maxValue = 100,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return CustomPaint(
size: Size(300, 300), // 图表的大小
painter: RadarChartPainter(
data: data,
labels: labels,
maxValue: maxValue,
),
);
}
}
class RadarChartPainter extends CustomPainter {
final List<List<double>> data;
final List<String> labels;
final double maxValue;
RadarChartPainter({
required this.data,
required this.labels,
required this.maxValue,
});
@override
void paint(Canvas canvas, Size size) {
Paint paintLine = Paint()
..color = Colors.blue
..style = PaintingStyle.stroke
..strokeWidth = 2;
Paint axisPaint = Paint()
..color = Colors.grey.withOpacity(0.5)
..strokeWidth = 1;
double centerX = size.width / 2;
double centerY = size.height / 2;
double radius = size.width / 2;
int numOfPoints = labels.length;
// 绘制雷达图的轴线
for (int i = 0; i < numOfPoints; i++) {
double angle = (2 * pi / numOfPoints) * i;
double x = centerX + radius * cos(angle);
double y = centerY + radius * sin(angle);
// 画轴线
canvas.drawLine(Offset(centerX, centerY), Offset(x, y), axisPaint);
// 绘制标签
TextPainter tp = TextPainter(
text: TextSpan(
text: labels[i],
style: TextStyle(color: Colors.black, fontSize: 12),
),
textDirection: ui.TextDirection.ltr,
);
tp.layout();
tp.paint(canvas, Offset(x + 8, y - 8)); // 设置标签位置
}
// 绘制多个数据集
for (int i = 0; i < data.length; i++) {
Paint fillPaint = Paint()
..color = Colors.primaries[i % Colors.primaries.length].withOpacity(0.3)
..style = PaintingStyle.fill;
Paint linePaint = Paint()
..color = Colors.primaries[i % Colors.primaries.length]
..style = PaintingStyle.stroke
..strokeWidth = 2;
List<Offset> points = [];
for (int j = 0; j < numOfPoints; j++) {
double angle = (2 * pi / numOfPoints) * j;
double pointRadius = (data[i][j] / maxValue) * radius;
double x = centerX + pointRadius * cos(angle);
double y = centerY + pointRadius * sin(angle);
points.add(Offset(x, y));
}
// 画出数据连接线
Path path = Path()..addPolygon(points, true);
canvas.drawPath(path, linePaint);
canvas.drawPath(path, fillPaint);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}

View File

@@ -0,0 +1,118 @@
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:vbvs_app/common/util/FitTool.dart';
import 'package:vbvs_app/common/util/MyUtils.dart';
class ScatterPlotChart extends StatelessWidget {
final List<ScatterSpot> points;
final int xMax;
final int yMax;
final Color pointColor;
final int divisions;
ScatterPlotChart({
required this.points,
required this.xMax,
required this.yMax,
required this.pointColor,
required this.divisions,
});
@override
Widget build(BuildContext context) {
// 计算向上取整后的最大值
double xMaxCeil = (xMax / 100).ceil() * 100.0;
double yMaxCeil = (yMax / 100).ceil() * 100.0;
return SizedBox(
child: ScatterChart(
ScatterChartData(
backgroundColor: Colors.transparent,
gridData: FlGridData(
show: true,
horizontalInterval: yMaxCeil / divisions,
verticalInterval: xMaxCeil / divisions,
getDrawingHorizontalLine: (value) {
return FlLine(
color: themeController.currentColor.sc4, // 设置网格线颜色
strokeWidth: 0.5,
);
},
getDrawingVerticalLine: (value) {
return FlLine(
color: themeController.currentColor.sc4, // 设置网格线颜色
strokeWidth: 0.5,
);
},
),
titlesData: FlTitlesData(
show: true,
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 60.rpx, // 给 y 轴标签更多空间
getTitlesWidget: (double value, TitleMeta meta) {
return Padding(
padding: EdgeInsets.only(right: 14.rpx), // 右侧加间距
child: Text(
value.toStringAsFixed(0),
style: TextStyle(
fontSize: 18.rpx,
color: themeController.currentColor.sc4,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.right, // 右对齐
),
);
},
),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (double value, TitleMeta meta) {
return Text(
value.toStringAsFixed(0),
style: TextStyle(
fontSize: 18.rpx,
color: themeController.currentColor.sc4,
),
);
},
),
),
rightTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
topTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
),
borderData: FlBorderData(
show: true,
border: Border.all(
color: themeController.currentColor.sc4,
width: 0.5,
),
),
// 修改散点的大小和颜色
scatterSpots: points.map((point) {
return ScatterSpot(
point.x, // x 坐标
point.y, // y 坐标
dotPainter: FlDotCirclePainter(
radius: 3.rpx, // 自定义大小
color: pointColor, // 自定义颜色
),
);
}).toList(),
minX: 0,
maxX: xMaxCeil,
minY: 0,
maxY: yMaxCeil,
),
),
);
}
}

View File

@@ -89,7 +89,7 @@ class SegmentedCircleWithCenterWidget extends StatelessWidget {
Positioned( Positioned(
right: 60.rpx, // 放置在右侧 right: 60.rpx, // 放置在右侧
child: SvgPicture.asset( child: SvgPicture.asset(
'assets/img/icon/add.svg', 'assets/img/icon/score_down.svg',
width: 14.rpx, width: 14.rpx,
height: 22.rpx, height: 22.rpx,
color: themeController.currentColor.sc9, color: themeController.currentColor.sc9,

View File

@@ -0,0 +1,100 @@
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:vbvs_app/common/color/appConstants.dart';
import 'package:vbvs_app/common/util/MyUtils.dart';
class SleepRadarChart extends StatelessWidget {
final Map<String, double> today;
final Map<String, double> yesterday;
const SleepRadarChart({
Key? key,
required this.today,
required this.yesterday,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
child: Column(
children: [
// 雷达图
_buildRadarChart(),
],
),
);
}
Widget _buildRadarChart() {
return AspectRatio(
aspectRatio: 1.3,
child: RadarChart(
RadarChartData(
dataSets: [
// 今日数据
RadarDataSet(
dataEntries: [
RadarEntry(value: today['type1']!), // 呼吸暂停
RadarEntry(value: today['type2']!), // 入睡时间
RadarEntry(value: today['type3']!), // 离床次数
RadarEntry(value: today['type4']!), // 深睡比例
RadarEntry(value: today['type5']!), // 睡眠时长
],
borderColor: stringToColor("#00C1AA"),
borderWidth: 2,
fillColor: Colors.transparent,
entryRadius: 0,
),
// 昨日数据
RadarDataSet(
dataEntries: [
RadarEntry(value: yesterday['type1']!), // 呼吸暂停
RadarEntry(value: yesterday['type2']!), // 入睡时间
RadarEntry(value: yesterday['type3']!), // 离床次数
RadarEntry(value: yesterday['type4']!), // 深睡比例
RadarEntry(value: yesterday['type5']!), // 睡眠时长
],
borderColor: stringToColor("#FFD251"),
borderWidth: 2,
fillColor: Colors.transparent,
entryRadius: 0,
),
],
radarBackgroundColor: stringToColor("#343844"),
radarBorderData:
BorderSide(color: themeController.currentColor.sc4, width: 1),
radarShape: RadarShape.polygon,
titlePositionPercentageOffset: 0.2,
titleTextStyle: TextStyle(
fontSize: AppConstants().normal_text_fontSize,
color: themeController.currentColor.sc3),
getTitle: (index, angle) {
switch (index) {
case 0:
return RadarChartTitle(text: '呼吸暂停');
case 1:
return RadarChartTitle(text: '入睡时间');
case 2:
return RadarChartTitle(text: '离床次数');
case 3:
return RadarChartTitle(text: '深睡比例');
case 4:
return RadarChartTitle(text: '睡眠时长');
default:
return const RadarChartTitle(text: '');
}
},
tickCount: 5,
ticksTextStyle:
const TextStyle(color: Colors.transparent, fontSize: 10),
// ticksColor: Colors.grey.shade300,
gridBorderData: BorderSide(color: Colors.transparent, width: 1),
tickBorderData:
BorderSide(color: themeController.currentColor.sc4, width: 1),
),
swapAnimationDuration: const Duration(milliseconds: 400),
),
);
}
}

View File

@@ -0,0 +1,273 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'dart:ui' as ui;
import 'package:vbvs_app/common/util/MyUtils.dart';
class TimeLineChart extends StatelessWidget {
final List<DataPoint> points;
final double yMin;
final double yMax;
final int startTime;
final int endTime;
final double width;
final double height;
const TimeLineChart({
super.key,
required this.points,
required this.yMin,
required this.yMax,
required this.startTime,
required this.endTime,
this.width = 400,
this.height = 300,
});
@override
Widget build(BuildContext context) {
return CustomPaint(
size: Size(width, height),
painter: _TimeLineChartPainter(
points: points,
yMin: yMin,
yMax: yMax,
startTime: startTime,
endTime: endTime,
),
);
}
}
class DataPoint {
final int timestamp;
final double value;
DataPoint(this.timestamp, this.value);
}
class _TimeLineChartPainter extends CustomPainter {
final List<DataPoint> points;
final double yMin;
final double yMax;
final int startTime;
final int endTime;
_TimeLineChartPainter({
required this.points,
required this.yMin,
required this.yMax,
required this.startTime,
required this.endTime,
});
@override
void paint(Canvas canvas, Size size) {
_drawYAxis(canvas, size);
_drawXAxis(canvas, size);
_drawLine(canvas, size);
}
void _drawXAxis(Canvas canvas, Size size) {
const margin = 40.0;
final paint = Paint()..color = Colors.black;
final textStyle = const TextStyle(color: Colors.black, fontSize: 12);
// Draw X axis line
canvas.drawLine(
Offset(margin, size.height - margin),
Offset(size.width - margin, size.height - margin),
paint,
);
// Generate time ticks
final timeFormatStartEnd = DateFormat('HH:mm');
final timeFormatMiddle = DateFormat('h');
final startDateTime = DateTime.fromMillisecondsSinceEpoch(startTime);
final endDateTime = DateTime.fromMillisecondsSinceEpoch(endTime);
List<DateTime> hourTicks = [];
DateTime current = DateTime(
startDateTime.year,
startDateTime.month,
startDateTime.day,
startDateTime.hour,
).add(const Duration(hours: 1));
while (current.isBefore(endDateTime)) {
if (current.isAfter(startDateTime)) {
hourTicks.add(current);
}
current = current.add(const Duration(hours: 1));
}
void drawTick(DateTime time, bool isEdge) {
final x = margin +
((time.millisecondsSinceEpoch - startTime) / (endTime - startTime)) *
(size.width - 2 * margin);
final text = isEdge
? timeFormatStartEnd.format(time)
: timeFormatMiddle.format(time);
_drawText(
canvas,
text,
Offset(x, size.height - margin + 20),
TextAlign.center,
);
}
drawTick(startDateTime, true);
drawTick(endDateTime, true);
for (var tick in hourTicks) {
drawTick(tick, false);
}
}
void _drawYAxis(Canvas canvas, Size size) {
const margin = 40.0;
final midValue = (yMax + yMin) / 2;
// 计算三条虚线之间的垂直间距
final lineSpacing = (size.height - 2 * margin) / 3; // 让三条线之间的间距相等
// 新增的 y=0 实线的垂直位置
final zeroLinePosition = margin + lineSpacing * 3; // 确保 y=0 位于三条虚线下方
void drawLine(double value, Color color,
{bool isDashed = false, bool isSolid = false}) {
final y =
(value - yMax) / (yMin - yMax) * (size.height - 2 * margin) + margin;
final path = Path();
path.moveTo(margin, y);
path.lineTo(size.width - margin, y);
final paint = Paint()
..color = color
..strokeWidth = (color != Colors.grey) ? 2 : 1
..style = PaintingStyle.stroke;
if (isDashed) {
Path dashedPath = _createDashedPath(path, dashWidth: 5, dashSpace: 5);
canvas.drawPath(dashedPath, paint);
} else if (isSolid) {
// 对于实线,直接绘制
canvas.drawPath(path, paint);
} else {
// 默认使用虚线绘制
canvas.drawPath(path, paint);
}
}
// 绘制 y=0 的灰色实线,并将其放置在三条虚线的下方
if (yMin < 0 && yMax > 0) {
drawLine(0, Colors.grey, isSolid: true); // 灰色实线绘制 y=0 线
}
// 绘制最小值、中间值、最大值的虚线
drawLine(yMin, themeController.currentColor.sc9, isDashed: true);
drawLine(midValue, themeController.currentColor.sc4, isDashed: true);
drawLine(yMax, themeController.currentColor.sc9, isDashed: true);
}
Path _createDashedPath(Path path,
{required double dashWidth, required double dashSpace}) {
final Path dashedPath = Path();
final ui.PathMetrics metrics = path.computeMetrics();
for (ui.PathMetric metric in metrics) {
double distance = 0;
while (distance < metric.length) {
dashedPath.addPath(
metric.extractPath(distance, distance + dashWidth),
Offset.zero,
);
distance += dashWidth + dashSpace;
}
}
return dashedPath;
}
void _drawLine(Canvas canvas, Size size) {
const margin = 40.0;
final sortedPoints = points
..sort((a, b) => a.timestamp.compareTo(b.timestamp));
Path? currentPath;
Paint currentPaint = _createPaint(Colors.green);
for (int i = 0; i < sortedPoints.length - 1; i++) {
final p1 = sortedPoints[i];
final p2 = sortedPoints[i + 1];
final x1 = margin +
((p1.timestamp - startTime) / (endTime - startTime)) *
(size.width - 2 * margin);
final y1 = margin +
(1 - (p1.value - yMin) / (yMax - yMin)) * (size.height - 2 * margin);
final x2 = margin +
((p2.timestamp - startTime) / (endTime - startTime)) *
(size.width - 2 * margin);
final y2 = margin +
(1 - (p2.value - yMin) / (yMax - yMin)) * (size.height - 2 * margin);
final shouldBeGreen = p1.value >= yMin &&
p1.value <= yMax &&
p2.value >= yMin &&
p2.value <= yMax;
// 根据当前线段的状态来决定是否切换颜色和虚线状态
if (shouldBeGreen != (currentPaint.color == Colors.green)) {
if (currentPath != null) {
canvas.drawPath(currentPath, currentPaint);
}
currentPath = Path();
currentPaint = _createPaint(shouldBeGreen ? Colors.green : Colors.red);
}
currentPath ??= Path();
if (i == 0) currentPath.moveTo(x1, y1);
currentPath.lineTo(x2, y2);
}
// 绘制剩余路径
if (currentPath != null) {
if (currentPaint.color == Colors.red) {
// 如果是红色线,绘制虚线
final dashedPath =
_createDashedPath(currentPath, dashWidth: 5, dashSpace: 5);
canvas.drawPath(dashedPath, currentPaint);
} else {
canvas.drawPath(currentPath, currentPaint);
}
}
}
Paint _createPaint(Color color) => Paint()
..color = color
..strokeWidth = 2
..style = PaintingStyle.stroke;
void _drawText(Canvas canvas, String text, Offset offset, TextAlign align) {
final textPainter = TextPainter(
text: TextSpan(
text: text,
style: const TextStyle(color: Colors.black, fontSize: 12),
),
textDirection: ui.TextDirection.ltr,
)..layout();
final centeredOffset = offset.translate(
-textPainter.width / 2,
-textPainter.height / 2,
);
textPainter.paint(canvas, centeredOffset);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}

View File

@@ -0,0 +1,281 @@
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:vbvs_app/common/util/FitTool.dart';
import 'dart:math';
import 'package:vbvs_app/common/util/MyUtils.dart';
class TimeSeriesChart extends StatelessWidget {
final int startTime;
final int endTime;
final double yMin;
final double yMax;
final List<TimeSeriesPoint> dataPoints;
TimeSeriesChart({
required this.startTime,
required this.endTime,
required this.yMin,
required this.yMax,
required this.dataPoints,
});
@override
Widget build(BuildContext context) {
final midValue = (yMax + yMin) / 2;
final xLabels = _generateXLabels();
// Prepare spots and segments
List<FlSpot> spots = [];
List<Color> lineColors = [];
for (int i = 0; i < dataPoints.length; i++) {
final point = dataPoints[i];
final xValue = _convertTimeToXValue(point.timestamp);
final yValue = point.value;
spots.add(FlSpot(xValue, yValue));
if (yValue >= yMin && yValue <= yMax) {
lineColors.add(Colors.green); // Color for points within range
} else {
lineColors.add(Colors.red); // Color for points outside range
}
}
return AspectRatio(
aspectRatio: 2,
child: LineChart(
LineChartData(
lineTouchData: LineTouchData(
touchTooltipData: LineTouchTooltipData(
getTooltipItems: (List<LineBarSpot> touchedSpots) {
return touchedSpots.map((spot) {
final time = DateTime.fromMillisecondsSinceEpoch(
_convertXValueToTime(spot.x),
);
return LineTooltipItem(
'${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}\n${spot.y.toStringAsFixed(0)}',
const TextStyle(color: Colors.black),
);
}).toList();
},
),
),
gridData: FlGridData(
show: true,
drawVerticalLine: false,
getDrawingHorizontalLine: (value) {
if (value == 0) {
return FlLine(
color: themeController.currentColor.sc4,
strokeWidth: 1,
);
} else if (value == yMin) {
return FlLine(
color: themeController.currentColor.sc9,
strokeWidth: 1,
dashArray: [5, 5],
);
} else if (value == yMax) {
return FlLine(
color: themeController.currentColor.sc9,
strokeWidth: 1,
dashArray: [5, 5],
);
} else if (value == midValue) {
return FlLine(
color: themeController.currentColor.sc4,
strokeWidth: 1,
dashArray: [5, 5],
);
}
return FlLine(
color: Colors.grey.withOpacity(0.1),
strokeWidth: 1,
);
},
),
titlesData: FlTitlesData(
show: true,
rightTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
topTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 30,
getTitlesWidget: (value, meta) {
final index = value.toInt();
if (index >= 0 && index < xLabels.length) {
return Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
xLabels[index].label,
style: TextStyle(
fontSize: 10,
color: Colors.grey,
),
),
);
}
return const Text('');
},
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
if (value == 0) {
return Padding(
padding: EdgeInsets.only(right: 14.rpx),
child: Text(
value.toStringAsFixed(0),
style: TextStyle(
fontSize: 18.rpx,
color: themeController.currentColor.sc4,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.right,
),
);
} else if (value == yMin) {
return Padding(
padding: EdgeInsets.only(right: 14.rpx),
child: Text(
yMin.toStringAsFixed(0),
style: TextStyle(
fontSize: 18.rpx,
color: themeController.currentColor.sc4,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.right,
),
);
} else if (value == midValue) {
return Padding(
padding: EdgeInsets.only(right: 14.rpx),
child: Text(
midValue.toStringAsFixed(0),
style: TextStyle(
fontSize: 18.rpx,
color: themeController.currentColor.sc4,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.right,
),
);
} else if (value == yMax) {
return Padding(
padding: EdgeInsets.only(right: 14.rpx),
child: Text(
yMax.toStringAsFixed(0),
style: TextStyle(
fontSize: 18.rpx,
color: themeController.currentColor.sc4,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.right,
),
);
}
return const Text('');
},
reservedSize: 40,
),
),
),
borderData: FlBorderData(
show: false,
border: Border.all(color: Colors.grey.withOpacity(0.3)),
),
minX: 0,
maxX: xLabels.length - 1,
minY: min(0, yMin) - (yMax - yMin) * 0.2,
maxY: yMax + (yMax - yMin) * 0.2,
lineBarsData: [
LineChartBarData(
spots: spots,
isCurved: false,
color: themeController.currentColor.sc2,
barWidth: 2,
isStrokeCapRound: true,
dotData: FlDotData(show: false), // Disable dots
belowBarData: BarAreaData(show: false),
),
],
),
),
);
}
List<XLabel> _generateXLabels() {
final labels = <XLabel>[];
final startDate = DateTime.fromMillisecondsSinceEpoch(startTime);
final endDate = DateTime.fromMillisecondsSinceEpoch(endTime);
labels.add(XLabel(
time: startTime,
label:
'${startDate.hour.toString().padLeft(2, '0')}:${startDate.minute.toString().padLeft(2, '0')}',
));
DateTime current = DateTime(
startDate.year,
startDate.month,
startDate.day,
startDate.hour + 1,
);
while (current.isBefore(endDate)) {
labels.add(XLabel(
time: current.millisecondsSinceEpoch,
label: current.hour.toString(),
));
current = current.add(Duration(hours: 1));
}
labels.add(XLabel(
time: endTime,
label:
'${endDate.hour.toString().padLeft(2, '0')}:${endDate.minute.toString().padLeft(2, '0')}',
));
return labels;
}
double _convertTimeToXValue(int timestamp) {
final totalDuration = endTime - startTime;
final pointDuration = timestamp - startTime;
final xLabels = _generateXLabels();
return (pointDuration / totalDuration) * (xLabels.length - 1);
}
int _convertXValueToTime(double xValue) {
final xLabels = _generateXLabels();
final totalDuration = endTime - startTime;
final ratio = xValue / (xLabels.length - 1);
return startTime + (totalDuration * ratio).round();
}
}
class TimeSeriesPoint {
final int timestamp;
final double value;
TimeSeriesPoint(this.timestamp, this.value);
}
class XLabel {
final int time;
final String label;
XLabel({required this.time, required this.label});
}

View File

@@ -0,0 +1,140 @@
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/AdviceComponnetWidget.dart';
class AIAdviceWidget extends StatefulWidget {
AIAdviceWidget({super.key});
@override
State<AIAdviceWidget> createState() => _AIAdviceWidgetState();
}
class _AIAdviceWidgetState extends State<AIAdviceWidget> {
@override
void setState(VoidCallback callback) {
super.setState(callback);
}
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
List advices = [
{
"title": "调整作息时间",
"description": "确保每天在相同的时间上床并醒来。保持规律的作息可以帮助调整你的生物钟,改善睡眠质量。",
},
{
"title": "减少睡前刺激",
"description": "避免在睡前使用电子设备,如手机、电脑等,减少屏幕时间,以防影响你的睡眠质量。",
},
{
"title": "创造理想睡眠环境",
"description": "确保卧室安静、黑暗且舒适。调节室温,并避免过于嘈杂或明亮的环境,帮助你快速入睡。",
},
{
"title": "避免摄入咖啡因和酒精",
"description": "避免在睡前几小时内摄入咖啡、茶、酒精等饮品,因为这些物质可能会干扰你的睡眠。",
},
{
"title": "增加日间活动",
"description": "适量的日间运动可以帮助提高睡眠质量,但要避免睡前剧烈运动,以免影响入睡。",
},
{
"title": "放松身心",
"description": "睡前可以进行一些放松活动,如深呼吸、冥想或听轻音乐,这有助于减轻压力并促进良好的睡眠。",
},
];
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(
"AI分析".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(
"AI分析介绍".tr,
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: 31.rpx,
),
Padding(
padding:
EdgeInsetsDirectional.fromSTEB(0.rpx, 0.rpx, 30.rpx, 0.rpx),
child: Column(
children: advices.map<Widget>((advice) {
return AdviceComponnetWidget(
title: advice["title"],
description: advice["description"],
).paddingOnly(bottom: 0.rpx); // 在每个组件下方添加间隔
}).toList(),
),
)
],
),
),
);
}
}

View File

@@ -0,0 +1,315 @@
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/TimeSeriesChart.dart';
class BreatheStandardWidget extends StatefulWidget {
BreatheStandardWidget({super.key});
@override
State<BreatheStandardWidget> createState() => _BreatheStandardWidgetState();
}
class _BreatheStandardWidgetState extends State<BreatheStandardWidget> {
@override
void setState(VoidCallback callback) {
super.setState(callback);
}
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
final now = DateTime.now();
final startTime = now.subtract(Duration(hours: 5)).millisecondsSinceEpoch;
final endTime = now.millisecondsSinceEpoch;
final dataPoints = [
TimeSeriesPoint(startTime + Duration(minutes: 10).inMilliseconds, 50),
TimeSeriesPoint(startTime + Duration(hours: 1).inMilliseconds, 120),
TimeSeriesPoint(startTime + Duration(hours: 2).inMilliseconds, 80),
TimeSeriesPoint(startTime + Duration(hours: 3).inMilliseconds, 180),
TimeSeriesPoint(startTime + Duration(hours: 4).inMilliseconds, 30),
TimeSeriesPoint(endTime - Duration(minutes: 10).inMilliseconds, 150),
];
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(
"呼吸基准介绍".tr,
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: 31.rpx,
),
Padding(
padding:
EdgeInsetsDirectional.fromSTEB(0.rpx, 0.rpx, 30.rpx, 0.rpx),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
// 圆形小球容器
Container(
width: 14.rpx, // 圆球的直径
height: 14.rpx,
decoration: BoxDecoration(
color: themeController.currentColor.sc2, // 小球的颜色
shape: BoxShape.circle, // 设置为圆形
),
),
SizedBox(width: 15.rpx), // 圆球和文字之间的间隔
// 文字
Text(
'正常范围(8~20)',
style: TextStyle(
fontSize:
AppConstants().smaller_text_fontSize, // 文字的大小
color: themeController.currentColor.sc3, // 文字颜色
),
),
],
),
Container(
// color: Colors.red,
width: double.infinity,
// height: 300.rpx,
child: TimeSeriesChart(
startTime: startTime,
endTime: endTime,
yMin: 50,
yMax: 150,
dataPoints: dataPoints,
),
),
Padding(
padding: EdgeInsetsDirectional.fromSTEB(
30.rpx, 0.rpx, 0.rpx, 0.rpx),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Column(
children: [
Text(
"平均呼吸",
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize:
AppConstants().normal_text_fontSize),
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
"12",
style: TextStyle(
color: themeController.currentColor.sc2,
fontSize:
AppConstants().normal_text_fontSize),
),
Text(
"次/分钟",
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize:
AppConstants().small_text_fontSize),
),
].divide(SizedBox(
width: 6.rpx,
)),
),
],
),
Column(
children: [
Text(
"基准呼吸",
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize:
AppConstants().normal_text_fontSize),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
"15",
style: TextStyle(
color: themeController.currentColor.sc2,
fontSize:
AppConstants().normal_text_fontSize),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Text(
"次/分钟",
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize:
AppConstants().small_text_fontSize),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
].divide(SizedBox(
width: 6.rpx,
)),
),
],
),
Column(
children: [
Text(
"最低呼吸",
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize:
AppConstants().normal_text_fontSize),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
"11",
style: TextStyle(
color: themeController.currentColor.sc2,
fontSize:
AppConstants().normal_text_fontSize),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Text(
"次/分钟",
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize:
AppConstants().small_text_fontSize),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
].divide(SizedBox(
width: 6.rpx,
)),
),
],
),
Column(
children: [
Text(
"最高呼吸",
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize:
AppConstants().normal_text_fontSize),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
"18",
style: TextStyle(
color: themeController.currentColor.sc2,
fontSize:
AppConstants().normal_text_fontSize),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Text(
"次/分钟",
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize:
AppConstants().small_text_fontSize),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
].divide(SizedBox(
width: 6.rpx,
)),
),
],
),
],
),
),
].divide(SizedBox(
height: 18.rpx,
)),
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,197 @@
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/AdviceComponnetWidget.dart';
import 'package:vbvs_app/pages/sleep_report/chart/RadarChart.dart';
import 'package:vbvs_app/pages/sleep_report/chart/SleepRadarChart.dart';
class CompareSleepWidget extends StatefulWidget {
CompareSleepWidget({super.key});
@override
State<CompareSleepWidget> createState() => _CompareSleepWidgetState();
}
class _CompareSleepWidgetState extends State<CompareSleepWidget> {
@override
void setState(VoidCallback callback) {
super.setState(callback);
}
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
var today = {
"type1": 40.0,
"type2": 80.0,
"type3": 60.0,
"type4": 70.0,
"type5": 100.0
};
var yesterday = {
"type1": 40.0,
"type2": 90.0,
"type3": 50.0,
"type4": 70.0,
"type5": 30.0
};
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(
"与昨日对比分析介绍".tr,
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: 31.rpx,
),
// Padding(
// padding:
// EdgeInsetsDirectional.fromSTEB(30.rpx, 0.rpx, 30.rpx, 0.rpx),
// child: SleepRadarChart(
// today: today,
// yesterday: yesterday,
// ),
// ),
Padding(
padding:
EdgeInsetsDirectional.fromSTEB(0.rpx, 0.rpx, 30.rpx, 0.rpx),
child: Stack(
children: [
// 雷达图
Padding(
padding: EdgeInsetsDirectional.fromSTEB(
30.rpx, 0.rpx, 0.rpx, 0.rpx),
child: SleepRadarChart(
today: today,
yesterday: yesterday,
),
),
// 在左侧添加一个 Text
Positioned(
left: 0, // 这里可以修改文本左边的距离
top: 0, // 这里可以修改文本顶部的距离
child: Container(
color: Colors.transparent, // 不需要背景色,可以去掉
child: Column(
children: [
Row(
children: [
// 长条容器
Container(
width:
34.rpx, // 你可以设置容器的宽度,或者使用 Expanded 填充剩余空间
height: 2.rpx, // 容器的高度
color: stringToColor("#00C1AA"), // 容器的颜色
),
SizedBox(width: 13.rpx), // 文字和容器之间的间距
// 文字
Text(
'今日数据', // 文字内容
style: TextStyle(
fontSize: 18.rpx, // 文字大小
color:
themeController.currentColor.sc4, // 文字颜色
),
),
],
),
Row(
children: [
// 长条容器
Container(
width:
34.rpx, // 你可以设置容器的宽度,或者使用 Expanded 填充剩余空间
height: 2.rpx, // 容器的高度
color: stringToColor("#FFD251"), // 容器的颜色
),
SizedBox(width: 13.rpx), // 文字和容器之间的间距
// 文字
Text(
'昨日数据', // 文字内容
style: TextStyle(
fontSize: 18.rpx, // 文字大小
color:
themeController.currentColor.sc4, // 文字颜色
),
),
],
),
].divide(SizedBox(height: 25.rpx)),
),
),
),
],
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,316 @@
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/DataShowWidget.dart';
class HeartChangeWidget extends StatefulWidget {
HeartChangeWidget({super.key});
@override
State<HeartChangeWidget> createState() => _HeartChangeWidgetState();
}
class _HeartChangeWidgetState extends State<HeartChangeWidget> {
@override
void setState(VoidCallback callback) {
super.setState(callback);
}
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
//0上升 1下降 2持平
List data = [
{
"name": "心脏总能量",
"value": 5262,
"range": "2055-6000",
"change": 0,
"desc": "心脏总能量介绍"
},
{
"name": "心率减速力",
"value": 5262,
"range": "2055-6000",
"change": 1,
"desc": "心率减速力介绍"
},
{
"name": "迷走神经张力指数",
"value": 5262,
"range": "2055-6000",
"change": 2,
"desc": "迷走神经张力指数介绍"
},
{
"name": "交感神经张力指数",
"value": 5262,
"range": "2055-6000",
"change": 0,
"desc": "交感神经张力指数介绍"
},
{
"name": "自主神经张力指数",
"value": 5262,
"range": "2055-6000",
"change": 2,
"desc": "自主神经张力指数介绍"
},
{
"name": "血管舒张指数",
"value": 5262,
"range": "2055-6000",
"change": 1,
"desc": "血管舒张指数介绍"
},
{
"name": "SDNN",
"value": 5262,
"range": "2055-6000",
"change": 0,
"desc": "SDNN介绍"
},
{
"name": "PNN50",
"value": 5262,
"range": "2055-6000",
"change": 1,
"desc": "PNN50介绍"
},
{
"name": "RMSSD",
"value": 5262,
"range": "2055-6000",
"change": 2,
"desc": "RMSSD介绍"
},
];
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介绍".tr,
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: 31.rpx,
),
Padding(
padding:
EdgeInsetsDirectional.fromSTEB(0.rpx, 0.rpx, 0.rpx, 0.rpx),
child: Column(
children: [
DataShowWidget(
alignment: MainAxisAlignment.center,
widget1: Text(
"名称",
style: TextStyle(
color: themeController.currentColor.sc4,
fontSize: AppConstants().normal_text_fontSize,
),
),
widget2: Text(
"测量值",
style: TextStyle(
color: themeController.currentColor.sc4,
fontSize: AppConstants().normal_text_fontSize,
),
),
widget3: Text(
"参考范围",
style: TextStyle(
color: themeController.currentColor.sc4,
fontSize: AppConstants().normal_text_fontSize,
),
),
widget4: Text(
"趋势",
style: TextStyle(
color: themeController.currentColor.sc4,
fontSize: AppConstants().normal_text_fontSize,
),
),
),
Column(
children: data.map<Widget>((data) {
return DataShowWidget(
alignment: MainAxisAlignment.center,
widget1: Row(
children: [
Text(
'${data['name']}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize: AppConstants().normal_text_fontSize,
),
),
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(
'${data['desc']}',
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,
),
),
),
],
),
widget2: Text(
'${data['value']}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize: AppConstants().normal_text_fontSize,
),
),
widget3: Text(
'${data['range']}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize: AppConstants().normal_text_fontSize,
),
),
widget4: data['change'] == 0
? Padding(
padding: EdgeInsetsDirectional.fromSTEB(
0, 0.rpx, 0, 0),
child: Container(
width: 22.rpx,
height: 22.rpx,
decoration: BoxDecoration(),
child: SvgPicture.asset(
'assets/img/icon/score_up.svg',
// fit: BoxFit.cover,
),
),
)
: data['change'] == 1
? Padding(
padding: EdgeInsetsDirectional.fromSTEB(
0, 0.rpx, 0, 0),
child: Container(
width: 22.rpx,
height: 22.rpx,
decoration: BoxDecoration(),
child: SvgPicture.asset(
'assets/img/icon/score_down.svg',
// fit: BoxFit.cover,
),
),
)
: Padding(
padding: EdgeInsetsDirectional.fromSTEB(
0, 0.rpx, 0, 0),
child: Container(
width: 22.rpx,
height: 22.rpx,
decoration: BoxDecoration(),
child: SvgPicture.asset(
'assets/img/icon/score_equal.svg',
// fit: BoxFit.fill,
),
),
),
).paddingOnly(bottom: 0.rpx); // 在每个组件下方添加间隔
}).toList(),
),
],
),
),
],
),
),
);
}
}

View File

@@ -1,4 +1,7 @@
import 'dart:math';
import 'package:ef/ef.dart'; import 'package:ef/ef.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:vbvs_app/common/color/appConstants.dart'; import 'package:vbvs_app/common/color/appConstants.dart';
@@ -6,7 +9,7 @@ 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/pages/device_bind/componnet/bind_dialog.dart'; import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
import 'package:vbvs_app/pages/sleep_report/chart/StatusBarWithIndicator.dart'; import 'package:vbvs_app/pages/sleep_report/chart/ScatterPlotChart.dart';
class HeartPointWidget extends StatefulWidget { class HeartPointWidget extends StatefulWidget {
HeartPointWidget({super.key}); HeartPointWidget({super.key});
@@ -33,6 +36,18 @@ class _HeartPointWidgetState extends State<HeartPointWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
List<ScatterSpot> data = List.generate(200, (index) {
// 随机生成 x 和 y 值,范围都在 0-1400 之间
double x = Random().nextDouble() * 1400; // x 值在 0-1400 范围
double y = Random().nextDouble() * 1400; // y 值也在 0-1400 范围
// 返回 ScatterSpot使用圆点绘制器自定义大小和颜色
return ScatterSpot(
x,
y,
);
});
return Container( return Container(
width: double.infinity, width: double.infinity,
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -91,24 +106,30 @@ class _HeartPointWidgetState extends State<HeartPointWidget> {
), ),
), ),
SizedBox( SizedBox(
height: 83.rpx, height: 31.rpx,
), ),
Padding( Padding(
padding: padding:
EdgeInsetsDirectional.fromSTEB(30.rpx, 0.rpx, 30.rpx, 0.rpx), EdgeInsetsDirectional.fromSTEB(30.rpx, 0.rpx, 30.rpx, 0.rpx),
child: StatusBarWithIndicator( child: Container(
selectKey: 2, width: MediaQuery.of(context).size.width * 0.7,
showLabel: [ height: MediaQuery.of(context).size.width * 0.7,
{"key": 1, "name": "正常", "color": Color(0xFF4CAF50)}, constraints: BoxConstraints(
{"key": 2, "name": "一般", "color": Color(0xFF8BC34A)}, minWidth: 430.rpx,
{"key": 3, "name": "注意", "color": Color(0xFFFFC107)}, minHeight: 430.rpx,
{"key": 4, "name": "警告", "color": Color(0xFFF44336)}, ),
], child: ScatterPlotChart(
points: data,
xMax: 1400, // x轴最大值
yMax: 1400, // y轴最大值
pointColor: stringToColor("#00C1AA"), // 点的颜色
divisions: 7, // 刻度分割数量
), ),
), ),
SizedBox(
height: 56.rpx,
), ),
// SizedBox(
// height: 31.rpx,
// ),
], ],
), ),
), ),

View File

@@ -0,0 +1,316 @@
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/TimeSeriesChart.dart';
class HeartRateStandardWidget extends StatefulWidget {
HeartRateStandardWidget({super.key});
@override
State<HeartRateStandardWidget> createState() =>
_HeartRateStandardWidgetState();
}
class _HeartRateStandardWidgetState extends State<HeartRateStandardWidget> {
@override
void setState(VoidCallback callback) {
super.setState(callback);
}
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
final now = DateTime.now();
final startTime = now.subtract(Duration(hours: 5)).millisecondsSinceEpoch;
final endTime = now.millisecondsSinceEpoch;
final dataPoints = [
TimeSeriesPoint(startTime + Duration(minutes: 10).inMilliseconds, 50),
TimeSeriesPoint(startTime + Duration(hours: 1).inMilliseconds, 120),
TimeSeriesPoint(startTime + Duration(hours: 2).inMilliseconds, 80),
TimeSeriesPoint(startTime + Duration(hours: 3).inMilliseconds, 180),
TimeSeriesPoint(startTime + Duration(hours: 4).inMilliseconds, 30),
TimeSeriesPoint(endTime - Duration(minutes: 10).inMilliseconds, 150),
];
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(
"心率基准介绍".tr,
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: 31.rpx,
),
Padding(
padding:
EdgeInsetsDirectional.fromSTEB(0.rpx, 0.rpx, 30.rpx, 0.rpx),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
// 圆形小球容器
Container(
width: 14.rpx, // 圆球的直径
height: 14.rpx,
decoration: BoxDecoration(
color: themeController.currentColor.sc2, // 小球的颜色
shape: BoxShape.circle, // 设置为圆形
),
),
SizedBox(width: 15.rpx), // 圆球和文字之间的间隔
// 文字
Text(
'正常范围(50~80)',
style: TextStyle(
fontSize:
AppConstants().smaller_text_fontSize, // 文字的大小
color: themeController.currentColor.sc3, // 文字颜色
),
),
],
),
Container(
// color: Colors.red,
width: double.infinity,
// height: 300.rpx,
child: TimeSeriesChart(
startTime: startTime,
endTime: endTime,
yMin: 50,
yMax: 150,
dataPoints: dataPoints,
),
),
Padding(
padding: EdgeInsetsDirectional.fromSTEB(
30.rpx, 0.rpx, 0.rpx, 0.rpx),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Column(
children: [
Text(
"平均心率",
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize:
AppConstants().normal_text_fontSize),
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
"89",
style: TextStyle(
color: themeController.currentColor.sc2,
fontSize:
AppConstants().normal_text_fontSize),
),
Text(
"次/分钟",
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize:
AppConstants().small_text_fontSize),
),
].divide(SizedBox(
width: 6.rpx,
)),
),
],
),
Column(
children: [
Text(
"基准心率",
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize:
AppConstants().normal_text_fontSize),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
"80",
style: TextStyle(
color: themeController.currentColor.sc2,
fontSize:
AppConstants().normal_text_fontSize),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Text(
"次/分钟",
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize:
AppConstants().small_text_fontSize),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
].divide(SizedBox(
width: 6.rpx,
)),
),
],
),
Column(
children: [
Text(
"最低心率",
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize:
AppConstants().normal_text_fontSize),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
"68",
style: TextStyle(
color: themeController.currentColor.sc2,
fontSize:
AppConstants().normal_text_fontSize),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Text(
"次/分钟",
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize:
AppConstants().small_text_fontSize),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
].divide(SizedBox(
width: 6.rpx,
)),
),
],
),
Column(
children: [
Text(
"最高心率",
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize:
AppConstants().normal_text_fontSize),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
"98",
style: TextStyle(
color: themeController.currentColor.sc2,
fontSize:
AppConstants().normal_text_fontSize),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Text(
"次/分钟",
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize:
AppConstants().small_text_fontSize),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
].divide(SizedBox(
width: 6.rpx,
)),
),
],
),
],
),
),
].divide(SizedBox(
height: 18.rpx,
)),
),
),
],
),
),
);
}
}

View File

@@ -61,7 +61,7 @@ class _SnoreViewWidgetWidgetState extends State<SnoreViewWidgetWidget> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
"呼吸暂停监测".tr, "打鼾监测".tr,
style: TextStyle( style: TextStyle(
color: themeController.currentColor.sc3, color: themeController.currentColor.sc3,
fontSize: AppConstants().title_text_fontSize), fontSize: AppConstants().title_text_fontSize),
@@ -77,7 +77,7 @@ class _SnoreViewWidgetWidgetState extends State<SnoreViewWidgetWidget> {
context, context,
Container( Container(
child: Text( child: Text(
"呼吸暂停监测介绍。", "打鼾监测介绍。",
style: TextStyle( style: TextStyle(
fontSize: 26.rpx, fontSize: 26.rpx,
color: themeController.currentColor.sc3, color: themeController.currentColor.sc3,

View File

@@ -9,10 +9,15 @@ 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/AIAdviceWidget.dart';
import 'package:vbvs_app/pages/sleep_report/component/BreathPauseWidget.dart'; import 'package:vbvs_app/pages/sleep_report/component/BreathPauseWidget.dart';
import 'package:vbvs_app/pages/sleep_report/component/BreatheStandardWidget.dart';
import 'package:vbvs_app/pages/sleep_report/component/CompareSleepWidget.dart';
import 'package:vbvs_app/pages/sleep_report/component/DiseasePercentsWidget.dart'; import 'package:vbvs_app/pages/sleep_report/component/DiseasePercentsWidget.dart';
import 'package:vbvs_app/pages/sleep_report/component/HeartChangeWidget.dart';
import 'package:vbvs_app/pages/sleep_report/component/HeartHealthWidget.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/HeartPointWidget.dart';
import 'package:vbvs_app/pages/sleep_report/component/HeartRateStandardWidget.dart';
import 'package:vbvs_app/pages/sleep_report/component/SkinPercentWidget.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/SnoreViewWidget.dart';
@@ -505,6 +510,14 @@ 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: CompareSleepWidget(),
),
),
Padding( Padding(
padding: EdgeInsetsDirectional.fromSTEB( padding: EdgeInsetsDirectional.fromSTEB(
30.rpx, 0.rpx, 30.rpx, 0), 30.rpx, 0.rpx, 30.rpx, 0),
@@ -513,6 +526,39 @@ class _NewSleepReportPageState extends State<NewSleepReportPage> {
child: HeartPointWidget(), child: HeartPointWidget(),
), ),
), ),
Padding(
padding: EdgeInsetsDirectional.fromSTEB(
30.rpx, 0.rpx, 30.rpx, 0),
child: Container(
// color: stringToColor("#242835"),
width: double.infinity,
child: AIAdviceWidget(),
),
),
Padding(
padding: EdgeInsetsDirectional.fromSTEB(
30.rpx, 0.rpx, 30.rpx, 0),
child: Container(
width: double.infinity,
child: HeartRateStandardWidget(),
),
),
Padding(
padding: EdgeInsetsDirectional.fromSTEB(
30.rpx, 0.rpx, 30.rpx, 0),
child: Container(
width: double.infinity,
child: HeartChangeWidget(),
),
),
Padding(
padding: EdgeInsetsDirectional.fromSTEB(
30.rpx, 0.rpx, 30.rpx, 0),
child: Container(
width: double.infinity,
child: BreatheStandardWidget(),
),
),
Padding( Padding(
padding: EdgeInsetsDirectional.fromSTEB( padding: EdgeInsetsDirectional.fromSTEB(
30.rpx, 0.rpx, 30.rpx, 0), 30.rpx, 0.rpx, 30.rpx, 0),

View File

@@ -0,0 +1,113 @@
// import 'package:ef/ef.dart';
// 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';
// import 'package:vbvs_app/component/tool/CustomCard.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/theme_controller/ThemeController.dart';
// import 'package:vbvs_app/controller/user_info_controller.dart';
// // import 'package:easydevice/easydevice.dart';
// class AboutUsPage extends StatefulWidget {
// const AboutUsPage({super.key});
// @override
// State<AboutUsPage> createState() => _AboutUsPageState();
// }
// class _AboutUsPageState extends State<AboutUsPage> {
// GlobalController globalController = Get.find();
// UserInfoController userInfoController = Get.find();
// BlueteethBindController blueteethBindController = Get.find();
// ThemeController themeController = Get.find();
// @override
// void initState() {
// super.initState();
// }
// @override
// Widget build(BuildContext context) {
// return LayoutBuilder(
// builder: (context, bodySize) => GestureDetector(
// onTap: () => FocusScope.of(context).unfocus(),
// child: Container(
// decoration: BoxDecoration(
// image: DecorationImage(
// image: AssetImage('assets/img/bgNoImg.png'), // 本地图片
// fit: BoxFit.fill, // 填满整个 Container
// ),
// ),
// child: Scaffold(
// backgroundColor: Colors.transparent, // 加上这一行
// appBar: AppBar(
// backgroundColor: themeController.currentColor.sc17,
// automaticallyImplyLeading: false,
// iconTheme: IconThemeData(
// color: themeController.currentColor.sc3,
// ),
// titleSpacing: 0,
// // leading: returnIconButtom,
// title: Container(
// width: double.infinity,
// height: 180.rpx,
// child: Stack(
// alignment: Alignment.center,
// children: [
// /// 居中标题
// Text(
// '关于我们.标题'.tr,
// style: FlutterFlowTheme.of(context).bodyMedium.override(
// fontFamily: 'Readex Pro',
// color: themeController.currentColor.sc3,
// letterSpacing: 0,
// fontSize: 30.rpx,
// ),
// ),
// /// 左边返回按钮
// Positioned(
// left: 0,
// child: returnIconButtom,
// ),
// ],
// ),
// ),
// actions: [],
// centerTitle: false,
// ),
// body: SafeArea(
// top: true,
// child: Padding(
// padding: EdgeInsetsDirectional.fromSTEB(30.rpx, 0, 30.rpx, 0),
// child: SingleChildScrollView(
// child: Column(
// mainAxisSize: MainAxisSize.max,
// children: [
// SizedBox(
// height: 30.rpx,
// ),
// Text(
// "企业简介\n\n\n嘉兴太和信息技术有限公司成立于2013年是一家以传感技术、室内定位技术和人工智能技术为基础的国家高新技术企业AI非接触生命体征传感器、高精度室内外一体定位平台、AI视频分析系统、射频消融等技术成果目前已经拥有30多类知识产权证书多项专利技术处于行业领先水平。\n\n\n我司研发的“非接触式生命体征传感器”是一款基于BCG信号原理通过检测人体心脏搏动引起的微小振动的传感器系统。传感器系统通过将人体微弱的心跳、呼吸信号转换未电信号进行相关生命体征分析。该传感器可为用户提供高灵敏度和精确度检测结构适用于需要非接触式、高分辨率的监测场景。该系统的硬件、软件及生产维护均由我司自主开发和管理拥有完全自主知识产权并已申请多项国家专利可依据用户需求定制个性化方案。\n\n\n该产品置于床垫下方使用全程完全无感。采集的体征数据通过睡眠健康管理平台实时显示用户的健康状态并对每次的睡眠报告进行系统化归档管理支持长期查询。一旦用户在使用过程中出现异常情况系统可及时做出判断并反馈预警信息和建议。目前心率监测的准确度可达97%以上呼吸监测的准确度可达95%以上,其他生理指标的监测精度也显著优于同类产品。该产品主体材质均采用符合国家标准的环保材料,部分硅胶配件达到食品级安全标准。产品尺寸可根据需求进行定制,适用于单人床、双人床、婴儿床、椅子及枕头等多种场景。\n\n\n睡眠健康管理平台通过实时预警与远程管理提升睡眠质量及慢病干预效率助力养老院、月子中心、康复中心、智能寝具等行业降本增效实现精准健康的科学管理。",
// style: TextStyle(
// fontSize: AppConstants().normal_text_fontSize,
// color: themeController.currentColor.sc3),
// ),
// ],
// ),
// ),
// ),
// ),
// ),
// ),
// ),
// );
// }
// }

View File

@@ -0,0 +1,118 @@
import 'package:ef/ef.dart';
import 'package:flutter/material.dart';
import 'package:flutter_pdfview/flutter_pdfview.dart';
import 'package:vbvs_app/common/util/FitTool.dart';
import 'package:vbvs_app/common/util/MyUtils.dart';
import 'package:vbvs_app/controller/setting/pdf/PrivacyPdfController.dart';
class PrivacySchemePage extends StatefulWidget {
PrivacySchemePage({super.key});
@override
State<PrivacySchemePage> createState() => _PrivacySchemePageState();
}
class _PrivacySchemePageState extends State<PrivacySchemePage> {
PrivacyPdfController pdfController = Get.find();
@override
void initState() {
super.initState();
String language = "zh_CN"; // 默认语言
if (languageController.selectLanguage?.value?.language_code != null) {
language = languageController.selectLanguage!.value!.language_code!;
}
pdfController.loadPdf(2,
"https://vsbst-api.he-info.cn/vsbs_sotrage/privacy-scheme/$language.pdf",
);
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, bodySize) => GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/img/bgNoImg.png'), // 本地图片
fit: BoxFit.fill, // 填满整个 Container
),
),
child: Scaffold(
backgroundColor: Colors.transparent, // 加上这一行
appBar: AppBar(
backgroundColor: themeController.currentColor.sc17,
automaticallyImplyLeading: false,
iconTheme: IconThemeData(
color: themeController.currentColor.sc3,
),
titleSpacing: 0,
// leading: returnIconButtom,
title: Container(
width: double.infinity,
height: 180.rpx,
child: Stack(
alignment: Alignment.center,
children: [
/// 居中标题
Text(
'隐私协议'.tr,
style: FlutterFlowTheme.of(context).bodyMedium.override(
fontFamily: 'Readex Pro',
color: themeController.currentColor.sc3,
letterSpacing: 0,
fontSize: 30.rpx,
),
),
/// 左边返回按钮
Positioned(
left: 0,
child: returnIconButtom,
),
],
),
),
actions: [],
centerTitle: false,
),
body: SafeArea(
top: true,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 30.rpx),
child: Column(
children: [
Expanded(
child: Obx(() {
if (pdfController.localPdfPath.value == null) {
return Center(child: CircularProgressIndicator());
} else {
return PDFView(
filePath: pdfController.localPdfPath.value!,
autoSpacing: false,
enableSwipe: true,
swipeHorizontal: false,
pageSnap: true,
fitEachPage: true,
defaultPage: 0,
onRender: (pages) => print('PDF 渲染完成,共 $pages'),
onError: (error) => print('PDF 加载错误: $error'),
);
}
}),
),
],
),
),
),
),
),
),
);
}
}

View File

@@ -0,0 +1,119 @@
import 'package:ef/ef.dart';
import 'package:flutter/material.dart';
import 'package:flutter_pdfview/flutter_pdfview.dart';
import 'package:vbvs_app/common/util/FitTool.dart';
import 'package:vbvs_app/common/util/MyUtils.dart';
import 'package:vbvs_app/controller/setting/pdf/UserPdfController.dart';
class UserSchemePage extends StatefulWidget {
UserSchemePage({super.key});
@override
State<UserSchemePage> createState() => _UserSchemePageState();
}
class _UserSchemePageState extends State<UserSchemePage> {
UserPdfController pdfController = Get.find();
@override
void initState() {
super.initState();
String language = "zh_CN"; // 默认语言
if (languageController.selectLanguage?.value?.language_code != null) {
language = languageController.selectLanguage!.value!.language_code!;
}
pdfController.loadPdf(
1,
"https://vsbst-api.he-info.cn/vsbs_sotrage/user-scheme/$language.pdf",
);
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, bodySize) => GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/img/bgNoImg.png'), // 本地图片
fit: BoxFit.fill, // 填满整个 Container
),
),
child: Scaffold(
backgroundColor: Colors.transparent, // 加上这一行
appBar: AppBar(
backgroundColor: themeController.currentColor.sc17,
automaticallyImplyLeading: false,
iconTheme: IconThemeData(
color: themeController.currentColor.sc3,
),
titleSpacing: 0,
// leading: returnIconButtom,
title: Container(
width: double.infinity,
height: 180.rpx,
child: Stack(
alignment: Alignment.center,
children: [
/// 居中标题
Text(
'用户协议'.tr,
style: FlutterFlowTheme.of(context).bodyMedium.override(
fontFamily: 'Readex Pro',
color: themeController.currentColor.sc3,
letterSpacing: 0,
fontSize: 30.rpx,
),
),
/// 左边返回按钮
Positioned(
left: 0,
child: returnIconButtom,
),
],
),
),
actions: [],
centerTitle: false,
),
body: SafeArea(
top: true,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 30.rpx),
child: Column(
children: [
Expanded(
child: Obx(() {
if (pdfController.localPdfPath.value == null) {
return Center(child: CircularProgressIndicator());
} else {
return PDFView(
filePath: pdfController.localPdfPath.value!,
autoSpacing: false,
enableSwipe: true,
swipeHorizontal: false,
pageSnap: true,
fitEachPage: true,
defaultPage: 0,
onRender: (pages) => print('PDF 渲染完成,共 $pages'),
onError: (error) => print('PDF 加载错误: $error'),
);
}
}),
),
],
),
),
),
),
),
),
);
}
}

View File

@@ -51,6 +51,7 @@ dependencies:
flutter_pdfview: ^1.4.0+1 flutter_pdfview: ^1.4.0+1
weather: ^3.1.1 weather: ^3.1.1
geocoding: ^2.1.0 geocoding: ^2.1.0
# fl_chart: ^1.0.0
dev_dependencies: dev_dependencies: