Added internationalization, Updated tests
parent
26c8cd977e
commit
daa79922ea
|
@ -0,0 +1,3 @@
|
|||
arb-dir: lib/l10n
|
||||
template-arb-file: app_de.arb
|
||||
output-localization-file: app_localizations.dart
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"title": "Zinseszinsrechner",
|
||||
"language_english": "Englisch",
|
||||
"language_german": "Deutsch",
|
||||
"choose_language": "Sprache auswählen",
|
||||
"initial_capital": "Anfangskapital",
|
||||
"initial_capital_tooltiptext": "Das Anfangskapital ist der Betrag, den Sie zu Beginn Ihrer Anlage haben.",
|
||||
"currency": "€",
|
||||
"amount": "{value}€",
|
||||
"@amount": {
|
||||
"value": {
|
||||
"type": "int"
|
||||
}
|
||||
},
|
||||
"monthly_savings_rate": "Monatliche Sparrate",
|
||||
"monthly_savings_rate_tooltiptext": "Die monatliche Sparrate ist der Betrag, den Sie jeden Monat zu Ihrer Investition hinzufügen.",
|
||||
"interest_rate": "Jährlicher Zinssatz",
|
||||
"interest_rate_tooltiptext": "Der jährliche Zinssatz ist der Prozentsatz, zu dem Ihr investiertes Kapital jedes Jahr wächst.",
|
||||
"investment_period": "Anlagezeitraum",
|
||||
"investment_period_tooltiptext": "Der Anlagezeitraum ist die Zeitspanne, für die Sie planen, Ihr Geld anzulegen.",
|
||||
"years": "Jahre",
|
||||
"yearly": "jährlich",
|
||||
"monthly": "monatlich",
|
||||
"calculate": "Berechnen",
|
||||
"invalid_input": "Ungültige Eingabe",
|
||||
"milestone_text1": "Smartphone\nPreis:",
|
||||
"milestone_text2": "eBike\nPreis:",
|
||||
"milestone_text3": "Weltreise\nPreis:",
|
||||
"milestone_text4": "Sportwagen\nPreis:",
|
||||
"milestone_text5": "150qm Einfamilienhaus\nPreis:",
|
||||
"final_capital": "Endkapital",
|
||||
"deposits": "Einzahlungen",
|
||||
"interest_received": "Erhaltene Zinsen",
|
||||
"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": {
|
||||
"investment_period": {
|
||||
"type" : "int"
|
||||
},
|
||||
"monthly_savings_rate": {
|
||||
"type" : "int"
|
||||
},
|
||||
"interest_rate": {
|
||||
"type" : "int"
|
||||
},
|
||||
"final_capital": {
|
||||
"type" : "int"
|
||||
},
|
||||
"invested_money": {
|
||||
"type" : "int"
|
||||
},
|
||||
"compound_interest": {
|
||||
"type" : "int"
|
||||
}
|
||||
},
|
||||
"graphic": "Grafik",
|
||||
"milestones": "Meilensteine",
|
||||
"payout_interval": "Ausschüttungsintervall",
|
||||
"payout_interval_tooltiptext": "Das Ausschüttungsintervall bezieht sich darauf, wie oft die Erträge aus Ihrer Investition ausgeschüttet werden.",
|
||||
"year": "Jahr",
|
||||
"currency_written_out": "Euro"
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"title": "Compound interest calculator",
|
||||
"language_english": "English",
|
||||
"language_german": "German",
|
||||
"choose_language": "Choose language",
|
||||
"initial_capital": "Initial capital",
|
||||
"initial_capital_tooltiptext": "The initial capital is the amount you have at the start of your investment.",
|
||||
"currency": "$",
|
||||
"amount": "${value}",
|
||||
"monthly_savings_rate": "Monthly savings rate",
|
||||
"monthly_savings_rate_tooltiptext": "The monthly savings rate is the amount you add to your investment each month.",
|
||||
"interest_rate": "Annual interest rate",
|
||||
"interest_rate_tooltiptext": "The annual interest rate is the percentage at which your invested capital grows each year.",
|
||||
"investment_period": "Investment period",
|
||||
"investment_period_tooltiptext": "The investment period is the length of time for which you plan to invest your money.",
|
||||
"years": "Years",
|
||||
"yearly": "yearly",
|
||||
"monthly": "monthly",
|
||||
"calculate": "Calculate",
|
||||
"invalid_input": "Invalid input",
|
||||
"milestone_text1": "Smartphone\nPrice: ",
|
||||
"milestone_text2": "eBike\nPrice: ",
|
||||
"milestone_text3": "World Travel\nPrice: ",
|
||||
"milestone_text4": "Sports car\nPrice: ",
|
||||
"milestone_text5": "150sqm single family house\nPrice: ",
|
||||
"final_capital": "Final capital",
|
||||
"deposits": "Deposits",
|
||||
"interest_received": "Interest received",
|
||||
"result_text": "If you invest ${monthly_savings_rate} per month over a period of {investment_period} years at an interest rate of {interest_rate}%, you will end up with a final capital of ${final_capital}. This consists of deposits of ${invested_money} and interest or capital gains of ${compound_interest}.",
|
||||
"graphic": "Graphic",
|
||||
"milestones": "Milestones",
|
||||
"payout_interval": "Payout interval",
|
||||
"payout_interval_tooltiptext": "The payout interval refers to how often the income from your investment is distributed.",
|
||||
"year": "Year",
|
||||
"currency_written_out": "Dollar"
|
||||
}
|
107
lib/main.dart
107
lib/main.dart
|
@ -1,5 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_application_1/calculator.dart';
|
||||
import 'package:flutter_application_1/enums.dart';
|
||||
import 'package:flutter_application_1/utils.dart';
|
||||
|
@ -7,31 +9,60 @@ 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';
|
||||
import 'package:flutter_application_1/widgets/language_switcher_widget.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
class MyApp extends StatefulWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
MyAppState createState() => MyAppState();
|
||||
}
|
||||
|
||||
class MyAppState extends State<MyApp> {
|
||||
Locale _locale = const Locale('de'); // Standard-Locale
|
||||
|
||||
void _changeLanguage(Locale locale) {
|
||||
setState(() {
|
||||
_locale = locale;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Zinseszinsrechner',
|
||||
title: 'Compound interest calculator',
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [
|
||||
Locale('en'), // English
|
||||
Locale('de'), // Deutsch
|
||||
],
|
||||
locale: _locale,
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: CupertinoColors.white, background: CupertinoColors.white),
|
||||
useMaterial3: true,
|
||||
),
|
||||
home: const MyHomePage(title: 'Zinseszinsrechner',),
|
||||
home: MyHomePage(
|
||||
title: 'Compound interest calculator',
|
||||
onLocaleChanged: _changeLanguage, // Übergibt die Sprachwechsel-Funktion an die Startseite
|
||||
),
|
||||
debugShowCheckedModeBanner: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({super.key, required this.title});
|
||||
const MyHomePage({super.key, required this.title, required this.onLocaleChanged});
|
||||
final String title;
|
||||
final Function(Locale) onLocaleChanged;
|
||||
|
||||
@override
|
||||
State<MyHomePage> createState() => _MyHomePageState();
|
||||
|
@ -162,9 +193,13 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final localizations = AppLocalizations.of(context)!;
|
||||
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
child: Stack(
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
|
@ -172,42 +207,42 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
InputWidget(
|
||||
label: 'Anfangskapital',
|
||||
label: localizations.initial_capital,
|
||||
controller: _initialCapitalController,
|
||||
focusNode: _initialCapitalFocusNode,
|
||||
isValid: _isInitialCapitalEntered,
|
||||
suffixText: '€',
|
||||
tooltipText: 'Das Anfangskapital ist der Betrag, den Sie zu Beginn Ihrer Anlage haben.'
|
||||
suffixText: localizations.currency,
|
||||
tooltipText: localizations.initial_capital_tooltiptext
|
||||
),
|
||||
InputWidget(
|
||||
label: 'Monatliche Sparrate',
|
||||
label: localizations.monthly_savings_rate,
|
||||
controller: _monthlySavingsRateController,
|
||||
focusNode: _monthlySavingsRateFocusNode,
|
||||
isValid: _isMonthlySavingsRateEntered,
|
||||
suffixText: '€',
|
||||
tooltipText: 'Die monatliche Sparrate ist der Betrag, den Sie jeden Monat zu Ihrer Investition hinzufügen.'
|
||||
suffixText: localizations.currency,
|
||||
tooltipText: localizations.monthly_savings_rate_tooltiptext
|
||||
),
|
||||
InputWidget(
|
||||
label: 'Jährlicher Zinssatz',
|
||||
label: localizations.interest_rate,
|
||||
controller: _interestRateController,
|
||||
focusNode: _interestRateFocusNode,
|
||||
isValid: _isInterestRateEntered,
|
||||
suffixText: '%',
|
||||
tooltipText: 'Der jährliche Zinssatz ist der Prozentsatz, zu dem Ihr investiertes Kapital jedes Jahr wächst.'
|
||||
tooltipText: localizations.interest_rate_tooltiptext
|
||||
),
|
||||
InputWidget(
|
||||
label: 'Anlagezeitraum',
|
||||
label: localizations.investment_period,
|
||||
controller: _timeController,
|
||||
focusNode: _timeFocusNode,
|
||||
isValid: _isTimeEntered,
|
||||
suffixText: 'Jahre',
|
||||
tooltipText: 'Der Anlagezeitraum ist die Zeitspanne, für die Sie planen, Ihr Geld anzulegen.'
|
||||
suffixText: localizations.years,
|
||||
tooltipText: localizations.investment_period_tooltiptext
|
||||
),
|
||||
IntervalWidget(
|
||||
selectedInterval: translateInterval(_payoutInterval),
|
||||
selectedInterval: _payoutInterval == PayoutInterval.yearly ? localizations.yearly : localizations.monthly,
|
||||
onChanged: (newInterval) {
|
||||
setState(() {
|
||||
_payoutInterval = newInterval == 'jährlich' ? PayoutInterval.yearly : PayoutInterval.monthly;
|
||||
_payoutInterval = newInterval == localizations.yearly ? PayoutInterval.yearly : PayoutInterval.monthly;
|
||||
});
|
||||
},
|
||||
),
|
||||
|
@ -246,9 +281,9 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||
),
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
'Berechnen',
|
||||
style: TextStyle(
|
||||
child: Text(
|
||||
localizations.calculate,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
|
@ -256,24 +291,34 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||
const SizedBox(height: 20),
|
||||
if(_isCalculated == CalculationPerformed.yes)
|
||||
ResultWidget(
|
||||
compoundInterest: '$_compoundInterest',
|
||||
investedMoney: '$_investedMoney',
|
||||
time: '$_time',
|
||||
monthlySavingsRate: '$_monthlySavingsRate',
|
||||
interestRate: '$_interestRate',
|
||||
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)
|
||||
const ErrWidget(
|
||||
errorMessage: 'Ungültige Eingabe',
|
||||
ErrWidget(
|
||||
errorMessage: localizations.invalid_input,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: MediaQuery.of(context).size.height / 2 - 20,
|
||||
right: 10,
|
||||
child: LanguageSwitcher(
|
||||
currentLocale: Localizations.localeOf(context),
|
||||
onLocaleChanged: widget.onLocaleChanged,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_application_1/widgets/chart_widget.dart';
|
||||
|
||||
// Widget für die Seite, die das gestapelte Säulendiagramm anzeigt
|
||||
|
@ -15,6 +16,8 @@ class ChartPage extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final localizations = AppLocalizations.of(context)!;
|
||||
|
||||
return Scaffold(
|
||||
body: CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
|
@ -34,9 +37,9 @@ class ChartPage extends StatelessWidget {
|
|||
Navigator.pop(context); // Zurück zur vorherigen Seite
|
||||
},
|
||||
),
|
||||
const Text(
|
||||
'Grafik',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
Text(
|
||||
localizations.graphic,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(width: 40),
|
||||
],
|
||||
|
@ -78,16 +81,16 @@ class ChartPage extends StatelessWidget {
|
|||
),
|
||||
children: [
|
||||
// Tabellenkopf
|
||||
const TableRow(
|
||||
decoration: BoxDecoration(
|
||||
color: CupertinoColors.darkBackgroundGray,
|
||||
TableRow(
|
||||
decoration: const BoxDecoration(
|
||||
color: CupertinoColors.black,
|
||||
),
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
'Jahr',
|
||||
style: TextStyle(
|
||||
localizations.year,
|
||||
style: const TextStyle(
|
||||
color: CupertinoColors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
|
@ -95,10 +98,10 @@ class ChartPage extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
'Einzahlungen',
|
||||
style: TextStyle(
|
||||
localizations.deposits,
|
||||
style: const TextStyle(
|
||||
color: CupertinoColors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
|
@ -106,10 +109,10 @@ class ChartPage extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
'Zinsen',
|
||||
style: TextStyle(
|
||||
localizations.interest_received,
|
||||
style: const TextStyle(
|
||||
color: CupertinoColors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
|
@ -117,10 +120,10 @@ class ChartPage extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
'Endkapital',
|
||||
style: TextStyle(
|
||||
localizations.final_capital,
|
||||
style: const TextStyle(
|
||||
color: CupertinoColors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
|
@ -144,7 +147,7 @@ class ChartPage extends StatelessWidget {
|
|||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
'€${investedMoneyList[i]}',
|
||||
localizations.amount(investedMoneyList[i]),
|
||||
style: const TextStyle(color: CupertinoColors.white),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
|
@ -152,7 +155,7 @@ class ChartPage extends StatelessWidget {
|
|||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
'€${compoundInterestList[i]}',
|
||||
localizations.amount(compoundInterestList[i]),
|
||||
style: const TextStyle(color: CupertinoColors.white),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
|
@ -160,7 +163,7 @@ class ChartPage extends StatelessWidget {
|
|||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
'€${compoundInterestList[i] + investedMoneyList[i]}',
|
||||
localizations.amount(compoundInterestList[i] + investedMoneyList[i]),
|
||||
style: const TextStyle(color: CupertinoColors.white),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_application_1/widgets/milestone_timeline_widget.dart';
|
||||
|
||||
// Widget für die Seite, die die Meilenstein-Timeline anzeigt
|
||||
|
@ -32,9 +33,9 @@ class MilestonePage extends StatelessWidget {
|
|||
Navigator.pop(context); // Zurück zur vorherigen Seite
|
||||
},
|
||||
),
|
||||
const Text(
|
||||
'Meilensteine',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.milestones,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(width: 40),
|
||||
],
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_application_1/enums.dart';
|
||||
|
||||
// Rundet den Wert im Textfeld-Controller auf die nächste ganze Zahl
|
||||
void roundToInteger(TextEditingController controller) {
|
||||
|
@ -21,13 +20,3 @@ void restoreDefaultValuesIfEmpty(TextEditingController controller) {
|
|||
controller.text = '0';
|
||||
}
|
||||
}
|
||||
|
||||
// Übersetzt das Auszahlungsintervall
|
||||
String translateInterval(PayoutInterval interval) {
|
||||
switch (interval) {
|
||||
case PayoutInterval.yearly:
|
||||
return 'jährlich';
|
||||
case PayoutInterval.monthly:
|
||||
return 'monatlich';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:syncfusion_flutter_charts/charts.dart';
|
||||
|
||||
// Widget für die Erstellung eines gestapelten Säulendiagramms
|
||||
|
@ -14,16 +15,18 @@ class StackedColumnChart extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final localizations = AppLocalizations.of(context)!;
|
||||
|
||||
return SfCartesianChart(
|
||||
legend: const Legend(
|
||||
isVisible: true,
|
||||
position: LegendPosition.top,
|
||||
),
|
||||
primaryXAxis: const CategoryAxis(
|
||||
title: AxisTitle(text: 'Jahr'),
|
||||
primaryXAxis: CategoryAxis(
|
||||
title: AxisTitle(text: localizations.year),
|
||||
),
|
||||
primaryYAxis: const NumericAxis(
|
||||
title: AxisTitle(text: 'Euro'),
|
||||
primaryYAxis: NumericAxis(
|
||||
title: AxisTitle(text: localizations.currency_written_out),
|
||||
),
|
||||
series: <CartesianSeries>[
|
||||
// Serie für die Einzahlungen (untere Werte)
|
||||
|
@ -31,7 +34,7 @@ class StackedColumnChart extends StatelessWidget {
|
|||
dataSource: _getLowerChartData(),
|
||||
xValueMapper: (SalesData sales, _) => sales.year,
|
||||
yValueMapper: (SalesData sales, _) => sales.value,
|
||||
name: 'Einzahlungen',
|
||||
name: localizations.deposits,
|
||||
color: CupertinoColors.systemRed.highContrastColor,
|
||||
),
|
||||
// Serie für die Zinsen (obere Werte)
|
||||
|
@ -39,7 +42,7 @@ class StackedColumnChart extends StatelessWidget {
|
|||
dataSource: _getUpperChartData(),
|
||||
xValueMapper: (SalesData sales, _) => sales.year,
|
||||
yValueMapper: (SalesData sales, _) => sales.value,
|
||||
name: 'Zinsen',
|
||||
name: localizations.interest_received,
|
||||
color: CupertinoColors.systemBlue.highContrastColor,
|
||||
),
|
||||
],
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_application_1/enums.dart';
|
||||
import 'package:flutter_application_1/utils.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
// Widget zur Auswahl des Ausschüttungsintervalls
|
||||
class IntervalWidget extends StatefulWidget {
|
||||
|
@ -41,13 +40,16 @@ class IntervalWidgetState extends State<IntervalWidget> {
|
|||
}
|
||||
|
||||
OverlayEntry _createOverlayEntry() {
|
||||
final localizations = AppLocalizations.of(context)!;
|
||||
final isMobile = Theme.of(context).platform == TargetPlatform.iOS || Theme.of(context).platform == TargetPlatform.android;
|
||||
|
||||
return OverlayEntry(
|
||||
builder: (context) => Positioned(
|
||||
width: 160,
|
||||
child: CompositedTransformFollower(
|
||||
link: _layerLink,
|
||||
showWhenUnlinked: false,
|
||||
offset: const Offset(0.0, 60),
|
||||
offset: Offset(0.0, isMobile ? 70 : 60),
|
||||
child: Material(
|
||||
color: CupertinoColors.extraLightBackgroundGray,
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
|
@ -57,34 +59,37 @@ class IntervalWidgetState extends State<IntervalWidget> {
|
|||
children: [
|
||||
// ListTile für jährliches Ausschüttungsintervall
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 15.0),
|
||||
title: Text(
|
||||
translateInterval(PayoutInterval.yearly),
|
||||
localizations.yearly,
|
||||
style: const TextStyle(
|
||||
color: CupertinoColors.black,
|
||||
fontSize: 14,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
trailing: widget.selectedInterval == translateInterval(PayoutInterval.yearly)
|
||||
? const Icon(CupertinoIcons.checkmark_alt, color: CupertinoColors.black)
|
||||
trailing: widget.selectedInterval == localizations.yearly
|
||||
? const Icon(CupertinoIcons.checkmark_alt, color: CupertinoColors.black, size: 20,)
|
||||
: null,
|
||||
onTap: () {
|
||||
widget.onChanged(translateInterval(PayoutInterval.yearly));
|
||||
widget.onChanged(localizations.yearly);
|
||||
_toggleDropdown();
|
||||
},
|
||||
),
|
||||
// ListTile für monatliches Ausschüttungsintervall
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 15.0),
|
||||
title: Text(
|
||||
translateInterval(PayoutInterval.monthly),
|
||||
localizations.monthly,
|
||||
style: const TextStyle(
|
||||
color: CupertinoColors.black,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
trailing: widget.selectedInterval == translateInterval(PayoutInterval.monthly)
|
||||
? const Icon(CupertinoIcons.checkmark_alt, color: CupertinoColors.black)
|
||||
trailing: widget.selectedInterval == localizations.monthly
|
||||
? const Icon(CupertinoIcons.checkmark_alt, color: CupertinoColors.black, size: 20,)
|
||||
: null,
|
||||
onTap: () {
|
||||
widget.onChanged(translateInterval(PayoutInterval.monthly));
|
||||
widget.onChanged(localizations.monthly);
|
||||
_toggleDropdown();
|
||||
},
|
||||
),
|
||||
|
@ -98,34 +103,36 @@ class IntervalWidgetState extends State<IntervalWidget> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final localizations = AppLocalizations.of(context)!;
|
||||
|
||||
return CompositedTransformTarget(
|
||||
link: _layerLink,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Row(
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'Ausschüttungsintervall',
|
||||
style: TextStyle(
|
||||
localizations.payout_interval,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 5),
|
||||
const SizedBox(width: 5),
|
||||
// Tooltip mit Erklärung zum Ausschüttungsintervall
|
||||
Tooltip(
|
||||
message: 'Das Ausschüttungsintervall bezieht sich darauf, wie oft die Erträge aus Ihrer Investition ausgeschüttet werden.',
|
||||
message: localizations.payout_interval_tooltiptext,
|
||||
triggerMode: TooltipTriggerMode.tap,
|
||||
decoration: BoxDecoration(
|
||||
decoration: const BoxDecoration(
|
||||
color: CupertinoColors.black,
|
||||
borderRadius: BorderRadius.all(Radius.circular(5))
|
||||
),
|
||||
textStyle: TextStyle(
|
||||
textStyle: const TextStyle(
|
||||
color: CupertinoColors.white,
|
||||
),
|
||||
margin: EdgeInsets.all(20),
|
||||
child: Icon(CupertinoIcons.question_circle_fill, size: 15),
|
||||
margin: const EdgeInsets.all(20),
|
||||
child: const Icon(CupertinoIcons.question_circle_fill, size: 15),
|
||||
),
|
||||
]
|
||||
),
|
||||
|
@ -135,6 +142,9 @@ class IntervalWidgetState extends State<IntervalWidget> {
|
|||
child: ElevatedButton(
|
||||
onPressed: _toggleDropdown,
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all<EdgeInsetsGeometry>(
|
||||
const EdgeInsets.symmetric(horizontal: 15.0),
|
||||
),
|
||||
backgroundColor: MaterialStateProperty.all<Color>(CupertinoColors.extraLightBackgroundGray),
|
||||
foregroundColor: MaterialStateProperty.all<Color>(CupertinoColors.black),
|
||||
overlayColor: MaterialStateProperty.all<Color>(CupertinoColors.extraLightBackgroundGray),
|
||||
|
@ -150,9 +160,13 @@ class IntervalWidgetState extends State<IntervalWidget> {
|
|||
Expanded(
|
||||
child: Text(
|
||||
widget.selectedInterval, // Text des ausgewählten Intervalls
|
||||
style: const TextStyle(
|
||||
color: CupertinoColors.black,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
Icon(isExpanded ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down),
|
||||
),
|
||||
Icon(isExpanded ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down, size: 20,),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
class LanguageSwitcher extends StatefulWidget {
|
||||
final Locale currentLocale;
|
||||
final Function(Locale) onLocaleChanged;
|
||||
|
||||
const LanguageSwitcher({
|
||||
super.key,
|
||||
required this.currentLocale,
|
||||
required this.onLocaleChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
LanguageSwitcherState createState() => LanguageSwitcherState();
|
||||
}
|
||||
|
||||
class LanguageSwitcherState extends State<LanguageSwitcher> {
|
||||
late Locale _selectedLocale;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_selectedLocale = widget.currentLocale;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final localizations = AppLocalizations.of(context)!;
|
||||
final isMobile = Theme.of(context).platform == TargetPlatform.iOS || Theme.of(context).platform == TargetPlatform.android;
|
||||
|
||||
return Theme(
|
||||
data: ThemeData.light().copyWith(
|
||||
popupMenuTheme: PopupMenuThemeData(
|
||||
color: CupertinoColors.extraLightBackgroundGray,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
textStyle: const TextStyle(color: CupertinoColors.black),
|
||||
),
|
||||
tooltipTheme: TooltipThemeData(
|
||||
decoration: BoxDecoration(
|
||||
color: CupertinoColors.black,
|
||||
borderRadius: BorderRadius.circular(4.0),
|
||||
),
|
||||
textStyle: const TextStyle(color: CupertinoColors.white),
|
||||
),
|
||||
),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: CupertinoColors.extraLightBackgroundGray,
|
||||
borderRadius: BorderRadius.circular(100.0),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Colors.black26,
|
||||
blurRadius: 8.0,
|
||||
offset: Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: PopupMenuButton<Locale>(
|
||||
offset: Offset(0.0, isMobile ? 55 : 45),
|
||||
onSelected: (Locale locale) {
|
||||
setState(() {
|
||||
_selectedLocale = locale;
|
||||
});
|
||||
widget.onLocaleChanged(locale);
|
||||
},
|
||||
tooltip: localizations.choose_language,
|
||||
icon: const Icon(
|
||||
CupertinoIcons.globe,
|
||||
color: CupertinoColors.black,
|
||||
),
|
||||
itemBuilder: (BuildContext context) {
|
||||
return [
|
||||
PopupMenuItem<Locale>(
|
||||
value: const Locale('en'),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(AppLocalizations.of(context)!.language_english),
|
||||
if (_selectedLocale.languageCode == 'en') const Icon(CupertinoIcons.checkmark_alt, size: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem<Locale>(
|
||||
value: const Locale('de'),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(AppLocalizations.of(context)!.language_german),
|
||||
if (_selectedLocale.languageCode == 'de') const Icon(CupertinoIcons.checkmark_alt, size: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
];
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -39,7 +39,7 @@ class MilestoneTimeline extends StatelessWidget {
|
|||
children: [
|
||||
Text(
|
||||
milestoneEmoji,
|
||||
style: const TextStyle(fontSize: 20),
|
||||
style: const TextStyle(fontSize: 30),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.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';
|
||||
|
||||
// Widget zur Anzeige der Ergebnisse der Berechnungen und zur Navigation zu anderen Seiten
|
||||
|
@ -31,22 +31,29 @@ class ResultWidget extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final localizations = AppLocalizations.of(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€'},
|
||||
{'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)},
|
||||
];
|
||||
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
_buildResultRow('Endkapital:', '$compoundInterest€'),
|
||||
_buildResultRow('Einzahlungen:', '$investedMoney€'),
|
||||
_buildResultRow('Erhaltene Zinsen:', '${double.parse(compoundInterest) + double.parse(investedMoney)}€'),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Expanded(child: _buildResultBox(localizations.final_capital, localizations.amount(compoundInterest))),
|
||||
Expanded(child: _buildResultBox(localizations.deposits, localizations.amount(investedMoney))),
|
||||
Expanded(child: _buildResultBox(localizations.interest_received, localizations.amount(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.'
|
||||
localizations.result_text(double.parse(compoundInterest) - double.parse(investedMoney), compoundInterest, interestRate, investedMoney, time, monthlySavingsRate)
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
CustomImageButton(
|
||||
|
@ -62,9 +69,9 @@ class ResultWidget extends StatelessWidget {
|
|||
);
|
||||
},
|
||||
backgroundImage: 'assets/images/button_bg1.jpg',
|
||||
child: const Text(
|
||||
'Grafik',
|
||||
style: TextStyle(color: CupertinoColors.white, fontSize: 20),
|
||||
child: Text(
|
||||
localizations.graphic,
|
||||
style: const TextStyle(color: CupertinoColors.white, fontSize: 20),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
@ -82,41 +89,41 @@ class ResultWidget extends StatelessWidget {
|
|||
);
|
||||
},
|
||||
backgroundImage: 'assets/images/button_bg2.jpg',
|
||||
child: const Text(
|
||||
'Meilensteine',
|
||||
style: TextStyle(color: CupertinoColors.white, fontSize: 20),
|
||||
child: Text(
|
||||
localizations.milestones,
|
||||
style: const 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,
|
||||
// Widget zum Aufbau einer Ergebnisbox
|
||||
Widget _buildResultBox(String label, String value) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 5.0),
|
||||
decoration: BoxDecoration(
|
||||
color: CupertinoColors.extraLightBackgroundGray,
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
Text(
|
||||
label,
|
||||
textAlign: TextAlign.start,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
Flexible(
|
||||
child: Text(
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
value,
|
||||
textAlign: TextAlign.end,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
11
pubspec.lock
11
pubspec.lock
|
@ -70,19 +70,24 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
flutter_localizations:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
intl:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: intl
|
||||
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.19.0"
|
||||
version: "0.18.1"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -36,6 +36,9 @@ dependencies:
|
|||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.6
|
||||
syncfusion_flutter_charts: ^25.1.42+1
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
intl: any
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
@ -53,7 +56,7 @@ dev_dependencies:
|
|||
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
|
||||
generate: true
|
||||
# The following line ensures that the Material Icons font is
|
||||
# included with your application, so that you can use the icons in
|
||||
# the material Icons class.
|
||||
|
|
|
@ -68,10 +68,5 @@ void main() {
|
|||
restoreDefaultValuesIfEmpty(controller);
|
||||
expect(controller.text, '5');
|
||||
});
|
||||
|
||||
test('translateInterval should return correct translation', () {
|
||||
expect(translateInterval(PayoutInterval.yearly), 'jährlich');
|
||||
expect(translateInterval(PayoutInterval.monthly), 'monatlich');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
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/widgets/chart_widget.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_application_1/enums.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';
|
||||
|
@ -104,103 +105,46 @@ void main() {
|
|||
expect(controller.text, '12345');
|
||||
});
|
||||
});
|
||||
group('Interal Widget Tests', () {
|
||||
testWidgets('Initial state', (WidgetTester tester) async {
|
||||
const selectedInterval = 'jährlich';
|
||||
group('Interval Widget Tests', () {
|
||||
testWidgets('Shows correct localized texts and handles dropdown selection', (WidgetTester tester) async {
|
||||
const Locale testLocale = Locale('en');
|
||||
final localizations = await AppLocalizations.delegate.load(testLocale);
|
||||
|
||||
String selectedInterval = localizations.yearly;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
locale: testLocale,
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
home: Scaffold(
|
||||
body: IntervalWidget(
|
||||
selectedInterval: selectedInterval,
|
||||
onChanged: (newInterval) {},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('Ausschüttungsintervall'), findsOneWidget);
|
||||
expect(find.text(selectedInterval), findsOneWidget);
|
||||
expect(find.byIcon(Icons.keyboard_arrow_down), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Tapping the button expands the dropdown', (WidgetTester tester) async {
|
||||
const selectedInterval = 'jährlich';
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: IntervalWidget(
|
||||
selectedInterval: selectedInterval,
|
||||
onChanged: (newInterval) {
|
||||
onChanged: (String newValue) {
|
||||
selectedInterval = newValue;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.byType(ElevatedButton));
|
||||
await tester.pump();
|
||||
expect(find.text(localizations.payout_interval), findsOneWidget);
|
||||
expect(find.text(localizations.payout_interval_tooltiptext), findsNothing);
|
||||
|
||||
expect(find.byType(ListTile), findsNWidgets(2));
|
||||
expect(find.byIcon(CupertinoIcons.checkmark_alt), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Selecting an interval updates the state', (WidgetTester tester) async {
|
||||
const selectedInterval = 'jährlich';
|
||||
var currentInterval = selectedInterval;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: IntervalWidget(
|
||||
selectedInterval: selectedInterval,
|
||||
onChanged: (newInterval) {
|
||||
currentInterval = newInterval;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(find.text(localizations.yearly), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byType(ElevatedButton));
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.text('monatlich'));
|
||||
await tester.pump();
|
||||
|
||||
expect(currentInterval, 'monatlich');
|
||||
expect(find.byType(ListTile), findsNothing);
|
||||
expect(find.byIcon(Icons.keyboard_arrow_down), 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();
|
||||
|
||||
expect(find.text(localizations.yearly), findsNWidgets(2)); // Eins im Button und eins im Dropdown-Menü
|
||||
expect(find.text(localizations.monthly), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text(localizations.monthly).last);
|
||||
expect(selectedInterval, localizations.monthly);
|
||||
});
|
||||
});
|
||||
group('Milestone Timeline Widget Tests', () {
|
||||
|
@ -232,20 +176,30 @@ void main() {
|
|||
});
|
||||
});
|
||||
group('Result Widget Tests', () {
|
||||
testWidgets('Displays result values correctly', (WidgetTester tester) async {
|
||||
const compoundInterest = '1000';
|
||||
const investedMoney = '500';
|
||||
const time = '5';
|
||||
testWidgets('Shows correct localized texts', (WidgetTester tester) async {
|
||||
const Locale testLocale = Locale('en');
|
||||
final localizations = await AppLocalizations.delegate.load(testLocale);
|
||||
|
||||
const compoundInterest = '12000';
|
||||
const investedMoney = '10000';
|
||||
const time = '10';
|
||||
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];
|
||||
const payoutInterval = PayoutInterval.yearly;
|
||||
final List<double> investedMoneyList = [1000, 2000, 3000, 4000, 5000];
|
||||
final List<double> compoundInterestList = [1100, 2300, 3600, 5000, 6600];
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Material(
|
||||
child: ResultWidget(
|
||||
locale: testLocale,
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
home: Scaffold(
|
||||
body: ResultWidget(
|
||||
compoundInterest: compoundInterest,
|
||||
investedMoney: investedMoney,
|
||||
time: time,
|
||||
|
@ -259,63 +213,25 @@ void main() {
|
|||
),
|
||||
);
|
||||
|
||||
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);
|
||||
});
|
||||
expect(find.text(localizations.final_capital), findsOneWidget);
|
||||
expect(find.text(localizations.amount(compoundInterest)), findsOneWidget);
|
||||
expect(find.text(localizations.deposits), findsOneWidget);
|
||||
expect(find.text(localizations.amount(investedMoney)), findsOneWidget);
|
||||
expect(find.text(localizations.interest_received), findsOneWidget);
|
||||
expect(find.text(localizations.amount(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,
|
||||
),
|
||||
),
|
||||
),
|
||||
final resultText = localizations.result_text(
|
||||
double.parse(compoundInterest) - double.parse(investedMoney),
|
||||
compoundInterest,
|
||||
interestRate,
|
||||
investedMoney,
|
||||
time,
|
||||
monthlySavingsRate,
|
||||
);
|
||||
expect(find.text(resultText), findsOneWidget);
|
||||
|
||||
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);
|
||||
expect(find.text(localizations.graphic), findsOneWidget);
|
||||
expect(find.text(localizations.milestones), findsOneWidget);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -337,4 +253,97 @@ void main() {
|
|||
expect(find.text(errorMessage), findsOneWidget);
|
||||
});
|
||||
});
|
||||
group('Chart Page Tests', () {
|
||||
testWidgets('Shows correct localized texts and table data', (WidgetTester tester) async {
|
||||
const Locale testLocale = Locale('en');
|
||||
final localizations = await AppLocalizations.delegate.load(testLocale);
|
||||
|
||||
final investedMoneyList = [1000.0, 2000.0, 3000.0, 4000.0, 5000.0];
|
||||
final compoundInterestList = [1100.0, 2200.0, 3300.0, 4400.0, 5500.0];
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
locale: testLocale,
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
home: ChartPage(
|
||||
investedMoneyList: investedMoneyList,
|
||||
compoundInterestList: compoundInterestList,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text(localizations.graphic), findsOneWidget);
|
||||
|
||||
expect(find.byType(SfCartesianChart), findsOneWidget);
|
||||
|
||||
expect(find.text(localizations.year), findsOneWidget);
|
||||
expect(find.text(localizations.deposits), findsNWidgets(2));
|
||||
expect(find.text(localizations.interest_received), findsNWidgets(2));
|
||||
expect(find.text(localizations.final_capital), findsOneWidget);
|
||||
|
||||
for (int i = 0; i < investedMoneyList.length; i++) {
|
||||
expect(find.text('${i + 1}'), findsOneWidget);
|
||||
expect(find.text(localizations.amount(investedMoneyList[i])), findsOneWidget);
|
||||
expect(find.text(localizations.amount(compoundInterestList[i])), findsOneWidget);
|
||||
expect(find.text(localizations.amount(compoundInterestList[i] + investedMoneyList[i])), findsOneWidget);
|
||||
}
|
||||
|
||||
await tester.tap(find.byIcon(CupertinoIcons.chevron_left));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byIcon(CupertinoIcons.chevron_left), findsNothing);
|
||||
});
|
||||
});
|
||||
group('Milestone Page Tests', () {
|
||||
testWidgets('Shows correct localized texts and milestone data', (WidgetTester tester) async {
|
||||
const Locale testLocale = Locale('en');
|
||||
final localizations = await AppLocalizations.delegate.load(testLocale);
|
||||
|
||||
const compoundInterest = '50000';
|
||||
const investedMoney = '30000';
|
||||
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)},
|
||||
];
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
locale: testLocale,
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
home: MilestonePage(
|
||||
compoundInterest: compoundInterest,
|
||||
investedMoney: investedMoney,
|
||||
milestoneList: milestoneList,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text(localizations.milestones), findsOneWidget);
|
||||
|
||||
expect(find.byType(MilestoneTimeline), findsOneWidget);
|
||||
|
||||
for (var milestone in milestoneList) {
|
||||
expect(find.text(milestone['emoji']), findsOneWidget);
|
||||
expect(find.text(milestone['text']), findsOneWidget);
|
||||
}
|
||||
|
||||
await tester.tap(find.byIcon(CupertinoIcons.chevron_left));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byIcon(CupertinoIcons.chevron_left), findsNothing);
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue