feat: consultations

This commit is contained in:
BLoC
2025-02-19 14:56:53 +03:00
parent fea83bb7bd
commit e142ce7b96
140 changed files with 20858 additions and 69 deletions

View File

@@ -4,8 +4,7 @@ import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_state.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_cubit.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/notifications_cubit/notifications_state.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_state.dart';
import 'package:eatvio_app/features/profile/models/profile_model.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:flutter/material.dart';
@@ -13,14 +12,14 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:go_router/go_router.dart';
int currentBottomNavBarIndex = 0;
class BottomNavigationBarWidget extends StatefulWidget {
final ProfileModel profileModel;
final Widget child;
const BottomNavigationBarWidget({
super.key,
required this.child,
required this.profileModel,
});
@override
@@ -28,32 +27,42 @@ class BottomNavigationBarWidget extends StatefulWidget {
}
class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
int currentBottomNavBarIndex = 0;
late final List<String> pages;
@override
void initState() {
super.initState();
if (widget.profileModel.roles.contains('consultant')) {
pages = [
'/my-profile-screen',
'/feed-screen',
'/diary-screen',
'/consultations-screen',
'/chat-select-screen',
'/notifications-screen',
'/menu-screen',
];
} else {
pages = [
'/my-profile-screen',
'/feed-screen',
'/diary-screen',
'/chat-select-screen',
'/notifications-screen',
'/menu-screen',
];
}
}
void _onItemTapped(int index) async {
if (currentBottomNavBarIndex != index) {
setState(() {
currentBottomNavBarIndex = index;
});
}
switch (currentBottomNavBarIndex) {
case 0:
GoRouter.of(context).replace('/my-profile-screen');
break;
case 1:
GoRouter.of(context).replace('/feed-screen');
break;
case 2:
GoRouter.of(context).replace('/diary-screen');
break;
case 3:
GoRouter.of(context).replace('/chat-select-screen');
break;
case 4:
GoRouter.of(context).replace('/notifications-screen');
break;
case 5:
GoRouter.of(context).replace('/menu-screen');
break;
}
GoRouter.of(context).replace(pages[currentBottomNavBarIndex]);
}
@override
@@ -71,19 +80,9 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
items: [
BottomNavigationBarItem(
label: '',
icon: BlocBuilder<ProfileAvatarCubit, ProfileAvatarState>(
builder: (BuildContext context, ProfileAvatarState state) {
if (state is ProfileAvatarStateLoaded) {
return CircleAvatar(
backgroundImage: NetworkImage(state.profile.profilePhotoUrl),
radius: 16,
);
}
return CircleAvatar(
radius: 16,
backgroundColor: Colors.black12,
);
},
icon: CircleAvatar(
backgroundImage: NetworkImage(widget.profileModel.profilePhotoUrl),
radius: 16,
),
),
BottomNavigationBarItem(
@@ -134,6 +133,31 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
),
),
),
if (widget.profileModel.roles.contains('consultant'))
BottomNavigationBarItem(
label: 'Консультации',
icon: Padding(
padding: const EdgeInsets.only(bottom: 2),
child: SvgPicture.asset(
colorFilter: ColorFilter.mode(Colors.grey, BlendMode.srcIn),
'assets/icons/consultations.svg',
width: 20,
height: 20,
),
),
activeIcon: Padding(
padding: const EdgeInsets.only(bottom: 2),
child: SvgPicture.asset(
'assets/icons/consultations.svg',
width: 20,
height: 20,
colorFilter: ColorFilter.mode(
appTheme.blueA400,
BlendMode.srcIn,
),
),
),
),
BottomNavigationBarItem(
label: s.chat,
icon: BlocBuilder<NewMessagesCubit, NewMessagesState>(
@@ -154,7 +178,7 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
ImageConstant.icnChat,
width: 20,
height: 20,
colorFilter: currentBottomNavBarIndex == 3
colorFilter: pages[currentBottomNavBarIndex] == '/chat-select-screen'
? ColorFilter.mode(
appTheme.blueA400,
BlendMode.srcIn,
@@ -184,7 +208,7 @@ class _BottomNavigationBarWidgetState extends State<BottomNavigationBarWidget> {
padding: const EdgeInsets.only(bottom: 2),
child: SvgPicture.asset(
ImageConstant.icnBell,
colorFilter: currentBottomNavBarIndex == 4
colorFilter: pages[currentBottomNavBarIndex] == '/notifications-screen'
? ColorFilter.mode(
appTheme.blueA400,
BlendMode.srcIn,

View File

@@ -0,0 +1,82 @@
import 'dart:math';
class ConsultationsEntity {
final String name; // Имя и фамилия пользователя который проходит план
final String planName; // Название плана который проходит
final String clientSlug; // Слаг пользователя который проходит
final String photoUrl; // Ссылка на аватрку пользователя
final DateTime startPlanDate; // Дата начала прохождения плана
final DateTime endPlanDate; // Дата окончания прохождения плана
final int paymentAmount; // Сумма выплаты которую получит консультант
final int currentDayOfPlan; // Текущий день прохождения плана
final int totalDaysOfPlan; // Общее число дней в плане
final List<DayOfPlanEntity> days; // Дни плана
final bool haveUnreadMessages; // Есть ли непрочитанные сообщения в чате от пользователя
ConsultationsEntity({
required this.name,
required this.planName,
required this.clientSlug,
required this.photoUrl,
required this.startPlanDate,
required this.endPlanDate,
required this.paymentAmount,
required this.days,
required this.haveUnreadMessages,
required this.currentDayOfPlan,
required this.totalDaysOfPlan,
});
}
enum LegendOfPlanDay {
Empty, // Дневник не заполнялся и никаких движений в тот день не было.
NotCompleted, // Начат дневник питания, но не закончен.
EndBuyUserDontCheck, // Дневник закончен и прокомментирован консультантом но не просмотрен пользователем.
CompletedButConsultantCommentEmpty, // Дневник заполнен, но не прокомментирован консультантом.
FullCompleted, // Дневник заполнен, прокомментирован консультантом и после этого клиент просмотрел комментарии.
}
class DayOfPlanEntity {
final DateTime dateTime;
final List<LegendOfPlanDay> daysLegends;
DayOfPlanEntity({
required this.dateTime,
required this.daysLegends,
});
}
List<ConsultationsEntity> generateTestData() {
final random = Random();
final List<String> names = ["Иван Петров", "Анна Смирнова", "Дмитрий Соколов", "Елена Козлова", "Алексей Иванов"];
final List<String> plans = ["Похудение", "Набор массы", "Детокс", "Здоровое питание", "Вегетарианство"];
List<ConsultationsEntity> testConsultations = List.generate(20, (index) {
DateTime startDate = DateTime.now().subtract(Duration(days: random.nextInt(30)));
DateTime endDate = startDate.add(Duration(days: 30));
List<DayOfPlanEntity> days = List.generate(30, (i) {
DateTime dayDate = startDate.add(Duration(days: i));
List<LegendOfPlanDay> legends = List.generate(random.nextInt(3) + 1, (j) => LegendOfPlanDay.values[random.nextInt(LegendOfPlanDay.values.length)]);
return DayOfPlanEntity(dateTime: dayDate, daysLegends: legends);
});
return ConsultationsEntity(
name: names[random.nextInt(names.length)],
planName: plans[random.nextInt(plans.length)],
clientSlug: "client_${index + 1}",
startPlanDate: startDate,
endPlanDate: endDate,
paymentAmount: (random.nextInt(10) + 1) * 1000,
days: days,
haveUnreadMessages: random.nextBool(),
photoUrl: '',
currentDayOfPlan: Random().nextInt(6) + 1,
totalDaysOfPlan: Random().nextInt(7) + 14,
);
});
return testConsultations;
}
List<ConsultationsEntity> testData = generateTestData();

View File

@@ -0,0 +1,28 @@
import 'package:eatvio_app/features/consultations/entities/consultations_entity.dart';
import 'package:eatvio_app/features/consultations/presentation/widgets/consultation_card_widget.dart';
import 'package:flutter/material.dart';
class ConsultationsScreen extends StatelessWidget {
const ConsultationsScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFFF0F2F5),
appBar: AppBar(
title: Text('Консультации'),
centerTitle: true,
),
body: ListView.separated(
padding: EdgeInsets.symmetric(vertical: 12),
itemBuilder: (context, index) => ConsultationCardWidget(
consultationsEntity: testData[index],
),
separatorBuilder: (context, index) => const SizedBox(
height: 12,
),
itemCount: testData.length,
),
);
}
}

View File

@@ -0,0 +1,268 @@
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/widgets/custom_divider.dart';
import 'package:eatvio_app/features/consultations/entities/consultations_entity.dart';
import 'package:flutter/material.dart';
import 'package:table_calendar/table_calendar.dart';
class ConsultationCardWidget extends StatelessWidget {
final List<String> titles = const [
'Дневник не заполнялся и никаких движений в тот день не было.',
'Показывает границы плана питания или публичной диеты в будущем.',
'Начат дневник питания, но не закончен.',
'Дневник закончен и прокомментирован консультантом но не просмотрен пользователем.',
'Дневник заполнен, но не прокомментирован консультантом.',
'Дневник заполнен, прокомментирован консультантом и после этого клиент просмотрел комментарии.',
];
final List<Color> legendColors = const [
Colors.white,
Color(0xFFCAC9C9),
Color(0xFFF0C497),
Color(0xFF2F80ED),
Color(0xFFFF9500),
Color(0xFF4CD964),
];
final ConsultationsEntity consultationsEntity;
const ConsultationCardWidget({
super.key,
required this.consultationsEntity,
});
@override
Widget build(BuildContext context) {
return Material(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
CircleAvatar(
radius: 30,
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
consultationsEntity.name,
style: TextStyle(
height: 1,
color: Color(0xFF323F4B),
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 4),
Text(
consultationsEntity.planName,
style: TextStyle(
height: 1,
color: Color(0xFF88949F),
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
],
),
],
),
const SizedBox(height: 12),
Text(
'Анкета пользователя',
style: TextStyle(
color: appTheme.blueA400,
decoration: TextDecoration.underline,
decorationColor: appTheme.blueA400,
fontWeight: FontWeight.w500,
height: 1,
),
),
const SizedBox(height: 12),
CustomDivider(),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'День ${consultationsEntity.currentDayOfPlan} из ${consultationsEntity.totalDaysOfPlan}',
style: TextStyle(
height: 1,
color: Color(0xFF323F4B),
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
GestureDetector(
onTap: () {
showModalBottomSheet(
context: context,
useSafeArea: true,
useRootNavigator: true,
showDragHandle: true,
builder: (context) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Легенда',
style: TextStyle(
height: 1,
fontSize: 20,
fontWeight: FontWeight.w700,
color: Color(0xFF323F4B),
),
),
const SizedBox(height: 16),
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _legendTitle(titles[index], legendColors[index]),
separatorBuilder: (context, index) => SizedBox(height: 16),
itemCount: titles.length,
),
const SizedBox(height: 16),
],
),
),
);
},
child: Icon(
Icons.help_outline_rounded,
color: Color(0xFF323F4B),
size: 20,
),
),
],
),
const SizedBox(height: 16),
TableCalendar(
focusedDay: DateTime.now(),
firstDay: DateTime.utc(2020, 10, 16),
lastDay: DateTime.utc(2030, 3, 14),
locale: 'ru_RU',
startingDayOfWeek: StartingDayOfWeek.monday,
headerVisible: false,
calendarFormat: CalendarFormat.week,
calendarStyle: CalendarStyle(
weekendDecoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
defaultDecoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
selectedDecoration: BoxDecoration(
color: appTheme.blueA400,
shape: BoxShape.circle,
),
todayTextStyle: TextStyle(
color: Colors.black,
),
todayDecoration: BoxDecoration(
border: Border.all(
color: appTheme.blueA400,
width: 2,
),
color: Colors.white,
shape: BoxShape.circle,
),
),
),
const SizedBox(height: 16),
CustomDivider(),
const SizedBox(height: 12),
Text(
'${consultationsEntity.paymentAmount} ₽',
style: TextStyle(
color: Color(0xFF019A3E),
fontSize: 20,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 16),
Row(
children: [
InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () {},
child: Ink(
width: MediaQuery.of(context).size.width - 32 - 52 - 8,
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: appTheme.blueA400,
),
child: Center(
child: Text(
'Комментировать отчёт',
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
),
),
const SizedBox(width: 8),
InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () {},
child: Ink(
height: 52,
width: 52,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Color(0xFFF6F6F6),
),
child: Center(
child: Icon(
Icons.chat_outlined,
size: 24,
),
),
),
),
],
),
],
),
),
);
}
_legendTitle(String text, Color color) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: 20,
width: 20,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: color,
),
),
const SizedBox(width: 12),
Flexible(
child: Text(
text,
style: TextStyle(
height: 1,
fontSize: 16,
fontWeight: FontWeight.w500,
color: Color(0xFF323F4B),
),
),
),
],
);
}
}

