feat(chart): interactive zoom/pan controls, pattern overlays, predictions, individual pattern filtering and landscape fullscreen mode
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../../core/theme/app_theme.dart';
|
||||
import '../../../../core/widgets/glass_container.dart';
|
||||
import '../../utils/metric_explanations.dart';
|
||||
|
||||
class IndicatorRibbonBar extends StatelessWidget {
|
||||
final bool showSma50;
|
||||
final bool showSma200;
|
||||
final bool showEma;
|
||||
final bool showSupertrend;
|
||||
final bool showPatterns;
|
||||
final bool showSignals;
|
||||
final ValueChanged<bool> onToggleSma50;
|
||||
final ValueChanged<bool> onToggleSma200;
|
||||
final ValueChanged<bool> onToggleEma;
|
||||
final ValueChanged<bool> onToggleSupertrend;
|
||||
final ValueChanged<bool> onTogglePatterns;
|
||||
final ValueChanged<bool> onToggleSignals;
|
||||
final VoidCallback? onToggleFullscreen;
|
||||
final bool isFullscreen;
|
||||
|
||||
const IndicatorRibbonBar({
|
||||
super.key,
|
||||
required this.showSma50,
|
||||
required this.showSma200,
|
||||
required this.showEma,
|
||||
required this.showSupertrend,
|
||||
required this.showPatterns,
|
||||
required this.showSignals,
|
||||
required this.onToggleSma50,
|
||||
required this.onToggleSma200,
|
||||
required this.onToggleEma,
|
||||
required this.onToggleSupertrend,
|
||||
required this.onTogglePatterns,
|
||||
required this.onToggleSignals,
|
||||
this.onToggleFullscreen,
|
||||
this.isFullscreen = false,
|
||||
});
|
||||
|
||||
Widget _buildChip(BuildContext context, String label, bool isSelected, ValueChanged<bool> 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),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GlassContainer(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: [
|
||||
_buildChip(context, 'SMA 50', showSma50, onToggleSma50, AppTheme.accentCyan),
|
||||
const SizedBox(width: 8),
|
||||
_buildChip(context, 'SMA 200', showSma200, onToggleSma200, Colors.amber),
|
||||
const SizedBox(width: 8),
|
||||
_buildChip(context, 'EMA 20', showEma, onToggleEma, Colors.purpleAccent),
|
||||
const SizedBox(width: 8),
|
||||
_buildChip(context, 'Supertrend', showSupertrend, onToggleSupertrend, AppTheme.primaryEmerald),
|
||||
const SizedBox(width: 8),
|
||||
_buildChip(context, 'Muster', showPatterns, onTogglePatterns, Colors.orangeAccent),
|
||||
const SizedBox(width: 8),
|
||||
_buildChip(context, 'Signale', showSignals, onToggleSignals, Colors.greenAccent),
|
||||
if (onToggleFullscreen != null) ...[
|
||||
const SizedBox(width: 12),
|
||||
Container(width: 1, height: 20, color: AppTheme.activePreset.glassBorder),
|
||||
const SizedBox(width: 12),
|
||||
InkWell(
|
||||
onTap: onToggleFullscreen,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.activePreset.cardSurface,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: AppTheme.activePreset.glassBorder),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
isFullscreen ? Icons.fullscreen_exit : Icons.fullscreen,
|
||||
size: 16,
|
||||
color: Colors.white,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
isFullscreen ? 'Normal' : 'Vollbild',
|
||||
style: const TextStyle(color: Colors.white, fontSize: 11, fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../../../../core/theme/app_theme.dart';
|
||||
import '../../../../core/widgets/glass_container.dart';
|
||||
import '../../../../core/widgets/status_badge.dart';
|
||||
import '../../models/technical_analysis_model.dart';
|
||||
import '../../utils/pattern_explanations.dart';
|
||||
|
||||
class PatternCardItem extends StatelessWidget {
|
||||
final ChartPatternModel pattern;
|
||||
final int index;
|
||||
final bool isEnabled;
|
||||
final ValueChanged<bool> onToggle;
|
||||
|
||||
const PatternCardItem({
|
||||
super.key,
|
||||
required this.pattern,
|
||||
required this.index,
|
||||
required this.isEnabled,
|
||||
required this.onToggle,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
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(
|
||||
value: isEnabled,
|
||||
activeColor: patternColor,
|
||||
checkColor: Colors.black,
|
||||
side: BorderSide(color: patternColor.withValues(alpha: 0.6)),
|
||||
onChanged: (bool? val) => onToggle(val ?? false),
|
||||
),
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
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/technical_analysis_model.dart';
|
||||
|
||||
class SignalCardItem extends StatelessWidget {
|
||||
final StrategySignalModel signal;
|
||||
|
||||
const SignalCardItem({super.key, required this.signal});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
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(
|
||||
'Strategisches Kaufsignal ausgelöst durch technische Indikatoren.',
|
||||
style: TextStyle(color: AppTheme.textSecondary, fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
StatusBadge(label: 'SIGNAL', color: color),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user