|
|
@@ -0,0 +1,657 @@
|
|
|
+import 'package:flutter/material.dart';
|
|
|
+import 'package:fl_chart/fl_chart.dart';
|
|
|
+import '../utils/colors.dart';
|
|
|
+import '../utils/score_manager.dart';
|
|
|
+
|
|
|
+class StatsScreen extends StatefulWidget {
|
|
|
+ const StatsScreen({super.key});
|
|
|
+
|
|
|
+ @override
|
|
|
+ State<StatsScreen> createState() => _StatsScreenState();
|
|
|
+}
|
|
|
+
|
|
|
+class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin {
|
|
|
+ late TabController _tabController;
|
|
|
+ bool _isLoading = true;
|
|
|
+
|
|
|
+ @override
|
|
|
+ void initState() {
|
|
|
+ super.initState();
|
|
|
+ _tabController = TabController(length: 2, vsync: this);
|
|
|
+ _loadData();
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ void dispose() {
|
|
|
+ _tabController.dispose();
|
|
|
+ super.dispose();
|
|
|
+ }
|
|
|
+
|
|
|
+ Future<void> _loadData() async {
|
|
|
+ // Ensure score manager is initialized
|
|
|
+ await ScoreManager.initialize();
|
|
|
+ setState(() {
|
|
|
+ _isLoading = false;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ Widget build(BuildContext context) {
|
|
|
+ return Scaffold(
|
|
|
+ backgroundColor: ZenColors.appBackground,
|
|
|
+ appBar: AppBar(
|
|
|
+ backgroundColor: ZenColors.appBackground,
|
|
|
+ elevation: 0,
|
|
|
+ leading: IconButton(
|
|
|
+ onPressed: () => Navigator.of(context).pop(true),
|
|
|
+ icon: const Icon(
|
|
|
+ Icons.arrow_back,
|
|
|
+ color: ZenColors.primaryText,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ title: const Text(
|
|
|
+ 'Statistics',
|
|
|
+ style: TextStyle(
|
|
|
+ color: ZenColors.primaryText,
|
|
|
+ fontSize: 24,
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ centerTitle: true,
|
|
|
+ bottom: TabBar(
|
|
|
+ controller: _tabController,
|
|
|
+ labelColor: ZenColors.buttonBackground,
|
|
|
+ unselectedLabelColor: ZenColors.secondaryText,
|
|
|
+ indicatorColor: ZenColors.buttonBackground,
|
|
|
+ tabs: const [
|
|
|
+ Tab(text: 'Overview'),
|
|
|
+ Tab(text: 'Charts'),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ body: _isLoading
|
|
|
+ ? const Center(
|
|
|
+ child: CircularProgressIndicator(
|
|
|
+ valueColor: AlwaysStoppedAnimation<Color>(ZenColors.buttonBackground),
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ : TabBarView(
|
|
|
+ controller: _tabController,
|
|
|
+ children: [
|
|
|
+ _buildOverviewTab(),
|
|
|
+ _buildChartsTab(),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget _buildOverviewTab() {
|
|
|
+ return SingleChildScrollView(
|
|
|
+ padding: const EdgeInsets.all(20),
|
|
|
+ child: Column(
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
+ children: [
|
|
|
+ // Quick Stats Cards
|
|
|
+ _buildQuickStatsGrid(),
|
|
|
+ const SizedBox(height: 30),
|
|
|
+
|
|
|
+ // Achievements Section
|
|
|
+ _buildAchievementsSection(),
|
|
|
+ const SizedBox(height: 30),
|
|
|
+
|
|
|
+ // Recent Activity
|
|
|
+ _buildRecentActivitySection(),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget _buildQuickStatsGrid() {
|
|
|
+ return Column(
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
+ children: [
|
|
|
+ const Text(
|
|
|
+ 'Your Relaxation Journey',
|
|
|
+ style: TextStyle(
|
|
|
+ color: ZenColors.primaryText,
|
|
|
+ fontSize: 22,
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 15),
|
|
|
+ GridView.count(
|
|
|
+ crossAxisCount: 2,
|
|
|
+ shrinkWrap: true,
|
|
|
+ physics: const NeverScrollableScrollPhysics(),
|
|
|
+ childAspectRatio: 1.5,
|
|
|
+ crossAxisSpacing: 15,
|
|
|
+ mainAxisSpacing: 15,
|
|
|
+ children: [
|
|
|
+ _buildStatCard(
|
|
|
+ 'Today\'s Points',
|
|
|
+ ScoreManager.todayScore.toString(),
|
|
|
+ Icons.today,
|
|
|
+ ZenColors.buttonBackground,
|
|
|
+ ),
|
|
|
+ _buildStatCard(
|
|
|
+ 'Total Points',
|
|
|
+ ScoreManager.totalScore.toString(),
|
|
|
+ Icons.stars,
|
|
|
+ ZenColors.red,
|
|
|
+ ),
|
|
|
+ _buildStatCard(
|
|
|
+ 'Bubbles Popped',
|
|
|
+ ScoreManager.totalBubblesPopped.toString(),
|
|
|
+ Icons.bubble_chart,
|
|
|
+ ZenColors.navyBlue,
|
|
|
+ ),
|
|
|
+ _buildStatCard(
|
|
|
+ 'Daily Average',
|
|
|
+ ScoreManager.averageDailyScore.toStringAsFixed(0),
|
|
|
+ Icons.trending_up,
|
|
|
+ Colors.green,
|
|
|
+ ),
|
|
|
+ _buildStatCard(
|
|
|
+ 'Current Streak',
|
|
|
+ '${ScoreManager.currentStreak} days',
|
|
|
+ Icons.local_fire_department,
|
|
|
+ Colors.orange,
|
|
|
+ ),
|
|
|
+ _buildStatCard(
|
|
|
+ 'Best Day',
|
|
|
+ ScoreManager.bestDayScore.toString(),
|
|
|
+ Icons.military_tech,
|
|
|
+ Colors.purple,
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget _buildStatCard(String title, String value, IconData icon, Color color) {
|
|
|
+ return Container(
|
|
|
+ padding: const EdgeInsets.all(16),
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: ZenColors.uiElements.withValues(alpha: 0.3),
|
|
|
+ borderRadius: BorderRadius.circular(15),
|
|
|
+ border: Border.all(
|
|
|
+ color: color.withValues(alpha: 0.3),
|
|
|
+ width: 1,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ child: Column(
|
|
|
+ mainAxisAlignment: MainAxisAlignment.center,
|
|
|
+ children: [
|
|
|
+ Icon(
|
|
|
+ icon,
|
|
|
+ color: color,
|
|
|
+ size: 28,
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 8),
|
|
|
+ Text(
|
|
|
+ value,
|
|
|
+ style: const TextStyle(
|
|
|
+ color: ZenColors.primaryText,
|
|
|
+ fontSize: 20,
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 4),
|
|
|
+ Text(
|
|
|
+ title,
|
|
|
+ style: TextStyle(
|
|
|
+ color: ZenColors.secondaryText,
|
|
|
+ fontSize: 12,
|
|
|
+ ),
|
|
|
+ textAlign: TextAlign.center,
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget _buildAchievementsSection() {
|
|
|
+ final achievements = _getAchievements();
|
|
|
+
|
|
|
+ return Column(
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
+ children: [
|
|
|
+ const Text(
|
|
|
+ 'Achievements',
|
|
|
+ style: TextStyle(
|
|
|
+ color: ZenColors.primaryText,
|
|
|
+ fontSize: 22,
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 15),
|
|
|
+ Container(
|
|
|
+ height: 120,
|
|
|
+ child: ListView.builder(
|
|
|
+ scrollDirection: Axis.horizontal,
|
|
|
+ itemCount: achievements.length,
|
|
|
+ itemBuilder: (context, index) {
|
|
|
+ final achievement = achievements[index];
|
|
|
+ return Container(
|
|
|
+ width: 100,
|
|
|
+ margin: const EdgeInsets.only(right: 15),
|
|
|
+ child: _buildAchievementCard(
|
|
|
+ achievement['title']!,
|
|
|
+ achievement['icon'] as IconData,
|
|
|
+ achievement['unlocked'] as bool,
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget _buildAchievementCard(String title, IconData icon, bool unlocked) {
|
|
|
+ return Container(
|
|
|
+ padding: const EdgeInsets.all(12),
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: unlocked
|
|
|
+ ? ZenColors.buttonBackground.withValues(alpha: 0.2)
|
|
|
+ : ZenColors.uiElements.withValues(alpha: 0.1),
|
|
|
+ borderRadius: BorderRadius.circular(12),
|
|
|
+ border: Border.all(
|
|
|
+ color: unlocked
|
|
|
+ ? ZenColors.buttonBackground.withValues(alpha: 0.5)
|
|
|
+ : ZenColors.uiElements.withValues(alpha: 0.3),
|
|
|
+ width: 1,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ child: Column(
|
|
|
+ mainAxisAlignment: MainAxisAlignment.center,
|
|
|
+ children: [
|
|
|
+ Icon(
|
|
|
+ icon,
|
|
|
+ color: unlocked ? ZenColors.buttonBackground : ZenColors.mutedText,
|
|
|
+ size: 32,
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 8),
|
|
|
+ Text(
|
|
|
+ title,
|
|
|
+ style: TextStyle(
|
|
|
+ color: unlocked ? ZenColors.primaryText : ZenColors.mutedText,
|
|
|
+ fontSize: 11,
|
|
|
+ fontWeight: unlocked ? FontWeight.w600 : FontWeight.normal,
|
|
|
+ ),
|
|
|
+ textAlign: TextAlign.center,
|
|
|
+ maxLines: 2,
|
|
|
+ overflow: TextOverflow.ellipsis,
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Map<String, dynamic>> _getAchievements() {
|
|
|
+ final totalScore = ScoreManager.totalScore;
|
|
|
+ final streak = ScoreManager.currentStreak;
|
|
|
+ final bubbles = ScoreManager.totalBubblesPopped;
|
|
|
+
|
|
|
+ return [
|
|
|
+ {
|
|
|
+ 'title': 'First Steps',
|
|
|
+ 'icon': Icons.baby_changing_station,
|
|
|
+ 'unlocked': totalScore >= 10,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ 'title': 'Zen Apprentice',
|
|
|
+ 'icon': Icons.self_improvement,
|
|
|
+ 'unlocked': totalScore >= 100,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ 'title': 'Bubble Master',
|
|
|
+ 'icon': Icons.bubble_chart,
|
|
|
+ 'unlocked': bubbles >= 100,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ 'title': 'Consistent',
|
|
|
+ 'icon': Icons.calendar_today,
|
|
|
+ 'unlocked': streak >= 3,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ 'title': 'Dedicated',
|
|
|
+ 'icon': Icons.local_fire_department,
|
|
|
+ 'unlocked': streak >= 7,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ 'title': 'Zen Master',
|
|
|
+ 'icon': Icons.psychology,
|
|
|
+ 'unlocked': totalScore >= 1000,
|
|
|
+ },
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget _buildRecentActivitySection() {
|
|
|
+ final lastDays = ScoreManager.getLastDaysScores(7);
|
|
|
+
|
|
|
+ return Column(
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
+ children: [
|
|
|
+ const Text(
|
|
|
+ 'Last 7 Days',
|
|
|
+ style: TextStyle(
|
|
|
+ color: ZenColors.primaryText,
|
|
|
+ fontSize: 22,
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 15),
|
|
|
+ Container(
|
|
|
+ padding: const EdgeInsets.all(16),
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: ZenColors.uiElements.withValues(alpha: 0.2),
|
|
|
+ borderRadius: BorderRadius.circular(15),
|
|
|
+ ),
|
|
|
+ child: Column(
|
|
|
+ children: lastDays.map((entry) {
|
|
|
+ final date = DateTime.parse(entry.key);
|
|
|
+ final dayName = _getDayName(date.weekday);
|
|
|
+ final isToday = _isToday(date);
|
|
|
+
|
|
|
+ return Padding(
|
|
|
+ padding: const EdgeInsets.symmetric(vertical: 4),
|
|
|
+ child: Row(
|
|
|
+ children: [
|
|
|
+ SizedBox(
|
|
|
+ width: 60,
|
|
|
+ child: Text(
|
|
|
+ dayName,
|
|
|
+ style: TextStyle(
|
|
|
+ color: isToday ? ZenColors.buttonBackground : ZenColors.secondaryText,
|
|
|
+ fontSize: 14,
|
|
|
+ fontWeight: isToday ? FontWeight.bold : FontWeight.normal,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ Expanded(
|
|
|
+ child: Container(
|
|
|
+ height: 8,
|
|
|
+ margin: const EdgeInsets.symmetric(horizontal: 10),
|
|
|
+ child: LinearProgressIndicator(
|
|
|
+ value: entry.value / (ScoreManager.bestDayScore.clamp(1, double.infinity)),
|
|
|
+ backgroundColor: ZenColors.uiElements.withValues(alpha: 0.3),
|
|
|
+ valueColor: AlwaysStoppedAnimation<Color>(
|
|
|
+ isToday ? ZenColors.buttonBackground : ZenColors.uiElements,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ SizedBox(
|
|
|
+ width: 50,
|
|
|
+ child: Text(
|
|
|
+ entry.value.toString(),
|
|
|
+ style: TextStyle(
|
|
|
+ color: isToday ? ZenColors.buttonBackground : ZenColors.primaryText,
|
|
|
+ fontSize: 14,
|
|
|
+ fontWeight: isToday ? FontWeight.bold : FontWeight.normal,
|
|
|
+ ),
|
|
|
+ textAlign: TextAlign.right,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }).toList(),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget _buildChartsTab() {
|
|
|
+ return SingleChildScrollView(
|
|
|
+ padding: const EdgeInsets.all(20),
|
|
|
+ child: Column(
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
+ children: [
|
|
|
+ _buildDailyChart(),
|
|
|
+ const SizedBox(height: 30),
|
|
|
+ _buildWeeklyChart(),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget _buildDailyChart() {
|
|
|
+ final lastDays = ScoreManager.getLastDaysScores(14);
|
|
|
+ final maxScore = lastDays.fold(0, (max, entry) => entry.value > max ? entry.value : max).clamp(1, double.infinity);
|
|
|
+
|
|
|
+ return Column(
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
+ children: [
|
|
|
+ const Text(
|
|
|
+ 'Daily Progress (Last 14 Days)',
|
|
|
+ style: TextStyle(
|
|
|
+ color: ZenColors.primaryText,
|
|
|
+ fontSize: 22,
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 15),
|
|
|
+ Container(
|
|
|
+ height: 300,
|
|
|
+ padding: const EdgeInsets.all(16),
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: ZenColors.uiElements.withValues(alpha: 0.2),
|
|
|
+ borderRadius: BorderRadius.circular(15),
|
|
|
+ ),
|
|
|
+ child: LineChart(
|
|
|
+ LineChartData(
|
|
|
+ backgroundColor: Colors.transparent,
|
|
|
+ gridData: FlGridData(
|
|
|
+ show: true,
|
|
|
+ drawVerticalLine: false,
|
|
|
+ getDrawingHorizontalLine: (value) {
|
|
|
+ return FlLine(
|
|
|
+ color: ZenColors.uiElements.withValues(alpha: 0.3),
|
|
|
+ strokeWidth: 1,
|
|
|
+ );
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ titlesData: FlTitlesData(
|
|
|
+ bottomTitles: AxisTitles(
|
|
|
+ sideTitles: SideTitles(
|
|
|
+ showTitles: true,
|
|
|
+ getTitlesWidget: (value, meta) {
|
|
|
+ if (value.toInt() >= 0 && value.toInt() < lastDays.length) {
|
|
|
+ final date = DateTime.parse(lastDays[value.toInt()].key);
|
|
|
+ return Text(
|
|
|
+ '${date.day}',
|
|
|
+ style: const TextStyle(
|
|
|
+ color: ZenColors.secondaryText,
|
|
|
+ fontSize: 12,
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return const Text('');
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ leftTitles: AxisTitles(
|
|
|
+ sideTitles: SideTitles(
|
|
|
+ showTitles: true,
|
|
|
+ reservedSize: 40,
|
|
|
+ getTitlesWidget: (value, meta) {
|
|
|
+ return Text(
|
|
|
+ value.toInt().toString(),
|
|
|
+ style: const TextStyle(
|
|
|
+ color: ZenColors.secondaryText,
|
|
|
+ fontSize: 12,
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
|
|
+ rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
|
|
+ ),
|
|
|
+ borderData: FlBorderData(show: false),
|
|
|
+ minX: 0,
|
|
|
+ maxX: (lastDays.length - 1).toDouble(),
|
|
|
+ minY: 0,
|
|
|
+ maxY: maxScore.toDouble(),
|
|
|
+ lineBarsData: [
|
|
|
+ LineChartBarData(
|
|
|
+ spots: lastDays.asMap().entries.map((entry) {
|
|
|
+ return FlSpot(entry.key.toDouble(), entry.value.value.toDouble());
|
|
|
+ }).toList(),
|
|
|
+ isCurved: true,
|
|
|
+ gradient: LinearGradient(
|
|
|
+ colors: [
|
|
|
+ ZenColors.buttonBackground.withValues(alpha: 0.8),
|
|
|
+ ZenColors.buttonBackground,
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ barWidth: 3,
|
|
|
+ dotData: FlDotData(
|
|
|
+ show: true,
|
|
|
+ getDotPainter: (spot, percent, barData, index) {
|
|
|
+ return FlDotCirclePainter(
|
|
|
+ radius: 4,
|
|
|
+ color: ZenColors.buttonBackground,
|
|
|
+ strokeWidth: 2,
|
|
|
+ strokeColor: ZenColors.primaryText,
|
|
|
+ );
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ belowBarData: BarAreaData(
|
|
|
+ show: true,
|
|
|
+ gradient: LinearGradient(
|
|
|
+ begin: Alignment.topCenter,
|
|
|
+ end: Alignment.bottomCenter,
|
|
|
+ colors: [
|
|
|
+ ZenColors.buttonBackground.withValues(alpha: 0.3),
|
|
|
+ ZenColors.buttonBackground.withValues(alpha: 0.1),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget _buildWeeklyChart() {
|
|
|
+ final weeklyScores = ScoreManager.getWeeklyScores(8);
|
|
|
+ final maxScore = weeklyScores.fold(0, (max, entry) => entry.value > max ? entry.value : max).clamp(1, double.infinity);
|
|
|
+
|
|
|
+ return Column(
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
+ children: [
|
|
|
+ const Text(
|
|
|
+ 'Weekly Summary (Last 8 Weeks)',
|
|
|
+ style: TextStyle(
|
|
|
+ color: ZenColors.primaryText,
|
|
|
+ fontSize: 22,
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 15),
|
|
|
+ Container(
|
|
|
+ height: 300,
|
|
|
+ padding: const EdgeInsets.all(16),
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: ZenColors.uiElements.withValues(alpha: 0.2),
|
|
|
+ borderRadius: BorderRadius.circular(15),
|
|
|
+ ),
|
|
|
+ child: BarChart(
|
|
|
+ BarChartData(
|
|
|
+ backgroundColor: Colors.transparent,
|
|
|
+ gridData: FlGridData(
|
|
|
+ show: true,
|
|
|
+ drawVerticalLine: false,
|
|
|
+ getDrawingHorizontalLine: (value) {
|
|
|
+ return FlLine(
|
|
|
+ color: ZenColors.uiElements.withValues(alpha: 0.3),
|
|
|
+ strokeWidth: 1,
|
|
|
+ );
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ titlesData: FlTitlesData(
|
|
|
+ bottomTitles: AxisTitles(
|
|
|
+ sideTitles: SideTitles(
|
|
|
+ showTitles: true,
|
|
|
+ getTitlesWidget: (value, meta) {
|
|
|
+ if (value.toInt() >= 0 && value.toInt() < weeklyScores.length) {
|
|
|
+ return Text(
|
|
|
+ 'W${value.toInt() + 1}',
|
|
|
+ style: const TextStyle(
|
|
|
+ color: ZenColors.secondaryText,
|
|
|
+ fontSize: 12,
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return const Text('');
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ leftTitles: AxisTitles(
|
|
|
+ sideTitles: SideTitles(
|
|
|
+ showTitles: true,
|
|
|
+ reservedSize: 40,
|
|
|
+ getTitlesWidget: (value, meta) {
|
|
|
+ return Text(
|
|
|
+ value.toInt().toString(),
|
|
|
+ style: const TextStyle(
|
|
|
+ color: ZenColors.secondaryText,
|
|
|
+ fontSize: 12,
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
|
|
+ rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
|
|
+ ),
|
|
|
+ borderData: FlBorderData(show: false),
|
|
|
+ maxY: maxScore.toDouble(),
|
|
|
+ barGroups: weeklyScores.asMap().entries.map((entry) {
|
|
|
+ return BarChartGroupData(
|
|
|
+ x: entry.key,
|
|
|
+ barRods: [
|
|
|
+ BarChartRodData(
|
|
|
+ toY: entry.value.value.toDouble(),
|
|
|
+ gradient: LinearGradient(
|
|
|
+ begin: Alignment.bottomCenter,
|
|
|
+ end: Alignment.topCenter,
|
|
|
+ colors: [
|
|
|
+ ZenColors.navyBlue.withValues(alpha: 0.8),
|
|
|
+ ZenColors.navyBlue,
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ width: 20,
|
|
|
+ borderRadius: const BorderRadius.only(
|
|
|
+ topLeft: Radius.circular(6),
|
|
|
+ topRight: Radius.circular(6),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ }).toList(),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ String _getDayName(int weekday) {
|
|
|
+ const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
|
|
+ return days[weekday - 1];
|
|
|
+ }
|
|
|
+
|
|
|
+ bool _isToday(DateTime date) {
|
|
|
+ final now = DateTime.now();
|
|
|
+ return date.year == now.year && date.month == now.month && date.day == now.day;
|
|
|
+ }
|
|
|
+}
|