diff --git a/assets/symbols/posters/poster.png b/assets/symbols/posters/poster.png
index 0c2771e9..cc3cc889 100644
Binary files a/assets/symbols/posters/poster.png and b/assets/symbols/posters/poster.png differ
diff --git a/assets/symbols/posters/poster_damaged.png b/assets/symbols/posters/poster_damaged.png
index c250990f..bee1ee97 100644
Binary files a/assets/symbols/posters/poster_damaged.png and b/assets/symbols/posters/poster_damaged.png differ
diff --git a/assets/symbols/posters/poster_missing.png b/assets/symbols/posters/poster_missing.png
new file mode 100644
index 00000000..3de3a132
Binary files /dev/null and b/assets/symbols/posters/poster_missing.png differ
diff --git a/assets/symbols/posters/poster_removed.png b/assets/symbols/posters/poster_removed.png
index ed2fba9c..3a6ed661 100644
Binary files a/assets/symbols/posters/poster_removed.png and b/assets/symbols/posters/poster_removed.png differ
diff --git a/assets/symbols/posters/poster_to_be_moved.png b/assets/symbols/posters/poster_to_be_moved.png
new file mode 100644
index 00000000..1ab347f5
Binary files /dev/null and b/assets/symbols/posters/poster_to_be_moved.png differ
diff --git a/assets/symbols/posters/svg_inverted/poster_damaged.svg b/assets/symbols/posters/svg_inverted/poster_damaged.svg
new file mode 100644
index 00000000..3e7c6995
--- /dev/null
+++ b/assets/symbols/posters/svg_inverted/poster_damaged.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/symbols/posters/svg_inverted/poster_missing.svg b/assets/symbols/posters/svg_inverted/poster_missing.svg
new file mode 100644
index 00000000..1f01d088
--- /dev/null
+++ b/assets/symbols/posters/svg_inverted/poster_missing.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/symbols/posters/svg_inverted/poster_ok.svg b/assets/symbols/posters/svg_inverted/poster_ok.svg
new file mode 100644
index 00000000..2b40be69
--- /dev/null
+++ b/assets/symbols/posters/svg_inverted/poster_ok.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/symbols/posters/svg_inverted/poster_removed.svg b/assets/symbols/posters/svg_inverted/poster_removed.svg
new file mode 100644
index 00000000..5135581e
--- /dev/null
+++ b/assets/symbols/posters/svg_inverted/poster_removed.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/symbols/posters/svg_inverted/poster_tobemoved.svg b/assets/symbols/posters/svg_inverted/poster_tobemoved.svg
new file mode 100644
index 00000000..9c136533
--- /dev/null
+++ b/assets/symbols/posters/svg_inverted/poster_tobemoved.svg
@@ -0,0 +1,3 @@
+
diff --git a/lib/app/services/converters/poi_poster_status_parsing.dart b/lib/app/services/converters/poi_poster_status_parsing.dart
index 371f6cb8..6c66605e 100644
--- a/lib/app/services/converters/poi_poster_status_parsing.dart
+++ b/lib/app/services/converters/poi_poster_status_parsing.dart
@@ -7,6 +7,7 @@ extension PoiPosterStatusParsing on PoiPosterStatus {
PoiPosterStatus.damaged => PosterStatus.damaged,
PoiPosterStatus.missing => PosterStatus.missing,
PoiPosterStatus.removed => PosterStatus.removed,
+ PoiPosterStatus.toBeMoved => PosterStatus.toBeMoved,
PoiPosterStatus.swaggerGeneratedUnknown => throw UnimplementedError(),
};
}
@@ -17,6 +18,7 @@ extension PoiPosterStatusParsing on PoiPosterStatus {
PoiPosterStatus.damaged => t.campaigns.poster.status.damaged.label,
PoiPosterStatus.removed => t.campaigns.poster.status.removed.label,
PoiPosterStatus.missing => t.campaigns.poster.status.missing.label,
+ PoiPosterStatus.toBeMoved => t.campaigns.poster.status.to_be_moved.label,
PoiPosterStatus.swaggerGeneratedUnknown => throw UnimplementedError(),
};
}
diff --git a/lib/app/services/converters/poster_status_parsing.dart b/lib/app/services/converters/poster_status_parsing.dart
index 29a73f14..bf73b635 100644
--- a/lib/app/services/converters/poster_status_parsing.dart
+++ b/lib/app/services/converters/poster_status_parsing.dart
@@ -7,6 +7,7 @@ extension PosterStatusParsing on PosterStatus {
PosterStatus.damaged => PoiPosterStatus.damaged,
PosterStatus.missing => PoiPosterStatus.missing,
PosterStatus.removed => PoiPosterStatus.removed,
+ PosterStatus.toBeMoved => PoiPosterStatus.toBeMoved,
};
}
diff --git a/lib/app/services/gruene_api_campaigns_statistics_service.dart b/lib/app/services/gruene_api_campaigns_statistics_service.dart
index 768d246f..515e854a 100644
--- a/lib/app/services/gruene_api_campaigns_statistics_service.dart
+++ b/lib/app/services/gruene_api_campaigns_statistics_service.dart
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
-import 'package:gruene_app/features/campaigns/models/statistics/campaign_statistics.dart';
+import 'package:gruene_app/features/campaigns/models/statistics/campaign_statistics_model.dart';
import 'package:gruene_app/features/campaigns/models/statistics/campaign_statistics_set.dart';
import 'package:gruene_app/swagger_generated_code/gruene_api.swagger.dart';
@@ -11,7 +11,7 @@ class GrueneApiCampaignsStatisticsService {
grueneApi = GetIt.I();
}
- Future getStatistics() async {
+ Future getStatistics() async {
try {
var statResult = await grueneApi.v1CampaignsStatisticsGet();
return statResult.body!.asCampaignStatistics();
@@ -23,9 +23,9 @@ class GrueneApiCampaignsStatisticsService {
}
}
-extension StatisticsParser on Statistics {
- CampaignStatistics asCampaignStatistics() {
- return CampaignStatistics(
+extension StatisticsParser on CampaignStatistics {
+ CampaignStatisticsModel asCampaignStatistics() {
+ return CampaignStatisticsModel(
flyerStats: flyer.asStatisticsSet(),
houseStats: house.asStatisticsSet(),
posterStats: poster.asStatisticsSet(),
diff --git a/lib/features/campaigns/helper/campaign_constants.dart b/lib/features/campaigns/helper/campaign_constants.dart
index 1e6c0051..a1c7f0da 100644
--- a/lib/features/campaigns/helper/campaign_constants.dart
+++ b/lib/features/campaigns/helper/campaign_constants.dart
@@ -6,7 +6,9 @@ class CampaignConstants {
static const flyerAssetName = 'assets/symbols/flyer/flyer.png';
static const posterOkAssetName = 'assets/symbols/posters/poster.png';
static const posterDamagedAssetName = 'assets/symbols/posters/poster_damaged.png';
+ static const posterMissingAssetName = 'assets/symbols/posters/poster_missing.png';
static const posterRemovedAssetName = 'assets/symbols/posters/poster_removed.png';
+ static const posterToBeMovedAssetName = 'assets/symbols/posters/poster_to_be_moved.png';
static const addMarkerAssetName = 'assets/symbols/add_marker.svg';
static const markerSourceName = 'markers';
diff --git a/lib/features/campaigns/helper/campaign_session_settings.dart b/lib/features/campaigns/helper/campaign_session_settings.dart
index 5d4bbf1a..488149b0 100644
--- a/lib/features/campaigns/helper/campaign_session_settings.dart
+++ b/lib/features/campaigns/helper/campaign_session_settings.dart
@@ -1,12 +1,12 @@
import 'package:gruene_app/app/services/nominatim_service.dart';
-import 'package:gruene_app/features/campaigns/models/statistics/campaign_statistics.dart';
+import 'package:gruene_app/features/campaigns/models/statistics/campaign_statistics_model.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
class CampaignSessionSettings {
LatLng? lastPosition;
double? lastZoomLevel;
- CampaignStatistics? recentStatistics;
+ CampaignStatisticsModel? recentStatistics;
DateTime? recentStatisticsFetchTimestamp;
bool imageConsentConfirmed = false;
diff --git a/lib/features/campaigns/helper/enums.dart b/lib/features/campaigns/helper/enums.dart
index 760a24bf..88571490 100644
--- a/lib/features/campaigns/helper/enums.dart
+++ b/lib/features/campaigns/helper/enums.dart
@@ -1,3 +1,5 @@
enum ModalEditResult { cancel, save, delete }
+enum ModalDetailResult { close, edit }
+
enum ImageType { jpeg, png }
diff --git a/lib/features/campaigns/helper/poster_status.dart b/lib/features/campaigns/helper/poster_status.dart
index 7cd3af28..c80100eb 100644
--- a/lib/features/campaigns/helper/poster_status.dart
+++ b/lib/features/campaigns/helper/poster_status.dart
@@ -2,21 +2,26 @@ import 'package:gruene_app/features/campaigns/models/posters/poster_detail_model
import 'package:gruene_app/i18n/translations.g.dart';
class PosterStatusHelper {
- static List<(PosterStatus, String, String)> getPosterStatusOptions = <(PosterStatus, String, String)>[
+ static List<(PosterStatus, String)> getPosterStatusList = <(PosterStatus status, String label)>[
+ (
+ PosterStatus.ok,
+ t.campaigns.poster.status.ok.label,
+ ),
(
PosterStatus.damaged,
t.campaigns.poster.status.damaged.label,
- t.campaigns.poster.status.damaged.hint,
),
(
PosterStatus.missing,
t.campaigns.poster.status.missing.label,
- t.campaigns.poster.status.missing.hint,
+ ),
+ (
+ PosterStatus.toBeMoved,
+ t.campaigns.poster.status.to_be_moved.label,
),
(
PosterStatus.removed,
t.campaigns.poster.status.removed.label,
- t.campaigns.poster.status.removed.hint,
),
];
}
diff --git a/lib/features/campaigns/models/posters/poster_detail_model.dart b/lib/features/campaigns/models/posters/poster_detail_model.dart
index 7bcbd5cc..414f1b04 100644
--- a/lib/features/campaigns/models/posters/poster_detail_model.dart
+++ b/lib/features/campaigns/models/posters/poster_detail_model.dart
@@ -15,6 +15,8 @@ enum PosterStatus {
missing,
@JsonValue(400)
removed,
+ @JsonValue(500)
+ toBeMoved,
}
@JsonSerializable()
diff --git a/lib/features/campaigns/models/statistics/campaign_statistics.dart b/lib/features/campaigns/models/statistics/campaign_statistics_model.dart
similarity index 79%
rename from lib/features/campaigns/models/statistics/campaign_statistics.dart
rename to lib/features/campaigns/models/statistics/campaign_statistics_model.dart
index 766cb4b4..1ea65652 100644
--- a/lib/features/campaigns/models/statistics/campaign_statistics.dart
+++ b/lib/features/campaigns/models/statistics/campaign_statistics_model.dart
@@ -1,9 +1,9 @@
import 'package:gruene_app/features/campaigns/models/statistics/campaign_statistics_set.dart';
-class CampaignStatistics {
+class CampaignStatisticsModel {
final CampaignStatisticsSet flyerStats, houseStats, posterStats;
- const CampaignStatistics({
+ const CampaignStatisticsModel({
required this.flyerStats,
required this.houseStats,
required this.posterStats,
diff --git a/lib/features/campaigns/screens/map_consumer.dart b/lib/features/campaigns/screens/map_consumer.dart
index 11d19e0f..365a8cf7 100644
--- a/lib/features/campaigns/screens/map_consumer.dart
+++ b/lib/features/campaigns/screens/map_consumer.dart
@@ -114,22 +114,44 @@ abstract class MapConsumer getPoiDetail,
GetPoiEditWidgetCallback getPoiEdit, {
Size desiredSize = const Size(100, 100),
+ bool useBottomSheet = false,
}) async {
final feature = rawFeature as Map;
final poiId = MapHelper.extractPoiIdFromFeature(feature);
U poi = await getPoi(poiId);
final poiDetailWidget = getPoiDetail(poi);
- var popupWidget = SizedBox(
- height: desiredSize.height,
- width: desiredSize.width,
- child: poiDetailWidget,
- );
- final coord = MapHelper.extractLatLngFromFeature(feature);
- mapController.showMapPopover(
- coord,
- popupWidget,
- () => _editPoi(() => getPoiEdit(poi)),
- desiredSize,
+ if (useBottomSheet) {
+ await mapController.setFocusToMarkerItem(rawFeature);
+ var result = await showDetailBottomSheet(poiDetailWidget);
+ if (result != null && result == ModalDetailResult.edit) {
+ _editPoi(() => getPoiEdit(poi));
+ }
+ await mapController.unsetFocusToMarkerItem();
+ } else {
+ var popupWidget = SizedBox(
+ height: desiredSize.height,
+ width: desiredSize.width,
+ child: poiDetailWidget,
+ );
+ final coord = MapHelper.extractLatLngFromFeature(feature);
+ mapController.showMapPopover(
+ coord,
+ popupWidget,
+ () => _editPoi(() => getPoiEdit(poi)),
+ desiredSize,
+ );
+ }
+ }
+
+ Future showDetailBottomSheet(Widget poiDetailWidget) async {
+ final theme = Theme.of(context);
+ return await showModalBottomSheet(
+ isScrollControlled: false,
+ isDismissible: true,
+ barrierColor: Colors.transparent,
+ context: context,
+ backgroundColor: theme.colorScheme.surface,
+ builder: (context) => poiDetailWidget,
);
}
diff --git a/lib/features/campaigns/screens/poster_detail.dart b/lib/features/campaigns/screens/poster_detail.dart
index 4778d070..b784c884 100644
--- a/lib/features/campaigns/screens/poster_detail.dart
+++ b/lib/features/campaigns/screens/poster_detail.dart
@@ -1,10 +1,14 @@
import 'dart:io';
import 'package:flutter/material.dart';
+import 'package:flutter_svg/flutter_svg.dart';
import 'package:gruene_app/app/services/converters.dart';
+import 'package:gruene_app/app/theme/theme.dart';
import 'package:gruene_app/features/campaigns/helper/campaign_constants.dart';
+import 'package:gruene_app/features/campaigns/helper/enums.dart';
import 'package:gruene_app/features/campaigns/models/posters/poster_detail_model.dart';
-import 'package:gruene_app/features/campaigns/widgets/address_field_detail.dart';
+import 'package:gruene_app/features/campaigns/widgets/close_edit_widget.dart';
+import 'package:gruene_app/i18n/translations.g.dart';
class PosterDetail extends StatelessWidget {
final PosterDetailModel poi;
@@ -13,36 +17,105 @@ class PosterDetail extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return Column(
- children: [
- AddressFieldDetail(
- street: poi.address.street,
- houseNumber: poi.address.houseNumber,
- ),
- Expanded(
- child: FutureBuilder(
- future: Future.delayed(
- Duration.zero,
- () => poi.thumbnailUrl == null ? null : (thumbnailUrl: poi.thumbnailUrl),
+ getStatusColor() {
+ switch (poi.status) {
+ case PosterStatus.ok:
+ return ThemeColors.secondary;
+ case PosterStatus.damaged:
+ case PosterStatus.missing:
+ case PosterStatus.toBeMoved:
+ return Colors.red;
+ case PosterStatus.removed:
+ return ThemeColors.textDisabled;
+ }
+ }
+
+ getStatusText() {
+ return switch (poi.status) {
+ PosterStatus.ok => t.campaigns.poster.status.ok.description,
+ PosterStatus.damaged => t.campaigns.poster.status.damaged.description,
+ PosterStatus.missing => t.campaigns.poster.status.missing.description,
+ PosterStatus.toBeMoved => t.campaigns.poster.status.to_be_moved.description,
+ PosterStatus.removed => t.campaigns.poster.status.removed.description,
+ };
+ }
+
+ var theme = Theme.of(context);
+ return GestureDetector(
+ onTap: () => _closeDialog(context, result: ModalDetailResult.edit),
+ child: SizedBox(
+ height: 250,
+ child: Column(
+ children: [
+ Container(
+ padding: EdgeInsets.all(16),
+ child: CloseEditWidget(
+ onClose: () => _closeDialog(context),
+ onEdit: () => _closeDialog(context, result: ModalDetailResult.edit),
+ ),
+ ),
+ Row(
+ children: [
+ Container(
+ padding: EdgeInsets.only(left: 12, bottom: 12),
+ height: 150,
+ width: 120,
+ child: FutureBuilder(
+ future: Future.delayed(
+ Duration.zero,
+ () => poi.thumbnailUrl == null ? null : (thumbnailUrl: poi.thumbnailUrl),
+ ),
+ builder: (context, snapshot) {
+ if (!snapshot.hasData && !snapshot.hasError) {
+ return Image.asset(CampaignConstants.dummyImageAssetName);
+ }
+ if (snapshot.data!.thumbnailUrl!.isNetworkImageUrl()) {
+ return FadeInImage.assetNetwork(
+ placeholder: CampaignConstants.dummyImageAssetName,
+ image: snapshot.data!.thumbnailUrl!,
+ );
+ } else {
+ return Image.file(
+ File(snapshot.data!.thumbnailUrl!),
+ );
+ }
+ },
+ ),
+ ),
+ SizedBox(width: 12),
+ Text(
+ '${poi.address.street} ${poi.address.houseNumber}\n${poi.address.zipCode} ${poi.address.city}',
+ style: theme.textTheme.labelLarge!.copyWith(color: ThemeColors.text),
+ ),
+ ],
),
- builder: (context, snapshot) {
- if (!snapshot.hasData && !snapshot.hasError) {
- return Image.asset(CampaignConstants.dummyImageAssetName);
- }
- if (snapshot.data!.thumbnailUrl!.isNetworkImageUrl()) {
- return FadeInImage.assetNetwork(
- placeholder: CampaignConstants.dummyImageAssetName,
- image: snapshot.data!.thumbnailUrl!,
- );
- } else {
- return Image.file(
- File(snapshot.data!.thumbnailUrl!),
- );
- }
- },
- ),
+ Expanded(
+ child: Container(
+ color: getStatusColor(),
+ padding: EdgeInsets.symmetric(vertical: 4),
+ child: Row(
+ children: [
+ SizedBox(width: 12),
+ SvgPicture.asset(
+ 'assets/symbols/posters/svg_inverted/poster_${poi.status.name.toLowerCase()}.svg',
+ height: 18,
+ ),
+ SizedBox(width: 12),
+ Text(
+ getStatusText(),
+ style: theme.textTheme.labelLarge!.copyWith(color: ThemeColors.background),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
),
- ],
+ ),
);
}
+
+ void _closeDialog(BuildContext context, {ModalDetailResult result = ModalDetailResult.close}) {
+ Navigator.maybePop(context, result);
+ }
}
diff --git a/lib/features/campaigns/screens/poster_edit.dart b/lib/features/campaigns/screens/poster_edit.dart
index 74d5be88..de02689c 100644
--- a/lib/features/campaigns/screens/poster_edit.dart
+++ b/lib/features/campaigns/screens/poster_edit.dart
@@ -37,8 +37,6 @@ class PosterEdit extends StatefulWidget {
}
class _PosterEditState extends State with AddressExtension, ConfirmDelete {
- Set _segmentedButtonSelection = {};
-
@override
TextEditingController streetTextController = TextEditingController();
@override
@@ -65,8 +63,8 @@ class _PosterEditState extends State with AddressExtension, ConfirmD
void initState() {
setAddress(widget.poster.address);
commentTextController.text = widget.poster.comment;
- if (widget.poster.status != PosterStatus.ok) _segmentedButtonSelection = {widget.poster.status};
_isPhotoDeleted = (widget.poster.imageUrl == null);
+ _selectedPosterStatus = widget.poster.status;
super.initState();
}
@@ -74,7 +72,6 @@ class _PosterEditState extends State with AddressExtension, ConfirmD
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
- final buttonStyle = _getSegmentedButtonStyle(theme);
final currentSize = MediaQuery.of(context).size;
final lightBorderColor = ThemeColors.textLight;
var imageRowHeight = 130.0;
@@ -218,51 +215,16 @@ class _PosterEditState extends State with AddressExtension, ConfirmD
),
Container(
padding: EdgeInsets.symmetric(vertical: 6),
- child: Row(
+ height: 217,
+ child: Column(
children: [
- Expanded(
- child: Column(
- children: [
- Align(
- alignment: Alignment.center,
- child: SegmentedButton(
- multiSelectionEnabled: false,
- emptySelectionAllowed: true,
- showSelectedIcon: false,
- selected: _segmentedButtonSelection,
- onSelectionChanged: (Set newSelection) {
- setState(() {
- _segmentedButtonSelection = newSelection;
- });
- },
- segments: PosterStatusHelper.getPosterStatusOptions.map>(
- ((PosterStatus, String, String) posterStatusContext) {
- return ButtonSegment(
- value: posterStatusContext.$1,
- label: Text(posterStatusContext.$2),
- );
- }).toList(),
- style: buttonStyle,
- ),
- ),
- SizedBox(
- height: 25,
- child: Text(
- _getCurrentPosterStatusHint(),
- style: theme.textTheme.labelMedium!.apply(
- color: ThemeColors.textDisabled,
- ),
- ),
- ),
- ],
- ),
- ),
+ ...PosterStatusHelper.getPosterStatusList.map(_getRadioItem),
],
),
),
Container(
padding: EdgeInsets.symmetric(vertical: 6),
- height: 140,
+ height: 148,
child: Row(
children: [
Expanded(
@@ -375,7 +337,7 @@ class _PosterEditState extends State with AddressExtension, ConfirmD
final updateModel = PosterUpdateModel(
id: widget.poster.id,
address: getAddress(),
- status: _segmentedButtonSelection.isEmpty ? PosterStatus.ok : _segmentedButtonSelection.single,
+ status: _selectedPosterStatus,
comment: commentTextController.text,
removePreviousPhotos: _isPhotoDeleted,
location: widget.poster.location,
@@ -387,54 +349,6 @@ class _PosterEditState extends State with AddressExtension, ConfirmD
_closeDialog(ModalEditResult.save);
}
- ButtonStyle _getSegmentedButtonStyle(ThemeData theme) {
- final WidgetStateProperty segmentedButtonBackgroundColor = WidgetStateProperty.resolveWith(
- (Set states) {
- if (states.contains(WidgetState.selected)) {
- return ThemeColors.secondary;
- }
- return ThemeColors.background;
- },
- );
- final WidgetStateProperty segmentedButtonForegroundColor = WidgetStateProperty.resolveWith(
- (Set states) {
- if (states.contains(WidgetState.selected)) {
- return ThemeColors.background;
- }
- return ThemeColors.text;
- },
- );
-
- final WidgetStateProperty segmentedButtonTextStyle = WidgetStateProperty.resolveWith(
- (Set states) {
- if (states.contains(WidgetState.selected)) {
- return theme.textTheme.labelMedium!.apply(
- color: Colors.red,
- );
- }
- return theme.textTheme.labelMedium;
- },
- );
-
- return ButtonStyle(
- textStyle: segmentedButtonTextStyle, //WidgetStatePropertyAll(theme.textTheme.labelMedium),
- backgroundColor: segmentedButtonBackgroundColor,
- foregroundColor: segmentedButtonForegroundColor,
- side: WidgetStatePropertyAll(BorderSide(color: ThemeColors.secondary)),
- );
- }
-
- String _getCurrentPosterStatusHint() {
- if (_segmentedButtonSelection.isEmpty) return '';
- return _segmentedButtonSelection.map(
- (selected) {
- return PosterStatusHelper.getPosterStatusOptions
- .firstWhere(((PosterStatus, String, String) posterStatusContext) => posterStatusContext.$1 == selected)
- .$3;
- },
- ).join('; ');
- }
-
void _acquireNewPhoto() async {
final photo = await MediaHelper.acquirePhoto(context);
@@ -491,4 +405,24 @@ class _PosterEditState extends State with AddressExtension, ConfirmD
}
bool get _hasPhoto => _currentPhoto != null || (widget.poster.imageUrl != null && !_isPhotoDeleted);
+
+ PosterStatus _selectedPosterStatus = PosterStatus.ok;
+
+ Widget _getRadioItem((PosterStatus, String) item) {
+ var theme = Theme.of(context);
+ return RadioListTile(
+ value: item.$1,
+ groupValue: _selectedPosterStatus,
+ onChanged: (value) => setState(() {
+ _selectedPosterStatus = value!;
+ }),
+ fillColor: WidgetStatePropertyAll(ThemeColors.primary),
+ title: Text(
+ item.$2,
+ style: theme.textTheme.bodyMedium,
+ ),
+ visualDensity: VisualDensity(vertical: VisualDensity.minimumDensity, horizontal: VisualDensity.minimumDensity),
+ materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ );
+ }
}
diff --git a/lib/features/campaigns/screens/posters_screen.dart b/lib/features/campaigns/screens/posters_screen.dart
index 21ac1588..77c581de 100644
--- a/lib/features/campaigns/screens/posters_screen.dart
+++ b/lib/features/campaigns/screens/posters_screen.dart
@@ -152,8 +152,9 @@ class _PostersScreenState extends MapConsumer().campaign.recentStatisticsFetchTimestamp ?? DateTime.now();
return SingleChildScrollView(
child: Container(
@@ -73,7 +73,7 @@ class StatisticsScreen extends StatelessWidget {
);
}
- Widget _getBadgeBox(CampaignStatistics statistics, BuildContext context, ThemeData theme) {
+ Widget _getBadgeBox(CampaignStatisticsModel statistics, BuildContext context, ThemeData theme) {
var mediaQuery = MediaQuery.of(context);
return Container(
padding: EdgeInsets.all(16),
@@ -107,7 +107,7 @@ class StatisticsScreen extends StatelessWidget {
);
}
- List _getBadges(CampaignStatistics statistics, ThemeData theme) {
+ List _getBadges(CampaignStatisticsModel statistics, ThemeData theme) {
return [
_getBadgeRow(t.campaigns.statistic.recorded_doors, statistics.houseStats.own.toInt(), theme),
_getBadgeRow(t.campaigns.statistic.recorded_posters, statistics.posterStats.own.toInt(), theme),
@@ -269,7 +269,7 @@ class StatisticsScreen extends StatelessWidget {
);
}
- Future _loadStatistics() async {
+ Future _loadStatistics() async {
var campaignSettings = GetIt.I().campaign;
if (campaignSettings.recentStatistics != null &&
diff --git a/lib/features/campaigns/widgets/close_edit_widget.dart b/lib/features/campaigns/widgets/close_edit_widget.dart
new file mode 100644
index 00000000..b1bcb0f8
--- /dev/null
+++ b/lib/features/campaigns/widgets/close_edit_widget.dart
@@ -0,0 +1,41 @@
+import 'package:flutter/material.dart';
+import 'package:gruene_app/i18n/translations.g.dart';
+
+class CloseEditWidget extends StatelessWidget {
+ final void Function()? onEdit;
+ final void Function() onClose;
+
+ const CloseEditWidget({
+ super.key,
+ this.onEdit,
+ required this.onClose,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ final theme = Theme.of(context);
+ return Row(
+ children: [
+ GestureDetector(
+ onTap: onClose,
+ child: Icon(Icons.close),
+ ),
+ _getSaveAction(theme),
+ ],
+ );
+ }
+
+ Widget _getSaveAction(ThemeData theme) {
+ if (onEdit == null) return SizedBox.shrink();
+ return Expanded(
+ child: GestureDetector(
+ onTap: onEdit,
+ child: Text(
+ t.common.actions.edit,
+ textAlign: TextAlign.right,
+ style: theme.textTheme.bodyLarge,
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/features/campaigns/widgets/map_container.dart b/lib/features/campaigns/widgets/map_container.dart
index a18038c5..9cfcb5be 100644
--- a/lib/features/campaigns/widgets/map_container.dart
+++ b/lib/features/campaigns/widgets/map_container.dart
@@ -24,6 +24,7 @@ import 'package:gruene_app/features/campaigns/widgets/map_controller.dart';
import 'package:gruene_app/features/campaigns/widgets/map_controller_simplified.dart';
import 'package:gruene_app/i18n/translations.g.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
+import 'package:turf/turf.dart' as turf;
typedef OnMapCreatedCallback = void Function(MapController controller);
typedef AddPOIClickedCallback = void Function(LatLng location);
@@ -96,6 +97,8 @@ class _MapContainerState extends State implements MapController, M
bool _showAddMarker = true;
+ bool _isInFocusMode = false;
+
@override
void didChangeDependencies() {
super.didChangeDependencies();
@@ -115,7 +118,7 @@ class _MapContainerState extends State implements MapController, M
height: 0,
width: 0,
);
- if (popups.isEmpty && _showAddMarker) {
+ if (popups.isEmpty && _showAddMarker & !_isInFocusMode) {
addMarker = Center(
child: Container(
padding: EdgeInsets.only(
@@ -318,7 +321,15 @@ class _MapContainerState extends State implements MapController, M
CampaignConstants.markerLayerName,
const SymbolLayerProperties(
iconImage: ['get', 'status_type'],
- iconSize: 2,
+ iconSize: [
+ Expressions.interpolate,
+ ['linear'],
+ [Expressions.zoom],
+ 11,
+ 1,
+ 16,
+ 2,
+ ],
iconAllowOverlap: true,
),
enableInteraction: false,
@@ -328,6 +339,25 @@ class _MapContainerState extends State implements MapController, M
['has', 'point_count'],
],
);
+
+ // add selected map layers
+ await _controller!.addGeoJsonSource(
+ '${CampaignConstants.markerSourceName}_selected',
+ MarkerItemHelper.transformListToGeoJson([]).toJson(),
+ );
+
+ await _controller!.addSymbolLayer(
+ '${CampaignConstants.markerSourceName}_selected',
+ '${CampaignConstants.markerLayerName}_selected',
+ const SymbolLayerProperties(
+ iconImage: ['get', 'status_type'],
+ iconSize: 3,
+ iconAllowOverlap: true,
+ ),
+ enableInteraction: false,
+ minzoom: minZoomMarkerItems,
+ );
+
// init context layers re-directed to context screens
widget.addMapLayersForContext!(_controller!);
}
@@ -415,6 +445,44 @@ class _MapContainerState extends State implements MapController, M
);
}
+ @override
+ Future setFocusToMarkerItem(Map feature) async {
+ // removes the add_marker
+ setState(() {
+ _isInFocusMode = true;
+ });
+ // align map to show feature in center area
+ final coord = MapHelper.extractLatLngFromFeature(feature);
+ await moveMapIfItemIsOnBorder(coord, Size(150, 150));
+ // set opacity of marker layer
+ await _controller!.setLayerProperties(
+ CampaignConstants.markerLayerName,
+ SymbolLayerProperties(iconOpacity: 0.2),
+ );
+ // set data for '_selected layer'
+ var featureObject = turf.Feature.fromJson(feature);
+ turf.FeatureCollection collection = turf.FeatureCollection(features: [featureObject]);
+ await _controller!.setGeoJsonSource(
+ '${CampaignConstants.markerSourceName}_selected',
+ collection.toJson(),
+ );
+ }
+
+ @override
+ Future unsetFocusToMarkerItem() async {
+ setState(() {
+ _isInFocusMode = false;
+ });
+ await _controller!.setLayerProperties(
+ CampaignConstants.markerLayerName,
+ SymbolLayerProperties(iconOpacity: 1),
+ );
+ await _controller!.setGeoJsonSource(
+ '${CampaignConstants.markerSourceName}_selected',
+ turf.FeatureCollection(features: []).toJson(),
+ );
+ }
+
Future moveMapIfItemIsOnBorder(LatLng itemCoordinate, Size desiredSize) async {
final mediaQuery = MediaQuery.of(context);
final currentSize = mediaQuery.size;
diff --git a/lib/features/campaigns/widgets/map_controller.dart b/lib/features/campaigns/widgets/map_controller.dart
index 464245ed..72e505fc 100644
--- a/lib/features/campaigns/widgets/map_controller.dart
+++ b/lib/features/campaigns/widgets/map_controller.dart
@@ -43,4 +43,7 @@ abstract class MapController {
void toggleInfoForMissingMapFeatures(bool enable);
void navigateMapTo(LatLng location);
+
+ Future setFocusToMarkerItem(Map feature);
+ Future unsetFocusToMarkerItem();
}
diff --git a/lib/i18n/app_de.json b/lib/i18n/app_de.json
index facd9b07..e00bcf1a 100644
--- a/lib/i18n/app_de.json
+++ b/lib/i18n/app_de.json
@@ -64,16 +64,24 @@
"info_poster_guidelines": "Wusstest Du, dass es je nach Region unterschiedliche gesetzliche Vorgaben und Regeln für das Aufhängen von Plakaten gibt? Bitte wende Dich an Deinen Orts- oder Kreisverband für weitere Informationen, bevor Du Plakate aufhängst!",
"status": {
"damaged": {
- "label": "Beschädigt",
- "hint": "Durch Fremdeinwirkung beschädigt"
+ "label": "Beschädigt (Als zu ersetzen melden)",
+ "description": "Plakat ist beschädigt, bitte ersetzen!"
},
"missing": {
"label": "Verschollen",
- "hint": "Durch Fremdeinwirkung entfernt"
+ "description": "Plakat ist verschollen, bitte ersetzen!"
},
"removed": {
"label": "Abgehängt",
- "hint": "Dauerhaft entfernt"
+ "description": "Plakat wurde abgehängt."
+ },
+ "to_be_moved": {
+ "label": "Umzuhängen",
+ "description": "Plakat hängt falsch, bitte umhängen!"
+ },
+ "ok": {
+ "label": "Plakat hängt",
+ "description": "Plakat hängt, kein Handlungsbedarf."
}
},
"comment": {
diff --git a/pubspec.yaml b/pubspec.yaml
index 643810d6..e2446dcb 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -94,6 +94,7 @@ flutter:
- assets/graphics/placeholders/
- assets/symbols/
- assets/symbols/posters/
+ - assets/symbols/posters/svg_inverted/
- assets/symbols/doors/
- assets/symbols/flyer/
- assets/maps/
diff --git a/swaggers/gruene-api.yaml b/swaggers/gruene-api.yaml
index 6c767d53..c1768980 100644
--- a/swaggers/gruene-api.yaml
+++ b/swaggers/gruene-api.yaml
@@ -331,7 +331,6 @@ paths:
security:
- api_key: []
- bearer: []
- - oauth2: []
post:
operationId: createProfile
summary: Create user profile
@@ -356,7 +355,6 @@ paths:
security:
- api_key: []
- bearer: []
- - oauth2: []
/v1/profiles/self:
get:
operationId: getOwnProfile
@@ -378,7 +376,6 @@ paths:
security:
- api_key: []
- bearer: []
- - oauth2: []
/v1/profiles/{profileId}:
get:
operationId: getProfile
@@ -405,7 +402,6 @@ paths:
security:
- api_key: []
- bearer: []
- - oauth2: []
put:
operationId: updateProfile
summary: Update user profile
@@ -442,7 +438,6 @@ paths:
security:
- api_key: []
- bearer: []
- - oauth2: []
delete:
operationId: deleteProfile
summary: Delete user profile
@@ -468,7 +463,6 @@ paths:
security:
- api_key: []
- bearer: []
- - oauth2: []
/v1/profiles/{profileId}/image:
put:
operationId: updateProfileImage
@@ -482,7 +476,8 @@ paths:
- profileImage
properties:
profileImage:
- type: file
+ type: string
+ format: binary
parameters:
- name: profileId
required: true
@@ -505,7 +500,6 @@ paths:
security:
- api_key: []
- bearer: []
- - oauth2: []
delete:
operationId: deleteProfileImage
summary: Delete user profile image
@@ -531,7 +525,6 @@ paths:
security:
- api_key: []
- bearer: []
- - oauth2: []
/v1/profile-tags:
get:
operationId: findProfileTags
@@ -918,7 +911,6 @@ paths:
- campaigns
security:
- bearer: []
- - oauth2: []
get:
operationId: findAreas
summary: Find Areas
@@ -942,7 +934,6 @@ paths:
- campaigns
security:
- bearer: []
- - oauth2: []
/v1/campaigns/areas/self:
get:
operationId: findOwnAreas
@@ -967,7 +958,6 @@ paths:
- campaigns
security:
- bearer: []
- - oauth2: []
/v1/campaigns/areas/{areaId}:
get:
operationId: getArea
@@ -993,7 +983,6 @@ paths:
- campaigns
security:
- bearer: []
- - oauth2: []
put:
operationId: updateArea
summary: Update an Area
@@ -1024,7 +1013,6 @@ paths:
- campaigns
security:
- bearer: []
- - oauth2: []
delete:
operationId: deleteArea
summary: Delete an Area
@@ -1049,7 +1037,6 @@ paths:
- campaigns
security:
- bearer: []
- - oauth2: []
/v1/campaigns/polling-stations:
post:
operationId: createPollingStation
@@ -1205,7 +1192,6 @@ paths:
- campaigns
security:
- bearer: []
- - oauth2: []
get:
operationId: findPois
summary: Find POIs
@@ -1240,7 +1226,6 @@ paths:
- campaigns
security:
- bearer: []
- - oauth2: []
/v1/campaigns/pois/self:
get:
operationId: findOwnPois
@@ -1276,7 +1261,6 @@ paths:
- campaigns
security:
- bearer: []
- - oauth2: []
/v1/campaigns/pois/{poiId}:
get:
operationId: getPoi
@@ -1302,7 +1286,6 @@ paths:
- campaigns
security:
- bearer: []
- - oauth2: []
put:
operationId: updatePoi
summary: Update a POI
@@ -1333,7 +1316,6 @@ paths:
- campaigns
security:
- bearer: []
- - oauth2: []
delete:
operationId: deletePoi
summary: Delete a POI
@@ -1358,7 +1340,6 @@ paths:
- campaigns
security:
- bearer: []
- - oauth2: []
/v1/campaigns/pois/{poiId}/photos:
post:
operationId: addPoiPhoto
@@ -1372,7 +1353,8 @@ paths:
- image
properties:
image:
- type: file
+ type: string
+ format: binary
parameters:
- name: poiId
required: true
@@ -1394,7 +1376,6 @@ paths:
- campaigns
security:
- bearer: []
- - oauth2: []
/v1/campaigns/pois/{poiId}/photos/{photoId}:
delete:
operationId: deletePoiPhoto
@@ -1425,7 +1406,6 @@ paths:
- campaigns
security:
- bearer: []
- - oauth2: []
/v1/campaigns/routes:
post:
operationId: createRoute
@@ -1592,7 +1572,6 @@ paths:
- campaigns
security:
- bearer: []
- - oauth2: []
get:
operationId: findExperienceAreas
summary: Find ExperienceAreas
@@ -1616,7 +1595,6 @@ paths:
- campaigns
security:
- bearer: []
- - oauth2: []
/v1/campaigns/experience-areas/{experienceAreaId}:
get:
operationId: getExperienceArea
@@ -1642,7 +1620,6 @@ paths:
- campaigns
security:
- bearer: []
- - oauth2: []
put:
operationId: updateExperienceArea
summary: Update a ExperienceArea
@@ -1673,7 +1650,6 @@ paths:
- campaigns
security:
- bearer: []
- - oauth2: []
delete:
operationId: deleteExperienceArea
summary: Delete a ExperienceArea
@@ -1698,7 +1674,6 @@ paths:
- campaigns
security:
- bearer: []
- - oauth2: []
/v1/campaigns/focus-areas:
post:
operationId: createFocusArea
@@ -1723,7 +1698,6 @@ paths:
- campaigns
security:
- bearer: []
- - oauth2: []
get:
operationId: findFocusAreas
summary: Find FocusAreas
@@ -1747,7 +1721,6 @@ paths:
- campaigns
security:
- bearer: []
- - oauth2: []
/v1/campaigns/focus-areas/{focusAreaId}:
get:
operationId: getFocusArea
@@ -1773,7 +1746,6 @@ paths:
- campaigns
security:
- bearer: []
- - oauth2: []
put:
operationId: updateFocusArea
summary: Update a FocusArea
@@ -1804,7 +1776,6 @@ paths:
- campaigns
security:
- bearer: []
- - oauth2: []
delete:
operationId: deleteFocusArea
summary: Delete a FocusArea
@@ -1829,11 +1800,10 @@ paths:
- campaigns
security:
- bearer: []
- - oauth2: []
/v1/campaigns/statistics:
get:
- operationId: getStatistics
- summary: Get statistics
+ operationId: getCampaignStatistics
+ summary: Get campaign statistics
parameters: []
responses:
'200':
@@ -1841,7 +1811,7 @@ paths:
content:
application/json:
schema:
- $ref: '#/components/schemas/Statistics'
+ $ref: '#/components/schemas/CampaignStatistics'
'401':
description: ''
tags:
@@ -1899,7 +1869,6 @@ paths:
- news
security:
- bearer: []
- - oauth2: []
/v1/news/{newsId}:
get:
operationId: getNews
@@ -1925,7 +1894,31 @@ paths:
- news
security:
- bearer: []
- - oauth2: []
+ /v1/client-info:
+ get:
+ operationId: ClientInfoController_getClientInfo
+ parameters: []
+ responses:
+ '200':
+ description: ''
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ClientInfo'
+ /v1/gnetz-applications:
+ get:
+ operationId: findGnetzApplications
+ summary: Find GNetz Applications
+ parameters: []
+ responses:
+ '200':
+ description: ''
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/FindGnetzApplicationsResponse'
+ tags:
+ - gnetz-applications
/health:
get:
tags:
@@ -2427,14 +2420,17 @@ components:
items:
type: string
purposes:
- description: Purposes associated with email
- examples:
- - - privat
- - - grüne
- - - dienstlich
- - - Postanschrift
- - - Rechnungsanschrift
- - - Lieferanschrift
+ description: |-
+ Purposes associated with email
+ Occuring values:
+ - privat
+ - grüne
+ - dienstlich
+ - Postanschrift
+ - Rechnungsanschrift
+ - Lieferanschrift
+ example:
+ - privat
type: array
items:
type: string
@@ -2623,6 +2619,19 @@ components:
- tags
- roles
- achievements
+ OffsetPaginationMeta:
+ type: object
+ properties:
+ count:
+ type: number
+ total:
+ type: number
+ offset:
+ type: number
+ limit:
+ type: number
+ hasNext:
+ type: boolean
FindProfilesResponse:
type: object
properties:
@@ -2631,20 +2640,7 @@ components:
items:
$ref: '#/components/schemas/PublicProfile'
meta:
- type: object
- properties:
- count:
- required: true
- type: number
- offset:
- required: true
- type: number
- limit:
- required: true
- type: number
- hasNext:
- required: true
- type: boolean
+ $ref: '#/components/schemas/OffsetPaginationMeta'
required:
- data
- meta
@@ -2871,20 +2867,7 @@ components:
items:
$ref: '#/components/schemas/ProfileTag'
meta:
- type: object
- properties:
- count:
- required: true
- type: number
- total:
- required: true
- type: number
- offset:
- required: true
- type: number
- limit:
- required: true
- type: number
+ $ref: '#/components/schemas/OffsetPaginationMeta'
required:
- data
- meta
@@ -2906,6 +2889,11 @@ components:
required:
- id
- username
+ KeysetPaginationMeta:
+ type: object
+ properties:
+ cursorNext:
+ type: string
FindOffboardingUsersResponse:
type: object
properties:
@@ -2914,11 +2902,7 @@ components:
items:
$ref: '#/components/schemas/OffboardingUserInfo'
meta:
- type: object
- properties:
- cursorNext:
- required: false
- type: string
+ $ref: '#/components/schemas/KeysetPaginationMeta'
required:
- data
- meta
@@ -2953,20 +2937,7 @@ components:
items:
$ref: '#/components/schemas/Division'
meta:
- type: object
- properties:
- count:
- required: true
- type: number
- total:
- required: true
- type: number
- offset:
- required: true
- type: number
- limit:
- required: true
- type: number
+ $ref: '#/components/schemas/OffsetPaginationMeta'
required:
- data
- meta
@@ -3107,20 +3078,7 @@ components:
items:
$ref: '#/components/schemas/Role'
meta:
- type: object
- properties:
- count:
- required: true
- type: number
- total:
- required: true
- type: number
- offset:
- required: true
- type: number
- limit:
- required: true
- type: number
+ $ref: '#/components/schemas/OffsetPaginationMeta'
required:
- data
- meta
@@ -3132,20 +3090,7 @@ components:
items:
$ref: '#/components/schemas/RoleTag'
meta:
- type: object
- properties:
- count:
- required: true
- type: number
- total:
- required: true
- type: number
- offset:
- required: true
- type: number
- limit:
- required: true
- type: number
+ $ref: '#/components/schemas/OffsetPaginationMeta'
required:
- data
- meta
@@ -3157,20 +3102,7 @@ components:
items:
$ref: '#/components/schemas/RoleCategory'
meta:
- type: object
- properties:
- count:
- required: true
- type: number
- total:
- required: true
- type: number
- offset:
- required: true
- type: number
- limit:
- required: true
- type: number
+ $ref: '#/components/schemas/OffsetPaginationMeta'
required:
- data
- meta
@@ -3200,27 +3132,26 @@ components:
Polygon:
type: object
properties:
- type:
- type: string
- description: Type of the polygon
- example: Polygon
- default: Polygon
coordinates:
type: array
+ description: |-
+ Coordinates of the polygon
+ Must follow the GeoJSON standard
items:
- name: coordinates
type: array
items:
- required: true
- description: |-
- Coordinates of the polygon
- Must follow the GeoJSON standard
type: array
items:
type: number
+ format: double
+ type:
+ type: string
+ description: Type of the polygon
+ example: Polygon
+ default: Polygon
required:
- - type
- coordinates
+ - type
UpdateArea:
type: object
properties:
@@ -3424,6 +3355,7 @@ components:
- DAMAGED
- REMOVED
- MISSING
+ - TO_BE_MOVED
type: string
comment:
type: string
@@ -3851,7 +3783,7 @@ components:
- division
- state
- germany
- Statistics:
+ CampaignStatistics:
type: object
properties:
poster:
@@ -3932,6 +3864,122 @@ components:
$ref: '#/components/schemas/News'
required:
- data
+ ClientInfo:
+ type: object
+ properties:
+ clientIp:
+ type: string
+ clientIpVersion:
+ enum:
+ - ivp4
+ - ipv6
+ type: string
+ required:
+ - clientIp
+ - clientIpVersion
+ GnetzApplicationCategory:
+ type: object
+ properties:
+ slug:
+ type: string
+ description: Category slug
+ example: beteiligung
+ title:
+ type: string
+ description: Category title
+ example: Beteiligung
+ order:
+ type: number
+ description: The category order used for display purposes
+ example: 1
+ required:
+ - slug
+ - title
+ - order
+ GnetzApplication:
+ type: object
+ properties:
+ shortDescription:
+ type: object
+ additionalProperties:
+ type: string
+ example:
+ de_DE: Beschreibung in Deutsch
+ en_US: description in english
+ description:
+ type: object
+ additionalProperties:
+ type: string
+ example:
+ de_DE: Beschreibung in Deutsch
+ en_US: description in english
+ slug:
+ type: string
+ description: Application slug
+ example: wolke
+ url:
+ type: string
+ nullable: true
+ description: URL to the application
+ example: https://wolke.netzbegruenung.de
+ title:
+ type: string
+ description: Application title
+ example: Wolke
+ documentation:
+ type: string
+ nullable: true
+ description: URL to the documentation
+ example: https://netz.gruene.de/de/wissenswerk/2024-08/wolke
+ googleStore:
+ type: string
+ nullable: true
+ description: URL to google play store
+ example: https://play.google.com/store/apps/details?id=com.nextcloud.client
+ appleStore:
+ type: string
+ nullable: true
+ description: URL to apple store
+ example: https://apps.apple.com/us/app/nextcloud/id1125420102
+ supportMail:
+ type: string
+ nullable: true
+ description: support email
+ example: support@example.com
+ icon:
+ type: string
+ nullable: true
+ description: icon as svg
+ example: >-
+
+ categories:
+ description: Application categories
+ type: array
+ items:
+ $ref: '#/components/schemas/GnetzApplicationCategory'
+ required:
+ - shortDescription
+ - description
+ - slug
+ - url
+ - title
+ - documentation
+ - googleStore
+ - appleStore
+ - supportMail
+ - icon
+ - categories
+ FindGnetzApplicationsResponse:
+ type: object
+ properties:
+ data:
+ type: array
+ items:
+ $ref: '#/components/schemas/GnetzApplication'
+ required:
+ - data
HealthCheckResponse:
type: object
properties: