From 0182bf463bf26cf90d4e13caab4695710d01a6d4 Mon Sep 17 00:00:00 2001 From: spinel Date: Fri, 8 Jul 2022 04:08:50 +0200 Subject: [PATCH] translations, set name props as unique, set child data as deleted on deleting parents --- TODO | 54 +- ...stcqvrbhqs.apps.googleusercontent.com.json | 1 + assets/i18n/de.json | 501 ++++++++++++++++++ assets/i18n/en.json | 501 ++++++++++++++++++ build.yaml | 6 + ios/Runner.xcodeproj/project.pbxproj | 6 +- ios/Runner/Info.plist | 2 +- lib/components/app_theme.dart | 2 +- lib/components/detail.dart | 16 +- .../forms/time_of_day_form_field.dart | 5 +- lib/data_export.dart | 366 +++++++++++++ lib/localization_keys.dart | 453 ++++++++++++++++ lib/main.dart | 123 +++-- lib/models/accuracy.dart | 38 +- lib/models/basal.dart | 47 ++ lib/models/basal_profile.dart | 48 +- lib/models/bolus.dart | 53 ++ lib/models/bolus_profile.dart | 30 ++ lib/models/glucose_target.dart | 41 +- lib/models/log_bolus.dart | 60 ++- lib/models/log_entry.dart | 76 ++- lib/models/log_event.dart | 44 ++ lib/models/log_event_type.dart | 31 ++ lib/models/log_meal.dart | 56 +- lib/models/meal.dart | 49 +- lib/models/meal_category.dart | 26 + lib/models/meal_portion_type.dart | 37 +- lib/models/meal_source.dart | 42 +- lib/models/settings.dart | 35 +- .../{ingredient.dart => x_ingredient.dart} | 6 +- lib/models/{recipe.dart => x_recipe.dart} | 6 +- lib/models/{user.dart => x_user.dart} | 4 +- lib/navigation.dart | 18 +- lib/objectbox-model.json | 120 ++++- lib/objectbox.g.dart | 363 ++++++++++--- lib/screens/basal/basal_detail.dart | 52 +- lib/screens/basal/basal_list.dart | 22 +- lib/screens/basal/basal_profile_detail.dart | 77 ++- lib/screens/basal/basal_profile_list.dart | 62 ++- lib/screens/bolus/bolus_detail.dart | 63 ++- lib/screens/bolus/bolus_list.dart | 32 +- lib/screens/bolus/bolus_profile_detail.dart | 54 +- lib/screens/bolus/bolus_profile_list.dart | 54 +- lib/screens/category/accuracy_detail.dart | 29 +- lib/screens/category/accuracy_list.dart | 10 +- lib/screens/category/categories.dart | 14 +- lib/screens/category/event_type_detail.dart | 29 +- lib/screens/category/event_type_list.dart | 10 +- .../category/meal_category_detail.dart | 22 +- lib/screens/category/meal_category_list.dart | 12 +- .../category/meal_portion_type_detail.dart | 24 +- .../category/meal_portion_type_list.dart | 12 +- lib/screens/category/meal_source_detail.dart | 31 +- lib/screens/category/meal_source_list.dart | 12 +- .../log/log_entry/log_bolus_detail.dart | 60 ++- lib/screens/log/log_entry/log_bolus_list.dart | 36 +- lib/screens/log/log_entry/log_entry.dart | 37 +- .../log/log_entry/log_meal_detail.dart | 44 +- lib/screens/log/log_entry/log_meal_list.dart | 12 +- .../log/log_event/log_event_detail.dart | 44 +- lib/screens/log/log_event/log_event_list.dart | 24 +- .../log/log_event/log_event_type_detail.dart | 304 ----------- .../log/log_event/log_event_type_list.dart | 123 ----- lib/screens/log/log_filter_dialog.dart | 230 ++++++++ lib/screens/log/log_overview.dart | 61 ++- lib/screens/meal/meal_detail.dart | 49 +- lib/screens/meal/meal_list.dart | 22 +- lib/screens/reports/daily_chart.dart | 25 +- lib/screens/reports/export.dart | 53 +- lib/screens/reports/reports.dart | 8 +- .../{recipe => x_recipe}/recipe_detail.dart | 4 +- .../{recipe => x_recipe}/recipe_list.dart | 6 +- lib/settings.dart | 96 ++-- lib/utils/dialog_utils.dart | 23 +- objectbox/data.mdb | Bin 2912256 -> 2953216 bytes objectbox/lock.mdb | Bin 8192 -> 8192 bytes pubspec.lock | 77 ++- pubspec.yaml | 11 +- test/widget_test.dart | 2 +- web/index.html | 174 +++--- windows/runner/Runner.rc | 2 +- 81 files changed, 4199 insertions(+), 1215 deletions(-) create mode 100644 android/client_secret_988592836243-pmdkvghnvd6fdeo4qm0sjgstcqvrbhqs.apps.googleusercontent.com.json create mode 100644 assets/i18n/de.json create mode 100644 assets/i18n/en.json create mode 100644 build.yaml create mode 100644 lib/data_export.dart create mode 100644 lib/localization_keys.dart rename lib/models/{ingredient.dart => x_ingredient.dart} (95%) rename lib/models/{recipe.dart => x_recipe.dart} (93%) rename lib/models/{user.dart => x_user.dart} (97%) delete mode 100644 lib/screens/log/log_event/log_event_type_detail.dart delete mode 100644 lib/screens/log/log_event/log_event_type_list.dart create mode 100644 lib/screens/log/log_filter_dialog.dart rename lib/screens/{recipe => x_recipe}/recipe_detail.dart (99%) rename lib/screens/{recipe => x_recipe}/recipe_list.dart (98%) diff --git a/TODO b/TODO index 3f69227..b57ddb2 100644 --- a/TODO +++ b/TODO @@ -1,48 +1,45 @@ MAIN TASKS: Database: - ☐ set name properties as unique (and add checks to forms) - ☐ implement users - ☐ check objectbox docs on how to make users - ☐ tie all data to users - ☐ add user filters to all getters - ☐ change settings to not be a singleton, but only one settings instance per user and one default entry - ☐ add login and authentification - ☐ enable restoring data from sync - ☐ find a solution for storage - ☐ hosting - ☐ objectbox sync (commercial use is not free) - ☐ implement alternative data export for now? ☐ create default datasets for configuration (meal categories, portion types, accuracies, event types, possibly meal source) + ☐ cleanup unneeded models (and make sure the app still runs) Features: - ☐ app icon - ☐ add explanations to each section - ☐ german language support - ☐ indicate nested creation process (creating from dropdown etc) ☐ indicate read only fields + ☐ indicate nested creation process (creating from dropdown etc) ☐ app info/credits screen + ☐ add explanations to each section + ☐ app icon Components/Framework: + ☐ check through all detail forms and set required fields/according messages + ☐ show indicator and make all fields readonly if user somehow gets to a deleted record detail view + ☐ check for changes before navigating as well (not just on cancel) + ☐ fix bug when navigating while creating/editing a record + ☐ change placement of delete and floating button because its very easy to accidentally hit delete + ☐ dropdown tweaks + ☐ edit item -> cancel: shouldn't clear dropdwon + ☐ account for deleted/disabled elements + ☐ change app id from "com.example.diameter" to "at.sarahziesel.diameter" ☐ come up with new concept for duration component ☐ update duration fields to use corresponding component ☐ log event type detail (reminder duration) ☐ log event detail (reminder duration) ☐ meal (bolus delay) ☐ log bolus (delay) - ☐ check through all detail forms and set required fields/according messages - ☐ change placement of delete and floating button because its very easy to accidentally hit delete - ☐ check for changes before navigating as well (not just on cancel) - ☐ show indicator and make all fields readonly if user somehow gets to a deleted record detail view - ☐ dropdown tweaks - ☐ edit item -> cancel: shouldn't clear dropdwon - ☐ account for deleted/disabled elements Log: ☐ add filters ☐ check if there is still an active bolus when suggesting glucose bolus + ☐ remember/display setting for correction boli correctly Log Events: ☐ add filters + ☐ don't show warning for existing event if it's the one being edited + ☐ add create button to event type dropdown Categorization: ☐ add colors to event types as indicators for log entries and graphs in reports - ☐ implement reminders as push notifications + ☐ implement reminders as push notifications Settings: + ☐ export functionality + ☐ implement loading from google drive + ☐ find out pricing for google apis + ☐ add some options (which data to export, including dead records...) ☐ option to switch theme ☐ add fields for glucose target tiers (as map of cutoff glucose and colors) ☐ add field for active insulin duration @@ -50,6 +47,15 @@ MAIN TASKS: ☐ add functionality to delete dead records (meaning: set deleted flag and no relations to undeleted records) Archive: + ✔ set child data as deleted on deleting parent (ie. log boli for log entry etc) @done(22-06-19 23:44) @project(MAIN TASKS.Database) + ✔ set name properties as unique @done(22-06-17 01:34) @project(MAIN TASKS.Database) + ✔ german language support @done(22-06-17 01:19) @project(MAIN TASKS.Features) + ✔ add translations for all sections @done(22-06-17 01:19) @project(MAIN TASKS.Features) + ✔ add data source field to all models @done(22-04-15 00:24) @project(MAIN TASKS.Database) + ✔ add export methods to all models @done(22-04-12 21:38) @project(MAIN TASKS.Database) + ✔ add import methods to all models @done(22-04-15 00:24) @project(MAIN TASKS.Database) + ✔ implement export to json @done(22-04-12 21:43) @project(MAIN TASKS.Settings) + ✔ implement saving export to google drive @done(22-04-13 15:01) @project(MAIN TASKS.Settings) ✔ switch day on swipe @done(22-03-19 23:20) @project(MAIN TASKS.Log) ✔ switch day on swipe @done(22-03-19 23:23) @project(MAIN TASKS.Log Events) ✔ switch day on swipe @done(22-03-19 23:20) @project(MAIN TASKS.Reports) diff --git a/android/client_secret_988592836243-pmdkvghnvd6fdeo4qm0sjgstcqvrbhqs.apps.googleusercontent.com.json b/android/client_secret_988592836243-pmdkvghnvd6fdeo4qm0sjgstcqvrbhqs.apps.googleusercontent.com.json new file mode 100644 index 0000000..672e5ff --- /dev/null +++ b/android/client_secret_988592836243-pmdkvghnvd6fdeo4qm0sjgstcqvrbhqs.apps.googleusercontent.com.json @@ -0,0 +1 @@ +{"installed":{"client_id":"988592836243-pmdkvghnvd6fdeo4qm0sjgstcqvrbhqs.apps.googleusercontent.com","project_id":"coastal-fiber-347020","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs"}} \ No newline at end of file diff --git a/assets/i18n/de.json b/assets/i18n/de.json new file mode 100644 index 0000000..6c58d01 --- /dev/null +++ b/assets/i18n/de.json @@ -0,0 +1,501 @@ +{ + "accuracy": { + "confirmDelete": "Willst du diese Präzision wirklich löschen?", + "deleted": "Präzision gelöscht", + "detail": { + "title": "{status} Präzision" + }, + "empty": "Du hast noch keine Präzisionen erstellt!", + "fields": { + "confidenceRating": "Reihung", + "fürCarbsRatio": "für KH-Verhältnis", + "fürPortionSize": "für Portionsgröße", + "name": "Name", + "notes": "Bemerkung", + "validators": { + "name": "Name ist leer" + } + }, + "new": "Neue", + "saved": { + "1": "{status}Präzision gespeichert", + "else": "{status}Präzisionen gespeichert" + }, + "title": "Präzision" + }, + "basal": { + "confirmDelete": "Willst du diese Basalrate wirklich löschen?", + "deleted": "Basalrate gelöscht", + "empty": "Du hast noch keine Basalraten erstellt!", + "fields": { + "endTime": "Endzeit", + "startTime": "Startzeit", + "units": "Einheiten" + }, + "new": "Neue", + "saved": { + "1": "{status}Basalrate gespeichert", + "else": "{status}Basalraten gespeichert" + }, + "title": "{status} Basalrate für {profileName}", + "warnings": { + "duplicate": "Es gibt bereits eine Rate mit dieser Startzeit.", + "endTimeLast": "Letzter Basal des Tages muss um 00:00 enden", + "gap": "Lücke zwischen dieser und der vorigen Rate", + "overlap": "Zeitraum der Rate überlappt mit einer anderen", + "startTimeFirst": "Erster Basal des Tages muss um 00:00 beginnen" + } + }, + "basalProfile": { + "activated": "{profileName} wurde als aktives Profil ausgewählt", + "active": "Derzeit aktives Profil", + "confirmDelete": "Willst du dieseBasal Profile?", + "copied": "Kopie von {profileName} hinzugefügt", + "copyOf": "Kopie von {profileName}", + "default": "Standard-Profil", + "deleted": "Basalprofil gelöscht", + "detail": { + "tabs": { + "profile": "Profil", + "rates": "Raten" + }, + "title": "{status} Basalprofil {profileName}" + }, + "empty": "Du hast noch kein Basalprofil erstellt!", + "fields": { + "active": "aktiv", + "name": "Name", + "notes": "Bemerkung", + "validators": { + "name": "Name ist leer" + } + }, + "new": "Neues", + "saved": "{status}Basalprofil gespeichert", + "title": "Basalprofile", + "warnings": { + "activeAlreadySet": "Es gibt bereits ein oder mehrere aktive Profile. Was möchtest du tun?", + "multipleActive": "Mehr als ein aktives Profil.", + "noActive": "Kein aktives Basalprofil.", + "noActiveOnCreate": "Im Moment ist kein Profil aktiv. Möchtest du dieses aktivieren?", + "resolve": { + "activate": "Profil aktivieren", + "activateCurrent": "Dieses Profil aktivieren", + "create": "Profil erstellen", + "createInstead": "Stattdessen neues Profil erstellen", + "deactivateOthers": "Alle anderen deaktivieren", + "deactivateProfile": "{profileName} deaktivieren", + "ignore": "Ignorierem", + "pick": "Wähle ein Profil" + } + } + }, + "bolus": { + "confirmDelete": "Willst du diese Bolusrate wirklich löschen?", + "deleted": "Bolusrate gelöscht", + "empty": "Du hast noch keine Bolusraten erstellt!", + "fields": { + "endTime": "Endzeit", + "perCarbs": "pro KH", + "perGlucose": "pro {glucoseMeasurementSuffix}", + "startTime": "Startzeit", + "units": "Einheiten" + }, + "new": "Neue", + "saved": { + "1": "{status}Bolusrate gespeichert", + "else": "{status}Bolusraten gespeichert" + }, + "title": "{status} Bolusrate für {profileName}", + "warnings": { + "duplicate": "Es gibt bereits eine Rate mit dieser Startzeit.", + "endTimeLast": "Letzter Bolus des Tages muss um 00:00 enden", + "gap": "Lücke zwischen dieser und der vorigen Rate", + "overlap": "Zeitraum der Rate überlappt mit einer anderen", + "startTimeFirst": "Erster Bolus des Tages muss um 00:00 beginnen" + } + }, + "bolusProfile": { + "activated": "{profileName} wurde als aktives Profil ausgewählt", + "active": "Derzeit aktives Profil", + "confirmDelete": "Willst du dieses Boluspofil wirklich löschen?", + "copied": "Kopie von {profileName} hinzugefügt", + "copyOf": "Kopie von {profileName}", + "default": "Standardprofil", + "deleted": "Bolusprofil gelöscht", + "detail": { + "tabs": { + "profile": "Profil", + "rates": "Raten" + }, + "title": "{status} Bolusprofil {profileName}" + }, + "empty": "Du hast noch keine Bolusprofile erstellt!", + "fields": { + "active": "aktiv", + "name": "Name", + "notes": "Bemerkung", + "validators": { + "name": "Bezeichnung ist leer" + } + }, + "new": "Neues", + "saved": "{status}Bolusprofil gespeichert", + "title": "Bolusprofile", + "warnings": { + "activeAlreadySet": "Es gibt bereits ein oder mehrere aktive Profile. Was möchtest du tun?", + "multipleActive": "Mehr als ein aktives Profil.", + "noActive": "Kein aktives Bolusprofil.", + "noActiveOnCreate": "Im Moment ist kein Profil aktiv. Möchtest du dieses aktivieren?", + "resolve": { + "activate": "Profil aktivieren", + "activateCurrent": "Dieses Profil aktivieren", + "create": "Profil erstellen", + "createInstead": "Stattdessen neues Profil erstellen", + "deactivateOthers": "Alle anderen deaktivieren", + "deactivateProfile": "{profileName} deaktivieren", + "ignore": "Ignorierem", + "pick": "Wähle ein Profil" + } + } + }, + "categories": "Kategorien", + "event": { + "confirmDelete": "Willst du dieses Ereignis wirklich löschen?", + "confirmEnd": "Willst du dieses Ereignis wirklich beenden?", + "deleted": "Log-Ereignis gelöscht", + "detail": { + "title": "{status} Log-Ereignis {name}" + }, + "empty": "Kkeine Ereigniss für dieses Datum!", + "emptyActive": "Keine aktiven Ereignisse!", + "end": "Ereignis beenden", + "ended": "Log-Ereignis beendet", + "fields": { + "basalProfile": "Basalprofil", + "bolusProfile": "Bolusprofil", + "date": "Datum", + "endDate": "Enddatum", + "endTime": "Endzeit", + "eventType": "Ereignis-Typ", + "hasEndTime": "hat Endzeitpunkt", + "notes": "Bemerkung", + "reminderDuration": "Dauer für Erinnerung", + "startDate": "Startdatum", + "startTime": "Startzeit", + "time": "Zeit" + }, + "new": "Neues", + "saved": "{status}Log-Ereignis gespeichert", + "title": "Log-Ereignisse", + "titleActive": "Aktive Ereignisse", + "warnings": { + "duplicate": "Im angegebenen Zeitraum gibt es bereits ein aktives Ereignis vom selben Typ. Was möchtest du tun?" + } + }, + "eventType": { + "deleted": "Log-Ereignis-Typ gelöscht", + "detail": { + "title": "{status} Log-Ereignis-Typ {name}" + }, + "empty": "Du hast noch keine Log-Ereignis-Typen erstellt!", + "fields": { + "basalProfile": "Basalprofil", + "bolusProfile": "Bolusprofil", + "defaultReminderDuration": "Standarddauer für Erinnerung", + "hasEndTime": "hat Endzeitpunkt", + "name": "Name", + "notes": "Bemerkung", + "validators": { + "name": "Name ist leer" + } + }, + "new": "Neuer", + "saved": "{status}Log-Ereignis-Typ gespeichert", + "title": "Log-Ereignis-Typen" + }, + "export": { + "error": "Error beim Import der folgenden Daten: {data}" + }, + "general": { + "apply": "Anwenden", + "cancel": "Abbrechen", + "close": "Schließen", + "confirm": "Bestätigen", + "confirmDelete": "Willst du diesen Eintrag wirklich löschen?", + "confirmDiscard": "Änderungen wurden bereits vorgenommen. Eingaben verwerfen?", + "discard": "Verwerfen", + "edit": "Bearbeiten", + "example": "Beispiel", + "keepEditing": "Weiter bearbeiten", + "next": "Weiter", + "per": "pro", + "save": "Speichern", + "saveAndClose": "Speichern & Schließen", + "saveAsIs": "Aktuellen Stand speichern", + "suffixes": { + "carbs": "{nutritionMeasurement} KH", + "carbsPerU": "{nutritionMeasurementSuffix} KH pro E", + "mins": " min", + "perDay": "am Tag", + "units": "E", + "uPerBreadUnit": "E pro BE", + "uPerGlucose": "{glucoseMeasurementSuffix} pro Einheit" + } + }, + "log": { + "confirmDelete": "Willst du diesen Logeintrag wirklich löschen?", + "deleted": "Logeintrag gelöscht", + "empty": "Du hast noch keine Logeinträge für dieses Datum erstellt!", + "fields": { + "date": "Datum", + "glucose": "Blutzucker", + "time": "Zeit" + }, + "filter": { + "endDate": "Endzeit", + "maxGlucose": "max. {glucoseMeasurement}", + "meal": "Mahlzeit", + "mealNameContains": "Name der Mahlzeit enthält", + "minGlucose": "min. {glucoseMeasurement}", + "noteContains": "Bemerkung enthält", + "startDate": "Startzeit" + }, + "saved": "{status}Logeintrag gespeichert", + "tabs": { + "bolus": { + "confirmDelete": "Willst du diesen Bolus wirklich löschen?", + "delayedBy": "(um {delay} verzögert)", + "deleted": "Bolus gelöscht", + "detail": { + "fields": { + "carbs": "KH", + "correction": "Korrektur", + "current": "Aktuell", + "delayedBolus": "Verzögerter Bolus", + "delayedBolusDuration": "Dauer Verzögerter Bolus", + "forGlucose": "für Blutzucker", + "forMeal": "für Mahlzeit", + "immediateBolus": "Sofortiger Bolus", + "meal": "Mahlzeit", + "setManually": "manuall einstellen", + "target": "Zielwert", + "units": "Bolus-Einheiten" + }, + "title": "{status} Bolus" + }, + "empty": "Du hast noch keine Boli für diesen Logeintrag erstellt!", + "forMeal": "für {meal}", + "new": "Neuer", + "saved": "{status}Bolus gespeichert", + "title": "Boli", + "toCorrect": "zur Korrektur von {correction}" + }, + "general": "Allgemein", + "meal": { + "confirmDelete": "Willst du diese Mahlzeit wirklich löschen?", + "deleted": "Mahlzeit gelöscht", + "detail": { + "fields": { + "amount": "Menge", + "carbsRatio": "KH-Verhältnis", + "carbsRatioAccuracy": "Präzision KH-Verhältnis", + "meal": "Mahlzeit", + "mealCategory": "Kategorie", + "mealPortionType": "Portionstyp", + "mealSource": "Herkunft", + "name": "Name", + "notes": "Bemerkung", + "portionSize": "Portionsgröße", + "portionSizeAccuracy": "Präzision Portionsgröße", + "setManually": "KH-Verhältnis manuell einstellen", + "validators": { + "name": "Name ist leer" + } + }, + "title": "{status} Mahlzeit" + }, + "empty": "Du hast noch keine Mahlzeiten für diesen Logeintrag erstellt!", + "new": "Neue", + "saved": "{status}Mahlzeit gespeichert", + "title": "Mahlzeiten" + } + }, + "title": "Logeinträge" + }, + "meal": { + "confirmDelete": "Willst du diese Mahlzeit wirklich löschen?", + "deleted": "Mahlzeit gelöscht", + "detail": { + "title": "{status} Mahlzeit {name}" + }, + "empty": "Du hast noch keine Mahlzeiten erstellt!", + "fields": { + "additional": { + "carbsRatioAccuracy": "Präzision KH-Verhältnis", + "mealCategory": "Kategorie", + "portionSizeAccuracy": "Präzision Portionsgröße", + "title": "Zusätzliche Felder" + }, + "carbsPerPortion": "KH pro Portion", + "carbsRatio": "KH-Verhältnis", + "delay": { + "duration": "Dauer", + "title": "Verzögerter Bolus" + }, + "mealPortionType": "Portionstyp", + "mealSource": "Herkunft", + "name": "Name", + "notes": "Bemerkung", + "portionSize": "Portionsgröße", + "setManually": "KH-Verhältnis manuell einstellen", + "validators": { + "name": "Name ist leer" + } + }, + "new": "Neue", + "saved": "{status}Mahlzeit gespeichert", + "title": "Mahlzeiten" + }, + "mealCategory": { + "deleted": "Mahlzeiten-Kategorie gelöscht", + "detail": { + "title": "{status} Mahlzeiten-Kategorie {name}" + }, + "empty": "Du hast noch keine Mahlzeiten-Kategorien erstellt!", + "fields": { + "name": "Name", + "notes": "Bemerkung", + "validators": { + "name": "Name ist leer" + } + }, + "new": "New", + "saved": "{status}Mahlzeiten-Kategorie gespeichert", + "title": "Mahlzeiten-Kategorien" + }, + "mealSource": { + "deleted": "Mahlzeiten-Herkunft gelöscht", + "detail": { + "title": "{status} Mahlzeiten-Herkunft {name}" + }, + "empty": "Du hast noch keine Mahlzeiten-Herkünfte erstellt!", + "fields": { + "defaultCarbsRatioAccuracy": "Standard-Präzision KH-Verhältnis", + "defaultMealCategory": "Standard Mahlzeiten-Kategorie", + "defaultMealPortionType": "Standard-Portionstyp", + "defaultPortionSizeAccuracy": "Standard-Präzision Portionsgröße", + "name": "Name", + "notes": "Bemerkung", + "validators": { + "name": "Name ist leer" + } + }, + "new": "New", + "saved": "{status}Mahlzeiten-Herkunft gespeichert", + "title": "Mahlzeiten-Herkünfte" + }, + "navigation": { + "basalProfiles": "Basalprofile", + "bolusProfiles": "Bolusprofile", + "categorization": "Kategorisierung", + "log": "Log", + "logEvent": "Log-Ereignisse", + "meals": "Mahlzeiten", + "reports": "Berichte", + "settings": "Einstellungen" + }, + "portionType": { + "deleted": "Mahlzeiten-Portionstyp gelöscht", + "detail": { + "title": "{status} Mahlzeiten-Portionstyp {name}" + }, + "empty": "Du hast noch keine Mahlzeiten-Portionstypen erstellt!", + "fields": { + "name": "Name", + "notes": "Bemerkung", + "validators": { + "name": "Name ist leer" + } + }, + "new": "New", + "saved": "{status}Mahlzeiten-Portionstyp gespeichert", + "title": "Mahlzeiten-Portionstypen" + }, + "reports": { + "dailyChart": { + "empty": "Du hast noch keine Logeinträge für dieses Datum erstellt!", + "showBasal": "zeige Basal", + "showBolus": "zeige Bolus", + "showChart": "zeige Grafik", + "showMeals": "zeige Mahlzeiten", + "title": "Tagesgrafik" + }, + "export": { + "date": "Datum", + "delayedBy": "(um {delay} verzögert)", + "endDate": "Enddatum", + "export": "exportieren", + "range": "Zeitraum", + "showBasal": "zeige Basal", + "showBolus": "zeige Bolus", + "showChart": "zeige Grafik", + "showMahlzeits": "zeige Mahlzeiten", + "showTable": "zeige Tabelle", + "singleDate": "Bestimmtes Datum", + "tableHeaders": { + "bolus": "Bolus", + "carbs": "Kohlenhydrate", + "glucose": "Blutzucker", + "mealBolus": "Mahlzeit Bolus", + "mealBemerkung": "Mahlzeit Bemerkung", + "meals": "Mahlzeit", + "notes": "Bemerkung", + "portionSize": "Portionsgröße", + "time": "Zeit" + }, + "title": "Log-Übersicht" + }, + "ids": { + "basal": "Basal", + "bolus": "Bolus", + "carbs": "Kohlenhydrate", + "glucose": "Blutzucker" + }, + "sections": { + "dailyReport": "Tagesbericht", + "pdfReport": "PDF-Übersicht" + }, + "title": "Berichte" + }, + "settings": { + "confirmReset": "Bist du sicher, dass du alle Einstellungen zurücksetzen möchtest?", + "fields": { + "confirmOnCancel": "beim Abbrechen der Bearbeitung eines Eintrags, wenn bereits Änderungen vorgenommen wurden", + "confirmOnDelete": "beim Löschen eines Eintrags", + "confirmOnEndEreignis": "beim Beenden eines Ereignisses", + "dateformat": "Datumsformat", + "displayBothDetail": "zeige beide Blutzucker-Maßeinheiten in der Detailansicht", + "displayBothList": "display both glucose measurements in list view", + "glucoseMeasurement": "Bevorzugte Maßeinheit für Blutzuckerwerte", + "insulinIncrement": "Insulin-Dosierungsschritte", + "longDateformat": "Datumsformat lang", + "longTimeformat": "Zeitformat lang", + "mmolLIncrement": "Schritte Mmol/l", + "nutritionIncrement": "Schritte Maßeinheit für Mahlzeiten", + "nutritionMeasurement": "Bevorzugte Maßeinheit für Mahlzeiten", + "onlyDisplayActive": "zeige nur aktive Blutzucker-Maßeinheit", + "targetGlucose": "Ziel-Blutzucker", + "timeformat": "Zeitformat" + }, + "reset": "Einstellungen wurden auf Standardeinstellungen zurückgesetzt", + "resetAll": "Alle zurücksetzen", + "sections": { + "confirmation": "Bestätigungen", + "dateTimeformat": "Zeit- und Datumsformat", + "measurements": "Maßeinheiten" + }, + "title": "Applikationseinstellungen", + "updated": "Einstellungen gespeichert" + } +} diff --git a/assets/i18n/en.json b/assets/i18n/en.json new file mode 100644 index 0000000..7d0fba8 --- /dev/null +++ b/assets/i18n/en.json @@ -0,0 +1,501 @@ +{ + "accuracy": { + "confirmDelete": "Are you sure you want to delete this Accuracy?", + "deleted": "Accuracy deleted", + "detail": { + "title": "{status} Accuracy" + }, + "empty": "You have not created any Accuracies yet!", + "fields": { + "confidenceRating": "Confidence Rating", + "forCarbsRatio": "for Carbs Ratio", + "forPortionSize": "for Portion Size", + "name": "Name", + "notes": "Notes", + "validators": { + "name": "Empty Name" + } + }, + "new": "New", + "saved": { + "1": "{status}Accuracy saved", + "else": "{status}Accuracy saved" + }, + "title": "Accuracies" + }, + "basal": { + "confirmDelete": "Are you sure you want to delete this Basal Rate?", + "deleted": "Basal Rate deleted", + "empty": "You have not created any Basal Rates yet!", + "fields": { + "endTime": "End Time", + "startTime": "Start Time", + "units": "Units" + }, + "new": "New", + "saved": { + "1": "{status}Basal Rate saved", + "else": "{status}Basal Rates saved" + }, + "title": "{status} Basal Rate for {profileName}", + "warnings": { + "duplicate": "There's already a rate with this start time.", + "endTimeLast": "Last Basal of the day needs to end at 00:00", + "gap": "There's a time gap between this and the previous rate", + "overlap": "This rate's time period overlaps with another one.", + "startTimeFirst": "First Basal of the day needs to start at 00:00" + } + }, + "basalProfile": { + "activated": "{profileName} has been set as your active Profile", + "active": "Current Active Profile", + "confirmDelete": "Are you sure you want to delete this Basal Profile?", + "copied": "Added copy of {profileName}", + "copyOf": "Copy of {profileName}", + "default": "Default Profile", + "deleted": "Basal Profile deleted", + "detail": { + "tabs": { + "profile": "Profile", + "rates": "Rates" + }, + "title": "{status} Basal Profile {profileName}" + }, + "empty": "You have not created any Basal Profiles yet!", + "fields": { + "active": "active", + "name": "Name", + "notes": "Notes", + "validators": { + "name": "Empty title" + } + }, + "new": "New", + "saved": "{status}Basal Profile saved", + "title": "Basal Profiles", + "warnings": { + "activeAlreadySet": "There are already one or more active profiles. What would you like to do?", + "multipleActive": "More than one active Basal Profile has been found.", + "noActive": "You currently do not have an active Basal Profile.", + "noActiveOnCreate": "There is currently no active profile. Would you like to set this one as active?", + "resolve": { + "activate": "Activate a Profile", + "activateCurrent": "Activate This Profile", + "create": "Create a Profile", + "createInstead": "Create a New Profile Instead", + "deactivateOthers": "Deactivate All Others", + "deactivateProfile": "Deactivate {profileName}", + "ignore": "Ignore", + "pick": "Pick a Profile" + } + } + }, + "bolus": { + "confirmDelete": "Are you sure you want to delete this Bolus Rate?", + "deleted": "Bolus Rate deleted", + "empty": "You have not created any Bolus Rates yet!", + "fields": { + "endTime": "End Time", + "perCarbs": "per carns", + "perGlucose": "per {glucoseMeasurementSuffix}", + "startTime": "Start Time", + "units": "Units" + }, + "new": "New", + "saved": { + "1": "{status}Bolus Rate saved", + "else": "{status}Bolus Rates saved" + }, + "title": "{status} Bolus Rate for {profileName}", + "warnings": { + "duplicate": "There's already a rate with this start time.", + "endTimeLast": "Last Bolus of the day needs to end at 00:00", + "gap": "There's a time gap between this and the previous rate", + "overlap": "This rate's time period overlaps with another one.", + "startTimeFirst": "First Bolus of the day needs to start at 00:00" + } + }, + "bolusProfile": { + "activated": "{profileName} has been set as your active Profile", + "active": "Current Active Profile", + "confirmDelete": "Are you sure you want to delete this Bolus Profile?", + "copied": "Added copy of {profileName}", + "copyOf": "Copy of {profileName}", + "default": "Default Profile", + "deleted": "Bolus Profile deleted", + "detail": { + "tabs": { + "profile": "Profile", + "rates": "Rates" + }, + "title": "{status} Bolus Profile {profileName}" + }, + "empty": "You have not created any Bolus Profiles yet!", + "fields": { + "active": "active", + "name": "Name", + "notes": "Notes", + "validators": { + "name": "Empty title" + } + }, + "new": "New", + "saved": "{status}Bolus Profile saved", + "title": "Bolus Profiles", + "warnings": { + "activeAlreadySet": "There are already one or more active profiles. What would you like to do?", + "multipleActive": "More than one active Bolus Profile has been found.", + "noActive": "You currently do not have an active Bolus Profile.", + "noActiveOnCreate": "There is currently no active profile. Would you like to set this one as active?", + "resolve": { + "activate": "Activate a Profile", + "activateCurrent": "Activate This Profile", + "create": "Create a Profile", + "createInstead": "Create a New Profile Instead", + "deactivateOthers": "Deactivate All Others", + "deactivateProfile": "Deactivate {profileName}", + "ignore": "Ignore", + "pick": "Pick a Profile" + } + } + }, + "categories": "Categories", + "event": { + "confirmDelete": "Are you sure you want to delete this Log Event?", + "confirmEnd": "Are you sure you want to end this Event?", + "deleted": "Log Event deleted", + "detail": { + "title": "{status} Log Event {name}" + }, + "empty": "There are no Events for that date!", + "emptyActive": "There are no Active Events!", + "end": "End Event", + "ended": "Log Event ended", + "fields": { + "basalProfile": "Basal Profile", + "bolusProfile": "Bolus Profile", + "date": "Date", + "endDate": "End Date", + "endTime": "End Time", + "eventType": "Event Type", + "hasEndTime": "has end time", + "notes": "Notes", + "reminderDuration": "Reminder Duration", + "startDate": "Start Date", + "startTime": "Start Time", + "time": "Time" + }, + "new": "New", + "saved": "{status}Log Event saved", + "title": "Log Events", + "titleActive": "Active Events", + "warnings": { + "duplicate": "An Event of this type is already active within the set time frame. What would you like to do?" + } + }, + "eventType": { + "deleted": "Log Event Type deleted", + "detail": { + "title": "{status} Log Event Type {name}" + }, + "empty": "You have not created any Log Event Types yet!", + "fields": { + "basalProfile": "Basal Profile", + "bolusProfile": "Bolus Profile", + "defaultReminderDuration": "Default Reminder Duration", + "hasEndTime": "has end time", + "name": "Name", + "notes": "Notes", + "validators": { + "name": "Empty name" + } + }, + "new": "New", + "saved": "{status}Log Event Type saved", + "title": "Log Event Types" + }, + "export": { + "error": "Error importing the following data: {data}" + }, + "general": { + "apply": "Apply", + "cancel": "Cancel", + "close": "Close", + "confirm": "Confirm", + "confirmDelete": "Are you sure you want to delete this record?", + "confirmDiscard": "You already made some changes. Discard your input?", + "discard": "Discard", + "edit": "Edit", + "example": "Example", + "keepEditing": "Keep Editing", + "next": "Next", + "per": "per", + "save": "Save", + "saveAndClose": "Save & Close", + "saveAsIs": "Save As Is", + "suffixes": { + "carbs": "{nutritionMeasurement} carbs", + "carbsPerU": "{nutritionMeasurementSuffix} carbs per U", + "mins": " min", + "perDay": "per day", + "units": "U", + "uPerBreadUnit": "U per bread unit", + "uPerGlucose": "{glucoseMeasurementSuffix} per unit" + } + }, + "log": { + "confirmDelete": "Are you sure you want to delete this Log Entry?", + "deleted": "Log Entry deleted", + "empty": "You have not created any Log Entries for this date yet!", + "fields": { + "date": "Date", + "glucose": "Blood Glucose", + "time": "Time" + }, + "filter": { + "endDate": "End Date", + "maxGlucose": "max {glucoseMeasurement}", + "meal": "Meal", + "mealNameContains": "Meal Name Contains", + "minGlucose": "min {glucoseMeasurement}", + "noteContains": "Note Contains", + "startDate": "Start Date" + }, + "saved": "{status}Log Entry saved", + "tabs": { + "bolus": { + "confirmDelete": "Are you sure you want to delete this Bolus?", + "delayedBy": "(delayed by {delay})", + "deleted": "Bolus deleted", + "detail": { + "fields": { + "carbs": "Carbs", + "correction": "Correction", + "current": "Current", + "delayedBolus": "Delayed Bolus", + "delayedBolusDuration": "Delayed Bolus Duration", + "forGlucose": "for glucose", + "forMeal": "for meal", + "immediateBolus": "Immediate Bolus", + "meal": "Meal", + "setManually": "set manually", + "target": "Target", + "units": "Bolus Units" + }, + "title": "{status} Bolus" + }, + "empty": "You have not added any Boli to this Log Entry yet!", + "forMeal": "for {meal}", + "new": "New", + "saved": "{status}Bolus saved", + "title": "Boli", + "toCorrect": "to correct {correction}" + }, + "general": "General", + "meal": { + "confirmDelete": "Are you sure you want to delete this Meal?", + "deleted": "Meal deleted", + "detail": { + "fields": { + "amount": "Amount", + "carbsRatio": "Carbs Ratio", + "carbsRatioAccuracy": "Carbs Ratio Accuracy", + "meal": "Meal", + "mealCategory": "Meal Category", + "mealPortionType": "Meal Portion Type", + "mealSource": "Meal Source", + "name": "Name", + "notes": "Notes", + "portionSize": "Portion Size", + "portionSizeAccuracy": "Portion Size Accuracy", + "setManually": "set carbs ratio manually", + "validators": { + "name": "Empty Name" + } + }, + "title": "{status} Meal" + }, + "empty": "You have not added any Meals to this Log Entry yet!", + "new": "New", + "saved": "{status}Meal saved", + "title": "Meals" + } + }, + "title": "Log Entries" + }, + "meal": { + "confirmDelete": "Are you sure you want to delete this Meal?", + "deleted": "Meal deleted", + "detail": { + "title": "{status} Meal {name}" + }, + "empty": "You have not created any Meals yet!", + "fields": { + "additional": { + "carbsRatioAccuracy": "Carbs Ratio Accuracy", + "mealCategory": "Meal Category", + "portionSizeAccuracy": "Portion Size Accuracy", + "title": "Additional Fields" + }, + "carbsPerPortion": "Carbs Per Portion", + "carbsRatio": "Carbs Ratio", + "delay": { + "duration": "Duration", + "title": "Bolus Delay" + }, + "mealPortionType": "Meal Portion Type", + "mealSource": "Meal Source", + "name": "Name", + "notes": "Notes", + "portionSize": "Portion Size", + "setManually": "set carbs ratio manually", + "validators": { + "name": "Empty Name" + } + }, + "new": "New", + "saved": "{status}Meal saved", + "title": "Meals" + }, + "mealCategory": { + "deleted": "Meal Category deleted", + "detail": { + "title": "{status} Meal Category {name}" + }, + "empty": "You have not created any Meal Categories yet!", + "fields": { + "name": "Name", + "notes": "Notes", + "validators": { + "name": "Empty name" + } + }, + "new": "New", + "saved": "{status}Meal Category saved", + "title": "Meal Categories" + }, + "mealSource": { + "deleted": "Meal Source deleted", + "detail": { + "title": "{status} Meal Source {name}" + }, + "empty": "You have not created any Meal Sources yet!", + "fields": { + "defaultCarbsRatioAccuracy": "Default Carbs Ratio Accuracy", + "defaultMealCategory": "Default Meal Category", + "defaultMealPortionType": "Default Meal Portion Type", + "defaultPortionSizeAccuracy": "Default Portion Size Accuracy", + "name": "Name", + "notes": "Notes", + "validators": { + "name": "Empty name" + } + }, + "new": "New", + "saved": "{status}Meal Source saved", + "title": "Meal Sources" + }, + "navigation": { + "basalProfiles": "Basal Profiles", + "bolusProfiles": "Bolus Profiles", + "categorization": "Categorization", + "log": "Log", + "logEvents": "Log Events", + "meals": "Meals", + "reports": "Reports", + "settings": "Settings" + }, + "portionType": { + "deleted": "Meal Portion Type deleted", + "detail": { + "title": "{status} Meal Portion Type {name}" + }, + "empty": "You have not created any Meal Portion Types yet!", + "fields": { + "name": "Name", + "notes": "Notes", + "validators": { + "name": "Empty name" + } + }, + "new": "New", + "saved": "{status}Meal Portion Type saved", + "title": "Meal Portion Types" + }, + "reports": { + "dailyChart": { + "empty": "You have not created any Log Entries for this date yet!", + "showBasal": "show Basal", + "showBolus": "show Bolus", + "showChart": "show Chart", + "showMeals": "show Meals", + "title": "Daily Chart" + }, + "export": { + "date": "Date", + "delayedBy": "(delayed by {delay})", + "endDate": "End Date", + "export": "export", + "range": "Range", + "showBasal": "show Basal", + "showBolus": "show Bolus", + "showChart": "show Chart", + "showMeals": "show Meals", + "showTable": "show Table", + "singleDate": "Single Date", + "tableHeaders": { + "bolus": "Bolus", + "carbs": "Carbohydrates", + "glucose": "Glucose", + "mealBolus": "Meal Bolus", + "mealNotes": "Meal Notes", + "meals": "Meals", + "notes": "Notes", + "portionSize": "Portion Size", + "time": "Time" + }, + "title": "Log Report" + }, + "ids": { + "basal": "Basal", + "bolus": "Bolus", + "carbs": "Carbohydrates", + "glucose": "Glucose" + }, + "sections": { + "dailyReport": "Daily Report", + "pdfReport": "PDF Report" + }, + "title": "Reports" + }, + "settings": { + "confirmReset": "Are you sure you want to reset all settings?", + "fields": { + "confirmOnCancel": "on cancelling edit or creation of a record if changes have already been made", + "confirmOnDelete": "on deleting a record", + "confirmOnEndEvent": "on stopping (ending) an event", + "dateFormat": "Date Format", + "displayBothDetail": "display both glucose measurements in detail view", + "displayBothList": "display both glucose measurements in list view", + "glucoseMeasurement": "Preferred Glucose Measurement", + "insulinIncrement": "Insulin Increment", + "longDateFormat": "Long Date Format", + "longTimeFormat": "Long Time Format", + "mmolLIncrement": "Mmol/l Increment", + "nutritionIncrement": "Nutrition Increment", + "nutritionMeasurement": "Preferred Nutrition Measurement", + "onlyDisplayActive": "only display active glucose measurement", + "targetGlucose": "Target Glucose", + "timeFormat": "Time Format" + }, + "reset": "Settings have been reset to default", + "resetAll": "Reset All", + "sections": { + "confirmation": "Confirmation Prompts", + "dateTimeFormat": "Time & Date Format", + "measurements": "Measurements" + }, + "title": "Application Settings", + "updated": "Settings updated" + } +} diff --git a/build.yaml b/build.yaml new file mode 100644 index 0000000..c789788 --- /dev/null +++ b/build.yaml @@ -0,0 +1,6 @@ +targets: + $default: + sources: + include: + - assets/i18n/** + - lib/** diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 902bdcb..758c625 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -291,7 +291,7 @@ ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.example.tide; + PRODUCT_BUNDLE_IDENTIFIER = com.example.diameter; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -415,7 +415,7 @@ ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.example.tide; + PRODUCT_BUNDLE_IDENTIFIER = com.example.diameter; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -434,7 +434,7 @@ ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.example.tide; + PRODUCT_BUNDLE_IDENTIFIER = com.example.diameter; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 441fb6d..ebf0767 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -11,7 +11,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - tide + diameter CFBundlePackageType APPL CFBundleShortVersionString diff --git a/lib/components/app_theme.dart b/lib/components/app_theme.dart index 53765f7..63e5728 100644 --- a/lib/components/app_theme.dart +++ b/lib/components/app_theme.dart @@ -5,7 +5,7 @@ class AppTheme { AppTheme._(); static ThemeData lightTheme = FlexColorScheme.light( - surfaceStyle: FlexSurface.medium, + // surfaceMode: FlexSurfaceMode.level, scheme: FlexScheme.aquaBlue, fontFamily: 'Roboto', ).toTheme; diff --git a/lib/components/detail.dart b/lib/components/detail.dart index 8beb917..e0fb03f 100644 --- a/lib/components/detail.dart +++ b/lib/components/detail.dart @@ -1,11 +1,13 @@ +import 'package:diameter/localization_keys.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class DetailBottomRow extends StatefulWidget { final void Function()? onCancel; final void Function()? onAction; final void Function()? onMiddleAction; - final String actionText; - final String middleActionText; + final String actionTextKey; + final String middleActionTextKey; final IconData actionIcon; final IconData middleActionIcon; @@ -14,9 +16,9 @@ class DetailBottomRow extends StatefulWidget { required this.onCancel, required this.onAction, this.onMiddleAction, - this.actionText = 'SAVE', + this.actionTextKey = LocalizationKeys.general_save, this.actionIcon = Icons.save, - this.middleActionText = 'SAVE & CLOSE', + this.middleActionTextKey = LocalizationKeys.general_saveAndClose, this.middleActionIcon = Icons.done}) : super(key: key); @@ -39,7 +41,7 @@ class _DetailBottomRowState extends State { Icons.close, size: 18.0, ), - label: const Text('CANCEL'), + label: Text(translate(LocalizationKeys.general_cancel).toUpperCase()), ), widget.onMiddleAction != null ? ElevatedButton.icon( @@ -48,7 +50,7 @@ class _DetailBottomRowState extends State { widget.middleActionIcon, size: 18.0, ), - label: Text(widget.middleActionText), + label: Text(translate(widget.middleActionTextKey)), ) : const Spacer(), ElevatedButton.icon( @@ -57,7 +59,7 @@ class _DetailBottomRowState extends State { widget.actionIcon, size: 18.0, ), - label: Text(widget.actionText), + label: Text(translate(widget.actionTextKey)), ), ], ), diff --git a/lib/components/forms/time_of_day_form_field.dart b/lib/components/forms/time_of_day_form_field.dart index 1d7b1da..19068aa 100644 --- a/lib/components/forms/time_of_day_form_field.dart +++ b/lib/components/forms/time_of_day_form_field.dart @@ -6,13 +6,15 @@ class TimeOfDayFormField extends StatefulWidget { final TextEditingController controller; final String label; final void Function(TimeOfDay?) onChanged; + final String? Function(String?)? validator; const TimeOfDayFormField( {Key? key, required this.time, required this.controller, required this.label, - required this.onChanged}) + required this.onChanged, + this.validator}) : super(key: key); @override @@ -35,6 +37,7 @@ class _TimeOfDayFormFieldState extends State { ); widget.onChanged(newTime); }, + validator: widget.validator, ); } } \ No newline at end of file diff --git a/lib/data_export.dart b/lib/data_export.dart new file mode 100644 index 0000000..9681e19 --- /dev/null +++ b/lib/data_export.dart @@ -0,0 +1,366 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:diameter/localization_keys.dart'; +import 'package:diameter/models/accuracy.dart'; +import 'package:diameter/models/basal.dart'; +import 'package:diameter/models/basal_profile.dart'; +import 'package:diameter/models/bolus.dart'; +import 'package:diameter/models/bolus_profile.dart'; +import 'package:diameter/models/glucose_target.dart'; +import 'package:diameter/models/log_bolus.dart'; +import 'package:diameter/models/log_entry.dart'; +import 'package:diameter/models/log_event.dart'; +import 'package:diameter/models/log_event_type.dart'; +import 'package:diameter/models/log_meal.dart'; +import 'package:diameter/models/meal.dart'; +import 'package:diameter/models/meal_category.dart'; +import 'package:diameter/models/meal_portion_type.dart'; +import 'package:diameter/models/meal_source.dart'; +import 'package:diameter/models/settings.dart'; +import 'package:flutter_translate/flutter_translate.dart'; +// import 'package:flutter/material.dart'; +import 'package:google_sign_in/google_sign_in.dart'; +import 'package:intl/intl.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:http/http.dart' as http; +import 'package:googleapis/drive/v3.dart' as drive; + +class GoogleAuthClient extends http.BaseClient { + final Map _headers; + + final http.Client _client = http.Client(); + + GoogleAuthClient(this._headers); + + @override + Future send(http.BaseRequest request) { + return _client.send(request..headers.addAll(_headers)); + } +} + +class DataExport { + static Map appDataToJson(DateTime timestamp) { + final Map data = {}; + + data['exportDate'] = timestamp.toString(); + data['accuracies'] = + Accuracy.box.getAll().map((accuracy) => accuracy.toJson()).toList(); + data['basalProfiles'] = BasalProfile.box + .getAll() + .map((basalProfile) => basalProfile.toJson()) + .toList(); + data['basalRates'] = + Basal.box.getAll().map((basal) => basal.toJson()).toList(); + data['bolusProfiles'] = BolusProfile.box + .getAll() + .map((bolusProfile) => bolusProfile.toJson()) + .toList(); + data['bolusRates'] = + Bolus.box.getAll().map((bolusRates) => bolusRates.toJson()).toList(); + data['glucoseTargets'] = GlucoseTarget.box + .getAll() + .map((glucoseTarget) => glucoseTarget.toJson()) + .toList(); + data['logBoli'] = + LogBolus.box.getAll().map((logBolus) => logBolus.toJson()).toList(); + data['logEntries'] = + LogEntry.box.getAll().map((logEntry) => logEntry.toJson()).toList(); + data['logEventTypes'] = LogEventType.box + .getAll() + .map((logEventType) => logEventType.toJson()) + .toList(); + data['logEvents'] = + LogEvent.box.getAll().map((logEvent) => logEvent.toJson()).toList(); + data['logMeals'] = + LogMeal.box.getAll().map((logMeal) => logMeal.toJson()).toList(); + data['mealCategories'] = MealCategory.box + .getAll() + .map((mealCategory) => mealCategory.toJson()) + .toList(); + data['mealPortionTypes'] = MealPortionType.box + .getAll() + .map((mealPortionType) => mealPortionType.toJson()) + .toList(); + data['mealSources'] = MealSource.box + .getAll() + .map((mealSource) => mealSource.toJson()) + .toList(); + data['meals'] = Meal.box.getAll().map((meal) => meal.toJson()).toList(); + data['settings'] = Settings.toJson(); + + return data; + } + + List appDataFromJson(File file, + {String source = '', bool overrideExisting = true, bool skipDeleted = false}) { + final Map data = json.decode(file.openRead().toString()); + final List errors = []; + + final exportDate = DateTime.tryParse(data['exportDate']); + if (exportDate != null && Settings.lastExportTimeStamp != null && exportDate.isAfter(Settings.lastExportTimeStamp!)) { + if (data.keys.contains('accuracies')) { + for (var entry in data['accuracies']) { + if (!skipDeleted || entry['deleted'] == false) { + final error = Accuracy.putFromJson(entry, overrideExisting, source); + if (error != null) { + errors.add(translate(LocalizationKeys.export_error, args: {"data": entry})); + } + } + } + } + + if (data.keys.contains('basalProfiles')) { + for (var entry in data['basalProfiles']) { + if (!skipDeleted || entry['deleted'] == false) { + final error = BasalProfile.putFromJson(entry, overrideExisting, source); + if (error != null) { + errors.add(translate(LocalizationKeys.export_error, args: {"data": entry})); + } + } + } + } + + if (data.keys.contains('basalRates')) { + for (var entry in data['basalRates']) { + if (!skipDeleted || entry['deleted'] == false) { + final error = Basal.putFromJson(entry, overrideExisting, source); + if (error != null) { + errors.add(translate(LocalizationKeys.export_error, args: {"data": entry})); + } + } + } + } + + if (data.keys.contains('bolusRates')) { + for (var entry in data['bolusRates']) { + if (!skipDeleted || entry['deleted'] == false) { + final error = Bolus.putFromJson(entry, overrideExisting, source); + if (error != null) { + errors.add(translate(LocalizationKeys.export_error, args: {"data": entry})); + } + } + } + } + + if (data.keys.contains('glucoseTargets')) { + for (var entry in data['glucoseTargets']) { + if (!skipDeleted || entry['deleted'] == false) { + final error = GlucoseTarget.putFromJson(entry, overrideExisting, source); + if (error != null) { + errors.add(translate(LocalizationKeys.export_error, args: {"data": entry})); + } + } + } + } + + if (data.keys.contains('logBoli')) { + for (var entry in data['logBoli']) { + if (!skipDeleted || entry['deleted'] == false) { + final error = LogBolus.putFromJson(entry, overrideExisting, source); + if (error != null) { + errors.add(translate(LocalizationKeys.export_error, args: {"data": entry})); + } + } + } + } + + if (data.keys.contains('logEntries')) { + for (var entry in data['logEntries']) { + if (!skipDeleted || entry['deleted'] == false) { + final error = LogEntry.putFromJson(entry, overrideExisting, source); + if (error != null) { + errors.add(translate(LocalizationKeys.export_error, args: {"data": entry})); + } + } + } + } + + if (data.keys.contains('logEvents')) { + for (var entry in data['logEvents']) { + if (!skipDeleted || entry['deleted'] == false) { + final error = LogEvent.putFromJson(entry, overrideExisting, source); + if (error != null) { + errors.add(translate(LocalizationKeys.export_error, args: {"data": entry})); + } + } + } + } + + if (data.keys.contains('logMeals')) { + for (var entry in data['logMeals']) { + if (!skipDeleted || entry['deleted'] == false) { + final error = LogMeal.putFromJson(entry, overrideExisting, source); + if (error != null) { + errors.add(translate(LocalizationKeys.export_error, args: {"data": entry})); + } + } + } + } + + if (data.keys.contains('mealCategories')) { + for (var entry in data['mealCategories']) { + if (!skipDeleted || entry['deleted'] == false) { + final error = MealCategory.putFromJson(entry, overrideExisting, source); + if (error != null) { + errors.add(translate(LocalizationKeys.export_error, args: {"data": entry})); + } + } + } + } + + if (data.keys.contains('mealPortionTypes')) { + for (var entry in data['mealPortionTypes']) { + if (!skipDeleted || entry['deleted'] == false) { + final error = MealPortionType.putFromJson(entry, overrideExisting, source); + if (error != null) { + errors.add(translate(LocalizationKeys.export_error, args: {"data": entry})); + } + } + } + } + + if (data.keys.contains('mealSources')) { + for (var entry in data['mealSources']) { + if (!skipDeleted || entry['deleted'] == false) { + final error = MealSource.putFromJson(entry, overrideExisting, source); + if (error != null) { + errors.add(translate(LocalizationKeys.export_error, args: {"data": entry})); + } + } + } + } + + if (data.keys.contains('meals')) { + for (var entry in data['meals']) { + if (!skipDeleted || entry['deleted'] == false) { + final error = Meal.putFromJson(entry, overrideExisting, source); + if (error != null) { + errors.add(translate(LocalizationKeys.export_error, args: {"data": entry})); + } + } + } + } + } else { + // show confirmation dialog because export is newer than last saved timestamp + } + + return errors; + } + + static Future exportDataToFile(DateTime timestamp) async { + final appDocDir = await getApplicationDocumentsDirectory(); + final appDocPath = appDocDir.path; + + final DateFormat formatter = DateFormat(DateFormat.YEAR_MONTH_DAY); + final date = DateTime.now(); + final file = File('$appDocPath/diameter_${formatter.format(date)}.json'); + + file.writeAsStringSync(json.encode(appDataToJson(timestamp))); + return file; + } + + static Future exportToGoogleDrive() async { + final login = GoogleSignIn.standard(scopes: [drive.DriveApi.driveScope]); + final GoogleSignInAccount? account = await login.signIn(); + + if (account != null) { + final authenticateClient = GoogleAuthClient(await account.authHeaders); + final driveApi = drive.DriveApi(authenticateClient); + + final timestamp = DateTime.now(); + var localFile = await DataExport.exportDataToFile(timestamp); + var media = drive.Media(localFile.openRead(), localFile.lengthSync()); + + final settings = Settings.get(); + settings.lastExportTimestamp = timestamp; + Settings.put(settings); + + drive.File driveFile = drive.File()..name = 'diameter.json'; + await driveApi.files.create(driveFile, uploadMedia: media); + } + } + + static Future importFromGoogleDrive() async { + final login = GoogleSignIn.standard(scopes: [drive.DriveApi.driveScope]); + final GoogleSignInAccount? account = await login.signIn(); + + if (account != null) { + final authenticateClient = GoogleAuthClient(await account.authHeaders); + final driveApi = drive.DriveApi(authenticateClient); + + final timestamp = DateTime.now(); + var localFile = await DataExport.exportDataToFile(timestamp); + var media = drive.Media(localFile.openRead(), localFile.lengthSync()); + + final settings = Settings.get(); + settings.lastExportTimestamp = timestamp; + Settings.put(settings); + + drive.File driveFile = drive.File()..name = 'diameter.json'; + await driveApi.files.create(driveFile, uploadMedia: media); + } + } +} + +// class DataExportDialog extends StatefulWidget { +// static const String routeName = '/data-export'; +// const DataExportDialog({Key? key}) : super(key: key); + +// @override +// _DataExportDialogState createState() => _DataExportDialogState(); +// } + +// class _DataExportDialogState extends State { +// final ScrollController _scrollController = ScrollController(); + +// bool _isSaving = false; + +// @override +// void dispose() { +// _scrollController.dispose(); +// super.dispose(); +// } + +// @override +// void initState() { +// super.initState(); +// } + +// void onExport(BuildContext context) async { +// setState(() { +// _isSaving = true; +// }); + +// Navigator.pop(context); +// setState(() { +// _isSaving = false; +// }); +// } + +// @override +// Widget build(BuildContext context) { +// return AlertDialog( +// content: Column( +// mainAxisSize: MainAxisSize.min, +// crossAxisAlignment: CrossAxisAlignment.center, +// children: const [ +// // CheckboxListTile( +// // value: showChart, +// // onChanged: (_) => setState(() => showChart = !showChart), +// // title: const Text('show Chart'), +// // controlAffinity: ListTileControlAffinity.leading, +// // ), +// ], +// ), +// actions: [ +// TextButton( +// onPressed: () => Navigator.pop(context), +// child: const Text('CANCEL'), +// ), +// ElevatedButton( +// onPressed: !_isSaving ? () => onExport(context) : null, +// child: const Text('EXPORT'), +// ), +// ]); +// } +// } diff --git a/lib/localization_keys.dart b/lib/localization_keys.dart new file mode 100644 index 0000000..681a15d --- /dev/null +++ b/lib/localization_keys.dart @@ -0,0 +1,453 @@ + +// ignore_for_file: constant_identifier_names + +class LocalizationKeys { + static const String general = "general"; + static const String general_apply = "$general.apply"; + static const String general_cancel = "$general.cancel"; + static const String general_confirm = "$general.confirm"; + static const String general_discard = "$general.discard"; + static const String general_confirmDiscard = "$general.confirmDiscard"; + static const String general_delete = "$general.delete"; + static const String general_confirmDelete = "$general.confirmDelete"; + static const String general_save = "$general.save"; + static const String general_close = "$general.close"; + static const String general_saveAndClose = "$general.saveAndClose"; + static const String general_saveAsIs = "$general.saveAsIs"; + static const String general_keepEditing = "$general.keepEditing"; + static const String general_next = "$general.next"; + static const String general_edit = "$general.edit"; + static const String general_per = "$general.per"; + static const String general_example = "$general.example"; + static const String general_suffixes = "$general.suffixes"; + static const String general_suffixes_units = "$general_suffixes.units"; + static const String general_suffixes_carbs = "$general_suffixes.carbs"; + static const String general_suffixes_mins = "$general_suffixes.mins"; + static const String general_suffixes_carbsPerU = "$general_suffixes.carbsPerU"; + static const String general_suffixes_uPerBreadUnit = "$general_suffixes.uPerBreadUnit"; + static const String general_suffixes_uPerGlucose = "$general_suffixes.uPerGlucose"; + static const String general_suffixes_perDay = "$general_suffixes.perDay"; + + static const String navigation = "navigation"; + static const String navigation_log = "$navigation.log"; + static const String navigation_logEvents = "$navigation.logEvents"; + static const String navigation_reports = "$navigation.reports"; + static const String navigation_meals = "$navigation.meals"; + static const String navigation_basalProfiles = "$navigation.basalProfiles"; + static const String navigation_bolusProfiles = "$navigation.bolusProfiles"; + static const String navigation_categorization = "$navigation.categorization"; + static const String navigation_settings = "$navigation.settings"; + + static const String accuracy = "accuracy"; + static const String accuracy_empty = "$accuracy.empty"; + static const String accuracy_saved = "$accuracy.saved"; + static const String accuracy_saved_1 = "$accuracy_saved.1"; + static const String accuracy_saved_else = "$accuracy_saved.else"; + static const String accuracy_deleted = "$accuracy.deleted"; + static const String accuracy_confirmDelete = "$accuracy.confirmDelete"; + static const String accuracy_new = "$accuracy.new"; + static const String accuracy_fields = "$accuracy.fields"; + static const String accuracy_fields_name = "$accuracy_fields.name"; + static const String accuracy_fields_forPortionSize = "$accuracy_fields.forPortionSize"; + static const String accuracy_fields_forCarbsRatio = "$accuracy_fields.forCarbsRatio"; + static const String accuracy_fields_confidenceRating = "$accuracy_fields.confidenceRating"; + static const String accuracy_fields_notes = "$accuracy_fields.notes"; + static const String accuracy_fields_validators = "$accuracy_fields.validators"; + static const String accuracy_fields_validators_name = "$accuracy_fields_validators.name"; + static const String accuracy_title = "$accuracy.title"; + static const String accuracy_detail = "$accuracy.detail"; + static const String accuracy_detail_title = "$accuracy_detail.title"; + + static const String basal = "basal"; + static const String basal_empty = "$basal.empty"; + static const String basal_warnings = "$basal.warnings"; + static const String basal_warnings_duplicate = "$basal_warnings.duplicate"; + static const String basal_warnings_overlap = "$basal_warnings.overlap"; + static const String basal_warnings_startTimeFirst = "$basal_warnings.startTimeFirst"; + static const String basal_warnings_gap = "$basal_warnings.gap"; + static const String basal_warnings_endTimeLast = "$basal_warnings.endTimeLast"; + static const String basal_saved = "$basal.saved"; + static const String basal_saved_1 = "$basal_saved.1"; + static const String basal_saved_else = "$basal_saved.else"; + static const String basal_deleted = "$basal.deleted"; + static const String basal_confirmDelete = "$basal.confirmDelete"; + static const String basal_new = "$basal.new"; + static const String basal_fields = "$basal.fields"; + static const String basal_fields_startTime = "$basal_fields.startTime"; + static const String basal_fields_endTime = "$basal_fields.endTime"; + static const String basal_fields_units = "$basal_fields.units"; + static const String basal_title = "$basal.title"; + + static const String basalProfile = "basalProfile"; + static const String basalProfile_empty = "$basalProfile.empty"; + static const String basalProfile_warnings = "$basalProfile.warnings"; + static const String basalProfile_warnings_noActive = "$basalProfile_warnings.noActive"; + static const String basalProfile_warnings_noActiveOnCreate = "$basalProfile_warnings.noActiveOnCreate"; + static const String basalProfile_warnings_multipleActive = "$basalProfile_warnings.multipleActive"; + static const String basalProfile_warnings_activeAlreadySet = "$basalProfile_warnings.activeAlreadySet"; + static const String basalProfile_warnings_resolve = "$basalProfile_warnings.resolve"; + static const String basalProfile_warnings_resolve_activate = "$basalProfile_warnings_resolve.activate"; + static const String basalProfile_warnings_resolve_activateCurrent = "$basalProfile_warnings_resolve.activateCurrent"; + static const String basalProfile_warnings_resolve_create = "$basalProfile_warnings_resolve.create"; + static const String basalProfile_warnings_resolve_createInstead = "$basalProfile_warnings_resolve.createInstead"; + static const String basalProfile_warnings_resolve_pick = "$basalProfile_warnings_resolve.pick"; + static const String basalProfile_warnings_resolve_ignore = "$basalProfile_warnings_resolve.ignore"; + static const String basalProfile_warnings_resolve_deactivateProfile = "$basalProfile_warnings_resolve.deactivateProfile"; + static const String basalProfile_warnings_resolve_deactivateOthers = "$basalProfile_warnings_resolve.deactivateOthers"; + static const String basalProfile_saved = "$basalProfile.saved"; + static const String basalProfile_default = "$basalProfile.default"; + static const String basalProfile_active = "$basalProfile.active"; + static const String basalProfile_copied = "$basalProfile.copied"; + static const String basalProfile_copyOf = "$basalProfile.copyOf"; + static const String basalProfile_deleted = "$basalProfile.deleted"; + static const String basalProfile_confirmDelete = "$basalProfile.confirmDelete"; + static const String basalProfile_activated = "$basalProfile.activated"; + static const String basalProfile_new = "$basalProfile.new"; + static const String basalProfile_fields = "$basalProfile.fields"; + static const String basalProfile_fields_name = "$basalProfile_fields.name"; + static const String basalProfile_fields_notes = "$basalProfile_fields.notes"; + static const String basalProfile_fields_active = "$basalProfile_fields.active"; + static const String basalProfile_fields_validators = "$basalProfile_fields.validators"; + static const String basalProfile_fields_validators_name = "$basalProfile_fields_validators.name"; + static const String basalProfile_title = "$basalProfile.title"; + static const String basalProfile_detail = "$basalProfile.detail"; + static const String basalProfile_detail_title = "$basalProfile_detail.title"; + static const String basalProfile_detail_tabs = "$basalProfile_detail.tabs"; + static const String basalProfile_detail_tabs_profile = "$basalProfile_detail_tabs.profile"; + static const String basalProfile_detail_tabs_rates = "$basalProfile_detail_tabs.rates"; + + static const String bolus = "bolus"; + static const String bolus_empty = "$bolus.empty"; + static const String bolus_warnings = "$bolus.warnings"; + static const String bolus_warnings_duplicate = "$bolus_warnings.duplicate"; + static const String bolus_warnings_overlap = "$bolus_warnings.overlap"; + static const String bolus_warnings_startTimeFirst = "$bolus_warnings.startTimeFirst"; + static const String bolus_warnings_gap = "$bolus_warnings.gap"; + static const String bolus_warnings_endTimeLast = "$bolus_warnings.endTimeLast"; + static const String bolus_saved = "$bolus.saved"; + static const String bolus_saved_1 = "$bolus_saved.1"; + static const String bolus_saved_else = "$bolus_saved.else"; + static const String bolus_deleted = "$bolus.deleted"; + static const String bolus_confirmDelete = "$bolus.confirmDelete"; + static const String bolus_new = "$bolus.new"; + static const String bolus_fields = "$bolus.fields"; + static const String bolus_fields_startTime = "$bolus_fields.startTime"; + static const String bolus_fields_endTime = "$bolus_fields.endTime"; + static const String bolus_fields_units = "$bolus_fields.units"; + static const String bolus_fields_perCarbs = "$bolus_fields.perCarbs"; + static const String bolus_fields_perGlucose = "$bolus_fields.perGlucose"; + static const String bolus_title = "$bolus.title"; + + static const String bolusProfile = "bolusProfile"; + static const String bolusProfile_empty = "$bolusProfile.empty"; + static const String bolusProfile_warnings = "$bolusProfile.warnings"; + static const String bolusProfile_warnings_noActive = "$bolusProfile_warnings.noActive"; + static const String bolusProfile_warnings_noActiveOnCreate = "$bolusProfile_warnings.noActiveOnCreate"; + static const String bolusProfile_warnings_multipleActive = "$bolusProfile_warnings.multipleActive"; + static const String bolusProfile_warnings_activeAlreadySet = "$bolusProfile_warnings.activeAlreadySet"; + static const String bolusProfile_warnings_resolve = "$bolusProfile_warnings.resolve"; + static const String bolusProfile_warnings_resolve_activate = "$bolusProfile_warnings_resolve.activate"; + static const String bolusProfile_warnings_resolve_activateCurrent = "$bolusProfile_warnings_resolve.activateCurrent"; + static const String bolusProfile_warnings_resolve_create = "$bolusProfile_warnings_resolve.create"; + static const String bolusProfile_warnings_resolve_createInstead = "$bolusProfile_warnings_resolve.createInstead"; + static const String bolusProfile_warnings_resolve_pick = "$bolusProfile_warnings_resolve.pick"; + static const String bolusProfile_warnings_resolve_ignore = "$bolusProfile_warnings_resolve.ignore"; + static const String bolusProfile_warnings_resolve_deactivateProfile = "$bolusProfile_warnings_resolve.deactivateProfile"; + static const String bolusProfile_warnings_resolve_deactivateOthers = "$bolusProfile_warnings_resolve.deactivateOthers"; + static const String bolusProfile_saved = "$bolusProfile.saved"; + static const String bolusProfile_default = "$bolusProfile.default"; + static const String bolusProfile_active = "$bolusProfile.active"; + static const String bolusProfile_copied = "$bolusProfile.copied"; + static const String bolusProfile_copyOf = "$bolusProfile.copyOf"; + static const String bolusProfile_deleted = "$bolusProfile.deleted"; + static const String bolusProfile_confirmDelete = "$bolusProfile.confirmDelete"; + static const String bolusProfile_activated = "$bolusProfile.activated"; + static const String bolusProfile_new = "$bolusProfile.new"; + static const String bolusProfile_fields = "$bolusProfile.fields"; + static const String bolusProfile_fields_name = "$bolusProfile_fields.name"; + static const String bolusProfile_fields_notes = "$bolusProfile_fields.notes"; + static const String bolusProfile_fields_active = "$bolusProfile_fields.active"; + static const String bolusProfile_fields_validators = "$bolusProfile_fields.validators"; + static const String bolusProfile_fields_validators_name = "$bolusProfile_fields_validators.name"; + static const String bolusProfile_title = "$bolusProfile.title"; + static const String bolusProfile_detail = "$bolusProfile.detail"; + static const String bolusProfile_detail_title = "$bolusProfile_detail.title"; + static const String bolusProfile_detail_tabs = "$bolusProfile_detail.tabs"; + static const String bolusProfile_detail_tabs_profile = "$bolusProfile_detail_tabs.profile"; + static const String bolusProfile_detail_tabs_rates = "$bolusProfile_detail_tabs.rates"; + + static const String categories = "categories"; + + static const String log = "log"; + static const String log_title = "$log.title"; + static const String log_empty = "$log.empty"; + static const String log_confirmDelete = "$log.confirmDelete"; + static const String log_saved = "$log.saved"; + static const String log_deleted = "$log.deleted"; + static const String log_new = "$log.new"; + static const String log_fields = "$log.fields"; + static const String log_fields_date = "$log_fields.date"; + static const String log_fields_time = "$log_fields.time"; + static const String log_fields_glucose = "$log_fields.glucose"; + static const String log_fields_notes = "$log_fields.notes"; + static const String log_filter = "$log.filter"; + static const String log_filter_startDate = "$log_filter.startDate"; + static const String log_filter_endDate = "$log_filter.endDate"; + static const String log_filter_minGlucose = "$log_filter.minGlucose"; + static const String log_filter_maxGlucose = "$log_filter.maxGlucose"; + static const String log_filter_meal = "$log_filter.meal"; + static const String log_filter_mealNameContains = "$log_filter.mealNameContains"; + static const String log_filter_noteContains = "$log_filter.noteContains"; + static const String log_detail = "$log.detail"; + static const String log_detail_title = "$log_detail.title"; + static const String log_detail_tabs = "$log_detail.tabs"; + static const String log_detail_tabs_general = "$log_detail_tabs.general"; + static const String log_detail_tabs_meal = "$log_detail_tabs.meal"; + static const String log_detail_tabs_meal_title = "$log_detail_tabs_meal.title"; + static const String log_detail_tabs_meal_saved = "$log_detail_tabs_meal.saved"; + static const String log_detail_tabs_meal_new = "$log_detail_tabs_meal.new"; + static const String log_detail_tabs_meal_empty = "$log_detail_tabs_meal.empty"; + static const String log_detail_tabs_meal_confirmDelete = "$log_detail_tabs_meal.confirmDelete"; + static const String log_detail_tabs_meal_deleted = "$log_detail_tabs_meal.deleted"; + static const String log_detail_tabs_meal_detail = "$log_detail_tabs_meal.detail"; + static const String log_detail_tabs_meal_detail_title = "$log_detail_tabs_meal_detail.title"; + static const String log_detail_tabs_meal_detail_additionalFields = "$log_detail_tabs_meal_detail.additionalFields"; + static const String log_detail_tabs_meal_detail_fields = "$log_detail_tabs_meal_detail.fields"; + static const String log_detail_tabs_meal_detail_fields_meal = "$log_detail_tabs_meal_detail_fields.meal"; + static const String log_detail_tabs_meal_detail_fields_name = "$log_detail_tabs_meal_detail_fields.name"; + static const String log_detail_tabs_meal_detail_fields_amount = "$log_detail_tabs_meal_detail_fields.amount"; + static const String log_detail_tabs_meal_detail_fields_portionSize = "$log_detail_tabs_meal_detail_fields.portionSize"; + static const String log_detail_tabs_meal_detail_fields_carbsRatio = "$log_detail_tabs_meal_detail_fields.carbsRatio"; + static const String log_detail_tabs_meal_detail_fields_totalCarbs = "$log_detail_tabs_meal_detail_fields.totalCarbs"; + static const String log_detail_tabs_meal_detail_fields_setManually = "$log_detail_tabs_meal_detail_fields.setManually"; + static const String log_detail_tabs_meal_detail_fields_notes = "$log_detail_tabs_meal_detail_fields.notes"; + static const String log_detail_tabs_meal_detail_fields_mealSource = "$log_detail_tabs_meal_detail_fields.mealSource"; + static const String log_detail_tabs_meal_detail_fields_mealCategory = "$log_detail_tabs_meal_detail_fields.mealCategory"; + static const String log_detail_tabs_meal_detail_fields_mealPortionType = "$log_detail_tabs_meal_detail_fields.mealPortionType"; + static const String log_detail_tabs_meal_detail_fields_portionSizeAccuracy = "$log_detail_tabs_meal_detail_fields.portionSizeAccuracy"; + static const String log_detail_tabs_meal_detail_fields_carbsRatioAccuracy = "$log_detail_tabs_meal_detail_fields.carbsRatioAccuracy"; + static const String log_detail_tabs_meal_detail_fields_validators = "$log_detail_tabs_meal_detail_fields.validators"; + static const String log_detail_tabs_meal_detail_fields_validators_name = "$log_detail_tabs_meal_detail_fields_validators.name"; + static const String log_detail_tabs_bolus = "$log_detail_tabs.bolus"; + static const String log_detail_tabs_bolus_title = "$log_detail_tabs_bolus.title"; + static const String log_detail_tabs_bolus_saved = "$log_detail_tabs_bolus.saved"; + static const String log_detail_tabs_bolus_new = "$log_detail_tabs_bolus.new"; + static const String log_detail_tabs_bolus_deleted = "$log_detail_tabs_bolus.deleted"; + static const String log_detail_tabs_bolus_confirmDelete = "$log_detail_tabs_bolus.confirmDelete"; + static const String log_detail_tabs_bolus_delayedBy = "$log_detail_tabs_bolus.delayedBy"; + static const String log_detail_tabs_bolus_forMeal = "$log_detail_tabs_bolus.forMeal"; + static const String log_detail_tabs_bolus_toCorrect = "$log_detail_tabs_bolus.toCorrect"; + static const String log_detail_tabs_bolus_empty = "$log_detail_tabs_bolus.empty"; + static const String log_detail_tabs_bolus_detail = "$log_detail_tabs_bolus.detail"; + static const String log_detail_tabs_bolus_detail_title = "$log_detail_tabs_bolus_detail.title"; + static const String log_detail_tabs_bolus_detail_fields = "$log_detail_tabs_bolus_detail.fields"; + static const String log_detail_tabs_bolus_detail_fields_units = "$log_detail_tabs_bolus_detail_fields.units"; + static const String log_detail_tabs_bolus_detail_fields_setManually = "$log_detail_tabs_bolus_detail_fields.setManually"; + static const String log_detail_tabs_bolus_detail_fields_forGlucose = "$log_detail_tabs_bolus_detail_fields.forGlucose"; + static const String log_detail_tabs_bolus_detail_fields_forMeal = "$log_detail_tabs_bolus_detail_fields.forMeal"; + static const String log_detail_tabs_bolus_detail_fields_current = "$log_detail_tabs_bolus_detail_fields.current"; + static const String log_detail_tabs_bolus_detail_fields_target = "$log_detail_tabs_bolus_detail_fields.target"; + static const String log_detail_tabs_bolus_detail_fields_correction = "$log_detail_tabs_bolus_detail_fields.correction"; + static const String log_detail_tabs_bolus_detail_fields_meal = "$log_detail_tabs_bolus_detail_fields.meal"; + static const String log_detail_tabs_bolus_detail_fields_carbs = "$log_detail_tabs_bolus_detail_fields.carbs"; + static const String log_detail_tabs_bolus_detail_fields_delayedBolusDuration = "$log_detail_tabs_bolus_detail_fields.delayedBolusDuration"; + static const String log_detail_tabs_bolus_detail_fields_immediateBolus = "$log_detail_tabs_bolus_detail_fields.immediateBolus"; + static const String log_detail_tabs_bolus_detail_fields_delayedBolus = "$log_detail_tabs_bolus_detail_fields.delayedBolus"; + + static const String meal = "meal"; + static const String meal_title = "$meal.title"; + static const String meal_empty = "$meal.empty"; + static const String meal_confirmDelete = "$meal.confirmDelete"; + static const String meal_saved = "$meal.saved"; + static const String meal_deleted = "$meal.deleted"; + static const String meal_new = "$meal.new"; + static const String meal_fields = "$meal.fields"; + static const String meal_fields_name = "$meal_fields.name"; + static const String meal_fields_mealSource = "$meal_fields.mealSource"; + static const String meal_fields_mealPortionType = "$meal_fields.mealPortionType"; + static const String meal_fields_notes = "$meal_fields.notes"; + static const String meal_fields_carbsRatio = "$meal_fields.carbsRatio"; + static const String meal_fields_setManually = "$meal_fields.setManually"; + static const String meal_fields_portionSize = "$meal_fields.portionSize"; + static const String meal_fields_carbsPerPortion = "$meal_fields.carbsPerPortion"; + static const String meal_fields_delay = "$meal_fields.delay"; + static const String meal_fields_delay_title = "$meal_fields_delay.title"; + static const String meal_fields_delay_duration = "$meal_fields_delay.duration"; + static const String meal_fields_additional = "$meal_fields.additional"; + static const String meal_fields_additional_title = "$meal_fields_additional.title"; + static const String meal_fields_additional_mealCategory = "$meal_fields_additional.mealCategory"; + static const String meal_fields_additional_portionSizeAccuracy = "$meal_fields_additional.portionSizeAccuracy"; + static const String meal_fields_additional_carbsRatioAccuracy = "$meal_fields_additional.carbsRatioAccuracy"; + static const String meal_fields_validators = "$meal_fields.validators"; + static const String meal_fields_validators_name = "$meal_fields_validators.name"; + static const String meal_detail = "$meal.detail"; + static const String meal_detail_title = "$meal_detail.title"; + + static const String mealSource = "mealSource"; + static const String mealSource_title = "$mealSource.title"; + static const String mealSource_empty = "$mealSource.empty"; + static const String mealSource_confirmDelete = "$mealSource.confirmDelete"; + static const String mealSource_saved = "$mealSource.saved"; + static const String mealSource_deleted = "$mealSource.deleted"; + static const String mealSource_new = "$mealSource.new"; + static const String mealSource_fields = "$mealSource.fields"; + static const String mealSource_fields_name = "$mealSource_fields.name"; + static const String mealSource_fields_notes = "$mealSource_fields.notes"; + static const String mealSource_fields_defaultCarbsRatioAccuracy = "$mealSource_fields.defaultCarbsRatioAccuracy"; + static const String mealSource_fields_defaultPortionSizeAccuracy = "$mealSource_fields.defaultPortionSizeAccuracy"; + static const String mealSource_fields_defaultMealCategory = "$mealSource_fields.defaultMealCategory"; + static const String mealSource_fields_defaultMealPortionType = "$mealSource_fields.defaultMealPortionType"; + static const String mealSource_fields_validators = "$mealSource_fields.validators"; + static const String mealSource_fields_validators_name = "$mealSource_fields_validators.name"; + static const String mealSource_detail = "$mealSource.detail"; + static const String mealSource_detail_title = "$mealSource_detail.title"; + + static const String mealCategory = "mealCategory"; + static const String mealCategory_title = "$mealCategory.title"; + static const String mealCategory_empty = "$mealCategory.empty"; + static const String mealCategory_confirmDelete = "$mealCategory.confirmDelete"; + static const String mealCategory_saved = "$mealCategory.saved"; + static const String mealCategory_deleted = "$mealCategory.deleted"; + static const String mealCategory_new = "$mealCategory.new"; + static const String mealCategory_fields = "$mealCategory.fields"; + static const String mealCategory_fields_name = "$mealCategory_fields.name"; + static const String mealCategory_fields_notes = "$mealCategory_fields.notes"; + static const String mealCategory_fields_validators = "$mealCategory_fields.validators"; + static const String mealCategory_fields_validators_name = "$mealCategory_fields_validators.name"; + static const String mealCategory_detail = "$mealCategory.detail"; + static const String mealCategory_detail_title = "$mealCategory_detail.title"; + + static const String portionType = "portionType"; + static const String portionType_title = "$portionType.title"; + static const String portionType_empty = "$portionType.empty"; + static const String portionType_confirmDelete = "$portionType.confirmDelete"; + static const String portionType_saved = "$portionType.saved"; + static const String portionType_deleted = "$portionType.deleted"; + static const String portionType_new = "$portionType.new"; + static const String portionType_fields = "$portionType.fields"; + static const String portionType_fields_name = "$portionType_fields.name"; + static const String portionType_fields_notes = "$portionType_fields.notes"; + static const String portionType_fields_validators = "$portionType_fields.validators"; + static const String portionType_fields_validators_name = "$portionType_fields_validators.name"; + static const String portionType_detail = "$portionType.detail"; + static const String portionType_detail_title = "$portionType_detail.title"; + + static const String eventType = "eventType"; + static const String eventType_title = "$eventType.title"; + static const String eventType_empty = "$eventType.empty"; + static const String eventType_saved = "$eventType.saved"; + static const String eventType_deleted = "$eventType.deleted"; + static const String eventType_new = "$eventType.new"; + static const String eventType_fields = "$eventType.fields"; + static const String eventType_fields_name = "$eventType_fields.name"; + static const String eventType_fields_notes = "$eventType_fields.notes"; + static const String eventType_fields_hasEndTime = "$eventType_fields.hasEndTime"; + static const String eventType_fields_defaultReminderDuration = "$eventType_fields.defaultReminderDuration"; + static const String eventType_fields_bolusProfile = "$eventType_fields.bolusProfile"; + static const String eventType_fields_basalProfile = "$eventType_fields.basalProfile"; + static const String eventType_fields_validators = "$eventType_fields.validators"; + static const String eventType_fields_validators_name = "$eventType_fields_validators.name"; + static const String eventType_detail = "$eventType.detail"; + static const String eventType_detail_title = "$eventType_detail.title"; + + static const String event = "event"; + static const String event_warnings = "$event.warnings"; + static const String event_warnings_duplicate = "$event_warnings.duplicate"; + static const String event_title = "$event.title"; + static const String event_titleActive = "$event.titleAcive"; + static const String event_empty = "$event.empty"; + static const String event_emptyActive = "$event.emptyActive"; + static const String event_saved = "$event.saved"; + static const String event_deleted = "$event.deleted"; + static const String event_confirmDelete = "$event.confirmDelete"; + static const String event_end = "$event.end"; + static const String event_ended = "$event.ended"; + static const String event_confirmEnd = "$event.confirmEnd"; + static const String event_new = "$event.new"; + static const String event_fields = "$event.fields"; + static const String event_fields_eventType = "$event_fields.eventType"; + static const String event_fields_startDate = "$event_fields.startDate"; + static const String event_fields_endDate = "$event_fields.endDate"; + static const String event_fields_date = "$event_fields.date"; + static const String event_fields_startTime = "$event_fields.startTime"; + static const String event_fields_endTime = "$event_fields.endTime"; + static const String event_fields_time = "$event_fields.time"; + static const String event_fields_notes = "$event_fields.notes"; + static const String event_fields_hasEndTime = "$event_fields.hasEndTime"; + static const String event_fields_reminderDuration = "$event_fields.reminderDuration"; + static const String event_fields_bolusProfile = "$event_fields.bolusProfile"; + static const String event_fields_basalProfile = "$event_fields.basalProfile"; + static const String event_detail = "$event.detail"; + static const String event_detail_title = "$event_detail.title"; + + static const String settings = "settings"; + static const String settings_title = "$settings.title"; + static const String settings_reset = "$settings.reset"; + static const String settings_resetAll = "$settings.resetAll"; + static const String settings_confirmReset = "$settings.confirmReset"; + static const String settings_updated = "$settings.updated"; + static const String settings_sections = "$settings.sections"; + static const String settings_sections_measurements = "$settings_sections.measurements"; + static const String settings_sections_confirmation = "$settings_sections.confirmation"; + static const String settings_sections_dateTimeFormat = "$settings_sections.dateTimeFormat"; + static const String settings_fields = "$settings.fields"; + static const String settings_fields_nutritionMeasurement = "$settings_fields.nutritionMeasurement"; + static const String settings_fields_glucoseMeasurement = "$settings_fields.glucoseMeasurement"; + static const String settings_fields_targetGlucose = "$settings_fields.targetGlucose"; + static const String settings_fields_insulinIncrement = "$settings_fields.insulinIncrement"; + static const String settings_fields_nutritionIncrement = "$settings_fields.nutritionIncrement"; + static const String settings_fields_mmolLIncrement = "$settings_fields.mmolLIncrement"; + static const String settings_fields_onlyDisplayActive = "$settings_fields.onlyDisplayActive"; + static const String settings_fields_displayBothDetail = "$settings_fields.displayBothDetail"; + static const String settings_fields_displayBothList = "$settings_fields.displayBothList"; + static const String settings_fields_confirmOnCancel = "$settings_fields.confirmOnCancel"; + static const String settings_fields_confirmOnDelete = "$settings_fields.confirmOnDelete"; + static const String settings_fields_confirmOnEndEvent = "$settings_fields.confirmOnEndEvent"; + static const String settings_fields_dateFormat = "$settings_fields.dateFormat"; + static const String settings_fields_longDateFormat = "$settings_fields.longDateFormat"; + static const String settings_fields_timeFormat = "$settings_fields.timeFormat"; + static const String settings_fields_longTimeFormat = "$settings_fields.longTimeFormat"; + + static const String reports = "reports"; + static const String reports_title = "$reports.title"; + static const String reports_ids = "$reports.ids"; + static const String reports_ids_glucose = "$reports_ids.glucose"; + static const String reports_ids_carbs = "$reports_ids.carbs"; + static const String reports_ids_basal = "$reports_ids.basal"; + static const String reports_ids_bolus = "$reports_ids.bolus"; + static const String reports_dailyCharts = "$reports.dailyCharts"; + static const String reports_dailyCharts_showChart = "$reports_dailyCharts.showChart"; + static const String reports_dailyCharts_showBolus = "$reports_dailyCharts.showBolus"; + static const String reports_dailyCharts_showBasal = "$reports_dailyCharts.showBasal"; + static const String reports_dailyCharts_showMeals = "$reports_dailyCharts.showMeals"; + static const String reports_dailyCharts_empty = "$reports_dailyCharts.empty"; + static const String reports_sections = "$reports.sections"; + static const String reports_sections_dailyReport = "$reports_sections.dailyReport"; + static const String reports_sections_pdfReport = "$reports_sections.pdfReport"; + static const String reports_export = "$reports.export"; + static const String reports_export_title = "$reports_export.title"; + static const String reports_export_singleDate = "$reports_export.singleDate"; + static const String reports_export_range = "$reports_export.range"; + static const String reports_export_date = "$reports_export.date"; + static const String reports_export_endDate = "$reports_export.endDate"; + static const String reports_export_showChart = "$reports_export.showChart"; + static const String reports_export_showBolus = "$reports_export.showBolus"; + static const String reports_export_showBasal = "$reports_export.showBasal"; + static const String reports_export_showMeals = "$reports_export.showMeals"; + static const String reports_export_showTable = "$reports_export.showTable"; + static const String reports_export_export = "$reports_export.export"; + static const String reports_export_tableHeaders = "$reports_export.tableHeaders"; + static const String reports_export_tableHeaders_time = "$reports_export_tableHeaders.time"; + static const String reports_export_tableHeaders_glucose = "$reports_export_tableHeaders.glucose"; + static const String reports_export_tableHeaders_bolus = "$reports_export_tableHeaders.bolus"; + static const String reports_export_tableHeaders_notes = "$reports_export_tableHeaders.notes"; + static const String reports_export_tableHeaders_meals = "$reports_export_tableHeaders.meals"; + static const String reports_export_tableHeaders_portionSize = "$reports_export_tableHeaders.portionSize"; + static const String reports_export_tableHeaders_carbs = "$reports_export_tableHeaders.carbs"; + static const String reports_export_tableHeaders_mealBolus = "$reports_export_tableHeaders.mealBolus"; + static const String reports_export_tableHeaders_mealNotes = "$reports_export_tableHeaders.mealNotes"; + + static const String export = "export"; + static const String export_error = "$export.error"; + +} diff --git a/lib/main.dart b/lib/main.dart index 89483c7..7e24627 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -25,65 +25,98 @@ import 'package:diameter/screens/meal/meal_list.dart'; import 'package:diameter/screens/reports/export.dart'; import 'package:diameter/screens/reports/reports.dart'; import 'package:diameter/settings.dart'; +import 'package:diameter/data_export.dart'; import 'package:flutter/material.dart'; import 'package:diameter/screens/basal/basal_profile_list.dart'; import 'package:diameter/screens/bolus/bolus_profile_list.dart'; import 'package:diameter/navigation.dart'; import 'package:objectbox/objectbox.dart'; +import 'package:flutter_translate/flutter_translate.dart'; late ObjectBox objectBox; + Future main() async { WidgetsFlutterBinding.ensureInitialized(); + + DataExport.exportToGoogleDrive(); + objectBox = await ObjectBox.create(); - + Sync.isAvailable(); - SyncClient syncClient = Sync.client( - objectBox.store, - 'ws://192.168.1.184:9999', - SyncCredentials.sharedSecretString(secret) - ); + SyncClient syncClient = Sync.client(objectBox.store, + 'ws://192.168.1.184:9999', SyncCredentials.sharedSecretString(secret)); syncClient.start(); syncClient.requestUpdates(subscribeForFuturePushes: false); + DataExport.exportToGoogleDrive(); + + var delegate = await LocalizationDelegate.create( + fallbackLocale: 'en_US', + supportedLocales: ['en_US', 'de'], + ); + runApp( - GestureDetector( - onTap: () => FocusManager.instance.primaryFocus?.unfocus(), - child: MaterialApp( - theme: AppTheme.makeTheme(AppTheme.lightTheme), - darkTheme: AppTheme.makeTheme(AppTheme.darkTheme), - themeMode: Settings.themeMode, - initialRoute: '/', - routes: { - '/': (context) => const LogScreen(), - Routes.log: (context) => const LogScreen(), - Routes.logEntry: (context) => const LogEntryScreen(), - Routes.logEvent: (context) => const LogEventDetailScreen(), - Routes.eventTypes: (context) => const EventTypeListScreen(), - Routes.eventType: (context) => const EventTypeDetailScreen(), - Routes.logEvents: (context) => const LogEventListScreen(), - Routes.reports: (context) => const ReportsOverviewScreen(), - Routes.export: (context) => const ExportDialog(), - Routes.dailyChart: (context) => const DailyChart(), - Routes.meals: (context) => const MealListScreen(), - Routes.meal: (context) => const MealDetailScreen(), - Routes.category: (context) => const CategoryOverviewScreen(), - Routes.mealCategories: (context) => const MealCategoryListScreen(), - Routes.mealCategory: (context) => const MealCategoryDetailScreen(), - Routes.mealPortionTypes: (context) => - const MealPortionTypeListScreen(), - Routes.mealPortionType: (context) => - const MealPortionTypeDetailScreen(), - Routes.mealSources: (context) => const MealSourceListScreen(), - Routes.mealSource: (context) => const MealSourceDetailScreen(), - Routes.accuracies: (context) => const AccuracyListScreen(), - Routes.accuracy: (context) => const AccuracyDetailScreen(), - Routes.bolusProfiles: (context) => const BolusProfileListScreen(), - Routes.bolusProfile: (context) => const BolusProfileDetailScreen(), - Routes.basalProfiles: (context) => const BasalProfileListScreen(), - Routes.basalProfile: (context) => const BasalProfileDetailScreen(), - Routes.settings: (context) => const SettingsScreen(), - }, - ), - ), + LocalizedApp(delegate, const App()), ); } + +class App extends StatelessWidget { + const App({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + var localizationDelegate = LocalizedApp.of(context).delegate; + + return LocalizationProvider( + state: LocalizationProvider.of(context).state, + child: GestureDetector( + onTap: () => FocusManager.instance.primaryFocus?.unfocus(), + child: MaterialApp( + localizationsDelegates: [ + localizationDelegate, + ], + supportedLocales: const [ + Locale('en', 'US'), + Locale('de', 'DE'), + ], + // localizationDelegate.supportedLocales, + locale: localizationDelegate.currentLocale, + theme: AppTheme.makeTheme(AppTheme.lightTheme), + darkTheme: AppTheme.makeTheme(AppTheme.darkTheme), + themeMode: Settings.themeMode, + initialRoute: '/', + routes: { + '/': (context) => const LogScreen(), + Routes.log: (context) => const LogScreen(), + Routes.logEntry: (context) => const LogEntryScreen(), + Routes.logEvent: (context) => const LogEventDetailScreen(), + Routes.eventTypes: (context) => const EventTypeListScreen(), + Routes.eventType: (context) => const EventTypeDetailScreen(), + Routes.logEvents: (context) => const LogEventListScreen(), + Routes.reports: (context) => const ReportsOverviewScreen(), + Routes.export: (context) => const ExportDialog(), + Routes.dailyChart: (context) => const DailyChart(), + Routes.meals: (context) => const MealListScreen(), + Routes.meal: (context) => const MealDetailScreen(), + Routes.category: (context) => const CategoryOverviewScreen(), + Routes.mealCategories: (context) => const MealCategoryListScreen(), + Routes.mealCategory: (context) => const MealCategoryDetailScreen(), + Routes.mealPortionTypes: (context) => + const MealPortionTypeListScreen(), + Routes.mealPortionType: (context) => + const MealPortionTypeDetailScreen(), + Routes.mealSources: (context) => const MealSourceListScreen(), + Routes.mealSource: (context) => const MealSourceDetailScreen(), + Routes.accuracies: (context) => const AccuracyListScreen(), + Routes.accuracy: (context) => const AccuracyDetailScreen(), + Routes.bolusProfiles: (context) => const BolusProfileListScreen(), + Routes.bolusProfile: (context) => const BolusProfileDetailScreen(), + Routes.basalProfiles: (context) => const BasalProfileListScreen(), + Routes.basalProfile: (context) => const BasalProfileDetailScreen(), + Routes.settings: (context) => const SettingsScreen(), + }, + ), + ), + ); + } +} diff --git a/lib/models/accuracy.dart b/lib/models/accuracy.dart index 680e377..e01f475 100644 --- a/lib/models/accuracy.dart +++ b/lib/models/accuracy.dart @@ -10,11 +10,13 @@ class Accuracy { // properties int id; bool deleted; + @Unique() String value; bool forCarbsRatio; bool forPortionSize; int? confidenceRating; String? notes; + String? source; // constructor Accuracy({ @@ -25,6 +27,7 @@ class Accuracy { this.forPortionSize = false, this.confidenceRating, this.notes, + this.source, }); // methods @@ -60,10 +63,14 @@ class Accuracy { } static void reorder(Accuracy accuracy, int? newPosition) { - QueryBuilder all = box.query(Accuracy_.deleted.equals(false).and(Accuracy_.id.notEquals(accuracy.id))) + QueryBuilder all = box.query(Accuracy_.deleted + .equals(false) + .and(Accuracy_.id.notEquals(accuracy.id))) ..order(Accuracy_.confidenceRating); List accuracies = all.build().find(); - newPosition == null || newPosition >= accuracies.length ? accuracies.add(accuracy) : accuracies.insert(newPosition, accuracy); + newPosition == null || newPosition >= accuracies.length + ? accuracies.add(accuracy) + : accuracies.insert(newPosition, accuracy); box.putMany(accuracies.map((item) { item.confidenceRating = accuracies.indexOf(item); return item; @@ -74,4 +81,31 @@ class Accuracy { String toString() { return value; } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['deleted'] = deleted; + data['value'] = value; + data['forCarbsRatio'] = forCarbsRatio; + data['forPortionSize'] = forPortionSize; + data['confidenceRating'] = confidenceRating; + data['notes'] = notes; + return data; + } + + static String? putFromJson(Map json, bool overrideExisting, String? source) { + final accuracy = Accuracy( + id: overrideExisting ? json['id'] : 0, + deleted: json['deleted'], + value: json['value'], + forCarbsRatio: json['forCarbsRatio'], + forPortionSize: json['forPortionSize'], + confidenceRating: json['confidenceRating'], + notes: json['notes'], + source: source, + ); + Accuracy.put(accuracy); + return null; + } } diff --git a/lib/models/basal.dart b/lib/models/basal.dart index 14c8435..c9e54aa 100644 --- a/lib/models/basal.dart +++ b/lib/models/basal.dart @@ -18,6 +18,7 @@ class Basal { @Property(type: PropertyType.date) DateTime endTime; double units; + String? source; // relations final basalProfile = ToOne(); @@ -29,6 +30,7 @@ class Basal { required this.startTime, required this.endTime, this.units = 0, + this.source, }); // methods @@ -43,6 +45,13 @@ class Basal { } } + static void removeAllForProfile(int id) { + box.putMany(getAllForProfile(id).map((item) { + item.deleted = true; + return item; + }).toList()); + } + static List getAllForProfile(int id) { QueryBuilder builder = box.query(Basal_.deleted.equals(false)) ..order(Basal_.startTime); @@ -94,4 +103,42 @@ class Basal { String toString() { return DateTimeUtils.displayTime(startTime); } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['deleted'] = deleted; + data['startTime'] = startTime.toIso8601String(); + data['endTime'] = endTime.toIso8601String(); + data['units'] = units; + data['basalProfile'] = basalProfile.targetId; + return data; + } + + static String? putFromJson(Map json, bool overrideExisting, String? source) { + DateTime? startTime = DateTime.tryParse(json['startTime']); + DateTime? endTime = DateTime.tryParse(json['endTime']); + + if (startTime == null || endTime == null) { + return startTime == null + ? endTime == null + ? 'start and end time are missing' + : 'start time is missing' + : 'end time is missing'; + } + + final basal = Basal( + id: overrideExisting ? json['id'] : 0, + deleted: json['deleted'], + startTime: startTime, + endTime: endTime, + units: json['units'], + source: source, + ); + + basal.basalProfile.targetId = json['basalProfile']; + Basal.put(basal); + + return null; + } } diff --git a/lib/models/basal_profile.dart b/lib/models/basal_profile.dart index 54a3df3..d43b456 100644 --- a/lib/models/basal_profile.dart +++ b/lib/models/basal_profile.dart @@ -1,4 +1,5 @@ import 'package:diameter/main.dart'; +import 'package:diameter/models/basal.dart'; import 'package:diameter/models/log_event.dart'; import 'package:objectbox/objectbox.dart'; import 'package:diameter/objectbox.g.dart' show BasalProfile_; @@ -11,9 +12,11 @@ class BasalProfile { // properties int id; bool deleted; + @Unique() String name; bool active; String? notes; + String? source; // constructor BasalProfile({ @@ -22,6 +25,7 @@ class BasalProfile { this.name = '', this.active = false, this.notes, + this.source, }); // methods @@ -29,7 +33,8 @@ class BasalProfile { static void put(BasalProfile basalProfile) => box.put(basalProfile); static List getAll() { - QueryBuilder all = box.query(BasalProfile_.deleted.equals(false)) + QueryBuilder all = box + .query(BasalProfile_.deleted.equals(false)) ..order(BasalProfile_.name); return all.build().find(); } @@ -38,13 +43,16 @@ class BasalProfile { final item = box.get(id); if (item != null) { item.deleted = true; + Basal.removeAllForProfile(id); box.put(item); } } static int activeCount() { Query query = box - .query(BasalProfile_.active.equals(true) & BasalProfile_.deleted.equals(false)).build(); + .query(BasalProfile_.active.equals(true) & + BasalProfile_.deleted.equals(false)) + .build(); return query.find().length; } @@ -58,13 +66,15 @@ class BasalProfile { static BasalProfile? getActive(DateTime? dateTime) { if (dateTime != null) { List activeEvents = LogEvent.getAllActiveForTime(dateTime) - .where((event) => event.basalProfile.target != null).toList(); + .where((event) => event.basalProfile.target != null) + .toList(); if (activeEvents.length > 1) { final now = DateTime.now(); - activeEvents = - activeEvents.where((item) => !activeEvents.any((other) => - item.time.isBefore(other.time) || (item.endTime ?? now).isAfter(other.endTime ?? now) - )).toList(); + activeEvents = activeEvents + .where((item) => !activeEvents.any((other) => + item.time.isBefore(other.time) || + (item.endTime ?? now).isAfter(other.endTime ?? now))) + .toList(); } if (activeEvents.length == 1) { return activeEvents.single.basalProfile.target; @@ -84,4 +94,28 @@ class BasalProfile { String toString() { return name; } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['deleted'] = deleted; + data['name'] = name; + data['active'] = active; + data['notes'] = notes; + return data; + } + + static String? putFromJson( + Map json, bool overrideExisting, String? source) { + final basalProfile = BasalProfile( + id: overrideExisting ? json['id'] : 0, + deleted: json['deleted'] == 'true', + name: json['name'], + active: json['active'] == 'true', + notes: json['notes'], + source: source, + ); + BasalProfile.put(basalProfile); + return null; + } } diff --git a/lib/models/bolus.dart b/lib/models/bolus.dart index ed043a0..ef4e366 100644 --- a/lib/models/bolus.dart +++ b/lib/models/bolus.dart @@ -21,6 +21,7 @@ class Bolus { double carbs; int? mgPerDl; double? mmolPerL; + String? source; // relations final bolusProfile = ToOne(); @@ -35,6 +36,7 @@ class Bolus { this.carbs = 0, this.mgPerDl, this.mmolPerL, + this.source, }); // methods @@ -56,6 +58,13 @@ class Bolus { } } + static void removeAllForProfile(int id) { + box.putMany(getAllForProfile(id).map((item) { + item.deleted = true; + return item; + }).toList()); + } + static Bolus? getRateForTime(DateTime? dateTime) { if (dateTime != null) { final bolusProfile = BolusProfile.getActive(dateTime); @@ -81,4 +90,48 @@ class Bolus { String toString() { return DateTimeUtils.displayTime(startTime); } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['deleted'] = deleted; + data['startTime'] = startTime.toIso8601String(); + data['endTime'] = endTime.toIso8601String(); + data['units'] = units; + data['carbs'] = carbs; + data['mgPerDl'] = mgPerDl; + data['mmolPerL'] = mmolPerL; + data['bolusProfile'] = bolusProfile.targetId; + return data; + } + + static String? putFromJson(Map json, bool overrideExisting, String? source) { + DateTime? startTime = DateTime.tryParse(json['startTime']); + DateTime? endTime = DateTime.tryParse(json['endTime']); + + if (startTime == null || endTime == null) { + return startTime == null + ? endTime == null + ? 'start and end time are missing' + : 'start time is missing' + : 'end time is missing'; + } + + final bolus = Bolus( + id: overrideExisting ? json['id'] : 0, + deleted: json['deleted'], + startTime: startTime, + endTime: endTime, + units: json['units'], + carbs: json['carbs'], + mgPerDl: json['mgPerDl'], + mmolPerL: json['mmolPerL'], + source: source, + ); + + bolus.bolusProfile.targetId = json['bolusProfile']; + Bolus.put(bolus); + + return null; + } } diff --git a/lib/models/bolus_profile.dart b/lib/models/bolus_profile.dart index 3e0408f..ff24ebb 100644 --- a/lib/models/bolus_profile.dart +++ b/lib/models/bolus_profile.dart @@ -1,4 +1,5 @@ import 'package:diameter/main.dart'; +import 'package:diameter/models/bolus.dart'; import 'package:diameter/models/log_event.dart'; import 'package:objectbox/objectbox.dart'; import 'package:diameter/objectbox.g.dart' show BolusProfile_; @@ -11,9 +12,11 @@ class BolusProfile { // properties int id; bool deleted; + @Unique() String name; bool active; String? notes; + String? source; // constructor BolusProfile({ @@ -22,6 +25,7 @@ class BolusProfile { this.name = '', this.active = false, this.notes, + this.source, }); // methods @@ -38,6 +42,7 @@ class BolusProfile { final item = box.get(id); if (item != null) { item.deleted = true; + Bolus.removeAllForProfile(id); box.put(item); } } @@ -87,4 +92,29 @@ class BolusProfile { String toString() { return name; } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['deleted'] = deleted; + data['name'] = name; + data['active'] = active; + data['notes'] = notes; + return data; + } + + static String? putFromJson( + Map json, bool overrideExisting, String? source) { + final bolusProfile = BolusProfile( + id: overrideExisting ? json['id'] : 0, + deleted: json['deleted'] == 'true', + name: json['name'], + active: json['active'], + notes: json['notes'], + source: source, + ); + + BolusProfile.put(bolusProfile); + return null; + } } diff --git a/lib/models/glucose_target.dart b/lib/models/glucose_target.dart index 890df51..ff306b0 100644 --- a/lib/models/glucose_target.dart +++ b/lib/models/glucose_target.dart @@ -17,6 +17,7 @@ class GlucoseTarget { double fromMmolPerL; double toMmolPerL; int color; + String? source; // constructor GlucoseTarget({ @@ -27,6 +28,7 @@ class GlucoseTarget { required this.fromMmolPerL, required this.toMmolPerL, required this.color, + this.source, }); // methods @@ -44,16 +46,20 @@ class GlucoseTarget { if (box.getAll().isEmpty) { reset(); } - + Condition condition; if (mgPerDl > 0 && (mmolPerL == 0 || Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl)) { - condition = GlucoseTarget_.fromMgPerDL.lessOrEqual(mgPerDl).and(GlucoseTarget_.toMgPerDl.greaterOrEqual(mgPerDl)); + condition = GlucoseTarget_.fromMgPerDL + .lessOrEqual(mgPerDl) + .and(GlucoseTarget_.toMgPerDl.greaterOrEqual(mgPerDl)); } else if (mmolPerL > 0 && (mgPerDl == 0 || Settings.glucoseMeasurement == GlucoseMeasurement.mmolPerL)) { - condition = GlucoseTarget_.fromMmolPerL.lessOrEqual(mmolPerL).and(GlucoseTarget_.toMmolPerL.greaterOrEqual(mmolPerL)); + condition = GlucoseTarget_.fromMmolPerL + .lessOrEqual(mmolPerL) + .and(GlucoseTarget_.toMmolPerL.greaterOrEqual(mmolPerL)); } else { return Colors.black; } @@ -119,4 +125,33 @@ class GlucoseTarget { ]; box.putMany(defaultTargets); } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['deleted'] = deleted; + data['fromMgPerDL'] = fromMgPerDL; + data['toMgPerDl'] = toMgPerDl; + data['fromMmolPerL'] = fromMmolPerL; + data['toMmolPerL'] = toMmolPerL; + data['color'] = color; + return data; + } + + static String? putFromJson(Map json, bool overrideExisting, String? source) { + final glucoseTarget = GlucoseTarget( + id: overrideExisting ? json['id'] : 0, + deleted: json['deleted'], + fromMgPerDL: json['fromMgPerDL'], + toMgPerDl: json['toMgPerDl'], + fromMmolPerL: json['fromMmolPerL'], + toMmolPerL: json['toMmolPerL'], + color: json['color'], + source: source, + ); + + GlucoseTarget.put(glucoseTarget); + + return null; + } } diff --git a/lib/models/log_bolus.dart b/lib/models/log_bolus.dart index 1b45234..84620bd 100644 --- a/lib/models/log_bolus.dart +++ b/lib/models/log_bolus.dart @@ -24,6 +24,7 @@ class LogBolus { double? mmolPerLCorrection; bool setManually; String? notes; + String? source; // relations final logEntry = ToOne(); @@ -45,6 +46,7 @@ class LogBolus { this.mmolPerLCorrection, this.setManually = false, this.notes, + this.source, }); // methods @@ -72,8 +74,7 @@ class LogBolus { } static bool bolusForMealExists(int id) { - QueryBuilder builder = box.query(LogBolus_.deleted - .equals(false)); + QueryBuilder builder = box.query(LogBolus_.deleted.equals(false)); builder.link(LogBolus_.meal, LogMeal_.id.equals(id)); return builder.build().find().isNotEmpty; } @@ -86,8 +87,63 @@ class LogBolus { } } + static void removeAllForEntry(int id) { + box.putMany(getAllForEntry(id).map((item) { + item.deleted = true; + return item; + }).toList()); + } + @override String toString() { return units.toString(); } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['deleted'] = deleted; + data['units'] = units; + data['carbs'] = carbs; + data['delay'] = delay; + data['mgPerDlCurrent'] = mgPerDlCurrent; + data['mgPerDlTarget'] = mgPerDlTarget; + data['mgPerDlCorrection'] = mgPerDlCorrection; + data['mmolPerLCurrent'] = mmolPerLCurrent; + data['mmolPerLTarget'] = mmolPerLTarget; + data['mmolPerLCorrection'] = mmolPerLCorrection; + data['setManually'] = setManually; + data['notes'] = notes; + data['logEntry'] = logEntry.targetId; + data['rate'] = rate.targetId; + data['meal'] = meal.targetId; + return data; + } + + static String? putFromJson(Map json, bool overrideExisting, String? source) { + final logBolus = LogBolus( + id: overrideExisting ? json['id'] : 0, + deleted: json['deleted'], + units: json['units'], + carbs: json['carbs'], + delay: json['delay'], + mgPerDlCurrent: json['mgPerDlCurrent'], + mgPerDlTarget: json['mgPerDlTarget'], + mgPerDlCorrection: json['mgPerDlCorrection'], + mmolPerLCurrent: json['mmolPerLCurrent'], + mmolPerLTarget: json['mmolPerLTarget'], + mmolPerLCorrection: json['mmolPerLCorrection'], + setManually: json['setManually'], + notes: json['notes'], + source: source, + ); + + logBolus.logEntry.targetId = json['logEntry']; + logBolus.rate.targetId = json['rate']; + logBolus.meal.targetId = json['meal']; + + LogBolus.put(logBolus); + + return null; + } } diff --git a/lib/models/log_entry.dart b/lib/models/log_entry.dart index 97f687e..9af1a65 100644 --- a/lib/models/log_entry.dart +++ b/lib/models/log_entry.dart @@ -1,5 +1,6 @@ import 'package:diameter/main.dart'; import 'package:diameter/models/log_bolus.dart'; +import 'package:diameter/models/log_meal.dart'; import 'package:diameter/models/settings.dart'; import 'package:diameter/utils/date_time_utils.dart'; import 'package:objectbox/objectbox.dart'; @@ -19,6 +20,7 @@ class LogEntry { double? mmolPerL; double? glucoseTrend; String? notes; + String? source; // constructor LogEntry({ @@ -29,6 +31,7 @@ class LogEntry { this.mmolPerL, this.glucoseTrend, this.notes, + this.source, }); // methods @@ -40,6 +43,8 @@ class LogEntry { final item = box.get(id); if (item != null) { item.deleted = true; + LogMeal.removeAllForEntry(id); + LogBolus.removeAllForEntry(id); box.put(item); } } @@ -63,9 +68,78 @@ class LogEntry { entry.time.isBefore(endOfDay)); }).toList(); } - + + static List getAllByFilter({ + DateTime? startDate, + DateTime? endDate, + int? minMgPerDl, + int? maxMgPerDl, + double? minMmolPerL, + double? maxMmolPerL, + int? mealId, + String? mealName, + String? note, + }) { + DateTime start = startDate ?? DateTime(2000, 1, 1); + DateTime end = endDate ?? DateTime.now(); + QueryBuilder builder = box.query(LogEntry_.deleted.equals(false) & + (Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl + ? LogEntry_.mgPerDl.between( + minMgPerDl ?? 0, maxMgPerDl ?? double.maxFinite.toInt()) + : LogEntry_.mmolPerL + .between(minMmolPerL ?? 0, maxMmolPerL ?? double.maxFinite))) + ..order(LogEntry_.time, flags: Order.descending); + + return builder.build().find().where((entry) { + return (note == null || (entry.notes ?? '').contains(note)) && + (mealId == null || + LogMeal.getAllForEntry(entry.id) + .any((LogMeal logMeal) => logMeal.meal.targetId == mealId)) && + (mealName == null || + LogMeal.getAllForEntry(entry.id).any( + (LogMeal logMeal) => logMeal.value.contains(mealName))) && + (entry.time.compareTo(start) >= 0 && entry.time.isBefore(end)); + }).toList(); + } + @override String toString() { return DateTimeUtils.displayDateTime(time); } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['deleted'] = deleted; + data['time'] = time.toIso8601String(); + data['mgPerDl'] = mgPerDl; + data['mmolPerL'] = mmolPerL; + data['glucoseTrend'] = glucoseTrend; + data['notes'] = notes; + return data; + } + + static String? putFromJson( + Map json, bool overrideExisting, String? source) { + DateTime? time = DateTime.tryParse(json['time']); + + if (time == null) { + return 'time is missing'; + } + + final logEntry = LogEntry( + id: overrideExisting ? json['id'] : 0, + deleted: json['deleted'], + time: time, + mgPerDl: json['mgPerDl'], + mmolPerL: json['mmolPerL'], + glucoseTrend: json['glucoseTrend'], + notes: json['notes'], + source: source, + ); + + LogEntry.put(logEntry); + + return null; + } } diff --git a/lib/models/log_event.dart b/lib/models/log_event.dart index 34a4728..fd0c247 100644 --- a/lib/models/log_event.dart +++ b/lib/models/log_event.dart @@ -20,6 +20,7 @@ class LogEvent { bool hasEndTime; int? reminderDuration; String? notes; + String? source; @Transient() String? title; @@ -40,6 +41,7 @@ class LogEvent { this.hasEndTime = false, this.reminderDuration, this.notes, + this.source, }); // methods @@ -143,4 +145,46 @@ class LogEvent { String toString() { return eventType.target?.value ?? ''; } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['deleted'] = deleted; + data['time'] = time.toIso8601String(); + data['endTime'] = endTime?.toIso8601String(); + data['hasEndTime'] = hasEndTime; + data['reminderDuration'] = reminderDuration; + data['notes'] = notes; + data['eventType'] = eventType.targetId; + data['bolusProfile'] = bolusProfile.targetId; + data['basalProfile'] = basalProfile.targetId; + return data; + } + + static String? putFromJson(Map json, bool overrideExisting, String? source) { + DateTime? time = DateTime.tryParse(json['time']); + + if (time == null) { + return 'time is missing'; + } + + final logEvent = LogEvent( + id: overrideExisting ? json['id'] : 0, + deleted: json['deleted'], + time: time, + endTime: DateTime.tryParse(json['endTime']), + hasEndTime: json['hasEndTime'], + reminderDuration: json['reminderDuration'], + notes: json['notes'], + source: source, + ); + + logEvent.eventType.targetId = json['eventType']; + logEvent.bolusProfile.targetId = json['bolusProfile']; + logEvent.basalProfile.targetId = json['basalProfile']; + + LogEvent.put(logEvent); + + return null; + } } diff --git a/lib/models/log_event_type.dart b/lib/models/log_event_type.dart index e1683fb..746f0e5 100644 --- a/lib/models/log_event_type.dart +++ b/lib/models/log_event_type.dart @@ -12,10 +12,12 @@ class LogEventType { // properties int id; bool deleted; + @Unique() String value; bool hasEndTime; int? defaultReminderDuration; String? notes; + String? source; // constructor LogEventType({ @@ -25,6 +27,7 @@ class LogEventType { this.hasEndTime = false, this.defaultReminderDuration, this.notes, + this.source, }); // relations @@ -52,4 +55,32 @@ class LogEventType { String toString() { return value; } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['deleted'] = deleted; + data['value'] = value; + data['hasEndTime'] = hasEndTime; + data['defaultReminderDuration'] = defaultReminderDuration; + data['notes'] = notes; + return data; + } + + + static String? putFromJson(Map json, bool overrideExisting, String? source) { + final logEventType = LogEventType( + id: overrideExisting ? json['id'] : 0, + deleted: json['deleted'], + value: json['value'], + hasEndTime: json['hasEndTime'], + defaultReminderDuration: json['defaultReminderDuration'], + notes: json['notes'], + source: source, + ); + + LogEventType.put(logEventType); + + return null; + } } diff --git a/lib/models/log_meal.dart b/lib/models/log_meal.dart index 8c9c471..21dbed3 100644 --- a/lib/models/log_meal.dart +++ b/lib/models/log_meal.dart @@ -22,8 +22,8 @@ class LogMeal { double? portionSize; double? totalCarbs; String? notes; - double? bolus; double amount; + String? source; // relations final logEntry = ToOne(); @@ -44,6 +44,7 @@ class LogMeal { this.portionSize, this.totalCarbs, this.notes, + this.source, }); // methods @@ -57,6 +58,13 @@ class LogMeal { } } + static void removeAllForEntry(int id) { + box.putMany(getAllForEntry(id).map((item) { + item.deleted = true; + return item; + }).toList()); + } + static List getAllForEntry(int id) { QueryBuilder builder = box.query(LogMeal_.deleted.equals(false)); builder.link(LogMeal_.logEntry, LogEntry_.id.equals(id)); @@ -86,4 +94,50 @@ class LogMeal { String toString() { return value; } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['deleted'] = deleted; + data['value'] = value; + data['carbsRatio'] = carbsRatio; + data['portionSize'] = portionSize; + data['totalCarbs'] = totalCarbs; + data['notes'] = notes; + data['amount'] = amount; + data['logEntry'] = logEntry.targetId; + data['meal'] = meal.targetId; + data['mealSource'] = mealSource.targetId; + data['mealCategory'] = mealCategory.targetId; + data['mealPortionType'] = mealPortionType.targetId; + data['portionSizeAccuracy'] = portionSizeAccuracy.targetId; + data['carbsRatioAccuracy'] = carbsRatioAccuracy.targetId; + return data; + } + + static String? putFromJson( + Map json, bool overrideExisting, String? source) { + final logMeal = LogMeal( + id: overrideExisting ? json['id'] : 0, + deleted: json['deleted'] == 'true', + value: json['value'], + carbsRatio: json['carbsRatio'], + portionSize: json['portionSize'], + totalCarbs: json['totalCarbs'], + amount: json['amount'], + notes: json['notes'], + source: source, + ); + + logMeal.logEntry.targetId = json['logEntry']; + logMeal.meal.targetId = json['meal']; + logMeal.mealSource.targetId = json['mealSource']; + logMeal.mealCategory.targetId = json['mealCategory']; + logMeal.mealPortionType.targetId = json['mealPortionType']; + logMeal.portionSizeAccuracy.targetId = json['portionSizeAccuracy']; + logMeal.carbsRatioAccuracy.targetId = json['carbsRatioAccuracy']; + + LogMeal.put(logMeal); + return null; + } } diff --git a/lib/models/meal.dart b/lib/models/meal.dart index c168dc0..d8ffaea 100644 --- a/lib/models/meal.dart +++ b/lib/models/meal.dart @@ -16,6 +16,7 @@ class Meal { // properties int id; bool deleted; + @Unique() String value; double? carbsRatio; double? portionSize; @@ -23,6 +24,7 @@ class Meal { int? delayedBolusDuration; double? delayedBolusPercentage; String? notes; + String? source; // relations final mealSource = ToOne(); @@ -42,6 +44,7 @@ class Meal { this.delayedBolusDuration, this.delayedBolusPercentage, this.notes, + this.source, }); // methods @@ -49,7 +52,7 @@ class Meal { static void put(Meal meal) => box.put(meal); static List getAll() { - QueryBuilder builder = box.query(Meal_.deleted.equals(false))..order(Meal_.value); + QueryBuilder builder = box.query(Meal_.deleted.equals(false))..order(Meal_.value); return builder.build().find(); } @@ -65,4 +68,48 @@ class Meal { String toString() { return value; } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['deleted'] = deleted; + data['value'] = value; + data['carbsRatio'] = carbsRatio; + data['portionSize'] = portionSize; + data['carbsPerPortion'] = carbsPerPortion; + data['delayedBolusDuration'] = delayedBolusDuration; + data['delayedBolusPercentage'] = delayedBolusPercentage; + data['notes'] = notes; + data['mealSource'] = mealSource.targetId; + data['mealCategory'] = mealCategory.targetId; + data['mealPortionType'] = mealPortionType.targetId; + data['portionSizeAccuracy'] = portionSizeAccuracy.targetId; + data['carbsRatioAccuracy'] = carbsRatioAccuracy.targetId; + return data; + } + + static String? putFromJson( + Map json, bool overrideExisting, String? source) { + final meal = Meal( + id: overrideExisting ? json['id'] : 0, + deleted: json['deleted'] == 'true', + value: json['value'], + carbsRatio: json['carbsRatio'], + portionSize: json['portionSize'], + carbsPerPortion: json['carbsPerPortion'], + delayedBolusDuration: json['delayedBolusDuration'], + delayedBolusPercentage: json['delayedBolusPercentage'], + notes: json['notes'], + source: source, + ); + + meal.mealSource.targetId = json['mealSource']; + meal.mealCategory.targetId = json['mealCategory']; + meal.mealPortionType.targetId = json['mealPortionType']; + meal.portionSizeAccuracy.targetId = json['portionSizeAccuracy']; + meal.carbsRatioAccuracy.targetId = json['carbsRatioAccuracy']; + + Meal.put(meal); + return null; + } } diff --git a/lib/models/meal_category.dart b/lib/models/meal_category.dart index 80a44a4..31a8a2a 100644 --- a/lib/models/meal_category.dart +++ b/lib/models/meal_category.dart @@ -10,8 +10,10 @@ class MealCategory { // properties int id; bool deleted; + @Unique() String value; String? notes; + String? source; // constructor MealCategory({ @@ -19,6 +21,7 @@ class MealCategory { this.deleted = false, this.value = '', this.notes, + this.source, }); // methods @@ -42,4 +45,27 @@ class MealCategory { String toString() { return value; } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['deleted'] = deleted; + data['value'] = value; + data['notes'] = notes; + return data; + } + + static String? putFromJson( + Map json, bool overrideExisting, String? source) { + final mealCategory = MealCategory( + id: overrideExisting ? json['id'] : 0, + deleted: json['deleted'] == 'true', + value: json['value'], + notes: json['notes'], + source: source, + ); + + MealCategory.put(mealCategory); + return null; + } } diff --git a/lib/models/meal_portion_type.dart b/lib/models/meal_portion_type.dart index f337b5b..27013a4 100644 --- a/lib/models/meal_portion_type.dart +++ b/lib/models/meal_portion_type.dart @@ -5,13 +5,16 @@ import 'package:diameter/objectbox.g.dart' show MealPortionType_; @Entity(uid: 2111511899235985637) @Sync() class MealPortionType { - static final Box box = objectBox.store.box(); + static final Box box = + objectBox.store.box(); // properties int id; bool deleted; + @Unique() String value; String? notes; + String? source; // constructor MealPortionType({ @@ -19,17 +22,20 @@ class MealPortionType { this.deleted = false, this.value = '', this.notes, + this.source, }); // methods static MealPortionType? get(int id) => box.get(id); static void put(MealPortionType mealPortionType) => box.put(mealPortionType); - + static List getAll() { - QueryBuilder builder = box.query(MealPortionType_.deleted.equals(false))..order(MealPortionType_.value); + QueryBuilder builder = box + .query(MealPortionType_.deleted.equals(false)) + ..order(MealPortionType_.value); return builder.build().find(); } - + static void remove(int id) { final item = box.get(id); if (item != null) { @@ -42,4 +48,27 @@ class MealPortionType { String toString() { return value; } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['deleted'] = deleted; + data['value'] = value; + data['notes'] = notes; + return data; + } + + static String? putFromJson( + Map json, bool overrideExisting, String? source) { + final mealPortionType = MealPortionType( + id: overrideExisting ? json['id'] : 0, + deleted: json['deleted'] == 'true', + value: json['value'], + notes: json['notes'], + source: source, + ); + + MealPortionType.put(mealPortionType); + return null; + } } diff --git a/lib/models/meal_source.dart b/lib/models/meal_source.dart index df007b0..25d9700 100644 --- a/lib/models/meal_source.dart +++ b/lib/models/meal_source.dart @@ -13,8 +13,10 @@ class MealSource { // properties int id; bool deleted; + @Unique() String value; String? notes; + String? source; // relations final defaultMealCategory = ToOne(); @@ -28,17 +30,19 @@ class MealSource { this.deleted = false, this.value = '', this.notes, + this.source, }); // methods static MealSource? get(int id) => box.get(id); static void put(MealSource mealSource) => box.put(mealSource); - + static List getAll() { - QueryBuilder builder = box.query(MealSource_.deleted.equals(false))..order(MealSource_.value); + QueryBuilder builder = + box.query(MealSource_.deleted.equals(false))..order(MealSource_.value); return builder.build().find(); } - + static void remove(int id) { final item = box.get(id); if (item != null) { @@ -51,4 +55,36 @@ class MealSource { String toString() { return value; } + + Map toJson() { + final Map data = {}; + data['id'] = id; + data['deleted'] = deleted; + data['value'] = value; + data['notes'] = notes; + data['defaultMealCategory'] = defaultMealCategory.targetId; + data['defaultMealPortionType'] = defaultMealPortionType.targetId; + data['defaultCarbsRatioAccuracy'] = defaultCarbsRatioAccuracy.targetId; + data['defaultPortionSizeAccuracy'] = defaultPortionSizeAccuracy.targetId; + return data; + } + + static String? putFromJson( + Map json, bool overrideExisting, String? source) { + final mealSource = MealSource( + id: overrideExisting ? json['id'] : 0, + deleted: json['deleted'] == 'true', + value: json['value'], + notes: json['notes'], + source: source, + ); + + mealSource.defaultMealCategory.targetId = json['defaultMealCategory']; + mealSource.defaultMealPortionType.targetId = json['defaultMealPortionType']; + mealSource.defaultCarbsRatioAccuracy.targetId = json['defaultCarbsRatioAccuracy']; + mealSource.defaultPortionSizeAccuracy.targetId = json['defaultPortionSizeAccuracy']; + + MealSource.put(mealSource); + return null; + } } diff --git a/lib/models/settings.dart b/lib/models/settings.dart index 75394ca..95164a7 100644 --- a/lib/models/settings.dart +++ b/lib/models/settings.dart @@ -69,6 +69,8 @@ class Settings { bool useDarkTheme; + DateTime? lastExportTimestamp; + // constructor Settings({ this.id = 0, @@ -89,6 +91,7 @@ class Settings { this.targetGlucoseMgPerDl = 100, this.targetGlucoseMmolPerL = 5.5, this.useDarkTheme = false, + this.lastExportTimestamp, }); // methods @@ -101,7 +104,8 @@ class Settings { static NutritionMeasurement get nutritionMeasurement => NutritionMeasurement.values[get().nutritionMeasurementIndex]; - static GlucoseMeasurement get glucoseMeasurement => GlucoseMeasurement.values[get().glucoseMeasurementIndex]; + static GlucoseMeasurement get glucoseMeasurement => + GlucoseMeasurement.values[get().glucoseMeasurementIndex]; static GlucoseDisplayMode get glucoseDisplayMode => GlucoseDisplayMode.values[get().glucoseDisplayModeIndex]; @@ -120,10 +124,39 @@ class Settings { static ThemeMode get themeMode => get().useDarkTheme ? ThemeMode.dark : ThemeMode.light; + static DateTime? get lastExportTimeStamp => get().lastExportTimestamp; + static void put(Settings settings) => box.put(settings); static void reset() { box.removeAll(); box.put(Settings(useDarkTheme: ThemeMode.system == ThemeMode.dark)); } + + static Map toJson() { + Settings settings = get(); + final Map data = {}; + data['id'] = settings.id; + data['nutritionMeasurementIndex'] = settings.nutritionMeasurementIndex; + data['glucoseDisplayModeIndex'] = settings.glucoseDisplayModeIndex; + data['glucoseMeasurementIndex'] = settings.glucoseMeasurementIndex; + data['targetGlucoseMgPerDl'] = settings.targetGlucoseMgPerDl; + data['targetGlucoseMmolPerL'] = settings.targetGlucoseMmolPerL; + data['insulinIncrements'] = settings.insulinIncrements; + data['nutritionIncrements'] = settings.nutritionIncrements; + data['mmolPerLIncrements'] = settings.mmolPerLIncrements; + data['amountIncrements'] = settings.amountIncrements; + data['dateFormat'] = settings.dateFormat; + data['longDateFormat'] = settings.longDateFormat; + data['timeFormat'] = settings.timeFormat; + data['longTimeFormat'] = settings.longTimeFormat; + data['showConfirmationDialogOnCancel'] = + settings.showConfirmationDialogOnCancel; + data['showConfirmationDialogOnDelete'] = + settings.showConfirmationDialogOnDelete; + data['showConfirmationDialogOnStopEvent'] = + settings.showConfirmationDialogOnStopEvent; + data['useDarkTheme'] = settings.useDarkTheme; + return data; + } } diff --git a/lib/models/ingredient.dart b/lib/models/x_ingredient.dart similarity index 95% rename from lib/models/ingredient.dart rename to lib/models/x_ingredient.dart index 486a178..99bbc01 100644 --- a/lib/models/ingredient.dart +++ b/lib/models/x_ingredient.dart @@ -1,12 +1,12 @@ import 'package:diameter/main.dart'; import 'package:diameter/models/meal.dart'; -import 'package:diameter/models/recipe.dart'; +import 'package:diameter/models/x_recipe.dart'; import 'package:diameter/utils/utils.dart'; import 'package:objectbox/objectbox.dart'; import 'package:diameter/objectbox.g.dart' show Ingredient_, Recipe_; -@Entity(uid: 6950311793136068892) -@Sync() +// @Entity(uid: 6950311793136068892) +// @Sync() class Ingredient { static final Box box = objectBox.store.box(); diff --git a/lib/models/recipe.dart b/lib/models/x_recipe.dart similarity index 93% rename from lib/models/recipe.dart rename to lib/models/x_recipe.dart index 24b8bc6..5600d6c 100644 --- a/lib/models/recipe.dart +++ b/lib/models/x_recipe.dart @@ -1,12 +1,12 @@ import 'package:diameter/main.dart'; -import 'package:diameter/models/ingredient.dart'; +import 'package:diameter/models/x_ingredient.dart'; import 'package:diameter/models/meal.dart'; import 'package:diameter/utils/utils.dart'; import 'package:objectbox/objectbox.dart'; import 'package:diameter/objectbox.g.dart' show Recipe_; -@Entity(uid: 6497942314956341514) -@Sync() +// @Entity(uid: 6497942314956341514) +// @Sync() class Recipe { static final Box box = objectBox.store.box(); diff --git a/lib/models/user.dart b/lib/models/x_user.dart similarity index 97% rename from lib/models/user.dart rename to lib/models/x_user.dart index deac546..514566e 100644 --- a/lib/models/user.dart +++ b/lib/models/x_user.dart @@ -1,8 +1,8 @@ import 'package:diameter/main.dart'; import 'package:objectbox/objectbox.dart'; -@Entity() -@Sync() +// @Entity() +// @Sync() class User { static final Box box = objectBox.store.box(); // properties diff --git a/lib/navigation.dart b/lib/navigation.dart index 996c38e..c4a0174 100644 --- a/lib/navigation.dart +++ b/lib/navigation.dart @@ -1,3 +1,4 @@ +import 'package:diameter/localization_keys.dart'; import 'package:diameter/screens/category/categories.dart'; import 'package:diameter/screens/category/accuracy_detail.dart'; import 'package:diameter/screens/category/accuracy_list.dart'; @@ -28,6 +29,7 @@ import 'package:diameter/screens/reports/export.dart'; import 'package:diameter/screens/reports/reports.dart'; import 'package:diameter/settings.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class Routes { static const String basal = BasalDetailScreen.routeName; @@ -119,7 +121,7 @@ class _NavigationState extends State { ), ), ListTile( - title: const Text('Log'), + title: Text(translate(LocalizationKeys.navigation_log)), leading: const Icon(Icons.dashboard), onTap: () { selectDestination(Routes.log); @@ -127,7 +129,7 @@ class _NavigationState extends State { selected: widget.currentLocation == Routes.log, ), ListTile( - title: const Text('Log Events'), + title: Text(translate(LocalizationKeys.navigation_logEvents)), leading: const Icon(Icons.event), onTap: () { selectDestination(Routes.logEvents); @@ -135,7 +137,7 @@ class _NavigationState extends State { selected: widget.currentLocation == Routes.logEvents, ), ListTile( - title: const Text('Reports'), + title: Text(translate(LocalizationKeys.navigation_reports)), leading: const Icon(Icons.show_chart), onTap: () { selectDestination(Routes.reports); @@ -143,7 +145,7 @@ class _NavigationState extends State { selected: Routes.reportRoutes.contains(widget.currentLocation), ), ListTile( - title: const Text('Meals'), + title: Text(translate(LocalizationKeys.navigation_meals)), leading: const Icon(Icons.dinner_dining), onTap: () { selectDestination(Routes.meals); @@ -151,7 +153,7 @@ class _NavigationState extends State { selected: Routes.mealRoutes.contains(widget.currentLocation), ), ListTile( - title: const Text('Basal Profiles'), + title: Text(translate(LocalizationKeys.navigation_basalProfiles)), leading: const Icon(Icons.access_time), onTap: () { selectDestination(Routes.basalProfiles); @@ -159,7 +161,7 @@ class _NavigationState extends State { selected: Routes.basalRoutes.contains(widget.currentLocation), ), ListTile( - title: const Text('Bolus Profiles'), + title: Text(translate(LocalizationKeys.navigation_bolusProfiles)), leading: const Icon(Icons.medication), onTap: () { selectDestination(Routes.bolusProfiles); @@ -167,7 +169,7 @@ class _NavigationState extends State { selected: Routes.bolusRoutes.contains(widget.currentLocation), ), ListTile( - title: const Text('Categorization'), + title: Text(translate(LocalizationKeys.navigation_categorization)), leading: const Icon(Icons.category), onTap: () { selectDestination(Routes.category); @@ -175,7 +177,7 @@ class _NavigationState extends State { selected: Routes.categoryRoutes.contains(widget.currentLocation), ), ListTile( - title: const Text('Settings'), + title: Text(translate(LocalizationKeys.navigation_settings)), leading: const Icon(Icons.settings), onTap: () { selectDestination(Routes.settings); diff --git a/lib/objectbox-model.json b/lib/objectbox-model.json index a96c422..f12c504 100644 --- a/lib/objectbox-model.json +++ b/lib/objectbox-model.json @@ -5,7 +5,7 @@ "entities": [ { "id": "2:1467758525778521891", - "lastPropertyId": "6:3409466778841164684", + "lastPropertyId": "7:3194552447464951410", "name": "Basal", "flags": 2, "properties": [ @@ -42,13 +42,18 @@ "id": "6:3409466778841164684", "name": "deleted", "type": 1 + }, + { + "id": "7:3194552447464951410", + "name": "source", + "type": 9 } ], "relations": [] }, { "id": "3:3613736032926903785", - "lastPropertyId": "5:8140071977687660397", + "lastPropertyId": "6:3153627070507809184", "name": "BasalProfile", "flags": 2, "properties": [ @@ -77,13 +82,18 @@ "id": "5:8140071977687660397", "name": "deleted", "type": 1 + }, + { + "id": "6:3153627070507809184", + "name": "source", + "type": 9 } ], "relations": [] }, { "id": "4:3417770529060202389", - "lastPropertyId": "9:7440090146687096977", + "lastPropertyId": "10:6138236789530596038", "name": "Bolus", "flags": 2, "properties": [ @@ -135,13 +145,18 @@ "id": "9:7440090146687096977", "name": "deleted", "type": 1 + }, + { + "id": "10:6138236789530596038", + "name": "source", + "type": 9 } ], "relations": [] }, { "id": "5:8812452529027052317", - "lastPropertyId": "5:8082994824481464395", + "lastPropertyId": "6:8746199841403936632", "name": "BolusProfile", "flags": 2, "properties": [ @@ -170,13 +185,18 @@ "id": "5:8082994824481464395", "name": "deleted", "type": 1 + }, + { + "id": "6:8746199841403936632", + "name": "source", + "type": 9 } ], "relations": [] }, { "id": "6:752131069307970560", - "lastPropertyId": "10:2505303363495348118", + "lastPropertyId": "11:8454251731096762477", "name": "LogEntry", "flags": 2, "properties": [ @@ -215,13 +235,18 @@ "id": "10:2505303363495348118", "name": "glucoseTrend", "type": 8 + }, + { + "id": "11:8454251731096762477", + "name": "source", + "type": 9 } ], "relations": [] }, { "id": "7:4303325892753185970", - "lastPropertyId": "12:3041952167628926163", + "lastPropertyId": "13:9206766629540069341", "name": "LogEvent", "flags": 2, "properties": [ @@ -284,13 +309,18 @@ "id": "12:3041952167628926163", "name": "reminderDuration", "type": 6 + }, + { + "id": "13:9206766629540069341", + "name": "source", + "type": 9 } ], "relations": [] }, { "id": "8:8362795406595606110", - "lastPropertyId": "8:1869014400856897151", + "lastPropertyId": "9:7377683740652293217", "name": "LogEventType", "flags": 2, "properties": [ @@ -340,13 +370,18 @@ "flags": 520, "indexId": "28:4563029809754152081", "relationTarget": "BasalProfile" + }, + { + "id": "9:7377683740652293217", + "name": "source", + "type": 9 } ], "relations": [] }, { "id": "9:411177866700467286", - "lastPropertyId": "19:8965198821438347033", + "lastPropertyId": "20:3296734778930341954", "name": "LogMeal", "flags": 2, "properties": [ @@ -371,11 +406,6 @@ "name": "portionSize", "type": 8 }, - { - "id": "6:8074052538574863399", - "name": "bolus", - "type": 8 - }, { "id": "9:1920579694098037947", "name": "notes", @@ -451,13 +481,18 @@ "id": "19:8965198821438347033", "name": "totalCarbs", "type": 8 + }, + { + "id": "20:3296734778930341954", + "name": "source", + "type": 9 } ], "relations": [] }, { "id": "10:382130101578692012", - "lastPropertyId": "15:8283810711091063880", + "lastPropertyId": "16:8612850434699732822", "name": "Meal", "flags": 2, "properties": [ @@ -546,13 +581,18 @@ "id": "15:8283810711091063880", "name": "delayedBolusPercentage", "type": 8 + }, + { + "id": "16:8612850434699732822", + "name": "source", + "type": 9 } ], "relations": [] }, { "id": "11:3158200688796904913", - "lastPropertyId": "4:824435977543069541", + "lastPropertyId": "5:2221810999445049020", "name": "MealCategory", "flags": 2, "properties": [ @@ -576,13 +616,18 @@ "id": "4:824435977543069541", "name": "deleted", "type": 1 + }, + { + "id": "5:2221810999445049020", + "name": "source", + "type": 9 } ], "relations": [] }, { "id": "12:2111511899235985637", - "lastPropertyId": "4:5680236937391945907", + "lastPropertyId": "5:6807468448392267469", "name": "MealPortionType", "flags": 2, "properties": [ @@ -606,13 +651,18 @@ "id": "4:5680236937391945907", "name": "deleted", "type": 1 + }, + { + "id": "5:6807468448392267469", + "name": "source", + "type": 9 } ], "relations": [] }, { "id": "13:1283034494527412242", - "lastPropertyId": "8:4547899751779962180", + "lastPropertyId": "9:6873017370661053783", "name": "MealSource", "flags": 2, "properties": [ @@ -668,13 +718,18 @@ "id": "8:4547899751779962180", "name": "deleted", "type": 1 + }, + { + "id": "9:6873017370661053783", + "name": "source", + "type": 9 } ], "relations": [] }, { "id": "14:8033487006694871160", - "lastPropertyId": "18:7503231998671134983", + "lastPropertyId": "19:3683854670538916324", "name": "LogBolus", "flags": 2, "properties": [ @@ -767,13 +822,18 @@ "id": "18:7503231998671134983", "name": "mmolPerLCorrection", "type": 8 + }, + { + "id": "19:3683854670538916324", + "name": "source", + "type": 9 } ], "relations": [] }, { "id": "15:291512798403320400", - "lastPropertyId": "7:6675647182186603076", + "lastPropertyId": "8:1800866627092978542", "name": "Accuracy", "flags": 2, "properties": [ @@ -812,13 +872,18 @@ "id": "7:6675647182186603076", "name": "deleted", "type": 1 + }, + { + "id": "8:1800866627092978542", + "name": "source", + "type": 9 } ], "relations": [] }, { "id": "16:3989341091218179227", - "lastPropertyId": "27:3553639710779248831", + "lastPropertyId": "28:7196743106880728684", "name": "Settings", "flags": 2, "properties": [ @@ -912,13 +977,18 @@ "id": "27:3553639710779248831", "name": "amountIncrements", "type": 8 + }, + { + "id": "28:7196743106880728684", + "name": "lastExportTimestamp", + "type": 10 } ], "relations": [] }, { "id": "17:5041265995704044399", - "lastPropertyId": "7:1333487551279074696", + "lastPropertyId": "8:1091046738846336945", "name": "GlucoseTarget", "flags": 2, "properties": [ @@ -957,6 +1027,11 @@ "id": "7:1333487551279074696", "name": "color", "type": 6 + }, + { + "id": "8:1091046738846336945", + "name": "source", + "type": 9 } ], "relations": [] @@ -1125,7 +1200,8 @@ 4678123663117222609, 780211923138281722, 763575433624979013, - 1225271130099322691 + 1225271130099322691, + 8074052538574863399 ], "retiredRelationUids": [], "version": 1 diff --git a/lib/objectbox.g.dart b/lib/objectbox.g.dart index ca3d579..7d64074 100644 --- a/lib/objectbox.g.dart +++ b/lib/objectbox.g.dart @@ -19,7 +19,7 @@ import 'models/basal_profile.dart'; import 'models/bolus.dart'; import 'models/bolus_profile.dart'; import 'models/glucose_target.dart'; -import 'models/ingredient.dart'; +import 'models/x_ingredient.dart'; import 'models/log_bolus.dart'; import 'models/log_entry.dart'; import 'models/log_event.dart'; @@ -29,9 +29,9 @@ import 'models/meal.dart'; import 'models/meal_category.dart'; import 'models/meal_portion_type.dart'; import 'models/meal_source.dart'; -import 'models/recipe.dart'; +import 'models/x_recipe.dart'; import 'models/settings.dart'; -import 'models/user.dart'; +import 'models/x_user.dart'; export 'package:objectbox/objectbox.dart'; // so that callers only have to import this file @@ -39,7 +39,7 @@ final _entities = [ ModelEntity( id: const IdUid(2, 1467758525778521891), name: 'Basal', - lastPropertyId: const IdUid(6, 3409466778841164684), + lastPropertyId: const IdUid(7, 3194552447464951410), flags: 2, properties: [ ModelProperty( @@ -73,6 +73,11 @@ final _entities = [ id: const IdUid(6, 3409466778841164684), name: 'deleted', type: 1, + flags: 0), + ModelProperty( + id: const IdUid(7, 3194552447464951410), + name: 'source', + type: 9, flags: 0) ], relations: [], @@ -80,7 +85,7 @@ final _entities = [ ModelEntity( id: const IdUid(3, 3613736032926903785), name: 'BasalProfile', - lastPropertyId: const IdUid(5, 8140071977687660397), + lastPropertyId: const IdUid(6, 3153627070507809184), flags: 2, properties: [ ModelProperty( @@ -107,6 +112,11 @@ final _entities = [ id: const IdUid(5, 8140071977687660397), name: 'deleted', type: 1, + flags: 0), + ModelProperty( + id: const IdUid(6, 3153627070507809184), + name: 'source', + type: 9, flags: 0) ], relations: [], @@ -114,7 +124,7 @@ final _entities = [ ModelEntity( id: const IdUid(4, 3417770529060202389), name: 'Bolus', - lastPropertyId: const IdUid(9, 7440090146687096977), + lastPropertyId: const IdUid(10, 6138236789530596038), flags: 2, properties: [ ModelProperty( @@ -163,6 +173,11 @@ final _entities = [ id: const IdUid(9, 7440090146687096977), name: 'deleted', type: 1, + flags: 0), + ModelProperty( + id: const IdUid(10, 6138236789530596038), + name: 'source', + type: 9, flags: 0) ], relations: [], @@ -170,7 +185,7 @@ final _entities = [ ModelEntity( id: const IdUid(5, 8812452529027052317), name: 'BolusProfile', - lastPropertyId: const IdUid(5, 8082994824481464395), + lastPropertyId: const IdUid(6, 8746199841403936632), flags: 2, properties: [ ModelProperty( @@ -197,6 +212,11 @@ final _entities = [ id: const IdUid(5, 8082994824481464395), name: 'deleted', type: 1, + flags: 0), + ModelProperty( + id: const IdUid(6, 8746199841403936632), + name: 'source', + type: 9, flags: 0) ], relations: [], @@ -204,7 +224,7 @@ final _entities = [ ModelEntity( id: const IdUid(6, 752131069307970560), name: 'LogEntry', - lastPropertyId: const IdUid(10, 2505303363495348118), + lastPropertyId: const IdUid(11, 8454251731096762477), flags: 2, properties: [ ModelProperty( @@ -241,6 +261,11 @@ final _entities = [ id: const IdUid(10, 2505303363495348118), name: 'glucoseTrend', type: 8, + flags: 0), + ModelProperty( + id: const IdUid(11, 8454251731096762477), + name: 'source', + type: 9, flags: 0) ], relations: [], @@ -248,7 +273,7 @@ final _entities = [ ModelEntity( id: const IdUid(7, 4303325892753185970), name: 'LogEvent', - lastPropertyId: const IdUid(12, 3041952167628926163), + lastPropertyId: const IdUid(13, 9206766629540069341), flags: 2, properties: [ ModelProperty( @@ -306,6 +331,11 @@ final _entities = [ id: const IdUid(12, 3041952167628926163), name: 'reminderDuration', type: 6, + flags: 0), + ModelProperty( + id: const IdUid(13, 9206766629540069341), + name: 'source', + type: 9, flags: 0) ], relations: [], @@ -313,7 +343,7 @@ final _entities = [ ModelEntity( id: const IdUid(8, 8362795406595606110), name: 'LogEventType', - lastPropertyId: const IdUid(8, 1869014400856897151), + lastPropertyId: const IdUid(9, 7377683740652293217), flags: 2, properties: [ ModelProperty( @@ -359,14 +389,19 @@ final _entities = [ type: 11, flags: 520, indexId: const IdUid(28, 4563029809754152081), - relationTarget: 'BasalProfile') + relationTarget: 'BasalProfile'), + ModelProperty( + id: const IdUid(9, 7377683740652293217), + name: 'source', + type: 9, + flags: 0) ], relations: [], backlinks: []), ModelEntity( id: const IdUid(9, 411177866700467286), name: 'LogMeal', - lastPropertyId: const IdUid(19, 8965198821438347033), + lastPropertyId: const IdUid(20, 3296734778930341954), flags: 2, properties: [ ModelProperty( @@ -389,11 +424,6 @@ final _entities = [ name: 'portionSize', type: 8, flags: 0), - ModelProperty( - id: const IdUid(6, 8074052538574863399), - name: 'bolus', - type: 8, - flags: 0), ModelProperty( id: const IdUid(9, 1920579694098037947), name: 'notes', @@ -462,6 +492,11 @@ final _entities = [ id: const IdUid(19, 8965198821438347033), name: 'totalCarbs', type: 8, + flags: 0), + ModelProperty( + id: const IdUid(20, 3296734778930341954), + name: 'source', + type: 9, flags: 0) ], relations: [], @@ -469,7 +504,7 @@ final _entities = [ ModelEntity( id: const IdUid(10, 382130101578692012), name: 'Meal', - lastPropertyId: const IdUid(15, 8283810711091063880), + lastPropertyId: const IdUid(16, 8612850434699732822), flags: 2, properties: [ ModelProperty( @@ -551,6 +586,11 @@ final _entities = [ id: const IdUid(15, 8283810711091063880), name: 'delayedBolusPercentage', type: 8, + flags: 0), + ModelProperty( + id: const IdUid(16, 8612850434699732822), + name: 'source', + type: 9, flags: 0) ], relations: [], @@ -558,7 +598,7 @@ final _entities = [ ModelEntity( id: const IdUid(11, 3158200688796904913), name: 'MealCategory', - lastPropertyId: const IdUid(4, 824435977543069541), + lastPropertyId: const IdUid(5, 2221810999445049020), flags: 2, properties: [ ModelProperty( @@ -580,6 +620,11 @@ final _entities = [ id: const IdUid(4, 824435977543069541), name: 'deleted', type: 1, + flags: 0), + ModelProperty( + id: const IdUid(5, 2221810999445049020), + name: 'source', + type: 9, flags: 0) ], relations: [], @@ -587,7 +632,7 @@ final _entities = [ ModelEntity( id: const IdUid(12, 2111511899235985637), name: 'MealPortionType', - lastPropertyId: const IdUid(4, 5680236937391945907), + lastPropertyId: const IdUid(5, 6807468448392267469), flags: 2, properties: [ ModelProperty( @@ -609,6 +654,11 @@ final _entities = [ id: const IdUid(4, 5680236937391945907), name: 'deleted', type: 1, + flags: 0), + ModelProperty( + id: const IdUid(5, 6807468448392267469), + name: 'source', + type: 9, flags: 0) ], relations: [], @@ -616,7 +666,7 @@ final _entities = [ ModelEntity( id: const IdUid(13, 1283034494527412242), name: 'MealSource', - lastPropertyId: const IdUid(8, 4547899751779962180), + lastPropertyId: const IdUid(9, 6873017370661053783), flags: 2, properties: [ ModelProperty( @@ -666,6 +716,11 @@ final _entities = [ id: const IdUid(8, 4547899751779962180), name: 'deleted', type: 1, + flags: 0), + ModelProperty( + id: const IdUid(9, 6873017370661053783), + name: 'source', + type: 9, flags: 0) ], relations: [], @@ -673,7 +728,7 @@ final _entities = [ ModelEntity( id: const IdUid(14, 8033487006694871160), name: 'LogBolus', - lastPropertyId: const IdUid(18, 7503231998671134983), + lastPropertyId: const IdUid(19, 3683854670538916324), flags: 2, properties: [ ModelProperty( @@ -761,6 +816,11 @@ final _entities = [ id: const IdUid(18, 7503231998671134983), name: 'mmolPerLCorrection', type: 8, + flags: 0), + ModelProperty( + id: const IdUid(19, 3683854670538916324), + name: 'source', + type: 9, flags: 0) ], relations: [], @@ -768,7 +828,7 @@ final _entities = [ ModelEntity( id: const IdUid(15, 291512798403320400), name: 'Accuracy', - lastPropertyId: const IdUid(7, 6675647182186603076), + lastPropertyId: const IdUid(8, 1800866627092978542), flags: 2, properties: [ ModelProperty( @@ -805,6 +865,11 @@ final _entities = [ id: const IdUid(7, 6675647182186603076), name: 'deleted', type: 1, + flags: 0), + ModelProperty( + id: const IdUid(8, 1800866627092978542), + name: 'source', + type: 9, flags: 0) ], relations: [], @@ -812,7 +877,7 @@ final _entities = [ ModelEntity( id: const IdUid(16, 3989341091218179227), name: 'Settings', - lastPropertyId: const IdUid(27, 3553639710779248831), + lastPropertyId: const IdUid(28, 7196743106880728684), flags: 2, properties: [ ModelProperty( @@ -904,6 +969,11 @@ final _entities = [ id: const IdUid(27, 3553639710779248831), name: 'amountIncrements', type: 8, + flags: 0), + ModelProperty( + id: const IdUid(28, 7196743106880728684), + name: 'lastExportTimestamp', + type: 10, flags: 0) ], relations: [], @@ -911,7 +981,7 @@ final _entities = [ ModelEntity( id: const IdUid(17, 5041265995704044399), name: 'GlucoseTarget', - lastPropertyId: const IdUid(7, 1333487551279074696), + lastPropertyId: const IdUid(8, 1091046738846336945), flags: 2, properties: [ ModelProperty( @@ -948,6 +1018,11 @@ final _entities = [ id: const IdUid(7, 1333487551279074696), name: 'color', type: 6, + flags: 0), + ModelProperty( + id: const IdUid(8, 1091046738846336945), + name: 'source', + type: 9, flags: 0) ], relations: [], @@ -1124,7 +1199,8 @@ ModelDefinition getObjectBoxModel() { 4678123663117222609, 780211923138281722, 763575433624979013, - 1225271130099322691 + 1225271130099322691, + 8074052538574863399 ], retiredRelationUids: const [], modelVersion: 5, @@ -1141,13 +1217,16 @@ ModelDefinition getObjectBoxModel() { object.id = id; }, objectToFB: (Basal object, fb.Builder fbb) { - fbb.startTable(7); + final sourceOffset = + object.source == null ? null : fbb.writeString(object.source!); + fbb.startTable(8); fbb.addInt64(0, object.id); fbb.addInt64(1, object.startTime.millisecondsSinceEpoch); fbb.addInt64(2, object.endTime.millisecondsSinceEpoch); fbb.addFloat64(3, object.units); fbb.addInt64(4, object.basalProfile.targetId); fbb.addBool(5, object.deleted); + fbb.addOffset(6, sourceOffset); fbb.finish(fbb.endTable()); return object.id; }, @@ -1163,8 +1242,10 @@ ModelDefinition getObjectBoxModel() { const fb.Int64Reader().vTableGet(buffer, rootOffset, 6, 0)), endTime: DateTime.fromMillisecondsSinceEpoch( const fb.Int64Reader().vTableGet(buffer, rootOffset, 8, 0)), - units: const fb.Float64Reader() - .vTableGet(buffer, rootOffset, 10, 0)); + units: + const fb.Float64Reader().vTableGet(buffer, rootOffset, 10, 0), + source: const fb.StringReader(asciiOptimization: true) + .vTableGetNullable(buffer, rootOffset, 16)); object.basalProfile.targetId = const fb.Int64Reader().vTableGet(buffer, rootOffset, 12, 0); object.basalProfile.attach(store); @@ -1182,12 +1263,15 @@ ModelDefinition getObjectBoxModel() { final nameOffset = fbb.writeString(object.name); final notesOffset = object.notes == null ? null : fbb.writeString(object.notes!); - fbb.startTable(6); + final sourceOffset = + object.source == null ? null : fbb.writeString(object.source!); + fbb.startTable(7); fbb.addInt64(0, object.id); fbb.addOffset(1, nameOffset); fbb.addBool(2, object.active); fbb.addOffset(3, notesOffset); fbb.addBool(4, object.deleted); + fbb.addOffset(5, sourceOffset); fbb.finish(fbb.endTable()); return object.id; }, @@ -1204,7 +1288,9 @@ ModelDefinition getObjectBoxModel() { active: const fb.BoolReader().vTableGet(buffer, rootOffset, 8, false), notes: const fb.StringReader(asciiOptimization: true) - .vTableGetNullable(buffer, rootOffset, 10)); + .vTableGetNullable(buffer, rootOffset, 10), + source: const fb.StringReader(asciiOptimization: true) + .vTableGetNullable(buffer, rootOffset, 14)); return object; }), @@ -1217,7 +1303,9 @@ ModelDefinition getObjectBoxModel() { object.id = id; }, objectToFB: (Bolus object, fb.Builder fbb) { - fbb.startTable(10); + final sourceOffset = + object.source == null ? null : fbb.writeString(object.source!); + fbb.startTable(11); fbb.addInt64(0, object.id); fbb.addInt64(1, object.startTime.millisecondsSinceEpoch); fbb.addInt64(2, object.endTime.millisecondsSinceEpoch); @@ -1227,6 +1315,7 @@ ModelDefinition getObjectBoxModel() { fbb.addFloat64(6, object.mmolPerL); fbb.addInt64(7, object.bolusProfile.targetId); fbb.addBool(8, object.deleted); + fbb.addOffset(9, sourceOffset); fbb.finish(fbb.endTable()); return object.id; }, @@ -1249,7 +1338,9 @@ ModelDefinition getObjectBoxModel() { mgPerDl: const fb.Int64Reader() .vTableGetNullable(buffer, rootOffset, 14), mmolPerL: const fb.Float64Reader() - .vTableGetNullable(buffer, rootOffset, 16)); + .vTableGetNullable(buffer, rootOffset, 16), + source: const fb.StringReader(asciiOptimization: true) + .vTableGetNullable(buffer, rootOffset, 22)); object.bolusProfile.targetId = const fb.Int64Reader().vTableGet(buffer, rootOffset, 18, 0); object.bolusProfile.attach(store); @@ -1267,12 +1358,15 @@ ModelDefinition getObjectBoxModel() { final nameOffset = fbb.writeString(object.name); final notesOffset = object.notes == null ? null : fbb.writeString(object.notes!); - fbb.startTable(6); + final sourceOffset = + object.source == null ? null : fbb.writeString(object.source!); + fbb.startTable(7); fbb.addInt64(0, object.id); fbb.addOffset(1, nameOffset); fbb.addBool(2, object.active); fbb.addOffset(3, notesOffset); fbb.addBool(4, object.deleted); + fbb.addOffset(5, sourceOffset); fbb.finish(fbb.endTable()); return object.id; }, @@ -1289,7 +1383,9 @@ ModelDefinition getObjectBoxModel() { active: const fb.BoolReader().vTableGet(buffer, rootOffset, 8, false), notes: const fb.StringReader(asciiOptimization: true) - .vTableGetNullable(buffer, rootOffset, 10)); + .vTableGetNullable(buffer, rootOffset, 10), + source: const fb.StringReader(asciiOptimization: true) + .vTableGetNullable(buffer, rootOffset, 14)); return object; }), @@ -1304,7 +1400,9 @@ ModelDefinition getObjectBoxModel() { objectToFB: (LogEntry object, fb.Builder fbb) { final notesOffset = object.notes == null ? null : fbb.writeString(object.notes!); - fbb.startTable(11); + final sourceOffset = + object.source == null ? null : fbb.writeString(object.source!); + fbb.startTable(12); fbb.addInt64(0, object.id); fbb.addInt64(1, object.time.millisecondsSinceEpoch); fbb.addInt64(2, object.mgPerDl); @@ -1312,6 +1410,7 @@ ModelDefinition getObjectBoxModel() { fbb.addOffset(7, notesOffset); fbb.addBool(8, object.deleted); fbb.addFloat64(9, object.glucoseTrend); + fbb.addOffset(10, sourceOffset); fbb.finish(fbb.endTable()); return object.id; }, @@ -1332,7 +1431,9 @@ ModelDefinition getObjectBoxModel() { glucoseTrend: const fb.Float64Reader() .vTableGetNullable(buffer, rootOffset, 22), notes: const fb.StringReader(asciiOptimization: true) - .vTableGetNullable(buffer, rootOffset, 18)); + .vTableGetNullable(buffer, rootOffset, 18), + source: const fb.StringReader(asciiOptimization: true) + .vTableGetNullable(buffer, rootOffset, 24)); return object; }), @@ -1348,7 +1449,9 @@ ModelDefinition getObjectBoxModel() { objectToFB: (LogEvent object, fb.Builder fbb) { final notesOffset = object.notes == null ? null : fbb.writeString(object.notes!); - fbb.startTable(13); + final sourceOffset = + object.source == null ? null : fbb.writeString(object.source!); + fbb.startTable(14); fbb.addInt64(0, object.id); fbb.addInt64(1, object.time.millisecondsSinceEpoch); fbb.addInt64(2, object.endTime?.millisecondsSinceEpoch); @@ -1359,6 +1462,7 @@ ModelDefinition getObjectBoxModel() { fbb.addInt64(9, object.bolusProfile.targetId); fbb.addInt64(10, object.basalProfile.targetId); fbb.addInt64(11, object.reminderDuration); + fbb.addOffset(12, sourceOffset); fbb.finish(fbb.endTable()); return object.id; }, @@ -1381,7 +1485,9 @@ ModelDefinition getObjectBoxModel() { reminderDuration: const fb.Int64Reader() .vTableGetNullable(buffer, rootOffset, 26), notes: const fb.StringReader(asciiOptimization: true) - .vTableGetNullable(buffer, rootOffset, 12)); + .vTableGetNullable(buffer, rootOffset, 12), + source: const fb.StringReader(asciiOptimization: true) + .vTableGetNullable(buffer, rootOffset, 28)); object.eventType.targetId = const fb.Int64Reader().vTableGet(buffer, rootOffset, 18, 0); object.eventType.attach(store); @@ -1406,7 +1512,9 @@ ModelDefinition getObjectBoxModel() { final valueOffset = fbb.writeString(object.value); final notesOffset = object.notes == null ? null : fbb.writeString(object.notes!); - fbb.startTable(9); + final sourceOffset = + object.source == null ? null : fbb.writeString(object.source!); + fbb.startTable(10); fbb.addInt64(0, object.id); fbb.addOffset(1, valueOffset); fbb.addBool(2, object.hasEndTime); @@ -1415,6 +1523,7 @@ ModelDefinition getObjectBoxModel() { fbb.addBool(5, object.deleted); fbb.addInt64(6, object.bolusProfile.targetId); fbb.addInt64(7, object.basalProfile.targetId); + fbb.addOffset(8, sourceOffset); fbb.finish(fbb.endTable()); return object.id; }, @@ -1433,7 +1542,9 @@ ModelDefinition getObjectBoxModel() { defaultReminderDuration: const fb.Int64Reader() .vTableGetNullable(buffer, rootOffset, 10), notes: const fb.StringReader(asciiOptimization: true) - .vTableGetNullable(buffer, rootOffset, 12)); + .vTableGetNullable(buffer, rootOffset, 12), + source: const fb.StringReader(asciiOptimization: true) + .vTableGetNullable(buffer, rootOffset, 20)); object.bolusProfile.targetId = const fb.Int64Reader().vTableGet(buffer, rootOffset, 16, 0); object.bolusProfile.attach(store); @@ -1462,12 +1573,13 @@ ModelDefinition getObjectBoxModel() { final valueOffset = fbb.writeString(object.value); final notesOffset = object.notes == null ? null : fbb.writeString(object.notes!); - fbb.startTable(20); + final sourceOffset = + object.source == null ? null : fbb.writeString(object.source!); + fbb.startTable(21); fbb.addInt64(0, object.id); fbb.addOffset(1, valueOffset); fbb.addFloat64(2, object.carbsRatio); fbb.addFloat64(3, object.portionSize); - fbb.addFloat64(5, object.bolus); fbb.addOffset(8, notesOffset); fbb.addInt64(9, object.logEntry.targetId); fbb.addInt64(10, object.meal.targetId); @@ -1479,6 +1591,7 @@ ModelDefinition getObjectBoxModel() { fbb.addBool(16, object.deleted); fbb.addFloat64(17, object.amount); fbb.addFloat64(18, object.totalCarbs); + fbb.addOffset(19, sourceOffset); fbb.finish(fbb.endTable()); return object.id; }, @@ -1501,9 +1614,9 @@ ModelDefinition getObjectBoxModel() { totalCarbs: const fb.Float64Reader() .vTableGetNullable(buffer, rootOffset, 40), notes: const fb.StringReader(asciiOptimization: true) - .vTableGetNullable(buffer, rootOffset, 20)) - ..bolus = const fb.Float64Reader() - .vTableGetNullable(buffer, rootOffset, 14); + .vTableGetNullable(buffer, rootOffset, 20), + source: const fb.StringReader(asciiOptimization: true) + .vTableGetNullable(buffer, rootOffset, 42)); object.logEntry.targetId = const fb.Int64Reader().vTableGet(buffer, rootOffset, 22, 0); object.logEntry.attach(store); @@ -1545,7 +1658,9 @@ ModelDefinition getObjectBoxModel() { final valueOffset = fbb.writeString(object.value); final notesOffset = object.notes == null ? null : fbb.writeString(object.notes!); - fbb.startTable(16); + final sourceOffset = + object.source == null ? null : fbb.writeString(object.source!); + fbb.startTable(17); fbb.addInt64(0, object.id); fbb.addOffset(1, valueOffset); fbb.addFloat64(2, object.carbsRatio); @@ -1560,6 +1675,7 @@ ModelDefinition getObjectBoxModel() { fbb.addInt64(12, object.carbsRatioAccuracy.targetId); fbb.addBool(13, object.deleted); fbb.addFloat64(14, object.delayedBolusPercentage); + fbb.addOffset(15, sourceOffset); fbb.finish(fbb.endTable()); return object.id; }, @@ -1584,7 +1700,9 @@ ModelDefinition getObjectBoxModel() { delayedBolusPercentage: const fb.Float64Reader() .vTableGetNullable(buffer, rootOffset, 32), notes: const fb.StringReader(asciiOptimization: true) - .vTableGetNullable(buffer, rootOffset, 18)); + .vTableGetNullable(buffer, rootOffset, 18), + source: const fb.StringReader(asciiOptimization: true) + .vTableGetNullable(buffer, rootOffset, 34)); object.mealSource.targetId = const fb.Int64Reader().vTableGet(buffer, rootOffset, 20, 0); object.mealSource.attach(store); @@ -1614,11 +1732,14 @@ ModelDefinition getObjectBoxModel() { final valueOffset = fbb.writeString(object.value); final notesOffset = object.notes == null ? null : fbb.writeString(object.notes!); - fbb.startTable(5); + final sourceOffset = + object.source == null ? null : fbb.writeString(object.source!); + fbb.startTable(6); fbb.addInt64(0, object.id); fbb.addOffset(1, valueOffset); fbb.addOffset(2, notesOffset); fbb.addBool(3, object.deleted); + fbb.addOffset(4, sourceOffset); fbb.finish(fbb.endTable()); return object.id; }, @@ -1633,7 +1754,9 @@ ModelDefinition getObjectBoxModel() { value: const fb.StringReader(asciiOptimization: true) .vTableGet(buffer, rootOffset, 6, ''), notes: const fb.StringReader(asciiOptimization: true) - .vTableGetNullable(buffer, rootOffset, 8)); + .vTableGetNullable(buffer, rootOffset, 8), + source: const fb.StringReader(asciiOptimization: true) + .vTableGetNullable(buffer, rootOffset, 12)); return object; }), @@ -1649,11 +1772,14 @@ ModelDefinition getObjectBoxModel() { final valueOffset = fbb.writeString(object.value); final notesOffset = object.notes == null ? null : fbb.writeString(object.notes!); - fbb.startTable(5); + final sourceOffset = + object.source == null ? null : fbb.writeString(object.source!); + fbb.startTable(6); fbb.addInt64(0, object.id); fbb.addOffset(1, valueOffset); fbb.addOffset(2, notesOffset); fbb.addBool(3, object.deleted); + fbb.addOffset(4, sourceOffset); fbb.finish(fbb.endTable()); return object.id; }, @@ -1668,7 +1794,9 @@ ModelDefinition getObjectBoxModel() { value: const fb.StringReader(asciiOptimization: true) .vTableGet(buffer, rootOffset, 6, ''), notes: const fb.StringReader(asciiOptimization: true) - .vTableGetNullable(buffer, rootOffset, 8)); + .vTableGetNullable(buffer, rootOffset, 8), + source: const fb.StringReader(asciiOptimization: true) + .vTableGetNullable(buffer, rootOffset, 12)); return object; }), @@ -1689,7 +1817,9 @@ ModelDefinition getObjectBoxModel() { final valueOffset = fbb.writeString(object.value); final notesOffset = object.notes == null ? null : fbb.writeString(object.notes!); - fbb.startTable(9); + final sourceOffset = + object.source == null ? null : fbb.writeString(object.source!); + fbb.startTable(10); fbb.addInt64(0, object.id); fbb.addOffset(1, valueOffset); fbb.addOffset(2, notesOffset); @@ -1698,6 +1828,7 @@ ModelDefinition getObjectBoxModel() { fbb.addInt64(5, object.defaultCarbsRatioAccuracy.targetId); fbb.addInt64(6, object.defaultPortionSizeAccuracy.targetId); fbb.addBool(7, object.deleted); + fbb.addOffset(8, sourceOffset); fbb.finish(fbb.endTable()); return object.id; }, @@ -1712,7 +1843,9 @@ ModelDefinition getObjectBoxModel() { value: const fb.StringReader(asciiOptimization: true) .vTableGet(buffer, rootOffset, 6, ''), notes: const fb.StringReader(asciiOptimization: true) - .vTableGetNullable(buffer, rootOffset, 8)); + .vTableGetNullable(buffer, rootOffset, 8), + source: const fb.StringReader(asciiOptimization: true) + .vTableGetNullable(buffer, rootOffset, 20)); object.defaultMealCategory.targetId = const fb.Int64Reader().vTableGet(buffer, rootOffset, 10, 0); object.defaultMealCategory.attach(store); @@ -1739,7 +1872,9 @@ ModelDefinition getObjectBoxModel() { objectToFB: (LogBolus object, fb.Builder fbb) { final notesOffset = object.notes == null ? null : fbb.writeString(object.notes!); - fbb.startTable(19); + final sourceOffset = + object.source == null ? null : fbb.writeString(object.source!); + fbb.startTable(20); fbb.addInt64(0, object.id); fbb.addFloat64(1, object.units); fbb.addFloat64(2, object.carbs); @@ -1756,6 +1891,7 @@ ModelDefinition getObjectBoxModel() { fbb.addFloat64(15, object.mmolPerLCurrent); fbb.addFloat64(16, object.mmolPerLTarget); fbb.addFloat64(17, object.mmolPerLCorrection); + fbb.addOffset(18, sourceOffset); fbb.finish(fbb.endTable()); return object.id; }, @@ -1788,7 +1924,9 @@ ModelDefinition getObjectBoxModel() { setManually: const fb.BoolReader() .vTableGet(buffer, rootOffset, 16, false), notes: const fb.StringReader(asciiOptimization: true) - .vTableGetNullable(buffer, rootOffset, 18)); + .vTableGetNullable(buffer, rootOffset, 18), + source: + const fb.StringReader(asciiOptimization: true).vTableGetNullable(buffer, rootOffset, 40)); object.logEntry.targetId = const fb.Int64Reader().vTableGet(buffer, rootOffset, 20, 0); object.logEntry.attach(store); @@ -1812,7 +1950,9 @@ ModelDefinition getObjectBoxModel() { final valueOffset = fbb.writeString(object.value); final notesOffset = object.notes == null ? null : fbb.writeString(object.notes!); - fbb.startTable(8); + final sourceOffset = + object.source == null ? null : fbb.writeString(object.source!); + fbb.startTable(9); fbb.addInt64(0, object.id); fbb.addOffset(1, valueOffset); fbb.addBool(2, object.forCarbsRatio); @@ -1820,6 +1960,7 @@ ModelDefinition getObjectBoxModel() { fbb.addInt64(4, object.confidenceRating); fbb.addOffset(5, notesOffset); fbb.addBool(6, object.deleted); + fbb.addOffset(7, sourceOffset); fbb.finish(fbb.endTable()); return object.id; }, @@ -1840,7 +1981,9 @@ ModelDefinition getObjectBoxModel() { confidenceRating: const fb.Int64Reader() .vTableGetNullable(buffer, rootOffset, 12), notes: const fb.StringReader(asciiOptimization: true) - .vTableGetNullable(buffer, rootOffset, 14)); + .vTableGetNullable(buffer, rootOffset, 14), + source: const fb.StringReader(asciiOptimization: true) + .vTableGetNullable(buffer, rootOffset, 18)); return object; }), @@ -1861,7 +2004,7 @@ ModelDefinition getObjectBoxModel() { final longTimeFormatOffset = object.longTimeFormat == null ? null : fbb.writeString(object.longTimeFormat!); - fbb.startTable(28); + fbb.startTable(29); fbb.addInt64(0, object.id); fbb.addOffset(1, dateFormatOffset); fbb.addOffset(2, longDateFormatOffset); @@ -1880,13 +2023,15 @@ ModelDefinition getObjectBoxModel() { fbb.addFloat64(24, object.nutritionIncrements); fbb.addFloat64(25, object.mmolPerLIncrements); fbb.addFloat64(26, object.amountIncrements); + fbb.addInt64(27, object.lastExportTimestamp?.millisecondsSinceEpoch); fbb.finish(fbb.endTable()); return object.id; }, objectFromFB: (Store store, ByteData fbData) { final buffer = fb.BufferContext(fbData); final rootOffset = buffer.derefObject(0); - + final lastExportTimestampValue = + const fb.Int64Reader().vTableGetNullable(buffer, rootOffset, 58); final object = Settings( id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0), nutritionMeasurementIndex: @@ -1916,7 +2061,8 @@ ModelDefinition getObjectBoxModel() { showConfirmationDialogOnStopEvent: const fb.BoolReader().vTableGet(buffer, rootOffset, 18, false), targetGlucoseMgPerDl: const fb.Int64Reader().vTableGet(buffer, rootOffset, 44, 0), targetGlucoseMmolPerL: const fb.Float64Reader().vTableGet(buffer, rootOffset, 46, 0), - useDarkTheme: const fb.BoolReader().vTableGet(buffer, rootOffset, 48, false)); + useDarkTheme: const fb.BoolReader().vTableGet(buffer, rootOffset, 48, false), + lastExportTimestamp: lastExportTimestampValue == null ? null : DateTime.fromMillisecondsSinceEpoch(lastExportTimestampValue)); return object; }), @@ -1929,7 +2075,9 @@ ModelDefinition getObjectBoxModel() { object.id = id; }, objectToFB: (GlucoseTarget object, fb.Builder fbb) { - fbb.startTable(8); + final sourceOffset = + object.source == null ? null : fbb.writeString(object.source!); + fbb.startTable(9); fbb.addInt64(0, object.id); fbb.addBool(1, object.deleted); fbb.addInt64(2, object.fromMgPerDL); @@ -1937,6 +2085,7 @@ ModelDefinition getObjectBoxModel() { fbb.addFloat64(4, object.fromMmolPerL); fbb.addFloat64(5, object.toMmolPerL); fbb.addInt64(6, object.color); + fbb.addOffset(7, sourceOffset); fbb.finish(fbb.endTable()); return object.id; }, @@ -1957,7 +2106,9 @@ ModelDefinition getObjectBoxModel() { toMmolPerL: const fb.Float64Reader().vTableGet(buffer, rootOffset, 14, 0), color: - const fb.Int64Reader().vTableGet(buffer, rootOffset, 16, 0)); + const fb.Int64Reader().vTableGet(buffer, rootOffset, 16, 0), + source: const fb.StringReader(asciiOptimization: true) + .vTableGetNullable(buffer, rootOffset, 18)); return object; }), @@ -2105,6 +2256,9 @@ class Basal_ { /// see [Basal.deleted] static final deleted = QueryBooleanProperty(_entities[0].properties[5]); + + /// see [Basal.source] + static final source = QueryStringProperty(_entities[0].properties[6]); } /// [BasalProfile] entity fields to define ObjectBox queries. @@ -2128,6 +2282,10 @@ class BasalProfile_ { /// see [BasalProfile.deleted] static final deleted = QueryBooleanProperty(_entities[1].properties[4]); + + /// see [BasalProfile.source] + static final source = + QueryStringProperty(_entities[1].properties[5]); } /// [Bolus] entity fields to define ObjectBox queries. @@ -2164,6 +2322,9 @@ class Bolus_ { /// see [Bolus.deleted] static final deleted = QueryBooleanProperty(_entities[2].properties[8]); + + /// see [Bolus.source] + static final source = QueryStringProperty(_entities[2].properties[9]); } /// [BolusProfile] entity fields to define ObjectBox queries. @@ -2187,6 +2348,10 @@ class BolusProfile_ { /// see [BolusProfile.deleted] static final deleted = QueryBooleanProperty(_entities[3].properties[4]); + + /// see [BolusProfile.source] + static final source = + QueryStringProperty(_entities[3].properties[5]); } /// [LogEntry] entity fields to define ObjectBox queries. @@ -2217,6 +2382,10 @@ class LogEntry_ { /// see [LogEntry.glucoseTrend] static final glucoseTrend = QueryDoubleProperty(_entities[4].properties[6]); + + /// see [LogEntry.source] + static final source = + QueryStringProperty(_entities[4].properties[7]); } /// [LogEvent] entity fields to define ObjectBox queries. @@ -2259,6 +2428,10 @@ class LogEvent_ { /// see [LogEvent.reminderDuration] static final reminderDuration = QueryIntegerProperty(_entities[5].properties[9]); + + /// see [LogEvent.source] + static final source = + QueryStringProperty(_entities[5].properties[10]); } /// [LogEventType] entity fields to define ObjectBox queries. @@ -2294,6 +2467,10 @@ class LogEventType_ { /// see [LogEventType.basalProfile] static final basalProfile = QueryRelationToOne( _entities[6].properties[7]); + + /// see [LogEventType.source] + static final source = + QueryStringProperty(_entities[6].properties[8]); } /// [LogMeal] entity fields to define ObjectBox queries. @@ -2312,51 +2489,52 @@ class LogMeal_ { static final portionSize = QueryDoubleProperty(_entities[7].properties[3]); - /// see [LogMeal.bolus] - static final bolus = QueryDoubleProperty(_entities[7].properties[4]); - /// see [LogMeal.notes] - static final notes = QueryStringProperty(_entities[7].properties[5]); + static final notes = QueryStringProperty(_entities[7].properties[4]); /// see [LogMeal.logEntry] static final logEntry = - QueryRelationToOne(_entities[7].properties[6]); + QueryRelationToOne(_entities[7].properties[5]); /// see [LogMeal.meal] static final meal = - QueryRelationToOne(_entities[7].properties[7]); + QueryRelationToOne(_entities[7].properties[6]); /// see [LogMeal.mealSource] static final mealSource = - QueryRelationToOne(_entities[7].properties[8]); + QueryRelationToOne(_entities[7].properties[7]); /// see [LogMeal.mealCategory] static final mealCategory = - QueryRelationToOne(_entities[7].properties[9]); + QueryRelationToOne(_entities[7].properties[8]); /// see [LogMeal.mealPortionType] static final mealPortionType = - QueryRelationToOne(_entities[7].properties[10]); + QueryRelationToOne(_entities[7].properties[9]); /// see [LogMeal.portionSizeAccuracy] static final portionSizeAccuracy = - QueryRelationToOne(_entities[7].properties[11]); + QueryRelationToOne(_entities[7].properties[10]); /// see [LogMeal.carbsRatioAccuracy] static final carbsRatioAccuracy = - QueryRelationToOne(_entities[7].properties[12]); + QueryRelationToOne(_entities[7].properties[11]); /// see [LogMeal.deleted] static final deleted = - QueryBooleanProperty(_entities[7].properties[13]); + QueryBooleanProperty(_entities[7].properties[12]); /// see [LogMeal.amount] static final amount = - QueryDoubleProperty(_entities[7].properties[14]); + QueryDoubleProperty(_entities[7].properties[13]); /// see [LogMeal.totalCarbs] static final totalCarbs = - QueryDoubleProperty(_entities[7].properties[15]); + QueryDoubleProperty(_entities[7].properties[14]); + + /// see [LogMeal.source] + static final source = + QueryStringProperty(_entities[7].properties[15]); } /// [Meal] entity fields to define ObjectBox queries. @@ -2413,6 +2591,9 @@ class Meal_ { /// see [Meal.delayedBolusPercentage] static final delayedBolusPercentage = QueryDoubleProperty(_entities[8].properties[13]); + + /// see [Meal.source] + static final source = QueryStringProperty(_entities[8].properties[14]); } /// [MealCategory] entity fields to define ObjectBox queries. @@ -2432,6 +2613,10 @@ class MealCategory_ { /// see [MealCategory.deleted] static final deleted = QueryBooleanProperty(_entities[9].properties[3]); + + /// see [MealCategory.source] + static final source = + QueryStringProperty(_entities[9].properties[4]); } /// [MealPortionType] entity fields to define ObjectBox queries. @@ -2451,6 +2636,10 @@ class MealPortionType_ { /// see [MealPortionType.deleted] static final deleted = QueryBooleanProperty(_entities[10].properties[3]); + + /// see [MealPortionType.source] + static final source = + QueryStringProperty(_entities[10].properties[4]); } /// [MealSource] entity fields to define ObjectBox queries. @@ -2487,6 +2676,10 @@ class MealSource_ { /// see [MealSource.deleted] static final deleted = QueryBooleanProperty(_entities[11].properties[7]); + + /// see [MealSource.source] + static final source = + QueryStringProperty(_entities[11].properties[8]); } /// [LogBolus] entity fields to define ObjectBox queries. @@ -2553,6 +2746,10 @@ class LogBolus_ { /// see [LogBolus.mmolPerLCorrection] static final mmolPerLCorrection = QueryDoubleProperty(_entities[12].properties[15]); + + /// see [LogBolus.source] + static final source = + QueryStringProperty(_entities[12].properties[16]); } /// [Accuracy] entity fields to define ObjectBox queries. @@ -2583,6 +2780,10 @@ class Accuracy_ { /// see [Accuracy.deleted] static final deleted = QueryBooleanProperty(_entities[13].properties[6]); + + /// see [Accuracy.source] + static final source = + QueryStringProperty(_entities[13].properties[7]); } /// [Settings] entity fields to define ObjectBox queries. @@ -2657,6 +2858,10 @@ class Settings_ { /// see [Settings.amountIncrements] static final amountIncrements = QueryDoubleProperty(_entities[14].properties[17]); + + /// see [Settings.lastExportTimestamp] + static final lastExportTimestamp = + QueryIntegerProperty(_entities[14].properties[18]); } /// [GlucoseTarget] entity fields to define ObjectBox queries. @@ -2688,6 +2893,10 @@ class GlucoseTarget_ { /// see [GlucoseTarget.color] static final color = QueryIntegerProperty(_entities[15].properties[6]); + + /// see [GlucoseTarget.source] + static final source = + QueryStringProperty(_entities[15].properties[7]); } /// [Recipe] entity fields to define ObjectBox queries. diff --git a/lib/screens/basal/basal_detail.dart b/lib/screens/basal/basal_detail.dart index 0903211..86b6fad 100644 --- a/lib/screens/basal/basal_detail.dart +++ b/lib/screens/basal/basal_detail.dart @@ -1,6 +1,7 @@ import 'package:diameter/components/detail.dart'; import 'package:diameter/components/forms/number_form_field.dart'; import 'package:diameter/components/forms/time_of_day_form_field.dart'; +import 'package:diameter/localization_keys.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; @@ -10,6 +11,7 @@ import 'package:flutter/material.dart'; import 'package:diameter/components/forms/form_wrapper.dart'; import 'package:diameter/models/basal.dart'; import 'package:diameter/models/basal_profile.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class BasalDetailScreen extends StatefulWidget { static const String routeName = '/basal'; @@ -130,7 +132,7 @@ class _BasalDetailScreenState extends State { _startTime.hour == other.startTime.hour && _startTime.minute == other.startTime.minute) .isNotEmpty) { - error = 'There\'s already a rate with this start time.'; + error = translate(LocalizationKeys.basal_warnings_duplicate); } if (basalRates @@ -141,7 +143,7 @@ class _BasalDetailScreenState extends State { DateTimeUtils.convertTimeOfDayToDateTime(_endTime) .isAfter(other.startTime)) .isNotEmpty) { - error = 'This rate\'s time period overlaps with another one.'; + error = translate(LocalizationKeys.basal_warnings_overlap); } return error == null @@ -154,11 +156,11 @@ class _BasalDetailScreenState extends State { actions: [ TextButton( onPressed: () => Navigator.pop(context, 'CANCEL'), - child: const Text('GO BACK TO EDITING'), + child: Text(translate(LocalizationKeys.general_keepEditing).toUpperCase()), ), ElevatedButton( onPressed: () => Navigator.pop(context, 'CONFIRM'), - child: const Text('SAVE AS IS'), + child: Text(translate(LocalizationKeys.general_saveAsIs).toUpperCase()), ), ], ); @@ -196,13 +198,26 @@ class _BasalDetailScreenState extends State { ).then((result) { Navigator.pop( context, - ['New Basal Rate${result[1] != null ? 's' : ''} saved', basal] + - [result[1]], + [ + translatePlural( + LocalizationKeys.basal_saved, + result.length, + args: { + "status": _isNew ? '${LocalizationKeys.basal_new} ' : '' + }, + ), + basal] + [result[1]], ); }); } else { Navigator.pop( - context, ['${_isNew ? 'New' : ''} Basal Rate saved', basal]); + context, [translatePlural( + LocalizationKeys.basal_saved, + 1, + args: { + "status": _isNew ? '${LocalizationKeys.basal_new} ' : '' + }, + ), basal]); } } }); @@ -241,7 +256,14 @@ class _BasalDetailScreenState extends State { return Scaffold( appBar: AppBar( title: Text( - '${_isNew ? 'New' : 'Edit'} Basal Rate for ${BasalProfile.get(widget.basalProfileId)?.name}'), + translate( + LocalizationKeys.basal_title, + args: { + "status": _isNew ? LocalizationKeys.basal_new : LocalizationKeys.general_edit, + "profileName": BasalProfile.get(widget.basalProfileId)?.name, + } + ), + ), ), drawer: const Navigation(currentLocation: BasalDetailScreen.routeName), body: Scrollbar( @@ -259,7 +281,7 @@ class _BasalDetailScreenState extends State { child: Padding( padding: const EdgeInsets.only(right: 5), child: TimeOfDayFormField( - label: 'Start Time', + label: translate(LocalizationKeys.basal_fields_startTime), controller: _startTimeController, time: _startTime, onChanged: updateStartTime, @@ -270,7 +292,7 @@ class _BasalDetailScreenState extends State { child: Padding( padding: const EdgeInsets.only(left: 5), child: TimeOfDayFormField( - label: 'End Time', + label: translate(LocalizationKeys.basal_fields_endTime), controller: _endTimeController, time: _endTime, onChanged: updateEndTime, @@ -281,8 +303,8 @@ class _BasalDetailScreenState extends State { ), NumberFormField( controller: _unitsController, - label: 'Units', - suffix: 'U', + label: translate(LocalizationKeys.basal_fields_units), + suffix: translate(LocalizationKeys.general_suffixes_units), autoRoundToMultipleOfStep: true, step: Settings.insulinSteps, onChanged: (value) { @@ -291,7 +313,7 @@ class _BasalDetailScreenState extends State { Utils.toStringMatchingTemplateFractionPrecision( value, Settings.insulinSteps); } - }), + }) ], ), ], @@ -305,8 +327,8 @@ class _BasalDetailScreenState extends State { onMiddleAction: _isSaving || _isFinalRate ? null : () => handleSaveAction(next: false), - actionText: _isFinalRate ? 'SAVE & CLOSE' : 'NEXT', - middleActionText: 'SAVE & CLOSE', + actionTextKey: translate(_isFinalRate ? LocalizationKeys.general_saveAndClose : LocalizationKeys.general_next).toUpperCase(), + middleActionTextKey: translate(LocalizationKeys.general_saveAndClose).toUpperCase(), ), ); } diff --git a/lib/screens/basal/basal_list.dart b/lib/screens/basal/basal_list.dart index 7685693..e820073 100644 --- a/lib/screens/basal/basal_list.dart +++ b/lib/screens/basal/basal_list.dart @@ -1,3 +1,4 @@ +import 'package:diameter/localization_keys.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/models/settings.dart'; import 'package:diameter/utils/date_time_utils.dart'; @@ -5,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:diameter/models/basal.dart'; import 'package:diameter/models/basal_profile.dart'; import 'package:diameter/screens/basal/basal_detail.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class BasalListScreen extends StatefulWidget { final BasalProfile basalProfile; @@ -61,7 +63,7 @@ class _BasalListScreenState extends State { void onDelete(Basal basal) { Basal.remove(basal.id); - reload(message: 'Basal Rate deleted'); + reload(message: translate(LocalizationKeys.basal_deleted)); } void handleDeleteAction(Basal basal) async { @@ -69,7 +71,7 @@ class _BasalListScreenState extends State { DialogUtils.showConfirmationDialog( context: context, onConfirm: () => onDelete(basal), - message: 'Are you sure you want to delete this Basal Rate?', + message: translate(LocalizationKeys.basal_confirmDelete), ); } else { onDelete(basal); @@ -83,26 +85,26 @@ class _BasalListScreenState extends State { // check for gaps if (index == 0 && (basal.startTime.hour != 0 || basal.startTime.minute != 0)) { - return 'First Basal of the day needs to start at 00:00'; + return translate(LocalizationKeys.basal_warnings_startTimeFirst); } if (index > 0) { var lastEndTime = basalRates[index - 1].endTime; if (basal.startTime.isAfter(lastEndTime)) { - return 'There\'s a time gap between this and the previous rate'; + return translate(LocalizationKeys.basal_warnings_gap); } } if (index == basalRates.length - 1 && (basal.endTime.hour != 0 || basal.endTime.minute != 0)) { - return 'Last Basal of the day needs to end at 00:00'; + return translate(LocalizationKeys.basal_warnings_endTimeLast); } // check for duplicates if (basalRates .where((other) => basal != other && basal.startTime == other.startTime) .isNotEmpty) { - return 'There are multiple rates with this start time'; + return translate(LocalizationKeys.basal_warnings_duplicate); } if (basalRates @@ -110,7 +112,7 @@ class _BasalListScreenState extends State { basal.startTime.isBefore(other.startTime) && basal.endTime.isAfter(other.startTime)) .isNotEmpty) { - return 'This rate\'s time period overlaps with another one'; + return translate(LocalizationKeys.basal_warnings_overlap); } return null; @@ -158,7 +160,7 @@ class _BasalListScreenState extends State { child: Text( '${DateTimeUtils.displayTime(basal.startTime)} - ${DateTimeUtils.displayTime(basal.endTime)}')), const Spacer(), - Expanded(child: Text('${basal.units} U')), + Expanded(child: Text('${basal.units} ${translate(LocalizationKeys.general_suffixes_units)}')), ], ), trailing: Row( @@ -180,8 +182,8 @@ class _BasalListScreenState extends State { }, ), ) - : const Center( - child: Text('You have not created any Basal Rates yet!'), + : Center( + child: Text(translate(LocalizationKeys.basal_empty)), ); } } diff --git a/lib/screens/basal/basal_profile_detail.dart b/lib/screens/basal/basal_profile_detail.dart index d558376..8c67390 100644 --- a/lib/screens/basal/basal_profile_detail.dart +++ b/lib/screens/basal/basal_profile_detail.dart @@ -1,5 +1,6 @@ import 'package:diameter/components/detail.dart'; import 'package:diameter/components/forms/boolean_form_field.dart'; +import 'package:diameter/localization_keys.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/models/basal.dart'; import 'package:diameter/models/settings.dart'; @@ -9,6 +10,7 @@ import 'package:flutter/material.dart'; import 'package:diameter/components/forms/form_wrapper.dart'; import 'package:diameter/models/basal_profile.dart'; import 'package:diameter/screens/basal/basal_list.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class BasalProfileDetailScreen extends StatefulWidget { static const String routeName = '/basal-profile'; @@ -133,21 +135,30 @@ class _BasalProfileDetailScreenState extends State { context: context, builder: (BuildContext context) { return AlertDialog( - content: const Text( - 'There are already one or more active profiles. What would you like to do?'), + content: Text(translate(LocalizationKeys.basalProfile_warnings_activeAlreadySet)), actions: [ TextButton( onPressed: () => Navigator.pop(context, 0), - child: const Text('IGNORE'), + child: Text(translate(LocalizationKeys.basalProfile_warnings_resolve_ignore).toUpperCase()), ), TextButton( onPressed: () => Navigator.pop(context, 1), child: - Text('DEACTIVATE ${_nameController.text.toUpperCase()}'), + Text( + translate( + LocalizationKeys.basalProfile_warnings_resolve_deactivateProfile, + args: { + "profileName": _nameController.text, + } + ).toUpperCase() + ), ), ElevatedButton( onPressed: () => Navigator.pop(context, 2), - child: const Text('DEACTIVATE ALL OTHERS'), + child: Text( + translate( + LocalizationKeys.basalProfile_warnings_resolve_deactivateOthers, + ).toUpperCase()), ) ], ); @@ -167,16 +178,25 @@ class _BasalProfileDetailScreenState extends State { context: context, builder: (BuildContext context) { return AlertDialog( - content: const Text( - 'There is currently no active profile. Would you like to set this one as active?'), + content: Text( + translate( + LocalizationKeys.basalProfile_warnings_noActiveOnCreate, + ).toUpperCase()), actions: [ TextButton( onPressed: () => Navigator.pop(context, 0), - child: const Text('IGNORE'), + child: Text( + translate( + LocalizationKeys.basalProfile_warnings_resolve_ignore, + ).toUpperCase()), ), TextButton( onPressed: () => Navigator.pop(context, 1), - child: const Text('ACTIVATE THIS PROFILE'), + child: Text( + translate( + LocalizationKeys.basalProfile_warnings_resolve_activateCurrent, + ).toUpperCase() + ), ), ], ); @@ -249,7 +269,11 @@ class _BasalProfileDetailScreenState extends State { if (close) { Navigator.pop(context, - ['${_isNew ? 'New' : ''} Basal Profile saved', basalProfile]); + [ + translate(LocalizationKeys.basalProfile_saved, args: { + "status": _isNew ? '${translate(LocalizationKeys.basalProfile_new)} ' : '' + }), + ]); } else { if (_isNew) { Navigator.push( @@ -260,7 +284,7 @@ class _BasalProfileDetailScreenState extends State { ), ).then((result) => Navigator.pop(context, result)); } else { - reload(message: 'Basal Profile saved'); + reload(message: translate(LocalizationKeys.basalProfile_saved)); } } } @@ -329,12 +353,12 @@ class _BasalProfileDetailScreenState extends State { fields: [ TextFormField( controller: _nameController, - decoration: const InputDecoration( - labelText: 'Name', + decoration: InputDecoration( + labelText: translate(LocalizationKeys.basalProfile_fields_name), ), validator: (value) { if (value!.trim().isEmpty) { - return 'Empty title'; + return translate(LocalizationKeys.basalProfile_fields_validators_name); } return null; }, @@ -342,8 +366,8 @@ class _BasalProfileDetailScreenState extends State { TextFormField( keyboardType: TextInputType.multiline, controller: _notesController, - decoration: const InputDecoration( - labelText: 'Notes', + decoration: InputDecoration( + labelText: translate(LocalizationKeys.basalProfile_fields_notes), ), minLines: 2, maxLines: 5, @@ -355,7 +379,7 @@ class _BasalProfileDetailScreenState extends State { _active = value; }); }, - label: 'active', + label: translate(LocalizationKeys.basalProfile_fields_active), ), ], ), @@ -374,13 +398,24 @@ class _BasalProfileDetailScreenState extends State { return Scaffold( appBar: AppBar( - title: Text(_isNew ? 'New Basal Profile' : _basalProfile!.name), + title: Text(translate(LocalizationKeys.basalProfile_detail_title, args: { + "status": _isNew ? '${translate(LocalizationKeys.basalProfile_new)} ' : '', + "profileName": _basalProfile!.name, + })), bottom: _isNew ? PreferredSize(child: Container(), preferredSize: Size.zero) - : const TabBar( + : TabBar( tabs: [ - Tab(text: 'PROFILE'), - Tab(text: 'RATES'), + Tab( + text: translate( + LocalizationKeys.basalProfile_detail_tabs_profile, + ).toUpperCase(), + ), + Tab( + text: translate( + LocalizationKeys.basalProfile_detail_tabs_rates, + ).toUpperCase(), + ), ], ), actions: appBarActions, diff --git a/lib/screens/basal/basal_profile_list.dart b/lib/screens/basal/basal_profile_list.dart index cfa561f..6e1fe3e 100644 --- a/lib/screens/basal/basal_profile_list.dart +++ b/lib/screens/basal/basal_profile_list.dart @@ -1,3 +1,4 @@ +import 'package:diameter/localization_keys.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/components/forms/auto_complete_dropdown_button.dart'; import 'package:diameter/models/basal.dart'; @@ -6,6 +7,7 @@ import 'package:diameter/navigation.dart'; import 'package:flutter/material.dart'; import 'package:diameter/models/basal_profile.dart'; import 'package:diameter/screens/basal/basal_profile_detail.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class BasalProfileListScreen extends StatefulWidget { static const String routeName = '/basal-profiles'; @@ -59,26 +61,33 @@ class _BasalProfileListScreenState extends State { banner = activeProfileCount != 1 ? MaterialBanner( content: Text(activeProfileCount == 0 - ? 'You currently do not have an active Basal Profile.' - : 'More than one active Basal Profile has been found.'), + ? translate(LocalizationKeys.basalProfile_warnings_noActive) + : translate( + LocalizationKeys.basalProfile_warnings_multipleActive)), leading: const CircleAvatar(child: Icon(Icons.warning)), forceActionsBelow: true, actions: activeProfileCount == 0 ? [ _basalProfiles.isNotEmpty ? TextButton( - child: const Text('ACTIVATE A PROFILE'), + child: Text(translate(LocalizationKeys + .basalProfile_warnings_resolve_activate) + .toUpperCase()), onPressed: handlePickActiveProfileAction, ) : Container(), TextButton( - child: const Text('CREATE A NEW PROFILE'), + child: Text(translate(LocalizationKeys + .basalProfile_warnings_resolve_create) + .toUpperCase()), onPressed: () => onNew(true), ), ] : [ TextButton( - child: const Text('PICK A PROFILE'), + child: Text(translate(LocalizationKeys + .basalProfile_warnings_resolve_pick) + .toUpperCase()), onPressed: handlePickActiveProfileAction, ), ], @@ -89,9 +98,10 @@ class _BasalProfileListScreenState extends State { void handleDuplicateAction(BasalProfile basalProfile) async { final copy = BasalProfile( - active: false, - name: 'Copy of ${basalProfile.name}', - ); + active: false, + name: translate(LocalizationKeys.basalProfile_copyOf, args: { + "profileName": basalProfile.name, + })); BasalProfile.put(copy); final rates = Basal.getAllForProfile(basalProfile.id); @@ -105,12 +115,19 @@ class _BasalProfileListScreenState extends State { Basal.put(basal); } - reload(message: 'Added copy of ${basalProfile.name}'); + reload( + message: translate( + LocalizationKeys.basalProfile_warnings_resolve_ignore, + args: {"profileName": basalProfile.name}, + )); } void onDelete(BasalProfile basalProfile) { BasalProfile.remove(basalProfile.id); - reload(message: 'Basal Profile deleted'); + reload( + message: translate( + LocalizationKeys.basalProfile_deleted, + )); } void handleDeleteAction(BasalProfile basalProfile) async { @@ -118,7 +135,7 @@ class _BasalProfileListScreenState extends State { DialogUtils.showConfirmationDialog( context: context, onConfirm: () => onDelete(basalProfile), - message: 'Are you sure you want to delete this Basal Profile?', + message: translate(LocalizationKeys.basalProfile_confirmDelete), ); } else { onDelete(basalProfile); @@ -131,7 +148,8 @@ class _BasalProfileListScreenState extends State { basalProfile.active = true; BasalProfile.put(basalProfile); reload( - message: '${basalProfile.name} has been set as your active Profile'); + message: translate(LocalizationKeys.basalProfile_activated, + args: {"profileName": basalProfile.name})); } } @@ -141,14 +159,16 @@ class _BasalProfileListScreenState extends State { content: AutoCompleteDropdownButton( controller: TextEditingController(text: ''), items: _basalProfiles, - label: 'Default Basal Profile', + label: translate(LocalizationKeys.basalProfile_default), onChanged: onPickActive, ), leading: const CircleAvatar(child: Icon(Icons.info)), forceActionsBelow: true, actions: [ TextButton( - child: const Text('CREATE A NEW PROFILE INSTEAD'), + child: Text(translate(LocalizationKeys + .basalProfile_warnings_resolve_createInstead) + .toUpperCase()), onPressed: () => onNew(true), ), ], @@ -178,7 +198,7 @@ class _BasalProfileListScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('Basal Profiles'), + title: Text(translate(LocalizationKeys.basalProfile_title)), actions: [ IconButton(onPressed: reload, icon: const Icon(Icons.refresh)) ], @@ -202,9 +222,9 @@ class _BasalProfileListScreenState extends State { double dailyTotal = Basal.getDailyTotalForProfile(basalProfile.id); String activeProfileText = basalProfile.active - ? ' (Default Profile)' + ? '(${translate(LocalizationKeys.basalProfile_default)})' : basalProfile.id == _activeProfile?.id - ? ' (Current Active Profile)' + ? ' (${translate(LocalizationKeys.basalProfile_active)})' : ''; return Card( child: ListTile( @@ -228,7 +248,9 @@ class _BasalProfileListScreenState extends State { ? [ Text(dailyTotal .toStringAsPrecision(3)), - const Text('U/day', + Text( + translate(LocalizationKeys + .general_suffixes_perDay), textScaleFactor: 0.75), ] : [], @@ -263,8 +285,8 @@ class _BasalProfileListScreenState extends State { }, ), ) - : const Center( - child: Text('You have not created any Basal Profiles yet!'), + : Center( + child: Text(translate(LocalizationKeys.basalProfile_empty)), ), ), ], diff --git a/lib/screens/bolus/bolus_detail.dart b/lib/screens/bolus/bolus_detail.dart index fb93f54..4b4a2ea 100644 --- a/lib/screens/bolus/bolus_detail.dart +++ b/lib/screens/bolus/bolus_detail.dart @@ -1,6 +1,7 @@ import 'package:diameter/components/detail.dart'; import 'package:diameter/components/forms/number_form_field.dart'; import 'package:diameter/components/forms/time_of_day_form_field.dart'; +import 'package:diameter/localization_keys.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; @@ -10,6 +11,7 @@ import 'package:flutter/material.dart'; import 'package:diameter/components/forms/form_wrapper.dart'; import 'package:diameter/models/bolus.dart'; import 'package:diameter/models/bolus_profile.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class BolusDetailScreen extends StatefulWidget { static const String routeName = '/bolus'; @@ -140,7 +142,7 @@ class _BolusDetailScreenState extends State { _startTime.hour == other.startTime.hour && _startTime.minute == other.startTime.minute) .isNotEmpty) { - error = 'There\'s already a rate with this start time.'; + error = translate(LocalizationKeys.bolus_warnings_duplicate); } if (bolusRates @@ -151,7 +153,7 @@ class _BolusDetailScreenState extends State { DateTimeUtils.convertTimeOfDayToDateTime(_endTime) .isAfter(other.startTime)) .isNotEmpty) { - error = 'This rate\'s time period overlaps with another one.'; + error = translate(LocalizationKeys.bolus_warnings_overlap); } return error == null @@ -164,11 +166,11 @@ class _BolusDetailScreenState extends State { actions: [ TextButton( onPressed: () => Navigator.pop(context, 'CANCEL'), - child: const Text('GO BACK TO EDITING'), + child: Text(translate(LocalizationKeys.general_keepEditing).toUpperCase()), ), ElevatedButton( onPressed: () => Navigator.pop(context, 'CONFIRM'), - child: const Text('SAVE AS IS'), + child: Text(translate(LocalizationKeys.general_saveAsIs).toUpperCase()), ), ], ); @@ -210,13 +212,27 @@ class _BolusDetailScreenState extends State { ).then((result) { Navigator.pop( context, - ['New Bolus Rate${result[1] != null ? 's' : ''} saved', bolus] + + [ + translatePlural( + LocalizationKeys.bolus_saved, + result.length, + args: { + "status": _isNew ? '${LocalizationKeys.bolus_new} ' : '' + }, + ), + bolus] + [result[1]], ); }); } else { Navigator.pop( - context, ['${_isNew ? 'New' : ''} Bolus Rate saved', bolus]); + context, [translatePlural( + LocalizationKeys.bolus_saved, + 1, + args: { + "status": _isNew ? '${LocalizationKeys.bolus_new} ' : '' + }, + ), bolus]); } } }); @@ -288,7 +304,14 @@ class _BolusDetailScreenState extends State { return Scaffold( appBar: AppBar( title: Text( - '${_isNew ? 'New' : 'Edit'} Bolus Rate for ${BolusProfile.get(widget.bolusProfileId)?.name}'), + translate( + LocalizationKeys.basal_title, + args: { + "status": _isNew ? LocalizationKeys.bolus_new : LocalizationKeys.general_edit, + "profileName": BolusProfile.get(widget.bolusProfileId)?.name, + } + ), + ), ), drawer: const Navigation(currentLocation: BolusDetailScreen.routeName), body: Scrollbar( @@ -306,7 +329,7 @@ class _BolusDetailScreenState extends State { child: Padding( padding: const EdgeInsets.only(right: 5), child: TimeOfDayFormField( - label: 'Start Time', + label: translate(LocalizationKeys.bolus_fields_startTime), controller: _startTimeController, time: _startTime, onChanged: updateStartTime, @@ -317,7 +340,7 @@ class _BolusDetailScreenState extends State { child: Padding( padding: const EdgeInsets.only(left: 5), child: TimeOfDayFormField( - label: 'End Time', + label: translate(LocalizationKeys.bolus_fields_endTime), controller: _endTimeController, time: _endTime, onChanged: updateEndTime, @@ -328,8 +351,8 @@ class _BolusDetailScreenState extends State { ), NumberFormField( controller: _unitsController, - label: 'Units', - suffix: 'U', + label: translate(LocalizationKeys.bolus_fields_units), + suffix: translate(LocalizationKeys.general_suffixes_units), autoRoundToMultipleOfStep: true, step: Settings.insulinSteps, onChanged: (value) { @@ -342,7 +365,7 @@ class _BolusDetailScreenState extends State { ), NumberFormField( controller: _carbsController, - label: 'per carbs', + label: translate(LocalizationKeys.bolus_fields_perCarbs), suffix: Settings.nutritionMeasurementSuffix, autoRoundToMultipleOfStep: true, step: Settings.nutritionSteps, @@ -365,8 +388,10 @@ class _BolusDetailScreenState extends State { ? Expanded( flex: Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl ? 2 : 1, child: NumberFormField( - label: 'per mg/dl', - suffix: 'mg/dl', + label: translate(LocalizationKeys.bolus_fields_perGlucose, args: { + "glucoseMeasurement": Settings.glucoseMeasurementSuffix + }), + suffix: Settings.glucoseMeasurementSuffix, readOnly: Settings.glucoseMeasurement == GlucoseMeasurement.mmolPerL, showSteppers: Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl, @@ -384,8 +409,10 @@ class _BolusDetailScreenState extends State { ? Expanded( flex: Settings.glucoseMeasurement == GlucoseMeasurement.mmolPerL ? 2 : 1, child: NumberFormField( - label: 'per mmol/l', - suffix: 'mmol/l', + label: translate(LocalizationKeys.bolus_fields_perGlucose, args: { + "glucoseMeasurement": Settings.glucoseMeasurementSuffix + }), + suffix: Settings.glucoseMeasurementSuffix, readOnly: Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl, showSteppers: Settings.glucoseMeasurement == GlucoseMeasurement.mmolPerL, @@ -410,8 +437,8 @@ class _BolusDetailScreenState extends State { onMiddleAction: _isSaving || _isFinalRate ? null : () => handleSaveAction(next: false), - actionText: _isFinalRate ? 'SAVE & CLOSE' : 'NEXT', - middleActionText: 'SAVE & CLOSE', + actionTextKey: _isFinalRate ? translate(LocalizationKeys.general_saveAndClose) : translate(LocalizationKeys.general_next), + middleActionTextKey: translate(LocalizationKeys.general_saveAndClose), ), ); } diff --git a/lib/screens/bolus/bolus_list.dart b/lib/screens/bolus/bolus_list.dart index 828719a..0fa7e36 100644 --- a/lib/screens/bolus/bolus_list.dart +++ b/lib/screens/bolus/bolus_list.dart @@ -1,3 +1,4 @@ +import 'package:diameter/localization_keys.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/models/settings.dart'; import 'package:diameter/utils/date_time_utils.dart'; @@ -5,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:diameter/models/bolus.dart'; import 'package:diameter/models/bolus_profile.dart'; import 'package:diameter/screens/bolus/bolus_detail.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class BolusListScreen extends StatefulWidget { final BolusProfile bolusProfile; @@ -61,7 +63,7 @@ class _BolusListScreenState extends State { void onDelete(Bolus bolus) { Bolus.remove(bolus.id); - reload(message: 'Bolus Rate deleted'); + reload(message: translate(LocalizationKeys.bolus_deleted)); } void handleDeleteAction(Bolus bolus) async { @@ -69,7 +71,7 @@ class _BolusListScreenState extends State { DialogUtils.showConfirmationDialog( context: context, onConfirm: () => onDelete(bolus), - message: 'Are you sure you want to delete this Bolus Rate?', + message: translate(LocalizationKeys.bolus_confirmDelete), ); } else { onDelete(bolus); @@ -82,26 +84,26 @@ class _BolusListScreenState extends State { if (index == 0 && (bolus.startTime.toLocal().hour != 0 || bolus.startTime.minute != 0)) { - return 'First Bolus of the day needs to start at 00:00'; + return translate(LocalizationKeys.bolus_warnings_startTimeFirst); } if (index > 0) { var lastEndTime = bolusRates[index - 1].endTime; if (bolus.startTime.isAfter(lastEndTime)) { - return 'There\'s a time gap between this and the previous rate'; + return translate(LocalizationKeys.bolus_warnings_gap); } } if (index == bolusRates.length - 1 && (bolus.endTime.toLocal().hour != 0 || bolus.endTime.minute != 0)) { - return 'Last Bolus of the day needs to end at 00:00'; + return translate(LocalizationKeys.bolus_warnings_endTimeLast); } // check for duplicates if (bolusRates .where((other) => bolus != other && bolus.startTime == other.startTime) .isNotEmpty) { - return 'There are multiple rates with this start time'; + return translate(LocalizationKeys.bolus_warnings_duplicate); } if (bolusRates @@ -109,7 +111,7 @@ class _BolusListScreenState extends State { bolus.startTime.isBefore(other.startTime) && bolus.endTime.isAfter(other.startTime)) .isNotEmpty) { - return 'This rate\'s time period overlaps with another one'; + return translate(LocalizationKeys.bolus_warnings_overlap); } return null; @@ -162,8 +164,9 @@ class _BolusListScreenState extends State { ? [ Text((bolus.carbs / bolus.units) .toStringAsPrecision(2)), - Text( - '${Settings.nutritionMeasurementSuffix} carbs per U', + Text(translate(LocalizationKeys.general_suffixes_carbsPerU, args: { + "nutritionMeasurementSuffix": Settings.nutritionMeasurementSuffix + }), textAlign: TextAlign.center, textScaleFactor: 0.75), ] @@ -176,7 +179,7 @@ class _BolusListScreenState extends State { ? [ Text((bolus.units / bolus.carbs * 12) .toStringAsPrecision(2)), - const Text('U per bread unit', + Text(translate(LocalizationKeys.general_suffixes_uPerBreadUnit), textAlign: TextAlign.center, textScaleFactor: 0.75), ] @@ -196,8 +199,9 @@ class _BolusListScreenState extends State { : bolus.mmolPerL ?? 0)! / bolus.units)) .toString()), - Text( - '${Settings.glucoseMeasurementSuffix} per unit', + Text(translate(LocalizationKeys.general_suffixes_uPerGlucose, args: { + "glucoseMeasurementSuffix": Settings.glucoseMeasurementSuffix + }), textAlign: TextAlign.center, textScaleFactor: 0.75), ] @@ -225,8 +229,8 @@ class _BolusListScreenState extends State { }, ), ) - : const Center( - child: Text('You have not created any Bolus Rates yet!'), + : Center( + child: Text(translate(LocalizationKeys.bolus_title)), ); } } diff --git a/lib/screens/bolus/bolus_profile_detail.dart b/lib/screens/bolus/bolus_profile_detail.dart index bb0aeed..818cc66 100644 --- a/lib/screens/bolus/bolus_profile_detail.dart +++ b/lib/screens/bolus/bolus_profile_detail.dart @@ -1,5 +1,6 @@ import 'package:diameter/components/detail.dart'; import 'package:diameter/components/forms/boolean_form_field.dart'; +import 'package:diameter/localization_keys.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/models/bolus.dart'; import 'package:diameter/models/settings.dart'; @@ -9,6 +10,7 @@ import 'package:flutter/material.dart'; import 'package:diameter/components/forms/form_wrapper.dart'; import 'package:diameter/models/bolus_profile.dart'; import 'package:diameter/screens/bolus/bolus_list.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class BolusProfileDetailScreen extends StatefulWidget { static const String routeName = '/bolus-profile'; @@ -131,20 +133,19 @@ class _BolusProfileDetailScreenState extends State { context: context, builder: (BuildContext context) { return AlertDialog( - content: const Text( - 'There are already one or more active profiles. What would you like to do?'), + content: Text(translate(LocalizationKeys.bolusProfile_warnings_activeAlreadySet)), actions: [ TextButton( onPressed: () => Navigator.pop(context, 0), - child: const Text('IGNORE'), + child: Text(translate(LocalizationKeys.bolusProfile_warnings_resolve_ignore).toUpperCase()), ), TextButton( onPressed: () => Navigator.pop(context, 1), - child: const Text('DEACTIVATE THIS PROFILE'), + child: Text(translate(LocalizationKeys.bolusProfile_warnings_resolve_deactivateProfile).toUpperCase()), ), ElevatedButton( onPressed: () => Navigator.pop(context, 2), - child: const Text('DEACTIVATE ALL OTHERS'), + child: Text(translate(LocalizationKeys.bolusProfile_warnings_resolve_deactivateOthers).toUpperCase()), ) ], ); @@ -164,16 +165,15 @@ class _BolusProfileDetailScreenState extends State { context: context, builder: (BuildContext context) { return AlertDialog( - content: const Text( - 'There is currently no active profile. Would you like to set this one as active?'), + content: Text(translate(LocalizationKeys.bolusProfile_warnings_noActiveOnCreate)), actions: [ TextButton( onPressed: () => Navigator.pop(context, 0), - child: const Text('IGNORE'), + child: Text(translate(LocalizationKeys.bolusProfile_warnings_resolve_ignore).toUpperCase()), ), TextButton( onPressed: () => Navigator.pop(context, 1), - child: const Text('ACTIVATE THIS PROFILE'), + child: Text(translate(LocalizationKeys.bolusProfile_warnings_resolve_activateCurrent).toUpperCase()), ), ], ); @@ -246,8 +246,15 @@ class _BolusProfileDetailScreenState extends State { BolusProfile.put(bolusProfile); if (close) { - Navigator.pop(context, - ['${_isNew ? 'New' : ''} Bolus Profile saved', bolusProfile]); + Navigator.pop(context, [ + translate( + LocalizationKeys.bolusProfile_saved, + args: { + "status": _isNew ? '${translate(LocalizationKeys.bolusProfile_new)} ' : '' + }, + ), bolusProfile + ], + ); } else { if (_isNew) { Navigator.push( @@ -258,7 +265,7 @@ class _BolusProfileDetailScreenState extends State { ), ).then((result) => Navigator.pop(context, result)); } else { - reload(message: 'Bolus Profile saved'); + reload(message: translate(LocalizationKeys.bolusProfile_saved)); } } } @@ -329,19 +336,19 @@ class _BolusProfileDetailScreenState extends State { fields: [ TextFormField( controller: _nameController, - decoration: const InputDecoration( - labelText: 'Name', + decoration: InputDecoration( + labelText: translate(LocalizationKeys.bolusProfile_fields_name), ), validator: (value) { if (value!.trim().isEmpty) { - return 'Empty title'; + return translate(LocalizationKeys.bolusProfile_fields_validators_name); } return null; }, ), TextFormField( - decoration: const InputDecoration( - labelText: 'Notes', + decoration: InputDecoration( + labelText: translate(LocalizationKeys.bolusProfile_fields_notes), ), controller: _notesController, keyboardType: TextInputType.multiline, @@ -355,7 +362,7 @@ class _BolusProfileDetailScreenState extends State { _active = value; }); }, - label: 'active', + label: translate(LocalizationKeys.bolusProfile_fields_active), ), ], ), @@ -374,13 +381,16 @@ class _BolusProfileDetailScreenState extends State { return Scaffold( appBar: AppBar( - title: Text(_isNew ? 'New Bolus Profile' : _bolusProfile!.name), + title: Text(translate(LocalizationKeys.bolusProfile_detail_title, args: { + "status": _isNew ? '${translate(LocalizationKeys.bolusProfile_new)} ' : '', + "profileName": _bolusProfile!.name, + })), bottom: _isNew ? PreferredSize(child: Container(), preferredSize: Size.zero) - : const TabBar( + : TabBar( tabs: [ - Tab(text: 'PROFILE'), - Tab(text: 'RATES'), + Tab(text: translate(LocalizationKeys.bolusProfile_detail_tabs_profile).toUpperCase()), + Tab(text: translate(LocalizationKeys.bolusProfile_detail_tabs_rates).toUpperCase()), ], ), actions: appBarActions, diff --git a/lib/screens/bolus/bolus_profile_list.dart b/lib/screens/bolus/bolus_profile_list.dart index 2b9a617..3ac8898 100644 --- a/lib/screens/bolus/bolus_profile_list.dart +++ b/lib/screens/bolus/bolus_profile_list.dart @@ -1,3 +1,4 @@ +import 'package:diameter/localization_keys.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/components/forms/auto_complete_dropdown_button.dart'; import 'package:diameter/models/bolus.dart'; @@ -6,6 +7,7 @@ import 'package:diameter/navigation.dart'; import 'package:flutter/material.dart'; import 'package:diameter/models/bolus_profile.dart'; import 'package:diameter/screens/bolus/bolus_profile_detail.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class BolusProfileListScreen extends StatefulWidget { static const String routeName = '/bolus-profiles'; @@ -62,26 +64,26 @@ class _BolusProfileListScreenState extends State { banner = activeProfileCount != 1 ? MaterialBanner( content: Text(activeProfileCount == 0 - ? 'You currently do not have an active Bolus Profile.' - : 'More than one active Bolus Profile has been found.'), + ? translate(LocalizationKeys.bolusProfile_warnings_noActive) + : translate(LocalizationKeys.bolusProfile_warnings_multipleActive)), leading: const CircleAvatar(child: Icon(Icons.warning)), forceActionsBelow: true, actions: activeProfileCount == 0 ? [ _bolusProfiles.isNotEmpty ? TextButton( - child: const Text('ACTIVATE A PROFILE'), + child: Text(translate(LocalizationKeys.bolusProfile_warnings_resolve_activate).toUpperCase()), onPressed: handlePickActiveProfileAction, ) : Container(), TextButton( - child: const Text('CREATE A NEW PROFILE'), + child: Text(translate(LocalizationKeys.bolusProfile_warnings_resolve_create).toUpperCase()), onPressed: () => onNew(true), ), ] : [ TextButton( - child: const Text('PICK A PROFILE'), + child: Text(translate(LocalizationKeys.bolusProfile_warnings_resolve_pick).toUpperCase()), onPressed: handlePickActiveProfileAction, ), ], @@ -93,7 +95,12 @@ class _BolusProfileListScreenState extends State { void handleDuplicateAction(BolusProfile bolusProfile) async { final copy = BolusProfile( active: false, - name: 'Copy of ${bolusProfile.name}', + name: translate( + LocalizationKeys.bolusProfile_copyOf, + args: { + "profileName": bolusProfile.name + }, + ), ); BolusProfile.put(copy); @@ -111,12 +118,19 @@ class _BolusProfileListScreenState extends State { Bolus.put(bolus); } - reload(message: 'Added copy of ${bolusProfile.name}'); + reload( + message: translate( + LocalizationKeys.bolusProfile_copied, + args: { + "profileName": bolusProfile.name + }, + ), + ); } void onDelete(BolusProfile bolusProfile) { BolusProfile.remove(bolusProfile.id); - reload(message: 'Bolus Profile deleted'); + reload(message: translate(LocalizationKeys.bolusProfile_deleted)); } void handleDeleteAction(BolusProfile bolusProfile) async { @@ -124,7 +138,7 @@ class _BolusProfileListScreenState extends State { DialogUtils.showConfirmationDialog( context: context, onConfirm: () => onDelete(bolusProfile), - message: 'Are you sure you want to delete this Bolus Profile?', + message: translate(LocalizationKeys.bolusProfile_confirmDelete), ); } else { onDelete(bolusProfile); @@ -137,7 +151,13 @@ class _BolusProfileListScreenState extends State { bolusProfile.active = true; BolusProfile.put(bolusProfile); reload( - message: '${bolusProfile.name} has been set as your active Profile'); + message: translate( + LocalizationKeys.bolusProfile_activated, + args: { + "profileName": bolusProfile.name + }, + ), + ); } } @@ -147,14 +167,14 @@ class _BolusProfileListScreenState extends State { content: AutoCompleteDropdownButton( controller: TextEditingController(text: ''), items: _bolusProfiles, - label: 'Default Basal Profile', + label: translate(LocalizationKeys.bolusProfile_default), onChanged: onPickActive, ), leading: const CircleAvatar(child: Icon(Icons.info)), forceActionsBelow: true, actions: [ TextButton( - child: const Text('CREATE A NEW PROFILE INSTEAD'), + child: Text(translate(LocalizationKeys.bolusProfile_warnings_resolve_createInstead).toUpperCase()), onPressed: () => onNew(true), ), ], @@ -184,7 +204,7 @@ class _BolusProfileListScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('Bolus Profiles'), + title: Text(translate(LocalizationKeys.bolusProfile_title)), actions: [ IconButton(onPressed: reload, icon: const Icon(Icons.refresh)) ], @@ -206,9 +226,9 @@ class _BolusProfileListScreenState extends State { itemBuilder: (context, index) { final bolusProfile = _bolusProfiles[index]; String activeProfileText = bolusProfile.active - ? ' (Default Profile)' + ? ' (${translate(LocalizationKeys.bolusProfile_default)})' : bolusProfile.id == _activeProfile?.id - ? ' (Current Active Profile)' + ? ' (${translate(LocalizationKeys.bolusProfile_active)})' : ''; return Card( child: ListTile( @@ -250,8 +270,8 @@ class _BolusProfileListScreenState extends State { }, ), ) - : const Center( - child: Text('You have not created any Bolus Profiles yet!'), + : Center( + child: Text(translate(LocalizationKeys.bolusProfile_empty)), ), ), ], diff --git a/lib/screens/category/accuracy_detail.dart b/lib/screens/category/accuracy_detail.dart index ac58d8c..8a2229c 100644 --- a/lib/screens/category/accuracy_detail.dart +++ b/lib/screens/category/accuracy_detail.dart @@ -1,12 +1,14 @@ import 'package:diameter/components/detail.dart'; import 'package:diameter/components/forms/boolean_form_field.dart'; import 'package:diameter/components/forms/number_form_field.dart'; +import 'package:diameter/localization_keys.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:flutter/material.dart'; import 'package:diameter/components/forms/form_wrapper.dart'; import 'package:diameter/models/accuracy.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class AccuracyDetailScreen extends StatefulWidget { static const String routeName = '/accuracy'; @@ -93,7 +95,12 @@ class _AccuracyDetailScreenState extends State { Accuracy.reorder( accuracy, int.tryParse(_confidenceRatingController.text)); Navigator.pop( - context, ['${_isNew ? 'New' : ''} Accuracy saved', accuracy]); + context, [translate( + LocalizationKeys.accuracy_saved, + args: { + "status": _isNew ? '${translate(LocalizationKeys.accuracy_new)} ' : '' + }, + ), accuracy]); } setState(() { _isSaving = false; @@ -130,7 +137,9 @@ class _AccuracyDetailScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text(_isNew ? 'New Accuracy' : _accuracy!.value), + title: Text(translate(LocalizationKeys.accuracy_detail_title, args: { + "status": _isNew ? '${translate(LocalizationKeys.accuracy_new)} ' : '', + })), ), drawer: const Navigation(currentLocation: AccuracyDetailScreen.routeName), body: Scrollbar( @@ -145,12 +154,12 @@ class _AccuracyDetailScreenState extends State { fields: [ TextFormField( controller: _valueController, - decoration: const InputDecoration( - labelText: 'Name', + decoration: InputDecoration( + labelText: translate(LocalizationKeys.accuracy_fields_name), ), validator: (value) { if (value!.trim().isEmpty) { - return 'Empty name'; + return translate(LocalizationKeys.accuracy_fields_validators_name); } return null; }, @@ -158,7 +167,7 @@ class _AccuracyDetailScreenState extends State { BooleanFormField( icon: const Icon(Icons.square_foot), value: _forPortionSize, - label: 'for portion size', + label: translate(LocalizationKeys.accuracy_fields_forPortionSize), onChanged: (value) { setState(() { _forPortionSize = value; @@ -168,7 +177,7 @@ class _AccuracyDetailScreenState extends State { BooleanFormField( icon: const Icon(Icons.pie_chart), value: _forCarbsRatio, - label: 'for carbs ratio', + label: translate(LocalizationKeys.accuracy_fields_forCarbsRatio), onChanged: (value) { setState(() { _forCarbsRatio = value; @@ -177,7 +186,7 @@ class _AccuracyDetailScreenState extends State { ), NumberFormField( controller: _confidenceRatingController, - label: 'Confidence Rating', + label: translate(LocalizationKeys.accuracy_fields_confidenceRating), onChanged: (value) { setState(() { _confidenceRatingController.text = @@ -188,8 +197,8 @@ class _AccuracyDetailScreenState extends State { TextFormField( controller: _notesController, keyboardType: TextInputType.multiline, - decoration: const InputDecoration( - labelText: 'Notes', + decoration: InputDecoration( + labelText: translate(LocalizationKeys.accuracy_fields_notes), ), minLines: 2, maxLines: 5, diff --git a/lib/screens/category/accuracy_list.dart b/lib/screens/category/accuracy_list.dart index df0f7fa..130ec06 100644 --- a/lib/screens/category/accuracy_list.dart +++ b/lib/screens/category/accuracy_list.dart @@ -1,9 +1,11 @@ +import 'package:diameter/localization_keys.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:diameter/screens/category/accuracy_detail.dart'; import 'package:flutter/material.dart'; import 'package:diameter/models/accuracy.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class AccuracyListScreen extends StatefulWidget { static const String routeName = '/accuracies'; @@ -58,7 +60,7 @@ class _AccuracyListScreenState extends State { DialogUtils.showConfirmationDialog( context: context, onConfirm: () => onDelete(accuracy), - message: 'Are you sure you want to delete this Accuracy?', + message: translate(LocalizationKeys.accuracy_confirmDelete), ); } else { onDelete(accuracy); @@ -81,7 +83,7 @@ class _AccuracyListScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('Accuracies'), + title: Text(translate(LocalizationKeys.accuracy_detail)), actions: [ IconButton(onPressed: reload, icon: const Icon(Icons.refresh)) ], @@ -164,8 +166,8 @@ class _AccuracyListScreenState extends State { ); }), ) - : const Center( - child: Text('You have not created any Accuracies yet!'), + : Center( + child: Text(translate(LocalizationKeys.accuracy_empty)), ), ), ], diff --git a/lib/screens/category/categories.dart b/lib/screens/category/categories.dart index 0d45c03..cb6169d 100644 --- a/lib/screens/category/categories.dart +++ b/lib/screens/category/categories.dart @@ -1,5 +1,7 @@ +import 'package:diameter/localization_keys.dart'; import 'package:diameter/navigation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class CategoryOverviewScreen extends StatefulWidget { static const String routeName = '/category'; @@ -22,7 +24,7 @@ class _CategoryOverviewScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('Reports'), + title: Text(translate(LocalizationKeys.categories)), ), drawer: const Navigation(currentLocation: CategoryOverviewScreen.routeName), @@ -45,7 +47,7 @@ class _CategoryOverviewScreenState extends State { child: Icon(Icons.local_grocery_store, size: 50, color: Theme.of(context).textTheme.subtitle2?.color), ), Text( - 'MEAL SOURCES', + translate(LocalizationKeys.mealSource_title).toUpperCase(), style: Theme.of(context).textTheme.subtitle2, textAlign: TextAlign.center ), @@ -66,7 +68,7 @@ class _CategoryOverviewScreenState extends State { child: Icon(Icons.category, size: 50, color: Theme.of(context).textTheme.subtitle2?.color), ), Text( - 'MEAL CATEGORIES', + translate(LocalizationKeys.mealCategory_title).toUpperCase(), style: Theme.of(context).textTheme.subtitle2, textAlign: TextAlign.center ), @@ -86,7 +88,7 @@ class _CategoryOverviewScreenState extends State { child: Icon(Icons.pie_chart, size: 50, color: Theme.of(context).textTheme.subtitle2?.color), ), Text( - 'PORTION TYPES', + translate(LocalizationKeys.portionType_title).toUpperCase(), style: Theme.of(context).textTheme.subtitle2, textAlign: TextAlign.center ), @@ -107,7 +109,7 @@ class _CategoryOverviewScreenState extends State { child: Icon(Icons.architecture, size: 50, color: Theme.of(context).textTheme.subtitle2?.color), ), Text( - 'ACCURACIES', + translate(LocalizationKeys.accuracy_title).toUpperCase(), style: Theme.of(context).textTheme.subtitle2, textAlign: TextAlign.center ), @@ -127,7 +129,7 @@ class _CategoryOverviewScreenState extends State { child: Icon(Icons.event, size: 50, color: Theme.of(context).textTheme.subtitle2?.color), ), Text( - 'EVENT TYPES', + translate(LocalizationKeys.eventType_title).toUpperCase(), style: Theme.of(context).textTheme.subtitle2, textAlign: TextAlign.center ), diff --git a/lib/screens/category/event_type_detail.dart b/lib/screens/category/event_type_detail.dart index eb584c5..3791e80 100644 --- a/lib/screens/category/event_type_detail.dart +++ b/lib/screens/category/event_type_detail.dart @@ -1,6 +1,7 @@ import 'package:diameter/components/detail.dart'; import 'package:diameter/components/forms/boolean_form_field.dart'; import 'package:diameter/components/forms/duration_form_field.dart'; +import 'package:diameter/localization_keys.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/components/forms/auto_complete_dropdown_button.dart'; import 'package:diameter/components/forms/form_wrapper.dart'; @@ -12,6 +13,7 @@ import 'package:diameter/navigation.dart'; import 'package:diameter/screens/basal/basal_profile_detail.dart'; import 'package:diameter/screens/bolus/bolus_profile_detail.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class EventTypeDetailScreen extends StatefulWidget { static const String routeName = '/log-event-type'; @@ -126,7 +128,9 @@ class _EventTypeDetailScreenState extends State { eventType.bolusProfile.target = _bolusProfile; LogEventType.put(eventType); Navigator.pop( - context, ['${_isNew ? 'New' : ''} Log Event Type Saved', eventType]); + context, [translate(LocalizationKeys.eventType_saved, args: { + "status": _isNew ? LocalizationKeys.eventType_new : '', + }), eventType]); } setState(() { _isSaving = false; @@ -161,7 +165,10 @@ class _EventTypeDetailScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text(_isNew ? 'New Log Event Type' : _logEventType!.value), + title: Text(translate(LocalizationKeys.eventType_detail_title, args: { + "status": _isNew ? LocalizationKeys.eventType_new : LocalizationKeys.general_edit, + "name": _isNew ? '' : _logEventType!.value, + })) ), drawer: const Navigation(currentLocation: EventTypeDetailScreen.routeName), @@ -175,19 +182,19 @@ class _EventTypeDetailScreenState extends State { FormWrapper(formState: _logEventTypeForm, fields: [ TextFormField( controller: _valueController, - decoration: const InputDecoration( - labelText: 'Name', + decoration: InputDecoration( + labelText: translate(LocalizationKeys.eventType_fields_name), ), validator: (value) { if (value!.trim().isEmpty) { - return 'Empty name'; + return translate(LocalizationKeys.eventType_fields_validators_name); } return null; }, ), BooleanFormField( value: _hasEndTime, - label: 'has end time', + label: translate(LocalizationKeys.eventType_fields_hasEndTime), onChanged: (value) { setState(() { _hasEndTime = value; @@ -201,7 +208,7 @@ class _EventTypeDetailScreenState extends State { padding: const EdgeInsets.only(bottom: 10.0), child: DurationFormField( minutes: _defaultReminderDuration, - label: 'Default Reminder Duration', + label: translate(LocalizationKeys.eventType_fields_defaultReminderDuration), onChanged: (value) => _defaultReminderDuration = value ?? 0, showSteppers: true, ), @@ -215,7 +222,7 @@ class _EventTypeDetailScreenState extends State { BolusProfile>( selectedItem: _bolusProfile, controller: _bolusProfileController, - label: 'Bolus Profile', + label: translate(LocalizationKeys.eventType_fields_bolusProfile), items: _bolusProfiles, onChanged: updateBolusProfile, ), @@ -252,7 +259,7 @@ class _EventTypeDetailScreenState extends State { AutoCompleteDropdownButton( controller: _basalProfileController, selectedItem: _basalProfile, - label: 'Basal Profile', + label: translate(LocalizationKeys.eventType_fields_basalProfile), items: _basalProfiles, onChanged: updateBasalProfile, ), @@ -283,8 +290,8 @@ class _EventTypeDetailScreenState extends State { : []), TextFormField( controller: _notesController, - decoration: const InputDecoration( - labelText: 'Notes', + decoration: InputDecoration( + labelText: translate(LocalizationKeys.eventType_fields_notes), ), keyboardType: TextInputType.multiline, minLines: 2, diff --git a/lib/screens/category/event_type_list.dart b/lib/screens/category/event_type_list.dart index b720a5c..05ce9e8 100644 --- a/lib/screens/category/event_type_list.dart +++ b/lib/screens/category/event_type_list.dart @@ -1,7 +1,9 @@ +import 'package:diameter/localization_keys.dart'; import 'package:diameter/models/log_event_type.dart'; import 'package:diameter/navigation.dart'; import 'package:diameter/screens/category/event_type_detail.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class EventTypeListScreen extends StatefulWidget { static const String routeName = '/log-event-types'; @@ -48,7 +50,7 @@ class _EventTypeListScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('Log Event Types'), actions: [ + appBar: AppBar(title: Text(translate(LocalizationKeys.eventType_title)), actions: [ IconButton(onPressed: reload, icon: const Icon(Icons.refresh)) ]), drawer: @@ -88,7 +90,7 @@ class _EventTypeListScreenState extends State { IconButton( onPressed: () async { LogEventType.remove(logEventType.id); - reload(message: 'Log Event Type deleted'); + reload(message: translate(LocalizationKeys.eventType_deleted)); }, icon: const Icon(Icons.delete, color: Colors.blue), @@ -100,9 +102,9 @@ class _EventTypeListScreenState extends State { }, ), ) - : const Center( + : Center( child: - Text('You have not created any Log Event Types yet!'), + Text(translate(LocalizationKeys.eventType_empty)), ), ), ], diff --git a/lib/screens/category/meal_category_detail.dart b/lib/screens/category/meal_category_detail.dart index b498ac0..361fc1d 100644 --- a/lib/screens/category/meal_category_detail.dart +++ b/lib/screens/category/meal_category_detail.dart @@ -1,10 +1,12 @@ import 'package:diameter/components/detail.dart'; +import 'package:diameter/localization_keys.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/components/forms/form_wrapper.dart'; import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:flutter/material.dart'; import 'package:diameter/models/meal_category.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class MealCategoryDetailScreen extends StatefulWidget { static const String routeName = '/meal-category'; @@ -76,7 +78,9 @@ class _MealCategoryDetailScreenState extends State { ); MealCategory.put(mealCategory); Navigator.pop(context, [ - '${_isNew ? 'New' : ''} Meal Category saved', mealCategory + translate(LocalizationKeys.mealCategory_saved, args: { + "status": _isNew ? LocalizationKeys.mealCategory_new : '', + }), mealCategory ]); } } @@ -102,7 +106,11 @@ class _MealCategoryDetailScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text(_isNew ? 'New Meal Category' : _mealCategory!.value), + title: Text(translate(LocalizationKeys.mealCategory_detail_title, args: { + "status": _isNew ? LocalizationKeys.mealCategory_new : '', + "name": _mealCategory!.value + }), + ), ), drawer: const Navigation(currentLocation: MealCategoryDetailScreen.routeName), @@ -118,20 +126,20 @@ class _MealCategoryDetailScreenState extends State { fields: [ TextFormField( controller: _valueController, - decoration: const InputDecoration( - labelText: 'Name', + decoration: InputDecoration( + labelText: translate(LocalizationKeys.mealCategory_fields_name), ), validator: (value) { if (value!.trim().isEmpty) { - return 'Empty name'; + return translate(LocalizationKeys.mealCategory_fields_validators_name); } return null; }, ), TextFormField( controller: _notesController, - decoration: const InputDecoration( - labelText: 'Notes', + decoration: InputDecoration( + labelText: translate(LocalizationKeys.mealCategory_fields_notes), ), keyboardType: TextInputType.multiline, minLines: 2, diff --git a/lib/screens/category/meal_category_list.dart b/lib/screens/category/meal_category_list.dart index cfe612a..55a6390 100644 --- a/lib/screens/category/meal_category_list.dart +++ b/lib/screens/category/meal_category_list.dart @@ -1,9 +1,11 @@ +import 'package:diameter/localization_keys.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:diameter/screens/category/meal_category_detail.dart'; import 'package:flutter/material.dart'; import 'package:diameter/models/meal_category.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class MealCategoryListScreen extends StatefulWidget { static const String routeName = '/meal-categories'; @@ -50,7 +52,7 @@ class _MealCategoryListScreenState extends State { void onDelete(MealCategory mealCategory) { MealCategory.remove(mealCategory.id); - reload(message: 'Meal Category deleted'); + reload(message: translate(LocalizationKeys.mealCategory_deleted)); } void handleDeleteAction(MealCategory mealCategory) async { @@ -58,7 +60,7 @@ class _MealCategoryListScreenState extends State { DialogUtils.showConfirmationDialog( context: context, onConfirm: () => onDelete(mealCategory), - message: 'Are you sure you want to delete this Meal Category?', + message: translate(LocalizationKeys.mealCategory_confirmDelete), ); } else { onDelete(mealCategory); @@ -69,7 +71,7 @@ class _MealCategoryListScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('Meal Categories'), + title: Text(translate(LocalizationKeys.mealCategory_title)), actions: [ IconButton( onPressed: reload, @@ -125,8 +127,8 @@ class _MealCategoryListScreenState extends State { ); }, ), - ): const Center( - child: Text('You have not created any Meal Categories yet!'), + ): Center( + child: Text(translate(LocalizationKeys.mealCategory_empty)), ), ), ], diff --git a/lib/screens/category/meal_portion_type_detail.dart b/lib/screens/category/meal_portion_type_detail.dart index eb5ffb8..5990559 100644 --- a/lib/screens/category/meal_portion_type_detail.dart +++ b/lib/screens/category/meal_portion_type_detail.dart @@ -1,10 +1,12 @@ import 'package:diameter/components/detail.dart'; +import 'package:diameter/localization_keys.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/components/forms/form_wrapper.dart'; import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:flutter/material.dart'; import 'package:diameter/models/meal_portion_type.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class MealPortionTypeDetailScreen extends StatefulWidget { static const String routeName = '/meal-portion-type'; @@ -77,8 +79,10 @@ class _MealPortionTypeDetailScreenState notes: _notesController.text, ); MealPortionType.put(mealPortionType); - Navigator.pop(context, - ['${_isNew ? 'New' : ''} Meal Portion Type saved', mealPortionType]); + Navigator.pop(context, + [translate(LocalizationKeys.portionType_saved, args: { + "status": _isNew ? LocalizationKeys.portionType_new : '', + }), mealPortionType]); } } @@ -102,10 +106,12 @@ class _MealPortionTypeDetailScreenState @override Widget build(BuildContext context) { - bool isNew = _mealPortionType == null; return Scaffold( appBar: AppBar( - title: Text(isNew ? 'New Meal Portion Type' : _mealPortionType!.value), + title: Text(translate(LocalizationKeys.portionType_detail_title, args: { + "status": translate(_isNew ? LocalizationKeys.portionType_new : LocalizationKeys.general_edit), + "name": _mealPortionType!.value, + })), ), drawer: const Navigation( currentLocation: MealPortionTypeDetailScreen.routeName), @@ -121,20 +127,20 @@ class _MealPortionTypeDetailScreenState fields: [ TextFormField( controller: _valueController, - decoration: const InputDecoration( - labelText: 'Name', + decoration: InputDecoration( + labelText: translate(LocalizationKeys.portionType_fields_name), ), validator: (value) { if (value!.trim().isEmpty) { - return 'Empty name'; + return translate(LocalizationKeys.portionType_fields_validators_name); } return null; }, ), TextFormField( controller: _notesController, - decoration: const InputDecoration( - labelText: 'Notes', + decoration: InputDecoration( + labelText: translate(LocalizationKeys.portionType_fields_notes), ), keyboardType: TextInputType.multiline, minLines: 2, diff --git a/lib/screens/category/meal_portion_type_list.dart b/lib/screens/category/meal_portion_type_list.dart index e100a62..92eee53 100644 --- a/lib/screens/category/meal_portion_type_list.dart +++ b/lib/screens/category/meal_portion_type_list.dart @@ -1,9 +1,11 @@ +import 'package:diameter/localization_keys.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:diameter/screens/category/meal_portion_type_detail.dart'; import 'package:flutter/material.dart'; import 'package:diameter/models/meal_portion_type.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class MealPortionTypeListScreen extends StatefulWidget { static const String routeName = '/meal-portion-types'; @@ -51,7 +53,7 @@ class _MealPortionTypeListScreenState extends State { void onDelete(MealPortionType mealPortionType) { MealPortionType.remove(mealPortionType.id); - reload(message: 'Meal Portion Type deleted'); + reload(message: translate(LocalizationKeys.portionType_deleted)); } void handleDeleteAction(MealPortionType mealPortionType) async { @@ -59,7 +61,7 @@ class _MealPortionTypeListScreenState extends State { DialogUtils.showConfirmationDialog( context: context, onConfirm: () => onDelete(mealPortionType), - message: 'Are you sure you want to delete this Meal Portion Type?', + message: translate(LocalizationKeys.portionType_confirmDelete), ); } else { onDelete(mealPortionType); @@ -70,7 +72,7 @@ class _MealPortionTypeListScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('Meal Portion Types'), + title: Text(translate(LocalizationKeys.portionType_title)), actions: [ IconButton(onPressed: reload, icon: const Icon(Icons.refresh)) ], @@ -124,8 +126,8 @@ class _MealPortionTypeListScreenState extends State { ); }, ), - ) : const Center( - child: Text('You have not created any Meal Portion Types yet!'), + ) : Center( + child: Text(translate(LocalizationKeys.portionType_empty)), ), ), ], diff --git a/lib/screens/category/meal_source_detail.dart b/lib/screens/category/meal_source_detail.dart index 55bbcc8..36b26b4 100644 --- a/lib/screens/category/meal_source_detail.dart +++ b/lib/screens/category/meal_source_detail.dart @@ -1,4 +1,5 @@ import 'package:diameter/components/detail.dart'; +import 'package:diameter/localization_keys.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/components/forms/auto_complete_dropdown_button.dart'; import 'package:diameter/components/forms/form_wrapper.dart'; @@ -12,6 +13,7 @@ import 'package:diameter/screens/category/accuracy_detail.dart'; import 'package:diameter/screens/category/meal_category_detail.dart'; import 'package:diameter/screens/category/meal_portion_type_detail.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class MealSourceDetailScreen extends StatefulWidget { static const String routeName = '/meal-source'; @@ -121,7 +123,9 @@ class _MealSourceDetailScreenState extends State { mealSource.defaultMealCategory.target = _defaultMealCategory; mealSource.defaultMealPortionType.target = _defaultMealPortionType; MealSource.put(mealSource); - Navigator.pop(context, ['${_isNew ? 'New' : ''} Meal Source saved', mealSource]); + Navigator.pop(context, [translate(LocalizationKeys.mealSource_saved, args: { + "status": _isNew ? LocalizationKeys.mealSource_new : '' + }), mealSource]); } void handleCancelAction() { @@ -158,7 +162,12 @@ class _MealSourceDetailScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text(_isNew ? 'New Meal Source' : _mealSource!.value), + title: Text( + translate(LocalizationKeys.mealSource_saved, args: { + "status": _isNew ? LocalizationKeys.mealSource_new : LocalizationKeys.general_edit, + "name": _mealSource!.value, + }) + ), ), drawer: const Navigation(currentLocation: MealSourceDetailScreen.routeName), @@ -174,12 +183,12 @@ class _MealSourceDetailScreenState extends State { fields: [ TextFormField( controller: _valueController, - decoration: const InputDecoration( - labelText: 'Name', + decoration: InputDecoration( + labelText: translate(LocalizationKeys.mealSource_fields_name), ), validator: (value) { if (value!.trim().isEmpty) { - return 'Empty name'; + return translate(LocalizationKeys.mealSource_fields_validators_name); } return null; }, @@ -190,7 +199,7 @@ class _MealSourceDetailScreenState extends State { child: AutoCompleteDropdownButton( selectedItem: _defaultCarbsRatioAccuracy, controller: _defaultCarbsRatioAccuracyController, - label: 'Default Carbs Ratio Accuracy', + label: translate(LocalizationKeys.mealSource_fields_defaultCarbsRatioAccuracy), items: _carbsRatioAccuracies, onChanged: (value) { setState(() { @@ -233,7 +242,7 @@ class _MealSourceDetailScreenState extends State { child: AutoCompleteDropdownButton( selectedItem: _defaultPortionSizeAccuracy, controller: _defaultPortionSizeAccuracyController, - label: 'Default Portion Size Accuracy', + label: translate(LocalizationKeys.mealSource_fields_defaultPortionSizeAccuracy), items: _portionSizeAccuracies, onChanged: (value) { setState(() { @@ -278,7 +287,7 @@ class _MealSourceDetailScreenState extends State { child: AutoCompleteDropdownButton( selectedItem: _defaultMealCategory, controller: _defaultMealCategoryController, - label: 'Default Meal Category', + label: translate(LocalizationKeys.mealSource_fields_defaultMealCategory), items: _mealCategories, onChanged: (value) { setState(() { @@ -320,7 +329,7 @@ class _MealSourceDetailScreenState extends State { child: AutoCompleteDropdownButton( selectedItem: _defaultMealPortionType, controller: _defaultMealPortionTypeController, - label: 'Default Meal Portion Type', + label: translate(LocalizationKeys.mealSource_fields_defaultMealPortionType), items: _mealPortionTypes, onChanged: (value) { setState(() { @@ -359,8 +368,8 @@ class _MealSourceDetailScreenState extends State { ), TextFormField( controller: _notesController, - decoration: const InputDecoration( - labelText: 'Notes', + decoration: InputDecoration( + labelText: translate(LocalizationKeys.mealSource_fields_notes), ), keyboardType: TextInputType.multiline, minLines: 2, diff --git a/lib/screens/category/meal_source_list.dart b/lib/screens/category/meal_source_list.dart index 00994f4..fce5a89 100644 --- a/lib/screens/category/meal_source_list.dart +++ b/lib/screens/category/meal_source_list.dart @@ -1,9 +1,11 @@ +import 'package:diameter/localization_keys.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/models/meal_source.dart'; import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:diameter/screens/category/meal_source_detail.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class MealSourceListScreen extends StatefulWidget { static const String routeName = '/meal-sources'; @@ -50,7 +52,7 @@ class _MealSourceListScreenState extends State { void onDelete(MealSource mealSource) { MealSource.remove(mealSource.id); - reload(message: 'Meal Source deleted'); + reload(message: translate(LocalizationKeys.mealSource_deleted)); } void handleDeleteAction(MealSource mealSource) async { @@ -58,7 +60,7 @@ class _MealSourceListScreenState extends State { DialogUtils.showConfirmationDialog( context: context, onConfirm: () => onDelete(mealSource), - message: 'Are you sure you want to delete this Meal Source?', + message: translate(LocalizationKeys.mealSource_confirmDelete), ); } else { onDelete(mealSource); @@ -69,7 +71,7 @@ class _MealSourceListScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('Meal Sources'), + title: Text(translate(LocalizationKeys.mealSource_title)), actions: [ IconButton(onPressed: reload, icon: const Icon(Icons.refresh)) ], @@ -122,8 +124,8 @@ class _MealSourceListScreenState extends State { ); } ), - ) : const Center( - child: Text('You have not created any Meal Sources yet!'), + ) : Center( + child: Text(translate(LocalizationKeys.mealSource_empty)), ), ), ], diff --git a/lib/screens/log/log_entry/log_bolus_detail.dart b/lib/screens/log/log_entry/log_bolus_detail.dart index bb70fcd..14ceda1 100644 --- a/lib/screens/log/log_entry/log_bolus_detail.dart +++ b/lib/screens/log/log_entry/log_bolus_detail.dart @@ -3,6 +3,7 @@ import 'dart:math'; import 'package:diameter/components/detail.dart'; import 'package:diameter/components/forms/boolean_form_field.dart'; import 'package:diameter/components/forms/number_form_field.dart'; +import 'package:diameter/localization_keys.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/components/forms/auto_complete_dropdown_button.dart'; import 'package:diameter/components/forms/form_wrapper.dart'; @@ -15,6 +16,7 @@ import 'package:diameter/navigation.dart'; import 'package:diameter/screens/log/log_entry/log_meal_detail.dart'; import 'package:diameter/utils/utils.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_translate/flutter_translate.dart'; enum BolusType { meal, @@ -453,7 +455,9 @@ class _LogBolusDetailScreenState extends State { } Navigator.pop(context, - ['${_isNew ? 'New' : ''} Bolus Saved', logBolus, delayedBolus]); + [translate(LocalizationKeys.log_detail_tabs_bolus_saved, args: { + "status": _isNew ? LocalizationKeys.log_detail_tabs_bolus_new : '' + }), logBolus, delayedBolus]); } setState(() { _isSaving = false; @@ -509,7 +513,9 @@ class _LogBolusDetailScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text(_isNew ? 'New Bolus' : 'Edit Bolus'), + title: Text(translate(LocalizationKeys.log_detail_tabs_bolus_detail_title, args: { + "status": _isNew ? LocalizationKeys.log_detail_tabs_bolus_new : LocalizationKeys.general_edit + })), ), drawer: const Navigation(currentLocation: LogBolusDetailScreen.routeName), body: Scrollbar( @@ -544,7 +550,7 @@ class _LogBolusDetailScreenState extends State { contentPadding: const EdgeInsets.only( left: 10.0, right: 10.0, top: 10.0), value: _setManually, - label: 'set manually', + label: translate(LocalizationKeys.log_detail_tabs_bolus_detail_fields_setManually), onChanged: (value) { setState(() { _setManually = value; @@ -559,7 +565,7 @@ class _LogBolusDetailScreenState extends State { children: [ Expanded( child: RadioListTile( - title: const Text('for glucose'), + title: Text(translate(LocalizationKeys.log_detail_tabs_bolus_detail_fields_forGlucose)), groupValue: _bolusType, value: BolusType.glucose, onChanged: (_) { @@ -571,7 +577,7 @@ class _LogBolusDetailScreenState extends State { ), Expanded( child: RadioListTile( - title: const Text('for meal'), + title: Text(translate(LocalizationKeys.log_detail_tabs_bolus_detail_fields_forMeal)), groupValue: _bolusType, value: BolusType.meal, onChanged: (value) { @@ -599,8 +605,8 @@ class _LogBolusDetailScreenState extends State { padding: const EdgeInsets.only(right: 5.0), child: NumberFormField( - label: 'Current', - suffix: 'mg/dl', + label: translate(LocalizationKeys.log_detail_tabs_bolus_detail_fields_current), + suffix: Settings.glucoseMeasurementSuffix, controller: _mgPerDlCurrentController, onChanged: (_) => onChangeGlucose(), @@ -613,8 +619,8 @@ class _LogBolusDetailScreenState extends State { padding: const EdgeInsets.symmetric( horizontal: 5.0), child: NumberFormField( - label: 'Target', - suffix: 'mg/dl', + label: translate(LocalizationKeys.log_detail_tabs_bolus_detail_fields_target), + suffix: Settings.glucoseMeasurementSuffix, controller: _mgPerDlTargetController, onChanged: (_) => onChangeGlucose(), @@ -627,9 +633,9 @@ class _LogBolusDetailScreenState extends State { padding: const EdgeInsets.only(left: 5.0), child: TextFormField( - decoration: const InputDecoration( - labelText: 'Correction', - suffixText: 'mg/dl', + decoration: InputDecoration( + labelText: translate(LocalizationKeys.log_detail_tabs_bolus_detail_fields_correction), + suffixText: Settings.glucoseMeasurementSuffix, ), controller: _mgPerDlCorrectionController, @@ -661,8 +667,8 @@ class _LogBolusDetailScreenState extends State { padding: const EdgeInsets.only( right: 5.0), child: NumberFormField( - label: 'Current', - suffix: 'mmol/l', + label: translate(LocalizationKeys.log_detail_tabs_bolus_detail_fields_current), + suffix: Settings.glucoseMeasurementSuffix, controller: _mmolPerLCurrentController, onChanged: (_) => @@ -676,8 +682,8 @@ class _LogBolusDetailScreenState extends State { padding: const EdgeInsets.symmetric( horizontal: 5.0), child: NumberFormField( - label: 'Target', - suffix: 'mmol/l', + label: translate(LocalizationKeys.log_detail_tabs_bolus_detail_fields_target), + suffix: Settings.glucoseMeasurementSuffix, controller: _mmolPerLTargetController, onChanged: (_) => @@ -691,9 +697,9 @@ class _LogBolusDetailScreenState extends State { padding: const EdgeInsets.only( left: 5.0), child: TextFormField( - decoration: const InputDecoration( - labelText: 'Correction', - suffixText: 'mmol/l', + decoration: InputDecoration( + labelText: translate(LocalizationKeys.log_detail_tabs_bolus_detail_fields_correction), + suffixText: Settings.glucoseMeasurementSuffix, ), controller: _mmolPerLCorrectionController, @@ -713,7 +719,7 @@ class _LogBolusDetailScreenState extends State { child: AutoCompleteDropdownButton( controller: _mealController, selectedItem: _meal, - label: 'Meal', + label: translate(LocalizationKeys.log_detail_tabs_bolus_detail_fields_meal), items: _logMeals, onChanged: onSelectMeal, ), @@ -758,9 +764,9 @@ class _LogBolusDetailScreenState extends State { children: [ Expanded( child: TextFormField( - decoration: const InputDecoration( - labelText: 'Delayed Bolus Duration', - suffixText: ' min', + decoration: InputDecoration( + labelText: translate(LocalizationKeys.log_detail_tabs_bolus_detail_fields_delayedBolusDuration), + suffixText: translate(LocalizationKeys.general_suffixes_mins), ), controller: _delayController, onChanged: (value) => setState(() {}), @@ -789,8 +795,8 @@ class _LogBolusDetailScreenState extends State { child: Padding( padding: const EdgeInsets.only(right: 5.0), child: NumberFormField( - label: 'Immediate Bolus', - suffix: ' U', + label: translate(LocalizationKeys.log_detail_tabs_bolus_detail_fields_immediateBolus), + suffix: translate(LocalizationKeys.general_suffixes_units), controller: _immediateUnitsController, max: double.tryParse(_unitsController.text), step: Settings.insulinSteps, @@ -804,8 +810,8 @@ class _LogBolusDetailScreenState extends State { child: Padding( padding: const EdgeInsets.only(left: 5.0), child: NumberFormField( - label: 'Delayed Bolus', - suffix: ' U', + label: translate(LocalizationKeys.log_detail_tabs_bolus_detail_fields_delayedBolus), + suffix: translate(LocalizationKeys.general_suffixes_units), controller: _delayedUnitsController, max: double.tryParse(_unitsController.text), step: Settings.insulinSteps, diff --git a/lib/screens/log/log_entry/log_bolus_list.dart b/lib/screens/log/log_entry/log_bolus_list.dart index 8ee43e2..6687b9f 100644 --- a/lib/screens/log/log_entry/log_bolus_list.dart +++ b/lib/screens/log/log_entry/log_bolus_list.dart @@ -1,3 +1,4 @@ +import 'package:diameter/localization_keys.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/models/log_bolus.dart'; import 'package:diameter/models/log_entry.dart'; @@ -5,6 +6,7 @@ import 'package:diameter/models/settings.dart'; import 'package:diameter/screens/log/log_entry/log_bolus_detail.dart'; import 'package:diameter/screens/log/log_entry/log_meal_detail.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class LogBolusListScreen extends StatefulWidget { final LogEntry logEntry; @@ -61,7 +63,7 @@ class _LogBolusListScreenState extends State { void onDelete(LogBolus logBolus) { LogBolus.remove(logBolus.id); - reload(message: 'Bolus deleted'); + reload(message: translate(LocalizationKeys.log_detail_tabs_bolus_deleted)); } void handleDeleteAction(LogBolus logBolus) async { @@ -69,7 +71,8 @@ class _LogBolusListScreenState extends State { DialogUtils.showConfirmationDialog( context: context, onConfirm: () => onDelete(logBolus), - message: 'Are you sure you want to delete this Bolus?', + message: + translate(LocalizationKeys.log_detail_tabs_bolus_confirmDelete), ); } else { onDelete(logBolus); @@ -92,17 +95,22 @@ class _LogBolusListScreenState extends State { Widget build(BuildContext context) { return widget.logBoli.isNotEmpty ? Scrollbar( - controller: _scrollController, - child: ListView.builder( - padding: const EdgeInsets.all(10.0), + controller: _scrollController, + child: ListView.builder( + padding: const EdgeInsets.all(10.0), controller: _scrollController, shrinkWrap: true, itemCount: widget.logBoli.length, itemBuilder: (context, index) { final bolus = widget.logBoli[index]; - String titleText = '${bolus.units} U ${(bolus.delay ?? 0) != 0 - ? ' (delayed by ${bolus.delay} min)' - : ''}'; + String titleText = + '${bolus.units} ${translate(LocalizationKeys.general_suffixes_units)} ${(bolus.delay ?? 0) != 0 ? + translate( + LocalizationKeys.log_detail_tabs_bolus_delayedBy, + args: { + "delay": '${bolus.delay} ${translate(LocalizationKeys.general_suffixes_mins)}' + } + ) : ''}'; return Card( child: ListTile( onTap: () => handleEditAction(bolus), @@ -110,8 +118,8 @@ class _LogBolusListScreenState extends State { titleText.toUpperCase(), style: Theme.of(context).textTheme.subtitle2, ), - subtitle: Text(bolus.carbs != null ? - 'for ${(bolus.meal.target ?? '').toString()} (${bolus.carbs}${Settings.nutritionMeasurementSuffix} carbs)' + subtitle: Text(bolus.carbs != null + ? 'for ${(bolus.meal.target ?? '').toString()} (${bolus.carbs}${Settings.nutritionMeasurementSuffix} carbs)' : 'to correct ${Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl ? bolus.mgPerDlCorrection : bolus.mmolPerLCorrection} ${Settings.glucoseMeasurementSuffix}'), trailing: Row( mainAxisSize: MainAxisSize.min, @@ -137,10 +145,10 @@ class _LogBolusListScreenState extends State { ); }, ), - ) - : const Center( - child: Text( - 'You have not added any Boli to this Log Entry yet!'), + ) + : Center( + child: + Text(translate(LocalizationKeys.log_detail_tabs_bolus_empty)), ); } } diff --git a/lib/screens/log/log_entry/log_entry.dart b/lib/screens/log/log_entry/log_entry.dart index 5878ec6..de29409 100644 --- a/lib/screens/log/log_entry/log_entry.dart +++ b/lib/screens/log/log_entry/log_entry.dart @@ -2,6 +2,7 @@ import 'package:diameter/components/detail.dart'; import 'package:diameter/components/forms/date_time_form_field.dart'; import 'package:diameter/components/forms/number_form_field.dart'; import 'package:diameter/components/forms/time_of_day_form_field.dart'; +import 'package:diameter/localization_keys.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/components/forms/form_wrapper.dart'; import 'package:diameter/models/log_bolus.dart'; @@ -18,6 +19,8 @@ import 'package:diameter/utils/utils.dart'; import 'package:flutter/material.dart'; import 'dart:math' as math; +import 'package:flutter_translate/flutter_translate.dart'; + class LogEntryScreen extends StatefulWidget { static const String routeName = '/log-entry'; final int id; @@ -192,7 +195,9 @@ class _LogEntryScreenState extends State { if (close) { Navigator.pop( - context, ['${_isNew ? 'New' : ''} Log Entry Saved', logEntry]); + context, [translate(LocalizationKeys.log_saved, args: { + "status": _isNew ? LocalizationKeys.log_new : '' + }), logEntry]); } else { if (_isNew) { Navigator.push( @@ -202,7 +207,7 @@ class _LogEntryScreenState extends State { ), ).then((result) => Navigator.pop(context, result)); } else { - reload(message: 'Log Entry Saved'); + reload(message: translate(LocalizationKeys.log_saved)); } } } @@ -306,7 +311,7 @@ class _LogEntryScreenState extends State { padding: const EdgeInsets.only(right: 5), child: DateTimeFormField( date: _time, - label: 'Date', + label: translate(LocalizationKeys.log_fields_date), controller: _dateController, onChanged: (newTime) { if (newTime != null) { @@ -329,7 +334,7 @@ class _LogEntryScreenState extends State { padding: const EdgeInsets.only(left: 5), child: TimeOfDayFormField( time: TimeOfDay.fromDateTime(_time), - label: 'Time', + label: translate(LocalizationKeys.log_fields_time), controller: _timeController, onChanged: (newTime) { if (newTime != null) { @@ -363,8 +368,8 @@ class _LogEntryScreenState extends State { ? 2 : 1, child: NumberFormField( - label: 'Blood Glucose', - suffix: 'mg/dl', + label: translate(LocalizationKeys.log_fields_glucose), + suffix: Settings.glucoseMeasurementSuffix, readOnly: Settings.glucoseMeasurement == GlucoseMeasurement.mmolPerL, showSteppers: @@ -388,8 +393,8 @@ class _LogEntryScreenState extends State { ? 2 : 1, child: NumberFormField( - label: 'Blood Glucose', - suffix: 'mmol/l', + label: translate(LocalizationKeys.log_fields_glucose), + suffix: Settings.glucoseMeasurementSuffix, readOnly: Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl, showSteppers: @@ -421,8 +426,8 @@ class _LogEntryScreenState extends State { ), TextFormField( controller: _notesController, - decoration: const InputDecoration( - labelText: 'Notes', + decoration: InputDecoration( + labelText: translate(LocalizationKeys.log_fields_notes), ), keyboardType: TextInputType.multiline, minLines: 2, @@ -444,14 +449,16 @@ class _LogEntryScreenState extends State { return Scaffold( appBar: AppBar( - title: Text(_isNew ? 'New Log Entry' : 'Edit Log Entry'), + title: Text(translate(LocalizationKeys.log_detail_title, args: { + "status": _isNew ? LocalizationKeys.log_new : LocalizationKeys.general_edit + })), bottom: _isNew ? PreferredSize(child: Container(), preferredSize: Size.zero) - : const TabBar( + : TabBar( tabs: [ - Tab(text: 'GENERAL'), - Tab(text: 'MEALS'), - Tab(text: 'BOLI'), + Tab(text: translate(LocalizationKeys.log_detail_tabs_general).toUpperCase()), + Tab(text: translate(LocalizationKeys.log_detail_tabs_meal_title).toUpperCase()), + Tab(text: translate(LocalizationKeys.log_detail_tabs_bolus_title).toUpperCase()), ], ), actions: appBarActions, diff --git a/lib/screens/log/log_entry/log_meal_detail.dart b/lib/screens/log/log_entry/log_meal_detail.dart index 7f188a9..7ab0a9a 100644 --- a/lib/screens/log/log_entry/log_meal_detail.dart +++ b/lib/screens/log/log_entry/log_meal_detail.dart @@ -1,6 +1,7 @@ import 'package:diameter/components/detail.dart'; import 'package:diameter/components/forms/boolean_form_field.dart'; import 'package:diameter/components/forms/number_form_field.dart'; +import 'package:diameter/localization_keys.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/components/forms/auto_complete_dropdown_button.dart'; import 'package:diameter/components/forms/form_wrapper.dart'; @@ -19,6 +20,7 @@ import 'package:diameter/screens/category/meal_source_detail.dart'; import 'package:diameter/screens/meal/meal_detail.dart'; import 'package:diameter/utils/utils.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class LogMealDetailScreen extends StatefulWidget { static const String routeName = '/log-meal'; @@ -230,7 +232,9 @@ class _LogMealDetailScreenState extends State { logMeal.carbsRatioAccuracy.target = _carbsRatioAccuracy; LogMeal.put(logMeal); - Navigator.pop(context, ['${_isNew ? 'New' : ''} Meal Saved', logMeal]); + Navigator.pop(context, [translate(LocalizationKeys.log_detail_tabs_meal_saved, args: { + "status": _isNew ? translate(LocalizationKeys.log_detail_tabs_bolus_new) : '' + }), logMeal]); } setState(() { _isSaving = false; @@ -405,7 +409,9 @@ class _LogMealDetailScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text(_isNew ? 'New Meal for Log Entry' : _logMeal!.value), + title: Text(translate(LocalizationKeys.log_detail_tabs_meal_detail_title, args: { + "status": _isNew ? translate(LocalizationKeys.log_detail_tabs_bolus_new) : LocalizationKeys.general_edit + })), ), drawer: const Navigation(currentLocation: LogMealDetailScreen.routeName), body: Scrollbar( @@ -424,7 +430,7 @@ class _LogMealDetailScreenState extends State { child: AutoCompleteDropdownButton( controller: _mealController, selectedItem: _meal, - label: 'Meal', + label: translate(LocalizationKeys.log_detail_tabs_meal_detail_fields_meal), items: _meals, onChanged: onSelectMeal, ), @@ -449,12 +455,12 @@ class _LogMealDetailScreenState extends State { ), TextFormField( controller: _valueController, - decoration: const InputDecoration( - labelText: 'Name', + decoration: InputDecoration( + labelText: translate(LocalizationKeys.log_detail_tabs_meal_detail_fields_name), ), validator: (value) { if (value!.trim().isEmpty) { - return 'Empty name'; + return translate(LocalizationKeys.log_detail_tabs_meal_detail_fields_validators_name); } return null; }, @@ -465,7 +471,7 @@ class _LogMealDetailScreenState extends State { flex: 10, child: NumberFormField( controller: _amountController, - label: 'Amount', + label: translate(LocalizationKeys.log_detail_tabs_meal_detail_fields_amount), suffix: _mealPortionType?.value, onChanged: updateAmount, ), @@ -524,7 +530,7 @@ class _LogMealDetailScreenState extends State { children: [ Expanded( child: NumberFormField( - label: 'Portion size', + label: translate(LocalizationKeys.log_detail_tabs_meal_detail_fields_portionSize), suffix: Settings.nutritionMeasurementSuffix, controller: _portionSizeController, showSteppers: false, @@ -548,7 +554,7 @@ class _LogMealDetailScreenState extends State { const SizedBox(width: 10), Expanded( child: NumberFormField( - label: 'Carbs ratio', + label: translate(LocalizationKeys.log_detail_tabs_meal_detail_fields_carbsRatio), suffix: '%', controller: _carbsRatioController, showSteppers: false, @@ -570,7 +576,7 @@ class _LogMealDetailScreenState extends State { const SizedBox(width: 10), Expanded( child: NumberFormField( - label: 'Total carbs', + label: translate(LocalizationKeys.log_detail_tabs_meal_detail_fields_totalCarbs), suffix: Settings.nutritionMeasurementSuffix, controller: _totalCarbsController, showSteppers: false, @@ -595,7 +601,7 @@ class _LogMealDetailScreenState extends State { ), BooleanFormField( value: _setManually, - label: 'set carbs ratio manually', + label: translate(LocalizationKeys.log_detail_tabs_meal_detail_fields_setManually), onChanged: (value) { setState(() { _setManually = value; @@ -605,8 +611,8 @@ class _LogMealDetailScreenState extends State { ), TextFormField( controller: _notesController, - decoration: const InputDecoration( - labelText: 'Notes', + decoration: InputDecoration( + labelText: translate(LocalizationKeys.log_detail_tabs_meal_detail_fields_notes), ), keyboardType: TextInputType.multiline, minLines: 2, @@ -621,7 +627,7 @@ class _LogMealDetailScreenState extends State { mainAxisSize: MainAxisSize.max, children: [ Text( - 'ADDITIONAL FIELDS', + translate(LocalizationKeys.log_detail_tabs_meal_detail_additionalFields).toUpperCase(), style: Theme.of(context).textTheme.subtitle2, ), const Spacer(), @@ -642,7 +648,7 @@ class _LogMealDetailScreenState extends State { AutoCompleteDropdownButton( controller: _mealSourceController, selectedItem: _mealSource, - label: 'Meal Source', + label: translate(LocalizationKeys.log_detail_tabs_meal_detail_fields_mealSource), items: _mealSources, onChanged: updateMealSource, ), @@ -677,7 +683,7 @@ class _LogMealDetailScreenState extends State { AutoCompleteDropdownButton( controller: _mealCategoryController, selectedItem: _mealCategory, - label: 'Meal Category', + label: translate(LocalizationKeys.log_detail_tabs_meal_detail_fields_mealCategory), items: _mealCategories, onChanged: updateMealCategory, ), @@ -712,7 +718,7 @@ class _LogMealDetailScreenState extends State { AutoCompleteDropdownButton( controller: _mealPortionTypeController, selectedItem: _mealPortionType, - label: 'Meal Portion Type', + label: translate(LocalizationKeys.log_detail_tabs_meal_detail_fields_mealPortionType), items: _mealPortionTypes, onChanged: updateMealPortionType, ), @@ -747,7 +753,7 @@ class _LogMealDetailScreenState extends State { AutoCompleteDropdownButton( controller: _portionSizeAccuracyController, selectedItem: _portionSizeAccuracy, - label: 'Portion Size Accuracy', + label: translate(LocalizationKeys.log_detail_tabs_meal_detail_fields_portionSizeAccuracy), items: _portionSizeAccuracies, onChanged: updatePortionSizeAccuracy, ), @@ -783,7 +789,7 @@ class _LogMealDetailScreenState extends State { AutoCompleteDropdownButton( controller: _carbsRatioAccuracyController, selectedItem: _carbsRatioAccuracy, - label: 'Carbs Ratio Accuracy', + label: translate(LocalizationKeys.log_detail_tabs_meal_detail_fields_carbsRatioAccuracy), items: _carbsRatioAccuracies, onChanged: updateCarbsRatioAccuracy, ), diff --git a/lib/screens/log/log_entry/log_meal_list.dart b/lib/screens/log/log_entry/log_meal_list.dart index b9a63ec..2d516d4 100644 --- a/lib/screens/log/log_entry/log_meal_list.dart +++ b/lib/screens/log/log_entry/log_meal_list.dart @@ -1,9 +1,11 @@ +import 'package:diameter/localization_keys.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/models/log_entry.dart'; import 'package:diameter/models/log_meal.dart'; import 'package:diameter/models/settings.dart'; import 'package:diameter/screens/log/log_entry/log_meal_detail.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class LogMealListScreen extends StatefulWidget { final LogEntry logEntry; @@ -57,7 +59,7 @@ class _LogMealListScreenState extends State { void onDelete(LogMeal logMeal) { LogMeal.remove(logMeal.id); - reload(message: 'Meal deleted'); + reload(message: translate(LocalizationKeys.log_detail_tabs_meal_deleted)); } void handleDeleteAction(LogMeal meal) async { @@ -65,7 +67,7 @@ class _LogMealListScreenState extends State { DialogUtils.showConfirmationDialog( context: context, onConfirm: () => onDelete(meal), - message: 'Are you sure you want to delete this Meal?', + message: translate(LocalizationKeys.log_detail_tabs_meal_confirmDelete), ); } else { onDelete(meal); @@ -99,7 +101,7 @@ class _LogMealListScreenState extends State { ? [ Text(meal.totalCarbs!.toStringAsPrecision(3)), Text( - '${Settings.nutritionMeasurementSuffix} carbs', + '${Settings.nutritionMeasurementSuffix} ${translate(LocalizationKeys.general_suffixes_carbs)}', textScaleFactor: 0.75), ] : [], @@ -119,9 +121,9 @@ class _LogMealListScreenState extends State { }, ), ) - : const Center( + : Center( child: Text( - 'You have not added any Meals to this Log Entry yet!'), + translate(LocalizationKeys.log_detail_tabs_meal_empty)), ); } } diff --git a/lib/screens/log/log_event/log_event_detail.dart b/lib/screens/log/log_event/log_event_detail.dart index 93ec4ab..434bcc8 100644 --- a/lib/screens/log/log_event/log_event_detail.dart +++ b/lib/screens/log/log_event/log_event_detail.dart @@ -2,6 +2,7 @@ import 'package:diameter/components/detail.dart'; import 'package:diameter/components/forms/boolean_form_field.dart'; import 'package:diameter/components/forms/date_time_form_field.dart'; import 'package:diameter/components/forms/time_of_day_form_field.dart'; +import 'package:diameter/localization_keys.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/components/forms/auto_complete_dropdown_button.dart'; import 'package:diameter/components/forms/form_wrapper.dart'; @@ -15,6 +16,7 @@ import 'package:diameter/screens/basal/basal_profile_detail.dart'; import 'package:diameter/screens/bolus/bolus_profile_detail.dart'; import 'package:diameter/utils/date_time_utils.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class LogEventDetailScreen extends StatefulWidget { static const String routeName = '/log-event'; @@ -194,20 +196,19 @@ class _LogEventDetailScreenState extends State { context: context, builder: (BuildContext context) { return AlertDialog( - content: const Text( - 'An Event of this type is already active within the set time frame. What would you like to do?'), + content: Text(translate(LocalizationKeys.event_warnings_duplicate)), actions: [ TextButton( onPressed: () => Navigator.pop(context, 'DISCARD'), - child: const Text('DISCARD'), + child: Text(translate(LocalizationKeys.general_discard).toUpperCase()), ), TextButton( onPressed: () => Navigator.pop(context, 'EDIT'), - child: const Text('KEEP EDITING'), + child: Text(translate(LocalizationKeys.general_keepEditing).toUpperCase()), ), ElevatedButton( onPressed: () => Navigator.pop(context, 'SAVE'), - child: const Text('SAVE'), + child: Text(translate(LocalizationKeys.general_save).toUpperCase()), ) ], ); @@ -236,7 +237,9 @@ class _LogEventDetailScreenState extends State { event.basalProfile.target = _basalProfile; event.bolusProfile.target = _bolusProfile; LogEvent.put(event); - Navigator.pop(context, ['${_isNew ? 'New' : ''} Event Saved', event]); + Navigator.pop(context, [translate(LocalizationKeys.event_saved, args: { + "status": _isNew ? LocalizationKeys.event_new : "", + }), event]); } void handleSaveAction() async { @@ -276,7 +279,10 @@ class _LogEventDetailScreenState extends State { final now = DateTime.now(); return Scaffold( appBar: AppBar( - title: Text(_isNew ? 'New Event' : 'Edit Event'), + title: Text(translate(LocalizationKeys.event_saved, args: { + "status": _isNew ? LocalizationKeys.event_new : LocalizationKeys.general_edit, + "name": _isNew ? '' : _logEvent?.eventType.target?.value + })), ), drawer: const Navigation(currentLocation: LogEventDetailScreen.routeName), body: Scrollbar( @@ -292,7 +298,7 @@ class _LogEventDetailScreenState extends State { AutoCompleteDropdownButton( controller: _eventTypeController, selectedItem: _eventType, - label: 'Event Type', + label: translate(LocalizationKeys.event_fields_eventType), items: _logEventTypes, onChanged: onSelectEventType, ), @@ -303,7 +309,7 @@ class _LogEventDetailScreenState extends State { padding: const EdgeInsets.only(right: 5), child: DateTimeFormField( date: _time, - label: _hasEndTime ? 'Start Date' : 'Date', + label: translate(_hasEndTime ? LocalizationKeys.event_fields_startDate : LocalizationKeys.event_fields_date), controller: _dateController, onChanged: (newTime) { if (newTime != null) { @@ -322,7 +328,7 @@ class _LogEventDetailScreenState extends State { padding: const EdgeInsets.only(left: 5), child: TimeOfDayFormField( time: TimeOfDay.fromDateTime(_time), - label: _hasEndTime ? 'Start Time' : 'Time', + label: translate(_hasEndTime ? LocalizationKeys.event_fields_startTime : LocalizationKeys.event_fields_time), controller: _timeController, onChanged: (newTime) { if (newTime != null) { @@ -345,7 +351,7 @@ class _LogEventDetailScreenState extends State { _hasEndTime = value; }); }, - label: 'has end time', + label: translate(LocalizationKeys.event_fields_hasEndTime), ), Column( children: _hasEndTime @@ -357,7 +363,7 @@ class _LogEventDetailScreenState extends State { padding: const EdgeInsets.only(right: 5), child: DateTimeFormField( date: _endTime ?? now, - label: 'End Date', + label: translate(LocalizationKeys.event_fields_endDate), controller: _endDateController, onChanged: (newTime) { if (newTime != null) { @@ -381,7 +387,7 @@ class _LogEventDetailScreenState extends State { child: TimeOfDayFormField( time: TimeOfDay.fromDateTime( _endTime ?? now), - label: 'End Time', + label: translate(LocalizationKeys.event_fields_endTime), controller: _endTimeController, onChanged: (newTime) { if (newTime != null) { @@ -408,8 +414,8 @@ class _LogEventDetailScreenState extends State { keyboardType: const TextInputType.numberWithOptions(), decoration: InputDecoration( - labelText: 'Default Reminder Duration', - suffixText: ' min', + labelText: translate(LocalizationKeys.event_fields_reminderDuration), + suffixText: translate(LocalizationKeys.general_suffixes_mins), enabled: _hasEndTime, ), ), @@ -421,7 +427,7 @@ class _LogEventDetailScreenState extends State { BolusProfile>( controller: _bolusProfileController, selectedItem: _bolusProfile, - label: 'Bolus Profile', + label: translate(LocalizationKeys.event_fields_bolusProfile), items: _bolusProfiles, onChanged: updateBolusProfile, ), @@ -457,7 +463,7 @@ class _LogEventDetailScreenState extends State { BasalProfile>( controller: _basalProfileController, selectedItem: _basalProfile, - label: 'Basal Profile', + label: translate(LocalizationKeys.event_fields_basalProfile), items: _basalProfiles, onChanged: updateBasalProfile, ), @@ -489,8 +495,8 @@ class _LogEventDetailScreenState extends State { : []), TextFormField( controller: _notesController, - decoration: const InputDecoration( - labelText: 'Notes', + decoration: InputDecoration( + labelText: translate(LocalizationKeys.event_fields_notes), ), keyboardType: TextInputType.multiline, minLines: 2, diff --git a/lib/screens/log/log_event/log_event_list.dart b/lib/screens/log/log_event/log_event_list.dart index 6ab1dc9..b1ceebd 100644 --- a/lib/screens/log/log_event/log_event_list.dart +++ b/lib/screens/log/log_event/log_event_list.dart @@ -1,3 +1,4 @@ +import 'package:diameter/localization_keys.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/models/log_event.dart'; import 'package:diameter/models/settings.dart'; @@ -5,6 +6,7 @@ import 'package:diameter/screens/log/log_event/log_event_detail.dart'; import 'package:diameter/utils/date_time_utils.dart'; import 'package:flutter/material.dart'; import 'package:diameter/navigation.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class LogEventListScreen extends StatefulWidget { static const String routeName = '/log-events'; @@ -88,7 +90,7 @@ class _LogEventListScreenState extends State { void onDelete(LogEvent logEvent) { LogEvent.remove(logEvent.id); - reload(message: 'Event deleted'); + reload(message: translate(LocalizationKeys.event_deleted)); } void handleDeleteAction(LogEvent logEvent) async { @@ -96,7 +98,7 @@ class _LogEventListScreenState extends State { DialogUtils.showConfirmationDialog( context: context, onConfirm: () => onDelete(logEvent), - message: 'Are you sure you want to delete this Event?', + message: translate(LocalizationKeys.event_confirmDelete), ); } else { onDelete(logEvent); @@ -106,7 +108,7 @@ class _LogEventListScreenState extends State { void onStop(LogEvent event) async { event.endTime = DateTime.now(); LogEvent.put(event); - reload(message: 'Event ended'); + reload(message: translate(LocalizationKeys.event_ended)); } void handleStopAction(LogEvent event) async { @@ -114,8 +116,8 @@ class _LogEventListScreenState extends State { DialogUtils.showConfirmationDialog( context: context, onConfirm: () => onStop(event), - message: 'Are you sure you want to end this Event?', - confirmationLabel: 'END EVENT', + message: translate(LocalizationKeys.event_confirmEnd), + confirmationLabel: translate(LocalizationKeys.event_end), ); } else { onStop(event); @@ -136,7 +138,7 @@ class _LogEventListScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('Log Events'), + title: Text(translate(LocalizationKeys.event_title)), actions: [ IconButton( onPressed: () => onChangeDate(DateTime.now()), @@ -159,7 +161,7 @@ class _LogEventListScreenState extends State { children: [ Expanded( child: Text( - 'ACTIVE EVENTS', + translate(LocalizationKeys.event_titleActive).toUpperCase(), style: Theme.of(context).textTheme.subtitle2, textAlign: TextAlign.center, ), @@ -235,8 +237,8 @@ class _LogEventListScreenState extends State { ); }, ) - : const Center( - child: Text('There are no Active Events!'), + : Center( + child: Text(translate(LocalizationKeys.event_emptyActive)), ), const Padding( padding: EdgeInsets.all(10.0), @@ -351,8 +353,8 @@ class _LogEventListScreenState extends State { ); }, )) - : const Center( - child: Text('There are no Events for that date!'), + : Center( + child: Text(translate(LocalizationKeys.event_empty)), ), ), ], diff --git a/lib/screens/log/log_event/log_event_type_detail.dart b/lib/screens/log/log_event/log_event_type_detail.dart deleted file mode 100644 index eb584c5..0000000 --- a/lib/screens/log/log_event/log_event_type_detail.dart +++ /dev/null @@ -1,304 +0,0 @@ -import 'package:diameter/components/detail.dart'; -import 'package:diameter/components/forms/boolean_form_field.dart'; -import 'package:diameter/components/forms/duration_form_field.dart'; -import 'package:diameter/utils/dialog_utils.dart'; -import 'package:diameter/components/forms/auto_complete_dropdown_button.dart'; -import 'package:diameter/components/forms/form_wrapper.dart'; -import 'package:diameter/models/basal_profile.dart'; -import 'package:diameter/models/bolus_profile.dart'; -import 'package:diameter/models/log_event_type.dart'; -import 'package:diameter/models/settings.dart'; -import 'package:diameter/navigation.dart'; -import 'package:diameter/screens/basal/basal_profile_detail.dart'; -import 'package:diameter/screens/bolus/bolus_profile_detail.dart'; -import 'package:flutter/material.dart'; - -class EventTypeDetailScreen extends StatefulWidget { - static const String routeName = '/log-event-type'; - final int id; - const EventTypeDetailScreen({Key? key, this.id = 0}) : super(key: key); - - @override - _EventTypeDetailScreenState createState() => _EventTypeDetailScreenState(); -} - -class _EventTypeDetailScreenState extends State { - LogEventType? _logEventType; - bool _isNew = true; - bool _isSaving = false; - - List _bolusProfiles = []; - List _basalProfiles = []; - - final GlobalKey _logEventTypeForm = GlobalKey(); - final ScrollController _scrollController = ScrollController(); - - final _valueController = TextEditingController(text: ''); - final _notesController = TextEditingController(text: ''); - - bool _hasEndTime = false; - int _defaultReminderDuration = 0; - BolusProfile? _bolusProfile; - BasalProfile? _basalProfile; - final _bolusProfileController = TextEditingController(text: ''); - final _basalProfileController = TextEditingController(text: ''); - - @override - void initState() { - super.initState(); - - reload(); - - _bolusProfiles = BolusProfile.getAll(); - _basalProfiles = BasalProfile.getAll(); - - if (_logEventType != null) { - _valueController.text = _logEventType!.value; - _defaultReminderDuration = - _logEventType!.defaultReminderDuration ?? 0; - _hasEndTime = _logEventType!.hasEndTime; - _notesController.text = _logEventType!.notes ?? ''; - _basalProfile = _logEventType!.basalProfile.target; - _basalProfileController.text = (_basalProfile ?? '').toString(); - _bolusProfile = _logEventType!.bolusProfile.target; - _bolusProfileController.text = (_bolusProfile ?? '').toString(); - } - } - - @override - void dispose() { - _scrollController.dispose(); - _valueController.dispose(); - _notesController.dispose(); - _bolusProfileController.dispose(); - _basalProfileController.dispose(); - super.dispose(); - } - - void reload({String? message}) { - if (widget.id != 0) { - setState(() { - _logEventType = LogEventType.get(widget.id); - }); - } - _isNew = _logEventType == null; - - setState(() { - if (message != null) { - var snackBar = SnackBar( - content: Text(message), - duration: const Duration(seconds: 2), - ); - ScaffoldMessenger.of(context) - ..removeCurrentSnackBar() - ..showSnackBar(snackBar); - } - }); - } - - void updateBasalProfile(BasalProfile? value) { - setState(() { - _basalProfile = value; - _basalProfileController.text = (_basalProfile ?? '').toString(); - }); - } - - void updateBolusProfile(BolusProfile? value) { - setState(() { - _bolusProfile = value; - _bolusProfileController.text = (_bolusProfile ?? '').toString(); - }); - } - - void handleSaveAction() async { - setState(() { - _isSaving = true; - }); - if (_logEventTypeForm.currentState!.validate()) { - LogEventType eventType = LogEventType( - id: widget.id, - value: _valueController.text, - notes: _notesController.text, - defaultReminderDuration: _defaultReminderDuration, - hasEndTime: _hasEndTime, - ); - eventType.basalProfile.target = _basalProfile; - eventType.bolusProfile.target = _bolusProfile; - LogEventType.put(eventType); - Navigator.pop( - context, ['${_isNew ? 'New' : ''} Log Event Type Saved', eventType]); - } - setState(() { - _isSaving = false; - }); - } - - void handleCancelAction() { - bool isNew = _logEventType == null; - if (Settings.get().showConfirmationDialogOnCancel && - ((isNew && - (_valueController.text != '' || - _defaultReminderDuration != 0 || - _notesController.text != '' || - _hasEndTime)) || - (!isNew && - (_valueController.text != _logEventType!.value || - _defaultReminderDuration != - _logEventType!.defaultReminderDuration || - _notesController.text != (_logEventType!.notes ?? '') || - _hasEndTime != _logEventType!.hasEndTime)))) { - DialogUtils.showCancelConfirmationDialog( - context: context, - isNew: isNew, - onSave: handleSaveAction, - ); - } else { - Navigator.pop(context); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(_isNew ? 'New Log Event Type' : _logEventType!.value), - ), - drawer: - const Navigation(currentLocation: EventTypeDetailScreen.routeName), - body: Scrollbar( - controller: _scrollController, - child: SingleChildScrollView( - controller: _scrollController, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - FormWrapper(formState: _logEventTypeForm, fields: [ - TextFormField( - controller: _valueController, - decoration: const InputDecoration( - labelText: 'Name', - ), - validator: (value) { - if (value!.trim().isEmpty) { - return 'Empty name'; - } - return null; - }, - ), - BooleanFormField( - value: _hasEndTime, - label: 'has end time', - onChanged: (value) { - setState(() { - _hasEndTime = value; - }); - }, - ), - Column( - children: _hasEndTime - ? [ - Padding( - padding: const EdgeInsets.only(bottom: 10.0), - child: DurationFormField( - minutes: _defaultReminderDuration, - label: 'Default Reminder Duration', - onChanged: (value) => _defaultReminderDuration = value ?? 0, - showSteppers: true, - ), - ), - Padding( - padding: const EdgeInsets.only(bottom: 10.0), - child: Row( - children: [ - Expanded( - child: AutoCompleteDropdownButton< - BolusProfile>( - selectedItem: _bolusProfile, - controller: _bolusProfileController, - label: 'Bolus Profile', - items: _bolusProfiles, - onChanged: updateBolusProfile, - ), - ), - IconButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => _bolusProfile == - null - ? const BolusProfileDetailScreen() - : BolusProfileDetailScreen( - id: _bolusProfile!.id), - ), - ).then((result) { - setState(() { - updateBolusProfile(result?[1]); - }); - reload(message: result?[0]); - }); - }, - icon: Icon(_bolusProfile == null - ? Icons.add - : Icons.edit), - ), - ], - ), - ), - Row( - children: [ - Expanded( - child: - AutoCompleteDropdownButton( - controller: _basalProfileController, - selectedItem: _basalProfile, - label: 'Basal Profile', - items: _basalProfiles, - onChanged: updateBasalProfile, - ), - ), - IconButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => _basalProfile == - null - ? const BasalProfileDetailScreen() - : BasalProfileDetailScreen( - id: _basalProfile!.id), - ), - ).then((result) { - updateBasalProfile(result?[1]); - reload(message: result?[0]); - }); - }, - icon: Icon(_basalProfile == null - ? Icons.add - : Icons.edit), - ), - ], - ), - ] - : []), - TextFormField( - controller: _notesController, - decoration: const InputDecoration( - labelText: 'Notes', - ), - keyboardType: TextInputType.multiline, - minLines: 2, - maxLines: 5, - ), - ]), - ], - ), - ), - ), - bottomNavigationBar: DetailBottomRow( - onCancel: handleCancelAction, - onAction: _isSaving ? null : handleSaveAction, - ), - ); - } -} diff --git a/lib/screens/log/log_event/log_event_type_list.dart b/lib/screens/log/log_event/log_event_type_list.dart deleted file mode 100644 index 07be148..0000000 --- a/lib/screens/log/log_event/log_event_type_list.dart +++ /dev/null @@ -1,123 +0,0 @@ -import 'package:diameter/models/log_event_type.dart'; -import 'package:diameter/navigation.dart'; -import 'package:diameter/screens/log/log_event/log_event_type_detail.dart'; -import 'package:flutter/material.dart'; - -class LogEventTypeListScreen extends StatefulWidget { - static const String routeName = '/log-event-types'; - const LogEventTypeListScreen({Key? key}) : super(key: key); - - @override - _LogEventTypeListScreenState createState() => _LogEventTypeListScreenState(); -} - -class _LogEventTypeListScreenState extends State { - List _logEventTypes = []; - - final ScrollController _scrollController = ScrollController(); - - @override - void initState() { - super.initState(); - reload(); - } - - @override - void dispose() { - _scrollController.dispose(); - super.dispose(); - } - - void reload({String? message}) { - setState(() { - _logEventTypes = LogEventType.getAll(); - }); - setState(() { - if (message != null) { - var snackBar = SnackBar( - content: Text(message), - duration: const Duration(seconds: 2), - ); - ScaffoldMessenger.of(context) - ..removeCurrentSnackBar() - ..showSnackBar(snackBar); - } - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Log Event Types'), actions: [ - IconButton(onPressed: reload, icon: const Icon(Icons.refresh)) - ]), - drawer: - const Navigation(currentLocation: LogEventTypeListScreen.routeName), - body: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: _logEventTypes.isNotEmpty - ? Scrollbar( - controller: _scrollController, - child: ListView.builder( - controller: _scrollController, - padding: const EdgeInsets.all(10.0), - itemCount: _logEventTypes.length, - itemBuilder: (context, index) { - final logEventType = _logEventTypes[index]; - return Card( - child: ListTile( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => EventTypeDetailScreen( - id: logEventType.id), - ), - ).then((result) => reload(message: result?[0])); - }, - title: Text( - logEventType.value.toUpperCase(), - style: Theme.of(context).textTheme.subtitle2, - ), - subtitle: Text(logEventType.notes ?? ''), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - onPressed: () async { - LogEventType.remove(logEventType.id); - reload(message: 'Log Event Type deleted'); - }, - icon: const Icon(Icons.delete, - color: Colors.blue), - ) - ], - ), - ), - ); - }, - ), - ) - : const Center( - child: - Text('You have not created any Log Event Types yet!'), - ), - ), - ], - ), - floatingActionButton: FloatingActionButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const EventTypeDetailScreen(), - ), - ).then((result) => reload(message: result?[0])); - }, - child: const Icon(Icons.add), - ), - ); - } -} diff --git a/lib/screens/log/log_filter_dialog.dart b/lib/screens/log/log_filter_dialog.dart new file mode 100644 index 0000000..ed787b8 --- /dev/null +++ b/lib/screens/log/log_filter_dialog.dart @@ -0,0 +1,230 @@ +import 'package:diameter/components/forms/auto_complete_dropdown_button.dart'; +import 'package:diameter/components/forms/date_time_form_field.dart'; +import 'package:diameter/components/forms/number_form_field.dart'; +import 'package:diameter/localization_keys.dart'; +import 'package:diameter/models/meal.dart'; +import 'package:diameter/models/settings.dart'; +import 'package:diameter/utils/date_time_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_translate/flutter_translate.dart'; + +class LogFilterDialog extends StatefulWidget { + static const String routeName = '/log/filter'; + + final DateTime? date; + final void Function( + {DateTime? startDate, + DateTime? endDate, + int? minMgPerDl, + int? maxMgPerDl, + double? minMmolPerL, + double? maxMmolPerL, + int? mealId, + String? mealName, + String? note}) onApplyFilter; + + const LogFilterDialog({Key? key, this.date, required this.onApplyFilter}) + : super(key: key); + + @override + _LogFilterDialogState createState() => _LogFilterDialogState(); +} + +class _LogFilterDialogState extends State { + final ScrollController _scrollController = ScrollController(); + + late DateTime filterStartDate; + late DateTime filterEndDate; + + Meal? meal; + List _meals = []; + + TextEditingController filterStartDateController = + TextEditingController(text: ''); + TextEditingController filterEndDateController = + TextEditingController(text: ''); + TextEditingController mealController = TextEditingController(text: ''); + TextEditingController minMgPerDlController = TextEditingController(text: ''); + TextEditingController maxMgPerDlController = TextEditingController(text: ''); + TextEditingController minMmolPerLController = TextEditingController(text: ''); + TextEditingController maxMmolPerLController = TextEditingController(text: ''); + TextEditingController mealNameController = TextEditingController(text: ''); + TextEditingController noteController = TextEditingController(text: ''); + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + @override + void initState() { + super.initState(); + filterStartDate = widget.date ?? DateTimeUtils.today(); + filterEndDate = DateTimeUtils.today(); + + filterStartDateController.text = DateTimeUtils.displayDate(filterStartDate); + filterEndDateController.text = DateTimeUtils.displayDate(filterEndDate); + + _meals = Meal.getAll(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + children: [ + Expanded( + child: DateTimeFormField( + date: filterStartDate, + label: translate(LocalizationKeys.log_filter_startDate), + controller: filterStartDateController, + onChanged: (newDate) { + if (newDate != null) { + filterStartDate = + DateTime(newDate.year, newDate.month, newDate.day); + filterStartDateController.text = + DateTimeUtils.displayDate(filterStartDate); + } + }, + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 5.0), + child: DateTimeFormField( + date: filterEndDate, + label: translate(LocalizationKeys.log_filter_endDate), + controller: filterEndDateController, + onChanged: (newDate) { + if (newDate != null) { + filterEndDate = DateTime( + newDate.year, newDate.month, newDate.day); + filterEndDateController.text = + DateTimeUtils.displayDate(filterEndDate); + } + }, + ), + ), + ) + ], + ), + Row( + children: Settings.glucoseMeasurement == + GlucoseMeasurement.mgPerDl + ? [ + Expanded( + child: NumberFormField( + label: translate(LocalizationKeys.log_filter_minGlucose, args: { + "glucoseMeasurement": Settings.glucoseMeasurementSuffix + }), + controller: minMgPerDlController, + onChanged: (value) { + if (value != null) { + minMgPerDlController.text = value.toString(); + } + }, + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 5.0), + child: NumberFormField( + label: translate(LocalizationKeys.log_filter_maxGlucose, args: { + "glucoseMeasurement": Settings.glucoseMeasurementSuffix + }), + controller: maxMgPerDlController, + onChanged: (value) { + if (value != null) { + maxMgPerDlController.text = value.toString(); + } + }, + ), + ), + ) + ] + : [ + Expanded( + child: NumberFormField( + label: translate(LocalizationKeys.log_filter_minGlucose, args: { + "glucoseMeasurement": Settings.glucoseMeasurementSuffix + }), + controller: minMmolPerLController, + onChanged: (value) { + if (value != null) { + minMmolPerLController.text = value.toString(); + } + }, + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 5.0), + child: NumberFormField( + label: translate(LocalizationKeys.log_filter_maxGlucose, args: { + "glucoseMeasurement": Settings.glucoseMeasurementSuffix + }), + controller: maxMmolPerLController, + onChanged: (value) { + if (value != null) { + maxMmolPerLController.text = value.toString(); + } + }, + ), + ), + ) + ], + ), + Expanded( + child: AutoCompleteDropdownButton( + controller: mealController, + selectedItem: meal, + label: translate(LocalizationKeys.log_filter_meal), + items: _meals, + onChanged: (value) { + setState(() { + meal = value; + }); + }, + ), + ), + TextFormField( + controller: mealNameController, + decoration: InputDecoration( + labelText: translate(LocalizationKeys.log_filter_mealNameContains), + ), + ), + TextFormField( + controller: noteController, + decoration: InputDecoration( + labelText: translate(LocalizationKeys.log_filter_noteContains), + ), + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(translate(LocalizationKeys.general_cancel).toUpperCase()), + ), + ElevatedButton( + onPressed: () => widget.onApplyFilter( + startDate: filterStartDate, + endDate: filterEndDate, + minMgPerDl: int.tryParse(minMgPerDlController.text), + maxMgPerDl: int.tryParse(maxMgPerDlController.text), + minMmolPerL: double.tryParse(minMmolPerLController.text), + maxMmolPerL: double.tryParse(maxMmolPerLController.text), + mealId: meal?.id, + mealName: mealNameController.text, + note: noteController.text, + ), + child: Text(translate(LocalizationKeys.general_apply).toUpperCase()), + ), + ]); + } +} diff --git a/lib/screens/log/log_overview.dart b/lib/screens/log/log_overview.dart index 6330d76..5c898ec 100644 --- a/lib/screens/log/log_overview.dart +++ b/lib/screens/log/log_overview.dart @@ -1,3 +1,5 @@ +import 'package:diameter/localization_keys.dart'; +import 'package:diameter/screens/log/log_filter_dialog.dart'; import 'package:diameter/screens/reports/export.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/models/glucose_target.dart'; @@ -11,6 +13,9 @@ import 'package:diameter/utils/date_time_utils.dart'; import 'package:flutter/material.dart'; import 'dart:math' as math; +import 'package:flutter_translate/flutter_translate.dart'; + + class LogScreen extends StatefulWidget { static const String routeName = '/log'; const LogScreen({Key? key}) : super(key: key); @@ -65,7 +70,7 @@ class _LogScreenState extends State { void onDelete(LogEntry logEntry) { LogEntry.remove(logEntry.id); - reload(message: 'Log Entry deleted'); + reload(message: translate(LocalizationKeys.log_deleted)); } void handleDeleteAction(LogEntry logEntry) async { @@ -73,7 +78,7 @@ class _LogScreenState extends State { DialogUtils.showConfirmationDialog( context: context, onConfirm: () => onDelete(logEntry), - message: 'Are you sure you want to delete this Log Entry?', + message: translate(LocalizationKeys.log_confirmDelete), ); } else { onDelete(logEntry); @@ -90,15 +95,48 @@ class _LogScreenState extends State { } } + void onChangeFilter( + {DateTime? startDate, + DateTime? endDate, + int? minMgPerDl, + int? maxMgPerDl, + double? minMmolPerL, + double? maxMmolPerL, + int? mealId, + String? mealName, + String? note}) { + setState(() { + _logEntries = LogEntry.getAllByFilter( + startDate: startDate, + endDate: endDate, + minMgPerDl: minMgPerDl, + maxMgPerDl: maxMgPerDl, + minMmolPerL: minMmolPerL, + maxMmolPerL: maxMmolPerL, + mealId: mealId, + mealName: mealName, + note: note, + ); + }); + } + @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('Log Entries'), + title: Text(translate(LocalizationKeys.log_title)), actions: [ IconButton( onPressed: () => onChangeDate(DateTime.now()), icon: const Icon(Icons.today)), + IconButton( + onPressed: () => showDialog( + context: context, + builder: (context) => LogFilterDialog( + date: _date, + onApplyFilter: onChangeFilter, + )), + icon: const Icon(Icons.filter_list)), IconButton( onPressed: () => showDialog( context: context, @@ -297,7 +335,7 @@ class _LogScreenState extends State { ? [ Text( bolus.toStringAsPrecision(3)), - const Text('U', + Text(translate(LocalizationKeys.general_suffixes_units), textScaleFactor: 0.75), ] : [], @@ -309,9 +347,13 @@ class _LogScreenState extends State { ? [ Text( carbs.toStringAsPrecision(3)), - Text( - '${Settings.nutritionMeasurementSuffix} carbs', - textScaleFactor: 0.75), + Text(translate( + LocalizationKeys.general_suffixes_carbs, + args: { + "nutritionMeasurementSuffix": Settings.nutritionMeasurementSuffix, + } + ), + textScaleFactor: 0.75), ] : [], ), @@ -334,9 +376,10 @@ class _LogScreenState extends State { }, ), ) - : const Center( + : Center( child: Text( - 'You have not created any Log Entries for this date yet!'), + translate(LocalizationKeys.log_empty) + ), ), ), ], diff --git a/lib/screens/meal/meal_detail.dart b/lib/screens/meal/meal_detail.dart index cf4a030..c2b03b6 100644 --- a/lib/screens/meal/meal_detail.dart +++ b/lib/screens/meal/meal_detail.dart @@ -1,6 +1,7 @@ import 'package:diameter/components/detail.dart'; import 'package:diameter/components/forms/boolean_form_field.dart'; import 'package:diameter/components/forms/number_form_field.dart'; +import 'package:diameter/localization_keys.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/components/forms/auto_complete_dropdown_button.dart'; import 'package:diameter/components/forms/form_wrapper.dart'; @@ -17,6 +18,7 @@ import 'package:diameter/screens/category/meal_portion_type_detail.dart'; import 'package:diameter/screens/category/meal_source_detail.dart'; import 'package:diameter/utils/utils.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class MealDetailScreen extends StatefulWidget { static const String routeName = '/meal'; @@ -195,7 +197,9 @@ class _MealDetailScreenState extends State { meal.carbsRatioAccuracy.target = _carbsRatioAccuracy; Meal.put(meal); - Navigator.pop(context, ['${_isNew ? 'New' : ''} Meal Saved', meal]); + Navigator.pop(context, [translate(LocalizationKeys.meal_saved, args: { + "status": _isNew ? translate(LocalizationKeys.meal_new) : '', + }), meal]); } setState(() { _isSaving = false; @@ -340,7 +344,10 @@ class _MealDetailScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text(_isNew ? 'New Meal' : _meal!.value), + title: Text(translate(LocalizationKeys.meal_saved, args: { + "status": _isNew ? translate(LocalizationKeys.meal_new) : '', + "name": _meal?.value, + })), ), drawer: const Navigation(currentLocation: MealDetailScreen.routeName), body: Scrollbar( @@ -354,12 +361,12 @@ class _MealDetailScreenState extends State { fields: [ TextFormField( controller: _valueController, - decoration: const InputDecoration( - labelText: 'Name', + decoration: InputDecoration( + labelText: translate(LocalizationKeys.meal_fields_name), ), validator: (value) { if (value!.trim().isEmpty) { - return 'Empty name'; + return translate(LocalizationKeys.meal_fields_validators_name); } return null; }, @@ -370,7 +377,7 @@ class _MealDetailScreenState extends State { child: AutoCompleteDropdownButton( controller: _mealSourceController, selectedItem: _mealSource, - label: 'Meal Source', + label: translate(LocalizationKeys.meal_fields_mealSource), items: _mealSources, onChanged: onSelectMealSource, ), @@ -400,7 +407,7 @@ class _MealDetailScreenState extends State { child: AutoCompleteDropdownButton( controller: _mealPortionTypeController, selectedItem: _mealPortionType, - label: 'Meal Portion Type', + label: translate(LocalizationKeys.meal_fields_mealPortionType), items: _mealPortionTypes, onChanged: updateMealPortionType, ), @@ -429,7 +436,7 @@ class _MealDetailScreenState extends State { children: [ Expanded( child: NumberFormField( - label: 'Carbs ratio', + label: translate(LocalizationKeys.meal_fields_carbsRatio), suffix: '%', controller: _carbsRatioController, showSteppers: false, @@ -445,7 +452,7 @@ class _MealDetailScreenState extends State { const SizedBox(width: 10), Expanded( child: NumberFormField( - label: 'Portion size', + label: translate(LocalizationKeys.meal_fields_portionSize), suffix: Settings.nutritionMeasurementSuffix, controller: _portionSizeController, showSteppers: false, @@ -461,7 +468,7 @@ class _MealDetailScreenState extends State { const SizedBox(width: 10), Expanded( child: NumberFormField( - label: 'Carbs per portion', + label: translate(LocalizationKeys.meal_fields_carbsPerPortion), suffix: Settings.nutritionMeasurementSuffix, controller: _carbsPerPortionController, showSteppers: false, @@ -479,7 +486,7 @@ class _MealDetailScreenState extends State { Expanded( child: BooleanFormField( value: _setManually, - label: 'set carbs ratio manually', + label: translate(LocalizationKeys.meal_fields_setManually), onChanged: (value) { setState(() { _setManually = value; @@ -490,8 +497,8 @@ class _MealDetailScreenState extends State { ), TextFormField( controller: _notesController, - decoration: const InputDecoration( - labelText: 'Notes', + decoration: InputDecoration( + labelText: translate(LocalizationKeys.meal_fields_notes), ), keyboardType: TextInputType.multiline, minLines: 2, @@ -503,7 +510,7 @@ class _MealDetailScreenState extends State { child: Row( children: [ Text( - 'BOLUS DELAY', + translate(LocalizationKeys.meal_fields_delay_title).toUpperCase(), style: Theme.of(context).textTheme.subtitle2, ), const Spacer(), @@ -514,9 +521,9 @@ class _MealDetailScreenState extends State { children: [ Expanded( child: TextFormField( - decoration: const InputDecoration( - labelText: 'Duration', - suffixText: ' min', + decoration: InputDecoration( + labelText: translate(LocalizationKeys.meal_fields_delay_duration), + suffixText: translate(LocalizationKeys.general_suffixes_mins), ), controller: _delayedBolusDurationController, onChanged: (value) => setState(() {}), @@ -553,7 +560,7 @@ class _MealDetailScreenState extends State { children: [ Expanded( child: Text( - 'ADDITIONAL FIELDS', + translate(LocalizationKeys.meal_fields_additional_title).toUpperCase(), style: Theme.of(context).textTheme.subtitle2, ), ), @@ -574,7 +581,7 @@ class _MealDetailScreenState extends State { AutoCompleteDropdownButton( controller: _mealCategoryController, selectedItem: _mealCategory, - label: 'Meal Category', + label: translate(LocalizationKeys.meal_fields_additional_mealCategory), items: _mealCategories, onChanged: updateMealCategory, ), @@ -609,7 +616,7 @@ class _MealDetailScreenState extends State { AutoCompleteDropdownButton( controller: _portionSizeAccuracyController, selectedItem: _portionSizeAccuracy, - label: 'Portion Size Accuracy', + label: translate(LocalizationKeys.meal_fields_additional_portionSizeAccuracy), items: _portionSizeAccuracies, onChanged: updatePortionSizeAccuracy, ), @@ -645,7 +652,7 @@ class _MealDetailScreenState extends State { AutoCompleteDropdownButton( controller: _carbsRatioAccuracyController, selectedItem: _carbsRatioAccuracy, - label: 'Carbs Ratio Accuracy', + label: translate(LocalizationKeys.meal_fields_additional_carbsRatioAccuracy), items: _carbsRatioAccuracies, onChanged: updateCarbsRatioAccuracy, ), diff --git a/lib/screens/meal/meal_list.dart b/lib/screens/meal/meal_list.dart index 579b77b..9b7def4 100644 --- a/lib/screens/meal/meal_list.dart +++ b/lib/screens/meal/meal_list.dart @@ -1,9 +1,11 @@ +import 'package:diameter/localization_keys.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/models/meal.dart'; import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:diameter/screens/meal/meal_detail.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class MealListScreen extends StatefulWidget { static const String routeName = '/meals'; @@ -50,7 +52,7 @@ class _MealListScreenState extends State { void onDelete(Meal meal) { Meal.remove(meal.id); - reload(message: 'Meal deleted'); + reload(message: translate(LocalizationKeys.meal_deleted)); } void handleDeleteAction(Meal meal) async { @@ -58,7 +60,7 @@ class _MealListScreenState extends State { DialogUtils.showConfirmationDialog( context: context, onConfirm: () => onDelete(meal), - message: 'Are you sure you want to delete this Meal?', + message: translate(LocalizationKeys.meal_confirmDelete), ); } else { onDelete(meal); @@ -68,8 +70,12 @@ class _MealListScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('Meals'), actions: [ - IconButton(onPressed: reload, icon: const Icon(Icons.refresh)) + appBar: AppBar( + title: Text( + translate(LocalizationKeys.meal_title) + ), + actions: [ + IconButton(onPressed: reload, icon: const Icon(Icons.refresh)) ]), drawer: const Navigation(currentLocation: MealListScreen.routeName), body: Column( @@ -84,7 +90,7 @@ class _MealListScreenState extends State { itemCount: _meals.length, itemBuilder: (context, index) { final meal = _meals[index]; - String portionType = meal.mealPortionType.hasValue ? ' per ${meal.mealPortionType.target!.value}' : ''; + String portionType = meal.mealPortionType.hasValue ? ' ${translate(LocalizationKeys.general_per)} ${meal.mealPortionType.target!.value}' : ''; return Card( child: ListTile( isThreeLine: true, @@ -118,7 +124,7 @@ class _MealListScreenState extends State { ? [ Text(meal.carbsPerPortion!.toStringAsPrecision(3)), Text( - '${Settings.nutritionMeasurementSuffix} carbs', + '${Settings.nutritionMeasurementSuffix} ${translate(LocalizationKeys.general_suffixes_carbs)}', textScaleFactor: 0.75), ] : [], @@ -166,8 +172,8 @@ class _MealListScreenState extends State { ); }, ), - ): const Center( - child: Text('You have not created any Meals yet!'), + ): Center( + child: Text(translate(LocalizationKeys.meal_empty)), ), ), ], diff --git a/lib/screens/reports/daily_chart.dart b/lib/screens/reports/daily_chart.dart index b3404ec..efc6b15 100644 --- a/lib/screens/reports/daily_chart.dart +++ b/lib/screens/reports/daily_chart.dart @@ -1,4 +1,5 @@ import 'package:charts_flutter/flutter.dart' as charts; +import 'package:diameter/localization_keys.dart'; import 'package:diameter/models/basal.dart'; import 'package:diameter/models/glucose_target.dart'; import 'package:diameter/models/log_bolus.dart'; @@ -9,6 +10,7 @@ import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:diameter/utils/date_time_utils.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_translate/flutter_translate.dart'; charts.TimeSeriesChart generateChart( DateTime date, @@ -48,7 +50,7 @@ charts.TimeSeriesChart generateChart( return charts.TimeSeriesChart( [ charts.Series( - id: 'Glucose', + id: translate(LocalizationKeys.reports_ids_glucose), colorFn: (LogEntry entry, _) => charts.Color.black, domainFn: (LogEntry entry, _) => entry.time, measureFn: (LogEntry entry, _) => @@ -58,7 +60,7 @@ charts.TimeSeriesChart generateChart( data: logEntries, )..setAttribute(charts.rendererIdKey, 'glucoseRenderer'), charts.Series( - id: 'Carbohydrates', + id: translate(LocalizationKeys.reports_ids_carbs), colorFn: (LogEntry entry, _) => charts.MaterialPalette.yellow.shadeDefault, domainFn: (LogEntry entry, _) => entry.time, @@ -67,7 +69,7 @@ charts.TimeSeriesChart generateChart( data: showMeals ? logEntries : [], )..setAttribute(charts.rendererIdKey, 'carbsRenderer'), charts.Series( - id: 'Basal', + id: translate(LocalizationKeys.reports_ids_basal), colorFn: (Basal basal, _) => charts.Color.black.lighter, domainFn: (Basal basal, _) => basal.startTime, measureFn: (Basal basal, _) => basal.units, @@ -77,7 +79,7 @@ charts.TimeSeriesChart generateChart( charts.measureAxisIdKey, charts.Axis.secondaryMeasureAxisId) ..setAttribute(charts.rendererIdKey, 'basalRenderer'), charts.Series( - id: 'Bolus', + id: translate(LocalizationKeys.reports_ids_bolus), colorFn: (LogEntry entry, _) => charts.MaterialPalette.blue.shadeDefault, domainFn: (LogEntry entry, _) => entry.time, @@ -260,7 +262,7 @@ class _DailyChartState extends State { value: showChart, onChanged: (_) => setState(() => showChart = !showChart), - title: const Text('show Chart'), + title: Text(translate(LocalizationKeys.reports_dailyCharts_showChart)), controlAffinity: ListTileControlAffinity.leading, ), Padding( @@ -271,7 +273,7 @@ class _DailyChartState extends State { ? (_) => setState(() => showBolus = !showBolus) : null, - title: const Text('show Bolus'), + title: Text(translate(LocalizationKeys.reports_dailyCharts_showBolus)), controlAffinity: ListTileControlAffinity.leading, ), @@ -284,7 +286,7 @@ class _DailyChartState extends State { ? (_) => setState(() => showBasal = !showBasal) : null, - title: const Text('show Basal'), + title: Text(translate(LocalizationKeys.reports_dailyCharts_showBasal)), controlAffinity: ListTileControlAffinity.leading, ), @@ -297,7 +299,7 @@ class _DailyChartState extends State { ? (_) => setState(() => showMeals = !showMeals) : null, - title: const Text('show Meals'), + title: Text(translate(LocalizationKeys.reports_dailyCharts_showMeals)), controlAffinity: ListTileControlAffinity.leading, ), @@ -307,7 +309,7 @@ class _DailyChartState extends State { actions: [ ElevatedButton( onPressed: () => Navigator.pop(context), - child: const Text('CLOSE'), + child: Text(translate(LocalizationKeys.general_close).toUpperCase()), ), ]), ), @@ -378,9 +380,8 @@ class _DailyChartState extends State { child: logEntries.isNotEmpty ? generateChart(date, logEntries, logEvents, targets, showMeals, showBasal, showBolus) - : const Center( - child: Text( - 'You have not created any Log Entries for this date yet!'), + : Center( + child: Text(translate(LocalizationKeys.reports_dailyCharts_empty)), ), ), ], diff --git a/lib/screens/reports/export.dart b/lib/screens/reports/export.dart index 29226e5..a44bae5 100644 --- a/lib/screens/reports/export.dart +++ b/lib/screens/reports/export.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'dart:math'; import 'dart:typed_data'; import 'package:diameter/components/forms/date_time_form_field.dart'; +import 'package:diameter/localization_keys.dart'; import 'package:diameter/models/glucose_target.dart'; import 'package:diameter/models/log_bolus.dart'; import 'package:diameter/models/log_entry.dart'; @@ -12,6 +13,7 @@ import 'package:diameter/screens/reports/daily_chart.dart'; import 'package:diameter/utils/date_time_utils.dart'; import 'package:diameter/utils/utils.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_translate/flutter_translate.dart'; import 'package:intl/intl.dart'; import 'package:open_file/open_file.dart'; import 'package:path_provider/path_provider.dart'; @@ -121,7 +123,7 @@ class _ExportDialogState extends State { children: [ Expanded( child: RadioListTile( - title: const Text('single date'), + title: Text(translate(LocalizationKeys.reports_export_singleDate)), groupValue: exportRange, value: false, onChanged: (_) { @@ -132,7 +134,7 @@ class _ExportDialogState extends State { ), Expanded( child: RadioListTile( - title: const Text('range'), + title: Text(translate(LocalizationKeys.reports_export_range)), groupValue: exportRange, value: true, onChanged: (value) { @@ -148,7 +150,7 @@ class _ExportDialogState extends State { Expanded( child: DateTimeFormField( date: exportStartDate, - label: 'Date', + label: translate(LocalizationKeys.reports_export_date), controller: exportStartDateController, onChanged: (newDate) { if (newDate != null) { @@ -166,7 +168,7 @@ class _ExportDialogState extends State { padding: const EdgeInsets.only(left: 5.0), child: DateTimeFormField( date: exportEndDate, - label: 'End Date', + label: translate(LocalizationKeys.reports_export_endDate), controller: exportEndDateController, onChanged: (newDate) { if (newDate != null) { @@ -185,7 +187,7 @@ class _ExportDialogState extends State { CheckboxListTile( value: showChart, onChanged: (_) => setState(() => showChart = !showChart), - title: const Text('show Chart'), + title: Text(translate(LocalizationKeys.reports_export_showChart)), controlAffinity: ListTileControlAffinity.leading, ), Padding( @@ -195,7 +197,7 @@ class _ExportDialogState extends State { onChanged: showChart ? (_) => setState(() => showBolus = !showBolus) : null, - title: const Text('show Bolus'), + title: Text(translate(LocalizationKeys.reports_export_showBolus)), controlAffinity: ListTileControlAffinity.leading, ), ), @@ -206,7 +208,7 @@ class _ExportDialogState extends State { onChanged: showChart ? (_) => setState(() => showBasal = !showBasal) : null, - title: const Text('show Basal'), + title: Text(translate(LocalizationKeys.reports_export_showBasal)), controlAffinity: ListTileControlAffinity.leading, ), ), @@ -217,14 +219,14 @@ class _ExportDialogState extends State { onChanged: showChart ? (_) => setState(() => showMeals = !showMeals) : null, - title: const Text('show Meals'), + title: Text(translate(LocalizationKeys.reports_export_showMeals)), controlAffinity: ListTileControlAffinity.leading, ), ), CheckboxListTile( value: showTable, onChanged: (_) => setState(() => showTable = !showTable), - title: const Text('show Table'), + title: Text(translate(LocalizationKeys.reports_export_showTable)), controlAffinity: ListTileControlAffinity.leading, ), ], @@ -232,13 +234,13 @@ class _ExportDialogState extends State { actions: [ TextButton( onPressed: () => Navigator.pop(context), - child: const Text('CANCEL'), + child: Text(translate(LocalizationKeys.general_cancel).toUpperCase()), ), ElevatedButton( onPressed: !_isSaving && (showChart || showTable) ? () => onExport(context) : null, - child: const Text('EXPORT'), + child: Text(translate(LocalizationKeys.reports_export_export).toUpperCase()), ), ]); } @@ -273,15 +275,14 @@ pw.Table generateTable(List logEntries) { const small = pw.TextStyle(fontSize: 8.0); final tableHeaders = [ - 'Time', - 'Glucose', - 'Bolus', - 'Notes', - 'Meals', - 'Portion Size', - 'Carbohydrates', - 'Meal Bolus', - 'Meal Notes', + translate(LocalizationKeys.reports_export_tableHeaders_time), + translate(LocalizationKeys.reports_export_tableHeaders_glucose), + translate(LocalizationKeys.reports_export_tableHeaders_notes), + translate(LocalizationKeys.reports_export_tableHeaders_meals), + translate(LocalizationKeys.reports_export_tableHeaders_portionSize), + translate(LocalizationKeys.reports_export_tableHeaders_carbs), + translate(LocalizationKeys.reports_export_tableHeaders_mealBolus), + translate(LocalizationKeys.reports_export_tableHeaders_mealNotes), ]; final List data = logEntries.map((logEntry) { @@ -304,7 +305,9 @@ pw.Table generateTable(List logEntries) { for (var bolus in boli .where((bolus) => bolus.meal.targetId == meal.id) .toList()) - '${bolus.units} U${bolus.delay != null ? ' (over ${bolus.delay} min)' : ''}' + '${bolus.units} ${translate(LocalizationKeys.general_suffixes_units)} ${bolus.delay != null ? translate(LocalizationKeys.log_detail_tabs_bolus_delayedBy, args: { + "delay": '${bolus.delay} ${translate(LocalizationKeys.general_suffixes_mins)}' + }) : ''}' ].join(' + '), notes: meal.notes ?? '', )) @@ -320,7 +323,9 @@ pw.Table generateTable(List logEntries) { bolus.meal.targetId != 0 && !meals.any((meal) => meal.id == bolus.meal.targetId)) .map((bolus) => - '${bolus.units} U${bolus.delay != null ? ' (over ${bolus.delay} min)' : ''}${bolus.meal.target != null ? ' (for ${bolus.meal.target!.value})' : ''}') + '${bolus.units} ${translate(LocalizationKeys.general_suffixes_units)} ${bolus.delay != null ? translate(LocalizationKeys.log_detail_tabs_bolus_delayedBy, args: { + "delay": '${bolus.delay} ${translate(LocalizationKeys.general_suffixes_mins)}' + }) : ''}') .toList(); return pw.TableRow( @@ -358,7 +363,7 @@ pw.Table generateTable(List logEntries) { ), // Bolus pw.Text( - [for (var bolus in glucoseBoli) '${bolus.units} U'].join(' + ')), + [for (var bolus in glucoseBoli) '${bolus.units} ${translate(LocalizationKeys.general_suffixes_units)}'].join(' + ')), // Notes pw.Text(logEntry.notes ?? '', style: small), // Meals @@ -449,7 +454,7 @@ Future generateLogReport( return pw.Column( children: [ pw.Text( - 'LOG REPORT', + translate(LocalizationKeys.reports_export_title).toUpperCase(), style: pw.TextStyle( fontWeight: pw.FontWeight.bold, fontSize: 12, diff --git a/lib/screens/reports/reports.dart b/lib/screens/reports/reports.dart index c74837b..89808ee 100644 --- a/lib/screens/reports/reports.dart +++ b/lib/screens/reports/reports.dart @@ -1,6 +1,8 @@ +import 'package:diameter/localization_keys.dart'; import 'package:diameter/navigation.dart'; import 'package:diameter/screens/reports/export.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class ReportsOverviewScreen extends StatefulWidget { static const String routeName = '/reports'; @@ -23,7 +25,7 @@ class _ReportsOverviewScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('Reports'), + title: Text(translate(LocalizationKeys.reports_title)), ), drawer: const Navigation(currentLocation: ReportsOverviewScreen.routeName), @@ -47,7 +49,7 @@ class _ReportsOverviewScreenState extends State { child: Icon(Icons.today, size: 50, color: Theme.of(context).textTheme.subtitle2?.color), ), Text( - 'DAILY REPORT', + translate(LocalizationKeys.reports_sections_dailyReport).toUpperCase(), style: Theme.of(context).textTheme.subtitle2, textAlign: TextAlign.center ), @@ -72,7 +74,7 @@ class _ReportsOverviewScreenState extends State { child: Icon(Icons.today, size: 50, color: Theme.of(context).textTheme.subtitle2?.color), ), Text( - 'PDF REPORT', + translate(LocalizationKeys.reports_sections_pdfReport).toUpperCase(), style: Theme.of(context).textTheme.subtitle2, textAlign: TextAlign.center ), diff --git a/lib/screens/recipe/recipe_detail.dart b/lib/screens/x_recipe/recipe_detail.dart similarity index 99% rename from lib/screens/recipe/recipe_detail.dart rename to lib/screens/x_recipe/recipe_detail.dart index 6c08173..bcd2f12 100644 --- a/lib/screens/recipe/recipe_detail.dart +++ b/lib/screens/x_recipe/recipe_detail.dart @@ -2,9 +2,9 @@ import 'package:diameter/components/detail.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/components/forms/auto_complete_dropdown_button.dart'; import 'package:diameter/components/forms/form_wrapper.dart'; -import 'package:diameter/models/ingredient.dart'; +import 'package:diameter/models/x_ingredient.dart'; import 'package:diameter/models/meal.dart'; -import 'package:diameter/models/recipe.dart'; +import 'package:diameter/models/x_recipe.dart'; import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:diameter/screens/meal/meal_detail.dart'; diff --git a/lib/screens/recipe/recipe_list.dart b/lib/screens/x_recipe/recipe_list.dart similarity index 98% rename from lib/screens/recipe/recipe_list.dart rename to lib/screens/x_recipe/recipe_list.dart index b4de28f..2a91707 100644 --- a/lib/screens/recipe/recipe_list.dart +++ b/lib/screens/x_recipe/recipe_list.dart @@ -1,9 +1,9 @@ +import 'package:diameter/screens/x_recipe/recipe_detail.dart'; import 'package:diameter/utils/dialog_utils.dart'; -import 'package:diameter/models/ingredient.dart'; -import 'package:diameter/models/recipe.dart'; +import 'package:diameter/models/x_ingredient.dart'; +import 'package:diameter/models/x_recipe.dart'; import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; -import 'package:diameter/screens/recipe/recipe_detail.dart'; import 'package:flutter/material.dart'; class RecipeListScreen extends StatefulWidget { diff --git a/lib/settings.dart b/lib/settings.dart index b875e88..ea8f505 100644 --- a/lib/settings.dart +++ b/lib/settings.dart @@ -1,11 +1,13 @@ import 'package:diameter/components/forms/boolean_form_field.dart'; import 'package:diameter/components/forms/number_form_field.dart'; +import 'package:diameter/localization_keys.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/components/forms/auto_complete_dropdown_button.dart'; import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:diameter/utils/utils.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_translate/flutter_translate.dart'; import 'package:intl/intl.dart'; class SettingsScreen extends StatefulWidget { @@ -152,19 +154,19 @@ class _SettingsScreenState extends State { showConfirmationDialogOnDelete: _showConfirmationDialogOnDelete, showConfirmationDialogOnStopEvent: _showConfirmationDialogOnStopEvent, )); - reload(message: 'Settings updated'); + reload(message: translate(LocalizationKeys.settings_updated)); } void onReset() { Settings.reset(); - reload(message: 'Settings have been reset to default'); + reload(message: translate(LocalizationKeys.settings_reset)); } void handleResetAction() async { DialogUtils.showConfirmationDialog( context: context, onConfirm: onReset, - message: 'Are you sure you want to reset all settings?', + message: translate(LocalizationKeys.settings_confirmReset), ); } @@ -172,7 +174,7 @@ class _SettingsScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('Application Settings'), + title: Text(translate(LocalizationKeys.settings_title)), ), drawer: const Navigation(currentLocation: SettingsScreen.routeName), body: SingleChildScrollView( @@ -191,7 +193,7 @@ class _SettingsScreenState extends State { children: [ Expanded( child: Text( - 'MEASUREMENTS', + translate(LocalizationKeys.settings_sections_measurements).toUpperCase(), style: Theme.of(context).textTheme.subtitle2, ), ), @@ -208,7 +210,7 @@ class _SettingsScreenState extends State { AutoCompleteDropdownButton( controller: _nutritionMeasurementLabelController, selectedItem: _nutritionMeasurementLabelController.text, - label: 'Preferred Nutrition Measurement', + label: translate(LocalizationKeys.settings_fields_nutritionMeasurement), items: nutritionMeasurementLabels, onChanged: (value) { _nutritionMeasurementLabelController.text = @@ -221,7 +223,7 @@ class _SettingsScreenState extends State { child: AutoCompleteDropdownButton( controller: _glucoseMeasurementLabelController, selectedItem: _glucoseMeasurementLabelController.text, - label: 'Preferred Glucose Measurement', + label: translate(LocalizationKeys.settings_fields_glucoseMeasurement), items: glucoseMeasurementLabels, onChanged: (value) { _glucoseMeasurementLabelController.text = @@ -232,8 +234,8 @@ class _SettingsScreenState extends State { ), Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl ? NumberFormField( - label: 'Target glucose', - suffix: 'mg/dl', + label: translate(LocalizationKeys.settings_fields_targetGlucose), + suffix: Settings.glucoseMeasurementSuffix, controller: _targetGlucoseMgPerDlController, showSteppers: false, onChanged: (_) async { @@ -254,8 +256,8 @@ class _SettingsScreenState extends State { }, ) : NumberFormField( - label: 'Target glucose', - suffix: 'mmol/l', + label: translate(LocalizationKeys.settings_fields_targetGlucose), + suffix: Settings.glucoseMeasurementSuffix, controller: _targetGlucoseMmolPerLController, showSteppers: false, onChanged: (_) async { @@ -277,7 +279,7 @@ class _SettingsScreenState extends State { child: NumberFormField( controller: _insulinIncrementsController, showSteppers: false, - label: 'Insulin increment', + label: translate(LocalizationKeys.settings_fields_insulinIncrement), onChanged: (value) { _insulinIncrementsController.text = (value ?? 0).toString(); @@ -287,7 +289,7 @@ class _SettingsScreenState extends State { NumberFormField( controller: _nutritionIncrementsController, showSteppers: false, - label: 'Nutrition increment', + label: translate(LocalizationKeys.settings_fields_nutritionIncrement), onChanged: (value) { _nutritionIncrementsController.text = (value ?? 0).toString(); @@ -298,7 +300,7 @@ class _SettingsScreenState extends State { child: NumberFormField( controller: _mmolPerLIncrementsController, showSteppers: false, - label: 'Mmol/L increment', + label: translate(LocalizationKeys.settings_fields_mmolLIncrement), onChanged: (value) { _mmolPerLIncrementsController.text = (value ?? 0).toString(); @@ -307,7 +309,7 @@ class _SettingsScreenState extends State { ), BooleanFormField( value: _onlyDisplayActiveGlucoseMeasurement, - label: 'only display active glucose measurement', + label: translate(LocalizationKeys.settings_fields_onlyDisplayActive), onChanged: (value) { _onlyDisplayActiveGlucoseMeasurement = value; saveSettings(); @@ -317,7 +319,7 @@ class _SettingsScreenState extends State { value: _displayBothGlucoseMeasurementsInDetailView, enabled: !_onlyDisplayActiveGlucoseMeasurement, label: - 'display both glucose measurements in detail view', + translate(LocalizationKeys.settings_fields_displayBothDetail), onChanged: (value) { _displayBothGlucoseMeasurementsInDetailView = value; saveSettings(); @@ -326,7 +328,7 @@ class _SettingsScreenState extends State { BooleanFormField( value: _displayBothGlucoseMeasurementsInListView, enabled: !_onlyDisplayActiveGlucoseMeasurement, - label: 'display both glucose measurements in list view', + label: translate(LocalizationKeys.settings_fields_displayBothList), onChanged: (value) { _displayBothGlucoseMeasurementsInListView = value; saveSettings(); @@ -347,7 +349,7 @@ class _SettingsScreenState extends State { children: [ Expanded( child: Text( - 'CONFIRMATION PROMPTS', + translate(LocalizationKeys.settings_sections_confirmation).toUpperCase(), style: Theme.of(context).textTheme.subtitle2, ), ), @@ -364,7 +366,7 @@ class _SettingsScreenState extends State { BooleanFormField( value: _showConfirmationDialogOnCancel, label: - 'on cancelling edit or creation of a record if changes have already been made', + translate(LocalizationKeys.settings_fields_confirmOnCancel), onChanged: (value) { _showConfirmationDialogOnCancel = value; saveSettings(); @@ -372,7 +374,7 @@ class _SettingsScreenState extends State { ), BooleanFormField( value: _showConfirmationDialogOnDelete, - label: 'on deleting a record', + label: translate(LocalizationKeys.settings_fields_confirmOnDelete), onChanged: (value) { _showConfirmationDialogOnDelete = value; saveSettings(); @@ -380,7 +382,7 @@ class _SettingsScreenState extends State { ), BooleanFormField( value: _showConfirmationDialogOnStopEvent, - label: 'on stopping (ending) an event', + label: translate(LocalizationKeys.settings_fields_confirmOnEndEvent), onChanged: (value) { _showConfirmationDialogOnStopEvent = value; saveSettings(); @@ -401,7 +403,7 @@ class _SettingsScreenState extends State { children: [ Expanded( child: Text( - 'TIME & DATE FORMAT', + translate(LocalizationKeys.settings_sections_dateTimeFormat).toUpperCase(), style: Theme.of(context).textTheme.subtitle2, ), ), @@ -421,15 +423,9 @@ class _SettingsScreenState extends State { Expanded( child: TextFormField( controller: _dateFormatController, - decoration: const InputDecoration( - labelText: 'Date Format', + decoration: InputDecoration( + labelText: translate(LocalizationKeys.settings_fields_dateFormat), ), - validator: (value) { - if (value!.trim().isEmpty) { - return 'Empty title'; - } - return null; - }, ), ), Expanded( @@ -439,7 +435,7 @@ class _SettingsScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Example', textScaleFactor: 0.75), + Text(translate(LocalizationKeys.general_example), textScaleFactor: 0.75), Text( DateFormat(_dateFormatController.text) .format(DateTime.now()), @@ -459,15 +455,9 @@ class _SettingsScreenState extends State { Expanded( child: TextFormField( controller: _longDateFormatController, - decoration: const InputDecoration( - labelText: 'Long Date Format', + decoration: InputDecoration( + labelText: translate(LocalizationKeys.settings_fields_longDateFormat), ), - validator: (value) { - if (value!.trim().isEmpty) { - return 'Empty title'; - } - return null; - }, ), ), Expanded( @@ -477,7 +467,7 @@ class _SettingsScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Example', + Text(translate(LocalizationKeys.general_example), textScaleFactor: 0.75), Text( DateFormat(_longDateFormatController.text) @@ -497,15 +487,9 @@ class _SettingsScreenState extends State { Expanded( child: TextFormField( controller: _timeFormatController, - decoration: const InputDecoration( - labelText: 'Time Format', + decoration: InputDecoration( + labelText: translate(LocalizationKeys.settings_fields_timeFormat), ), - validator: (value) { - if (value!.trim().isEmpty) { - return 'Empty title'; - } - return null; - }, ), ), Expanded( @@ -515,7 +499,7 @@ class _SettingsScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Example', textScaleFactor: 0.75), + Text(translate(LocalizationKeys.general_example), textScaleFactor: 0.75), Text( DateFormat(_timeFormatController.text) .format(DateTime.now()), @@ -535,15 +519,9 @@ class _SettingsScreenState extends State { Expanded( child: TextFormField( controller: _longTimeFormatController, - decoration: const InputDecoration( - labelText: 'Long Time Format', + decoration: InputDecoration( + labelText: translate(LocalizationKeys.settings_fields_longTimeFormat), ), - validator: (value) { - if (value!.trim().isEmpty) { - return 'Empty title'; - } - return null; - }, ), ), Expanded( @@ -553,7 +531,7 @@ class _SettingsScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text('Example', + Text(translate(LocalizationKeys.general_example), textScaleFactor: 0.75), Text( DateFormat(_longTimeFormatController.text) @@ -584,7 +562,7 @@ class _SettingsScreenState extends State { Icons.settings_backup_restore, size: 18.0, ), - label: const Text('RESET ALL'), + label: Text(translate(LocalizationKeys.settings_resetAll).toUpperCase()), ), const Spacer(), ], diff --git a/lib/utils/dialog_utils.dart b/lib/utils/dialog_utils.dart index 4be8235..15f27bb 100644 --- a/lib/utils/dialog_utils.dart +++ b/lib/utils/dialog_utils.dart @@ -1,4 +1,6 @@ +import 'package:diameter/localization_keys.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_translate/flutter_translate.dart'; class DialogUtils { static void showCancelConfirmationDialog( @@ -13,30 +15,29 @@ class DialogUtils { List actions = [ TextButton( onPressed: () => Navigator.pop(context, 'CANCEL'), - child: const Text('CANCEL'), + child: Text(translate(LocalizationKeys.general_cancel).toUpperCase()), ), ]; actions.add(isNew ? ElevatedButton( onPressed: () => Navigator.pop(context, 'DISCARD'), - child: const Text('DISCARD'), + child: Text(translate(LocalizationKeys.general_discard).toUpperCase()), ) : TextButton( onPressed: () => Navigator.pop(context, 'DISCARD'), - child: const Text('DISCARD'), + child: Text(translate(LocalizationKeys.general_discard).toUpperCase()), )); if (!isNew) { actions.add(ElevatedButton( onPressed: () => Navigator.pop(context, 'SAVE'), - child: const Text('SAVE'), + child: Text(translate(LocalizationKeys.general_save).toUpperCase()), )); } return AlertDialog( - content: Text(message ?? - 'You already made some changes. Discard your input?'), + content: Text(message ?? translate(LocalizationKeys.general_confirmDiscard)), actions: actions, ); }).then((value) { @@ -52,21 +53,21 @@ class DialogUtils { static void showConfirmationDialog( {required BuildContext context, required void Function() onConfirm, - String message = 'Are you sure you want to delete this record?', - String confirmationLabel = 'DELETE'}) { + String? message, + String? confirmationLabel}) { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( - content: Text(message), + content: Text(message ?? translate(LocalizationKeys.general_confirmDelete)), actions: [ TextButton( onPressed: () => Navigator.pop(context, 'CANCEL'), - child: const Text('CANCEL'), + child: Text(translate(LocalizationKeys.general_cancel).toUpperCase()), ), ElevatedButton( onPressed: () => Navigator.pop(context, 'CONFIRM'), - child: Text(confirmationLabel), + child: Text(confirmationLabel ?? translate(LocalizationKeys.general_confirm).toUpperCase()), ), ], ); diff --git a/objectbox/data.mdb b/objectbox/data.mdb index 938908120d2cc79efc3152adbecd6af909afe7fd..8dd30947fccfebb748f938dd6b86a11bdd0e5a89 100644 GIT binary patch delta 1871 zcmZ`(eQZ-z6u+-6YuB#p>(;Mzo2=a@>x;h6zP6CL1!J}ajgAi&VA9E&IJ0(R8(hK3A zDr<1XnOYWKZ*ZCHyR>_tCU@ZTCrjP9y}?z-m~iLGO1J&}Y%&1e(d2QQY;d{piziE2 ze6PXfY0cLTu4}g-P1a?l30&5yFlhd;c2_`?*R=Ri28$;uw;`xU(173)1oIFyB4|P| zA3-yM76c0rv?5rDpbbG5K|6v)2o@vgK+uVx3qd!69t2Ae^deY_U>Snt2tI<~QUogy z^daa!2P@5e*vwYD#)miA@X%Rr2h)OYp7mBvRGAN^&Cz9N4CzUSe?hNXt4p7n^gnMr zm#Qil(?c+VZJDwS&Jp93HD!g?s1;aK1&sw63$#?=Ek1*Le5}w|96bsE0}Q~>tOu8K zJ*Lw?mbI8rv>HWgP_!0B>riwViq@lO;B=H@ts0CB$N$7)?_1*$Le#& z$PoZ02wxz4KVA8keU;eGUlu+dA`@;Wu?_-e!3q!}*KlM%%0L0J0eFqDOn50_N{jQQ z_=^h0+A&m~c3zzQJfgj!>>=%l78N4z_ihaUQ&j*QsU!|R08fw%Y&igXJODs)+fD*} zxd?!(HUREf09a!NU||WtkzJU=f{*b8rWY4E5=^s_gDhl_%^-rL_reWMzlo>8(cUp3{pdB;L+dTT7++1*3r z4GpI!I;Ay}3+=zZ{^yJdZw1!rJk$6Tm3`Crw_AZWUE4H%dNxp_ z^G@S!vw?cu{0u%t?QduBXS0Fzy80PhGe`VaW^mga!H>@1({q6ueAmaY_`A7)2bcMY zW|$9@;buSM!18>+k?!*|6O8E-c9MIQeOAC+6$AgVCaM_Tw7NGwf9XR<0RKd-uUDYM zo8%))a8)(2;>mk2xZXaqEWNRsIp)_Dyp;al#J%y9Q;kT9A_b+eD)6cr7UfV#64LwE zaV19FxP~h)66J`*^O7Qj6+z_*O2oce&dYWu2DbKeCt4Dl`(yD~Vi14X$~8DdB_i`8 z!G{zfB+EpX!nmoFbK^hObNTrXqFrJzA_=@K38KOWc~y}~b|s8&KgPM)cyG@$vHn#L zMmWWX)r)r(b8cH_ymxD2@BzFJCt}?4bf$w#T5w|<=X8mDL>47g42wdLSHz$g6oQg~ zSvyx2P$H77s%l6Q)o@tmgQQ_484=G5r0bv*;w3Hcz-F$g zSdN6qv8wQr99ATeKITxN<&DA*{S}h%&%cy-w__iYlw&2n>t{QLY z=1Tr&0O?dW*Jr5D{##CewKa8NUuxf-g+Hb7_ipsi|Bd&kv3t+xj>7|))?W`@Q+Kk- Z;bPqPmd=nIVGoTN(p?GeI5Rw!{}*43-lqTn delta 451 zcmXYtPe>GD7{=%C&L4MnzSPFm$|iR;+vyOpRCF4I#6y%P@nAKF4tokO)j^0CgvUK3 zIIZ-Fh)#i>I_!e@UFT4;fu|19ak_Q167=Z6`ohcad7tO<@KUlbQ`V8&v#iDLn=OCK zsY|M4NWEEBdSn;(lczngA@_KAk$+o>*=o$>d=(Khb@{oyErmwrrf%Z$G7F$?_BF+B z;(XUcV^8}fi*P7NO^^!GKsu-ZGC;XwfoxD8s0eaEC6EhZpi7{B&;V!jg!ckxmrO65ZpqME8}< z;F)hc<{#36*UGZ8HfM)6@7|f1au=-7nzWd8W@cvBY;*ayKEPQ=Z4@NyIQMwiksr8P z__zM(^yA*zPuv}QIn4k6DZR>{!{2VPQQr7bdM8dk=|$%tsTSL!=zC3tAIGZAccz1p zyc_RM2eX1zye|g3xAXAD;iB_?s=4;>&9nKh;;81;bwuNcCJ^03G>PaIq8g&$b99@X E1LP&3l;Dab&U^uY6>*V+P MiGf-h4>-sJ0E99c8~^|S delta 57 zcmZp0XmAj`ci{a#rUZSRW4sJtATUu-oN>a$MCpk#Jd6`2C$i{IJRrb!U^-KqTRo7c Jwef(1JOB;*5<&m~ diff --git a/pubspec.lock b/pubspec.lock index b861133..160dd13 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,27 +1,34 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _discoveryapis_commons: + dependency: transitive + description: + name: _discoveryapis_commons + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" _fe_analyzer_shared: dependency: transitive description: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "36.0.0" + version: "38.0.0" analyzer: dependency: "direct dev" description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "3.3.1" + version: "3.4.1" archive: dependency: transitive description: name: archive url: "https://pub.dartlang.org" source: hosted - version: "3.2.2" + version: "3.3.0" args: dependency: transitive description: @@ -231,7 +238,7 @@ packages: name: flex_color_scheme url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "4.2.0" flutter: dependency: "direct main" description: flutter @@ -244,11 +251,23 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.4" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_translate: + dependency: "direct main" + description: + name: flutter_translate + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" flutter_web_plugins: dependency: transitive description: flutter @@ -268,6 +287,34 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.2" + google_sign_in: + dependency: "direct main" + description: + name: google_sign_in + url: "https://pub.dartlang.org" + source: hosted + version: "5.2.4" + google_sign_in_platform_interface: + dependency: transitive + description: + name: google_sign_in_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + google_sign_in_web: + dependency: transitive + description: + name: google_sign_in_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.0+5" + googleapis: + dependency: "direct main" + description: + name: googleapis + url: "https://pub.dartlang.org" + source: hosted + version: "7.0.0" graphs: dependency: transitive description: @@ -519,7 +566,7 @@ packages: name: printing url: "https://pub.dartlang.org" source: hosted - version: "5.7.4" + version: "5.7.5" process: dependency: transitive description: @@ -555,13 +602,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.0" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1+1" shelf: dependency: transitive description: name: shelf url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0" shelf_web_socket: dependency: transitive description: @@ -644,6 +698,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.0" + universal_io: + dependency: transitive + description: + name: universal_io + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" vector_math: dependency: transitive description: @@ -671,7 +732,7 @@ packages: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "2.4.2" + version: "2.5.1" xdg_directories: dependency: transitive description: @@ -694,5 +755,5 @@ packages: source: hosted version: "3.1.0" sdks: - dart: ">=2.15.0 <3.0.0" + dart: ">=2.16.0 <3.0.0" flutter: ">=2.8.0" diff --git a/pubspec.yaml b/pubspec.yaml index d7cd3e7..97499c9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,16 +11,21 @@ environment: dependencies: flutter: sdk: flutter + flutter_localizations: + sdk: flutter path_provider: ^2.0.9 cupertino_icons: ^1.0.2 - flex_color_scheme: ^3.0.1 + flex_color_scheme: ^4.2.0 intl: ^0.17.0 objectbox: ^1.2.0 objectbox_sync_flutter_libs: any - charts_flutter: ^0.12.0 printing: ^5.7.2 pdf: ^3.7.1 open_file: ^3.2.1 + flutter_translate: ^3.0.1 + googleapis: ^7.0.0 + google_sign_in: ^5.2.1 + charts_flutter: ^0.12.0 dev_dependencies: flutter_test: @@ -33,3 +38,5 @@ dev_dependencies: flutter: uses-material-design: true + assets: + - assets/i18n/ diff --git a/test/widget_test.dart b/test/widget_test.dart index 5c45780..ce604e4 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -8,7 +8,7 @@ // import 'package:flutter/material.dart'; // import 'package:flutter_test/flutter_test.dart'; -// import 'package:tide/main.dart'; +// import 'package:diameter/main.dart'; void main() { // testWidgets('Counter increments smoke test', (WidgetTester tester) async { diff --git a/web/index.html b/web/index.html index ec9bfb5..2be2baf 100644 --- a/web/index.html +++ b/web/index.html @@ -1,7 +1,7 @@ - - - + - - - + + + - - - - - + + + + + - tide - - - - - - + if ("serviceWorker" in navigator) { + // Service workers are supported. Use them. + window.addEventListener("load", function () { + // Wait for registration to finish before dropping the + diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc index 8194ca1..378d094 100644 --- a/windows/runner/Runner.rc +++ b/windows/runner/Runner.rc @@ -89,7 +89,7 @@ BEGIN BEGIN BLOCK "040904e4" BEGIN - VALUE "CompanyName", "com.example" "\0" + VALUE "CompanyName", "at.sarahziesel" "\0" VALUE "FileDescription", "diameter" "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "InternalName", "diameter" "\0"