final beta Version

This commit is contained in:
2025-08-24 19:54:36 +02:00
parent fec555991d
commit c183d84a86
16 changed files with 923 additions and 209 deletions

View File

@@ -0,0 +1,264 @@
import 'package:appwrite/appwrite.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import '../../data/models/chart_model.dart';
import '../../data/models/tank_model.dart';
import '../../data/repository/appwrite_repository.dart';
import '../../utils/extensions/static_helper.dart';
class GraphController extends GetxController {
//AppWrite API-REST get Data
final AppwriteRepository _authRepository = AppwriteRepository();
final _dataBox = GetStorage('MyUserStorage');
final szRxUserId = 'NoUser'.obs;
final szBarTitle = ''.obs;
final listYearModel = <YearModel>[].obs;
late List<AppWriteTankModel> tankListOriginal;
final tankList = <AppWriteTankModel>[].obs;
final yearValue = DateTime.now().year.obs;
final blIsLoading = false.obs;
final dataPointsEuro = <double>[].obs;
final dataPointsGasoline = <double>[].obs;
final pointsEuro = <PricePoints>[].obs;
final pointsGasoline = <PricePoints>[].obs;
final sumListData = <SumDataModel>[].obs;
final mnCurrentAveragePerLiter = 0.00.obs;
final pointsPerLiter = <PricePoints>[].obs;
final dataPointsPerLiter = <double>[].obs;
final mnCurrentSummEuroYear = 0.0.obs;
final mnCurrentSummLiterYear = 0.0.obs;
@override
void onInit() {
szRxUserId(_dataBox.read('userId'));
_getTankList();
super.onInit();
}
@override
void onReady() {
super.onReady();
}
@override
void onClose() {}
void _getTankList() async {
blIsLoading(true);
bool isErrorByLoading = false;
String message = '';
try {
await _authRepository
.listTankStops(szRxUserId.value)
.then((tankListData) {
if (tankListData.documents.isEmpty) {
blIsLoading(false);
isErrorByLoading = true;
message = 'Leere Liste keine Daten vorhanden';
return;
}
tankList.clear();
var data = tankListData.toMap();
List d = data['documents'].toList();
tankList.value = d
.map((e) => AppWriteTankModel.fromMap(e['data']))
.toList();
tankList.sort((a, b) {
final DateTime dateA = DateTime.parse(a.date);
final DateTime dateB = DateTime.parse(b.date);
return dateB.compareTo(dateA);
});
message = 'Liste wurde erfolgreich geladen';
})
.catchError((error) {
blIsLoading(true);
isErrorByLoading = true;
if (error is AppwriteException) {
message = error.message!;
} else {
message = 'Uuups da ist was schief gelaufen';
}
});
} catch (e) {
blIsLoading(true);
isErrorByLoading = true;
message = 'Fehler beim Laden der Tankliste';
print('Error fetching tank list: $e');
}
String title = isErrorByLoading ? 'Fehler' : 'Erfolg';
Get.snackbar(
title,
message,
backgroundColor: isErrorByLoading ? Colors.red : Colors.green,
snackPosition: SnackPosition.BOTTOM,
duration: const Duration(seconds: 4),
);
tankListOriginal = tankList;
update();
setListMapYear();
getTankListPerYear();
}
List<String> getHeadDescription() {
List<String> localListString = [];
tankList.sort((a, b) {
final DateTime dateA = DateTime.parse(a.date);
final DateTime dateB = DateTime.parse(b.date);
return dateB.compareTo(dateA);
});
if (tankList.isNotEmpty) {
for (var benzinItem in tankList) {
String szDay = '';
var dateDay = DateTime.parse(benzinItem.date);
if (dateDay.day <= 9) {
szDay = '0${dateDay.day}';
} else {
szDay = '${dateDay.day}';
}
String szMonth = '';
if (dateDay.month <= 9) {
szMonth = '0${dateDay.month}';
} else {
szMonth = '${dateDay.month}';
}
String szData = '$szDay$szMonth';
localListString.add(szData);
}
}
update();
return localListString;
}
void setListMapYear() {
int year = 2022;
for (var i = year; i <= DateTime.now().year; i++) {
listYearModel.add(YearModel('Jahr $year', year));
year = year + 1;
}
update();
}
void getTankListPerYear() {
blIsLoading.value = true;
tankList
.where((e) => DateTime.parse(e.date).year == yearValue.value)
.toList();
getDataPointsForGraph();
getMonthListData();
blIsLoading.value = false;
update();
}
void getDataPointsForGraph() {
//daten ermitteln für Jahr xxxx
//debugPrint('${yearValue.value}');
List<double> mnDataPointsEuro = [];
List<double> mnDataPointsGasoline = [];
// 08.08.23 Mod. add Price per Liter
List<double> mnDataPointsPerLiter = [];
var mnSummPerLiter = 0.0;
var mnTankListCount = tankList.length;
for (var item in tankList) {
var mnLiterGesamt = double.parse(item.liters);
var mnEuroGesamt =
double.parse(item.liters) * double.parse(item.pricePerLiter);
var mnPerLiter = double.parse(item.pricePerLiter);
mnDataPointsEuro.add(mnEuroGesamt);
mnDataPointsGasoline.add(mnLiterGesamt);
mnDataPointsPerLiter.add(mnPerLiter.toPrecision(2));
mnSummPerLiter += mnPerLiter;
}
if (mnSummPerLiter > 0 && mnTankListCount > 0) {
mnCurrentAveragePerLiter.value = (mnSummPerLiter / mnTankListCount)
.toPrecision(2);
}
dataPointsEuro.value = mnDataPointsEuro;
dataPointsGasoline.value = mnDataPointsGasoline;
dataPointsPerLiter.value = mnDataPointsPerLiter;
getPricePointsEuro();
getPricePointsGasoline();
getPricePointsPerLiter();
update();
}
void getMonthListData() {
if (tankList.isNotEmpty) {
sumListData.clear();
List<AppWriteTankModel> tankListPerYear = tankList;
List<dynamic> monthList = StaticHelper.listMonth;
for (var monthMap in monthList) {
var szMonth = monthMap['month'].toString();
var szValueMonth = monthMap['value'];
var result = StaticHelper.staticListGetDaysInBetween(
tankListPerYear,
szValueMonth,
yearValue.value,
);
var tankListPerMon = result as List<AppWriteTankModel>;
if (tankListPerMon.isNotEmpty) {
SumDataModel sumDataModel = SumDataModel('', '', '', 0);
sumDataModel.szMonth = szMonth;
sumDataModel.mnTankungen = tankListPerMon.length;
double mnSumEuro = 0.0;
double mnSumBenzin = 0.0;
for (var tankungModel in tankListPerMon) {
var mnEuroGesamt =
double.parse(tankungModel.liters) *
double.parse(tankungModel.pricePerLiter);
mnSumBenzin += double.parse(tankungModel.liters);
mnSumEuro += mnEuroGesamt;
}
sumDataModel.szVerbrauch = mnSumBenzin.toStringAsFixed(2);
sumDataModel.szSumme = mnSumEuro.toStringAsFixed(2);
sumListData.add(sumDataModel);
}
}
_currentSumForYear();
}
}
void getPricePointsEuro() {
var listPoints = dataPointsEuro.indexed
.map((e) => PricePoints(x: e.$1.toDouble(), y: e.$2))
.toList();
pointsEuro.value = listPoints;
update();
}
void getPricePointsGasoline() {
var listPoints = dataPointsGasoline.indexed
.map((e) => PricePoints(x: e.$1.toDouble(), y: e.$2))
.toList();
pointsGasoline.value = listPoints;
update();
}
void getPricePointsPerLiter() {
var listPoints = dataPointsPerLiter.indexed
.map((e) => PricePoints(x: e.$1.toDouble(), y: e.$2))
.toList();
pointsPerLiter.value = listPoints;
update();
}
void _currentSumForYear() {
mnCurrentSummEuroYear.value = 0.0;
mnCurrentSummLiterYear.value = 0.0;
if (sumListData.isNotEmpty) {
for (var rxListItem in sumListData) {
mnCurrentSummEuroYear.value =
mnCurrentSummEuroYear.value + double.parse(rxListItem.szSumme!);
mnCurrentSummLiterYear.value =
mnCurrentSummLiterYear.value +
double.parse(rxListItem.szVerbrauch!);
}
}
update();
}
}

