import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../core/theme/app_theme.dart'; import '../../../../core/widgets/glass_container.dart'; import '../../../../core/widgets/status_badge.dart'; import '../../bloc/technical/asset_technical_bloc.dart'; import '../../bloc/technical/asset_technical_event.dart'; import '../../bloc/technical/asset_technical_state.dart'; import '../../utils/metric_explanations.dart'; import '../../utils/pattern_explanations.dart'; import '../../widgets/chart/candlestick_chart.dart'; class TechnicalTab extends StatefulWidget { final String isin; final String? symbol; final bool isDesktopLeftPanel; const TechnicalTab({ super.key, this.symbol, this.isDesktopLeftPanel = false, required this.isin, }); @override State createState() => _TechnicalTabState(); } class _TechnicalTabState extends State { bool _showSma50 = true; bool _showSma200 = true; bool _showEma = true; bool _showPatterns = true; bool _showSignals = true; bool _showSupertrend = true; // Set of disabled pattern indices for individual toggling final Set _disabledPatternIndices = {}; @override void initState() { super.initState(); } @override void didUpdateWidget(covariant TechnicalTab oldWidget) { super.didUpdateWidget(oldWidget); } @override Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { if (state is AssetTechnicalLoading) { return Center( child: CircularProgressIndicator(color: AppTheme.primaryEmerald)); } if (state is AssetTechnicalError) { return Center( child: GlassContainer( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.show_chart, color: AppTheme.accentRed, size: 48), const SizedBox(height: 12), Text( 'Fehler beim Laden der Technischen Analyse: ${state.message}', style: const TextStyle(color: Colors.white70)), const SizedBox(height: 16), ElevatedButton.icon( onPressed: () => context.read().add( LoadAssetTechnical(widget.isin, ticker: widget.symbol, forceRefresh: true)), icon: const Icon(Icons.refresh), label: const Text('Erneut versuchen'), ), ], ), ), ); } if (state is AssetTechnicalLoaded) { final data = state.data; List candles = []; List patterns = []; List signals = []; List indicators = []; if (data != null) { candles = data.candles .map((c) => CandleModel( time: c.timestamp, open: c.open, high: c.high, low: c.low, close: c.close, volume: c.volume)) .toList(); patterns = data.patterns .map((p) => ChartPatternModel( type: p.type, upperLine: p.upperLine.map((pt) => PatternPoint(pt.time, pt.price)).toList(), lowerLine: p.lowerLine.map((pt) => PatternPoint(pt.time, pt.price)).toList(), )) .toList(); signals = data.signals .map((s) => StrategySignalModel( type: 'strategy', timestamp: s.date, direction: s.type, price: s.price, description: s.title)) .toList(); indicators = data.indicators .map((i) => IndicatorModel( timestamp: i.timestamp, ema20: i.ema20, sma50: i.sma50, sma200: i.sma200, supertrendUpper: i.supertrendUpper, supertrendLower: i.supertrendLower, supertrendDirection: i.supertrendDirection)) .toList(); } // Filter patterns according to individual checkbox states final activePatterns = [ for (int i = 0; i < patterns.length; i++) if (!_disabledPatternIndices.contains(i)) patterns[i] ]; return SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Glassmorphic Indicator & Pattern Control Ribbon GlassContainer( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ _buildIndicatorChip( 'EMA (20)', _showEma, (v) => setState(() => _showEma = v), Colors.blueAccent), const SizedBox(width: 6), _buildIndicatorChip( 'SMA (50)', _showSma50, (v) => setState(() => _showSma50 = v), Colors.orangeAccent), const SizedBox(width: 6), _buildIndicatorChip( 'SMA (200)', _showSma200, (v) => setState(() => _showSma200 = v), Colors.redAccent), const SizedBox(width: 6), _buildIndicatorChip( 'Supertrend', _showSupertrend, (v) => setState(() => _showSupertrend = v), AppTheme.primaryEmerald), const SizedBox(width: 6), _buildIndicatorChip( 'Alle Muster', _showPatterns, (v) => setState(() => _showPatterns = v), Colors.amberAccent), const SizedBox(width: 6), _buildIndicatorChip( 'Signale', _showSignals, (v) => setState(() => _showSignals = v), AppTheme.accentCyan), ], ), ), ), const SizedBox(height: 8), // Interactive Candlestick Chart SizedBox( height: 380, child: CandlestickChart( candles: candles, patterns: activePatterns, signals: signals, indicators: indicators, showPatterns: _showPatterns, showEma: _showEma, showSma50: _showSma50, showSma200: _showSma200, showSignals: _showSignals, showSupertrend: _showSupertrend, ), ), const SizedBox(height: 16), // Dedicated Chart Patterns & Signal Description List Section Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Icon(Icons.architecture_outlined, color: AppTheme.primaryEmerald, size: 20), const SizedBox(width: 8), const Text('Erkannte Chart-Muster & Signale', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white)), ], ), if (patterns.isNotEmpty) TextButton.icon( onPressed: () { setState(() { if (_disabledPatternIndices.length == patterns.length) { _disabledPatternIndices.clear(); } else { _disabledPatternIndices.addAll( List.generate( patterns.length, (i) => i)); } }); }, icon: Icon( _disabledPatternIndices.isEmpty ? Icons.deselect : Icons.select_all, size: 16, color: Colors.amberAccent), label: Text( _disabledPatternIndices.isEmpty ? 'Alle abwählen' : 'Alle anwählen', style: const TextStyle( color: Colors.amberAccent, fontSize: 12)), ), ], ), const SizedBox(height: 12), if (patterns.isEmpty && signals.isEmpty) GlassContainer( padding: const EdgeInsets.all(16), child: Center( child: Text( 'Zurzeit wurden keine akuten Formationen oder Strategie-Signale identifiziert.', style: TextStyle( color: AppTheme.textMuted, fontSize: 12)), ), ) else ...[ if (patterns.isNotEmpty) ...[ Text( 'Formationen & Trendlinien (Mit Checkbox im Chart schalten):', style: TextStyle( color: AppTheme.textSecondary, fontWeight: FontWeight.w600, fontSize: 13)), const SizedBox(height: 6), ...List.generate( patterns.length, (index) => _buildPatternCard(patterns[index], index)), const SizedBox(height: 12), ], if (signals.isNotEmpty) ...[ Text('Strategie-Signale:', style: TextStyle( color: AppTheme.textSecondary, fontWeight: FontWeight.w600, fontSize: 13)), const SizedBox(height: 6), ...signals.map((s) => _buildSignalCard(s)), ], ], ], ), ), const SizedBox(height: 16), ], ), ); } return Center( child: Text('Keine technisches Indikatoren verfügbar', style: TextStyle(color: AppTheme.textMuted)), ); }, ); } Widget _buildPatternCard(ChartPatternModel pattern, int index) { final isEnabled = !_disabledPatternIndices.contains(index); final patternColor = PatternExplanations.getColorForPattern(pattern.type); final allPoints = [...pattern.upperLine, ...pattern.lowerLine]; DateTime? startDate; DateTime? endDate; if (allPoints.isNotEmpty) { allPoints.sort((a, b) => a.time.compareTo(b.time)); startDate = allPoints.first.time; endDate = allPoints.last.time; } final dateFormat = DateFormat('dd.MM.yy'); final dateStr = startDate != null && endDate != null ? '${dateFormat.format(startDate)} - ${dateFormat.format(endDate)}' : 'Unbekannt'; return Padding( padding: const EdgeInsets.only(bottom: 8), child: GlassContainer( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), child: Row( children: [ // Checkbox for individual pattern toggling on the chart Checkbox( value: isEnabled, activeColor: patternColor, checkColor: Colors.black, side: BorderSide(color: patternColor.withValues(alpha: 0.6)), onChanged: (bool? val) { setState(() { if (val == true) { _disabledPatternIndices.remove(index); } else { _disabledPatternIndices.add(index); } }); }, ), Expanded( child: InkWell( onTap: () => PatternExplanations.showPatternDetails( context, pattern.type), borderRadius: BorderRadius.circular(8), child: Padding( padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 4), child: Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: isEnabled ? patternColor.withValues(alpha: 0.15) : AppTheme.glassSurface, borderRadius: BorderRadius.circular(8), ), child: Icon(Icons.polyline_outlined, color: isEnabled ? patternColor : AppTheme.textMuted, size: 20), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( pattern.type, style: TextStyle( fontWeight: FontWeight.bold, color: isEnabled ? Colors.white : AppTheme.textMuted, fontSize: 14, decoration: isEnabled ? null : TextDecoration.lineThrough, ), ), const SizedBox(width: 6), Icon(Icons.info_outline, size: 14, color: AppTheme.textMuted), ], ), Text( 'Zeitraum: $dateStr\n' 'Linien: Oben (${pattern.upperLine.length} Pkt.) / Unten (${pattern.lowerLine.length} Pkt.)', style: TextStyle( color: AppTheme.textMuted, fontSize: 11, height: 1.3), ), ], ), ), StatusBadge( label: isEnabled ? 'AKTIV' : 'AUS', color: isEnabled ? patternColor : AppTheme.textMuted, ), ], ), ), ), ), ], ), ), ); } Widget _buildSignalCard(StrategySignalModel signal) { final isBuy = signal.type.toUpperCase() == 'BUY'; final color = isBuy ? AppTheme.primaryEmerald : AppTheme.accentRed; return Padding( padding: const EdgeInsets.only(bottom: 8), child: GlassContainer( padding: const EdgeInsets.all(12), child: Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: color.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(8), ), child: Icon(isBuy ? Icons.north_east : Icons.south_east, color: color, size: 20), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text(signal.type.toUpperCase(), style: TextStyle( fontWeight: FontWeight.bold, color: color, fontSize: 14)), const SizedBox(width: 8), Text('@ €${signal.price.toStringAsFixed(2)}', style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 13)), ], ), const SizedBox(height: 4), Text( signal.description.isNotEmpty ? signal.description : 'Strategisches Kaufsignal ausgelöst durch technische Indikatoren.', style: TextStyle( color: AppTheme.textSecondary, fontSize: 12)), ], ), ), StatusBadge(label: 'SIGNAL', color: color), ], ), ), ); } Widget _buildIndicatorChip(String label, bool isSelected, ValueChanged onChanged, Color color) { return Row( mainAxisSize: MainAxisSize.min, children: [ FilterChip( selected: isSelected, label: Text(label, style: TextStyle( color: isSelected ? Colors.black : color, fontSize: 11, fontWeight: FontWeight.bold)), selectedColor: color, backgroundColor: color.withValues(alpha: 0.15), side: BorderSide(color: color.withValues(alpha: 0.4)), showCheckmark: false, onSelected: onChanged, ), InkWell( onTap: () => MetricExplanations.show(context, label), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 2), child: Icon(Icons.info_outline, size: 14, color: AppTheme.textMuted), ), ), ], ); } }