Aufgaben-JSON testweise von Web-URL laden, Ankunft-Task als Button, RadioGroup-Umstellung

- TEMP-Loader: Aufgaben beim Oeffnen der Task-Seite von
  https://appcreation.de/download/task.json laden (Fallback: Tour-Daten)
- Task-Typ 1 (Ankunft melden) als Bestaetigungs-Button statt Checkbox
- Statusauswahl (Typ 11) auf RadioGroup umgestellt (Deprecation behoben)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
SvenandClaude Fable 5 committed 2026-07-02 10:15:01 +02:00
1 parent 1d0d23c1a9
commit d864b9c72a
2 files changed
+88 -8

No files matched your search

+88 -7
View File
@@ -2,6 +2,8 @@ import 'dart:convert';
import 'package:dart_mq/dart_mq.dart'; import 'package:dart_mq/dart_mq.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
// TEMP (nur zum Testen, s. _tempTaskJsonUrl): wieder entfernen.
import 'package:http/http.dart' as http;
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:image/image.dart' as image_lib; import 'package:image/image.dart' as image_lib;
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
@@ -25,6 +27,44 @@ import '../model/tour_task.dart';
import '../tools/app_localizations.dart'; import '../tools/app_localizations.dart';
import '../tools/util.dart'; import '../tools/util.dart';
// =============================================================================
// TEMPORÄR — NUR ZUM TESTEN, WIEDER ENTFERNEN
//
// Lädt das Aufgaben-JSON beim Öffnen der Seite von einer Webseite statt die
// Aufgaben der übergebenen Tour zu verwenden. Die URL muss entweder eine
// JSON-Liste von Aufgaben liefern oder ein Objekt mit einer "tasks"-Liste.
//
// Zum Entfernen: diesen Block, den http-Import oben und die mit
// "TEMP" markierte Zeile in _fetchTaskList() löschen. Alternativ die URL auf
// null setzen, dann greift wieder das normale Verhalten (auch bei Ladefehler).
// =============================================================================
// ignore: unnecessary_nullable_for_final_variable_declarations
const String? _tempTaskJsonUrl = "https://appcreation.de/download/task.json";
Future<List<TourTask>?> _tempLoadTasksFromWeb() async {
final url = _tempTaskJsonUrl;
if (url == null) {
return null;
}
try {
final response = await http.get(Uri.parse(url));
if (response.statusCode != 200) {
debugPrint("TEMP: Aufgaben-JSON laden fehlgeschlagen, HTTP ${response.statusCode}");
return null;
}
final decoded = jsonDecode(utf8.decode(response.bodyBytes));
final list = decoded is List ? decoded : (decoded as Map<String, dynamic>)['tasks'] as List<dynamic>;
return list.map((e) => TourTask.fromJson(e as Map<String, dynamic>)).toList();
} catch (e) {
debugPrint("TEMP: Aufgaben-JSON laden fehlgeschlagen: $e");
return null;
}
}
// ========================================================== ENDE TEMPORÄR ====
/// Seiten-Wizard zur Bearbeitung der Aufgaben einer Tour. Pro Aufgabe wird /// Seiten-Wizard zur Bearbeitung der Aufgaben einer Tour. Pro Aufgabe wird
/// genau eine Seite angezeigt; mit „Zurück"/„Weiter" navigiert der Bearbeiter /// genau eine Seite angezeigt; mit „Zurück"/„Weiter" navigiert der Bearbeiter
/// zwischen den Aufgaben, die letzte Seite schließt die Tour ab. /// zwischen den Aufgaben, die letzte Seite schließt die Tour ab.
@@ -98,7 +138,10 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
Future<List<TaskInfo>> _fetchTaskList() async { Future<List<TaskInfo>> _fetchTaskList() async {
List<TaskInfo> taskList = []; List<TaskInfo> taskList = [];
for (var task in widget.tour.tasks!) { // TEMP (nur zum Testen): Aufgaben von Webseite statt aus der Tour laden.
final tasks = await _tempLoadTasksFromWeb() ?? widget.tour.tasks!;
for (var task in tasks) {
final box = MyApp.objectBoxStore.box<TaskInfo>(); final box = MyApp.objectBoxStore.box<TaskInfo>();
final query = box.query(TaskInfo_.sId.equals(task.sId)).build(); final query = box.query(TaskInfo_.sId.equals(task.sId)).build();
var taskInfo = query.findFirst(); var taskInfo = query.findFirst();
@@ -274,7 +317,7 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
switch (type) { switch (type) {
case 1: case 1:
return _buildConfirmBody(draft, 'confirm_arrival'.tr); return _buildConfirmButtonBody(draft, 'confirm_arrival'.tr);
case 2: case 2:
return _buildSignatureBody(taskInfo, draft); return _buildSignatureBody(taskInfo, draft);
case 3: case 3:
@@ -300,7 +343,39 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
} }
} }
/// Bestätigungs-Aufgabe (Ankunft melden = 1, Checkbox = 6). /// Bestätigung per Button (Ankunft melden = 1). Antippen bestätigt bzw.
/// nimmt die Bestätigung wieder zurück; der Zustand wird über ein
/// Häkchen-Symbol im Button angezeigt.
Widget _buildConfirmButtonBody(_TaskDraft draft, String label) {
final confirmed = draft.confirmed;
return SizedBox(
height: 52,
child: MaterialButton(
color: Constants.colorPrimary,
elevation: 3,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(15))),
onPressed: () => setState(() => draft.confirmed = !confirmed),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (confirmed) ...[
const Icon(Icons.check_circle, color: Colors.white),
const SizedBox(width: 8),
],
Flexible(
child: Text(
label,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 16, color: Colors.white),
),
),
],
),
),
);
}
/// Bestätigungs-Aufgabe als Checkbox (Typ 6).
Widget _buildConfirmBody(_TaskDraft draft, String label) { Widget _buildConfirmBody(_TaskDraft draft, String label) {
return CheckboxListTile( return CheckboxListTile(
value: draft.confirmed, value: draft.confirmed,
@@ -339,14 +414,20 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
...options.map((o) => RadioListTile<int>( RadioGroup<int>(
value: o.code,
groupValue: draft.selectedCode, groupValue: draft.selectedCode,
onChanged: (value) => setState(() => draft.selectedCode = value),
child: Column(
children: options
.map((o) => RadioListTile<int>(
value: o.code,
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
activeColor: Constants.colorPrimary, activeColor: Constants.colorPrimary,
title: Text(o.label), title: Text(o.label),
onChanged: (value) => setState(() => draft.selectedCode = value), ))
)), .toList(),
),
),
if (requiresRemark) ...[ if (requiresRemark) ...[
const SizedBox(height: 8), const SizedBox(height: 8),
Util.getDefaultTextField( Util.getDefaultTextField(
-1
View File
@@ -16,4 +16,3 @@ class MessageHeader {
Map<String, dynamic> toJson() => _$MessageHeaderToJson(this); Map<String, dynamic> toJson() => _$MessageHeaderToJson(this);
} }