View File

@@ -0,0 +1,148 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../utils/extensions/constants.dart';
import './graph_controller.dart';
import './widgets/chart_desc.dart';
import './widgets/chart_lines.dart';
import './widgets/chart_single_lines.dart';
import './widgets/my_list_tile_card.dart';
import '../../pages/tanklist/tanklist_view.dart';
class GraphPage extends GetView<GraphController> {
static const namedRoute = '/graph-statistic-page';
const GraphPage({super.key});
@override
Widget build(BuildContext context) {
Size queryDisplaySize = MediaQuery.of(context).size;
var graphCtrl = controller;
return PopScope(
canPop: false,
child: SafeArea(
child: Scaffold(
body: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
width: 50,
child: IconButton(
onPressed: () =>
Get.offAndToNamed(TanklistPage.namedRoute),
icon: const Icon(Icons.list),
),
),
SizedBox(
width: queryDisplaySize.width - 70,
child: Obx(() => _displayDropDownMenue(graphCtrl)),
),
],
),
),
const SizedBox(height: 8.0),
Obx(
() => Container(
color: Colors.grey.shade900,
padding: const EdgeInsets.symmetric(horizontal: 40),
width: MediaQuery.of(context).size.width,
child: Column(
children: [
ChartDescription(
backgroundColor: kColorEuroChart,
mnWidth: double.infinity,
szDescText: '€ Jahressumme',
szCurrentSumData:
graphCtrl.mnCurrentSummEuroYear.toStringAsFixed(2),
),
ChartDescription(
backgroundColor: kColorBenzinChart,
mnWidth: double.infinity,
szDescText: 'L Jahresverbrauch',
szCurrentSumData:
graphCtrl.mnCurrentSummLiterYear.toStringAsFixed(2),
),
],
),
),
),
const Divider(),
LineChartLines(
firstLineColor: kColorEuroChart,
secondLineColor: kColorBenzinChart,
),
Obx(
() => graphCtrl.blIsLoading.value
? const Center(
child: CircularProgressIndicator(color: Colors.cyan),
)
: const SizedBox.shrink(),
),
const Divider(),
Obx(
() => Container(
color: Colors.grey.shade900,
padding: const EdgeInsets.symmetric(horizontal: 40),
child: ChartDescription(
backgroundColor: kColorPerLiterChart,
mnWidth: queryDisplaySize.width,
szDescText: '€/L Durchschnitt',
szCurrentSumData: graphCtrl.mnCurrentAveragePerLiter.value
.toStringAsFixed(2),
),
),
),
const Divider(),
LineChartSingleLine(singleLineColor: kColorPerLiterChart),
const Divider(),
Obx(
() => graphCtrl.blIsLoading.value
? const Center(
child: CircularProgressIndicator(color: Colors.cyan),
)
: const SizedBox.shrink(),
),
Obx(
() => Expanded(
child: ListView.builder(
itemCount: graphCtrl.sumListData.length,
itemBuilder: (context, index) {
return MyListTileCard(graphCtrl: graphCtrl, index: index);
},
),
),
),
],
),
),
),
);
}
DropdownButtonFormField _displayDropDownMenue(GraphController controller) {
var graphCtrl = controller;
var list = graphCtrl.listYearModel;
var valueOfList = graphCtrl.yearValue.value;
return DropdownButtonFormField(
decoration: kInputDecorationDropDownMenueYear,
items: list.map((map) {
return DropdownMenuItem(
value: map.mnYear,
child: Text(map.szDescription),
);
}).toList(),
isDense: true,
isExpanded: true,
value: valueOfList,
onChanged: ((value) {
valueOfList = value;
graphCtrl.yearValue.value = valueOfList;
if (graphCtrl.yearValue.value > 0) {
graphCtrl.getTankListPerYear();
}
}),
);
}
}

