feat(app): clean architecture with typed repositories, DTO models and calendar event logos
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../core/theme/app_theme.dart';
|
||||
import '../../../core/widgets/glass_container.dart';
|
||||
import '../../../core/widgets/status_badge.dart';
|
||||
import '../models/news_article_model.dart';
|
||||
|
||||
class FinbertSentimentTab extends StatelessWidget {
|
||||
final NewsArticleModel article;
|
||||
|
||||
const FinbertSentimentTab({super.key, required this.article});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final finbert = article.finbertResult;
|
||||
final double compoundScore = finbert?.score ?? article.sentimentScore;
|
||||
final double confidenceScore = article.confidence;
|
||||
final String label = (finbert?.label ?? article.sentiment).toUpperCase();
|
||||
|
||||
final double posRatio = finbert?.positiveProbability ?? (label == 'POSITIVE' ? 0.8 : 0.1);
|
||||
final double neuRatio = finbert?.neutralProbability ?? (label == 'NEUTRAL' ? 0.8 : 0.1);
|
||||
final double negRatio = finbert?.negativeProbability ?? (label == 'NEGATIVE' ? 0.8 : 0.1);
|
||||
|
||||
final String aiText = finbert?.summarySnippet ?? (article.summary.isNotEmpty ? article.summary : 'FinBERT Sentiment-Analyse verarbeitet.');
|
||||
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.psychology, color: AppTheme.accentCyan, size: 24),
|
||||
const SizedBox(width: 8),
|
||||
const Text('FinBERT NLP Modell', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16, color: Colors.white)),
|
||||
],
|
||||
),
|
||||
StatusBadge.sentiment(label.isNotEmpty ? label : 'NEUTRAL', score: compoundScore),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
GlassContainer(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('Sentiment Verteilung (Wahrscheinlichkeiten):', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13, color: Colors.white)),
|
||||
const SizedBox(height: 12),
|
||||
_buildProbBar('Positiv', posRatio, AppTheme.primaryEmerald),
|
||||
const SizedBox(height: 8),
|
||||
_buildProbBar('Neutral', neuRatio, Colors.amber),
|
||||
const SizedBox(height: 8),
|
||||
_buildProbBar('Negativ', negRatio, AppTheme.accentRed),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
GlassContainer(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text('Konfidenz-Score:', style: TextStyle(color: Colors.white70, fontSize: 13)),
|
||||
Text('${(confidenceScore * 100).toStringAsFixed(1)}%', style: TextStyle(color: AppTheme.accentCyan, fontWeight: FontWeight.bold, fontSize: 14)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text('Compound Sentiment-Wert:', style: TextStyle(color: Colors.white70, fontSize: 13)),
|
||||
Text(compoundScore.toStringAsFixed(2), style: TextStyle(color: compoundScore >= 0 ? AppTheme.primaryEmerald : AppTheme.accentRed, fontWeight: FontWeight.bold, fontSize: 14)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Text('KI-Zusammenfassung & Relevanz:', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14, color: Colors.white)),
|
||||
const SizedBox(height: 8),
|
||||
Text(aiText, style: const TextStyle(color: Colors.white70, fontSize: 13, height: 1.4)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildProbBar(String label, double ratio, Color color) {
|
||||
final pct = (ratio * 100).clamp(0.0, 100.0);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(label, style: TextStyle(color: AppTheme.textMuted, fontSize: 12)),
|
||||
Text('${pct.toStringAsFixed(1)}%', style: TextStyle(color: color, fontWeight: FontWeight.bold, fontSize: 12)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
child: LinearProgressIndicator(
|
||||
value: ratio.clamp(0.0, 1.0),
|
||||
backgroundColor: Colors.white.withValues(alpha: 0.08),
|
||||
valueColor: AlwaysStoppedAnimation<Color>(color),
|
||||
minHeight: 6,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user