View File

@@ -21,7 +21,9 @@ class FeedConsultantModel with _$FeedConsultantModel {
const factory FeedConsultantModel({
required String slug,
required String name,
@JsonKey(name: 'rating_place') required int ratingPlace,
@JsonKey(name: 'additional_info') required String description,
@JsonKey(name: 'rating_place') int? ratingPlace,
@JsonKey(name: 'total_scores') required int totalScores,
@JsonKey(name: 'profile_photo_url') required String photo,
required List<BadgeModel> badges,
}) = _FeedConsultantModel;

View File

@@ -258,8 +258,12 @@ FeedConsultantModel _$FeedConsultantModelFromJson(Map<String, dynamic> json) {
mixin _$FeedConsultantModel {
String get slug => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError;
@JsonKey(name: 'additional_info')
String get description => throw _privateConstructorUsedError;
@JsonKey(name: 'rating_place')
int get ratingPlace => throw _privateConstructorUsedError;
int? get ratingPlace => throw _privateConstructorUsedError;
@JsonKey(name: 'total_scores')
int get totalScores => throw _privateConstructorUsedError;
@JsonKey(name: 'profile_photo_url')
String get photo => throw _privateConstructorUsedError;
List<BadgeModel> get badges => throw _privateConstructorUsedError;
@@ -283,7 +287,9 @@ abstract class $FeedConsultantModelCopyWith<$Res> {
$Res call(
{String slug,
String name,
@JsonKey(name: 'rating_place') int ratingPlace,
@JsonKey(name: 'additional_info') String description,
@JsonKey(name: 'rating_place') int? ratingPlace,
@JsonKey(name: 'total_scores') int totalScores,
@JsonKey(name: 'profile_photo_url') String photo,
List<BadgeModel> badges});
}
@@ -305,7 +311,9 @@ class _$FeedConsultantModelCopyWithImpl<$Res, $Val extends FeedConsultantModel>
$Res call({
Object? slug = null,
Object? name = null,
Object? ratingPlace = null,
Object? description = null,
Object? ratingPlace = freezed,
Object? totalScores = null,
Object? photo = null,
Object? badges = null,
}) {
@@ -318,9 +326,17 @@ class _$FeedConsultantModelCopyWithImpl<$Res, $Val extends FeedConsultantModel>
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
ratingPlace: null == ratingPlace
description: null == description
? _value.description
: description // ignore: cast_nullable_to_non_nullable
as String,
ratingPlace: freezed == ratingPlace
? _value.ratingPlace
: ratingPlace // ignore: cast_nullable_to_non_nullable
as int?,
totalScores: null == totalScores
? _value.totalScores
: totalScores // ignore: cast_nullable_to_non_nullable
as int,
photo: null == photo
? _value.photo
@@ -345,7 +361,9 @@ abstract class _$$FeedConsultantModelImplCopyWith<$Res>
$Res call(
{String slug,
String name,
@JsonKey(name: 'rating_place') int ratingPlace,
@JsonKey(name: 'additional_info') String description,
@JsonKey(name: 'rating_place') int? ratingPlace,
@JsonKey(name: 'total_scores') int totalScores,
@JsonKey(name: 'profile_photo_url') String photo,
List<BadgeModel> badges});
}
@@ -365,7 +383,9 @@ class __$$FeedConsultantModelImplCopyWithImpl<$Res>
$Res call({
Object? slug = null,
Object? name = null,
Object? ratingPlace = null,
Object? description = null,
Object? ratingPlace = freezed,
Object? totalScores = null,
Object? photo = null,
Object? badges = null,
}) {
@@ -378,9 +398,17 @@ class __$$FeedConsultantModelImplCopyWithImpl<$Res>
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
ratingPlace: null == ratingPlace
description: null == description
? _value.description
: description // ignore: cast_nullable_to_non_nullable
as String,
ratingPlace: freezed == ratingPlace
? _value.ratingPlace
: ratingPlace // ignore: cast_nullable_to_non_nullable
as int?,
totalScores: null == totalScores
? _value.totalScores
: totalScores // ignore: cast_nullable_to_non_nullable
as int,
photo: null == photo
? _value.photo
@@ -400,7 +428,9 @@ class _$FeedConsultantModelImpl implements _FeedConsultantModel {
const _$FeedConsultantModelImpl(
{required this.slug,
required this.name,
@JsonKey(name: 'rating_place') required this.ratingPlace,
@JsonKey(name: 'additional_info') required this.description,
@JsonKey(name: 'rating_place') this.ratingPlace,
@JsonKey(name: 'total_scores') required this.totalScores,
@JsonKey(name: 'profile_photo_url') required this.photo,
required final List<BadgeModel> badges})
: _badges = badges;
@@ -413,8 +443,14 @@ class _$FeedConsultantModelImpl implements _FeedConsultantModel {
@override
final String name;
@override
@JsonKey(name: 'additional_info')
final String description;
@override
@JsonKey(name: 'rating_place')
final int ratingPlace;
final int? ratingPlace;
@override
@JsonKey(name: 'total_scores')
final int totalScores;
@override
@JsonKey(name: 'profile_photo_url')
final String photo;
@@ -428,7 +464,7 @@ class _$FeedConsultantModelImpl implements _FeedConsultantModel {
@override
String toString() {
return 'FeedConsultantModel(slug: $slug, name: $name, ratingPlace: $ratingPlace, photo: $photo, badges: $badges)';
return 'FeedConsultantModel(slug: $slug, name: $name, description: $description, ratingPlace: $ratingPlace, totalScores: $totalScores, photo: $photo, badges: $badges)';
}
@override
@@ -438,15 +474,26 @@ class _$FeedConsultantModelImpl implements _FeedConsultantModel {
other is _$FeedConsultantModelImpl &&
(identical(other.slug, slug) || other.slug == slug) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.description, description) ||
other.description == description) &&
(identical(other.ratingPlace, ratingPlace) ||
other.ratingPlace == ratingPlace) &&
(identical(other.totalScores, totalScores) ||
other.totalScores == totalScores) &&
(identical(other.photo, photo) || other.photo == photo) &&
const DeepCollectionEquality().equals(other._badges, _badges));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, slug, name, ratingPlace, photo,
int get hashCode => Object.hash(
runtimeType,
slug,
name,
description,
ratingPlace,
totalScores,
photo,
const DeepCollectionEquality().hash(_badges));
/// Create a copy of FeedConsultantModel
@@ -470,7 +517,9 @@ abstract class _FeedConsultantModel implements FeedConsultantModel {
const factory _FeedConsultantModel(
{required final String slug,
required final String name,
@JsonKey(name: 'rating_place') required final int ratingPlace,
@JsonKey(name: 'additional_info') required final String description,
@JsonKey(name: 'rating_place') final int? ratingPlace,
@JsonKey(name: 'total_scores') required final int totalScores,
@JsonKey(name: 'profile_photo_url') required final String photo,
required final List<BadgeModel> badges}) = _$FeedConsultantModelImpl;
@@ -482,8 +531,14 @@ abstract class _FeedConsultantModel implements FeedConsultantModel {
@override
String get name;
@override
@JsonKey(name: 'additional_info')
String get description;
@override
@JsonKey(name: 'rating_place')
int get ratingPlace;
int? get ratingPlace;
@override
@JsonKey(name: 'total_scores')
int get totalScores;
@override
@JsonKey(name: 'profile_photo_url')
String get photo;

View File

@@ -30,7 +30,9 @@ _$FeedConsultantModelImpl _$$FeedConsultantModelImplFromJson(
_$FeedConsultantModelImpl(
slug: json['slug'] as String,
name: json['name'] as String,
ratingPlace: (json['rating_place'] as num).toInt(),
description: json['additional_info'] as String,
ratingPlace: (json['rating_place'] as num?)?.toInt(),
totalScores: (json['total_scores'] as num).toInt(),
photo: json['profile_photo_url'] as String,
badges: (json['badges'] as List<dynamic>)
.map((e) => BadgeModel.fromJson(e as Map<String, dynamic>))
@@ -42,7 +44,9 @@ Map<String, dynamic> _$$FeedConsultantModelImplToJson(
<String, dynamic>{
'slug': instance.slug,
'name': instance.name,
'additional_info': instance.description,
'rating_place': instance.ratingPlace,
'total_scores': instance.totalScores,
'profile_photo_url': instance.photo,
'badges': instance.badges,
};

View File

@@ -81,7 +81,14 @@ class ImtChartWidget extends StatelessWidget {
series: _getSplineSeries(),
tooltipBehavior: TooltipBehavior(enable: true),
),
if (weightPoints.isEmpty) Text('Индекс Массы Тела рассчитывается при указанных весе и росте'),
if (weightPoints.isEmpty)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Text(
'Индекс Массы Тела рассчитывается при указанных весе и росте',
textAlign: TextAlign.center,
),
),
],
),
],