View File

@@ -0,0 +1,124 @@
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import '../../../utils/extensions/constants.dart';
import '../graph_controller.dart';
class BarChartWidget extends StatelessWidget {
final GraphController graphCtrl;
const BarChartWidget({super.key, required this.graphCtrl});
@override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: 2,
child: BarChart(
BarChartData(
//alignment: BarChartAlignment.center,
maxY: 50,
baselineY: 0,
barTouchData: barTouchData,
titlesData: titlesData,
borderData: borderData,
barGroups: barGroups,
gridData: const FlGridData(show: false),
),
),
);
}
BarTouchData get barTouchData => BarTouchData(
enabled: false,
touchTooltipData: BarTouchTooltipData(
//tooltipBgColor: Colors.transparent,
tooltipPadding: EdgeInsets.zero,
tooltipMargin: 8,
getTooltipItem:
(
BarChartGroupData group,
int groupIndex,
BarChartRodData rod,
int rodIndex,
) {
return BarTooltipItem(
rod.toY.round().toString(),
const TextStyle(color: Colors.cyan, fontWeight: FontWeight.bold),
);
},
),
);
Widget getTitles(double value, TitleMeta meta) {
List<String> dayMonListString = graphCtrl.getHeadDescription();
for (var i = 0; i < dayMonListString.length; i++) {
if (i == value.toInt()) {
graphCtrl.szBarTitle.value = dayMonListString[i];
}
}
return SideTitleWidget(
fitInside: SideTitleFitInsideData.fromTitleMeta(meta),
space: 4,
meta: meta,
child: Text(graphCtrl.szBarTitle.value, style: kBarTitleStyle),
);
}
FlTitlesData get titlesData => FlTitlesData(
show: true,
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 30,
getTitlesWidget: getTitles,
),
),
leftTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
);
FlBorderData get borderData => FlBorderData(show: false);
LinearGradient get _barsGradient => LinearGradient(
colors: [Colors.blue.shade400, Colors.red.shade300],
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
);
List<BarChartGroupData> get barGroups => [
BarChartGroupData(
x: 0,
barRods: [BarChartRodData(toY: 8, gradient: _barsGradient)],
showingTooltipIndicators: [0],
),
BarChartGroupData(
x: 1,
barRods: [BarChartRodData(toY: 10, gradient: _barsGradient)],
showingTooltipIndicators: [0],
),
BarChartGroupData(
x: 2,
barRods: [BarChartRodData(toY: 14, gradient: _barsGradient)],
showingTooltipIndicators: [0],
),
BarChartGroupData(
x: 3,
barRods: [BarChartRodData(toY: 15, gradient: _barsGradient)],
showingTooltipIndicators: [0],
),
BarChartGroupData(
x: 4,
barRods: [BarChartRodData(toY: 13, gradient: _barsGradient)],
showingTooltipIndicators: [0],
),
BarChartGroupData(
x: 5,
barRods: [BarChartRodData(toY: 10, gradient: _barsGradient)],
showingTooltipIndicators: [0],
),
BarChartGroupData(
x: 6,
barRods: [BarChartRodData(toY: 16, gradient: _barsGradient)],
showingTooltipIndicators: [0],
),
];
}

