Added text to milestone page, Updated widget tests
parent
daa79922ea
commit
825d82212f
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"title": "Zinseszinsrechner",
|
"title": "Zinseszinsrechner",
|
||||||
"language_english": "Englisch",
|
"language_english": "Englisch/USD",
|
||||||
"language_german": "Deutsch",
|
"language_german": "Deutsch/EUR",
|
||||||
"choose_language": "Sprache auswählen",
|
"choose_language": "Sprache auswählen",
|
||||||
"initial_capital": "Anfangskapital",
|
"initial_capital": "Anfangskapital",
|
||||||
"initial_capital_tooltiptext": "Das Anfangskapital ist der Betrag, den Sie zu Beginn Ihrer Anlage haben.",
|
"initial_capital_tooltiptext": "Das Anfangskapital ist der Betrag, den Sie zu Beginn Ihrer Anlage haben.",
|
||||||
|
@ -34,22 +34,22 @@
|
||||||
"result_text": "Wenn du über einen Zeitraum von {investment_period} Jahren monatlich {monthly_savings_rate}€ mit einem Zinssatz von {interest_rate}% investierst, erreichst du am Ende ein Endkapital von {final_capital}€. Dieses setzt sich aus Einzahlungen von {invested_money}€ und Zinsen bzw. Kapitalerträgen in Höhe von {compound_interest}€ zusammen.",
|
"result_text": "Wenn du über einen Zeitraum von {investment_period} Jahren monatlich {monthly_savings_rate}€ mit einem Zinssatz von {interest_rate}% investierst, erreichst du am Ende ein Endkapital von {final_capital}€. Dieses setzt sich aus Einzahlungen von {invested_money}€ und Zinsen bzw. Kapitalerträgen in Höhe von {compound_interest}€ zusammen.",
|
||||||
"@result_text": {
|
"@result_text": {
|
||||||
"investment_period": {
|
"investment_period": {
|
||||||
"type" : "int"
|
"type": "String"
|
||||||
},
|
},
|
||||||
"monthly_savings_rate": {
|
"monthly_savings_rate": {
|
||||||
"type" : "int"
|
"type": "String"
|
||||||
},
|
},
|
||||||
"interest_rate": {
|
"interest_rate": {
|
||||||
"type" : "int"
|
"type": "String"
|
||||||
},
|
},
|
||||||
"final_capital": {
|
"final_capital": {
|
||||||
"type" : "int"
|
"type": "int"
|
||||||
},
|
},
|
||||||
"invested_money": {
|
"invested_money": {
|
||||||
"type" : "int"
|
"type": "String"
|
||||||
},
|
},
|
||||||
"compound_interest": {
|
"compound_interest": {
|
||||||
"type" : "int"
|
"type": "String"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"graphic": "Grafik",
|
"graphic": "Grafik",
|
||||||
|
@ -57,5 +57,22 @@
|
||||||
"payout_interval": "Ausschüttungsintervall",
|
"payout_interval": "Ausschüttungsintervall",
|
||||||
"payout_interval_tooltiptext": "Das Ausschüttungsintervall bezieht sich darauf, wie oft die Erträge aus Ihrer Investition ausgeschüttet werden.",
|
"payout_interval_tooltiptext": "Das Ausschüttungsintervall bezieht sich darauf, wie oft die Erträge aus Ihrer Investition ausgeschüttet werden.",
|
||||||
"year": "Jahr",
|
"year": "Jahr",
|
||||||
"currency_written_out": "Euro"
|
"currency_written_out": "Euro",
|
||||||
|
"what_you_can_afford": "Herzlichen Glückwunsch! Mit Hilfe des Zinseszinsrechners hast du deine Investitionen und deren erzielte Zinsen genau im Blick behalten. Diese kluge finanzielle Planung ermöglicht es dir, mehr zu tun, als du vielleicht erwartet hast. Mit den erwirtschafteten Zinsen in Höhe von {interest_received} könntest du zum Beispiel:",
|
||||||
|
"@what_you_can_afford": {
|
||||||
|
"interest_received": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"what_you_cant_afford": "Herzlichen Glückwunsch! Mit Hilfe des Zinseszinsrechners hast du deine Investitionen und deren erzielte Zinsen genau im Blick behalten. Diese kluge finanzielle Planung legt den Grundstein für deine zukünftigen finanziellen Ziele. Auch wenn du bisher keinen der Meilensteine erreicht hast, bleib dran! Deine erwirtschafteten Zinsen in Höhe von {interest_received} eröffnen dir weiterhin einige Möglichkeiten.",
|
||||||
|
"@what_you_cant_afford": {
|
||||||
|
"interest_received": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"smartphone": "Ein aktuelles Smartphone kaufen.",
|
||||||
|
"ebike": "Ein hochwertiges eBike anschaffen.",
|
||||||
|
"world_tour": "Eine unvergessliche Weltreise planen.",
|
||||||
|
"sports_car": "Einen luxuriösen Sportwagen erwerben.",
|
||||||
|
"single_family_house": "Ein geräumiges Einfamilienhaus mit 150 qm Wohnfläche zu kaufen."
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"title": "Compound interest calculator",
|
"title": "Compound interest calculator",
|
||||||
"language_english": "English",
|
"language_english": "English/USD",
|
||||||
"language_german": "German",
|
"language_german": "German/EUR",
|
||||||
"choose_language": "Choose language",
|
"choose_language": "Choose language",
|
||||||
"initial_capital": "Initial capital",
|
"initial_capital": "Initial capital",
|
||||||
"initial_capital_tooltiptext": "The initial capital is the amount you have at the start of your investment.",
|
"initial_capital_tooltiptext": "The initial capital is the amount you have at the start of your investment.",
|
||||||
|
@ -32,5 +32,12 @@
|
||||||
"payout_interval": "Payout interval",
|
"payout_interval": "Payout interval",
|
||||||
"payout_interval_tooltiptext": "The payout interval refers to how often the income from your investment is distributed.",
|
"payout_interval_tooltiptext": "The payout interval refers to how often the income from your investment is distributed.",
|
||||||
"year": "Year",
|
"year": "Year",
|
||||||
"currency_written_out": "Dollar"
|
"currency_written_out": "Dollar",
|
||||||
|
"what_you_can_afford": "Congratulations! With the help of the compound interest calculator, you can keep a close eye on your investments and the interest earned on them. This smart financial planning allows you to do more than you might have expected. With the earned interest of {interest_received} you could, for example:",
|
||||||
|
"what_you_cant_afford": "Congratulations! With the help of the compound interest calculator, you can keep a close eye on your investments and the interest earned on them. This smart financial planning lays the foundation for your future financial goals. Even if you haven't reached any of the milestones yet, stick with it! Your earned interest of {interest_received} continues to open up a number of opportunities for you.",
|
||||||
|
"smartphone": "Buy a current smartphone.",
|
||||||
|
"ebike": "Buy a high-quality eBike.",
|
||||||
|
"world_tour": "Plan an unforgettable trip around the world.",
|
||||||
|
"sports_car": "Buy a luxurious sports car.",
|
||||||
|
"single_family_house": "To buy a spacious single-family home with 150 square meters of living space."
|
||||||
}
|
}
|
214
lib/main.dart
214
lib/main.dart
|
@ -198,117 +198,117 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
SingleChildScrollView(
|
SingleChildScrollView(
|
||||||
physics: const BouncingScrollPhysics(),
|
physics: const BouncingScrollPhysics(),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(20.0),
|
padding: const EdgeInsets.all(20.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
InputWidget(
|
InputWidget(
|
||||||
label: localizations.initial_capital,
|
label: localizations.initial_capital,
|
||||||
controller: _initialCapitalController,
|
controller: _initialCapitalController,
|
||||||
focusNode: _initialCapitalFocusNode,
|
focusNode: _initialCapitalFocusNode,
|
||||||
isValid: _isInitialCapitalEntered,
|
isValid: _isInitialCapitalEntered,
|
||||||
suffixText: localizations.currency,
|
suffixText: localizations.currency,
|
||||||
tooltipText: localizations.initial_capital_tooltiptext
|
tooltipText: localizations.initial_capital_tooltiptext
|
||||||
),
|
),
|
||||||
InputWidget(
|
InputWidget(
|
||||||
label: localizations.monthly_savings_rate,
|
label: localizations.monthly_savings_rate,
|
||||||
controller: _monthlySavingsRateController,
|
controller: _monthlySavingsRateController,
|
||||||
focusNode: _monthlySavingsRateFocusNode,
|
focusNode: _monthlySavingsRateFocusNode,
|
||||||
isValid: _isMonthlySavingsRateEntered,
|
isValid: _isMonthlySavingsRateEntered,
|
||||||
suffixText: localizations.currency,
|
suffixText: localizations.currency,
|
||||||
tooltipText: localizations.monthly_savings_rate_tooltiptext
|
tooltipText: localizations.monthly_savings_rate_tooltiptext
|
||||||
),
|
),
|
||||||
InputWidget(
|
InputWidget(
|
||||||
label: localizations.interest_rate,
|
label: localizations.interest_rate,
|
||||||
controller: _interestRateController,
|
controller: _interestRateController,
|
||||||
focusNode: _interestRateFocusNode,
|
focusNode: _interestRateFocusNode,
|
||||||
isValid: _isInterestRateEntered,
|
isValid: _isInterestRateEntered,
|
||||||
suffixText: '%',
|
suffixText: '%',
|
||||||
tooltipText: localizations.interest_rate_tooltiptext
|
tooltipText: localizations.interest_rate_tooltiptext
|
||||||
),
|
),
|
||||||
InputWidget(
|
InputWidget(
|
||||||
label: localizations.investment_period,
|
label: localizations.investment_period,
|
||||||
controller: _timeController,
|
controller: _timeController,
|
||||||
focusNode: _timeFocusNode,
|
focusNode: _timeFocusNode,
|
||||||
isValid: _isTimeEntered,
|
isValid: _isTimeEntered,
|
||||||
suffixText: localizations.years,
|
suffixText: localizations.years,
|
||||||
tooltipText: localizations.investment_period_tooltiptext
|
tooltipText: localizations.investment_period_tooltiptext
|
||||||
),
|
),
|
||||||
IntervalWidget(
|
IntervalWidget(
|
||||||
selectedInterval: _payoutInterval == PayoutInterval.yearly ? localizations.yearly : localizations.monthly,
|
selectedInterval: _payoutInterval == PayoutInterval.yearly ? localizations.yearly : localizations.monthly,
|
||||||
onChanged: (newInterval) {
|
onChanged: (newInterval) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_payoutInterval = newInterval == localizations.yearly ? PayoutInterval.yearly : PayoutInterval.monthly;
|
_payoutInterval = newInterval == localizations.yearly ? PayoutInterval.yearly : PayoutInterval.monthly;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (_isInitialCapitalEntered &&
|
if (_isInitialCapitalEntered &&
|
||||||
_isMonthlySavingsRateEntered &&
|
_isMonthlySavingsRateEntered &&
|
||||||
_isInterestRateEntered &&
|
_isInterestRateEntered &&
|
||||||
_isTimeEntered) {
|
_isTimeEntered) {
|
||||||
setInitialCapital();
|
setInitialCapital();
|
||||||
setMonthlySavingsRate();
|
setMonthlySavingsRate();
|
||||||
setInterestRate();
|
setInterestRate();
|
||||||
setTime();
|
setTime();
|
||||||
_investedMoney = calculateInvestedMoney(_initialCapital, _monthlySavingsRate, _time, _investedMoneyList);
|
_investedMoney = calculateInvestedMoney(_initialCapital, _monthlySavingsRate, _time, _investedMoneyList);
|
||||||
_compoundInterest = calculateCompoundInterest(
|
_compoundInterest = calculateCompoundInterest(
|
||||||
_initialCapital,
|
_initialCapital,
|
||||||
_monthlySavingsRate,
|
_monthlySavingsRate,
|
||||||
_interestRate,
|
_interestRate,
|
||||||
_time,
|
_time,
|
||||||
_payoutInterval,
|
_payoutInterval,
|
||||||
_investedMoneyList,
|
_investedMoneyList,
|
||||||
_compoundInterestList
|
_compoundInterestList
|
||||||
);
|
);
|
||||||
_isCalculated = CalculationPerformed.yes;
|
_isCalculated = CalculationPerformed.yes;
|
||||||
} else {
|
} else {
|
||||||
_isCalculated = CalculationPerformed.no;
|
_isCalculated = CalculationPerformed.no;
|
||||||
}
|
}
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
backgroundColor: MaterialStateProperty.all<Color>(CupertinoColors.black),
|
backgroundColor: MaterialStateProperty.all<Color>(CupertinoColors.black),
|
||||||
foregroundColor: MaterialStateProperty.all<Color>(CupertinoColors.white),
|
foregroundColor: MaterialStateProperty.all<Color>(CupertinoColors.white),
|
||||||
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
|
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
|
||||||
RoundedRectangleBorder(
|
RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(5),
|
borderRadius: BorderRadius.circular(5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
localizations.calculate,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 20),
|
||||||
child: Text(
|
if(_isCalculated == CalculationPerformed.yes)
|
||||||
localizations.calculate,
|
ResultWidget(
|
||||||
style: const TextStyle(
|
compoundInterest: _compoundInterest.toStringAsFixed(0),
|
||||||
fontWeight: FontWeight.bold,
|
investedMoney: _investedMoney.toStringAsFixed(0),
|
||||||
),
|
time: _time.toStringAsFixed(0),
|
||||||
),
|
monthlySavingsRate: _monthlySavingsRate.toStringAsFixed(0),
|
||||||
|
interestRate: _interestRate.toStringAsFixed(0),
|
||||||
|
payoutInterval: _payoutInterval,
|
||||||
|
investedMoneyList: _investedMoneyList,
|
||||||
|
compoundInterestList: _compoundInterestList,
|
||||||
|
),
|
||||||
|
if(_isCalculated == CalculationPerformed.no)
|
||||||
|
ErrWidget(
|
||||||
|
errorMessage: localizations.invalid_input,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
),
|
||||||
if(_isCalculated == CalculationPerformed.yes)
|
|
||||||
ResultWidget(
|
|
||||||
compoundInterest: _compoundInterest.toStringAsFixed(0),
|
|
||||||
investedMoney: _investedMoney.toStringAsFixed(0),
|
|
||||||
time: _time.toStringAsFixed(0),
|
|
||||||
monthlySavingsRate: _monthlySavingsRate.toStringAsFixed(0),
|
|
||||||
interestRate: _interestRate.toStringAsFixed(0),
|
|
||||||
payoutInterval: _payoutInterval,
|
|
||||||
investedMoneyList: _investedMoneyList,
|
|
||||||
compoundInterestList: _compoundInterestList,
|
|
||||||
),
|
|
||||||
if(_isCalculated == CalculationPerformed.no)
|
|
||||||
ErrWidget(
|
|
||||||
errorMessage: localizations.invalid_input,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
Positioned(
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
top: MediaQuery.of(context).size.height / 2 - 20,
|
top: MediaQuery.of(context).size.height / 2 - 20,
|
||||||
right: 10,
|
right: 10,
|
||||||
child: LanguageSwitcher(
|
child: LanguageSwitcher(
|
||||||
|
|
|
@ -46,7 +46,7 @@ class MilestonePage extends StatelessWidget {
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(10.0),
|
padding: const EdgeInsets.all(10.0),
|
||||||
child: MilestoneTimeline(milestones: milestoneList, totalInterest: double.parse(compoundInterest) - double.parse(investedMoney)),
|
child: MilestoneTimeline(milestones: milestoneList, totalInterest: (double.parse(compoundInterest) - double.parse(investedMoney)).round()),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -60,6 +60,7 @@ class LanguageSwitcherState extends State<LanguageSwitcher> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: PopupMenuButton<Locale>(
|
child: PopupMenuButton<Locale>(
|
||||||
|
key: const Key('languagePopupMenu'),
|
||||||
offset: Offset(0.0, isMobile ? 55 : 45),
|
offset: Offset(0.0, isMobile ? 55 : 45),
|
||||||
onSelected: (Locale locale) {
|
onSelected: (Locale locale) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
// Widget einer Meilenstein-Timeline basierend auf den übergebenen Meilensteinen und dem gesamten Zinsertrag
|
|
||||||
class MilestoneTimeline extends StatelessWidget {
|
class MilestoneTimeline extends StatelessWidget {
|
||||||
final List<Map<String, dynamic>> milestones; // Liste der Meilensteine
|
final List<Map<String, dynamic>> milestones;
|
||||||
final double totalInterest; // Gesamter Zinsertrag
|
final int totalInterest;
|
||||||
|
|
||||||
const MilestoneTimeline({
|
const MilestoneTimeline({
|
||||||
super.key,
|
super.key,
|
||||||
|
@ -13,55 +13,134 @@ class MilestoneTimeline extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox(
|
final localizations = AppLocalizations.of(context)!;
|
||||||
child: Center(
|
|
||||||
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 Row(
|
||||||
return Column(
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
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;
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
if (index > 0)
|
||||||
|
Container(
|
||||||
|
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: 30),
|
||||||
|
),
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
// Trennlinie zwischen den Meilensteinen, außer für den ersten Meilenstein
|
Container(
|
||||||
if (index > 0)
|
width: double.infinity,
|
||||||
Container(
|
padding: const EdgeInsets.all(16.0),
|
||||||
height: 25,
|
decoration: BoxDecoration(
|
||||||
width: 2,
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
color: milestoneReached ? CupertinoColors.systemGreen : CupertinoColors.black,
|
|
||||||
),
|
),
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
milestoneEmoji,
|
totalInterest < milestones[0]['value'] ? localizations.what_you_cant_afford(localizations.amount(totalInterest)) : localizations.what_you_can_afford(localizations.amount(totalInterest)),
|
||||||
style: const TextStyle(fontSize: 30),
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
Text(
|
|
||||||
milestoneText,
|
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (totalInterest >= milestones[0]['value']) ...[
|
||||||
|
_buildListItem(context, localizations.smartphone),
|
||||||
|
],
|
||||||
|
if (totalInterest >= milestones[1]['value']) ...[
|
||||||
|
_buildListItem(context, localizations.ebike),
|
||||||
|
],
|
||||||
|
if (totalInterest >= milestones[2]['value']) ...[
|
||||||
|
_buildListItem(context, localizations.world_tour),
|
||||||
|
],
|
||||||
|
if (totalInterest >= milestones[3]['value']) ...[
|
||||||
|
_buildListItem(context, localizations.sports_car),
|
||||||
|
],
|
||||||
|
if (totalInterest >= milestones[4]['value']) ...[
|
||||||
|
_buildListItem(context, localizations.single_family_house),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// 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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
),
|
||||||
}),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildListItem(BuildContext context, String text) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
CupertinoIcons.circle_fill,
|
||||||
|
size: 12,
|
||||||
|
color: CupertinoColors.black,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,12 +48,12 @@ class ResultWidget extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: _buildResultBox(localizations.final_capital, localizations.amount(compoundInterest))),
|
Expanded(child: _buildResultBox(localizations.final_capital, localizations.amount(compoundInterest))),
|
||||||
Expanded(child: _buildResultBox(localizations.deposits, localizations.amount(investedMoney))),
|
Expanded(child: _buildResultBox(localizations.deposits, localizations.amount(investedMoney))),
|
||||||
Expanded(child: _buildResultBox(localizations.interest_received, localizations.amount(double.parse(compoundInterest) - double.parse(investedMoney)))),
|
Expanded(child: _buildResultBox(localizations.interest_received, localizations.amount((double.parse(compoundInterest) - double.parse(investedMoney)).round()))),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
Text(
|
Text(
|
||||||
localizations.result_text(double.parse(compoundInterest) - double.parse(investedMoney), compoundInterest, interestRate, investedMoney, time, monthlySavingsRate)
|
localizations.result_text((double.parse(compoundInterest) - double.parse(investedMoney)).round(), compoundInterest, interestRate, investedMoney, time, monthlySavingsRate)
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
CustomImageButton(
|
CustomImageButton(
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_application_1/pages/chart_page.dart';
|
import 'package:flutter_application_1/pages/chart_page.dart';
|
||||||
import 'package:flutter_application_1/pages/milestone_page.dart';
|
import 'package:flutter_application_1/pages/milestone_page.dart';
|
||||||
|
import 'package:flutter_application_1/widgets/language_switcher_widget.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:flutter_application_1/enums.dart';
|
import 'package:flutter_application_1/enums.dart';
|
||||||
|
@ -45,7 +46,6 @@ void main() {
|
||||||
expect(find.byType(CupertinoTextField), findsOneWidget);
|
expect(find.byType(CupertinoTextField), findsOneWidget);
|
||||||
expect(find.byType(Tooltip), findsOneWidget);
|
expect(find.byType(Tooltip), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Displays validation icon correctly', (WidgetTester tester) async {
|
testWidgets('Displays validation icon correctly', (WidgetTester tester) async {
|
||||||
final controller = TextEditingController();
|
final controller = TextEditingController();
|
||||||
final focusNode = FocusNode();
|
final focusNode = FocusNode();
|
||||||
|
@ -82,7 +82,6 @@ void main() {
|
||||||
expect(find.byIcon(CupertinoIcons.check_mark_circled_solid), findsOneWidget);
|
expect(find.byIcon(CupertinoIcons.check_mark_circled_solid), findsOneWidget);
|
||||||
expect(find.byIcon(CupertinoIcons.clear_circled_solid), findsNothing);
|
expect(find.byIcon(CupertinoIcons.clear_circled_solid), findsNothing);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Text input updates controller', (WidgetTester tester) async {
|
testWidgets('Text input updates controller', (WidgetTester tester) async {
|
||||||
final controller = TextEditingController();
|
final controller = TextEditingController();
|
||||||
final focusNode = FocusNode();
|
final focusNode = FocusNode();
|
||||||
|
@ -148,31 +147,103 @@ void main() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
group('Milestone Timeline Widget Tests', () {
|
group('Milestone Timeline Widget Tests', () {
|
||||||
testWidgets('Milestone timeline displays correctly', (WidgetTester tester) async {
|
testWidgets('Shows timeline and and some milestones were reached', (WidgetTester tester) async {
|
||||||
final List<Map<String, dynamic>> milestones = [
|
const Locale testLocale = Locale('en');
|
||||||
{'value': 100.0, 'emoji': '😊', 'text': 'First milestone'},
|
final localizations = await AppLocalizations.delegate.load(testLocale);
|
||||||
{'value': 200.0, 'emoji': '😃', 'text': 'Second milestone'},
|
|
||||||
{'value': 300.0, 'emoji': '😁', 'text': 'Third milestone'},
|
List<Map<String, dynamic>> milestoneList = [
|
||||||
];
|
{'value': 700.0, 'emoji': '📱', 'text': localizations.milestone_text1 + localizations.amount(700)},
|
||||||
const double totalInterest = 250.0;
|
{'value': 3250.0, 'emoji': '🚲', 'text': localizations.milestone_text2 + localizations.amount(3250)},
|
||||||
|
{'value': 20000.0, 'emoji': '🌎', 'text': localizations.milestone_text3 + localizations.amount(20000)},
|
||||||
|
{'value': 100000.0, 'emoji': '🏎️', 'text': localizations.milestone_text4 + localizations.amount(100000)},
|
||||||
|
{'value': 350000.0, 'emoji': '🏡', 'text': localizations.milestone_text5 + localizations.amount(350000)},
|
||||||
|
];
|
||||||
|
|
||||||
|
const int totalInterest = 5000;
|
||||||
|
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
CupertinoApp(
|
MaterialApp(
|
||||||
|
locale: testLocale,
|
||||||
|
localizationsDelegates: const [
|
||||||
|
AppLocalizations.delegate,
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
GlobalCupertinoLocalizations.delegate,
|
||||||
|
],
|
||||||
home: Material(
|
home: Material(
|
||||||
child: MilestoneTimeline(
|
child: MilestoneTimeline(
|
||||||
milestones: milestones,
|
milestones: milestoneList,
|
||||||
totalInterest: totalInterest,
|
totalInterest: totalInterest,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(find.text('First milestone'), findsOneWidget);
|
expect(find.text(localizations.milestone_text1 + localizations.amount(700)), findsOneWidget);
|
||||||
expect(find.text('Second milestone'), findsOneWidget);
|
expect(find.text(localizations.milestone_text2 + localizations.amount(3250)), findsOneWidget);
|
||||||
expect(find.text('Third milestone'), findsOneWidget);
|
expect(find.text(localizations.milestone_text3 + localizations.amount(20000)), findsOneWidget);
|
||||||
|
expect(find.text(localizations.milestone_text4 + localizations.amount(100000)), findsOneWidget);
|
||||||
|
expect(find.text(localizations.milestone_text5 + localizations.amount(350000)), findsOneWidget);
|
||||||
|
|
||||||
expect(find.byIcon(CupertinoIcons.check_mark_circled_solid), findsNWidgets(2));
|
expect(find.byIcon(CupertinoIcons.check_mark_circled_solid), findsNWidgets(2));
|
||||||
expect(find.byIcon(CupertinoIcons.circle_fill), findsOneWidget);
|
|
||||||
|
expect(find.text(localizations.what_you_cant_afford(localizations.amount(totalInterest))), findsNothing);
|
||||||
|
expect(find.text(localizations.what_you_can_afford(localizations.amount(totalInterest))), findsOneWidget);
|
||||||
|
|
||||||
|
expect(find.text(localizations.smartphone), findsOneWidget);
|
||||||
|
expect(find.text(localizations.ebike), findsOneWidget);
|
||||||
|
expect(find.text(localizations.world_tour), findsNothing);
|
||||||
|
expect(find.text(localizations.sports_car), findsNothing);
|
||||||
|
expect(find.text(localizations.single_family_house), findsNothing);
|
||||||
|
});
|
||||||
|
testWidgets('Shows timeline but no milestone was reached', (WidgetTester tester) async {
|
||||||
|
const Locale testLocale = Locale('en');
|
||||||
|
final localizations = await AppLocalizations.delegate.load(testLocale);
|
||||||
|
|
||||||
|
List<Map<String, dynamic>> milestoneList = [
|
||||||
|
{'value': 700.0, 'emoji': '📱', 'text': localizations.milestone_text1 + localizations.amount(700)},
|
||||||
|
{'value': 3250.0, 'emoji': '🚲', 'text': localizations.milestone_text2 + localizations.amount(3250)},
|
||||||
|
{'value': 20000.0, 'emoji': '🌎', 'text': localizations.milestone_text3 + localizations.amount(20000)},
|
||||||
|
{'value': 100000.0, 'emoji': '🏎️', 'text': localizations.milestone_text4 + localizations.amount(100000)},
|
||||||
|
{'value': 350000.0, 'emoji': '🏡', 'text': localizations.milestone_text5 + localizations.amount(350000)},
|
||||||
|
];
|
||||||
|
|
||||||
|
const int totalInterest = 0;
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
locale: testLocale,
|
||||||
|
localizationsDelegates: const [
|
||||||
|
AppLocalizations.delegate,
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
GlobalCupertinoLocalizations.delegate,
|
||||||
|
],
|
||||||
|
home: Material(
|
||||||
|
child: MilestoneTimeline(
|
||||||
|
milestones: milestoneList,
|
||||||
|
totalInterest: totalInterest,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.text(localizations.milestone_text1 + localizations.amount(700)), findsOneWidget);
|
||||||
|
expect(find.text(localizations.milestone_text2 + localizations.amount(3250)), findsOneWidget);
|
||||||
|
expect(find.text(localizations.milestone_text3 + localizations.amount(20000)), findsOneWidget);
|
||||||
|
expect(find.text(localizations.milestone_text4 + localizations.amount(100000)), findsOneWidget);
|
||||||
|
expect(find.text(localizations.milestone_text5 + localizations.amount(350000)), findsOneWidget);
|
||||||
|
|
||||||
|
expect(find.byIcon(CupertinoIcons.check_mark_circled_solid), findsNothing);
|
||||||
|
|
||||||
|
expect(find.text(localizations.what_you_can_afford(localizations.amount(totalInterest))), findsNothing);
|
||||||
|
expect(find.text(localizations.what_you_cant_afford(localizations.amount(totalInterest))), findsOneWidget);
|
||||||
|
|
||||||
|
expect(find.text(localizations.smartphone), findsNothing);
|
||||||
|
expect(find.text(localizations.ebike), findsNothing);
|
||||||
|
expect(find.text(localizations.world_tour), findsNothing);
|
||||||
|
expect(find.text(localizations.sports_car), findsNothing);
|
||||||
|
expect(find.text(localizations.single_family_house), findsNothing);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
group('Result Widget Tests', () {
|
group('Result Widget Tests', () {
|
||||||
|
@ -218,10 +289,10 @@ void main() {
|
||||||
expect(find.text(localizations.deposits), findsOneWidget);
|
expect(find.text(localizations.deposits), findsOneWidget);
|
||||||
expect(find.text(localizations.amount(investedMoney)), findsOneWidget);
|
expect(find.text(localizations.amount(investedMoney)), findsOneWidget);
|
||||||
expect(find.text(localizations.interest_received), findsOneWidget);
|
expect(find.text(localizations.interest_received), findsOneWidget);
|
||||||
expect(find.text(localizations.amount(double.parse(compoundInterest) - double.parse(investedMoney))), findsOneWidget);
|
expect(find.text(localizations.amount((double.parse(compoundInterest) - double.parse(investedMoney)).round())), findsOneWidget);
|
||||||
|
|
||||||
final resultText = localizations.result_text(
|
final resultText = localizations.result_text(
|
||||||
double.parse(compoundInterest) - double.parse(investedMoney),
|
(double.parse(compoundInterest) - double.parse(investedMoney)).round(),
|
||||||
compoundInterest,
|
compoundInterest,
|
||||||
interestRate,
|
interestRate,
|
||||||
investedMoney,
|
investedMoney,
|
||||||
|
@ -234,7 +305,6 @@ void main() {
|
||||||
expect(find.text(localizations.milestones), findsOneWidget);
|
expect(find.text(localizations.milestones), findsOneWidget);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('Error Widget Tests', () {
|
group('Error Widget Tests', () {
|
||||||
testWidgets('Displays error message correctly', (WidgetTester tester) async {
|
testWidgets('Displays error message correctly', (WidgetTester tester) async {
|
||||||
const String errorMessage = 'This is an error message';
|
const String errorMessage = 'This is an error message';
|
||||||
|
@ -253,6 +323,52 @@ void main() {
|
||||||
expect(find.text(errorMessage), findsOneWidget);
|
expect(find.text(errorMessage), findsOneWidget);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
group('Language Switcher Widget Tests', () {
|
||||||
|
testWidgets('LanguageSwitcher widget test', (WidgetTester tester) async {
|
||||||
|
const Locale testLocale = Locale('en');
|
||||||
|
final localizations = await AppLocalizations.delegate.load(testLocale);
|
||||||
|
|
||||||
|
Locale selectedLocale = testLocale;
|
||||||
|
|
||||||
|
void handleLocaleChange(Locale locale) {
|
||||||
|
selectedLocale = locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
locale: testLocale,
|
||||||
|
localizationsDelegates: const [
|
||||||
|
AppLocalizations.delegate,
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
GlobalCupertinoLocalizations.delegate,
|
||||||
|
],
|
||||||
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
|
home: Scaffold(
|
||||||
|
body: LanguageSwitcher(
|
||||||
|
currentLocale: selectedLocale,
|
||||||
|
onLocaleChanged: handleLocaleChange,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.byKey(const Key('languagePopupMenu')), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(find.byKey(const Key('languagePopupMenu')));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Sicherstellen, dass das Ziel-Widget sichtbar ist
|
||||||
|
await tester.ensureVisible(find.text(localizations.language_german));
|
||||||
|
|
||||||
|
expect(find.text(localizations.language_english), findsOneWidget);
|
||||||
|
expect(find.byIcon(CupertinoIcons.checkmark_alt), findsOneWidget);
|
||||||
|
expect(find.text(localizations.language_german), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(find.text(localizations.language_german));
|
||||||
|
});
|
||||||
|
});
|
||||||
group('Chart Page Tests', () {
|
group('Chart Page Tests', () {
|
||||||
testWidgets('Shows correct localized texts and table data', (WidgetTester tester) async {
|
testWidgets('Shows correct localized texts and table data', (WidgetTester tester) async {
|
||||||
const Locale testLocale = Locale('en');
|
const Locale testLocale = Locale('en');
|
||||||
|
|
Loading…
Reference in New Issue