Compare commits
12
Commits
1d0d23c1a9
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c63018918b | ||
|
|
684e8a57fc | ||
|
|
d585da11b5 | ||
|
|
5e7babf265 | ||
|
|
c0b667c4ee | ||
|
|
bb7e0b6b82 | ||
|
|
4b649c6ed9 | ||
|
|
ac4266079a | ||
|
|
aa44311776 | ||
|
|
b8f321daf9 | ||
|
|
f58f3f0c59 | ||
|
|
d864b9c72a |
No files matched your search
@@ -8,7 +8,16 @@
|
|||||||
"Bash(source ~/.bash_profile)",
|
"Bash(source ~/.bash_profile)",
|
||||||
"Bash(/Users/svencarstensen/Documents/Entwicklung/flutter/bin/flutter pub:*)",
|
"Bash(/Users/svencarstensen/Documents/Entwicklung/flutter/bin/flutter pub:*)",
|
||||||
"Bash(/Users/svencarstensen/Documents/Entwicklung/flutter/bin/flutter analyze:*)",
|
"Bash(/Users/svencarstensen/Documents/Entwicklung/flutter/bin/flutter analyze:*)",
|
||||||
"Bash(/Users/svencarstensen/Documents/Entwicklung/flutter/bin/flutter:*)"
|
"Bash(/Users/svencarstensen/Documents/Entwicklung/flutter/bin/flutter:*)",
|
||||||
|
"Bash(flutter emulators *)",
|
||||||
|
"Bash(flutter devices *)",
|
||||||
|
"Read(//Users/svencarstensen/development/flutter/bin/**)",
|
||||||
|
"Read(//Users/svencarstensen/**)",
|
||||||
|
"Read(//Users/svencarstensen/Library/Android/sdk/emulator/**)",
|
||||||
|
"Bash(~/Library/Android/sdk/emulator/emulator -list-avds)",
|
||||||
|
"Read(//Users/svencarstensen/.android/avd/**)",
|
||||||
|
"Bash(xcrun simctl *)",
|
||||||
|
"Read(//opt/homebrew/Caskroom/**)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,3 +45,9 @@ app.*.map.json
|
|||||||
/android/app/debug
|
/android/app/debug
|
||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
|
|
||||||
|
# Java heap dumps
|
||||||
|
*.hprof
|
||||||
|
|
||||||
|
# Lokale Build-Konfigurationen mit Zugangsdaten
|
||||||
|
/config/*.local.json
|
||||||
Vendored
+2
-2
@@ -6,7 +6,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"type": "dart",
|
"type": "dart",
|
||||||
"flutterMode": "debug",
|
"flutterMode": "debug",
|
||||||
"deviceId": "emulator-5554",
|
"emulatorId": "Pixel_10_Pro",
|
||||||
"program": "lib/main.dart"
|
"program": "lib/main.dart"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"type": "dart",
|
"type": "dart",
|
||||||
"flutterMode": "release",
|
"flutterMode": "release",
|
||||||
"deviceId": "emulator-5554",
|
"emulatorId": "Pixel_10_Pro",
|
||||||
"program": "lib/main.dart"
|
"program": "lib/main.dart"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"java.configuration.updateBuildConfiguration": "automatic"
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="de.assecutor.stadtbote">
|
package="de.assecutor.stadtbote">
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<application
|
<application
|
||||||
android:label="Stadtbote"
|
android:label="Stadtbote"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
|
|||||||
@@ -1,5 +1,32 @@
|
|||||||
package de.assecutor.stadtbote
|
package de.assecutor.stadtbote
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
class MainActivity: FlutterActivity()
|
class MainActivity: FlutterActivity() {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
requestNotificationPermissionIfNeeded()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ab Android 13 (API 33) ist POST_NOTIFICATIONS eine "dangerous" Permission,
|
||||||
|
* die zur Laufzeit angefragt werden muss. Ohne sie darf die App keine
|
||||||
|
* Benachrichtigungen anzeigen und der Schalter in den Systemeinstellungen
|
||||||
|
* bleibt ausgegraut.
|
||||||
|
*/
|
||||||
|
private fun requestNotificationPermissionIfNeeded() {
|
||||||
|
if (Build.VERSION.SDK_INT >= 33 &&
|
||||||
|
checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), REQUEST_CODE_POST_NOTIFICATIONS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val REQUEST_CODE_POST_NOTIFICATIONS = 1001
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
org.gradle.jvmargs=-Xmx4096M -XX:MaxMetaspaceSize=512m
|
org.gradle.jvmargs=-Xmx4096M -XX:MaxMetaspaceSize=512m
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
|
# This builtInKotlin flag was added automatically by Flutter migrator
|
||||||
|
android.builtInKotlin=false
|
||||||
|
# This newDsl flag was added automatically by Flutter migrator
|
||||||
|
android.newDsl=false
|
||||||
@@ -19,7 +19,7 @@ pluginManagement {
|
|||||||
plugins {
|
plugins {
|
||||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
id "com.android.application" version "8.9.1" apply false
|
id "com.android.application" version "8.9.1" apply false
|
||||||
id "org.jetbrains.kotlin.android" version "2.1.0" apply false
|
id "org.jetbrains.kotlin.android" version "2.2.20" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
include ":app"
|
include ":app"
|
||||||
@@ -91,13 +91,12 @@ class _MessagesPageState extends State<MessagesPage> with ProducerMixin {
|
|||||||
void _handleSendPressed(types.PartialText text) async {
|
void _handleSendPressed(types.PartialText text) async {
|
||||||
var now = DateTime.now();
|
var now = DateTime.now();
|
||||||
|
|
||||||
var message = sbmessage.ChatMessage();
|
final message = sbmessage.ChatMessage.outgoing(
|
||||||
message.text = text.text;
|
externalId: await sbmessage.ChatMessage.getNextId(),
|
||||||
message.from = _sid;
|
time: Util.nowUnixSecondsUtc(),
|
||||||
message.subj = '';
|
from: _sid,
|
||||||
message.time = Util.nowUnixSecondsUtc();
|
text: text.text,
|
||||||
message.read = true;
|
);
|
||||||
message.id = await sbmessage.ChatMessage.getNextId();
|
|
||||||
|
|
||||||
final box = MyApp.objectBoxStore.box<sbmessage.ChatMessage>();
|
final box = MyApp.objectBoxStore.box<sbmessage.ChatMessage>();
|
||||||
box.put(message);
|
box.put(message);
|
||||||
@@ -205,7 +204,8 @@ class _MessagesPageState extends State<MessagesPage> with ProducerMixin {
|
|||||||
if (unreadChatMessages.isNotEmpty) {
|
if (unreadChatMessages.isNotEmpty) {
|
||||||
List<String> chatMessageIds = [];
|
List<String> chatMessageIds = [];
|
||||||
for (var message in unreadChatMessages) {
|
for (var message in unreadChatMessages) {
|
||||||
chatMessageIds.add(message.id.toString());
|
// Der Server erwartet seine Nachrichten-ID, nicht die lokale ObjectBox-ID.
|
||||||
|
chatMessageIds.add(message.externalId.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
var chatMessagesShowed = ChatMessagesReceived();
|
var chatMessagesShowed = ChatMessagesReceived();
|
||||||
|
|||||||
@@ -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();
|
||||||
@@ -122,6 +165,14 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
// Aufbau der Seite
|
// Aufbau der Seite
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
String _taskText(TaskInfo taskInfo, String key) {
|
||||||
|
// Die Unterschriftenerfassung bleibt unabhängig von der App-Sprache deutsch.
|
||||||
|
return AppLocalizations.tr(
|
||||||
|
key,
|
||||||
|
language: taskInfo.task?.effectiveType == 2 ? AppLanguage.german : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (!_initCompleted) {
|
if (!_initCompleted) {
|
||||||
@@ -147,13 +198,13 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 6),
|
padding: const EdgeInsets.only(bottom: 6),
|
||||||
child: Text(
|
child: Text(
|
||||||
'${'commission_number_short'.tr}: $_commisionNumber',
|
'${_taskText(taskInfo, 'commission_number_short')}: $_commisionNumber',
|
||||||
style: const TextStyle(fontSize: 18),
|
style: const TextStyle(fontSize: 18),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'${'task'.tr} ${_index + 1} / ${_taskList.length}',
|
'${_taskText(taskInfo, 'task')} ${_index + 1} / ${_taskList.length}',
|
||||||
style: const TextStyle(fontSize: 13, color: Colors.grey),
|
style: const TextStyle(fontSize: 13, color: Colors.grey),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
@@ -172,7 +223,7 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
(taskInfo.task?.topic ?? '') + ((taskInfo.task?.opt ?? false) ? "" : " *"),
|
(taskInfo.task?.topic ?? '') + (_isTaskOptional(taskInfo) ? "" : " *"),
|
||||||
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -195,7 +246,7 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _navButton(
|
child: _navButton(
|
||||||
'back'.tr,
|
_taskText(taskInfo, 'back'),
|
||||||
_index > 0 ? () => setState(() => _index--) : null,
|
_index > 0 ? () => setState(() => _index--) : null,
|
||||||
primary: false,
|
primary: false,
|
||||||
),
|
),
|
||||||
@@ -204,9 +255,9 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: isLast
|
child: isLast
|
||||||
? (widget.readOnly
|
? (widget.readOnly
|
||||||
? _navButton('close'.tr, () => _popBack())
|
? _navButton(_taskText(taskInfo, 'close'), () => _popBack())
|
||||||
: _navButton('finish'.tr, canProceed ? () => _onFinish() : null))
|
: _navButton(_taskText(taskInfo, 'finish'), canProceed ? () => _onFinish() : null))
|
||||||
: _navButton('next'.tr, canProceed ? () => _onNext() : null),
|
: _navButton(_taskText(taskInfo, 'next'), canProceed ? () => _onNext() : null),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -242,7 +293,11 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
Widget _buildReadOnlyBody(TaskInfo taskInfo) {
|
Widget _buildReadOnlyBody(TaskInfo taskInfo) {
|
||||||
final summary = (taskInfo.additionalText ?? '').trim();
|
var summary = (taskInfo.additionalText ?? '').trim();
|
||||||
|
if (taskInfo.task?.effectiveType == 2 && summary.isNotEmpty) {
|
||||||
|
// Auch früher in einer anderen Sprache gespeicherte Bestätigungen anzeigen.
|
||||||
|
summary = _taskText(taskInfo, 'signature_captured');
|
||||||
|
}
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(14),
|
padding: const EdgeInsets.all(14),
|
||||||
@@ -254,7 +309,7 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('completed'.tr, style: const TextStyle(fontWeight: FontWeight.bold)),
|
Text(_taskText(taskInfo, 'completed'), style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||||
if (summary.isNotEmpty) ...[
|
if (summary.isNotEmpty) ...[
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
Text(summary, style: const TextStyle(fontSize: 15)),
|
Text(summary, style: const TextStyle(fontSize: 15)),
|
||||||
@@ -270,19 +325,19 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
|
|
||||||
Widget _buildEditableBody(TaskInfo taskInfo) {
|
Widget _buildEditableBody(TaskInfo taskInfo) {
|
||||||
final draft = _draftFor(taskInfo);
|
final draft = _draftFor(taskInfo);
|
||||||
final type = taskInfo.task?.type ?? 0;
|
// Benutzerdefinierte Typen aus dem Workflow-Editor (>= 1000) verhalten
|
||||||
|
// sich wie ihr Basis-Typ, siehe TourTask.effectiveType.
|
||||||
|
final type = taskInfo.task?.effectiveType ?? 0;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 1:
|
|
||||||
return _buildConfirmBody(draft, 'confirm_arrival'.tr);
|
|
||||||
case 2:
|
case 2:
|
||||||
return _buildSignatureBody(taskInfo, draft);
|
return _buildSignatureBody(taskInfo, draft);
|
||||||
case 3:
|
case 3:
|
||||||
|
return _buildConfirmButtonBody(draft, 'confirm_arrival'.tr);
|
||||||
|
case 4:
|
||||||
return _buildPhotosBody(taskInfo, draft);
|
return _buildPhotosBody(taskInfo, draft);
|
||||||
case 5:
|
case 5:
|
||||||
return Text('barcode_disabled'.tr, style: const TextStyle(color: Colors.grey));
|
return Text('barcode_disabled'.tr, style: const TextStyle(color: Colors.grey));
|
||||||
case 6:
|
|
||||||
return _buildConfirmBody(draft, taskInfo.task?.topic ?? 'confirm'.tr);
|
|
||||||
case 7:
|
case 7:
|
||||||
case 8:
|
case 8:
|
||||||
case 9:
|
case 9:
|
||||||
@@ -290,17 +345,51 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
case 11:
|
case 11:
|
||||||
return _buildStatusBody(taskInfo, draft);
|
return _buildStatusBody(taskInfo, draft);
|
||||||
case 12:
|
case 12:
|
||||||
return _buildFieldsBody(taskInfo, draft, numeric: false);
|
return _buildConfirmBody(draft, taskInfo.task?.topic ?? 'confirm'.tr);
|
||||||
case 13:
|
case 13:
|
||||||
return _buildFieldsBody(taskInfo, draft, numeric: true);
|
return _buildFieldsBody(taskInfo, draft, numeric: false);
|
||||||
case 14:
|
case 14:
|
||||||
|
return _buildFieldsBody(taskInfo, draft, numeric: true);
|
||||||
|
case 15:
|
||||||
return _buildCheckboxListBody(taskInfo, draft);
|
return _buildCheckboxListBody(taskInfo, draft);
|
||||||
default:
|
default:
|
||||||
return Text('task_type_unsupported'.tr, style: const TextStyle(color: Colors.grey));
|
return Text('task_type_unsupported'.tr, style: const TextStyle(color: Colors.grey));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Bestätigungs-Aufgabe (Ankunft melden = 1, Checkbox = 6).
|
/// Bestätigung per Button (Ankunft melden = 3). 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 12).
|
||||||
Widget _buildConfirmBody(_TaskDraft draft, String label) {
|
Widget _buildConfirmBody(_TaskDraft draft, String label) {
|
||||||
return CheckboxListTile(
|
return CheckboxListTile(
|
||||||
value: draft.confirmed,
|
value: draft.confirmed,
|
||||||
@@ -339,14 +428,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),
|
||||||
contentPadding: EdgeInsets.zero,
|
child: Column(
|
||||||
activeColor: Constants.colorPrimary,
|
children: options
|
||||||
title: Text(o.label),
|
.map((o) => RadioListTile<int>(
|
||||||
onChanged: (value) => setState(() => draft.selectedCode = value),
|
value: o.code,
|
||||||
)),
|
contentPadding: EdgeInsets.zero,
|
||||||
|
activeColor: Constants.colorPrimary,
|
||||||
|
title: Text(o.label),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
if (requiresRemark) ...[
|
if (requiresRemark) ...[
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Util.getDefaultTextField(
|
Util.getDefaultTextField(
|
||||||
@@ -359,7 +454,7 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Text-/Zahlen-Liste (Typ 12/13): je Feld ein beschriftetes Eingabefeld.
|
/// Text-/Zahlen-Liste (Typ 13/14): je Feld ein beschriftetes Eingabefeld.
|
||||||
Widget _buildFieldsBody(TaskInfo taskInfo, _TaskDraft draft, {required bool numeric}) {
|
Widget _buildFieldsBody(TaskInfo taskInfo, _TaskDraft draft, {required bool numeric}) {
|
||||||
final fields = taskInfo.task?.fields ?? [];
|
final fields = taskInfo.task?.fields ?? [];
|
||||||
if (fields.isEmpty) {
|
if (fields.isEmpty) {
|
||||||
@@ -374,7 +469,16 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
if ((fields[i].title ?? '').isNotEmpty)
|
if ((fields[i].title ?? '').isNotEmpty)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 4, top: 4),
|
padding: const EdgeInsets.only(bottom: 4, top: 4),
|
||||||
child: Text(fields[i].title!, style: const TextStyle(fontWeight: FontWeight.w600)),
|
child: Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
text: fields[i].title!,
|
||||||
|
children: [
|
||||||
|
if (fields[i].required == true)
|
||||||
|
const TextSpan(text: ' *', style: TextStyle(color: Colors.red)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Util.getDefaultTextField(
|
Util.getDefaultTextField(
|
||||||
fields[i].placeholder ?? '',
|
fields[i].placeholder ?? '',
|
||||||
@@ -390,7 +494,7 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checkbox-Liste (Typ 14): Mehrfachauswahl aus den entries.
|
/// Checkbox-Liste (Typ 15): Mehrfachauswahl aus den entries.
|
||||||
Widget _buildCheckboxListBody(TaskInfo taskInfo, _TaskDraft draft) {
|
Widget _buildCheckboxListBody(TaskInfo taskInfo, _TaskDraft draft) {
|
||||||
final entries = taskInfo.task?.entries ?? [];
|
final entries = taskInfo.task?.entries ?? [];
|
||||||
if (entries.isEmpty) {
|
if (entries.isEmpty) {
|
||||||
@@ -404,7 +508,15 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
activeColor: Constants.colorPrimary,
|
activeColor: Constants.colorPrimary,
|
||||||
title: Text(e.text),
|
title: Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
text: e.text,
|
||||||
|
children: [
|
||||||
|
if (e.required == true)
|
||||||
|
const TextSpan(text: ' *', style: TextStyle(color: Colors.red)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
onChanged: (value) => setState(() {
|
onChanged: (value) => setState(() {
|
||||||
if (value ?? false) {
|
if (value ?? false) {
|
||||||
draft.checked.add(e.code);
|
draft.checked.add(e.code);
|
||||||
@@ -448,7 +560,7 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: TextButton.icon(
|
child: TextButton.icon(
|
||||||
icon: const Icon(Icons.clear),
|
icon: const Icon(Icons.clear),
|
||||||
label: Text('clear'.tr),
|
label: Text(_taskText(taskInfo, 'clear')),
|
||||||
onPressed: () => setState(() => draft.signatureController!.clear()),
|
onPressed: () => setState(() => draft.signatureController!.clear()),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -456,7 +568,7 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fotos aufnehmen (Typ 3) – Inline-Aufnahme und Vorschau.
|
/// Fotos aufnehmen (Typ 4) – Inline-Aufnahme und Vorschau.
|
||||||
Widget _buildPhotosBody(TaskInfo taskInfo, _TaskDraft draft) {
|
Widget _buildPhotosBody(TaskInfo taskInfo, _TaskDraft draft) {
|
||||||
final max = taskInfo.task?.max ?? 999;
|
final max = taskInfo.task?.max ?? 999;
|
||||||
final min = taskInfo.task?.min ?? 0;
|
final min = taskInfo.task?.min ?? 0;
|
||||||
@@ -535,17 +647,42 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
return _isValid(taskInfo);
|
return _isValid(taskInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ob die Aufgabe insgesamt optional ist (steuert das "*" im Titel):
|
||||||
|
/// entweder per {@code opt}-Flag oder – bei Listen-Tasks – weil kein
|
||||||
|
/// einziger Eintrag als Pflichtfeld gekennzeichnet ist. Altdaten ohne
|
||||||
|
/// Pflicht-Kennzeichen gelten wie bisher als verpflichtend.
|
||||||
|
bool _isTaskOptional(TaskInfo taskInfo) {
|
||||||
|
final task = taskInfo.task;
|
||||||
|
if (task == null) return true;
|
||||||
|
if (task.opt ?? false) return true;
|
||||||
|
switch (task.effectiveType) {
|
||||||
|
case 13:
|
||||||
|
case 14:
|
||||||
|
final fields = task.fields ?? [];
|
||||||
|
if (fields.isEmpty) return true;
|
||||||
|
if (fields.every((f) => f.required == null)) return false;
|
||||||
|
return fields.every((f) => f.required != true);
|
||||||
|
case 15:
|
||||||
|
final entries = task.entries ?? [];
|
||||||
|
if (entries.isEmpty) return true;
|
||||||
|
if (entries.every((e) => e.required == null)) return false;
|
||||||
|
return entries.every((e) => e.required != true);
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool _isValid(TaskInfo taskInfo) {
|
bool _isValid(TaskInfo taskInfo) {
|
||||||
final draft = _draftFor(taskInfo);
|
final draft = _draftFor(taskInfo);
|
||||||
final type = taskInfo.task?.type ?? 0;
|
final type = taskInfo.task?.effectiveType ?? 0;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 1:
|
|
||||||
case 6:
|
|
||||||
return draft.confirmed;
|
|
||||||
case 2:
|
case 2:
|
||||||
final sig = draft.signatureController;
|
final sig = draft.signatureController;
|
||||||
return sig != null && sig.points.length > 50;
|
return sig != null && sig.points.length > 50;
|
||||||
case 3:
|
case 3:
|
||||||
|
case 12:
|
||||||
|
return draft.confirmed;
|
||||||
|
case 4:
|
||||||
final min = (taskInfo.task?.min ?? 0);
|
final min = (taskInfo.task?.min ?? 0);
|
||||||
final max = (taskInfo.task?.max ?? 999);
|
final max = (taskInfo.task?.max ?? 999);
|
||||||
final required = min > 0 ? min : 1;
|
final required = min > 0 ? min : 1;
|
||||||
@@ -564,32 +701,64 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
return (draft.remark?.text.trim().isNotEmpty ?? false);
|
return (draft.remark?.text.trim().isNotEmpty ?? false);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
case 12:
|
|
||||||
_ensureFieldControllers(draft, taskInfo.task?.fields?.length ?? 0);
|
|
||||||
return draft.fields.isNotEmpty && draft.fields.every((c) => c.text.trim().isNotEmpty);
|
|
||||||
case 13:
|
case 13:
|
||||||
_ensureFieldControllers(draft, taskInfo.task?.fields?.length ?? 0);
|
_ensureFieldControllers(draft, taskInfo.task?.fields?.length ?? 0);
|
||||||
return draft.fields.isNotEmpty && draft.fields.every((c) => _isNumeric(c.text));
|
return _fieldsValid(taskInfo, draft, numeric: false);
|
||||||
case 14:
|
case 14:
|
||||||
return draft.checked.isNotEmpty;
|
_ensureFieldControllers(draft, taskInfo.task?.fields?.length ?? 0);
|
||||||
|
return _fieldsValid(taskInfo, draft, numeric: true);
|
||||||
|
case 15:
|
||||||
|
final entries = taskInfo.task?.entries ?? [];
|
||||||
|
// Ohne definierte Einträge gibt es keine Pflichtfelder -> freigeben.
|
||||||
|
if (entries.isEmpty) return true;
|
||||||
|
// Altdaten ohne Pflicht-Kennzeichen: wie bisher mindestens ein Häkchen.
|
||||||
|
if (entries.every((e) => e.required == null)) {
|
||||||
|
return draft.checked.isNotEmpty;
|
||||||
|
}
|
||||||
|
return entries
|
||||||
|
.where((e) => e.required == true)
|
||||||
|
.every((e) => draft.checked.contains(e.code));
|
||||||
default:
|
default:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prüft die Eingabefelder einer Text-/Zahlen-Liste (Typ 13/14) gegen ihre
|
||||||
|
/// Pflicht-Kennzeichen. Altdaten ohne Kennzeichen behalten das bisherige
|
||||||
|
/// Verhalten (alle Felder müssen gefüllt bzw. numerisch sein). Sonst müssen
|
||||||
|
/// nur Pflichtfelder gefüllt (bei Zahlenlisten numerisch) sein; optionale
|
||||||
|
/// Felder dürfen leer bleiben, müssen aber – falls ausgefüllt – bei
|
||||||
|
/// Zahlenlisten ebenfalls numerisch sein.
|
||||||
|
bool _fieldsValid(TaskInfo taskInfo, _TaskDraft draft, {required bool numeric}) {
|
||||||
|
final fields = taskInfo.task?.fields ?? [];
|
||||||
|
// Ohne definierte Felder gibt es keine Pflichtfelder -> freigeben.
|
||||||
|
if (fields.isEmpty) return true;
|
||||||
|
final legacy = fields.every((f) => f.required == null);
|
||||||
|
for (int i = 0; i < fields.length && i < draft.fields.length; i++) {
|
||||||
|
final text = draft.fields[i].text.trim();
|
||||||
|
final mandatory = legacy || fields[i].required == true;
|
||||||
|
if (mandatory) {
|
||||||
|
if (numeric ? !_isNumeric(text) : text.isEmpty) return false;
|
||||||
|
} else if (numeric && text.isNotEmpty && !_isNumeric(text)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// Ob der Bearbeiter überhaupt Daten erfasst hat (für optionale Aufgaben:
|
/// Ob der Bearbeiter überhaupt Daten erfasst hat (für optionale Aufgaben:
|
||||||
/// entscheidet, ob eine Antwort geschrieben oder die Aufgabe übersprungen wird).
|
/// entscheidet, ob eine Antwort geschrieben oder die Aufgabe übersprungen wird).
|
||||||
bool _hasData(TaskInfo taskInfo) {
|
bool _hasData(TaskInfo taskInfo) {
|
||||||
final draft = _draftFor(taskInfo);
|
final draft = _draftFor(taskInfo);
|
||||||
final type = taskInfo.task?.type ?? 0;
|
final type = taskInfo.task?.effectiveType ?? 0;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 1:
|
|
||||||
case 6:
|
|
||||||
return draft.confirmed;
|
|
||||||
case 2:
|
case 2:
|
||||||
final sig = draft.signatureController;
|
final sig = draft.signatureController;
|
||||||
return sig != null && sig.points.length > 50;
|
return sig != null && sig.points.length > 50;
|
||||||
case 3:
|
case 3:
|
||||||
|
case 12:
|
||||||
|
return draft.confirmed;
|
||||||
|
case 4:
|
||||||
return draft.photos.isNotEmpty;
|
return draft.photos.isNotEmpty;
|
||||||
case 7:
|
case 7:
|
||||||
case 8:
|
case 8:
|
||||||
@@ -597,10 +766,10 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
return (draft.text?.text.trim().isNotEmpty ?? false);
|
return (draft.text?.text.trim().isNotEmpty ?? false);
|
||||||
case 11:
|
case 11:
|
||||||
return draft.selectedCode != null;
|
return draft.selectedCode != null;
|
||||||
case 12:
|
|
||||||
case 13:
|
case 13:
|
||||||
return draft.fields.any((c) => c.text.trim().isNotEmpty);
|
|
||||||
case 14:
|
case 14:
|
||||||
|
return draft.fields.any((c) => c.text.trim().isNotEmpty);
|
||||||
|
case 15:
|
||||||
return draft.checked.isNotEmpty;
|
return draft.checked.isNotEmpty;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
@@ -645,7 +814,7 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
|
|
||||||
Future<void> _commitCurrent(TaskInfo taskInfo) async {
|
Future<void> _commitCurrent(TaskInfo taskInfo) async {
|
||||||
final draft = _draftFor(taskInfo);
|
final draft = _draftFor(taskInfo);
|
||||||
final type = taskInfo.task?.type ?? 0;
|
final type = taskInfo.task?.effectiveType ?? 0;
|
||||||
|
|
||||||
// Optionale Aufgabe ohne Eingabe: als erledigt markieren, keine Antwort.
|
// Optionale Aufgabe ohne Eingabe: als erledigt markieren, keine Antwort.
|
||||||
if (!_hasData(taskInfo)) {
|
if (!_hasData(taskInfo)) {
|
||||||
@@ -657,7 +826,16 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 1: // Ankunft melden
|
case 2: // Unterschrift
|
||||||
|
final answer = await _createAnswerHeader(taskInfo);
|
||||||
|
answer.sign = _convertSignaturePoints(draft.signatureController!.points);
|
||||||
|
taskInfo.answer = jsonEncode(answer.toJson());
|
||||||
|
taskInfo.additionalText = _taskText(taskInfo, 'signature_captured');
|
||||||
|
taskInfo.completed = true;
|
||||||
|
_persist(taskInfo);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3: // Ankunft melden
|
||||||
final answer = await _createAnswerHeader(taskInfo);
|
final answer = await _createAnswerHeader(taskInfo);
|
||||||
taskInfo.answer = jsonEncode(answer.toJson());
|
taskInfo.answer = jsonEncode(answer.toJson());
|
||||||
taskInfo.additionalText = DateFormat("dd.MM.yyyy HH:mm").format(DateTime.now());
|
taskInfo.additionalText = DateFormat("dd.MM.yyyy HH:mm").format(DateTime.now());
|
||||||
@@ -666,16 +844,7 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
_sendArrivedMessage(taskInfo);
|
_sendArrivedMessage(taskInfo);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2: // Unterschrift
|
case 4: // Fotos
|
||||||
final answer = await _createAnswerHeader(taskInfo);
|
|
||||||
answer.sign = _convertSignaturePoints(draft.signatureController!.points);
|
|
||||||
taskInfo.answer = jsonEncode(answer.toJson());
|
|
||||||
taskInfo.additionalText = 'signature_captured'.tr;
|
|
||||||
taskInfo.completed = true;
|
|
||||||
_persist(taskInfo);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 3: // Fotos
|
|
||||||
_storePhotos(draft.photos);
|
_storePhotos(draft.photos);
|
||||||
taskInfo.answer = null; // Bilder werden separat als pic-Nachrichten gesendet.
|
taskInfo.answer = null; // Bilder werden separat als pic-Nachrichten gesendet.
|
||||||
taskInfo.additionalText = 'photos_taken'.tr;
|
taskInfo.additionalText = 'photos_taken'.tr;
|
||||||
@@ -683,7 +852,7 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
_persist(taskInfo);
|
_persist(taskInfo);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 6: // Checkbox / Bestätigung
|
case 12: // Checkbox / Bestätigung
|
||||||
final answer = await _createAnswerHeader(taskInfo);
|
final answer = await _createAnswerHeader(taskInfo);
|
||||||
answer.val = "1";
|
answer.val = "1";
|
||||||
taskInfo.answer = jsonEncode(answer.toJson());
|
taskInfo.answer = jsonEncode(answer.toJson());
|
||||||
@@ -716,8 +885,8 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
_persist(taskInfo);
|
_persist(taskInfo);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 12: // Text-Liste
|
case 13: // Text-Liste
|
||||||
case 13: // Zahlen-Liste
|
case 14: // Zahlen-Liste
|
||||||
final answer = await _createAnswerHeader(taskInfo);
|
final answer = await _createAnswerHeader(taskInfo);
|
||||||
final fields = taskInfo.task?.fields ?? [];
|
final fields = taskInfo.task?.fields ?? [];
|
||||||
final answers = <FinFieldAnswer>[];
|
final answers = <FinFieldAnswer>[];
|
||||||
@@ -736,7 +905,7 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
_persist(taskInfo);
|
_persist(taskInfo);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 14: // Checkbox-Liste
|
case 15: // Checkbox-Liste
|
||||||
final answer = await _createAnswerHeader(taskInfo);
|
final answer = await _createAnswerHeader(taskInfo);
|
||||||
final checked = draft.checked.toList()..sort();
|
final checked = draft.checked.toList()..sort();
|
||||||
answer.checked = checked;
|
answer.checked = checked;
|
||||||
@@ -788,9 +957,9 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
/// erneut bearbeitbaren Aufgaben aus der gespeicherten Antwort vor.
|
/// erneut bearbeitbaren Aufgaben aus der gespeicherten Antwort vor.
|
||||||
_TaskDraft _buildDraft(TaskInfo taskInfo) {
|
_TaskDraft _buildDraft(TaskInfo taskInfo) {
|
||||||
final draft = _TaskDraft();
|
final draft = _TaskDraft();
|
||||||
final type = taskInfo.task?.type ?? 0;
|
final type = taskInfo.task?.effectiveType ?? 0;
|
||||||
|
|
||||||
if (type == 12 || type == 13) {
|
if (type == 13 || type == 14) {
|
||||||
_ensureFieldControllers(draft, taskInfo.task?.fields?.length ?? 0);
|
_ensureFieldControllers(draft, taskInfo.task?.fields?.length ?? 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -802,8 +971,8 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
try {
|
try {
|
||||||
final answer = FinTourTask.fromJson(jsonDecode(taskInfo.answer!) as Map<String, dynamic>);
|
final answer = FinTourTask.fromJson(jsonDecode(taskInfo.answer!) as Map<String, dynamic>);
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 1:
|
case 3:
|
||||||
case 6:
|
case 12:
|
||||||
draft.confirmed = true;
|
draft.confirmed = true;
|
||||||
break;
|
break;
|
||||||
case 7:
|
case 7:
|
||||||
@@ -815,14 +984,14 @@ class _TaskListPageState extends State<TaskListPage> with ProducerMixin {
|
|||||||
draft.selectedCode = answer.code;
|
draft.selectedCode = answer.code;
|
||||||
draft.remark = _listenedController(answer.val ?? '');
|
draft.remark = _listenedController(answer.val ?? '');
|
||||||
break;
|
break;
|
||||||
case 12:
|
|
||||||
case 13:
|
case 13:
|
||||||
|
case 14:
|
||||||
final values = answer.fields ?? [];
|
final values = answer.fields ?? [];
|
||||||
for (int i = 0; i < draft.fields.length && i < values.length; i++) {
|
for (int i = 0; i < draft.fields.length && i < values.length; i++) {
|
||||||
draft.fields[i].text = values[i].val ?? '';
|
draft.fields[i].text = values[i].val ?? '';
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 14:
|
case 15:
|
||||||
draft.checked = (answer.checked ?? []).toSet();
|
draft.checked = (answer.checked ?? []).toSet();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1118,7 +1287,7 @@ class _HeaderWidgetState extends State<_HeaderWidget> with ProducerMixin {
|
|||||||
child: const Center(
|
child: const Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
"Liste der Aufgaben",
|
"Workflow",
|
||||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18, color: Colors.white),
|
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18, color: Colors.white),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import 'package:stadtbote/content_views/task_list_page.dart';
|
|||||||
import 'package:stadtbote/content_views/utils/content_view_widget.dart';
|
import 'package:stadtbote/content_views/utils/content_view_widget.dart';
|
||||||
import 'package:stadtbote/messages/pop_content_view_message.dart';
|
import 'package:stadtbote/messages/pop_content_view_message.dart';
|
||||||
import 'package:stadtbote/model/job_tour.dart';
|
import 'package:stadtbote/model/job_tour.dart';
|
||||||
|
import 'package:stadtbote/popups/map_popup.dart';
|
||||||
|
|
||||||
import '../model/job.dart';
|
import '../model/job.dart';
|
||||||
import '../popups/files_popup.dart';
|
import '../popups/files_popup.dart';
|
||||||
import '../popups/map_popup.dart';
|
|
||||||
import '../tools/app_localizations.dart';
|
import '../tools/app_localizations.dart';
|
||||||
import '../tools/constants.dart';
|
import '../tools/constants.dart';
|
||||||
import '../tools/util.dart';
|
import '../tools/util.dart';
|
||||||
@@ -124,18 +124,13 @@ class _TourDetailsPageState extends State<TourDetailsPage> with ProducerMixin {
|
|||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
getDefaultButton(
|
getDefaultButton(
|
||||||
'map'.tr,
|
'map'.tr,
|
||||||
() => {
|
() {
|
||||||
if (widget.tour.pos != null)
|
if (widget.tour.pos != null) {
|
||||||
{
|
showMapPopup(context, widget.tour, widget.job.tours);
|
||||||
showMapPopup(
|
} else {
|
||||||
context, widget.tour, widget.job.tours)
|
showAlertDialog('information'.tr, 'no_position'.tr);
|
||||||
}
|
}
|
||||||
else
|
}),
|
||||||
{
|
|
||||||
showAlertDialog('information'.tr,
|
|
||||||
'no_position'.tr)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ class _TourListPageState extends State<TourListPage> with ProducerMixin {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
width: 100,
|
width: 100,
|
||||||
child: Visibility(
|
child: Visibility(
|
||||||
visible: widget.job.tours[0].pos != null,
|
visible: _tours.isNotEmpty && _tours.first.pos != null,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
shape: WidgetStateProperty.all<RoundedRectangleBorder>(
|
shape: WidgetStateProperty.all<RoundedRectangleBorder>(
|
||||||
@@ -102,7 +102,7 @@ class _TourListPageState extends State<TourListPage> with ProducerMixin {
|
|||||||
backgroundColor: WidgetStateProperty.all<Color>(Colors.lightGreen),
|
backgroundColor: WidgetStateProperty.all<Color>(Colors.lightGreen),
|
||||||
foregroundColor: WidgetStateProperty.all<Color>(Colors.black),
|
foregroundColor: WidgetStateProperty.all<Color>(Colors.black),
|
||||||
),
|
),
|
||||||
onPressed: () => {showMapPopup(context, widget.job.tours[0], widget.job.tours)},
|
onPressed: () => showMapPopup(context, _tours.first, _tours),
|
||||||
child: Text('map'.tr)),
|
child: Text('map'.tr)),
|
||||||
))
|
))
|
||||||
],
|
],
|
||||||
@@ -305,4 +305,4 @@ class _HeaderWidgetState extends State<_HeaderWidget> with ProducerMixin {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+3
-1
@@ -7,6 +7,7 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:geolocator/geolocator.dart';
|
import 'package:geolocator/geolocator.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:shared_map/shared_map.dart';
|
import 'package:shared_map/shared_map.dart';
|
||||||
|
import 'package:stadtbote/content_views/assigned_jobs_page.dart';
|
||||||
import 'package:stadtbote/messages/pop_content_view_message.dart';
|
import 'package:stadtbote/messages/pop_content_view_message.dart';
|
||||||
import 'package:stadtbote/model/log_entry.dart';
|
import 'package:stadtbote/model/log_entry.dart';
|
||||||
import 'package:stadtbote/popups/accept_jobs_popup.dart';
|
import 'package:stadtbote/popups/accept_jobs_popup.dart';
|
||||||
@@ -529,7 +530,8 @@ class _MainState extends State<Main> with SingleTickerProviderStateMixin, Produc
|
|||||||
|
|
||||||
void _startCheckForNewState0JobsJobsTimer() {
|
void _startCheckForNewState0JobsJobsTimer() {
|
||||||
_checkForNewState0JobsJobsTimer ??= Timer.periodic(const Duration(seconds: 5), (Timer t) async {
|
_checkForNewState0JobsJobsTimer ??= Timer.periodic(const Duration(seconds: 5), (Timer t) async {
|
||||||
if (await _jobMap!.length() > 0 && !_dialogOpened) {
|
// Auf der Auftragsliste können neue Aufträge direkt beantwortet werden.
|
||||||
|
if (await _jobMap!.length() > 0 && !_dialogOpened && _currentContent is! AssignedJobsPage) {
|
||||||
_dialogOpened = true;
|
_dialogOpened = true;
|
||||||
await showAcceptJobsPopup(_jobMap!.sharedReference(), () async {
|
await showAcceptJobsPopup(_jobMap!.sharedReference(), () async {
|
||||||
_dialogOpened = false;
|
_dialogOpened = false;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class ChatMessage {
|
|||||||
@Id()
|
@Id()
|
||||||
int id = 0;
|
int id = 0;
|
||||||
|
|
||||||
// External ID from server
|
// Nachrichten-ID für den Server, getrennt von der lokalen ObjectBox-ID.
|
||||||
@Unique()
|
@Unique()
|
||||||
late int externalId;
|
late int externalId;
|
||||||
|
|
||||||
@@ -28,6 +28,22 @@ class ChatMessage {
|
|||||||
|
|
||||||
ChatMessage();
|
ChatMessage();
|
||||||
|
|
||||||
|
factory ChatMessage.outgoing({
|
||||||
|
required int externalId,
|
||||||
|
required int time,
|
||||||
|
required String from,
|
||||||
|
required String text,
|
||||||
|
}) {
|
||||||
|
// ObjectBox vergibt die lokale ID beim ersten Speichern selbst.
|
||||||
|
return ChatMessage()
|
||||||
|
..externalId = externalId
|
||||||
|
..time = time
|
||||||
|
..from = from
|
||||||
|
..subj = ''
|
||||||
|
..text = text
|
||||||
|
..read = true;
|
||||||
|
}
|
||||||
|
|
||||||
factory ChatMessage.fromJson(Map<String, dynamic> json) {
|
factory ChatMessage.fromJson(Map<String, dynamic> json) {
|
||||||
final message = ChatMessage();
|
final message = ChatMessage();
|
||||||
message.id = 0; // Let ObjectBox assign the ID
|
message.id = 0; // Let ObjectBox assign the ID
|
||||||
|
|||||||
@@ -97,4 +97,3 @@ class Job {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,6 +1,16 @@
|
|||||||
class TourTask {
|
class TourTask {
|
||||||
|
/// Untergrenze der im Workflow-Editor vergebenen Typnummern für
|
||||||
|
/// benutzerdefinierte Typen.
|
||||||
|
static const int customTypeStart = 1000;
|
||||||
|
|
||||||
late int sId;
|
late int sId;
|
||||||
late int type;
|
late int type;
|
||||||
|
|
||||||
|
/// Typnummer des Basis-Typs bei benutzerdefinierten Typen
|
||||||
|
/// (>= [customTypeStart]) aus dem Workflow-Editor. `null` bei
|
||||||
|
/// Standard-Typen und bei Altdaten ohne Angabe.
|
||||||
|
int? baseType;
|
||||||
|
|
||||||
late String topic;
|
late String topic;
|
||||||
bool? opt;
|
bool? opt;
|
||||||
bool? reenter;
|
bool? reenter;
|
||||||
@@ -17,15 +27,36 @@ class TourTask {
|
|||||||
/// Auswahloptionen eines Status-Tasks (Typ 11).
|
/// Auswahloptionen eines Status-Tasks (Typ 11).
|
||||||
List<TourChoice>? choices;
|
List<TourChoice>? choices;
|
||||||
|
|
||||||
/// Eingabefelder einer Text-/Zahlen-Liste (Typ 12/13).
|
/// Eingabefelder einer Text-/Zahlen-Liste (Typ 13/14).
|
||||||
List<TourField>? fields;
|
List<TourField>? fields;
|
||||||
|
|
||||||
TourTask();
|
TourTask();
|
||||||
|
|
||||||
|
/// Der für Darstellung und Validierung maßgebliche Typ: Standard-Typen
|
||||||
|
/// unverändert; benutzerdefinierte Typen (>= [customTypeStart]) werden auf
|
||||||
|
/// ihren Basis-Typ abgebildet. Fehlt {@code base_type} (Altdaten), wird der
|
||||||
|
/// Basis-Typ aus den typprägenden Eigenschaften abgeleitet.
|
||||||
|
int get effectiveType {
|
||||||
|
if (type < customTypeStart) {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
if (baseType != null) {
|
||||||
|
return baseType!;
|
||||||
|
}
|
||||||
|
if (choices != null && choices!.isNotEmpty) return 11; // Statusauswahl
|
||||||
|
if (entries != null && entries!.isNotEmpty) return 15; // Checkbox-Liste
|
||||||
|
if (fields != null && fields!.isNotEmpty) return 13; // Text-Liste
|
||||||
|
if (min != null || max != null) return 4; // Fotos
|
||||||
|
if (maxLen != null) return 8; // Texteingabe (Bemerkung)
|
||||||
|
if (track != null) return 3; // Ankunft melden
|
||||||
|
return 12; // Bestätigung per Checkbox
|
||||||
|
}
|
||||||
|
|
||||||
factory TourTask.fromJson(Map<String, dynamic> json) {
|
factory TourTask.fromJson(Map<String, dynamic> json) {
|
||||||
final task = TourTask();
|
final task = TourTask();
|
||||||
task.sId = json['s_id'] as int;
|
task.sId = json['s_id'] as int;
|
||||||
task.type = json['type'] as int;
|
task.type = json['type'] as int;
|
||||||
|
task.baseType = json['base_type'] as int?;
|
||||||
task.topic = json['topic'] as String;
|
task.topic = json['topic'] as String;
|
||||||
task.opt = json['opt'] as bool? ?? false;
|
task.opt = json['opt'] as bool? ?? false;
|
||||||
task.reenter = json['reenter'] as bool? ?? false;
|
task.reenter = json['reenter'] as bool? ?? false;
|
||||||
@@ -59,6 +90,7 @@ class TourTask {
|
|||||||
return {
|
return {
|
||||||
's_id': sId,
|
's_id': sId,
|
||||||
'type': type,
|
'type': type,
|
||||||
|
if (baseType != null) 'base_type': baseType,
|
||||||
'topic': topic,
|
'topic': topic,
|
||||||
'opt': opt,
|
'opt': opt,
|
||||||
'reenter': reenter,
|
'reenter': reenter,
|
||||||
@@ -82,6 +114,10 @@ class TourEntry {
|
|||||||
late String text;
|
late String text;
|
||||||
late bool queryRemark;
|
late bool queryRemark;
|
||||||
|
|
||||||
|
/// Pflicht-Kennzeichen aus dem Workflow-Editor (Checkbox-Liste, Typ 15).
|
||||||
|
/// `null`, wenn das JSON keine Angabe enthält (Altdaten).
|
||||||
|
bool? required;
|
||||||
|
|
||||||
TourEntry();
|
TourEntry();
|
||||||
|
|
||||||
factory TourEntry.fromJson(Map<String, dynamic> json) {
|
factory TourEntry.fromJson(Map<String, dynamic> json) {
|
||||||
@@ -89,6 +125,7 @@ class TourEntry {
|
|||||||
entry.code = json['code'] as int;
|
entry.code = json['code'] as int;
|
||||||
entry.text = json['text'] as String;
|
entry.text = json['text'] as String;
|
||||||
entry.queryRemark = json['query_remark'] as bool? ?? false;
|
entry.queryRemark = json['query_remark'] as bool? ?? false;
|
||||||
|
entry.required = json['required'] as bool?;
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,6 +134,7 @@ class TourEntry {
|
|||||||
'code': code,
|
'code': code,
|
||||||
'text': text,
|
'text': text,
|
||||||
'query_remark': queryRemark,
|
'query_remark': queryRemark,
|
||||||
|
if (required != null) 'required': required,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,17 +177,22 @@ class TourChoice {
|
|||||||
bool get requiresRemark => (quest != null && quest!.trim().isNotEmpty);
|
bool get requiresRemark => (quest != null && quest!.trim().isNotEmpty);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ein Eingabefeld einer Text-/Zahlen-Liste (Typ 12/13).
|
/// Ein Eingabefeld einer Text-/Zahlen-Liste (Typ 13/14).
|
||||||
class TourField {
|
class TourField {
|
||||||
String? title;
|
String? title;
|
||||||
String? placeholder;
|
String? placeholder;
|
||||||
|
|
||||||
|
/// Pflicht-Kennzeichen aus dem Workflow-Editor. `null`, wenn das JSON
|
||||||
|
/// keine Angabe enthält (Altdaten).
|
||||||
|
bool? required;
|
||||||
|
|
||||||
TourField();
|
TourField();
|
||||||
|
|
||||||
factory TourField.fromJson(Map<String, dynamic> json) {
|
factory TourField.fromJson(Map<String, dynamic> json) {
|
||||||
final field = TourField();
|
final field = TourField();
|
||||||
field.title = json['title'] as String?;
|
field.title = json['title'] as String?;
|
||||||
field.placeholder = json['placeholder'] as String?;
|
field.placeholder = json['placeholder'] as String?;
|
||||||
|
field.required = json['required'] as bool?;
|
||||||
return field;
|
return field;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,6 +200,7 @@ class TourField {
|
|||||||
return {
|
return {
|
||||||
'title': title,
|
'title': title,
|
||||||
'placeholder': placeholder,
|
'placeholder': placeholder,
|
||||||
|
if (required != null) 'required': required,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "3:808686831791135454",
|
"id": "3:808686831791135454",
|
||||||
"lastPropertyId": "13:1803006975816940878",
|
"lastPropertyId": "14:4499212061291648440",
|
||||||
"name": "Job",
|
"name": "Job",
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
@@ -426,7 +426,8 @@
|
|||||||
2981635918450905844,
|
2981635918450905844,
|
||||||
4551221086536914442,
|
4551221086536914442,
|
||||||
1176594062059570557,
|
1176594062059570557,
|
||||||
4272081635478028034
|
4272081635478028034,
|
||||||
|
4499212061291648440
|
||||||
],
|
],
|
||||||
"retiredRelationUids": [],
|
"retiredRelationUids": [],
|
||||||
"version": 1
|
"version": 1
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ final _entities = <obx_int.ModelEntity>[
|
|||||||
obx_int.ModelEntity(
|
obx_int.ModelEntity(
|
||||||
id: const obx_int.IdUid(3, 808686831791135454),
|
id: const obx_int.IdUid(3, 808686831791135454),
|
||||||
name: 'Job',
|
name: 'Job',
|
||||||
lastPropertyId: const obx_int.IdUid(13, 1803006975816940878),
|
lastPropertyId: const obx_int.IdUid(14, 4499212061291648440),
|
||||||
flags: 0,
|
flags: 0,
|
||||||
properties: <obx_int.ModelProperty>[
|
properties: <obx_int.ModelProperty>[
|
||||||
obx_int.ModelProperty(
|
obx_int.ModelProperty(
|
||||||
@@ -483,7 +483,8 @@ obx_int.ModelDefinition getObjectBoxModel() {
|
|||||||
2981635918450905844,
|
2981635918450905844,
|
||||||
4551221086536914442,
|
4551221086536914442,
|
||||||
1176594062059570557,
|
1176594062059570557,
|
||||||
4272081635478028034
|
4272081635478028034,
|
||||||
|
4499212061291648440
|
||||||
],
|
],
|
||||||
retiredRelationUids: const [],
|
retiredRelationUids: const [],
|
||||||
modelVersion: 5,
|
modelVersion: 5,
|
||||||
@@ -542,7 +543,7 @@ obx_int.ModelDefinition getObjectBoxModel() {
|
|||||||
final remOffset = fbb.writeString(object.rem);
|
final remOffset = fbb.writeString(object.rem);
|
||||||
final toursJsonOffset = fbb.writeString(object.toursJson);
|
final toursJsonOffset = fbb.writeString(object.toursJson);
|
||||||
final filesJsonOffset = fbb.writeString(object.filesJson);
|
final filesJsonOffset = fbb.writeString(object.filesJson);
|
||||||
fbb.startTable(14);
|
fbb.startTable(15);
|
||||||
fbb.addInt64(0, object.id);
|
fbb.addInt64(0, object.id);
|
||||||
fbb.addOffset(1, verOffset);
|
fbb.addOffset(1, verOffset);
|
||||||
fbb.addInt64(2, object.state);
|
fbb.addInt64(2, object.state);
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ Future<List<Widget>> _fetchJobWidgets(StateSetter dialogState, Function() onFini
|
|||||||
style: TextStyle(backgroundColor: isJobTooFarInFuture(job) ? Colors.red : Colors.transparent),
|
style: TextStyle(backgroundColor: isJobTooFarInFuture(job) ? Colors.red : Colors.transparent),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: double.infinity, child: Text('${'order_number'.tr}: ${job.id}', textAlign: TextAlign.left)),
|
SizedBox(width: double.infinity, child: Text('${'order_number'.tr}: ${job.externalId}', 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].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].street, textAlign: TextAlign.left)),
|
||||||
SizedBox(width: double.infinity, child: Text(job.tours[0].city, textAlign: TextAlign.left)),
|
SizedBox(width: double.infinity, child: Text(job.tours[0].city, textAlign: TextAlign.left)),
|
||||||
@@ -294,7 +294,7 @@ Future<List<Widget>> _fetchJobWidgets(StateSetter dialogState, Function() onFini
|
|||||||
style: TextStyle(backgroundColor: isJobTooFarInFuture(job) ? Colors.red : Colors.transparent),
|
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('Auftragsnummer: ${job.externalId}', 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].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].street, textAlign: TextAlign.left)),
|
||||||
SizedBox(width: double.infinity, child: Text(job.tours[0].city, textAlign: TextAlign.left)),
|
SizedBox(width: double.infinity, child: Text(job.tours[0].city, textAlign: TextAlign.left)),
|
||||||
|
|||||||
+40
-35
@@ -114,14 +114,13 @@ class _FileListItemState extends State<_FileListItem> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else if (widget.file.isImage) {
|
} else if (widget.file.isImage) {
|
||||||
// Show image in a dialog on top of the files dialog
|
// Bildanhänge wie PDFs vergrößern und im gezoomten Zustand verschieben.
|
||||||
await showDialog(
|
await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return Dialog(
|
return Dialog(
|
||||||
insetPadding: const EdgeInsets.all(16),
|
insetPadding: const EdgeInsets.all(16),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
children: [
|
||||||
AppBar(
|
AppBar(
|
||||||
title: Text(widget.file.name),
|
title: Text(widget.file.name),
|
||||||
@@ -131,41 +130,47 @@ class _FileListItemState extends State<_FileListItem> {
|
|||||||
),
|
),
|
||||||
automaticallyImplyLeading: false,
|
automaticallyImplyLeading: false,
|
||||||
),
|
),
|
||||||
Flexible(
|
Expanded(
|
||||||
child: Image.network(
|
child: InteractiveViewer(
|
||||||
widget.file.url,
|
minScale: 1,
|
||||||
fit: BoxFit.contain,
|
maxScale: 5,
|
||||||
loadingBuilder: (context, child, loadingProgress) {
|
child: SizedBox.expand(
|
||||||
if (loadingProgress == null) return child;
|
child: Image.network(
|
||||||
return Center(
|
widget.file.url,
|
||||||
child: CircularProgressIndicator(
|
fit: BoxFit.contain,
|
||||||
value: loadingProgress.expectedTotalBytes != null
|
loadingBuilder: (context, child, loadingProgress) {
|
||||||
? loadingProgress.cumulativeBytesLoaded /
|
if (loadingProgress == null) return child;
|
||||||
loadingProgress.expectedTotalBytes!
|
return Center(
|
||||||
: null,
|
child: CircularProgressIndicator(
|
||||||
),
|
value: loadingProgress.expectedTotalBytes != null
|
||||||
);
|
? loadingProgress.cumulativeBytesLoaded /
|
||||||
},
|
loadingProgress.expectedTotalBytes!
|
||||||
errorBuilder: (context, error, stackTrace) {
|
: null,
|
||||||
return Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Icon(
|
|
||||||
Icons.error_outline,
|
|
||||||
size: 64,
|
|
||||||
color: Colors.red,
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
);
|
||||||
Text(
|
},
|
||||||
'Fehler beim Laden des Bildes:\n$error',
|
errorBuilder: (context, error, stackTrace) {
|
||||||
textAlign: TextAlign.center,
|
return Center(
|
||||||
style: const TextStyle(color: Colors.red),
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.error_outline,
|
||||||
|
size: 64,
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'Fehler beim Laden des Bildes:\n$error',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(color: Colors.red),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
);
|
||||||
),
|
},
|
||||||
);
|
),
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ showSignaturePopup(BuildContext context, TaskInfo taskInfo, Function(String?) on
|
|||||||
canPop: false,
|
canPop: false,
|
||||||
onPopInvokedWithResult: (bool didPop, dynamic result) async {
|
onPopInvokedWithResult: (bool didPop, dynamic result) async {
|
||||||
if (!didPop) {
|
if (!didPop) {
|
||||||
final shouldPop = await taskDialogSecurityException(context);
|
final shouldPop = await taskDialogSecurityException(context, language: AppLanguage.german);
|
||||||
if (shouldPop) {
|
if (shouldPop) {
|
||||||
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
@@ -79,13 +79,18 @@ showSignaturePopup(BuildContext context, TaskInfo taskInfo, Function(String?) on
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
getDefaultButton(
|
getDefaultButton(
|
||||||
'apply'.tr,
|
AppLocalizations.tr('apply', language: AppLanguage.german),
|
||||||
buttonHeight: 50,
|
buttonHeight: 50,
|
||||||
() => {
|
() => {
|
||||||
if (controller.points.length > 50)
|
if (controller.points.length > 50)
|
||||||
{Navigator.of(context).pop(), onFinished(convertPoints(controller.points))}
|
{Navigator.of(context).pop(), onFinished(convertPoints(controller.points))}
|
||||||
else
|
else
|
||||||
{showAlertDialog('error'.tr, 'signature_too_short'.tr)}
|
{
|
||||||
|
showAlertDialog(
|
||||||
|
AppLocalizations.tr('error', language: AppLanguage.german),
|
||||||
|
AppLocalizations.tr('signature_too_short', language: AppLanguage.german),
|
||||||
|
)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ class AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Hilfsmethode für einfachen Zugriff
|
// Hilfsmethode für einfachen Zugriff
|
||||||
static String tr(String key) {
|
static String tr(String key, {AppLanguage? language}) {
|
||||||
return _instance._translations[_instance._currentLanguage.code]?[key] ??
|
return _instance._translations[(language ?? _instance._currentLanguage).code]?[key] ??
|
||||||
_instance._translations['de']?[key] ??
|
_instance._translations['de']?[key] ??
|
||||||
key;
|
key;
|
||||||
}
|
}
|
||||||
@@ -145,7 +145,7 @@ class AppLocalizations {
|
|||||||
'unknown': 'Unbekannt',
|
'unknown': 'Unbekannt',
|
||||||
|
|
||||||
// Aufgabenliste
|
// Aufgabenliste
|
||||||
'task_list': 'Liste der Aufgaben',
|
'task_list': 'Workflow',
|
||||||
'commission_number_short': 'Komm-#',
|
'commission_number_short': 'Komm-#',
|
||||||
'photos_taken': 'Fotos aufgenommen',
|
'photos_taken': 'Fotos aufgenommen',
|
||||||
'signature_captured': 'Unterschrift erfasst',
|
'signature_captured': 'Unterschrift erfasst',
|
||||||
@@ -287,7 +287,7 @@ class AppLocalizations {
|
|||||||
'unknown': 'Unknown',
|
'unknown': 'Unknown',
|
||||||
|
|
||||||
// Task List
|
// Task List
|
||||||
'task_list': 'Task List',
|
'task_list': 'Workflow',
|
||||||
'commission_number_short': 'Comm-#',
|
'commission_number_short': 'Comm-#',
|
||||||
'photos_taken': 'Photos taken',
|
'photos_taken': 'Photos taken',
|
||||||
'signature_captured': 'Signature captured',
|
'signature_captured': 'Signature captured',
|
||||||
@@ -418,7 +418,7 @@ class AppLocalizations {
|
|||||||
'unknown': 'Desconocido',
|
'unknown': 'Desconocido',
|
||||||
|
|
||||||
// Task List
|
// Task List
|
||||||
'task_list': 'Lista de Tareas',
|
'task_list': 'Workflow',
|
||||||
'commission_number_short': 'Núm. Com.',
|
'commission_number_short': 'Núm. Com.',
|
||||||
'photos_taken': 'Fotos tomadas',
|
'photos_taken': 'Fotos tomadas',
|
||||||
'signature_captured': 'Firma capturada',
|
'signature_captured': 'Firma capturada',
|
||||||
@@ -539,7 +539,7 @@ class AppLocalizations {
|
|||||||
'unknown': 'Inconnu',
|
'unknown': 'Inconnu',
|
||||||
|
|
||||||
// Task List
|
// Task List
|
||||||
'task_list': 'Liste des Tâches',
|
'task_list': 'Workflow',
|
||||||
'commission_number_short': 'N° Comm.',
|
'commission_number_short': 'N° Comm.',
|
||||||
'photos_taken': 'Photos prises',
|
'photos_taken': 'Photos prises',
|
||||||
'signature_captured': 'Signature capturée',
|
'signature_captured': 'Signature capturée',
|
||||||
@@ -660,7 +660,7 @@ class AppLocalizations {
|
|||||||
'unknown': 'Nieznany',
|
'unknown': 'Nieznany',
|
||||||
|
|
||||||
// Task List
|
// Task List
|
||||||
'task_list': 'Lista Zadań',
|
'task_list': 'Workflow',
|
||||||
'commission_number_short': 'Nr Zlec.',
|
'commission_number_short': 'Nr Zlec.',
|
||||||
'photos_taken': 'Zrobione zdjęcia',
|
'photos_taken': 'Zrobione zdjęcia',
|
||||||
'signature_captured': 'Podpis zapisany',
|
'signature_captured': 'Podpis zapisany',
|
||||||
@@ -781,7 +781,7 @@ class AppLocalizations {
|
|||||||
'unknown': 'Неизвестно',
|
'unknown': 'Неизвестно',
|
||||||
|
|
||||||
// Task List
|
// Task List
|
||||||
'task_list': 'Список Задач',
|
'task_list': 'Workflow',
|
||||||
'commission_number_short': '№ Комм.',
|
'commission_number_short': '№ Комм.',
|
||||||
'photos_taken': 'Фотографии сделаны',
|
'photos_taken': 'Фотографии сделаны',
|
||||||
'signature_captured': 'Подпись сохранена',
|
'signature_captured': 'Подпись сохранена',
|
||||||
@@ -902,7 +902,7 @@ class AppLocalizations {
|
|||||||
'unknown': 'Bilinmiyor',
|
'unknown': 'Bilinmiyor',
|
||||||
|
|
||||||
// Task List
|
// Task List
|
||||||
'task_list': 'Görev Listesi',
|
'task_list': 'Workflow',
|
||||||
'commission_number_short': 'Kom. No.',
|
'commission_number_short': 'Kom. No.',
|
||||||
'photos_taken': 'Fotoğraflar çekildi',
|
'photos_taken': 'Fotoğraflar çekildi',
|
||||||
'signature_captured': 'İmza kaydedildi',
|
'signature_captured': 'İmza kaydedildi',
|
||||||
@@ -1023,7 +1023,7 @@ class AppLocalizations {
|
|||||||
'unknown': 'Teadmata',
|
'unknown': 'Teadmata',
|
||||||
|
|
||||||
// Task List
|
// Task List
|
||||||
'task_list': 'Ülesannete Nimekiri',
|
'task_list': 'Workflow',
|
||||||
'commission_number_short': 'Kom. Nr.',
|
'commission_number_short': 'Kom. Nr.',
|
||||||
'photos_taken': 'Fotod tehtud',
|
'photos_taken': 'Fotod tehtud',
|
||||||
'signature_captured': 'Allkiri salvestatud',
|
'signature_captured': 'Allkiri salvestatud',
|
||||||
@@ -1144,7 +1144,7 @@ class AppLocalizations {
|
|||||||
'unknown': 'Nezināms',
|
'unknown': 'Nezināms',
|
||||||
|
|
||||||
// Task List
|
// Task List
|
||||||
'task_list': 'Uzdevumu Saraksts',
|
'task_list': 'Workflow',
|
||||||
'commission_number_short': 'Kom. Nr.',
|
'commission_number_short': 'Kom. Nr.',
|
||||||
'photos_taken': 'Fotogrāfijas uzņemtas',
|
'photos_taken': 'Fotogrāfijas uzņemtas',
|
||||||
'signature_captured': 'Paraksts saglabāts',
|
'signature_captured': 'Paraksts saglabāts',
|
||||||
@@ -1265,7 +1265,7 @@ class AppLocalizations {
|
|||||||
'unknown': 'Nežinoma',
|
'unknown': 'Nežinoma',
|
||||||
|
|
||||||
// Task List
|
// Task List
|
||||||
'task_list': 'Užduočių Sąrašas',
|
'task_list': 'Workflow',
|
||||||
'commission_number_short': 'Kom. Nr.',
|
'commission_number_short': 'Kom. Nr.',
|
||||||
'photos_taken': 'Nuotraukos padarytos',
|
'photos_taken': 'Nuotraukos padarytos',
|
||||||
'signature_captured': 'Parašas užfiksuotas',
|
'signature_captured': 'Parašas užfiksuotas',
|
||||||
|
|||||||
@@ -16,4 +16,3 @@ class MessageHeader {
|
|||||||
|
|
||||||
Map<String, dynamic> toJson() => _$MessageHeaderToJson(this);
|
Map<String, dynamic> toJson() => _$MessageHeaderToJson(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6,6 +6,7 @@ import 'dart:math';
|
|||||||
import 'package:archive/archive.dart';
|
import 'package:archive/archive.dart';
|
||||||
import 'package:dart_mq/dart_mq.dart';
|
import 'package:dart_mq/dart_mq.dart';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:geolocator/geolocator.dart';
|
import 'package:geolocator/geolocator.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:mqtt5_client/mqtt5_client.dart';
|
import 'package:mqtt5_client/mqtt5_client.dart';
|
||||||
@@ -98,6 +99,22 @@ class MqttCore with ProducerMixin, ConsumerMixin {
|
|||||||
_processBootstrap();
|
_processBootstrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ermöglicht Tests des Nachrichtenflusses ohne Bootstrap und Netzwerkzugriff.
|
||||||
|
@visibleForTesting
|
||||||
|
MqttCore.withClient({
|
||||||
|
required MqttServerClient client,
|
||||||
|
required String clientId,
|
||||||
|
required String fromServerTopic,
|
||||||
|
required String toServerTopic,
|
||||||
|
}) : _mqttClient = client,
|
||||||
|
_clientId = clientId,
|
||||||
|
_fromServerTopic = fromServerTopic,
|
||||||
|
_toServerTopic = toServerTopic {
|
||||||
|
instance = this;
|
||||||
|
initMessages();
|
||||||
|
_setupMqtt();
|
||||||
|
}
|
||||||
|
|
||||||
ConnectionStateEnum getConnectionStatus() {
|
ConnectionStateEnum getConnectionStatus() {
|
||||||
return connectionState;
|
return connectionState;
|
||||||
}
|
}
|
||||||
@@ -185,15 +202,24 @@ class MqttCore with ProducerMixin, ConsumerMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _publishMessage(String topic, Map<String, dynamic> message, bool acknowledgeRequired, {bool enqueueOnError = true}) {
|
bool _publishMessage(String topic, Map<String, dynamic> message, bool acknowledgeRequired) {
|
||||||
try {
|
try {
|
||||||
String messageId = const Uuid().v4();
|
final messageId = const Uuid().v4();
|
||||||
String messageText = jsonEncode(message);
|
|
||||||
|
|
||||||
if (acknowledgeRequired) {
|
if (acknowledgeRequired) {
|
||||||
_enqueueMessage(messageId, topic, messageText);
|
// Vor dem ersten Versand speichern; erst die Serverbestätigung entfernt den Eintrag.
|
||||||
|
_enqueueMessage(messageId, topic, jsonEncode(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return _sendMessage(topic, message, acknowledgeRequired, messageId);
|
||||||
|
} catch (ex) {
|
||||||
|
Util.logInfo("send failed: $ex");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _sendMessage(String topic, Map<String, dynamic> message, bool acknowledgeRequired, String messageId) {
|
||||||
|
try {
|
||||||
var encapsulatedMessageText = _encapsulateMessage(messageId, acknowledgeRequired, message);
|
var encapsulatedMessageText = _encapsulateMessage(messageId, acknowledgeRequired, message);
|
||||||
|
|
||||||
var buffer = Uint8Buffer();
|
var buffer = Uint8Buffer();
|
||||||
@@ -761,7 +787,7 @@ class MqttCore with ProducerMixin, ConsumerMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _enqueueMessage(String uuid, String receiver, String payload) async {
|
void _enqueueMessage(String uuid, String receiver, String payload) {
|
||||||
var enqueuedMessage = EnqueuedMessage();
|
var enqueuedMessage = EnqueuedMessage();
|
||||||
enqueuedMessage.messageId = uuid;
|
enqueuedMessage.messageId = uuid;
|
||||||
enqueuedMessage.topic = receiver;
|
enqueuedMessage.topic = receiver;
|
||||||
@@ -780,9 +806,8 @@ class MqttCore with ProducerMixin, ConsumerMixin {
|
|||||||
try {
|
try {
|
||||||
var json = jsonDecode(enqueuedMessage.payload);
|
var json = jsonDecode(enqueuedMessage.payload);
|
||||||
|
|
||||||
if (_publishMessage(enqueuedMessage.topic, json, false, enqueueOnError: false)) {
|
// Wiederholungen behalten ihre ID und benötigen weiterhin eine Bestätigung.
|
||||||
box.remove(enqueuedMessage.id);
|
_sendMessage(enqueuedMessage.topic, json, true, enqueuedMessage.messageId);
|
||||||
}
|
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
Util.logInfo("resend enqueued message failed: $ex");
|
Util.logInfo("resend enqueued message failed: $ex");
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -489,22 +489,22 @@ bool isJobTooFarInFuture(Job job) {
|
|||||||
return ordertime > dateTimeThreshold;
|
return ordertime > dateTimeThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> taskDialogSecurityException(BuildContext context) async {
|
Future<bool> taskDialogSecurityException(BuildContext context, {AppLanguage? language}) async {
|
||||||
final result = await showDialog<bool>(
|
final result = await showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
builder: (dialogContext) {
|
builder: (dialogContext) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text('attention'.tr),
|
title: Text(AppLocalizations.tr('attention', language: language)),
|
||||||
content: Text('close_dialog_warning'.tr),
|
content: Text(AppLocalizations.tr('close_dialog_warning', language: language)),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(dialogContext).pop(true),
|
onPressed: () => Navigator.of(dialogContext).pop(true),
|
||||||
child: Text('close'.tr),
|
child: Text(AppLocalizations.tr('close', language: language)),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(dialogContext).pop(false),
|
onPressed: () => Navigator.of(dialogContext).pop(false),
|
||||||
child: Text('back'.tr),
|
child: Text(AppLocalizations.tr('back', language: language)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <audioplayers_linux/audioplayers_linux_plugin.h>
|
#include <audioplayers_linux/audioplayers_linux_plugin.h>
|
||||||
#include <file_selector_linux/file_selector_plugin.h>
|
#include <file_selector_linux/file_selector_plugin.h>
|
||||||
#include <objectbox_flutter_libs/objectbox_flutter_libs_plugin.h>
|
#include <objectbox_flutter_libs/objectbox_flutter_libs_plugin.h>
|
||||||
|
#include <permission_manager/permission_manager_plugin.h>
|
||||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||||
|
|
||||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
@@ -21,6 +22,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
|||||||
g_autoptr(FlPluginRegistrar) objectbox_flutter_libs_registrar =
|
g_autoptr(FlPluginRegistrar) objectbox_flutter_libs_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "ObjectboxFlutterLibsPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "ObjectboxFlutterLibsPlugin");
|
||||||
objectbox_flutter_libs_plugin_register_with_registrar(objectbox_flutter_libs_registrar);
|
objectbox_flutter_libs_plugin_register_with_registrar(objectbox_flutter_libs_registrar);
|
||||||
|
g_autoptr(FlPluginRegistrar) permission_manager_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "PermissionManagerPlugin");
|
||||||
|
permission_manager_plugin_register_with_registrar(permission_manager_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
audioplayers_linux
|
audioplayers_linux
|
||||||
file_selector_linux
|
file_selector_linux
|
||||||
objectbox_flutter_libs
|
objectbox_flutter_libs
|
||||||
|
permission_manager
|
||||||
url_launcher_linux
|
url_launcher_linux
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
pdfium_flutter
|
jni
|
||||||
)
|
)
|
||||||
|
|
||||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import file_selector_macos
|
|||||||
import geolocator_apple
|
import geolocator_apple
|
||||||
import objectbox_flutter_libs
|
import objectbox_flutter_libs
|
||||||
import package_info_plus
|
import package_info_plus
|
||||||
import path_provider_foundation
|
import pdfium_flutter
|
||||||
|
import permission_manager
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
import url_launcher_macos
|
import url_launcher_macos
|
||||||
|
|
||||||
@@ -22,7 +23,8 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
|
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
|
||||||
ObjectboxFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "ObjectboxFlutterLibsPlugin"))
|
ObjectboxFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "ObjectboxFlutterLibsPlugin"))
|
||||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PDFiumFlutterPlugin.register(with: registry.registrar(forPlugin: "PDFiumFlutterPlugin"))
|
||||||
|
PermissionManagerPlugin.register(with: registry.registrar(forPlugin: "PermissionManagerPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||||
}
|
}
|
||||||
+201
-153
@@ -29,10 +29,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: archive
|
name: archive
|
||||||
sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd"
|
sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.7"
|
version: "4.0.9"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -45,74 +45,74 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: async
|
name: async
|
||||||
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.13.0"
|
version: "2.13.1"
|
||||||
audioplayers:
|
audioplayers:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: audioplayers
|
name: audioplayers
|
||||||
sha256: "5441fa0ceb8807a5ad701199806510e56afde2b4913d9d17c2f19f2902cf0ae4"
|
sha256: "2ba4bb2944baacbdd5372ff8254a8e7feb8c10d7739545e392f5605a8f618745"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.5.1"
|
version: "6.8.1"
|
||||||
audioplayers_android:
|
audioplayers_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: audioplayers_android
|
name: audioplayers_android
|
||||||
sha256: "60a6728277228413a85755bd3ffd6fab98f6555608923813ce383b190a360605"
|
sha256: f5ff5b15620fbab8cb0849e9636c48e2b96c3f0f71723bbbe2ad3c761b205f05
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.2.1"
|
version: "5.3.0"
|
||||||
audioplayers_darwin:
|
audioplayers_darwin:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: audioplayers_darwin
|
name: audioplayers_darwin
|
||||||
sha256: "0811d6924904ca13f9ef90d19081e4a87f7297ddc19fc3d31f60af1aaafee333"
|
sha256: "1ca553add991384ecf421b9569da850f3ab2472ffb83f6970b0416365abc51be"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.0"
|
version: "6.5.0"
|
||||||
audioplayers_linux:
|
audioplayers_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: audioplayers_linux
|
name: audioplayers_linux
|
||||||
sha256: f75bce1ce864170ef5e6a2c6a61cd3339e1a17ce11e99a25bae4474ea491d001
|
sha256: "15178b726b7cdee5364d0463c8d445630c4e0fb7d26612b73c767e7d25de9417"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.1"
|
version: "4.3.0"
|
||||||
audioplayers_platform_interface:
|
audioplayers_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: audioplayers_platform_interface
|
name: audioplayers_platform_interface
|
||||||
sha256: "0e2f6a919ab56d0fec272e801abc07b26ae7f31980f912f24af4748763e5a656"
|
sha256: "765f6f0e6dca55cb471c9483fc77700564b3484d19198aca4ebb5147c6c85acb"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.1.1"
|
version: "7.2.0"
|
||||||
audioplayers_web:
|
audioplayers_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: audioplayers_web
|
name: audioplayers_web
|
||||||
sha256: "1c0f17cec68455556775f1e50ca85c40c05c714a99c5eb1d2d57cc17ba5522d7"
|
sha256: ae1e0103c865a03e273f6d13d97b93f5595eac09915729cd5e37ef96e2857319
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.1.1"
|
version: "5.3.0"
|
||||||
audioplayers_windows:
|
audioplayers_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: audioplayers_windows
|
name: audioplayers_windows
|
||||||
sha256: "4048797865105b26d47628e6abb49231ea5de84884160229251f37dfcbe52fd7"
|
sha256: a70ae82bba2dfcb6eb03dd4815d737a2d46d33ea5a96a03f535cfcaac490e413
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.1"
|
version: "4.4.1"
|
||||||
badges:
|
badges:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: badges
|
name: badges
|
||||||
sha256: a7b6bbd60dce418df0db3058b53f9d083c22cdb5132a052145dc267494df0b84
|
sha256: cf1c88fb3777df69ccd630b80de5267f54efa4a39381b0404a7c03d56cb7c041
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
version: "3.2.0"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -141,10 +141,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_daemon
|
name: build_daemon
|
||||||
sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957
|
sha256: fd754058c342243718d5171a95f352cfc9fcf0cba8cfa26df67cb13a5836db78
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.1"
|
version: "4.1.2"
|
||||||
build_resolvers:
|
build_resolvers:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -181,26 +181,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: built_value
|
name: built_value
|
||||||
sha256: "426cf75afdb23aa74bd4e471704de3f9393f3c7b04c1e2d9c6f1073ae0b8b139"
|
sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.12.1"
|
version: "8.12.6"
|
||||||
carousel_slider:
|
carousel_slider:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: carousel_slider
|
name: carousel_slider
|
||||||
sha256: bcc61735345c9ab5cb81073896579e735f81e35fd588907a393143ea986be8ff
|
sha256: febf4b0163e0242adc13d7a863b04965351f59e7dfea56675c7c2caa7bcd7476
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.1.1"
|
version: "5.1.2"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: characters
|
name: characters
|
||||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.1"
|
||||||
charcode:
|
charcode:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -233,6 +233,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
version: "1.1.2"
|
||||||
|
code_assets:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: code_assets
|
||||||
|
sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
code_builder:
|
code_builder:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -261,10 +269,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: cross_file
|
name: cross_file
|
||||||
sha256: "701dcfc06da0882883a2657c445103380e53e647060ad8d9dfb710c100996608"
|
sha256: "92c9c43c383bfa1c32079d3bc492d55d6d4318044b7b47edaff8971cbb555c51"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.5+1"
|
version: "0.3.5+4"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -285,10 +293,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: cupertino_icons
|
name: cupertino_icons
|
||||||
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.8"
|
version: "1.0.9"
|
||||||
dart_earcut:
|
dart_earcut:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -349,26 +357,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dio
|
name: dio
|
||||||
sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
|
sha256: ea2bad3c89a27635ce2d85cce4d6b199da49a5a48ec77b03e45b65a3b90922b0
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.9.0"
|
version: "5.10.0"
|
||||||
dio_web_adapter:
|
dio_web_adapter:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dio_web_adapter
|
name: dio_web_adapter
|
||||||
sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
|
sha256: dd58dc3861eb36edb13b217efc006a1c21e5bbc341de8c229b85634fa5e362e4
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.2.0"
|
||||||
equatable:
|
equatable:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: equatable
|
name: equatable
|
||||||
sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b"
|
sha256: "3bce007a596ff8b3119c45d68aaef631272537c03d30e5d4534dd24bf4c5eaa2"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.8"
|
version: "2.1.0"
|
||||||
event_bus:
|
event_bus:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -397,10 +405,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: ffi
|
name: ffi
|
||||||
sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c
|
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.5"
|
version: "2.2.0"
|
||||||
file:
|
file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -450,7 +458,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.1"
|
||||||
flat_buffers:
|
flat_buffers:
|
||||||
dependency: transitive
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: flat_buffers
|
name: flat_buffers
|
||||||
sha256: "380bdcba5664a718bfd4ea20a45d39e13684f5318fcd8883066a55e21f37f4c3"
|
sha256: "380bdcba5664a718bfd4ea20a45d39e13684f5318fcd8883066a55e21f37f4c3"
|
||||||
@@ -514,34 +522,34 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_map
|
name: flutter_map
|
||||||
sha256: "391e7dc95cc3f5190748210a69d4cfeb5d8f84dcdfa9c3235d0a9d7742ccb3f8"
|
sha256: ce1debbb29cade61964334b2a19c7c76e5bb2ef7dacf126c6424ee44036232f8
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.2.2"
|
version: "8.3.1"
|
||||||
flutter_osm_interface:
|
flutter_osm_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_osm_interface
|
name: flutter_osm_interface
|
||||||
sha256: fe7b02fee205aec861d0f08f643d4d6c49a5b9df06d3b43919fdf6699a275829
|
sha256: "98cc8855f3d94e1c953b72c31ae594221d54a5ef1b5a6689f8c0df2f613fe356"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.5.0"
|
||||||
flutter_osm_plugin:
|
flutter_osm_plugin:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_osm_plugin
|
name: flutter_osm_plugin
|
||||||
sha256: a35b76082818d457c750eaf071d9dbbbb49ee3a0092be6bf7ddcd0c345396b86
|
sha256: "2c2be585a23f9e213993e82765b66fe04c1bf1e7f337ee11ff4b8ee5fb5cd2dd"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.8"
|
version: "1.4.5"
|
||||||
flutter_osm_web:
|
flutter_osm_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_osm_web
|
name: flutter_osm_web
|
||||||
sha256: "8542dc76aee29479b75fc79861ac748cd0a58482b71f0dfd116af6306e377300"
|
sha256: "4b457aed05915a4af820b74e8bd387a8d1b562b538b4cac14f8fadee9fffa80e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.5"
|
version: "1.4.4"
|
||||||
flutter_parsed_text:
|
flutter_parsed_text:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -554,18 +562,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_plugin_android_lifecycle
|
name: flutter_plugin_android_lifecycle
|
||||||
sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1
|
sha256: "3854fe5e3bff0b113c658f260b90c95dea17c92db0f2addeac2e343dd9969785"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.33"
|
version: "2.0.35"
|
||||||
flutter_svg:
|
flutter_svg:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_svg
|
name: flutter_svg
|
||||||
sha256: "87fbd7c534435b6c5d9d98b01e1fd527812b82e68ddd8bd35fc45ed0fa8f0a95"
|
sha256: "35882981abcbfb8c15b286f0cd690ff25bac12d95eff3e25ee207f37d4c42e7f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.3"
|
version: "2.3.0"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -604,26 +612,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: geolocator_apple
|
name: geolocator_apple
|
||||||
sha256: dbdd8789d5aaf14cf69f74d4925ad1336b4433a6efdf2fce91e8955dc921bf22
|
sha256: "853803d6bb1713c094e935b4a5ae5f19c0308acf81da13fa9ff84fb4c70c0b73"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.13"
|
version: "2.3.14"
|
||||||
geolocator_platform_interface:
|
geolocator_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: geolocator_platform_interface
|
name: geolocator_platform_interface
|
||||||
sha256: "30cb64f0b9adcc0fb36f628b4ebf4f731a2961a0ebd849f4b56200205056fe67"
|
sha256: cdb082e4f048b69da244117b7914cc60d2a8897546ffaa4f2529c786ded7aee2
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.6"
|
version: "4.2.8"
|
||||||
geolocator_web:
|
geolocator_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: geolocator_web
|
name: geolocator_web
|
||||||
sha256: b1ae9bdfd90f861fde8fd4f209c37b953d65e92823cb73c7dee1fa021b06f172
|
sha256: "19e485a0f8d6a88abcf9c53cba3a4105e14b7435ed8ac1c108c067b938fe8429"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.3"
|
version: "4.1.4"
|
||||||
geolocator_windows:
|
geolocator_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -656,6 +664,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.3.2"
|
||||||
|
hooks:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: hooks
|
||||||
|
sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
html:
|
html:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -692,26 +708,26 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: image
|
name: image
|
||||||
sha256: "492bd52f6c4fbb6ee41f781ff27765ce5f627910e1e0cbecfa3d9add5562604c"
|
sha256: "6300175e00616bbc832e2fc91bfa4d776af5402c81c7151bee6905bb08473c52"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.7.2"
|
version: "4.9.1"
|
||||||
image_picker:
|
image_picker:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: image_picker
|
name: image_picker
|
||||||
sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320"
|
sha256: d8402284df184bc05f4a2210c6c23983b0720f4cd87cbd05c5390a78af602667
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.1"
|
version: "1.2.3"
|
||||||
image_picker_android:
|
image_picker_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_android
|
name: image_picker_android
|
||||||
sha256: "5e9bf126c37c117cf8094215373c6d561117a3cfb50ebc5add1a61dc6e224677"
|
sha256: "6f3a1995eafb000333174fae92202622033b0ee7fd917a6cd3730295264df84a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.13+10"
|
version: "0.8.13+19"
|
||||||
image_picker_for_web:
|
image_picker_for_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -724,10 +740,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_ios
|
name: image_picker_ios
|
||||||
sha256: "956c16a42c0c708f914021666ffcd8265dde36e673c9fa68c81f7d085d9774ad"
|
sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.13+3"
|
version: "0.8.13+6"
|
||||||
image_picker_linux:
|
image_picker_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -764,10 +780,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: intl
|
name: intl
|
||||||
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
sha256: "1ca20c894b1717686a2319b8548763d812bc0aabdac580420a44c5178c57a867"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.20.2"
|
version: "0.20.3"
|
||||||
io:
|
io:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -776,6 +792,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.5"
|
version: "1.0.5"
|
||||||
|
jni:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: jni
|
||||||
|
sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
|
jni_flutter:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: jni_flutter
|
||||||
|
sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
js:
|
js:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -848,22 +880,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.1.1"
|
version: "5.1.1"
|
||||||
lists:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: lists
|
|
||||||
sha256: "4ca5c19ae4350de036a7e996cdd1ee39c93ac0a2b840f4915459b7d0a7d4ab27"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.1"
|
|
||||||
logger:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: logger
|
|
||||||
sha256: a7967e31b703831a893bbc3c3dd11db08126fe5f369b5c648a36f821979f5be3
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.6.2"
|
|
||||||
logging:
|
logging:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -876,34 +892,34 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.17"
|
version: "0.12.19"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.11.1"
|
version: "0.13.0"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.17.0"
|
version: "1.18.0"
|
||||||
mgrs_dart:
|
mgrs_dart:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: mgrs_dart
|
name: mgrs_dart
|
||||||
sha256: fb89ae62f05fa0bb90f70c31fc870bcbcfd516c843fb554452ab3396f78586f7
|
sha256: "385e7168ecc77eb545220223c49eef8ab249da7bf57f22781c40a04d23fb196f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "3.0.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -916,10 +932,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: mqtt5_client
|
name: mqtt5_client
|
||||||
sha256: "3cbb546019d455330494357c922b5c7758c622f0e6f3955146dd2712fa8f7f7a"
|
sha256: a716e6bddc4715cd585ac7147383080dffafb6b7d55f370dc596b38c85e8c557
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.15.2"
|
version: "4.17.1"
|
||||||
objectbox:
|
objectbox:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -944,6 +960,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.0"
|
version: "4.1.0"
|
||||||
|
objective_c:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: objective_c
|
||||||
|
sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "9.4.1"
|
||||||
package_config:
|
package_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -988,42 +1012,42 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path_provider
|
name: path_provider
|
||||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.5"
|
version: "2.1.6"
|
||||||
path_provider_android:
|
path_provider_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_android
|
name: path_provider_android
|
||||||
sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e
|
sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.22"
|
version: "2.3.1"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_foundation
|
name: path_provider_foundation
|
||||||
sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4"
|
sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.1"
|
version: "2.6.0"
|
||||||
path_provider_linux:
|
path_provider_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_linux
|
name: path_provider_linux
|
||||||
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.2.2"
|
||||||
path_provider_platform_interface:
|
path_provider_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_platform_interface
|
name: path_provider_platform_interface
|
||||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.3"
|
||||||
path_provider_windows:
|
path_provider_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1036,34 +1060,34 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: pdfium_dart
|
name: pdfium_dart
|
||||||
sha256: f1683b9070ddc5c9189c6ee008c285791da66328ce1b882c7162d3393f3a4a74
|
sha256: "86e95c66b09f3245b95c4924f2edce8d4f7c9786876e5c5ee8e36b104a94bbb0"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.3"
|
version: "0.2.5"
|
||||||
pdfium_flutter:
|
pdfium_flutter:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: pdfium_flutter
|
name: pdfium_flutter
|
||||||
sha256: "0c8b7d5d11d20a1486eade599648e907067568955bd14a1b06de076a968b60a1"
|
sha256: "0b115c0917aef9cf6bb9c4d47d0efc61d97f44844cd76287c921f997c26cf15d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.9"
|
version: "0.2.3"
|
||||||
pdfrx:
|
pdfrx:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: pdfrx
|
name: pdfrx
|
||||||
sha256: "82e719f7e7994cb78344a1d3c0e204ffdf86ddf9364ff495368acdae6e3f489f"
|
sha256: acce61944c31bd36f0c3031b1bbf41f59d0b0d4e5e27614fbdc74ff093d55464
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.20"
|
version: "2.4.7"
|
||||||
pdfrx_engine:
|
pdfrx_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: pdfrx_engine
|
name: pdfrx_engine
|
||||||
sha256: a28e9d4436dcb9a659b50f71ca1eed72f30b5c45340a9b68299fa904e735128c
|
sha256: ef8f8cfa64255bfc91a559dd2d32d7cfe7a0f8003bac297bbf9a0aed1182fae5
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.7"
|
version: "0.4.6"
|
||||||
permission_handler:
|
permission_handler:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1084,10 +1108,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_apple
|
name: permission_handler_apple
|
||||||
sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023
|
sha256: "79dfa1df734798aa3cfdad166d3a3698c206d8813de13516ea1071b5d7e2f420"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.4.7"
|
version: "9.4.10"
|
||||||
permission_handler_html:
|
permission_handler_html:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1112,14 +1136,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.1"
|
version: "0.2.1"
|
||||||
|
permission_manager:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_manager
|
||||||
|
sha256: c3ad9951dd23fa713559a6dde3974421273b53cf62fe0088d2c860260fa03623
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.9"
|
||||||
petitparser:
|
petitparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: petitparser
|
name: petitparser
|
||||||
sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1"
|
sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.1"
|
version: "7.0.2"
|
||||||
photo_view:
|
photo_view:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1164,18 +1196,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: posix
|
name: posix
|
||||||
sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61"
|
sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.3"
|
version: "6.5.0"
|
||||||
proj4dart:
|
proj4dart:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: proj4dart
|
name: proj4dart
|
||||||
sha256: c8a659ac9b6864aa47c171e78d41bbe6f5e1d7bd790a5814249e6b68bc44324e
|
sha256: ddcedc1f7876e62717de43ab3491e2829bdad0b028261805f94aa080967e5859
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "3.0.0"
|
||||||
pub_semver:
|
pub_semver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1192,6 +1224,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.0"
|
version: "1.5.0"
|
||||||
|
record_use:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: record_use
|
||||||
|
sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.0"
|
||||||
routing_client_dart:
|
routing_client_dart:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1228,18 +1268,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: shared_preferences
|
name: shared_preferences
|
||||||
sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64"
|
sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.4"
|
version: "2.5.5"
|
||||||
shared_preferences_android:
|
shared_preferences_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_android
|
name: shared_preferences_android
|
||||||
sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc"
|
sha256: "0634e64bd719f89c012f392938e173521f535d3ecaf66558fa94a056d22b5cc7"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.18"
|
version: "2.4.27"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1260,10 +1300,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_platform_interface
|
name: shared_preferences_platform_interface
|
||||||
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
|
sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.1"
|
version: "2.4.2"
|
||||||
shared_preferences_web:
|
shared_preferences_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1304,6 +1344,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.0"
|
version: "6.3.0"
|
||||||
|
simple_sparse_list:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: simple_sparse_list
|
||||||
|
sha256: aa648fd240fa39b49dcd11c19c266990006006de6699a412de485695910fbc1f
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.4"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -1329,10 +1377,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_span
|
name: source_span
|
||||||
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.1"
|
version: "1.10.2"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1369,10 +1417,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: synchronized
|
name: synchronized
|
||||||
sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0
|
sha256: "61894a1956de6b4fc1aefd0892e109514a1a706cbece3ac59decd90ff5a7a423"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.4.0"
|
version: "3.4.1+1"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1385,10 +1433,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
|
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.7"
|
version: "0.7.11"
|
||||||
timing:
|
timing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1409,10 +1457,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: unicode
|
name: unicode
|
||||||
sha256: "0f69e46593d65245774d4f17125c6084d2c20b4e473a983f6e21b7d7762218f1"
|
sha256: a6f7bcfc8ea1d5ce1f6c0b1c39117a9919f4953edd9fd7a64090a9796c499b57
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.1"
|
version: "1.1.9"
|
||||||
universal_html:
|
universal_html:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1441,18 +1489,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_android
|
name: url_launcher_android
|
||||||
sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611"
|
sha256: b413d49b73867ac08dd2f9890efd3cc11f2a0e577618d50843440a1fb3776c32
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.28"
|
version: "6.3.32"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_ios
|
name: url_launcher_ios
|
||||||
sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad
|
sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.6"
|
version: "6.4.1"
|
||||||
url_launcher_linux:
|
url_launcher_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1481,10 +1529,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_web
|
name: url_launcher_web
|
||||||
sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
|
sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.1"
|
version: "2.4.3"
|
||||||
url_launcher_windows:
|
url_launcher_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1497,18 +1545,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: uuid
|
name: uuid
|
||||||
sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
|
sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.5.2"
|
version: "4.6.0"
|
||||||
vector_graphics:
|
vector_graphics:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vector_graphics
|
name: vector_graphics
|
||||||
sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6
|
sha256: "2306c03da2ba81724afeb589c351ebbc0aa7d86005925be8f8735856dbe5e42d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.19"
|
version: "1.2.2"
|
||||||
vector_graphics_codec:
|
vector_graphics_codec:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1521,10 +1569,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vector_graphics_compiler
|
name: vector_graphics_compiler
|
||||||
sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc
|
sha256: "142a9146f447d15b10bdc00e21d5f4d83e5b32bb5f8f8f5a04c75311344923a3"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.19"
|
version: "1.2.6"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1545,10 +1593,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
|
sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "15.0.2"
|
version: "15.2.0"
|
||||||
watcher:
|
watcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1617,10 +1665,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: xml
|
name: xml
|
||||||
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
|
sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.6.1"
|
version: "7.0.1"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1630,5 +1678,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.9.0 <4.0.0"
|
dart: ">=3.12.0 <4.0.0"
|
||||||
flutter: ">=3.35.1"
|
flutter: ">=3.44.0"
|
||||||
+2
-1
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 354.0.0+354
|
version: 358.0.0+358
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.9.0 <4.0.0'
|
sdk: '>=3.9.0 <4.0.0'
|
||||||
@@ -72,6 +72,7 @@ dependencies:
|
|||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
flat_buffers: ^23.5.26
|
||||||
build_runner: ^2.4.13
|
build_runner: ^2.4.13
|
||||||
objectbox_generator: ^4.0.3
|
objectbox_generator: ^4.0.3
|
||||||
json_serializable: ^6.6.2
|
json_serializable: ^6.6.2
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import 'package:flat_buffers/flat_buffers.dart' as fb;
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:objectbox/internal.dart' show EntityDefinition;
|
||||||
|
import 'package:stadtbote/model/chat_message.dart';
|
||||||
|
import 'package:stadtbote/objectbox.g.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('ChatMessage IDs', () {
|
||||||
|
test('initialisiert ausgehende Nachrichten vollständig', () {
|
||||||
|
final message = _outgoingMessage();
|
||||||
|
|
||||||
|
expect(message.id, 0);
|
||||||
|
expect(message.toJson(), {
|
||||||
|
'id': 42,
|
||||||
|
'time': 1791366832,
|
||||||
|
'from': 'courier-123',
|
||||||
|
'subj': '',
|
||||||
|
'text': 'Auftrag erledigt',
|
||||||
|
'read': true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('serialisiert ausgehende Nachrichten für ObjectBox ohne Fehler', () {
|
||||||
|
final message = _outgoingMessage();
|
||||||
|
final definition = getObjectBoxModel().bindings[ChatMessage]!
|
||||||
|
as EntityDefinition<ChatMessage>;
|
||||||
|
final builder = fb.Builder();
|
||||||
|
|
||||||
|
expect(definition.objectToFB(message, builder), 0);
|
||||||
|
|
||||||
|
final buffer = fb.BufferContext.fromBytes(builder.buffer);
|
||||||
|
final root = buffer.derefObject(0);
|
||||||
|
const intReader = fb.Int64Reader();
|
||||||
|
const stringReader = fb.StringReader();
|
||||||
|
|
||||||
|
expect(intReader.vTableGet(buffer, root, 4, -1), 0);
|
||||||
|
expect(intReader.vTableGet(buffer, root, 6, -1), 42);
|
||||||
|
expect(intReader.vTableGet(buffer, root, 8, -1), 1791366832);
|
||||||
|
expect(stringReader.vTableGet(buffer, root, 10, ''), 'courier-123');
|
||||||
|
expect(stringReader.vTableGet(buffer, root, 12, 'missing'), '');
|
||||||
|
expect(stringReader.vTableGet(buffer, root, 14, ''), 'Auftrag erledigt');
|
||||||
|
expect(const fb.BoolReader().vTableGet(buffer, root, 16, false), isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('verwendet nach lokaler ID-Vergabe weiterhin die Nachrichten-ID', () {
|
||||||
|
final message = _outgoingMessage();
|
||||||
|
final definition = getObjectBoxModel().bindings[ChatMessage]!
|
||||||
|
as EntityDefinition<ChatMessage>;
|
||||||
|
|
||||||
|
definition.setId(message, 99);
|
||||||
|
|
||||||
|
expect(message.id, 99);
|
||||||
|
expect(message.externalId, 42);
|
||||||
|
expect(message.toJson()['id'], 42);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('übernimmt empfangene Server-IDs getrennt von der lokalen ID', () {
|
||||||
|
final message = ChatMessage.fromJson({
|
||||||
|
'id': 21891302,
|
||||||
|
'time': 1791366832,
|
||||||
|
'from': 'Zentrale',
|
||||||
|
'subj': 'Rückfrage',
|
||||||
|
'text': 'Bitte melden',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(message.id, 0);
|
||||||
|
expect(message.externalId, 21891302);
|
||||||
|
expect(message.read, isFalse);
|
||||||
|
expect(message.toJson(), {
|
||||||
|
'id': 21891302,
|
||||||
|
'time': 1791366832,
|
||||||
|
'from': 'Zentrale',
|
||||||
|
'subj': 'Rückfrage',
|
||||||
|
'text': 'Bitte melden',
|
||||||
|
'read': false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatMessage _outgoingMessage() => ChatMessage.outgoing(
|
||||||
|
externalId: 42,
|
||||||
|
time: 1791366832,
|
||||||
|
from: 'courier-123',
|
||||||
|
text: 'Auftrag erledigt',
|
||||||
|
);
|
||||||
@@ -0,0 +1,317 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:archive/archive.dart';
|
||||||
|
import 'package:dart_mq/dart_mq.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:mqtt5_client/mqtt5_client.dart';
|
||||||
|
import 'package:mqtt5_client/mqtt5_server_client.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:stadtbote/main.dart';
|
||||||
|
import 'package:stadtbote/model/enqueued_message.dart';
|
||||||
|
import 'package:stadtbote/model/job.dart';
|
||||||
|
import 'package:stadtbote/model/log_entry.dart';
|
||||||
|
import 'package:stadtbote/objectbox.g.dart';
|
||||||
|
import 'package:stadtbote/tools/constants.dart';
|
||||||
|
import 'package:stadtbote/tools/message_header.dart';
|
||||||
|
import 'package:stadtbote/tools/messages/publish_payload_message.dart';
|
||||||
|
import 'package:stadtbote/tools/mqtt_core.dart';
|
||||||
|
import 'package:stadtbote/tools/util.dart';
|
||||||
|
// ignore: depend_on_referenced_packages
|
||||||
|
import 'package:typed_data/typed_buffers.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
late _MessageStore store;
|
||||||
|
late _MqttClient client;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
SharedPreferences.setMockInitialValues({});
|
||||||
|
await Util.init();
|
||||||
|
MQClient.initialize();
|
||||||
|
for (final topic in [
|
||||||
|
Constants.messageTopicPublishPayload,
|
||||||
|
Constants.messageTopicDisconnectClient,
|
||||||
|
Constants.messageTopicConnectClient,
|
||||||
|
Constants.messageTopicRequestConnectionStatus,
|
||||||
|
Constants.messageTopicConnectionStatusChanged,
|
||||||
|
]) {
|
||||||
|
MQClient.instance.declareQueue(topic);
|
||||||
|
}
|
||||||
|
store = _MessageStore();
|
||||||
|
MyApp.objectBoxStore = store;
|
||||||
|
client = _MqttClient();
|
||||||
|
MqttCore.withClient(
|
||||||
|
client: client,
|
||||||
|
clientId: 'test-device',
|
||||||
|
fromServerTopic: 'server/a/b/c/d/e/',
|
||||||
|
toServerTopic: 'client/a/b/c/d/e/',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() async {
|
||||||
|
await client.incoming.close();
|
||||||
|
MQClient.instance.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> publish(WidgetTester tester, {bool requiresAck = true}) async {
|
||||||
|
MQClient.instance.sendMessage(
|
||||||
|
routingKey: Constants.messageTopicPublishPayload,
|
||||||
|
message: Message(
|
||||||
|
payload: PublishPayloadMessage(
|
||||||
|
topic: 'mssg',
|
||||||
|
message: {'text': 'Auftrag erledigt'},
|
||||||
|
acknowledgeRequired: requiresAck,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> retry(WidgetTester tester) async {
|
||||||
|
client.receive('sync_request', {});
|
||||||
|
await tester.pump();
|
||||||
|
}
|
||||||
|
|
||||||
|
testWidgets('behält ID, Inhalt und Bestätigungspflicht bei Wiederholungen', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
// Auch beim ersten Sendeversuch muss die Nachricht bereits gespeichert sein.
|
||||||
|
List<EnqueuedMessage>? queuedAtFirstSend;
|
||||||
|
client.onPublish = () => queuedAtFirstSend = store.messages.getAll();
|
||||||
|
await publish(tester);
|
||||||
|
client.onPublish = null;
|
||||||
|
final original = client.chatMessages.single;
|
||||||
|
final queued = store.messages.rows.single;
|
||||||
|
expect(queuedAtFirstSend, hasLength(1));
|
||||||
|
expect(queuedAtFirstSend!.single.messageId, original.messageId);
|
||||||
|
expect(queued.messageId, original.messageId);
|
||||||
|
expect(jsonDecode(queued.payload), original.payload);
|
||||||
|
|
||||||
|
await retry(tester);
|
||||||
|
await retry(tester);
|
||||||
|
|
||||||
|
expect(client.chatMessages, hasLength(3));
|
||||||
|
for (final message in client.chatMessages) {
|
||||||
|
expect(message.toJson(), original.toJson());
|
||||||
|
expect(message.requiresAck, isTrue);
|
||||||
|
}
|
||||||
|
expect(store.messages.rows.single.id, queued.id);
|
||||||
|
expect(store.messages.putCount, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('sendet bestätigte Nachrichten nicht erneut', (tester) async {
|
||||||
|
await publish(tester);
|
||||||
|
final messageId = client.chatMessages.single.messageId;
|
||||||
|
await retry(tester);
|
||||||
|
expect(store.messages.rows, hasLength(1));
|
||||||
|
|
||||||
|
client.receive('acknowledge', {'messageId': messageId});
|
||||||
|
await tester.pump();
|
||||||
|
expect(store.messages.rows, isEmpty);
|
||||||
|
|
||||||
|
await retry(tester);
|
||||||
|
expect(client.chatMessages, hasLength(2));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('behält unbestätigte Nachrichten nach fehlgeschlagenem Versand', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
client.failPublish = true;
|
||||||
|
await publish(tester);
|
||||||
|
final messageId = store.messages.rows.single.messageId;
|
||||||
|
await retry(tester);
|
||||||
|
expect(store.messages.rows.single.messageId, messageId);
|
||||||
|
expect(client.chatMessages, isEmpty);
|
||||||
|
|
||||||
|
client.failPublish = false;
|
||||||
|
await retry(tester);
|
||||||
|
expect(client.chatMessages.single.messageId, messageId);
|
||||||
|
expect(client.chatMessages.single.requiresAck, isTrue);
|
||||||
|
expect(store.messages.rows, hasLength(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('verwendet die ID bereits gespeicherter Nachrichten', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
store.messages.put(
|
||||||
|
EnqueuedMessage()
|
||||||
|
..messageId = 'persisted-message-id'
|
||||||
|
..topic = 'mssg'
|
||||||
|
..payload = '{"text":"Vor dem Neustart gespeichert"}',
|
||||||
|
);
|
||||||
|
|
||||||
|
await retry(tester);
|
||||||
|
|
||||||
|
final message = client.chatMessages.single;
|
||||||
|
expect(message.messageId, 'persisted-message-id');
|
||||||
|
expect(message.requiresAck, isTrue);
|
||||||
|
expect(message.payload['text'], 'Vor dem Neustart gespeichert');
|
||||||
|
expect(store.messages.rows, hasLength(1));
|
||||||
|
expect(store.messages.putCount, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('wiederholt Nachrichten ohne Bestätigungspflicht nicht', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
await publish(tester, requiresAck: false);
|
||||||
|
await retry(tester);
|
||||||
|
|
||||||
|
expect(client.chatMessages, hasLength(1));
|
||||||
|
expect(client.chatMessages.single.requiresAck, isFalse);
|
||||||
|
expect(store.messages.rows, isEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'vergibt für neue Nachrichten auch bei gleichem Inhalt eigene IDs',
|
||||||
|
(tester) async {
|
||||||
|
await publish(tester);
|
||||||
|
await publish(tester);
|
||||||
|
|
||||||
|
final ids = client.chatMessages
|
||||||
|
.map((message) => message.messageId)
|
||||||
|
.toList();
|
||||||
|
expect(ids.toSet(), hasLength(2));
|
||||||
|
await retry(tester);
|
||||||
|
expect(client.chatMessages.map((message) => message.messageId), [
|
||||||
|
...ids,
|
||||||
|
...ids,
|
||||||
|
]);
|
||||||
|
expect(store.messages.rows, hasLength(2));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets('sendet bei fehlgeschlagener Speicherung keine neue Nachricht', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
store.messages.failPut = true;
|
||||||
|
await publish(tester);
|
||||||
|
|
||||||
|
expect(client.chatMessages, isEmpty);
|
||||||
|
expect(store.messages.rows, isEmpty);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MqttClient extends Fake implements MqttServerClient {
|
||||||
|
final incoming =
|
||||||
|
StreamController<List<MqttReceivedMessage<MqttMessage>>>.broadcast();
|
||||||
|
final chatMessages = <MessageHeader>[];
|
||||||
|
bool failPublish = false;
|
||||||
|
void Function()? onPublish;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<List<MqttReceivedMessage<MqttMessage>>> get updates => incoming.stream;
|
||||||
|
|
||||||
|
@override
|
||||||
|
MqttSubscription? subscribe(String topic, MqttQos qosLevel) => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int publishMessage(
|
||||||
|
String topic,
|
||||||
|
MqttQos qualityOfService,
|
||||||
|
Uint8Buffer data, {
|
||||||
|
bool retain = false,
|
||||||
|
List<MqttUserProperty>? userProperties,
|
||||||
|
}) {
|
||||||
|
onPublish?.call();
|
||||||
|
if (failPublish) throw StateError('Keine Verbindung');
|
||||||
|
if (topic.endsWith('/mssg')) {
|
||||||
|
chatMessages.add(
|
||||||
|
MessageHeader.fromJson(
|
||||||
|
jsonDecode(utf8.decode(GZipDecoder().decodeBytes(data))),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void receive(String topic, Map<String, dynamic> payload) {
|
||||||
|
final data = Uint8Buffer()
|
||||||
|
..addAll(
|
||||||
|
GZipEncoder().encode(
|
||||||
|
utf8.encode(
|
||||||
|
jsonEncode({
|
||||||
|
'messageId': 'server-message-id',
|
||||||
|
'requiresAck': false,
|
||||||
|
'payload': payload,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final message = MqttPublishMessage()
|
||||||
|
.toTopic('server/a/b/c/d/e/test-device/$topic')
|
||||||
|
.publishData(data);
|
||||||
|
incoming.add([MqttReceivedMessage(topic, message)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MessageStore extends Fake implements Store {
|
||||||
|
final messages = _MessageBox();
|
||||||
|
final logs = _MemoryBox<LogEntry>();
|
||||||
|
final jobs = _MemoryBox<Job>();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Box<T> box<T>() {
|
||||||
|
if (T == EnqueuedMessage) return messages as Box<T>;
|
||||||
|
if (T == LogEntry) return logs as Box<T>;
|
||||||
|
if (T == Job) return jobs as Box<T>;
|
||||||
|
throw StateError('Unerwarteter Entity-Typ: $T');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MemoryBox<T> extends Fake implements Box<T> {
|
||||||
|
final rows = <T>[];
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<T> getAll() => List.of(rows);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int put(T object, {PutMode mode = PutMode.put}) => 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
QueryBuilder<T> query([Condition<T>? qc]) => _MemoryQueryBuilder(rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MessageBox extends _MemoryBox<EnqueuedMessage> {
|
||||||
|
int putCount = 0;
|
||||||
|
bool failPut = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int put(EnqueuedMessage object, {PutMode mode = PutMode.put}) {
|
||||||
|
if (failPut) throw StateError('Speicherung fehlgeschlagen');
|
||||||
|
object.id = ++putCount;
|
||||||
|
rows.add(object);
|
||||||
|
return object.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int removeMany(List<int> ids) {
|
||||||
|
final count = rows.length;
|
||||||
|
rows.removeWhere((row) => ids.contains(row.id));
|
||||||
|
return count - rows.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Die nativen Datenbankzugriffe sind ersetzt. Im Bestätigungstest enthält
|
||||||
|
// die Warteschlange genau die eine zum ACK passende Nachricht.
|
||||||
|
class _MemoryQueryBuilder<T> extends Fake implements QueryBuilder<T> {
|
||||||
|
final List<T> rows;
|
||||||
|
|
||||||
|
_MemoryQueryBuilder(this.rows);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Query<T> build() => _MemoryQuery(rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MemoryQuery<T> extends Fake implements Query<T> {
|
||||||
|
final List<T> rows;
|
||||||
|
|
||||||
|
_MemoryQuery(this.rows);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<T> find() => List.of(rows);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void close() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:dart_mq/dart_mq.dart';
|
||||||
|
import 'package:flat_buffers/flat_buffers.dart' as fb;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:objectbox/internal.dart' show EntityDefinition;
|
||||||
|
import 'package:stadtbote/content_views/messages_page.dart';
|
||||||
|
import 'package:stadtbote/main.dart';
|
||||||
|
import 'package:stadtbote/model/chat_message.dart';
|
||||||
|
import 'package:stadtbote/model/session_status.dart';
|
||||||
|
import 'package:stadtbote/objectbox.g.dart';
|
||||||
|
import 'package:stadtbote/tools/constants.dart';
|
||||||
|
import 'package:stadtbote/tools/messages/publish_payload_message.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
setUp(() {
|
||||||
|
MQClient.initialize();
|
||||||
|
MQClient.instance.declareQueue(Constants.messageTopicPublishPayload);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() {
|
||||||
|
MQClient.instance.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('bestätigt die Server-ID auch nach der lokalen ID-Vergabe', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
final message = ChatMessage.fromJson({
|
||||||
|
'id': 6094830,
|
||||||
|
'time': 1791536566,
|
||||||
|
'from': 'NL',
|
||||||
|
'subj': 'Test1',
|
||||||
|
'text': 'Test2',
|
||||||
|
});
|
||||||
|
final store = _MessageStore();
|
||||||
|
final definition =
|
||||||
|
getObjectBoxModel().bindings[ChatMessage]!
|
||||||
|
as EntityDefinition<ChatMessage>;
|
||||||
|
|
||||||
|
// Die nativen Datenbankzugriffe werden ersetzt; die generierte
|
||||||
|
// ObjectBox-Serialisierung und der Nachrichtenversand laufen unverändert.
|
||||||
|
definition.setId(message, 2);
|
||||||
|
final builder = fb.Builder();
|
||||||
|
definition.objectToFB(message, builder);
|
||||||
|
final restored = definition.objectFromFB(
|
||||||
|
store,
|
||||||
|
ByteData.sublistView(builder.buffer),
|
||||||
|
);
|
||||||
|
expect(restored.id, 2);
|
||||||
|
expect(restored.externalId, 6094830);
|
||||||
|
expect(restored.read, isFalse);
|
||||||
|
|
||||||
|
store.messages = _MessageBox(restored);
|
||||||
|
MyApp.objectBoxStore = store;
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const MaterialApp(home: Scaffold(body: MessagesPage())),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
final published =
|
||||||
|
MQClient.instance
|
||||||
|
.getLatestMessage(Constants.messageTopicPublishPayload)!
|
||||||
|
.payload
|
||||||
|
as PublishPayloadMessage;
|
||||||
|
expect(published.topic, 'mssg_received');
|
||||||
|
expect(published.acknowledgeRequired, isFalse);
|
||||||
|
expect(jsonDecode(jsonEncode(published.message)), {
|
||||||
|
'ids': ['6094830'],
|
||||||
|
'time': isA<int>(),
|
||||||
|
});
|
||||||
|
expect(restored.read, isTrue);
|
||||||
|
expect(find.text('Test1\nTest2', findRichText: true), findsOneWidget);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MessageStore extends Fake implements Store {
|
||||||
|
late final _MessageBox messages;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Box<T> box<T>() {
|
||||||
|
if (T == ChatMessage) return messages as Box<T>;
|
||||||
|
if (T == SessionStatus) return _SessionBox() as Box<T>;
|
||||||
|
throw StateError('Unerwarteter Entity-Typ: $T');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SessionBox extends Fake implements Box<SessionStatus> {
|
||||||
|
@override
|
||||||
|
List<SessionStatus> getAll() => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MessageBox extends Fake implements Box<ChatMessage> {
|
||||||
|
final ChatMessage message;
|
||||||
|
|
||||||
|
_MessageBox(this.message);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<ChatMessage> getAll() => [message];
|
||||||
|
|
||||||
|
@override
|
||||||
|
QueryBuilder<ChatMessage> query([Condition<ChatMessage>? qc]) =>
|
||||||
|
_UnreadQueryBuilder(message);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int put(ChatMessage object, {PutMode mode = PutMode.put}) => object.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UnreadQueryBuilder extends Fake implements QueryBuilder<ChatMessage> {
|
||||||
|
final ChatMessage message;
|
||||||
|
|
||||||
|
_UnreadQueryBuilder(this.message);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Query<ChatMessage> build() => _UnreadQuery(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UnreadQuery extends Fake implements Query<ChatMessage> {
|
||||||
|
final ChatMessage message;
|
||||||
|
|
||||||
|
_UnreadQuery(this.message);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<ChatMessage> find() => [if (!message.read) message];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void close() {}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <geolocator_windows/geolocator_windows.h>
|
#include <geolocator_windows/geolocator_windows.h>
|
||||||
#include <objectbox_flutter_libs/objectbox_flutter_libs_plugin.h>
|
#include <objectbox_flutter_libs/objectbox_flutter_libs_plugin.h>
|
||||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||||
|
#include <permission_manager/permission_manager_plugin.h>
|
||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
@@ -24,6 +25,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||||||
registry->GetRegistrarForPlugin("ObjectboxFlutterLibsPlugin"));
|
registry->GetRegistrarForPlugin("ObjectboxFlutterLibsPlugin"));
|
||||||
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
||||||
|
PermissionManagerPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("PermissionManagerPlugin"));
|
||||||
UrlLauncherWindowsRegisterWithRegistrar(
|
UrlLauncherWindowsRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||||
}
|
}
|
||||||
@@ -8,11 +8,12 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
geolocator_windows
|
geolocator_windows
|
||||||
objectbox_flutter_libs
|
objectbox_flutter_libs
|
||||||
permission_handler_windows
|
permission_handler_windows
|
||||||
|
permission_manager
|
||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
pdfium_flutter
|
jni
|
||||||
)
|
)
|
||||||
|
|
||||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||||
|
|||||||
Reference in new issue
Block a user