finishCommit

This commit is contained in:
atseirjo
2026-07-23 09:17:56 +02:00
parent 9f7ee3f356
commit c8a3cc9812
12 changed files with 822 additions and 175 deletions
+199 -83
View File
@@ -1,6 +1,11 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
import 'package:http/http.dart' as http;
import '../models/response_output.dart';
import '../helper/constants.dart'; import '../helper/constants.dart';
@@ -14,74 +19,193 @@ class HomeController extends GetxController {
final currentView = MainView.dashboard.obs; final currentView = MainView.dashboard.obs;
//--------------Settings View--------------------------- //--------------Settings View---------------------------
final askiUserController = TextEditingController().obs; final userWebMethodesController = TextEditingController().obs;
final askiPasswordController = TextEditingController().obs; final passwordWebMethodesController = TextEditingController().obs;
final loginUrlController = TextEditingController().obs;
final powerUrlController = TextEditingController().obs; final powerUrlController = TextEditingController().obs;
final pvTraunDeviceController = TextEditingController().obs;
final pvTraunDeviceMeterIdPVController = TextEditingController().obs;
final pvTraunDeviceMeterIdImportController = TextEditingController().obs;
final pvSarleinsbachDeviceController = TextEditingController().obs;
final pvSarleinsbachDeviceMeterIdPV01Controller = TextEditingController().obs;
final pvSarleinsbachDeviceMeterIdPV02Controller = TextEditingController().obs;
final pvSarleinsbachDeviceMeterIdPV03Controller = TextEditingController().obs;
final pvSarleinsbachDeviceMeterIdImportController = TextEditingController().obs;
final pvLannachDeviceController = TextEditingController().obs;
final pvLannachDeviceMeterIdPVController = TextEditingController().obs;
final pvLannachDeviceMeterIdImportController = TextEditingController().obs;
void saveSettings() { void saveSettings() {
String askiUser = askiUserController.value.text; String wbUser = userWebMethodesController.value.text;
String askiPassword = askiPasswordController.value.text; String wbPassword = passwordWebMethodesController.value.text;
String loginUrl = loginUrlController.value.text; String wbPowerUrl = powerUrlController.value.text;
String powerUrl = powerUrlController.value.text;
//traun
String pvTraunDevice = pvTraunDeviceController.value.text;
int pvTraunDeviceMeterIdPV = int.parse(pvTraunDeviceMeterIdPVController.value.text);
int pvTraunDeviceMeterIdImport = int.parse(pvTraunDeviceMeterIdImportController.value.text);
//sarleinsbach
String pvSarleinsbachDevice = pvSarleinsbachDeviceController.value.text;
int pvSarleinsbachDeviceMeterIdPV01 = int.parse(pvSarleinsbachDeviceMeterIdPV01Controller.value.text);
int pvSarleinsbachDeviceMeterIdPV02 = int.parse(pvSarleinsbachDeviceMeterIdPV02Controller.value.text);
int pvSarleinsbachDeviceMeterIdPV03 = int.parse(pvSarleinsbachDeviceMeterIdPV03Controller.value.text);
int pvSarleinsbachDeviceMeterIdImport = int.parse(pvSarleinsbachDeviceMeterIdImportController.value.text);
//lannach
String pvLannachDevice = pvLannachDeviceController.value.text;
int pvLannachDeviceMeterIdPV = int.parse(pvLannachDeviceMeterIdPVController.value.text);
int pvLannachDeviceMeterIdImport = int.parse(pvLannachDeviceMeterIdImportController.value.text);
boxSettings.write('wbUser', wbUser);
boxSettings.write('askiUser', askiUser); boxSettings.write('wbPassword', wbPassword);
boxSettings.write('askiPassword', askiPassword); boxSettings.write('wbPowerUrl', wbPowerUrl);
boxSettings.write('loginUrl', loginUrl);
boxSettings.write('powerUrl', powerUrl);
boxSettings.write('pvTraunDevice', pvTraunDevice);
boxSettings.write('pvTraunDeviceMeterIdPV', pvTraunDeviceMeterIdPV);
boxSettings.write('pvTraunDeviceMeterIdImport', pvTraunDeviceMeterIdImport);
boxSettings.write('pvSarleinsbachDevice', pvSarleinsbachDevice);
boxSettings.write('pvSarleinsbachDeviceMeterIdPV01', pvSarleinsbachDeviceMeterIdPV01);
boxSettings.write('pvSarleinsbachDeviceMeterIdPV02', pvSarleinsbachDeviceMeterIdPV02);
boxSettings.write('pvSarleinsbachDeviceMeterIdPV03', pvSarleinsbachDeviceMeterIdPV03);
boxSettings.write('pvSarleinsbachDeviceMeterIdImport', pvSarleinsbachDeviceMeterIdImport);
boxSettings.write('pvLannachDevice', pvLannachDevice);
boxSettings.write('pvLannachDeviceMeterIdPV', pvLannachDeviceMeterIdPV);
boxSettings.write('pvLannachDeviceMeterIdImport', pvLannachDeviceMeterIdImport);
kDisplaySnackBarGreen("Settings saved successfully!"); kDisplaySnackBarGreen("Settings saved successfully!");
print('Einstellungen gespeichert:'); print('Einstellungen gespeichert:');
print('ASKI User: $askiUser'); print('Admin User: $wbUser');
print('ASKI Password: $askiPassword'); print('Admin Password: $wbPassword');
print('Login URL: $loginUrl'); print('Web Methodes Power URL: $wbPowerUrl');
print('Power URL: $powerUrl');
} }
//------------------------------------------------------ //------------------------------------------------------
// http get request to fetch data from the provided URL
//------------------------------------------------------
final dateFromToIsoController = TextEditingController().obs;
final dateToIsoController = TextEditingController().obs;
final inputSwitchBranches = RxnString('TraunPV');
final responseOutputTraunPV = ResponseOutput().obs;
final responseOutputTraunImport = ResponseOutput().obs;
final responseOutputSarleinsbachPV = ResponseOutput().obs;
final responseOutputSarleinsbachImport = ResponseOutput().obs;
final responseOutputLannachPV = ResponseOutput().obs;
final responseOutputLannachImport = ResponseOutput().obs;
/// Map: Branch → (PV Observable, Import Observable)
Map<String, Map<String, dynamic>> get _branchConfig => {
'TraunPV': {
'pv': responseOutputTraunPV,
'import': responseOutputTraunImport,
'pvSuffix': 'TraunPV',
'importSuffix': 'TraunImport',
},
'TraunImport': {
'pv': responseOutputTraunPV,
'import': responseOutputTraunImport,
'pvSuffix': 'TraunPV',
'importSuffix': 'TraunImport',
},
'SarleinsbachPV': {
'pv': responseOutputSarleinsbachPV,
'import': responseOutputSarleinsbachImport,
'pvSuffix': 'SarleinsbachPV',
'importSuffix': 'SarleinsbachImport',
},
'SarleinsbachImport': {
'pv': responseOutputSarleinsbachPV,
'import': responseOutputSarleinsbachImport,
'pvSuffix': 'SarleinsbachPV',
'importSuffix': 'SarleinsbachImport',
},
'LannachPV': {
'pv': responseOutputLannachPV,
'import': responseOutputLannachImport,
'pvSuffix': 'LannachPV',
'importSuffix': 'LannachImport',
},
'LannachImport': {
'pv': responseOutputLannachPV,
'import': responseOutputLannachImport,
'pvSuffix': 'LannachPV',
'importSuffix': 'LannachImport',
},
};
void fetchData() async {
final inputFromIso = dateFromToIsoController.value.text;
final inputToIso = dateToIsoController.value.text;
final branch = inputSwitchBranches.value;
if (branch == null || branch.isEmpty) {
kDisplaySnackBarRed('Ungültiger Standort ausgewählt.');
return;
}
final baseUrl = _getBaseUrl();
if (baseUrl.isEmpty) {
kDisplaySnackBarRed('Bitte zuerst die Server-URL eintragen.');
return;
}
final config = _branchConfig[branch];
if (config == null) {
kDisplaySnackBarRed('Unbekannte Branche: $branch');
return;
}
final credentials =
'${userWebMethodesController.value.text}:${passwordWebMethodesController.value.text}';
final basicAuth = 'Basic ${base64Encode(utf8.encode(credentials))}';
final baseUri = Uri.parse(baseUrl);
try {
// Fetch PV Daten
await _fetchAndStore(baseUri, inputFromIso, inputToIso,
config['pvSuffix'], config['pv'], basicAuth);
// Fetch Import Daten
await _fetchAndStore(baseUri, inputFromIso, inputToIso,
config['importSuffix'], config['import'], basicAuth);
} catch (e) {
print('Error: $e');
kDisplaySnackBarRed('Netzwerkfehler: $e');
}
}
/// Hilfsfunktion - reduziert Codeverdoppelung
Future<void> _fetchAndStore(
Uri baseUri,
String inputFromIso,
String inputToIso,
String switchBranch,
dynamic responseObservable,
String basicAuth,
) async {
final uri = baseUri.replace(
queryParameters: {
...baseUri.queryParameters,
'InputFromIso': inputFromIso,
'InputToIso': inputToIso,
'InputSwitchBranches': switchBranch,
},
);
try {
final response = await http.get(
uri,
headers: {'Authorization': basicAuth, 'Accept': 'application/json'},
);
final statusGroup = response.statusCode ~/ 100;
if (statusGroup == 2) {
print('$switchBranch erfolgreich geladen');
responseObservable.value = ResponseOutput.fromJson(jsonDecode(response.body));
} else if (statusGroup == 4) {
kDisplaySnackBarRed('Fehler 4xx: ${response.statusCode}');
} else if (statusGroup == 5) {
kDisplaySnackBarRed('Fehler 5xx: ${response.statusCode}');
} else {
kDisplaySnackBarRed('Fehler: ${response.statusCode}');
}
} catch (e) {
print('Error fetching $switchBranch: $e');
rethrow;
}
}
/// Wählt die richtige Base-URL:
/// Web: localhost:3000 (Reverse Proxy)
/// Desktop: Direkt von Settings
String _getBaseUrl() {
final settingsUrl = powerUrlController.value.text.trim();
// Auf Web: verwende Reverse Proxy auf localhost:3000
if (kIsWeb) {
return 'http://localhost:3000/invoke/IFN_ASKI_Service.flows/getAskiData?';
}
// Auf Desktop (Windows, Linux, macOS): direkt von Settings
return settingsUrl;
}
void changeView(MainView view) { void changeView(MainView view) {
currentView.value = view; currentView.value = view;
if (currentView.value == MainView.dashboard) {
inputSwitchBranches.value = 'TraunPV';
} else if (currentView.value == MainView.traun) {
inputSwitchBranches.value = 'TraunPV';
} else if (currentView.value == MainView.sarleinsbach) {
inputSwitchBranches.value = 'SarleinsbachPV';
} else if (currentView.value == MainView.lannach) {
inputSwitchBranches.value = 'LannachPV';
}
if (currentView.value != MainView.dashboard) {
fetchData(); // Fetch data whenever the view changes
}
} }
void toggleDrawer() { void toggleDrawer() {
@@ -107,42 +231,34 @@ class HomeController extends GetxController {
} }
void _readStorageData() { void _readStorageData() {
bool hasAskiUser = boxSettings.hasData('askiUser'); bool hasWbUser = boxSettings.hasData('wbUser');
if (hasAskiUser) { if (hasWbUser) {
askiUserController.value.text = boxSettings.read('askiUser'); userWebMethodesController.value.text = boxSettings.read('wbUser');
} }
bool hasAskiPassword = boxSettings.hasData('askiPassword'); bool hasWbPassword = boxSettings.hasData('wbPassword');
if (hasAskiPassword) { if (hasWbPassword) {
askiPasswordController.value.text = boxSettings.read('askiPassword'); passwordWebMethodesController.value.text = boxSettings.read('wbPassword');
} }
bool hasLoginUrl = boxSettings.hasData('loginUrl'); bool hasWbPowerUrl = boxSettings.hasData('wbPowerUrl');
if (hasLoginUrl) { if (hasWbPowerUrl) {
loginUrlController.value.text = boxSettings.read('loginUrl'); powerUrlController.value.text = boxSettings.read('wbPowerUrl');
}
bool hasPowerUrl = boxSettings.hasData('powerUrl');
if (hasPowerUrl) {
powerUrlController.value.text = boxSettings.read('powerUrl');
} }
//fromDateTimeIso and toDateTimeIso
dateFromToIsoController.value.text = DateTime(
DateTime.now().year,
DateTime.now().month,
DateTime.now().day,
).toIso8601String();
dateToIsoController.value.text = DateTime.now().toIso8601String();
} }
@override @override
void onClose() { void onClose() {
askiUserController.close(); userWebMethodesController.close();
askiPasswordController.close();
loginUrlController.close();
powerUrlController.close(); powerUrlController.close();
pvTraunDeviceController.close(); passwordWebMethodesController.close();
pvTraunDeviceMeterIdPVController.close(); dateFromToIsoController.close();
pvTraunDeviceMeterIdImportController.close(); dateToIsoController.close();
pvSarleinsbachDeviceController.close();
pvSarleinsbachDeviceMeterIdPV01Controller.close();
pvSarleinsbachDeviceMeterIdPV02Controller.close();
pvSarleinsbachDeviceMeterIdPV03Controller.close();
pvSarleinsbachDeviceMeterIdImportController.close();
pvLannachDeviceController.close();
pvLannachDeviceMeterIdPVController.close();
pvLannachDeviceMeterIdImportController.close();
super.onClose(); super.onClose();
} }
} }
+25 -15
View File
@@ -3,11 +3,15 @@ import 'package:get/get.dart';
var kFontSizeDouble = 15.0; var kFontSizeDouble = 15.0;
var kTextSyleLittleGrey = var kTextSyleLittleGrey = TextStyle(
TextStyle(fontSize: 12.0, color: Colors.grey.shade800); fontSize: 12.0,
color: Colors.grey.shade800,
);
var kTextSyleHeaderGrey = var kTextSyleHeaderGrey = TextStyle(
TextStyle(fontSize: 25.0, color: Colors.grey.shade800); fontSize: 25.0,
color: Colors.grey.shade800,
);
var kElevatetButtonStyleDrawer = ElevatedButton.styleFrom( var kElevatetButtonStyleDrawer = ElevatedButton.styleFrom(
backgroundColor: Colors.blueGrey, backgroundColor: Colors.blueGrey,
@@ -28,24 +32,30 @@ var kInputDecorationDropDownMenue = InputDecoration(
); );
SnackbarController kDisplaySnackBarRed(String szInfoText) { SnackbarController kDisplaySnackBarRed(String szInfoText) {
return Get.snackbar('Info', szInfoText, return Get.snackbar(
backgroundColor: Colors.red[600], 'Info',
snackPosition: SnackPosition.TOP, szInfoText,
colorText: Colors.white, backgroundColor: Colors.red[600],
snackStyle: SnackStyle.GROUNDED); snackPosition: SnackPosition.TOP,
colorText: Colors.white,
snackStyle: SnackStyle.GROUNDED,
);
} }
SnackbarController kDisplaySnackBarGreen(String szInfoText) { SnackbarController kDisplaySnackBarGreen(String szInfoText) {
return Get.snackbar('Info', szInfoText, return Get.snackbar(
backgroundColor: Colors.green[600], 'Info',
snackPosition: SnackPosition.TOP, szInfoText,
colorText: Colors.black, backgroundColor: Colors.green[600],
snackStyle: SnackStyle.GROUNDED); snackPosition: SnackPosition.TOP,
colorText: Colors.black,
snackStyle: SnackStyle.GROUNDED,
);
} }
//Setting Wait for Delete //Setting Wait for Delete
List<Map<String, dynamic>> kListMapSetting = [ List<Map<String, dynamic>> kListMapSetting = [
{'ContainerID': '', 'Info': ''} {'ContainerID': '', 'Info': ''},
]; ];
//Switching PROD/DEV //Switching PROD/DEV
+81
View File
@@ -0,0 +1,81 @@
class ResponseOutput {
DataResult? dataResult;
ResponseOutput({this.dataResult});
ResponseOutput.fromJson(Map<String, dynamic> json) {
dataResult = json['DataResult'] != null
? DataResult.fromJson(json['DataResult'])
: null;
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
if (dataResult != null) {
data['DataResult'] = dataResult!.toJson();
}
return data;
}
}
class DataResult {
String? httpStatus;
String? orderType;
String? location;
int? meterId;
List<Values>? values;
DataResult(
{this.httpStatus,
this.orderType,
this.location,
this.meterId,
this.values});
DataResult.fromJson(Map<String, dynamic> json) {
httpStatus = json['HttpStatus'];
orderType = json['OrderType'];
location = json['Location'];
meterId = json['meterId'];
if (json['values'] != null) {
values = <Values>[];
json['values'].forEach((v) {
values!.add(Values.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['HttpStatus'] = httpStatus;
data['OrderType'] = orderType;
data['Location'] = location;
data['meterId'] = meterId;
if (values != null) {
data['values'] = values!.map((v) => v.toJson()).toList();
}
return data;
}
}
class Values {
String? datetime;
double? value;
Null tariff;
Values({this.datetime, this.value, this.tariff});
Values.fromJson(Map<String, dynamic> json) {
datetime = json['datetime'];
value = json['value'];
tariff = json['tariff'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['datetime'] = datetime;
data['value'] = value;
data['tariff'] = tariff;
return data;
}
}
+333
View File
@@ -0,0 +1,333 @@
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import '../models/response_output.dart';
/// Wiederverwendbares Balkendiagramm Widget für PV/Import Vergleich
///
/// Zeigt zwei Datensätze (z.B. TraunPV + TraunImport) als nebeneinander angeordnete Balken
/// - PV Daten in Orange
/// - Import Daten in Rot
/// - Y-Achse: kW Werte
/// - X-Achse: Datetime (als Uhrzeit angezeigt)
class ComparisonBarChart extends StatelessWidget {
final ResponseOutput pvData;
final ResponseOutput importData;
final String title;
const ComparisonBarChart({
required this.pvData,
required this.importData,
required this.title,
super.key,
});
@override
Widget build(BuildContext context) {
final pvValues = pvData.dataResult?.values ?? [];
final importValues = importData.dataResult?.values ?? [];
// Erstelle eine Map mit datetime als Schlüssel
final Map<String, double> pvMap = {
for (var v in pvValues) (v.datetime ?? ''): (v.value ?? 0.0),
};
final Map<String, double> importMap = {
for (var v in importValues) (v.datetime ?? ''): (v.value ?? 0.0),
};
// Alle eindeutigen datetimes sammeln und sortieren
final allDatetimes = {...pvMap.keys, ...importMap.keys}.toList();
allDatetimes.sort();
// Begrenzen auf die letzten 20 Einträge für bessere Lesbarkeit
// final displayDatetimes = allDatetimes.length > 20
// ? allDatetimes.sublist(allDatetimes.length - 20)
// : allDatetimes;
final displayDatetimes = allDatetimes;
if (displayDatetimes.isEmpty) {
return const Center(child: Text('Keine Daten verfügbar'));
}
// Berechne Maximalwert
final allValues = [...pvMap.values, ...importMap.values];
final maxValue = allValues.fold(0.0, (a, b) => a > b ? a : b);
// Maxima für PV und Import separat
double maxPvValue = 0;
int maxPvIndex = 0;
double maxImportValue = 0;
int maxImportIndex = 0;
for (int i = 0; i < displayDatetimes.length; i++) {
final dt = displayDatetimes[i];
final pv = pvMap[dt] ?? 0.0;
final imp = importMap[dt] ?? 0.0;
if (pv > maxPvValue) {
maxPvValue = pv;
maxPvIndex = i;
}
if (imp > maxImportValue) {
maxImportValue = imp;
maxImportIndex = i;
}
}
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(height: 16),
Text(
title,
style: Theme.of(context).textTheme.headlineSmall,
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
// Chart mit fester Höhe
SizedBox(
height: 500,
child: BarChart(
BarChartData(
alignment: BarChartAlignment.spaceAround,
maxY: maxValue * 1.02,
minY: 0,
gridData: FlGridData(
show: true,
drawVerticalLine: false,
horizontalInterval: maxValue * 0.2,
),
titlesData: FlTitlesData(
show: true,
topTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 40,
getTitlesWidget: (value, meta) {
final index = value.toInt();
// PV Maximum - oranger Pin
if (index == maxPvIndex) {
return Transform.translate(
offset: const Offset(-5, 0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
decoration: BoxDecoration(
color: Colors.orange,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withValues(alpha: 0.3),
blurRadius: 4,
),
],
),
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
child: Text(
maxPvValue.toStringAsFixed(0),
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
),
// Pin nach unten
CustomPaint(
size: const Size(20, 10),
painter: PinPainter(Colors.orange),
),
],
),
);
}
// Import Maximum - roter Pin
if (index == maxImportIndex) {
return Transform.translate(
offset: const Offset(5, 0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withValues(alpha: 0.3),
blurRadius: 4,
),
],
),
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
child: Text(
maxImportValue.toStringAsFixed(0),
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
),
// Pin nach unten
CustomPaint(
size: const Size(20, 10),
painter: PinPainter(Colors.red),
),
],
),
);
}
return const SizedBox();
},
),
),
rightTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 50,
getTitlesWidget: (value, meta) {
return Text(
'${value.toInt()} kW',
style: const TextStyle(fontSize: 12),
);
},
),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 60,
getTitlesWidget: (value, meta) {
final index = value.toInt();
if (index < 0 || index >= displayDatetimes.length) {
return const SizedBox();
}
final datetime = displayDatetimes[index];
// Extrahiere nur Uhrzeit (HH:mm)
final timePart = datetime.contains('T')
? datetime.split('T').last.substring(0, 5)
: datetime.substring(0, 5);
return Padding(
padding: const EdgeInsets.only(top: 8.0),
child: RotatedBox(
quarterTurns: 1,
child: Text(
timePart,
style: const TextStyle(fontSize: 10),
),
),
);
},
),
),
),
barGroups: _generateBarGroups(
displayDatetimes,
pvMap,
importMap,
),
borderData: FlBorderData(show: true),
),
),
),
const SizedBox(height: 24),
// Legende
Wrap(
alignment: WrapAlignment.center,
spacing: 24,
runSpacing: 8,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(width: 16, height: 16, color: Colors.orange),
const SizedBox(width: 8),
const Text('PV-Anlage(kW)'),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(width: 16, height: 16, color: Colors.red),
const SizedBox(width: 8),
const Text('Netzbezug(kW)'),
],
),
],
),
const SizedBox(height: 16),
],
),
),
);
}
List<BarChartGroupData> _generateBarGroups(
List<String> datetimes,
Map<String, double> pvMap,
Map<String, double> importMap,
) {
return List.generate(datetimes.length, (index) {
final datetime = datetimes[index];
final pvValue = pvMap[datetime] ?? 0.0;
final importValue = importMap[datetime] ?? 0.0;
return BarChartGroupData(
x: index,
barRods: [
// Orange Bar für PV
BarChartRodData(
toY: pvValue,
color: Colors.orange,
width: 8,
borderRadius: const BorderRadius.vertical(top: Radius.circular(4)),
),
// Roter Bar für Import
BarChartRodData(
toY: importValue,
color: Colors.red,
width: 8,
borderRadius: const BorderRadius.vertical(top: Radius.circular(4)),
),
],
);
});
}
}
class PinPainter extends CustomPainter {
final Color color;
PinPainter(this.color);
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = color;
final path = Path();
path.moveTo(size.width / 2, size.height);
path.lineTo(0, 0);
path.lineTo(size.width, 0);
path.close();
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(PinPainter oldDelegate) => false;
}
+38
View File
@@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class StaticLocationDropdown extends StatelessWidget {
const StaticLocationDropdown({super.key, required this.selectedValue});
final RxnString selectedValue;
static const List<String> _locations = [
'TraunPV',
'TraunImport',
'SarleinsbachPV',
'SarleinsbachImport',
'LannachPV',
'LannachImport',
];
@override
Widget build(BuildContext context) {
return Obx(() => DropdownButtonFormField<String>(
initialValue: selectedValue.value ?? _locations.first,
decoration: const InputDecoration(
labelText: 'Select Location / Data Source',
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 15),
),
items: _locations.map((String location) {
return DropdownMenuItem<String>(
value: location,
child: Text(location),
);
}).toList(),
onChanged: (String? newValue) {
selectedValue.value = newValue;
},
));
}
}
+60 -29
View File
@@ -11,38 +11,69 @@ class DashboardView extends StatelessWidget {
var hCtrl = Get.find<HomeController>(); var hCtrl = Get.find<HomeController>();
return Center( return Center(
child: Container( child: Container(
margin: const EdgeInsets.all(20), padding: const EdgeInsets.all(20),
width: 1000, width: 1000,
child: Wrap( child: Wrap(
spacing: 20, spacing: 20,
runSpacing: 20, runSpacing: 20,
children: [ children: [
InkWell( Padding(
onTap: () { padding: const EdgeInsets.only(right: 35.0),
hCtrl.changeView(MainView.dashboard); child: TextField(
}, controller: hCtrl.dateFromToIsoController.value,
child: Image.asset('assets/images/AskiDashboard.png',width: 450), decoration: InputDecoration(
labelText: 'Input From ISO',
border: OutlineInputBorder(),
hintText: 'Enter Input From ISO',
),
),
),
Padding(
padding: const EdgeInsets.only(right: 35.0),
child: TextField(
controller: hCtrl.dateToIsoController.value,
decoration: InputDecoration(
labelText: 'Input To ISO',
border: OutlineInputBorder(),
hintText: 'Enter Input To ISO',
),
),
),
// Padding(
// padding: const EdgeInsets.only(right: 35.0),
// child: StaticLocationDropdown(
// selectedValue: hCtrl.inputSwitchBranches,
// ),
// ),
InkWell(
onTap: () {
hCtrl.changeView(MainView.dashboard);
},
child: Image.asset('assets/images/AskiDashboard.png', width: 450),
),
InkWell(
onTap: () {
hCtrl.changeView(MainView.traun);
},
child: Image.asset('assets/images/AskiTraun.png', width: 450),
),
InkWell(
onTap: () {
hCtrl.changeView(MainView.sarleinsbach);
},
child: Image.asset(
'assets/images/AskiSarleinsbach.png',
width: 450,
), ),
InkWell( ),
onTap: () { InkWell(
hCtrl.changeView(MainView.traun); onTap: () {
}, hCtrl.changeView(MainView.lannach);
child: Image.asset('assets/images/AskiTraun.png',width: 450), },
), child: Image.asset('assets/images/AskiLannach.png', width: 450),
InkWell( ),
onTap: () { ],
hCtrl.changeView(MainView.sarleinsbach); ),
},
child: Image.asset('assets/images/AskiSarleinsbach.png',width: 450),
),
InkWell(
onTap: () {
hCtrl.changeView(MainView.lannach);
},
child: Image.asset('assets/images/AskiLannach.png',width: 450),
),
],
),
), ),
); );
} }
+14 -5
View File
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../controller/home_controller.dart'; import '../../controller/home_controller.dart';
import '../comparison_bar_chart.dart';
class LannachView extends StatelessWidget { class LannachView extends StatelessWidget {
const LannachView({super.key}); const LannachView({super.key});
@@ -23,11 +24,19 @@ class LannachView extends StatelessWidget {
}, },
) )
), ),
body: const Center( body: Obx(
child: Text( () => hCtrl.responseOutputLannachPV.value.dataResult?.values?.isEmpty ?? true
"PV Lannach", ? const Center(
style: TextStyle(fontSize: 30), child: Text(
), 'Keine Daten verfügbar',
style: TextStyle(fontSize: 18),
),
)
: ComparisonBarChart(
pvData: hCtrl.responseOutputLannachPV.value,
importData: hCtrl.responseOutputLannachImport.value,
title: 'LannachPV/Import Vergleich',
),
), ),
); );
} }
+14 -5
View File
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../controller/home_controller.dart'; import '../../controller/home_controller.dart';
import '../comparison_bar_chart.dart';
class SarleinsbachView extends StatelessWidget { class SarleinsbachView extends StatelessWidget {
const SarleinsbachView({super.key}); const SarleinsbachView({super.key});
@@ -24,11 +25,19 @@ class SarleinsbachView extends StatelessWidget {
}, },
) )
), ),
body: const Center( body: Obx(
child: Text( () => hCtrl.responseOutputSarleinsbachPV.value.dataResult?.values?.isEmpty ?? true
"PV Sarleinsbach", ? const Center(
style: TextStyle(fontSize: 30), child: Text(
), 'Keine Daten verfügbar',
style: TextStyle(fontSize: 18),
),
)
: ComparisonBarChart(
pvData: hCtrl.responseOutputSarleinsbachPV.value,
importData: hCtrl.responseOutputSarleinsbachImport.value,
title: 'SarleinsbachPV/Import Vergleich',
),
), ),
); );
} }
+8 -31
View File
@@ -11,7 +11,7 @@ class SettingsView extends StatelessWidget {
var hCtrl = Get.find<HomeController>(); var hCtrl = Get.find<HomeController>();
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Data Settings'), title: const Text('System Settings'),
centerTitle: true, centerTitle: true,
backgroundColor: Colors.blueGrey, backgroundColor: Colors.blueGrey,
foregroundColor: Colors.white, foregroundColor: Colors.white,
@@ -41,43 +41,34 @@ class SettingsView extends StatelessWidget {
children: [ children: [
SizedBox(height: 10), SizedBox(height: 10),
Text( Text(
"Please enter User and Password", "Please enter User and Password for Web Methodes",
style: TextStyle(fontSize: 18), style: TextStyle(fontSize: 18),
), ),
SizedBox(height: 10), SizedBox(height: 10),
TextField( TextField(
controller: hCtrl.askiUserController.value, controller: hCtrl.userWebMethodesController.value,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Input User', labelText: 'Input User',
border: OutlineInputBorder(), border: OutlineInputBorder(),
hintText: 'Enter ASKI User', hintText: 'Enter Admin User',
), ),
), ),
SizedBox(height: 10), SizedBox(height: 10),
TextField( TextField(
controller: hCtrl.askiPasswordController.value, controller: hCtrl.passwordWebMethodesController.value,
obscureText: true, obscureText: true,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Input Password', labelText: 'Input Password',
border: OutlineInputBorder(), border: OutlineInputBorder(),
hintText: 'Enter ASKI Password', hintText: 'Enter Admin Password',
), ),
), ),
SizedBox(height: 10), SizedBox(height: 10),
Text( Text(
"Please enter the URLs for the ASKI login and power data", "Please enter the URLs for WebMethodes PV data Url",
style: TextStyle(fontSize: 18), style: TextStyle(fontSize: 18),
), ),
SizedBox(height: 10), SizedBox(height: 10),
TextField(
controller: hCtrl.loginUrlController.value,
decoration: InputDecoration(
labelText: 'Login URL',
border: OutlineInputBorder(),
hintText: 'Enter ASKI Login URL',
),
),
SizedBox(height: 10),
TextField( TextField(
controller: hCtrl.powerUrlController.value, controller: hCtrl.powerUrlController.value,
decoration: InputDecoration( decoration: InputDecoration(
@@ -85,21 +76,7 @@ class SettingsView extends StatelessWidget {
border: OutlineInputBorder(), border: OutlineInputBorder(),
hintText: 'Enter ASKI Power URL', hintText: 'Enter ASKI Power URL',
), ),
), )
SizedBox(height: 10),
Text(
"Please enter the device for PV Traun and PV Sarleinsbach and PV Lannach",
style: TextStyle(fontSize: 18),
),
SizedBox(height: 10),
TextField(
controller: hCtrl.pvTraunDeviceController.value,
decoration: InputDecoration(
labelText: 'PV Traun Device',
border: OutlineInputBorder(),
hintText: 'Enter PV Traun Device',
),
),
], ],
), ),
), ),
+14 -5
View File
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../controller/home_controller.dart'; import '../../controller/home_controller.dart';
import '../comparison_bar_chart.dart';
class TraunView extends StatelessWidget { class TraunView extends StatelessWidget {
const TraunView({super.key}); const TraunView({super.key});
@@ -23,11 +24,19 @@ class TraunView extends StatelessWidget {
}, },
) )
), ),
body: const Center( body: Obx(
child: Text( () => hCtrl.responseOutputTraunPV.value.dataResult?.values?.isEmpty ?? true
"PV Traun", ? const Center(
style: TextStyle(fontSize: 30), child: Text(
), 'Keine Daten verfügbar',
style: TextStyle(fontSize: 18),
),
)
: ComparisonBarChart(
pvData: hCtrl.responseOutputTraunPV.value,
importData: hCtrl.responseOutputTraunImport.value,
title: 'TraunPV/Import Vergleich',
),
), ),
); );
} }
+32
View File
@@ -73,6 +73,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.9" version: "1.0.9"
equatable:
dependency: transitive
description:
name: equatable
sha256: "3bce007a596ff8b3119c45d68aaef631272537c03d30e5d4534dd24bf4c5eaa2"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@@ -89,6 +97,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.2.0"
fl_chart:
dependency: "direct main"
description:
name: fl_chart
sha256: d0f0d49112f2f4b192481c16d05b6418bd7820e021e265a3c22db98acf7ed7fb
url: "https://pub.dev"
source: hosted
version: "0.68.0"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@@ -131,6 +147,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.2" version: "2.0.2"
http:
dependency: "direct main"
description:
name: http
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
url: "https://pub.dev"
source: hosted
version: "1.6.0"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
version: "4.1.2"
intl: intl:
dependency: "direct main" dependency: "direct main"
description: description:
+2
View File
@@ -10,10 +10,12 @@ environment:
dependencies: dependencies:
cupertino_icons: ^1.0.8 cupertino_icons: ^1.0.8
fl_chart: ^0.68.0
flutter: flutter:
sdk: flutter sdk: flutter
get: ^4.7.3 get: ^4.7.3
get_storage: ^2.1.1 get_storage: ^2.1.1
http: ^1.6.0
intl: ^0.20.3 intl: ^0.20.3
dev_dependencies: dev_dependencies: