202 lines
8.0 KiB
Dart
202 lines
8.0 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:url_launcher/url_launcher.dart';
|
|
import '../models/news_article_model.dart';
|
|
import '../../../core/theme/app_theme.dart';
|
|
import '../../../core/widgets/status_badge.dart';
|
|
import 'finbert_sentiment_tab.dart';
|
|
|
|
class ArticleSentimentDialog extends StatefulWidget {
|
|
final NewsArticleModel articleData;
|
|
|
|
const ArticleSentimentDialog({
|
|
super.key,
|
|
required this.articleData,
|
|
});
|
|
|
|
@override
|
|
State<ArticleSentimentDialog> createState() => _ArticleSentimentDialogState();
|
|
}
|
|
|
|
class _ArticleSentimentDialogState extends State<ArticleSentimentDialog> with SingleTickerProviderStateMixin {
|
|
late TabController _tabController;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_tabController = TabController(length: 2, vsync: this);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_tabController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _openOriginalSource() async {
|
|
final urlStr = widget.articleData.sourceUrl;
|
|
if (urlStr.isNotEmpty) {
|
|
final uri = Uri.parse(urlStr);
|
|
if (await canLaunchUrl(uri)) {
|
|
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final article = widget.articleData;
|
|
final title = article.title.isNotEmpty ? article.title : 'Nachrichtenartikel';
|
|
final author = article.author.isNotEmpty ? article.author : 'Finlytic News';
|
|
final summary = article.summary;
|
|
final sourceUrl = article.sourceUrl;
|
|
final contentRaw = article.contentRaw;
|
|
final publishedAt = "${article.publishedAt.day}.${article.publishedAt.month}.${article.publishedAt.year}";
|
|
final status = article.status.isNotEmpty ? article.status : 'Completed';
|
|
final rawSentiment = article.sentiment;
|
|
|
|
final compoundScore = article.finbertResult?.score ?? article.sentimentScore;
|
|
|
|
final Widget listBadge = rawSentiment.isNotEmpty
|
|
? StatusBadge.sentiment(rawSentiment.toUpperCase(), score: compoundScore)
|
|
: StatusBadge(
|
|
label: status.toUpperCase(),
|
|
color: status.toLowerCase().contains('analyz') ? AppTheme.primaryEmerald : AppTheme.accentCyan,
|
|
);
|
|
|
|
return Dialog(
|
|
backgroundColor: AppTheme.cardSurface,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(16),
|
|
side: BorderSide(color: AppTheme.glassBorder),
|
|
),
|
|
child: Container(
|
|
width: 650,
|
|
height: 600,
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
title,
|
|
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16, color: Colors.white),
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
'Quelle: $author • $publishedAt',
|
|
style: TextStyle(color: AppTheme.textMuted, fontSize: 12),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
listBadge,
|
|
IconButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
icon: const Icon(Icons.close, color: Colors.white70),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
color: AppTheme.glassSurface,
|
|
borderRadius: BorderRadius.circular(10),
|
|
border: Border.all(color: AppTheme.glassBorder),
|
|
),
|
|
child: TabBar(
|
|
controller: _tabController,
|
|
indicatorColor: AppTheme.primaryEmerald,
|
|
labelColor: AppTheme.primaryEmerald,
|
|
unselectedLabelColor: AppTheme.textMuted,
|
|
indicatorSize: TabBarIndicatorSize.tab,
|
|
tabs: const [
|
|
Tab(icon: Icon(Icons.article_outlined, size: 18), text: 'Artikel-Inhalt'),
|
|
Tab(icon: Icon(Icons.psychology_outlined, size: 18), text: 'FinBERT Sentiment'),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Expanded(
|
|
child: TabBarView(
|
|
controller: _tabController,
|
|
children: [
|
|
SingleChildScrollView(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
if (summary.isNotEmpty) ...[
|
|
const Text('Zusammenfassung:', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13, color: Colors.white)),
|
|
const SizedBox(height: 4),
|
|
Container(
|
|
padding: const EdgeInsets.all(10),
|
|
decoration: BoxDecoration(
|
|
color: AppTheme.glassSurface,
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Text(summary, style: const TextStyle(color: Colors.white70, fontSize: 13)),
|
|
),
|
|
const SizedBox(height: 12),
|
|
],
|
|
if (article.matchedAssets.isNotEmpty) ...[
|
|
const Text('Zugeordnete Assets:', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13, color: Colors.white)),
|
|
const SizedBox(height: 6),
|
|
Wrap(
|
|
spacing: 6,
|
|
children: article.matchedAssets.map((asset) {
|
|
return Chip(
|
|
label: Text('${asset.symbol} (${asset.isin})', style: const TextStyle(fontSize: 11, color: Colors.white)),
|
|
backgroundColor: AppTheme.glassSurface,
|
|
side: BorderSide(color: AppTheme.glassBorder),
|
|
);
|
|
}).toList(),
|
|
),
|
|
const SizedBox(height: 12),
|
|
],
|
|
const Text('Vollständiger Artikeltext:', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13, color: Colors.white)),
|
|
const SizedBox(height: 6),
|
|
Text(
|
|
contentRaw.isNotEmpty ? contentRaw : 'Kein vollständiger Text verfügbar.',
|
|
style: const TextStyle(color: Colors.white70, fontSize: 13, height: 1.4),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
FinbertSentimentTab(article: article),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
if (sourceUrl.isNotEmpty)
|
|
TextButton.icon(
|
|
onPressed: _openOriginalSource,
|
|
icon: const Icon(Icons.open_in_new, size: 16),
|
|
label: const Text('Originalquelle im Browser öffnen'),
|
|
style: TextButton.styleFrom(foregroundColor: AppTheme.accentCyan),
|
|
)
|
|
else
|
|
const SizedBox.shrink(),
|
|
ElevatedButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
style: ElevatedButton.styleFrom(backgroundColor: AppTheme.primaryEmerald, foregroundColor: Colors.black),
|
|
child: const Text('Schließen'),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|