|
|
@@ -12,7 +12,8 @@ class StatsScreen extends StatefulWidget {
|
|
|
State<StatsScreen> createState() => _StatsScreenState();
|
|
|
}
|
|
|
|
|
|
-class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin {
|
|
|
+class _StatsScreenState extends State<StatsScreen>
|
|
|
+ with TickerProviderStateMixin {
|
|
|
late TabController _tabController;
|
|
|
bool _isLoading = true;
|
|
|
|
|
|
@@ -48,10 +49,7 @@ class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin
|
|
|
elevation: 0,
|
|
|
leading: IconButton(
|
|
|
onPressed: () => Navigator.of(context).pop(true),
|
|
|
- icon: Icon(
|
|
|
- Icons.arrow_back,
|
|
|
- color: ZenColors.currentPrimaryText,
|
|
|
- ),
|
|
|
+ icon: Icon(Icons.arrow_back, color: ZenColors.currentPrimaryText),
|
|
|
),
|
|
|
title: Text(
|
|
|
AppLocalizations.of(context)!.statistics,
|
|
|
@@ -73,19 +71,19 @@ class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin
|
|
|
],
|
|
|
),
|
|
|
),
|
|
|
- body: _isLoading
|
|
|
- ? Center(
|
|
|
- child: CircularProgressIndicator(
|
|
|
- valueColor: AlwaysStoppedAnimation<Color>(ZenColors.currentButtonBackground),
|
|
|
+ body:
|
|
|
+ _isLoading
|
|
|
+ ? Center(
|
|
|
+ child: CircularProgressIndicator(
|
|
|
+ valueColor: AlwaysStoppedAnimation<Color>(
|
|
|
+ ZenColors.currentButtonBackground,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ : TabBarView(
|
|
|
+ controller: _tabController,
|
|
|
+ children: [_buildOverviewTab(), _buildChartsTab()],
|
|
|
),
|
|
|
- )
|
|
|
- : TabBarView(
|
|
|
- controller: _tabController,
|
|
|
- children: [
|
|
|
- _buildOverviewTab(),
|
|
|
- _buildChartsTab(),
|
|
|
- ],
|
|
|
- ),
|
|
|
);
|
|
|
},
|
|
|
);
|
|
|
@@ -100,11 +98,11 @@ class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin
|
|
|
// Quick Stats Cards
|
|
|
_buildQuickStatsGrid(),
|
|
|
const SizedBox(height: 30),
|
|
|
-
|
|
|
+
|
|
|
// Achievements Section
|
|
|
_buildAchievementsSection(),
|
|
|
const SizedBox(height: 30),
|
|
|
-
|
|
|
+
|
|
|
// Recent Activity
|
|
|
_buildRecentActivitySection(),
|
|
|
],
|
|
|
@@ -125,75 +123,83 @@ class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin
|
|
|
),
|
|
|
),
|
|
|
const SizedBox(height: 15),
|
|
|
- GridView.count(
|
|
|
- crossAxisCount: 2,
|
|
|
- shrinkWrap: true,
|
|
|
- physics: const NeverScrollableScrollPhysics(),
|
|
|
- childAspectRatio: 1.5,
|
|
|
- crossAxisSpacing: 15,
|
|
|
- mainAxisSpacing: 15,
|
|
|
- children: [
|
|
|
- _buildStatCard(
|
|
|
- AppLocalizations.of(context)!.todaysPoints,
|
|
|
- ScoreManager.todayScore.toString(),
|
|
|
- Icons.today,
|
|
|
- ZenColors.currentButtonBackground,
|
|
|
- ),
|
|
|
- _buildStatCard(
|
|
|
- AppLocalizations.of(context)!.totalPoints,
|
|
|
- ScoreManager.totalScore.toString(),
|
|
|
- Icons.stars,
|
|
|
- ZenColors.red,
|
|
|
- ),
|
|
|
- _buildStatCard(
|
|
|
- AppLocalizations.of(context)!.bubblesPopped,
|
|
|
- ScoreManager.totalBubblesPopped.toString(),
|
|
|
- Icons.bubble_chart,
|
|
|
- ZenColors.navyBlue,
|
|
|
- ),
|
|
|
- _buildStatCard(
|
|
|
- AppLocalizations.of(context)!.dailyAverage,
|
|
|
- ScoreManager.averageDailyScore.toStringAsFixed(0),
|
|
|
- Icons.trending_up,
|
|
|
- Colors.green,
|
|
|
- ),
|
|
|
- _buildStatCard(
|
|
|
- AppLocalizations.of(context)!.currentStreak,
|
|
|
- AppLocalizations.of(context)!.streakDays(ScoreManager.currentStreak),
|
|
|
- Icons.local_fire_department,
|
|
|
- Colors.orange,
|
|
|
- ),
|
|
|
- _buildStatCard(
|
|
|
- AppLocalizations.of(context)!.bestDay,
|
|
|
- ScoreManager.bestDayScore.toString(),
|
|
|
- Icons.military_tech,
|
|
|
- Colors.purple,
|
|
|
- ),
|
|
|
- ],
|
|
|
+ LayoutBuilder(
|
|
|
+ builder: (context, constraints) {
|
|
|
+ final screenWidth = constraints.maxWidth;
|
|
|
+ final crossAxisCount = screenWidth < 600 ? 2 : 3;
|
|
|
+ final childAspectRatio = screenWidth < 400 ? 1.3 : 1.5;
|
|
|
+
|
|
|
+ return GridView.count(
|
|
|
+ crossAxisCount: crossAxisCount,
|
|
|
+ shrinkWrap: true,
|
|
|
+ physics: const NeverScrollableScrollPhysics(),
|
|
|
+ childAspectRatio: childAspectRatio,
|
|
|
+ crossAxisSpacing: 15,
|
|
|
+ mainAxisSpacing: 15,
|
|
|
+ children: [
|
|
|
+ _buildStatCard(
|
|
|
+ AppLocalizations.of(context)!.todaysPoints,
|
|
|
+ ScoreManager.todayScore.toString(),
|
|
|
+ Icons.today,
|
|
|
+ ZenColors.currentButtonBackground,
|
|
|
+ ),
|
|
|
+ _buildStatCard(
|
|
|
+ AppLocalizations.of(context)!.totalPoints,
|
|
|
+ ScoreManager.totalScore.toString(),
|
|
|
+ Icons.stars,
|
|
|
+ ZenColors.red,
|
|
|
+ ),
|
|
|
+ _buildStatCard(
|
|
|
+ AppLocalizations.of(context)!.bubblesPopped,
|
|
|
+ ScoreManager.totalBubblesPopped.toString(),
|
|
|
+ Icons.bubble_chart,
|
|
|
+ ZenColors.navyBlue,
|
|
|
+ ),
|
|
|
+ _buildStatCard(
|
|
|
+ AppLocalizations.of(context)!.dailyAverage,
|
|
|
+ ScoreManager.averageDailyScore.toStringAsFixed(0),
|
|
|
+ Icons.trending_up,
|
|
|
+ Colors.green,
|
|
|
+ ),
|
|
|
+ _buildStatCard(
|
|
|
+ AppLocalizations.of(context)!.currentStreak,
|
|
|
+ AppLocalizations.of(
|
|
|
+ context,
|
|
|
+ )!.streakDays(ScoreManager.currentStreak),
|
|
|
+ Icons.local_fire_department,
|
|
|
+ Colors.orange,
|
|
|
+ ),
|
|
|
+ _buildStatCard(
|
|
|
+ AppLocalizations.of(context)!.bestDay,
|
|
|
+ ScoreManager.bestDayScore.toString(),
|
|
|
+ Icons.military_tech,
|
|
|
+ Colors.purple,
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ },
|
|
|
),
|
|
|
],
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- Widget _buildStatCard(String title, String value, IconData icon, Color color) {
|
|
|
+ Widget _buildStatCard(
|
|
|
+ String title,
|
|
|
+ String value,
|
|
|
+ IconData icon,
|
|
|
+ Color color,
|
|
|
+ ) {
|
|
|
return Container(
|
|
|
padding: const EdgeInsets.all(16),
|
|
|
decoration: BoxDecoration(
|
|
|
color: ZenColors.currentUiElements.withValues(alpha: 0.3),
|
|
|
borderRadius: BorderRadius.circular(15),
|
|
|
- border: Border.all(
|
|
|
- color: color.withValues(alpha: 0.3),
|
|
|
- width: 1,
|
|
|
- ),
|
|
|
+ border: Border.all(color: color.withValues(alpha: 0.3), width: 1),
|
|
|
),
|
|
|
child: Column(
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
children: [
|
|
|
- Icon(
|
|
|
- icon,
|
|
|
- color: color,
|
|
|
- size: 28,
|
|
|
- ),
|
|
|
+ Icon(icon, color: color, size: 28),
|
|
|
const SizedBox(height: 8),
|
|
|
Text(
|
|
|
value,
|
|
|
@@ -219,7 +225,7 @@ class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin
|
|
|
|
|
|
Widget _buildAchievementsSection() {
|
|
|
final achievements = _getAchievements();
|
|
|
-
|
|
|
+
|
|
|
return Column(
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
children: [
|
|
|
@@ -259,14 +265,16 @@ class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin
|
|
|
return Container(
|
|
|
padding: const EdgeInsets.all(12),
|
|
|
decoration: BoxDecoration(
|
|
|
- color: unlocked
|
|
|
- ? ZenColors.currentButtonBackground.withValues(alpha: 0.2)
|
|
|
- : ZenColors.currentUiElements.withValues(alpha: 0.1),
|
|
|
+ color:
|
|
|
+ unlocked
|
|
|
+ ? ZenColors.currentButtonBackground.withValues(alpha: 0.2)
|
|
|
+ : ZenColors.currentUiElements.withValues(alpha: 0.1),
|
|
|
borderRadius: BorderRadius.circular(12),
|
|
|
border: Border.all(
|
|
|
- color: unlocked
|
|
|
- ? ZenColors.currentButtonBackground.withValues(alpha: 0.5)
|
|
|
- : ZenColors.currentUiElements.withValues(alpha: 0.3),
|
|
|
+ color:
|
|
|
+ unlocked
|
|
|
+ ? ZenColors.currentButtonBackground.withValues(alpha: 0.5)
|
|
|
+ : ZenColors.currentUiElements.withValues(alpha: 0.3),
|
|
|
width: 1,
|
|
|
),
|
|
|
),
|
|
|
@@ -275,14 +283,20 @@ class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin
|
|
|
children: [
|
|
|
Icon(
|
|
|
icon,
|
|
|
- color: unlocked ? ZenColors.currentButtonBackground : ZenColors.currentMutedText,
|
|
|
+ color:
|
|
|
+ unlocked
|
|
|
+ ? ZenColors.currentButtonBackground
|
|
|
+ : ZenColors.currentMutedText,
|
|
|
size: 32,
|
|
|
),
|
|
|
const SizedBox(height: 8),
|
|
|
Text(
|
|
|
title,
|
|
|
style: TextStyle(
|
|
|
- color: unlocked ? ZenColors.currentPrimaryText : ZenColors.currentMutedText,
|
|
|
+ color:
|
|
|
+ unlocked
|
|
|
+ ? ZenColors.currentPrimaryText
|
|
|
+ : ZenColors.currentMutedText,
|
|
|
fontSize: 11,
|
|
|
fontWeight: unlocked ? FontWeight.w600 : FontWeight.normal,
|
|
|
),
|
|
|
@@ -337,7 +351,7 @@ class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin
|
|
|
|
|
|
Widget _buildRecentActivitySection() {
|
|
|
final lastDays = ScoreManager.getLastDaysScores(7);
|
|
|
-
|
|
|
+
|
|
|
return Column(
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
children: [
|
|
|
@@ -357,55 +371,90 @@ class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin
|
|
|
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.currentButtonBackground : ZenColors.currentSecondaryText,
|
|
|
- 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.currentUiElements.withValues(alpha: 0.3),
|
|
|
- valueColor: AlwaysStoppedAnimation<Color>(
|
|
|
- isToday ? ZenColors.currentButtonBackground : ZenColors.currentUiElements,
|
|
|
- ),
|
|
|
- ),
|
|
|
- ),
|
|
|
- ),
|
|
|
- SizedBox(
|
|
|
- width: 50,
|
|
|
- child: Text(
|
|
|
- entry.value.toString(),
|
|
|
- style: TextStyle(
|
|
|
- color: isToday ? ZenColors.currentButtonBackground : ZenColors.currentPrimaryText,
|
|
|
- fontSize: 14,
|
|
|
- fontWeight: isToday ? FontWeight.bold : FontWeight.normal,
|
|
|
- ),
|
|
|
- textAlign: TextAlign.right,
|
|
|
- ),
|
|
|
+ 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: LayoutBuilder(
|
|
|
+ builder: (context, constraints) {
|
|
|
+ final screenWidth = constraints.maxWidth;
|
|
|
+ final dayWidth =
|
|
|
+ screenWidth * 0.2; // 20% of available width
|
|
|
+ final scoreWidth =
|
|
|
+ screenWidth * 0.15; // 15% of available width
|
|
|
+
|
|
|
+ return Row(
|
|
|
+ children: [
|
|
|
+ SizedBox(
|
|
|
+ width: dayWidth,
|
|
|
+ child: Text(
|
|
|
+ dayName,
|
|
|
+ style: TextStyle(
|
|
|
+ color:
|
|
|
+ isToday
|
|
|
+ ? ZenColors.currentButtonBackground
|
|
|
+ : ZenColors.currentSecondaryText,
|
|
|
+ fontSize: 14,
|
|
|
+ fontWeight:
|
|
|
+ isToday
|
|
|
+ ? FontWeight.bold
|
|
|
+ : FontWeight.normal,
|
|
|
+ ),
|
|
|
+ overflow: TextOverflow.ellipsis,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ Expanded(
|
|
|
+ child: Container(
|
|
|
+ height: 8,
|
|
|
+ margin: const EdgeInsets.symmetric(
|
|
|
+ horizontal: 10,
|
|
|
+ ),
|
|
|
+ child: LinearProgressIndicator(
|
|
|
+ value:
|
|
|
+ entry.value /
|
|
|
+ (ScoreManager.bestDayScore.clamp(
|
|
|
+ 1,
|
|
|
+ double.infinity,
|
|
|
+ )),
|
|
|
+ backgroundColor: ZenColors.currentUiElements
|
|
|
+ .withValues(alpha: 0.3),
|
|
|
+ valueColor: AlwaysStoppedAnimation<Color>(
|
|
|
+ isToday
|
|
|
+ ? ZenColors.currentButtonBackground
|
|
|
+ : ZenColors.currentUiElements,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ SizedBox(
|
|
|
+ width: scoreWidth,
|
|
|
+ child: Text(
|
|
|
+ entry.value.toString(),
|
|
|
+ style: TextStyle(
|
|
|
+ color:
|
|
|
+ isToday
|
|
|
+ ? ZenColors.currentButtonBackground
|
|
|
+ : ZenColors.currentPrimaryText,
|
|
|
+ fontSize: 14,
|
|
|
+ fontWeight:
|
|
|
+ isToday
|
|
|
+ ? FontWeight.bold
|
|
|
+ : FontWeight.normal,
|
|
|
+ ),
|
|
|
+ textAlign: TextAlign.right,
|
|
|
+ overflow: TextOverflow.ellipsis,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ },
|
|
|
),
|
|
|
- ],
|
|
|
- ),
|
|
|
- );
|
|
|
- }).toList(),
|
|
|
+ );
|
|
|
+ }).toList(),
|
|
|
),
|
|
|
),
|
|
|
],
|
|
|
@@ -428,7 +477,9 @@ class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin
|
|
|
|
|
|
Widget _buildDailyChart() {
|
|
|
final lastDays = ScoreManager.getLastDaysScores(14);
|
|
|
- final maxScore = lastDays.fold(0, (max, entry) => entry.value > max ? entry.value : max).clamp(1, double.infinity);
|
|
|
+ final maxScore = lastDays
|
|
|
+ .fold(0, (max, entry) => entry.value > max ? entry.value : max)
|
|
|
+ .clamp(1, double.infinity);
|
|
|
|
|
|
return Column(
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
@@ -442,107 +493,138 @@ class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin
|
|
|
),
|
|
|
),
|
|
|
const SizedBox(height: 15),
|
|
|
- Container(
|
|
|
- height: 300,
|
|
|
- padding: const EdgeInsets.all(16),
|
|
|
- decoration: BoxDecoration(
|
|
|
- color: ZenColors.currentUiElements.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.currentUiElements.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: TextStyle(
|
|
|
- color: ZenColors.currentSecondaryText,
|
|
|
- fontSize: 12,
|
|
|
- ),
|
|
|
- );
|
|
|
- }
|
|
|
- return const Text('');
|
|
|
- },
|
|
|
- ),
|
|
|
- ),
|
|
|
- leftTitles: AxisTitles(
|
|
|
- sideTitles: SideTitles(
|
|
|
- showTitles: true,
|
|
|
- reservedSize: 40,
|
|
|
- getTitlesWidget: (value, meta) {
|
|
|
- return Text(
|
|
|
- value.toInt().toString(),
|
|
|
- style: TextStyle(
|
|
|
- color: ZenColors.currentSecondaryText,
|
|
|
- fontSize: 12,
|
|
|
- ),
|
|
|
- );
|
|
|
- },
|
|
|
- ),
|
|
|
- ),
|
|
|
- topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
|
|
- rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
|
|
|
+ LayoutBuilder(
|
|
|
+ builder: (context, constraints) {
|
|
|
+ final chartHeight = constraints.maxWidth < 400 ? 250.0 : 300.0;
|
|
|
+ final labelFontSize = constraints.maxWidth < 400 ? 10.0 : 12.0;
|
|
|
+
|
|
|
+ return Container(
|
|
|
+ height: chartHeight,
|
|
|
+ padding: const EdgeInsets.all(16),
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: ZenColors.currentUiElements.withValues(alpha: 0.2),
|
|
|
+ borderRadius: BorderRadius.circular(15),
|
|
|
),
|
|
|
- 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.currentButtonBackground.withValues(alpha: 0.8),
|
|
|
- ZenColors.currentButtonBackground,
|
|
|
- ],
|
|
|
- ),
|
|
|
- barWidth: 3,
|
|
|
- dotData: FlDotData(
|
|
|
+ child: LineChart(
|
|
|
+ LineChartData(
|
|
|
+ backgroundColor: Colors.transparent,
|
|
|
+ gridData: FlGridData(
|
|
|
show: true,
|
|
|
- getDotPainter: (spot, percent, barData, index) {
|
|
|
- return FlDotCirclePainter(
|
|
|
- radius: 4,
|
|
|
- color: ZenColors.currentButtonBackground,
|
|
|
- strokeWidth: 2,
|
|
|
- strokeColor: ZenColors.currentPrimaryText,
|
|
|
+ drawVerticalLine: false,
|
|
|
+ getDrawingHorizontalLine: (value) {
|
|
|
+ return FlLine(
|
|
|
+ color: ZenColors.currentUiElements.withValues(
|
|
|
+ alpha: 0.3,
|
|
|
+ ),
|
|
|
+ strokeWidth: 1,
|
|
|
);
|
|
|
},
|
|
|
),
|
|
|
- belowBarData: BarAreaData(
|
|
|
- show: true,
|
|
|
- gradient: LinearGradient(
|
|
|
- begin: Alignment.topCenter,
|
|
|
- end: Alignment.bottomCenter,
|
|
|
- colors: [
|
|
|
- ZenColors.currentButtonBackground.withValues(alpha: 0.3),
|
|
|
- ZenColors.currentButtonBackground.withValues(alpha: 0.1),
|
|
|
- ],
|
|
|
+ titlesData: FlTitlesData(
|
|
|
+ bottomTitles: AxisTitles(
|
|
|
+ sideTitles: SideTitles(
|
|
|
+ showTitles: true,
|
|
|
+ reservedSize: 30,
|
|
|
+ interval: lastDays.length > 10 ? 2 : 1,
|
|
|
+ getTitlesWidget: (value, meta) {
|
|
|
+ if (value.toInt() >= 0 &&
|
|
|
+ value.toInt() < lastDays.length) {
|
|
|
+ final date = DateTime.parse(
|
|
|
+ lastDays[value.toInt()].key,
|
|
|
+ );
|
|
|
+ return Transform.rotate(
|
|
|
+ angle: constraints.maxWidth < 400 ? -0.5 : 0,
|
|
|
+ child: Text(
|
|
|
+ '${date.day}',
|
|
|
+ style: TextStyle(
|
|
|
+ color: ZenColors.currentSecondaryText,
|
|
|
+ fontSize: labelFontSize,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return const Text('');
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ leftTitles: AxisTitles(
|
|
|
+ sideTitles: SideTitles(
|
|
|
+ showTitles: true,
|
|
|
+ reservedSize: 40,
|
|
|
+ getTitlesWidget: (value, meta) {
|
|
|
+ return Text(
|
|
|
+ value.toInt().toString(),
|
|
|
+ style: TextStyle(
|
|
|
+ color: ZenColors.currentSecondaryText,
|
|
|
+ fontSize: labelFontSize,
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ 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.currentButtonBackground.withValues(
|
|
|
+ alpha: 0.8,
|
|
|
+ ),
|
|
|
+ ZenColors.currentButtonBackground,
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ barWidth: 3,
|
|
|
+ dotData: FlDotData(
|
|
|
+ show: true,
|
|
|
+ getDotPainter: (spot, percent, barData, index) {
|
|
|
+ return FlDotCirclePainter(
|
|
|
+ radius: 4,
|
|
|
+ color: ZenColors.currentButtonBackground,
|
|
|
+ strokeWidth: 2,
|
|
|
+ strokeColor: ZenColors.currentPrimaryText,
|
|
|
+ );
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ belowBarData: BarAreaData(
|
|
|
+ show: true,
|
|
|
+ gradient: LinearGradient(
|
|
|
+ begin: Alignment.topCenter,
|
|
|
+ end: Alignment.bottomCenter,
|
|
|
+ colors: [
|
|
|
+ ZenColors.currentButtonBackground.withValues(
|
|
|
+ alpha: 0.3,
|
|
|
+ ),
|
|
|
+ ZenColors.currentButtonBackground.withValues(
|
|
|
+ alpha: 0.1,
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
),
|
|
|
- ],
|
|
|
- ),
|
|
|
- ),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ },
|
|
|
),
|
|
|
],
|
|
|
);
|
|
|
@@ -550,7 +632,9 @@ class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin
|
|
|
|
|
|
Widget _buildWeeklyChart() {
|
|
|
final weeklyScores = ScoreManager.getWeeklyScores(8);
|
|
|
- final maxScore = weeklyScores.fold(0, (max, entry) => entry.value > max ? entry.value : max).clamp(1, double.infinity);
|
|
|
+ final maxScore = weeklyScores
|
|
|
+ .fold(0, (max, entry) => entry.value > max ? entry.value : max)
|
|
|
+ .clamp(1, double.infinity);
|
|
|
|
|
|
return Column(
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
@@ -564,89 +648,105 @@ class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin
|
|
|
),
|
|
|
),
|
|
|
const SizedBox(height: 15),
|
|
|
- Container(
|
|
|
- height: 300,
|
|
|
- padding: const EdgeInsets.all(16),
|
|
|
- decoration: BoxDecoration(
|
|
|
- color: ZenColors.currentUiElements.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.currentUiElements.withValues(alpha: 0.3),
|
|
|
- strokeWidth: 1,
|
|
|
- );
|
|
|
- },
|
|
|
+ LayoutBuilder(
|
|
|
+ builder: (context, constraints) {
|
|
|
+ final chartHeight = constraints.maxWidth < 400 ? 250.0 : 300.0;
|
|
|
+ final labelFontSize = constraints.maxWidth < 400 ? 10.0 : 12.0;
|
|
|
+
|
|
|
+ return Container(
|
|
|
+ height: chartHeight,
|
|
|
+ padding: const EdgeInsets.all(16),
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: ZenColors.currentUiElements.withValues(alpha: 0.2),
|
|
|
+ borderRadius: BorderRadius.circular(15),
|
|
|
),
|
|
|
- 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: TextStyle(
|
|
|
- color: ZenColors.currentSecondaryText,
|
|
|
- fontSize: 12,
|
|
|
- ),
|
|
|
- );
|
|
|
- }
|
|
|
- return const Text('');
|
|
|
- },
|
|
|
- ),
|
|
|
- ),
|
|
|
- leftTitles: AxisTitles(
|
|
|
- sideTitles: SideTitles(
|
|
|
- showTitles: true,
|
|
|
- reservedSize: 40,
|
|
|
- getTitlesWidget: (value, meta) {
|
|
|
- return Text(
|
|
|
- value.toInt().toString(),
|
|
|
- style: TextStyle(
|
|
|
- color: ZenColors.currentSecondaryText,
|
|
|
- fontSize: 12,
|
|
|
+ child: BarChart(
|
|
|
+ BarChartData(
|
|
|
+ backgroundColor: Colors.transparent,
|
|
|
+ gridData: FlGridData(
|
|
|
+ show: true,
|
|
|
+ drawVerticalLine: false,
|
|
|
+ getDrawingHorizontalLine: (value) {
|
|
|
+ return FlLine(
|
|
|
+ color: ZenColors.currentUiElements.withValues(
|
|
|
+ alpha: 0.3,
|
|
|
),
|
|
|
+ strokeWidth: 1,
|
|
|
);
|
|
|
},
|
|
|
),
|
|
|
- ),
|
|
|
- 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,
|
|
|
- ],
|
|
|
+ titlesData: FlTitlesData(
|
|
|
+ bottomTitles: AxisTitles(
|
|
|
+ sideTitles: SideTitles(
|
|
|
+ showTitles: true,
|
|
|
+ reservedSize: 30,
|
|
|
+ getTitlesWidget: (value, meta) {
|
|
|
+ if (value.toInt() >= 0 &&
|
|
|
+ value.toInt() < weeklyScores.length) {
|
|
|
+ return Text(
|
|
|
+ 'W${value.toInt() + 1}',
|
|
|
+ style: TextStyle(
|
|
|
+ color: ZenColors.currentSecondaryText,
|
|
|
+ fontSize: labelFontSize,
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return const Text('');
|
|
|
+ },
|
|
|
),
|
|
|
- width: 20,
|
|
|
- borderRadius: const BorderRadius.only(
|
|
|
- topLeft: Radius.circular(6),
|
|
|
- topRight: Radius.circular(6),
|
|
|
+ ),
|
|
|
+ leftTitles: AxisTitles(
|
|
|
+ sideTitles: SideTitles(
|
|
|
+ showTitles: true,
|
|
|
+ reservedSize: 40,
|
|
|
+ getTitlesWidget: (value, meta) {
|
|
|
+ return Text(
|
|
|
+ value.toInt().toString(),
|
|
|
+ style: TextStyle(
|
|
|
+ color: ZenColors.currentSecondaryText,
|
|
|
+ fontSize: labelFontSize,
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ },
|
|
|
),
|
|
|
),
|
|
|
- ],
|
|
|
- );
|
|
|
- }).toList(),
|
|
|
- ),
|
|
|
- ),
|
|
|
+ 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(),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ },
|
|
|
),
|
|
|
],
|
|
|
);
|
|
|
@@ -654,12 +754,22 @@ class _StatsScreenState extends State<StatsScreen> with TickerProviderStateMixin
|
|
|
|
|
|
String _getDayName(int weekday) {
|
|
|
final l10n = AppLocalizations.of(context)!;
|
|
|
- final days = [l10n.monday, l10n.tuesday, l10n.wednesday, l10n.thursday, l10n.friday, l10n.saturday, l10n.sunday];
|
|
|
+ final days = [
|
|
|
+ l10n.monday,
|
|
|
+ l10n.tuesday,
|
|
|
+ l10n.wednesday,
|
|
|
+ l10n.thursday,
|
|
|
+ l10n.friday,
|
|
|
+ l10n.saturday,
|
|
|
+ l10n.sunday,
|
|
|
+ ];
|
|
|
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;
|
|
|
+ return date.year == now.year &&
|
|
|
+ date.month == now.month &&
|
|
|
+ date.day == now.day;
|
|
|
}
|
|
|
-}
|
|
|
+}
|