nokken/lib/src/features/stats/screens/insights_screen.dart
2025-04-20 11:17:03 -04:00

362 lines
12 KiB
Dart

// SPDX-FileCopyrightText: © 2025 Nøkken.io <nokken.io@proton.me>
// SPDX-License-Identifier: AGPL-3.0
//
// insights_screen.dart
//
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:nokken/src/core/ui/theme/app_colors.dart';
import 'package:nokken/src/core/ui/theme/app_text_styles.dart';
import 'package:nokken/src/core/ui/theme/app_theme.dart';
import 'package:nokken/src/features/stats/models/stat_analysis.dart';
import 'package:nokken/src/features/stats/utils/stat_utils.dart';
import 'package:nokken/src/features/stats/widgets/loading_error_views.dart';
import 'package:nokken/src/features/stats/charts/impact_chart.dart';
import 'package:nokken/src/features/stats/screens/emotion_analysis_screen.dart';
class InsightsScreen extends ConsumerWidget {
final ComprehensiveAnalysis analysis;
final String timeframe;
const InsightsScreen({
super.key,
required this.analysis,
required this.timeframe,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(
title: const Text('Health Insights'),
actions: [
// Add navigation to Health Analysis
IconButton(
icon: const Icon(Icons.analytics_outlined),
tooltip: 'Detailed Analysis',
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => EmotionAnalysisScreen(
initialTimeFrame: timeframe,
),
));
},
),
],
),
body: ListView(
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0),
children: [
// Introduction card with analysis button
_buildIntroCard(context),
const SizedBox(height: 16),
// Top Patterns Section
if (analysis.patterns.isNotEmpty) ...[
_buildHeader('Key Patterns', Icons.trending_up, Colors.indigo),
const SizedBox(height: 4),
...analysis.patterns
.take(3)
.map((pattern) => _buildCompactInsightCard(
title: pattern.name,
description: pattern.description,
icon: StatUtils.getPatternIcon(pattern.type),
color: pattern.patternColor,
strength: pattern.significance,
)),
const SizedBox(height: 16),
],
// Top Correlations Section
if (analysis.relationships.isNotEmpty) ...[
_buildHeader(
'Strongest Correlations', Icons.compare_arrows, Colors.purple),
Padding(
padding: const EdgeInsets.only(left: 26, bottom: 4),
child: Text(
'Shows how two health factors relate to each other',
style: AppTextStyles.bodySmall
.copyWith(fontSize: 10, color: Colors.grey),
),
),
...analysis.relationships
.take(3)
.map((relationship) => _buildCompactInsightCard(
title: relationship.factorNames.join(' & '),
description: relationship.description,
icon: Icons.compare_arrows,
color: relationship.relationshipColor,
strength: relationship.strength.abs(),
)),
const SizedBox(height: 16),
],
// Key Factors Section
if (analysis.factorRankings.isNotEmpty) ...[
_buildHeader('Impact Factors', Icons.bar_chart, Colors.teal),
Padding(
padding: const EdgeInsets.only(left: 26, bottom: 4),
child: Text(
'Shows which factors have the most impact on specific outcomes',
style: AppTextStyles.bodySmall
.copyWith(fontSize: 10, color: Colors.grey),
),
),
...analysis.factorRankings
.take(3)
.map((ranking) => _buildCompactFactorCard(ranking: ranking)),
const SizedBox(height: 16),
],
// Anomalies & Unusual Data
if (analysis.anomalies.isNotEmpty) ...[
_buildHeader(
'Unusual Data Points', Icons.warning_amber, Colors.orange),
const SizedBox(height: 4),
...analysis.anomalies
.take(3)
.map((anomaly) => _buildCompactInsightCard(
title:
'${anomaly.factorName} on ${anomaly.formattedDate}',
description: anomaly.description,
icon: Icons.warning,
color: anomaly.anomalyColor,
strength: anomaly.anomalyScore,
)),
],
],
),
);
}
Widget _buildIntroCard(BuildContext context) {
return Card(
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.blue.withAlpha(20),
shape: BoxShape.circle,
),
child:
const Icon(Icons.info_outline, color: Colors.blue, size: 18),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Health insights based on your tracked data. Insights update as you add more entries.',
style: AppTextStyles.bodySmall,
),
const SizedBox(height: 6),
TextButton(
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
minimumSize: const Size(0, 0),
),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => EmotionAnalysisScreen(
initialTimeFrame: timeframe,
),
));
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Text('View Detailed Analysis'),
const SizedBox(width: 4),
Icon(Icons.arrow_forward, size: 14),
],
),
),
],
),
),
],
),
),
);
}
Widget _buildHeader(String title, IconData icon, Color color) {
return Padding(
padding: const EdgeInsets.only(left: 4.0, bottom: 2.0),
child: Row(
children: [
Icon(icon, color: color, size: 16),
const SizedBox(width: 6),
Text(
title,
style: AppTextStyles.titleSmall.copyWith(color: color),
),
],
),
);
}
Widget _buildCompactInsightCard({
required String title,
required String description,
required IconData icon,
required Color color,
required double strength,
}) {
// Format strength as percentage
final strengthPercent = (strength * 100).toInt();
return Card(
margin: const EdgeInsets.only(bottom: 8),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
elevation: 0,
color: AppColors.surfaceContainer,
child: Padding(
padding: const EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header with strength indicator
Row(
children: [
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: color.withAlpha(30),
shape: BoxShape.circle,
),
child: Icon(
icon,
color: color,
size: 14,
),
),
const SizedBox(width: 8),
Expanded(
child: Text(
title,
style: AppTextStyles.bodySmall.copyWith(
fontWeight: FontWeight.bold,
),
),
),
Container(
padding:
const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: color.withAlpha(20),
borderRadius: BorderRadius.circular(6),
),
child: Text(
'$strengthPercent%',
style: TextStyle(
fontSize: 10,
color: color,
fontWeight: FontWeight.bold,
),
),
),
],
),
const SizedBox(height: 6),
Text(
description,
style: AppTextStyles.bodySmall,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
);
}
Widget _buildCompactFactorCard({
required FactorImpactRanking ranking,
}) {
return Card(
margin: const EdgeInsets.only(bottom: 8),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
elevation: 0,
color: AppColors.surfaceContainer,
child: Padding(
padding: const EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header
Row(
children: [
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: Colors.teal.withAlpha(30),
shape: BoxShape.circle,
),
child: Icon(
Icons.insights,
color: Colors.teal,
size: 14,
),
),
const SizedBox(width: 8),
Expanded(
child: Text(
'Factors affecting ${ranking.targetFactor}',
style: AppTextStyles.bodySmall.copyWith(
fontWeight: FontWeight.bold,
),
),
),
],
),
const SizedBox(height: 6),
// Top factors list (just names)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: ranking.impactingFactors.take(3).map((factor) {
return Padding(
padding: const EdgeInsets.only(bottom: 2),
child: Row(
children: [
Icon(
Icons.arrow_right,
color: factor.impactColor,
size: 12,
),
const SizedBox(width: 2),
Expanded(
child: Text(
factor.name,
style: AppTextStyles.bodySmall,
overflow: TextOverflow.ellipsis,
),
),
Text(
'${(factor.impactScore * 100).toInt()}%',
style: TextStyle(
fontSize: 10,
color: factor.impactColor,
fontWeight: FontWeight.bold,
),
),
],
),
);
}).toList(),
),
],
),
),
);
}
}