diff --git a/lib/class/abstract_Place.dart b/lib/class/abstract_Place.dart index cffc2038..237714e9 100644 --- a/lib/class/abstract_Place.dart +++ b/lib/class/abstract_Place.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:foss_warn/class/class_notificationPreferences.dart'; +import 'package:foss_warn/enums/WarningSource.dart'; import 'package:foss_warn/services/saveAndLoadSharedPreferences.dart'; import 'package:provider/provider.dart'; +import '../enums/Severity.dart'; import '../main.dart'; -import '../services/listHandler.dart'; import '../services/updateProvider.dart'; import 'class_NotificationService.dart'; import 'class_WarnMessage.dart'; @@ -13,17 +15,23 @@ abstract class Place { List _warnings = []; String eTag = ""; - Place({required String name, required List warnings, required String eTag}) : _warnings = warnings, _name = name { + Place( + {required String name, + required List warnings, + required String eTag}) + : _warnings = warnings, + _name = name { eTag = eTag; } String get name => _name; - int get countWarnings=> this.warnings.length; + int get countWarnings => this.warnings.length; List get warnings => _warnings; // control the list for warnings void addWarningToList(WarnMessage warnMessage) => _warnings.add(warnMessage); - void removeWarningFromList(WarnMessage warnMessage) => _warnings.remove(warnMessage); + void removeWarningFromList(WarnMessage warnMessage) => + _warnings.remove(warnMessage); // check if all warnings in `warnings` are // also in the alreadyReadWarnings list @@ -43,7 +51,7 @@ abstract class Place { bool checkIfThereIsAWarningToNotify() { for (WarnMessage myWarning in _warnings) { if (!myWarning.notified && - notificationSettingsImportance.contains(myWarning.severity)) { + _checkIfEventShouldBeNotified(myWarning.source, myWarning.severity)) { // there is min. one warning without notification return true; } @@ -55,13 +63,16 @@ abstract class Place { Future sendNotificationForWarnings() async { for (WarnMessage myWarnMessage in _warnings) { print(myWarnMessage.headline); - print("Read: " + myWarnMessage.read.toString() + " notified " + myWarnMessage.notified.toString()); - print("should notify? :" + - ((!myWarnMessage.read && !myWarnMessage.notified) && - _checkIfEventShouldBeNotified(myWarnMessage.event)) - .toString()); + //print("Read: " + myWarnMessage.read.toString() + " notified " + myWarnMessage.notified.toString()); + /*print("should notify? :" + + (_checkIfEventShouldBeNotified( + myWarnMessage.source, myWarnMessage.severity)) + .toString());c*/ + //(!myWarnMessage.read && !myWarnMessage.notified) && + if ((!myWarnMessage.read && !myWarnMessage.notified) && - _checkIfEventShouldBeNotified(myWarnMessage.event)) { + _checkIfEventShouldBeNotified( + myWarnMessage.source, myWarnMessage.severity)) { // Alert is not already read or shown as notification // set notified to true to avoid sending notification twice myWarnMessage.notified = true; @@ -86,6 +97,7 @@ abstract class Place { void markAllWarningsAsRead(BuildContext context) { for (WarnMessage myWarnMessage in _warnings) { myWarnMessage.read = true; + NotificationService.cancelOneNotification(myWarnMessage.identifier.hashCode); } final updater = Provider.of(context, listen: false); updater.updateReadStatusInList(); @@ -94,7 +106,7 @@ abstract class Place { /// set the read and notified status from all warnings to false /// used for debug purpose - /// @context to update view + /// [@context] to update view void resetReadAndNotificationStatusForAllWarnings(BuildContext context) { for (WarnMessage myWarnMessage in _warnings) { myWarnMessage.read = false; @@ -105,18 +117,25 @@ abstract class Place { saveMyPlacesList(); } - /// return [true] or false if the warning should be irgnored or not - /// The event could be listed in the map notificationEventsSettings. - /// if it is listed in the map, return the stored value for the event - /// If not return as default true - bool _checkIfEventShouldBeNotified(String event) { - if (userPreferences.notificationEventsSettings[event] != null) { - print(event + " " + userPreferences.notificationEventsSettings[event]!.toString()); - return userPreferences.notificationEventsSettings[event]!; - } else { - return true; - } - } + /// Return [true] if the user wants a notification - [false] if not. + /// + /// The source should be listed in the List notificationSourceSettings. + /// check if the user wants to be notified for + /// the given source and the given severity + /// + /// example: + /// + /// Warning severity | Notification setting | notification?
+ /// Moderate (2) | Minor (3) | 3 >= 2 => true
+ /// Minor (3) | Moderate (2) | 2 >= 3 => false + bool _checkIfEventShouldBeNotified(WarningSource source, Severity severity) { + NotificationPreferences notificationSourceSetting = userPreferences + .notificationSourceSettings + .firstWhere((element) => element.warningSource == source); - Map toJson(); + return notificationSourceSetting.disabled == false && + Severity.getIndexFromSeverity( + notificationSourceSetting.notificationLevel) >= + Severity.getIndexFromSeverity(severity); + } } diff --git a/lib/class/class_NotificationService.dart b/lib/class/class_NotificationService.dart index fc9cfb65..02d92973 100644 --- a/lib/class/class_NotificationService.dart +++ b/lib/class/class_NotificationService.dart @@ -2,6 +2,7 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:foss_warn/services/translateAndColorizeWarning.dart'; import 'package:rxdart/rxdart.dart'; import 'package:flutter/material.dart'; + /// /// ID 2: Status notification /// ID 3: No Places selected warning @@ -14,16 +15,13 @@ class NotificationService { static Future _notificationsDetails(String channel) async { return NotificationDetails( android: AndroidNotificationDetails( - 'foss_warn_notifications_' + channel.trim().toLowerCase(), + 'de.nucleus.foss_warn.notifications_' + channel.trim().toLowerCase(), "Warnstufe: " + translateWarningSeverity(channel), - channelDescription: - 'FOSS Warn notifications for ' + channel.trim().toLowerCase(), groupKey: "FossWarnWarnings", - category: AndroidNotificationCategory.alarm, - importance: Importance.max, + category: AndroidNotificationCategory.message, priority: Priority.max, - //enable multiline notification + // enable multiline notification styleInformation: BigTextStyleInformation(''), color: Colors.red, // makes the icon red, ledColor: Colors.red, @@ -36,7 +34,7 @@ class NotificationService { static Future _statusNotificationsDetails() async { return NotificationDetails( android: AndroidNotificationDetails( - 'foss_warn_status', + 'de.nucleus.foss_warn.notifications_state', 'Statusanzeige', channelDescription: 'Status der Hintergrund Updates', groupKey: "FossWarnService", @@ -141,21 +139,74 @@ class NotificationService { // init the different notifications channels try { - await androidNotificationPlugin.createNotificationChannel( - AndroidNotificationChannel( - "foss_warn_notifications_minor", "Warnstufe: Gering")); + await androidNotificationPlugin.createNotificationChannelGroup( + AndroidNotificationChannelGroup( + "de.nucleus.foss_warn.notifications_emergency_information", + "Gefahreninformationen", + description: "Benachrichtigungen zu Gefahrenmeldungen")); + + await androidNotificationPlugin.createNotificationChannelGroup( + AndroidNotificationChannelGroup( + "de.nucleus.foss_warn.notifications_other", "Sonstiges", + description: "Sonstige Benachrichtigungen")); + + await androidNotificationPlugin + .createNotificationChannel(AndroidNotificationChannel( + "de.nucleus.foss_warn.notifications_minor", + "Warnstufe: Gering", + description: + "Warnung vor einer Beeinträchtigung des normalen Tagesablaufs.", + groupId: "de.nucleus.foss_warn.notifications_emergency_information", + importance: Importance.max, + )); - await androidNotificationPlugin.createNotificationChannel( - AndroidNotificationChannel( - "foss_warn_notifications_moderate", "Warnstufe: Mittel")); + await androidNotificationPlugin + .createNotificationChannel(AndroidNotificationChannel( + "de.nucleus.foss_warn.notifications_moderate", + "Warnstufe: Moderat", + description: + "Eine Warnung vor einer starken Beeinträchtigung des normalen Tagesablaufs.", + groupId: "de.nucleus.foss_warn.notifications_emergency_information", + importance: Importance.max, + )); + + await androidNotificationPlugin + .createNotificationChannel(AndroidNotificationChannel( + "de.nucleus.foss_warn.notifications_severe", + "Warnstufe: Schwer", + description: + "Eine Warnung vor einer Gefahr, die ihre Gesundheit, ihr Eigentum und/oder öffentliche Infrastruktur beeinträchtigen kann.", + groupId: "de.nucleus.foss_warn.notifications_emergency_information", + importance: Importance.max, + )); - await androidNotificationPlugin.createNotificationChannel( - AndroidNotificationChannel( - "foss_warn_notifications_severe", "Warnstufe: Schwer")); + await androidNotificationPlugin + .createNotificationChannel(AndroidNotificationChannel( + "de.nucleus.foss_warn.notifications_extreme", + "Warnstufe: Extrem", + description: + "Eine Warnung vor einer Gefahr, die sich kurzfristig signifikant auf ihre Gesundheit, ihr Eigentum und/oder öffentliche Infrastruktur auswirken kann.", + groupId: "de.nucleus.foss_warn.notifications_emergency_information", + importance: Importance.max, + )); + + await androidNotificationPlugin + .createNotificationChannel(AndroidNotificationChannel( + "de.nucleus.foss_warn.notifications_state", + "Statusanzeige", + description: "Zeit den aktuellen Status der Hintergrundupdates an.", + groupId: "de.nucleus.foss_warn.notifications_other", + importance: Importance.low, + )); - await androidNotificationPlugin.createNotificationChannel( - AndroidNotificationChannel( - "foss_warn_notifications_extreme", "Warnstufe: Extrem")); + await androidNotificationPlugin + .createNotificationChannel(AndroidNotificationChannel( + "de.nucleus.foss_warn.notifications_other", + "Sonstiges", + description: "Sonstige Benachrichtigungen", + groupId: "de.nucleus.foss_warn.notifications_other", + importance: Importance.defaultImportance, + )); } catch (e) { print("Error while creating notification channels: " + e.toString()); } @@ -179,12 +230,12 @@ class NotificationService { Future cleanUpNotificationChannels() async { List channelIds = []; - channelIds.add("foss_warn_notifications_minor"); - channelIds.add("foss_warn_notifications_severe"); - channelIds.add("foss_warn_notifications_moderate"); - channelIds.add("foss_warn_notifications_extreme"); - channelIds.add("foss_warn_status"); - channelIds.add("foss_warn_notifications_other"); + channelIds.add("de.nucleus.foss_warn.notifications_minor"); + channelIds.add("de.nucleus.foss_warn.notifications_moderate"); + channelIds.add("de.nucleus.foss_warn.notifications_severe"); + channelIds.add("de.nucleus.foss_warn.notifications_extreme"); + channelIds.add("de.nucleus.foss_warn.notifications_state"); + channelIds.add("de.nucleus.foss_warn.notifications_other"); print("[android notification channels]"); List? temp = @@ -229,7 +280,7 @@ class NotificationService { if (activeNotifications!.length == 2 && activeNotifications - .any((element) => element.channelId == "foss_warn_status")) { + .any((element) => element.channelId == "de.nucleus.foss_warn.notifications_state")) { if (activeNotifications[0].id == 0) { // summery notification has id 0 cancelOneNotification(0); diff --git a/lib/class/class_WarnMessage.dart b/lib/class/class_WarnMessage.dart index d5938393..9e5c86e9 100644 --- a/lib/class/class_WarnMessage.dart +++ b/lib/class/class_WarnMessage.dart @@ -1,3 +1,5 @@ +import 'package:foss_warn/enums/WarningSource.dart'; + import '../enums/Certainty.dart'; import '../enums/Severity.dart'; import 'class_Area.dart'; @@ -6,7 +8,7 @@ import '../services/createAreaListFromJson.dart'; class WarnMessage { final String identifier; final String publisher; - final String source; + final WarningSource source; final String sender; final String sent; final String status; @@ -62,7 +64,7 @@ class WarnMessage { return WarnMessage( identifier: json['identifier'], publisher: json['publisher'], - source: json['source'], + source: WarningSource.fromString(json['source'].toString()), sender: json['sender'], sent: json['sent'], status: json['status'], @@ -94,7 +96,7 @@ class WarnMessage { String publisher, List areaList) { print("Neue WarnMessage wird angelegt..."); return WarnMessage( - source: provider, + source: WarningSource.fromString(provider), identifier: json["identifier"] ?? "?", sender: json["sender"] ?? "?", sent: json["sent"] ?? "?", @@ -104,7 +106,7 @@ class WarnMessage { category: json["info"][0]["category"][0] ?? "?", event: json["info"][0]["event"] ?? "?", urgency: json["info"][0]["urgency"] ?? "?", - severity: getSeverity(json["info"][0]["severity"].toString().toLowerCase()), + severity: Severity.fromString(json["info"][0]["severity"].toString().toLowerCase()), certainty: getCertainty(json["info"][0]["certainty"].toString().toLowerCase()), effective: json["info"][0]["effective"] ?? "", onset: json["info"][0]["onset"] ?? "", @@ -126,7 +128,7 @@ class WarnMessage { factory WarnMessage.fromJsonAlertSwiss(Map json, List areaList, String instructions, String license) { return WarnMessage( - source: "Alert Swiss", + source: WarningSource.alertSwiss, identifier: json["identifier"] ?? "?", sender: json["sender"] ?? "?", sent: json["sent"] ?? "?", @@ -136,16 +138,16 @@ class WarnMessage { category: json["event"] ?? "?", // missing event: json["event"] ?? "?", urgency: "?", - severity: getSeverity(json["severity"]), + severity: Severity.fromString(json["severity"]), certainty: getCertainty(""), // missing effective: "", // missing onset: json["onset"] ?? "", // m expires: json["expires"] ?? "", // m - headline: json["title"] ?? "?", - description: json["description"] ?? "", + headline: json["title"]["title"] ?? "?", + description: json["description"]["description"] ?? "", instruction: instructions, publisher: license, - contact: json["contact"] ?? "", + contact: json["contact"]["contact"] ?? "", web: json["link"] ?? "", areaList: areaList, notified: false, diff --git a/lib/class/class_alarmManager.dart b/lib/class/class_alarmManager.dart index cfa1166e..fae5036c 100644 --- a/lib/class/class_alarmManager.dart +++ b/lib/class/class_alarmManager.dart @@ -22,7 +22,7 @@ class AlarmManager { final int isolateId = Isolate.current.hashCode; print("[$now] Call APIs! isolate=$isolateId function='$callback'"); - await checkForMyPlacesWarnings(true, true); + await checkForMyPlacesWarnings(true); print("Call APIs executed"); } diff --git a/lib/class/class_notificationPreferences.dart b/lib/class/class_notificationPreferences.dart new file mode 100644 index 00000000..4854035b --- /dev/null +++ b/lib/class/class_notificationPreferences.dart @@ -0,0 +1,26 @@ +import 'package:foss_warn/enums/Severity.dart'; +import 'package:foss_warn/enums/WarningSource.dart'; + +/// to store the chosen notificationLevel for a warningSource +class NotificationPreferences { + Severity notificationLevel; + bool disabled; + WarningSource warningSource; + + NotificationPreferences( + {required this.warningSource, required this.notificationLevel, this.disabled = false}); + + factory NotificationPreferences.fromJson(Map json) { + return NotificationPreferences( + warningSource: WarningSource.fromString(json['warningSource'].toString()), + disabled: json['disabled'], + notificationLevel: + Severity.fromJson(json['notificationLevel'])); + } + + Map toJson() => { + 'notificationLevel': notificationLevel.toJson(), + 'disabled': disabled, + 'warningSource': warningSource.toJson(), + }; +} diff --git a/lib/class/class_userPreferences.dart b/lib/class/class_userPreferences.dart index 3c89f426..c932d7a4 100644 --- a/lib/class/class_userPreferences.dart +++ b/lib/class/class_userPreferences.dart @@ -1,19 +1,49 @@ import 'package:flutter/material.dart'; import 'package:foss_warn/themes/themes.dart'; +import '../enums/Severity.dart'; +import '../enums/WarningSource.dart'; +import 'class_notificationPreferences.dart'; + /// handle user preferences. The values written here are default values /// the correct values are loaded in loadSettings() from sharedPreferences class UserPreferences { + @deprecated bool notificationWithExtreme = true; + @deprecated bool notificationWithSevere = true; + @deprecated bool notificationWithModerate = true; + @deprecated bool notificationWithMinor = false; bool shouldNotifyGeneral = true; bool showStatusNotification = true; + @deprecated Map notificationEventsSettings = new Map(); + // to save the user settings for which source + // the user would like to be notified + List notificationSourceSettings = _getDefaultValueForNotificationSourceSettings(); + + static List _getDefaultValueForNotificationSourceSettings() { + List temp = []; + + for(WarningSource source in WarningSource.values) { + if(source == WarningSource.dwd || source == WarningSource.lhp) { + temp.add(NotificationPreferences( + warningSource: source, + notificationLevel: Severity.severe)); + } else { + temp.add(NotificationPreferences( + warningSource: source, + notificationLevel: Severity.minor)); + } + } + + return temp; + } bool showExtendedMetaData = false; // show more tags in WarningDetailView ThemeMode selectedThemeMode = ThemeMode.system; @@ -29,7 +59,7 @@ class UserPreferences { bool showAllWarnings = false; bool areWarningsFromCache = false; - String versionNumber = "0.6.2"; // shown in the about view + String versionNumber = "0.7.0"; // shown in the about view bool activateAlertSwiss = false; bool isFirstStart = true; diff --git a/lib/enums/Severity.dart b/lib/enums/Severity.dart index b5fc9e15..2dd73465 100644 --- a/lib/enums/Severity.dart +++ b/lib/enums/Severity.dart @@ -1,20 +1,35 @@ enum Severity { - minor, - moderate, extreme, severe, - other; + moderate, + minor; String toJson() => name; static Severity fromJson(String json) => values.byName(json); -} -/// extract the severity from the string and return the corresponding enum -Severity getSeverity(String severity) { - for (Severity sev in Severity.values) { - if (sev.name == severity) { - return sev; + /// extract the severity from the string and return the corresponding enum + static Severity fromString(String severity) { + for (Severity sev in Severity.values) { + if (sev.name == severity) { + return sev; + } } + return Severity.minor; + } + + static double getIndexFromSeverity(Severity notificationLevel) { + final severities = Severity.values; + for (int i = 0; i < severities.length; i++) { + if (severities[i] == notificationLevel) { + return i.toDouble(); + } + } + + // default return value: index of Severity.minor + return 3; } - return Severity.other; } + + + + diff --git a/lib/enums/WarningSource.dart b/lib/enums/WarningSource.dart new file mode 100644 index 00000000..bc814c42 --- /dev/null +++ b/lib/enums/WarningSource.dart @@ -0,0 +1,42 @@ +enum WarningSource { + alertSwiss, + biwapp, + dwd, + katwarn, + lhp, + mowas, + other; + + String toJson() => name; + + static WarningSource fromString(String source) { + switch (source.toUpperCase()) { + case "ALERTSWISS": case "ALERT SWISS": + return WarningSource.alertSwiss; + case "BIWAPP": + return WarningSource.biwapp; + case "DWD": + return WarningSource.dwd; + case "KATWARN": + return WarningSource.katwarn; + case "LHP": + return WarningSource.lhp; + case "MOWAS": + return WarningSource.mowas; + default: + return WarningSource.other; + } + } + + static int getIndexFromWarningSource(WarningSource source) { + final sources = WarningSource.values; + for (int i = 0; i < sources.length; i++) { + if (sources[i] == source) { + return i.toInt(); + } + } + + // default return value: index of WarningSource.other + return 6; + } +} diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 66dacf5a..be4b1fe5 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -428,5 +428,33 @@ "legacy_warning_dialog_title": "Version aktualisiert auf", "@legacy_warning_dialog_title": {}, "legacy_warning_dialog_text": "FOSS Warn wurde auf eine neue Version aktualisiert, die größere Verbesserungen in der inneren Struktur enthält. Dadurch mussten wir deine Appeinstellungen und gespeicherten Orte zurücksetzen. Bitte prüfe deine Einstellungen und füge deine Orte wieder neu hinzu.\n\nWir entschuldigen uns für den notwendigen Umstand.", - "@legacy_warning_dialog_text": {} + "@legacy_warning_dialog_text": {}, + "notification_settings_slidervalue_extreme": "Extrem", + "@notification_settings_slidervalue_extreme": {}, + "notification_settings_slidervalue_moderate": "Moderat", + "@notification_settings_slidervalue_moderate": {}, + "notification_settings_source_disabled": "Quellen deaktiviert - Du bekommst keine Benachrichtigungen", + "@notification_settings_source_disabled": {}, + "notification_settings_slidervalue_severe": "Schwer", + "@notification_settings_slidervalue_severe": {}, + "notification_settings_slidervalue_minor": "Gering", + "@notification_settings_slidervalue_minor": {}, + "notification_settings_open_severity_explanation": "Erklärungen zum Schweregrad", + "@notification_settings_open_severity_explanation": {}, + "warning_severity_explanation_dialog_moderate_description": "Eine Warnung vor einer möglichen Bedrohung von Leben oder Eigentum. Kann den normalen Tagesablauf stark beeinträchtigen.", + "@warning_severity_explanation_dialog_moderate_description": {}, + "source_other_description": "Allgemeine Einstellung, die verwendet wird, wenn keine genauere Einstellung vorgenommen wurde.", + "@source_other_description": {}, + "warning_severity_explanation_dialog_minor_description": "Minimale bis keine bekannte Bedrohung für Leben oder Eigentum. Kann den normalen Tagesablauf beeinträchtigen.", + "@warning_severity_explanation_dialog_minor_description": {}, + "notification_settings_description": "Hier können Sie die Warnstufe für jede Quelle festlegen, bei der Sie eine Warnung erhalten möchten.", + "@notification_settings_description": {}, + "source_other_title": "Sonstige", + "@source_other_title": {}, + "warning_severity_explanation_dialog_extreme_description": "Außerordentliche Bedrohung für Leben oder Eigentum. Kann sich kurzfristig signifikant auf ihre Gesundheit, ihr Eigentum und/oder öffentliche Infrastruktur auswirken.", + "@warning_severity_explanation_dialog_extreme_description": {}, + "warning_severity_explanation_dialog_severe_description": "Erhebliche Bedrohung für Leben oder Eigentum. Kann ihre Gesundheit, ihr Eigentum und/oder öffentliche Infrastruktur beeinträchtigen.", + "@warning_severity_explanation_dialog_severe_description": {}, + "warning_severity_explanation_dialog_headline": "Schweregrad", + "@warning_severity_explanation_dialog_headline": {} } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 2e7cfabd..899cd8b4 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -428,5 +428,33 @@ "legacy_warning_dialog_title": "Version updated to", "@legacy_warning_dialog_title": {}, "legacy_warning_dialog_text": "FOSS Warn has been updated to a new version improving the internal structure of the app. The shift has caused all settings and your saved locations to be reset. Please check your settings and add back your wanted locations.\n\nWe apologize for any inconvenience.", - "@legacy_warning_dialog_text": {} + "@legacy_warning_dialog_text": {}, + "notification_settings_slidervalue_extreme": "extreme", + "@notification_settings_slidervalue_extreme": {}, + "notification_settings_slidervalue_moderate": "moderate", + "@notification_settings_slidervalue_moderate": {}, + "notification_settings_source_disabled": "Source disabled - you won't get a notification", + "@notification_settings_source_disabled": {}, + "notification_settings_slidervalue_severe": "severe", + "@notification_settings_slidervalue_severe": {}, + "notification_settings_slidervalue_minor": "minor", + "@notification_settings_slidervalue_minor": {}, + "source_other_description": "General setting that is used if no more precise setting has been made.", + "@source_other_description": {}, + "source_other_title": "Other", + "@source_other_title": {}, + "notification_settings_open_severity_explanation": "Explanations of the degree of severity", + "@notification_settings_open_severity_explanation": {}, + "notification_settings_description": "Here you can set the warning level for each source for which you want to receive a warning.", + "@notification_settings_description": {}, + "warning_severity_explanation_dialog_severe_description": "Significant threat to life or property. May affect your health, property and/or public infrastructure.", + "@warning_severity_explanation_dialog_severe_description": {}, + "warning_severity_explanation_dialog_moderate_description": "A warning of a possible threat to life or property. Can severely disrupt normal daily routines.", + "@warning_severity_explanation_dialog_moderate_description": {}, + "warning_severity_explanation_dialog_minor_description": "Minimal to no known threat to life or property. May interfere with normal daily activities.", + "@warning_severity_explanation_dialog_minor_description": {}, + "warning_severity_explanation_dialog_extreme_description": "Extraordinary threat to life or property. May have a significant short-term impact on your health, property and/or public infrastructure.", + "@warning_severity_explanation_dialog_extreme_description": {}, + "warning_severity_explanation_dialog_headline": "Severity", + "@warning_severity_explanation_dialog_headline": {} } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index ed8ca088..3e97faf1 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -428,5 +428,33 @@ "legacy_warning_dialog_title": "Version mise à jour à", "@legacy_warning_dialog_title": {}, "legacy_warning_dialog_text": "FOSS Warn a été mis à jour vers une nouvelle version améliorant la structure interne de l'application. Ce changement a entraîné la réinitialisation de tous les paramètres et des emplacements sauvegardés. Veuillez vérifier vos paramètres et ajouter à nouveau vos emplacements souhaités.\n\nNous nous excusons pour tout inconvénient.", - "@legacy_warning_dialog_text": {} + "@legacy_warning_dialog_text": {}, + "notification_settings_slidervalue_extreme": "extrême", + "@notification_settings_slidervalue_extreme": {}, + "notification_settings_slidervalue_moderate": "modéré", + "@notification_settings_slidervalue_moderate": {}, + "notification_settings_source_disabled": "Source désactivée - vous ne recevrez pas de notification", + "@notification_settings_source_disabled": {}, + "notification_settings_slidervalue_severe": "sévères", + "@notification_settings_slidervalue_severe": {}, + "notification_settings_slidervalue_minor": "mineurs", + "@notification_settings_slidervalue_minor": {}, + "notification_settings_open_severity_explanation": "Explications sur le degré de gravité", + "@notification_settings_open_severity_explanation": {}, + "warning_severity_explanation_dialog_moderate_description": "Avertissement d'une menace possible pour la vie ou les biens. Peut gravement perturber les activités quotidiennes normales.", + "@warning_severity_explanation_dialog_moderate_description": {}, + "source_other_description": "Réglage général qui est utilisé si aucun réglage plus précis n'a été effectué.", + "@source_other_description": {}, + "warning_severity_explanation_dialog_minor_description": "Risque minime ou inexistant de menace pour la vie ou les biens. Peut interférer avec les activités quotidiennes normales.", + "@warning_severity_explanation_dialog_minor_description": {}, + "notification_settings_description": "Vous pouvez ici définir le niveau d'avertissement pour chaque source pour laquelle vous souhaitez recevoir un avertissement.", + "@notification_settings_description": {}, + "source_other_title": "Autres", + "@source_other_title": {}, + "warning_severity_explanation_dialog_extreme_description": "Menace extraordinaire pour la vie ou les biens. Peut avoir un impact significatif à court terme sur votre santé, vos biens et/ou les infrastructures publiques.", + "@warning_severity_explanation_dialog_extreme_description": {}, + "warning_severity_explanation_dialog_severe_description": "Menace importante pour la vie ou les biens. Peut affecter votre santé, vos biens et/ou les infrastructures publiques.", + "@warning_severity_explanation_dialog_severe_description": {}, + "warning_severity_explanation_dialog_headline": "Gravité", + "@warning_severity_explanation_dialog_headline": {} } diff --git a/lib/services/alertSwiss.dart b/lib/services/alertSwiss.dart index 143b55a5..6c3dedcd 100644 --- a/lib/services/alertSwiss.dart +++ b/lib/services/alertSwiss.dart @@ -82,7 +82,7 @@ WarnMessage? createWarning(var data) { for (int i = 0; i < data.length; i++) { tempAreaList.add( Area( - areaDesc: data[i]["description"], + areaDesc: data[i]["description"]["description"], geocodeList: [ Geocode( geocodeName: data[i]["regions"][0]["region"], diff --git a/lib/services/checkForMyPlacesWarnings.dart b/lib/services/checkForMyPlacesWarnings.dart index 70d8864a..260a3efe 100644 --- a/lib/services/checkForMyPlacesWarnings.dart +++ b/lib/services/checkForMyPlacesWarnings.dart @@ -7,17 +7,11 @@ import 'saveAndLoadSharedPreferences.dart'; /// check all warnings if one of them is of a myPlace and if yes send a notification
/// [true] if there are/is a warning - false if not
-/// [useEtag]: if the etags should be used while calling the API -Future checkForMyPlacesWarnings(bool useEtag, bool loadManually) async { +Future checkForMyPlacesWarnings(bool loadManually) async { bool _returnValue = true; print("check for warnings"); // get data first await callAPI(); - if (notificationSettingsImportance.isEmpty) { - print("notificationSettingsImportanceList is empty"); - await loadNotificationSettingsImportanceList(); - print(notificationSettingsImportance); - } if (myPlaceList.isEmpty) { print("myPlaceList is empty - load list"); await loadMyPlacesList(); diff --git a/lib/services/getData.dart b/lib/services/getData.dart index 05558b23..a0e6a8cc 100644 --- a/lib/services/getData.dart +++ b/lib/services/getData.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'package:foss_warn/enums/DataFetchStatus.dart'; +import 'package:foss_warn/enums/WarningSource.dart'; import 'package:foss_warn/services/alertSwiss.dart'; import '../enums/Certainty.dart'; @@ -82,7 +83,7 @@ Future getData(bool useEtag) async { } WarnMessage tempWarnMessage = WarnMessage( - source: "MOWAS", + source: WarningSource.mowas, identifier: _data[i]["identifier"] ?? "?", sender: _data[i]["sender"] ?? "?", sent: _data[i]["sent"] ?? "?", @@ -92,7 +93,7 @@ Future getData(bool useEtag) async { category: _data[i]["info"][0]["category"][0] ?? "?", event: _data[i]["info"][0]["event"] ?? "?", urgency: _data[i]["info"][0]["urgency"] ?? "?", - severity: getSeverity( + severity: Severity.fromString( _data[i]["info"][0]["severity"].toString().toLowerCase()), certainty: getCertainty( _data[i]["info"][0]["certainty"].toString().toLowerCase()), @@ -178,7 +179,7 @@ Future getData(bool useEtag) async { } WarnMessage tempWarnMessage = WarnMessage( - source: "KATWARN", + source: WarningSource.katwarn, identifier: _data[i]["identifier"] ?? "?", sender: _data[i]["sender"] ?? "?", sent: _data[i]["sent"] ?? "?", @@ -188,7 +189,7 @@ Future getData(bool useEtag) async { category: _data[i]["info"][0]["category"][0] ?? "?", event: _data[i]["info"][0]["event"] ?? "?", urgency: _data[i]["info"][0]["urgency"] ?? "?", - severity: getSeverity( + severity: Severity.fromString( _data[i]["info"][0]["severity"].toString().toLowerCase()), certainty: getCertainty( _data[i]["info"][0]["certainty"].toString().toLowerCase()), @@ -276,7 +277,7 @@ Future getData(bool useEtag) async { } WarnMessage tempWarnMessage = WarnMessage( - source: "BIWAPP", + source: WarningSource.biwapp, identifier: _data[i]["identifier"] ?? "?", sender: _data[i]["sender"] ?? "?", sent: _data[i]["sent"] ?? "?", @@ -286,7 +287,7 @@ Future getData(bool useEtag) async { category: _data[i]["info"][0]["category"][0] ?? "?", event: _data[i]["info"][0]["event"] ?? "?", urgency: _data[i]["info"][0]["urgency"] ?? "?", - severity: getSeverity( + severity: Severity.fromString( _data[i]["info"][0]["severity"].toString().toLowerCase()), certainty: getCertainty( _data[i]["info"][0]["certainty"].toString().toLowerCase()), @@ -376,7 +377,7 @@ Future getData(bool useEtag) async { } WarnMessage tempWarnMessage = WarnMessage( - source: "DWD", + source: WarningSource.dwd, identifier: _data[i]["identifier"] ?? "?", sender: _data[i]["sender"] ?? "?", sent: _data[i]["sent"] ?? "?", @@ -386,7 +387,7 @@ Future getData(bool useEtag) async { category: _data[i]["info"][0]["category"][0] ?? "?", event: _data[i]["info"][0]["event"] ?? "?", urgency: _data[i]["info"][0]["urgency"] ?? "?", - severity: getSeverity( + severity: Severity.fromString( _data[i]["info"][0]["severity"].toString().toLowerCase()), certainty: getCertainty( _data[i]["info"][0]["certainty"].toString().toLowerCase()), @@ -472,7 +473,7 @@ Future getData(bool useEtag) async { } WarnMessage tempWarnMessage = WarnMessage( - source: "LHP", + source: WarningSource.lhp, identifier: _data[i]["identifier"] ?? "?", sender: _data[i]["sender"] ?? "?", sent: _data[i]["sent"] ?? "?", @@ -482,7 +483,7 @@ Future getData(bool useEtag) async { category: _data[i]["info"][0]["category"][0] ?? "?", event: _data[i]["info"][0]["event"] ?? "?", urgency: _data[i]["info"][0]["urgency"] ?? "?", - severity: getSeverity( + severity: Severity.fromString( _data[i]["info"][0]["severity"].toString().toLowerCase()), certainty: getCertainty( _data[i]["info"][0]["certainty"].toString().toLowerCase()), diff --git a/lib/services/saveAndLoadSharedPreferences.dart b/lib/services/saveAndLoadSharedPreferences.dart index 617fbf6f..0a1a954d 100644 --- a/lib/services/saveAndLoadSharedPreferences.dart +++ b/lib/services/saveAndLoadSharedPreferences.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'package:foss_warn/class/class_notificationPreferences.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:flutter/material.dart'; @@ -100,8 +101,8 @@ saveSettings() async { userPreferences.availableDarkThemes .indexOf(userPreferences.selectedDarkTheme)); preferences.setBool("showAllWarnings", userPreferences.showAllWarnings); - preferences.setString("notificationEventsSettings", - jsonEncode(userPreferences.notificationEventsSettings)); + preferences.setString("notificationSourceSettings", + jsonEncode(userPreferences.notificationSourceSettings)); preferences.setBool("activateAlertSwiss", userPreferences.activateAlertSwiss); print("Settings saved"); } @@ -159,7 +160,7 @@ loadSettings() async { if (preferences.containsKey("warningFontSize")) { userPreferences.warningFontSize = preferences.getDouble("warningFontSize")!; } else { - saveSettings(); + saveSettings(); //@todo remove? loadSettings(); } if (preferences.containsKey("showWelcomeScreen")) { @@ -223,10 +224,14 @@ loadSettings() async { if (preferences.containsKey("showAllWarnings")) { userPreferences.showAllWarnings = preferences.getBool("showAllWarnings")!; } - if (preferences.containsKey("notificationEventsSettings")) { - String temp = preferences.getString("notificationEventsSettings")!; - userPreferences.notificationEventsSettings = - Map.from(jsonDecode(temp)); + if (preferences.containsKey("notificationSourceSettings")) { + List data = + jsonDecode(preferences.getString("notificationSourceSettings")!); + userPreferences.notificationSourceSettings.clear(); + for (int i = 0; i < data.length; i++) { + userPreferences.notificationSourceSettings + .add(NotificationPreferences.fromJson(data[i])); + } } if (preferences.containsKey("activateAlertSwiss")) { userPreferences.activateAlertSwiss = @@ -234,6 +239,7 @@ loadSettings() async { } } +@deprecated saveNotificationSettingsImportanceList() async { print("Save saveNotificationSettingsImportanceList"); notificationSettingsImportance.clear(); @@ -256,6 +262,7 @@ saveNotificationSettingsImportanceList() async { print(notificationSettingsImportance); } +@deprecated loadNotificationSettingsImportanceList() async { SharedPreferences preferences = await SharedPreferences.getInstance(); //check if notificationSettingsImportance already exists diff --git a/lib/services/sortWarnings.dart b/lib/services/sortWarnings.dart index 5b36a857..9e67b993 100644 --- a/lib/services/sortWarnings.dart +++ b/lib/services/sortWarnings.dart @@ -1,46 +1,16 @@ +import 'package:foss_warn/enums/WarningSource.dart'; import '../class/class_WarnMessage.dart'; import '../enums/Severity.dart'; import '../main.dart'; -/// used to sort warning -/// returns an int corresponding to the severity -int convertSeverityToInt(Severity severity) { - switch (severity) { - case Severity.minor: - case Severity.other: - return 0; - case Severity.moderate: - return 1; - case Severity.extreme: - return 2; - case Severity.severe: - return 3; - } -} - -/// used to sort warning -int convertSourceToInt(String source) { - switch (source) { - case "MOWAS": - return 0; - case "KATWARN": - return 1; - case "BIWAPP": - return 2; - case "DWD": - return 3; - } - return 0; -} - void sortWarnings(List list) { if (userPreferences.sortWarningsBy == "severity") { - list.sort((a, b) => convertSeverityToInt(b.severity) - .compareTo(convertSeverityToInt(a.severity))); + list.sort((a, b) => Severity.getIndexFromSeverity(b.severity) + .compareTo(Severity.getIndexFromSeverity(a.severity))); } else if (userPreferences.sortWarningsBy == "date") { list.sort((a, b) => b.sent.compareTo(a.sent)); } else if (userPreferences.sortWarningsBy == "source") { - list.sort((a, b) => convertSourceToInt(b.publisher) - .compareTo(convertSourceToInt(a.publisher))); + list.sort((a, b) => WarningSource.getIndexFromWarningSource(b.source) + .compareTo(WarningSource.getIndexFromWarningSource(a.source))); } } diff --git a/lib/services/translateAndColorizeWarning.dart b/lib/services/translateAndColorizeWarning.dart index a76dd1ef..086199e1 100644 --- a/lib/services/translateAndColorizeWarning.dart +++ b/lib/services/translateAndColorizeWarning.dart @@ -136,7 +136,7 @@ String translateWarningSeverity(String severity) { case "minor": return "Gering"; case "moderate": - return "Mittel"; + return "Moderat"; case "extreme": return "Extrem"; case "severe": diff --git a/lib/views/AllWarningsView.dart b/lib/views/AllWarningsView.dart index 8c43cf4c..650d4a3b 100644 --- a/lib/views/AllWarningsView.dart +++ b/lib/views/AllWarningsView.dart @@ -51,7 +51,7 @@ class _AllWarningsViewState extends State { // call (new) api just for my places/ alert swiss await callAPI(); } - checkForMyPlacesWarnings(false, true); + checkForMyPlacesWarnings(true); sortWarnings(allWarnMessageList); loadNotificationSettingsImportanceList(); setState(() { diff --git a/lib/views/DevSettingsView.dart b/lib/views/DevSettingsView.dart index 86bd93dd..e936d13d 100644 --- a/lib/views/DevSettingsView.dart +++ b/lib/views/DevSettingsView.dart @@ -37,11 +37,11 @@ class _DevSettingsState extends State { subtitle: Text(AppLocalizations.of(context) !.dev_settings_test_notification_text), onTap: () { - checkForMyPlacesWarnings(false, true); + checkForMyPlacesWarnings(true); bool thereIsNoWarning = true; for (Place myPlace in myPlaceList) { //check if there are warning and if it they are important enough - thereIsNoWarning = myPlace.checkIfThereIsAWarningToNotify(); + thereIsNoWarning = !(myPlace.checkIfThereIsAWarningToNotify()); } if (thereIsNoWarning) { final snackBar = SnackBar( diff --git a/lib/views/NotificationSettingsView.dart b/lib/views/NotificationSettingsView.dart index d716101c..ba92b5ef 100644 --- a/lib/views/NotificationSettingsView.dart +++ b/lib/views/NotificationSettingsView.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:foss_warn/widgets/NotificationPreferencesListTileWidget.dart'; +import 'package:foss_warn/widgets/dialogs/WarningSeverityExplanation.dart'; -import '../class/class_alarmManager.dart'; import '../main.dart'; -import '../services/saveAndLoadSharedPreferences.dart'; class NotificationSettingsView extends StatefulWidget { const NotificationSettingsView({Key? key}) : super(key: key); @@ -15,6 +15,7 @@ class NotificationSettingsView extends StatefulWidget { class _NotificationSettingsViewState extends State { final EdgeInsets settingsTileListPadding = EdgeInsets.fromLTRB(25, 2, 25, 2); + @override Widget build(BuildContext context) { return Scaffold( @@ -28,200 +29,48 @@ class _NotificationSettingsViewState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - SizedBox(height: 10), - Text( - AppLocalizations.of(context)!.notification_settings_notify_by, - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - ListTile( - contentPadding: settingsTileListPadding, - title: Text(AppLocalizations.of(context) - !.notification_settings_notify_by_extreme), - trailing: Switch( - value: userPreferences.notificationWithExtreme, - onChanged: (value) { - if (userPreferences.shouldNotifyGeneral) { - setState(() { - userPreferences.notificationWithExtreme = value; - saveNotificationSettingsImportanceList(); - AlarmManager().cancelBackgroundTask(); - AlarmManager().registerBackgroundTask(); - }); - } else { - print("Background notification is disabled"); - } - }), - ), - ListTile( - contentPadding: settingsTileListPadding, - title: Text(AppLocalizations.of(context) - !.notification_settings_notify_by_severe), - trailing: Switch( - value: userPreferences.notificationWithSevere, - onChanged: (value) { - if (userPreferences.shouldNotifyGeneral) { - setState(() { - userPreferences.notificationWithSevere = value; - saveNotificationSettingsImportanceList(); - AlarmManager().cancelBackgroundTask(); - AlarmManager().registerBackgroundTask(); - }); - } else { - print("Background notification is disabled"); - } - }), - ), - ListTile( - contentPadding: settingsTileListPadding, - title: Text(AppLocalizations.of(context) - !.notification_settings_notify_by_moderate), - trailing: Switch( - value: userPreferences.notificationWithModerate, - onChanged: (value) { - if (userPreferences.shouldNotifyGeneral) { - setState(() { - userPreferences.notificationWithModerate = value; - saveNotificationSettingsImportanceList(); - AlarmManager().cancelBackgroundTask(); - AlarmManager().registerBackgroundTask(); - }); - } else { - print("Background notification is disabled"); - } - }), - ), - ListTile( - contentPadding: settingsTileListPadding, - title: Text(AppLocalizations.of(context) - !.notification_settings_notify_by_minor), - trailing: Switch( - value: userPreferences.notificationWithMinor, - onChanged: (value) { - if (userPreferences.shouldNotifyGeneral) { - setState(() { - userPreferences.notificationWithMinor = value; - saveNotificationSettingsImportanceList(); - AlarmManager().cancelBackgroundTask(); - AlarmManager().registerBackgroundTask(); - }); - } else { - print("Background notification is disabled"); - } - }), - ), SizedBox( height: 10, ), - Text( - "DWD", - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + Container( + padding: settingsTileListPadding, + child: Text( + AppLocalizations.of(context)!.notification_settings_description), // ), - ListTile( - contentPadding: settingsTileListPadding, - title: Text(AppLocalizations.of(context) - !.notification_settings_thunderstorm), - trailing: Switch( - value: userPreferences.notificationEventsSettings[ - "STARKES GEWITTER"] != - null - ? userPreferences - .notificationEventsSettings["STARKES GEWITTER"]! - : true, - onChanged: (value) { - setState(() { - userPreferences.notificationEventsSettings - .putIfAbsent("STARKES GEWITTER", () => value); - userPreferences.notificationEventsSettings - .update("STARKES GEWITTER", (newValue) => value); - }); - saveSettings(); - print(userPreferences.notificationEventsSettings); - })), - ListTile( - contentPadding: settingsTileListPadding, - title: Text(AppLocalizations.of(context) - !.notification_settings_strong_weather), - trailing: Switch( - value: userPreferences.notificationEventsSettings[ - "STARKES WETTER"] != - null - ? userPreferences - .notificationEventsSettings["STARKES WETTER"]! - : true, - onChanged: (value) { - setState(() { - userPreferences.notificationEventsSettings - .putIfAbsent("STARKES WETTER", () => value); - userPreferences.notificationEventsSettings - .update("STARKES WETTER", (newValue) => value); - }); - saveSettings(); - })), - ListTile( - contentPadding: settingsTileListPadding, - title: Text(AppLocalizations.of(context) - !.notification_settings_everything_else), - trailing: Switch( - value: true, - onChanged: null)), SizedBox( height: 10, ), - Text( - "Mowas", - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + Container( + padding: settingsTileListPadding, + child: TextButton( + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) { + return WarningSeverityExplanation(); + }, + ); + }, + child: Row( + children: [ + Icon(Icons.info), + SizedBox( + width: 10, + ), + Text( + AppLocalizations.of(context)!.notification_settings_open_severity_explanation), // + ], + )), ), - ListTile( - contentPadding: settingsTileListPadding, - title: Text(AppLocalizations.of(context) - !.notification_settings_everything), - trailing: Switch( - value: true, - onChanged: null)), SizedBox( height: 10, ), - Text( - "BIWAPP", - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - ListTile( - contentPadding: settingsTileListPadding, - title: Text(AppLocalizations.of(context) - !.notification_settings_everything), - trailing: Switch( - value: true, - onChanged: null), - ), - SizedBox( - height: 10, - ), - Text( - "KATWARN", - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - ListTile( - contentPadding: settingsTileListPadding, - title: Text(AppLocalizations.of(context) - !.notification_settings_everything), - trailing: Switch( - value: true, - onChanged: null), - ), - SizedBox( - height: 10, - ), - Text( - "LHP", - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - ListTile( - contentPadding: settingsTileListPadding, - title: Text(AppLocalizations.of(context) - !.notification_settings_everything), - trailing: Switch( - value: true, - onChanged: null)), + // generate the settings tiles + ...userPreferences.notificationSourceSettings + .map((element) => NotificationPreferencesListTileWidget( + notificationPreferences: element, + )) + .toList(), ], ), ), diff --git a/lib/views/SettingsView.dart b/lib/views/SettingsView.dart index 62194268..edfca3c5 100644 --- a/lib/views/SettingsView.dart +++ b/lib/views/SettingsView.dart @@ -106,6 +106,7 @@ class _SettingsState extends State { AppLocalizations.of(context)!.settings_background_service), trailing: Switch( value: userPreferences.shouldNotifyGeneral, + //@todo maybe we should add a confirmation dialog to prevent accidentally disabled background updates onChanged: (value) { setState(() { userPreferences.shouldNotifyGeneral = value; @@ -116,16 +117,11 @@ class _SettingsState extends State { AlarmManager().registerBackgroundTask(); } else { AlarmManager().cancelBackgroundTask(); - setState(() { - userPreferences.notificationWithExtreme = false; - userPreferences.notificationWithSevere = false; - userPreferences.notificationWithModerate = false; - userPreferences.notificationWithMinor = false; - }); print("background notification disabled"); } }), ), + userPreferences.shouldNotifyGeneral ? ListTile( title: Row( mainAxisSize: MainAxisSize.max, @@ -167,11 +163,17 @@ class _SettingsState extends State { } }, onTapOutside: (e) { - FocusScope.of(context).unfocus(); - saveSettings(); - AlarmManager().cancelBackgroundTask(); - AlarmManager().registerBackgroundTask(); - callAPI(); // call api and update notification + // Check whether the text field is in focus, + // because this method is executed every time + // you tap somewhere in the settings, even + // if the text field is not in focus at all + if (FocusScope.of(context).isFirstFocus) { + FocusScope.of(context).unfocus(); + saveSettings(); + AlarmManager().cancelBackgroundTask(); + AlarmManager().registerBackgroundTask(); + callAPI(); // call api and update notification + } }, onEditingComplete: () { FocusScope.of(context).unfocus(); @@ -217,7 +219,7 @@ class _SettingsState extends State { ), ], ), - ), + ) : SizedBox(), Divider( height: 50, indent: 15.0, diff --git a/lib/views/WarningDetailView.dart b/lib/views/WarningDetailView.dart index 81d4d664..bb4061c7 100644 --- a/lib/views/WarningDetailView.dart +++ b/lib/views/WarningDetailView.dart @@ -1,6 +1,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:foss_warn/class/class_NotificationService.dart'; import '../class/class_WarnMessage.dart'; import '../class/class_Area.dart'; import '../class/class_Geocode.dart'; @@ -11,9 +12,13 @@ import '../services/translateAndColorizeWarning.dart'; import 'package:share_plus/share_plus.dart'; +import '../widgets/dialogs/WarningSeverityExplanation.dart'; + class DetailScreen extends StatefulWidget { final WarnMessage _warnMessage; - const DetailScreen({Key? key, required WarnMessage warnMessage}) : _warnMessage = warnMessage, super(key: key); + const DetailScreen({Key? key, required WarnMessage warnMessage}) + : _warnMessage = warnMessage, + super(key: key); @override _DetailScreenState createState() => _DetailScreenState(); @@ -188,8 +193,8 @@ class _DetailScreenState extends State { launchUrlInBrowser(url); }, child: Text( - AppLocalizations.of(context) - !.warning_open_picture_with_browser, + AppLocalizations.of(context)! + .warning_open_picture_with_browser, style: TextStyle(color: Colors.white), ), style: TextButton.styleFrom(backgroundColor: Colors.blue))); @@ -212,6 +217,8 @@ class _DetailScreenState extends State { }); // save places List to store new read state saveMyPlacesList(); + // cancel the notification + NotificationService.cancelOneNotification(widget._warnMessage.identifier.hashCode); List generateAreaDescList(int length) { List tempList = []; @@ -266,37 +273,37 @@ class _DetailScreenState extends State { title: Text(widget._warnMessage.headline), actions: [ IconButton( - tooltip: AppLocalizations.of(context)!.warning_share, - onPressed: () { - final String shareText = widget._warnMessage.headline + - "\n\n" + - AppLocalizations.of(context)!.warning_from + - ": " + - formatSentDate(widget._warnMessage.sent) + - "\n\n" + - AppLocalizations.of(context)!.warning_region + - ": " + - generateAreaDescList(-1).toString().substring( - 1, generateAreaDescList(-1).toString().length - 1) + - "\n\n" + - AppLocalizations.of(context)!.warning_description + - ":\n" + - replaceHTMLTags(widget._warnMessage.description) + - " \n\n" + - AppLocalizations.of(context)!.warning_recommended_action + - ":\n" + - replaceHTMLTags(widget._warnMessage.instruction) + - "\n\n" + - AppLocalizations.of(context)!.warning_source + - ":\n" + - widget._warnMessage.publisher + - "\n\n-- " + - AppLocalizations.of(context)!.warning_shared_by_foss_warn + - " --"; - final String shareSubject = widget._warnMessage.headline; - shareWarning(context, shareText, shareSubject); - }, - icon: Icon(Icons.share), + tooltip: AppLocalizations.of(context)!.warning_share, + onPressed: () { + final String shareText = widget._warnMessage.headline + + "\n\n" + + AppLocalizations.of(context)!.warning_from + + ": " + + formatSentDate(widget._warnMessage.sent) + + "\n\n" + + AppLocalizations.of(context)!.warning_region + + ": " + + generateAreaDescList(-1).toString().substring( + 1, generateAreaDescList(-1).toString().length - 1) + + "\n\n" + + AppLocalizations.of(context)!.warning_description + + ":\n" + + replaceHTMLTags(widget._warnMessage.description) + + " \n\n" + + AppLocalizations.of(context)!.warning_recommended_action + + ":\n" + + replaceHTMLTags(widget._warnMessage.instruction) + + "\n\n" + + AppLocalizations.of(context)!.warning_source + + ":\n" + + widget._warnMessage.publisher + + "\n\n-- " + + AppLocalizations.of(context)!.warning_shared_by_foss_warn + + " --"; + final String shareSubject = widget._warnMessage.headline; + shareWarning(context, shareText, shareSubject); + }, + icon: Icon(Icons.share), ), ], ), @@ -318,7 +325,8 @@ class _DetailScreenState extends State { ": " + formatSentDate(widget._warnMessage.sent), style: TextStyle( - fontSize: userPreferences.warningFontSize, fontWeight: FontWeight.bold), + fontSize: userPreferences.warningFontSize, + fontWeight: FontWeight.bold), ), widget._warnMessage.effective != "" ? Padding( @@ -391,9 +399,11 @@ class _DetailScreenState extends State { child: Text( AppLocalizations.of(context)!.warning_event + ": " + - translateWarningCategory(widget._warnMessage.event, context), + translateWarningCategory( + widget._warnMessage.event, context), style: TextStyle( - color: Colors.white, fontSize: userPreferences.warningFontSize), + color: Colors.white, + fontSize: userPreferences.warningFontSize), ), ), Container( @@ -410,7 +420,8 @@ class _DetailScreenState extends State { translateWarningType( widget._warnMessage.messageType, context), style: TextStyle( - color: Colors.white, fontSize: userPreferences.warningFontSize), + color: Colors.white, + fontSize: userPreferences.warningFontSize), ), ), Container( @@ -421,12 +432,24 @@ class _DetailScreenState extends State { color: chooseWarningSeverityColor( widget._warnMessage.severity.name), ), - child: Text( - AppLocalizations.of(context)!.warning_severity + - ": " + - translateWarningSeverity(widget._warnMessage.severity.name), - style: TextStyle( - color: Colors.white, fontSize: userPreferences.warningFontSize), + child: InkWell( + onTap: () { + showDialog( + context: context, + builder: (BuildContext context) { + return WarningSeverityExplanation(); + }, + ); + }, + child: Text( + AppLocalizations.of(context)!.warning_severity + + ": " + + translateWarningSeverity( + widget._warnMessage.severity.name), + style: TextStyle( + color: Colors.white, + fontSize: userPreferences.warningFontSize), + ), ), ), userPreferences.showExtendedMetaData @@ -482,7 +505,8 @@ class _DetailScreenState extends State { AppLocalizations.of(context)!.warning_scope + ": " + widget._warnMessage.scope, - style: TextStyle(fontSize: userPreferences.warningFontSize), + style: TextStyle( + fontSize: userPreferences.warningFontSize), ), ), SizedBox( @@ -499,7 +523,8 @@ class _DetailScreenState extends State { AppLocalizations.of(context)!.warning_identifier + ": " + widget._warnMessage.identifier, - style: TextStyle(fontSize: userPreferences.warningFontSize), + style: TextStyle( + fontSize: userPreferences.warningFontSize), ), ), SizedBox( @@ -516,7 +541,8 @@ class _DetailScreenState extends State { AppLocalizations.of(context)!.warning_sender + ": " + widget._warnMessage.sender, - style: TextStyle(fontSize: userPreferences.warningFontSize), + style: TextStyle( + fontSize: userPreferences.warningFontSize), ), ), SizedBox( @@ -534,7 +560,8 @@ class _DetailScreenState extends State { ": " + translateWarningStatus( widget._warnMessage.status), - style: TextStyle(fontSize: userPreferences.warningFontSize), + style: TextStyle( + fontSize: userPreferences.warningFontSize), ), ), SizedBox( @@ -586,7 +613,8 @@ class _DetailScreenState extends State { fontWeight: FontWeight.bold, color: Colors.red), ) - : Text(AppLocalizations.of(context)!.warning_show_more, + : Text( + AppLocalizations.of(context)!.warning_show_more, style: TextStyle( fontWeight: FontWeight.bold, color: Colors.green)), @@ -627,12 +655,14 @@ class _DetailScreenState extends State { ? SelectableText( generateGeocodeNameList(-1).toString().substring( 1, generateGeocodeNameList(-1).toString().length - 1), - style: TextStyle(fontSize: userPreferences.warningFontSize), + style: + TextStyle(fontSize: userPreferences.warningFontSize), ) : SelectableText( generateGeocodeNameList(10).toString().substring( 1, generateGeocodeNameList(10).toString().length - 1), - style: TextStyle(fontSize: userPreferences.warningFontSize), + style: + TextStyle(fontSize: userPreferences.warningFontSize), ), generateGeocodeNameList(-1).length > 10 ? InkWell( @@ -643,7 +673,8 @@ class _DetailScreenState extends State { fontWeight: FontWeight.bold, color: Colors.red), ) - : Text(AppLocalizations.of(context)!.warning_show_more, + : Text( + AppLocalizations.of(context)!.warning_show_more, style: TextStyle( fontWeight: FontWeight.bold, color: Colors.green)), @@ -678,9 +709,10 @@ class _DetailScreenState extends State { ), SelectableText.rich( TextSpan( - children: - generateDescriptionBody(widget._warnMessage.description), - style: TextStyle(fontSize: userPreferences.warningFontSize)), + children: generateDescriptionBody( + widget._warnMessage.description), + style: + TextStyle(fontSize: userPreferences.warningFontSize)), ), SizedBox( height: 5, @@ -699,7 +731,8 @@ class _DetailScreenState extends State { ":", style: TextStyle( fontWeight: FontWeight.bold, - fontSize: userPreferences.warningFontSize + 5), + fontSize: + userPreferences.warningFontSize + 5), ), ], ), @@ -731,12 +764,13 @@ class _DetailScreenState extends State { width: 5, ), Text( - AppLocalizations.of(context) - !.warning_recommended_action + + AppLocalizations.of(context)! + .warning_recommended_action + ":", style: TextStyle( fontWeight: FontWeight.bold, - fontSize: userPreferences.warningFontSize + 5), + fontSize: + userPreferences.warningFontSize + 5), ), ], ), @@ -751,7 +785,8 @@ class _DetailScreenState extends State { TextSpan( children: generateDescriptionBody( widget._warnMessage.instruction), - style: TextStyle(fontSize: userPreferences.warningFontSize)), + style: TextStyle( + fontSize: userPreferences.warningFontSize)), ) : SizedBox(), SizedBox( @@ -786,7 +821,8 @@ class _DetailScreenState extends State { width: 5, ), Text( - AppLocalizations.of(context)!.warning_contact_website + + AppLocalizations.of(context)! + .warning_contact_website + ":", style: TextStyle( fontWeight: FontWeight.bold, @@ -806,7 +842,8 @@ class _DetailScreenState extends State { ":", style: TextStyle( fontWeight: FontWeight.bold, - fontSize: userPreferences.warningFontSize + 5), + fontSize: + userPreferences.warningFontSize + 5), ), ], ) diff --git a/lib/widgets/NotificationPreferencesListTileWidget.dart b/lib/widgets/NotificationPreferencesListTileWidget.dart new file mode 100644 index 00000000..bd1e6fea --- /dev/null +++ b/lib/widgets/NotificationPreferencesListTileWidget.dart @@ -0,0 +1,212 @@ +import 'package:flutter/material.dart'; +import 'package:foss_warn/enums/Severity.dart'; +import 'package:foss_warn/main.dart'; +import '../class/class_notificationPreferences.dart'; +import '../enums/WarningSource.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import '../services/saveAndLoadSharedPreferences.dart'; + +class NotificationPreferencesListTileWidget extends StatefulWidget { + final NotificationPreferences notificationPreferences; + const NotificationPreferencesListTileWidget( + {super.key, required this.notificationPreferences}); + + @override + State createState() => + _NotificationPreferencesListTileWidgetState(); +} + +class _NotificationPreferencesListTileWidgetState + extends State { + final EdgeInsets settingsTileListPadding = EdgeInsets.fromLTRB(25, 2, 25, 2); + + // return the label for the given value + String getLabelForWarningSeverity(double sliderValue) { + switch (sliderValue.toInt()) { + case 0: + return AppLocalizations.of(context)! + .notification_settings_notify_by_extreme; + case 1: + return AppLocalizations.of(context)! + .notification_settings_notify_by_severe; + case 2: + return AppLocalizations.of(context)! + .notification_settings_notify_by_moderate; + case 3: + return AppLocalizations.of(context)! + .notification_settings_notify_by_minor; + default: + return "Error"; + } + } + + /// return the description for the given source + String _getDescriptionForEventSetting(WarningSource source) { + switch (source) { + case WarningSource.dwd: + return AppLocalizations.of(context)!.source_dwd_description; + case WarningSource.mowas: + return AppLocalizations.of(context)!.source_mowas_description; + case WarningSource.biwapp: + return AppLocalizations.of(context)!.source_biwapp_description; + case WarningSource.katwarn: + return AppLocalizations.of(context)!.source_katwarn_description; + case WarningSource.lhp: + return AppLocalizations.of(context)!.source_lhp_description; + case WarningSource.alertSwiss: + return AppLocalizations.of(context)!.source_alertswiss_description; + case WarningSource.other: + return AppLocalizations.of(context)!.source_other_description; + default: + return "Error"; + } + } + + /// decide if a source can be disabled + bool _isTogglableSource(WarningSource source) { + switch (source) { + case WarningSource.dwd: + return true; + case WarningSource.mowas: + return false; + case WarningSource.biwapp: + return false; + case WarningSource.katwarn: + return false; + case WarningSource.lhp: + return true; + case WarningSource.other: + return false; + default: + return false; + } + } + + @override + Widget build(BuildContext context) { + if (widget.notificationPreferences.warningSource == + WarningSource.alertSwiss && + !userPreferences.activateAlertSwiss) { + return SizedBox(); + } else { + return Column( + children: [ + Padding( + padding: settingsTileListPadding, + child: Divider(), + ), + ListTile( + contentPadding: settingsTileListPadding, + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + widget.notificationPreferences.warningSource.name.toUpperCase(), + style: Theme.of(context).textTheme.titleMedium, + ), + // show a switch when source can be disabled + if (_isTogglableSource( + widget.notificationPreferences.warningSource)) + Switch( + value: !widget.notificationPreferences.disabled, + onChanged: (value) { + // the slider will be hidden when source is disabled + setState(() { + widget.notificationPreferences.disabled = !value; + }); + saveSettings(); + }, + ) + else + SizedBox(), + ], + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _getDescriptionForEventSetting( + widget.notificationPreferences.warningSource), + style: Theme.of(context).textTheme.bodyMedium), + // hide the slider when source is disabled + if (_isTogglableSource( + widget.notificationPreferences.warningSource) && + widget.notificationPreferences.disabled) + Container( + padding: EdgeInsets.only(top: 5), + child: Text( + AppLocalizations.of(context)!.notification_settings_source_disabled), + ) + else + Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.max, + children: [ + Icon( + Icons.notifications_active, + color: Colors.red, + ), + Flexible( + child: Slider( + label: getLabelForWarningSeverity( + Severity.getIndexFromSeverity(widget + .notificationPreferences.notificationLevel)), + divisions: 3, + min: 0, + max: 3, + value: Severity.getIndexFromSeverity( + widget.notificationPreferences.notificationLevel), + onChanged: (value) { + setState( + () { + final notificationLevel = + Severity.values[value.toInt()]; + + print(widget.notificationPreferences + .warningSource.name + + ":" + + notificationLevel.toString()); + + // update notification level with slider value + widget.notificationPreferences + .notificationLevel = notificationLevel; + }, + ); + }, + onChangeEnd: (value) { + // save settings, after change is complete + saveSettings(); + }, + ), + ), + Icon( + Icons.notifications, + color: Colors.orangeAccent, + ), + ], + ), + Padding( + padding: const EdgeInsets.only(left: 30, right: 30), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(AppLocalizations.of(context)!.notification_settings_slidervalue_extreme), + Text(AppLocalizations.of(context)!.notification_settings_slidervalue_severe), + Text(AppLocalizations.of(context)!.notification_settings_slidervalue_moderate), + Text(AppLocalizations.of(context)!.notification_settings_slidervalue_minor), + ], + ), + ) + ], + ), + ], + ), + ), + ], + ); + } + } +} diff --git a/lib/widgets/WarningWidget.dart b/lib/widgets/WarningWidget.dart index e0daa45b..69176d53 100644 --- a/lib/widgets/WarningWidget.dart +++ b/lib/widgets/WarningWidget.dart @@ -179,7 +179,7 @@ class WarningWidget extends StatelessWidget { width: 20, ), Text( - _warnMessage.source, + _warnMessage.source.name.toUpperCase(), style: TextStyle(fontSize: 12), ) ], diff --git a/lib/widgets/dialogs/ChangeLogDialog.dart b/lib/widgets/dialogs/ChangeLogDialog.dart index 77de8f86..264ea505 100644 --- a/lib/widgets/dialogs/ChangeLogDialog.dart +++ b/lib/widgets/dialogs/ChangeLogDialog.dart @@ -13,6 +13,13 @@ class ChangeLogDialog extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ + Text( + "0.7.0 (beta)", + style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold), + ), + Text("* Neues Design mit Material 3 \n" + "* Neue Benachrichtigungseinstellungen \n" + "* Fehlerbehebungen \n"), Text( "0.6.1 (beta)", style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold), diff --git a/lib/widgets/dialogs/WarningSeverityExplanation.dart b/lib/widgets/dialogs/WarningSeverityExplanation.dart new file mode 100644 index 00000000..da1e3f7a --- /dev/null +++ b/lib/widgets/dialogs/WarningSeverityExplanation.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class WarningSeverityExplanation extends StatefulWidget { + const WarningSeverityExplanation({Key? key}) : super(key: key); + + @override + _WarningSeverityExplanationState createState() => _WarningSeverityExplanationState(); +} + +class _WarningSeverityExplanationState extends State { + @override + Widget build(BuildContext context) { + return AlertDialog( + title: + Text(AppLocalizations.of(context)!.warning_severity_explanation_dialog_headline), + content: Container( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + RichText( + text: TextSpan( + text: '', + style: Theme.of(context) + .textTheme + .bodyMedium, //DefaultTextStyle.of(context).style, + children: [ + TextSpan( + text: AppLocalizations.of(context)!.notification_settings_slidervalue_extreme, + style: TextStyle(fontWeight: FontWeight.bold)), + TextSpan(text: '\n'), + TextSpan( + text: //warning_severity_explanation_dialog_extreme_description + AppLocalizations.of(context)!.warning_severity_explanation_dialog_extreme_description), + TextSpan(text: '\n\n'), + TextSpan( + text: AppLocalizations.of(context)!.notification_settings_slidervalue_severe, + style: TextStyle(fontWeight: FontWeight.bold)), + TextSpan(text: '\n'), + TextSpan( + text: //warning_severity_explanation_dialog_severe_description + AppLocalizations.of(context)!.warning_severity_explanation_dialog_severe_description), + TextSpan(text: '\n\n'), + TextSpan( + text: AppLocalizations.of(context)!.notification_settings_slidervalue_moderate, + style: TextStyle(fontWeight: FontWeight.bold)), + TextSpan(text: '\n'), + TextSpan( + text: //warning_severity_explanation_dialog_moderate_description + AppLocalizations.of(context)!.warning_severity_explanation_dialog_moderate_description), + TextSpan(text: '\n\n'), + TextSpan( + text: AppLocalizations.of(context)!.notification_settings_slidervalue_minor, + style: TextStyle(fontWeight: FontWeight.bold)), + TextSpan(text: '\n'), + TextSpan( + text: //warning_severity_explanation_dialog_minor_description + AppLocalizations.of(context)!.warning_severity_explanation_dialog_minor_description), + ], + ), + ), + ], + ), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text(AppLocalizations.of(context)!.main_dialog_close), + ), + ], + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 69860e8a..c55618e2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: foss_warn description: Eine FOSS Umsetzung von NINA publish_to: 'none' -version: 0.6.1+29 +version: 0.7.0+30 environment: sdk: ">=2.17.0 <3.13.3"