292 lines
8.3 KiB
Dart
292 lines
8.3 KiB
Dart
import 'package:fl_chart/fl_chart.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:onufitness/constants/color_constant.dart';
|
|
import 'package:onufitness/constants/text_constant.dart';
|
|
import 'package:onufitness/screens/goals/controllers/goal_controller.dart';
|
|
import 'package:onufitness/screens/home/controllers/home_controller.dart';
|
|
import 'package:onufitness/screens/home/widgets/empty_data_widget.dart';
|
|
import 'package:onufitness/screens/navbar/bottom_nav_bar.dart';
|
|
import 'package:onufitness/utils/custom_sneakbar.dart';
|
|
|
|
class GoalPieChartSection extends StatelessWidget {
|
|
final FitnessController controller;
|
|
final NavigationController bottomNavController;
|
|
final GoalController goalController;
|
|
|
|
const GoalPieChartSection({
|
|
super.key,
|
|
required this.controller,
|
|
required this.bottomNavController,
|
|
required this.goalController,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
padding: EdgeInsets.all(16.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(20.r),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withValues(alpha: 0.05),
|
|
blurRadius: 15,
|
|
offset: Offset(0, 5),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
goalPieChartHeader(context),
|
|
SizedBox(height: 30.h),
|
|
Obx(() {
|
|
if (controller.isGoalPieChartLoading.value) {
|
|
return _buildLoadingChart();
|
|
}
|
|
|
|
if (controller.goalPieChartData.value.data == null ||
|
|
controller.goalPieChartData.value.data!.dataCount!.isEmpty ||
|
|
controller
|
|
.goalPieChartData
|
|
.value
|
|
.data!
|
|
.dataPercentage!
|
|
.isEmpty) {
|
|
return emptyChartWidget();
|
|
}
|
|
|
|
return InkWell(
|
|
onTap: () {
|
|
bottomNavController.changeIndex(2);
|
|
goalController.switchTab(1);
|
|
},
|
|
child: _buildPieChart(),
|
|
);
|
|
}),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget goalPieChartHeader(BuildContext context) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Goal Progress Overview',
|
|
style: TextStyle(
|
|
fontSize: mediumSizeText,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
SizedBox(height: 16.h),
|
|
Row(
|
|
children: [
|
|
Expanded(child: _dateButtonForGoal(context, true)),
|
|
SizedBox(width: 12.w),
|
|
Icon(Icons.arrow_forward, size: 20.w, color: Colors.grey),
|
|
SizedBox(width: 12.w),
|
|
Expanded(child: _dateButtonForGoal(context, false)),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _dateButtonForGoal(BuildContext context, bool isStartDate) {
|
|
return Obx(() {
|
|
DateTime date =
|
|
isStartDate
|
|
? controller.goalPieChartStartDate.value
|
|
: controller.goalPieChartEndDate.value;
|
|
return InkWell(
|
|
onTap: () => _selectDateForGoal(context, isStartDate),
|
|
child: Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 10.h),
|
|
decoration: BoxDecoration(
|
|
border: Border.all(color: Colors.grey[300]!),
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.calendar_today, size: 15.w, color: Colors.grey[600]),
|
|
SizedBox(width: 8.w),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
isStartDate ? 'Start Date' : 'End Date',
|
|
style: TextStyle(
|
|
fontSize: verySmallSizeText,
|
|
color: Colors.grey[600],
|
|
),
|
|
),
|
|
Text(
|
|
DateFormat('MMM dd, yyyy').format(date),
|
|
style: TextStyle(
|
|
fontSize: verySmallSizeText,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
});
|
|
}
|
|
|
|
Future<void> _selectDateForGoal(
|
|
BuildContext context,
|
|
bool isStartDate,
|
|
) async {
|
|
DateTime initialDate =
|
|
isStartDate
|
|
? controller.goalPieChartStartDate.value
|
|
: controller.goalPieChartEndDate.value;
|
|
|
|
final DateTime? picked = await showDatePicker(
|
|
context: context,
|
|
initialDate: initialDate,
|
|
firstDate: DateTime(2020),
|
|
lastDate: DateTime.now(),
|
|
builder: (context, child) {
|
|
return Theme(
|
|
data: Theme.of(context).copyWith(
|
|
colorScheme: ColorScheme.light(
|
|
primary: Color(primaryColor),
|
|
onPrimary: Colors.white,
|
|
),
|
|
),
|
|
child: child!,
|
|
);
|
|
},
|
|
);
|
|
|
|
if (picked != null) {
|
|
if (isStartDate) {
|
|
if (picked.isBefore(controller.goalPieChartEndDate.value)) {
|
|
controller.updatePieChartDateRangeForGoal(
|
|
picked,
|
|
controller.goalPieChartEndDate.value,
|
|
);
|
|
} else {
|
|
customSnackbar(
|
|
title: 'Invalid Date Range',
|
|
message: 'Start date must be before end date',
|
|
duration: 2,
|
|
);
|
|
}
|
|
} else {
|
|
if (picked.isAfter(controller.goalPieChartStartDate.value)) {
|
|
controller.updatePieChartDateRangeForGoal(
|
|
controller.goalPieChartStartDate.value,
|
|
picked,
|
|
);
|
|
} else {
|
|
customSnackbar(
|
|
title: 'Invalid Date Range',
|
|
message: 'End date must be after start date',
|
|
duration: 2,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Widget _buildPieChart() {
|
|
final data = controller.goalPieChartData.value.data!;
|
|
final colors = [
|
|
Color(primaryColor),
|
|
Colors.orange,
|
|
Colors.blue,
|
|
Colors.purple,
|
|
Colors.green,
|
|
Colors.red,
|
|
];
|
|
|
|
List<PieChartSectionData> sections = [];
|
|
for (int i = 0; i < data.labels!.length; i++) {
|
|
sections.add(
|
|
PieChartSectionData(
|
|
value: data.dataPercentage![i],
|
|
title: '${data.dataPercentage![i].toStringAsFixed(2)}%',
|
|
color: colors[i % colors.length],
|
|
radius: 80.r,
|
|
titleStyle: TextStyle(
|
|
fontSize: smallSizeText,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
return Column(
|
|
children: [
|
|
SizedBox(
|
|
height: 250.h,
|
|
child: PieChart(
|
|
PieChartData(
|
|
sections: sections,
|
|
sectionsSpace: 2,
|
|
centerSpaceRadius: 40.r,
|
|
),
|
|
),
|
|
),
|
|
SizedBox(height: 30.h),
|
|
_buildLegend(data.labels!, data.dataCount!, colors),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildLegend(
|
|
List<String> labels,
|
|
List<int> counts,
|
|
List<Color> colors,
|
|
) {
|
|
return Wrap(
|
|
spacing: 16.w,
|
|
runSpacing: 12.h,
|
|
children: List.generate(labels.length, (index) {
|
|
return Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Container(
|
|
width: 16.w,
|
|
height: 16.h,
|
|
decoration: BoxDecoration(
|
|
color: colors[index % colors.length],
|
|
shape: BoxShape.circle,
|
|
),
|
|
),
|
|
SizedBox(width: 8.w),
|
|
Text(
|
|
'${labels[index]} (${counts[index]})',
|
|
style: TextStyle(fontSize: smallSizeText, color: Colors.black87),
|
|
),
|
|
],
|
|
);
|
|
}),
|
|
);
|
|
}
|
|
|
|
Widget _buildLoadingChart() {
|
|
return SizedBox(
|
|
height: 350.h,
|
|
child: Center(
|
|
child: CircularProgressIndicator(color: Color(primaryColor)),
|
|
),
|
|
);
|
|
}
|
|
}
|