View File

@@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import '../../../utils/extensions/constants.dart';
class ChartDescription extends StatelessWidget {
final double mnWidth;
final Color backgroundColor;
final String szDescText;
final String szCurrentSumData;
const ChartDescription({
super.key,
required this.mnWidth,
required this.backgroundColor,
required this.szDescText,
required this.szCurrentSumData,
});
@override
Widget build(BuildContext context) {
return SizedBox(
width: mnWidth,
child: Row(
children: [
Container(
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(15),
),
width: 70,
height: 20,
child: Center(
child: Text(szCurrentSumData, style: kChartDescriptionFontStyle),
),
),
const SizedBox(width: 10),
Text(szDescText, style: kTextStyle),
],
),
);
}
}

View File

@@ -0,0 +1,49 @@
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:get/get.dart';
import '../graph_controller.dart';
// 08.08.23 Mod. Line Chart with tree Lines
class LineChartLines extends GetView<GraphController> {
final Color firstLineColor;
final Color secondLineColor;
const LineChartLines({
super.key,
required this.firstLineColor,
required this.secondLineColor,
});
@override
Widget build(BuildContext context) {
final GraphController gCtrl = controller;
return AspectRatio(
aspectRatio: 3,
child: Obx(
() => LineChart(
LineChartData(
lineBarsData: [
LineChartBarData(
color: firstLineColor,
spots: gCtrl.pointsEuro
.map((point) => FlSpot(point.x, point.y))
.toList(),
isCurved: false,
dotData: const FlDotData(show: true),
),
LineChartBarData(
isStepLineChart: false,
color: secondLineColor,
spots: gCtrl.pointsGasoline
.map((point) => FlSpot(point.x, point.y))
.toList(),
isCurved: false,
dotData: const FlDotData(show: true),
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:get/get.dart';
import '../graph_controller.dart';
// 08.08.23 Mod. Line Chart with tree Lines
class LineChartSingleLine extends GetView<GraphController> {
final Color singleLineColor;
const LineChartSingleLine({super.key, required this.singleLineColor});
@override
Widget build(BuildContext context) {
final GraphController gCtrl = controller;
return AspectRatio(
aspectRatio: 3.55,
child: Obx(
() => LineChart(
LineChartData(
lineBarsData: [
LineChartBarData(
color: singleLineColor,
spots: gCtrl.pointsPerLiter
.map((point) => FlSpot(point.x, point.y))
.toList(),
isCurved: false,
dotData: const FlDotData(show: true),
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,46 @@
import 'package:flutter/material.dart';
import '../../../utils/extensions/constants.dart';
import '../graph_controller.dart';
class MyListTileCard extends StatelessWidget {
const MyListTileCard({
super.key,
required this.graphCtrl,
required this.index,
});
final GraphController graphCtrl;
final int index;
@override
Widget build(BuildContext context) {
return Card(
shadowColor: Colors.grey.shade200,
child: ListTile(
title: Text(
'${graphCtrl.sumListData[index].szMonth}',
style: kTextStyle,
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Monatsausgaben: ${graphCtrl.sumListData[index].szSumme}',
style: kTextStyleSub,
),
Text(
'Monatsverbrauch: ${graphCtrl.sumListData[index].szVerbrauch} L',
style: kTextStyleSub,
),
Text(
'Tankungen: ${graphCtrl.sumListData[index].mnTankungen}',
style: kTextStyleSub,
),
],
),
),
);
}
}