feat(app): add evaluation history, bot control panel, simulation visualizer, and vendored signalr_core

This commit is contained in:
2026-08-24 21:37:43 +02:00
parent 676496b77d
commit 0894c40f07
113 changed files with 12413 additions and 3613 deletions
@@ -35,7 +35,7 @@ class DashboardScreen extends StatelessWidget {
const SizedBox(height: 24),
TradesStreamWidget(apiClient: apiClient),
const SizedBox(height: 24),
DailyNewsSnapshot(apiClient: apiClient),
DailyNewsSnapshot(apiClient: apiClient, signalRService: signalRService),
],
),
),
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../core/network/api_client.dart';
import '../../../core/network/signalr_service.dart';
import '../../../core/theme/app_theme.dart';
import '../../../core/utils/time_utils.dart';
import '../../../core/widgets/glass_container.dart';
@@ -14,11 +15,13 @@ import '../../news/widgets/article_sentiment_dialog.dart';
class DailyNewsSnapshot extends StatelessWidget {
final ApiClient apiClient;
final SignalRService? signalRService;
final String? backendUrl;
const DailyNewsSnapshot({
super.key,
required this.apiClient,
this.signalRService,
this.backendUrl,
});
@@ -26,7 +29,11 @@ class DailyNewsSnapshot extends StatelessWidget {
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => NewsBloc(
repository: NewsRepository(apiClient: apiClient, backendUrl: backendUrl ?? ApiClient.baseUrl),
repository: NewsRepository(
apiClient: apiClient,
backendUrl: backendUrl ?? ApiClient.baseUrl,
signalRService: signalRService,
),
)..add(FetchNews(date: DateTime.now().toIso8601String().substring(0, 10))),
child: const _DailyNewsSnapshotContent(),
);
@@ -75,7 +75,7 @@ class FavoritesCarousel extends StatelessWidget {
children: [
AssetLogoWidget(
symbolOrName: isin,
imageUrl: fav.image.isNotEmpty ? fav.image : null,
imageUrl: isin.isNotEmpty ? '/api/v1/logo/$isin' : (fav.image.isNotEmpty ? fav.image : null),
size: 24,
),
const SizedBox(width: 8),
@@ -101,7 +101,7 @@ class _TradesStreamWidgetContent extends StatelessWidget {
itemCount: proposals.length,
itemBuilder: (context, index) {
final p = proposals[index];
final isBuy = p.signalType.toUpperCase() == 'BUY' || p.signalType.toUpperCase() == 'LONG';
final isBuy = p.direction.isLong;
final signalColor = isBuy ? AppTheme.primaryEmerald : AppTheme.accentRed;
return GestureDetector(
@@ -110,8 +110,8 @@ class _TradesStreamWidgetContent extends StatelessWidget {
context,
MaterialPageRoute(
builder: (ctx) => AssetDetailScreen(
isin: p.isin,
name: p.companyName,
isin: p.underlyingIsin,
name: p.symbol,
symbol: p.symbol.isNotEmpty ? p.symbol : null,
apiClient: context.read<TradeBloc>().repository.apiClient,
),
@@ -158,35 +158,24 @@ class _TradesStreamWidgetContent extends StatelessWidget {
border: Border.all(color: signalColor.withValues(alpha: 0.3)),
),
child: Text(
p.signalType,
p.direction.label,
style: TextStyle(color: signalColor, fontWeight: FontWeight.bold, fontSize: 11),
),
),
],
),
if (p.companyName.isNotEmpty) ...[
const SizedBox(height: 4),
Text(
p.companyName,
style: TextStyle(color: AppTheme.textMuted, fontSize: 12),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
const SizedBox(height: 4),
Text(
p.underlyingIsin,
style: TextStyle(color: AppTheme.textMuted, fontSize: 12),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const Spacer(),
if (p.reasoning.isNotEmpty) ...[
Text(
p.reasoning,
style: const TextStyle(color: Colors.white70, fontSize: 11, fontStyle: FontStyle.italic),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 10),
],
Row(
children: [
Text(
'KI Score: ${(p.winRate).toStringAsFixed(0)}%',
p.instrumentType.label,
style: TextStyle(color: AppTheme.accentCyan, fontWeight: FontWeight.w600, fontSize: 13),
),
const Spacer(),