Added tests, updated widget structure
parent
880696ad18
commit
a9e7380d69
|
@ -1,4 +1,10 @@
|
||||||
enum PayoutInterval{
|
enum PayoutInterval{
|
||||||
yearly,
|
yearly,
|
||||||
monthly
|
monthly
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CalculationPerformed{
|
||||||
|
noFirstTimeItLoaded,
|
||||||
|
no,
|
||||||
|
yes
|
||||||
}
|
}
|
|
@ -6,6 +6,7 @@ import 'package:flutter_application_1/utils.dart';
|
||||||
import 'package:flutter_application_1/widgets/input_widget.dart';
|
import 'package:flutter_application_1/widgets/input_widget.dart';
|
||||||
import 'package:flutter_application_1/widgets/interval_widget.dart';
|
import 'package:flutter_application_1/widgets/interval_widget.dart';
|
||||||
import 'package:flutter_application_1/widgets/result_widget.dart';
|
import 'package:flutter_application_1/widgets/result_widget.dart';
|
||||||
|
import 'package:flutter_application_1/widgets/error_widget.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
|
@ -17,7 +18,7 @@ class MyApp extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'Flutter Demo',
|
title: 'Zinseszinsrechner',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: CupertinoColors.white, background: CupertinoColors.white),
|
colorScheme: ColorScheme.fromSeed(seedColor: CupertinoColors.white, background: CupertinoColors.white),
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
|
@ -157,7 +158,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool calculationPerformed = false;
|
CalculationPerformed _isCalculated = CalculationPerformed.noFirstTimeItLoaded;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -170,10 +171,38 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
buildInputWidget('Anfangskapital', _initialCapitalController, _initialCapitalFocusNode, _isInitialCapitalEntered, '€', 'Das Anfangskapital ist der Betrag, den Sie zu Beginn Ihrer Anlage haben.'),
|
InputWidget(
|
||||||
buildInputWidget('Monatliche Sparrate', _monthlySavingsRateController, _monthlySavingsRateFocusNode, _isMonthlySavingsRateEntered, '€', 'Die monatliche Sparrate ist der Betrag, den Sie jeden Monat zu Ihrer Investition hinzufügen.'),
|
label: 'Anfangskapital',
|
||||||
buildInputWidget('Jährlicher Zinssatz', _interestRateController, _interestRateFocusNode, _isInterestRateEntered, '%', 'Der jährliche Zinssatz ist der Prozentsatz, zu dem Ihr investiertes Kapital jedes Jahr wächst.'),
|
controller: _initialCapitalController,
|
||||||
buildInputWidget('Anlagezeitraum', _timeController, _timeFocusNode, _isTimeEntered, 'Jahre', 'Der Anlagezeitraum ist die Zeitspanne, für die Sie planen, Ihr Geld anzulegen.'),
|
focusNode: _initialCapitalFocusNode,
|
||||||
|
isValid: _isInitialCapitalEntered,
|
||||||
|
suffixText: '€',
|
||||||
|
tooltipText: 'Das Anfangskapital ist der Betrag, den Sie zu Beginn Ihrer Anlage haben.'
|
||||||
|
),
|
||||||
|
InputWidget(
|
||||||
|
label: 'Monatliche Sparrate',
|
||||||
|
controller: _monthlySavingsRateController,
|
||||||
|
focusNode: _monthlySavingsRateFocusNode,
|
||||||
|
isValid: _isMonthlySavingsRateEntered,
|
||||||
|
suffixText: '€',
|
||||||
|
tooltipText: 'Die monatliche Sparrate ist der Betrag, den Sie jeden Monat zu Ihrer Investition hinzufügen.'
|
||||||
|
),
|
||||||
|
InputWidget(
|
||||||
|
label: 'Jährlicher Zinssatz',
|
||||||
|
controller: _interestRateController,
|
||||||
|
focusNode: _interestRateFocusNode,
|
||||||
|
isValid: _isInterestRateEntered,
|
||||||
|
suffixText: '%',
|
||||||
|
tooltipText: 'Der jährliche Zinssatz ist der Prozentsatz, zu dem Ihr investiertes Kapital jedes Jahr wächst.'
|
||||||
|
),
|
||||||
|
InputWidget(
|
||||||
|
label: 'Anlagezeitraum',
|
||||||
|
controller: _timeController,
|
||||||
|
focusNode: _timeFocusNode,
|
||||||
|
isValid: _isTimeEntered,
|
||||||
|
suffixText: 'Jahre',
|
||||||
|
tooltipText: 'Der Anlagezeitraum ist die Zeitspanne, für die Sie planen, Ihr Geld anzulegen.'
|
||||||
|
),
|
||||||
IntervalWidget(
|
IntervalWidget(
|
||||||
selectedInterval: translateInterval(_payoutInterval),
|
selectedInterval: translateInterval(_payoutInterval),
|
||||||
onChanged: (newInterval) {
|
onChanged: (newInterval) {
|
||||||
|
@ -184,13 +213,29 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setInitialCapital();
|
if (_isInitialCapitalEntered &&
|
||||||
setMonthlySavingsRate();
|
_isMonthlySavingsRateEntered &&
|
||||||
setInterestRate();
|
_isInterestRateEntered &&
|
||||||
setTime();
|
_isTimeEntered) {
|
||||||
_investedMoney = calculateInvestedMoney(_initialCapital, _monthlySavingsRate, _time, _investedMoneyList);
|
setInitialCapital();
|
||||||
_compoundInterest = calculateCompoundInterest(_initialCapital, _monthlySavingsRate, _interestRate, _time, _payoutInterval, _investedMoneyList, _compoundInterestList);
|
setMonthlySavingsRate();
|
||||||
calculationPerformed = true;
|
setInterestRate();
|
||||||
|
setTime();
|
||||||
|
_investedMoney = calculateInvestedMoney(_initialCapital, _monthlySavingsRate, _time, _investedMoneyList);
|
||||||
|
_compoundInterest = calculateCompoundInterest(
|
||||||
|
_initialCapital,
|
||||||
|
_monthlySavingsRate,
|
||||||
|
_interestRate,
|
||||||
|
_time,
|
||||||
|
_payoutInterval,
|
||||||
|
_investedMoneyList,
|
||||||
|
_compoundInterestList
|
||||||
|
);
|
||||||
|
_isCalculated = CalculationPerformed.yes;
|
||||||
|
} else {
|
||||||
|
_isCalculated = CalculationPerformed.no;
|
||||||
|
}
|
||||||
|
setState(() {});
|
||||||
},
|
},
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
backgroundColor: MaterialStateProperty.all<Color>(CupertinoColors.black),
|
backgroundColor: MaterialStateProperty.all<Color>(CupertinoColors.black),
|
||||||
|
@ -209,8 +254,21 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
if (calculationPerformed)
|
if(_isCalculated == CalculationPerformed.yes)
|
||||||
buildResultWidget(context, '$_compoundInterest', '$_investedMoney', '$_time', '$_monthlySavingsRate', '$_interestRate', _payoutInterval, _investedMoneyList, _compoundInterestList),
|
ResultWidget(
|
||||||
|
compoundInterest: '$_compoundInterest',
|
||||||
|
investedMoney: '$_investedMoney',
|
||||||
|
time: '$_time',
|
||||||
|
monthlySavingsRate: '$_monthlySavingsRate',
|
||||||
|
interestRate: '$_interestRate',
|
||||||
|
payoutInterval: _payoutInterval,
|
||||||
|
investedMoneyList: _investedMoneyList,
|
||||||
|
compoundInterestList: _compoundInterestList,
|
||||||
|
),
|
||||||
|
if(_isCalculated == CalculationPerformed.no)
|
||||||
|
const ErrWidget(
|
||||||
|
errorMessage: 'Ungültige Eingabe',
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,179 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter_application_1/widgets/chart_widget.dart';
|
||||||
|
|
||||||
|
// Widget für die Seite, die das gestapelte Säulendiagramm anzeigt
|
||||||
|
class ChartPage extends StatelessWidget {
|
||||||
|
final List<double> investedMoneyList; // Liste der investierten Geldbeträge
|
||||||
|
final List<double> compoundInterestList; // Liste der zusammengesetzten Zinsen
|
||||||
|
|
||||||
|
const ChartPage({
|
||||||
|
super.key,
|
||||||
|
required this.investedMoneyList,
|
||||||
|
required this.compoundInterestList,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: CustomScrollView(
|
||||||
|
slivers: <Widget>[
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: 10,
|
||||||
|
right: 10,
|
||||||
|
top: MediaQuery.of(context).padding.top + 10,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(CupertinoIcons.chevron_left, size: 15),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context); // Zurück zur vorherigen Seite
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
'Grafik',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 40),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Anzeige des gestapelten Säulendiagramms
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(10.0),
|
||||||
|
child: StackedColumnChart(
|
||||||
|
lowerValues: investedMoneyList,
|
||||||
|
upperValues: compoundInterestList,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Tabelle mit Einzahlungen, Zinsen und Endkapital
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(10.0),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: CupertinoColors.black,
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
),
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
child: Table(
|
||||||
|
columnWidths: const {
|
||||||
|
0: FixedColumnWidth(60),
|
||||||
|
1: FlexColumnWidth(),
|
||||||
|
2: FlexColumnWidth(),
|
||||||
|
3: FlexColumnWidth(),
|
||||||
|
},
|
||||||
|
border: TableBorder.symmetric(
|
||||||
|
inside: const BorderSide(
|
||||||
|
color: CupertinoColors.white,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
// Tabellenkopf
|
||||||
|
const TableRow(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: CupertinoColors.darkBackgroundGray,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
'Jahr',
|
||||||
|
style: TextStyle(
|
||||||
|
color: CupertinoColors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
'Einzahlungen',
|
||||||
|
style: TextStyle(
|
||||||
|
color: CupertinoColors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
'Zinsen',
|
||||||
|
style: TextStyle(
|
||||||
|
color: CupertinoColors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
'Endkapital',
|
||||||
|
style: TextStyle(
|
||||||
|
color: CupertinoColors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
// Datenzeilen
|
||||||
|
for (int i = 0; i < investedMoneyList.length; i++)
|
||||||
|
TableRow(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
'${i + 1}',
|
||||||
|
style: const TextStyle(color: CupertinoColors.white),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
'€${investedMoneyList[i]}',
|
||||||
|
style: const TextStyle(color: CupertinoColors.white),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
'€${compoundInterestList[i]}',
|
||||||
|
style: const TextStyle(color: CupertinoColors.white),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
'€${compoundInterestList[i] + investedMoneyList[i]}',
|
||||||
|
style: const TextStyle(color: CupertinoColors.white),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_application_1/widgets/milestone_timeline_widget.dart';
|
||||||
|
|
||||||
|
// Widget für die Seite, die die Meilenstein-Timeline anzeigt
|
||||||
|
class MilestonePage extends StatelessWidget {
|
||||||
|
final String compoundInterest; // Gesamte zusammengesetzte Zinsen
|
||||||
|
final String investedMoney; // Gesamte investierte Geldmenge
|
||||||
|
final List<Map<String, dynamic>> milestoneList; // Liste von Meilensteinen
|
||||||
|
|
||||||
|
const MilestonePage({
|
||||||
|
super.key,
|
||||||
|
required this.compoundInterest,
|
||||||
|
required this.investedMoney,
|
||||||
|
required this.milestoneList,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: CustomScrollView(
|
||||||
|
slivers: <Widget>[
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.only(left: 10, right: 10, top: MediaQuery.of(context).padding.top + 10),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(CupertinoIcons.chevron_left, size: 15),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context); // Zurück zur vorherigen Seite
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
'Meilensteine',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 40),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Anzeige der Meilenstein-Timeline
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(10.0),
|
||||||
|
child: MilestoneTimeline(milestones: milestoneList, totalInterest: double.parse(compoundInterest) - double.parse(investedMoney)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ void restoreDefaultValuesIfEmpty(TextEditingController controller) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Übersetzt das Auszahlungsintervall
|
||||||
String translateInterval(PayoutInterval interval) {
|
String translateInterval(PayoutInterval interval) {
|
||||||
switch (interval) {
|
switch (interval) {
|
||||||
case PayoutInterval.yearly:
|
case PayoutInterval.yearly:
|
||||||
|
@ -29,4 +30,4 @@ String translateInterval(PayoutInterval interval) {
|
||||||
case PayoutInterval.monthly:
|
case PayoutInterval.monthly:
|
||||||
return 'monatlich';
|
return 'monatlich';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:syncfusion_flutter_charts/charts.dart';
|
import 'package:syncfusion_flutter_charts/charts.dart';
|
||||||
|
|
||||||
// Widget zur Erstellung eines gestapelten Säulendiagramms
|
// Widget für die Erstellung eines gestapelten Säulendiagramms
|
||||||
class StackedColumnChart extends StatelessWidget {
|
class StackedColumnChart extends StatelessWidget {
|
||||||
final List<double> lowerValues; // Liste der unteren Werte für das Diagramm
|
final List<double> lowerValues; // Liste der unteren Werte für das Diagramm
|
||||||
final List<double> upperValues; // Liste der oberen Werte für das Diagramm
|
final List<double> upperValues; // Liste der oberen Werte für das Diagramm
|
||||||
|
@ -44,7 +43,6 @@ class StackedColumnChart extends StatelessWidget {
|
||||||
color: CupertinoColors.systemGreen.highContrastColor,
|
color: CupertinoColors.systemGreen.highContrastColor,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
// Konfiguration für die Interaktivität des Diagramms
|
|
||||||
trackballBehavior: TrackballBehavior(
|
trackballBehavior: TrackballBehavior(
|
||||||
enable: true,
|
enable: true,
|
||||||
tooltipSettings: const InteractiveTooltip(enable: true),
|
tooltipSettings: const InteractiveTooltip(enable: true),
|
||||||
|
@ -75,160 +73,8 @@ class StackedColumnChart extends StatelessWidget {
|
||||||
|
|
||||||
// Klasse zur Modellierung der Datenpunkte im Diagramm
|
// Klasse zur Modellierung der Datenpunkte im Diagramm
|
||||||
class SalesData {
|
class SalesData {
|
||||||
final int year;
|
final int year; // Jahr des Datenpunkts
|
||||||
final double value;
|
final double value; // Wert des Datenpunkts
|
||||||
|
|
||||||
SalesData(this.year, this.value);
|
SalesData(this.year, this.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Widget, welches das Diagramm auf eine neue Seite auslagert
|
|
||||||
@override
|
|
||||||
Widget buildChartPage(BuildContext context, List<double> investedMoneyList, List<double> compoundInterestList) {
|
|
||||||
return Scaffold(
|
|
||||||
body: CustomScrollView(
|
|
||||||
slivers: <Widget>[
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.only(left: 10, right: 10, top: MediaQuery.of(context).padding.top + 10),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(CupertinoIcons.chevron_left, size: 15), // Zurück-Pfeil
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const Text(
|
|
||||||
'Grafik',
|
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 40), // Platzhalter für zentrierte Ausrichtung
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SliverToBoxAdapter( // Diagramm Anzeigen
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(10.0),
|
|
||||||
child: StackedColumnChart(
|
|
||||||
lowerValues: investedMoneyList,
|
|
||||||
upperValues: compoundInterestList,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SliverToBoxAdapter( // Tabelle mit allen Werten anzeigen
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(10.0),
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: CupertinoColors.black,
|
|
||||||
borderRadius: BorderRadius.circular(5),
|
|
||||||
),
|
|
||||||
clipBehavior: Clip.antiAlias,
|
|
||||||
child: Table(
|
|
||||||
columnWidths: const {
|
|
||||||
0: FixedColumnWidth(60), // Spaltenbreite für die erste Spalte verringern
|
|
||||||
1: FlexColumnWidth(),
|
|
||||||
2: FlexColumnWidth(),
|
|
||||||
3: FlexColumnWidth(),
|
|
||||||
},
|
|
||||||
border: TableBorder.symmetric(
|
|
||||||
inside: const BorderSide(color: CupertinoColors.white, width: 1),
|
|
||||||
),
|
|
||||||
children: [
|
|
||||||
const TableRow(
|
|
||||||
decoration: BoxDecoration(color: CupertinoColors.darkBackgroundGray),
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.all(8.0),
|
|
||||||
child: Text(
|
|
||||||
'Jahr',
|
|
||||||
style: TextStyle(
|
|
||||||
color: CupertinoColors.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.all(8.0),
|
|
||||||
child: Text(
|
|
||||||
'Einzahlungen',
|
|
||||||
style: TextStyle(
|
|
||||||
color: CupertinoColors.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.all(8.0),
|
|
||||||
child: Text(
|
|
||||||
'Zinsen',
|
|
||||||
style: TextStyle(
|
|
||||||
color: CupertinoColors.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.all(8.0),
|
|
||||||
child: Text(
|
|
||||||
'Endkapital',
|
|
||||||
style: TextStyle(
|
|
||||||
color: CupertinoColors.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
for (int i = 0; i < investedMoneyList.length; i++)
|
|
||||||
TableRow(
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Text(
|
|
||||||
'${i + 1}',
|
|
||||||
style: const TextStyle(color: CupertinoColors.white),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Text(
|
|
||||||
'€${investedMoneyList[i]}',
|
|
||||||
style: const TextStyle(color: CupertinoColors.white),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Text(
|
|
||||||
'€${compoundInterestList[i]}',
|
|
||||||
style: const TextStyle(color: CupertinoColors.white),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Text(
|
|
||||||
'€${compoundInterestList[i] + investedMoneyList[i]}',
|
|
||||||
style: const TextStyle(color: CupertinoColors.white),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,9 +1,10 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
// Widget für eine benutzerdefinierten Button mit einem Hintergrundbild
|
||||||
class CustomImageButton extends StatelessWidget {
|
class CustomImageButton extends StatelessWidget {
|
||||||
final VoidCallback onPressed;
|
final VoidCallback onPressed; // Callback-Funktion für den Tastendruck
|
||||||
final Widget child;
|
final Widget child; // Das Widget, das innerhalb der Schaltfläche angezeigt werden soll
|
||||||
final String backgroundImage;
|
final String backgroundImage; // Pfad zum Hintergrundbild
|
||||||
|
|
||||||
const CustomImageButton({
|
const CustomImageButton({
|
||||||
super.key,
|
super.key,
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
|
||||||
|
// Klasse für die Erstellung eines Fehler-Widgets mit einer Fehlermeldung
|
||||||
|
class ErrWidget extends StatelessWidget {
|
||||||
|
final String errorMessage; // Fehlermeldung, die angezeigt werden soll
|
||||||
|
|
||||||
|
const ErrWidget({super.key, required this.errorMessage});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
CupertinoIcons.exclamationmark_circle_fill,
|
||||||
|
color: CupertinoColors.systemRed,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
errorMessage,
|
||||||
|
style: const TextStyle(color: CupertinoColors.systemRed),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,81 +1,103 @@
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
// Erstellt ein Widget für die Eingabe mit einem Label, einem Texteingabefeld, einer Validierungsanzeige und einem Tooltip
|
// Widget zur Eingabe mit einem Label, einem Texteingabefeld, einer Validierungsanzeige und einem Tooltip
|
||||||
Widget buildInputWidget(String label, TextEditingController controller, FocusNode focusNode, bool isValid, String suffixText, String tooltipText) {
|
class InputWidget extends StatefulWidget {
|
||||||
return Column(
|
final String label; // Beschriftung des Eingabefelds
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
final TextEditingController controller; // Controller für die Texteingabe
|
||||||
children: [
|
final FocusNode focusNode; // FocusNode für die Eingabe
|
||||||
Row(
|
final bool isValid; // Validierungsstatus
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
final String suffixText; // Suffix-Text für das Eingabefeld
|
||||||
children: [
|
final String tooltipText; // Erklärungstext im Tooltip
|
||||||
Row(
|
|
||||||
children: [
|
const InputWidget({
|
||||||
Row(
|
super.key,
|
||||||
children: [
|
required this.label,
|
||||||
// Label für das Eingabefeld
|
required this.controller,
|
||||||
Text(
|
required this.focusNode,
|
||||||
label,
|
required this.isValid,
|
||||||
style: const TextStyle(
|
required this.suffixText,
|
||||||
fontWeight: FontWeight.bold,
|
required this.tooltipText,
|
||||||
fontSize: 16,
|
});
|
||||||
),
|
|
||||||
|
@override
|
||||||
|
InputWidgetState createState() => InputWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class InputWidgetState extends State<InputWidget> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.label,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 5),
|
),
|
||||||
// Tooltip-Icon mit Erklärungstext
|
const SizedBox(width: 5),
|
||||||
Tooltip(
|
// Tooltip mit Erklärungstext
|
||||||
message: tooltipText,
|
Tooltip(
|
||||||
triggerMode: TooltipTriggerMode.tap,
|
message: widget.tooltipText,
|
||||||
decoration: const BoxDecoration(
|
triggerMode: TooltipTriggerMode.tap,
|
||||||
color: CupertinoColors.black,
|
decoration: const BoxDecoration(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(5))
|
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),
|
|
||||||
),
|
),
|
||||||
],
|
textStyle: const TextStyle(
|
||||||
|
color: CupertinoColors.white,
|
||||||
|
),
|
||||||
|
margin: const EdgeInsets.all(20),
|
||||||
|
child: const Icon(
|
||||||
|
CupertinoIcons.question_circle_fill,
|
||||||
|
size: 15,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
// Icon zur Anzeige der Validierung
|
||||||
|
widget.isValid
|
||||||
|
? const Icon(
|
||||||
|
CupertinoIcons.check_mark_circled_solid,
|
||||||
|
color: CupertinoColors.systemGreen,
|
||||||
|
size: 15,
|
||||||
|
)
|
||||||
|
: const Icon(
|
||||||
|
CupertinoIcons.clear_circled_solid,
|
||||||
|
color: CupertinoColors.systemRed,
|
||||||
|
size: 15,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
CupertinoTextField(
|
||||||
|
controller: widget.controller,
|
||||||
|
focusNode: widget.focusNode,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
suffix: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 5.0),
|
||||||
|
child: Text(
|
||||||
|
widget.suffixText,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
// Icon zur Anzeige der Validierung (grün für gültig, rot für ungültig)
|
|
||||||
isValid
|
|
||||||
? const Icon(
|
|
||||||
CupertinoIcons.check_mark_circled_solid,
|
|
||||||
color: CupertinoColors.systemGreen,
|
|
||||||
size: 15,
|
|
||||||
)
|
|
||||||
: const Icon(
|
|
||||||
CupertinoIcons.clear_circled_solid,
|
|
||||||
color: CupertinoColors.systemRed,
|
|
||||||
size: 15,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
// Texteingabefeld mit Suffix-Text
|
|
||||||
CupertinoTextField(
|
|
||||||
controller: controller,
|
|
||||||
focusNode: focusNode,
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
suffix: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 5.0),
|
|
||||||
child: Text(
|
|
||||||
suffixText,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: CupertinoColors.extraLightBackgroundGray,
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
),
|
||||||
|
cursorColor: CupertinoColors.black,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
const SizedBox(height: 20),
|
||||||
color: CupertinoColors.extraLightBackgroundGray,
|
],
|
||||||
borderRadius: BorderRadius.circular(5),
|
);
|
||||||
),
|
}
|
||||||
cursorColor: CupertinoColors.black,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,10 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_application_1/enums.dart';
|
import 'package:flutter_application_1/enums.dart';
|
||||||
import 'package:flutter_application_1/utils.dart';
|
import 'package:flutter_application_1/utils.dart';
|
||||||
|
|
||||||
|
// Widget zur Auswahl des Ausschüttungsintervalls
|
||||||
class IntervalWidget extends StatefulWidget {
|
class IntervalWidget extends StatefulWidget {
|
||||||
final String selectedInterval;
|
final String selectedInterval; // Das aktuell ausgewählte Intervall
|
||||||
final Function(String) onChanged;
|
final Function(String) onChanged; // Funktion, die aufgerufen wird, wenn das Intervall geändert wird
|
||||||
|
|
||||||
const IntervalWidget({
|
const IntervalWidget({
|
||||||
super.key,
|
super.key,
|
||||||
|
@ -51,77 +52,94 @@ class IntervalWidgetState extends State<IntervalWidget> {
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
CupertinoTextField(
|
Container(
|
||||||
placeholder: 'Ausschüttungsintervall auswählen',
|
alignment: Alignment.centerLeft,
|
||||||
readOnly: true,
|
width: 160,
|
||||||
onTap: () {
|
child: ElevatedButton(
|
||||||
// Öffnet das ModalBottomSheet zur Auswahl des Ausschüttungsintervalls
|
onPressed: () {
|
||||||
showModalBottomSheet(
|
// Öffnet das ModalBottomSheet zur Auswahl des Ausschüttungsintervalls
|
||||||
context: context,
|
showModalBottomSheet(
|
||||||
backgroundColor: CupertinoColors.black,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
backgroundColor: CupertinoColors.black,
|
||||||
return Column(
|
builder: (BuildContext context) {
|
||||||
mainAxisSize: MainAxisSize.min,
|
return Column(
|
||||||
children: [
|
mainAxisSize: MainAxisSize.min,
|
||||||
// ListTile für jährliches Ausschüttungsintervall
|
children: [
|
||||||
Padding(
|
// ListTile für jährliches Ausschüttungsintervall
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
Padding(
|
||||||
child: ListTile(
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
title: Text(
|
child: ListTile(
|
||||||
translateInterval(PayoutInterval.yearly),
|
title: Text(
|
||||||
style: const TextStyle(
|
translateInterval(PayoutInterval.yearly),
|
||||||
fontWeight: FontWeight.bold,
|
style: const TextStyle(
|
||||||
color: CupertinoColors.white,
|
fontWeight: FontWeight.bold,
|
||||||
|
color: CupertinoColors.white,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
trailing: widget.selectedInterval == translateInterval(PayoutInterval.yearly) ? const Icon(CupertinoIcons.checkmark_alt, color: CupertinoColors.white,) : null,
|
||||||
|
onTap: () {
|
||||||
|
widget.onChanged(translateInterval(PayoutInterval.yearly));
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
trailing: widget.selectedInterval == translateInterval(PayoutInterval.yearly) ? const Icon(CupertinoIcons.checkmark_alt, color: CupertinoColors.white,) : null,
|
|
||||||
onTap: () {
|
|
||||||
widget.onChanged(translateInterval(PayoutInterval.yearly));
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
// ListTile für monatliches Ausschüttungsintervall
|
||||||
// ListTile für monatliches Ausschüttungsintervall
|
Padding(
|
||||||
Padding(
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
child: ListTile(
|
||||||
child: ListTile(
|
title: Text(
|
||||||
title: Text(
|
translateInterval(PayoutInterval.monthly),
|
||||||
translateInterval(PayoutInterval.monthly),
|
style: const TextStyle(
|
||||||
style: const TextStyle(
|
fontWeight: FontWeight.bold,
|
||||||
fontWeight: FontWeight.bold,
|
color: CupertinoColors.white,
|
||||||
color: CupertinoColors.white,
|
),
|
||||||
),
|
),
|
||||||
|
trailing: widget.selectedInterval == translateInterval(PayoutInterval.monthly) ? const Icon(CupertinoIcons.checkmark_alt, color: CupertinoColors.white,) : null,
|
||||||
|
onTap: () {
|
||||||
|
widget.onChanged(translateInterval(PayoutInterval.monthly));
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
trailing: widget.selectedInterval == translateInterval(PayoutInterval.monthly) ? const Icon(CupertinoIcons.checkmark_alt, color: CupertinoColors.white,) : null,
|
|
||||||
onTap: () {
|
|
||||||
widget.onChanged(translateInterval(PayoutInterval.monthly));
|
|
||||||
Navigator.pop(context);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
);
|
||||||
);
|
},
|
||||||
},
|
).then((value) {
|
||||||
).then((value) {
|
setState(() {
|
||||||
setState(() {
|
isExpanded = false; // Setzt den Zustand auf nicht erweitert zurück, wenn das Modal geschlossen wird
|
||||||
isExpanded = false; // Setzt den Zustand auf nicht erweitert zurück, wenn das Modal geschlossen wird
|
});
|
||||||
});
|
});
|
||||||
});
|
setState(() {
|
||||||
setState(() {
|
isExpanded = true; // Setzt den Zustand auf erweitert
|
||||||
isExpanded = true; // Setzt den Zustand auf erweitert
|
});
|
||||||
});
|
},
|
||||||
},
|
style: ButtonStyle(
|
||||||
// Suffix-Icon, das den Zustand anzeigt (erweitert oder nicht)
|
backgroundColor: MaterialStateProperty.all<Color>(CupertinoColors.black),
|
||||||
suffix: Padding(
|
foregroundColor: MaterialStateProperty.all<Color>(CupertinoColors.white),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 5.0),
|
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
|
||||||
child: Icon(isExpanded ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down),
|
RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
|
child: Text(
|
||||||
|
widget.selectedInterval, // Text des ausgewählten Intervalls
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
|
child: Icon(isExpanded ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
controller: TextEditingController(text: widget.selectedInterval), // Setzt den Text des Textfeldes auf das ausgewählte Intervall
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: CupertinoColors.extraLightBackgroundGray,
|
|
||||||
borderRadius: BorderRadius.circular(5),
|
|
||||||
),
|
|
||||||
cursorColor: CupertinoColors.black,
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,91 +1,68 @@
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
// Erstellt eine Zeitleiste für Meilensteine
|
// Widget einer Meilenstein-Timeline basierend auf den übergebenen Meilensteinen und dem gesamten Zinsertrag
|
||||||
Widget buildMilestoneTimeline(List<Map<String, dynamic>> milestones, double totalInterest) {
|
class MilestoneTimeline extends StatelessWidget {
|
||||||
return SizedBox(
|
final List<Map<String, dynamic>> milestones; // Liste der Meilensteine
|
||||||
child: Center(
|
final double totalInterest; // Gesamter Zinsertrag
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: List.generate(milestones.length, (index) {
|
|
||||||
double milestoneValue = milestones[index]['value'];
|
|
||||||
String milestoneEmoji = milestones[index]['emoji'];
|
|
||||||
String milestoneText = milestones[index]['text'];
|
|
||||||
bool milestoneReached = totalInterest >= milestoneValue; // Überprüfen, ob Meilenstein erreicht wurde
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
if (index > 0)
|
|
||||||
Container(
|
|
||||||
height: 25,
|
|
||||||
width: 2,
|
|
||||||
color: milestoneReached ? CupertinoColors.systemGreen : CupertinoColors.black, // Linie zwischen Meilensteinen
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
milestoneEmoji,
|
|
||||||
style: const TextStyle(fontSize: 20), // Emoji für Meilenstein
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
Text(
|
|
||||||
milestoneText,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
|
||||||
child: Icon(
|
|
||||||
milestoneReached ? CupertinoIcons.check_mark_circled_solid : CupertinoIcons.circle_fill,
|
|
||||||
size: 20,
|
|
||||||
color: milestoneReached ? CupertinoColors.systemGreen : CupertinoColors.black, // Icon, das den Status des Meilensteins anzeigt
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Widget, welches den Meilenstein-Zeitstrahl auf eine neue Seite auslagert
|
const MilestoneTimeline({
|
||||||
@override
|
super.key,
|
||||||
Widget buildMilestonePage(BuildContext context, String compoundInterest, String investedMoney, List<Map<String, dynamic>> milestoneList) {
|
required this.milestones,
|
||||||
return Scaffold(
|
required this.totalInterest,
|
||||||
body: CustomScrollView(
|
});
|
||||||
slivers: <Widget>[
|
|
||||||
SliverToBoxAdapter(
|
@override
|
||||||
child: Container(
|
Widget build(BuildContext context) {
|
||||||
padding: EdgeInsets.only(left: 10, right: 10, top: MediaQuery.of(context).padding.top + 10),
|
return SizedBox(
|
||||||
child: Row(
|
child: Center(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: List.generate(milestones.length, (index) {
|
||||||
|
double milestoneValue = milestones[index]['value']; // Der Wert des aktuellen Meilensteins
|
||||||
|
String milestoneEmoji = milestones[index]['emoji']; // Das Emoji des aktuellen Meilensteins
|
||||||
|
String milestoneText = milestones[index]['text']; // Der Text des aktuellen Meilensteins
|
||||||
|
bool milestoneReached = totalInterest >= milestoneValue; // Gibt an, ob der Meilenstein erreicht wurde
|
||||||
|
|
||||||
|
// Widget für den aktuellen Meilenstein
|
||||||
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
// Trennlinie zwischen den Meilensteinen, außer für den ersten Meilenstein
|
||||||
icon: const Icon(CupertinoIcons.chevron_left, size: 15), // Zurück-Pfeil
|
if (index > 0)
|
||||||
onPressed: () {
|
Container(
|
||||||
Navigator.pop(context);
|
height: 25,
|
||||||
},
|
width: 2,
|
||||||
|
color: milestoneReached ? CupertinoColors.systemGreen : CupertinoColors.black,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
milestoneEmoji,
|
||||||
|
style: const TextStyle(fontSize: 20),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Text(
|
||||||
|
milestoneText,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const Text(
|
// Widget für das Symbol (Kreis oder Häkchen) je nachdem, ob der Meilenstein erreicht wurde
|
||||||
'Grafik',
|
Padding(
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||||
|
child: Icon(
|
||||||
|
milestoneReached ? CupertinoIcons.check_mark_circled_solid : CupertinoIcons.circle_fill,
|
||||||
|
size: 20,
|
||||||
|
color: milestoneReached ? CupertinoColors.systemGreen : CupertinoColors.black,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 40), // Platzhalter für zentrierte Ausrichtung
|
|
||||||
],
|
],
|
||||||
),
|
);
|
||||||
),
|
}),
|
||||||
),
|
|
||||||
SliverToBoxAdapter( // Meilenstein-Zeitstrahl anzeigen
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(10.0),
|
|
||||||
child: buildMilestoneTimeline(milestoneList, double.parse(compoundInterest) - double.parse(investedMoney))
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,95 +1,124 @@
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_application_1/enums.dart';
|
import 'package:flutter_application_1/enums.dart';
|
||||||
|
import 'package:flutter_application_1/pages/chart_page.dart';
|
||||||
|
import 'package:flutter_application_1/pages/milestone_page.dart';
|
||||||
import 'package:flutter_application_1/utils.dart';
|
import 'package:flutter_application_1/utils.dart';
|
||||||
import 'package:flutter_application_1/widgets/custom_image_button_widget.dart';
|
import 'package:flutter_application_1/widgets/custom_image_button_widget.dart';
|
||||||
import 'package:flutter_application_1/widgets/chart_widget.dart';
|
|
||||||
import 'package:flutter_application_1/widgets/milestone_timeline_widget.dart';
|
|
||||||
|
|
||||||
// Erstellt ein Widget, das die Berechnungsergebnisse anzeigt
|
// Widget zur Anzeige der Ergebnisse der Berechnungen und zur Navigation zu anderen Seiten
|
||||||
Widget buildResultWidget(BuildContext context, String compoundInterest, String investedMoney, String time, String monthlySavingsRate, String interestRate, PayoutInterval payoutInterval, List<double> investedMoneyList, List<double> compoundInterestList) {
|
class ResultWidget extends StatelessWidget {
|
||||||
// Liste von Meilensteinen mit Werten, Emojis und Beschreibungen
|
final String compoundInterest;
|
||||||
List<Map<String, dynamic>> milestoneList = [
|
final String investedMoney;
|
||||||
{'value': 700.0, 'emoji': '📱', 'text': 'Smartphone\nPreis: 700€'},
|
final String time;
|
||||||
{'value': 3250.0, 'emoji': '🚲', 'text': 'eBike\nPreis: 3250€'},
|
final String monthlySavingsRate;
|
||||||
{'value': 20000.0, 'emoji': '🌎', 'text': 'Weltreise\nPreis: 20000€'},
|
final String interestRate;
|
||||||
{'value': 100000.0, 'emoji': '🏎️', 'text': 'Sportwagen\nPreis: 100000€'},
|
final PayoutInterval payoutInterval;
|
||||||
{'value': 350000.0, 'emoji': '🏡', 'text': '150qm Einfamilienhaus\nPreis: 350000€'},
|
final List<double> investedMoneyList;
|
||||||
];
|
final List<double> compoundInterestList;
|
||||||
|
|
||||||
return Column(
|
|
||||||
children: <Widget>[
|
|
||||||
_buildResultRow('Endkapital:', '$compoundInterest€'),
|
|
||||||
_buildResultRow('Einzahlungen:', '$investedMoney€'),
|
|
||||||
_buildResultRow('Erhaltene Zinsen:', '${double.parse(compoundInterest) + double.parse(investedMoney)}€'),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
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.'
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
CustomImageButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => buildChartPage(context, investedMoneyList, compoundInterestList),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
backgroundImage: 'assets/images/button_bg1.jpg',
|
|
||||||
child: const Text(
|
|
||||||
'Grafik',
|
|
||||||
style: TextStyle(color: CupertinoColors.white, fontSize: 20),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
|
||||||
CustomImageButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => buildMilestonePage(context, compoundInterest, investedMoney, milestoneList),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
backgroundImage: 'assets/images/button_bg2.jpg',
|
|
||||||
child: const Text(
|
|
||||||
'Meilensteine',
|
|
||||||
style: TextStyle(color: CupertinoColors.white, fontSize: 20),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Erstellt eine Zeile mit einem Label und einem Wert
|
const ResultWidget({
|
||||||
Widget _buildResultRow(String label, String value) {
|
super.key,
|
||||||
return Padding(
|
required this.compoundInterest,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
required this.investedMoney,
|
||||||
child: Row(
|
required this.time,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
required this.monthlySavingsRate,
|
||||||
children: [
|
required this.interestRate,
|
||||||
Flexible(
|
required this.payoutInterval,
|
||||||
child: Text(
|
required this.investedMoneyList,
|
||||||
label,
|
required this.compoundInterestList,
|
||||||
textAlign: TextAlign.start,
|
});
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
@override
|
||||||
),
|
Widget build(BuildContext context) {
|
||||||
|
List<Map<String, dynamic>> milestoneList = [
|
||||||
|
{'value': 700.0, 'emoji': '📱', 'text': 'Smartphone\nPreis: 700€'},
|
||||||
|
{'value': 3250.0, 'emoji': '🚲', 'text': 'eBike\nPreis: 3250€'},
|
||||||
|
{'value': 20000.0, 'emoji': '🌎', 'text': 'Weltreise\nPreis: 20000€'},
|
||||||
|
{'value': 100000.0, 'emoji': '🏎️', 'text': 'Sportwagen\nPreis: 100000€'},
|
||||||
|
{'value': 350000.0, 'emoji': '🏡', 'text': '150qm Einfamilienhaus\nPreis: 350000€'},
|
||||||
|
];
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
_buildResultRow('Endkapital:', '$compoundInterest€'),
|
||||||
|
_buildResultRow('Einzahlungen:', '$investedMoney€'),
|
||||||
|
_buildResultRow('Erhaltene Zinsen:', '${double.parse(compoundInterest) + double.parse(investedMoney)}€'),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
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.'
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
CustomImageButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => ChartPage(
|
||||||
|
investedMoneyList: investedMoneyList,
|
||||||
|
compoundInterestList: compoundInterestList,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
backgroundImage: 'assets/images/button_bg1.jpg',
|
||||||
|
child: const Text(
|
||||||
|
'Grafik',
|
||||||
|
style: TextStyle(color: CupertinoColors.white, fontSize: 20),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 20),
|
const SizedBox(height: 20),
|
||||||
Flexible(
|
CustomImageButton(
|
||||||
child: Text(
|
onPressed: () {
|
||||||
value,
|
Navigator.push(
|
||||||
textAlign: TextAlign.end,
|
context,
|
||||||
style: const TextStyle(
|
MaterialPageRoute(
|
||||||
fontWeight: FontWeight.bold,
|
builder: (context) => MilestonePage(
|
||||||
),
|
compoundInterest: compoundInterest,
|
||||||
|
investedMoney: investedMoney,
|
||||||
|
milestoneList: milestoneList
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
backgroundImage: 'assets/images/button_bg2.jpg',
|
||||||
|
child: const Text(
|
||||||
|
'Meilensteine',
|
||||||
|
style: TextStyle(color: CupertinoColors.white, fontSize: 20),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
|
|
||||||
|
// Widget zum Aufbau einer Ergebniszeile
|
||||||
|
Widget _buildResultRow(String label, String value) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 20),
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
value,
|
||||||
|
textAlign: TextAlign.end,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter_application_1/utils.dart';
|
||||||
import 'package:flutter_application_1/enums.dart';
|
import 'package:flutter_application_1/enums.dart';
|
||||||
import 'package:flutter_application_1/calculator.dart';
|
import 'package:flutter_application_1/calculator.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group('Calculation Tests', () {
|
group('Calculation Tests', () {
|
||||||
test('Test calculateInvestedMoney function', () {
|
test('Calculates correct invested money', () {
|
||||||
// Testen mit verschiedenen Eingabewerten
|
|
||||||
expect(calculateInvestedMoney(1000, 100, 5, []), equals(7000.0));
|
expect(calculateInvestedMoney(1000, 100, 5, []), equals(7000.0));
|
||||||
expect(calculateInvestedMoney(2000, 50, 10, []), equals(8000.0));
|
expect(calculateInvestedMoney(2000, 50, 10, []), equals(8000.0));
|
||||||
});
|
});
|
||||||
test('Test calculateCompoundInterest function', () {
|
test('Calculates correct compound interest', () {
|
||||||
// Testen mit jährlicher Auszahlung
|
// Testen mit jährlicher Auszahlung
|
||||||
List<double> investedMoneyListYearly = [];
|
List<double> investedMoneyListYearly = [];
|
||||||
List<double> compoundInterestListYearly = [];
|
List<double> compoundInterestListYearly = [];
|
||||||
|
@ -22,23 +23,55 @@ void main() {
|
||||||
calculateInvestedMoney(2000, 50, 10, investedMoneyListMonthly);
|
calculateInvestedMoney(2000, 50, 10, investedMoneyListMonthly);
|
||||||
expect(calculateCompoundInterest(2000, 50, 5, 10, PayoutInterval.monthly, investedMoneyListMonthly, compoundInterestListMonthly), equals(11058.0));
|
expect(calculateCompoundInterest(2000, 50, 5, 10, PayoutInterval.monthly, investedMoneyListMonthly, compoundInterestListMonthly), equals(11058.0));
|
||||||
});
|
});
|
||||||
});
|
test('Calculates correct invested money with extremely high input values', () {
|
||||||
group('Edge Case Tests', () {
|
|
||||||
test('Test calculateInvestedMoney function with extremely high input values', () {
|
|
||||||
// Testen mit extrem hohen Eingabewerten
|
|
||||||
expect(calculateInvestedMoney(1e15, 1e12, 100, []), equals(2.2e15));
|
expect(calculateInvestedMoney(1e15, 1e12, 100, []), equals(2.2e15));
|
||||||
});
|
});
|
||||||
test('Test calculateInvestedMoney function with extremely low input values', () {
|
test('Calculates correct invested money with extremely low input values', () {
|
||||||
// Testen mit extrem niedrigen Eingabewerten
|
|
||||||
expect(calculateInvestedMoney(0, 0, 0, []), equals(0.0));
|
expect(calculateInvestedMoney(0, 0, 0, []), equals(0.0));
|
||||||
expect(calculateInvestedMoney(0.1, 0.01, 0.001, []), equals(0.0));
|
expect(calculateInvestedMoney(0.1, 0.01, 0.001, []), equals(0.0));
|
||||||
});
|
});
|
||||||
test('Test calculateCompoundInterest function with extremely high interest rate', () {
|
test('Calculates correct compound interest with extremely high interest rate', () {
|
||||||
// Testen mit extrem hohen Zinssatz
|
|
||||||
List<double> investedMoneyListHighInterest = [];
|
List<double> investedMoneyListHighInterest = [];
|
||||||
List<double> compoundInterestListHighInterest = [];
|
List<double> compoundInterestListHighInterest = [];
|
||||||
calculateInvestedMoney(1000, 100, 10, investedMoneyListHighInterest);
|
calculateInvestedMoney(1000, 100, 10, investedMoneyListHighInterest);
|
||||||
expect(calculateCompoundInterest(1000, 100, 1000, 10, PayoutInterval.yearly, investedMoneyListHighInterest, compoundInterestListHighInterest), equals(29049915553000.0));
|
expect(calculateCompoundInterest(1000, 100, 1000, 10, PayoutInterval.yearly, investedMoneyListHighInterest, compoundInterestListHighInterest), equals(29049915553000.0));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('Utility Methods Tests', () {
|
||||||
|
test('roundToInteger should round the value to the nearest integer', () {
|
||||||
|
final controller = TextEditingController();
|
||||||
|
controller.text = '2.7';
|
||||||
|
roundToInteger(controller);
|
||||||
|
expect(controller.text, '3');
|
||||||
|
|
||||||
|
controller.text = '2.3';
|
||||||
|
roundToInteger(controller);
|
||||||
|
expect(controller.text, '2');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isNumeric should check if a string is a valid number', () {
|
||||||
|
expect(isNumeric('123'), true);
|
||||||
|
expect(isNumeric('12.3'), true);
|
||||||
|
expect(isNumeric('12,3'), true);
|
||||||
|
expect(isNumeric('abc'), false);
|
||||||
|
expect(isNumeric('12a'), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('restoreDefaultValuesIfEmpty should set default value 0 if the field is empty', () {
|
||||||
|
final controller = TextEditingController();
|
||||||
|
controller.text = '';
|
||||||
|
restoreDefaultValuesIfEmpty(controller);
|
||||||
|
expect(controller.text, '0');
|
||||||
|
|
||||||
|
controller.text = '5';
|
||||||
|
restoreDefaultValuesIfEmpty(controller);
|
||||||
|
expect(controller.text, '5');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('translateInterval should return correct translation', () {
|
||||||
|
expect(translateInterval(PayoutInterval.yearly), 'jährlich');
|
||||||
|
expect(translateInterval(PayoutInterval.monthly), 'monatlich');
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,332 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_application_1/enums.dart';
|
||||||
|
import 'package:flutter_application_1/pages/chart_page.dart';
|
||||||
|
import 'package:flutter_application_1/pages/milestone_page.dart';
|
||||||
|
import 'package:flutter_application_1/utils.dart';
|
||||||
|
import 'package:flutter_application_1/widgets/chart_widget.dart';
|
||||||
|
import 'package:flutter_application_1/widgets/error_widget.dart';
|
||||||
|
import 'package:flutter_application_1/widgets/input_widget.dart';
|
||||||
|
import 'package:flutter_application_1/widgets/interval_widget.dart';
|
||||||
|
import 'package:flutter_application_1/widgets/milestone_timeline_widget.dart';
|
||||||
|
import 'package:flutter_application_1/widgets/result_widget.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:syncfusion_flutter_charts/charts.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('InputWidget Tests', () {
|
||||||
|
testWidgets('Displays label, tooltip, and input field', (WidgetTester tester) async {
|
||||||
|
const label = 'Test Label';
|
||||||
|
const tooltipText = 'This is a tooltip';
|
||||||
|
const suffixText = 'suffix';
|
||||||
|
final controller = TextEditingController();
|
||||||
|
final focusNode = FocusNode();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
CupertinoApp(
|
||||||
|
home: CupertinoPageScaffold(
|
||||||
|
navigationBar: const CupertinoNavigationBar(
|
||||||
|
middle: Text('Test InputWidget'),
|
||||||
|
),
|
||||||
|
child: InputWidget(
|
||||||
|
label: label,
|
||||||
|
controller: controller,
|
||||||
|
focusNode: focusNode,
|
||||||
|
isValid: false,
|
||||||
|
suffixText: suffixText,
|
||||||
|
tooltipText: tooltipText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.text(label), findsOneWidget);
|
||||||
|
expect(find.text(suffixText), findsOneWidget);
|
||||||
|
expect(find.byType(CupertinoTextField), findsOneWidget);
|
||||||
|
expect(find.byType(Tooltip), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Displays validation icon correctly', (WidgetTester tester) async {
|
||||||
|
final controller = TextEditingController();
|
||||||
|
final focusNode = FocusNode();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
CupertinoApp(
|
||||||
|
home: InputWidget(
|
||||||
|
label: 'Test Label',
|
||||||
|
controller: controller,
|
||||||
|
focusNode: focusNode,
|
||||||
|
isValid: false,
|
||||||
|
suffixText: 'suffix',
|
||||||
|
tooltipText: 'This is a tooltip',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.byIcon(CupertinoIcons.clear_circled_solid), findsOneWidget);
|
||||||
|
expect(find.byIcon(CupertinoIcons.check_mark_circled_solid), findsNothing);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
CupertinoApp(
|
||||||
|
home: InputWidget(
|
||||||
|
label: 'Test Label',
|
||||||
|
controller: controller,
|
||||||
|
focusNode: focusNode,
|
||||||
|
isValid: true,
|
||||||
|
suffixText: 'suffix',
|
||||||
|
tooltipText: 'This is a tooltip',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.byIcon(CupertinoIcons.check_mark_circled_solid), findsOneWidget);
|
||||||
|
expect(find.byIcon(CupertinoIcons.clear_circled_solid), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Text input updates controller', (WidgetTester tester) async {
|
||||||
|
final controller = TextEditingController();
|
||||||
|
final focusNode = FocusNode();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
CupertinoApp(
|
||||||
|
home: InputWidget(
|
||||||
|
label: 'Test Label',
|
||||||
|
controller: controller,
|
||||||
|
focusNode: focusNode,
|
||||||
|
isValid: false,
|
||||||
|
suffixText: 'suffix',
|
||||||
|
tooltipText: 'This is a tooltip',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.enterText(find.byType(CupertinoTextField), '12345');
|
||||||
|
|
||||||
|
expect(controller.text, '12345');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
group('Interal Widget Tests', () {
|
||||||
|
testWidgets('Displays label and selected interval', (WidgetTester tester) async {
|
||||||
|
final selectedInterval = translateInterval(PayoutInterval.yearly);
|
||||||
|
final Widget widget = CupertinoApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: IntervalWidget(
|
||||||
|
selectedInterval: selectedInterval,
|
||||||
|
onChanged: (interval) {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(widget);
|
||||||
|
|
||||||
|
expect(find.text('Ausschüttungsintervall'), findsOneWidget);
|
||||||
|
expect(find.text(selectedInterval), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Expands on button press', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: IntervalWidget(
|
||||||
|
selectedInterval: translateInterval(PayoutInterval.yearly),
|
||||||
|
onChanged: (interval) {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tap(find.byType(ElevatedButton));
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(find.text(translateInterval(PayoutInterval.yearly)), findsNWidgets(2));
|
||||||
|
expect(find.text(translateInterval(PayoutInterval.monthly)), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
testWidgets('Selects interval on tap', (WidgetTester tester) async {
|
||||||
|
String selectedInterval = '';
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: IntervalWidget(
|
||||||
|
selectedInterval: selectedInterval,
|
||||||
|
onChanged: (interval) {
|
||||||
|
selectedInterval = interval;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tap(find.byType(ElevatedButton));
|
||||||
|
await tester.pump();
|
||||||
|
await tester.tap(find.text(translateInterval(PayoutInterval.monthly)));
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(find.text('monatlich'), findsOneWidget);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
group('Chart Widget Tests', () {
|
||||||
|
testWidgets('Displays chart correctly', (WidgetTester tester) async {
|
||||||
|
final List<double> lowerValues = [100, 200, 300, 400];
|
||||||
|
final List<double> upperValues = [50, 150, 250, 350];
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: StackedColumnChart(
|
||||||
|
lowerValues: lowerValues,
|
||||||
|
upperValues: upperValues,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.byType(SfCartesianChart), findsOneWidget);
|
||||||
|
|
||||||
|
expect(find.text('Einzahlungen'), findsOneWidget);
|
||||||
|
expect(find.text('Zinsen'), findsOneWidget);
|
||||||
|
|
||||||
|
expect(find.byType(CategoryAxis), findsOneWidget);
|
||||||
|
expect(find.byType(NumericAxis), findsOneWidget);
|
||||||
|
|
||||||
|
expect(find.text('Einzahlungen'), findsOneWidget);
|
||||||
|
expect(find.text('Zinsen'), findsOneWidget);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
group('Milestone Timeline Widget Tests', () {
|
||||||
|
testWidgets('Milestone timeline displays correctly', (WidgetTester tester) async {
|
||||||
|
final List<Map<String, dynamic>> milestones = [
|
||||||
|
{'value': 100.0, 'emoji': '😊', 'text': 'First milestone'},
|
||||||
|
{'value': 200.0, 'emoji': '😃', 'text': 'Second milestone'},
|
||||||
|
{'value': 300.0, 'emoji': '😁', 'text': 'Third milestone'},
|
||||||
|
];
|
||||||
|
const double totalInterest = 250.0;
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
CupertinoApp(
|
||||||
|
home: Material(
|
||||||
|
child: MilestoneTimeline(
|
||||||
|
milestones: milestones,
|
||||||
|
totalInterest: totalInterest,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.text('First milestone'), findsOneWidget);
|
||||||
|
expect(find.text('Second milestone'), findsOneWidget);
|
||||||
|
expect(find.text('Third milestone'), findsOneWidget);
|
||||||
|
|
||||||
|
expect(find.byIcon(CupertinoIcons.check_mark_circled_solid), findsNWidgets(2));
|
||||||
|
expect(find.byIcon(CupertinoIcons.circle_fill), findsOneWidget);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
group('Result Widget Tests', () {
|
||||||
|
testWidgets('Displays result values correctly', (WidgetTester tester) async {
|
||||||
|
const compoundInterest = '1000';
|
||||||
|
const investedMoney = '500';
|
||||||
|
const time = '5';
|
||||||
|
const monthlySavingsRate = '100';
|
||||||
|
const interestRate = '5';
|
||||||
|
const payoutInterval = PayoutInterval.monthly;
|
||||||
|
final List<double> investedMoneyList = [100, 200, 300, 400, 500];
|
||||||
|
final List<double> compoundInterestList = [100, 200, 300, 400, 1000];
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Material(
|
||||||
|
child: ResultWidget(
|
||||||
|
compoundInterest: compoundInterest,
|
||||||
|
investedMoney: investedMoney,
|
||||||
|
time: time,
|
||||||
|
monthlySavingsRate: monthlySavingsRate,
|
||||||
|
interestRate: interestRate,
|
||||||
|
payoutInterval: payoutInterval,
|
||||||
|
investedMoneyList: investedMoneyList,
|
||||||
|
compoundInterestList: compoundInterestList,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.text('Endkapital:'), findsOneWidget);
|
||||||
|
expect(find.text('$compoundInterest€'), findsOneWidget);
|
||||||
|
expect(find.text('Einzahlungen:'), findsOneWidget);
|
||||||
|
expect(find.text('$investedMoney€'), findsOneWidget);
|
||||||
|
expect(find.text('Erhaltene Zinsen:'), findsOneWidget);
|
||||||
|
expect(find.text('${double.parse(compoundInterest) + double.parse(investedMoney)}€'), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Navigates to ChartPage on "Grafik" button press', (WidgetTester tester) async {
|
||||||
|
final List<double> investedMoneyList = [100, 200, 300, 400, 500];
|
||||||
|
final List<double> compoundInterestList = [100, 200, 300, 400, 1000];
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Material(
|
||||||
|
child: ResultWidget(
|
||||||
|
compoundInterest: '1000',
|
||||||
|
investedMoney: '500',
|
||||||
|
time: '5',
|
||||||
|
monthlySavingsRate: '100',
|
||||||
|
interestRate: '5',
|
||||||
|
payoutInterval: PayoutInterval.monthly,
|
||||||
|
investedMoneyList: investedMoneyList,
|
||||||
|
compoundInterestList: compoundInterestList,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tap(find.text('Grafik'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.byType(ChartPage), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Navigates to MilestonePage on "Meilensteine" button press', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const MaterialApp(
|
||||||
|
home: Material(
|
||||||
|
child: ResultWidget(
|
||||||
|
compoundInterest: '1000',
|
||||||
|
investedMoney: '500',
|
||||||
|
time: '5',
|
||||||
|
monthlySavingsRate: '100',
|
||||||
|
interestRate: '5',
|
||||||
|
payoutInterval: PayoutInterval.monthly,
|
||||||
|
investedMoneyList: [],
|
||||||
|
compoundInterestList: [],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tap(find.text('Meilensteine'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.byType(MilestonePage), findsOneWidget);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Error Widget Tests', () {
|
||||||
|
testWidgets('Displays error message correctly', (WidgetTester tester) async {
|
||||||
|
const String errorMessage = 'This is an error message';
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const CupertinoApp(
|
||||||
|
home: Material(
|
||||||
|
child: ErrWidget(
|
||||||
|
errorMessage: errorMessage,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.byIcon(CupertinoIcons.exclamationmark_circle_fill), findsOneWidget);
|
||||||
|
expect(find.text(errorMessage), findsOneWidget);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in New Issue