diff --git a/assets/images/flyer-gotour6-model.png b/assets/images/flyer-gotour6-model.png deleted file mode 100644 index 98eeb34..0000000 Binary files a/assets/images/flyer-gotour6-model.png and /dev/null differ diff --git a/assets/images/iphone-15-pro-model.png b/assets/images/iphone-15-pro-model.png deleted file mode 100644 index 4f29360..0000000 Binary files a/assets/images/iphone-15-pro-model.png and /dev/null differ diff --git a/assets/images/porsche-model.png b/assets/images/porsche-model.png deleted file mode 100644 index f4f8b07..0000000 Binary files a/assets/images/porsche-model.png and /dev/null differ diff --git a/assets/images/real-estate-model.png b/assets/images/real-estate-model.png deleted file mode 100644 index b245cd6..0000000 Binary files a/assets/images/real-estate-model.png and /dev/null differ diff --git a/lib/main.dart b/lib/main.dart index 0017d3e..a54fa56 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -201,50 +201,52 @@ class _MyHomePageState extends State { @override Widget build(BuildContext context) { return Scaffold( - body: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(20.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - buildInputWidget('Anfangskapital', _initialCapitalController, _initialCapitalFocusNode, _isInitialCapitalEntered, '€', 'Das Anfangskapital ist der Betrag, den Sie zu Beginn Ihrer Anlage haben.'), - buildInputWidget('Monatliche Sparrate', _monthlySavingsRateController, _monthlySavingsRateFocusNode, _isMonthlySavingsRateEntered, '€', 'Die monatliche Sparrate ist der Betrag, den Sie jeden Monat zu Ihrer Investition hinzufügen.'), - buildInputWidget('Jährlicher Zinssatz', _interestRateController, _interestRateFocusNode, _isInterestRateEntered, '%', 'Der jährliche Zinssatz ist der Prozentsatz, zu dem Ihr investiertes Kapital jedes Jahr wächst.'), - buildInputWidget('Anlagezeitraum', _timeController, _timeFocusNode, _isTimeEntered, 'Jahre', 'Der Anlagezeitraum ist die Zeitspanne, für die Sie planen, Ihr Geld anzulegen.'), - IntervalWidget( - selectedInterval: translateInterval(_payoutInterval), - onChanged: (newInterval) { - setState(() { - _payoutInterval = newInterval == 'jährlich' ? PayoutInterval.yearly : PayoutInterval.monthly; - }); - }, - ), - ElevatedButton( - onPressed: () { - setInitialCapital(); - setMonthlySavingsRate(); - setInterestRate(); - setTime(); - _calculateInvestedMoney(); - _calculateCompoundInterest(); - }, - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(CupertinoColors.black), - foregroundColor: MaterialStateProperty.all(CupertinoColors.white), + body: SafeArea( + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + buildInputWidget('Anfangskapital', _initialCapitalController, _initialCapitalFocusNode, _isInitialCapitalEntered, '€', 'Das Anfangskapital ist der Betrag, den Sie zu Beginn Ihrer Anlage haben.'), + buildInputWidget('Monatliche Sparrate', _monthlySavingsRateController, _monthlySavingsRateFocusNode, _isMonthlySavingsRateEntered, '€', 'Die monatliche Sparrate ist der Betrag, den Sie jeden Monat zu Ihrer Investition hinzufügen.'), + buildInputWidget('Jährlicher Zinssatz', _interestRateController, _interestRateFocusNode, _isInterestRateEntered, '%', 'Der jährliche Zinssatz ist der Prozentsatz, zu dem Ihr investiertes Kapital jedes Jahr wächst.'), + buildInputWidget('Anlagezeitraum', _timeController, _timeFocusNode, _isTimeEntered, 'Jahre', 'Der Anlagezeitraum ist die Zeitspanne, für die Sie planen, Ihr Geld anzulegen.'), + IntervalWidget( + selectedInterval: translateInterval(_payoutInterval), + onChanged: (newInterval) { + setState(() { + _payoutInterval = newInterval == 'jährlich' ? PayoutInterval.yearly : PayoutInterval.monthly; + }); + }, ), - child: const Text( - 'Berechnen', - style: TextStyle( - fontWeight: FontWeight.bold, + ElevatedButton( + onPressed: () { + setInitialCapital(); + setMonthlySavingsRate(); + setInterestRate(); + setTime(); + _calculateInvestedMoney(); + _calculateCompoundInterest(); + }, + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(CupertinoColors.black), + foregroundColor: MaterialStateProperty.all(CupertinoColors.white), + ), + child: const Text( + 'Berechnen', + style: TextStyle( + fontWeight: FontWeight.bold, + ), ), ), - ), - const SizedBox(height: 20), - if(_calculationPerformed) - buildResultWidget('$_compoundInterest', '$_investedMoney', '$_time', '$_monthlySavingsRate', '$_interestRate', _payoutInterval, _investedMoneyList, _compoundInterestList), - - ], - ), + const SizedBox(height: 20), + if(_calculationPerformed) + buildResultWidget('$_compoundInterest', '$_investedMoney', '$_time', '$_monthlySavingsRate', '$_interestRate', _payoutInterval, _investedMoneyList, _compoundInterestList), + ], + ), + ) ) ) ); diff --git a/lib/widgets/chart_widget.dart b/lib/widgets/chart_widget.dart index 02b0ee1..a8d4bc5 100644 --- a/lib/widgets/chart_widget.dart +++ b/lib/widgets/chart_widget.dart @@ -15,6 +15,10 @@ class StackedColumnChart extends StatelessWidget { @override Widget build(BuildContext context) { return SfCartesianChart( + legend: const Legend( + isVisible: true, // Legende anzeigen + position: LegendPosition.top, // Legende unterhalb des Diagramms platzieren + ), primaryXAxis: const CategoryAxis( title: AxisTitle(text: 'Jahr'), ), @@ -27,6 +31,7 @@ class StackedColumnChart extends StatelessWidget { dataSource: _getLowerChartData(), xValueMapper: (SalesData sales, _) => sales.year, yValueMapper: (SalesData sales, _) => sales.value, + name: 'Einzahlungen', color: CupertinoColors.systemRed.highContrastColor, ), // Obere Teil der Säule @@ -34,9 +39,17 @@ class StackedColumnChart extends StatelessWidget { dataSource: _getUpperChartData(), xValueMapper: (SalesData sales, _) => sales.year, yValueMapper: (SalesData sales, _) => sales.value, + name: 'Zinsen', color: CupertinoColors.systemBlue.highContrastColor, ), ], + // Trackball für Hover-Interaktion + trackballBehavior: TrackballBehavior( + enable: true, + tooltipSettings: const InteractiveTooltip(enable: true), + activationMode: ActivationMode.singleTap, + hideDelay: 2000, + ), ); } diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart index 16ece1a..96a84a3 100644 --- a/lib/widgets/input_widget.dart +++ b/lib/widgets/input_widget.dart @@ -24,6 +24,15 @@ Widget buildInputWidget(String label, TextEditingController controller, FocusNod const SizedBox(width: 5), Tooltip( message: tooltipText, + triggerMode: TooltipTriggerMode.tap, + decoration: const BoxDecoration( + color: CupertinoColors.black, + borderRadius: BorderRadius.all(Radius.circular(5)) + ), + textStyle: const TextStyle( + color: CupertinoColors.white, + ), + margin: const EdgeInsets.all(20), child: const Icon(CupertinoIcons.question_circle_fill, size: 15), ), ], diff --git a/lib/widgets/interval_widget.dart b/lib/widgets/interval_widget.dart index 18f2b53..73b8746 100644 --- a/lib/widgets/interval_widget.dart +++ b/lib/widgets/interval_widget.dart @@ -36,6 +36,15 @@ class _IntervalWidgetState extends State { SizedBox(width: 5), Tooltip( message: 'Das Ausschüttungsintervall bezieht sich darauf, wie oft die Erträge aus Ihrer Investition ausgeschüttet werden.', + triggerMode: TooltipTriggerMode.tap, + decoration: BoxDecoration( + color: CupertinoColors.black, + borderRadius: BorderRadius.all(Radius.circular(5)) + ), + textStyle: TextStyle( + color: CupertinoColors.white, + ), + margin: EdgeInsets.all(20), child: Icon(CupertinoIcons.question_circle_fill, size: 15), ), ] diff --git a/lib/widgets/result_widget.dart b/lib/widgets/result_widget.dart index 9d15bfd..afddb2d 100644 --- a/lib/widgets/result_widget.dart +++ b/lib/widgets/result_widget.dart @@ -8,16 +8,16 @@ import 'package:flutter_application_1/widgets/chart_widget.dart'; Widget buildResultWidget(String compoundInterest, String investedMoney, String time, String monthlySavingsRate, String interestRate, PayoutInterval payoutInterval, List investedMoneyList, List compoundInterestList) { // Liste von Meilensteinen mit Werten, Bildern und Texten. List> milestoneList = [ - {'value': 1329, 'image': 'iphone-15-pro-model.png', 'text': 'iPhone 15 Pro Max\nPreis: 1329€', 'size': 20}, - {'value': 3071, 'image': 'flyer-gotour6-model.png', 'text': 'Flyer Gotour6 3.40\nPreis: 3071€', 'size': 70}, - {'value': 248157, 'image': 'porsche-model.png', 'text': 'Porsche 992 GT3 RS\nPreis: 248157€', 'size': 100}, - {'value': 450000, 'image': 'real-estate-model.png', 'text': '150qm Einfamilienhaus\nPreis: ca. 450000€', 'size': 100}, + {'value': 1329.0, 'emoji': '📱', 'text': 'iPhone 15 Pro Max\nPreis: 1329€'}, + {'value': 3071.0, 'emoji': '🚲', 'text': 'Flyer Gotour6 3.40\nPreis: 3071€'}, + {'value': 248157.0, 'emoji': '🏎️', 'text': 'Porsche 992 GT3 RS\nPreis: 248157€'}, + {'value': 450000.0, 'emoji': '🏡', 'text': '150qm Einfamilienhaus\nPreis: ca. 450000€'}, ]; return Column( children: [ - _buildResultRow('Endkapital:', '$compoundInterest€'), - _buildResultRow('Gesamte Einzahlungen:', '$investedMoney€'), - _buildResultRow('Erhaltene Zinszahlungen:', '${double.parse(compoundInterest) - double.parse(investedMoney)}€'), + _buildResultRow('Endkapital:', '$compoundInterest€'), + _buildResultRow('Einzahlungen:', '$investedMoney€'), + _buildResultRow('Erhaltene Zinsen:', '${double.parse(compoundInterest) - double.parse(investedMoney)}€'), const SizedBox(height: 20), Text( 'Wenn du über einen Zeitraum von $time Jahren ${translateInterval(payoutInterval)} $monthlySavingsRate€ mit einem Zinssatz von $interestRate% investierst, erreichst du am Ende ein Endkapital von $compoundInterest€. Dieses setzt sich aus Einzahlungen von $investedMoney€ und Zinsen bzw. Kapitalerträgen in Höhe von ${double.parse(compoundInterest) - double.parse(investedMoney)}€ zusammen.' @@ -41,20 +41,27 @@ Widget buildResultWidget(String compoundInterest, String investedMoney, String t Widget _buildResultRow(String label, String value) { return Padding( - padding: const EdgeInsets.symmetric(horizontal: 100), + padding: const EdgeInsets.symmetric(horizontal: 20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - label, - style: const TextStyle( - fontWeight: FontWeight.bold, + Flexible( + child: Text( + label, + textAlign: TextAlign.start, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), ), ), - Text( - value, - style: const TextStyle( - fontWeight: FontWeight.bold, + const SizedBox(width: 20), + Flexible( + child: Text( + value, + textAlign: TextAlign.end, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), ), ), ], @@ -62,6 +69,8 @@ Widget _buildResultRow(String label, String value) { ); } + + // Erstellt die Meilenstein-Timeline. Widget buildMilestoneTimeline(List> milestones, double totalInterest) { return SizedBox( @@ -71,13 +80,12 @@ Widget buildMilestoneTimeline(List> milestones, double tota children: List.generate(milestones.length, (index) { // Werte aus dem Meilenstein-Objekt extrahieren double milestoneValue = milestones[index]['value']; - String milestoneImage = milestones[index]['image']; + String milestoneEmoji = milestones[index]['emoji']; String milestoneText = milestones[index]['text']; - double milestoneSize = milestones[index]['size']; bool milestoneReached = totalInterest >= milestoneValue; return Column( children: [ - if (index < milestones.length && index > 0) // Zeigt eine vertikale Linie an zwischen den Meilensteinen + if (index > 0) // Zeigt eine vertikale Linie an zwischen den Meilensteinen Container( height: 50, width: 2, @@ -87,10 +95,9 @@ Widget buildMilestoneTimeline(List> milestones, double tota padding: const EdgeInsets.symmetric(vertical: 4.0), child: Column( children: [ - Image.asset( - 'assets/images/$milestoneImage', - width: milestoneSize, - fit: BoxFit.cover, + Text( + milestoneEmoji, + style: const TextStyle(fontSize: 25), ), const SizedBox(height: 5), Text( diff --git a/pubspec.lock b/pubspec.lock index fc0bc8f..06d3e6b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -188,18 +188,18 @@ packages: dependency: "direct main" description: name: syncfusion_flutter_charts - sha256: "75253e2d32ce2e739c8efe0aec52668334d880e2a96f6d0d2b4422f49af7c608" + sha256: "01ff26c73725cb4f9d04492daae4d0365c2ed2cbf43cd83132a24f47d4d4931c" url: "https://pub.dev" source: hosted - version: "25.1.42+1" + version: "25.2.4" syncfusion_flutter_core: dependency: transitive description: name: syncfusion_flutter_core - sha256: fc61878342caee6da7979ca0ca9bb178f1eb5ca9b836dada34b83caa39e4bfed + sha256: "9e8bac2367d03bd902844f59e866c0e7b1e10b0b8fa5a6a926c0d8ae8c344533" url: "https://pub.dev" source: hosted - version: "25.1.42" + version: "25.2.4" term_glyph: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 15c9eae..b77cfc7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -60,11 +60,8 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - assets: - - assets/images/iphone-15-pro-model.png - - assets/images/flyer-gotour6-model.png - - assets/images/porsche-model.png - - assets/images/real-estate-model.png + # assets: + # - assets/images/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware