Added tests, updated widget structure
parent
880696ad18
commit
a9e7380d69
|
@ -2,3 +2,9 @@ enum PayoutInterval{
|
|||
yearly,
|
||||
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/interval_widget.dart';
|
||||
import 'package:flutter_application_1/widgets/result_widget.dart';
|
||||
import 'package:flutter_application_1/widgets/error_widget.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
|
@ -17,7 +18,7 @@ class MyApp extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
title: 'Zinseszinsrechner',
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: CupertinoColors.white, background: CupertinoColors.white),
|
||||
useMaterial3: true,
|
||||
|
@ -157,7 +158,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||
});
|
||||
}
|
||||
|
||||
bool calculationPerformed = false;
|
||||
CalculationPerformed _isCalculated = CalculationPerformed.noFirstTimeItLoaded;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -170,10 +171,38 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
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.'),
|
||||
InputWidget(
|
||||
label: 'Anfangskapital',
|
||||
controller: _initialCapitalController,
|
||||
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(
|
||||
selectedInterval: translateInterval(_payoutInterval),
|
||||
onChanged: (newInterval) {
|
||||
|
@ -184,13 +213,29 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
if (_isInitialCapitalEntered &&
|
||||
_isMonthlySavingsRateEntered &&
|
||||
_isInterestRateEntered &&
|
||||
_isTimeEntered) {
|
||||
setInitialCapital();
|
||||
setMonthlySavingsRate();
|
||||
setInterestRate();
|
||||
setTime();
|
||||
_investedMoney = calculateInvestedMoney(_initialCapital, _monthlySavingsRate, _time, _investedMoneyList);
|
||||
_compoundInterest = calculateCompoundInterest(_initialCapital, _monthlySavingsRate, _interestRate, _time, _payoutInterval, _investedMoneyList, _compoundInterestList);
|
||||
calculationPerformed = true;
|
||||
_compoundInterest = calculateCompoundInterest(
|
||||
_initialCapital,
|
||||
_monthlySavingsRate,
|
||||
_interestRate,
|
||||
_time,
|
||||
_payoutInterval,
|
||||
_investedMoneyList,
|
||||
_compoundInterestList
|
||||
);
|
||||
_isCalculated = CalculationPerformed.yes;
|
||||
} else {
|
||||
_isCalculated = CalculationPerformed.no;
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStateProperty.all<Color>(CupertinoColors.black),
|
||||
|
@ -209,8 +254,21 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
if (calculationPerformed)
|
||||
buildResultWidget(context, '$_compoundInterest', '$_investedMoney', '$_time', '$_monthlySavingsRate', '$_interestRate', _payoutInterval, _investedMoneyList, _compoundInterestList),
|
||||
if(_isCalculated == CalculationPerformed.yes)
|
||||
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) {
|
||||
switch (interval) {
|
||||
case PayoutInterval.yearly:
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.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 {
|
||||
final List<double> lowerValues; // Liste der unteren 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,
|
||||
),
|
||||
],
|
||||
// Konfiguration für die Interaktivität des Diagramms
|
||||
trackballBehavior: TrackballBehavior(
|
||||
enable: true,
|
||||
tooltipSettings: const InteractiveTooltip(enable: true),
|
||||
|
@ -75,160 +73,8 @@ class StackedColumnChart extends StatelessWidget {
|
|||
|
||||
// Klasse zur Modellierung der Datenpunkte im Diagramm
|
||||
class SalesData {
|
||||
final int year;
|
||||
final double value;
|
||||
final int year; // Jahr des Datenpunkts
|
||||
final double value; // Wert des Datenpunkts
|
||||
|
||||
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';
|
||||
|
||||
// Widget für eine benutzerdefinierten Button mit einem Hintergrundbild
|
||||
class CustomImageButton extends StatelessWidget {
|
||||
final VoidCallback onPressed;
|
||||
final Widget child;
|
||||
final String backgroundImage;
|
||||
final VoidCallback onPressed; // Callback-Funktion für den Tastendruck
|
||||
final Widget child; // Das Widget, das innerhalb der Schaltfläche angezeigt werden soll
|
||||
final String backgroundImage; // Pfad zum Hintergrundbild
|
||||
|
||||
const CustomImageButton({
|
||||
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,8 +1,32 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// Erstellt ein Widget für die 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) {
|
||||
// Widget zur Eingabe mit einem Label, einem Texteingabefeld, einer Validierungsanzeige und einem Tooltip
|
||||
class InputWidget extends StatefulWidget {
|
||||
final String label; // Beschriftung des Eingabefelds
|
||||
final TextEditingController controller; // Controller für die Texteingabe
|
||||
final FocusNode focusNode; // FocusNode für die Eingabe
|
||||
final bool isValid; // Validierungsstatus
|
||||
final String suffixText; // Suffix-Text für das Eingabefeld
|
||||
final String tooltipText; // Erklärungstext im Tooltip
|
||||
|
||||
const InputWidget({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.controller,
|
||||
required this.focusNode,
|
||||
required this.isValid,
|
||||
required this.suffixText,
|
||||
required this.tooltipText,
|
||||
});
|
||||
|
||||
@override
|
||||
InputWidgetState createState() => InputWidgetState();
|
||||
}
|
||||
|
||||
class InputWidgetState extends State<InputWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
|
@ -11,37 +35,35 @@ Widget buildInputWidget(String label, TextEditingController controller, FocusNod
|
|||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
// Label für das Eingabefeld
|
||||
Text(
|
||||
label,
|
||||
widget.label,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
// Tooltip-Icon mit Erklärungstext
|
||||
// Tooltip mit Erklärungstext
|
||||
Tooltip(
|
||||
message: tooltipText,
|
||||
message: widget.tooltipText,
|
||||
triggerMode: TooltipTriggerMode.tap,
|
||||
decoration: const BoxDecoration(
|
||||
color: CupertinoColors.black,
|
||||
borderRadius: BorderRadius.all(Radius.circular(5))
|
||||
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),
|
||||
child: const Icon(
|
||||
CupertinoIcons.question_circle_fill,
|
||||
size: 15,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
// Icon zur Anzeige der Validierung (grün für gültig, rot für ungültig)
|
||||
isValid
|
||||
// Icon zur Anzeige der Validierung
|
||||
widget.isValid
|
||||
? const Icon(
|
||||
CupertinoIcons.check_mark_circled_solid,
|
||||
color: CupertinoColors.systemGreen,
|
||||
|
@ -55,15 +77,14 @@ Widget buildInputWidget(String label, TextEditingController controller, FocusNod
|
|||
],
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
// Texteingabefeld mit Suffix-Text
|
||||
CupertinoTextField(
|
||||
controller: controller,
|
||||
focusNode: focusNode,
|
||||
controller: widget.controller,
|
||||
focusNode: widget.focusNode,
|
||||
keyboardType: TextInputType.number,
|
||||
suffix: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5.0),
|
||||
child: Text(
|
||||
suffixText,
|
||||
widget.suffixText,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
|
@ -79,3 +100,4 @@ Widget buildInputWidget(String label, TextEditingController controller, FocusNod
|
|||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,10 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_application_1/enums.dart';
|
||||
import 'package:flutter_application_1/utils.dart';
|
||||
|
||||
// Widget zur Auswahl des Ausschüttungsintervalls
|
||||
class IntervalWidget extends StatefulWidget {
|
||||
final String selectedInterval;
|
||||
final Function(String) onChanged;
|
||||
final String selectedInterval; // Das aktuell ausgewählte Intervall
|
||||
final Function(String) onChanged; // Funktion, die aufgerufen wird, wenn das Intervall geändert wird
|
||||
|
||||
const IntervalWidget({
|
||||
super.key,
|
||||
|
@ -51,10 +52,11 @@ class IntervalWidgetState extends State<IntervalWidget> {
|
|||
),
|
||||
]
|
||||
),
|
||||
CupertinoTextField(
|
||||
placeholder: 'Ausschüttungsintervall auswählen',
|
||||
readOnly: true,
|
||||
onTap: () {
|
||||
Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
width: 160,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
// Öffnet das ModalBottomSheet zur Auswahl des Ausschüttungsintervalls
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
|
@ -111,17 +113,33 @@ class IntervalWidgetState extends State<IntervalWidget> {
|
|||
isExpanded = true; // Setzt den Zustand auf erweitert
|
||||
});
|
||||
},
|
||||
// Suffix-Icon, das den Zustand anzeigt (erweitert oder nicht)
|
||||
suffix: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5.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,
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStateProperty.all<Color>(CupertinoColors.black),
|
||||
foregroundColor: MaterialStateProperty.all<Color>(CupertinoColors.white),
|
||||
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
cursorColor: CupertinoColors.black,
|
||||
),
|
||||
),
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
|
|
|
@ -1,24 +1,37 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// Erstellt eine Zeitleiste für Meilensteine
|
||||
Widget buildMilestoneTimeline(List<Map<String, dynamic>> milestones, double totalInterest) {
|
||||
// Widget einer Meilenstein-Timeline basierend auf den übergebenen Meilensteinen und dem gesamten Zinsertrag
|
||||
class MilestoneTimeline extends StatelessWidget {
|
||||
final List<Map<String, dynamic>> milestones; // Liste der Meilensteine
|
||||
final double totalInterest; // Gesamter Zinsertrag
|
||||
|
||||
const MilestoneTimeline({
|
||||
super.key,
|
||||
required this.milestones,
|
||||
required this.totalInterest,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
child: Center(
|
||||
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
|
||||
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: [
|
||||
// Trennlinie zwischen den Meilensteinen, außer für den ersten Meilenstein
|
||||
if (index > 0)
|
||||
Container(
|
||||
height: 25,
|
||||
width: 2,
|
||||
color: milestoneReached ? CupertinoColors.systemGreen : CupertinoColors.black, // Linie zwischen Meilensteinen
|
||||
color: milestoneReached ? CupertinoColors.systemGreen : CupertinoColors.black,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
|
@ -26,7 +39,7 @@ Widget buildMilestoneTimeline(List<Map<String, dynamic>> milestones, double tota
|
|||
children: [
|
||||
Text(
|
||||
milestoneEmoji,
|
||||
style: const TextStyle(fontSize: 20), // Emoji für Meilenstein
|
||||
style: const TextStyle(fontSize: 20),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
|
@ -36,12 +49,13 @@ Widget buildMilestoneTimeline(List<Map<String, dynamic>> milestones, double tota
|
|||
],
|
||||
),
|
||||
),
|
||||
// Widget für das Symbol (Kreis oder Häkchen) je nachdem, ob der Meilenstein erreicht wurde
|
||||
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
|
||||
color: milestoneReached ? CupertinoColors.systemGreen : CupertinoColors.black,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -51,41 +65,4 @@ Widget buildMilestoneTimeline(List<Map<String, dynamic>> milestones, double tota
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Widget, welches den Meilenstein-Zeitstrahl auf eine neue Seite auslagert
|
||||
@override
|
||||
Widget buildMilestonePage(BuildContext context, String compoundInterest, String investedMoney, List<Map<String, dynamic>> milestoneList) {
|
||||
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( // Meilenstein-Zeitstrahl anzeigen
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: buildMilestoneTimeline(milestoneList, double.parse(compoundInterest) - double.parse(investedMoney))
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,14 +1,36 @@
|
|||
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/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 buildResultWidget(BuildContext context, String compoundInterest, String investedMoney, String time, String monthlySavingsRate, String interestRate, PayoutInterval payoutInterval, List<double> investedMoneyList, List<double> compoundInterestList) {
|
||||
// Liste von Meilensteinen mit Werten, Emojis und Beschreibungen
|
||||
// Widget zur Anzeige der Ergebnisse der Berechnungen und zur Navigation zu anderen Seiten
|
||||
class ResultWidget extends StatelessWidget {
|
||||
final String compoundInterest;
|
||||
final String investedMoney;
|
||||
final String time;
|
||||
final String monthlySavingsRate;
|
||||
final String interestRate;
|
||||
final PayoutInterval payoutInterval;
|
||||
final List<double> investedMoneyList;
|
||||
final List<double> compoundInterestList;
|
||||
|
||||
const ResultWidget({
|
||||
super.key,
|
||||
required this.compoundInterest,
|
||||
required this.investedMoney,
|
||||
required this.time,
|
||||
required this.monthlySavingsRate,
|
||||
required this.interestRate,
|
||||
required this.payoutInterval,
|
||||
required this.investedMoneyList,
|
||||
required this.compoundInterestList,
|
||||
});
|
||||
|
||||
@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€'},
|
||||
|
@ -32,7 +54,10 @@ Widget buildResultWidget(BuildContext context, String compoundInterest, String i
|
|||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => buildChartPage(context, investedMoneyList, compoundInterestList),
|
||||
builder: (context) => ChartPage(
|
||||
investedMoneyList: investedMoneyList,
|
||||
compoundInterestList: compoundInterestList,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
@ -48,7 +73,11 @@ Widget buildResultWidget(BuildContext context, String compoundInterest, String i
|
|||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => buildMilestonePage(context, compoundInterest, investedMoney, milestoneList),
|
||||
builder: (context) => MilestonePage(
|
||||
compoundInterest: compoundInterest,
|
||||
investedMoney: investedMoney,
|
||||
milestoneList: milestoneList
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
@ -60,10 +89,9 @@ Widget buildResultWidget(BuildContext context, String compoundInterest, String i
|
|||
),
|
||||
],
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
// Erstellt eine Zeile mit einem Label und einem Wert
|
||||
// Widget zum Aufbau einer Ergebniszeile
|
||||
Widget _buildResultRow(String label, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
|
@ -93,3 +121,4 @@ Widget _buildResultRow(String label, String value) {
|
|||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
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/calculator.dart';
|
||||
|
||||
void main() {
|
||||
group('Calculation Tests', () {
|
||||
test('Test calculateInvestedMoney function', () {
|
||||
// Testen mit verschiedenen Eingabewerten
|
||||
test('Calculates correct invested money', () {
|
||||
expect(calculateInvestedMoney(1000, 100, 5, []), equals(7000.0));
|
||||
expect(calculateInvestedMoney(2000, 50, 10, []), equals(8000.0));
|
||||
});
|
||||
test('Test calculateCompoundInterest function', () {
|
||||
test('Calculates correct compound interest', () {
|
||||
// Testen mit jährlicher Auszahlung
|
||||
List<double> investedMoneyListYearly = [];
|
||||
List<double> compoundInterestListYearly = [];
|
||||
|
@ -22,23 +23,55 @@ void main() {
|
|||
calculateInvestedMoney(2000, 50, 10, investedMoneyListMonthly);
|
||||
expect(calculateCompoundInterest(2000, 50, 5, 10, PayoutInterval.monthly, investedMoneyListMonthly, compoundInterestListMonthly), equals(11058.0));
|
||||
});
|
||||
});
|
||||
group('Edge Case Tests', () {
|
||||
test('Test calculateInvestedMoney function with extremely high input values', () {
|
||||
// Testen mit extrem hohen Eingabewerten
|
||||
test('Calculates correct invested money with extremely high input values', () {
|
||||
expect(calculateInvestedMoney(1e15, 1e12, 100, []), equals(2.2e15));
|
||||
});
|
||||
test('Test calculateInvestedMoney function with extremely low input values', () {
|
||||
// Testen mit extrem niedrigen Eingabewerten
|
||||
test('Calculates correct invested money with extremely low input values', () {
|
||||
expect(calculateInvestedMoney(0, 0, 0, []), equals(0.0));
|
||||
expect(calculateInvestedMoney(0.1, 0.01, 0.001, []), equals(0.0));
|
||||
});
|
||||
test('Test calculateCompoundInterest function with extremely high interest rate', () {
|
||||
// Testen mit extrem hohen Zinssatz
|
||||
test('Calculates correct compound interest with extremely high interest rate', () {
|
||||
List<double> investedMoneyListHighInterest = [];
|
||||
List<double> compoundInterestListHighInterest = [];
|
||||
calculateInvestedMoney(1000, 100, 10, investedMoneyListHighInterest);
|
||||
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/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