Erweiterungen

This commit is contained in:
Sven committed 2026-02-14 08:52:06 +01:00
1 parent ba1f7d5440
commit bb3dc9e856
28 files changed
+1672 -1291

No files matched your search

+10 -1
View File
@@ -18,10 +18,19 @@
"program": "lib/main.dart" "program": "lib/main.dart"
}, },
{ {
"name": "Stadtbote - Aktuelles Gerät", "name": "Stadtbote - Pixel 7",
"request": "launch", "request": "launch",
"type": "dart", "type": "dart",
"flutterMode": "debug", "flutterMode": "debug",
"deviceId": "2B101FDH2003HB",
"program": "lib/main.dart"
},
{
"name": "Stadtbote - Pixel 7 (Release)",
"request": "launch",
"type": "dart",
"flutterMode": "release",
"deviceId": "2B101FDH2003HB",
"program": "lib/main.dart" "program": "lib/main.dart"
} }
] ]
+2 -2
View File
@@ -29,7 +29,7 @@ if (keystorePropertiesFile.exists()) {
} }
android { android {
compileSdkVersion 34 compileSdk 36
ndkVersion flutter.ndkVersion ndkVersion flutter.ndkVersion
namespace "de.assecutor.stadtbote" namespace "de.assecutor.stadtbote"
@@ -50,7 +50,7 @@ android {
applicationId "de.assecutor.stadtbote" applicationId "de.assecutor.stadtbote"
minSdkVersion flutter.minSdkVersion minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion targetSdkVersion flutter.targetSdkVersion
compileSdk = 35
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
minSdkVersion 28 minSdkVersion 28
+1 -1
View File
@@ -1,3 +1,3 @@
org.gradle.jvmargs=-Xmx1536M org.gradle.jvmargs=-Xmx4096M -XX:MaxMetaspaceSize=512m
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
+2 -2
View File
@@ -18,8 +18,8 @@ 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.1.0" apply false id "com.android.application" version "8.9.1" apply false
id "org.jetbrains.kotlin.android" version "1.8.10" apply false id "org.jetbrains.kotlin.android" version "2.1.0" apply false
} }
include ":app" include ":app"
+41 -30
View File
@@ -8,6 +8,7 @@ import 'package:stadtbote/messages/pop_content_view_message.dart';
import '../main.dart'; import '../main.dart';
import '../model/job.dart'; import '../model/job.dart';
import '../objectbox.g.dart'; import '../objectbox.g.dart';
import '../popups/files_popup.dart';
import '../tools/constants.dart'; import '../tools/constants.dart';
import '../tools/util.dart'; import '../tools/util.dart';
@@ -92,38 +93,48 @@ class _AcceptedJobsPageState extends State<AcceptedJobsPage>
border: Border.all(color: Colors.grey, width: 0.5), border: Border.all(color: Colors.grey, width: 0.5),
borderRadius: const BorderRadius.all(Radius.circular(13))), borderRadius: const BorderRadius.all(Radius.circular(13))),
padding: const EdgeInsets.all(15.0), padding: const EdgeInsets.all(15.0),
child: Column( child: Stack(
crossAxisAlignment: CrossAxisAlignment.start, children: [
mainAxisAlignment: MainAxisAlignment.center, Column(
children: <Widget>[ crossAxisAlignment: CrossAxisAlignment.start,
Text( mainAxisAlignment: MainAxisAlignment.center,
DateFormat('dd.MM.yyyy HH:mm') children: <Widget>[
.format(DateTime.fromMillisecondsSinceEpoch(job.ordertime * 1000, isUtc: true).toLocal()) Text(
.toString(), DateFormat('dd.MM.yyyy HH:mm')
style: TextStyle( .format(DateTime.fromMillisecondsSinceEpoch(job.ordertime * 1000, isUtc: true).toLocal())
backgroundColor: isJobTooFarInFuture(job) .toString(),
? Colors.red style: TextStyle(
: Colors.transparent), backgroundColor: isJobTooFarInFuture(job)
textAlign: TextAlign.left, ? Colors.red
), : Colors.transparent),
Text( textAlign: TextAlign.left,
job.crFilter != null ? job.crFilter.toString() : "", ),
textAlign: TextAlign.left, Text(
), job.crFilter != null ? job.crFilter.toString() : "",
Row( textAlign: TextAlign.left,
children: [ ),
Image.asset( Row(
"assets/${mapVehicleTypeToImageName(job.vhtId)}", children: [
width: 60, Image.asset(
height: 60), "assets/${mapVehicleTypeToImageName(job.vhtId)}",
const SizedBox(width: 10), width: 60,
Text(mapVehicleTypeToText(job.vhtId)), height: 60),
const SizedBox(width: 10),
Text(mapVehicleTypeToText(job.vhtId)),
],
),
Text(
bottomText,
textAlign: TextAlign.left,
),
], ],
), ),
Text( if (job.hasFiles)
bottomText, Positioned(
textAlign: TextAlign.left, top: 0,
), right: 0,
child: FilesBadge(files: job.files),
),
], ],
), ),
)), )),
+43 -19
View File
@@ -17,6 +17,7 @@ import 'package:stadtbote/tools/messages/remove_state_0_job_from_map.dart';
import '../main.dart'; import '../main.dart';
import '../model/job.dart'; import '../model/job.dart';
import '../popups/files_popup.dart';
import '../tools/constants.dart'; import '../tools/constants.dart';
import '../tools/util.dart'; import '../tools/util.dart';
@@ -38,6 +39,7 @@ class _AssignedJobsPageState extends State<AssignedJobsPage> with ProducerMixin,
late bool _initCompleted = false; late bool _initCompleted = false;
List<Job> _jobs = []; List<Job> _jobs = [];
final List<ExpandableController> _expandableControllers = [];
@override @override
void initState() { void initState() {
@@ -59,6 +61,11 @@ class _AssignedJobsPageState extends State<AssignedJobsPage> with ProducerMixin,
// Unsubscribe from all message topics // Unsubscribe from all message topics
unsubscribe(queueId: Constants.messageTopicJobReceived); unsubscribe(queueId: Constants.messageTopicJobReceived);
// Dispose all expandable controllers
for (var controller in _expandableControllers) {
controller.dispose();
}
super.dispose(); super.dispose();
} }
@@ -92,9 +99,16 @@ class _AssignedJobsPageState extends State<AssignedJobsPage> with ProducerMixin,
List<Widget> _createWidgets() { List<Widget> _createWidgets() {
List<Widget> widgets = []; List<Widget> widgets = [];
// Clear previous controllers before creating new ones
for (var controller in _expandableControllers) {
controller.dispose();
}
_expandableControllers.clear();
for (var job in _jobs) { for (var job in _jobs) {
var expandableController = ExpandableController(); var expandableController = ExpandableController();
expandableController.expanded = true; expandableController.expanded = true;
_expandableControllers.add(expandableController);
widgets.add(ExpandableNotifier( widgets.add(ExpandableNotifier(
child: ListTile( child: ListTile(
@@ -126,27 +140,29 @@ class _AssignedJobsPageState extends State<AssignedJobsPage> with ProducerMixin,
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.all(12.0), padding: const EdgeInsets.all(12.0),
child: Column( child: Stack(
children: [ children: [
Text( Column(
DateFormat('dd.MM.yyyy HH:mm').format(DateTime.fromMillisecondsSinceEpoch(job.ordertime * 1000, isUtc: true).toLocal()).toString(),
textAlign: TextAlign.left,
style: TextStyle(backgroundColor: isJobTooFarInFuture(job) ? Colors.red : Colors.white),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Image.asset("assets/${mapVehicleTypeToImageName(job.vhtId)}", width: 60, height: 60),
const SizedBox(width: 10),
Text( Text(
mapVehicleTypeToText(job.vhtId) DateFormat('dd.MM.yyyy HH:mm').format(DateTime.fromMillisecondsSinceEpoch(job.ordertime * 1000, isUtc: true).toLocal()).toString(),
textAlign: TextAlign.left,
style: TextStyle(backgroundColor: isJobTooFarInFuture(job) ? Colors.red : Colors.white),
), ),
], Row(
), mainAxisAlignment: MainAxisAlignment.center,
Text('Auftragsnummer: ${job.externalId}'), children: [
Text(job.tours[0].comp), Image.asset("assets/${mapVehicleTypeToImageName(job.vhtId)}", width: 60, height: 60),
Text(job.tours[0].street), const SizedBox(width: 10),
Text(job.tours[0].city), Text(
mapVehicleTypeToText(job.vhtId)
),
],
),
Text('Auftragsnummer: ${job.externalId}'),
Text(job.tours[0].comp),
Text(job.tours[0].street),
Text(job.tours[0].city),
const SizedBox(height: 20), const SizedBox(height: 20),
ScrollOnExpand( ScrollOnExpand(
scrollOnExpand: true, scrollOnExpand: true,
@@ -217,11 +233,19 @@ class _AssignedJobsPageState extends State<AssignedJobsPage> with ProducerMixin,
]) ])
], ],
), ),
), if (job.hasFiles)
Positioned(
top: 0,
right: 0,
child: FilesBadge(files: job.files),
),
],
), ),
), ),
), ),
)); ),
),
));
} }
return widgets; return widgets;
+37 -26
View File
@@ -8,6 +8,7 @@ import 'package:stadtbote/messages/pop_content_view_message.dart';
import '../main.dart'; import '../main.dart';
import '../model/job.dart'; import '../model/job.dart';
import '../popups/files_popup.dart';
import '../tools/constants.dart'; import '../tools/constants.dart';
import '../tools/util.dart'; import '../tools/util.dart';
@@ -85,34 +86,44 @@ class _FinishedJobsPageState extends State<FinishedJobsPage>
border: Border.all(color: Colors.grey, width: 0.5), border: Border.all(color: Colors.grey, width: 0.5),
borderRadius: const BorderRadius.all(Radius.circular(13))), borderRadius: const BorderRadius.all(Radius.circular(13))),
padding: const EdgeInsets.all(15.0), padding: const EdgeInsets.all(15.0),
child: Column( child: Stack(
crossAxisAlignment: CrossAxisAlignment.start, children: [
mainAxisAlignment: MainAxisAlignment.center, Column(
children: <Widget>[ crossAxisAlignment: CrossAxisAlignment.start,
Text( mainAxisAlignment: MainAxisAlignment.center,
DateFormat('dd.MM.yyyy HH:mm') children: <Widget>[
.format(DateTime.fromMillisecondsSinceEpoch(job.ordertime * 1000, isUtc: true).toLocal()) Text(
.toString(), DateFormat('dd.MM.yyyy HH:mm')
textAlign: TextAlign.left, .format(DateTime.fromMillisecondsSinceEpoch(job.ordertime * 1000, isUtc: true).toLocal())
), .toString(),
Text( textAlign: TextAlign.left,
job.crFilter != null ? job.crFilter.toString() : "", ),
textAlign: TextAlign.left, Text(
), job.crFilter != null ? job.crFilter.toString() : "",
Row( textAlign: TextAlign.left,
children: [ ),
Image.asset( Row(
"assets/${mapVehicleTypeToImageName(job.vhtId)}", children: [
width: 60, Image.asset(
height: 60), "assets/${mapVehicleTypeToImageName(job.vhtId)}",
const SizedBox(width: 10), width: 60,
Text(mapVehicleTypeToText(job.vhtId)), height: 60),
const SizedBox(width: 10),
Text(mapVehicleTypeToText(job.vhtId)),
],
),
Text(
bottomText,
textAlign: TextAlign.left,
),
], ],
), ),
Text( if (job.hasFiles)
bottomText, Positioned(
textAlign: TextAlign.left, top: 0,
), right: 0,
child: FilesBadge(files: job.files),
),
], ],
), ),
)), )),
@@ -102,6 +102,12 @@ class _SetDesiredZipPageState extends State<SetDesiredZipPage>
acknowledgeRequired: true), acknowledgeRequired: true),
); );
} }
@override
void dispose() {
textEditController.dispose();
super.dispose();
}
} }
class _HeaderWidget extends StatefulWidget { class _HeaderWidget extends StatefulWidget {
+17
View File
@@ -6,6 +6,7 @@ import 'package:stadtbote/messages/pop_content_view_message.dart';
import 'package:stadtbote/model/job_tour.dart'; import 'package:stadtbote/model/job_tour.dart';
import '../model/job.dart'; import '../model/job.dart';
import '../popups/files_popup.dart';
import '../popups/map_popup.dart'; import '../popups/map_popup.dart';
import '../tools/constants.dart'; import '../tools/constants.dart';
import '../tools/util.dart'; import '../tools/util.dart';
@@ -103,6 +104,22 @@ class _TourDetailsPageState extends State<TourDetailsPage> with ProducerMixin {
"Komm-#: ${widget.tour.com}", "Komm-#: ${widget.tour.com}",
textAlign: TextAlign.left, textAlign: TextAlign.left,
), ),
if (widget.tour.files != null && widget.tour.files!.isNotEmpty) ...[
const SizedBox(height: 10),
Row(
children: [
FilesBadge(files: widget.tour.files),
const SizedBox(width: 8),
const Text(
'Dateien verfügbar',
style: TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
],
),
],
const SizedBox(height: 10), const SizedBox(height: 10),
getDefaultButton( getDefaultButton(
'Karte', 'Karte',
+11 -3
View File
@@ -7,6 +7,7 @@ import 'package:stadtbote/model/job_tour.dart';
import 'package:stadtbote/popups/map_popup.dart'; import 'package:stadtbote/popups/map_popup.dart';
import '../model/job.dart'; import '../model/job.dart';
import '../popups/files_popup.dart';
import '../tools/constants.dart'; import '../tools/constants.dart';
import '../tools/util.dart'; import '../tools/util.dart';
@@ -152,9 +153,16 @@ class _TourListPageState extends State<TourListPage> with ProducerMixin {
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Text( Row(
"${"Station ${tour.sort}"} ${mapTourMode(tour.mode)}", children: [
textAlign: TextAlign.left, Expanded(
child: Text(
"${"Station ${tour.sort}"} ${mapTourMode(tour.mode)}",
textAlign: TextAlign.left,
),
),
FilesBadge(files: tour.files),
],
), ),
Text( Text(
tour.comp, tour.comp,
+33 -1
View File
@@ -188,7 +188,7 @@ class Main extends StatefulWidget {
State<Main> createState() => _MainState(); State<Main> createState() => _MainState();
} }
class _MainState extends State<Main> with SingleTickerProviderStateMixin, ProducerMixin, ConsumerMixin { class _MainState extends State<Main> with SingleTickerProviderStateMixin, ProducerMixin, ConsumerMixin, WidgetsBindingObserver {
DateTime? _lastBackPressed; DateTime? _lastBackPressed;
late ContentViewWidget _currentContent; late ContentViewWidget _currentContent;
late WidgetStack<ContentViewWidget> contentStack = WidgetStack(); late WidgetStack<ContentViewWidget> contentStack = WidgetStack();
@@ -210,7 +210,11 @@ class _MainState extends State<Main> with SingleTickerProviderStateMixin, Produc
@override @override
void dispose() { void dispose() {
// Unregister from app lifecycle events
WidgetsBinding.instance.removeObserver(this);
_animationController?.dispose(); _animationController?.dispose();
_checkForNewState0JobsJobsTimer?.cancel();
// Unsubscribe from all message topics // Unsubscribe from all message topics
unsubscribe(queueId: Constants.messageTopicPushContentView); unsubscribe(queueId: Constants.messageTopicPushContentView);
@@ -228,6 +232,9 @@ class _MainState extends State<Main> with SingleTickerProviderStateMixin, Produc
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// Register for app lifecycle events
WidgetsBinding.instance.addObserver(this);
_animationController = AnimationController(vsync: this, duration: const Duration(milliseconds: 1000)); _animationController = AnimationController(vsync: this, duration: const Duration(milliseconds: 1000));
_animationController!.repeat(reverse: true); _animationController!.repeat(reverse: true);
@@ -253,6 +260,31 @@ class _MainState extends State<Main> with SingleTickerProviderStateMixin, Produc
}); });
} }
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
switch (state) {
case AppLifecycleState.paused:
case AppLifecycleState.inactive:
case AppLifecycleState.hidden:
// App geht in den Hintergrund - Animation und Timer pausieren
_animationController?.stop();
_checkForNewState0JobsJobsTimer?.cancel();
Util.logInfo('App in Hintergrund - Rendering pausiert');
break;
case AppLifecycleState.resumed:
// App kommt zurück in den Vordergrund - Animation und Timer fortsetzen
_animationController?.repeat(reverse: true);
_startCheckForNewState0JobsJobsTimer();
Util.logInfo('App im Vordergrund - Rendering fortgesetzt');
break;
case AppLifecycleState.detached:
// App wird beendet
break;
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (!_initCompleted) { if (!_initCompleted) {
+26
View File
@@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:objectbox/objectbox.dart'; import 'package:objectbox/objectbox.dart';
import 'package:stadtbote/model/job_tour.dart'; import 'package:stadtbote/model/job_tour.dart';
import 'package:stadtbote/model/tour_file.dart';
@Entity() @Entity()
class Job { class Job {
@@ -34,6 +35,25 @@ class Job {
toursJson = jsonEncode(value.map((e) => e.toJson()).toList()); toursJson = jsonEncode(value.map((e) => e.toJson()).toList());
} }
// Store files as JSON string for ObjectBox
String filesJson = '';
@Transient()
List<TourFile> get files {
if (filesJson.isEmpty) {
return [];
}
final List<dynamic> decoded = jsonDecode(filesJson);
return decoded.map((e) => TourFile.fromJson(e as Map<String, dynamic>)).toList();
}
set files(List<TourFile> value) {
filesJson = jsonEncode(value.map((e) => e.toJson()).toList());
}
// Helper method to check if job has files
bool get hasFiles => files.isNotEmpty;
Job(); Job();
factory Job.fromJson(Map<String, dynamic> json) { factory Job.fromJson(Map<String, dynamic> json) {
@@ -52,6 +72,11 @@ class Job {
job.tours = (json['tours'] as List<dynamic>) job.tours = (json['tours'] as List<dynamic>)
.map((e) => JobTour.fromJson(e as Map<String, dynamic>)) .map((e) => JobTour.fromJson(e as Map<String, dynamic>))
.toList(); .toList();
if (json['files'] != null) {
job.files = (json['files'] as List<dynamic>)
.map((e) => TourFile.fromJson(e as Map<String, dynamic>))
.toList();
}
return job; return job;
} }
@@ -68,6 +93,7 @@ class Job {
'rem': rem, 'rem': rem,
'delayOrderTime': delayOrderTime, 'delayOrderTime': delayOrderTime,
'tours': tours.map((e) => e.toJson()).toList(), 'tours': tours.map((e) => e.toJson()).toList(),
'files': files.map((e) => e.toJson()).toList(),
}; };
} }
} }
+8
View File
@@ -1,3 +1,4 @@
import 'package:stadtbote/model/tour_file.dart';
import 'package:stadtbote/model/tour_position.dart'; import 'package:stadtbote/model/tour_position.dart';
import 'package:stadtbote/model/tour_task.dart'; import 'package:stadtbote/model/tour_task.dart';
@@ -15,6 +16,7 @@ class JobTour {
TourPosition? pos; TourPosition? pos;
late String rem; late String rem;
List<TourTask>? tasks; List<TourTask>? tasks;
List<TourFile>? files;
JobTour(); JobTour();
@@ -39,6 +41,11 @@ class JobTour {
.map((e) => TourTask.fromJson(e as Map<String, dynamic>)) .map((e) => TourTask.fromJson(e as Map<String, dynamic>))
.toList() .toList()
: null; : null;
tour.files = json['files'] != null
? (json['files'] as List<dynamic>)
.map((e) => TourFile.fromJson(e as Map<String, dynamic>))
.toList()
: null;
return tour; return tour;
} }
@@ -57,6 +64,7 @@ class JobTour {
'pos': pos?.toJson(), 'pos': pos?.toJson(),
'rem': rem, 'rem': rem,
'tasks': tasks?.map((e) => e.toJson()).toList(), 'tasks': tasks?.map((e) => e.toJson()).toList(),
'files': files?.map((e) => e.toJson()).toList(),
}; };
} }
} }
+39
View File
@@ -0,0 +1,39 @@
class TourFile {
late int index;
late String name;
String? remark;
late String url;
TourFile();
factory TourFile.fromJson(Map<String, dynamic> json) {
final file = TourFile();
file.index = json['index'] as int;
file.name = json['name'] as String;
file.remark = json['remark'] as String?;
file.url = json['url'] as String;
return file;
}
Map<String, dynamic> toJson() {
return {
'index': index,
'name': name,
'remark': remark,
'url': url,
};
}
bool get isPdf {
return name.toLowerCase().endsWith('.pdf');
}
bool get isImage {
final ext = name.toLowerCase();
return ext.endsWith('.jpg') ||
ext.endsWith('.jpeg') ||
ext.endsWith('.png') ||
ext.endsWith('.gif') ||
ext.endsWith('.webp');
}
}
+72 -51
View File
@@ -34,7 +34,7 @@
}, },
{ {
"id": "3:808686831791135454", "id": "3:808686831791135454",
"lastPropertyId": "12:5320049580625017283", "lastPropertyId": "13:1803006975816940878",
"name": "Job", "name": "Job",
"properties": [ "properties": [
{ {
@@ -99,6 +99,11 @@
"type": 6, "type": 6,
"flags": 40, "flags": 40,
"indexId": "1:2108836818156887439" "indexId": "1:2108836818156887439"
},
{
"id": "13:1803006975816940878",
"name": "filesJson",
"type": 9
} }
], ],
"relations": [] "relations": []
@@ -302,52 +307,6 @@
], ],
"relations": [] "relations": []
}, },
{
"id": "11:8606238749478059367",
"lastPropertyId": "7:4056540326670791484",
"name": "ChatMessage",
"properties": [
{
"id": "1:3236243497308902958",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "2:1689142031630531044",
"name": "time",
"type": 6
},
{
"id": "3:9168654785572108691",
"name": "from",
"type": 9
},
{
"id": "4:3167039386322657979",
"name": "subj",
"type": 9
},
{
"id": "5:371212500165499194",
"name": "text",
"type": 9
},
{
"id": "6:7798301556204918939",
"name": "read",
"type": 1
},
{
"id": "7:4056540326670791484",
"name": "externalId",
"type": 6,
"flags": 40,
"indexId": "2:428171949166894655"
}
],
"relations": []
},
{ {
"id": "12:4865606913057136676", "id": "12:4865606913057136676",
"lastPropertyId": "5:4858121848992922038", "lastPropertyId": "5:4858121848992922038",
@@ -381,17 +340,65 @@
} }
], ],
"relations": [] "relations": []
},
{
"id": "14:3621615714595976689",
"lastPropertyId": "7:5084802055406173119",
"name": "ChatMessage",
"properties": [
{
"id": "1:8160599835026826926",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "2:5736027494386837185",
"name": "externalId",
"type": 6,
"flags": 40,
"indexId": "4:8487631368009819350"
},
{
"id": "3:4672344908186587803",
"name": "time",
"type": 6
},
{
"id": "4:2026513613729432961",
"name": "from",
"type": 9
},
{
"id": "5:6544369704442218939",
"name": "subj",
"type": 9
},
{
"id": "6:8234519373253706290",
"name": "text",
"type": 9
},
{
"id": "7:5084802055406173119",
"name": "read",
"type": 1
}
],
"relations": []
} }
], ],
"lastEntityId": "12:4865606913057136676", "lastEntityId": "14:3621615714595976689",
"lastIndexId": "2:428171949166894655", "lastIndexId": "4:8487631368009819350",
"lastRelationId": "0:0", "lastRelationId": "0:0",
"lastSequenceId": "0:0", "lastSequenceId": "0:0",
"modelVersion": 5, "modelVersion": 5,
"modelVersionParserMinimum": 5, "modelVersionParserMinimum": 5,
"retiredEntityUids": [ "retiredEntityUids": [
3121960281516643928, 3121960281516643928,
3330049220205502332 3330049220205502332,
8606238749478059367,
9126058286032860192
], ],
"retiredIndexUids": [], "retiredIndexUids": [],
"retiredPropertyUids": [ "retiredPropertyUids": [
@@ -405,7 +412,21 @@
3530187464938423960, 3530187464938423960,
2939471599149775609, 2939471599149775609,
4870701960967093484, 4870701960967093484,
9072329172508444422 9072329172508444422,
3236243497308902958,
1689142031630531044,
9168654785572108691,
3167039386322657979,
371212500165499194,
7798301556204918939,
4056540326670791484,
2028590175083469347,
7733678916130816919,
5111729991837061065,
2981635918450905844,
4551221086536914442,
1176594062059570557,
4272081635478028034
], ],
"retiredRelationUids": [], "retiredRelationUids": [],
"version": 1 "version": 1
+933 -1097
View File
File diff suppressed because it is too large. Load diff
+1
View File
@@ -133,6 +133,7 @@ Future<void> showAcceptJobsPopup(SharedMapReference mapReference, Function() onF
// Ensure we always clean up subscriptions even if the dialog is dismissed via barrier tap or back button // Ensure we always clean up subscriptions even if the dialog is dismissed via barrier tap or back button
dialogFuture.whenComplete(() { dialogFuture.whenComplete(() {
isActive = false; isActive = false;
_scrollController.dispose();
if (builderCreated) { if (builderCreated) {
try { try {
extendedStatefulBuilder.unsubscribe(queueId: Constants.messageTopicJobReceived); extendedStatefulBuilder.unsubscribe(queueId: Constants.messageTopicJobReceived);
+262
View File
@@ -0,0 +1,262 @@
import 'package:flutter/material.dart';
import 'package:stadtbote/content_views/pdf_viewer_page.dart';
import 'package:stadtbote/model/tour_file.dart';
import 'package:stadtbote/tools/constants.dart';
import 'package:stadtbote/tools/util.dart' show showAlertDialog;
import 'package:stadtbote/tools/util.dart' as util;
void showFilesPopup(BuildContext context, List<TourFile> files) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Dateien'),
content: SizedBox(
width: double.maxFinite,
height: 400,
child: ListView.builder(
shrinkWrap: true,
itemCount: files.length,
itemBuilder: (context, index) {
final file = files[index];
return _FileListItem(file: file);
},
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Schließen'),
),
],
);
},
);
}
class _FileListItem extends StatefulWidget {
final TourFile file;
const _FileListItem({required this.file});
@override
State<_FileListItem> createState() => _FileListItemState();
}
class _FileListItemState extends State<_FileListItem> {
bool _isLoading = false;
@override
Widget build(BuildContext context) {
IconData iconData;
if (widget.file.isPdf) {
iconData = Icons.picture_as_pdf;
} else if (widget.file.isImage) {
iconData = Icons.image;
} else {
iconData = Icons.insert_drive_file;
}
return Card(
margin: const EdgeInsets.symmetric(vertical: 4),
child: ListTile(
leading: Icon(iconData, color: Constants.colorPrimary),
title: Text(widget.file.name),
subtitle: widget.file.remark != null && widget.file.remark!.isNotEmpty
? Text(widget.file.remark!)
: null,
trailing: _isLoading
? const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.open_in_new),
onTap: () => _openFile(context),
),
);
}
Future<void> _openFile(BuildContext context) async {
if (_isLoading) return;
setState(() {
_isLoading = true;
});
try {
if (widget.file.isPdf) {
Navigator.of(context).pop(); // Close the files dialog
// Navigate to PDF viewer
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => PdfViewerPage(
pdfUrl: widget.file.url,
title: widget.file.name,
),
),
);
} else if (widget.file.isImage) {
Navigator.of(context).pop(); // Close the files dialog
// Show image in a dialog
await showDialog(
context: context,
builder: (BuildContext context) {
return Dialog(
insetPadding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
AppBar(
title: Text(widget.file.name),
leading: IconButton(
icon: const Icon(Icons.close),
onPressed: () => Navigator.of(context).pop(),
),
automaticallyImplyLeading: false,
),
Flexible(
child: Image.network(
widget.file.url,
fit: BoxFit.contain,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
);
},
errorBuilder: (context, error, stackTrace) {
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',
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.red),
),
],
),
);
},
),
),
],
),
);
},
);
} else {
// For other file types, try to open with external app
showAlertDialog(
'Information',
'Dateityp wird nicht direkt unterstützt. Die Datei wird im Browser geöffnet.',
);
}
} catch (e) {
util.Util.logException('Error opening file: $e');
if (context.mounted) {
showAlertDialog(
'Fehler',
'Fehler beim Öffnen der Datei: $e',
);
}
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
}
/// Widget to display a file icon button if files are available
class FilesIconButton extends StatelessWidget {
final List<TourFile>? files;
final double iconSize;
const FilesIconButton({
super.key,
required this.files,
this.iconSize = 24,
});
@override
Widget build(BuildContext context) {
if (files == null || files!.isEmpty) {
return const SizedBox.shrink();
}
return IconButton(
icon: const Icon(Icons.attach_file),
iconSize: iconSize,
color: Constants.colorPrimary,
tooltip: '${files!.length} Datei(en)',
onPressed: () => showFilesPopup(context, files!),
);
}
}
/// Badge widget showing file count with icon
class FilesBadge extends StatelessWidget {
final List<TourFile>? files;
final VoidCallback? onTap;
const FilesBadge({
super.key,
required this.files,
this.onTap,
});
@override
Widget build(BuildContext context) {
if (files == null || files!.isEmpty) {
return const SizedBox.shrink();
}
return GestureDetector(
onTap: onTap ?? () => showFilesPopup(context, files!),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Constants.colorPrimary.withAlpha(30),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Constants.colorPrimary, width: 1),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.attach_file,
size: 16,
color: Constants.colorPrimary,
),
const SizedBox(width: 4),
Text(
'${files!.length}',
style: TextStyle(
color: Constants.colorPrimary,
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
],
),
),
);
}
}
+17 -8
View File
@@ -11,6 +11,9 @@ import '../model/job_tour.dart';
List<Color> _colors = [Colors.red, Colors.green]; List<Color> _colors = [Colors.red, Colors.green];
showMapPopup(BuildContext context, JobTour currentTour, List<JobTour> tourList) async { showMapPopup(BuildContext context, JobTour currentTour, List<JobTour> tourList) async {
MapController? mapController;
Map<int, StreamController<bool>> streamControllers = {};
await showDialog( await showDialog(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
@@ -19,24 +22,24 @@ showMapPopup(BuildContext context, JobTour currentTour, List<JobTour> tourList)
shadowColor: Colors.transparent, shadowColor: Colors.transparent,
backgroundColor: Colors.white, backgroundColor: Colors.white,
content: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { content: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
var mapController = MapController(); mapController = MapController();
Map<int, StreamController<bool>> streamController = {}; streamControllers = {};
List<Widget> tourWidgets = []; List<Widget> tourWidgets = [];
for (JobTour tour in tourList) { for (JobTour tour in tourList) {
var currentStreamController = StreamController<bool>(); var currentStreamController = StreamController<bool>();
streamController[tour.sort] = currentStreamController; streamControllers[tour.sort] = currentStreamController;
tourWidgets.add( tourWidgets.add(
GestureDetector( GestureDetector(
onTap: () async { onTap: () async {
if (tour.pos != null) { if (tour.pos != null) {
mapController.move(LatLng(tour.pos!.lat, tour.pos!.long), mapController.camera.zoom); mapController!.move(LatLng(tour.pos!.lat, tour.pos!.long), mapController!.camera.zoom);
streamController[currentTour.sort]!.sink.add(false); streamControllers[currentTour.sort]!.sink.add(false);
currentTour = tour; currentTour = tour;
streamController[tour.sort]!.sink.add(true); streamControllers[tour.sort]!.sink.add(true);
} else { } else {
showAlertDialog('Information', 'Es liegt keine Position für diese Station vor'); showAlertDialog('Information', 'Es liegt keine Position für diese Station vor');
} }
@@ -158,7 +161,7 @@ showMapPopup(BuildContext context, JobTour currentTour, List<JobTour> tourList)
height: 25, height: 25,
onChanged: (b) { onChanged: (b) {
valueNotifier.value = b; valueNotifier.value = b;
mapController.move(mapController.camera.center, b ? 14.0 : 16.0); mapController!.move(mapController!.camera.center, b ? 14.0 : 16.0);
}, },
styleBuilder: (i) => ToggleStyle(indicatorColor: i == true ? _colors[0] : _colors[1]), styleBuilder: (i) => ToggleStyle(indicatorColor: i == true ? _colors[0] : _colors[1]),
textBuilder: (value) => value ? const Center(child: Text('Fern')) : const Center(child: Text('Nah')), textBuilder: (value) => value ? const Center(child: Text('Fern')) : const Center(child: Text('Nah')),
@@ -173,7 +176,7 @@ showMapPopup(BuildContext context, JobTour currentTour, List<JobTour> tourList)
child: AbsorbPointer( child: AbsorbPointer(
absorbing: true, absorbing: true,
child: FlutterMap( child: FlutterMap(
mapController: mapController, mapController: mapController!,
options: MapOptions( options: MapOptions(
initialCenter: LatLng(currentTour.pos!.lat, currentTour.pos!.long), initialCenter: LatLng(currentTour.pos!.lat, currentTour.pos!.long),
initialZoom: 14, initialZoom: 14,
@@ -196,4 +199,10 @@ showMapPopup(BuildContext context, JobTour currentTour, List<JobTour> tourList)
)); ));
})); }));
}); });
// Dispose all controllers after dialog is closed
mapController?.dispose();
for (var controller in streamControllers.values) {
controller.close();
}
} }
@@ -50,7 +50,12 @@ showComissionNumberPopup(BuildContext context, TaskInfo taskInfo, Function(Strin
controller: textEditingController, controller: textEditingController,
autofocus: true), autofocus: true),
const SizedBox(height: 20), const SizedBox(height: 20),
getDefaultButton('Übernehmen', () => {Navigator.of(context).pop(), onFinished(textEditingController.text)}) getDefaultButton('Übernehmen', () {
final result = textEditingController.text;
textEditingController.dispose();
Navigator.of(context).pop();
onFinished(result);
})
], ],
), ),
), ),
@@ -24,6 +24,8 @@ showPickupDeliveryStatusPopup(BuildContext context, TaskInfo taskInfo, Function(
if (!didPop) { if (!didPop) {
final shouldPop = await taskDialogSecurityException(context); final shouldPop = await taskDialogSecurityException(context);
if (shouldPop && context.mounted) { if (shouldPop && context.mounted) {
controller.dispose();
remController.dispose();
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
} }
@@ -73,6 +75,9 @@ showPickupDeliveryStatusPopup(BuildContext context, TaskInfo taskInfo, Function(
if (remController.text.isNotEmpty) { if (remController.text.isNotEmpty) {
Navigator.of(context).pop(), Navigator.of(context).pop(),
controller.dispose(),
remController.dispose(),
onFinished(selectedTourEntry, remController.text) onFinished(selectedTourEntry, remController.text)
} else { } else {
showAlertDialog("Fehler", "Bitte geben Sie eine Bemerkung ein") showAlertDialog("Fehler", "Bitte geben Sie eine Bemerkung ein")
@@ -80,6 +85,9 @@ showPickupDeliveryStatusPopup(BuildContext context, TaskInfo taskInfo, Function(
} else { } else {
Navigator.of(context).pop(), Navigator.of(context).pop(),
controller.dispose(),
remController.dispose(),
onFinished(selectedTourEntry, null) onFinished(selectedTourEntry, null)
} }
}) })
@@ -77,7 +77,11 @@ showReceiptGiverPopup(BuildContext context, TaskInfo taskInfo, Function(String,
if (textEditingController.text.isNotEmpty && selectedTourEntry != null) { if (textEditingController.text.isNotEmpty && selectedTourEntry != null) {
Navigator.of(context).pop(); Navigator.of(context).pop();
onFinished(textEditingController.text, selectedTourEntry!); final result = textEditingController.text;
textEditingController.dispose();
controller.dispose();
onFinished(result, selectedTourEntry!);
} else { } else {
showAlertDialog("Fehler", "Sie müssen einen Text eingeben und ein Geschlecht auswählen"); showAlertDialog("Fehler", "Sie müssen einen Text eingeben und ein Geschlecht auswählen");
} }
@@ -54,10 +54,12 @@ showStationNoticePopup(BuildContext context, TaskInfo taskInfo, Function(String)
autofocus: true), autofocus: true),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
getDefaultButton('Übernehmen', buttonHeight: 50, () => { getDefaultButton('Übernehmen', buttonHeight: 50, () {
Navigator.of(context).pop(), final result = textEditingController.text;
textEditingController.dispose();
onFinished(textEditingController.text)}) Navigator.of(context).pop();
onFinished(result);
})
], ],
), ),
), ),
+10 -9
View File
@@ -67,15 +67,16 @@ showZipPopup(BuildContext context, String text, String buttonText,
const SizedBox(height: 20), const SizedBox(height: 20),
getDefaultButton( getDefaultButton(
buttonText, buttonText,
() => { () {
if (_checkInput(context, if (_checkInput(context,
textEditingController.text)) textEditingController.text)) {
{ final result = textEditingController.text;
Navigator.of(context).pop(), textEditingController.dispose();
onFinished( focusNode.dispose();
textEditingController.text) Navigator.of(context).pop();
}, onFinished(result);
}), }
}),
], ],
), ),
), ),
+11 -2
View File
@@ -16,6 +16,7 @@ import 'package:stadtbote/model/chat_message.dart';
import 'package:stadtbote/model/location_status.dart'; import 'package:stadtbote/model/location_status.dart';
import 'package:stadtbote/model/rank.dart'; import 'package:stadtbote/model/rank.dart';
import 'package:stadtbote/model/status.dart'; import 'package:stadtbote/model/status.dart';
import 'package:stadtbote/model/tour_file.dart';
import 'package:stadtbote/objectbox.g.dart'; import 'package:stadtbote/objectbox.g.dart';
import 'package:stadtbote/network_messages/acknowledge_job.dart'; import 'package:stadtbote/network_messages/acknowledge_job.dart';
import 'package:stadtbote/network_messages/acknowledge_message.dart'; import 'package:stadtbote/network_messages/acknowledge_message.dart';
@@ -401,8 +402,16 @@ class MqttCore with ProducerMixin, ConsumerMixin {
query.close(); query.close();
if (existingJob != null) { if (existingJob != null) {
// Update existing job // Update existing job - preserve the ID
newJob.id = existingJob.id; newJob.id = existingJob.id;
// Ensure files are updated even if the job already exists
// This handles migration from old app versions without files support
if (payloadData['files'] != null) {
newJob.files = (payloadData['files'] as List<dynamic>)
.map((e) => TourFile.fromJson(e as Map<String, dynamic>))
.toList();
}
} }
box.put(newJob); box.put(newJob);
@@ -754,7 +763,7 @@ class MqttCore with ProducerMixin, ConsumerMixin {
void _commandLogoutReceived(Map<String, dynamic> payload) { void _commandLogoutReceived(Map<String, dynamic> payload) {
try { try {
String timestamp = payload['timestamp'] ?? 'Unknown timestamp'; String timestamp = payload['timestamp']?.toString() ?? 'Unknown timestamp';
Util.logInfo('Command logout received with timestamp: $timestamp'); Util.logInfo('Command logout received with timestamp: $timestamp');
// Disconnect MQTT client gracefully // Disconnect MQTT client gracefully
+23
View File
@@ -9,6 +9,7 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'package:stadtbote/main.dart'; import 'package:stadtbote/main.dart';
import 'package:stadtbote/model/job.dart'; import 'package:stadtbote/model/job.dart';
import 'package:stadtbote/model/log_entry.dart'; import 'package:stadtbote/model/log_entry.dart';
import 'package:stadtbote/objectbox.g.dart';
import 'package:stadtbote/tools/constants.dart'; import 'package:stadtbote/tools/constants.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
@@ -97,6 +98,28 @@ class Util {
final box = MyApp.objectBoxStore.box<LogEntry>(); final box = MyApp.objectBoxStore.box<LogEntry>();
box.put(logEntry); box.put(logEntry);
// Cleanup old logs (delete entries older than 3 days)
_cleanupOldLogs();
}
static void _cleanupOldLogs() async {
try {
final box = MyApp.objectBoxStore.box<LogEntry>();
// Delete logs older than 3 days
final threeDaysAgo = DateTime.now().subtract(const Duration(days: 3));
final oldLogsQuery = box.query(LogEntry_.time.lessThan(threeDaysAgo.millisecondsSinceEpoch)).build();
final oldLogs = oldLogsQuery.find();
oldLogsQuery.close();
if (oldLogs.isNotEmpty) {
box.removeMany(oldLogs.map((e) => e.id).toList());
}
} catch (e) {
// Silent fail for cleanup - don't log to avoid infinite loop
debugPrint('Log cleanup failed: $e');
}
} }
static logInfo(String message) { static logInfo(String message) {
+41 -33
View File
@@ -5,18 +5,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: _fe_analyzer_shared name: _fe_analyzer_shared
sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "91.0.0" version: "67.0.0"
analyzer: analyzer:
dependency: transitive dependency: "direct dev"
description: description:
name: analyzer name: analyzer
sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08 sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.4.1" version: "6.4.1"
animated_toggle_switch: animated_toggle_switch:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -125,18 +125,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build name: build
sha256: ce76b1d48875e3233fde17717c23d1f60a91cc631597e49a400c89b475395b1d sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.0" version: "2.4.1"
build_config: build_config:
dependency: transitive dependency: transitive
description: description:
name: build_config name: build_config
sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187" sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.1.2"
build_daemon: build_daemon:
dependency: transitive dependency: transitive
description: description:
@@ -149,26 +149,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build_resolvers name: build_resolvers
sha256: d1d57f7807debd7349b4726a19fd32ec8bc177c71ad0febf91a20f84cd2d4b46 sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.3" version: "2.4.2"
build_runner: build_runner:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
sha256: b24597fceb695969d47025c958f3837f9f0122e237c6a22cb082a5ac66c3ca30 sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.7.1" version: "2.4.13"
build_runner_core: build_runner_core:
dependency: transitive dependency: transitive
description: description:
name: build_runner_core name: build_runner_core
sha256: "066dda7f73d8eb48ba630a55acb50c4a84a2e6b453b1cb4567f581729e794f7b" sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.3.1" version: "7.3.2"
built_collection: built_collection:
dependency: transitive dependency: transitive
description: description:
@@ -317,10 +317,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: dart_style name: dart_style
sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.3" version: "2.3.6"
device_info_plus: device_info_plus:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -776,6 +776,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.5" version: "1.0.5"
js:
dependency: transitive
description:
name: js
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
url: "https://pub.dev"
source: hosted
version: "0.7.2"
json_annotation: json_annotation:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -788,10 +796,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: json_serializable name: json_serializable
sha256: c5b2ee75210a0f263c6c7b9eeea80553dbae96ea1bf57f02484e806a3ffdffa3 sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.11.2" version: "6.8.0"
latlong2: latlong2:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -916,26 +924,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: objectbox name: objectbox
sha256: "3cc186749178a3556e1020c9082d0897d0f9ecbdefcc27320e65c5bc650f0e57" sha256: "3d1cb5f9aa564f95c76ba251299f6cb1591c3dd8ff05fd76fa0549d899d9fe31"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.3.1" version: "4.1.0"
objectbox_flutter_libs: objectbox_flutter_libs:
dependency: "direct main" dependency: "direct main"
description: description:
name: objectbox_flutter_libs name: objectbox_flutter_libs
sha256: cd754766e04229a4f51250f121813d9a3c1a74fc21cd68e48b3c6085cbcd6c85 sha256: "4f54ebbd7a3b72f1a5ef4fea76cb01cc36440a4cac1f63bfb6719afba400eedb"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.3.1" version: "4.1.0"
objectbox_generator: objectbox_generator:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: objectbox_generator name: objectbox_generator
sha256: "71a3f6948e631be5c7160d512ad2a8cb7471cdbcf1731ec6baf2a794b82386d7" sha256: "777924e14daf18603a023b346333acac1c4fce9fe4a715b85d98cfcc723d9a1a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.3.1" version: "4.1.0"
package_config: package_config:
dependency: transitive dependency: transitive
description: description:
@@ -1140,10 +1148,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: pointycastle name: pointycastle
sha256: "92aa3841d083cc4b0f4709b5c74fd6409a3e6ba833ffc7dc6a8fee096366acf5" sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.0" version: "3.9.1"
pool: pool:
dependency: transitive dependency: transitive
description: description:
@@ -1284,10 +1292,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shelf_web_socket name: shelf_web_socket
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.0" version: "2.0.1"
signature: signature:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1305,18 +1313,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: source_gen name: source_gen
sha256: "7b19d6ba131c6eb98bfcbf8d56c1a7002eba438af2e7ae6f8398b2b0f4f381e3" sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.0" version: "1.5.0"
source_helper: source_helper:
dependency: transitive dependency: transitive
description: description:
name: source_helper name: source_helper
sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723" sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.8" version: "1.3.5"
source_span: source_span:
dependency: transitive dependency: transitive
description: description:
+1
View File
@@ -77,6 +77,7 @@ dev_dependencies:
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
analyzer: ^6.0.0
# The "flutter_lints" package below contains a set of recommended lints to # The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is # encourage good coding practices. The lint set provided by the package is