Erweiterungen

This commit is contained in:
Sven committed 2026-02-25 10:40:29 +01:00
1 parent 955d7b3cb4
commit e9811da904
25 files changed
+1505 -128

No files matched your search

+5 -4
View File
@@ -16,6 +16,7 @@ import 'package:stadtbote/tools/messages/remove_job_message.dart';
import 'package:stadtbote/tools/mqtt_core.dart';
import 'package:stadtbote/tools/util.dart';
import '../tools/app_localizations.dart';
import '../tools/messages/publish_payload_message.dart';
final ScrollController _scrollController = ScrollController();
@@ -109,7 +110,7 @@ Future<void> showAcceptJobsPopup(SharedMapReference mapReference, Function() onF
),
),
),
getDefaultButton("Liste schliessen", () {
getDefaultButton('close_list'.tr, () {
isActive = false;
if (builderCreated) {
extendedStatefulBuilder.unsubscribe(queueId: Constants.messageTopicJobReceived);
@@ -191,7 +192,7 @@ Future<List<Widget>> _fetchJobWidgets(StateSetter dialogState, Function() onFini
style: TextStyle(backgroundColor: isJobTooFarInFuture(job) ? Colors.red : Colors.transparent),
),
),
SizedBox(width: double.infinity, child: Text('Auftragsnummer: ${job.id}', textAlign: TextAlign.left)),
SizedBox(width: double.infinity, child: Text('${'order_number'.tr}: ${job.id}', textAlign: TextAlign.left)),
SizedBox(width: double.infinity, child: Text(job.tours[0].comp, textAlign: TextAlign.left)),
SizedBox(width: double.infinity, child: Text(job.tours[0].street, textAlign: TextAlign.left)),
SizedBox(width: double.infinity, child: Text(job.tours[0].city, textAlign: TextAlign.left)),
@@ -212,7 +213,7 @@ Future<List<Widget>> _fetchJobWidgets(StateSetter dialogState, Function() onFini
const SizedBox(height: 10),
Row(children: [
Expanded(
child: getDefaultButton(!job.global ? 'Ablehnen' : 'Ignorieren', () {
child: getDefaultButton(!job.global ? 'decline'.tr : 'ignore'.tr, () {
dialogState(() {
_sendAnswer(mapReference, job, !job.global ? 2 : 3);
@@ -229,7 +230,7 @@ Future<List<Widget>> _fetchJobWidgets(StateSetter dialogState, Function() onFini
),
const SizedBox(width: 20),
Expanded(
child: getDefaultButton('Annehmen', enabledTimerActive: false, () {
child: getDefaultButton('accept'.tr, enabledTimerActive: false, () {
dialogState(() {
_sendAnswer(mapReference, job, 1);
+62 -15
View File
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:stadtbote/main.dart';
import 'package:stadtbote/model/session_status.dart';
import 'package:stadtbote/tools/app_localizations.dart';
import 'package:stadtbote/tools/mqtt_core.dart';
import 'package:stadtbote/tools/util.dart';
@@ -10,7 +11,9 @@ void showInfosPopup(SessionStatus? status) async {
await showDialog(
context: MyApp.appNavigatorKey.currentContext!,
builder: (BuildContext context) {
late String connectedString = MqttCore.instance.getConnectionStatus() == ConnectionStateEnum.connected ? "Verbunden" : "NICHT verbunden";
late String connectedString = MqttCore.instance.getConnectionStatus() == ConnectionStateEnum.connected
? 'connected'.tr
: 'not_connected'.tr;
return AlertDialog(
contentPadding: EdgeInsets.zero,
@@ -18,31 +21,42 @@ void showInfosPopup(SessionStatus? status) async {
backgroundColor: Colors.white,
content: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
Future<String> getGpsStatustext() async {
String gpsStatus = "nicht verfügbar";
String gpsStatus = 'gps_not_available'.tr;
var currentPosition = await MyApp.getCurrentPosition();
if (currentPosition != null) {
var nowUtc = DateTime.now().toUtc();
var minutesDelta = nowUtc.difference(currentPosition.timestamp);
if (minutesDelta >= const Duration(minutes: 15)) {
gpsStatus = "veraltet";
gpsStatus = 'gps_outdated'.tr;
} else {
gpsStatus = "aktuell";
gpsStatus = 'gps_available'.tr;
}
}
return gpsStatus;
}
void onLanguageChanged(AppLanguage? newLanguage) {
if (newLanguage != null) {
AppLocalizations().setLanguage(newLanguage);
setState(() {});
// Sende Nachricht an MainPage, um UI zu aktualisieren
// Wir verwenden einen einfachen Ansatz: Die App wird neu geladen
// In einer echten App würde man hier einen State-Management-Mechanismus verwenden
}
}
return FutureBuilder<String>(
future: getGpsStatustext(),
builder: (context, snapshot) {
String gpsStatus = snapshot.data ?? "wird geladen...";
String gpsStatus = snapshot.data ?? 'loading'.tr;
var text = "Versionsnummer: a$version\n";
text += "Verbindungsstatus: $connectedString\n";
text += "GPS-Status: $gpsStatus\n";
var text = "${'version'.tr}: a$version\n";
text += "${'connection_status'.tr}: $connectedString\n";
text += "${'gps_status'.tr}: $gpsStatus\n";
text += "\n";
text += "User Status:\n";
text += "${'user_status'.tr}:\n";
if (status != null) {
text += "cr_id: ${status.crId}\n";
text += "cust_id: ${status.custId}\n";
@@ -51,12 +65,12 @@ void showInfosPopup(SessionStatus? status) async {
text += "server_version: ${status.serverVersion}\n";
text += "server_instance: ${status.serverInstance}\n";
} else {
text += "Keine Informationen gefunden";
text += 'no_info_found'.tr;
}
return SizedBox(
key: GlobalKey(),
height: 350,
height: 450,
width: MediaQuery.of(context).size.width * 0.8,
child: Container(
height: 100,
@@ -72,9 +86,44 @@ void showInfosPopup(SessionStatus? status) async {
padding: const EdgeInsets.all(12.0),
child: Column(
children: [
Expanded(child: Text(text, textAlign: TextAlign.left)),
Expanded(child: SingleChildScrollView(child: Text(text, textAlign: TextAlign.left))),
const Divider(),
// Sprachauswahl
Row(
children: [
Text('${'language'.tr}: ', style: const TextStyle(fontWeight: FontWeight.bold)),
Expanded(
child: DropdownButtonHideUnderline(
child: DropdownButton<AppLanguage>(
isExpanded: true,
value: AppLocalizations().currentLanguage,
items: AppLanguage.values.map((AppLanguage language) {
return DropdownMenuItem<AppLanguage>(
value: language,
child: Row(
children: [
Text(language.flag, style: const TextStyle(fontSize: 20)),
const SizedBox(width: 8),
Flexible(
child: Text(
language.name,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 14),
),
),
],
),
);
}).toList(),
onChanged: onLanguageChanged,
),
),
),
],
),
const SizedBox(height: 12),
getDefaultButton(
'OK',
'ok'.tr,
() {
Navigator.pop(context);
},
@@ -88,5 +137,3 @@ void showInfosPopup(SessionStatus? status) async {
}));
});
}
+4 -3
View File
@@ -4,6 +4,7 @@ import 'package:animated_toggle_switch/animated_toggle_switch.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
import 'package:stadtbote/tools/app_localizations.dart';
import 'package:stadtbote/tools/util.dart';
import '../model/job_tour.dart';
@@ -41,7 +42,7 @@ showMapPopup(BuildContext context, JobTour currentTour, List<JobTour> tourList)
currentTour = tour;
streamControllers[tour.sort]!.sink.add(true);
} else {
showAlertDialog('Information', 'Es liegt keine Position für diese Station vor');
showAlertDialog('information'.tr, 'no_position'.tr);
}
},
child: SizedBox(
@@ -64,7 +65,7 @@ showMapPopup(BuildContext context, JobTour currentTour, List<JobTour> tourList)
height: 12.0,
width: 70,
child: Text(
"Station ${tour.sort}",
"${'station'.tr} ${tour.sort}",
softWrap: true,
textAlign: TextAlign.center,
style: const TextStyle(
@@ -164,7 +165,7 @@ showMapPopup(BuildContext context, JobTour currentTour, List<JobTour> tourList)
mapController!.move(mapController!.camera.center, b ? 14.0 : 16.0);
},
styleBuilder: (i) => ToggleStyle(indicatorColor: i == true ? _colors[0] : _colors[1]),
textBuilder: (value) => value ? const Center(child: Text('Fern')) : const Center(child: Text('Nah')),
textBuilder: (value) => value ? Center(child: Text('far'.tr)) : Center(child: Text('near'.tr)),
);
},
),
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:stadtbote/model/task_info.dart';
import 'package:stadtbote/tools/app_localizations.dart';
import 'package:stadtbote/tools/util.dart';
showComissionNumberPopup(BuildContext context, TaskInfo taskInfo, Function(String) onFinished) async {
@@ -42,15 +43,15 @@ showComissionNumberPopup(BuildContext context, TaskInfo taskInfo, Function(Strin
padding: const EdgeInsets.all(35.0),
child: Column(
children: [
const Text('Bitte geben Sie die Kommisionsnummer ein'),
Text('enter_commission_number'.tr),
const SizedBox(height: 20),
Util.getDefaultTextField('Kommisionsnummer',
Util.getDefaultTextField('commission_number_hint'.tr,
textInputType: TextInputType.text,
formatter: [FilteringTextInputFormatter.singleLineFormatter],
controller: textEditingController,
autofocus: true),
const SizedBox(height: 20),
getDefaultButton('Übernehmen', () {
getDefaultButton('apply'.tr, () {
final result = textEditingController.text;
textEditingController.dispose();
Navigator.of(context).pop();
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:stadtbote/model/task_info.dart';
import 'package:stadtbote/model/tour_task.dart';
import 'package:stadtbote/tools/app_localizations.dart';
import 'package:stadtbote/tools/util.dart';
showPickupDeliveryStatusPopup(BuildContext context, TaskInfo taskInfo, Function(TourEntry?, String? remark) onFinished) async {
@@ -58,7 +59,7 @@ showPickupDeliveryStatusPopup(BuildContext context, TaskInfo taskInfo, Function(
DropdownMenu<TourEntry>(
expandedInsets: const EdgeInsets.all(0),
controller: controller,
hintText: 'Status',
hintText: 'status'.tr,
onSelected: (TourEntry? value) {
selectedTourEntry = value;
@@ -68,9 +69,9 @@ showPickupDeliveryStatusPopup(BuildContext context, TaskInfo taskInfo, Function(
dropdownMenuEntries: entries,
),
const SizedBox(height: 20),
Visibility(visible: selectedTourEntry != null ? selectedTourEntry!.queryRemark : false, child: Util.getDefaultTextField(controller: remController, "Bemerkung")),
Visibility(visible: selectedTourEntry != null ? selectedTourEntry!.queryRemark : false, child: Util.getDefaultTextField(controller: remController, 'remark'.tr)),
const SizedBox(height: 20),
getDefaultButton('Übernehmen', buttonHeight: 50, () => {
getDefaultButton('apply'.tr, buttonHeight: 50, () => {
if (selectedTourEntry!.queryRemark) {
if (remController.text.isNotEmpty) {
Navigator.of(context).pop(),
@@ -80,7 +81,7 @@ showPickupDeliveryStatusPopup(BuildContext context, TaskInfo taskInfo, Function(
onFinished(selectedTourEntry, remController.text)
} else {
showAlertDialog("Fehler", "Bitte geben Sie eine Bemerkung ein")
showAlertDialog('error'.tr, 'enter_remark'.tr)
}
} else {
Navigator.of(context).pop(),
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:stadtbote/model/task_info.dart';
import 'package:stadtbote/model/tour_task.dart';
import 'package:stadtbote/tools/app_localizations.dart';
import 'package:stadtbote/tools/util.dart';
showReceiptGiverPopup(BuildContext context, TaskInfo taskInfo, Function(String, TourEntry) onFinished) async {
@@ -57,7 +58,7 @@ showReceiptGiverPopup(BuildContext context, TaskInfo taskInfo, Function(String,
DropdownMenu<TourEntry>(
expandedInsets: const EdgeInsets.all(0),
controller: controller,
hintText: 'Geschlecht',
hintText: 'gender'.tr,
onSelected: (TourEntry? value) {
selectedTourEntry = value;
@@ -73,7 +74,7 @@ showReceiptGiverPopup(BuildContext context, TaskInfo taskInfo, Function(String,
maxLength: 40,
autofocus: true),
const SizedBox(height: 20),
getDefaultButton('Übernehmen', () {
getDefaultButton('apply'.tr, () {
if (textEditingController.text.isNotEmpty && selectedTourEntry != null) {
Navigator.of(context).pop();
@@ -83,7 +84,7 @@ showReceiptGiverPopup(BuildContext context, TaskInfo taskInfo, Function(String,
onFinished(result, selectedTourEntry!);
} else {
showAlertDialog("Fehler", "Sie müssen einen Text eingeben und ein Geschlecht auswählen");
showAlertDialog('error'.tr, 'enter_text_and_gender'.tr);
}
})
],
+3 -2
View File
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:signature/signature.dart';
import 'package:stadtbote/model/task_info.dart';
import 'package:stadtbote/tools/app_localizations.dart';
import 'package:stadtbote/tools/util.dart';
showSignaturePopup(BuildContext context, TaskInfo taskInfo, Function(String?) onFinished) async {
@@ -78,13 +79,13 @@ showSignaturePopup(BuildContext context, TaskInfo taskInfo, Function(String?) on
),
),
getDefaultButton(
'Übernehmen',
'apply'.tr,
buttonHeight: 50,
() => {
if (controller.points.length > 50)
{Navigator.of(context).pop(), onFinished(convertPoints(controller.points))}
else
{showAlertDialog("Fehler", "Die Unterschrift ist zu kurz.")}
{showAlertDialog('error'.tr, 'signature_too_short'.tr)}
})
],
),
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:stadtbote/model/task_info.dart';
import 'package:stadtbote/tools/app_localizations.dart';
import 'package:stadtbote/tools/util.dart';
showStationNoticePopup(BuildContext context, TaskInfo taskInfo, Function(String) onFinished) async {
@@ -54,7 +55,7 @@ showStationNoticePopup(BuildContext context, TaskInfo taskInfo, Function(String)
autofocus: true),
),
const SizedBox(height: 20),
getDefaultButton('Übernehmen', buttonHeight: 50, () {
getDefaultButton('apply'.tr, buttonHeight: 50, () {
final result = textEditingController.text;
textEditingController.dispose();
Navigator.of(context).pop();
@@ -3,6 +3,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:image/image.dart' as image_lib;
import 'package:stadtbote/model/task_info.dart';
import 'package:stadtbote/tools/app_localizations.dart';
import 'package:stadtbote/tools/util.dart';
Future<void> showTakePhotosPopup(BuildContext context, TaskInfo taskInfo, Function(List<Uint8List>) onFinished) async {
@@ -53,7 +54,7 @@ Future<void> showTakePhotosPopup(BuildContext context, TaskInfo taskInfo, Functi
onTap: () {},
child: GestureDetector(
onTap: () async {
await showQuestionDialog(context, "Achtung", "Wollen Sie das Bild wirklich löschen?", [
await showQuestionDialog(context, 'attention'.tr, 'delete_photo_confirm'.tr, [
TextButton(onPressed: () => {Navigator.of(context).pop(),
imageDataList.removeAt(currentCarousselSliderIndex),
imageWidgetsList.removeAt(currentCarousselSliderIndex),
@@ -63,8 +64,8 @@ Future<void> showTakePhotosPopup(BuildContext context, TaskInfo taskInfo, Functi
currentCarousselSliderIndex = 0,
setState(() {})
}, child: const Text('Ja')),
TextButton(onPressed: () => {Navigator.of(context).pop()}, child: const Text('Nein')),
}, child: Text('yes'.tr)),
TextButton(onPressed: () => {Navigator.of(context).pop()}, child: Text('no'.tr)),
]);
},
child: Image.memory(resizedImageData),
@@ -99,7 +100,7 @@ Future<void> showTakePhotosPopup(BuildContext context, TaskInfo taskInfo, Functi
padding: const EdgeInsets.all(35.0),
child: Column(
children: [
const Text('Aufgenommene Fotos'),
Text('photos'.tr),
const SizedBox(height: 20),
Container(
decoration: BoxDecoration(
@@ -129,16 +130,16 @@ Future<void> showTakePhotosPopup(BuildContext context, TaskInfo taskInfo, Functi
const SizedBox(height: 20),
Visibility(
visible: imageDataList.length < (taskInfo.task?.max ?? 999),
child: getDefaultButton('Hinzufügen', buttonHeight: 50, () => {capturePhoto()})),
child: getDefaultButton('add'.tr, buttonHeight: 50, () => {capturePhoto()})),
const SizedBox(height: 12),
getDefaultButton(
'Übernehmen',
'apply'.tr,
buttonHeight: 50,
() => {
if (imageDataList.length >= (taskInfo.task?.min ?? 0) && imageDataList.length <= (taskInfo.task?.max ?? 999))
{Navigator.of(context).pop(), onFinished(imageDataList)}
else
{showAlertDialog('Fehler', 'Es müssen mindestens ${taskInfo.task?.min ?? 0} Foto(s) aufgenommen werden')}
{showAlertDialog('error'.tr, 'min_photos_error'.trParams({'min': taskInfo.task?.min ?? 0}))}
})
],
),
+4 -3
View File
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:stadtbote/tools/app_localizations.dart';
import 'package:stadtbote/tools/util.dart';
showZipPopup(BuildContext context, String text, String buttonText,
@@ -55,7 +56,7 @@ showZipPopup(BuildContext context, String text, String buttonText,
style: const TextStyle(
fontWeight: FontWeight.bold)),
const SizedBox(height: 20),
Util.getDefaultTextField('Postleitzahl',
Util.getDefaultTextField('zip'.tr,
formatter: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(5),
@@ -89,11 +90,11 @@ bool _checkInput(BuildContext context, String input) {
String message = '';
if (input.length != 5) {
message += 'Bitte geben Sie eine fünfstellige Postleitzahl ein';
message += 'enter_zip'.tr;
}
if (message.isNotEmpty) {
showAlertDialog('Fehler', message);
showAlertDialog('error'.tr, message);
}
return message.isEmpty;